Skip to content

release: v0.1.12 — F18 time-travel SINGLE + --experimental + mosaic + light theme#65

Merged
explosivebit merged 33 commits into
mainfrom
release/v0.1.12
May 6, 2026
Merged

release: v0.1.12 — F18 time-travel SINGLE + --experimental + mosaic + light theme#65
explosivebit merged 33 commits into
mainfrom
release/v0.1.12

Conversation

@explosivebit
Copy link
Copy Markdown
Contributor

Summary

Bundles three feature streams from develop into v0.1.12:

COMPARE mode for time-travel (Alt-drag two scrubbers + diff overlay) intentionally deferred — endpoint returns 501 with TODO marker.

Why

Five feature PRs accumulated on develop since v0.1.11 (May 6). Cutting them as a single minor-patch release rather than gating on the v0.2.0 milestone (full proactive-surfacer arc) keeps the cadence steady and ships the bundle-size win + multi-graph layout to users now.

Test plan

  • npm run check — 0 errors / 0 warnings across 486 files
  • npx vitest run — 14 files / 127 tests pass
  • PR feat(web): F18 — time-travel slider (SINGLE mode) #63 CI matrix (3 OS, node 22) — green before merge
  • release/v0.1.12 CI matrix — pending on this PR
  • Manual smoke after tag → GitHub Release fires release.yml workflow

Refs

PRDs activated since v0.1.11: PRD-008, PRD-012, PRD-013, PRD-014, PRD-015.
RFCs activated: RFC-007, RFC-011, RFC-012, RFC-013, RFC-014.
Evidence: EVID-016 / EVID-017 / EVID-018 / EVID-019 / EVID-020.

🤖 Generated with Claude Code

explosivebit and others added 30 commits May 6, 2026 14:39
Per guides/GIT-FLOW-GUIDE.ru.md §6.10. main = npm = v0.1.11.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
PRD-008 (Standard) — Time-travel slider for workspace history.
Timeline panel below canvas with scrubber; SINGLE mode shows
state at T, COMPARE mode (Alt-drag, 2 scrubbers) overlays diff
between T1 and T2. SC-1..SC-10 + 11 FRs. Subsumes the ad-hoc
PR-Diff feature.

RFC-007 (Standard) — Time-travel snapshot reconstruction +
scrubber UI. Pins server algorithm (forgeplan journal --json
--until=ISO replay), endpoint contract /api/snapshot, scrubber
debounce 200ms, COMPARE diff classes (added/activated/superseded
/degraded). Extends READ_ONLY_SUBCOMMANDS allow-list with
`journal` (rule 22 compliant). 8 implementation phases F18-T1..T8.

PRD-009 (Standard) — Risk overlay for workspace decay surface.
Toggle in canvas-toolbar; glow halo on artifacts where R_eff is
low or decay imminent (R_eff < 0.6 threshold). New riskScore
pure function. Risk anatomy section in ArtifactPanel. SC-1..SC-10
+ 10 FRs. Sankey + Sunburst skip overlay (their layouts already
encode hierarchy).

RFC-008 (Standard) — Risk overlay rendering + riskScore
composition. Pins multiplicative riskScore = (1-R_eff) ×
decay_factor with 90-day decay window. Rendering via SVG drop-
shadow filter with N>200 fallback to outer-circle halo. 7
implementation phases F19-T1..T7.

All 4 validated: PRD-008/009 PASS (with non-blocking orphan-FR
warnings), RFC-007/008 PASS clean.

Refs: PRD-008 RFC-007 PRD-009 RFC-008
## Summary

Forgeplan artifacts seeding the next two strategic features:

