Skip to content

fix(workspace): calm sidebar first load; rail tabs reordered, Asks always present (#3672, ent#892) - #3682

Merged
vybe merged 5 commits into
devfrom
feature/3672-workspace-calm-load
Oct 13, 2026
Merged

vybe merged 5 commits into
devfrom
feature/3672-workspace-calm-load

Conversation

@trinity-ability

Copy link
Copy Markdown
Contributor

Closes #3672. Implements abilityai/trinity-enterprise#892 (private tracker; closing keywords don't cross repos, so close it manually at release).

#3672 — the Workspace sidebar holds its layout while it loads

Cause. The roster, the chat list and the asks feed land at different times, and each one re-laid the list:

  • agent rows re-sorted and grew a subtitle when the chats arrived;
  • an agent with an ask was appended past the top-5 limit when the asks arrived;
  • the chat groups popped in;
  • the Projects link appeared after the roster and pushed the search box and both lists down.

Fix.

  • sidebarSettled (pure, portalUtils.js) is true once bootstrap has read the roster and the chats (success or failure) and the asks feed has a verdict (loaded, 404/403, or failed). It is keyed on state, never a timer, and can't hang. Portal.vue latches it as sidebarReady and resets it only when the session ends, so a background refresh never brings the skeleton back.
  • Until then, both sidebar sections show skeleton rows (new PortalSkeleton variants sidebar-agents / sidebar-chats). A platform session also holds the Projects link's 36px slot (sidebar-link).
  • Once settled, both sections render in one pass inside one .ws-fade-in: a 180ms opacity fade, off under prefers-reduced-motion.
  • An empty chat list keeps a stable empty state: "No chats yet — open an agent above, or start a new chat."

Measured with Playwright, cold /workspace load, 3 runs each, fast and with roster, chats and asks delayed by different amounts:

Cumulative layout shift
Before (dev) 0.008
After 0.0003

The remaining 0.0003 is in the Inbox pane header, not the sidebar. Layout-shift entries only count existing elements that move, not content that pops in, so the "before" number understates the jump people actually see.

ent#892 — rail tabs reordered; Asks always present

  • The order is now Info · Canvas · Files · Projects · Asks · Work · Loops (RAIL_TAB_ORDER), and its comment states the new reason.
  • Asks is always present (operator ruling 2026-10-13). ent#836's presence rule and asksTabPresent are removed, and tabPassesDoor no longer has a presence check.
  • The registry entry now declares an empty state: "No asks", "Nothing from agent is waiting on you…", and an Open the Inbox button that goes to the agent's asks in the Inbox.
  • PortalRailAsks owns all four states via railAsksView: a skeleton before a verdict, LoadFailed for a failed first read, the empty state, and the rows. The "needs you" mark still appears only when an ask is waiting.
  • Work is still the default tab, and a remembered tab still wins. ⌥. walks the new visual order.

Behaviour notes for the reviewer

  • Client fallback tab. An outside client can't see Work, so the rail opens on the first tab it can show. That is now Info instead of Canvas.
  • Open-rail overflow. With the rail open on a desktop width, only Info, Canvas and Files fit; Projects, Asks, Work and Loops go under "More". So the default tab (Work) and waiting asks only show as a dot on "More". The overflow behaviour is existing; this order makes it hit these tabs. Worth a look if Asks should sit earlier.

Tests

  • New: portalSidebarCalmLoad.mount.spec.js (11 tests; mounted, jsdom).
  • Extended: portalRailAsks.spec.js and portalRailAsks.mount.spec.js (always present, order, empty copy, ⌥. walk, the verdict, the empty / skeleton / failed states mounted).
  • Order pins in portalRail.spec.js updated as the issue asks. Two source-text pins (portalRosterRow.spec.js, portalSidebarSearch.spec.js) now point at the new sidebar gate.
  • Mutation check: with PortalSidebar.vue reverted, 5 of the 11 new sidebar tests go red. With portalRail.js reverted, 5 of 14 portalRailAsks.spec.js tests go red. Both files were restored byte-identical.
  • npm run test:unit: 5772 passed. vite build OK.
  • Raw-colour ratchet: PortalSidebar.vue's gray count dropped 35 → 29; its baseline entry was lowered by hand so the bug(ci): the raw-colour ratchet is documented as enforced but nothing runs it, and the tree has drifted past its baseline #2605 re-freeze record stays intact.

Verification

  • /verify-local --skip-agent --skip-unit:

    Stage Result
    build + import smoke pass
    boot + health pass
    test preflight pass
    integration 68 passed, 1 failed

    The one failure is integration/test_circuit_breaker.py:395, a timing assertion (expected a ≥0.4s gap, measured 0.19s) on a host running several other test jobs. This PR changes no backend code.

  • Backend unit suite not verified. On the first attempt it froze at 97% on a backend test, so it was skipped on the rerun.

  • Production frontend image served against the local dev stack and checked by eye:

    • the cold-load filmstrip;
    • an agent page with asks waiting (the asks feed was substituted in the browser, because a real Workspace ask needs a roster share);
    • an agent and a room with no asks;
    • both themes, and 390px phone width.

Docs

🤖 Generated with Claude Code

vybe and others added 5 commits October 13, 2026 15:05
…l tabs, Asks always present (#3672, ent#892)

#3672 — the Workspace sidebar's agent and chat lists jumped on a cold load.
The roster, the chat list and the asks feed land at different times, and
each re-laid the list: agent rows re-sorted and grew a subtitle when chats
arrived, an asked agent was appended past the collapse limit when asks
arrived, chat groups popped in, and the Projects link (its capability rides
the roster) pushed the search box and both lists down.

- `sidebarSettled` (portalUtils, pure): bootstrap placed the caller AND the
  asks feed has a verdict (loaded / 404-403 / failed) — state, never a timer.
  Portal.vue latches it (`sidebarReady`), resetting only when the session ends.
- PortalSidebar: both sections hold skeleton rows (new PortalSkeleton
  `sidebar-agents` / `sidebar-chats` variants) until settled, then render in
  one pass inside one `.ws-fade-in` (180ms opacity, off under reduced motion).
  A platform session holds the Projects link's slot (`sidebar-link`). An empty
  chat list keeps a stable "No chats yet" state.
- Measured (Playwright, cold /workspace, 3 runs, fast and with staggered
  latency): CLS 0.008 -> 0.0003; the residual is in the Inbox pane header.

Mutation check: with PortalSidebar.vue reverted, 5 of the 11 tests in
portalSidebarCalmLoad.mount.spec.js go red; restored byte-identical.

trinity-enterprise#892 — the agent rail's tabs read Info, Canvas, Files,
Projects, Asks, Work, Loops. Asks is always present (operator ruling
2026-10-13): ent#836's presence rule and `asksTabPresent` are removed, the
registry entry declares an empty state ("No asks" / "Open the Inbox"), and
PortalRailAsks owns all four states via `railAsksView` (skeleton before a
verdict, LoadFailed, empty, rows). Default tab stays Work; a remembered tab
still wins; ⌥. walks the new order. With portalRail.js reverted, 5 of 14
portalRailAsks.spec.js tests go red.

Order-pin specs updated per the issue; two source-text pins moved to the new
sidebar gate; PortalSidebar's raw-gray ratchet entry lowered 35 -> 29.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `sidebar-link` skeleton held a 36px slot for a platform session until
the sidebar settled, then vanished on every install not entitled to
Projects (all OSS installs) - a new layout shift to spare a late pop-in on
the few entitled ones. Operator ruling: no hold. The link renders once the
roster says `projectsAvailable`; the unused `sidebar-link` variant is
removed, the spec asserts nothing sits between New chat and the search box
before the verdict, and the feature flow no longer promises a held slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the first `fetchAllSessions` fails, the store returns its last good
list - `[]` - and the sidebar told the person they had no chats. Portal.vue
now passes `:threads-failed="store.sessionsFailed"` (the same signal the
Inbox reads) to both sidebars. The empty copy renders only for a trusted
list; a failed read shows an InlineError instead (the stale banner over
rows, "Couldn't load your chats" over none) with a retry wired to
`refreshThreads`, dismissible and back on the next failure. Mount tests
cover all four cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PortalRailAsks read `view.state` but never `view.stale`, so a failed asks
refresh left the last list (or the empty state) on screen with no word of
it. It now raises the stale InlineError the way PortalRailCanvas,
PortalRailFiles and PortalLoops do ("Couldn't refresh your asks - showing
data from ..."), with a retry and a dismiss that re-arms on the next
failure. The two `./portalUtils` imports are merged into one. Mount tests
cover the banner over rows, over the empty state, and its absence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…3672)

Binding `:settled="true"` in Portal.vue left the suite green: the calm-load
spec mounts PortalSidebar alone. portalSidebarLatch.mount.spec.js mounts the
shell (shallow, the portalAgentLandingStage harness) with the asks read held
open and checks both sidebars hold `settled=false` until the verdict, latch
once, stay latched when the verdict's inputs drop on a later refresh, and
reset on sign-out so the next session's first load holds again.

Mutation check: `:settled="true"` -> 3 of 4 red; dropping the sign-out reset
-> 1 red; replacing the latch with a plain mirror -> 1 red; restored
byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vybe

vybe commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

merge-train: pushed four mechanical commits to this branch (head 83da10a), per the operator's ruling on the train review:

  • 460f024: drops the Projects-link placeholder hold. On every install not entitled to projects (all OSS installs), the 36px placeholder appeared and then vanished, so the sidebar jumped up. The link now renders only once the roster says projectsAvailable, and entitled installs accept a late pop-in.
  • 7279319: no false "No chats yet". When the first sessions read fails, the sidebar shows an InlineError with a retry (store.sessionsFailed) instead of the empty state.
  • d2d5807: the Asks tab shows a stale InlineError when view.stale is set, matching Canvas, Files and Loops. Also merges the two portalUtils imports into one.
  • 83da10a: new portalSidebarLatch.mount.spec.js mounts Portal.vue and pins the latch and the sign-out reset. Under :settled="true", 3 of 4 tests go red.
    Frontend suite: 356 files / 5784 tests green, ratchets unchanged.

@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.

merge-train: batch validated on train/20261013-2303 (#3685)

@vybe
vybe merged commit 397903e into dev Oct 13, 2026
26 checks passed
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