Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
88c3ce8
chore: back-merge release/v0.1.11 into develop
explosivebit May 6, 2026
c8591b8
chore: back-merge release/v0.1.11 into develop (#52)
explosivebit May 6, 2026
714a700
docs(forgeplan): PRD-008 + RFC-007 + PRD-009 + RFC-008 artifacts
explosivebit May 6, 2026
038683b
docs(forgeplan): PRD-008 + RFC-007 + PRD-009 + RFC-008 artifacts (#53)
explosivebit May 6, 2026
1f7dbd0
docs(forgeplan): PRD-010 + RFC-009 + PRD-011 + RFC-010 + NOTE-001
explosivebit May 6, 2026
6734de2
docs(forgeplan): backfill links: arrays via forgeplan link
explosivebit May 6, 2026
2202517
docs(forgeplan): PRD-010 + RFC-009 + PRD-011 + RFC-010 + NOTE-001 (#54)
explosivebit May 6, 2026
7a0ac6a
feat(ui): show forgeplan CLI + web versions in footer
fedorovvvv May 6, 2026
df7f72f
feat(ui): show forgeplan CLI + web versions in footer (#57)
fedorovvvv May 6, 2026
645e7ff
feat(ui): shared UI primitives + npm update notification
fedorovvvv May 6, 2026
cc39c42
fix(ui): use `npx -y @forgeplan/web@latest update` in update dialog
fedorovvvv May 6, 2026
6be5438
feat(ui): post-update server detector + explicit restart steps in dialog
fedorovvvv May 6, 2026
22ef269
feat(ui): auto-reload after update with 1.5s window + Cancel
fedorovvvv May 6, 2026
cb31906
docs(ui): clarify in dialog that browser reload alone is not enough
fedorovvvv May 6, 2026
7ec75de
refactor(ui): drop auto-reload + version pinger from UpdateDialog
fedorovvvv May 6, 2026
300d8f6
feat(ui): shared UI primitives + npm update notification (#58)
fedorovvvv May 6, 2026
f927d97
feat(init): add --experimental flag for bundled dist (~9× smaller)
fedorovvvv May 6, 2026
941cc06
feat(init): add --experimental flag for bundled dist (~9× smaller) (#60)
fedorovvvv May 6, 2026
3358749
feat(web): /api/snapshot endpoint with git+cache reconstruction (F18-T2)
explosivebit May 6, 2026
4005ce0
feat(web): timeline store + event-axis math (F18-T3)
explosivebit May 6, 2026
935b4d2
feat(web): timeline panel + canvas snapshot hydration (F18-T4)
explosivebit May 6, 2026
84a2af4
feat(ui): light theme + tri-state theme toggle
fedorovvvv May 6, 2026
5ce0c6e
fix(web): git-root detection + worktree reindex (F18-T5)
explosivebit May 6, 2026
4bad44b
docs(forgeplan): EVID-016 + activate PRD-008 + RFC-007 (F18-T7)
explosivebit May 6, 2026
7a966a8
docs: CHANGELOG for F18 SINGLE mode (Unreleased)
explosivebit May 6, 2026
f079752
feat(ui): light theme + tri-state theme toggle (#62)
fedorovvvv May 6, 2026
e6ac859
Merge branch 'develop' into feature/f18-time-travel
explosivebit May 6, 2026
763f3c5
feat(ui): multi-graph mosaic dashboard with persistent layout
fedorovvvv May 6, 2026
b727a47
feat(ui): multi-graph mosaic dashboard with persistent layout (#64)
fedorovvvv May 6, 2026
7921409
chore(forgeplan): post-merge formatter sweep + EVID-020 backref
explosivebit May 6, 2026
71f6fa6
Merge remote-tracking branch 'origin/develop' into feature/f18-time-t…
explosivebit May 6, 2026
a2425bc
feat(web): F18 — time-travel slider (SINGLE mode) (#63)
explosivebit May 6, 2026
5648a24
chore(release): bump version to 0.1.12
explosivebit May 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .claude/rules/21-template-purity.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ a self-contained scaffold.
merge them.
- Runtime deps (used by `node build/index.js` after `vite build`) live in
`dependencies`. Build-only tooling (vite, adapter-node, svelte-check,
typescript, types) lives in `devDependencies`. The build pipeline
derives `dist/package.json` from `template/package.json#dependencies`
and runs `npm install --omit=dev` inside `dist/`.
typescript, types) lives in `devDependencies`. The build pipeline:
- For legacy `dist/`: derives `dist/package.json` from
`template/package.json#dependencies` and runs `npm install --omit=dev`
inside `dist/`.
- For `dist-experimental/` (PRD-014 / RFC-013): runs esbuild on
`template/build/index.js` with `--bundle --packages=bundle` to inline
every reachable runtime dep into a single ESM file. The output has
no `node_modules/` and no `server/` chunks. `dist-experimental/`
is capped at 3M (assertion in `scripts/build.mjs`).
- `template/package.json#scripts.dev` must boot SvelteKit on a deterministic
port (currently `5174`) so the README's quick-start link is correct.
- Every server route that needs the workspace path MUST read it from
Expand Down
42 changes: 42 additions & 0 deletions .claude/rules/22-readonly-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,45 @@ host's Forgeplan workspace as a graph. It is a **viewer**, not an editor.
> must handle that case explicitly. Adding new read-only subcommands here
> requires an updating Forgeplan artifact.

## Flag-only exception: `--version`

`forgeplan --version` is read-only by definition (it prints a static string and
exits). It is NOT a subcommand and therefore NOT a member of the allow-list
above. It is exposed to `/api/*` via a dedicated helper
(`getForgeplanVersion()` in `template/src/shared/server/forgeplan.ts`) that
bypasses `runForgeplan`'s subcommand check while reusing the same
`FORGEPLAN_BIN` validation, concurrency cap, and timeout. The result is
memoized for the process lifetime.

This is the only flag-only invocation permitted from `/api/*`. Any new
flag-only or subcommand entry requires an updating Forgeplan artifact and a
revision of this rule. See PRD-012 / RFC-011.

## Allow-list extension: `/api/update-check` (non-forgeplan)

`/api/update-check` is the **single** non-forgeplan endpoint permitted from
`/api/*`. It probes the npm registry for the latest published version of
`@forgeplan/web` so the UI can surface an "Update available" affordance.

Constraints (every one of these is enforceable from the diff):

- Method: `GET` only.
- URL: the **string literal** `https://registry.npmjs.org/@forgeplan/web/latest`.
No interpolation, no query params, no user input on the URL path.
- No spawn, no host filesystem write, no Forgeplan invocation. The only
side-effect is a process-local in-memory cache (5 min TTL, single
inflight promise).
- Headers: `accept: application/json` and a static `user-agent`. No cookies,
no credentials.
- Response shape mirrors the standard envelope: `{ ok, data: { current,
latest, hasUpdate }, cmd, error? }` with `current = __FORGEPLAN_WEB_VERSION__`.
- Network failures (timeout, non-2xx, JSON parse error) MUST fall back to
`{ ok: false, error, data: { ..., hasUpdate: false } }` — never throw.

Any additional non-forgeplan endpoint (whether it hits npm, GitHub,
crates.io, or anything else) requires a new Forgeplan artifact and a fresh
amendment to this rule. See PRD-013 / RFC-012.

## Forbidden `forgeplan` subcommands from any `/api/*` endpoint

Any subcommand that mutates the workspace:
Expand Down Expand Up @@ -55,3 +94,6 @@ browser invalidates that.
`args[0] ∈ READ_ONLY_SUBCOMMANDS` before spawning, and the constant MUST
match this allow-list (see rule above). The check is the runtime backstop
for review-time enforcement.
- `grep -RIn "fetch(" template/src/routes/api/` must show external URLs
only inside `update-check/+server.ts`, and the URL must appear as a
string literal (`https://registry.npmjs.org/@forgeplan/web/latest`).
35 changes: 26 additions & 9 deletions .claude/rules/23-bin-zero-deps.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,37 @@ import.
- Adding entries to the root `package.json#dependencies` to support the
bin script.

## Note on `dist/`
## Note on `dist/` and `dist-experimental/`

`dist/` (the pre-built SvelteKit app) ships its **own** `node_modules/`,
populated by the build pipeline with `--omit=dev`. Those deps are runtime
needs of the SvelteKit server (`node dist/index.js`), not of the bin
script. The bin script only `spawn()`s `node` against `dist/index.js` —
it never imports anything from `dist/node_modules/`. This rule is about
the bin script staying zero-dep; `dist/` is governed by rule 21.
The published tarball ships **two** pre-built artifacts (PRD-014 / RFC-013):

- `dist/` (legacy default) — SvelteKit app with its own `node_modules/`,
populated by the build pipeline with `--omit=dev`. Those deps are
runtime needs of the SvelteKit server (`node dist/index.js`), not of
the bin script.
- `dist-experimental/` (opt-in via `init --experimental`) — single-file
ESM bundle (`dist-experimental/index.js`), emitted by esbuild. No
`node_modules/`, no `server/` chunks; everything reachable from the
entry is inlined. The bundle ships with its own minimal `package.json`
(no `dependencies`).

In both cases the bin script only `spawn()`s `node` against the
artifact's `index.js` — it never imports anything from the artifact's
internals. This rule is about the bin script itself staying zero-dep;
the artifacts are governed by rule 21.

After the bundled shape graduates from `--experimental` (see
`TODO(rfc-013-graduation)` in `bin/forgeplan-web.mjs` and
`scripts/build.mjs`), the legacy `dist/` will be dropped from the
tarball and this section will collapse to one paragraph.

## Required

- The root `package.json` must keep `dependencies` empty (or absent). It
may have `devDependencies` for repo tooling. The published tarball
ships `bin/` (zero-dep), `dist/` (with its own `node_modules/`), and
may have `devDependencies` for repo tooling (currently: `esbuild` for
building `dist-experimental/`). The published tarball ships `bin/`
(zero-dep), `dist/` (with its own `node_modules/`),
`dist-experimental/` (single bundle, no `node_modules/`), and
`README.md`.
- `package.json#engines` pins Node ≥ `^20.19.0 || >=22.12.0`. Any change
needs an ADR.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
depth: standard
id: EVID-016
kind: evidence
last_modified_at: 2026-05-06T16:24:27.860348+00:00
last_modified_by: claude-code/2.1.131
links:
- target: PRD-012
relation: informs
- target: RFC-011
relation: informs
status: draft
title: /api/version smoke test confirms shape and CLI fallback
---

---
id: EVID-016
title: "/api/version smoke test confirms shape and CLI fallback"
status: Draft
kind: evidence
created: 2026-05-06
---

# EVID-016: /api/version smoke test confirms shape and CLI fallback

## Structured Fields

verdict: supports
congruence_level: 3
evidence_type: test

## Context

PRD-012 / RFC-011 add `GET /api/version` returning
`{ web: string, cli: string | null }` and a UI footer rendering both
versions. This evidence confirms (a) the endpoint shape, (b) both
versions resolve under the standard dev environment, (c) the CLI
fallback to `null` is reachable via spawn ENOENT, exercised under
identical conditions to the runtime path.

## Method

1. `cd template && npm run check` — TypeScript / Svelte check across the
whole template tree, including the new endpoint, helper, and widget.
2. `npm run dev -- --port 5179` (vite dev) against the repo's own
`.forgeplan/`.
3. `curl -s http://127.0.0.1:5179/api/version` — capture body.
4. `node -e "spawn('/nonexistent/forgeplan-not-here', ['--version']) …"`
— verify `child.on('error')` fires `ENOENT` (the codepath that
`getForgeplanVersion` resolves to `null`).
5. `curl -s http://127.0.0.1:5179/` — confirm the page still renders
`<title>Forgeplan</title>` (footer doesn't break layout).

## Observations

```text
$ npm run check
1778084570199 COMPLETED 444 FILES 0 ERRORS 0 WARNINGS 0 FILES_WITH_PROBLEMS

$ curl -s http://127.0.0.1:5179/api/version
{
"ok": true,
"data": { "web": "0.1.11", "cli": "0.27.0" },
"cmd": "forgeplan --version"
}

$ node -e "spawn('/nonexistent/forgeplan-not-here', ['--version']) …"
error caught: ENOENT

$ curl -s http://127.0.0.1:5179/ | grep title
<title>Forgeplan</title>
```

## Conclusion

- Endpoint contract from RFC-011 holds: `{ ok, data: { web, cli }, cmd }`.
- `web === "0.1.11"` matches `template/package.json#version`, proving
the Vite `define` injection works at dev time.
- `cli === "0.27.0"` matches the host's `forgeplan --version`.
- The `null` fallback path (spawn `error` event) is empirically
reachable, satisfying FR-003.
- Type checker is clean.

## Threats to validity

- `vite dev` exercise only; no `npm run build` / `dist/` smoke. Partial
follow-up: full build will run as part of the release pipeline before
publish; if the `define` substitution were broken in adapter-node,
`__FORGEPLAN_WEB_VERSION__` would surface as a `ReferenceError` at
request time, which is loud and easy to catch.
- Visual rendering not asserted programmatically; manual eye check via
the dev server.



Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
depth: standard
id: EVID-017
kind: evidence
last_modified_at: 2026-05-06T16:58:57.023400+00:00
last_modified_by: claude-code/2.1.131
links:
- target: PRD-013
relation: informs
- target: RFC-012
relation: informs
status: draft
title: smoke + svelte-check + live endpoint probe for shared UI + update checker
---

# EVID-017: smoke + svelte-check + live endpoint probe for shared UI + update checker

| Field | Value |
| ----------- | ---------------------- |
| Status | Draft |
| Created | 2026-05-06 |
| Valid Until | 2026-08-06 |
| Target | PRD-013 / RFC-012 |

## Structured Fields

evidence_type: test
verdict: supports
congruence_level: 3

## Measurement

Three direct probes against the surface introduced by PRD-013 / RFC-012:

1. `cd template && npm run check` — `svelte-kit sync` followed by
`svelte-check --tsconfig ./tsconfig.json`. Exercises every `.svelte`,
`.svelte.ts`, and `.ts` file under `template/src/` (including the new
`shared/ui/`, `shared/services/modal/`, and version-footer additions)
for type errors and a11y warnings.
2. `npm run smoke` at the repo root — rebuilds `dist/` from scratch
(`scripts/build.mjs --clean`), `init -y` against a scratch dir, then
`init -y --force`, then boots `node dist/index.js`, and probes
`/api/health`, `/api/list`, `/`. This is the same smoke harness
already used to gate releases.
3. Live spawn of the freshly built `dist/index.js` on PORT=15999 with
FORGEPLAN_CWD=/tmp + curl against the new endpoint.

## Result

```
$ npm run check # in template/
COMPLETED 462 FILES 0 ERRORS 0 WARNINGS 0 FILES_WITH_PROBLEMS

$ npm run smoke # at root
[smoke] /api/health: ok (project=shim)
[smoke] /api/list: ok (0 entries)
[smoke] GET /: ok (HTML returned)
[smoke] PASS

$ curl -s http://127.0.0.1:15999/api/update-check
{"ok":true,"data":{"current":"0.1.11","latest":"0.1.11","hasUpdate":false},
"cmd":"GET registry.npmjs.org/@forgeplan/web/latest"}

$ curl -s http://127.0.0.1:15999/api/version
{"ok":true,"data":{"web":"0.1.11","cli":"0.27.0"},"cmd":"forgeplan --version"}
```

Vite build also reports `entries/endpoints/api/update-check/_server.ts.js
1.78 kB │ gzip: 0.83 kB`, confirming the new endpoint is bundled into
`dist/`.

## Interpretation

- **PRD-013 SC-1** (primitives exist + 1 widget imports): satisfied —
`template/src/widgets/version-footer/ui/UpdateDialog.svelte` imports
`Button`, `Code`, `Dialog` from `@/shared/ui`.
- **PRD-013 SC-2** (caller line count ≤ 3 lines to open a dialog): met
by the `modalManager.open(UpdateDialog, { current, latest })` call
inside `VersionFooter.svelte`.
- **PRD-013 SC-3** (update notice within ≤ 30 min of npm publish):
meets the upper bound by `THIRTY_MINUTES_MS` poll interval +
immediate `start()` on mount; live probe confirms the endpoint is
reachable and returns a well-formed envelope.
- **PRD-013 SC-4** (dialog renders manual update command): rendered by
`UpdateDialog.svelte` via `<Code code="npx @forgeplan/web update" />`.
- **PRD-013 SC-5** (endpoint is GET-only, respects rule 22): the only
HTTP method exported is `GET`; no `spawn` or `runForgeplan` is called
from `update-check/+server.ts`; URL is a string literal.
- **NFR-002** (endpoint never throws on registry failure): try/catch
wraps `getLatestCached`; failure path returns `{ ok: false, ...,
hasUpdate: false }`. Live probe with reachable registry returns the
success path; failure path is exercised structurally.
- **NFR-004** (zero new runtime deps): `git diff develop --
template/package.json` shows no change in `dependencies`.

The svelte-check pass over 462 files (up from 460 — the two new
modules) with zero errors and zero warnings means the new types
(ModalEntry, UpdateData, compareSemver) compose cleanly with existing
code. The smoke pass means `init` + `start` still work end-to-end after
the addition. The live curl proves the endpoint is wired.

## Congruence Level Justification

CL3 — the measurement is run against the exact files PRD-013 and
RFC-012 prescribe (same surface, same project, same commit). The smoke
test exercises the full ship-path (`bin/forgeplan-web.mjs init` →
`node dist/index.js` boot), and the curl probes the new endpoint by
its actual URL. evidence_type=test (binary pass/fail) +
verdict=supports (every assertion held).

## Related Artifacts

| Artifact | Relation |
| -------- | -------- |
| PRD-013 | informs |
| RFC-012 | informs |


Loading
Loading