# Tasks: cloud-dashboard-visual-parity

## Mode
Strict TDD (fail → pass → refactor). Each implementation task names its RED test seam. No production code is written before the RED test exists and fails for the right reason.

---

## Batch 1 — Templ bootstrap + verbatim static assets + asset-floor RED

> Depends on: nothing. Blocks: Batches 2, 3, 4, 5.

- [x] 1.1 RED: Create `internal/cloud/dashboard/templ_policy_test.go` with `TestStaticAssetByteFloors` — assert `fs.Stat(StaticFS, "static/htmx.min.js").Size() >= 40_000`, `fs.Stat(StaticFS, "static/pico.min.css").Size() >= 60_000`, `fs.Stat(StaticFS, "static/styles.css").Size() >= 20_000`; test MUST fail (current stubs are 42 B / 46 B / 2 284 B). Satisfies REQ-100.
- [x] 1.2 RED: Add `TestTemplGeneratedFilesAreCheckedIn` in same file — `os.Stat` each of `components_templ.go`, `layout_templ.go`, `login_templ.go`; assert header regex `^// Code generated by templ`; assert `components_templ.go >= 100_000` bytes. Test MUST fail (no generated files yet). Satisfies REQ-101.
- [x] 1.3 Add `github.com/a-h/templ v0.3.1001` to `go.mod` as a direct dependency; add `golang.org/x/net` for html tokenizer; run `go mod tidy` and commit updated `go.mod` + `go.sum`.
- [x] 1.4 Create `tools/tools.go` with `//go:build tools` + `// +build tools` + blank import `_ "github.com/a-h/templ/cmd/templ"` (package `tools`).
- [x] 1.5 Add `Makefile` target `templ` that runs `go tool templ generate ./internal/cloud/dashboard/...`.
- [x] 1.6 GREEN: Copy `static/htmx.min.js` (50 917 B), `static/pico.min.css` (71 072 B), `static/styles.css` (23 185 B) verbatim from `/Users/alanbuscaglia/work/engram-cloud/internal/cloud/dashboard/static/`; run `TestStaticAssetByteFloors` — MUST pass.
- [x] 1.7 REFACTOR: Verify `go build ./...` still compiles cleanly after go.mod changes; no other changes needed.

---

## Batch 2 — Templ sources port + generated files + structural HTML RED

> Depends on: Batch 1 (go.mod + templ binary). Blocks: Batch 4 (handler templ calls). May parallelize with Batch 3 (disjoint file sets).

- [x] 2.1 RED: Add to `internal/cloud/dashboard/dashboard_test.go`:
  - `TestDashboardLayoutHTMLStructure` — GET `/dashboard/` for authed Principal; assert class markers `shell-body`, `shell-backdrop`, `app-shell`, `shell-header`, `brand-stack`, `shell-nav`, `shell-main`, `shell-footer` using `golang.org/x/net/html` tokenizer helpers `hasElementWithClass(body, tag, class)`.
  - `TestStatusRibbonAndFooterPresent` — same GET; `strings.Contains` for `status-ribbon`, `status-pill`, `CLOUD ACTIVE`, `ENGRAM CLOUD / SHARED MEMORY INDEX / LIVE SYNC READY`.
  - `TestNavTabsRenderedCorrectly` — same GET (isAdmin=true); assert `href="/dashboard/"`, `href="/dashboard/browser"`, `href="/dashboard/projects"`, `href="/dashboard/contributors"`, `href="/dashboard/admin"`.
  - `TestLoginPageTokenFormAndCopy` — GET `/dashboard/login`; assert `name="token"`, `Engram Cloud`, `CLOUD ACTIVE`, hidden input `name="next"`.
  - All MUST fail. Satisfies REQ-107, REQ-111.
