Skip to content

feat(ui): dashboard Grid view — magnetic tile canvas third mode (abilityai/trinity-enterprise#47) - #1475

Merged
dolho merged 3 commits into
devfrom
feature/47-grid-dashboard-view
Jul 6, 2026
Merged

feat(ui): dashboard Grid view — magnetic tile canvas third mode (abilityai/trinity-enterprise#47)#1475
dolho merged 3 commits into
devfrom
feature/47-grid-dashboard-view

Conversation

@vybe

@vybe vybe commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a Grid mode to the Dashboard alongside Graph and Timeline (mode toggle → Grid / Graph / Timeline; not the default — Timeline stays default; choice persists to localStorage). Implements the design of record embedded in abilityai/trinity-enterprise#47 as-is.
  • Magnetic tile canvas: five-zone 384×216 agent tiles (half-out avatar identity, adaptive chip strip with live working timer, Activity·14d stacked-by-trigger + Context·7d trend charts, success micro-meter + stats, Run/Auto toggles) snapping to a sparse unbounded pan/zoom lattice — iPhone-style drag with socket preview, occupied-cell swap with live preview, Tidy up / Reset, keyboard arrow reorder, prefers-reduced-motion honored, system agent keeps its purple treatment.
  • Performance-first per the issue's AC: skeleton-first render from the agents list already in the store; per-tile analytics hydrate lazily (viewport-gated, concurrency-capped 4) into the existing executions store (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 + culling
  • src/frontend/src/components/AgentTile.vue (new) — five-zone tile composing AgentAvatar / RuntimeBadge / RunningStateToggle / AutonomyToggle
  • src/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 data
  • src/frontend/src/utils/gridLayout.js (new) — pure lattice math (spiral placement, normalize, tidy, bbox)
  • src/frontend/src/stores/network.js — 3-state viewMode (isTimelineMode → computed; legacy saved values migrate; default unchanged), circuitBreakers map, WS-driven workingState map (poll-reconciled), routes the backend's real agent_started/agent_stopped events, 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 controls
  • src/frontend/e2e/dashboard-grid-view.spec.js (new) — 5 specs incl. 2 @smoke
  • Docs: requirements §9.8 (core-agent.md), feature-flows/dashboard-grid-view.md + index, architecture.md frontend section

Review

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[], not agents[]; (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

  • Playwright e2e: npx playwright test dashboard-grid-view — 6 passed (mode toggle + tile zones, persistence across reload, drag + socket + layout persist, tidy/reset, Graph/Timeline coexistence)
  • Existing @smoke suite — 14 passed, 0 failed (pre-existing conditional skips only)
  • Production build clean (vite build); design-token check OK
  • Verified live on localhost: light + dark, drag/swap/keyboard/zoom/tidy/reset, toggle state persistence, zero console errors; Graph and Timeline unchanged

Out 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

…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>
Comment thread src/frontend/src/components/AgentTile.vue Fixed
Eugene Vyborov and others added 2 commits July 6, 2026 12:50
…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 },

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.

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)

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.

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)

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.

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

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 startedhandleActivityStatusChange_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 orphaned localStorage layout 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.
  • workingNow computed three times (Dashboard workingNowCount, store workingState, tile workingInfo) — each re-derives "is this agent working" from the same two sources. Consider a single store getter to avoid drift.

@dolho
dolho merged commit 4a816ae into dev Jul 6, 2026
20 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.

3 participants