LearnNewsExamplesServices
Frontmatter
id17538
titleThe dock splitter''s visual language lives in apps, not the engine
stateClosed
labels
bugairefactoring
assigneesneo-opus-grace
createdAtAug 22, 2026, 2:21 PM
updatedAtAug 22, 2026, 3:52 PM
githubUrlhttps://github.com/neomjs/neo/issues/17538
authorneo-opus-grace
commentsCount0
parentIssue17241
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 22, 2026, 3:52 PM

The dock splitter's visual language lives in apps, not the engine

Closed Backlog/active-chunk-18 bugairefactoring
neo-opus-grace
neo-opus-grace commented on Aug 22, 2026, 2:21 PM

Context

The second leaf of #17241, split out for the same reason its sibling #17522 was: the parent covers the dock's whole visual language moving from apps/ into the engine, and that is more than one PR can honestly close.

#17522 took the rail tabs — both apps declare the same structure and differ only in values, so it was mechanical. The splitter was deliberately left behind, because the engine's neutral default there was an open design decision rather than a refactor. That decision is now made and recorded on #17241 (@neo-fable, 2026-08-22): option 3 — the engine ships a discoverable affordance; an app opts out by token. This ticket implements it.

Filed on @neo-gpt's RA-1 against PR #17531, which correctly found that Resolves #17241 over-closes the parent: the head delivers the splitter half only, while #17241 additionally retains the edge/band/rail chrome, the dark-theme home decision, and the cross-cutting net-LOC criterion. #17241 stays open as the two-leaf parent.

Live latest-open sweep: latest 20 open issues checked 2026-08-22T12:19:54Z; no equivalent — #17522 is the rail sibling, #17211 is the cockpit-ergonomics escalation that consumes this. A2A in-flight claim sweep: 30 most recent messages, newest 2026-08-22T01:24:41Z; no claim on the splitter scope.

The Problem

resources/scss/src/dashboard/Container.scss is the engine home for Neo.dashboard.Container. Before this work it gave .neo-dashboard-dock-splitter flex-shrink, position, touch-action, z-index, a hit-target ::before and a cursor — no background, no border, no handle, no hover, no active. Line 159 said so in place: "Demo-consumable minimal hooks only — the real visual language lands with the F-tranche."

resources/scss/src/apps/workstation/Workspace.scss:31-82 — an application stylesheet — carried the entire affordance: band background, inset ring, a 36×2px ::after grip, hover glow, active state.

So a consumer that adopts the dock inherits an invisible drag target. That is not a neutral default; it is the reported defect #17211 (finding 1) was filed about, and it is why the splitter is discoverable in the workstation and invisible in the FleetCockpit — the two are not styled by the same layer at all.

The Architectural Reality

  • resources/scss/src/dashboard/Container.scss:113 — the engine splitter rule, structure only
  • Container.scss:14-27 — the existing --dock-* token precedent, motion only; paint is the gap
  • resources/scss/src/apps/workstation/Workspace.scss:31-82 — the app-owned affordance, already token-driven but reading --workstation-*, so its content is the promotion candidate almost verbatim and only its token names are app-specific
  • resources/scss/src/apps/agentos/fleet/FleetCockpit.scss:134 — FM's partial horizontal-splitter override

The workstation's active state carried opacity: 1 !important. An !important means a competing rule is being fought rather than resolved; the promotion must fix the specificity cause instead of carrying the escape hatch into the engine.

The Fix

Three layers, and the middle one is the whole design:

layer owner content
structure engine, untokened flex-shrink, position, touch-action, z-index, the hit-target ::before
affordance floor engine, token defaults visible band, the 36×2 centred handle, hover raise, functional active — without the pill ring and outer glow
identity app, token values workstation re-adds ring + glow; FM declares --dock-splitter-handle-size: 0

Ring and glow are workstation signal-language — they were born reading --workstation-signal — so shipping them as the engine default would push one consumer's identity onto every other. FM's flat splitter becomes an explicit --dock-splitter-handle-size: 0: a design statement that greps, rather than an absence that reads as an oversight.

Neutral defaults mix from currentColor rather than a literal grey — the engine cannot know the host palette, and a guess reads wrong on half of them.

SCSS token/skin layers only; zero CSS-in-JS.

Contract Ledger Matrix

The consumed token surface. Source of authority for every row is resources/scss/src/dashboard/Container.scss (engine defaults) — each verified present in that file at 450ef52b68 except the -active handle row, which is the gap this ticket closes.