- [x] 2.2 Add test helpers `hasElementWithClass(body string, tag string, class string) bool` and `countElementsWithClass(body string, class string) int` to `dashboard_test.go` using `golang.org/x/net/html.NewTokenizer`. Satisfies Design Decision 7.
- [x] 2.3 Copy `layout.templ` (~52 L) from legacy verbatim; adapt import path to `github.com/Gentleman-Programming/engram/internal/cloud/cloudstore`; `username` parameter stays as `string` (receives `Principal.DisplayName()` from caller). File: `internal/cloud/dashboard/layout.templ`.
- [x] 2.4 Copy `login.templ` (~63 L) from legacy verbatim; adapt import path; add `next string` as second parameter; add `<input type="hidden" name="next" value="{ next }">` inside the form. File: `internal/cloud/dashboard/login.templ`.
- [x] 2.5 Copy `components.templ` (~1 134 L) from legacy verbatim; adapt import path; substitute all legacy rich types with flat `DashboardXxxRow` equivalents (`CloudObservation` → `DashboardObservationRow`, `CloudSession` → `DashboardSessionRow`, `CloudPrompt` → `DashboardPromptRow`, `ProjectStat` → `DashboardProjectRow`, `ContributorStat` → `DashboardContributorRow`, `SystemHealthInfo` → `DashboardSystemHealth`, `CloudUser` → `string`). Drop fields not present on flat rows or map to available fields per component (documented per-component in inline comments). File: `internal/cloud/dashboard/components.templ`.
- [x] 2.6 Run `make templ` (`go tool templ generate ./internal/cloud/dashboard/...`); commit generated `components_templ.go`, `layout_templ.go`, `login_templ.go`.
- [x] 2.7 GREEN: Adapt templ type references where compile errors occur (stub component bodies if a type field is absent — use blank string or zero value with `// TODO: populate from flat row`); rerun structural tests — MUST pass.
- [x] 2.8 REFACTOR: Keep `.templ` source files as close to verbatim copy as possible; document each deviation with an inline `// ADAPTED:` comment; keep generated files committed.

---

## Batch 3 — Row-type extensions + cloudstore additions + Principal bridge

> Depends on: Batch 1 (go.mod). May parallelize with Batch 2 (disjoint file sets). Blocks: Batch 4.

- [x] 3.1 RED: Add to `internal/cloud/cloudstore/dashboard_queries_test.go`:
  - `TestDashboardRowDetailFields` — seed a Postgres test DB with an observation chunk containing `content`, `topic_key`, `tool_name`; call `buildDashboardReadModel`; assert `row.Content`, `row.TopicKey`, `row.ToolName`, `row.ChunkID` are non-empty and correct; assert `DashboardSessionRow.EndedAt`, `Summary`, `Directory` populated; assert `DashboardPromptRow.ChunkID` populated. MUST fail (fields missing from struct). Satisfies REQ-102.
  - `TestCloudstoreSystemHealthAggregates` — seed 2 projects / 3 sessions / 10 observations / 5 prompts; call `SystemHealth()`; assert counts match; assert `DBConnected = true`. MUST fail (no `SystemHealth()` method). Satisfies REQ-105.
  - `TestDashboardPaginationSortStability` — seed 50 rows across 2 projects; call `ListRecentSessionsPaginated` for page 3 size 10 twice; assert identical `SessionID` slices. Satisfies Design Decision 1.
- [x] 3.2 RED: Create `internal/cloud/cloudstore/project_controls_test.go` with:
  - `TestProjectSyncControlPersists` — `SetProjectSyncEnabled("proj", false, "op", "maint")` → `IsProjectSyncEnabled` returns `false` → `GetProjectSyncControl` returns record with `SyncEnabled=false, PausedReason="maint"`. MUST fail (no table / methods). Satisfies REQ-104.
  - `TestProjectSyncControlUnknownProjectDefaultsEnabled` — `IsProjectSyncEnabled("no-such-project")` returns `true, nil`. MUST fail.
  - `TestProjectSyncControlListIncludesKnownChunkProjects` — seed chunk for `proj-x`, call `ListProjectSyncControls()`; result includes `proj-x` (via UNION with `cloud_chunks`). MUST fail.
- [x] 3.3 RED: Add to `internal/cloud/dashboard/dashboard_test.go`:
  - `TestGetDisplayNameFallback` — construct `MountConfig{GetDisplayName: nil}`; call `principalFromRequest(r).DisplayName()`; assert returns `"OPERATOR"`. MUST fail (no `GetDisplayName` field, no `principal.go`). Satisfies REQ-103.
  - `TestPrincipalBridgeNoPanicOnEmptyContext` — `GetDisplayName` returns `""`; assert `DisplayName()` still returns `"OPERATOR"` (empty string treated as absent). Satisfies REQ-113.
