Skip to content

Probe same-origin for a vault and prefill connect flow - #12

Merged
unforced merged 1 commit into
mainfrom
probe-default-vault
Apr 18, 2026
Merged

Probe same-origin for a vault and prefill connect flow#12
unforced merged 1 commit into
mainfrom
probe-default-vault

Conversation

@unforced

Copy link
Copy Markdown
Contributor

Summary

Most self-hosters will run Lens on the same origin as their Parachute Vault. When a new user lands on Lens with no stored vaults, probe ${window.location.origin}/.well-known/oauth-authorization-server once (2.5s timeout) and, on success, skip straight to a one-click Connect flow.

  • Home: when the probe succeeds, swap the default CTA for a "Looks like there's a vault at <origin>" banner with a primary Connect button (prefills /add via ?url=) and a secondary "Or connect to a different vault" link.
  • /add: pre-fill the URL input from ?url= or from the probe, auto-focus so the user can submit with Enter. ?url= wins over the probe and over user typing.
  • Port-strip retry: if the origin has a port and the primary probe fails, retry once against the port-stripped origin. Covers Tailscale serve (lens:8443 → vault on :443).
  • Silent fallback: network error, 404, invalid metadata — the default CTA renders, no errors surfaced.
  • Skip when we have vaults: if any vault is in storage, we never probe; Home redirects to /notes as before.

Files

  • src/lib/vault/probe.ts (new) — probeVaultAtOrigin() + useOriginVaultProbe() hook
  • src/lib/vault/index.ts — re-export probe
  • src/app/routes/Home.tsx — conditional banner vs default CTA
  • src/app/routes/AddVault.tsx?url= + probe prefill, auto-focus
  • Three new test files (14 tests total)

Test plan

  • bun run lint
  • bun run typecheck
  • bun run test — 126 pass (14 new)
  • bun run build
  • Manual: visit Lens with no vaults on an origin with a vault → banner renders, Connect goes to /add?url=… with input prefilled
  • Manual: visit Lens with no vaults on an origin without a vault → default CTA
  • Manual: visit /add?url=http://... directly → input prefilled regardless of probe

🤖 Generated with Claude Code

When the user lands on Lens with no stored vaults, probe
`${window.location.origin}/.well-known/oauth-authorization-server` with a
2.5s timeout. On success, Home shows a "Looks like there's a vault at
<origin>" banner with a primary Connect button (prefilling /add via
?url=) and a secondary link to connect to a different vault. /add
pre-fills the URL input from ?url= or from the probe, and auto-focuses
so the user can submit with Enter.

If the origin has a port and the primary probe fails, retry once against
the port-stripped origin — covers Tailscale serve setups where Lens runs
on :8443 and the vault answers on :443. The probe skips entirely when
any vault is already in storage, and silently falls back to the default
CTA on network error, 404, or invalid metadata.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@unforced
unforced merged commit e1f13d9 into main Apr 18, 2026
@unforced
unforced deleted the probe-default-vault branch April 18, 2026 22:40
unforced added a commit that referenced this pull request May 12, 2026
… items

After Aaron read the audit, he gave directional input that the doc
should capture durably:

- New "North star" subsection up top: Notes should serve as an
  Apple-Notes / Obsidian replacement. Three pillars — easy as Apple
  Notes, flexible as Obsidian-ish, Parachute-shaped via per-vault
  customization. Every improvement candidate now reads against this
  question: "does this make Notes a credible Apple-Notes replacement?"
- §3 item #11: view-level text-size control. A device-local zoom knob
  (Default / Larger / Largest) affecting editor + read views. View
  preference, not content — markdown on disk stays untouched. Matches
  Apple Notes' affordance.
- §3 item #12: unify capture surfaces. Collapse the /new + TextCapture
  + MemoCapture duality into one create-or-edit interface where the
  "quick" experience is the same surface with smart defaults
  (auto-title, role-driven tags, default path). Voice still uses its
  own input modality but hands off to the unified surface.
- §5 sequence reordered: unified-capture + text-size now sit at
  positions 2 and 3, right after the vault popover. Authoring ease
  outranks chrome refinement because someone migrating from Apple
  Notes judges the app by day-one typing feel.

