macOS hidden titlebar with vibrancy sidebar
macOS hidden titlebar with vibrancy sidebar
The desktop app drew the stock macOS titlebar: an opaque bar above the web UI that repeats chrome the page already has, spends vertical space, and keeps the sidebar from reaching the window's top edge. The window looked like a browser tab rather than a macOS application, and no mechanism existed for platform-specific presentation — every pixel was identical on macOS, Windows, and the plain web.
English
Problem
The desktop app drew the stock macOS titlebar: an opaque bar above the web UI that repeats chrome the page already has, spends vertical space, and keeps the sidebar from reaching the window's top edge. The window looked like a browser tab rather than a macOS application, and no mechanism existed for platform-specific presentation — every pixel was identical on macOS, Windows, and the plain web.
Decision
The Electron main process opens the main window on darwin with titleBarStyle: 'hiddenInset', trafficLightPosition: { x: 16, y: 18 }, vibrancy: 'sidebar', visualEffectState: 'active', and a transparent backgroundColor. 'active' keeps the material stable behind an unfocused window; 'followWindow' washed the sidebar out on blur.
Every macOS web-side adjustment keys off html[data-platform='darwin'], which only the desktop preloads set (document.documentElement.dataset.platform = process.platform). These rules do not apply to plain Web or other desktop platforms. The Windows caption decision owns its separate presentation.
Transparency chain. Vibrancy shows only through transparent pixels: on darwin html/body (ui-web base.css) and the AppFrame are transparent, the center column paints --dsw-alias-bg-base opaque, and the sidebar column paints a translucent color-mix tint of the sidebar fill so the material reads through it. SidebarRoot's own opaque fill moves to the frame column for the same reason.
Native theme sync. The vibrancy material follows nativeTheme.themeSource, which otherwise tracks the OS appearance and diverges from the app's own theme preference. The ui-theme boot script and ui-layout ThemePresenter publish html[data-ds-theme-source] (light, dark, or system; a fixed preference, including registered theme ids, publishes its resolved scheme). The app preload observes the attribute and forwards it over dsh-desktop:native-theme-set; main validates the value and the sender (the main window's WebContents, including its local static Web document) and assigns nativeTheme.themeSource. Publishing the preference rather than the resolved scheme preserves OS-follow while the preference is system.
Sidebar top strip and full hide. On darwin the sidebar opens with a 52px top strip that clears the traffic lights and carries the collapse toggle; the strip marks itself data-window-drag, so its own 52px box is the window's drag region (ui-web base.css declares the one darwin drag rule) and the toggle subtracts itself through the base.css interactive rule. Collapsing the sidebar hides the column entirely — computeColumns takes an explicit collapsedWidth and the AppFrame passes 0 on darwin desktop — instead of the 56px rail the other platforms keep. The reopen affordances live in the frame's root-scoped shell.leading window-chrome seat, which the AppFrame mounts only while the column is fully hidden; ui-sidebar registers HeaderLeadingControls (open sidebar + New Session, reusing the shell's inject face and locale) into it. The seat placement, the --dsh-frame-leading-clearance variable panels pad by, and the removal of the earlier conversation-header seat are owned by the shell.leading Agent Note.
Window fullscreen marker. macOS fullscreen hides the traffic lights, so layouts built around them must relax. The main process relays isFullScreen() over dsh-desktop:window-fullscreen on enter-full-screen, leave-full-screen, and each did-finish-load; the app preload's syncWindowFullscreen() mirrors it onto html[data-fullscreen]. Consumers: the AppFrame drops --dsh-frame-leading-clearance to 84px and moves the seat to left: 12px, the sidebar's .topStrip moves the toggle to the strip's left edge, and the right sidebar's fullscreen panel resets its first pane's dockkit strip inset to the ordinary 10px.
Drag regions. Electron computes drag regions from window geometry in DOM order, not stacking, so the shell declares -webkit-app-region: drag once, over the mark a chrome row puts on its own box, each row's box being its draggable geometry. The ui-theme app-region gate's CHROME_ROWS manifest is the list of rows that must carry the mark, so this note keeps only the mechanism. The frame declares none — the Windows caption row is that platform's own chrome — so no fixed band has to match any row's height. Everything interactive subtracts itself through two global ui-web base.css rules: one :is(...) selector opting out every interactive element (buttons, links, inputs, roles, [tabindex]), and one body > :not(#root) rule covering every overlay portaled beside #root. The ordering rule those two rest on: a surface that covers the window and must stay clickable portals beside #root (the settings overlay does), which puts it after every drag row in document order; content containers never declare drag, which would override earlier-mounted overlays. The coverage contract note owns the manifest, the browser lane, and the manual state matrix.
Fullscreen traffic-light clearance. ui-dockkit publishes the tab strip's start inset as --dsh-dockkit-strip-inline-start (fallback the design's own 10px). The right sidebar's fullscreen presentation on darwin sets 88px on the panel body and resets 10px for every non-first split cell's subtree, so exactly the pane touching the window's top-left corner clears the lights at any split depth.
Alternatives considered
titleBarStyle: 'hidden' with custom window controls. Rebuilding the traffic lights forfeits native behavior (hover glyphs, fullscreen transitions) for no gain; hiddenInset keeps them native and only asks the page to route around them.
Keeping the 56px rail on darwin. The rail under floating traffic lights doubled the chrome in the window's corner and wasted the width the collapse exists to reclaim; full hide with header-hosted reopen controls matches macOS sidebar conventions.
Mirroring the resolved theme instead of the preference. Forwarding light/dark while the user preference is system would freeze the window material at the value resolved at send time; forwarding system lets macOS keep following the OS appearance natively.
Handling traffic-light clearance inside ui-dockkit. The kit is host-agnostic and cannot know which host corner touches window chrome; publishing an inset variable keeps the policy in the host that owns the placement (ui-sidebar-right) and costs the kit one custom property.
A collapse-state prop pipe into the header controls. The AppFrame already publishes data-sidebar-collapsed; CSS visibility against it avoids a second state path that could disagree with the frame's transition timeline.
Consequences
- The macOS window gains a translucent sidebar and hidden titlebar at zero cost to other platforms: every rule is scoped to
[data-platform='darwin'], which only the Electron preload sets. - The vibrancy material follows the app theme, including third-party registered themes (their resolved scheme). Screenshots and screen recordings differ from the flat web rendering.
- The reopen controls occupy the frame's
shell.leadingseat, a public slot in the client catalog; the earlierconversation.session.header.leadingseat is removed (shell.leading Agent Note). - Drag-region ownership is a window-global invariant:
-webkit-app-region: dragappears only in ui-webbase.css(the one darwin rule overdata-window-drag) and in ui-layoutAppFrame.module.css(the Windows caption.frame::before); other packages rely on the base.css opt-outs or add a scopedno-drag, never a new drag surface. The ui-theme app-region gate pairs each chrome row's markup mark with its sheet, selector, and authored height, and refuses a mark anywhere else. - The transparent window plus vibrancy is accepted to look different in screen sharing and may flash on startup; the transparent
backgroundColormitigates the flash.
Testing
ui-theme boot and ui-layout presenter specs pin the data-ds-theme-source publication and disposal. The ui-sidebar apply spec pins the shell.leading registration (component, locale, shared inject face) and its removal on teardown; the ui-layout app-frame spec pins the seat's darwin mount. The desktop main-startup spec pins the fullscreen relay (transitions, reloads, destroyed-window guard, darwin-only registration) and the preload-platform spec pins the html[data-fullscreen] mirror. The ui-theme corner-shape and full-round style gates cover the new stylesheet, and its app-region gate pins the one darwin drag rule and every chrome row's markup mark.
中文
Problem
桌面应用此前使用 macOS 原生标题栏:Web UI 上方一条不透明的横条,重复页面已有的窗口装饰、占用纵向空间,并让侧边栏无法触及窗口顶边。窗口看起来像浏览器标签页而非 macOS 应用,且不存在任何按平台差异化呈现的机制——macOS、Windows 与纯 Web 上每个像素都完全一致。
Decision
Electron 主进程在 darwin 上以 titleBarStyle: 'hiddenInset'、trafficLightPosition: { x: 16, y: 18 }、vibrancy: 'sidebar'、visualEffectState: 'active' 与透明 backgroundColor 打开主窗口。'active' 让窗口失焦时材质保持稳定;'followWindow' 会在失焦时把侧边栏冲淡。
所有 macOS Web 侧调整均以 html[data-platform='darwin'] 为开关,该属性仅由桌面 preload 设置(document.documentElement.dataset.platform = process.platform)。这些规则不适用于纯 Web 或其他桌面平台。Windows 顶栏决策负责其独立呈现。
透明链。 毛玻璃只透过透明像素显现:darwin 上 html/body(ui-web base.css)与 AppFrame 透明,中间列铺不透明的 --dsw-alias-bg-base,侧边栏列铺侧边栏底色的半透明 color-mix,让材质透出。SidebarRoot 自身的不透明底色出于同一原因移到框架列。
原生主题同步。 毛玻璃材质跟随 nativeTheme.themeSource,后者默认跟踪系统外观,会与应用自身的主题偏好背离。ui-theme 引导脚本与 ui-layout 的 ThemePresenter 发布 html[data-ds-theme-source](light、dark 或 system;固定偏好——包括注册主题 id——发布其解析后的配色)。应用 preload 观察该属性并经 dsh-desktop:native-theme-set 转发;主进程校验取值与发送者(主窗口的 WebContents,包括其本地静态 Web 文档)后赋给 nativeTheme.themeSource。发布偏好而非解析值,可在偏好为 system 时保留跟随系统。
侧边栏顶部条与完全隐藏。 darwin 上侧边栏展开时有一条 52px 的顶部条,避开红绿灯并承载收起按钮;顶部条自己打上 data-window-drag,于是它自身那 52px 盒子就是窗口的拖拽区(唯一的 darwin drag 规则由 ui-web base.css 声明),按钮经 base.css 交互规则自行减除。收起侧边栏时整列隐藏——computeColumns 接受显式 collapsedWidth,AppFrame 在 darwin 桌面传 0——而非其他平台保留的 56px rail。重新打开的入口位于框架 root 作用域的 shell.leading 窗口 chrome 座,由 AppFrame 仅在整列隐藏时挂载;ui-sidebar 向其注册 HeaderLeadingControls(打开侧边栏 + 新会话,复用 shell 的 inject face 与 locale)。座的位置、面板用于避让的 --dsh-frame-leading-clearance 变量,以及早先会话头部座的移除,归 shell.leading Agent Note 所有。
窗口全屏标记。 macOS 全屏隐藏红绿灯,围绕它们构建的布局必须放松。主进程在 enter-full-screen、leave-full-screen 与每次 did-finish-load 时经 dsh-desktop:window-fullscreen 转发 isFullScreen();应用 preload 的 syncWindowFullscreen() 把它镜像到 html[data-fullscreen]。消费者:AppFrame 把 --dsh-frame-leading-clearance 降到 84px 并把座移到 left: 12px,侧边栏 .topStrip 把收起按钮移到条带左端,右侧边栏全屏面板把首格 pane 的 dockkit strip 内边距重置回常规 10px。
拖拽区。 Electron 按 DOM 顺序以窗口几何计算拖拽区,不看层叠,因此外壳只声明一次 -webkit-app-region: drag,作用于 chrome 行打在自身盒子上的标记,行自身的盒子就是它的可拖几何;哪些行必须带这个标记由 ui-theme app-region 门禁的 CHROME_ROWS 清单拥有,本 note 只保留机制。框架不再声明任何 drag——Windows 标题栏行是那个平台自己的 chrome——因此没有一条固定带子需要匹配某一行的高度。所有可交互内容通过 ui-web base.css 的两条全局规则自行减除:一条 :is(...) 选择器让每个交互元素(按钮、链接、输入框、role、[tabindex])退出拖拽,一条 body > :not(#root) 规则覆盖所有 portal 到 #root 之外的浮层。这两条规则依赖的顺序约定:覆盖窗口且必须可点的表面 portal 到 #root 之外(settings 浮层即如此),从而在文档顺序上晚于所有 drag 行;内容容器永不声明 drag——那会覆盖更早挂载的浮层。清单、浏览器车道与人工状态矩阵归 覆盖契约 note。
全屏避开红绿灯。 ui-dockkit 把 tab 条起始内边距发布为 --dsh-dockkit-strip-inline-start(回退为设计自身的 10px)。右侧边栏全屏形态在 darwin 上对面板主体设 88px,并对每个非首格 split 单元的子树重置为 10px,使得任意分屏深度下恰好只有触及窗口左上角的 pane 避开红绿灯。
Alternatives considered
titleBarStyle: 'hidden' + 自绘窗口控件。 重造红绿灯会失去原生行为(悬停图形、全屏过渡)且无收益;hiddenInset 保留原生控件,只要求页面绕行。
darwin 上保留 56px rail。 浮动红绿灯下的 rail 让窗口角落装饰翻倍,也浪费了收起本要回收的宽度;完全隐藏 + 头部承载重开控件符合 macOS 侧边栏惯例。
同步解析后的主题而非偏好。 偏好为 system 时转发 light/dark 会把窗口材质冻结在发送时刻的解析值;转发 system 让 macOS 原生持续跟随系统外观。
在 ui-dockkit 内部处理红绿灯避让。 kit 与宿主无关,不可能知道哪个宿主角落贴着窗口装饰;发布内边距变量把策略留在拥有布局位置的宿主(ui-sidebar-right),kit 只付出一个自定义属性。
向头部控件加收起状态 prop 管道。 AppFrame 已发布 data-sidebar-collapsed;用 CSS 对其判断显隐,避免了可能与框架过渡时间线不一致的第二条状态路径。
Consequences
- macOS 窗口获得半透明侧边栏与隐藏标题栏,对其他平台零成本:所有规则限定在
[data-platform='darwin']下,该属性仅由 Electron preload 设置。 - 毛玻璃材质跟随应用主题,含第三方注册主题(取其解析配色)。截图与录屏与纯 Web 的平面渲染不同。
- 重新打开控件占据框架的
shell.leading座,是客户端 catalog 中的公开 slot;早先的conversation.session.header.leading座已移除(shell.leading Agent Note)。 - 拖拽区归属是窗口级全局不变量:
-webkit-app-region: drag只出现在 ui-webbase.css(作用于data-window-drag的唯一 darwin 规则)与 ui-layoutAppFrame.module.css(Windows 顶栏.frame::before)内;其他包依赖 base.css 的退出规则或添加限定范围的no-drag,永不新增拖拽面。ui-theme 的 app-region 门禁把每个 chrome 行的 markup 标记与它的样式表、选择器和写死高度配成一条,并拒绝别处的标记。 - 接受透明窗口 + 毛玻璃在屏幕共享中呈现不同、启动可能闪烁;透明
backgroundColor缓解闪烁。
Testing
ui-theme 引导与 ui-layout presenter 测试钉住 data-ds-theme-source 的发布与清除。ui-sidebar apply 测试钉住 shell.leading 注册(组件、locale、共享 inject face)及 teardown 移除;ui-layout 的 app-frame 测试钉住 darwin 下座的挂载。desktop main-startup 测试钉住全屏转发(状态切换、重载、已销毁窗口守卫、仅 darwin 注册),preload-platform 测试钉住 html[data-fullscreen] 镜像。ui-theme 的 corner-shape 与 full-round 样式门覆盖新样式表,其 app-region 门禁钉住唯一那条 darwin drag 规则与每个 chrome 行的 markup 标记。