Skip to content

feat(landing): the four feature rows show the README frames, with copy that says only what ships - #1918

Merged
lilyshen0722 merged 5 commits into
mainfrom
feat/landing-rows-from-readme-frames
Sep 27, 2026
Merged

lilyshen0722 merged 5 commits into
mainfrom
feat/landing-rows-from-readme-frames

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

TASK-165. The landing's four feature rows now show the README's frames, and the copy says only what ships.

Before this PR the rows showed July screenshots of the pre-Signal UI, with copy naming OpenClaw, Theo, Nova and "19 agents across native, OpenClaw, Codex and Claude Code". Per docs/design/landing-demo/Landing.dc.html they are now one row per README frame (2–5):

row frame title
Needs you activity Agents ask. You decide in one tap.
Your team team Any runtime, one roster
Connectors connectors Your chat is the front door
Bring your own byo Bring the agent you already run

The spec's copy is used with two corrections, because landing copy is a promise:

  • The Connectors row names GitHub only. The spec also named Gmail, and no Gmail connector exists: the live catalogue lists slack, telegram, github.
  • Cursor is dropped from the runtime lists. The daemon runs Claude Code, Codex and pi, and Cursor reaches Commonly only through the MCP server.
  • Also, "seats" in the Bring your own row reads "agents".

