feat(deploy): share strip and Next steps trail under the Deployment Task Success Record (AIM-354) - #345
Conversation
… (AIM-354) When a deployment verifies, the moment a product goes live is the moment people want to show it to someone. The Deployment Task Success Record now carries a quiet share strip under the card — `Share <product>`, a QR popover for a phone (`Open on your phone` + host, dark-on-light in both themes, viewfinder corners), and one-click posts to X, LinkedIn, Facebook and Reddit. It appears only when the record has a primary HTTP(S) entry and always shares that entry's URL exactly as the record snapshotted it; every link opens a new tab with no referrer, so nothing of Brain is shared. The declared first-use steps move out of the card, where they competed with the verified facts, into a `Next steps` trail below the strip: a numbered circle per step, a hairline between them, the label and its optional monospace detail. It renders only when the record declared at least one step, independently of the strip. The section root becomes a wrapper owning card, strip and trail, so the scroll-into-view target and the `deployment-task-success` slot cover the whole conclusion; the Timeline pane still renders one section (ADR-0078). The share module is feature-local and its copy/href builders are pure. No contract, sanitizer, API or runner change. Ships the CONTEXT.md sentences for sharing and `Next steps`, adds the qrcode.react dependency, and removes the deployment-success-share prototype route now that its Merge variant has been promoted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwJfKahqCYxMzJXGbF522M
Review follow-up for AIM-354. The QR trigger and the four channel links now compose AppIconButton through its render prop, as the copy controls and the Open link already do, instead of reaching for the raw variants. The numbered circle uses the text-[11px] form the card already had, the viewfinder corners are one mapped list, the encoder alias is gone, and `Next steps` is a real heading. The href test now covers a space inside the address itself, not only inside the product name. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwJfKahqCYxMzJXGbF522M
The share strip's quiet icon buttons ended flush on the Timeline pane's own padding, so the result read as cramped at the bottom. The section wrapper now carries a little bottom padding of its own, so whichever block ends the conclusion — the card, the strip, or the Next steps trail — sits off the pane border. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwJfKahqCYxMzJXGbF522M
…ccess record The Deployment Task Success Record could name the product but not say what kind of product it was: a template's catalog categories stopped at the catalog and the deploy form, never reaching the task or the record. The share copy (AIM-354) wants to speak differently to a game server and to an AI app, and reading the live catalog at render time would break the rule that the record is a snapshot. A template-sourced Deployment Task now carries `templateCategories` from the catalog item the user chose, and the runner writes the template name into the record's `productId` (declared in the contract, never filled until now) and the categories into a new optional `productCategories`. The sanitizer trims, de-duplicates and caps the list; the record's signature includes it. Other sources declare neither, and records written before this change simply lack the fields. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwJfKahqCYxMzJXGbF522M
The X post was a bare "Just launched <name> 🚀 <url>". It now reads like
the user announcing their own launch and names Sealos: the generic post,
plus a game-server voice, an AI-app voice, and one hard-coded voice for
the EaglerCraft server. The voice is chosen from the facts the success
record snapshotted — the product id first, then the first category —
never from the live catalog, so a record keeps the post it was written
with. A record without a product name drops the name rather than
inventing one, and no post promises a deployment time.
Reddit's link-post title becomes "<name> is live — just shipped with
Sealos" ("My app is live …" without a name). LinkedIn and Facebook
still share the address alone. The handle and hashtags are module
constants; the copy and href builders stay pure and tested.
This reverses two decisions recorded on AIM-354 (the 🚀 copy, and no
platform attribution) and #336's "no product branch"; the issue is
revised to match.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwJfKahqCYxMzJXGbF522M
Four lines run together read as a block, with the hashtags jammed under a long wrapped link. The post now separates its announcement, its link and its hashtags with blank lines, the way posts on X usually scan, and the generic voice moves @Sealos_io up to the first line so every voice opens the same way and the link stands alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwJfKahqCYxMzJXGbF522M
|
@cursoragent review this |
|
Request changes on PR #345. The share strip, Next steps trail, snapshot rule, and tests are solid. The game/AI voices will not fire on the deployments people actually share.
Contract v2, sanitizer, signature, HTTP(S)-only sharing with Full walkthrough: AIM-354 PR #345 review. The same notes are on the PR. |
ReviewRequest changes. The share strip, Next steps trail, snapshot rule, and tests are in good shape. The category pipeline that the new game/AI voices depend on is not wired on the two first-launch create paths, so those voices will not fire for the deployments people actually share. Blocking:
|
| Create path | Has catalog item | Passes templateCategories |
|---|---|---|
| In-project Template pane | choice.category |
yes |
| In-project GitHub template recommendation | template.category |
yes |
New project · template (use-project-creator.ts onTemplateConfirm) |
choice.category |
no |
New project · GitHub template recommendation (handleGithubTemplateDeploy) |
input.template.category |
no |
Chat createDeployTask |
catalog tool already returns categories |
stripped by chat-deploy-task-input.ts |
productId still lands (source.templateName was always there), so the EaglerCraft voice still works. Every other template falls through to generic. Clones inherit the predecessor source, so the first successful deploy — the moment this strip exists for — is the one that matters.
CONTEXT.md currently says a template record snapshots the declared categories. That is only true for the two in-project panes.
Fix: pass templateCategories: choice.category / input.template.category in the two use-project-creator.ts calls, matching template-deployment-pane.tsx and github-deployment-pane.tsx. For chat, do not trust the model to copy the field: resolve categories from the catalog by templateName at create time (or add the field to the tool schema and fill it in the tool). Add a test on each create site.
What holds
- Contract stays at v2:
productIdwas already optional;productCategoriesis additive. Older records just lack the fields. - Sanitizer trims, de-dupes, caps at 16, omits empty lists; signature includes categories.
- Share/Open use the snapshotted primary HTTP(S) entry,
rel="noopener noreferrer". This is not ADR-0015 preview sharing. WS-only records render no strip. - Next steps is independent of the strip; no heading when the record declared no steps. One Timeline section (ADR-0078).
- Voice order (EaglerCraft id → first category
game/ai→ generic), nameless records, no promised deploy time, X three-beat body, Reddit title, LinkedIn/Facebook URL-only, encoding of#/&/ space /%0A.
Non-blocking
- Channel
AppIconButtoncomposition (deployment-task-success-share.tsx): icon lives inside the<a>, children are{null}. House style (e.g.billing-tab-shell.tsx) is an empty render host with the icon as children. Pane tests only capture the opening<a>tag, so an empty merge would still pass. Match the house pattern and assert the icon is inside the link. - QR popover a11y: Base UI Popover is
role="dialog"with noPopoverTitle/aria-label. Promote “Open on your phone” toPopoverTitle. - New
text-[11px]:globals.cssalready registerstext-2xs. Use that on the new surfaces. productNameis the template slug. Share copy will sayeaglercraft-server, while the tests use display titles. Snapshotchoice.titleif the post should name the product. The dedicated EaglerCraft voice is unaffected.successText: comment says “single line”; it onlytrim()s. Interior newlines inproductNamewould break the X beats. Unlikely from the catalog.- Sanitizer cap test asserts
length < 40, not exactly 16.
I did not run the UI in a browser. Review is against the source, ADRs, CONTEXT.md, and the AIM-354 tests.
Structured walkthrough: AIM-354 PR #345 review
The share voices read `productCategories` off the success record, but only the two in-project panes sent `templateCategories` with the create request. New-project template deploys and the chat `createDeployTask` tool — the first-success paths the share strip exists for — dropped the field, so every template but EaglerCraft fell through to the generic post. Both new-project template paths now build their request through `newProjectTemplateRequest`, which carries the chosen catalog item's categories the way the panes do. The chat tool resolves categories from the catalog by template name at create time rather than trusting the model to copy them; an unknown name or an unreachable catalog leaves the source as declared and never blocks the deploy. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L75qX7NHHC165cykRCyMGG
Channel links compose `AppIconButton` as the rest of the app does: an empty anchor as the render host, the icon as children. The pane test now captures the whole `<a>` element and asserts the icon sits inside it. The QR popover's "Open on your phone" line becomes its `PopoverTitle`, so the dialog Base UI announces carries a name. The new 11px surfaces use the registered `text-2xs` token instead of an arbitrary size. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L75qX7NHHC165cykRCyMGG
`successText` promised a single line but only trimmed, so an interior newline in a product name would have broken the X post's beats. It now folds whitespace runs to one space. The sanitizer test asserts the category cap exactly (16) instead of merely "fewer than 40". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L75qX7NHHC165cykRCyMGG


