feat(workspace): the scanline is for charts, and the conversation gets its rail (#2540, ent#474) - #2543
Merged
Conversation
…s its rail (#2540, ent#474) Two operator rulings from the 2026-09-06 Workspace review, one PR because the second is built under the first. #2540 — the loading rule. Design-system principle 12 is amended: the scanline beam + wipe-in reveal is the CHART-loading motion only; every other first load — pages, panels, lists, message threads — is a skeleton placeholder keyed on "no data yet". The three Workspace zones #2163 wrapped in `ScanlineReveal` (the stage in Portal.vue, the thread in PortalConversation.vue, the hint zone in PortalBriefing.vue) render `PortalSkeleton` (stage / thread / briefing) instead, gated on `stage.state === 'loading'`, `!historyLoaded` and `zone.state === 'pending'` — never a bare `<x>.loading` path, which the #1927 ratchet counts as a bare gate. The `reveal` verdict the zones computed for the primitive is gone. The footprint (`min-h`, `max-w`) moves onto a wrapper both faces sit inside, so the swap never shifts. `tests/unit/portalLoadingTreatment.spec.js` pins the `ScanlineReveal` importer set as an allowlist: three chart consumers plus the two pre-ruling non-chart holdovers (LibrarySkillsSection, FinishSetupCard), recorded on #1921 — whose sweep is re-pointed by comment: bespoke spinners on non-chart surfaces become skeletons, not scanlines. The doc names the canonical forms (`SkeletonLoader.vue`, or content-shaped) and the recipe; `/audit-design-system` reads the amended doc without a skill change. ent#474 — the rail shell, to the approved design pass (48px collapsed / `w-96` open, `trinity-workspace-rail`, mobile strip + sheet, two signal shapes). Scope is the operator's own split: the three-column layout, the tab contract, the collapsed signal, Work docked EMPTY — its content is #457's and the re-homing of loops / canvas / files is #472's second child; the sidebar, thread tab strip, top band, Agent-details panel and drop target of the approved conversation page are later steps of the same build, and #492 lands the grid variables the rail's widths then follow. - `portalRail.js` is the contract: a tab declares door (platform / audience / agent), participant scope, empty state and signal shape; `visibleTabs` is the ONE gate for render AND mount, so a tab whose door the session fails has no icon, no label and no mounted body — nothing it would fetch is ever requested (the per-door test; an external client sees no rail until an audience tab docks). An unknown door fails closed. - `PortalRail.vue` is a sibling of <main>, outside `convKey`, so a chat switch remounts the conversation and never the rail; visibility keys on the route and the stage verdict, never on data still arriving. Collapsed by default; open/collapsed + active tab persist under one key, read before first paint. - The Work signal is DERIVED — from the conversation's in-flight `sending` (1:1) and the room's server-reported `working` list — reset on every chat switch and cleared on unmount, so it structurally cannot stick. - A room groups by participant over `portalLoopUtils.byAgent`, absence visible; `1 running` is a `BaseBadge`. `OverflowTabs` gains an optional per-tab `signal` dot, measured in its mirror row. - Mobile: `PortalRailStrip` above the composer through a `#rail-strip` slot on both conversations, opening the same component as a bottom sheet (Esc closes). Verified live on the Docker frontend: collapsed → open → persisted across reload; light and dark; mobile strip → sheet → Escape; the stage skeleton under a slowed roster; no console errors. Unit suite 1889/1889; both ratchets flat (no bare gate added, no touched file's raw-gray count grew). Fixes #2540 Part of abilityai/trinity-enterprise#474 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgAncnTAbgjJS3EGwuEtd4
vybe
approved these changes
Sep 6, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
Validated via /validate-pr: security scans clean, unit suite 1889/1889 on the PR head in Docker, both ratchets flat, docs updated across requirements / architecture area file / feature flows. Structural spot-check of the door gate, derived signal and stage skeleton found no issues. Approving.
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two operator rulings from the 2026-09-06 Workspace review, in one PR because the second is built under the first.
ScanlineReveal— stage, thread, briefing — now renderPortalSkeleton, gated onstage.state === 'loading'/!historyLoaded/zone.state === 'pending'(never a bare<x>.loadingpath, which the bug: background polls re-flash loaded content and reset UI state (design-system p13/p14) #1927 ratchet counts). TheScanlineRevealimporter set is pinned as an allowlist; the two pre-ruling non-chart holdovers (LibrarySkillsSection,FinishSetupCard) are recorded on refactor: replace bespoke spinners with skeleton placeholders on non-chart surfaces (scanline stays on charts only — design-system p12 as amended 2026-09-06) #1921, whose sweep is re-pointed to skeletons.w-96open,trinity-workspace-rail, mobile strip + sheet, two signal shapes). Scope is the operator's own split: the three-column layout, the tab contract, the collapsed signal, and Work docked empty — its content is bug: long strings in chat responses break layout in chat tab #457's; loops / canvas / files re-home in fix(security): operator-queue access control — scope items to accessible agents (#470) #472's second child; the sidebar, thread tab strip, top band, Agent-details panel and drop target of the approved conversation page are later steps of the same build; chore(#488): ship status-in-dev automation to main #492 lands the grid variables the rail's widths then follow.How the rail works
components/portal/portalRail.jsis the contract: a tab declares its door (platform/audience/agent), participant scope, empty state and signal shape.visibleTabsis the one gate for render and mount — a tab whose door the session fails has no icon, no label and no mounted body, so nothing it would fetch is ever requested. An unknown door fails closed. Today an external client sees no rail (Work is platform-only); with the four-tab set they see Canvas · Files.PortalRail.vueis a sibling of<main>, outsideconvKey: a chat switch remounts the conversation, never the rail. Visibility keys on the route and the stage verdict, never on data still arriving. Collapsed by default; open/collapsed + active tab persist under one key, read before first paint.sending(1:1) and the room's server-reportedworkinglist — reset on every chat switch and cleared on unmount.portalLoopUtils.byAgent), absence visible;OverflowTabsgains an optional per-tabsignaldot measured in its mirror row; the mobile strip sits above the composer through a#rail-stripslot on both conversations and opens the same component as a bottom sheet (Esc closes).Changes
portal/portalRail.js,portal/PortalRail.vue,portal/PortalRailStrip.vue,portal/PortalSkeleton.vueviews/Portal.vue(stage skeleton, rail wiring),portal/PortalConversation.vue+portal/PortalRoom.vue(thread skeleton,#rail-stripslot,work-state/participants-changedemits),portal/PortalBriefing.vue,portal/portalBriefingState.js(therevealverdict is gone),components/OverflowTabs.vue(additivesignal)design-system.md§6 + principle 12 + §8 table,design-system-contract.md, requirements §5.16 (amended) + new §5.19,feature-flows/workspace-rail.md(new),workspace-roster-briefing.md,architecture/workspace.md, the flow indexportalLoadingTreatment.spec.js(new),portalRail.spec.js(new),portalBriefingState.spec.js+workspaceRoomsGate.spec.js(updated for the skeleton gate)Test Plan
npm run test:unit— 1889/1889 across 86 files (both ratchets green: no bare loading gate added; no touched file's raw-gray count grew — measured againstorigin/dev)aria-busyunder a slowed roster; no console errors1 runningbadge appears while an agent is working (rides the room'sworkinglist)Fixes #2540
Part of abilityai/trinity-enterprise#474
🤖 Generated with Claude Code
https://claude.ai/code/session_01FgAncnTAbgjJS3EGwuEtd4