- [x] 3.4 Extend `DashboardObservationRow` in `internal/cloud/cloudstore/dashboard_queries.go` with fields: `ChunkID string`, `Content string`, `TopicKey string`, `ToolName string`. Extend `DashboardSessionRow` with: `EndedAt string`, `Summary string`, `Directory string`. Extend `DashboardPromptRow` with: `ChunkID string`. Update `buildDashboardReadModel` (`upsertDashboard*` helpers + `applyDashboardMutation`) to populate all new fields from the chunk JSON payload.
- [x] 3.5 Add `DashboardSystemHealth` struct and `CloudStore.SystemHealth() (DashboardSystemHealth, error)` in `dashboard_queries.go`; implementation walks `cs.loadDashboardReadModel()` for counts; `DBConnected` from `cs.db.PingContext(ctx)` with 500ms timeout.
- [x] 3.6 Add five paginated list helpers in `dashboard_queries.go`:
  - `ListProjectsPaginated(query string, limit, offset int) ([]DashboardProjectRow, int, error)`
  - `ListRecentObservationsPaginated(project, query, obsType string, limit, offset int) ([]DashboardObservationRow, int, error)`
  - `ListRecentSessionsPaginated(project, query string, limit, offset int) ([]DashboardSessionRow, int, error)`
  - `ListRecentPromptsPaginated(project, query string, limit, offset int) ([]DashboardPromptRow, int, error)`
  - `ListContributorsPaginated(query string, limit, offset int) ([]DashboardContributorRow, int, error)`
  Each slices the in-memory read model (no SQL LIMIT/OFFSET); returns `(page, total, err)`.
- [x] 3.7 Add three detail query methods in `dashboard_queries.go`:
  - `GetSessionDetail(project, sessionID string) (DashboardSessionRow, []DashboardObservationRow, []DashboardPromptRow, error)`
  - `GetObservationDetail(project, sessionID, chunkID string) (DashboardObservationRow, DashboardSessionRow, []DashboardObservationRow, error)`
  - `GetPromptDetail(project, sessionID, chunkID string) (DashboardPromptRow, DashboardSessionRow, []DashboardPromptRow, error)`
- [x] 3.8 Create `internal/cloud/cloudstore/project_controls.go` with:
  - `ProjectSyncControl` struct (`Project`, `SyncEnabled bool`, `PausedReason *string`, `UpdatedAt string`, `UpdatedBy *string`).
  - `IsProjectSyncEnabled(project string) (bool, error)` — SELECT with absent-row default `true`.
  - `SetProjectSyncEnabled(project string, enabled bool, updatedBy, reason string) error` — UPSERT.
  - `GetProjectSyncControl(project string) (*ProjectSyncControl, error)`.
  - `ListProjectSyncControls() ([]ProjectSyncControl, error)` — UNION `SELECT DISTINCT project_name FROM cloud_chunks` with `cloud_project_controls`.
- [x] 3.9 Append two Postgres statements to the `queries` slice in `CloudStore.migrate()` at `internal/cloud/cloudstore/cloudstore.go:427`:
  ```sql
  CREATE TABLE IF NOT EXISTS cloud_project_controls (
      project TEXT PRIMARY KEY,
      sync_enabled BOOLEAN NOT NULL DEFAULT TRUE,
      paused_reason TEXT,
      updated_by TEXT,
      updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  );
  CREATE INDEX IF NOT EXISTS idx_cloud_project_controls_enabled ON cloud_project_controls(sync_enabled);
  ```
  Use Postgres types (`BOOLEAN DEFAULT TRUE`, `TIMESTAMPTZ`), NOT SQLite types.
- [x] 3.10 Extend `DashboardStore` interface in `internal/cloud/dashboard/dashboard.go` with all new methods: 5 `ListXxxPaginated`, 3 `GetXxxDetail`, `SystemHealth`, `ListProjectSyncControls`, `GetProjectSyncControl`, `SetProjectSyncEnabled`, `IsProjectSyncEnabled`.
- [x] 3.11 Create `internal/cloud/dashboard/principal.go` with `Principal{displayName string, isAdmin bool}` struct, `DisplayName() string` method (empty → `"OPERATOR"`), `IsAdmin() bool`, and `func (h *handlers) principalFromRequest(r *http.Request) Principal` reading from `h.cfg.GetDisplayName` and `h.cfg.IsAdmin` closures.
- [x] 3.12 Add `GetDisplayName func(r *http.Request) string` optional field to `dashboard.MountConfig` struct in `dashboard.go`.
- [x] 3.13 GREEN: Run all Batch 3 RED tests — `TestDashboardRowDetailFields`, `TestCloudstoreSystemHealthAggregates`, `TestDashboardPaginationSortStability`, `TestProjectSyncControlPersists`, `TestProjectSyncControlUnknownProjectDefaultsEnabled`, `TestProjectSyncControlListIncludesKnownChunkProjects`, `TestGetDisplayNameFallback`, `TestPrincipalBridgeNoPanicOnEmptyContext` — all MUST pass.
- [x] 3.14 REFACTOR: Keep cloudstore query and pagination concerns in `dashboard_queries.go`; keep sync-control persistence in `project_controls.go`; keep Principal bridge in `principal.go`; no mixed concerns across files.

