feat(workspace): loops, canvas and files dock into the rail; the strip and the drawer are gone (abilityai/trinity-enterprise#475) - #2544
Merged
Conversation
…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
6 tasks
vybe
approved these changes
Sep 6, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
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
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>
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
PortalFilesPanel.vuedeleted), and the agent canvas gets its conversation-side placement as the Canvas tab — the sameCanvasPanel+ client-portal store the Workspace agent page uses, audience-narrowed by the ent#438 ruling. Old placements removed, not duplicated.composables/usePortalRailFeeds.js): feedsstores/portalLoops.jsand the newstores/portalRailFeeds.jsoff 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 undertrinity-workspace-rail-seen, compared as instants). Refresh is event-driven (turn end, room idle, loop events, terminalagent_activity, tab open, upload); no timer while idle.sm+, sheet below); "Ask for a canvas" pre-fills the composer in a conversation and a room, never sends;CanvasPanelre-reads blocks when the selected canvas'supdated_atmoves; 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,askCanvasPrefillsrc/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(railvariant),PortalFilesPanel.vue(deleted)src/frontend/src/views/Portal.vue— slots, feeds owner,openRailOn,askForCanvas;PortalConversation.vue/PortalRoom.vue— loops mount removed, room gainsprefill;components/canvas/CanvasPanel.vue— refetch onupdated_at;utils/websocket.js— third consumerdocs/memory/requirements/core-agent.md§5.20; feature flowsworkspace-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— greennpm run test:unit— 87 files / 1931 tests green;vite buildclean; raw-color counts unchanged on touched files; loading-gate baseline regenerated (the drawer's bare gate is gone)/loopsrequest (portalRailFeeds.spec.jsproves it under Pinia; worth one manual look with a portal token)Notes for review
/autoplantaste 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)./reviewcaught and fixed a watcher-ordering bug (arail.reset()in the chat-switch watch ran after the owner had re-scoped the stores); recorded indocs/memory/learnings.md.Fixes abilityai/trinity-enterprise#475
🤖 Generated with Claude Code
https://claude.ai/code/session_01PBTEMcpMsvPC5U4QnKevLC