feat: remove nextjs production runtime (#1389)

This commit is contained in:
Carlos Florêncio
2026-06-18 18:36:48 +01:00
committed by GitHub
parent 2781556ead
commit aee1846cd5
368 changed files with 10228 additions and 3306 deletions
+8 -7
View File
@@ -12,7 +12,7 @@ flowchart TB
run --> resolve["Resolve installed runtime"]
dev --> makedev["make dev"]
start --> binary["Local binary + next start"]
start --> binary["Local binary<br/>(embedded Vite assets)"]
resolve --> envvar["KANDEV_BUNDLE_DIR<br/>(Homebrew, tests)"]
resolve --> npmpkg["@kdlbs/runtime-{platform}<br/>(npm/npx)"]
@@ -26,7 +26,7 @@ flowchart TB
subgraph supervisor["Process Supervisor"]
direction LR
s1["Manages backend + web processes"]
s1["Manages backend process"]
s2["Handles graceful shutdown"]
s3["Forwards signals"]
end
@@ -34,7 +34,7 @@ flowchart TB
## Overview
The Kandev CLI (`kandev`) is the primary way to run the Kandev application. It launches the backend and web processes from an installed runtime bundle and provides a unified interface for end users and developers.
The Kandev CLI (`kandev`) is the primary way to run the Kandev application. It launches the backend from an installed runtime bundle; the backend serves the web assets, API, and WebSocket gateway.
## Installation
@@ -107,11 +107,12 @@ kandev dev
**What happens:**
1. Locates the repo root (looks for `apps/backend` and `apps/web`).
2. Runs `make dev` for the backend (Go with hot-reload).
3. Runs `pnpm dev` for the web app (Next.js dev server).
3. Runs `pnpm dev` for the web app (Vite dev server).
### `kandev start`
Runs the application using local production builds. Requires running `make build` first.
Runs the application using the local production backend binary. Requires running
`make build` first so the Vite SPA is embedded into that binary.
```bash
make build
@@ -124,7 +125,7 @@ kandev start
|--------|-------------|---------|
| `--version`, `-V` | Print CLI version and exit | `kandev --version` |
| `--port <port>` | Backend port (alias: `--backend-port`) | `--port 3000` |
| `--web-internal-port <port>` | Override internal Next.js port | `--web-internal-port 13000` |
| `--web-internal-port <port>` | Override internal web app port | `--web-internal-port 13000` |
| `--verbose`, `-v` | Show info logs from backend + web | `--verbose` |
| `--debug` | Show debug logs + agent message dumps | `--debug` |
| `--help`, `-h` | Show help | `--help` |
@@ -164,7 +165,7 @@ If the default port is in use, the CLI finds the next available port automatical
|----------|-------------|
| `KANDEV_BUNDLE_DIR` | Force the runtime bundle location (set by Homebrew wrapper) |
| `KANDEV_PORT` / `KANDEV_BACKEND_PORT` | Backend port (CLI flag wins) |
| `KANDEV_WEB_PORT` | Internal Next.js port |
| `KANDEV_WEB_PORT` | Internal web app port |
| `KANDEV_HEALTH_TIMEOUT_MS` | Override health check timeout (ms) |
| `KANDEV_GITHUB_OWNER` / `KANDEV_GITHUB_REPO` | Override GitHub repo for `--runtime-version` downloads |
| `KANDEV_GITHUB_TOKEN` | GitHub token for `--runtime-version` API access |
@@ -0,0 +1,39 @@
# 0021: Go-served SPA with boot state
**Status:** accepted
**Date:** 2026-06-15
**Area:** backend, frontend, cli
## Context
Kandev currently ships a Go backend plus a Next.js standalone web server. The CLI starts both processes in production, and the backend reverse-proxies browser routes to the Next server. Next server components also fetch request-time data and hydrate the Zustand store, so removing Next cannot regress first paint into loading spinners.
## Decision
Kandev will migrate the web runtime to a Vite-built React SPA served by the Go backend. Production will not run a Node.js web server. The Go backend will serve embedded frontend assets, route unknown browser paths to the SPA shell, and inject a JSON boot payload containing runtime config, route metadata, and `Partial<AppState>` initial state.
ADR-0022 specifies the packaging detail for those frontend assets: release-quality production builds embed the Vite output into the backend binary, with `KANDEV_WEB_DIST_DIR` retained as an explicit override for non-release workflows.
ADR-0023 specifies the active-workspace persistence rule used by boot state: only the current workspace ID is stored in a server-readable cookie, while broader settings stay in backend settings, URL params, or localStorage.
ADR-0024 specifies the development-mode counterpart: `make dev` still runs Vite internally for transforms and HMR, but browser document requests enter through Go so boot payload behavior matches production.
The boot-state builder will live on the Go side and use backend services directly where practical. The SPA will initialize `StateProvider` from the injected payload, and client-side route transitions that require preloaded data will fetch the same payload shape from a backend app-state endpoint before rendering the destination view.
Full React SSR from Go is explicitly out of scope. The server-rendered surface is the HTML shell and serialized data contract; React renders in the browser.
Operational migration guidance for in-progress PRs lives in [`../nextjs-spa-migration.md`](../nextjs-spa-migration.md).
## Consequences
The production runtime becomes simpler: one Go web process serves HTTP APIs, WebSockets, static assets, SPA fallback, and boot data. Release artifacts no longer need Next.js standalone output or a Node process for the web UI.
The migration must preserve the existing first-paint hydration contract. Some TypeScript SSR mapping logic will need a Go equivalent, so golden tests and narrow mappers are required to avoid drift. Feature gates, redirects, not-found behavior, and active-workspace cookie precedence must move from Next server components into Go boot-state builders.
Development still uses Node-based tooling for Vite, TypeScript, linting, tests, and package management. This decision removes the production Node web runtime first; removing all Node tooling is a separate effort.
## Alternatives Considered
1. **Keep Next.js and optimize packaging.** This preserves existing behavior but keeps the production Node runtime dependency that this migration is meant to remove.
2. **Full React SSR from Go.** Rejected because it would require embedding or supervising a JavaScript runtime, or maintaining a second renderer, which defeats the runtime simplification.
3. **Pure client-side SPA fetches after mount.** Rejected as the target architecture because it would regress first paint for routes that currently SSR-fetch and hydrate Zustand.
@@ -0,0 +1,27 @@
# 0022: Embedded Vite Assets
**Status:** accepted
**Date:** 2026-06-17
**Area:** backend, frontend, cli
## Context
ADR-0021 moved Kandev toward a Go-served Vite SPA with boot state and no production Node.js web runtime. The first implementation still required a built `apps/web/dist` directory beside the backend binary in local `start` and release bundles, with `KANDEV_WEB_DIST_DIR` pointing the backend at those files. That kept frontend serving in Go, but it left production packaging dependent on an external asset directory.
## Decision
Production builds embed the Vite output into the backend binary. The root build runs `build-web`, syncs `apps/web/dist` into `apps/backend/internal/webapp/embedded/generated`, and then compiles `cmd/kandev` with those assets embedded via `go:embed`. The backend still accepts `KANDEV_WEB_DIST_DIR` as an explicit override for local debugging, previews, and tests.
Release bundles no longer require a sibling `web/` directory for runtime. The CLI starts the backend without setting `KANDEV_WEB_DIST_DIR`, so the normal production path serves the embedded assets.
## Consequences
Production runtime packaging is simpler: the backend binary contains the SPA shell and static assets, while the CLI remains only a launcher. Build ordering now matters: the web build must be synced before compiling the backend for release-quality binaries. Backend-only builds remain possible because the embed package includes a minimal fallback shell, but those binaries are not release-quality frontend builds.
Tests and preview flows can continue to set `KANDEV_WEB_DIST_DIR` when they need to serve a mutable filesystem dist.
## Alternatives Considered
1. **Keep filesystem-only assets.** Rejected because it preserves an avoidable runtime packaging dependency.
2. **Move Vite output permanently under `apps/backend`.** Rejected because it couples frontend build output to backend source layout and increases generated-file churn.
3. **Embed only for release builds with build tags.** Rejected as unnecessary complexity; an override env var is enough for non-release workflows.
@@ -0,0 +1,27 @@
# 0023: Active workspace cookie for boot state
**Status:** accepted
**Date:** 2026-06-18
**Area:** backend, frontend
## Context
The Vite migration moved first-paint data loading from Next server components to Go boot payloads. Active workspace selection still came from several places: URL params, user settings, an Office-named cookie, and kanban localStorage. Go cannot read localStorage while serving HTML, so a hard reload could hydrate a different workspace than the browser sidebar remembered.
## Decision
Kandev will use `kandev-active-workspace` as the general browser cookie for the current active workspace. The Go boot-state builders and SPA route bootstraps read it after explicit URL workspace params and before user settings. The legacy `office-active-workspace` cookie remains a read fallback and is still written for Office compatibility during migration.
The cookie stores only the workspace ID. Broader preferences and filters should not move into cookies by default: durable user settings stay in backend user/workspace settings, shareable route state stays in URL params, and purely local view state may stay in localStorage. Add another cookie only when Go must know that value before serving the SPA shell.
## Consequences
Hard reloads, production boot payloads, and Vite dev app-state fetches can resolve the same active workspace. The cookie is sent on normal HTTP requests, so keeping it limited to a small non-sensitive ID avoids unnecessary request bloat and avoids leaking external integration data into the boot path.
Vite dev uses a separate web port, so boot-state fetches must include credentials and the Go CORS middleware must echo the request origin when allowing credentials. Production remains same-origin because Go serves both the SPA and API.
## Alternatives Considered
1. **Keep kanban localStorage only.** Rejected because Go cannot read it for first-paint boot state.
2. **Use only user settings.** Rejected because Office intentionally does not overwrite kanban's shared `workspace_id`, and quick workspace switches should not necessarily mutate durable settings.
3. **Move all UI settings to cookies.** Rejected because cookies are sent on every request and should stay limited to values the server needs before serving HTML.
@@ -0,0 +1,29 @@
# 0024: Go-fronted Vite dev mode
**Status:** accepted
**Date:** 2026-06-18
**Area:** backend, frontend, cli
## Context
After migrating production to a Go-served Vite SPA, `make dev` still opened the Vite dev server directly. That kept React Fast Refresh, but it made development materially different from production: browser requests were cross-origin, route reloads did not naturally exercise Go shell rendering, and boot-payload behavior could differ from `make start`.
## Decision
Development mode uses the Go backend as the browser entrypoint. The CLI still starts Vite as an internal frontend transform and HMR server, but it prints and opens the backend URL. The backend's `KANDEV_WEB_INTERNAL_URL` path now uses `webapp.DevHandler`: HTML navigation requests fetch Vite's current `index.html`, inject the Go boot payload, and return the shell from Go; Vite module, static asset, and HMR requests are proxied through to the internal Vite server.
Browser-facing Vite env vars that force cross-origin API calls, such as `VITE_KANDEV_API_PORT`, are unset in dev. Frontend code should use the same-origin runtime config injected by the Go boot payload, matching production.
## Consequences
Full reloads in dev now exercise the same Go route classification and boot-payload path as production while retaining Vite's fast TypeScript/React transform loop and HMR. CORS becomes less central to normal local development because the app URL is same-origin.
Vite remains a development dependency. Replacing Vite's transform, React Fast Refresh, worker handling, CSS pipeline, and module graph with Go-native tooling is out of scope. If a production-like no-HMR loop is needed later, it can be added as a separate `vite build --watch` plus Go static-serving mode.
## Alternatives Considered
1. **Keep opening Vite directly.** Rejected because it preserves cross-origin browser behavior and misses Go boot-payload rendering on document requests.
2. **Replace Vite entirely in dev.** Rejected because rebuilding Vite's transform and HMR feature set in Go would add substantial custom tooling with little product value.
3. **Use `vite build --watch` and full-page reloads as default dev.** Rejected as the default because it is more production-like but loses React Fast Refresh. It remains a reasonable optional mode.
No feature spec update is required; this is an internal development-runtime decision, not a user-facing product capability.
+26 -22
View File
@@ -4,25 +4,29 @@ Architecture Decision Records (ADRs) for the Kandev project. Each decision captu
Read individual ADRs for full context. Create new ones via `/record decision` or manually following the template in `0001-file-based-knowledge-system.md`.
| # | Title | Status | Area | Date |
|---|-------|--------|------|------|
| 0001 | [File-based knowledge system](0001-file-based-knowledge-system.md) | accepted | infra | 2026-03-28 |
| 0002 | [Host utility agentctl for sessionless ACP flows](0002-host-utility-agentctl-for-sessionless-flows.md) | accepted | backend | 2026-04-08 |
| 0003 | [executors_running as the single source of truth for agent_execution_id](0003-executors-running-as-execution-id-source-of-truth.md) | accepted | backend | 2026-05-03 |
| 0004 | [Task model unification — shared base, per-strategy meta, shared kernel](0004-task-model-unification.md) | proposed | backend, frontend | 2026-05-05 |
| 0005 | [Agent model unification — one `agents` table](0005-agent-model-unification.md) | proposed | backend, frontend | 2026-05-06 |
| 0006 | [Tier routing vs cheap_agent_profile_id coexistence](0006-tier-routing-vs-cheap-agent-profile-coexistence.md) | superseded | backend | 2026-05-11 |
| 0007 | [profiles.yaml — runtime defaults for prod / dev / e2e](0007-runtime-feature-flags.md) | accepted | backend, frontend | 2026-05-16 |
| 0008 | [DB upgrade safety - meta table, pre-migration backup, migration logging](0008-db-upgrade-safety.md) | accepted | backend | 2026-05-16 |
| 0009 | [Fail-closed GC semantics for filesystem and container cleanup](0009-fail-closed-gc-semantics.md) | accepted | backend | 2026-05-16 |
| 0010 | [Worktree copy-files — per-repo, idempotent, host-local](0010-worktree-copy-files.md) | accepted | backend, frontend | 2026-05-19 |
| 0011 | [Transient provider errors (529 Overloaded) auto-retry with visible backoff](0011-transient-provider-error-retry.md) | accepted | backend, frontend | 2026-05-30 |
| 0012 | [Service-only UI self-update](0012-service-only-self-update.md) | accepted | backend, frontend, cli | 2026-05-29 |
| 0013 | [Multi-branch task support — N (repo, branch) pairs per task](0013-multi-branch-tasks.md) | accepted | backend, frontend | 2026-06-01 |
| 0014 | [Per-CLI MCP server injection for passthrough mode](0014-passthrough-mcp-injection-strategies.md) | accepted | backend | 2026-05-29 |
| 0015 | [Explicit completion signal for auto-advance](0015-explicit-completion-signal-for-auto-advance.md) | proposed | backend, frontend | 2026-06-04 |
| 0016 | [Read-only absolute file paths](0016-observed-external-file-reads.md) | accepted | backend | 2026-06-14 |
| 0017 | [Resource metrics sampling](0017-resource-metrics-sampling.md) | accepted | backend, frontend, protocol | 2026-06-14 |
| 0018 | [Runtime settings overrides](0018-runtime-settings-overrides.md) | accepted | backend, frontend, cli | 2026-06-14 |
| 0019 | [Restart supervisor owns backend restarts](0019-restart-supervisor.md) | accepted | backend, frontend, cli | 2026-06-14 |
| 0020 | [Pi project MCP config injection](0020-pi-project-mcp-config-injection.md) | accepted | backend | 2026-06-16 |
| # | Title | Status | Area | Date |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------- | --------------------------- | ---------- |
| 0001 | [File-based knowledge system](0001-file-based-knowledge-system.md) | accepted | infra | 2026-03-28 |
| 0002 | [Host utility agentctl for sessionless ACP flows](0002-host-utility-agentctl-for-sessionless-flows.md) | accepted | backend | 2026-04-08 |
| 0003 | [executors_running as the single source of truth for agent_execution_id](0003-executors-running-as-execution-id-source-of-truth.md) | accepted | backend | 2026-05-03 |
| 0004 | [Task model unification — shared base, per-strategy meta, shared kernel](0004-task-model-unification.md) | proposed | backend, frontend | 2026-05-05 |
| 0005 | [Agent model unification — one `agents` table](0005-agent-model-unification.md) | proposed | backend, frontend | 2026-05-06 |
| 0006 | [Tier routing vs cheap_agent_profile_id coexistence](0006-tier-routing-vs-cheap-agent-profile-coexistence.md) | superseded | backend | 2026-05-11 |
| 0007 | [profiles.yaml — runtime defaults for prod / dev / e2e](0007-runtime-feature-flags.md) | accepted | backend, frontend | 2026-05-16 |
| 0008 | [DB upgrade safety - meta table, pre-migration backup, migration logging](0008-db-upgrade-safety.md) | accepted | backend | 2026-05-16 |
| 0009 | [Fail-closed GC semantics for filesystem and container cleanup](0009-fail-closed-gc-semantics.md) | accepted | backend | 2026-05-16 |
| 0010 | [Worktree copy-files — per-repo, idempotent, host-local](0010-worktree-copy-files.md) | accepted | backend, frontend | 2026-05-19 |
| 0011 | [Transient provider errors (529 Overloaded) auto-retry with visible backoff](0011-transient-provider-error-retry.md) | accepted | backend, frontend | 2026-05-30 |
| 0012 | [Service-only UI self-update](0012-service-only-self-update.md) | accepted | backend, frontend, cli | 2026-05-29 |
| 0013 | [Multi-branch task support — N (repo, branch) pairs per task](0013-multi-branch-tasks.md) | accepted | backend, frontend | 2026-06-01 |
| 0014 | [Per-CLI MCP server injection for passthrough mode](0014-passthrough-mcp-injection-strategies.md) | accepted | backend | 2026-05-29 |
| 0015 | [Explicit completion signal for auto-advance](0015-explicit-completion-signal-for-auto-advance.md) | proposed | backend, frontend | 2026-06-04 |
| 0016 | [Read-only absolute file paths](0016-observed-external-file-reads.md) | accepted | backend | 2026-06-14 |
| 0017 | [Resource metrics sampling](0017-resource-metrics-sampling.md) | accepted | backend, frontend, protocol | 2026-06-14 |
| 0018 | [Runtime settings overrides](0018-runtime-settings-overrides.md) | accepted | backend, frontend, cli | 2026-06-14 |
| 0019 | [Restart supervisor owns backend restarts](0019-restart-supervisor.md) | accepted | backend, frontend, cli | 2026-06-14 |
| 0020 | [Pi project MCP config injection](0020-pi-project-mcp-config-injection.md) | accepted | backend | 2026-06-16 |
| 0021 | [Go-served SPA with boot state](0021-go-served-spa-with-boot-state.md) | accepted | backend, frontend, cli | 2026-06-15 |
| 0022 | [Embedded Vite assets](0022-embedded-vite-assets.md) | accepted | backend, frontend, cli | 2026-06-17 |
| 0023 | [Active workspace cookie for boot state](0023-active-workspace-cookie.md) | accepted | backend, frontend | 2026-06-18 |
| 0024 | [Go-fronted Vite dev mode](0024-go-fronted-vite-dev-mode.md) | accepted | backend, frontend, cli | 2026-06-18 |
+1 -1
View File
@@ -132,7 +132,7 @@ docker run -p 38429:38429 \
## Port
Kandev exposes a single port. The Go backend serves the API, WebSocket, and reverse-proxies the Next.js frontend — all on one port:
Kandev exposes a single port. The Go backend serves the API, WebSocket, static SPA assets, and page boot data — all on one port:
| Port | Service |
|------|---------|
+1 -1
View File
@@ -80,7 +80,7 @@ kubectl port-forward svc/kandev 38429:38429
# Open http://localhost:38429
```
**Ingress**: Edit `k8s/ingress.yaml` to set your domain, then apply. The ingress routes all traffic to the backend on port 38429 (the Go backend reverse-proxies the Next.js frontend internally).
**Ingress**: Edit `k8s/ingress.yaml` to set your domain, then apply. The ingress routes all traffic to the backend on port 38429; the Go backend serves API, WebSocket, and SPA traffic on that port.
### Custom Domain / Reverse Proxy
+112
View File
@@ -0,0 +1,112 @@
# Next.js to Go-served SPA migration guide
Use this guide when rebasing or finishing an in-progress PR that was written
against the old Next.js web runtime. The target architecture is ADR-0020:
production serves a Vite React SPA from the Go backend, and Go injects the boot
payload needed for first paint.
## Target contract
- Production does not run a Next.js server or any Node.js web runtime.
- `apps/web` may still use Node tooling for Vite, TypeScript, linting, tests,
and development.
- The Go backend serves the SPA shell, static Vite assets, APIs, WebSockets, and
`/api/v1/app-state`.
- Go injects `window.__KANDEV_BOOT_PAYLOAD__` before React mounts. The payload
contains route metadata, runtime config, and `Partial<AppState>` initial
state.
- Data that was visible on first paint in a Next server component must move to
the Go boot payload or a route bootstrap that can skip fetching when the store
is already hydrated.
## Rebase workflow
1. Resolve conflicts by preserving SPA adapters and Go boot-state behavior.
2. Audit the rebased changes for new `next/*` imports, mocks, server component
fetches, `redirect()`, `notFound()`, `cookies()`, and `headers()`.
3. For each new store read added by the incoming PR, decide whether it affects
first paint. If it does, add that data to the Go boot payload.
4. Add or update focused tests before broad verification.
5. Re-run the Next audit command before handing off.
Useful audit commands:
```bash
rtk rg -n 'from "next/|from '\''next/|vi\.mock\("next/|next/link|next/navigation|next/image|next/dynamic|next/headers|next-themes|styled-jsx|eslint-config-next|next\.config' apps/web apps/packages/ui apps/pnpm-lock.yaml apps/package.json
rtk rg -n 'redirect\(|notFound\(|cookies\(|headers\(|StateProvider initialState|searchParams|params:' apps/web/app
```
The first command should have no production `next/*` imports or tests mocking
Next modules. Comments and unrelated variable names are acceptable only after
manual review.
## Import replacements
| Old import | Replacement |
| ----------------- | -------------------------------- |
| `next/link` | `@/components/routing/app-link` |
| `next/navigation` | `@/lib/routing/client-router` |
| `next/image` | `@/components/routing/app-image` |
| `next/dynamic` | `@/lib/routing/client-dynamic` |
| `next-themes` | `@/components/theme/app-theme` |
When replacing `next/navigation` in tests, mock
`@/lib/routing/client-router`, not the old Next module.
If a component needs route params from a new dynamic SPA route, update
`paramsForPath` in `apps/web/lib/routing/client-router.ts` and add coverage in
`apps/web/lib/routing/client-router.test.ts`.
## Migrating server-side data
Old Next pages often did this:
- Fetch request-time data in `app/**/page.tsx` or `layout.tsx`.
- Pass it to a client component as props.
- Hydrate Zustand with `<StateProvider initialState={...}>`.
- Use `redirect()` or `notFound()` for route decisions.
Migrate that as follows:
1. Identify the exact store fields the page needs before first paint.
2. Add route classification or params in `apps/backend/internal/webapp/routes.go`
if Go needs to distinguish the route.
3. Add the data to `apps/backend/cmd/kandev/boot_state.go`. Prefer backend
services directly over self-calling HTTP APIs.
4. If an HTTP handler already returns the same data shape, extract a small DTO
mapper in that backend package and reuse it from both the handler and the
boot-state builder.
5. Keep JSON shape aligned with the frontend store/API types. If the existing
API shape is snake_case, the boot payload should use the same shape unless
the store already expects otherwise.
6. Update the SPA route/client bootstrap so it skips its client fetch when the
store already has boot data.
7. Add tests for the Go mapper or boot builder, and a frontend test proving the
client does not refetch when boot state is present.
Optional or below-the-fold data may still fetch after mount, but do not regress
routes that previously rendered useful data without an initial loading spinner.
## Routing and redirects
- Add browser route rendering in `apps/web/src/spa-routes.tsx`,
`apps/web/src/settings-routes.tsx`, or `apps/web/src/office-routes.tsx`.
- Use `useRouter().replace()` for client-side redirect behavior.
- Move server-only gating that must happen before first paint into Go boot-state
route logic or expose enough boot state for the SPA to render the correct
initial screen.
- Replace `notFound()` with the route fallback pattern already used by the SPA
route modules, or return an explicit load/error state from the client route.
## Verification checklist
Run focused tests for the files you touched, then at minimum:
```bash
rtk go test ./cmd/kandev ./internal/webapp
rtk pnpm --dir apps --filter @kandev/web typecheck
rtk pnpm --dir apps --filter @kandev/web lint
```
For data-hydration changes, include one backend test for the boot payload or DTO
shape and one frontend test that starts with hydrated store state.
+1 -1
View File
@@ -38,7 +38,7 @@ Without these, `tsc` fails with missing module errors for `@/generated/release-n
## Running dev mode
`make dev` from the repo root builds the backend and starts both the Go backend (port 38429) and Next.js frontend (port 37429). The CLI launcher sets `KANDEV_DEBUG_DEV_MODE=true` which activates the `dev` profile from `profiles.yaml`, enabling mock agent and other dev conveniences. No external services (database, message queue, Docker) are needed — SQLite is embedded and the event bus runs in-memory.
`make dev` from the repo root builds the backend and starts both the Go backend (port 38429) and Vite frontend (port 37429). The CLI launcher sets `KANDEV_DEBUG_DEV_MODE=true` which activates the `dev` profile from `profiles.yaml`, enabling mock agent and other dev conveniences. No external services (database, message queue, Docker) are needed — SQLite is embedded and the event bus runs in-memory.
## Key commands
+2 -2
View File
@@ -16,9 +16,9 @@ Kandev is currently installable via `brew install kdlbs/kandev/kandev` from the
## What
- Author a homebrew-core-compliant `Formula/kandev.rb` that **builds entirely from source** — Go backend (cgo + sqlite via `mattn/go-sqlite3`), Next.js standalone web bundle, and the TypeScript CLI esbuild bundle.
- Author a homebrew-core-compliant `Formula/kandev.rb` that **builds entirely from source** — Go backend (cgo + sqlite via `mattn/go-sqlite3`), Vite web bundle, and the TypeScript CLI esbuild bundle.
- Source comes from the GitHub-generated tag archive (`https://github.com/kdlbs/kandev/archive/refs/tags/vX.Y.Z.tar.gz`); per-release sha256 is captured at submission/bump time.
- Build deps: `go => :build`, `pnpm => :build`. Runtime dep: `node` (CLI launcher + Next.js standalone server). `uses_from_macos "rsync" => :build` (used by `scripts/release/package-web.sh` to dereference symlinks in the Next.js standalone output) and `uses_from_macos "sqlite"` (cgo sqlite3 link) — both ship with macOS, only installed via Homebrew on Linux.
- Build deps: `go => :build`, `pnpm => :build`. Runtime dep: `node` (CLI launcher). `uses_from_macos "sqlite"` (cgo sqlite3 link) ships with macOS and is only installed via Homebrew on Linux.
- Install layout: `libexec/{bin,web,cli}` plus a single `bin/kandev` wrapper produced by `write_env_script`, setting `KANDEV_BUNDLE_DIR=<libexec>` and `KANDEV_VERSION=<version>`. This is the contract `apps/cli/src/runtime.ts` already honors.
- `livecheck do; url :stable; regex(/^v?(\d+(?:\.\d+)+)$/i); end` — Git strategy (the default for `url :stable`) checks tags directly and avoids the GitHub API rate limit.
- Test block: spawns `libexec/bin/kandev` with an isolated `KANDEV_HOME_DIR` and random `KANDEV_SERVER_PORT`, polls `/api/v1/system/health` until the response contains `"healthy"` (60s timeout), then shuts the backend down. Also asserts the formula `version` appears in `bin/kandev --version`.
+12 -13
View File
@@ -1,6 +1,6 @@
# Web E2E Tests
Browser-based end-to-end tests that exercise the full stack: Next.js frontend, Go backend, and mock agent. Tests run in parallel with complete isolation — each Playwright worker gets its own backend process, frontend process, database, and temp directory.
Browser-based end-to-end tests that exercise the full stack: Go-served Vite SPA, Go backend, and mock agent. Tests run in parallel with complete isolation — each Playwright worker gets its own backend process, database, and temp directory.
**Location:** `apps/web/e2e/`
@@ -11,8 +11,7 @@ Browser-based end-to-end tests that exercise the full stack: Next.js frontend, G
│ Worker 0 Worker 1 │
│ ┌──────────────────────┐ ┌──────────────────────┐│
│ │ Backend :18080 │ │ Backend :18081 ││
│ │ Frontend :13000 │ │ Frontend :13001 ││
│ │ SSR → localhost:18080│ │ SSR → localhost:18081││
│ │ Web UI :18080 │ │ Web UI :18081 ││
│ │ HOME=/tmp/e2e-0 │ │ HOME=/tmp/e2e-1 ││
│ │ own SQLite DB │ │ own SQLite DB ││
│ │ mock-agent only │ │ mock-agent only ││
@@ -22,8 +21,8 @@ Browser-based end-to-end tests that exercise the full stack: Next.js frontend, G
```
- **One backend per worker** — port `18080 + workerIndex`, isolated temp HOME, own SQLite DB
- **One frontend per worker** — port `13000 + workerIndex`, SSR routed to that worker's backend via `KANDEV_API_BASE_URL`
- **Shared `.next/` build** — all frontend processes serve from the same pre-built output (read-only)
- **Web UI served by backend** — each worker's backend serves Vite `dist` and boot data on the same port as the API
- **Shared `dist/` build** — all workers serve from the same pre-built Vite output (read-only)
- **Mock agent** — deterministic responses, no API keys needed
- **Mock GitHub client** — reports as authenticated, returns configurable data via REST API
- **No external dependencies** — no Docker, no GitHub tokens, no real git credentials
@@ -36,7 +35,7 @@ Build the backend binaries and web app before running tests:
make build-backend build-web
```
This produces `apps/backend/bin/kandev`, `apps/backend/bin/mock-agent`, and `apps/web/.next/`. The global setup script verifies all exist.
This produces `apps/backend/bin/kandev`, `apps/backend/bin/mock-agent`, and `apps/web/dist/`. The global setup script verifies all exist.
## Running Tests
@@ -93,7 +92,7 @@ apps/web/e2e/
### Backend + frontend fixture (`fixtures/backend.ts`)
Each Playwright worker spawns an isolated `kandev` backend and a dedicated `next start` frontend:
Each Playwright worker spawns an isolated `kandev` backend that also serves the SPA:
**Backend:**
1. Creates a temp directory as `HOME` with its own `.gitconfig`, data dir, and SQLite DB
@@ -109,12 +108,12 @@ Each Playwright worker spawns an isolated `kandev` backend and a dedicated `next
3. Spawns `apps/backend/bin/kandev` and waits for `/health` to return 200
**Frontend:**
1. Spawns `npx next start --port {13000 + workerIndex}` from `apps/web/`
2. Sets `KANDEV_API_BASE_URL` to the worker's backend URL so SSR routes correctly
3. Waits for the frontend to become healthy
4. All `next start` processes share the same `.next/` build output (read-only)
1. Sets `KANDEV_WEB_DIST_DIR` to `apps/web/dist`
2. Uses the worker backend URL as the browser URL
3. The backend serves `index.html`, static assets, and injected boot payloads
4. All workers share the same `dist/` build output (read-only)
**Teardown:** sends SIGTERM to frontend then backend, falls back to SIGKILL after 5s, cleans up temp dir.
**Teardown:** sends SIGTERM to the backend process group, falls back to SIGKILL after 7s, cleans up temp dir.
### Test base (`fixtures/test-base.ts`)
@@ -288,7 +287,7 @@ The GitHub Actions workflow (`.github/workflows/e2e-tests.yml`) runs on pushes a
**Tests fail with "Backend did not become healthy" or "Service did not become healthy"**
- Ensure `make build-backend build-web` completed successfully
- Check that `apps/backend/bin/kandev`, `apps/backend/bin/mock-agent`, and `apps/web/.next/` exist
- Check that `apps/backend/bin/kandev`, `apps/backend/bin/mock-agent`, and `apps/web/dist/` exist
- Run with `E2E_DEBUG=1` to see backend and frontend stderr
**Tests fail with "Cannot find module" or import errors**