technical-plan.md
c21 Technical Plan — Inventory + Users / Devices
Audience: AI agents executing this cycle in bob-wire Source of truth for content: design-brief.md — settled decisions in §2 are constraints; open questions in §6 may be explored but must be annotated on the wireframe, not silently decided.
Venue change (2026-09-01): execution moved hi-fi-first into
../designapp/pages/pages/(Mark's call). Three pages —inventory-companies-v1,users-devices-v1,user-detail-v1— on shared fixtures../design/app/data/c21-inventory-fixtures.ts, registered in design'sapp/data/page-registry.ts(groupsinventory/teams). The Fleet Overview page is dropped: design c78'sfleet-overview-v3owns the landing thread; c21 widget ideas feed it. The bob-wire wf1 plan below stands as reference if a lo-fi pass is ever wanted, but is not the active path.
Scope
wf1 wireframe iteration: a clickable prototype of the restructured qcontrol inventory area — Fleet Overview entry widgets, the Inventory page (technology view), the Users / Devices page (people view), and one user-detail drill. Lo-fi, Wire* components, layout: 'wireframe'.
Out of scope for wf1: enforcement UI (account blocking is shown as visibility + an annotated "next step" affordance only), governance/compliance pages, the gamified adoption checklist (parked idea — at most a placeholder widget slot on Fleet Overview), mockups (m1 comes after Thursday-call feedback).
Pages
All under app/pages/c21/wireframes/wf1/inventory-v1/:
| Page | File | Content |
|---|---|---|
| Fleet Overview | fleet-overview.vue | Entry-story widgets: agent visibility (unique agents, monitored %), team deployment status (coverage % vs identity source of truth), token usage per model per period, accounts posture (enterprise vs personal count). Each widget click-navigates to its page. |
| Inventory | inventory.vue | Company→technology→instance rollup (Anthropic → Claude Code/Claude Desktop → instances), monitored vs detected-only, models directory w/ usage trends (tokens/$ toggle), accounts & licensing panel (enterprise/personal/unknown flagged), surface directories (MCP servers, tools, plugins, skills, repos, external services). User filter chip appears when arriving via drill-through. No machines, no sessions, no per-user rows, no risk posturing. |
| Users / Devices | users-devices.vue | Coverage header: sensor deployment status vs definitive source of truth (identity/MDM integration state shown, e.g. "Okta connected · Jamf connected · 47 known workstations"), actionable uncovered list ("Alexis — no sensor — investigate"). User/team table: agents used, provider mix, account type (corporate/personal), tokens/$ (toggle), intensity, one-line LLM summary. Rows drill to user detail. Agent-attribute filters mirror Inventory's user filter. |
| User Detail | user-detail.vue | One user: devices → agents on each (relationship chain workstation ↔ identity ↔ agent kept visible), account usage incl. any personal accounts, usage trend, LLM-derived work summary + concrete tool-calling surface (MCP servers, repos), and sessions list (sessions live here, not in Inventory). Links back to Inventory pre-filtered ("View in Inventory"). |
Cross-links to implement (the call's drill paths):
- Users / Devices → user → click an agent (e.g. Claude) → Inventory with
?user=tyler&agent=claudefilter chips shown. - Inventory → account panel "used by 3 people" → Users / Devices filtered.
- Fleet Overview widgets → respective pages.
- Include an unknown/unattributed identity row somewhere (brief §6, identity attribution edge case).
Data
Shared sample-data module: app/data/c21-inventory.ts. Shapes:
Company→technologies[]→instances[](instance: user, device, monitored: boolean, account ref)Account { provider, accountId, licensing: 'enterprise' | 'personal' | 'unknown', users[] }Person { name, team, devices[], sensorInstalled per device, source: 'okta' | 'manual', tokens, dollars, intensity, summary, surface: { mcpServers[], repos[] }, sessions[] }UsageTrendPoint { period, model, tokens, dollars }— enough periods to show the Anthropic→OpenAI→Grok drift story from the call
Realistic names/numbers; include at least: one unmonitored-but-detected agent (Hermes — the call's example of a surprise), one personal ChatGPT account on a named user, several people with no sensor installed, one unattributed agent instance.
Conventions checklist
- Wire* components only; grey borders, dashed placeholders, no shadows;
.wire-*classes ok.WireViewTogglefor tokens/$ and monitored/all toggles;wire-annotationfor open-question callouts. definePageMeta({ layout: 'wireframe' })on every page.- Register all four pages in
app/data/wireframe-registry.ts:type: 'wireframe',iteration: 'wf1', featureinventory-v1. - Create
app/pages/c21/index.vuecycle gallery (copy c16/c20 pattern) and add c21 to the root TOC inapp/pages/index.vue. - Copy
design-brief.mdinto the feature dir for/api/specs/colocation (per brief-colocation convention). - Annotate, don't decide: where the brief lists open questions (agent vs harness label, page name, fleet definition), pick a working label and mark it with a
wire-annotation(e.g. "label TBD: agents vs harnesses").
Execution phases
- Phase 0 (done): cycle records, design brief, this plan.
- Phase 1 — wf1 build:
/wireframe(wireframe-builder) againstdesign-brief.md, four pages above. Builders write .vue files; batch registry entries after parallel runs complete (established parallel-run convention). - Phase 2 — flow:
/flowto wire the drill paths and filter-chip navigation. - Phase 3 — review:
/review inventory-v1against the brief; fix conformance gaps. - Phase 4 — feedback loop: present at the Thursday follow-up (Mark Peterson product-spec session); capture deltas as an update brief via
/write-brief, then/iterate→ wf2, or promote to m1 mockups if structure holds.
Product-side notes (not bob-wire work — for the humans)
Captured from the call so they aren't lost; none block the wireframes:
- Windows installer/tapping (Kamal) blocks Mark Peterson self-testing; macOS-only today.
- Centralize the control plane (Docker + Postgres) vs current local-only mode; wireframes assume centralized mode, with local mode noted in brief §2.10.
- OpenFlow's provider mapping should be corrected to OpenAI in the real data.
- Identity attachment is "one of the simplest things we do" per Tyler once an identity integration exists — wireframes can assume identity data is available when integration is connected.
- Data model already holds provider; confirm it represents the company→technology→instance hierarchy the UI now needs.
- Summarizer model required for per-user work summaries (customer supplies an LLM endpoint).