Skip to content

feat(workspace): loops, canvas and files dock into the rail; the strip and the drawer are gone (abilityai/trinity-enterprise#475) - #2544

Merged
vybe merged 1 commit into
devfrom
feature/475-rail-rehome-tabs
Sep 6, 2026
Merged

vybe merged 1 commit into
devfrom
feature/475-rail-rehome-tabs

Conversation

@trinity-ability

Copy link
Copy Markdown
Contributor

Summary

  • Slice 2 of the conversation rail (ent#472): the loops strip above the composer becomes the Loops tab, the Files slide-over becomes the Files tab (PortalFilesPanel.vue deleted), and the agent canvas gets its conversation-side placement as the Canvas tab — the same CanvasPanel + client-portal store the Workspace agent page uses, audience-narrowed by the ent#438 ruling. Old placements removed, not duplicated.
  • One owner for what the tabs read (composables/usePortalRailFeeds.js): feeds stores/portalLoops.js and the new stores/portalRailFeeds.js off the rail's door gate and only while the rail is on screen — a session that fails a door never fetches that tab's data, and the collapsed rail signals with no body mounted. Loops = live; Canvas/Files = updated since last view (newest server timestamp vs a seen marker under trinity-workspace-rail-seen, compared as instants). Refresh is event-driven (turn end, room idle, loop events, terminal agent_activity, tab open, upload); no timer while idle.
  • Header paperclip opens the rail on Files (column at sm+, sheet below); "Ask for a canvas" pre-fills the composer in a conversation and a room, never sends; CanvasPanel re-reads blocks when the selected canvas's updated_at moves; the Files spinner becomes the rail skeleton keyed on the feed's verdict (AC 6 as amended 2026-09-06).

Changes

  • src/frontend/src/components/portal/portalRail.js — the three tabs registered; feedsFor, loopsSignalFrom, timestampMs/newestTimestamp/updatedSignal, seen-marker helpers, feedView, railOpenPlan/isWideViewport, askCanvasPrefill
  • src/frontend/src/stores/portalRailFeeds.js (new), src/frontend/src/composables/usePortalRailFeeds.js (new)
  • src/frontend/src/components/portal/PortalLoops.vue (tab body; no strip, no store ownership), PortalRailFiles.vue (new), PortalRailCanvas.vue (new), PortalSkeleton.vue (rail variant), PortalFilesPanel.vue (deleted)
  • src/frontend/src/views/Portal.vue — slots, feeds owner, openRailOn, askForCanvas; PortalConversation.vue / PortalRoom.vue — loops mount removed, room gains prefill; components/canvas/CanvasPanel.vue — refetch on updated_at; utils/websocket.js — third consumer
  • Docs: docs/memory/requirements/core-agent.md §5.20; feature flows workspace-rail.md (slice 2), workspace-loops.md, agent-canvas.md; docs/memory/learnings.md (watcher-order reset trap); docs/security-reports/cso-diff-2026-09-06-ent475-rail-rehome.{md,json}

Test Plan

  • npx vitest run tests/unit/portalRail.spec.js tests/unit/portalRailFeeds.spec.js tests/unit/portalLoops.spec.js tests/unit/portalLoadingTreatment.spec.js tests/unit/loadingGateRatchet.spec.js tests/unit/portalUndefinedCalls.spec.js — green
  • npm run test:unit — 87 files / 1931 tests green; vite build clean; raw-color counts unchanged on touched files; loading-gate baseline regenerated (the drawer's bare gate is gone)
  • Live on the Docker frontend: four tabs render for a platform session; Loops shows rows + Stop + form; Canvas empty → Ask for a canvas pre-fills the composer without sending; Files shows the send zone + both lists; collapse → paperclip reopens on Files; room: one rail, Send-to select, a section per participant, Loops grouped with absence visible; 390px: strip → sheet → Esc; rail state survives chat switches; zero console errors
  • Reviewer: an external-client session sees Canvas · Files only and issues no /loops request (portalRailFeeds.spec.js proves it under Pinia; worth one manual look with a portal token)

Notes for review

  • /autoplan taste decisions approved by the operator: seen markers under a second key (the approved {open, tab} key untouched); event-driven refresh with no idle poll; rail canvas audience = roster for every principal (ent#438's ruling, same as the agent page).
  • Deferred and registered in the debt inbox: a backend broadcast for canvas writes / shared files (external clients see a scheduled canvas rewrite at their next turn or tab open).
  • /review caught and fixed a watcher-ordering bug (a rail.reset() in the chat-switch watch ran after the owner had re-scoped the stores); recorded in docs/memory/learnings.md.
  • Cross-tracker: the closing keyword does not auto-close the private issue; ent#475 is closed at release.

Fixes abilityai/trinity-enterprise#475

🤖 Generated with Claude Code

https://claude.ai/code/session_01PBTEMcpMsvPC5U4QnKevLC

…p and the drawer are gone (abilityai/trinity-enterprise#475)

Slice 2 of the conversation rail (ent#472): the three placements #472 found
move into the ent#474 frame. The loops strip above the composer becomes the
Loops tab, the Files slide-over becomes the Files tab (PortalFilesPanel
deleted), and the agent canvas gets its conversation-side placement as the
Canvas tab — the same CanvasPanel and client-portal store the Workspace agent
page uses, audience-narrowed by the ent#438 ruling.

One owner for what the tabs read (composables/usePortalRailFeeds.js): it
feeds stores/portalLoops.js and the new stores/portalRailFeeds.js off the
rail's door gate (feedsFor over visibleTabs) and only while the rail is on
screen, so a session that fails a tab's door never fetches its data, a deep
link to an unreachable agent issues no request, and the collapsed rail can
signal with no body mounted. Loops = live (the store's active loops); Canvas
and Files = "updated since last view" — newest server timestamp per
participant vs a seen marker (epoch compare via parseUTC, Invariant #16),
persisted under trinity-workspace-rail-seen. Refresh is push/event-driven
(turn end, room idle, loop events, terminal agent_activity — debounced), on
tab open and after an upload; no timer while idle.

The header paperclip opens the rail on Files (column at sm+, sheet below,
never persisting open from a phone). "Ask for a canvas" pre-fills the
composer in a conversation AND a room; never sends. CanvasPanel re-reads
blocks when the selected canvas's updated_at moves, so a lit dot never opens
onto stale content. The Files spinner is replaced by a rail skeleton keyed on
the feed's verdict (AC 6 as amended 2026-09-06); LoadFailed / InlineError on
failure.

Review finding folded in: no rail.reset() in the shell's chat-switch watch —
watchers run in creation order and the owner had already re-scoped both
stores, so the reset wiped the new chat's data (docs/memory/learnings.md).

Tests: portalRail.spec (four-tab registry, doors → fetch, timestamps as
instants, updatedSignal, seen markers, feedView, railOpenPlan, removed
placements, owner wiring), new portalRailFeeds.spec (store + owner under
Pinia), portalLoops.spec re-pinned to the one owner, loading-treatment guard
extended, loading-gate baseline regenerated. Live pass on the Docker frontend:
four tabs, ask → prefill, paperclip → rail, room grouping, mobile strip →
sheet → Esc, zero console errors.

Fixes abilityai/trinity-enterprise#475

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBTEMcpMsvPC5U4QnKevLC

@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: closing keyword resolves to abilityai/trinity-enterprise#475 (P1 feature, cross-tracker → manual status bump), targets dev, 25 files, no secrets/emails/IPs/host paths/mode flips, frontend-only (no packaging surface), requirements §5.20 + three feature flows + CSO diff report in the diff, raw-color counts on touched files identical to dev, loading-gate baseline shrank by one. Local: 87 files / 1932 unit tests green, vite build clean. CI: all pytest seeds, e2e, journey-smoke green.

@vybe
vybe merged commit 6e5a60b into dev Sep 6, 2026
27 checks passed
vybe added a commit that referenced this pull request Sep 7, 2026
…et allowlists together, correct the docs that invented chart (#2110) (#2548)

* test(compat): pin the widget-type allowlists together; fix the docs that invented `chart` (#2110)

The dashboard widget-type set is hand-copied into three code sites (backend
`_WIDGET_TYPES`, agent-server `valid_types` — the gate that strips unknown
widgets — and the DashboardPanel.vue render chain) and five contract docs,
with no test tying any two together. Two user-docs pages advertised `chart`,
`badge`, `countdown` (none ever existed) and omitted `markdown`, `divider`,
`spacer`; both feed the docs Q&A index, which is the most plausible source of
a fleet generator's belief in `type: chart`.

New stdlib-only guard `tests/unit/test_2110_widget_type_parity.py`:
- Tier A: backend tuple == agent-server list (members AND order, read by AST
  in source order, single binding, no AugAssign/.append after definition)
  == the Vue `widget.type === '…'` branches.
- Tier B: five contract docs list exactly the backend set (extractors are
  `(text) -> set[str]` functions with named anchors that fail loudly).
- Tier C: regression signature — no "widget type(s)" line in docs/user-docs,
  the agent guide (Revision History sliced off) or the spec may backtick
  chart/badge/countdown.
- Meta: planted-violation tests prove every checker bites on every run.

Red before the docs fix (3 failed, 13 passed): the two user-docs pins and the
signature check, each naming file:line and token. Green after.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(compat): D-002/D-003 name the offending widget; D block survives real YAML; sink belt (#2110)

`D-002` failed with the bare string "unsupported dashboard widget type(s)":
the offending types were computed into `detail.types`, but the Overview panel
renders `c.message` only, so the operator saw a label with no cause and no
fix. The message now carries the specifics — and because `_check_dict` copies
it verbatim into the report dict, one string change reaches the UI row, the
`CompatibilityReport` JSON (and therefore MCP `get_agent_compatibility_report`),
and the persisted `checks_json`, with zero frontend/MCP/schema changes:

  unsupported dashboard widget type(s): 'chart' ×5 — not rendered; supported:
  metric, status, progress, text, markdown, table, list, link, image, divider,
  spacer (there is no chart widget — trend lines come from metric/progress
  history: give those widgets a stable id and the platform draws the sparkline)

Quoted names with counts, alphabetical, ≤5 named then "+N more", each clipped
to 40 printable chars BEFORE it becomes the counting key so message and
`detail` show the same bounded string; the trend hint only when `chart` is
among the bad types; the supported list generated from the tuple so it cannot
drift. `_WIDGET_TYPES` becomes a tuple in the agent-server's order (its sole
consumer is `c_d002`). D-003 (HARD, the identical "which widget?" pain) gains
the same shape after its unchanged prefix: "'list' needs items, 'text' needs
content".

Hardening against the YAML the D block actually receives (all reproduced at
HEAD by the new tests): `type: 5` beside `type: chart` raised in D-002's
set-sort, `type: {a: 1}` raised in D-003's `req.get` (a spurious HARD "check
could not be evaluated"), `color: 5` beside `color: teal` raised in D-005's
sort — and the hardened loader constructs `date` objects, so a progress widget
with `label: 2026-01-01` and an out-of-range value put a `date` into `detail`
and `json.dumps(checks)` in `upsert_result` raised, failing persistence of the
WHOLE 89-check report. D-004/D-005 keep their message text and gain bounded,
JSON-safe `detail`; `upsert_result` serialises with `default=str` as the belt
so one check can never take the report's persistence down again.

Tests: +13 in test_compatibility_checks.py (98 → 111); red before the change
was 11 failed / 3 passed (the two pins pass at HEAD by design).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(dashboard): the widget set is closed — spec prose, agent guide, requirements, area files (#2110)

- agent-validation-spec.md D-002: `Allowed types:` kept byte-identical (it is
  the parity anchor); "Unknown types are silently ignored by the UI" was
  false — the agent server strips the widget and the Dashboard tab lists it
  (a file with no `sections:` is rejected whole), and D-002 now names each
  offending type with its count. States the list is closed.
- TRINITY_COMPATIBLE_AGENT_GUIDE.md (served verbatim by MCP
  `get_agent_requirements` — the guide IS the MCP surface for the widget
  contract): closed-set paragraph after the Widget Types table (fictional
  names in plain prose, per the parity guard's authoring rule) + a Revision
  History row.
- requirements/content-files.md §13.4: the closed set spelled out, the
  no-chart/badge/countdown note, and the allowlist-parity bullet.
- architecture/agent-lifecycle.md: Checks bullet records that D-002/D-003
  carry specifics in `message` and the D-block detail/sink JSON-safety;
  `requirements §41` → `requirements/lifecycle-observability.md §42.1`
  (stale since the #1406 split).
- architecture/agent-runtime.md: `valid_types` is a semantic twin of
  `_WIDGET_TYPES`, parity-tested, change both together.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(feature-flows): sync compat-validation and agent-dashboard flows for #2110

- agent-compatibility-validation.md: one Backend bullet on the self-explaining
  D-002/D-003 message shape, the D-block hardening (non-string type/color,
  YAML dates) and the `default=str` sink belt; the new tests under Testing;
  both stale `requirements §41` pointers → `requirements/lifecycle-observability.md`
  §42.1 (post-#1406 split).
- agent-dashboard.md: the closed-set paragraph after the Widget Renderers
  table (parity guard; no chart/badge/countdown widget ever existed; trends via
  DASH-001 history on metric/progress widgets keyed by a stable id); the
  table's line numbers refreshed against DashboardPanel.vue at HEAD; a
  Revision History row.

No index change (feature-flows.md) — no flow was added or renamed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(compat): Tier-C signature also catches the YAML-key spelling of a fictional widget type (#2110)

Review-stage tightening of the parity guard's regression-signature check.
The token regex matched only a bare backticked `chart`/`badge`/`countdown`
on a "widget type(s)" line — the observed regression. The other way a doc
presents a name AS a type is the YAML-key spelling `type: chart`, which
escaped the guard. The regex now accepts an optional `type:` prefix inside
the code span; zero hits over the live corpus (139 user-docs pages, the
guide outside Revision History, the spec).

Deliberately NOT widened to "any code span containing the word": the spec's
D-002 prose quotes the check's own output (`… 'chart' ×5 …`) and must stay
truthful and un-flagged. The meta-test now pins both sides of that line —
a planted `type: chart` line is flagged, the quoted-output line is spared —
and the docstring's authoring rule names the YAML-key form.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(review): /cso --diff report and the YAML-natives-at-a-JSON-sink ledger entry (#2110)

Stage-4 review artefacts. The /cso --diff audit of the branch (same shape and
location as the #2544 precedent): no findings at the daily gate, four INFO
observations, every sink of the agent-authored string traced (text
interpolation, JSON, bound-parameter upsert) and the enterprise submodule
grepped read-only for consumers of the changed message text — none.

The learnings entry records the class the branch closed: the hardened YAML
loader hands checks `datetime.date` for unquoted ISO scalars, one such value
in a check's `detail` made `json.dumps(checks)` raise inside a best-effort
`except`, and the whole report silently stopped persisting. Written for the
next author of static checks over dashboard.yaml/template.yaml (#2527).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Eugene Vyborov <1073874+vybe@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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