From 6f64bc05a33449fcb0c27c2b7ee2f8e909d33e02 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:07:31 -0700 Subject: [PATCH 1/3] feat(v2): 52px phone top bar and one-column body for feature pages (TASK-730 shell) The artboard's 390 frame is a 52px top bar over a single column. Feature pages share V2PageHead and the V2FeaturePage body, so the bar, the 12px body and settings' title bar are set once, last in the sheet, with a zh letter-spacing reset and a presence test. Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: wisp Agent-Model: claude/claude-sonnet-5-5/default --- .../src/v2/__tests__/v2-phone-shell.test.ts | 28 +++++++++++++++ frontend/src/v2/v2.css | 36 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 frontend/src/v2/__tests__/v2-phone-shell.test.ts diff --git a/frontend/src/v2/__tests__/v2-phone-shell.test.ts b/frontend/src/v2/__tests__/v2-phone-shell.test.ts new file mode 100644 index 000000000..7e48a6285 --- /dev/null +++ b/frontend/src/v2/__tests__/v2-phone-shell.test.ts @@ -0,0 +1,28 @@ +import fs from 'fs'; +import path from 'path'; + +// TASK-730 (D223): the artboard's 390 frame is a 52px top bar over a single +// column. jsdom has no layout engine, so this pins the presence of the rules +// that carry it, the same way v2-layout-invariants.test.ts does. + +const css = fs.readFileSync(path.join(__dirname, '../v2.css'), 'utf8'); +// The block is the tail of the sheet, from its marker comment to the end. +const phone = css.slice(css.indexOf('D223 (TASK-730 shell)')); + +describe('D223 phone shell', () => { + test('feature pages get a 52px top bar with the display title', () => { + expect(phone).toMatch(/\.v2-feature__header\s*\{[^}]*height:\s*52px/); + expect(phone).toMatch(/\.v2-feature__header\s+\.v2-feature__title\s*\{[^}]*font:\s*800 21px\/1 var\(--v2-font-display\)/); + }); + + test('the page body is one tight column and settings leads with the same bar', () => { + expect(phone).toMatch(/\.v2-feature__body\s*\{[^}]*padding:\s*12px 12px 16px/); + expect(phone).toMatch(/\.v2-settings__title\s*\{[^}]*height:\s*52px/); + }); + + test('the block comes last in the sheet, so it outranks the earlier phone rules', () => { + const at = css.indexOf('D223 (TASK-730 shell)'); + expect(at).toBeGreaterThan(css.indexOf('.v2-settings__title {')); + expect(css.indexOf('.v2-feature__body {')).toBeLessThan(at); + }); +}); diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 0cb32c445..834e13eb1 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -10891,6 +10891,7 @@ details[open] > .v2-settings__summary > .v2-settings__icon:last-child { transfor .v2-root:lang(zh) .v2-inspector__detail-name, .v2-root:lang(zh) .v2-login__title, .v2-root:lang(zh) .v2-feature__title, +.v2-root:lang(zh) .v2-feature__header .v2-feature__title, .v2-root:lang(zh) .v2-settings__title, .v2-root:lang(zh) .v2-settings__label, .v2-root:lang(zh) .v2-settings__account-name, @@ -14060,3 +14061,38 @@ details[open] > .v2-settings__summary > .v2-settings__icon:last-child { transfor .v2-root button.v2-board__card-main:focus-visible, .v2-root button.v2-board__card-move:focus-visible { outline: 2px solid var(--v2-accent); outline-offset: 2px; box-shadow: none; } + +/* D223 (TASK-730 shell): the artboard's 390 frame is a 52px top bar over one + column. Feature pages (connectors, team, settings) share V2PageHead and the + V2FeaturePage body, so the bar and the tighter body live here once instead + of in each page. The tab bar's height is already reserved on .v2-feature. */ +@media (max-width: 760px) { + .v2-feature__header { + display: flex; + align-items: center; + height: 52px; + padding: 0 10px 0 12px; + gap: 8px; + } + .v2-feature__header .v2-page-head__identity { flex-wrap: nowrap; align-items: center; gap: 8px; } + .v2-feature__header .v2-feature__title { + font: 800 21px/1 var(--v2-font-display); + letter-spacing: -0.03em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .v2-feature__header .v2-page-head__meta { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .v2-feature__body { padding: 12px 12px 16px; } + .v2-settings__layout { padding-top: 0; } + .v2-settings__title { + display: flex; + align-items: center; + height: 52px; + margin: 0 -12px 12px; + padding: 0 12px; + border-bottom: 1px solid var(--v2-border-soft); + font: 800 21px/1 var(--v2-font-display); + letter-spacing: -0.03em; + } +} From 19f3a4cf0da07a2d45f633dff2e1c19a0765f9a8 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:09:17 -0700 Subject: [PATCH 2/3] fix(v2): phone top bar grows for an open hint; settings bar flush to the top (TASK-730 shell) Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: wisp Agent-Model: claude/claude-sonnet-5-5/default --- frontend/src/v2/__tests__/v2-phone-shell.test.ts | 4 ++-- frontend/src/v2/v2.css | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-phone-shell.test.ts b/frontend/src/v2/__tests__/v2-phone-shell.test.ts index 7e48a6285..6a5537b82 100644 --- a/frontend/src/v2/__tests__/v2-phone-shell.test.ts +++ b/frontend/src/v2/__tests__/v2-phone-shell.test.ts @@ -11,13 +11,13 @@ const phone = css.slice(css.indexOf('D223 (TASK-730 shell)')); describe('D223 phone shell', () => { test('feature pages get a 52px top bar with the display title', () => { - expect(phone).toMatch(/\.v2-feature__header\s*\{[^}]*height:\s*52px/); + expect(phone).toMatch(/\.v2-feature__header\s*\{[^}]*min-height:\s*52px/); expect(phone).toMatch(/\.v2-feature__header\s+\.v2-feature__title\s*\{[^}]*font:\s*800 21px\/1 var\(--v2-font-display\)/); }); test('the page body is one tight column and settings leads with the same bar', () => { expect(phone).toMatch(/\.v2-feature__body\s*\{[^}]*padding:\s*12px 12px 16px/); - expect(phone).toMatch(/\.v2-settings__title\s*\{[^}]*height:\s*52px/); + expect(phone).toMatch(/\.v2-settings__title\s*\{[^}]*min-height:\s*52px/); }); test('the block comes last in the sheet, so it outranks the earlier phone rules', () => { diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 834e13eb1..0e0c63db6 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -14070,11 +14070,11 @@ details[open] > .v2-settings__summary > .v2-settings__icon:last-child { transfor .v2-feature__header { display: flex; align-items: center; - height: 52px; + min-height: 52px; padding: 0 10px 0 12px; gap: 8px; } - .v2-feature__header .v2-page-head__identity { flex-wrap: nowrap; align-items: center; gap: 8px; } + .v2-feature__header .v2-page-head__identity { align-items: center; gap: 8px; } .v2-feature__header .v2-feature__title { font: 800 21px/1 var(--v2-font-display); letter-spacing: -0.03em; @@ -14088,8 +14088,8 @@ details[open] > .v2-settings__summary > .v2-settings__icon:last-child { transfor .v2-settings__title { display: flex; align-items: center; - height: 52px; - margin: 0 -12px 12px; + min-height: 52px; + margin: -12px -12px 12px; padding: 0 12px; border-bottom: 1px solid var(--v2-border-soft); font: 800 21px/1 var(--v2-font-display); From e2ee0654dbd0ae10c4cd6ce5760ea0d8f83cbbde Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:20:30 -0700 Subject: [PATCH 3/3] test(v2): pin the phone title's one-line clip and the 760px scope (TASK-730 shell) Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: wisp Agent-Model: claude/claude-sonnet-5-5/default --- frontend/src/v2/__tests__/v2-phone-shell.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src/v2/__tests__/v2-phone-shell.test.ts b/frontend/src/v2/__tests__/v2-phone-shell.test.ts index 6a5537b82..3d75a0287 100644 --- a/frontend/src/v2/__tests__/v2-phone-shell.test.ts +++ b/frontend/src/v2/__tests__/v2-phone-shell.test.ts @@ -15,6 +15,18 @@ describe('D223 phone shell', () => { expect(phone).toMatch(/\.v2-feature__header\s+\.v2-feature__title\s*\{[^}]*font:\s*800 21px\/1 var\(--v2-font-display\)/); }); + test('the title clips to one line, and the whole block stays inside the 760px query', () => { + const title = phone.match(/\.v2-feature__header\s+\.v2-feature__title\s*\{([^}]*)\}/); + expect(title).not.toBeNull(); + expect(title![1]).toContain('white-space: nowrap'); + expect(title![1]).toContain('overflow: hidden'); + expect(title![1]).toContain('text-overflow: ellipsis'); + const mediaAt = phone.indexOf('@media (max-width: 760px)'); + expect(mediaAt).toBeGreaterThan(-1); + expect(phone.indexOf('.v2-feature__header {')).toBeGreaterThan(mediaAt); + expect(phone.indexOf('.v2-feature__header .v2-feature__title')).toBeGreaterThan(mediaAt); + }); + test('the page body is one tight column and settings leads with the same bar', () => { expect(phone).toMatch(/\.v2-feature__body\s*\{[^}]*padding:\s*12px 12px 16px/); expect(phone).toMatch(/\.v2-settings__title\s*\{[^}]*min-height:\s*52px/);