From c10365053613081fd57fb49ce82d5dffb4eb3cff Mon Sep 17 00:00:00 2001 From: RedStar071 Date: Sat, 5 Sep 2026 17:32:56 +0000 Subject: [PATCH 01/15] docs: plan the work that makes the dashboard usable Records what was verified by building and running the app on 2026-09-05: the dashboard renders and the control plane executes runs, but nothing feeds it, nothing refreshes it, and starting it needs a database. The plan restructures the edge rather than the packages, in five phases with a demonstrable exit criterion each. Co-Authored-By: Claude Opus 5 (1M context) --- docs/PLAN.md | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 docs/PLAN.md diff --git a/docs/PLAN.md b/docs/PLAN.md new file mode 100644 index 0000000..4cdceb6 --- /dev/null +++ b/docs/PLAN.md @@ -0,0 +1,134 @@ +# Piano: dashboard funzionante per code-zero + +Riferimento: [wolfstar-agent-kit](https://github.com/wolfstar-project/wolfstar-agent-kit), +pacchetto `packages/wolfstar-github-agent` (servizio locale + dashboard Nuxt). +Stato verificato il 2026-09-05 su `main` (`8087c6d`). + +## Cosa funziona oggi (verificato, non letto) + +- `turbo run build --filter=@code-zero/dashboard` compila 13 pacchetti e produce `.output/`. +- Con `AUTH_E2E_MEMORY=true` il bundle parte senza Postgres: `/login` 200, signup via + `/api/auth/sign-up/email`, `/` renderizza "Control Plane" con la sessione. +- `POST /api/v1/tasks` con bearer token esegue un run in-process, lo salva nel KV `fs-lite` + (`.data/kv/tasks/*`) e `GET /api/v1/dashboard` lo restituisce con eventi e verdetto. + +## Cosa non funziona (perché la dashboard sembra "vuota") + +1. **Niente la alimenta.** I task nascono solo da un webhook GitHub (serve URL pubblico, secret, + `CODE_ZERO_CHECKOUT_PATH`) o da una chiamata API con token. La UI non ha un form per creare un + task né un pulsante per approvarne uno, anche se `tasks.create` e `approvals.decide` esistono + nel router. `zero run` da CLI non scrive nello stesso store, quindi i run locali non compaiono. +2. **Niente si aggiorna da solo.** `index.vue` usa `useQuery` senza `refetchInterval`, niente SSE. + `tasks.create` blocca la risposta HTTP fino a fine run, quindi Queued e Running non si vedono mai. +3. **Avvio difficile.** Per default servono Postgres, `NUXT_BETTER_AUTH_SECRET`, token, repo + allow-list. `aube run build --filter=...` salta turbo e fallisce su `@code-zero/auth/dist` + mancante: il comando giusto è `aube exec turbo run build --filter=...`. `aube` non è su npm, + solo via mise o GitHub release. +4. **Sidebar con 9 voci inerti** (tasks, runners, models, approvals, findings, repositories, + policies, integrations, settings). Solo `/` e `/audit` esistono. +5. **Nessun contratto di design.** Il kit lavora con `DESIGN.md` e la skill `nuxt-frontend-review` + che avvia la pagina e la confronta col contratto. Qui non c'è nulla da confrontare. + +## Decisione: ristrutturare, non riscrivere + +I pacchetti (`agent`, `runner`, `api`, `source-control`, `models`, `config`) sono solidi, testati e +indipendenti dall'HTTP. Riscriverli è lavoro senza guadagno. Si rifà il **bordo**: come i task +entrano, come lo stato esce, come si avvia in dev. Dal riferimento si prendono quattro idee: + +| Idea del riferimento | Dove finisce in code-zero | +| ----------------------------------------------------- | --------------------------------------------- | +| Uno snapshot server-side spinto via SSE a ogni cambio | `server/api/events.get.ts` + store che emette | +| Il servizio trova lavoro da solo (poll dei repo) | Nitro plugin `server/plugins/poller.ts` | +| Il task torna subito Queued, il run continua in coda | `tasks.create` ritorna dopo `store.save` | +| `DESIGN.md` + review nel browser prima del merge | `apps/dashboard/DESIGN.md` + skill del kit | + +Non si prende: monorepo separato, Nuxt UI (qui c'è UnoCSS con tema già fatto), mock server di +dev, tre provider agent, tray, routine. + +## Fasi + +Ogni fase chiude quando `aube run lint:ci && aube run typecheck && aube test && aube run build` +passano e il criterio "fatto quando" è dimostrato in browser o con `curl`. + +### Fase 0: avvio in un comando (mezza giornata) + +- `apps/dashboard`: script `dev:solo` = `nuxt dev` con `AUTH_E2E_MEMORY=true`, + `AUTH_ENABLE_SIGNUP=true`, token `dev:dev`, modes `dev:observe|suggest|fix`, repo allow-list + dalla env `CODE_ZERO_REPOSITORIES`. Nessun Postgres. +- README: sezione "Primo avvio" con i tre comandi (install, `aube exec turbo run build`, `dev:solo`). +- `bin/check` copiato dal kit, più hook `pre-commit-push` e `oxlint` on save in `.claude/`. + +Fatto quando: da clone pulito, `mise install && aube install && aube run dev:solo` apre la +dashboard e il signup funziona. + +### Fase 1: stato vivo (1 giorno) + +- `TaskStore.save` emette su un `EventEmitter` di processo (`server/utils/store.ts`, 10 righe). +- `server/api/events.get.ts`: SSE con `createEventStream` di h3, push dell'overview a ogni + evento, heartbeat 15 s. +- `app/composables/useLiveOverview.ts`: `EventSource` nativo, a ogni messaggio + `queryClient.invalidateQueries` sull'overview. Riconnessione a 1.5 s. Badge "stale" se l'ultimo + messaggio è più vecchio di 30 s (come `isSnapshotStale` del riferimento). +- `operations.createTask` ritorna il record Queued dopo il primo `store.save`; il run prosegue nello + scheduler. Il webhook fa lo stesso: risponde `accepted` con l'id senza aspettare. + +Fatto quando: un `curl` che crea un task fa comparire la riga Queued, poi Running con gli eventi +che scorrono nella Timeline, poi Completed, senza premere Refresh. + +### Fase 2: la UI fa le cose che il router già sa fare (1 giorno) + +- Inspector: pulsanti Approve e Reject su `needs-human` (`approvals.decide`), con commento. +- Header: "New task" con form repository (select dall'allow-list), mode, trigger. Chiama + `tasks.create`. In `observe` non serve alcuna chiave modello, quindi funziona anche in `dev:solo`. +- Sidebar: eliminare le 9 voci senza pagina. Restano Control Plane e Audit Log. +- `DESIGN.md` scritto dai token già in `uno.theme.ts` e `main.css`. Poche regole, ognuna deve poter + bocciare un cambiamento. + +Fatto quando: la skill `nuxt-frontend-review` gira `dev:solo`, esercita approve, reject e new task +a 1440 e 375, light e dark, e non trova rifiuti duri. Screenshot nella PR. + +### Fase 3: il servizio trova lavoro da solo (2 giorni) + +- `server/plugins/poller.ts`: ogni `CODE_ZERO_POLL_INTERVAL_SECONDS` (default 60) legge le PR + aperte dei repo configurati con l'adapter GitHub di `packages/source-control`, e per ogni head + SHA non ancora visto crea un task `proactive` in `observe` (o nel mode di policy del repo). +- Mappa repo → checkout locale in `CODE_ZERO_REPOSITORIES` (`owner/name=/path`), come i + `trustedCheckoutRoots` del riferimento. Un task per SHA, dedup nello store. +- Worktree per task (`git worktree add` in una cartella temporanea, rimossa a fine run) così due + run sullo stesso repo non si pestano. Il runner già limita cosa può eseguire. +- Il plugin non parte in `dev:solo` senza repo configurati, e si ferma su `nitroApp.hooks.hook('close')`. + +Fatto quando: con un repo reale configurato, un push su una PR fa comparire un task entro un +ciclo di poll senza webhook, e due PR sullo stesso repo girano in worktree distinti. + +### Fase 4: la CLI scrive dove legge la dashboard (mezza giornata) + +- `zero run` con `CODE_ZERO_URL` e sessione da `zero login` chiama `tasks.create` invece di + eseguire in locale, e stampa l'id e il link alla dashboard. Senza URL resta il comportamento attuale. + +Fatto quando: `zero run --proactive` da terminale compare nella Board entro un secondo. + +### Fase 5: pulizia (mezza giornata) + +- `.env.example` del dashboard riordinato: prima i 5 valori per `dev:solo`, poi il resto. +- `docs/architecture.md`: sezione "Live state" che descrive SSE e poller. +- Test: uno per l'emitter dello store, uno per `events.get`, uno Playwright per approve. + +## Rimandato, e quando + +| Cosa | Quando | +| -------------------------------------- | ------------------------------------------------------------ | +| Pagine Runners, Models, Findings, ecc. | quando lo store ha dati che quelle pagine mostrerebbero | +| Nuxt UI al posto di UnoCSS | mai, salvo richiesta: il tema esiste e passa i test | +| Provider multipli nella stessa istanza | già supportato via policy; nessuna UI finché non serve | +| Postgres per i task al posto del KV | quando due istanze devono condividere lo stesso store | +| Riscrittura completa da zero | se le fasi 1-3 mostrano che `packages/api` non regge la coda | + +## Rischi + +- **Run lunghi dentro `nuxt dev`**: HMR riavvia Nitro e uccide il run. Mitigazione: `dev:solo` in + `observe`, run veri solo su `.output/` o con `nuxt dev --no-fork`. +- **KV `fs-lite` senza scrittura atomica**: `list()` legge tutte le chiavi a ogni overview. Va bene + fino a qualche migliaio di task; poi Postgres (già in repo per l'auth). +- **`tasks.create` che non attende più** cambia il contratto REST: chi lo usa in CI deve fare poll + su `tasks.get`. Documentare nel changelog, versione 0.5. From f61351dcc1fe38c0b46d630d41efcec0c6fdb0cc Mon Sep 17 00:00:00 2001 From: RedStar071 Date: Sat, 5 Sep 2026 17:33:04 +0000 Subject: [PATCH 02/15] feat(dashboard): start the dashboard alone with dev:solo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seeing the dashboard required Postgres, a migration, a signing secret, and control-plane tokens before the first page could render, so the quickest way to look at it was not to. `dev:solo` is `nuxt dev --dotenv .env.solo`: the same app, the same router, and the same `/api/auth/**` endpoints, with Better Auth on the in-memory store the Playwright preview server already uses. `.env.solo` is checked in because it holds nothing worth keeping out of the repository — the session store dies with the process, and the control-plane token is only accepted by a server started this way. It is loaded only when a command names it with `--dotenv`, so `.env` and every deployment are untouched. Verified: `turbo run dev:solo --filter=@code-zero/dashboard` from a checkout with no database — `/api/v1/health` 200, `/login` 200, `POST /api/auth/sign-up/email` returns a session, and `/` renders Control Plane with that cookie. check:repo, format:check, and typecheck pass. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 +++ AGENTS.md | 1 + README.md | 17 ++++++++++++++++ apps/dashboard/.env.solo | 29 ++++++++++++++++++++++++++++ apps/dashboard/package.json | 1 + apps/dashboard/server/auth.config.ts | 13 ++++++++----- package.json | 1 + turbo.jsonc | 8 ++++++++ 8 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 apps/dashboard/.env.solo diff --git a/.gitignore b/.gitignore index 7305574..518001c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ test-report.junit.xml .env .env.* !.env.example +# Checked in like the examples above: it configures `dev:solo`, whose whole point is that it holds +# nothing worth keeping out of the repository. +!.env.solo .code-zero/ .data/ *.log diff --git a/AGENTS.md b/AGENTS.md index 2b313b5..32ec3f3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,6 +85,7 @@ cp apps/dashboard/.env.example apps/dashboard/.env ```bash aube run dev # watch workspace development tasks +aube run dev:solo # dashboard alone, no database (apps/dashboard/.env.solo) aube run zero doctor # inspect the local environment aube test # deterministic Vitest suites aube run test:browser # dashboard and marketing browser suites diff --git a/README.md b/README.md index 7beeb74..931921b 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,23 @@ aube run dev The root `.env` configures the CLI. Each app loads its own file: the dashboard uses `apps/dashboard/.env`, while the docs app optionally uses `apps/docs/.env` for `NUXT_APP_BASE_URL`. +To see the dashboard before configuring anything, start it on its own instead: + +```bash +mise install +aube ci +aube run dev:solo # http://localhost:3000, then sign up at /signup +``` + +`dev:solo` is `nuxt dev` reading [`apps/dashboard/.env.solo`](./apps/dashboard/.env.solo) in place of +`.env`: Better Auth runs on an in-memory store, so there is no Postgres to install and no migration +to apply, and the account you create lives until you stop the process. Nothing else about the app +changes — it is the same UI, the same router, and the same authentication endpoints a deployment +serves. Tasks still need a checkout to target, so add one to +`CODE_ZERO_CONTROL_PLANE_REPOSITORIES` in that file; `observe` runs no model, so a task can be +created and inspected without a provider credential. Use `aube run dev` and `apps/dashboard/.env` +for anything that has to persist. + `aube run diff --git a/apps/dashboard/modules/dashboard/components/task/Inspector.vue b/apps/dashboard/modules/dashboard/components/task/Inspector.vue index 79d4822..e054e5e 100644 --- a/apps/dashboard/modules/dashboard/components/task/Inspector.vue +++ b/apps/dashboard/modules/dashboard/components/task/Inspector.vue @@ -66,6 +66,62 @@

{{ task.result.summary }}

+ + +
+

{{ $t('dashboard.inspector.approval.title') }}

+

+ {{ $t('dashboard.inspector.approval.body') }} +

+