From 5baa86b97f7d0c207bc0dfab1ffabf5c3bba407b Mon Sep 17 00:00:00 2001 From: kingwl Date: Mon, 3 Aug 2026 17:22:51 +0800 Subject: [PATCH] fix(web): align subagent catalog spacing --- .../branchless-layout.expected.md | 5 ++ apps/web/tests/subagent-conversation.e2e.ts | 75 +++++++++++++++++++ .../client/SubagentCatalogAction.module.css | 70 +++++++++++------ 3 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 apps/web/tests/snapshots/subagent-conversation/branchless-layout.expected.md diff --git a/apps/web/tests/snapshots/subagent-conversation/branchless-layout.expected.md b/apps/web/tests/snapshots/subagent-conversation/branchless-layout.expected.md new file mode 100644 index 0000000000..962e68b808 --- /dev/null +++ b/apps/web/tests/snapshots/subagent-conversation/branchless-layout.expected.md @@ -0,0 +1,5 @@ +menu: 360px; padding 4px; gap 4px; radius 12px +row: 352×54px; padding 7px 8px 7px 4px; radius 8px +content: 340×40px; left inset 8px; right inset 12px +label: 14px/20px/400 +summary: 12px/18px/400 diff --git a/apps/web/tests/subagent-conversation.e2e.ts b/apps/web/tests/subagent-conversation.e2e.ts index 6f339cf9a9..94b67a29c1 100644 --- a/apps/web/tests/subagent-conversation.e2e.ts +++ b/apps/web/tests/subagent-conversation.e2e.ts @@ -21,6 +21,7 @@ const BASE_FIXTURE = fileURLToPath(new URL('./snapshots/live-interactions/sessio const AVAILABLE_CHILD_EXPECTED = fileURLToPath(new URL('./snapshots/subagent-conversation/ui.expected.md', import.meta.url)) const TREE_EXPECTED = fileURLToPath(new URL('./snapshots/subagent-conversation/tree.expected.md', import.meta.url)) const BRANCHLESS_EXPECTED = fileURLToPath(new URL('./snapshots/subagent-conversation/branchless.expected.md', import.meta.url)) +const BRANCHLESS_LAYOUT_EXPECTED = fileURLToPath(new URL('./snapshots/subagent-conversation/branchless-layout.expected.md', import.meta.url)) const STALE_CATALOG_EXPECTED = fileURLToPath(new URL('./snapshots/subagent-conversation/stale-catalog.expected.md', import.meta.url)) const SIDEBAR_EXPECTED = fileURLToPath(new URL('./snapshots/subagent-conversation/sidebar.expected.md', import.meta.url)) const UNAVAILABLE_GRANDCHILD_EXPECTED = fileURLToPath(new URL('./snapshots/subagent-conversation/nested.expected.md', import.meta.url)) @@ -400,6 +401,80 @@ describe('web e2e: persisted subagent conversation and human continuation', () = const tree = page.getByRole('tree', { name: 'Subagent sessions' }) const nestedRow = tree.getByRole('treeitem', { name: new RegExp(NESTED_LABEL) }) expect(await nestedRow.locator(':scope > *').count()).toBe(1) + const clickArea = nestedRow.locator(':scope > *') + const label = nestedRow.getByText(NESTED_LABEL, { exact: true }) + const summary = nestedRow.getByText('continuable · not running', { exact: true }) + const [treeBox, rowBox, clickAreaBox, treeStyle, rowStyle, labelStyle, summaryStyle] = await Promise.all([ + tree.boundingBox(), + nestedRow.boundingBox(), + clickArea.boundingBox(), + tree.evaluate((element) => { + const style = getComputedStyle(element) + return { gap: style.gap, padding: style.padding, radius: style.borderRadius } + }), + nestedRow.evaluate((element) => { + const style = getComputedStyle(element) + return { padding: style.padding, radius: style.borderRadius } + }), + label.evaluate((element) => { + const style = getComputedStyle(element) + return { size: style.fontSize, lineHeight: style.lineHeight, weight: style.fontWeight } + }), + summary.evaluate((element) => { + const style = getComputedStyle(element) + return { size: style.fontSize, lineHeight: style.lineHeight, weight: style.fontWeight } + }), + ]) + expect(treeBox).not.toBeNull() + expect(rowBox).not.toBeNull() + expect(clickAreaBox).not.toBeNull() + const leftInset = Math.round(clickAreaBox!.x - treeBox!.x) + const rightInset = Math.round( + treeBox!.x + treeBox!.width - clickAreaBox!.x - clickAreaBox!.width, + ) + const layout = { + menuWidth: Math.round(treeBox!.width), + menuPadding: treeStyle.padding, + menuGap: treeStyle.gap, + menuRadius: treeStyle.radius, + rowWidth: Math.round(rowBox!.width), + rowHeight: Math.round(rowBox!.height), + rowPadding: rowStyle.padding, + rowRadius: rowStyle.radius, + contentWidth: Math.round(clickAreaBox!.width), + contentHeight: Math.round(clickAreaBox!.height), + leftInset, + rightInset, + label: labelStyle, + summary: summaryStyle, + } + expect(layout).toEqual({ + menuWidth: 360, + menuPadding: '4px', + menuGap: '4px', + menuRadius: '12px', + rowWidth: 352, + rowHeight: 54, + rowPadding: '7px 8px 7px 4px', + rowRadius: '8px', + contentWidth: 340, + contentHeight: 40, + leftInset: 8, + rightInset: 12, + label: { size: '14px', lineHeight: '20px', weight: '400' }, + summary: { size: '12px', lineHeight: '18px', weight: '400' }, + }) + await compareOrRefreshGolden( + BRANCHLESS_LAYOUT_EXPECTED, + [ + `menu: ${layout.menuWidth}px; padding ${layout.menuPadding}; gap ${layout.menuGap}; radius ${layout.menuRadius}`, + `row: ${layout.rowWidth}×${layout.rowHeight}px; padding ${layout.rowPadding}; radius ${layout.rowRadius}`, + `content: ${layout.contentWidth}×${layout.contentHeight}px; left inset ${layout.leftInset}px; right inset ${layout.rightInset}px`, + `label: ${layout.label.size}/${layout.label.lineHeight}/${layout.label.weight}`, + `summary: ${layout.summary.size}/${layout.summary.lineHeight}/${layout.summary.weight}`, + ].join('\n'), + MODE, + ) await compareOrRefreshGolden( BRANCHLESS_EXPECTED, await captureStableAria(page, '[role="tree"][aria-label="Subagent sessions"]', scaffold.workspaceCwd), diff --git a/packages/client/ui-subagent/src/client/SubagentCatalogAction.module.css b/packages/client/ui-subagent/src/client/SubagentCatalogAction.module.css index 239081c59c..88e75de7ce 100644 --- a/packages/client/ui-subagent/src/client/SubagentCatalogAction.module.css +++ b/packages/client/ui-subagent/src/client/SubagentCatalogAction.module.css @@ -49,42 +49,43 @@ box-sizing: border-box; display: flex; flex-direction: column; - width: 336px; + gap: 4px; + width: 360px; max-width: min(400px, calc(100vw - 32px)); - max-height: min(560px, calc(100vh - 140px)); + max-height: calc(100vh - 140px); padding: 4px; overflow: auto; - border: 1px solid var(--dsw-alias-border-l2); + border: 0; border-radius: 12px; background: var(--dsw-specific-menu); --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2); --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2); - box-shadow: var(--dsw-shadow-lv3); + box-shadow: inset 0 0 0 1px var(--dsw-alias-border-inverted), var(--dsw-shadow-lv3); } .node { position: relative; + display: flex; + flex-direction: column; + gap: 4px; min-width: 0; } -.menu > .node { - margin-left: -8px; -} .row { position: relative; display: flex; align-items: flex-start; - gap: 8px; + gap: 4px; box-sizing: border-box; width: 100%; - min-height: 50px; - padding: 7px 8px 7px 11px; + min-height: 54px; + padding: 7px 8px 7px 4px; border: 0; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-primary); - font-size: 13px; - line-height: 18px; + font-size: 14px; + line-height: 20px; text-align: left; cursor: pointer; outline: none; @@ -101,16 +102,26 @@ flex: 1; align-self: stretch; align-items: flex-start; - gap: 8px; + gap: 6px; min-width: 0; - margin: -7px -8px -7px; - padding: 7px 8px; - border-radius: 8px; + border-radius: 12px; } -.row > :global([data-state]), .clickarea > :global([data-state]) { - margin-top: 4px; + margin: 5px 3px 0; +} + +.row:has(> .disclosure) > .clickarea, +.row:has(> .disclosureSpace) > .clickarea { + padding-left: 18px; +} + +.row:has(> .disclosureSpace):not(:has(> .clickarea)) { + padding-left: 22px; +} + +.row > :global([data-state]) { + margin: 5px 3px 0; } .disabled { @@ -128,9 +139,16 @@ .disclosure, .disclosureSpace { + position: absolute; + top: 7px; + left: 4px; flex: none; width: 14px; - height: 18px; + height: 20px; +} + +.disclosureSpace { + display: none; } .disclosure { @@ -157,6 +175,7 @@ display: flex; flex: 1; flex-direction: column; + gap: 2px; min-width: 0; } @@ -169,19 +188,21 @@ .label { color: inherit; + font-size: 14px; font-weight: 400; + line-height: 20px; } .summary, .metrics { color: var(--dsw-alias-label-tertiary); - font-size: 11px; - line-height: 16px; + font-size: 12px; + line-height: 18px; } .metrics { display: grid; - grid-template-rows: 18px 16px; + grid-template-rows: 20px 18px; flex: none; font-variant-numeric: tabular-nums; text-align: right; @@ -190,7 +211,7 @@ .metricToken { grid-row: 1; - line-height: 18px; + line-height: 20px; } .metricDuration { @@ -199,6 +220,9 @@ .children { position: relative; + display: flex; + flex-direction: column; + gap: 4px; margin-left: 18px; padding-left: 4px; }