Skip to content

Commit ff080ee

Browse files
docs: expand UI navigation rework into an agent-ready spec
Add a verified current-architecture map (routes, Sidebar, Dashboard/Fleet, data layer, instance store) with file references, the target Server>Workspace>section IA, phased action items (Phase 1 merge Dashboard/Fleet is settled; Phases 2-3 need brainstorming), validation steps, open questions, and conventions.
1 parent a634c8f commit ff080ee

1 file changed

Lines changed: 219 additions & 68 deletions

File tree

Lines changed: 219 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,227 @@
1-
# UI Navigation Rework — Future Improvements (Draft)
1+
# UI Navigation Rework — Implementation Spec
22

33
- **Date captured:** 2026-06-02
4-
- **Status:** DRAFT — vision captured from discussion, **not yet brainstormed or scheduled**.
5-
Needs a full `brainstorming``writing-plans` pass before any implementation.
6-
- **Author intent:** reduce overlap between three navigation surfaces and make
7-
drill-down navigation clearer.
8-
9-
## Motivation
10-
11-
Today three surfaces overlap:
12-
13-
- **Instance selector** — pick the active Honcho connection.
14-
- **Dashboard** (`packages/web/src/components/dashboard/Dashboard.tsx`) — workspace
15-
count + queue status for the active instance.
16-
- **Fleet** (`packages/web/src/components/fleet/`) — cross-instance observability:
17-
workspaces / sessions / queue side-by-side per instance.
18-
19-
Dashboard and Fleet are **nearly identical** in what they show (workspace/queue
20-
metrics), differing mainly in single-instance vs all-instances scope. That overlap
21-
is the thing to collapse.
22-
23-
## Proposed information architecture
24-
25-
A single hierarchical drill-down, navigable as an **outline / tree**:
4+
- **Status:** SPEC — agent-ready. Phases 2–3 carry open product questions; resolve them
5+
via `superpowers:brainstorming` against this doc before implementing those phases.
6+
Phase 1 is well-defined and can start directly.
7+
- **Branch/PR:** `docs/ui-navigation-rework` (PR #55). Implementation should branch
8+
separately, one concern per PR (see [[branch-and-pr-one-concern]]).
9+
10+
## For the agent picking this up — start here
11+
12+
1. Read this whole doc, then read the **Current architecture** map below and open the
13+
cited files to confirm they still match (the codebase moves; verify before acting).
14+
2. If you're doing **Phase 1** (merge Dashboard + Fleet), the design is settled —
15+
follow the action items. If **Phase 2/3** (the Server drill-down, Fleet
16+
repositioning), first run `superpowers:brainstorming` to resolve the **Open
17+
questions** section, then `superpowers:writing-plans`.
18+
3. Honor the repo conventions in **Conventions** below. Gate every change with
19+
`make ci-web`; tests live in `packages/web/src/test/`.
20+
4. This is a frontend-only change. No backend/data-layer rewrite is required — the
21+
multi-instance fan-out already exists (see **Data layer**).
22+
23+
## Goals
24+
25+
- **Eliminate the Dashboard/Fleet overlap.** Today `Dashboard` (active instance only)
26+
and `Fleet` (all instances) render nearly the same metrics. Merge into one
27+
server-aware Dashboard.
28+
- **Make the hierarchy navigable as Server → Workspace → Peers / Sessions /
29+
Conclusions / …**, surfaced as an outline/drill-down. The Workspace→children level
30+
already exists; the missing level is **Server** (today "server" = the active
31+
instance, switched globally, not navigated).
32+
- **Unify cross-instance browsing into the Dashboard**: show all workspaces across all
33+
servers labelled `<workspace> (<server>)`, **filterable by server**. Fleet stops
34+
being a separate browsing page.
35+
- Keep navigation clear via breadcrumbs + an outline view.
36+
37+
## Non-goals
38+
39+
- No backend or Honcho API changes. No data-layer rewrite — `compareQueries.ts` +
40+
`scopedClient.ts` already fan out across instances.
41+
- No new "real" server entity — a "Server" is an existing `Instance`
42+
(`packages/web/src/lib/config.ts:25` `instanceSchema`). This is an IA/UX change, not
43+
a data-model change.
44+
- Not changing the localStorage multi-instance store model.
45+
46+
## Current architecture (where to look)
47+
48+
> Verified 2026-06-02. Paths relative to repo root; `packages/web/` is the SPA.
49+
50+
### Routes (TanStack flat-route; `routeTree.gen.ts` is generated — never hand-edit)
51+
- Generator: `@tanstack/router-plugin/vite` in `packages/web/vite.config.ts` (regen on save).
52+
- Root layout: `src/routes/__root.tsx` → renders `Sidebar` + `<Outlet/>`; redirects to
53+
`/settings` when no instance configured.
54+
- Top-level: `src/routes/index.tsx` (`/``Dashboard`), `fleet.tsx` (`/fleet`
55+
`FleetDashboard`), `workspaces.tsx` (`/workspaces``WorkspaceList`),
56+
`seed-kits.tsx`, `settings.tsx`, `explore.tsx` (redirect helper).
57+
- Workspace-scoped (the existing drill-down): `workspaces_.$workspaceId.tsx`
58+
(`WorkspaceDetail` hub) and
59+
`workspaces_.$workspaceId_.{peers,sessions,conclusions,dreams,queue,webhooks}.tsx`.
60+
- Detail/param routes: `workspaces_.$workspaceId_.peers_.$peerId.tsx`,
61+
`…peers_.$peerId_.{chat,playground}.tsx`, `…sessions_.$sessionId.tsx`.
62+
63+
### Navigation
64+
- `src/components/layout/Sidebar.tsx`:
65+
- `TOP_NAV` array (~line 33) — top-level items (Dashboard, Fleet, Workspaces, Seed
66+
Kits, Settings).
67+
- `WORKSPACE_SECTIONS` array (~line 41) — the workspace sub-nav (Peers, Sessions,
68+
Conclusions, Dreams, Webhooks), conditionally rendered (~271–335) when a workspace
69+
route is active.
70+
- Active-instance switcher (~145–226): renders `active.name`/`baseUrl`, health dot,
71+
dropdown over `instances` via `useInstances()`; `activate(id)` switches.
72+
- Active-context detection via `matchRoute({ to: "/workspaces/$workspaceId", fuzzy: true })` (~108).
73+
- `src/components/layout/Breadcrumb.tsx` — used by list pages (e.g. `PeerList.tsx`).
74+
75+
### Dashboard vs Fleet (the overlap to merge)
76+
- `src/components/dashboard/Dashboard.tsx`**active instance only**. Uses
77+
`useWorkspaces(page, 50)`; per-workspace `useQueueStatus()` via `WorkspaceQueueRow`;
78+
`GlobalQueueBanner` aggregates totals. Polls 2.5s active / 10s idle.
79+
- `src/components/fleet/FleetDashboard.tsx`**all instances**. Iterates
80+
`useInstances().instances` → one `<FleetRow instance=…>` each.
81+
- `src/components/fleet/FleetRow.tsx` — per-instance metrics via
82+
`createScopedClient(instance)` + `useScopedWorkspaces` + `useQueries` fan-out of
83+
`scopedQueueStatusOptions` / `scopedConclusionsCountOptions`.
84+
- `src/components/fleet/fleetAggregates.ts``computeFleetAggregates(rows)` sums
85+
workspaces/conclusions/queue/health across instances. **Pure + transport-agnostic —
86+
reuse as-is.**
87+
88+
### Data layer (already multi-instance capable)
89+
- `src/api/queries.ts` — active-instance hooks: `useWorkspaces`, `useWorkspace`,
90+
`useQueueStatus`, `usePeers`, `usePeer`, `usePeerRepresentation`, `usePeerCard`,
91+
`useSessions`, `useSessionMessages`, `useConclusions`, `useChat`, `useScheduleDream`, …
92+
- `src/api/compareQueries.ts`**scoped (per-instance)** hooks + `useQueries` option
93+
builders: `useScopedWorkspaces`, `useScopedPeers`, `useScopedQueueStatus`,
94+
`useScopedConclusionsCount`, `scopedQueueStatusOptions`, `scopedConclusionsCountOptions`.
95+
Query keys namespaced by `instance.id` (`CK` map) so caches never collide.
96+
- `src/api/client.ts` (`client.current`, active) vs `src/api/scopedClient.ts`
97+
(`createScopedClient(instance)`, per-instance). `src/api/keys.ts``QK` query keys.
98+
- **Conclusion:** fanning a query across all instances is already a solved pattern
99+
(FleetRow demonstrates it). Server-level aggregation = generalize that pattern.
100+
101+
### Instance ("Server") store
102+
- `src/lib/config.ts``instanceSchema`/`Instance` (~25), `InstanceStore`
103+
(`{instances, activeId}`), CRUD (`addInstance`/`updateInstance`/`deleteInstance`/
104+
`setActiveInstance`). Persisted to `localStorage` `openconcho:instances`.
105+
- `src/hooks/useInstances.ts``{ instances, active, activeId, add, update, remove, activate }`
106+
via `useSyncExternalStore`; `activate`/`remove` clear the query cache.
107+
- Tests: `src/test/instances.test.ts` (store CRUD), `src/test/fleet.test.tsx`
108+
(multi-instance fan-out).
109+
110+
### Domain components to reuse for Server-scoped views
111+
- `workspaces/WorkspaceList.tsx`, `workspaces/WorkspaceDetail.tsx`
112+
- `peers/PeerList.tsx`, `peers/PeerDetail.tsx`
113+
- `sessions/SessionList.tsx`, `sessions/SessionDetail.tsx`
114+
- `conclusions/ConclusionBrowser.tsx`
115+
- Each takes a `workspaceId` and calls the active-instance `use*` hooks.
116+
117+
## Target information architecture
26118

27119
```
28-
Server (instance)
29-
└─ Workspace
30-
├─ Peers
120+
Server (= Instance) ← NEW navigable level (today: global switcher only)
121+
└─ Workspace ← exists: /workspaces/$workspaceId
122+
├─ Peers (+ Peer → chat/playground)
31123
├─ Sessions
32124
├─ Conclusions
33-
└─ … (queue, dreams, webhooks, etc.)
125+
└─ Dreams / Queue / Webhooks
34126
```
35127

36-
- Navigation drills **Server → Workspace → {Peers | Sessions | Conclusions | …}**.
37-
- An **outline view** makes the hierarchy explicit and improves wayfinding (vs. the
38-
current flat per-feature pages).
39-
40-
## Fleet folds into Dashboard
41-
42-
- The Dashboard becomes the **unified, all-servers view**: every workspace across
43-
every server, each labelled **`<workspace> (<server>)`**.
44-
- **Filterable by server** — selecting a server narrows the Dashboard to that
45-
server's workspaces. Fleet stops being a separate page and becomes an **overlay /
46-
filter state** on the Dashboard.
47-
48-
## Open question: does standalone Fleet survive?
49-
50-
Fleet "still has merit" — but possibly **repositioned as a settings/management-style
51-
view** (fleet health, per-instance admin) rather than a primary nav destination.
52-
To decide in brainstorming: what is Fleet's unique job once cross-instance browsing
53-
lives in the Dashboard? (Likely: fleet *health/ops*, not fleet *browsing*.)
54-
55-
## Open questions (resolve during brainstorming)
56-
57-
- How does the **instance selector** relate to the new top-level "Server" node — is
58-
it absorbed into the tree, or kept as a global switcher?
59-
- **Routing**: how does `Server → Workspace → …` map onto TanStack Router flat-route
60-
files, and how are deep links / breadcrumbs handled?
61-
- What exactly lives in a **settings-like Fleet** (health, queue depth, version, auth
62-
status)?
63-
- Responsive/mobile behaviour of the outline/tree.
64-
- Migration: does the current `/fleet` route redirect, or stay as the ops view?
65-
66-
## Non-goals (for this capture)
67-
68-
- This is a **vision record**, not an implementation plan. No code, no schedule.
69-
- Does not change the data layer — `compareQueries.ts` / `fleetAggregates.ts`
70-
already fan out across instances and can back a unified Dashboard unchanged.
71-
72-
## Next step
73-
74-
When prioritised: run `superpowers:brainstorming` against this doc to resolve the
75-
open questions, then `writing-plans` for the implementation. Keep it a separate
76-
concern (its own branch/PR) from the proxy work in #54.
128+
- **Dashboard** = the all-servers landing: every workspace across every server as
129+
`<workspace> (<server>)`, **filterable by server**; server filter = "all" by default.
130+
Selecting a single server narrows to that server (and is the entry to its drill-down).
131+
- **Outline/tree** view makes Server→Workspace→section explicit; breadcrumbs reflect it.
132+
133+
## Phased action items
134+
135+
### Phase 1 — Merge Fleet into a server-aware Dashboard (settled; start here)
136+
**Goal:** one Dashboard that shows all workspaces across all servers, server-filterable;
137+
remove the redundant standalone Fleet *browsing* page (see Phase 3 for Fleet's future).
138+
139+
- **Where:** `src/components/dashboard/Dashboard.tsx`, `src/components/fleet/*`,
140+
`src/routes/index.tsx`, `src/routes/fleet.tsx`, `src/components/layout/Sidebar.tsx`.
141+
- **Steps:**
142+
1. Generalize the Dashboard to iterate `useInstances().instances` instead of only the
143+
active instance. Reuse `FleetRow`'s scoped fan-out pattern
144+
(`createScopedClient` + `scopedQueueStatusOptions`/`scopedConclusionsCountOptions`)
145+
and `computeFleetAggregates` (reuse as-is) for the totals banner.
146+
2. Render a unified workspace table: row per workspace across all servers, labelled
147+
`<workspace> (<server>)`. Add a **server filter** control (dropdown: "All servers"
148+
+ each `instance.name`); default "All". When one server is selected, the table
149+
narrows and the row link enters that server's workspace drill-down.
150+
3. Fold `fleetAggregates.ts` totals into the Dashboard header (workspaces, conclusions,
151+
healthy/unreachable counts) — this is the current Fleet metric-card content.
152+
4. Update `Sidebar.tsx` `TOP_NAV`: remove the separate "Fleet" item (or repoint it —
153+
see Phase 3). Keep "Dashboard".
154+
5. Redirect `/fleet``/` (keep the route file as a redirect for back-compat) OR
155+
remove it and add a redirect in `__root.tsx`.
156+
- **Reuse:** `fleetAggregates.ts`, `FleetRow` logic, `compareQueries.ts`, `useInstances`.
157+
- **Net-new:** server-filter control; the merged Dashboard table.
158+
- **Tests:** extend `src/test/fleet.test.tsx` (or new `dashboard.test.tsx`): asserts the
159+
unified table renders rows for ≥2 instances with `<workspace> (<server>)` labels, and
160+
the server filter narrows to one server. Keep `computeFleetAggregates` snapshot tests.
161+
162+
### Phase 2 — Server as a navigable level + outline drill-down (needs brainstorming)
163+
**Goal:** drill Server → Workspace → section from the Dashboard, with an outline view.
164+
165+
- **Where:** `src/routes/` (new server-scoped routes), `Sidebar.tsx` (server-context
166+
sub-nav analogous to the existing workspace sub-nav), `Breadcrumb.tsx`.
167+
- **Likely steps (confirm in brainstorming):**
168+
1. Decide the URL scheme — e.g. `/servers/$serverId/workspaces/$workspaceId/...` vs
169+
keeping `/workspaces/...` scoped to the active server + server selection as state.
170+
(Resolve "instance selector vs Server node" open question first.)
171+
2. Add server-context detection in `Sidebar.tsx` (mirror the `matchRoute` workspace
172+
pattern ~108) and a server-level sub-nav.
173+
3. Add an outline/tree component for Server→Workspace→section wayfinding.
174+
4. Extend breadcrumbs to include the Server segment.
175+
- **Reuse:** existing workspace-scoped routes/components; `createScopedClient` to query a
176+
non-active server without switching the global active instance.
177+
- **Net-new:** server-scoped routes/loaders; outline component; Sidebar server context.
178+
179+
### Phase 3 — Reposition standalone Fleet as an ops/settings view (needs brainstorming)
180+
**Goal:** decide Fleet's residual job once browsing lives in the Dashboard.
181+
182+
- **Open decision:** does Fleet survive as a *health/ops* surface (per-server health,
183+
queue depth, version, auth status, reachability) — likely under Settings — or is it
184+
fully absorbed? Resolve in brainstorming.
185+
- **Where:** `src/components/fleet/*`, `src/components/settings/SettingsPage.tsx`,
186+
`Sidebar.tsx`.
187+
188+
## Validation
189+
190+
- After every change: `make ci-web` (lint + typecheck + test + build) must pass.
191+
- Targeted tests: `pnpm --filter @openconcho/web exec vitest run src/test/<file>`.
192+
- Manual: `make up` (docker) or `make dev-web` (Vite) with ≥2 instances configured;
193+
confirm the unified Dashboard lists workspaces across servers, the server filter
194+
narrows correctly, drill-down navigates Server→Workspace→section, and breadcrumbs are
195+
correct. Toggle demo mode (masking) and light/dark — both must stay correct.
196+
- Acceptance (write as Gherkin per [[gherkin-acceptance-criteria]] when planning):
197+
e.g. *Given two configured servers, When I open the Dashboard, Then I see each
198+
server's workspaces labelled `<workspace> (<server>)`; When I filter to one server,
199+
Then only its workspaces show.*
200+
201+
## Open questions (resolve in brainstorming before Phase 2/3)
202+
203+
1. **Instance selector vs Server node** — does the global switcher stay, get absorbed
204+
into the Server level, or coexist (switcher = "default server", drill-down = browse
205+
any)?
206+
2. **Routing**`/servers/$serverId/...` nested routes vs server-as-state on the
207+
existing `/workspaces/...` routes. Affects deep links + breadcrumbs.
208+
3. **Fleet's residual role** — ops/health view under Settings, or fully absorbed?
209+
4. **Server-scoped aggregation** — do Peers/Sessions/Conclusions ever aggregate
210+
*across workspaces within a server* (new `useServer*` hooks), or is drill-down always
211+
Server→one Workspace→section (no cross-workspace union)? The latter needs zero new
212+
queries; the former needs new aggregation hooks in `compareQueries.ts`.
213+
5. **Responsive/mobile** behaviour of the outline/tree.
214+
215+
## Conventions to honor
216+
217+
- TanStack flat-route params: cast `as never` at every `navigate()`/`<Link>` callsite
218+
(see `Sidebar.tsx`, `PeerList.tsx`). Never hand-edit `routeTree.gen.ts`.
219+
- framer-motion: `import { type Variants }` and annotate variant objects; never
220+
`as const`.
221+
- CSS variables only for theme colors (`var(--text-1)` etc.) — no Tailwind color
222+
utilities.
223+
- Tests in `packages/web/src/test/`, behavior-focused, mock only at the fetch boundary
224+
(`@/lib/http`). Gate with `make ci-web`.
225+
- Conventional commits; one concern per PR; push under `offendingcommit`.
226+
- No environment-specific values in code/docs — use `honcho.example.net` / `192.0.2.x`
227+
(enforced by the pre-commit secret scan).

0 commit comments

Comments
 (0)