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..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 @@ -199,9 +200,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 +228,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 +240,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 +271,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 {