What
When a deployment verifies, the Deployment Task Success Record now ends with two quiet blocks under the card:
Share <product>, a QR popover (Open on your phone+ host, dark-on-light tile, viewfinder corners), and one-click posts to X, LinkedIn, Facebook and Reddit. Shown only when the record has a primary HTTP(S) entry; always shares that entry's URL exactly as snapshotted. Every link opens a new tab withrel="noopener noreferrer", so nothing of Brain is shared.Next stepsheading. Shown only when the record declares steps, independently of the strip.The section root becomes a wrapper owning card, strip and trail, so the scroll-into-view target and the
deployment-task-successslot cover the whole conclusion. The Timeline pane still renders one section (ADR-0078).Share copy
The X post speaks in the user's voice and names Sealos. The voice is chosen from facts the record snapshotted, never from the live catalog:
productIdiseaglercraft-server→ the EaglerCraft voice (the one deliberate product branch; supersedes feat(deploy): celebrate verified deployment success in the Timeline #336's "no product branch").gameorai.A record without a product name drops the name rather than inventing one. No post promises a deployment time. Reddit's link-post title is
<name> is live — just shipped with Sealos. LinkedIn and Facebook share the address alone.Record contract
To make that possible, a template-sourced Deployment Task now carries
templateCategoriesfrom the catalog item the user chose, and the runner writes the template name into the record'sproductId(declared, never filled before) and the categories into a new optionalproductCategories. The sanitizer trims, de-duplicates and caps the list; the record's signature includes it. Contract version unchanged; older records simply lack the fields; other sources declare neither.Also
deployment-task-success-share.tsx); it moves to@workspace/uionly if a second consumer appears.AppIconButtonthrough its render prop, matching the card's copy controls.Next steps, and snapshot sentences.qrcode.reactadded; thedeployment-success-shareprototype route tree removed.Tests
target/rel/ labels plus the QR trigger, nameless record, EaglerCraft and AI voices, trail with and without a strip, no heading without steps.%0Aline breaks,#/&/space encoding, Reddit title, QR panel content.templateCategories; pipeline copies them into the source; source→product helper and readiness claim carry id and categories; sanitizer trims/de-dupes/capsproductCategories; signature includes them.bun typecheck,bun checkclean.Out of scope
Per-template share copy declared by the template itself (beyond the EaglerCraft voice); any producer of first-use steps (AIM-353); Web Share API, WeChat/Weibo, copy-link in the strip, localisation, analytics.
🤖 Generated with Claude Code
https://claude.ai/code/session_01AwJfKahqCYxMzJXGbF522M