Skip to content

fix(desktop): square icon-only controls, one chrome-control seat, one traffic-light reserve, one preview drag owner - #455

Merged
vastsa merged 17 commits into
vastsa:mainfrom
zeroy1024:fix/window-chrome-geometry
Sep 16, 2026
Merged

vastsa merged 17 commits into
vastsa:mainfrom
zeroy1024:fix/window-chrome-geometry

Conversation

@zeroy1024

@zeroy1024 zeroy1024 commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

以下每个小节先中文、后英文。 / Each section below is given in Chinese first, then in English.

摘要 / Summary

本次 PR 修复 5 处窗口 chrome 问题。共同线索是同一个控件被反复重新描述:它的尺寸是偶然决定的,它的座(seat)随所在界面不同,一处留白被手工复制,一条带上有两个原生拖拽区域而实际只需要一个。本修订确定的几何量是 28 / 4 / 12 / 88 / 44 / 68。

This PR fixes five window-chrome issues. The through-line is one control that kept being described more than once: its size was decided by accident, its seat varied by which surface it sat on, a reserve was hand-copied, and a band had two native drag regions where it needed one. The geometry this revision settles is 28 / 4 / 12 / 88 / 44 / 68.

1. 仅图标控件此前并非正方形 / Icon-only controls were never square

.icon-btn 与带文字的 pill 共用,因此宽度由内容决定——字形加左右各 8px 内边距——而高度是固定的。于是每个「仅图标」用法都宽大于高:侧边栏折叠控件与 Composer 的添加按钮渲染为 31×28(宽 31、高 28),Composer 的润色与撤销控件在 .composer-right 内为 35×28,设置页的 provider 操作按钮为 30×28。同一个控件在会话顶栏中已是 28×28 正方形,即一个功能有两种尺寸。现在仅图标用法显式声明 .icon-btn-square,把两个轴都钉到新 token --ds-control-size(28px),保留 flex: 0 0 以免拥挤的工具行把控件压出正方形,并去掉左右内边距——在全局 border-box 下,它此前把 15px 字形挤进 12px 内容盒。

.icon-btn is shared with label-driven pills, so it took its width from its content — glyph plus 8px of side padding — while its height was fixed. Every icon-only use was therefore wider than tall: the sidebar collapse control and the composer's add button rendered 31×28 (31 wide, 28 tall), the composer's enhance and undo controls 35×28 inside .composer-right, and the settings provider actions 30×28. The same control in the conversation topbar was already a 28×28 square, so one function had two sizes. Icon-only uses now state .icon-btn-square, which pins both axes to a new --ds-control-size token (28px), keeps flex: 0 0 so a crowded toolbar row cannot shrink the control back out of square, and drops the side padding that under the global border-box left a 12px content box for a 15px glyph.

2. 同一控件曾有两套几何 / The same control had two geometries

预览带与路由带的行内操作渲染为 22px 方块,使用 --radius-2xs 和 --ds-tile 座,与同一 46px 带内的 28px 同类控件并列——会话顶栏的同一个 dock 切换、视口固定的工作面板切换、以及工作面板自身的头部操作。它们现在采用共享的 chrome-control 几何:来自 --ds-work-panel-toggle-size 的 28px 正方形,flex: 0 0 以免拥挤的带把控件压出正方形,--radius-md,透明座加语义 hover 底色,以及与其他 chrome 控件一致的 15px 字形。.title-nav-btn 只保留按压反馈与激活色调。预览操作通道由它为其预留空间的控件推导(calc(2 * var(--ds-work-panel-toggle-size) + 4px + 8px) = 68px),因此通道与按钮不会漂移。

The preview- and route-band lane actions rendered as 22px tiles with --radius-2xs and a --ds-tile seat, beside 28px siblings — the same dock toggle in the conversation topbar, the viewport-fixed work-panel toggle, and the work-panel's own header actions — in the same 46px band. They now take the shared chrome-control geometry: a 28px square from --ds-work-panel-toggle-size, flex: 0 0 so a crowded band cannot squeeze them out of square, --radius-md, a transparent seat with the semantic hover wash, and the 15px glyph every other chrome control uses. .title-nav-btn keeps only its press feedback and active tone. The preview action lane is derived from the control it reserves room for (calc(2 * var(--ds-work-panel-toggle-size) + 4px + 8px) = 68px), so the lane and the buttons cannot drift apart.

3. macOS 红绿灯留白原先没有单一来源 / The macOS traffic-light reserve had no single source

