Problem scope
Operator architecture critique, paired session 2026-08-22, on AgentOS.view.fleet.TasksPane and its siblings: "Pane violates neo naming conventions — Pane is not a base class. You chose a container with nested containers and components, not a buffered grid or list. This is a problem in other views too — ignoring what the neo component library offers. BUFFERED views matter: activity, memories, a2a messages, maybe more. Dropping everything into ONE fleet folder — camelCase and PascalCase wild mixture. Missing PLANNING."
The inventory (2026-08-22, apps/agentos/view/fleet/, 43 files: 27 PascalCase classes + 16 camelCase modules in one flat folder):
- The suffix lies about the base class. Twelve classes extend
Neo.container.Base and none is named …Container — ActivityStream, AgentCard, AgentDetail, CatchUpPane, FleetCockpit, FleetGrid (a container, not a grid), HealthBar, InstanceManager, MailboxPane, MemoriesPane, OperatorMailbox, WakeRoutePane. Seven extend Neo.component.Base without …Component (ActorChip, AgentConfigCard, EventChip, FamilyRail, HealthSwatch, SourceHealthMarker, StateDot). The only correctly-suffixed classes are the ones that consume library primitives (RecipientChipList, InstanceMenuList, InstanceSwitcher → Button, the two …Form containers).
- Data-carrying views hand-roll their rows. Activity events, agent and operator mailbox messages, session summaries/turns, catch-up entries, wake-route seats, the roster and the tasks rows are all rendered as nested containers/components from hand-mapped records — while
Neo.grid.Container (buffered via bufferRowRange, sorting, filtering), Neo.list.Base / Neo.list.Component (component items, list.plugin.Animate) and their extension points sit unused. The app-work gate's store-binding rule was applied to the data layer and ignored at the view layer: a Store exists, a primitive does not.
- State and logic live in the wrong layer.
FleetCockpit.mjs carries 4,018 lines of imperative plumbing — getMemoriesPane()?.set({agentOptions…})-style pushes, per-pane loaders, selection held as owner fields — where the engine offers hierarchical state.Providers (bindings) and per-view controller.Components. apps/portal/view/** is the in-repo precedent: a folder per surface, each with MainContainer + MainContainerController + MainContainerStateProvider, ten controllers, components named by base class.
- The mechanism that produced it: every leaf "read 1–2 siblings" and conformed to the sibling rather than to the library — precedent-following, the named anti-pattern in
ticket-create §8 — and no view-architecture plan existed to conform to. The cockpit grew one ticket at a time into a flat folder.
Why an Epic: the repair spans a mechanical topology move (touching every file, spec, dock item id and SCSS class), per-view primitive migrations, a controller/provider decomposition, and a substrate line in the app-work gate — coordinated toward one outcome, deliverable only as several one-PR leaves in a fixed order.
Intended solution shape — the four laws (operator-stated, 2026-08-22)
- Base class first, library second, custom last. Before designing a view: which library class already does this? A headerless "table" in the design spec is a custom-styled
Neo.grid.Container (sorting, filtering, buffered rendering for free); an item collection is a Neo.list.Base or a Neo.list.Component with component-based items; both extend cleanly. Composition inside views stays welcome — hand-rolled data views do not.
- The suffix IS the base family:
…Container, …Component, …List, …Grid (only for a real grid.Container), …Controller, …StateProvider, …Form, …Button. Role words ("Pane", "Stream", "Card", "Detail", "Bar", "Rail", "Dot") become folder names, never suffixes; the folder supplies the namespace (AgentOS.view.fleet.tasks.Container, …roster.List, …mailbox.Grid).
- Controllers own business logic; providers own cross-view state. Each surface gets a
…Controller (intents, lifecycle round-trips, reads) and, where state is shared, a nested …StateProvider under the cockpit-root provider — e.g. the selected agent becomes provider data that the detail and memories views consume through bindings, replacing the imperative set() pushes. This is also the decomposition method for the cockpit root itself.
- Topology mirrors the surfaces:
view/fleet/ becomes cockpit/, roster/ (+ roster/card/), activity/, tasks/, memories/, mailbox/ (agent + operator + compose + recipient chips), detail/, instances/, health/. The camelCase pure-function modules (Brain-side style that leaked into Body-side app code) become Body-style util classes under apps/agentos/util/ (AgentOS.util.ViewerTime, …SourceHealth, …).
Primitive map (the direction, refined per leaf): mailbox surfaces, memories and catch-up → grid.Container; the roster → list.Component + list.plugin.Animate; tasks (≤12 rows, three sections) → list.Base with useHeaders section records; activity → grid.Container or the buffered component list (#17554), chosen by whether its rows need component items; wake routes → a list.
Order, no big-bang: the in-flight fleet-folder PRs land first; then ONE mechanical move-and-rename leaf relocates the EXISTING tree; per-view primitive migrations follow as their own leaves; the cockpit-root decomposition rides on the controllers/providers law. New-surface leaves never wait for the move leaf: a new surface is born conformant in its own folder with law-1 names (additive folders cannot conflict with the mechanical move, whose file list is whatever exists at its rebase time); a new view that replaces an old file retires that file in its own PR.
Out of scope
- Visual design of the panes (the §04 bar, #17268-class content design) — this epic governs class shape, placement and primitives, not typography.
- The dock workspace host migration (#17539 arc) — the cockpit leaf of that arc is planned inside this topology, not replaced by it.
- New engine primitives beyond what leaves need (the buffered component list has its own engine ticket).
Avoided traps
- Big-bang rewrite: one PR touching 43 files + every spec is unreviewable; the move leaf is mechanical-only, the primitive migrations are per view.
- Renaming without re-basing: a
MailboxPane renamed to MailboxContainer that still hand-rolls rows has conformed to law 1 and violated law 0; each migration leaf picks the primitive first.
- Controllers as a second component: business logic moves to controllers; views keep composition and rendering, providers keep state — no fourth layer.
Related
Parent: #14560. Precedent: apps/portal/view/**. Structure map: ai:structure-map --root apps/agentos/view (view/ 3 files, view/fleet 43 files — the flat folder this epic dissolves).
Decision Record impact: none — applies existing engine conventions (src/ naming, Portal view topology); no ADR amended.
Live latest-open sweep: checked latest 20 open issues at 2026-08-22T16:55:35Z; no equivalent epic (adjacent leaves #17335, #17553, #17550, #17554 become/are linked subs). A2A claim sweep: no competing claim.
Origin Session ID: 28bee2e0-4dc8-4375-8514-78fcf38d0d30
Retrieval Hint: query_raw_memories("cockpit view layer conforms component library suffix base class topology controllers providers buffered grid")
Problem scope
Operator architecture critique, paired session 2026-08-22, on
AgentOS.view.fleet.TasksPaneand its siblings: "Pane violates neo naming conventions — Pane is not a base class. You chose a container with nested containers and components, not a buffered grid or list. This is a problem in other views too — ignoring what the neo component library offers. BUFFERED views matter: activity, memories, a2a messages, maybe more. Dropping everything into ONE fleet folder — camelCase and PascalCase wild mixture. Missing PLANNING."The inventory (2026-08-22,
apps/agentos/view/fleet/, 43 files: 27 PascalCase classes + 16 camelCase modules in one flat folder):Neo.container.Baseand none is named…Container—ActivityStream,AgentCard,AgentDetail,CatchUpPane,FleetCockpit,FleetGrid(a container, not a grid),HealthBar,InstanceManager,MailboxPane,MemoriesPane,OperatorMailbox,WakeRoutePane. Seven extendNeo.component.Basewithout…Component(ActorChip,AgentConfigCard,EventChip,FamilyRail,HealthSwatch,SourceHealthMarker,StateDot). The only correctly-suffixed classes are the ones that consume library primitives (RecipientChipList,InstanceMenuList,InstanceSwitcher→ Button, the two…Formcontainers).Neo.grid.Container(buffered viabufferRowRange, sorting, filtering),Neo.list.Base/Neo.list.Component(component items,list.plugin.Animate) and their extension points sit unused. The app-work gate's store-binding rule was applied to the data layer and ignored at the view layer: a Store exists, a primitive does not.FleetCockpit.mjscarries 4,018 lines of imperative plumbing —getMemoriesPane()?.set({agentOptions…})-style pushes, per-pane loaders, selection held as owner fields — where the engine offers hierarchicalstate.Providers (bindings) and per-viewcontroller.Components.apps/portal/view/**is the in-repo precedent: a folder per surface, each withMainContainer+MainContainerController+MainContainerStateProvider, ten controllers, components named by base class.ticket-create §8— and no view-architecture plan existed to conform to. The cockpit grew one ticket at a time into a flat folder.Why an Epic: the repair spans a mechanical topology move (touching every file, spec, dock item id and SCSS class), per-view primitive migrations, a controller/provider decomposition, and a substrate line in the app-work gate — coordinated toward one outcome, deliverable only as several one-PR leaves in a fixed order.
Intended solution shape — the four laws (operator-stated, 2026-08-22)
Neo.grid.Container(sorting, filtering, buffered rendering for free); an item collection is aNeo.list.Baseor aNeo.list.Componentwith component-based items; both extend cleanly. Composition inside views stays welcome — hand-rolled data views do not.…Container,…Component,…List,…Grid(only for a realgrid.Container),…Controller,…StateProvider,…Form,…Button. Role words ("Pane", "Stream", "Card", "Detail", "Bar", "Rail", "Dot") become folder names, never suffixes; the folder supplies the namespace (AgentOS.view.fleet.tasks.Container,…roster.List,…mailbox.Grid).…Controller(intents, lifecycle round-trips, reads) and, where state is shared, a nested…StateProviderunder the cockpit-root provider — e.g. the selected agent becomes provider data that the detail and memories views consume through bindings, replacing the imperativeset()pushes. This is also the decomposition method for the cockpit root itself.view/fleet/becomescockpit/,roster/(+roster/card/),activity/,tasks/,memories/,mailbox/(agent + operator + compose + recipient chips),detail/,instances/,health/. The camelCase pure-function modules (Brain-side style that leaked into Body-side app code) become Body-style util classes underapps/agentos/util/(AgentOS.util.ViewerTime,…SourceHealth, …).Primitive map (the direction, refined per leaf): mailbox surfaces, memories and catch-up →
grid.Container; the roster →list.Component+list.plugin.Animate; tasks (≤12 rows, three sections) →list.BasewithuseHeaderssection records; activity →grid.Containeror the buffered component list (#17554), chosen by whether its rows need component items; wake routes → a list.Order, no big-bang: the in-flight fleet-folder PRs land first; then ONE mechanical move-and-rename leaf relocates the EXISTING tree; per-view primitive migrations follow as their own leaves; the cockpit-root decomposition rides on the controllers/providers law. New-surface leaves never wait for the move leaf: a new surface is born conformant in its own folder with law-1 names (additive folders cannot conflict with the mechanical move, whose file list is whatever exists at its rebase time); a new view that replaces an old file retires that file in its own PR.
Out of scope
Avoided traps
MailboxPanerenamed toMailboxContainerthat still hand-rolls rows has conformed to law 1 and violated law 0; each migration leaf picks the primitive first.Related
Parent: #14560. Precedent:
apps/portal/view/**. Structure map:ai:structure-map --root apps/agentos/view(view/ 3 files, view/fleet 43 files — the flat folder this epic dissolves).Decision Record impact: none — applies existing engine conventions (
src/naming, Portal view topology); no ADR amended.Live latest-open sweep: checked latest 20 open issues at 2026-08-22T16:55:35Z; no equivalent epic (adjacent leaves #17335, #17553, #17550, #17554 become/are linked subs). A2A claim sweep: no competing claim.
Origin Session ID: 28bee2e0-4dc8-4375-8514-78fcf38d0d30 Retrieval Hint:
query_raw_memories("cockpit view layer conforms component library suffix base class topology controllers providers buffered grid")