No code, just the doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
unforced added a commit that referenced this pull request May 12, 2026
* docs(design): Notes UI audit + vault-selector design proposal

Captures the current state inventory (twenty routes, three nav
primitives, where per-vault state is implicitly singular) and proposes
a vault popover that fronts the hub's `/.well-known/parachute.json`
vault list to fix the multi-vault-on-one-hub gap surfaced when Aaron
dogfooded the new vault MCP install across four vaults.

Surfaces ten broader UI improvement candidates with scope/leverage
reads, engages with the surface-direction research note
(parachute-patterns#54) on how Notes might evolve as a configured
surface instance, and recommends a sequencing for the broader audit.

No code changes. Doc-only PR — skips RC per
patterns/governance.md rule 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(design): fold reviewer fact-checks on Notes UI audit

Five citation/accuracy nits from #114 reviewer pass:

- §2 well-known.ts line range corrected: WellKnownVaultEntry is at :11-22,
  not :35-54 (which was WellKnownServicesEntry).
- §2 CORS framing: same-origin in standard installs (Notes at /notes/ on
  the hub origin); CORS-open header covers cross-origin deployments only.
- §1 getActiveVault count anchored: 17 direct call sites + broader
  useVaultStore touched across dozens of components. The "113" figure
  was a rough estimate that would've gaslit a future implementer.
- §2 beginOAuth reframe: authorize URL is built last after DCR +
  metadata discovery, so the vault-hint params bag is URL-decoration at
  the last step, not a structural change. Cheaper than the original
  framing implied.
- §4 LensSettings rename residue: flagged the coordinated migration
  (hook → type → stored-key). The legacy lens storage path fallback is
  intentional and stays — only the hook surface renames.

Plus open question added in §2: hub-origin discovery from VaultRecord.
The popover needs to fetch /.well-known/parachute.json against a hub
URL, but VaultRecord stores it as `issuer` (OAuth-time) without a clean
"hub origin" surface. Design call deferred to Phase 2 PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(design): absorb Aaron's north-star framing + add two improvement items

After Aaron read the audit, he gave directional input that the doc
should capture durably:

- New "North star" subsection up top: Notes should serve as an
  Apple-Notes / Obsidian replacement. Three pillars — easy as Apple
  Notes, flexible as Obsidian-ish, Parachute-shaped via per-vault
  customization. Every improvement candidate now reads against this
  question: "does this make Notes a credible Apple-Notes replacement?"
- §3 item #11: view-level text-size control. A device-local zoom knob
  (Default / Larger / Largest) affecting editor + read views. View
  preference, not content — markdown on disk stays untouched. Matches
  Apple Notes' affordance.
- §3 item #12: unify capture surfaces. Collapse the /new + TextCapture
  + MemoCapture duality into one create-or-edit interface where the
  "quick" experience is the same surface with smart defaults
  (auto-title, role-driven tags, default path). Voice still uses its
  own input modality but hands off to the unified surface.
- §5 sequence reordered: unified-capture + text-size now sit at
  positions 2 and 3, right after the vault popover. Authoring ease
  outranks chrome refinement because someone migrating from Apple
  Notes judges the app by day-one typing feel.

No code, just the doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
unforced added a commit that referenced this pull request May 13, 2026
….15-rc.6) (#123)

* feat(ui): unified capture surface + view-level text-size control (0.3.15-rc.6)

Two items from design/2026-05-12-notes-ui-audit.md §3 bundled per the
audit's sequencing note: #12 (unify capture) + #11 (view text-size).
Both north-star "Apple-Notes-grade ease" items.

### Unified capture surface (audit §3 item #12)

Adds polish to Capture; preserves NoteNew at /new for the file-drop
attachment-on-create path so this PR ships clean without losing the
working attachment flow. Capture is the canonical quick-path; NoteNew
remains the heavy-editor escape hatch one click away.

- **More fields panel** — collapsible <details> in Capture exposing
  a path override + summary input. Closed by default (textarea stays
  the no-friction focus); operators who need to set an explicit path
  (e.g. capturing into "Daily/2026-05-12") get the form without
  leaving Capture. Empty path = "vault auto-assigns"; empty summary
  = "no metadata.summary". Path override wins over the audio-only
  memo auto-path.
- **Inactivity autosave** — fires save() after 5 seconds of editing
  inactivity, in addition to the existing unmount-flush. Skipped
  while audio is staged (manual Capture click only), while
  recording/saving, and while body is empty. Hardcoded 5s per brief.
- **Escape hatch** — "Need to attach a file? Open the full editor"
  link in the More-fields panel points at /new (NoteNew, kept). Cmd+K
  keeps both "New note" + "Capture" entries for discoverability.

### View-level text-size control (audit §3 item #11)

- **lib/text-size.ts** — mirrors lib/theme.ts shape. Three steps
  (Default / Larger / Largest); per-device localStorage at
  `notes:textSize`; applied via `data-text-size` attribute on <html>.
- **styles/index.css** — `--font-size-prose` + `--font-size-editor`
  CSS variables on :root with two `:root[data-text-size="…"]`
  overrides. `.prose-note` reads the prose one; CodeMirror reads the
  editor one (its hardcoded `fontSize: "14px"` swapped for
  `var(--font-size-editor, 14px)`).
- **Settings TextSizeSection** — three radio buttons; applies +
  persists in one motion. Markdown on disk unaffected — pure view
  preference.
- **App.tsx** — applies the stored size on mount so the preference
  takes effect on every route, not just Settings.

### Tests

- 4 new in text-size.test.ts (round-trip, default handling,
  data-attribute application, labels).
- 8 new in Capture.test.tsx (disclosure closed by default; path +
  summary override payload shape; empty path → no `path` in payload;
  path override wins over audio memo auto-path; autosave-after-5s;
  edit-resets-timer; empty content no-autosave; audio-staged
  suppresses autosave).

### Hardening

- Unmount-flush enqueue now swallows IDB teardown rejections — the
  SyncProvider closes its handle in the same tick as Capture's
  unmount-flush, documented race in SyncProvider.tsx:60. No
  user-visible surface to report failures during nav-away anyway.

### Follow-ups not in this PR

- Attachments in Capture's quick flow (file-drop staging against the
  enqueue path) — currently only via /new. Will file as separate issue.