### F18 — Time-travel slider (PRD-008 + RFC-007)
- Timeline panel below canvas with scrubber.
- SINGLE mode: state at any past T.
- COMPARE mode (Alt-drag, 2 scrubbers): diff overlay between T1 and T2.
- Subsumes the ad-hoc PR-Diff feature — same backend, two scrubber
positions.
- Server: new \`/api/snapshot?at=ISO[&compare=ISO]\` endpoint, read-only
proxy to \`forgeplan journal --json --until=ISO\`.
- Diff classes: \`node-added\` / \`node-activated\` /
\`node-superseded\` / \`node-degraded\`.

### F19 — Risk overlay (PRD-009 + RFC-008)
- Toggle in canvas-toolbar.
- Glow halo on artifacts with composite risk > 0 (R_eff < 0.6 OR decay
imminent).
- \`riskScore = (1 - R_eff) × decay_factor\`, 90-day decay window.
- Risk anatomy section in ArtifactPanel: evidence list with weakest
highlighted, decay timer.
- Sankey + Sunburst skip overlay (their layouts already encode
hierarchy).
- Render via SVG drop-shadow with N>200 fallback to outer-circle halo
(perf).

## Verify

- forgeplan reindex clean: 47 synced, 0 errors.
- forgeplan validate: PRD-008/009 PASS (with non-blocking orphan-FR
warnings); RFC-007/008 PASS clean.

## Test plan

- [ ] PR review of PRD/RFC content for clarity.
- [ ] After merge: F18 and F19 implementation can start in parallel
feature branches.
- [ ] EVID-016 (F18 acceptance) and EVID-017 (F19 acceptance) follow on
implementation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
PRD-010 (Standard) — Workspace pulse: stats dashboard + health
score + trends. 4 charts (R_eff histogram, decay calendar, weekly
velocity, status transitions Sankey) + 0..100 health score with
30-day sparkline. CRITICAL UX constraint: every chart MUST ship
with hover tooltip + static caption + plain-language interpretation
badge. SC-1..SC-10 + 15 FRs.

RFC-009 (Standard) — Stats dashboard charts + plain-language
interpretation. Pins exact chart shapes, axis math, colour mapping;
heuristic 🟢/🟡/🔴 thresholds per chart with copy templates;
median-based health score formula resistant to single-artifact
gaming. /api/pulse endpoint contract. 8 implementation phases F22-T1..T8.

PRD-011 (Standard) — Proactive hints engine for workspace anomalies.
Top-3 hints panel above HealthBar; 8+ rules covering common
forgeplan-anomalies; severity × recency ranking; snooze 1d/1w with
TTL auto-cleanup. SC-1..SC-10 + 12 FRs.

RFC-010 (Standard) — Hints rule DSL + ranking dispatcher. Pins
HintRule shape (single-file extension point), 8 initial rules with
trigger + copy templates, ranking algorithm, snooze model,
localization-ready copy map. 7 implementation phases F23-T1..T7.

NOTE-001 (Tactical) — Web utilities backlog. Captures 12 deferred
ideas (F25-F36): "Ask AI" light, RSS feed, view-state permalink,
inline structured search, walkthrough mode, GitHub PR cross-link,
decision genealogy, snapshot bookmarks, annotation layer, embedded
chat (heavy — separate product), coverage section, onboarding hint.
Decision criteria + which-not-to-do.

All 5 linked via `forgeplan link`:
- RFC-009 -refines-> PRD-010, RFC-010 -refines-> PRD-011
- PRD-008 -informs-> PRD-010 (time-travel feeds pulse)
- PRD-010 -informs-> PRD-011 (stats feeds hints)
- NOTE-001 -informs-> PRD-008/009/010/011

Reindex: 70 synced, 0 errors. forgeplan health: green.

Refs: PRD-010 RFC-009 PRD-011 RFC-010 NOTE-001
forgeplan link wrote `links:` arrays into frontmatter of the
already-merged F18/F19 artifacts when establishing the cross-feature
relations:
- PRD-008 -informs-> PRD-010 (time-travel feeds pulse data)
- PRD-009 (received NOTE-001 -informs-> PRD-009)
- RFC-007 / RFC-008 received NOTE-001 -informs-> them

These are pure metadata updates (Lance index already had the edges
from the F18/F19 merge; this just syncs markdown frontmatter).

Refs: PRD-008 PRD-009 RFC-007 RFC-008
## Summary

5 artifacts seeding the next strategic features after F18/F19.

### F22 — Workspace pulse (PRD-010 + RFC-009)
Stats dashboard with 4 charts (R_eff histogram, decay calendar, weekly
velocity, status transitions Sankey) + 0..100 health score with 30-day
sparkline.
**Critical UX constraint**: every chart MUST ship with hover tooltip +
static caption + 🟢/🟡/🔴 plain-language interpretation badge.

### F23 — Proactive hints engine (PRD-011 + RFC-010)
Top-3 hints panel above HealthBar; 8 initial rules covering forgeplan
anomalies; severity × recency ranking; snooze 1d/1w with TTL
auto-cleanup.

### NOTE-001 — Backlog
12 deferred ideas (F25-F36) with cost estimates and "do/skip/wait"
criteria. Includes "Ask AI" light, RSS feed, permalink, inline search,
walkthrough, GitHub cross-link, annotation layer, embedded-chat (heavy —
separate product).

## Linked properly via forgeplan link

- RFC-009 -refines→ PRD-010
- RFC-010 -refines→ PRD-011
- PRD-008 -informs→ PRD-010 (time-travel feeds pulse data)
- PRD-010 -informs→ PRD-011 (stats metrics feed hints)
- NOTE-001 -informs→ PRD-008/009/010/011

forgeplan reindex: 70 synced, 0 errors. forgeplan health: green.

## Verify

- forgeplan reindex clean.
- forgeplan validate: all PASS (PRD-010/011 with non-blocking orphan-FR
warnings; RFCs/Note clean).
- forgeplan graph shows 8 new edges.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds a small bottom-left footer rendering both the @forgeplan/web package
version (baked at vite-build time via a `define` constant) and the host
forgeplan CLI version (resolved on first request via a memoized
`forgeplan --version` spawn). New read-only `/api/version` endpoint serves
both. CLI lookup degrades to `null` on ENOENT / parse failure; UI then
shows `cli ?` instead of erroring.

Rule 22 is amended to make the flag-only `--version` invocation an
explicit, narrowly scoped exception to the subcommand allow-list — it
flows through a dedicated `getForgeplanVersion()` helper that reuses the
same FORGEPLAN_BIN validation, concurrency cap, and timeout as
`runForgeplan`, and never reaches the subcommand check.

Smoke: `npm run check` clean; `curl /api/version` returns
`{ web: "0.1.11", cli: "0.27.0" }` against vite dev with the repo's own
.forgeplan/; spawn-error path empirically reachable via ENOENT.

Refs: PRD-012, RFC-011, EVID-016

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a small bottom-left footer rendering both the @forgeplan/web
package version (baked at vite-build time via a `define` constant) and
the host forgeplan CLI version (resolved on first request via a memoized
`forgeplan --version` spawn). New read-only `/api/version` endpoint
serves both. CLI lookup degrades to `null` on ENOENT / parse failure; UI
then shows `cli ?` instead of erroring.

Rule 22 is amended to make the flag-only `--version` invocation an
explicit, narrowly scoped exception to the subcommand allow-list — it
flows through a dedicated `getForgeplanVersion()` helper that reuses the
same FORGEPLAN_BIN validation, concurrency cap, and timeout as
`runForgeplan`, and never reaches the subcommand check.

Smoke: `npm run check` clean; `curl /api/version` returns `{ web:
"0.1.11", cli: "0.27.0" }` against vite dev with the repo's own
.forgeplan/; spawn-error path empirically reachable via ENOENT.

Refs: PRD-012, RFC-011, EVID-016
Add Button, Code-with-copy, Dialog under template/src/shared/ui/ and a
modalManager service under template/src/shared/services/modal/ so widgets
can open dialogs without per-call mounting. ModalRoot is mounted once in
+layout.svelte.

Add /api/update-check endpoint that probes registry.npmjs.org for the
latest @forgeplan/web (5-min server-process cache, 5-second timeout, GET
only, never throws). VersionFooter polls it once at mount and every
30 minutes; when hasUpdate, an UpdateButton appears above the footer and
opens UpdateDialog (current → latest + copyable manual command).

Auto-update is intentionally out of scope: running `npx @forgeplan/web
update` from the running server would rmSync the very files serving the
request. Dialog explains this and offers the manual path only.

Rule 22 amended to allow exactly one non-forgeplan endpoint hitting the
literal URL https://registry.npmjs.org/@forgeplan/web/latest.

Refs: PRD-013, RFC-012, EVID-017

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`npx @forgeplan/web update` has two interactive failure modes:
- if the package is not cached, npx prompts "Ok to proceed? (y)" and
  blocks waiting for Enter — bad UX when the user pasted the command;
- if a stale version is cached, npx silently runs the old copy, which
  copies the OLD dist/ into .forgeplan-web/ — the update is a no-op.

Switch the dialog command to `npx -y @forgeplan/web@latest update`:
`-y` auto-confirms the install prompt; `@latest` forces npx to fetch
the newest tarball. Add a one-line footnote explaining both flags.

Refs: PRD-013

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After `update` runs, the host process keeps serving the OLD code:
- macOS/Linux: the running Node process holds the old .forgeplan-web/
  files open via open inodes, so rmSync from the new bin doesn't kill
  the request loop. The browser sees the old version until the user
  manually restarts `node .forgeplan-web/index.js`.
- Windows: the rm step itself can fail with EBUSY because Windows
  locks open files; update may partially fail, but again the running
  server is unaffected.

Either way the user has to stop+start the server. HMR is not an option
here — adapter-node has no dev-server hooks at runtime.

So the dialog now:
- enumerates the four steps (Stop, Update, Restart, Reload);
- pings /api/version every 5 s while open;
- shows a "Server now serves vX.Y.Z" banner with a Reload button when
  the polled web version differs from the dialog's `current`;
- shows an "offline" banner with a `npx @forgeplan/web start` hint
  when the ping fails (process killed but not yet restarted).

Refs: PRD-013

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dialog already detects the server-side version change. Reloading is
the inevitable next step — adding a click between "we saw the new
version" and "you see the new version" is friction without value (the
user explicitly opened the update dialog, intent is clear).

So when the polled /api/version differs from the dialog's `current`:
- show a banner that the server now serves the new version + a fading
  "reloading…" hint;
- arm a 1.5 s setTimeout that calls window.location.reload();
- offer a Cancel button to stop the timer (preserves any in-tab state
  the user wants to keep) and a "Reload now" button to skip the wait;
- guard against the timer firing twice if the poller produces multiple
  hits before the page unloads (only the first detection arms it).

Refs: PRD-013

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reload alone won't pick up the new version: Node holds the old ES
modules in memory after the rmSync — open inodes keep them alive, and
new requests are served from the in-memory module cache. The process
itself has to be restarted before browser reload becomes meaningful.

Add a warn-styled line right under the step list to make the chain
explicit (Stop → Update → Restart → Reload) and prevent the common
"I just refreshed but nothing changed" support question.

Refs: PRD-013

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per request: keep manual update only. The dialog now shows the
current → latest header, the four-step manual recipe, and the
copyable command. Everything else — the /api/version pinger,
serverDown banner, detected-version banner, auto-reload setTimeout,
Cancel/Reload-now buttons — is removed.

Manual update is the only supported path. The user runs the command,
restarts the server themselves, and reloads the tab on their own.

Refs: PRD-013

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add Button, Code-with-copy, Dialog under template/src/shared/ui/ and a
modalManager service under template/src/shared/services/modal/ so
widgets can open dialogs without per-call mounting. ModalRoot is mounted
once in +layout.svelte.

Add /api/update-check endpoint that probes registry.npmjs.org for the
latest @forgeplan/web (5-min server-process cache, 5-second timeout, GET
only, never throws). VersionFooter polls it once at mount and every 30
minutes; when hasUpdate, an UpdateButton appears above the footer and
opens UpdateDialog (current → latest + copyable manual command).

Auto-update is intentionally out of scope: running `npx @forgeplan/web
update` from the running server would rmSync the very files serving the
request. Dialog explains this and offers the manual path only.

Rule 22 amended to allow exactly one non-forgeplan endpoint hitting the
literal URL https://registry.npmjs.org/@forgeplan/web/latest.

Refs: PRD-013, RFC-012, EVID-017
Build pipeline now produces a second pre-built artifact alongside the
legacy `dist/`: `dist-experimental/`, a single-file esbuild bundle of
the SvelteKit server. esbuild inlines every reachable runtime dep
(@sveltejs/kit, d3-*, dompurify, marked, …) into one ESM file, dropping
the ~12M `node_modules/` from the artifact. Total: 14M → 1.5M (×9.3),
1696 → 62 files (×27).

The new shape is gated behind `npx @forgeplan/web init --experimental`.
Legacy `dist/` is left byte-identical and remains the default until the
bundled shape graduates (≥2 minor versions without regressions). On
graduation the flag is removed, legacy `dist/` is dropped, and
`dist-experimental/` is renamed to `dist/`. TODO markers in
`bin/forgeplan-web.mjs` and `scripts/build.mjs` flag the removal site.

Build pipeline (`scripts/build.mjs`):
- esbuild added to root devDependencies (^0.24.0)
- new `bundleExperimentalDist()` runs after `copyToDist()`; reads the
  same template/build/index.js the legacy pipeline produced
- shape asserts: no node_modules/, no server/, ≤3M (hard cap, fail-loud)
- emits minimal package.json without `dependencies` and a build manifest
  with `experimental: true`

Bin (`bin/forgeplan-web.mjs`):
- --experimental switches SOURCE_DIST to dist-experimental/
- update inherits the persisted choice; --no-experimental migrates back
- forgeplan-web.json gains `experimental: bool`
- bin zero-deps invariant preserved (rule 23)

CI:
- smoke.yml + release.yml install root devDependencies via `npm ci`
  before running the build (esbuild needs to be on disk)
- package-lock.json now committed at root for deterministic CI installs

Smoke (verified locally on darwin-arm64):
- legacy init: HTTP 200 on /api/version + / + correct envelope on /api/health
- --experimental init: same envelopes, 1.5M, 62 files, no node_modules/
- experimental against real workspace (47 artifacts): /api/list, /graph,
  /health all match legacy shape

Refs: PRD-014, RFC-013, EVID-018

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build pipeline now produces a second pre-built artifact alongside the
legacy `dist/`: `dist-experimental/`, a single-file esbuild bundle of
the SvelteKit server. esbuild inlines every reachable runtime dep
(@sveltejs/kit, d3-*, dompurify, marked, …) into one ESM file, dropping
the ~12M `node_modules/` from the artifact. Total: 14M → 1.5M (×9.3),
1696 → 62 files (×27).

The new shape is gated behind `npx @forgeplan/web init --experimental`.
Legacy `dist/` is left byte-identical and remains the default until the
bundled shape graduates (≥2 minor versions without regressions). On
graduation the flag is removed, legacy `dist/` is dropped, and
`dist-experimental/` is renamed to `dist/`. TODO markers in
`bin/forgeplan-web.mjs` and `scripts/build.mjs` flag the removal site.

Build pipeline (`scripts/build.mjs`):
- esbuild added to root devDependencies (^0.24.0)
- new `bundleExperimentalDist()` runs after `copyToDist()`; reads the
same template/build/index.js the legacy pipeline produced
- shape asserts: no node_modules/, no server/, ≤3M (hard cap, fail-loud)
- emits minimal package.json without `dependencies` and a build manifest
with `experimental: true`

Bin (`bin/forgeplan-web.mjs`):
- --experimental switches SOURCE_DIST to dist-experimental/
- update inherits the persisted choice; --no-experimental migrates back
- forgeplan-web.json gains `experimental: bool`
- bin zero-deps invariant preserved (rule 23)

CI:
- smoke.yml + release.yml install root devDependencies via `npm ci`
before running the build (esbuild needs to be on disk)
- package-lock.json now committed at root for deterministic CI installs

Smoke (verified locally on darwin-arm64):
- legacy init: HTTP 200 on /api/version + / + correct envelope on
/api/health
- --experimental init: same envelopes, 1.5M, 62 files, no node_modules/
- experimental against real workspace (47 artifacts): /api/list, /graph,
/health all match legacy shape

Refs: PRD-014, RFC-013, EVID-018
PRD-008/RFC-007 originally targeted forgeplan journal --json --until=ISO
for time-travel reconstruction. F18-T1 verified that flag does not exist
in CLI 0.28.0 and that forgeplan log --json is reindex-flat (history
collapsed). Triggered RFC-007 Risk R-1 fallback: git is the only durable
timeline since markdown is the source of truth (parent ADR-003).

Implements RFC-007 Path D (git+cache hybrid):
- Resolve at→sha via git rev-list -1 --before=<at> --first-parent HEAD -- .forgeplan/
- Memory LRU (32 entries, 60s TTL) → disk cache (.forgeplan-web/.snapshots/<sha>.json)
- Cold path: git worktree add --detach to os.tmpdir() + forgeplan list/graph --json inside
- Worktree removed best-effort after each reconstruction

Updates RFC-007 algorithm + phases + options-considered + risks.
PRD-008 SC-7/NFR-003/R-1 reworded to drop the journal-only assumption.
runForgeplan() gains opts.cwd so the same helper can target a worktree.

COMPARE mode endpoint returns 501 until F18-T6 lands diff projection.

Refs: PRD-008 RFC-007

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the client-side foundations for the time-travel scrubber without
any UI yet:

- widgets/timeline/lib/snapshot-state.svelte.ts — runes-aware singleton
  ($state) holding mode/activeAt/t1/t2/collapsed/loading + loadSnapshotAt
  fetch wrapper that targets /api/snapshot.
- widgets/timeline/lib/event-axis.ts — pure math for the scrubber:
  domain computation, timestamp ↔ pixel mapping, snap-to-nearest-event,
  prev/next event stepping. Single-event domain pads ±1h so the axis
  has somewhere to render.
- widgets/timeline/lib/event-axis.test.ts — 21 unit tests covering
  empty domain, padding, clamping, invalid timestamps, snapping, and
  step direction edge cases.
- widgets/timeline/index.ts — barrel.

Collapsed state persists in localStorage. Falls back gracefully when
localStorage is missing (SSR) or quota-exceeded.

T4 will add the Timeline.svelte panel + /api/timeline-events derived
from git log of .forgeplan/, then wire HomePage + DependencyGraph.

Refs: PRD-008 RFC-007

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the time-travel scrubber to life:

- /api/timeline-events — read-only proxy over `git log --first-parent
  .forgeplan/`. Emits one event per commit with at/kind/artifactId/sha/
  subject. kind classified heuristically (activate / supersede /
  evid-score / created-default).
- widgets/timeline/ui/Timeline.svelte — collapsible panel below
  canvas-body. SVG axis with coloured tick marks per event kind, draggable
  scrubber with PointerEvent capture, ArrowLeft/Right step-by-event,
  Home/End jump, 200ms debounced fetch to /api/snapshot. role=slider for
  a11y.
- widgets/timeline/index.ts — exports Timeline component.
- pages/home/HomePage.svelte — renders <Timeline /> below the graph;
  derived `nodes`/`edges` switch between live pollers and snapshotStore.
  current when mode === 'single'. Status indicator surfaces "viewing
  snapshot at HH:MM" / loading / error / live·now.

Cold path measured implicitly via /api/snapshot (T5 will profile
formally). COMPARE mode endpoint still 501-stubbed pending T6.

Refs: PRD-008 RFC-007

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a cream/beige light palette alongside the existing dark theme,
plus an Auto/Light/Dark toggle in the header. Theme is keyed via
data-theme on <html>; an inline pre-paint script in app.html sets
it before SvelteKit hydrates so first paint is correct.

All graph views (Force, Tree, Radial, Lanes, Matrix, Sankey,
Sunburst) and shared chrome (Dialog, Button, HealthBar) now read
from theme-aware CSS tokens instead of hardcoded white rgbas.
SVG attribute usages switched to Svelte style: directives so
var() resolves through inline CSS.

Workaround: lightningcss tree-shook the new tokens from the build,
so vite.config.ts now forces postcss + esbuild for CSS minify.
Tracked via FIXME(prd-015-css-minify).

Refs: PRD-015, RFC-014, EVID-019

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Smoke-tested /api/snapshot end-to-end against the dev server and
discovered two real issues:

1. workspaceRoot() points at template/src/ in dev mode (vite SSR loads
   modules directly), so git pathspec '.forgeplan/' silently misses.
   Added gitRepoRoot() — caches the result of `git rev-parse
   --show-toplevel` once at first call. Both /api/timeline-events and
   /api/snapshot use it instead of workspaceRoot() for git invocations.

2. .forgeplan/lance/ is gitignored and never checked in (markdown is the
   source of truth per parent-repo ADR-003), so a freshly created
   worktree has no LanceDB index and `forgeplan list --json` fails with
   "Table 'artifacts' was not found". Added spawnForgeplanReindex(cwd)
   which calls `forgeplan reindex` inside the ephemeral worktree before
   list/graph. Bypasses runForgeplan's read-only allow-list — the write
   is scoped to /tmp, never the host workspace.

Measured cold path 660ms (39 artifacts: worktree add + reindex + list +
graph). Memory cache hits at 10-11ms, ≥60x speedup. Disk cache covers
restart. NFR-001 target (<300ms cold) misses on first access; warm path
well within budget.

T5 was originally scoped to formalise the cache layer. Cache shipped in
T2 already; this commit closes the gap by making the underlying
reconstruction actually return data.

Refs: PRD-008 RFC-007

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EVID-016 captures the end-to-end measurement against the running dev
server: HTTP 200 from /api/snapshot for two distinct ISO timestamps
producing different commit SHAs and artifact counts (39 vs 20),
warm-path 10-11ms via memory cache, cold-path 660ms dominated by
forgeplan reindex inside the ephemeral worktree. CL3 / measurement /
supports — the probe runs the actual production code path through
loopback, not isolated unit tests.

forgeplan link EVID-016 PRD-008 / RFC-007 (informs); forgeplan score
puts both at quality A/B with reliability 1.00/0.90; forgeplan activate
flips PRD-008, RFC-007, and EVID-016 to active.

CHANGELOG entry covers SINGLE mode delivery and explicit COMPARE-mode
deferral to v0.2.1 (endpoint returns 501 with TODO).

Refs: PRD-008 RFC-007 EVID-016

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the [Unreleased] section describing the F18 server endpoints
(/api/snapshot, /api/timeline-events), the Timeline panel widget,
canvas snapshot hydration, and gitRepoRoot() helper. Explicitly
documents COMPARE mode deferral to v0.2.1.

Refs: PRD-008 RFC-007 EVID-016

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a cream/beige light palette alongside the existing dark theme, plus
an Auto/Light/Dark toggle in the header. Theme is keyed via data-theme
on <html>; an inline pre-paint script in app.html sets it before
SvelteKit hydrates so first paint is correct.

All graph views (Force, Tree, Radial, Lanes, Matrix, Sankey, Sunburst)
and shared chrome (Dialog, Button, HealthBar) now read from theme-aware
CSS tokens instead of hardcoded white rgbas. SVG attribute usages
switched to Svelte style: directives so var() resolves through inline
CSS.

Workaround: lightningcss tree-shook the new tokens from the build, so
vite.config.ts now forces postcss + esbuild for CSS minify. Tracked via
FIXME(prd-015-css-minify).

Refs: PRD-015, RFC-014, EVID-019
# Conflicts:
#	CHANGELOG.md
#	template/src/pages/home/ui/HomePage.svelte
#	template/src/shared/server/index.ts
Replace single-canvas-with-view-toggle home page with a recursive
binary split-tree mosaic that hosts 1–4 DependencyGraph panes.
Each pane has its own view selector, reset, add and close controls
in a draggable header; layout (open panes + sizes in %) persists to
localStorage under forgeplan-web:layout:v1.

Drag semantics:
- Pane header onto another pane's center  → swap views (sizes kept).
- Pane header onto another pane's edge    → move (remove source +
                                              insert at target's edge).
- Splitter pointer/keyboard drag          → resize, clamped [10..90]%.

Layout model lives under template/src/widgets/mosaic/ (FSD widget):
- model/{types,tree}.ts pure tree ops with vitest coverage (20 tests).
- lib/{drag,persist}.ts module-level drag payload (workaround for the
  spec-mandated empty getData during dragover) + 5 quadrant tests.
- ui/{MosaicCanvas,MosaicNodeView,PaneFrame,Splitter,DropOverlay}.svelte
  recursive render via 3-track CSS Grid (a% 0px b% with overlay
  splitter) — no JS layout math at render time.

A11y: splitters expose role=separator with arrow-key resize, pane
headers are toolbar with tabindex=-1, drag fallback writes text/plain
so the operation initialises in browsers that require non-empty data.

Refs: PRD-016, RFC-015, EVID-020

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace single-canvas-with-view-toggle home page with a recursive binary
split-tree mosaic that hosts 1–4 DependencyGraph panes. Each pane has
its own view selector, reset, add and close controls in a draggable
header; layout (open panes + sizes in %) persists to localStorage under
forgeplan-web:layout:v1.

Drag semantics:
- Pane header onto another pane's center  → swap views (sizes kept).
- Pane header onto another pane's edge    → move (remove source +
                                              insert at target's edge).
- Splitter pointer/keyboard drag          → resize, clamped [10..90]%.

Layout model lives under template/src/widgets/mosaic/ (FSD widget):
- model/{types,tree}.ts pure tree ops with vitest coverage (20 tests).
- lib/{drag,persist}.ts module-level drag payload (workaround for the
spec-mandated empty getData during dragover) + 5 quadrant tests.
- ui/{MosaicCanvas,MosaicNodeView,PaneFrame,Splitter,DropOverlay}.svelte
recursive render via 3-track CSS Grid (a% 0px b% with overlay splitter)
— no JS layout math at render time.

A11y: splitters expose role=separator with arrow-key resize, pane
headers are toolbar with tabindex=-1, drag fallback writes text/plain so
the operation initialises in browsers that require non-empty data.

Refs: PRD-016, RFC-015, EVID-020
Formatter normalised YAML list indentation in PRD-008/RFC-007/EVID-020
frontmatter (0-space → 2-space) after the merge resolution. PRD-008
Related Artifacts table EVID-016 → EVID-020 also picked up here.

Refs: PRD-008 RFC-007 EVID-020
## Summary

- F18 SINGLE mode: scrub canvas to any past workspace state via SVG
scrubber + `/api/snapshot?at=ISO`.
- Reconstruction = `git worktree add --detach` to `os.tmpdir()` +
`forgeplan reindex` + `forgeplan list/graph --json`. Two-tier cache
(memory LRU + disk).
- New endpoint `/api/timeline-events` — read-only `git log` proxy
emitting one event per `.forgeplan/`-touching commit.
- `gitRepoRoot()` helper detects host git top via `git rev-parse
--show-toplevel`.
- COMPARE mode (Alt-drag, diff overlay) deferred to v0.2.1 — endpoint
returns 501 with TODO marker.

## Why

PRD-008 + RFC-007 originally proposed `forgeplan journal --json
--until=ISO` replay. F18-T1 verified that flag does not exist in CLI
0.28.0 and that `forgeplan log --json` is reindex-flat (history
collapsed). Triggered Risk R-1: pivot to git-based reconstruction since
markdown is the source of truth (parent-repo ADR-003) and git is the
only durable timeline.

## Test plan

- [x] `npm run check` — 0 errors / 0 warnings across 450 files
- [x] `npx vitest run` — 12 files / 102 tests pass (incl. 21 new
`event-axis.test.ts`)
- [x] End-to-end smoke against running dev server — see EVID-016: cold
660 ms, warm 10–11 ms, two distinct ISO timestamps produce distinct SHA
+ artifact counts (39 vs 20)
- [x] `forgeplan score PRD-008` → A (0.87, R 1.00). `forgeplan score
RFC-007` → B (0.67, R 0.90). All three artifacts (PRD-008, RFC-007,
EVID-016) `active`.
- [ ] CI matrix smoke (3 OS) — runs on PR

## Refs

- PRD-008 — Time-travel slider for workspace history
- RFC-007 — Time-travel snapshot reconstruction + scrubber UI
- EVID-016 — F18 acceptance (CL3 / measurement / supports)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@fedorovvvv
Copy link
Copy Markdown
Collaborator

include #66

@explosivebit explosivebit merged commit 93ce167 into main May 6, 2026
3 checks passed
@explosivebit explosivebit deleted the release/v0.1.12 branch May 6, 2026 21:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants