feat(grid): widget chassis — InfoTile, GRID_WIDGETS registry, layout v2, Tiles menu (ent#325) - #2042
Open
dolho wants to merge 1 commit into
Open
Conversation
…v2, Tiles menu (ent#325) The frontend half of epic ent#94's foundation. Until now every tile sub-issue (#95-#101, #259) was unclaimable, because the first person to pick one had to build the whole chassis off-issue. Built ON TOP of the org overlay (ent#305, PR #1918) rather than beside it, per the issue's sequencing note — this branch forks from feature/305-grid-org-overlay, not dev. ## Info tiles are occupants of the same lattice Widgets join the SAME layout map under `widget:*` keys rather than a second map. `:` cannot appear in an agent name (sanitize_agent_name strips it), so collision is impossible and `isWidgetKey` is exact. The payoff: drag, swap-with-preview, keyboard reorder, viewport culling and the snap socket all work for tiles through the existing `data-agent` path, with no second code path to keep in sync as those behaviours evolve. ## Org-overlay interlock (the load-bearing part) A. `tidyByDept` and `arrangeByDept` rebuilt a fresh map keyed only from `agents`, and `applyLayout` replaced the layout wholesale — so one "Tidy up" or "Group by dept" deleted every tile position. Fixed at both ends per the issue: both helpers now carry `widget:*` keys through (seeded first, so department blocks flow around them via nearestFreeCell), AND applyLayout merges instead of blind-replacing. Adding nearestFreeCell to arrangeByDept is behaviour-preserving with no widgets present — every requested cell is free — which a test pins, since that equivalence is the whole safety argument. B. `computeZones` iterates agents, so tiles are excluded from hulls by construction. Left alone deliberately; a test pins that a tile parked beside a department does not stretch its frame. C. The peg overhang is 16px, inside the GAP_X budget of 40, so it cannot collide with zone chrome. D. Seeding consults `zoneAt` and skips cells inside a department frame — rows -3..-1 are empty of tiles but not necessarily of hulls. ## Layout v2 `trinity-grid-layout-v2` with a one-time COPY from v1 (v1 left in place, so a downgrade is not data loss). `normalizeLayout` gains a `widgetKeys` argument; without it, widget entries were dropped on every reconcile — pinned as a test so the regression stays visible. Agents are reconciled first, so a collision evicts the tile rather than moving a constellation the user arranged. Default placement is the band above the fleet (rows -3..-1), which is what makes "default-on tiles appear without moving any saved agent position" true by construction: defaultLayout starts agents at r=0. ## Tiles menu Per-user show/hide, persisted under its own key so it can never clobber the overlay's Zones/Lines prefs. Preferences are a sparse OVERRIDE map, not an allow-list — so a tile added in a later release appears for existing users instead of staying invisible behind a stale list, and Reset is just an empty map. Lives in the bottom control cluster rather than as a fifth header button, per the issue's responsive-ladder note. ## Registry `utils/gridWidgets.js` stays free of `.vue` imports so vitest's node environment can test it (gridOrg imports isWidgetKey from it, so a component import would have broken the existing gridOrg.spec.js too); catalog entries live in `components/tiles/catalog.js`. `cells` is declared and ignored — multi-cell occupancy is #94's stated v2. Ships ONE reference tile (Fleet summary) reading the agents array the grid already holds and issuing no request, so the chassis has no data dependency of its own and could not be blocked behind ent#326. The epic's own ACs require a default-on tile to render, so an empty catalog would have made half of them unverifiable. ## Tests `tests/unit/gridWidgets.spec.js` — 28 tests: key namespace, registry (incl. adminOnly never enabled for a non-admin even if prefs say so), override-map semantics, seeding band + veto, normalize round-trip (both directions), the tidy/arrange preservation rule, and the zone-hull exclusion. 55/55 pass across all three specs — #1918's 27 unbroken. Related to Abilityai/trinity-enterprise#325 Co-Authored-By: Claude Opus 5 (1M context) <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.
Related to abilityai/trinity-enterprise#325 · foundation for epic ent#94
What
The widget chassis every tile sub-issue on ent#94 (#95–#101, #259) is blocked on. Until now none of them was claimable, because whoever picked one first had to build the whole chassis off-issue.
InfoTile.vue— the tile shellGRID_WIDGETSregistry +widget:*key namespaceInfo tiles are occupants of the same lattice, not a parallel system
Widgets join the same layout map under
widget:*keys.:cannot appear in an agent name (sanitize_agent_namestrips everything outside[A-Za-z0-9_-]), so collision is impossible andisWidgetKeyis exact rather than a heuristic.The payoff is that drag, swap-with-preview, keyboard reorder, viewport culling and the snap socket all work for tiles through the existing
data-agentpath — no second code path to keep in sync as those behaviours evolve. Keyboard came free:@keydownis on the world container and resolves throughlayout[name].Deliberately absent from
InfoTile: Run/Autonomy toggles (info tiles summarize, they don't operate — a fleet-scope operating control makes a misclick fleet-wide), and any org affordance. No connect port, so a tile cannot be a reporting-line endpoint; that is enforced by omitting the hover handlersAgentTileuses, not by a runtime check.Org-overlay interlock — the load-bearing part
A. Tidy and Group-by-dept would have silently deleted every tile position. Both
tidyByDeptandarrangeByDeptbuild a fresh map keyed only fromagents, andapplyLayoutreplaced the layout wholesale. Fixed at both ends as the issue asks: the helpers carrywidget:*keys through (seeded first, so department blocks flow around them vianearestFreeCell), andapplyLayoutmerges instead of blind-replacing. Either alone is a single point of failure, and one-guard-only is how this bug existed in the first place.Adding
nearestFreeCelltoarrangeByDeptis behaviour-preserving when no widgets are present — every requested cell is free, so placement is unchanged. There is a test pinning that equivalence, because it is the entire safety argument for touching that function.B. Widgets stay out of zone hulls — left alone deliberately.
computeZonesiteratesagents, so awidget:*key is excluded by construction. Not "fixed" by iterating the layout map; pinned by a test asserting a tile parked beside a department does not stretch its frame.C. Gap budget. The peg overhangs 16px, inside
GAP_X(40), so it cannot collide withZONE_CHROME.D. Seeding consults
zoneAtand skips cells inside a department frame — rows −3…−1 are empty of tiles but not necessarily of hulls.Layout v2
trinity-grid-layout-v2, migrated by a one-time copy from v1 — v1 is left in place, so downgrading is not a data-loss event.normalizeLayoutgains awidgetKeysargument. Without it everywidget:*entry was dropped on the next reconcile; that is pinned as a test so the regression stays visible rather than becoming folklore. Agents reconcile first, so a collision evicts the tile — an info tile moving one cell is a smaller surprise than a constellation the user arranged shifting under them. Thechangedcheck now covers widgets too, or every reconcile would re-persist localStorage forever.Default placement is the band above the fleet, which is what makes "default-on tiles appear without moving any saved agent position" true by construction:
defaultLayoutstarts agents atr=0and grows downward, so negative rows are unclaimed on every existing install.Tiles menu
Per-user, persisted under its own key so it can never clobber the overlay's Zones/Lines prefs. Preferences are a sparse override map, not an allow-list — so a tile added in a later release appears for existing users instead of staying invisible behind a stale list, a user can keep a default-on tile off, and "Reset" is just an empty map. Placed in the bottom control cluster rather than as a fifth top-level header button, per the issue's responsive-ladder note.
Registry, and why the catalog lives elsewhere
utils/gridWidgets.jsstays free of.vueimports so vitest's node environment can test it — and becausegridOrg.jsnow importsisWidgetKeyfrom it, a component import there would have broken #1918's existinggridOrg.spec.js. Catalog entries live incomponents/tiles/catalog.js, imported byFleetGridfor its side effect. Adding a tile is oneregisterWidget({...})block plus the component.cellsis declared and ignored: v1 renders single-cell only, multi-cell occupancy is #94's stated v2. Declaring it now means catalog entries don't need rewriting whengridLayout.jslearns occupancy.One reference tile ships (Fleet summary): it reads the
agentsarray the grid already holds and issues no request, so the chassis carries no data dependency of its own and could not be blocked behind the sibling ent#326 endpoint. This is not scope creep — the epic's ACs require a default-on tile to render, so an empty catalog would leave half of them unverifiable. Real tiles arrive one sub-issue at a time.Tests
tests/unit/gridWidgets.spec.js— 28 tests: key namespace (incl. that no agent-shaped name reads as a widget), registry, anadminOnlytile never enabled for a non-admin even when prefs say on (prefs survive a role change; the catalog filter has to be what decides), override-map semantics incl. a newly-registered default-on tile reaching a user with existing prefs, seeding band + veto, normalize round-trip in both directions, the tidy/arrange preservation rule, and zone-hull exclusion. One guard asserts stray non-widget keys are not carried, so the carry-through can't drift into "preserve anything unknown" and resurrect a deleted agent forever.Verification note, stated honestly
npm run buildandnpm run test:unitcannot run against this machine'snode_modules— it predates #1918 (novitest) and is missingmermaid/qrcode. Both were run out-of-band instead: vitest standalone with an equivalent inline config (the 55 above), and a productionvite buildwith the stale deps externalized, which transformed 638 modules clean — covering every file in this PR. CI runs both properly.Not in scope
Multi-cell tiles (#94 v2), agent-declared custom tiles (#94 v2, unfiled), and the actual data tiles (#95–#101, #259).
🤖 Generated with Claude Code