feat(ui): vault popover + hub-discovery + OAuth vault-hint - #115
Merged
Conversation
Replace the header `<select>` with a popover that lists locally-connected vaults alongside the operator's hub-side vault list (fetched from `<hub>/.well-known/parachute.json`). Implements §2 of the merged design doc `design/2026-05-12-notes-ui-audit.md` — the first item in the §5 ship sequence. - New `VaultPopover` component (`src/components/VaultPopover.tsx`) with `header` and `inline` variants. Connected section lets the user switch active vault with one click; Available section adds a per-vault "Connect" button that kicks OAuth against the hub. Outside-click + Escape close. - Pure `buildVaultPopoverRows()` for the diffing logic (URL match via `normalizeVaultUrl`) so the connected-vs-available split is testable without React. - New `fetchHubVaults()` + `hubOriginForVault()` in `src/lib/vault/hub-discovery.ts`. Hub origin is derived from `VaultRecord.issuer` (already captured at OAuth time in `OAuthCallback.tsx`) — no new VaultRecord field, no migration. Under a standalone-vault deployment the well-known fetch returns no peers and the Available section is omitted (graceful). - `beginOAuth` now accepts an `options.params` bag, appended last to the authorize URL and guarded so caller-supplied params can never overwrite standard OAuth/PKCE params. Used by the popover to send `vault=<name>` as a Path A hint (design doc §2): pre-#240 hubs ignore it, future hubs can pre-select the consent screen with no Notes change. - Header.tsx desktop + mobile both replaced — the standalone "Manage" button is gone since the popover footer owns the link to /vaults. Tests: 24 new (13 hub-discovery, 11 popover + diffing + component, 2 oauth params). 667/667 pass, typecheck clean, lint clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Implements Phase 2 of the merged Notes UI audit — §2 of
design/2026-05-12-notes-ui-audit.md, §5 ship-sequence item #1.Replaces the header
<select>switcher with a popover that surfaces the operator's full hub-side vault list alongside the locally-connected ones. The named gap (Aaron runs four vaults —boulder/default/gitcoin/techne— but Notes only shows the ones already connected, so each new vault means another trip to/add) closes here.What's in this PR
VaultPopovercomponent (src/components/VaultPopover.tsx) — two sections (Connected / Available from your hub) plus a "Manage vaults →" footer; outside-click + Escape to close; two variants (headerfloating,inlinefor the mobile menu).buildVaultPopoverRows()pure diffing function (URL match vianormalizeVaultUrl) so the Connected-vs-Available split is testable without React.fetchHubVaults()+hubOriginForVault()insrc/lib/vault/hub-discovery.ts. Returns the parsedWellKnownVaultEntry[]ornullon any failure (network, non-2xx, malformed JSON).beginOAuth(..., options)— new fourth arg withparams: Record<string,string>. Appended last and guarded so caller params can never overwrite standard OAuth/PKCE params.Design calls made
VaultRecord.issuerrather than storing a new field. Rationale:issueris already captured at OAuth time (OAuthCallback.tsx:54) and under hub-as-issuer semantically equals the hub origin (per the comment atprobe.ts:10-13).issuer === vault URL→ well-known fetch returnsnullor empty vaults → Available section omitted. Graceful degradation, no caller code to special-case.VaultRecordschema change, no migration, no field that could drift out of sync withissuer.vault=<name>hint plumbing. Last-step URL decoration onauthorizeUrl.searchParamsvia a newoptions.paramsbag, exactly as suggested in the design doc. Guarded against standard-param collisions for defense-in-depth.vault=<name>blind. Pre-#240 hubs ignore it; the consent screen renders the picker as today. Future hubs can adopt the hint to pre-select on consent — no Notes change required when they do. Hub steward coordination tracked as a follow-up, not a blocker here.What's out of scope (per design doc §5)
/vault/<name>/...) deep-link routing — later PR.Version + changelog
package.json0.3.14 → 0.3.15-rc.1 (rc.N convention perparachute-patterns/patterns/governance.mdrule 2).## Unreleased.Test plan
bun run typecheck)bun run lint)bun run test→ 667/667, 24 new)vault=<name>in the URL🤖 Generated with Claude Code