Remove internal planning/deploy docs from public repo
ci-cd / quality (push) Waiting to run
ci-cd / e2e (push) Blocked by required conditions

This commit is contained in:
2026-09-10 09:54:40 -04:00
parent 9fac1d42de
commit 66865bc5cc
5 changed files with 0 additions and 1868 deletions
-77
View File
@@ -1,77 +0,0 @@
# Project E — Deploy Guide
Production runs as a docker-compose stack on the deploy host (`10.0.0.52`). The stack has four services: PostgreSQL, the Hono API, the Vite SPA served by Caddy, and the Bun worker. **Komodo now owns builds and deploys** — Gitea Actions runs quality+e2e only.
## Architecture
```
Internet → :3000 (SPA container, Caddy)
├── /api/* → api:3000 (Hono/Bun)
├── /mcp* → api:3000 (Hono/Bun)
└── /* → index.html (SPA fallback)
API (:3001, direct) → PostgreSQL (:5432)
Worker → PostgreSQL
```
Caddy (in the `spa` container) serves the built SPA and reverse-proxies `/api/*` and `/mcp*` to the `api` service. The API is also exposed directly on :3001 for debugging.
## Services
| Service | Container | Image | Ports | Notes |
|---------|-----------|-------|-------|-------|
| `db` | `project-e-db` | `postgres:16-alpine` | 5432 | Data in `project-e-pg-data` volume |
| `api` | `project-e-api` | `git.buzzbee.dev/BuzzbeeSCD/projecte-api:{tag}` | 3001 → 3000 | Hono on Bun |
| `spa` | `project-e-spa` | `git.buzzbee.dev/BuzzbeeSCD/projecte-spa:{tag}` | 3000 → 80 | Built Vite SPA + Caddy |
| `worker` | `project-e-worker` | `git.buzzbee.dev/BuzzbeeSCD/projecte-worker:{tag}` | none | Bun worker |
All services share the `project-e-network` bridge and restart unless stopped.
## CI/CD pipeline
### Gitea Actions (quality + e2e only)
The workflow lives at `.gitea/workflows/ci.yml` and runs on the self-hosted runner `projecte-runner`. It runs only quality checks and e2e tests:
- **`quality`** — runs on every push and PR: typecheck → web build → docker compose build
- **`e2e`** — runs after quality: ephemeral Postgres → db:migrate → Playwright tests
### Komodo (build + deploy)
Komodo manages image builds and stack deploys:
- **Build trigger:** push a `v*` tag → Gitea webhook fires → Komodo builds images → pushes to Gitea registry
- **Deploy trigger:** Komodo procedure `release-projecte` builds all three images, then deploys the stack on `10.0.0.52`
- **Webhook URL:** `https://komodo.example.com/listener/github/repo/{id}/build`
- **Rollback:** re-deploy a previous version tag via Komodo UI or API
## Deploying
### Primary: Komodo
1. Push a version tag: `git tag v1.0.0 && git push origin v1.0.0`
2. Komodo builds images and deploys automatically
3. Verify: `docker ps` on .52, health checks
### Break-glass: deploy.sh
If Komodo is unavailable, `script/deploy.sh` still works:
```bash
ssh projecte
cd /opt/app/ProjectE
export PROJECTE_IMAGE_TAG=v1.0.0
bash script/deploy.sh
```
This pulls images from the registry and redeploys. It no longer builds — that's Komodo's job.
## Rollback
1. In Komodo UI: Deployments → projecte → select previous version tag → Redeploy
2. Or via API: `POST /execute/DeployStack` with the previous image tag
3. Verify health checks pass
## Secrets
Komodo manages runtime secrets (POSTGRES_PASSWORD, AUTH_SECRET, etc.) as Komodo variables. The host `.env` is kept as break-glass fallback only.
-100
View File
@@ -1,100 +0,0 @@
# Project E — UI Redesign Baseline
**Date:** 2026-07-31
**Server hostname:** `projecte` (10.0.0.204)
**Branch:** `redesign/ui-v2` (from `integration/ux-28-gaps`, commit `c1e0a08`)
**Bun version:** 1.3.14
**DB schema snapshot:** `.migration-baseline-schema.sql` (1372 lines)
## Disk before cleanup
- 20G total, 11G used, 9.1G free
- `node_modules`: 415M (root) + 294K (apps/web)
- `.next` build outputs: 109M
- Total repo size: 575M
## Disk after cleanup
- 20G total, 12G used, 9.0G free
- Repo size: 48M (node_modules, .next, tsbuildinfo removed)
- ~527 MB freed from build artifacts
## Container state after T0
| Container | Status |
|-----------|--------|
| `project-e-db` | Running (healthy) — PostgreSQL 16 |
| `project-e-worker` | Running — Node.js worker |
| `project-e-web` | **Stopped & removed** — Next.js (big-bang per user decision) |
## URL status
- http://10.0.0.204:3000 — **DOWN** (until Phase 7 deploys the new SPA)
## What T1 (Phase 1 Scaffold) needs to do next
1. Create the Vite SPA scaffold in `apps/web/` (replacing the old Next.js app)
2. Create the Hono API scaffold in `apps/api/`
3. Set up Bun workspace in root `package.json`
4. Wire up the new `docker-compose.yml` for the new stack
5. Reference `.migration-baseline-schema.sql` for the DB schema the new API must preserve
6. Use `bun` (v1.3.14) for all package management and runtime
7. Keep `db` and `worker` containers running
## T1 — Phase 1 Scaffold (completed)
### Legacy code moved
- Old Next.js `apps/web/``apps/web-legacy/` (preserved for T2-T8 reference)
### New apps created
| App | Path | Tech | Port |
|-----|------|------|------|
| SPA | `apps/web/` | Vite 5 + React 19 + TanStack Router/Query + shadcn/ui | :3000 (dev) |
| API | `apps/api/` | Hono 4 + Bun | :3001 |
| Worker | `apps/worker/` | Bun + Drizzle | — |
### Shared infrastructure
- `db/client.ts` — shared Drizzle client (postgres-js driver)
- `db/` — root-level directory (shared by api + worker)
- `bunfig.toml` — Bun package manager config
- Root `package.json` — Bun workspaces monorepo (`apps/*`, `packages/*`)
### Docker
- `Dockerfile.api` — Bun base, copies apps/api + db + packages
- `Dockerfile.worker` — Bun base, copies apps/worker + db + packages
- `Dockerfile.spa` — Multi-stage: Bun build → Caddy serve
- `Caddyfile` — Serves dist on :80, reverse-proxies /api/* and /mcp to api:3000
- `docker-compose.yml` — 4 services: db, api, spa, worker (old web service removed)
## Phase 7 — Deploy (completed)
**Deploy time:** 2026-08-01T02:40Z
**Commit:** `fc59431140329bd93223dfae3c72406d9e02646e`
**Branch:** `redesign/ui-v2`
### Image SHAs
| Image | SHA256 |
|-------|--------|
| `projecte-api` | `fc46878d1618bb3a0d6a43d645a21cad14caa1bb6eaf773e4e32579a268adc10` |
| `projecte-spa` | `1cced4e61fb35d31df53225b3c8f5863a3d581b82ed961c13286bd5e3f940f48` |
| `projecte-worker` | `7fc57170100f6cf6b12439835f5700383c7c4570eecf3c4ab3f06fa739d10d07` |
### Compose file SHA
`bb21417829c866048509f81881304e81b5e29430270a278e0b3a77caa68f8b1b`
### Verification results
| Check | Result |
|-------|--------|
| `docker compose ps` — all 4 services running, db healthy | ✅ |
| `curl :3000/api/health` — 200 + `{"status":"ok"}` | ✅ |
| `curl :3000/` — 200 + HTML | ✅ |
| Login (user@example.com) — token received | ✅ |
| `/api/domains` — 2 domains returned | ✅ |
| `/mcp` — endpoint reachable (requires API key) | ✅ |
| All 13 page routes return 200 | ✅ (13/13) |
### Container state
| Container | Status | Ports |
|-----------|--------|-------|
| `project-e-db` | Running (healthy) | :5432 |
| `project-e-api` | Running | :3001 |
| `project-e-spa` | Running | :3000 |
| `project-e-worker` | Running | — |
### URL
- http://10.0.0.204:3000 — **LIVE** (SPA served by Caddy, API reverse-proxied)
-67
View File
@@ -1,67 +0,0 @@
Project E — Full Review, Functional Completion & CI/CD Plan
What we know
Actual stack: Vite + React 19 + TanStack Router/Query + shadcn/ui SPA (apps/web) · Hono/Bun API (apps/api) · Bun worker · Drizzle/Postgres. README/AGENTS.md describe a Next.js app that no longer exists — docs are stale.
Deployment: docker-compose on the projecte host (10.0.0.204): db, api, spa (Caddy), worker. Secrets come from a host .env (already/soon exists). Remote = git.buzzbee.dev (Gitea).
CI/CD: No workflows exist yet. Will add Gitea Actions (.gitea/workflows/) using runner label projecte-runner, deploy = docker compose on the same host, triggered by push to main + manual dispatch.
Phase 0 — CI/CD foundation (do first, so everything after ships through it)
.gitea/workflows/ci.yml with two jobs:
quality (every push + PR): bun install --frozen-lockfile → typecheck (api + worker + web) → vite build → docker compose build (catches Dockerfile breakage early).
deploy (push to main + workflow_dispatch): docker compose build → apply migrations → docker compose up -d → health checks (SPA 200, /api/health DB ping, login smoke via POST /api/auth/credentials).
Deploy job runs on projecte-runner; uses host .env for secrets; keeps previous image tags for rollback.
Migration story — currently broken: db:push doesn't apply drizzle/*.sql, README references a non-existent drizzle/0000_postgres.sql, and 0005 is hand-written. Reconcile: verify the 5 migration files reproduce the .migration-baseline-schema.sql schema, make a single idempotent db:migrate script, and call it in the deploy job before up -d.
Docs refresh: rewrite README, AGENTS.md, DEPLOY.md to describe the real stack, deploy flow, and Gitea Actions pipeline.
Conventions to fix first (they gate everything): implement requireWorkspaceAccess() per AGENTS.md, a shared recordActivity wrapper that always derives workspaceId correctly, and a shared error helper — then phases 14 build on them.
Phase 1 — Critical functional fixes (app must stop lying)
# Fix Where
1 Calendar delete — useApiMutation("delete", "", …) → DELETE /api 404. Use DELETE /calendar/events/{id} + wire drag/resize handlers + enable withDragAndDrop calendar.tsx:173
2 Canvas persistence — blocks are local state; handleSave sends only {name}. Add canvas-card CRUD endpoints (API), persist blocks (content/type/order), load on open, save on debounce, route detail page through it canvas.tsx:243, API routes/canvas.ts
3 Logout — no POST /api/auth/logout; cookie never cleared. Add route that clears the session cookie; client already calls it in 3 places API routes/auth.ts, sidebar.tsx
4 Graph edge write/delete 500 — recordActivity({ workspaceId: "" }) → invalid uuid. Derive domain from the edge API routes/graph.ts:150,190
5 Analytics fake data — replace Math.random() with real endpoints; fix /analytics/habits domain scoping; make /analytics/projects real; real CSV export analytics.tsx:137, API routes/analytics.ts
6 Task board DnD — add droppable columns, persist status change + order via API tasks.tsx:215
7 Notes editor — wire installed Tiptap (replace contentEditable): autosave debounce, placeholder fix, stop stopPropagation killing shortcuts, render HTML on detail page (use editor output, not raw innerHTML text) notes.tsx, notes/$id.tsx
8 Passkey security hole — authenticates without verifying signature. Either implement real WebAuthn verification or remove the passkey surface API routes/auth.ts:115
9 Domain/owner scoping (IDOR) — add owner/domain checks to all by-ID GET/PATCH/DELETE routes; scope /api/search and /api/export to active domain all API routes
10 Command palette routing — singular vs plural types → non-existent /search/{id}. Fix type→route map; fix @mention agent fetch (response is {items}, q ignored) command-palette.tsx:313
11 Agent activity SSE handler — wrong event type match + data.payload doesn't exist; would crash on match agents/activity.tsx:54
12 Graph fly-to — centerAt(undefined, undefined) on raw nodes (no x/y). Track positions via onNodeDrag or search the rendered graph data graph.tsx:157
Phase 2 — UX & polish pass (current shadcn look, elevated)
Rebuild login on the design system: Input/Button/Label, autofocus, loading/disabled state, error styling, branding mark. (Worst screen in the app today.)
Feedback everywhere: mount Sonner <Toaster/>; add success/error toasts to every create/update/delete mutation; inline error handling on failed mutations.
State coverage: shared LoadingState (skeleton), EmptyState (icon + text + CTA), ErrorState components; apply to every page (calendar, graph, analytics, settings tabs, search, agents…).
Settings honesty: implement density (CSS variable that actually changes spacing), wire sidebarPos to reposition the sidebar, add real reduce-motion CSS — or remove the controls.
Typography: add a real font via --font-sans (self-hosted or Google Fonts) — currently browser-default only.
Consolidate color maps: one shared src/lib/status-colors.ts (status/priority/entity colors as semantic tokens) replacing 8+ divergent copies; make accent theming actually visible.
Unify detail routes (tasks/$id, habits/$id, projects/$id, notes/$id, canvas/$id) with the app design language; make list UIs link to them.
Dashboard: fix sort=dueDate→due_date (widget shows wrong tasks), align realtime invalidation keys with widget query keys, make widget grid responsive.
Responsive: notes/graph/settings panes on mobile, dashboard grid stacking, calendar height.
A11y: aria-labels on icon/scale buttons, radiogroup for mood/energy, fix dangerouslySetInnerHTML snippet XSS surface (sanitize <mark>), notification badge label.
Phase 3 — Secondary feature completion
Canvas: persist block editor (from Phase 1) + real save UX + delete blocks/cards.
Daily notes: timezone bug (UTC midnight stored, local day shown), add DELETE /api/daily-notes/:id, autosave cleanup, mood/energy before content creates the note.
Graph: domain picker (shared with topbar), node detail → navigable entity links.
Analytics: real charts (tasks completed over time, created vs completed, habit consistency, project progress, time per domain, productivity heatmap) + honest empty states + working CSV.
Domains: active-domain selection store + picker; API already supports multi-domain.
Tags: assign/remove tags on tasks/habits/notes from the UI (currently create-only); fix in-memory tag filter after pagination.
Import/Export: scope export to domain (currently exports every user's data), implement advertised CSV format, validate import.
Custom fields: decide and wire to entities, or hide if inert.
Phase 4 — Advanced features (selected; largest phase)
Webhooks pipeline: enqueue webhook_delivery jobs on entity events; fix POST /webhooks/:id/test; worker already knows how to deliver — just wire the queue.
Recurring tasks: create scheduledJobs from recurrenceRule; worker recurring_spawn already exists — wire creation.
Agents: real CRUD UI, permission editing, activity filters (from/to/action honored server-side).
Notifications: real notification count/feed instead of hardcoded 0.
Reports & Milestones: minimal working versions, or remove from nav until built (recommend: build minimal).
MCP polish: validation, correct error codes, domain scoping, drop the dead ?? sql`` `` `` `` leftover.
Worker: wire ai_dispatch or mark it disabled.
Phase 5 — E2E rewrite + docs
Rewrite the Playwright suite (e2e/*.spec.ts) for the new SPA: auth cookie flow, tasks/habits/projects/notes/calendar/canvas/daily/graph/search/analytics/settings/agents-activity; new auth helper; drop specs for removed features (reports/mcp UI).
CI: dedicated ephemeral Postgres + dev servers for E2E, run chromium only in CI (full 5-browser matrix locally/on demand); npx playwright install --with-deps chromium on the runner.
Update docs/API.md to match reality.
Risks & mitigations
Huge scope → sequenced phases; each phase lands on main and deploys independently, so value ships incrementally.
DB migrations on live prod data → Phase 0 makes migrations idempotent + verifiable before any deploy; backup volume (project-e-pg-data) noted in DEPLOY.md.
E2E against prod → E2E runs in CI against an ephemeral test DB, never prod.
Secrets → host .env (gitignored), never committed; Gitea Actions secrets only if needed later.
DnD persistence + Tiptap are the two most invasive frontend changes → done early (Phase 1) so regressions surface in CI before polish.
Verification
Per user: CI + manual testing — quality job gates every push (typecheck/build/docker build); deploy job gates main; you verify on the live site (10.0.0.204:3000) after each deploy. E2E suite (Phase 5) becomes the automated gate once rewritten.
-252
View File
@@ -1,252 +0,0 @@
# Project E — Phase 8 Interactive Test Report
**Verdict: NEEDS FIXES — 2 CRITICAL + 3 HIGH + 3 MEDIUM bugs must be fixed before production use**
**Test date:** 2026-08-01 (UTC)
**App URL:** http://10.0.0.204:3000
**Server:** `hermes@10.0.0.204` (commit `54d788e`, branch `redesign/ui-v2`)
**Tested by:** coding-coder (T10)
**Tester login:** `user@example.com` / `87AwFGzfMuNcitie0g1C`
**Containers up:** project-e-db (healthy), project-e-api, project-e-spa, project-e-worker
**All 15 routes return HTTP 200 at the SPA shell level** (SPA serves index.html for everything; authentication is enforced by the client).
---
## Summary
| Metric | Value |
|---|---|
| Pages visited | 15 / 15 |
| Interactive elements cataloged | ~140 |
| Operations exercised | ~45 |
| **CRITICAL bugs** | **2** |
| **HIGH bugs** | **3** |
| **MEDIUM bugs** | **3** |
| **LOW bugs** | **2** |
| Bug cards filed | 5 |
| Screenshots saved | 0 (Camofox backend does not write PNGs; see "Limitations" below) |
---
## Pass/Fail per page
| # | Page | Status | Notes |
|---|---|---|---|
| 1 | `/login` | **FAIL (CRITICAL)** | Form submits, but no auth cookie/token is persisted. Every login attempt loops back to /login. |
| 2 | `/` Dashboard | PASS (with caveat) | Renders, but shows "Unknown widget: stats" for a pre-existing widget of an unsupported type. |
| 3 | `/tasks` | PASS | Board view, list toggle, New task dialog, search, combobox, edit dialog, delete confirm all work. CRUD verified (created + deleted "T10 test task from card"). |
| 4 | `/habits` | PASS | List renders, "Mark complete" increments streak (Debug habit 0→1). |
| 5 | `/projects` | PASS | Card list, progressbar, "New Project" dialog (Name/Description/Status/Color/Target Date). |
| 6 | `/projects/:id` | **FAIL (HIGH)** | Route file missing — every project detail URL renders "Not Found". |
| 7 | `/notes` | PASS | List + split editor; clicking a note loads it in the right pane. Backlinks, Version history, Delete buttons present. |
| 8 | `/calendar` | PASS | Agenda (default), Month, Week, Day, Prev/Today/Next, New Event dialog. Event "v-test" visible across days. |
| 9 | `/graph` | **FAIL (CRITICAL)** | Page loads but graph is empty. SPA hard-codes `?domain=placeholder`, which 500s on the API. |
| 10 | `/search` | **FAIL (HIGH)** | Input + type filters work, but returns 0 results for any query. `search_vector` column is never populated. |
| 11 | `/analytics` | PASS | 6 charts render (Tasks Completed, Created vs Completed, Habit Completion, Project Progress, Time per Domain, Productivity Heatmap). Combobox for date range. |
| 12 | `/agents/activity` | **FAIL (HIGH)** | "All agents" filter calls `/api/agents/_all/activity` which 500s. Specific agent works. |
| 13 | `/canvas` | PASS | "New Canvas" button, list ("v-canvas", 0 blocks freeform). |
| 14 | `/daily` | PASS | Calendar sidebar (Jul 2026), Mood/Energy buttons (110), "Start Writing" CTA. |
| 15 | `/settings` | PASS (with caveats) | All 9 tabs render and switch. Appearance tab buttons are decorative — see Bug #6. |
---
## Bugs found
### BUG #1 — CRITICAL: Login is completely broken (no auth persistence)
- **Severity:** CRITICAL (blocks use)
- **Page:** `/login` + global
- **What happened:** Filling email + password and clicking "Sign In" does nothing. The page does not transition; refreshing brings back the login form. (Verified manually in browser + curl.)
- **Steps to reproduce:**
1. `browser_navigate http://10.0.0.204:3000/login`
2. `browser_type` user@example.com into email textbox
3. `browser_type` 87AwFGzfMuNcitie0g1C into password textbox
4. `browser_click` "Sign In" button
5. Observed: URL stays at `/login`, no error message displayed, dashboard never appears.
- **Expected behavior:** Successful login navigates to `/` (dashboard).
- **Actual behavior:** Login API returns 200 + `{user, token}`, but the response is **not** converted to a `session` cookie or localStorage entry. The browser is then unauthenticated for every subsequent call.
- **Root cause:**
- `apps/api/src/routes/auth.ts` line 44: `authRoutes.post("/credentials", ...)` returns JSON but **does not call `setCookie()`**. The auth middleware in `apps/api/src/middleware/auth.ts:46` reads the `session` cookie via `c.req.header("Cookie")` — but the cookie is never set.
- `apps/web/src/routes/login.tsx:1830` reads the JSON, only checks `res.ok`, and navigates to `/` without writing the token anywhere.
- `apps/web/src/lib/api.ts:28` uses `credentials: "include"` (so the SPA **expects** the server to set the cookie) but the SPA never falls back to setting `Authorization: Bearer <token>` from a stored value.
- **Console errors:** Browser console capture unavailable (Camofox backend), but verified by curl that the response has no `Set-Cookie` header and the SPA has no `localStorage.setItem("token", ...)` or `document.cookie = ...` in `login.tsx`.
- **Workaround for the test:** I dropped a static HTML helper at `/usr/share/caddy/auth-helper.html` that sets the cookie from a hard-coded token, then redirects. With that, the rest of the app is fully usable.
### BUG #2 — CRITICAL: Graph page is empty (hard-coded "placeholder" domain)
- **Severity:** CRITICAL (core feature completely broken)
- **Page:** `/graph`
- **What happened:** The page loads the controls (search, Filters, Zoom in/out, Reset view) but the canvas is empty. No nodes, no edges.
- **Steps to reproduce:**
1. Log in
2. Navigate to `/graph`
3. Wait — the canvas stays blank indefinitely.
- **Expected behavior:** At least 20 nodes should render (the dataset has 50+ entities).
- **Actual behavior:** `useApiQuery(["graph","nodes"], "/graph/nodes?domain=placeholder")` and `.../edges?domain=placeholder` are sent. The API validates the `domain` parameter and returns `{"error":{"code":"VALIDATION_ERROR","message":"domain parameter is required"}}` (status 400 / 500 depending on code path).
- **Root cause:** `apps/web/src/routes/_app/graph.tsx` line ~30 hard-codes the string `"placeholder"` instead of resolving the active domain from the `useDomainStore` / domain list. The route never received a real domain id.
- **Console errors:** API call returns 500 with "Failed to get graph nodes". UI silently shows an empty canvas.
### BUG #3 — HIGH: Search returns 0 results for every query
- **Severity:** HIGH (core feature completely broken)
- **Page:** `/search` (and any UI that uses full-text search)
- **What happened:** Type "test" → "No results found for 'test'". Type "tasks" → same. The dataset has 20 tasks, 8 habits, 6 projects, 7 notes with the word "test" in the title.
- **Steps to reproduce:**
1. `browser_navigate /search`
2. `browser_type` "test" into the search textbox
3. Observed: "No results found for 'test'"
- **Expected behavior:** Returns tasks/notes/etc. that match.
- **Actual behavior:** `/api/search?q=test` returns `{"results":[],"totalCount":0,"query":"test"}`.
- **Root cause:** The Postgres `search_vector tsvector` column is **never populated**. `docker exec project-e-db psql -U project_e -d project_e -c "SELECT count(*), count(search_vector) AS with_vec FROM tasks"` shows `0 of 20` tasks have a vector. There is no Drizzle migration that creates a trigger or GENERATED column to populate the vector on INSERT/UPDATE.
- **Fix:** Add a Postgres trigger (BEFORE INSERT OR UPDATE) that sets `NEW.search_vector = to_tsvector('english', coalesce(NEW.title,'') || ' ' || coalesce(NEW.description,''))` for each searchable table, then backfill existing rows.
### BUG #4 — HIGH: Project detail page does not exist
- **Severity:** HIGH (one of the spec'd 7 core entity detail pages is missing)
- **Page:** `/projects/:id`
- **What happened:** Any project detail URL renders a 404 "Not Found" page.
- **Steps to reproduce:**
1. From the projects list, click any project card
2. Observed: page shows "Not Found"
- **Expected behavior:** Project detail page with Overview, Tasks, Sections, Members, Notes, Activity tabs (per the Phase 8 test plan).
- **Actual behavior:** The `apps/web/src/routes/_app/projects/` directory exists but is empty — no `[id].tsx` route file. T6's PR claim "7 core entity pages built" was inaccurate for the detail view.
- **Fix:** Add `apps/web/src/routes/_app/projects/$id.tsx` (TanStack Router file-based or programmatic). Same likely true for `/tasks/:id`, `/habits/:id`, `/notes/:id`, `/canvas/:id` — only the list pages exist.
### BUG #5 — HIGH: "All agents" filter on Agent Activity page returns 500
- **Severity:** HIGH (default filter on the page is broken)
- **Page:** `/agents/activity`
- **What happened:** The "All agents" filter (which is the default) calls `/api/agents/_all/activity` and gets a 500, leaving the page stuck on "Loading activity...".
- **Steps to reproduce:**
1. Log in
2. `browser_navigate /agents/activity`
3. Observed: spinner "Loading activity..." stays forever; API call returned 500.
- **Expected behavior:** Should show all agents' activity.
- **Actual behavior:** The SPA passes the literal string `_all` as the agent id. The API route at `apps/api/src/routes/agents.ts:180` does `db.select().from(agentActivity).where(eq(agentActivity.agentId, id))` — there's no `_all` sentinel handling; passing `_all` to Postgres fails on the UUID cast.
- **Fix:** Either (a) make the SPA filter out the `_all` case and call a new endpoint `/api/agents/_all/activity` (or a `?agentId=` query param), or (b) add a server-side branch that skips the `WHERE` when `id === "_all"`.
### BUG #6 — MEDIUM: Two parallel theme systems fight each other in Settings
- **Severity:** MEDIUM (theme button is decorative, doesn't sync across tabs)
- **Page:** `/settings` (Appearance tab)
- **What happened:** The Light/Dark/System buttons in settings appear to work (the active border updates), but they're driven by a local `useState` in `settings.tsx` that is never wired to the rest of the app. A `useThemeStore` (Zustand) in `lib/stores/use-theme-store.ts` is what `theme-provider.tsx` actually reads from, but no UI writes to it.
- **Steps to reproduce:**
1. Log in, open `/settings`, click "Light"
2. Open a new tab → page still renders as dark
3. Open the command palette (Cmd+K) and click "Switch to light mode" → that does work (it writes to Zustand)
4. Go back to Settings → "Dark" is highlighted again (because settings re-reads from `localStorage.getItem("theme")` on mount)
- **Expected behavior:** One theme system, consistent across all entry points.
- **Actual behavior:** Two parallel state stores. Settings' buttons write to `localStorage` + `document.documentElement.className`; the command palette writes to Zustand. They don't sync.
- **Fix:** Make the Appearance tab use the Zustand store directly, or remove the Zustand store and migrate everything to the `localStorage` + `classList` approach.
### BUG #7 — MEDIUM: Dashboard has a stale "Unknown widget: stats"
- **Severity:** MEDIUM (cosmetic, easy to fix)
- **Page:** `/` (Dashboard)
- **What happened:** The dashboard shows a card titled "v" with body "Unknown widget: stats". It's a widget that exists in the DB but the SPA's `WIDGET_TYPES` array (in `_app/index.tsx`) doesn't include a renderer for `type: "stats"`.
- **Steps to reproduce:**
1. Log in, land on `/`
2. Observed: widget card "v" with "Unknown widget: stats" message
- **Expected behavior:** Either the widget renders something meaningful, or it's removed from the default seed.
- **Actual behavior:** Pre-existing widget from earlier phases that doesn't have a renderer. User can hit the "Remove widget" button (e16) to delete it.
- **Fix:** Add a "stats" widget renderer, or remove the widget from the seed/migration.
### BUG #8 — MEDIUM: `/api/agents/activity` (no id) returns 500
- **Severity:** MEDIUM
- **Page:** API (called by various agent-activity UIs)
- **What happened:** `GET /api/agents/activity` returns `{"error":{"code":"INTERNAL_ERROR","message":"Failed to get agent"}}` (500). The route was renamed to `:id/activity` but the bare `/activity` path was never added.
- **Steps to reproduce:**
- `curl -H "Authorization: Bearer …" http://10.0.0.204:3000/api/agents/activity` → 500
- **Expected behavior:** 404 with a helpful message, or 200 with all agents' activity.
- **Actual behavior:** 500 "Failed to get agent" — the bare path falls into the `/:id/activity` handler with `id === "activity"`, which fails the UUID lookup.
### BUG #9 — LOW: Page-level console errors not capturable
- **Severity:** LOW (test infrastructure, not app)
- **Note:** The Camofox browser backend used by the test agent does not persist console.log / JS errors (`browser_console` always returns 0 messages). The agent had to rely on direct API curl + DOM snapshots to characterize behavior. This is not a bug in the app under test.
### BUG #10 — LOW: 7 entity detail routes missing
- **Severity:** LOW (consistency)
- **Note:** In addition to the missing `/projects/:id` (Bug #4), the route files for `/tasks/:id`, `/habits/:id`, `/notes/:id`, `/canvas/:id` are also absent. The current UX uses in-page side panels for editing (which works) so detail pages are a polish item rather than a blocker — except for `/projects/:id`, which the Phase 8 spec lists as a distinct page.
---
## Performance observations
- **Initial page loads:** All 15 pages return HTTP 200 (HTML) in <300ms via Caddy. The SPA bundle is loaded once and routes are client-side; navigation between pages is instant after the initial load.
- **API response times:** Health endpoint 12ms; list endpoints 20100ms with 2050 rows.
- **No jank observed** during interactions (New Task dialog, New Project dialog, sidebar drawer, command palette).
- **Analytics page** renders 6 charts simultaneously without lag.
- **The auth-helper workaround** is the only thing slowing down the test; with a working login flow, an end-to-end test would feel snappy.
---
## Accessibility observations
- **Keyboard nav:** Tab order is reasonable on every tested page. Dialogs trap focus correctly (New Task, New Project, New Event, New Canvas, etc.).
- **Shortcuts help:** `?` opens a well-structured dialog with all shortcuts listed (works).
- **Command palette:** `Cmd+K` works (via clicking the topbar button); cmdk supports arrow keys, Enter to select, Esc to close.
- **Concerns:**
- Theme buttons in Settings are `<button>` elements with no `aria-pressed` / `aria-checked` — a screen reader can't tell which theme is active.
- The "Notifications" topbar button shows `"0"` as plain text; no `aria-label="0 notifications"`.
- Mood/Energy buttons in Daily Notes are a 110 grid with no group label visible (the heading "Mood" / "Energy" is there but no `role="radiogroup"`).
---
## Verdict
**NOT production-ready as a single-user primary tool** — the login flow is fundamentally broken, which means a real user (who can't deploy an `auth-helper.html` to bypass it) cannot get past the login screen. The agent activity "all" filter, the graph, and full-text search are also broken, but those are addressable on a per-page basis.
### Top 3 must-fix items
1. **Fix login persistence** (Bug #1). Server must `setCookie('session', token, {httpOnly, secure, sameSite, path, maxAge})` on `POST /api/auth/credentials`, OR the login page must write the token to `localStorage` and the `api.ts` must read it and set `Authorization: Bearer <token>` on every call. Without this, the app is unusable.
2. **Populate `search_vector`** (Bug #3). Add a Postgres trigger (or Drizzle-generated column) on `tasks`/`notes`/`projects`/`habits` that calls `to_tsvector('english', title || ' ' || description)` on INSERT/UPDATE, then backfill existing rows.
3. **Resolve the graph's domain id** (Bug #2). Replace the hard-coded `?domain=placeholder` with `useDomainStore.getState().activeDomainId` (or fetch the first domain from `/api/domains`).
### What works well
- 13/15 pages render correctly.
- Tasks CRUD end-to-end is solid (create / edit / delete via dialogs).
- Habits completion + streak display works.
- Calendar (agenda/month/week/day) renders events.
- Analytics renders 6 distinct charts.
- Settings has 9 working tabs.
- Command palette is well-built.
- Keyboard shortcuts (g+t, ?, Cmd+K) all work.
- The shell (sidebar, topbar) is consistent and well-organized.
---
## Limitations of this test
1. **No screenshots saved.** The Camofox browser backend does not persist PNGs (only the agent's own session-cache screenshots are kept, not committed). The test relied on browser_snapshot accessibility trees and direct DOM observation. The user can re-run any of these flows in a real browser to see what the agent saw.
2. **No JS console capture.** Browser console is unavailable in this environment; the agent had to infer errors from API responses and DOM state. The user should open DevTools and re-run a couple of flows to confirm there are no client-side exceptions.
3. **Realtime SSE** was not exhaustively tested (no two-tab verification of the same session). The `/api/realtime` endpoint and `use-realtime` hook are wired into several pages, but a side-by-side test was out of scope.
4. **The auth-helper workaround** was needed to bypass Bug #1. The agent did NOT modify production code; the helper was placed in a one-time static file inside the running `project-e-spa` container and will be removed by the next container restart.
---
## Repro: how to verify the verdict yourself
```bash
# 1. Confirm the app is up
curl -s http://10.0.0.204:3000/api/health
# → {"status":"ok","database":{"connected":true,"ping_ms":2},...}
# 2. Verify the login response is missing Set-Cookie
curl -i -X POST http://10.0.0.204:3000/api/auth/credentials \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"87AwFGzfMuNcitie0g1C"}'
# → no Set-Cookie header; only {"user":{...},"token":"..."}
# 3. Verify search is broken
TOKEN=$(curl -s -X POST http://10.0.0.204:3000/api/auth/credentials \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"87AwFGzfMuNcitie0g1C"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
curl -s -H "Authorization: Bearer $TOKEN" "http://10.0.0.204:3000/api/search?q=test"
# → {"results":[],"totalCount":0,"query":"test"}
# 4. Verify graph is broken (with auth)
curl -s -H "Authorization: Bearer $TOKEN" "http://10.0.0.204:3000/api/graph/nodes?domain=placeholder"
# → {"error":{"code":"INTERNAL_ERROR","message":"Failed to get graph nodes"}}
```
File diff suppressed because it is too large Load Diff