窗口化时为原生红绿灯留出的空间此前是手工复制的 76px,出现在五处声明中(.main-titlebar-left 作为其派生值 64px、.conversation-topbar.ct-collapsed、.sidebar-header、.window-chrome-row:not(.sidebar-expanded)、预览的 .work-panel-header),每处各配一个 [data-fullscreen="true"] 兄弟选择器。它们都不与主进程交给 Electron 的 trafficLightPosition 关联,任一侧单独变动都会造成漂移。该留白也正好停在集群右边缘,这正是第一个 shell 控件紧贴绿灯的原因。原生几何现在在 @pi-desktop/shared 中只有一个归属:MAC_TRAFFIC_LIGHT_POSITION(主进程实际应用的值)、MAC_TRAFFIC_LIGHT_CLUSTER_WIDTH_DIP,以及二者之和 MAC_TRAFFIC_LIGHT_EDGE_DIP;渲染进程在首帧之前把该边缘注入 --ds-traffic-light-edge。chrome.css 一次性组合出 --ds-window-lead-inset = 边缘 + 新 token --ds-traffic-light-gap(12px),因此窗口化 macOS 预留 88px,第一个 shell 控件获得 12px 净空。全屏(按钮隐藏)通过同一 token 回退到 8px gutter,由此删除了五个逐选择器覆盖。.main-titlebar-left 现在以 max(0px, calc(var(--ds-window-lead-inset) - 12px)) 推导偏移,使标题在留白处开始,同时避免在留白为 0 时出现负 padding。

The windowed space the renderer left clear for the native traffic lights was a hand-copied 76px in five declarations (.main-titlebar-left as its derived 64px, .conversation-topbar.ct-collapsed, .sidebar-header, .window-chrome-row:not(.sidebar-expanded), and the preview .work-panel-header), each with its own [data-fullscreen="true"] sibling. None of them was tied to the trafficLightPosition the main process hands Electron, so either side changing alone would have drifted. The reserve also ended exactly on the cluster's right edge, which is why the first shell control sat flush against the green light. Native geometry now has one home in @pi-desktop/shared — MAC_TRAFFIC_LIGHT_POSITION (what the main process applies), MAC_TRAFFIC_LIGHT_CLUSTER_WIDTH_DIP, and their sum as MAC_TRAFFIC_LIGHT_EDGE_DIP — and the renderer injects that edge into --ds-traffic-light-edge before first paint. chrome.css composes --ds-window-lead-inset once as the edge plus the new --ds-traffic-light-gap (12px), so windowed macOS reserves 88px and the first shell control gains 12px of clearance. Fullscreen (buttons hidden) falls back to the 8px gutter through the same token, which removes the five per-selector overrides. .main-titlebar-left now derives its offset as max(0px, calc(var(--ds-window-lead-inset) - 12px)), so the title keeps starting at the reserve without a negative padding where the reserve is 0.

4. 预览模式曾有两个重叠的原生拖拽区域 / Preview mode had two overlapping native drag regions

侧边栏收起且工作面板最大化时,shell 会挂载一条承载侧边栏与新任务控件的 overlay 行,其下方的面板头部同样横跨窗口。两者都声明了整带拖拽区域,因此该行的控件落进了头部矩形之内。在 Electron 中拖拽区域不是命中测试:Blink 收集有序矩形列表,由包含该点的最后一个矩形决定,所以该行自己的 no-drag 控件无法把这条带切回来,控件失去指针输入而变成窗口拖拽。项目此前已为其他带修过两次同类故障,这也是 chrome.css 中「视觉内边距不排除拖拽矩形」规则的由来。现在 overlay 行及其 spacer 既不声明 drag 也不声明 no-drag——完全不贡献矩形——面板头部成为这条带唯一的拖拽归属,其边框盒在 shell 操作通道之前结束(用 margin-left,而不是 padding-left),使排除是几何的而非视觉的。通道保留自己的头部高度背景,带在视觉上仍然连续。留白现在在所有平台上以同一方式推导:macOS 使用共享 lead inset,Windows 与 Linux 得到相同的通道预留但没有红绿灯留白,展开侧边栏的情况也预留新任务控件。此前通道预留只存在于 macOS,因此在 Windows 与 Linux 上第一个面板标签曾压在那些控件之下。

With the sidebar collapsed and the work panel maximized, the shell mounts an overlay row carrying the sidebar and new-task controls, and the panel header beneath it also spans the window. Both declared a full-band drag region, so the row's controls fell inside the header's rectangle. In Electron a draggable region is not a hit test: Blink collects an ordered rect list and the last rect containing the point decides, so the row's own no-drag controls could not carve the band back out, and the controls lost their pointer input to window dragging. This is the same failure the project already fixed twice for other bands, which is why chrome.css carries the rule that visual padding does not exclude a drag rectangle. The overlay row and its spacer now declare neither drag nor no-drag — they contribute no rectangle at all — and the panel header is the band's single drag owner, with its border box ending before the shell-action lane (margin-left, not padding-left) so the exclusion is geometric rather than visual. The lane keeps its own header-height background so the band reads continuous. The reserve is now derived the same way on every platform: macOS uses the shared lead inset, Windows and Linux get the same lane reserve with no traffic-light reserve, and the expanded-sidebar case reserves the new-task control too. Previously the lane reserve existed only on macOS, so on Windows and Linux the first panel tab sat underneath those controls.

