mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-25 22:10:25 +00:00
b3ca3a9691
* fix(web): vitest refuses a project it cannot run instead of dropping it (BUG-3045) The web suite is three vitest projects. Two of them — `jsdom`, which owns every `*.svelte.test.ts`, and `idb` — were registered only if their devDependencies resolved, and simply OMITTED otherwise. So an incomplete install narrowed the run instead of failing it: `npm test` exited 0 having executed none of the rendered-component suites, and nothing in the output said so. That is a gate that passes when it cannot run — the shape scripts/ci-audit.mjs was taught to refuse in BUG-2881, where an unreachable advisory service was indistinguishable from a clean audit. Two halves, because narrowing has two causes and they deserve opposite answers: - An unresolvable dependency now REFUSES the run. The check sits at module scope in vitest.config.ts, so it lands while the config is still loading: vitest reports a startup error and exits 1 rather than falling back to a default config. The message names every unrunnable project and every missing package, so one `npm ci` fixes the whole set. - DELIBERATE narrowing is not refused — `--project` and file filters are legitimate — so a reporter prints how many of the registered projects actually ran files. A full run says `3/3 ran test files`; `--project node` says `1/1`; a single-file run says `1/3` and names the projects that ran nothing. A narrowed run no longer looks identical to a complete one. No escape hatch. All five packages are declared devDependencies, and CI does a full `npm ci`, so there is no legitimate call this refuses — only the state a green run must not be reported from. The decision and both messages live in src/test/vitestProjects.ts with direct tests. Fifteen mutants, all killed: thirteen by their own test leg, two by the config refusal itself (a mutant that breaks the decision for the REAL dependency set stops the suite before any test runs). There is deliberately no test that the package names resolve — it could never run, for exactly that reason. Claude-Session: https://claude.ai/code/session_019EmDd3Wy8b3cPG1TZBSzBu * fix(web): close the fall-through hole and prove the config uses the table (BUG-3045) Codex round 1, enumeration-prompted. Three findings acted on, one refuted. **A test file no project runs (finding 3).** The refusal covers a project that cannot run; it did nothing about a file that no project's glob matches. A `*.spec.ts`, `*.test.tsx` or `*.test.js` under src/ is never collected, never fails, and the run exits 0 — this bug's own class, one layer down. Codex checked the tree and found no member today, which is exactly when the guard is cheap: the first one to appear would otherwise arrive silently. `projectForTestFile` now answers which project owns a path, the three globs moved beside it so the config and the guard read ONE declaration rather than two copies, and a test walks src/ and names any orphan. It asserts its own precondition first — an empty file list would make every later assertion vacuous, which is the failure mode this whole bug is about. **The tests vouched for functions, not for the wiring (finding 8, CONVE-19).** Nothing would have noticed if vitest.config.ts had stopped calling any of this, gone back to probing dependencies inline, or dropped a project. Four tests now read the real config: it registers exactly node/idb/jsdom, that set equals `node` plus the requirements table, it keeps vitest's default reporter beside the project-count one, and each project routes with the shared globs. **Prose that overstated the refusal's reach (findings 1, 2, 4, 6).** The refusal is unconditional and precedes `--project`, so `vitest --project node` is refused too when the jsdom deps are missing, even though node alone would have run. Kept — an incomplete install is a broken state and stopping on it whatever subset was asked for is the honest answer — but the comment claiming deliberate narrowing is never refused was false in exactly the case the refusal fires, and now says so. Also written down: the line counts FILES, so it does not prove an assertion executed; an idle project is printed rather than failed; and `--reporter=<name>` replaces configured reporters, so `npm test -- --reporter=dot` prints no summary at all. **Refuted by measurement (finding 5).** Codex read the CLI source and concluded `context.projects` survives `--project` filtering, so a narrowed run would print `1/3` rather than the documented `1/1`. Running it says otherwise, twice: `--project node` prints `1/1 ran test files — node (70 files)` with no idle line, and `--project jsdom` prints `0/1` naming only jsdom. Under the claimed behaviour both would have carried the other two projects. No change made. Seven more mutants on the new legs, all killed, for 22/22 across the unit. N5 (drop the default reporter) is worth its own note: it exited 1 but my runner called it a runner error, because the mutation removes the very output lines the classifier reads. Re-run through `--reporter=default` on the CLI, it dies by its named test. Third instrument in this unit whose question depended on something the mutant controlled — the tsc gate and the kill-door grep were the first two. Claude-Session: https://claude.ai/code/session_019EmDd3Wy8b3cPG1TZBSzBu
Pad Web UI
SvelteKit 2 + Svelte 5 frontend for Pad, compiled to static files and embedded into the Go binary.
Development
npm install
npm run dev # Dev server at localhost:5173 (proxies API to localhost:7777)
npm run build # Production build to build/
npm run check # Type checking with svelte-check
When developing, run the Go backend separately with make dev from the project root.
Building for Production
Do not build in isolation. Always use make build from the project root — this builds the web frontend, then compiles the Go binary with the build output embedded via //go:embed.
Stack
- Svelte 5 with runes (
$state,$derived,$effect) - SvelteKit 2 with
adapter-static(SPA mode) - Tiptap block editor with markdown round-trip
- svelte-dnd-action for drag-and-drop in board/list views
- SSE for real-time updates
- TypeScript throughout
Structure
src/
routes/ SvelteKit pages
+layout.svelte App shell (sidebar + main)
+page.svelte Landing/redirect
[workspace]/
+page.svelte Dashboard (collections, phases, activity)
+layout.svelte SSE connection per workspace
[collection]/
+page.svelte Collection view (board/list)
[collection]/[item]/
+page.svelte Item detail + editor
conventions/ Purpose-built conventions page
playbooks/ Purpose-built playbooks page
settings/ Workspace settings
lib/
api/client.ts HTTP API client
components/
layout/ Sidebar, navigation
editor/ Tiptap editor, raw markdown editor
fields/ FieldEditor, relation picker
items/ ItemCard, ItemDetail
collections/ BoardView, ListView
common/ StatusBadge, badges, modals
search/ CommandPalette
stores/ Svelte 5 reactive stores
workspace.svelte.ts Workspace state
collections.svelte.ts Collection + item state
ui.svelte.ts Sidebar, mobile state
types/index.ts TypeScript types and constants
app.css Global styles and design tokens