Skip to content

feat(landing): the hero is the product, not a video (TASK-147) - #1855

Merged
lilyshen0722 merged 4 commits into
mainfrom
feat/task-147-landing-hero-demo
Sep 25, 2026
Merged

lilyshen0722 merged 4 commits into
mainfrom
feat/task-147-landing-hero-demo

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

The landing hero stops being a video and becomes the product: an interactive fake of the workspace, built from the real v2 components with fixture data. Spec: docs/design/landing-demo/Demo.dc.html (TASK-147 behind #1841, ux-lead PASS at 7de01bce).

What is in it

  • Four pods (Launch pinned; Support, Website, Growth) with their own threads, agents, board rows and channel state.
  • A decision card you can rule on. Picking an option posts Sam's ruling, shows a typing row, then the agent's reply — and the pod's "needs you" clears in all three places it was claimed: the inspector, the board row and the pod count.
  • Suggested prompts that run a scripted chain (two agents, timed), and free text answered by the pod's lead agent.
  • Each pod keeps its own history and its own decision when you switch between them.
  • The demo says what it is: sample workspace · replies are scripted lives in its own chrome, and the caption says "This is the product, not a video."

Built from the components, not the board's markup (the spec's rule)

The decision card renders the product's own .v2-decision-card* classes, transcript rows carry .v2-msg, and every face comes from characterAvatarFor(seed, kind) — the board's /_blob/… URLs do not resolve outside the canvas. What the demo borrows is appearance; what it cannot borrow is behaviour, because every real component on this surface is wired to a pod id, a socket and an API that a marketing page has none of. So the shell is demo-scoped (.v2-demo*) and the pieces that carry identity are the product's.

Where the board and the product disagree, the component wins, per ux-lead's carry note: the board paints the active pod row cobalt-filled, while the live rail uses tint + a 3px accent mark + accent-free title. The demo follows the rail, and a guard pins it.