---

## Batch 4 — New 11 routes + HTMX wiring + admin + push-path pause guard

> Depends on: Batches 2 (templ components) + 3 (store methods + Principal bridge). Blocks: Batch 5.

- [x] 4.1 RED: Add to `internal/cloud/dashboard/dashboard_test.go`:
  - `TestDashboardHomeHTMXWiring` — GET `/dashboard/`; `strings.Contains(body, "hx-get=\"/dashboard/stats\"")`, `hx-get="/dashboard/activity"`, `hx-trigger="load"`. MUST fail. Satisfies REQ-108.
  - `TestBrowserPageHTMXWiring` — GET `/dashboard/browser`; assert `hx-get="/dashboard/browser/observations"`, `hx-target="#browser-content"`, `hx-include="#browser-project"`. MUST fail. Satisfies REQ-108.
  - `TestProjectsPageHTMXWiring` — GET `/dashboard/projects`; assert `hx-get="/dashboard/projects/list"`, `hx-target="#projects-content"`, `hx-trigger` contains `keyup`. MUST fail. Satisfies REQ-108.
  - `TestAdminPageSurfacePresent` — GET `/dashboard/admin` (isAdmin=true); assert admin metrics present + `hx-post` toggle form. MUST fail. Satisfies REQ-107.
  - `TestAdminHealthPageRendersMetrics` — GET `/dashboard/admin/health`; assert DB status + project/contributor/session/observation/prompt counts rendered. MUST fail. Satisfies REQ-106.
  - `TestAdminUsersPageRendersContributors` — GET `/dashboard/admin/users`; assert contributor rows rendered. MUST fail. Satisfies REQ-106.
  - `TestAdminSyncTogglePosts` — POST `/dashboard/admin/projects/myproject/sync` with admin=true, body `enabled=false&reason=maintenance`; assert 303 redirect + `HX-Redirect` header. MUST fail. Satisfies REQ-112.
  - `TestAdminSyncToggleRequiresAdmin` — POST same URL with isAdmin=false; assert 403. MUST fail. Satisfies REQ-112.
  - `TestFullHTMXEndpointSurface` — all 11 new routes return 200 + `Content-Type: text/html` for authed, 302 for unauthed. MUST fail (routes not registered). Satisfies REQ-106.
  - `TestCopyParityStrings` — GET `/dashboard/browser` contains `KNOWLEDGE BROWSER`; `/dashboard/projects` contains `PROJECT ATLAS`; `/dashboard/contributors` contains `CONTRIBUTOR SIGNAL`; `/dashboard/admin` contains `ADMIN SURFACE`; `/dashboard/login` contains `Engram Cloud` and `CLOUD ACTIVE`. MUST fail. Satisfies REQ-111.
- [x] 4.2 RED: Add to `internal/cloud/cloudserver/cloudserver_test.go`:
  - `TestPushPathPauseEnforcement` — create `fakeStore` implementing `IsProjectSyncEnabled` returning `false`; POST to `/sync/push`; assert 409 + body contains `sync-paused`. MUST fail. Satisfies REQ-109.
  - `TestInsecureModeLoginRedirects` — construct `CloudServer` with `auth == nil`; GET `/dashboard/login`; assert 303 to `/dashboard/`. MUST fail (behavior not yet regression-guarded). Satisfies REQ-110.
- [x] 4.3 Replace string-concat render functions in `internal/cloud/dashboard/dashboard.go` handlers with templ component calls:
  - `handleDashboardHome` → `Layout("Dashboard", p.DisplayName(), "home", p.IsAdmin(), DashboardHome(...))`.
  - `handleLoginPage` → `LoginPage(errorMsg, next).Render(...)`.
  - `handleBrowserPage` → `Layout("Browser", ..., BrowserPage(...))`.
  - `handleProjects` → `Layout("Projects", ..., ProjectsPage(...))`.
  - `handleContributors` → `Layout("Contributors", ..., ContributorsPage(...))`.
  - `handleAdmin` → `Layout("Admin", ..., AdminPage(...))`.
  All handlers call `p := h.principalFromRequest(r)` — NO direct context reads for identity. Remove legacy `shellNavLink` / `browserSubtabLink` raw-string helpers (replaced by templ components).