Target surface Proposed behavior Fallback (no app tokens) Evidence
--dock-splitter-background / -hover / -active band paint per state color-mix(currentColor 9% / 16% / 26%, transparent) engine-only consumer renders a visible band
--dock-splitter-radius band + handle rounding 999px computed style
--dock-splitter-handle-size / -thickness handle long / short axis; size: 0 is the documented opt-out 36px / 2px FM opt-out grep + computed style
--dock-splitter-handle-color / -hover handle paint, resting and hover color-mix(currentColor 45%, transparent) / currentColor computed style, both states
--dock-splitter-handle-color-active handle paint while dragging — the missing row var(--dock-splitter-handle-color-hover) computed style in the active state; workstation sets the value only
--dock-splitter-ring / -hover / -active identity slot none — engine never fills it guard asserts the none default
--dock-splitter-handle-glow-hover identity slot none — engine never fills it guard asserts the none default

Decision Record impact

aligned-with ADR 0029 (learn/agentos/decisions/0029-docking-design.md — renamed 2026-08-21 from 0029-harness-docking-design.md by #17503; the harness misnomer is retired, persisted schema strings stay frozen). Presentation only — behavior and state ownership are unchanged.

Acceptance Criteria

  • .neo-dashboard-dock-splitter renders a discoverable affordance from the engine layer alone — asserted against examples/dashboard/dock, the consumer #17211 was filed about, with no app tokens set
  • The engine's affordance floor is token defaults; ring and glow remain empty identity slots the engine never fills
  • No application stylesheet paints a splitter, including in its own nested state and pseudo blocks. &:active::after is a state of the splitter itself, not a descendant, and must stay inside the census
  • The census rejects every visual property except an explicit allowlist of documented structural deviations — a fixed five-property denylist lets filter, outline or color re-enter undetected
  • The active handle is an engine token consumed by the engine rule; Workspace.scss sets a value and declares no background
  • FM's flat splitter is an explicit --dock-splitter-handle-size: 0, not an omission
  • No !important in the promoted engine rule; if one seems required, the specificity cause is fixed instead
  • Resting, hover and active computed styles are asserted in both apps and both themes, including parity for the values meant to stay unchanged and FM's flat opt-out — rendered effect, not SCSS source shape
  • Mutation: reintroducing nested app-side active paint reddens the census
  • Mutation: removing the engine token block makes the engine-only consumer fail, so the test observes the promotion rather than an app override

Out of Scope

  • The rail tabs#17522's leaf, delivered by PR #17524.
  • The dock edge zone / row / band / rail chrome, the dark-theme home decision, and the cross-cutting net-LOC criterion. Those stay on #17241; this leaf does not close the parent.
  • Dock behavior, drag mechanics, and the DockSplitter component API — presentation only.
  • Whether FM keeps a flat splitter now that a real handle exists. That is @neo-fable-clio's call on FM's design language, and the engine answer does not depend on it.

Avoided Traps

Fixing FM by copying the workstation's SCSS into FleetCockpit.scss. That is how the current state arose; a third copy makes the next consumer's bug certain.

Promoting the workstation's tokens as-is. --workstation-signal in an engine stylesheet inverts the dependency this work exists to fix.

Asserting the promotion on SCSS source alone. A source guard proves declarations exist and some properties are absent; it cannot prove token resolution, visible output, hover/active parity, selector load order, or consumer adoption. The close-target criterion is rendered effect.

Verifying by screenshot. The visual suite runs in no workflow and 8/8 goldens fail on an unmodified tree (#17518), so it cannot discriminate this change. Computed-style equality asserts the property rather than a rendering of it.

Related

  • #17241 — parent; retains the edge/band/rail chrome, the dark-theme home, and net LOC. Stays open. Whether it should convert from enhancement to epic now that it has two leaves is a taxonomy call left to its closeout, not decided here
  • #17522 / PR #17524 — the rail-tab sibling leaf
  • #17211 — cockpit ergonomics; its splitter-visibility finding is discharged by this leaf
  • #17514 / PR #17515 — the Body-side architecture guide specifying the neutral-token shape
  • PR #17531 — the in-flight implementation, retargeted here from #17241 on @neo-gpt's RA-1
  • ADR 0029

Retrieval Hint: dock splitter affordance floor engine token promotion Container.scss Workspace.scss active handle

Origin Session ID: cb3eb9c7-875a-4eac-a716-02878fa535c5

tobiu referenced in commit 5d23f96 - "The dock splitter's visual language lives in apps, not the engine (#17531) on Aug 22, 2026, 3:52 PM
tobiu closed this issue on Aug 22, 2026, 3:52 PM