Not in this PR (deliberately, for the gate to rule on rather than for me to assume)

  1. Landing.dc.html also restyles the front door — cobalt hero band, feature rows rebuilt from the four frames, new row copy. This PR replaces the hero's contents; the band stays as it is and the feature rows are untouched. My read is that the restyle is its own row, but that is ux-lead's call and I have not treated it as settled.
  2. The demo's own copy is English-only, like the frames. The page's copy stays translated (both locales updated for the caption and the demo's accessible name).

Verified

  • 11 mutations, each red on exactly the test that defends it — including the two CSS ones that matter here: removing the phone layout's height: auto (the stage would clip the stacked columns, because .v2-demo is overflow: hidden) and removing the transcript's phone min-height (the thread renders as a blank strip).
  • Full frontend suite green locally: 109 suites / 905 tests.
  • eslint 0 errors; tsc --noEmit clean.

For the gates

  • ux-lead (design) — the phone layout is the part I want ruled on: at ≤760 the rail and the DM rows drop, pods become a wrapping strip, the thread gets a 300px transcript floor, and the inspector becomes a two-column block with "needs you" full width above it. That is my proposal, not the board's (no 390 board exists). I also cannot take 1200/390 screenshots from this seat, so the evidence pairs are yours.
  • sprint-review (code) — the state machine is the thing to attack: decided/extra/used/typing per pod, the timer cleanup on unmount, and the fixture's two functions that branch on the ruling.
  • Test-file note: this branch and feat(v2): a pod with no agent says so, and offers the one step #1846 both add a phone-block helper to v2-layout-invariants.test.ts (different names, same idea). Whichever lands second should fold them into one.

Also in this diff: the hero's 3.9MB demo-2x.mp4 and its poster are deleted — nothing references them once the hero is the demo, and the poster/metadata were being fetched on every landing load.

Not done, and owed: the README frames at 2x. They are exports of these components, not of the boards, and this seat has no browser — they need a capture run.

@samxu01
samxu01 force-pushed the feat/task-147-landing-hero-demo branch from a6ae821 to 308e923 Compare September 23, 2026 23:33
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Rebased onto main fcb751bf (#1842, TASK-142) — head is now 308e9235, behind 0.

The delta over this branch's own files is 0 lines: #1842 is backend-only (authController + its test) and touches nothing here, so the earlier local run stands as-is and the gates have nothing to re-measure. CI re-runs on the new head regardless.

@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.

GATE: PASS at 308e9235 — one gap, covering two of the three areas you named. Non-blocking: the code is correct, the suite cannot tell.

Measured at a6ae821d; the head moved to 308e9235 while I was running, so I checked rather than re-ran: that move is a rebase picking up #1842, and all 12 of this PR's files are byte-identical between the two heads (DemoWorkspace.tsx included, same line numbers). The results below carry exactly.

BASE 124/124 (DemoWorkspace, v2-layout-invariants, V2LandingPage.stats), RESTORED 124/124, tree clean after every mutation. Anchor count 1 each.

Timer cleanup is pinned. Removing useEffect(() => () => { timers.current.forEach(clearTimeout); … }) → 1 red. Good: that one is invisible in a browser and surfaces only as a warning inside someone else's suite.


Gap: nothing rules a second pod, so per-pod state is only half pinned

Two mutations, one root cause — both leave 124/124 green:

mutation on DemoWorkspace.tsx result
:91 every reply appends to START_POD instead of target green
:118 setDecided(() => ({ [target]: option.id })) — a ruling drops every other pod's green

keeps each pod's own history and decision when you switch between them looks like it covers this, and it is a good test, but it rules only one pod. Launch is START_POD, so misrouting every message to the start pod changes nothing observable; and with a single ruling in play, a decided map that forgets the others is indistinguishable from one that keeps them.

Both are reachable — the fixture gives launch and support an ask each. One probe catches both (passes at head, fails under either mutation):

fireEvent.click(screen.getByRole('button', { name: 'Email them now' }));   // rule Launch
advance(1200);
fireEvent.click(podButton('Support'));
fireEvent.click(screen.getByRole('button', { name: 'Leave them out' }));   // rule Support too
advance(1200);
expect(screen.getByText('Exported without the private threads and sent Dana the link in Slack.')).toBeInTheDocument();
fireEvent.click(podButton('Launch'));
expect(screen.queryByRole('button', { name: 'Email them now' })).not.toBeInTheDocument();
expect(screen.queryByText('Exported without the private threads and sent Dana the link in Slack.')).not.toBeInTheDocument();

Under the decided mutation, Launch's card comes back: a visitor rules two pods and watches the first un-rule itself. Under the extra mutation, Support's reply never arrives in Support, because it was appended to Launch.

Your third named area — the fixture's two branches on the ruling — is covered: each option's rule/reply pair is asserted. The blind spot is the pod dimension, not the option dimension.

@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.

Gap closed. PASS stands at 62ceda24.

I re-gated rather than carried, because the new test is the object in question and a carry cannot speak to it.

  • Test-only delta confirmed: +32 lines in DemoWorkspace.test.tsx, and git diff --name-only 308e9235 62ceda24 outside __tests__ is empty. So the production object I gated is untouched.
  • BASE 125/125 (124 + the new test), RESTORED 125/125, tree clean.
  • :91 replies → START_POD: 1 red, the new test alone.
  • :118 setDecided collapsed to one slot: 1 red, the new test alone.

Both now fail on the pod dimension specifically, which is what was missing. Nothing further from me.

@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: FAIL @ 62ceda2 — two CSS-only fixes at 390, everything else passes.

1200 (fixture built at a6ae821, scope patch-id excl. __tests__ c912e4f8 = 62ceda2): active pod is the live component (tint rgb(233,237,251) + 3px cobalt mark + 600, matches .v2-pods__item--active); card 2px cobalt ring, primary cobalt-filled, Wait/Other white bordered, options 34; Send 32 ink radius 4; chips 28 mono 12; labels mono 11; min font 11; no green; no shadow beyond the pre-existing landing frame. Interaction: rule → Sam line → typing row (role=status) → Wren reply at ~1.2s, card gone, needs-you clears to the empty line, board TASK-32 flips needs-you → wren · wip, 2 agents working; chip send, free text (input clears), and per-pod state (Support keeps its card while Launch is ruled) all hold. Tab reaches pods and options with the 3px cobalt focus ring. 1440: stage stays 1118 (cap), no stretch.

390: card options 44 full width (product CSS), no horizontal overflow (scrollWidth 390), log 660 ≥ the 300 floor, sample line visible under the strip, needs-you full width first, thread head stacks clean. Two fixes:

  1. Active pod chip loses its tint at ≤760. demo-workspace.css:572 .v2-root button.v2-demo__pod { background: var(--v2-surface) } has the same specificity as line 157 and comes later, so the active chip renders white with a transparent border — only the mark and the weight carry selection. Add background: var(--v2-accent-soft); to the --active rule at 579. Measured after: rgb(233,237,251).
  2. Two-column inspector truncates every board row at 390 (156px columns; TASK-32 Note to users needs 142 + state 56 + padding 24 ≈ 230 per column) and wraps the channel line to two. Two columns are clean at 760 and 600, one row already clips at 480. Go single column at ≤640 (v2.css's phone tier): @media (max-width: 640px) { .v2-demo__inspector { grid-template-columns: minmax(0, 1fr); } }. Measured after at 390: no truncation, channel 20 tall, inspector 452 (+84), frame 1508.

Re-gate on the next head = 390 pods strip + inspector only; patch-id over frontend/src/v2/landing + locales excluding __tests__.

Non-blocking taste notes, not part of the verdict: a waiting agent row carries two cobalt marks (dot + waiting on you text) where the Your Team ruling was one per row; ● N agents working is cobalt meta that is not a link (the product's cobalt header text is the board link); composer placeholder clips at 390 (… @ an age) — drop the coaching tail under 760 if it comes back for polish.

samxu01 pushed a commit that referenced this pull request Sep 23, 2026
…o one column at 390 (#1855)

ux-lead's 390 gate at 62ceda2, two failures, both order/specificity:

- The phone row rule (.v2-root button.v2-demo__pod, background surface) has the
  same specificity as the desktop --active rule and sits later, so it won: the
  open pod was painted exactly like its neighbours at the width where the strip
  is the only navigation. Restated the tint in the phone --active rule, and
  paired the hover rule, which outranks the active one (the product's hover is
  weaker than its active state, so this follows the component).
- The two-column inspector is a 641-760 affordance: at 390 each column is
  ~156px against a board row that needs ~230, so every title truncated to an
  ellipsis. Moved the grid into a 641-760 query and gave <=640 one column with
  the needs-you panel hoisted first.

Guards for both in v2-layout-invariants.test.ts, with a mediaAt() helper that
matches an at-rule by its full text (the 760 query is a prefix of the 641-760
one, so a prefix match reads whichever the file puts first).
samxu01 pushed a commit that referenced this pull request Sep 23, 2026
…o one column at 390 (#1855)

ux-lead's 390 gate at 62ceda2, two failures, both order/specificity:

- The phone row rule (.v2-root button.v2-demo__pod, background surface) has the
  same specificity as the desktop --active rule and sits later, so it won: the
  open pod was painted exactly like its neighbours at the width where the strip
  is the only navigation. Restated the tint in the phone --active rule, and
  paired the hover rule, which outranks the active one (the product's hover is
  weaker than its active state, so this follows the component).
- The two-column inspector is a 641-760 affordance: at 390 each column is
  ~156px against a board row that needs ~230, so every title truncated to an
  ellipsis. Moved the grid into a 641-760 query and gave <=640 one column with
  the needs-you panel hoisted first.

Guards for both in v2-layout-invariants.test.ts, with a mediaAt() helper that
matches an at-rule by its full text (the 760 query is a prefix of the 641-760
one, so a prefix match reads whichever the file puts first).
@samxu01
samxu01 force-pushed the feat/task-147-landing-hero-demo branch from f76fe6d to 46746d3 Compare September 23, 2026 23:45
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

ux-lead's 390 gate at 62ceda24 — both failures fixed at 46746d3a, CSS only.

Strip. The phone row rule (.v2-root button.v2-demo__pod, background: var(--v2-surface)) has the same specificity as the desktop --active rule and sits later in the file, so it won and the open pod was painted exactly like its neighbours — at the one width where the strip is the only navigation. The tint is now restated in the phone --active rule. Also paired .v2-demo__pod--active:hover with the same tint: hover outranks the active rule, and the product's own hover (.v2-pods__item:hover, 0,2,0) is weaker than its active state (.v2-root button.v2-pods__item--active, 0,2,1), so the pairing follows the component — flagged to ux-lead as a third change they may reject.

Inspector. Two columns are a 641–760 affordance; at 390 each column is ~156px against a board row that needs ~230, so every title truncated to an ellipsis with nothing left to read. The grid moved into (max-width: 760px) and (min-width: 641px); <=640 falls back to the base single column with the needs-you panel hoisted first.

Evidence. Four mutations, each red on exactly one test and nothing else: tint removed from the phone --active rule · hover pairing removed · grid put back into the plain 760 block · needs not hoisted at 640. Guards live in v2-layout-invariants.test.ts behind a new mediaAt() helper that matches an at-rule by its full text (the 760 query is a prefix of the 641–760 one, so a prefix match silently reads whichever the file puts first). 127 tests across the demo, invariants and landing-stats files; tsc clean; eslint 0 errors (6 pre-existing warnings, none in the added lines).

Limit. Those guards pin the CSS shape, not the rendering — jsdom has no layout engine. Whether the rows actually fit at 390 is ux-lead's measurement, and the head is behind 0 on 2d56a639.

@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 @ 46746d3 — both 390 fixes land; hover pairing accepted. Scope patch-id (landing + locales, excl tests) c912e4f8 → 30f16b3a; the whole delta is demo-workspace.css, so the 62ceda2 pass carries for everything else. Measured on the built head at 390/480/640/641/760/1200: active chip rgb(233,237,251) + 3px cobalt mark + 600 at every width, and it stays tint under hover (control: other chips hover rgb(228,231,236)); inspector flex single column ≤640 with needs-you first, grid 2×1fr at 641–760 (281.5/341px cols, needs first), 0 truncated leaf rows at any width, scrollWidth = clientWidth throughout; 390 inspector 452 tall as ruled. The hover pairing reads as the component's own precedence (active outranks hover on .v2-pods__item), not a new state — accepted. Non-blocking notes from the 62ceda2 stamp stand (two cobalt marks on the waiting row, agents-working meta not a link, composer placeholder clip at 390). README 2x frames and the Landing.dc.html front-door restyle are separate rows.

@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.

GATE: PASS at 46746d3a — measured. One gap in the new guard, non-blocking.

  • PR-only delta is the 2 files you name. avatarCharacter.test.ts also appears in a raw two-head diff; it is main's #1843 arriving, not yours.
  • CSS parses clean: braces balanced 109/109, no line opening two blocks. (A stray brace folds the rest of a sheet into one rule and every string assertion still passes, so this is worth its own check.)
  • BASE 127/127, RESTORED 127/127, tree clean. mediaAt's brace-depth scan is right, and passing '@media (max-width: 760px) {' with the trailing brace is what actually separates it from the 641–760 query.
mutation on demo-workspace.css result
remove the --active:hover pairing 1 red
phone --active loses its tint (the defect ux-lead caught) 1 red
two-column grid reaches 390 again (min-width: 641px → 0px) 1 red
needs-you not hoisted (order: -1 → 0) 1 red

Gap: the guard cannot see order, and order is the whole defect

The two rules have equal specificity — .v2-root button.v2-demo__pod and .v2-root button.v2-demo__pod--active are both (0,2,1) — so the tint survives only because --active sits later in the block. Your own comment says exactly this.

The assertions are toMatch over the block's text, so they pass on the set of rules present, in any order. I moved the --active rule above the row rule inside the phone block, changing no text at all: 127/127 still green.

That mutation is not cosmetic. Real browser, viewport 390, both sheets served over http:

plain row active row identical?
shipped rgb(255,255,255) rgb(232,240,254) no
rules reordered rgb(255,255,255) rgb(255,255,255) yes

Both sheets parse to 86 rules, so this is cascade, not a parse break. The open pod paints exactly like its neighbours — the defect the 390 gate just caught, shipping again with a green suite.

One line closes it, since you already have the block text:

expect(phone.indexOf('.v2-root button.v2-demo__pod--active {'))
  .toBeGreaterThan(phone.indexOf('.v2-root button.v2-demo__pod {'));

Not blocking: the file is correct today, and ux-lead's 390 gate is the live check. But a presence assertion certifies content, never cascade, and this is the one defect class this PR already shipped once.

samxu01 pushed a commit that referenced this pull request Sep 23, 2026
…o one column at 390 (#1855)

ux-lead's 390 gate at 62ceda2, two failures, both order/specificity:

- The phone row rule (.v2-root button.v2-demo__pod, background surface) has the
  same specificity as the desktop --active rule and sits later, so it won: the
  open pod was painted exactly like its neighbours at the width where the strip
  is the only navigation. Restated the tint in the phone --active rule, and
  paired the hover rule, which outranks the active one (the product's hover is
  weaker than its active state, so this follows the component).
- The two-column inspector is a 641-760 affordance: at 390 each column is
  ~156px against a board row that needs ~230, so every title truncated to an
  ellipsis. Moved the grid into a 641-760 query and gave <=640 one column with
  the needs-you panel hoisted first.

Guards for both in v2-layout-invariants.test.ts, with a mediaAt() helper that
matches an at-rule by its full text (the 760 query is a prefix of the 641-760
one, so a prefix match reads whichever the file puts first).
samxu01 pushed a commit that referenced this pull request Sep 23, 2026
…nce (#1855)

sprint-review moved the phone --active rule above the row rule with every
declaration unchanged: still 127/127, and in a browser at 390 the active pod
repainted white — the same defect ux-lead's gate caught, shipping again on a
green suite. Both rules are 0,2,1, so order is the whole mechanism and textual
presence cannot see it. The tint test now asserts the --active index is greater
than the row rule's within the extracted phone block; the mutation reds that
assertion alone and nothing else in the suite.
@samxu01
samxu01 force-pushed the feat/task-147-landing-hero-demo branch from 46746d3 to b2060b1 Compare September 23, 2026 23:56
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

sprint-review's order gap closed at b2060b12 — test-only, 11 lines.

Their probe: in demo-workspace.css the phone block's row rule and its --active rule are both specificity (0,2,1), so the tint survives only because --active sits later. They moved it above the row rule changing no text and the suite stayed 127/127, while a browser run at 390 repainted the active pod white — ux-lead's defect shipping again on a green suite, because a presence guard cannot see order.

The tint test now asserts order inside the extracted phone block (--active index greater than the row rule's), with their browser measurement cited as the reason. Re-running their mutation verbatim reds that assertion alone: toBeGreaterThan, 757 > 873 false, 115 others pass. 127/127 with the guard in; tsc clean; eslint 0 errors.

Also their correction: what defeats the prefix collision is the trailing { in the selector ('@media (max-width: 760px) {'), not full-text matching by itself — a bare '@media (max-width: 760px)' matches the 641–760 query too.

Head is behind 0 on 4a0661d4 (#1844). Delta over the gated 46746d3a is those 11 test lines and nothing in production, so both gate stamps carry by object; the head moved, so the press waits for the CI re-run on it.

@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.

Order gap closed. PASS stands at b2060b12.

Re-ran rather than carried, because the new assertion is the object in question.

  • BASE 127/127, RESTORED 127/127, tree clean.
  • The order mutation now reds exactly 1 — the tint test, on the new pair. I re-applied it the same way: lift the phone --active rule above the row rule, every declaration unchanged. Proof it landed and is a pure move: 1 file changed, 4 insertions(+), 4 deletions(-), with both selector strings still present in the file afterwards. So the guard is reading position, not presence.
  • PR-only delta is the 11 test lines. V2Layout.tsx shows up in a raw diff against the head I gated; it is #1844 arriving through the rebase, absent from git diff --name-only <merge-base> <head>. No production change.

Your note about the first attempt silently not applying — 4-space anchor against a 2-space file, suite green on an unmutated file — is the right thing to have caught and said out loud. A mutation that never applied is indistinguishable from one the suite ignores, and it always reads as the reassuring result. The --shortstat proof is the cheap defence, which is why I print it above too.

@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 @ b2060b1 — re-pin only. Delta since 46746d3 is b2060b1 (test-only: pins rule order in the phone strip, v2-layout-invariants.test.ts) plus main 4a0661d (#1844) — no gated file touched. Scoped patch-id over frontend/src/v2/landing (excluding tests) + i18n/locales is 30f16b3a, identical to the PASS at 46746d3. Measurements from that gate stand.

samxu01 pushed a commit that referenced this pull request Sep 24, 2026
…o one column at 390 (#1855)

ux-lead's 390 gate at 62ceda2, two failures, both order/specificity:

- The phone row rule (.v2-root button.v2-demo__pod, background surface) has the
  same specificity as the desktop --active rule and sits later, so it won: the
  open pod was painted exactly like its neighbours at the width where the strip
  is the only navigation. Restated the tint in the phone --active rule, and
  paired the hover rule, which outranks the active one (the product's hover is
  weaker than its active state, so this follows the component).
- The two-column inspector is a 641-760 affordance: at 390 each column is
  ~156px against a board row that needs ~230, so every title truncated to an
  ellipsis. Moved the grid into a 641-760 query and gave <=640 one column with
  the needs-you panel hoisted first.

Guards for both in v2-layout-invariants.test.ts, with a mediaAt() helper that
matches an at-rule by its full text (the 760 query is a prefix of the 641-760
one, so a prefix match reads whichever the file puts first).
samxu01 pushed a commit that referenced this pull request Sep 24, 2026
…nce (#1855)

sprint-review moved the phone --active rule above the row rule with every
declaration unchanged: still 127/127, and in a browser at 390 the active pod
repainted white — the same defect ux-lead's gate caught, shipping again on a
green suite. Both rules are 0,2,1, so order is the whole mechanism and textual
presence cannot see it. The tint test now asserts the --active index is greater
than the row rule's within the extracted phone block; the mutation reds that
assertion alone and nothing else in the suite.
@samxu01
samxu01 force-pushed the feat/task-147-landing-hero-demo branch from b2060b1 to e2721f4 Compare September 24, 2026 12:46
Replaces the hero video with an interactive fake of the workspace: four
pods, a decision card you can rule on, suggested prompts with timed
replies, free text, and an inspector that follows the state. No backend —
every reply is scripted, and the demo says so in its own chrome.

Built from the real components and fixture data, per the spec
(docs/design/landing-demo/Demo.dc.html, #1841): the decision card renders
the product's own .v2-decision-card classes and faces come from
characterAvatarFor, so the hero cannot drift into a product that does not
exist. The shell is demo-scoped because every real in-app component on
this surface is wired to a pod id, a socket and an API.

Also removes the 3.9MB hero video and its poster, which nothing
references now, and updates the demo caption and its accessible name in
both locales.
…ASK-147)

Sprint-review's code gate found per-pod state only half pinned: nothing in the
suite ruled a second pod, so routing a ruling's reply to the start pod stayed
green, and so did collapsing the ruled state to a single slot. One probe rules
Launch, rules Support, then visits each again — it fails under either.
…o one column at 390 (#1855)

ux-lead's 390 gate at 62ceda2, two failures, both order/specificity:

- The phone row rule (.v2-root button.v2-demo__pod, background surface) has the
  same specificity as the desktop --active rule and sits later, so it won: the
  open pod was painted exactly like its neighbours at the width where the strip
  is the only navigation. Restated the tint in the phone --active rule, and
  paired the hover rule, which outranks the active one (the product's hover is
  weaker than its active state, so this follows the component).
- The two-column inspector is a 641-760 affordance: at 390 each column is
  ~156px against a board row that needs ~230, so every title truncated to an
  ellipsis. Moved the grid into a 641-760 query and gave <=640 one column with
  the needs-you panel hoisted first.

Guards for both in v2-layout-invariants.test.ts, with a mediaAt() helper that
matches an at-rule by its full text (the 760 query is a prefix of the 641-760
one, so a prefix match reads whichever the file puts first).
…nce (#1855)

sprint-review moved the phone --active rule above the row rule with every
declaration unchanged: still 127/127, and in a browser at 390 the active pod
repainted white — the same defect ux-lead's gate caught, shipping again on a
green suite. Both rules are 0,2,1, so order is the whole mechanism and textual
presence cannot see it. The tint test now asserts the --active index is greater
than the row rule's within the extracted phone block; the mutation reds that
assertion alone and nothing else in the suite.
@samxu01
samxu01 force-pushed the feat/task-147-landing-hero-demo branch from e2721f4 to 4eecec6 Compare September 24, 2026 23:26

@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 @ 4eecec6 — rebase onto main 10a37a1; scoped patch-id 30f16b3a (landing + locales, tests excluded) unchanged from b2060b1, whole-PR patch-id unchanged; main's intervening changes touch no landing file; locale JSON parses. Stamp carries.

@lilyshen0722
lilyshen0722 added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 8ded5f2 Sep 25, 2026
15 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