5. 工作面板头部的三个按钮曾是两个簇、两个座 / The work-panel header's three buttons were two clusters with two seats

头部内的操作组(+ 与最大化,间距 4px)与它之外、视口固定的折叠切换,被 .work-panel-actions 上四层叠加声明分隔——margin-right: 8px、padding-right: 8px、1px 的 --ds-border-subtle 分隔线,以及一条独立的 --ds-work-panel-toggle-gap: 20px 通道 token——实际形成 37px 间距再加一条发丝线。此外 + 与最大化各自带 --ds-tile 座,而切换按钮在其上又叠加 --ds-tile-deep 与 aria-pressed="true" 下的 --ds-raised-shadow,因此在浅色主题下这一行读起来是三个实心方块,切换按钮像一个悬浮的凸起药丸,与旁边的左上角侧边栏、新任务控件完全不像。

现在由一个 token 决定整行间距:--ds-work-panel-control-gap: 4px 取代 --ds-work-panel-toggle-gap,同时作为头部的 flex gap(标签条到操作组)、.work-panel-actions 的 gap(+ 到最大化),以及由头部右内边距以 calc(size + inset + control-gap) 支出的 44px——即操作组到固定折叠切换的间距。.work-panel-actions 只声明间距;分隔线、inset 与 margin 均已移除。+ 与最大化随后加入 chrome.css 中的共享 chrome-control 组,因此三个按钮共用一套几何、一个透明座、指针悬停时的语义 --ds-bg-hover 底色,以及 opacity: 0.4 的禁用态;work-panel.css 中重复的规则被删除,几何只声明一次。切换按钮的按下态去掉填充与浮起阴影,只保留激活墨色,因此打开态 = 字形切换 + --ds-text-primary。aria-pressed 仍把状态传达给辅助技术,键盘焦点保留全局 :focus-visible 轮廓,切换按钮的视口固定归属与 no-drag 豁免不变。4px 控件间距的取值、12px 视口 inset、通道预留,以及所有命中区域契约均未改动。

The action group inside the header (+ and maximize, 4px apart) and the viewport-fixed collapse toggle outside it were separated by four compounding declarations on .work-panel-actions — margin-right: 8px, padding-right: 8px, a 1px --ds-border-subtle divider, and a separate --ds-work-panel-toggle-gap: 20px lane token — for a real 37px gap plus a hairline rule. On top of that, + and maximize carried a --ds-tile seat of their own while the toggle added --ds-tile-deep plus --ds-raised-shadow under aria-pressed="true", so in the light theme the row read as three filled squares with the toggle floating as a raised circular pill — nothing like the top-left sidebar and new-task controls beside them.

One token now spaces the whole row: --ds-work-panel-control-gap: 4px replaces --ds-work-panel-toggle-gap and is the header's flex gap (tab strip to action group), the .work-panel-actions gap (+ to maximize), and — spent by the header's right padding as calc(size + inset + control-gap), 44px — the gap from the action group to the fixed collapse toggle. .work-panel-actions states spacing only; the divider, inset, and margin are gone. + and maximize then join the shared chrome-control group in chrome.css, so all three buttons take one geometry, one transparent seat, the semantic --ds-bg-hover wash on pointer, and the opacity: 0.4 disabled state; their duplicated rules in work-panel.css are deleted, leaving the geometry stated once. The toggle's pressed state drops the fill and the raised shadow and keeps the engaged ink, so the open state is the glyph swap plus --ds-text-primary. aria-pressed still carries the state to assistive technology, keyboard focus keeps the global :focus-visible outline, and the toggle's viewport-fixed ownership and no-drag carve-out are unchanged. The 4px control-gap value, the 12px viewport inset, the lane reserve, and every hit-region contract are untouched.