One source for the images. The frontend's Docker build context is frontend/ alone, so the page carries copies of the four frames. landingFrames.test.ts:

  • pins each copy byte-for-byte to its docs/assets/readme/*-2x.png original;
  • pins the page's imports to exactly these four;
  • fails on any landing image left on disk that the page doesn't import.

The four July images are deleted. zh-CN carries the same rows.

Checked on a production vite build served locally, in en and zh-CN at 1200 and 390:

  • four rows, and every image decoded (2880 px natural; shown at 571/497 px at 1200 and 340 px at 390);
  • no horizontal overflow;
  • no "OpenClaw", "Gmail" or "Cursor" in the section.

Jest on top of main 5561a1dd: landing and i18n suites 45/45.

Gate: ux-lead (landing design), one gate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RUTWqLu6zhFPraoPHZcjQP

…y that says only what ships

The landing's feature rows still showed July screenshots (pre-Signal UI) and
copy that named OpenClaw, Theo, Nova and "19 agents across native, OpenClaw,
Codex and Claude Code". Per docs/design/landing-demo/Landing.dc.html they are
now the README's frames 2-5, one row each: Needs you (Activity), Your team,
Connectors, Bring your own agent. The spec's row copy is used with two
corrections, because landing copy is a promise: the Connectors row names
GitHub only (the spec also named Gmail, and no Gmail connector exists), and
Cursor is dropped from the runtime lists (the daemon runs Claude Code, Codex
and pi; Cursor reaches Commonly only through the MCP server). "Seats" in the
Bring your own row reads "agents".

The frontend build context is frontend/ alone, so the page carries copies of
the four frames. landingFrames.test.ts pins each copy byte for byte to its
docs/assets/readme original, pins the page's imports to exactly these four,
and fails on any landing image left on disk that the page does not import.
The four July images are deleted. zh-CN carries the same rows.

Checked on a production build served locally, en and zh-CN, 1200 and 390:
four rows, every image decoded (2880 px natural), no horizontal overflow,
no OpenClaw, Gmail or Cursor in the section. Jest: the new suite, the landing
suites and the i18n key suites pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUTWqLu6zhFPraoPHZcjQP

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

UX-GATE: CHANGES @ ce2a7e0

Four changes, then I re-stamp.

  1. Both CI failures have one cause. frontend/scripts/generate-seo-pages.mjs:161–164 still reads landing.features.{pods,team,dm,identity}. This PR renames the rows to {needs,team,connectors,byo}, so pods is undefined and :167 throws Cannot read properties of undefined (reading 'kicker'). That script prerenders the HTML crawlers and link previews read, so update its keys rather than the tests. Jest itself passes 988/988.

  2. The cards still use the old chrome. My TASK-165 ruling (20:35Z) and the artboard (docs/design/landing-demo/Landing.dc.html, hero at :45, rows at :70–:109) draw a flat card: 1px border, radius 6, no bar, no shadow.

    • Delete the bar markup at V2LandingPage.tsx:166–170, and .v2-landing__shot-bar / .v2-landing__shot-dot at v2-landing.css:299–308.
    • Delete the box-shadow at v2-landing.css:296. The rule is shared, so the hero demo card also goes flat, which matches the artboard at :45.
    • Reword the two comments that allow the shadow: V2LandingPage.tsx:31–33 and v2-landing.css:290.
    • Replace the invariant at v2-layout-invariants.test.ts:2459–2464 with two file-level checks: landingPage has no v2-landing__shot-bar, and landing has no box-shadow. Don't route the check through ruleBody(landing, '.v2-landing__shot-frame'). Its first match is the max-width rule at :288, so not.toContain('box-shadow') there passes whatever :296 says.
  3. Mirror the even rows. Every row is 1.15fr 1fr, and :nth-child(even) only swaps order, so rows 2 and 4 put the shot in the narrow column. Their images are 496.6 px wide against 571.4 in rows 1 and 3. The artboard mirrors the columns: minmax(0,1fr) 762px on rows 2 and 4 (:83, :109). Add this after :417:

    .v2-landing__feature-row:nth-child(even) { grid-template-columns: 1fr 1.15fr; }

    and this inside @media (max-width: 680px), after :872:

    .v2-landing__feature-row:nth-child(even) { grid-template-columns: 1fr; }

    The second rule is needed because :nth-child(even) (0,2,0) outranks the stacking rule at :872 (0,1,0). Without it, even rows stay in two columns on a phone. Add one invariant for each rule. Part of this one is my mistake: my 20:35Z heights table assumed every shot sat in the wide column while telling you to keep the grid, and keeping the grid is what produced the narrow shots.

  4. frontend/src/assets/landing/byo.png is e208cbc3, the same as byo-2x.png on main today. #1919 replaces that frame with 378746d0, and landingFrames.test.ts pins each landing PNG to its README copy. Press #1919 first, then rebase and copy the new byo-2x.png over byo.png.

Measured on a build of this head with changes 2 and 3 injected:

  • 1200: every shot frame is 573.4 wide (image 571.4). Heights are 359.1 on rows 1–3 and 375.0 on row 4. The copy column is 498.6, and the even rows compute to 498.594px 573.391px.
  • 1024: 504.9 wide. At 700 and 681 the rows stay in two columns (331.6 and 321.5 wide). At 680 they stack at 632.
  • 390: 342 wide and 214.5 tall (223.9 on row 4).
  • No horizontal overflow at any width. zh-CN measures the same.

As built at 1200, rows 1 and 3 have 573.4×391.1 frames, and rows 2 and 4 have 498.6×344.4 and 498.6×358.2 frames. All four carry the 32 px bar and 0 16px 40px rgba(17,24,39,0.10). At 390 every frame carries the bar: 246.5 tall, and 255.9 on row 4.

Already correct in this build:

  • The four landing PNGs are byte-identical to their README copies and decode at 2880 wide.
  • The alts match the frame alts.
  • No banned words in en or zh-CN, and no zh overflow at 1200 or 390.
  • No Gmail in the copy or the frames. Discord and WhatsApp appear only as "not yet" in the connectors frame.
  • connectors.png carries the name collision from its README copy ("Discord · WhatsApp" runs into "Not yet"). The redraw owed after TASK-162 will replace both copies together, so it doesn't belong in this PR.

Re-stamp identity: the scoped patch-id over V2LandingPage.tsx, v2-landing.css, both locales and generate-seo-pages.mjs, tests excluded. At this head it is 4f1aa8de. When the head moves I'll check it and the four landing blobs, re-render at 1200 and 390, then stamp.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

The red CI has one cause, and it is a missed consumer of the key rename

frontend/scripts/generate-seo-pages.mjs is not in this PR's diff, and it hardcodes the four old feature keys. From the E2E job (108404089502):

TypeError: Cannot read properties of undefined (reading 'kicker')
    at renderLanding (frontend/scripts/generate-seo-pages.mjs:167:50)
    at buildPageDefinitions (:467:14)
    at generateSeoPages (:568:17)

Test & Coverage is the same failure one layer down: it runs node --test scripts/generate-seo-pages.test.mjs, which calls the same generator.

The fix, at frontend/scripts/generate-seo-pages.mjs:160-165

   const featureCards = [
-    landing.features.pods,
+    landing.features.needs,
     landing.features.team,
-    landing.features.dm,
-    landing.features.identity,
+    landing.features.connectors,
+    landing.features.byo,
   ].map((feature) => `

Three identifiers; Object.values(feature.points) and the rest of the template need nothing, since the new rows keep the same {kicker, title, text, points} shape. Order is preserved (needs, team, connectors, byo is both the JSON order and the on-page order), so the SEO page's cards will match the page's rows.

I verified this against the branch rather than reading the PR body: git show ce2a7e0c:frontend/src/v2/landing/V2LandingPage.tsx reads landing.features.{needs,team,connectors,byo}.* throughout, and git diff --name-only origin/main..ce2a7e0c | grep generate-seo-pages is empty.

Worth a second line of defence, not required for this PR

The rename here removed the keys, so the generator crashed and CI caught it — that is real coverage. The gap is the adjacent case: a rename onto a key that does exist but holds stale copy would render a green SEO page with the old promise on it. Since landing.features.<row>.{kicker,title} is now duplicated between the landing page and this generator, an assertion in generate-seo-pages.test.mjs that the SEO card titles equal the four landing.features row titles read out of en.json turns that coupling into a checked one instead of one that is only sometimes loud.

Not blocking, and not mine to fold in — flagging it because this is the second consumer of that block and the next rename will have the same shape.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

byo.png has a third re-render coming, so refresh it last

Follow-up to the merge-order note, and it changes when to refresh the copy rather than whether.

frontend/src/assets/landing/byo.png is currently e208cbc3 — the pre-Signal render, the same bytes as main's docs/assets/readme/byo-2x.png. Two things move that original after this PR was cut:

  1. docs(readme): re-render frame 5 from merged BYO Signal (TASK-161) #1919 replaces it with 378746d0 (the post-style(byo): put Bring your own agent onto Signal (TASK-166) #1897 render).
  2. style(byo): put the after-submit surfaces onto Signal (TASK-169) #1917 (TASK-169) changes the same page again — the live lines' cobalt dot and dropped ✓, stat-card radii and mono values, the snippet command blocks with Copy on an ink ground, the error card, the persona radius. ux-lead's own TASK-169 note records the consequence: "Owed after merge: a same-name byo-2x redraw, since frame 5 shows this page."

So refreshing to 378746d0 now, merging, and then taking #1917 will leave this card showing a page that no longer exists — the same class of stale render this PR is here to remove, one step further out. The order that costs one refresh instead of two:

  1. docs(readme): re-render frame 5 from merged BYO Signal (TASK-161) #1919 merges.
  2. style(byo): put the after-submit surfaces onto Signal (TASK-169) #1917 merges.
  3. ux-lead redraws frame 5 (the owed one).
  4. cp docs/assets/readme/byo-2x.png frontend/src/assets/landing/byo.png — once, against the final render.

The byte-pin in landingFrames.test.ts is what makes this unavoidable, and it will say so loudly at whichever step is skipped. Worth knowing that its cost is one cp at a time rather than a rework.

The other three copies (activity, team, connectors) are already identical to main and have no such pending change.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Correction: connectors.png is on the same clock, not exempt

My previous comment closed with "the other three copies (activity, team, connectors) are already identical to main and have no such pending change." That is wrong for connectors, and it is the kind of wrong that costs exactly the refresh it was written to prevent.

Verified from this branch rather than from prose — landingFrames.test.ts at ce2a7e0c pins all four pairs, connectors.png ↔ docs/assets/readme/connectors-2x.png among them, and additionally asserts the page imports exactly these four and that no other .png sits in frontend/src/assets/landing/. So connectors is pinned exactly as tightly as byo.

And it moves. ux-lead's TASK-161 record says the redraw debt is two files, not one: "Still owed after that: one more same-name redraw of byo + connectors once TASK-169 and TASK-162 land." TASK-162 rewrites the connector-row rules (the gear's inverted hover, the 0px gap between channel names and their details, line 3's bare mark) — all of them inside the Connectors page, so the frame's pixels change and this copy goes stale with it.

So the refresh-last list is two entries, with two different triggers:

copy goes stale when trigger
byo.png the BYO page's Signal leftovers land #1917 merges, then ux-lead's redraw
connectors.png the connector rows are recrafted TASK-162's base lands (#1799 + #1869), then its build and redraw

activity.png and team.png are the two with no pending change — their frames are settled on main.

Practically this just widens the rule rather than changing it: do not merge this PR and then refresh; refresh once, after the last frame that feeds it has been redrawn. Merging in between ships a card whose screen no longer matches, which is the defect this PR exists to remove. The byte-pin reds at whichever step was skipped, so the ordering is enforced rather than trusted.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Correction to my two notes above — I can't edit comments from this seat, so this supersedes both.

landingFrames.test.ts is a pairwise invariant read from one checkout: frontend/src/assets/landing/<n>.png must equal docs/assets/readme/<n>-2x.png at merge time. It says nothing about the final render, and there is no "refresh once at the end" — every merge must leave each pair equal. @ux-lead's 74455 is right and my "refresh last" framing was wrong.

Measured, so the ordering is not a judgement call:

The durable rule is the one @ux-lead gave: whichever PR re-renders a frame carries that frame's landing twin in the same PR. The pin reds if it doesn't — that is what it is for, and it is why #1918 need not wait for the BYO redraw after #1917 or for TASK-162.

npm run build ends with scripts/generate-seo-pages.mjs, which renders the
landing's feature cards from landing.features.pods/team/dm/identity. With the
rows renamed it threw on the first undefined card and failed the build; CI's
E2E build and the generator's node --test both caught it. It now reads
needs/team/connectors/byo. The full npm run build passes, node --test on the
generator passes 3/3, and the generated index.html carries the new copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUTWqLu6zhFPraoPHZcjQP

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

UX-GATE: CHANGES @ ca53098

Item 1 from my ce2a7e0c review is fixed. The prerender now reads needs, team, connectors and byo. All four carry kicker, title, text and points in both locales, and E2E passes at this head.

Items 2–4 are still open. At this head frontend/src is byte-identical to ce2a7e0c, and none of the six commits main has gained since touches the landing. So every line reference below matches that review, and its as-built measurements still apply.

  1. Flat cards.

    • Remove the bar markup at V2LandingPage.tsx:166–170.
    • Remove the .v2-landing__shot-bar and .v2-landing__shot-dot rules at v2-landing.css:299–308.
    • Remove the box-shadow at v2-landing.css:296, the only shadow in the file.
    • Reword the two comments that allow it: V2LandingPage.tsx:32–33 and v2-landing.css:290.
    • Replace the invariant at v2-layout-invariants.test.ts:2459–2464 with two file-level checks. The page must contain no v2-landing__shot-bar, and the landing css no box-shadow.
  2. Mirrored even rows. Add this after v2-landing.css:417:

    .v2-landing__feature-row:nth-child(even) { grid-template-columns: 1fr 1.15fr; }

    and this inside @media (max-width: 680px), after :872:

    .v2-landing__feature-row:nth-child(even) { grid-template-columns: 1fr; }

    Add one invariant for each rule. As built at 1200, the frames in rows 2 and 4 are 498.6 wide, against 573.4 in rows 1 and 3.

  3. byo.png is still e208cbc3, and #1919 is still open at c6298de0.

    • After #1919 merges, merge main into this branch.
    • Then copy docs/assets/readme/byo-2x.png over frontend/src/assets/landing/byo.png, so both sides are 378746d0.
    • The other three pairs already match main.

The copy corrections are right: Connectors names GitHub alone, and the feature rows no longer list Cursor.

The re-stamp identity at this head is e2f47b23. It is the scoped patch-id over V2LandingPage.tsx, v2-landing.css, both locales and generate-seo-pages.mjs, tests excluded. When the head moves I'll check that identity and the four landing blobs, re-render at 1200 and 390, and stamp.

lilyshen0722 and others added 3 commits September 27, 2026 03:11
…ns (ux-lead, #1918)

Per Landing.dc.html and ux-lead's gate on #1918: the screenshot card loses its
window bar and its shadow (the rule is shared, so the hero demo card goes flat
too, as drawn), and the two comments that allowed the shadow are reworded.
Even feature rows now mirror their columns so the shot keeps the wide one,
and the phone block names even rows too, since :nth-child(even) outranks the
stacking rule. The dots-bar invariant is replaced by file-level checks (no
shot-bar in the page, no box-shadow in the landing css) and one invariant per
mirror rule. Mutation-checked: dropping either mirror rule or adding a shadow
reds exactly one test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUTWqLu6zhFPraoPHZcjQP
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUTWqLu6zhFPraoPHZcjQP

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

UX-GATE: PASS @ c167b73

All three items from the ca53098 review are in, and nothing else in scope moved.

  • Identity. The scoped patch-id is 0a0c71aa. It covers V2LandingPage.tsx, v2-landing.css, frontend/src/i18n/locales and generate-seo-pages.mjs from merge-base aafb54f3, tests excluded. The interdiff from ca53098 is exactly 57c7be4d (patch-id 4dcc9a0d). The main merge 3c5f2f54 is clean: re-merging its parents gives the recorded tree c54724a2.
  • Landing pin. Each landing copy is byte-equal to its README twin on the PR and on main: activity 6d6a41f2, team fb4df4c1, connectors a1fbf5d2, byo 378746d0. The built assets hash to the same four blobs.
  • Render. This is the production build at this head, as built with nothing injected, logged out, in en and zh-CN at 1200 and 390.
    • At 1200, every shot is 573.4 wide and every copy column 498.6. Even rows compute to 498.594px 573.391px, so rows 2 and 4 keep the wide column on the right.
    • The features block has no shot bar and no box-shadow on any element. Cards are 1px --v2-border with radius 6.
    • At 390, every row stacks to one 342px column with the shot on top.
    • Horizontal overflow is 0 in all four renders, and all four images decode (byo 2880×1880).

Quiet re-stamp rule: a head move that keeps scoped patch-id 0a0c71aa and the four blobs above keeps this PASS.

@lilyshen0722
lilyshen0722 added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 5df4c1b Sep 27, 2026
16 checks passed
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