Skip to content

feat(website): the stage on the homepage — one real run, scrubbed by scroll (live-stage plan 3 of 3) - #1032

Merged
blove merged 22 commits into
mainfrom
blove/stage-homepage
Sep 6, 2026
Merged

feat(website): the stage on the homepage — one real run, scrubbed by scroll (live-stage plan 3 of 3)#1032
blove merged 22 commits into
mainfrom
blove/stage-homepage

Conversation

@blove

@blove blove commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Why

Plan 3 of the homepage live stage (docs/superpowers/specs/2026-09-05-homepage-live-stage-design.md §4.1, §4.3, §5.2, §6–§10). Plan 1 restructured the page (#1024); plan 2 built the seekable /stage route in the demo app (#1030). This PR puts it on the homepage.

What

  • One pinned act (data-sc-act="pin" data-sc-span="6") driven by scroll-craft's engine, vendored unmodified and hash-pinned at 0b81622 (MIT). Its CSS is not vendored; the sticky and cue rules live under the website's style contract. The engine is mounted on the act's parent: its act scan is querySelectorAll, which matches descendants only.
  • stage-beats.ts: shares (1.3 / 1.2 / 2.4 / 1.1 vh), the piecewise monotonic scroll → time map, the 35–70% hold inside Approve where recorded time is pinned at the interrupt, the threshold at 70% that dispatches the resume, the render tail on the mounted form, and the rail's cue windows — one table, two views.
  • A DOM-only publisher posts { type: 'tplane-stage', t } to the iframe when t changes and the act is on screen; the frame's { applied, phase } lands in data-sc-verify-state, the hold in data-sc-verify-hold, so the harness sees the bespoke stage. It shape-checks the frame's ready and survives a throwing tick.
  • The demo's /stage ready message now carries hold and reloadEndMs. Until the canonical demo redeploys, production /stage sends neither, the website ignores that message, and the section falls back to the stills after 8 s — the intended degradation for the minutes the two deploys are apart.
  • Fallback: four stacked stills with the same copy — the server-rendered default, kept below 1024px, under reduced motion, without JS, or when the frame never reports ready. The act is keyboard-skippable.
  • Analytics: marketing:stage_progress with enter | beat | threshold | complete.
  • The four capability FeatureBlocks leave the homepage (they stay for the library pages); their copy is single-sourced in positioning.ts.
  • scroll-craft's shoot.mjs runs in CI against nx serve --configuration=production at desktop; it fails on dead scroll outside the declared hold and on cues that never peak; contact sheets are an artifact. Phone and reduced-motion passes are opt-in because the harness requires a pinned act and those viewports render the stills.

Tests

Beat map (monotonic incl. every seam, boundaries, hold, threshold, tail, cues), publisher (posts on change only, origin check, verify attributes, milestones once each, malformed ready ignored, throwing tick survives, hook deps through a ref), Stage mode switch + engine root, stills, style contract, analytics, positioning; e2e for the upgrade and 600vh pin, scroll-driven cues and the hold, the stills, and (env-gated STAGE_LIVE_FRAME) the live frame's verify state. Verified locally: website unit suite, lint (0 errors), production builds of the website and the demo app (initial bundle unchanged at 1.65 MB), demo stage suite 60/60, website e2e 16 passed / 1 skipped, and the harness at desktop: no dead scroll, all cues clear 4.5:1.

🤖 Generated with Claude Code

@blove
blove enabled auto-merge (squash) September 6, 2026 04:28
@vercel

vercel Bot commented Sep 6, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
threadplane Ready Ready Preview Sep 6, 2026 4:42am UTC

Request Review

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

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


I'll analyze this and get back to you.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

blove and others added 22 commits September 5, 2026 21:39
Eleven tasks: extend the frame's ready message with the hold and reload
boundary; vendor the scroll-craft engine hash-pinned at 0b81622; the beat
map (shares, the 35–70% hold, the threshold, the render tail, cue windows);
rail copy in positioning.ts; the stills fallback; a DOM-only publisher;
the pinned act with engine mount and analytics; the page swap; e2e; the
scroll-craft harness in CI; verification and PR.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oad boundary

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… fixture's reload run

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…windows

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t re-authors

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es forward on a missing beat

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…and is deeply readonly

The rail's beat union is now the beat map's own type, so renaming a beat in
stage-beats.ts breaks the rail at compile time instead of drifting. Tests
assert the eyebrow matches the beat, every string is present, and the three
hold lines in full. The verbatim-vs-page.tsx cross-check was deliberately not
written: Task 8 deletes the page's FeatureBlocks and positioning.ts becomes
the only source.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ibutes, milestones

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… analytics

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…urvives a throwing tick

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…omepage

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, and is keyboard-skippable

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hold, the stills

Four tests for the pinned act, the first time the vendored engine runs
end to end in a real browser: the desktop upgrade (span 6, the /stage
iframe, sticky .stage-pin, inline height = 6 viewports), scroll-driven
--sc-p with the stream and render cues and the declared verify hold, the
live-frame verify-state test (skipped unless STAGE_LIVE_FRAME=true until
the demo /stage redeploys with hold/reloadEndMs), and the stills on
phones and under reduced motion.

The plan's hold position was 0.55 with a miscomputed window: with the
authored shares (approve 2.4 of 6) the hold is 0.5567..0.6967, so 0.55
sits just outside it. The test scrolls to 0.68, inside the hold and on
the last hold line's plateau. No component change was needed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the hold where it is

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nt symlink survives

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rve substitution

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…I harness step builds once

Ready handshake: with a strict Referrer-Policy the frame had nowhere to
post `ready` and the publisher posted nothing before `ready`, so neither
side ever spoke. The bridge now remembers the last `ready` and re-posts
it the moment an allowlisted message teaches it the parent origin; the
stage component subscribes to seeks before posting `ready` so that
listener exists. The publisher says hello (`{ type, t: 0 }`) at most
once per 500 ms until `ready` arrives. Specs cover both sides.

CI harness step: the stage scroll verification built the website twice
(a standalone build, then the serve's own build dependency) and the
second run dropped the content symlink. The standalone build is gone;
the symlink is added after the readiness curl succeeds. The scroll-craft
README's local commands mirror the same ordering.

Minor: StageAct drops the unused `stage-frame-chrome` and
`stage-rail-heading` class names and gains `stage-rail-beat` /
`stage-rail-hold` test ids, which home-stage.spec.ts now locates by.
`StageMilestone` lives in stage-beats.ts (re-exported from the
publisher) and analytics/events.ts types `stage_event` and `beat` from
it instead of duplicating the unions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 6, 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 force-pushed the blove/stage-homepage branch from 15a998c to f7997ea Compare September 6, 2026 04:39
@github-actions

github-actions Bot commented Sep 6, 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 fee3545 into main Sep 6, 2026
76 checks passed
blove added a commit that referenced this pull request Sep 6, 2026
…me reports pause (#1034)

* fix(website): pin the stage hold one millisecond inside it so the frame reports pause

Verified against production after #1032 deployed: the frame went ready,
applied exactly through the interrupt (stream:930), but reported phase
`stream` at the hold because the website pinned t at hold.startMs and the
frame's phaseReachedAt treats a boundary instant as the outgoing run's.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(website): the pre-hold ramp ends on the pinned hold value so the seam is continuous

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <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