- [x] 4.4 Register 11 new routes in `dashboard.Mount` using Go 1.22 `mux.HandleFunc` pattern:
  1. `GET /dashboard/projects/list` → `h.requireSession(h.handleProjectsList)` — projects list HTMX partial.
  2. `GET /dashboard/projects/{name}/observations` → `h.requireSession(h.handleProjectObservationsPartial)`.
  3. `GET /dashboard/projects/{name}/sessions` → `h.requireSession(h.handleProjectSessionsPartial)`.
  4. `GET /dashboard/projects/{name}/prompts` → `h.requireSession(h.handleProjectPromptsPartial)`.
  5. `GET /dashboard/admin/users` → `h.requireSession(h.handleAdminUsers)`.
  6. `GET /dashboard/admin/health` → `h.requireSession(h.handleAdminHealth)`.
  7. `POST /dashboard/admin/projects/{name}/sync` → `h.requireSession(h.handleAdminSyncTogglePost)` (admin-gated inside handler).
  8. `GET /dashboard/admin/projects/{name}/sync/form` → `h.requireSession(h.handleAdminSyncToggleForm)`.
  9. `GET /dashboard/sessions/{project}/{sessionID}` → `h.requireSession(h.handleSessionDetail)`.
  10. `GET /dashboard/observations/{project}/{sessionID}/{chunkID}` → `h.requireSession(h.handleObservationDetail)`.
  11. `GET /dashboard/prompts/{project}/{sessionID}/{chunkID}` → `h.requireSession(h.handlePromptDetail)`.
  Path values extracted via `r.PathValue(name)`. Satisfies REQ-106, Design Decision 3.
- [x] 4.5 Implement 11 new handler methods in `dashboard.go`:
  - `handleProjectsList` — parse `q`, `limit`, `cursor`; call `ListProjectsPaginated`; render `ProjectsListPartial(rows, pg)`.
  - `handleProjectObservationsPartial`, `handleProjectSessionsPartial`, `handleProjectPromptsPartial` — extract `{name}` via `r.PathValue("name")`; call respective `ListXxxPaginated`; render partial templ component.
  - `handleAdminUsers` — call `ListContributorsPaginated`; render `AdminUsersPage`.
  - `handleAdminHealth` — call `SystemHealth()`; render `AdminHealthPage`.
  - `handleAdminSyncTogglePost` — check `h.cfg.IsAdmin(r)` → 403 if false; parse `enabled` + `reason` from POST form; call `SetProjectSyncEnabled(name, enabled, h.principalFromRequest(r).DisplayName(), reason)`; 303 + `HX-Redirect` header.
  - `handleAdminSyncToggleForm` — GET form partial for HTMX-driven toggle UI.
  - `handleSessionDetail`, `handleObservationDetail`, `handlePromptDetail` — extract composite path values; call `GetXxxDetail`; validate non-empty ChunkID (`len <= 128`); render `SessionDetailPage` / `ObservationDetailPage` / `PromptDetailPage`.
- [x] 4.6 Replace legacy `helpers.go` content in `internal/cloud/dashboard/helpers.go` with the ported legacy version (~424 L from `engram-cloud`): `Pagination` struct, `parsePagination`, `paginationURL`, `formatTimestamp`, `truncateContent`, `typePillClass`, `renderStructuredContent`, `controlsByProject`, `projectControlReasonValue`, etc. Change import path; remap `ProjectStat` → `DashboardProjectRow`; remove `ProjectSyncControl`-specific helpers that now live in `project_controls.go`.
- [x] 4.7 Insert push-path pause guard in `internal/cloud/cloudserver/cloudserver.go:handlePushChunk` immediately after `authorizeProjectScope` succeeds, before `WriteChunk`. Use structural interface assertion `interface{ IsProjectSyncEnabled(string) (bool, error) }` — NOT added to `ChunkStore` interface. On `!enabled`: call `writeActionableError(w, http.StatusConflict, ..., "sync-paused", ...)`. Satisfies REQ-109, Design Decision 5.
- [x] 4.8 Wire `GetDisplayName: func(r *http.Request) string { return "OPERATOR" }` in `internal/cloud/cloudserver/cloudserver.go:routes()` inside the `dashboard.Mount` call's `MountConfig` literal. Satisfies REQ-103, Design Decision 6.
- [x] 4.9 GREEN: Run all Batch 4 RED tests — all 12 dashboard tests + both cloudserver tests MUST pass. Run `go test ./internal/cloud/dashboard/... ./internal/cloud/cloudserver/...`.
- [x] 4.10 REFACTOR: Factor common auth/error handling into `helpers.go`; keep handler bodies ≤ 30 lines each; ensure every HTMX route returns meaningful standalone HTML (no full shell) when `isHTMXRequest(r)` is true.

