Commit Graph
11 Commits
Author SHA1 Message Date
Snow LeeandClaude Opus 4.8 f14601a983 refactor(web): extract MetaStore + typed repos; file backend (phase 1)
Introduce a pluggable metadata persistence layer: a MetaStore of typed
repositories (AccountRepo, ProjectRepo, OrgRepo, ShareRepo, DeviceRepo).
The five registries keep their in-memory maps + logic and now persist each
change as one record through a repo, instead of rewriting a whole JSON file
inline. The `file` backend (db_file.go) reproduces the exact on-disk JSON
formats, so a running hub loads unchanged. Open*(path) constructors stay as
thin wrappers over the file backend.

Zero behavior change: full webapp suite green; the example hub boots and
existing auth.json/orgs.json/projects.json load and sign-in works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 00:01:56 -07:00
Snow LeeandClaude Opus 4.8 bd7ea16200 feat(web): invite-only signup by default; harden signup postures
A hub on a public URL could be signed up to with any fake email. Make the
safe posture the default and turn the dangerous ones into startup errors.

Three supported postures:
- invite-only (NEW default): allow_signup defaults false. A valid org
  invite link bootstraps an account even when self-signup is closed — the
  only way in. pageLogin/pageSignup detect a /join/<token> target via
  BuiltinAuth.InviteValid (wired to OrgDB.ValidInvite) and offer account
  creation; signupInvited skips the domain/approval/verification gates and
  activates immediately (the owner's invite is the vetting).
- approval-gated: allow_signup + require_approval (no SMTP needed).
- domain-restricted + verified: allow_signup + allowed_domains +
  require_verification (needs smtp).

Hardening:
- BuiltinAuth.ValidateSignupPolicy (run at hub startup in web.go) refuses to
  boot an open hub with no gate, or require_verification without a mailer
  (the link would otherwise only reach the server log).
- handleAdminPolicy rejects enabling verification without SMTP; the UI
  toggle is disabled with clearer copy in that case.

Tests: TestValidateSignupPolicy, TestInviteBootstrapsAccountWhenSignupClosed,
TestPolicyVerificationNeedsMailer. Verified end to end in the running hub
(outside-domain invitee onboards via a link; direct signup stays closed).
Docs updated (README, CLAUDE.md, SKILL.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-09 16:57:49 -07:00
Snow LeeandClaude Opus 4.8 05e75a6b9d redesign(web): Linear-grade viewer/admin + file-tree UX
Rework the bdrive web UI away from the violet Obsidian-ish theme to a
near-black, honey-accented, hairline-crafted look in the spirit of Linear:

- Replace the mixed emoji glyphs (chevrons, clocks, palette icons, admin
  badge, history markers, share dialog) with one coherent SVG line-icon
  sprite + a svgIcon() helper; colored letter-marks for projects.
- Repoint the design tokens and rewrite style.css: near-black grounds,
  7% hairline borders, tightened type, honey spent only on active state,
  the ⌘K selection, and brand marks.
- Restyle the server-rendered auth pages and the public /s/ share shell
  to the same tokens so sign-in and the app read as one product.

File-tree behavior:
- Folders start collapsed; a lone root folder opens on load.
- Opening a file (search, wikilink, deep link) unfolds the path to it and
  scrolls the row into view. State now tracks open dirs (not collapsed),
  so it survives the periodic tree refresh.
- Plain-text/JSONL views wrap long unbreakable lines instead of
  overflowing the reading column.

Verified in the running example hub across desktop + mobile; go build,
go vet, go test ./... all green; zero horizontal page overflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-09 13:40:22 -07:00
Snow LeeandClaude Fable 5 d80548a5c3 design: two final polish nits from the confirmation review
- Auth signup domain parenthetical #888→#969696 (4.27→5.12:1, full AA).
- Sidebar org-name label gets a 44px touch height on mobile.

Design confirmation review: Visual 5 / UX 5 / Layout 5 / Accessibility 5,
overall 5.0, all objective a11y checks pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-09 00:07:17 -07:00
Snow LeeandClaude Fable 5 81547c97c8 design(round 3): last AA residuals + topbar hierarchy
Flips the final objective gate (contrast_aa) true; design-only.

- Palette match-highlight on a SELECTED row: recolor .plabel b to
  --accent-bright (#c9b3ff) → 6.19:1 on the tinted row (was accent 3.98:1).
- Auth-page secondary text .alt #8a8a8a→#969696 → 5.12:1 on the card
  (was 4.38:1).
- Polish: active-project marker uses --accent-bright to match its label;
  Upload/Download join Share/History as uniform ghost buttons so no
  secondary action shouts louder than the rest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 23:58:03 -07:00
Snow LeeandClaude Fable 5 442232bab2 design(round 1): accessibility + layout pass (design-only)
Addresses the baseline design review's failing objective gates and layout
defects; no backend or flow changes.

Accessibility (was the ceiling — 3 of 4 objective gates failed):
- Contrast to AA: lifted --text-faint (#6e6e6e→#8a8a8a) and --text-dim;
  darkened --accent-dim (#7c5cd6→#6a48e0) so white button labels reach
  4.5:1; new --accent-bright (#c9b3ff) for accent text on the tinted
  active background (tree/project active rows, admin badge, invite/ghost
  buttons all lifted off sub-AA pairings). Same fix applied to the
  server-rendered auth pages, which now share the app's token values.
- Keyboard + focus: file tree, project, and org-name rows are now
  focusable (tabindex/role + Enter/Space) with a global :focus-visible
  ring; restored input focus rings on app and auth pages.
- Touch targets: every header + sidebar control is a 44x44 hit area on
  mobile; secondary file actions (History/Upload/Download) collapse under
  a "⋯ More" menu so the row still fits with zero horizontal overflow.

Layout defects:
- #meta no longer wraps to 5 lines / shoves the action buttons — single
  truncating line; #crumb truncates too.
- Long tree filenames ellipsize (label span flex:1 min-width:0) instead
  of hard-clipping.

Consistency:
- Fixed the .markdown specificity leak: admin/history/onboarding views no
  longer inherit markdown type rules (content class toggles per view), so
  the admin type scale renders as declared.
- Recolored the off-system gold Admin badge to the accent family; added a
  3-step radius token scale; swapped the mojibake-ish ▣/⛛ markers.
- Added a global [hidden] guard so explicit display rules can't override
  the hidden attribute.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 23:36:41 -07:00
Snow LeeandClaude Fable 5 40fcda7cf7 polish(hub): round-3 — mobile actions reachable, admin gating settings UI
Fixes the two round-2 majors.

User: on mobile the per-file actions (Share/History/Upload/Download) are
now icon buttons in the header — reachable again (round 2 wrongly hid
them). Search tolerates simple English plurals (ideas→idea), the
no-matches state states what search covers, the sidebar shows the hub
brand instead of the raw device name, and history-row downloads carry a
download attribute. Logged-out loads redirect to sign-in from /api/config
instead of firing 401-ing API calls.

Admin: a hub-admin "Signup & access" settings screen (⚙ Admin in the
sidebar) toggles email verification and admin approval live — persisted to
auth.json and surviving restart — while the domain allowlist and admin
list are shown read-only (deliberately server-config-owned so a browser
session can't widen access). Pending approvals live on the same screen.
Config toggles are now *bool so an explicit config value pins the setting
each boot, else the UI-saved policy stands. Invite revoke confirms; role
change re-renders the panel.

Tests: policy persistence + reload, policy API admin-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 22:55:14 -07:00
Snow LeeandClaude Fable 5 e022998c9f polish(hub): round-2 UX — close the gap to the usability bar
Admin: top-level ⚙ settings entry (owners/admins), signup form states the
domain restriction up front, invite list shows creator + join count,
org-wide share audit shows creator/date and confirms before revoke,
self-row role/remove controls disabled to avoid footguns.

User: mobile header no longer overflows (per-file actions move to the ⌘K
palette on narrow viewports; tables/pre scroll in their own container),
empty-state copy works on mobile, share confirmation is now an explicit
"anyone with this link can view" dialog with copy/open/revoke, invite
links carry a "you've been invited" banner through login, joining opens
the joined project, brand shown as the title, logout labeled, palette
placeholder clarified to "file names".

Tests: invite use-counter + creator in the owner list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 22:39:07 -07:00
Snow LeeandClaude Fable 5 baad9e6dc2 feat(hub): signup gating, admin lifecycle, and onboarding UX
Makes a self-hosted hub safe to expose on a public URL and operable
without hand-editing JSON — addressing the blocker/major findings from
the persona usability evaluations.

Signup gating (config auth block, all optional):
- allowed_domains: signup email must match (e.g. only @runbear.io)
- require_verification: email-link activation before sign-in (reuses mailer)
- require_approval: hub admins approve new accounts (admins list)
- brand shown on the sign-in page; allow_signup:false already hid Sign up
Accounts carry a Status (active/unverified/pending); non-active accounts
cannot authenticate.

Admin lifecycle (endpoints + web UI):
- org: rename, member role change, member remove (last-owner guarded),
  invite list + revoke
- project: create (web), rename, delete (from the org panel)
- hub admins: approve/deny pending signups (sidebar bell + panel)
- org-wide public-share audit with revoke

UX: onboarding empty-state (explains invites, paste-invite + create-project)
instead of a blank sidebar; visible "Search ⌘K" button; toasts replace
blocking alert(); responsive layout with an off-canvas sidebar; joining
via #join now survives a logged-out click (token carried through login).
Web uploads are attributed to the signed-in account, not the server.
Login/signup are rate-limited per IP.

Tests: domain/verification/approval gates, auth rate limit, org+project
lifecycle, owner-only guards, invite→join→role→remove over HTTP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 22:23:27 -07:00
Snow LeeandClaude Fable 5 94ef66a4a9 feat(hub): organizations — projects walled by team membership, with invites
Every hub project now belongs to exactly one org (file-backed orgs.json,
same load-at-open + atomic-rewrite discipline as the other registries).
Membership (email -> owner|member) gates every per-project route: the
viewer APIs, uploads, history, blobs, shares management, and the device
sync store proxy; /api/projects lists only your orgs' projects, and
project names are now scoped per org. Public share links (/s/) stay
public by design.

Design choices, per the simplest-consistent rule:
- Migration: a pre-org hub sweeps all org-less projects into a "default"
  org at startup; ALL existing accounts join it (they could all see every
  project before, so anything narrower would lock someone out), oldest
  account as owner. Zero manual steps.
- An account in no org that creates a project gets a fresh org named
  after itself, so nobody is ever blocked from starting to sync.
- Invites are expiring multi-use links (default 7 days): an owner mints
  /#join/<token>, any signed-in account that opens it joins as member.
  The web UI shows the org in a sidebar footer (members on click,
  Invite button for owners).

bdrive init needed no changes: its connect-existing flow lists projects
through the now-filtered API.

Tests: OrgDB + migration units; a 403/access matrix over every
per-project route; invite mint/redeem flow; and a multi-device syncer
test proving a device holding an org-B token can neither pull org A's
files nor push into its store (sync degrades to Offline, never partial).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 17:03:48 -07:00
Snow LeeandClaude Fable 5 2d7f2f8bfa feat: auth, move-proof projects, interactive init/login, web history
Authentication (previous phase, now landed together with its follow-ups):
- Email+password+name accounts behind an AuthProvider interface; the OSS
  server ships BuiltinAuth only (file-backed auth.json: bcrypt password
  hashes + SHA-256 token digests, plaintext never stored; server-owned
  /auth/* pages; managed deployments can swap in another provider).
- bdrive login: loopback-callback browser flow (sign-up on the page, the
  terminal finishes itself) with a device-code fallback for headless
  machines; long-lived revocable device tokens in settings.json.
- Password reset via plain SMTP (stdlib) with a log-link fallback when no
  SMTP is configured.

Move-proof projects:
- .bdrive is now a directory; config.json carries a stable mount id.
  The volume store (~/.bdrive/volumes/<mount-id>/) and registry are keyed
  by that id — never the folder path — so renames/moves are free.
- The daemon re-reads the project config each tick and exits cleanly
  (propagating nothing) when its folder vanishes; the registry self-heals
  and the next bdrive command at the new location resumes with zero
  spurious changes.

bdrive init is the front door (mnt/umnt removed; bdrive stop pauses):
- Interactive on a TTY (create new / connect existing project from the
  server's list; whole folder / shared subfolder via the include list),
  full flag bypass (--name/--project/--shared/--yes), never prompts
  without a TTY. Runs the login flow first when there is no session.
  Default server: beardrive.ai (config.DefaultServer).

Web history (revert-ready):
- Hubs now always require auth; journal ops carry the signed-in account
  (user/user_name) alongside the git/OS fallback author.
- File-backed device registry: per-device name, OS, account, and the
  public IP the server observed, joined into history at read time.
- GET /api/p/<id>/history?path=|prefix= (newest first) and
  GET /api/p/<id>/blob?sha= stream any exact version — blobs are retained
  forever, so the next phase's revert is re-putting an old blob.
- UI: History button (file versions or project feed), per-folder history
  shortcut, view/download of any past version.

Tests: auth flows (callback, device-code, reset single-use, persistence,
gating), history API + device registry, folder-move survival, registry
self-heal, ops-carry-account; docs (README/SKILL/CLAUDE) updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-08 13:12:49 -07:00