Skip to content

feat(workspace): the scanline is for charts, and the conversation gets its rail (#2540, ent#474) - #2543

Merged
vybe merged 1 commit into
devfrom
feature/2540-loading-rule-rail-shell
Sep 6, 2026
Merged

vybe merged 1 commit into
devfrom
feature/2540-loading-rule-rail-shell

Conversation

@trinity-ability

Copy link
Copy Markdown
Contributor

Summary

Two operator rulings from the 2026-09-06 Workspace review, in one PR because the second is built under the first.

How the rail works

  • components/portal/portalRail.js is the contract: a tab declares its door (platform / audience / agent), participant scope, empty state and signal shape. visibleTabs is 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.vue is a sibling of <main>, outside convKey: 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.
  • The Work signal is derived, never latched — 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.
  • A room groups every tab by participant (portalLoopUtils.byAgent), absence visible; OverflowTabs gains an optional per-tab signal dot measured in its mirror row; the mobile strip sits above the composer through a #rail-strip slot on both conversations and opens the same component as a bottom sheet (Esc closes).

Changes

  • New: portal/portalRail.js, portal/PortalRail.vue, portal/PortalRailStrip.vue, portal/PortalSkeleton.vue
  • Edited: views/Portal.vue (stage skeleton, rail wiring), portal/PortalConversation.vue + portal/PortalRoom.vue (thread skeleton, #rail-strip slot, work-state / participants-changed emits), portal/PortalBriefing.vue, portal/portalBriefingState.js (the reveal verdict is gone), components/OverflowTabs.vue (additive signal)
  • Docs: 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 index
  • Tests: portalLoadingTreatment.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 against origin/dev)
  • Live on the Docker frontend (Playwright): collapsed strip → open rail with the Work empty state → persisted across reload; light and dark; mobile (390px) strip above the composer → bottom sheet → Escape closes; the stage skeleton renders with aria-busy under a slowed roster; no console errors
  • Reviewer: open a room — every participant gets a row with "nothing in flight"; the 1 running badge appears while an agent is working (rides the room's working list)

Fixes #2540
Part of abilityai/trinity-enterprise#474

🤖 Generated with Claude Code

https://claude.ai/code/session_01FgAncnTAbgjJS3EGwuEtd4

…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 vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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