- captureText role tag applied in NoteNew (today only Capture
  applies the role). Small consistency win — separate issue.

Gates: 80 test files / 722 tests pass (+12 from this PR; was 710 in
#122). Typecheck clean, lint clean, build green (1561 KiB precache).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(capture): release savingRef on successful save; fold reviewer nits

Reviewer F1 (critical, data-loss) — savingRef was never reset after a
successful save. With autosave, the user stays mounted after a save, so:
- The next autosave timer's `if (savingRef.current) return` always bailed.
- The unmount-flush's same guard always bailed.

Net effect: type thought 1, autosave fires + succeeds, type thought 2,
close tab → thought 2 silently lost.

Fix: one line after `reset()` in the success path. Pre-autosave the
manual Capture click was the only entry point and a fresh mount handled
the reset implicitly; with the 5s timer the mount is reused across saves.

Two regression tests:
- "second autosave after first fires (savingRef releases on success)"
  asserts two distinct saves land in the queue from one mount.
- "unmount-flush after a successful autosave still flushes new typed
  content" asserts post-autosave drafts aren't dropped on nav-away.
  Uses the existing Toggler pattern so the SyncProvider's IDB handle
  stays open while the unmount-flush enqueue runs.

Reviewer F2 (polish) — add `@vitest-environment jsdom` pragma to
text-size.test.ts. The vitest config already defaults to jsdom but the
pragma makes the dependency explicit for stray `bun test` invocations.

Reviewer F3 (polish) — Settings TextSizeSection used useEffect to
read localStorage after the first render, causing a visible flash from
"Default" to the stored value. Switched to a lazy initializer so the
correct value is rendered on first paint.

Reviewer F4 (skipped) — the <details>/<summary> test pattern is
already documented inline. No change needed.

Gates: 80 test files / 724 tests pass (+2 from F1 regression tests),
typecheck clean, lint clean, build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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