---

## Batch 5 — Docs + final regression verify

> Depends on: Batches 1–4 all green. No new RED tests (polish batch).

- [x] 5.1 Update `DOCS.md`: add subsection "Dashboard templ regeneration" documenting `go mod download` prerequisite + `make templ` command + contributor workflow. Satisfies Design Decision 4.
- [x] 5.2 Update `README.md`: add one-line pointer to DOCS.md templ regeneration section (contributor note for dashboard changes).
- [x] 5.3 Update `docs/ARCHITECTURE.md`: add subsection "Dashboard visual-parity layer" covering:
  - Principal bridge (`principal.go`, `MountConfig.GetDisplayName`, nil fallback to `"OPERATOR"`).
  - Push-path pause guard (`cloud_project_controls` table, 409 Conflict on sync-paused, structural interface assertion pattern).
  - Composite-ID URL scheme (`/dashboard/sessions/{project}/{sessionID}`, etc.).
  - Insecure-mode regression guard reference (`TestInsecureModeLoginRedirects`).
- [x] 5.4 Update `docs/AGENT-SETUP.md`: add templ regeneration prerequisite command for contributors touching dashboard templates.
- [x] 5.5 Run `go test ./...` — full suite MUST pass with zero failures.
- [x] 5.6 Run `go test -cover ./...` — confirm overall coverage ≥ 79 % (baseline from `cloud-dashboard-parity`).
- [x] 5.7 Run `go vet ./...` and `gofmt -l ./...` — both MUST report clean.
- [x] 5.8 Verify REQ → test mapping is explicit in this document's TDD Evidence table (fill in during apply).

---

## TDD Cycle Evidence (to be filled during apply)

| Task | Test File | Spec REQ | Layer | RED | GREEN | TRIANGULATE | REFACTOR |
|---|---|---|---|---|---|---|---|
| 1.1 | `templ_policy_test.go::TestStaticAssetByteFloors` | REQ-100 | embed fs | ✓ | ✓ | ✓ | ✓ |
| 1.2 | `templ_policy_test.go::TestTemplGeneratedFilesAreCheckedIn` | REQ-101 | disk/package | ✓ | ✓ | ✓ | ✓ |
| 2.1a | `dashboard_test.go::TestDashboardLayoutHTMLStructure` | REQ-107 | html tokenizer | ✓ | ✓ | ✓ | ✓ |
| 2.1b | `dashboard_test.go::TestStatusRibbonAndFooterPresent` | REQ-107 | substring | ✓ | ✓ | ✓ | ✓ |
| 2.1c | `dashboard_test.go::TestNavTabsRenderedCorrectly` | REQ-107 | html tokenizer | ✓ | ✓ | ✓ | ✓ |
| 2.1d | `dashboard_test.go::TestLoginPageTokenFormAndCopy` | REQ-111 | substring | ✓ | ✓ | ✓ | ✓ |
| 3.1a | `dashboard_queries_test.go::TestDashboardRowDetailFields` | REQ-102 | cloudstore | ✓ | ✓ | ✓ | ✓ |
| 3.1b | `dashboard_queries_test.go::TestCloudstoreSystemHealthAggregates` | REQ-105 | cloudstore | ✓ | ✓ | ✓ | ✓ |
| 3.1c | `dashboard_queries_test.go::TestDashboardPaginationSortStability` | D1 | cloudstore | ✓ | ✓ | ✓ | ✓ |
| 3.2a | `project_controls_test.go::TestProjectSyncControlPersists` | REQ-104 | cloudstore | ✓ | ✓ | ✓ | ✓ |
| 3.2b | `project_controls_test.go::TestProjectSyncControlUnknownProjectDefaultsEnabled` | REQ-104 | cloudstore | ✓ | ✓ | ✓ | ✓ |
| 3.2c | `project_controls_test.go::TestProjectSyncControlListIncludesKnownChunkProjects` | REQ-104 | cloudstore | ✓ | ✓ | ✓ | ✓ |
| 3.3a | `dashboard_test.go::TestGetDisplayNameFallback` | REQ-103 | handler | ✓ | ✓ | ✓ | ✓ |
| 3.3b | `dashboard_test.go::TestPrincipalBridgeNoPanicOnEmptyContext` | REQ-113 | handler | ✓ | ✓ | ✓ | ✓ |
| 4.1a | `dashboard_test.go::TestDashboardHomeHTMXWiring` | REQ-108 | substring | ✓ | ✓ | ✓ | ✓ |
| 4.1b | `dashboard_test.go::TestBrowserPageHTMXWiring` | REQ-108 | substring | ✓ | ✓ | ✓ | ✓ |
| 4.1c | `dashboard_test.go::TestProjectsPageHTMXWiring` | REQ-108 | substring | ✓ | ✓ | ✓ | ✓ |
| 4.1d | `dashboard_test.go::TestAdminPageSurfacePresent` | REQ-107, REQ-112 | route httptest | ✓ | ✓ | ✓ | ✓ |
| 4.1e | `dashboard_test.go::TestAdminHealthPageRendersMetrics` | REQ-106 | route httptest | ✓ | ✓ | ✓ | ✓ |
| 4.1f | `dashboard_test.go::TestAdminUsersPageRendersContributors` | REQ-106 | route httptest | ✓ | ✓ | ✓ | ✓ |
| 4.1g | `dashboard_test.go::TestAdminSyncTogglePosts` | REQ-112 | route httptest | ✓ | ✓ | ✓ | ✓ |
| 4.1h | `dashboard_test.go::TestAdminSyncToggleRequiresAdmin` | REQ-112 | route httptest | ✓ | ✓ | ✓ | ✓ |
| 4.1i | `dashboard_test.go::TestFullHTMXEndpointSurface` | REQ-106 | route httptest | ✓ | ✓ | ✓ | ✓ |
| 4.1j | `dashboard_test.go::TestCopyParityStrings` | REQ-111 | substring | ✓ | ✓ | ✓ | ✓ |
| 4.2a | `cloudserver_test.go::TestPushPathPauseEnforcement` | REQ-109 | route httptest | ✓ | ✓ | ✓ | ✓ |
| 4.2b | `cloudserver_test.go::TestInsecureModeLoginRedirects` | REQ-110 | route httptest | ✓ | ✓ | ✓ | ✓ |

