fix(website): readable code panes, self-healing hero handshake, focus ring, mobile hero - #998
Merged
Conversation
The embedded walkthrough could reach `ready` and then sit on the empty welcome state forever, because the whole handshake rode on a single message that either side could drop. Parent (HeroDemo): the first visibility post fired while the iframe's contentWindow was still `about:blank`, so it was dropped with the "target origin does not match the recipient window's origin" console error that appeared on every production load. It now posts only after the frame's window has navigated — proven by the iframe `load` event or by the frame having spoken to us — and it answers EVERY `ready`, not just the first, with the current visibility. Frame (HeroMode): the `message` listener is registered inside `boot()`, after the lazy route chunk has loaded, so the parent's `load`-time post could land before anyone was listening and nothing retried. An embedded frame now re-announces `ready` every 500ms (capped at 10s) until any visibility message arrives, then stops; it also stops on destroy. HeroBridge's origin allowlist and `e.source === parent` check are untouched. Verified locally against the real iframe with the demo served from localhost: 0/12 loads started the walkthrough before, 12/12 after, and the postMessage console error is gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…poser `.hero__take` was `position: absolute; bottom: 72px`, so it floated over the chat surface: at 768px it sat on the streaming answer and half-covered the composer's send button, and at 390px it landed on "How can I help?". Moved into normal flow as the last row of the `.hero` column, so it can never overlap anything at any width. Sampled at six points across a full walkthrough at 768px and 390px: the pill's box previously hit chat-welcome / chat-typing-indicator / chat-scroll at every sample, and now hits nothing inside the surface. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…g, fix the mobile hero Three defects found on the deployed homepage. Code panes hid their payload. `white-space: pre` plus `overflow-x: auto` is invisible clipping on macOS, where the overlay scrollbar draws nothing until a scroll is already under way: the runtime-parity pane ended mid-string at `assistantId: 'agent` (scrollWidth 609 / clientWidth 550 at 1440px) and the install dialog showed `... @langchain/langgraph @langcha`. Every shiki pane and every install-dialog pane now soft-wraps (`pre-wrap`), with `break-word` as the backstop for a run that carries no whitespace at all. A scrollbar was measured and rejected as the affordance: `scrollbar-width: thin` and `::-webkit-scrollbar` both leave 0px of layout gutter on macOS. The install command wraps outright — it is one shell line with no meaningful breaks, and copy still takes the full string from state. Buttons had no visible focus ring. The UA default `outline: auto 1px rgb(0, 95, 204)` is invisible on the primary button's own #004090 fill, which is exactly where focus lands when the install dialog closes. The mobile hero read as bare text on a smudge. The play control's `#111` fill was camouflaged against the near-black poster, and its `left: 50%` inset let it shrink-to-fit into half the stage. The 1200x720 desktop poster is now cropped rather than shrunk below 768px, so the chat is legible at ~2x. Verified against a production build at 1440/768/390: all 17 code panes report scrollWidth == clientWidth. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
blove
enabled auto-merge (squash)
September 4, 2026 19:33
Contributor
blove
added a commit
that referenced
this pull request
Sep 4, 2026
…edesigned OG card (#1002) * feat(website): redesign the Open Graph card for feed legibility The old card put the product name in an 18px eyebrow, the runtimes and the capability list inside a three-line 26px paragraph, and three 15px mono pills along the bottom. At the ~500px a timeline actually renders a share card, all of that is grey texture: the only legible element was the headline, and the brand name was the smallest type on the card. Rebuilt around what survives a 0.42x downscale. Four elements, none below 30px source: the wordmark at 100px, the category line (HERO_H1, now on one line) at 52px, HERO_SUBHEAD at 36px for what you get, and a LangGraph + AG-UI pill at 34px for what it plugs into. Centred rather than left-aligned so a square thumbnail crop keeps the product name. Moved onto the production dark-surface tokens from ui.css ([data-ui="section"][data-surface="dark"]) resolved to literals, since Satori cannot read CSS variables, plus the radial accent glow from landing.css .proof-strip::before and --color-angular-red for the seam. Text contrast is 16.8:1 (primary), 11:1 (secondary) and 8.6:1 (accent). Copy stays single-sourced: the wordmark is derived from PRIMARY_TAGLINE rather than retyped, and the runtime claim is POSITIONING_PROOF_POINTS[0]. `alt` now describes what the card says instead of naming the page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(website): ship the OG card's dimensions and alt text Next's file-convention metadata (the alt and size exports in app/opengraph-image.tsx) is overridden the moment openGraph.images is set explicitly. Both layout.tsx and createPageMetadata passed a bare URL string, so production emitted og:image alone — no width or height for a platform to lay the card out before fetching it, and no alt text at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(examples): record the hero poster at phone width A parallel to record-hero-poster.record.ts that drives /hero at 390x650 and writes hero-walkthrough-poster-mobile.webp. Same beat as the desktop script — the first streamed reply — because 650 CSS px is the shortest frame that holds the whole answer from its first line, and because matching beats mean crossing the breakpoint swaps the source without changing the story. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(website): ship a phone-width hero poster The hero poster is the server-rendered LCP and the only thing a phone sees until it taps Play. It was a 1200x720 desktop capture; at 390px it landed in a 348px stage, so the last change narrowed the stage to 4/5 and pushed object-position to 40% so cover would crop rather than shrink. That bought legibility and paid for it in credibility: every line of prose lost its right edge, and the frame read as a broken screenshot. Replaced with a second render of the same moment at phone dimensions (585x975, 33,576 bytes against the desktop poster's 37,718), chosen by a <picture> media source on the same 768px boundary the stage ratio and MIN_AUTOPLAY_WIDTH already use. The stage below the breakpoint now holds 3/5 — the capture's own ratio — so object-fit: cover crops nothing and the object-position nudge is gone. currentSrc resolves to the phone poster at 390 and to the desktop one at 768 and 1440; the stage box measures the same with the image aborted as with it loaded at all three widths, so the swap costs no layout shift. Two follow-on fixes the new asset exposed: the phone frame ends on the demo's own "Take control" button, which read straight through the play control's 97% fill and sat half-uncovered beneath it. The fill is now opaque and the control drops to 12px on phones so it lands squarely on the dead one. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(website): re-record both hero posters on the shipping layout The desktop poster was stale. It showed "Take control" floating over the composer — the absolutely-positioned layout replaced in #998 — and a beat where the walkthrough's second prompt was already typed into the composer. It is the LCP element for every desktop visitor, so the largest image on the homepage was showing a UI that no longer exists. Both recorders now hold 2500ms after the interrupt detaches instead of 1500ms. That is about the scripted cursor, not the text: HOLD_AFTER_ANSWER_MS (2000) plus CURSOR_MOVE_MS (650) means at 1500ms the arrow is still parked where it pressed Accept, which at phone width dropped it onto the word `retain` in step 3 — an artifact rather than a hint that the demo is live. At 2500ms it has reached the composer, which is the beat the desktop script's docstring has always claimed. The beat intent is otherwise unchanged: the first streamed reply, with the user turn, the tool call and the whole answer on screen. Both scripts now assert what they captured — `.hero__take` visible in normal flow, composer empty — so a drifted wait or another layout change fails the recording instead of silently shipping a poster of a UI we no longer have. Desktop 37,718 -> 38,114 bytes; phone 33,576 -> 33,624. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #997, from verifying the deployed homepage in a real browser. Everything here is a defect found on the live site, not a refinement.
The hero bridge could stall, and did
The frame does not start its walkthrough until the parent posts
visible: true, and both sides sent that handshake once with no retry. The parent's first post always fired whilecontentWindowwas stillabout:blankand was dropped, which is thepostMessage ... does not match the recipient window's originwarning on every production load. The only surviving post came from the iframe'sloadevent, and the frame registers its listener after its lazy chunk boots, so ifloadwon that race the message was lost and nothing retried. Live, this stalled once in nine loads: the pill said ready and the walkthrough never started.The handshake is now self-healing from either side. The parent gates its first post on the frame having navigated and answers every
ready; the frame re-announcesreadyevery 500ms, capped at 10s, until an embedder answers.Measured against a real iframe, website production build embedding the local demo, where the dev server's chunk latency makes the frame lose the race every time:
The permanent console warning is gone.
hero-bridge.tsis untouched: the origin allowlist and thee.source === parentcheck are unchanged.Code panes hid the argument they were making
The runtime-parity pane cut off at
assistantId: 'agent, and the install dialog showednpm install @threadplane/chat @threadplane/langgraph @langcha…. Both were horizontal scrollers, and macOS overlay scrollbars leave no affordance, so the one line carrying each section's point was invisible.A scrollbar affordance was tried first and measured dead: with
scrollbar-width: thinthe gutter was 0px, because Chromium keeps the overlay either way. So the panes wrap instead. Across all 17 code panes at 1440, 768 and 390, hidden content is now zero. Copy still copies the full unwrapped command.Also
elementsFromPointthrough a full walkthrough at both widths.Verification
1119 website tests, 179 demo tests, 0 lint errors, clean type-check, green production builds. Four style contracts pin the load-bearing declarations, including a note recording the measured 0px scrollbar gutter so nobody restores the scrollbar as the fix. The handshake specs are mutation-checked against the old code.
🤖 Generated with Claude Code