From b8cc726655baa97045bc2c5b9e1edd4394eb98ba Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:42:03 -0800 Subject: [PATCH 1/2] fix(first-run): Scout's welcome sits on the composer, its starts on its text A new pod's welcome rendered under the header with the rest of the pane empty, while the d215 stream bottom-anchors. The history edge now takes the slack above a short conversation; an overflowing one scrolls as before, and an empty pod keeps its empty state. On a phone the message grid is 30 + 10, but the side-row slot kept the desktop 48, so the starts sat 8px right of the text they answer. The slot now follows the phone grid, derived in the same invariant. The starts take the Signal key and fill material the lifted surfaces wear, at the frame's 10px radius, with the focus ring restated over the key shadow. The artboard drops the pre-click beta-limits line from the welcome frames: the limits moved to the hosted agent's live status in #2391 on purpose. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpMD6Qh13f7fj6mbGT3 --- docs/design/artboards/d215/index.html | 16 +++++----- .../v2/__tests__/v2-layout-invariants.test.ts | 28 ++++++++++++++++- frontend/src/v2/chat/chat-pane.css | 9 ++++++ frontend/src/v2/chat/chat-states.css | 31 +++++++++++++++---- 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/docs/design/artboards/d215/index.html b/docs/design/artboards/d215/index.html index 1bf56d6a5..3089c457a 100644 --- a/docs/design/artboards/d215/index.html +++ b/docs/design/artboards/d215/index.html @@ -1234,16 +1234,16 @@

Same look. Less said.

First run

/v2/pods/:id · Scout welcome + Guide dialog
helper words welcome – → 16dialog – → 68scroll 390 – → 1.0×
-
Scout welcome · 1440new pod, first message
NWNorthwind1now
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Beta: 1 hosted agent per account, 50 turns a day.

Message Northwind
Needs you 0
Agents 1
Files 0
Who can join
-
Dialog · waiting · 1440step 2 not connected yet
NWNorthwind1now
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Beta: 1 hosted agent per account, 50 turns a day.

Message Northwind
Needs you 0
Agents 1
Files 0
Who can join
+
Scout welcome · 1440new pod, first message
NWNorthwind1now
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Message Northwind
Needs you 0
Agents 1
Files 0
Who can join
+
Dialog · waiting · 1440step 2 not connected yet
NWNorthwind1now
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Message Northwind
Needs you 0
Agents 1
Files 0
Who can join
-
Dialog · connected · 1440steps 1–2 done
NWNorthwind1now
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Beta: 1 hosted agent per account, 50 turns a day.

Message Northwind
Needs you 0
Agents 1
Files 0
Who can join
-
Dialog · hello failed · 1440room did not open
NWNorthwind1now
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Beta: 1 hosted agent per account, 50 turns a day.

Message Northwind
Needs you 0
Agents 1
Files 0
Who can join
+
Dialog · connected · 1440steps 1–2 done
NWNorthwind1now
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Message Northwind
Needs you 0
Agents 1
Files 0
Who can join
+
Dialog · hello failed · 1440room did not open
NWNorthwind1now
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Message Northwind
Needs you 0
Agents 1
Files 0
Who can join
-
Scout welcome · 390
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Beta: 1 hosted agent per account, 50 turns a day.

Message Northwind
Pods
Needs you
Team
Settings
-
Dialog · waiting · 390
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Beta: 1 hosted agent per account, 50 turns a day.

Message Northwind
Pods
Needs you
Team
Settings
+
Scout welcome · 390
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Message Northwind
Pods
Needs you
Team
Settings
+
Dialog · waiting · 390
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Message Northwind
Pods
Needs you
Team
Settings
  • Two doors, one pod. Scout's welcome is the first offer and needs no dialog: three starts under one message. Run an agent here is the one ink key; the other two are bordered. The dialog is the longer route for someone who wants to bring their own tool.
  • The dialog is the live component: v2-first-run__* names and the same three steps. Marks that were unicode (✓, ↗) become 16px icons. The overlay is a flat --scrim tint; the card uses the page's pop edge, not a drop shadow of its own.
  • @@ -1258,8 +1258,8 @@

    Same look. Less said.

-
Dialog · connected · 390
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Beta: 1 hosted agent per account, 50 turns a day.

Message Northwind
Pods
Needs you
Team
Settings
-
Dialog · hello failed · 390
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Beta: 1 hosted agent per account, 50 turns a day.

