Context
Found in manual testing of apps/workstation on dev (2026-10-22), in the same pass as #19982 and #19984. Dragging a column header of the 100k Matrix grid (here Throughput) shows a drag proxy whose header and cells use the neo theme's default grid colors: a blue header and near-black cells. Workstation's own palette is gone. A screenshot follows in a comment.
Measurements
Headless Chromium, 1440×900, at 8bb3574d0f (its src/, apps/ and resources/scss/ are identical to dev at 84b711878a). A real pointer drag on the Throughput header, with computed styles read while the proxy is live:
| skin |
property |
source grid |
drag proxy |
| dark |
header background / text |
rgb(26,33,44) / rgb(214,220,230) |
#182449 / rgb(240,242,240) |
| dark |
cell background |
rgb(20,26,35) |
#0E0F0D |
| light |
header background / text |
rgb(247,249,252) / rgb(31,39,51) |
#5d83a7 / #fff |
| light |
cell background |
#fff |
#fff |
The proxy is a direct child of document.body with neo-grid-container neo-theme-neo-{dark,light} neo-dragproxy. Its theme class is the live one in both skins. What is missing is Workstation's own skin.
The Problem
Workstation skins grids with an app-scoped token bridge, not with a theme. Viewport.scss maps --grid-* onto its --workstation-* palette under :root .workstation-viewport[class*="neo-theme-"], :root .workstation-viewport [class*="neo-theme-"], and sets the header tokens under .workstation-viewport .neo-grid-header-button.
The column-drag proxy is mounted outside that subtree. No bridge rule matches it, so every grid token on it resolves to the theme's default.
The Architectural Reality
src/draggable/DragZone.mjs: proxyParentId_ defaults to 'document.body', and the source's theme class is pushed onto the proxy.
src/draggable/grid/header/toolbar/SortZone.mjs createDragProxy(): cls = ['neo-grid-container', owner.getTheme()]. Inside, the proxy nests a neo-grid-container carrying the source grid's own cls, a header toolbar and cloned rows and cells.
resources/scss/src/apps/workstation/Viewport.scss: the viewport bridge. Its comment explains why the selector mirrors the theme's own reach. For an inherited custom property the nearest declaring ancestor wins, and inner grid primitives carry skin classes too (#17984).
- Design authority: Workstation's own precedent for a body-mounted embodiment, the overflow menu in
Workspace.scss. "The generic menu is a second floating direct-body embodiment. Its own live neo-theme-* class carries the active palette; this Workstation-only bridge projects that palette into menu-domain tokens without coupling the shared tab plugin or shared menu component to product colours." It is implemented as body:has(.workstation-viewport) .neo-menu-list.neo-tab-overflow-menu { --menu-list-*: var(--workstation-*) }. The grid proxy is a third such embodiment and has no bridge.
The Fix
The fix is on the app side, in Workstation's SCSS; no engine change. The proxy already carries the live theme class and a grid identity.
Move the grid half of the viewport bridge into a single shared mixin, so the viewport scope and the proxy scope cannot drift. That half is the --grid-container-* and --grid-cell-* tokens, plus the .neo-grid-header-button token set, its justify-content and padding, and the glyph positioning.
Apply it to body:has(.workstation-viewport) .neo-grid-container.neo-dragproxy, and to every skin-carrying element inside the proxy (… .neo-dragproxy [class*="neo-theme-"], mirroring the viewport bridge's shape). The proxy's inner neo-grid-container may carry its own theme class, and a bridge on the outer node alone would be shadowed there.
Put it in Viewport.scss, the sheet every boot mode loads. A ?popout= vessel never loads Workspace.css, and a popped-out grid drags its columns too.
Specificity: body:has(.workstation-viewport) .neo-grid-container.neo-dragproxy is (0,3,1), against the theme's (0,2,0).
Acceptance Criteria
Out of Scope
- A real Workstation theme. None exists yet. A theme would give every body-mounted embodiment the right skin at once; this ticket keeps the current bridge model.
- Other body-mounted embodiments (tooltips, pickers, other menus). They were not reported and not audited here.
Related
- #16120 (closed): a body-mounted dock tab drag proxy painted on a transparent surface. Same class, dock side.
- #17984 (closed): the viewport bridge's specificity contract.
- #19982, #19984: the same testing pass.
Live latest-open sweep: checked latest 20 open issues at 2026-10-22T09:25:12Z; no equivalent found.
Keyword sweep: "drag proxy theme colors", "column drag proxy", "dragproxy workstation", "proxy skin": closed adjacent only (#16120, #6394).
A2A claim sweep: no overlapping lane-claim.
MC sweep: "grid column header drag proxy shows default theme colors…", 6 results, no prior decision on the grid proxy.
Own-assignment sweep: none overlapping.
Origin Session ID: 70e59a84-efd6-4e7a-9262-c5478bd314ff
Retrieval Hint: "Workstation grid column drag proxy theme colors, body-mounted embodiment, app token bridge"
Context
Found in manual testing of
apps/workstationondev(2026-10-22), in the same pass as #19982 and #19984. Dragging a column header of the 100k Matrix grid (here Throughput) shows a drag proxy whose header and cells use the neo theme's default grid colors: a blue header and near-black cells. Workstation's own palette is gone. A screenshot follows in a comment.Measurements
Headless Chromium, 1440×900, at
8bb3574d0f(itssrc/,apps/andresources/scss/are identical todevat84b711878a). A real pointer drag on the Throughput header, with computed styles read while the proxy is live:rgb(26,33,44)/rgb(214,220,230)#182449/rgb(240,242,240)rgb(20,26,35)#0E0F0Drgb(247,249,252)/rgb(31,39,51)#5d83a7/#fff#fff#fffThe proxy is a direct child of
document.bodywithneo-grid-container neo-theme-neo-{dark,light} neo-dragproxy. Its theme class is the live one in both skins. What is missing is Workstation's own skin.The Problem
Workstation skins grids with an app-scoped token bridge, not with a theme.
Viewport.scssmaps--grid-*onto its--workstation-*palette under:root .workstation-viewport[class*="neo-theme-"], :root .workstation-viewport [class*="neo-theme-"], and sets the header tokens under.workstation-viewport .neo-grid-header-button.The column-drag proxy is mounted outside that subtree. No bridge rule matches it, so every grid token on it resolves to the theme's default.
The Architectural Reality
src/draggable/DragZone.mjs:proxyParentId_defaults to'document.body', and the source's theme class is pushed onto the proxy.src/draggable/grid/header/toolbar/SortZone.mjscreateDragProxy():cls = ['neo-grid-container', owner.getTheme()]. Inside, the proxy nests aneo-grid-containercarrying the source grid's owncls, a header toolbar and cloned rows and cells.resources/scss/src/apps/workstation/Viewport.scss: the viewport bridge. Its comment explains why the selector mirrors the theme's own reach. For an inherited custom property the nearest declaring ancestor wins, and inner grid primitives carry skin classes too (#17984).Workspace.scss. "The generic menu is a second floating direct-body embodiment. Its own liveneo-theme-*class carries the active palette; this Workstation-only bridge projects that palette into menu-domain tokens without coupling the shared tab plugin or shared menu component to product colours." It is implemented asbody:has(.workstation-viewport) .neo-menu-list.neo-tab-overflow-menu { --menu-list-*: var(--workstation-*) }. The grid proxy is a third such embodiment and has no bridge.The Fix
The fix is on the app side, in Workstation's SCSS; no engine change. The proxy already carries the live theme class and a grid identity.
Move the grid half of the viewport bridge into a single shared mixin, so the viewport scope and the proxy scope cannot drift. That half is the
--grid-container-*and--grid-cell-*tokens, plus the.neo-grid-header-buttontoken set, its justify-content and padding, and the glyph positioning.Apply it to
body:has(.workstation-viewport) .neo-grid-container.neo-dragproxy, and to every skin-carrying element inside the proxy (… .neo-dragproxy [class*="neo-theme-"], mirroring the viewport bridge's shape). The proxy's innerneo-grid-containermay carry its own theme class, and a bridge on the outer node alone would be shadowed there.Put it in
Viewport.scss, the sheet every boot mode loads. A?popout=vessel never loadsWorkspace.css, and a popped-out grid drags its columns too.Specificity:
body:has(.workstation-viewport) .neo-grid-container.neo-dragproxyis (0,3,1), against the theme's (0,2,0).Acceptance Criteria
test/playwright/e2e/workstation/(e.g.WorkstationGridDragProxySkinNL.spec.mjs) starts a real pointer drag on a 100k Matrix column header and compares the live proxy's computed styles with the source's, in dark and then light (via the theme toggle). It must be red ondev(dark header#182449againstrgb(26,33,44)) and green with the fix. It joins the hosted Workstation tier (#19655).Out of Scope
Related
Live latest-open sweep: checked latest 20 open issues at 2026-10-22T09:25:12Z; no equivalent found. Keyword sweep: "drag proxy theme colors", "column drag proxy", "dragproxy workstation", "proxy skin": closed adjacent only (#16120, #6394). A2A claim sweep: no overlapping lane-claim. MC sweep: "grid column header drag proxy shows default theme colors…", 6 results, no prior decision on the grid proxy. Own-assignment sweep: none overlapping.
Origin Session ID: 70e59a84-efd6-4e7a-9262-c5478bd314ff Retrieval Hint: "Workstation grid column drag proxy theme colors, body-mounted embodiment, app token bridge"