---

## Batch 6 — Connected navigation gap remediation

> Depends on: Batches 1-5 complete (templ components, cloudstore read-model, routes). Scope source: `connected-nav-exploration.md`. UX decision: `handleBrowserSessionDetail` keeps search redirect (legacy sessionID-only URL); new composite URLs come from `SessionsPartial` directly.

- [x] 6.1 RED: Add `TestGetContributorDetailReturnsScopedData` to `internal/cloud/cloudstore/dashboard_queries_test.go` — seed read model with 2 contributors across 2 projects, call `GetContributorDetail("alice")`, assert returned sessions/observations/prompts slices are filtered to alice's projects only. MUST fail (no method). Satisfies (h).
- [x] 6.2 GREEN: Implement `GetContributorDetail(name string) (DashboardContributorRow, []DashboardSessionRow, []DashboardObservationRow, []DashboardPromptRow, error)` in `dashboard_queries.go` — purely in-memory scan of `dashboardReadModel`.
- [x] 6.3 RED: Add `TestListDistinctTypesScansReadModel` — seed read model with observations of types `"bugfix"`, `"architecture"`, `""` (empty), `"bugfix"` again; assert `ListDistinctTypes()` returns `["architecture", "bugfix"]` sorted, no empty. MUST fail. Satisfies (m).
- [x] 6.4 GREEN: Implement `ListDistinctTypes() ([]string, error)` in `dashboard_queries.go` — collect distinct non-empty types from `projectDetails.Observations`, sort alphabetically.
- [x] 6.5 Extend `DashboardStore` interface in `internal/cloud/dashboard/dashboard.go` with `GetContributorDetail` + `ListDistinctTypes`; add `parityStoreStub` stubs in `dashboard_test.go` that return stored fixtures.
- [x] 6.6 RED: Add `TestContributorDetailPageRendersDrillDown` to `dashboard_test.go` — seed parityStoreStub with contributor + sessions/observations/prompts; GET `/dashboard/contributors/alice`; assert body contains `CONTRIBUTOR DETAIL`, `Recent Sessions`, `Recent Observations`, `Recent Prompts` substrings. MUST fail. Satisfies (h).
- [x] 6.7 GREEN: Rewrite `handleContributorDetail` in `dashboard.go` — call `GetContributorDetail`; render `ContributorDetailPage` templ via `renderComponent` (NOT raw HTML).
- [x] 6.8 RED: Add `TestBrowserObservationsAreClickable`, `TestBrowserSessionsAreClickable`, `TestBrowserPromptsAreClickable` to `dashboard_test.go` — GET each browser partial endpoint; assert body contains `href="/dashboard/observations/"`, `href="/dashboard/sessions/"`, `href="/dashboard/prompts/"` respectively. MUST fail (raw HTML has no links). Satisfies (a), (b), (c).
- [x] 6.9 GREEN: Rewrite `handleBrowserObservations` — call `ListRecentObservationsPaginated`; render `ObservationsPartial` templ + pagination via pg parameter.
- [x] 6.10 GREEN: Rewrite `handleBrowserSessions` — call `ListRecentSessionsPaginated`; render `SessionsPartial` templ + pagination via pg parameter.
- [x] 6.11 GREEN: Rewrite `handleBrowserPrompts` — call `ListRecentPromptsPaginated`; render `PromptsPartial` templ + pagination via pg parameter.
- [x] 6.12 RED: Add `TestBrowserTypePillsSourcedFromStore` — seed parityStoreStub with distinctTypes fixture; GET `/dashboard/browser`; assert body contains pill elements for each type. MUST fail (obsTypes is always empty). Satisfies (m).
- [x] 6.13 GREEN: Modify `handleBrowser` to call `h.cfg.Store.ListDistinctTypes()` and pass result as `obsTypes` to `BrowserPage`. Handle error gracefully (nil types → no pill strip).
- [x] 6.14 RED: Add `TestProjectCardShowsPausedBadge` — seed parityStoreStub with `ProjectSyncControl{Project: "proj-a", SyncEnabled: false, PausedReason: ptr("maintenance")}`; GET `/dashboard/projects/list`; assert body contains `Paused` badge for proj-a. MUST fail (handler passes nil controls). Satisfies (i).
- [x] 6.15 GREEN: Modify `handleProjectsList` to call `h.cfg.Store.ListProjectSyncControls()` and pass the controls map to `ProjectsListPartial`. On error, pass empty map (no badges, no panic).
- [x] 6.16 RED: Add `TestAdminProjectsPageRendersToggles` — GET `/dashboard/admin/projects` as admin; assert body contains `hx-post="/dashboard/admin/projects/` and `<form method="post" action="/dashboard/admin/projects/`. MUST fail (route delegates to handleProjects). Satisfies (k).
- [x] 6.17 GREEN: Add dedicated `handleAdminProjectControls` that calls `ListProjectSyncControls()` + renders `AdminProjectsPage` templ. Rewire `/dashboard/admin/projects` route in `dashboard.Mount` to point at it instead of `handleProjects`.
- [x] 6.18 RED: Add `TestProjectDetailShowsPauseAudit` — seed paused `ProjectSyncControl` with `PausedReason` + `UpdatedBy`; GET `/dashboard/projects/{name}`; assert body contains `PROJECT DETAIL`, `Paused`, the reason string, and the updater. MUST fail (handler renders raw HTML without control lookup). Satisfies (j).
- [x] 6.19 GREEN: Rewrite `handleProjectDetail` — fetch `GetProjectSyncControl(name)`; call `GetProjectDetail` (or existing detail query); render `ProjectDetailPage` templ (NOT raw HTML).
- [x] 6.20 REFACTOR: Scan `dashboard.go` for any remaining raw-HTML render functions where templ components exist; remove dead code. Remove `renderObservationsTable`, `renderSessionsTable`, `renderPromptsTable`, `renderContributorDetailStub`, raw `handleProjectDetail` render helpers if unused.
- [x] 6.21 GREEN: `go test ./internal/cloud/dashboard ./internal/cloud/cloudstore ./internal/cloud/cloudserver` — all pass. Then `go test ./...` — full suite PASS with zero failures.
- [x] 6.22 Update `apply-progress.md`: append Batch 6 section with RED/GREEN/REFACTOR evidence rows for each sub-task, final test execution logs.

---

## Verification Logs (to be filled during verify)

_Fill in during `sdd-verify` phase._

---

## Remaining Work (to be filled during apply)

_Track deviations, blocked items, or carry-over tasks here._

---

## Deviations (to be filled if any)

_Document any deviations from this task list with rationale here._
