Skip to content

feat(examples/chat): open the demo on a chosen suggestion via ?featured= - #832

Merged
blove merged 2 commits into
mainfrom
blove/demo-featured-suggestion
Aug 26, 2026
Merged

feat(examples/chat): open the demo on a chosen suggestion via ?featured=#832
blove merged 2 commits into
mainfrom
blove/demo-featured-suggestion

Conversation

@blove

@blove blove commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Phase 2 groundwork for the homepage medium switcher. The website's live-demo tabs need each section to frame its own scenario — otherwise every section embeds the same empty demo under a different heading, which is the duplication the switcher exists to avoid.

Why not the ?prompt= the spec asked for

I researched the specced design before building it, and it was the expensive path:

  1. ChatComponent has no draft input. Prefilling the composer means adding a public input to @threadplane/chat — a semver-relevant API addition to a commercially licensed published package, requiring npm run generate-api-docs, for the sake of a marketing page.
  2. Free text in a URL is a defacement vector. Any link could display arbitrary attacker-chosen words inside the Threadplane demo UI. The original design guarded against auto-execution but not against this.

The demo already had what was needed: welcome-suggestions renders a featured chip chosen by suggestionsForAppMode(). So ?featured=<id> selects from the curated list instead. No library change, and selecting still requires a click.

The spec has been updated to record the deviation and why.

The id is a key, never content

An unrecognised id falls back to the default rather than rendering what the link supplied.

Mutation-tested — making an unknown id synthesize a suggestion from the URL fails the guard:

× falls back to the default when the id is unknown

And verified in a browser, not just in tests. Loading ?featured=Free%20Bitcoin%20-%20click%20here:

{ "featuredChip": "Generative UI: contact form",
  "urlTextLeakedAnywhereOnPage": false,
  "messageCount": 0 }

The string appears nowhere in the DOM, and nothing auto-sent.

The happy path works too — ?featured=tell-me-about-coral features that chip, messageCount: 0.

Notes

  • Suggestion ids are explicit, not derived from labels at runtime: the website will link to these ids, so renaming a label must not silently break a link.
  • The shell reads the param once at construction, like appmode. Re-reading it reactively would let a later navigation reshuffle the chips under someone mid-session.
  • No sanitising in the shell, deliberately — and a comment says why. Sanitising there would imply the value is rendered as content somewhere, which is exactly the impression to avoid.
  • Wired through all three modes (embed, popup, sidebar).

Verification

  • npx vitest run --config examples/chat/angular/vite.config.mts: 110 passed, 0 failed (7 new)
  • nx lint examples-chat-angular: 0 errors
  • nx build examples-chat-angular --configuration=production: succeeds
  • Browser-verified both the happy path and the unknown-id fallback

🤖 Generated with Claude Code

Researching the original design before implementing it turned up two problems.
ChatComponent has no draft input, so prefilling the composer would mean adding a
public input to @threadplane/chat — a semver-relevant change to a commercially
licensed published package, for a marketing page. And free text in a URL lets any
link display arbitrary chosen text inside the demo UI; the original design
guarded against auto-execution but not against defacement.

The demo already has the mechanism: welcome-suggestions renders a featured chip
chosen by suggestionsForAppMode(). A keyed ?featured=<id> selects from that
curated list — no library change, still never auto-sends, and an unknown id falls
back to the default instead of rendering what the URL says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview Aug 26, 2026 5:20pm

Request Review

The website's live-demo tabs need each homepage section to frame its own
scenario. Without a way to say which, every section would embed the same empty
demo under a different heading.

The spec originally called for a free-text `?prompt=` that prefills the composer.
Researching it first turned up two problems. `ChatComponent` has no draft input,
so prefilling would mean adding a public input to `@threadplane/chat` — a
semver-relevant change to a commercially licensed published package, for the sake
of a marketing page. And free text in a URL lets any link display arbitrary
chosen words inside the demo UI; the original design guarded against
auto-execution but not against defacement.

The demo already had the mechanism. `welcome-suggestions` renders a featured chip
chosen by `suggestionsForAppMode()`, so `?featured=<id>` selects from that curated
list instead. No library change, and selecting still requires a click.

The id is treated as a KEY, never as content: an unrecognised id falls back to the
default rather than rendering what the link supplied. That property is
mutation-tested — making an unknown id synthesize a suggestion from the URL fails
`falls back to the default when the id is unknown` — and verified in a browser: a
`?featured=Free%20Bitcoin%20-%20click%20here` link renders the default chip and
the string appears nowhere in the DOM.

Suggestion ids are explicit rather than derived from labels at runtime, because
the website links to them; renaming a label must not silently break a link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit 4a865a0 into main Aug 26, 2026
30 checks passed
blove added a commit that referenced this pull request Aug 26, 2026
…bs (#833)

Completes the switcher across all four homepage sections. Render and Ship gain
video and code tabs, and every section gains a Live tab that frames the real
demo opened on that section's own scenario.

Two new clips, recorded against aimock fixtures so a recut reproduces
frame-for-frame:

- Render — the agent emits a spec and a real contact form mounts as Angular
  components, `render_a2ui_surface` visible in the tool call.
- Ship — a rich markdown answer, then a page reload, and it all comes back.
  Durability was initially judged unwatchable; it is exactly watchable, and it
  is the claim that section makes.

The live tab uses `?featured=` (merged in #832) so each section opens the demo on
its own curated scenario instead of the same empty chat under four headings.

`section-media.spec.ts` gains the guard that matters here: it reads the DEMO's
actual suggestion list off disk and asserts every `live.featured` id exists
there. `?featured=` falls back silently for an unknown id, so a typo would
quietly turn a live tab back into the generic demo with nothing else failing.
Mutation-tested — a one-character typo fails with
`approve -> approve-before-a-destructiv: expected [...] to include`.

The active-pane-only guarantee holds at full scale, which was the spec's main
technical risk. The built homepage carries 4 tablists, **4** `<video>` elements
rather than 12, and **0** iframes — the live iframe is not requested until its
tab is selected. Verified in a browser too: clicking Live takes iframes 0 -> 1
and videos 5 -> 4 as the video pane unmounts.

Co-authored-by: Claude Opus 5 <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