Skip to content

feat(ui): Library Manager — desktop connect + signed-in discovery (LM-3/LM-4)#168

Merged
eliotlim merged 3 commits into
mainfrom
feat/library-manager-connect
Jul 14, 2026
Merged

feat(ui): Library Manager — desktop connect + signed-in discovery (LM-3/LM-4)#168
eliotlim merged 3 commits into
mainfrom
feat/library-manager-connect

Conversation

@eliotlim

Copy link
Copy Markdown
Owner

Summary

Library Manager, next two steps. The switcher stops being an "add a server row" list and becomes a real connect + discovery surface:

  • LM-3 - Connect from the switcher. "Add a library..." becomes "Connect to a library...", which opens a Connect to a library dialog that adds the server and switches this device onto it (reload re-point), enforcing the safe-URL + mixed-content guards up front instead of failing on an opaque browser error later.
  • LM-4 - Signed-in discovery. When an account is connected, the list splits into "On this device" and "From your account" - surfacing libraries synced to the account (incl. ones configured on another device) with a one-click connect. Signed out, it stays a single flat list.

Plus a design-review pass (Devon - clear with nits, applied): the mixed-content-blocked row now explains itself, dead i18n keys are gone, the URL-error copy is unified, and account-backed rows carry a per-row synced cue.

Before / After

Behaviour Before (main) After (this branch)
Switcher list + connect entry
Flat "Libraries" list; a generic Add a library... row. No grouping, no account awareness.

Signed in: On this device / From your account groups; account-backed rows carry a subtle synced (cloud) cue; entry is Connect to a library....
Signed-out list
Single flat list.

Still a single flat list (no account -> no groups) - only the connect entry is renamed.
Connect/Add dialog + URL error
Add a library; a bad URL fails a bare new URL() check - "That doesn't look like a valid URL."

Connect to a library; the safe-URL guard shows the actionable "Enter an http:// or https:// server URL."

Design-review fixes (this branch's fix(ui) commit)

  1. Mixed-content-blocked row now explains itself. The greyed row previously used disabled, which kills hover and keyboard focus - so its title reason was unreachable. It now renders the reason inline as a small caption under the row and uses aria-disabled (row stays focusable/hoverable) while onSelect still blocks the connect. Reachable by mouse and keyboard. (See "not fully drivable" below.)
  2. Dead i18n removed / wired. Dropped the unused library.addLibrary / addTitle / addDescription (and their de/ja/zh entries). library.accountSyncedHint is now wired as the per-row synced-cue tooltip.
  3. Unified URL-error copy. Both the switcher and Manage libraries settings now use the more-actionable library.urlUnsafe for the same isSafeServerUrl failure; the vaguer library.urlInvalid is removed everywhere (incl. locales).
  4. Per-row synced cue (nit). Account-backed rows that are also local now show a small cloud marker, so a synced library no longer reads as a plain local row (previously only the group header signalled provenance).

Testing

ui-scoped verify, all green (observed exits 0):

  • @book.dev/sdk build - @book.dev/ui typecheck - @book.dev/ui build - @book.dev/ui lint - @book.dev/ui vitest (113 files / 1063 tests passed).
  • Captures driven through the Playwright web e2e harness (throwaway specs, removed after).

Not fully drivable in the http e2e harness (captured closest + described):

  • Mixed-content-blocked row. isMixedContentBlocked keys off location.protocol and only blocks an http:// target from an https page; the e2e shell is served over http://localhost, so the blocked state can't be reproduced there. The inline-reason rendering is exercised by ui vitest and visible in code; the "After" grouped still shows the same account-row layout the reason attaches to.
  • One-click "Connect" affordance. In steady state adopt() merges every well-formed synced library into the local list and sets syncedLibraries from that same merged output, so account rows resolve as already-local (they show the synced cue + select, not "Connect"). The "Connect" branch only lights up in the transient window where a synced library isn't yet in the local list - not reproducible after settle. Flagged as an observation, not changed here (outside the design-review scope). The grouped capture therefore shows the real, settled state.

Notes

  • i18n: only English strings were updated for the renamed/added copy; de/ja/zh fall back to English for the new keys (translation backlog).
  • Files touched: LibrarySelectMenu.tsx, settings/LibrarySettings.tsx, AccountProvider.tsx, and i18n/messages/{en,de,ja,zh}.ts.

Verify: green (ui-scoped - sdk build, ui typecheck/build/lint/vitest).
Gate: design (Devon) - CLEAR WITH NITS, all nits applied.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w

eliotlim and others added 3 commits July 14, 2026 13:08
Make "connect to another library" a clear, prominent action from the library
switcher (present on the desktop titlebar switcher and the web sidebar). The
add-a-library flow IS the connect flow: submitting the dialog now adds the
server AND switches this device onto it (reload-switch, mirroring
selectLibrary's server re-point), instead of only appending it to the list.

Enforce the safe-URL guard (isSafeServerUrl — reject non-http(s)/malformed) and
the mixed-content guard (isMixedContentBlocked — an https app can't reach a
plain http:// LAN address) inline, with explanatory copy. Relabel the action
and dialog to "Connect to a library" / "Connect".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w
…M-4)

Surface the libraries a signed-in user has configured across their devices,
straight off the account's synced settings blob (MVP — no account directory
API). AccountProvider now exposes `syncedLibraries`: the active account's library
list, refreshed on every reconcile/push and cleared on sign-out.

The switcher uses it to split the list into "On this device" and "From your
account" groups when signed in, distinguishing account-backed libraries from
purely-local ones. A synced library that isn't local here yet shows a one-click
"Connect" (addLibrary + reload-switch); mixed-content-blocked entries are shown
disabled with an explanation. Signed out, the list stays a single flat group
(no behaviour change). No account/deploy change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w
…or copy (design review)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app.book.pub Ready Ready Preview, Comment Jul 14, 2026 5:42am

Request Review

@eliotlim eliotlim merged commit 7657150 into main Jul 14, 2026
10 checks passed
@eliotlim eliotlim deleted the feat/library-manager-connect branch July 14, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant