Skip to content

feat(demo): URL routing for shareable demo state#494

Open
blove wants to merge 12 commits into
mainfrom
claude/url-routing-demo
Open

feat(demo): URL routing for shareable demo state#494
blove wants to merge 12 commits into
mainfrom
claude/url-routing-demo

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 20, 2026

Summary

  • Supersedes #500 (reverted in this branch) with a broader URL-routing implementation.
  • Thread id moves to a :threadId path segment per mode; agent knobs (model, effort, genui, theme, color, project) round-trip via query params with defaults omitted.
  • URL hydration is ephemeral — overrides signals on visit but never writes to a recipient's localStorage. Explicit user actions still persist locally.
  • Drops threadId from localStorage entirely.

Spec: docs/superpowers/specs/2026-05-20-demo-url-routing-design.md
Plan: docs/superpowers/plans/2026-05-20-demo-url-routing.md

Relationship to PR #500

PR #500 landed first with the thread-id-in-URL half (/embed, /embed/:threadId) plus id validation/redirect on 404/422. This PR reverts that commit and re-implements the same routing with a broader scope: also rounds knob state (?model=&effort=&theme=&color=&genui=&project=) through the URL, and pins ephemeral hydration semantics so a shared link cannot clobber a recipient's localStorage.

Net change relative to PR 500: +knob URL writes, +knob hydration from URL, +ephemeral test contract, +e2e deep-link smoke. Removed: PR 500's getThread() id validation + 404/422 redirect (out of scope for this PR — happy to add as a follow-up; the spec deliberately deferred validation as "thread ids are guessable but threads contain no sensitive data").

URL shape

/<mode>[/<thread-id>][?model=&effort=&genui=&theme=&color=&project=]

Examples:

  • /embed — fresh demo, all defaults
  • /embed/019e434c-... — that thread, defaults for everything else
  • /popup/abc123?genui=json-render&theme=material-dark&color=light — full state

History behavior

Trigger Policy
Mode change (segmented control) push
Thread switch (sidenav click) push
Backend-allocated thread id (SDK onThreadId) replace
Knob change (any dropdown) replace

Test plan

  • Unit: 34/34 in examples-chat-angular (hydration, navigation, ephemeral contract)
  • Build: nx build examples-chat-angular green
  • Lint: nx lint examples-chat-angular clean
  • E2E: deep-link + mode-switch (nx e2e examples-chat-angular) — runs in CI; note the e2e job is currently failing on main too (pre-existing, unrelated to this PR)
  • Manual: copy /embed/<id>?theme=material-dark to incognito tab → loads correctly, localStorage untouched
  • Manual: switch mode preserves thread + knobs
  • Manual: change knob to default → param drops from URL

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

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

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment May 21, 2026 5:05am

Request Review

blove and others added 11 commits May 20, 2026 21:47
Locks decisions: full state in URL (thread id in path + agent knobs +
theme + color scheme + project as query params), defaults omitted to
keep links short, ephemeral hydration semantics (URL writes signals
but not localStorage, so shared links don't infect recipients'
preferences), thread id moves from localStorage to URL (drops
threadId persistence entirely).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the localStorage init read; threadIdSignal now starts as null and
is set by readUrlState() on mount and on every NavigationEnd, reading
the :threadId param from the active leaf route. Also fix pre-existing
LANGGRAPH_THREADS_CONFIG provider missing from all spec beforeEach blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace direct threadIdSignal.set() + persistence.write('threadId') calls
in onThreadSelected, onNewThread, threadActions.delete/archive, and the
agent's onThreadId callback with router.navigate(); onThreadId uses
replaceUrl: true to avoid extra history entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add KNOB_DEFAULTS table and buildQueryParams()/writeKnobsToUrl() helpers.
Wire each knob handler (model, effort, genui, theme, color, project) to
call writeKnobsToUrl after set+persist; omit params that match defaults
so a fresh session shares a bare URL. replaceUrl:true keeps dropdown
clicks out of browser history. Also wire the constructor color-scheme
auto-sync effect that updates the A2UI theme preset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extend readUrlState() to read model, effort, genui, theme, color, and
project query params on mount and every NavigationEnd. Hydration is
ephemeral — no persistence.write() calls inside readUrlState(). Five
new tests cover the round-trip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@blove blove force-pushed the claude/url-routing-demo branch from 549889f to 66467eb Compare May 21, 2026 04:59
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