受影响的规范、ADR 与 E2E 场景 / Affected specs, ADRs and E2E scenarios

  • 04-ux/07-ui-design-system.md(§2 工具栏行、§4.3 工作面板头部行与座、§10 预览模式、§11.1 图标控件)、04-ux/08-component-spec.md(§2.3 Topbar chrome 图标控件、§3.7 控件、§5.2 WorkPanel 头部)、04-ux/01-ui-ia.md、04-ux/09-interaction-patterns.md —— 英文原文与 zh-CN 镜像同步。

    04-ux/07-ui-design-system.md (§2 toolbar row, §4.3 work-panel header row and seat, §10 preview mode, §11.1 icon controls), 04-ux/08-component-spec.md (§2.3 Topbar chrome icon control, §3.7 controls, §5.2 WorkPanel header), 04-ux/01-ui-ia.md, 04-ux/09-interaction-patterns.md — EN and zh-CN mirrors.

  • 08-meta/decisions-log.md:新增条目 D433("macOS 红绿灯留白只有一个来源")、两条图标控件条目、共享的工作面板控件间距、安静的图标座;D408 就地修订。ADR 0238 §6 记录 88px 留白与单一预览拖拽归属。

    08-meta/decisions-log.md: new entry D433 ("The macOS traffic-light reserve has one source"), the two icon-control entries, the shared work-panel control gap, and the quiet icon seat; D408 amended in place. ADR 0238 §6 records the 88px reserve and the single preview drag owner.

  • 06-delivery/04-e2e-test-plan.md:顶栏场景与 E2E-LAYOUT-three-column-width-priority 描述渲染留白检查、单一拖拽归属路径、面板操作组的共享控件间距,以及透明座(并明确标注:座由源码契约钉住,渲染出的表面仍需人眼确认)。

    06-delivery/04-e2e-test-plan.md: the topbar scenario and E2E-LAYOUT-three-column-width-priority describe the rendered-reserve check, the single-drag-owner path, the panel action group's shared control gap, and the transparent seat (with an explicit status note that the seat is source-contract-pinned while the rendered surface needs the eyes-on pass).

  • 不需要新的 E2E 场景:E2E-LAYOUT-three-column-width-priority 获得了正方形控件断言,改为读取解析后的左内边距而不是复述留白数值,断言 overlay 行不贡献拖拽区域且头部盒子在操作通道之前结束,并新增对面板控件共享控件间距的断言。

    No new E2E scenario was needed: E2E-LAYOUT-three-column-width-priority gained the square-control assertion, reads the resolved left padding instead of restating the reserve number, asserts the overlay row contributes no drag region and the header's box stops before the action lane, and now asserts the panel controls' shared control gap.

  • 不需要 ADR:没有架构边界、公共契约、安全边界或已冻结决策的变化。

    No ADR is needed: no architectural boundary, public contract, security boundary, or frozen decision changes.

验证 — 集成的本地 main @ 4e9bb650 / Validation — integrated local main @ 4e9bb650

分支头 fix/window-chrome-geometry = 4e9bb650;以下结果均来自该集成的本地 main 提交。

Branch head fix/window-chrome-geometry = 4e9bb650; every result below comes from that integrated local main commit.

命令 Command 结果 Result
pnpm build:js 通过 / pass
pnpm --filter @pi-desktop/desktop typecheck 通过 / pass
pnpm lint 通过 / pass (biome + check-style-tokens.mjs)
node scripts/check-architecture.mjs 通过 / pass
pnpm -r --if-present test desktop 2048 通过 / 0 失败 (2048 pass / 0 fail),docs 11 通过 / pass,plugin-SDK / i18n / shared / plugin-devkit / agent-host / agent-runtime 全绿 / green
cargo fmt --all -- --check 通过 / pass
cargo test -p host-core --locked 432 通过 / 0 失败 (432 passed / 0 failed)(Rust 树未改动 / unchanged Rust tree)
pnpm test:e2e 21 / 21 通过 (passed),2 跳过 (skipped)(PI_DESKTOP_TEST_API_KEY 未设置 / unset)
pnpm test:e2e:boot 3 / 3 检查通过 (checks passed)
pnpm test:e2e:layout 50 / 50 检查通过 (checks passed)
pnpm test:e2e:theme-surfaces 6 / 6 主题探针 ok,failures: []
pnpm docs:check 通过 / pass —— 77 组 EN/ZH 配对 (pairs),445 页 (pages)
pnpm docs:build 通过 / pass(无死链 / no dead links)

E2E 套件依据 06-delivery/04-e2e-test-plan.md 的集成表选择:默认的跨系统 smoke、Electron boot/preload 探针、三栏布局套件(本次改动自身的回归面),以及针对浅色/深色 chrome 座的主题表面探针。

