feat(ui): Library Manager — desktop connect + signed-in discovery (LM-3/LM-4)#168
Merged
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Library Manager, next two steps. The switcher stops being an "add a server row" list and becomes a real connect + discovery surface:
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
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....
Single flat list.
Still a single flat list (no account -> no groups) - only the connect entry is renamed.
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)disabled, which kills hover and keyboard focus - so itstitlereason was unreachable. It now renders the reason inline as a small caption under the row and usesaria-disabled(row stays focusable/hoverable) whileonSelectstill blocks the connect. Reachable by mouse and keyboard. (See "not fully drivable" below.)library.addLibrary/addTitle/addDescription(and their de/ja/zh entries).library.accountSyncedHintis now wired as the per-row synced-cue tooltip.library.urlUnsafefor the sameisSafeServerUrlfailure; the vaguerlibrary.urlInvalidis removed everywhere (incl. locales).Testing
ui-scoped verify, all green (observed exits
0):@book.dev/sdkbuild -@book.dev/uitypecheck -@book.dev/uibuild -@book.dev/uilint -@book.dev/uivitest (113 files / 1063 tests passed).Not fully drivable in the http e2e harness (captured closest + described):
isMixedContentBlockedkeys offlocation.protocoland only blocks anhttp://target from an https page; the e2e shell is served overhttp://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.adopt()merges every well-formed synced library into the local list and setssyncedLibrariesfrom 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
LibrarySelectMenu.tsx,settings/LibrarySettings.tsx,AccountProvider.tsx, andi18n/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