feat(ui): dashboard Grid view — magnetic tile canvas third mode (abilityai/trinity-enterprise#47) - #1475
Conversation
…ityai/trinity-enterprise#47) Adds a Grid mode to the Dashboard alongside Graph and Timeline (not the default; selection persists to localStorage). Implements the approved design of record: five-zone 384x216 agent tiles on a sparse, unbounded pan/zoom lattice with iPhone-style drag, live socket preview, swap-with-preview, tidy/reset, and keyboard reorder. - FleetGrid.vue: pan/zoom viewport (0.25-1.6x around cursor, world-space dots), drag physics (1:1 zoom-aware follow, velocity tilt, overshoot spring + lock pulse), viewport culling, prefers-reduced-motion, multi-touch pointer discrimination - AgentTile.vue: identity (half-out avatar) / adaptive chip strip with live working timer / Activity-14d stacked-by-trigger + Context-7d trend charts / success micro-meter / Run+Auto toggles; composes the existing AgentAvatar, RuntimeBadge, RunningStateToggle, AutonomyToggle; system agent keeps its purple treatment - fleetGrid store: per-user self-healing layout (localStorage v1; filters never destroy hidden tiles' positions), lazy analytics hydration (viewport-gated, concurrency-capped, stale-while-revalidate over the executions store cache), batch chip data (sync-health, operator-queue) on a visibility-aware poll active only while the Grid is mounted - network store: 3-state viewMode (legacy values migrate; Timeline stays default), circuitBreakers map, WS-driven workingState map (reconciled by the context-stats poll), agent_started/agent_stopped WS routing, toggles now update both render surfaces (agents[] and graph nodes) - No new backend endpoints; charts read the existing #1107 analytics - Playwright e2e: mode toggle/persistence, tile render, drag+persist, tidy/reset, Graph+Timeline coexistence (6 passing) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ts (abilityai/trinity-enterprise#47) - Tile depth: layered box-shadow (top-edge glass highlight + contact + key + soft ambient falloff, per-theme) with a deeper hover stack, and a subtle top sheen gradient; system tile keeps its purple background under the sheen - Sparse data: activity chart renders a faint baseline stub for each empty day so 1-2 runs read as a 14-day rhythm instead of a lone floating bar; empty context state anchors its baseline at the bottom (mid-height read as a divider) and shows an em dash instead of "0%" when nothing is tracked yet - System agent now carries the calm schedules chip too — an empty chip row left a visual void on the tile Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves the js/incomplete-url-substring-sanitization alert on PR #1475: `repo.includes('github.com/')` matches the token anywhere in a URL, so a crafted value like https://evil.com/github.com/x would be mis-parsed. Display-only, but parse properly anyway: URL() + exact hostname check; non-URL "owner/repo" values fall through unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| async function fetchOpQueuePending() { | ||
| try { | ||
| const res = await axios.get('/api/operator-queue', { | ||
| params: { status: 'pending', limit: 200 }, |
There was a problem hiding this comment.
Silent cap on chip data. limit: 200 on the pending operator-queue fetch means on a fleet with >200 pending items the oldest-created rows drop off the page, so some agents' needs response/approval pending chips silently disappear even though the items exist. The PR description prides itself on "no silent caps" elsewhere — either paginate, or at minimum this is worth a note. Low severity.
| const contextSeries = computed(() => { | ||
| const timeline = analytics.value?.timeline || [] | ||
| return timeline | ||
| .slice(-7) |
There was a problem hiding this comment.
Single day of context history renders as empty. series.length < 2 returns null → the tile falls through to the ctxflat empty-state even when there is one day of context_avg data. A brand-new/low-traffic agent with exactly one tracked day shows "no data" despite having data. Consider rendering a single dot (or a flat 1-point line) when length === 1. Low severity / UX.
| function moveTile(name, c, r) { | ||
| const from = layout.value[name] | ||
| if (!from) return | ||
| const occ = occupantAt(layout.value, c, r, name) |
There was a problem hiding this comment.
Swap ignores filter-hidden occupants. occupantAt scans layout.value, which holds only the currently visible agents. With an owner/tag filter active, dropping a tile onto a cell occupied by a hidden agent won't swap — _persist then merges both onto the same (c,r) in _savedRaw. No data loss (the next unfiltered normalizeLayout relocates one to the nearest free cell), but it surprises the user by moving a tile they never touched, which slightly undercuts the "filter-safe layout" guarantee. Low severity / edge.
dolho
left a comment
There was a problem hiding this comment.
Reviewed the Grid view end-to-end (new files + network.js diff). Solid work — drag lifecycle, pointer-capture/multi-touch discrimination, prefers-reduced-motion, mid-drag roster-removal cleanup, WS-vs-poll working-state reconciliation, and mode-scoped teardown (v-if) all look correct, and the working-state SET path (agent_activity started → handleActivityStatusChange → _updateWorkingState) is wired. Owner filter matches the graph's (a.owner || null) expression, and raw-axios + authStore.authHeader matches every existing store, so no invariant-#7 concern in practice.
A few low-severity items below — none blocking. Grouped as inline comments.
Also two non-anchored nits:
- Unbounded per-session growth:
analyticsState,_fetchedAt, and orphanedlocalStoragelayout entries are never pruned when an agent is deleted. Fine for a normal session; a long-lived tab with heavy agent churn slowly grows these. Reset clears layout but not the two in-memory maps. workingNowcomputed three times (DashboardworkingNowCount, storeworkingState, tileworkingInfo) — each re-derives "is this agent working" from the same two sources. Consider a single store getter to avoid drift.
Summary
prefers-reduced-motionhonored, system agent keeps its purple treatment.executionsstore(agent, window)cache with stale-while-revalidate; chip data via batch endpoints (/api/agents/sync-health, operator-queue pending) on a 60s visibility-aware poll that tears down when the mode is inactive (v-if); viewport culling for 50+ fleets. No new backend endpoints.Changes
src/frontend/src/components/FleetGrid.vue(new) — pan/zoom viewport + drag physics + cullingsrc/frontend/src/components/AgentTile.vue(new) — five-zone tile composing AgentAvatar / RuntimeBadge / RunningStateToggle / AutonomyTogglesrc/frontend/src/stores/fleetGrid.js(new) — per-user self-healing layout (localStorage v1; owner/tag filters never destroy hidden tiles' saved positions), hydration queue, batch chip datasrc/frontend/src/utils/gridLayout.js(new) — pure lattice math (spiral placement, normalize, tidy, bbox)src/frontend/src/stores/network.js— 3-stateviewMode(isTimelineMode→ computed; legacy saved values migrate; default unchanged),circuitBreakersmap, WS-drivenworkingStatemap (poll-reconciled), routes the backend's realagent_started/agent_stoppedevents, start/stop + autonomy toggles now update both render surfaces (agents[]+ graph nodes)src/frontend/src/views/Dashboard.vue— 3-way mode toggle, grid pane with skeleton/error/empty states, "N working now" header stat, Tidy up / Reset controlssrc/frontend/e2e/dashboard-grid-view.spec.js(new) — 5 specs incl. 2 @smokecore-agent.md),feature-flows/dashboard-grid-view.md+ index,architecture.mdfrontend sectionReview
Independent pre-landing review ran over all new files + the diff; all 8 findings fixed, including two that would have shipped broken UX: (1) tile Run/Auto toggles silently reverting because store toggles only mutated graph
nodes[], notagents[]; (2) owner/tag filters permanently erasing hidden agents' saved layout (fixed via merge-on-persist). Also fixed: hydration in-flight dedupe, multi-touch pointer discrimination, tidy bounds clamp, mid-drag roster-removal cleanup, zero-movement snap-state leak, stale-poll eviction of fresh WS working entries.Test Plan
npx playwright test dashboard-grid-view— 6 passed (mode toggle + tile zones, persistence across reload, drag + socket + layout persist, tidy/reset, Graph/Timeline coexistence)@smokesuite — 14 passed, 0 failed (pre-existing conditional skips only)vite build); design-token check OKOut of scope (per issue)
Fleet KPI strip; "Needs your attention" + live-activity right rail — tracked as follow-up issues.
Fixes abilityai/trinity-enterprise#47
🤖 Generated with Claude Code