E2E suites were selected from the integration table in 06-delivery/04-e2e-test-plan.md: the default cross-system smoke, the Electron boot/preload probe, the three-column layout suite (the change's own regression surface), and the theme-surface probe for the light/dark chrome seats.

布局套件中的三条断言(在运行中的应用里测得)/ Three assertions from the layout suite, as measured in the running app:

PASS every rendered icon-only control is a square hit target  — {"expected":28,"measured":3,"offenders":[]}
PASS the panel actions and the fixed collapse toggle share one control gap  — {"tokenGap":4,"groupGap":4,"gapToToggle":4,"borderRight":0,"paddingRight":0,"marginRight":0}
PASS preview actions clear the macOS traffic-light hit area  — bandInset=88 (firstActionLeft=88)

留白断言也检查过「有牙齿」:把 window.ts 还原为字面量 {x:16,y:16} 会立即让 traffic-light-reserve.test.mjs 失败。座的变化由两侧的源码契约覆盖——面板控件必须是共享 chrome-control 组的成员且静止态为透明座,git grep 级别的检查确认它们不再出现在任何自有规则中。

The reserve assertions were also checked for teeth: reverting window.ts to the literal {x:16,y:16} fails traffic-light-reserve.test.mjs immediately. The seat change is covered by source contracts on both sides — the panel controls must be members of the shared chrome-control group with a transparent rest seat, and git grep-level checks confirm they appear in no rule of their own.

兼容性、迁移与安全 / Compatibility, migration, security

  • Windows/Linux:这两个平台保留渲染进程绘制的控件与右侧 120px 带,--ds-window-lead-inset 保持默认 0px。契约测试会遍历每条 data-platform="win32"/"linux" 规则,若其中一条设置了该留白即失败。预览通道预留不再是 macOS 专属,从而消除了这些平台在预览模式下第一个标签的重叠。

    Windows/Linux: they keep the renderer-drawn controls in the 120px right-side band, and --ds-window-lead-inset keeps its 0px default. A contract test walks every data-platform="win32"/"linux" rule and fails if one sets the reserve. The preview lane reserve is no longer macOS-only, which removes the first-tab overlap those platforms had in preview mode.

  • 全屏不变:8px,现在经由同一 token。

    Fullscreen unchanged: 8px, now via the same token.

  • 插件面板窗口创建的是无原生红绿灯的朴素无边框 BrowserWindow,因此不受影响。

    Plugin panel windows create a plain frameless BrowserWindow with no native traffic lights, so they are unaffected.

  • 外观变化,行为不变(作用域限定):工作面板的 +、最大化与折叠控件失去填充/浮起的座,+ 控件失去聚焦时的背景底色;指针悬停使用与其他 chrome 图标控件相同的 --ds-bg-hover token,键盘焦点保留全局 :focus-visible 轮廓。这一「不变」只针对座与视觉表现:按钮尺寸、标签、tooltip、aria-pressed、12px 视口 inset、通道预留与命中区域都未改变。但这不等于整行布局完全没动:第 5 项中操作组与固定折叠切换之间由叠加声明形成的 37px 实际间距加 1px 分隔线,已被共享的 4px 控件间距取代(由头部 44px 右内边距 calc(size + inset + control-gap) 支出),这一累积间距的变化是有意的。

    Visual change, no behavior change (scoped): the work-panel +, maximize, and collapse controls lose their filled/raised seats and the + control's focus-time background wash. Pointer hover uses the same --ds-bg-hover token as every other chrome icon control, and keyboard focus keeps the global :focus-visible outline. That "unchanged" claim is scoped to the seat and the visual treatment: button sizes, labels, tooltips, aria-pressed, the 12px viewport inset, the lane reserve, and hit regions are unchanged. It is not a claim that the row's layout is untouched: in item 5, the effective 37px gap plus 1px divider between the action group and the fixed collapse toggle is replaced by the shared 4px control gap (spent as the header's 44px right padding, calc(size + inset + control-gap)); that cumulative-spacing change is intentional.

  • 无协议、IPC、存储、schema 或迁移变化;无新默认值;无新依赖。

    No protocol, IPC, storage, schema, or migration change; no new default; no new dependency.

遗留风险 / Remaining risk

1. 60 DIP 常量与 88px 留白 / The 60 DIP constant and the 88px reserve. 88px 留白依赖 MAC_TRAFFIC_LIGHT_CLUSTER_WIDTH_DIP(60 DIP),该值来自 macOS 26、2x 显示器上的一次测量。Electron 不在运行时暴露原生按钮的 frame,因此该常量由单元测试与注释钉住,而非从系统读取,也不随显示器缩放或缩放比例做补偿。若未来的 macOS 改变红绿灯几何,需要重新测量(按钮位置会自动跟随 MAC_TRAFFIC_LIGHT_POSITION)。

The 88px reserve depends on MAC_TRAFFIC_LIGHT_CLUSTER_WIDTH_DIP (60 DIP), measured on macOS 26 at a 2x display. Electron does not expose the native buttons' frames at runtime, so the constant is pinned by a unit test and a comment rather than read from the OS, and it carries no compensation for display zoom or scale changes; a future macOS that changes the traffic-light metrics would need this re-measured (the button position follows MAC_TRAFFIC_LIGHT_POSITION automatically).

2. 原生拖拽区域只能靠人工确认 / Native drag regions are backed only by a manual check. 原生拖拽区域行为属于窗口服务器范畴,没有任何已提交的测试能触达:DOM click()、CDP Input.dispatchMouseEvent 与 elementFromPoint 都绕过它。因此评审依据是一次手工、尚未自动化的检查,且它是在本分支较早的一个修订版本上完成的,不是对当前 head 4e9bb650 的验证:在该分支构建上(侧边栏收起、面板最大化),一次坐标驱动的按压把受信任的 pointerdown/click 投递给侧边栏切换与新任务控件,二者都改变了预期的 shell 状态——即本 PR 所修的症状。当前修订没有再做手工 UI 检查。用带的空白区域拖动窗口,以及 Windows/Linux 上的预览交互,均未验证。建议这两个平台的评审者确认带仍能拖动、其控件仍有响应。

Native drag-region behaviour is a window-server concern that no committed test can reach: DOM click(), CDP Input.dispatchMouseEvent, and elementFromPoint all bypass it. Review is therefore backed by a manual, not-yet-automated check that was performed on an earlier revision of this branch, not on the current head 4e9bb650: a coordinate-driven press on that branch build (sidebar collapsed, panel maximized) delivered trusted pointerdown/click to both the sidebar toggle and the new-task control, and each changed the expected shell state — the symptom this PR fixes. That evidence belongs to the earlier revision; no manual UI check was repeated for the current revision. Dragging the window by the empty part of the band, and preview interaction on Windows/Linux, were not exercised. Reviewers on those platforms should confirm the band still drags and its controls still respond.

3. 渲染出的座未被观察 / The rendered seat was not observed. chrome 控件的新座在样式表层被断言(属于共享组、静止态透明座、任何状态规则中都没有 tile 或浮起座),并在构建后的 CSS bundle 中确认,但渲染出的外观未被观察:布局套件测量矩形与计算几何,而不是绘制出的座,且本次修订没有做截图或人眼检查。评审者应以目视确认工作面板的 +、最大化与折叠控件静止时是安静的透明图标、悬停时为灰色 wash,以及折叠切换的打开态表现为字形切换加更亮的墨色。把 scripts/e2e-three-column-layout.mjs 扩展为测量这三个控件的计算 background-color/box-shadow 可以补上这个缺口;它不属于本次修订。

The chrome-controls' new seat is asserted at the stylesheet level (membership in the shared group, transparent rest seat, no tile or raised seat in any state rule) and verified in the built CSS bundle, but the rendered appearance was not observed: the layout suite measures rectangles and computed geometry, not the painted seat, and no screenshot/eyes-on pass was run for this revision. A reviewer should confirm by eye that the work-panel +, maximize, and collapse controls are quiet transparent icons at rest, grey-wash on hover, and that the collapse toggle's open state reads as the glyph swap plus brighter ink. Extending scripts/e2e-three-column-layout.mjs to measure the computed background-color/box-shadow of the three controls would close this gap; it is not part of this revision.

4. 远程检查(先前检查时的状态,不是当前断言)/ Remote checks (status at the prior check, not current assertions). 本 PR 上当时只有一个 Vercel deployment 检查,它对该 fork PR 报告 Authorization required to deploy ——缺少组织授权时它无法通过,也不说明代码情况;当时也没有本 PR 的 GitHub Actions 运行记录。本次正文更新未重新运行 CI。

At the prior check, the only remote check on this PR was a Vercel deployment check reporting Authorization required to deploy for this fork PR — it cannot pass without org authorization and says nothing about the code — and no GitHub Actions run was reported for this PR at that time. These are statuses observed then, not claims about the current state; CI was not re-run for this body update.

The windowed space the renderer left clear for the macOS traffic lights was a
hand-copied 76px in five declarations, each with its own fullscreen sibling,
tied to nothing: the main process places the buttons with its own
`trafficLightPosition` literal. Either side could move alone. The reserve also
ended exactly on the cluster's right edge, so the first shell control sat flush
against the green light.

Native geometry now has one home in `@pi-desktop/shared`:
`MAC_TRAFFIC_LIGHT_POSITION` (what the main process applies) and
`MAC_TRAFFIC_LIGHT_CLUSTER_WIDTH_DIP`, with `MAC_TRAFFIC_LIGHT_EDGE_DIP` as
their sum. The renderer injects that edge into `--ds-traffic-light-edge` before
first paint, and `chrome.css` composes `--ds-window-lead-inset` once as the edge
plus the new `--ds-traffic-light-gap` (12px), so windowed macOS reserves 88px.
Fullscreen (buttons hidden) falls back to the 8px gutter through the same token,
which removes the five per-selector overrides; Windows/Linux keep 0.

`.main-titlebar-left` now derives its 64px as `max(0px, inset - 12px)`, so the
title keeps starting at the reserve without a negative padding where the reserve
is 0.

The intended behavior change: the cluster's right edge is unchanged and the
first shell control gains 12px of clearance.
Updates every place that stated the macOS windowed traffic-light reserve as
76px to the new derivation: the shared geometry constant's cluster edge plus the
12px gap, i.e. 88px (8px in fullscreen, 0 on Windows/Linux). Adds D433 in both
decisions logs, amends D408 and ADR 0238 in place, and rewrites the
E2E-LAYOUT-three-column-width-priority expectation to the resolved-padding check
the suite now runs.
`.icon-btn` is shared with label-driven pills, so it takes its width from its
content: glyph plus 8px of side padding. Every icon-only use of it was
therefore taller than it was wide — 31x28 for the sidebar collapse control and
the composer's add button, 35x28 for the composer's enhance and undo controls
inside `.composer-right`, and 30x28 in the settings provider actions — while
the same function in the conversation topbar was already a 28x28 square.

Icon-only uses now state `.icon-btn-square`, which pins both axes to the new
`--ds-control-size` token (28px), keeps `flex: 0 0` so a crowded toolbar row
cannot shrink the control back out of square, and drops the side padding that
under the global `border-box` would leave a 12px content box for a 15px glyph.

`.composer-right .icon-btn` no longer sets inline padding: the label-driven
model/thinking chip sets its own padding with `!important`, so that
declaration only ever widened the icon-only controls beside it. The maximize
enhance control keeps its label geometry while it is loading.

Renderer only: no protocol, storage, host, permission, or migration change.
The layout suite already drives the sidebar toggle and the work-panel geometry
over CDP, so it is where the rendered control size can be checked: every
visible `.icon-btn` without a label must be a 28px square. A stylesheet
contract alone cannot catch a control that a flex row or the cascade resized.
07 §11.1 gains the icon-only row and the rule behind it, 08 §3.7 says which
controls state it, and the decisions log carries the entry. Both locales are
updated so the pair check stays green.
The preview- and route-band lane actions rendered as 22px tiles with
`--radius-2xs` and a `--ds-tile` seat, beside 28px siblings: the same dock
toggle in the conversation topbar, the viewport-fixed work-panel toggle, and
the work-panel's own actions. One control, two sizes and two shapes, in the
same 46px band.

They now take the shared chrome-control geometry: a 28px square from
`--ds-work-panel-toggle-size`, `flex: 0 0` so a crowded band cannot squeeze
them out of square, `--radius-md`, a transparent seat with the semantic hover
wash, and the 15px glyph every other chrome control uses. `.title-nav-btn`
keeps only its press feedback and active tone.

The preview action lane is derived from the control it reserves room for
(`calc(2 * var(--ds-work-panel-toggle-size) + 4px + 8px)` = 68px), so the panel
header's reserve and the buttons cannot drift apart.

Renderer only: no protocol, storage, host, permission, or migration change.
08 §2 states the shared 28px square for every chrome icon control and the
derived lane reserve; the decisions log carries the entry. Both locales are
updated so the pair check stays green.
@zeroy1024 zeroy1024 changed the title fix(desktop): square icon-only controls and a single source for the traffic-light reserve fix(desktop): square icon-only controls, one traffic-light reserve, one preview drag owner Sep 16, 2026
The panel header's `+`, maximize, and the viewport-fixed collapse toggle read as
two clusters: four declarations on `.work-panel-actions` (an 8px margin, an 8px
padding inset, a 1px `--ds-border-subtle` divider) plus the separate
`--ds-work-panel-toggle-gap: 20px` lane token put 37px and a hairline rule
between the maximize control and the toggle.

One token now spaces the whole row. `--ds-work-panel-control-gap` replaces the
lane token and is the header's flex gap (tab strip to action group), the
`.work-panel-actions` gap (`+` to maximize), and — spent by the header's right
padding as `calc(toggle-size + toggle-inset + control-gap)`, 44px — the gap from
the action group to the fixed collapse toggle. The action group states spacing
only, so the same three 28px squares read as one group.

The fixed toggle keeps its ownership, 12px viewport inset, and platform control
reservation; only the header's right padding changes, so the native drag
rectangle and the action group's own geometry are untouched. The lane still
reserves the whole toggle and the `+` trigger still clears it by 36px, keeping
the 24px hit-region contract. The stylesheet contract test now asserts the
shared token in both places and the absent separator, and the layout suite
measures the resolved gap in the live chrome.
Both locales state the panel header's 44px safe lane (28px control + 12px
viewport inset + the shared 4px control gap) instead of the retired 60px lane
plus a separated action rail, so `+`, maximize, and the viewport-fixed collapse
toggle are documented as one button group. The decisions log carries the entry
and E2E-152 and E2E-LAYOUT-three-column-width-priority now state the same
contract.
`+` and maximize carried a `--ds-tile` seat of their own, and the
viewport-fixed collapse toggle added `--ds-tile-deep` plus
`--ds-raised-shadow` while `aria-pressed="true"`. In the light theme that
turned the panel header's three 28px controls into filled squares, with the
toggle reading as a raised circular pill floating over the header — nothing
like the top-left sidebar toggle and new-task controls beside them.

Both controls now join the shared chrome-control group in `chrome.css`,
which already seats the topbar's dock toggle, the viewport-fixed panel
toggle, and the preview/route-band lane actions: one geometry (28px square,
`--radius-md`, `flex: 0 0`), one transparent seat, the semantic
`--ds-bg-hover` wash on pointer, and the `opacity: 0.4` disabled state. The
duplicated rules in `work-panel.css` are gone so the geometry is stated
once; the partial keeps only the action group's spacing.

The toggle's pressed state drops the fill and the raised shadow and keeps
the engaged ink, so the open state is the glyph swap plus
`--ds-text-primary`. `aria-pressed` still carries the state to assistive
technology, keyboard focus keeps the global `:focus-visible` outline, and
the toggle's viewport-fixed ownership and `no-drag` carve-out are
unchanged. The 4px control gap, 12px viewport inset, and lane reserve are
untouched.

`chrome-control-geometry.test.mjs` pins the panel controls' membership in
the shared group and its rest/hover/disabled states, and
`work-panel.test.mjs` asserts the transparent seat instead of the retired
`--ds-tile` one.
07 §4.3 and 08 §2.3/§5.2 now state the shared transparent seat and the
glyph-and-ink open state, the decisions log carries the entry, and
E2E-LAYOUT-three-column-width-priority gains the appearance expectation plus
an honest status note: the seat is pinned by source contracts while the
rendered surface needs the eyes-on pass. Both locales are updated so the
pair check stays green.
@zeroy1024 zeroy1024 changed the title fix(desktop): square icon-only controls, one traffic-light reserve, one preview drag owner fix(desktop): square icon-only controls, one chrome-control seat, one traffic-light reserve, one preview drag owner Sep 16, 2026
@zeroy1024

Copy link
Copy Markdown
Contributor Author

统一按钮风格
PixPin_2026-09-16_15-07-20
修复左侧折叠右侧展开放大 左侧按钮不可点,并增加距红绿灯的间距
PixPin_2026-09-16_15-07-52

@vastsa

vastsa commented Sep 16, 2026

Copy link
Copy Markdown
Owner

非常感谢您的 pr

@zeroy1024

Copy link
Copy Markdown
Contributor Author

Added follow-up fix b96a8ab to this PR.

The shared chrome-control rules previously covered preview .window-chrome-row actions but omitted ordinary .main-titlebar actions. This restores the shared 28px geometry and rest/hover/disabled styles for collapsed-sidebar controls on Plugins, Pull requests, and Scheduled routes. Native platform insets, window controls, and drag regions are unchanged.

Regression coverage:

  • The strengthened source test failed before the CSS fix and passed afterward.
  • The layout suite now loads all three real routes, measures both titlebar actions in light/dark themes, checks hover styling, and exercises sidebar/New Task DOM actions.
  • Component and E2E specifications were updated.

Validation:

  • Desktop tests: 2048/2048 passed.
  • JavaScript build, desktop typecheck, lint, and documentation checks passed.
  • After integration into local main at b96a8ab, rebuilt and ran pnpm test:e2e:layout: 89/89 checks passed on macOS.
  • Native Windows/Linux interaction tests were NOT RUN (current environment is macOS). Renderer/CDP and simulated platform geometry checks are not native hit-testing evidence.

This update does not merge the PR. Validation above applies to the stated commit, not to an untested merge with newer upstream main.

Resolve decisions-log conflicts by keeping both ADR 0268 (already on main)
and the window-chrome geometry entries from PR vastsa#455, including D433.
@vastsa
vastsa merged commit 64648a1 into vastsa:main Sep 16, 2026
1 check failed
@zeroy1024
zeroy1024 deleted the fix/window-chrome-geometry branch September 21, 2026 14:53
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.

2 participants