Message Northwind
Pods
Needs you
Team
Settings
+
Dialog · connected · 390
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Message Northwind
Pods
Needs you
Team
Settings
+
Dialog · hello failed · 390
Northwind
Scoutguide · now

I'm Scout, your first agent here. Pick one below, or just tell me what you're working on.

Message Northwind
Pods
Needs you
Team
Settings
diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 490806b8e..c1263abb5 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -3204,6 +3204,18 @@ describe('v2 layout invariants (CSS rule presence)', () => { // S3b: D215 .app.m .stream is 8 12 4. expect(phone).toContain('padding: 8px 12px 4px'); expect(phone).not.toMatch(/\.v2-chat__(?:delivery-hint|no-agents|starts\b)/); + // On a phone the thread grid is narrower, so the slot follows it in its + // own block, derived the same way. At 48 Scout's starts sat 8px right of + // the text they answer (frame-diff at 390, 2026-11-01). + const phoneGrid = ruleBody(v2, '.v2-thread .v2-msg', { within: mediaBlockContaining(v2, '.v2-thread .v2-msg {') }); + const phoneLead = /grid-template-columns:\s*(\d+)px/.exec(phoneGrid); + const phoneGap = /(?:^|\s)gap:\s*(\d+)px/.exec(phoneGrid); + const phoneIndent = `${Number(phoneLead?.[1]) + Number(phoneGap?.[1])}px`; + expect(phoneIndent).toBe('40px'); + const phoneSlotBlock = mediaBlockContaining(v2, ' .v2-chat__delivery-hint,\n .v2-chat__no-agents,\n .v2-chat__starts {'); + const phoneSlot = phoneSlotBlock.slice(phoneSlotBlock.indexOf('.v2-chat__delivery-hint,')); + expect(phoneSlot.slice(0, phoneSlot.indexOf('}'))).toContain(`margin-left: ${phoneIndent};`); + expect(phoneSlot.slice(0, phoneSlot.indexOf('}'))).toContain(`width: calc(100% - ${phoneIndent});`); }); test("Scout's starts sit in the shared side-row slot and are 44px touch targets on a phone", () => { @@ -3216,7 +3228,12 @@ describe('v2 layout invariants (CSS rule presence)', () => { // equal specificity the later rule wins, and a base rule placed after // the media block would silently render 32 on a phone. expect(ruleBody(v2, '.v2-root button.v2-chat__start')).toContain('min-height: 32px'); - expect(ruleBody(v2, '.v2-root button.v2-chat__start')).toContain('border-radius: var(--v2-radius)'); + // D215 keys: the Signal key and fill material, at the frame's 10px radius. + expect(ruleBody(v2, '.v2-root button.v2-chat__start')).toContain('border-radius: var(--v2-r-10)'); + expect(ruleBody(v2, '.v2-root button.v2-chat__start')).toContain('linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b))'); + expect(ruleBody(v2, '.v2-root button.v2-chat__start.v2-chat__start--primary')).toContain('linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b))'); + // The key shadow outranks the shell's focus ring, so the ring is restated. + expect(ruleBody(v2, '.v2-root button.v2-chat__start:focus-visible')).toContain('box-shadow: var(--v2-focus-ring)'); const phone = mediaBlockContaining(v2, 'button.v2-chat__start'); expect(phone).toContain('min-height: 44px'); const base = v2.indexOf('\n.v2-root button.v2-chat__start {'); @@ -5923,4 +5940,13 @@ describe('chat sheets lifted out of v2.css (S3)', () => { expect(list).toContain('max-height: 132px'); expect(list).toContain('overflow-y: auto'); }); + + test('a conversation shorter than the pane sits on the composer, and an empty pod does not move', () => { + const pane = chatSheets[1]; + // The edge is the scroller's first row, so its auto margin takes the slack + // above every message. A justify-content: flex-end on the scroller would + // also bottom-anchor, but it makes overflowed rows above unreachable. + expect(ruleBody(pane, '.v2-thread__edge:not([data-state="empty"])')).toContain('margin-top: auto'); + expect(ruleBody(pane, '.v2-chat__messages')).not.toContain('justify-content: flex-end'); + }); }); diff --git a/frontend/src/v2/chat/chat-pane.css b/frontend/src/v2/chat/chat-pane.css index 26b0381cb..da3f62444 100644 --- a/frontend/src/v2/chat/chat-pane.css +++ b/frontend/src/v2/chat/chat-pane.css @@ -571,6 +571,15 @@ padding: 6px 0 10px; } +/* D215 .stream bottom-anchors: a conversation shorter than the pane sits on + the composer, not under the header, so a new pod's welcome is read where the + reader types. The edge takes the slack above it; once rows overflow the + auto margin resolves to 0 and scrolling is untouched. An empty pod keeps + its empty state where it is. */ +.v2-thread__edge:not([data-state="empty"]) { + margin-top: auto; +} + .v2-thread__edge-line { padding: 0; border: 0; diff --git a/frontend/src/v2/chat/chat-states.css b/frontend/src/v2/chat/chat-states.css index a85ec3f93..568917873 100644 --- a/frontend/src/v2/chat/chat-states.css +++ b/frontend/src/v2/chat/chat-states.css @@ -199,9 +199,12 @@ .v2-root button.v2-chat__start { min-height: 32px; padding: 6px 12px; - border: 1px solid var(--v2-border); - border-radius: var(--v2-radius); - background: var(--v2-surface); + /* D215 keys: the Signal key material (secondary) and fill (primary) the + lifted surfaces already wear, at the frame's 10px radius. */ + border: 1px solid var(--v2-key-edge); + border-radius: var(--v2-r-10); + background: linear-gradient(180deg, var(--v2-key-a), var(--v2-key-b)); + box-shadow: var(--v2-key-inset); color: var(--v2-text-primary); font-size: 13px; font-weight: 500; @@ -224,9 +227,10 @@ /* TASK-647 item 4: the run-hosted start is the screen's one filled action. Keyed on the action in the component, not on its position. */ .v2-root button.v2-chat__start.v2-chat__start--primary { - background: var(--v2-ink); - border-color: var(--v2-ink); - color: var(--v2-on-ink); + background: linear-gradient(180deg, var(--v2-fill-a), var(--v2-fill-b)); + border-color: var(--v2-fill-edge); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), 0 1px 2px rgba(16, 24, 40, 0.18); + color: var(--v2-on-fill); font-weight: 600; } @@ -235,6 +239,12 @@ border-color: var(--v2-ink-hover); } +/* The key's own shadow outranks the shell's focus ring at equal specificity, + so the ring is restated here. */ +.v2-root button.v2-chat__start:focus-visible { + box-shadow: var(--v2-focus-ring); +} + /* A line that belongs to the start above it: the hosted start's beta limits, or why it could not start. Same slot and size as the delivery hint. */ .v2-chat__start-note { @@ -260,6 +270,15 @@ .v2-root button.v2-chat__start { min-height: 44px; } + /* The phone grid is 30 + 10 (`.v2-thread .v2-msg`, S3b), so the side-row + slot moves to 40 with it. At 48 Scout's starts sat 8px right of the text + they answer. */ + .v2-chat__delivery-hint, + .v2-chat__no-agents, + .v2-chat__starts { + width: calc(100% - 40px); + margin-left: 40px; + } } .v2-chat__error { From e5b99d99a25b35bea841721115e222a3cccdb505 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:43:51 -0800 Subject: [PATCH 2/2] docs(chat): the side-row slot comment names today's grid, 48 and 40 on a phone Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015AKgpMD6Qh13f7fj6mbGT3 --- frontend/src/v2/chat/chat-states.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/v2/chat/chat-states.css b/frontend/src/v2/chat/chat-states.css index 568917873..72c403656 100644 --- a/frontend/src/v2/chat/chat-states.css +++ b/frontend/src/v2/chat/chat-states.css @@ -113,9 +113,10 @@ column of the transcript they sit in. Both are siblings of `.v2-msg` inside `.v2-chat__messages`, so the indent is the avatar track plus the gap of the rule that governs those rows HERE, which is the thread override - `.v2-thread .v2-msg { grid-template-columns: 28px minmax(0, 1fr); gap: 8px }` - — 28 + 8 = 36, NOT the base `.v2-msg` grid (38 + 12 = 50), which that - override replaces (0,3,0 beats 0,1,0). The first cut of this rule derived + `.v2-thread .v2-msg { grid-template-columns: 36px minmax(0, 1fr); gap: 12px }` + — 36 + 12 = 48 (30 + 10 = 40 on a phone, in the 760 block below), NOT the + base `.v2-msg` grid (38 + 12 = 50), which that override replaces (0,3,0 + beats 0,1,0). The first cut of this rule derived from the base grid and was 14px out; ux-lead's #1860 gate measured it at 390, 720 and 1440 — 50px put both rows 14px right of `.v2-msg__content`, 36px put them on it (injection on their fixture, right edge and scrollWidth