chore(release): v0.1.13#107
Merged
Merged
Conversation
Clicking the cluster −/+ toggle previously reset the radial view's zoom and pan because (1) toggleClusterCollapse forced didFit=false and (2) the shape-tracking effect treated collapse-induced layout changes identically to filter changes, retriggering fitToView(). Now: drop the explicit didFit reset on collapse, and gate the shape-effect's didFit reset on collapsedSig stability so only filter/data-driven shape changes refit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the native <select> in PaneFrame with a bits-ui-based Select component that lives in shared/ui and renders a Lucide icon next to each of the 7 graph views (force, tree, radial, matrix, lanes, sankey, sunburst). Trigger and listbox share the same iconography so the active view is recognisable at a glance without reading the label. The component is headless (bits-ui handles ARIA semantics and keyboard navigation), styled exclusively with the existing CSS-variable design tokens (no Tailwind, no inline styles), and exposed via @/shared/ui for future consumers. The drag-handle in PaneFrame is updated to ignore the new trigger and the portal-rendered listbox so picking a view no longer fights the swap-pane gesture. Adds two runtime deps to template/package.json: bits-ui and @lucide/svelte. Build, svelte-check, and live render all green. Refs: PRD-017
## Summary Standard back-merge of `release/v0.1.12` into `develop` per the project's GitFlow: - Brings the version bump (`0.1.11 → 0.1.12`) and the new `[0.1.12] - 2026-05-07` CHANGELOG section back onto develop, so the next feature branch starts from the released version, not the pre-release one. - 3 files changed (CHANGELOG.md, package.json, template/package.json). - No code conflicts — the merge is a forward-port of the version commit only; all feature commits in v0.1.12 already came from develop. ## Why Without back-merge, develop's `package.json` would stay at `0.1.11` while npm + main are at `0.1.12`. The next release branch would either: - silently inherit `0.1.11` and we'd have to bump again, or - conflict on the next merge to main. ## Test plan - [x] `git diff origin/develop..origin/release/v0.1.12 --stat` — only version + CHANGELOG entries - [ ] CI matrix on this PR (auto) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Swap enabled plugin in `.claude/settings.json#enabledPlugins`: `dev-toolkit@ForgePlan-marketplace` -> `fpl-skills@ForgePlan-marketplace`. `fpl-skills` is the successor: includes everything from `dev-toolkit` (`/audit`, `/sprint`, `forge-report`, dev-advisor agent, safety hooks) plus 13 new commands. `dev-toolkit` is soft-deprecated upstream. Repo had no `/dev-toolkit:*` slash-command references in CLAUDE.md or docs, so settings.json is the only file change. The actual plugin install/uninstall is driven by interactive slash commands the user runs: /plugin marketplace update ForgePlan-marketplace /plugin install fpl-skills@ForgePlan-marketplace /plugin uninstall dev-toolkit@ForgePlan-marketplace /reload-plugins Reversible: revert this commit + reinstall dev-toolkit. Refs: - https://github.com/ForgePlan/marketplace/blob/main/docs/MIGRATION-DEV-TOOLKIT-TO-FPL-SKILLS.md - https://github.com/ForgePlan/marketplace/blob/main/docs/USAGE-GUIDE.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nfig
CLAUDE.md changes:
- Methodology table — add `fpl-skills shortcut` column mapping
/restore, /briefing, /rfc, /refine, /research, /sprint, /audit,
/diagnose to OBSERVE -> ROUTE -> SHAPE -> BUILD -> PROVE -> SHIP.
- Process section — annotate steps 1-5 with the corresponding
slash-command shortcut + add step for /diagnose on hard bugs.
- New "Slash commands (fpl-skills + companions)" reference table
between Common commands and Hard requirements — quick O(1)
lookup. Includes companion plugins (fpf, forgeplan-workflow,
forgeplan-orchestra, laws-of-ux).
- Reference section — link to docs/agents/*.md as fpl-skills
per-project config.
docs/CONTRIBUTING.md — append shortcut hints to Forgeplan workflow
section + pointer to docs/agents/.
docs/agents/ (new) — four config files normally generated by
/fpl-skills:setup wizard, hand-tailored to this project:
- issue-tracker.md queue is .forgeplan/ (no Linear/Jira/Issues)
- build-config.md npm scripts, smoke gate, forbidden CI cmds
- paths.md top-level + FSD layers + where skills write
- domain.md package surfaces, Forgeplan glossary, R_eff,
routing depth, branch naming
.claude/settings.json — incidental linter reformat (env block
moved from end to top); no semantic change.
Skills never bypass artifact discipline: R_eff > 0 and active
gates from rule 11 still apply. CLAUDE-MD-GUIDE constraints
(cry-wolf, dilution, redundancy cost) respected — content links
back to existing sources rather than duplicating.
Refs:
- https://github.com/ForgePlan/marketplace/blob/main/docs/USAGE-GUIDE.md
- https://github.com/ForgePlan/marketplace/blob/main/docs/MIGRATION-DEV-TOOLKIT-TO-FPL-SKILLS.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r shape
`forgeplan score --all --json` returns `{ errors, results: ScoreEntry[] }`,
but scorePoller was typed as `ScoreEntry[]` and consumers called `.map(...)`
directly on the wrapper, throwing `(... ?? []).map is not a function` at
runtime in InsightsRail and ForceView (via HomePage's `scores` derived).
Introduce ScoreResponse, retype the poller, read `data.results` in the two
call sites. No API contract change — endpoint stays a passthrough.
Refs: rule 22 (read-only proxy preserved)
#71) …r shape `forgeplan score --all --json` returns `{ errors, results: ScoreEntry[] }`, but scorePoller was typed as `ScoreEntry[]` and consumers called `.map(...)` directly on the wrapper, throwing `(... ?? []).map is not a function` at runtime in InsightsRail and ForceView (via HomePage's `scores` derived). Introduce ScoreResponse, retype the poller, read `data.results` in the two call sites. No API contract change — endpoint stays a passthrough. Refs: rule 22 (read-only proxy preserved)
…ed/ui Standard-depth artifacts backing GitHub issue #68. Adds 26 primitives to template/src/shared/ui/ via bits-ui wrappers + CSS-var styling (no Tailwind). PRD-018 lists groups 1-7 + integration; RFC-016 pins the per-primitive shape and bits-ui touchpoints. Both pass `forgeplan validate` with 0 MUST errors (SHOULD/COULD warnings tracked but out of scope here). RFC-016 -> PRD-018 linked (based_on). Refs: PRD-018, RFC-016, #68
…ard/Alert/Progress Group 1 of 8 from issue #68 (PRD-018 / RFC-016). Seven pure-CSS visual atoms under template/src/shared/ui/, styled by tokens from app.css (no Tailwind, no new deps). Same shape as existing Button/Select. - Badge: variant (primary/secondary/success/danger/ghost), size (sm/md) - Separator: orientation (horizontal/vertical), decorative flag - Skeleton: width/height/radius props, shimmer with reduced-motion fallback - Spinner: size (sm/md/lg), aria-label, reduced-motion fallback - Card: padding (none/sm/md/lg), variant (flat/outlined/elevated), header/footer snippets - Alert: variant (info/success/warning/danger), Lucide icon defaults, role auto-toggle - Progress: 0..max value or indeterminate, variant (primary/success/warning/danger) Barrel `shared/ui/index.ts` now exports 12 named primitives (was 5). Refs: PRD-018, RFC-016, #68, #72
Group 2 of 8 from issue #68 (PRD-018 / RFC-016). Four form atoms styled by tokens from app.css. No bits-ui — Input is native <input> with token-driven states; Field and InputGroup are composition primitives. - Label: required/optional indicators, for-association, sm font-size - Input: type forwarded; size (sm/md); invalid prop drives data-invalid + aria-invalid; focus-visible ring + accent border via tokens - Field: pairs Label + control + helper/error; auto-generates id, ties describedBy/aria-invalid via render snippet props - InputGroup: prefix/suffix snippets around an Input, focus-within ring on the wrapper; collapses inner Input border via :global Barrel `shared/ui/index.ts` now exports 16 named primitives. Refs: PRD-018, RFC-016, #68, #73
…heckbox/Slider Group 3 of 8 from issue #68 (PRD-018 / RFC-016). Six toggle-style primitives, five wrap bits-ui (Switch, Checkbox, Toggle, ToggleGroup, Slider) and ButtonGroup is a pure-CSS composition wrapper. - Toggle: pressed/onPressedChange, size (sm/md), variant (default/outline) - ToggleGroup + ToggleGroupItem: single/multiple modes, horizontal/vertical - ButtonGroup: orientation, attached vs spaced layout (collapses inner radii) - Switch: bind:checked, accent track when on, theme-token thumb - Checkbox: bind:checked + bind:indeterminate (bits-ui v2 separates them); Lucide check/minus icons via snippet - Slider: multi-thumb support; horizontal/vertical; range fill via accent Barrel `shared/ui/index.ts` now exports 22 named primitives. Refs: PRD-018, RFC-016, #68, #74
Group 4 of 8 from issue #68 (PRD-018 / RFC-016). Wraps bits-ui RadioGroup; RadioGroup carries value/onValueChange + form-submission name; Radio is the standalone item rendered with an accent dot when checked. Barrel `shared/ui/index.ts` now exports 24 named primitives. Refs: PRD-018, RFC-016, #68, #75
Group 5 of 8 from issue #68 (PRD-018 / RFC-016). Three disclosure primitives, all bits-ui wrappers. Each splits into Root + auxiliary parts (Tabs.List etc.) and ships its own CSS via :global() rules keyed off bits-ui data-state. - Tabs / TabsList / TabsTrigger / TabsContent — orientation, activationMode, underline trigger style; vertical mode uses right-border accent - Collapsible / CollapsibleTrigger / CollapsibleContent — slide animation with reduced-motion fallback - Accordion / AccordionItem / AccordionTrigger / AccordionContent — single + multiple modes; chevron rotates on open Barrel `shared/ui/index.ts` now exports 31 named primitives (10 atoms + 21 from groups). Refs: PRD-018, RFC-016, #68, #76
Group 6 of 8 from issue #68 (PRD-018 / RFC-016). Three overlay primitives: two thin bits-ui wrappers (Tooltip, Popover) and an in-house Toaster store (no new dep — Svelte 5 runes class with timer-driven dismissal). - Tooltip + TooltipProvider: side/align/sideOffset, accent border via tokens. Provider mounted in `+layout.svelte` so any descendant can use Tooltip. - Popover + PopoverTrigger + PopoverContent: portal-based, optional Arrow - Toaster + toast() helper: queue with id-keyed dismissal, variant-driven left-border accent, 6 corner positions, reduced-motion fallback. toast() also exposes .info/.success/.warning/.danger/.dismiss/.clear. Toaster mounted alongside ModalRoot in `+layout.svelte`. Renamed toaster.svelte.ts -> toaster-store.svelte.ts to avoid macOS case-insensitive filename collision with Toaster.svelte. Barrel `shared/ui/index.ts` now exports 38 named primitives. Refs: PRD-018, RFC-016, #68, #77
Group 7 of 8 from issue #68 (PRD-018 / RFC-016). bits-ui Command wrapped into a self-contained palette: Root + Input + List + Empty + Group + Separator + Item. The Item primitive (Item.svelte) is the per-row entry; onSelect, value, keywords, and disabled are forwarded. Sub-parts: - Command: bind:value + shouldFilter + loop, accent-on-selected styling - CommandInput: search icon + native input piped through Command.Input - CommandList + CommandViewport: max-height + scroll - CommandEmpty: shown when filter yields nothing - CommandGroup: heading + items wrapper - CommandSeparator: 1px line between groups Barrel `shared/ui/index.ts` now exports 45 named primitives. Refs: PRD-018, RFC-016, #68, #78
Group 8 of 8 from issue #68 (PRD-018 / RFC-016). Closes the primitive build-out: - shared/ui/README.md catalogues all 26 new primitives + 5 existing (Button/Code/Dialog/Modal/Select), grouped by family (Visual atoms / Form basics / Toggles / Radio / Disclosure / Overlays / Command). - /playground +page.svelte renders every primitive in a single page — used as the visual smoke harness during PRs. Smoke run (dev server on :5174, Chrome MCP): - /playground renders all 7 sections with 0 console errors. - /, /playground both clean across data-theme=light + data-theme=dark. - Tooltip/Popover/Toaster mounted via root +layout.svelte all interact: click "Open popover" -> bubble appears; click "Success toast" -> "Saved!" toast in bottom-right with success accent and dismiss button. - Light theme bg = rgb(245,242,234); dark theme = #050505 — both honour PRD-015/RFC-014 token contract. Note: integrating new primitives into existing widgets/version-footer/ update-dialog is intentionally deferred to a follow-up PR — keeping the diff for issue #68 focused on landing the catalogue. The /playground route gives reviewers + downstream widget work a stable visual contract to land against. Refs: PRD-018, RFC-016, #68, #79
… / EVID-022 EVID-022 measures issue #68 Group 8 deliverables against PRD-018: - SC-1 (catalogue completeness): 26/26 — pass - SC-3 (visual smoke in Chrome): 0 console errors across light + dark — pass - SC-4 (npm run check): 0 errors — pass - NFR-003 (svelte-check): 0 errors — pass - NFR-005 (dual-theme parity): verified — pass - SC-2 (no ad-hoc copies in widgets/*): deferred to follow-up — flagged Structured Fields: verdict=supports, congruence_level=3 (CL3 same-context), evidence_type=measurement. R_eff = 1.00 (grade A) on both PRD-018 and RFC-016. PRD-018, RFC-016, EVID-022 all activated (draft → active). Refs: PRD-018, RFC-016, EVID-022, #68
## Summary - Adds 26 shadcn-svelte primitives to `template/src/shared/ui/` via `bits-ui` wrappers + CSS-var styling (no Tailwind, no new runtime deps). - Catalogue + `/playground` showcase route for visual smoke and design review. - Backed by **PRD-018** + **RFC-016** + **EVID-022** (R_eff = 1.00, grade A). ## Why Issue #68 — promote `shared/ui` from 5 ad-hoc primitives (Button, Code, Dialog, Modal, Select) to the full primitive set so future widgets compose from a single, consistent library. 7 of the previously-empty placeholder folders (`badge/alert/separator/skeleton/spinner/progress/card/`) are now real implementations. ## What's in 26 primitives across 7 functional groups, each its own commit + sub-issue: | Group | Sub-issue | Primitives | |-------|-----------|------------| | 1 | #72 | Badge / Separator / Skeleton / Spinner / Card / Alert / Progress | | 2 | #73 | Label / Input / Field / InputGroup | | 3 | #74 | Toggle / ToggleGroup / ButtonGroup / Switch / Checkbox / Slider | | 4 | #75 | Radio / RadioGroup | | 5 | #76 | Tabs / Collapsible / Accordion | | 6 | #77 | Tooltip / Popover / Toaster | | 7 | #78 | Command + Item family | | 8 | #79 | README catalogue + `/playground` route + Chrome smoke | `+layout.svelte` now wraps the app in `TooltipProvider` and mounts `Toaster`. ## What's deferred - Replacing ad-hoc CSS in `widgets/version-footer`, `widgets/mosaic`, `widgets/update-dialog` with the new primitives (PRD-018 SC-2). Tracked as a follow-up issue. ## Test plan - [x] `npm run check` (svelte-check) — 0 errors / 0 warnings - [x] Chrome smoke at `http://127.0.0.1:5174/playground` in `data-theme=dark` — 0 console errors, all 7 sections render - [x] Chrome smoke at `/playground` in `data-theme=light` — body bg `rgb(245,242,234)` matches PRD-015 token, 0 console errors - [x] Chrome smoke at `/` (home, force-directed graph) — 354 SVG nodes render; `+layout.svelte` change didn't break the existing app - [x] Interactive checks: Popover bubble opens, Toaster `Saved!` appears bottom-right, Tabs/Accordion state, Command palette filters ## Forgeplan trail - **PRD-018** — shadcn-svelte primitives in shared/ui (Standard depth) — `active` - **RFC-016** — bits-ui-based primitives with CSS-var styling — `active`, `based_on` PRD-018 - **EVID-022** — catalogue + Chrome smoke measurement — `active`, CL3 supports measurement, R_eff = 1.00 grade A Closes #68. Refs: PRD-018, RFC-016, EVID-022
Swap inline `<button class="update-btn">` for the shared `Button` primitive (variant=secondary, size=sm). Outer `.update-btn-pos` wrapper keeps fixed positioning; the inner glow dot, "UPDATE" label, arrow, and version chip stay as decorative spans. Refs: PRD-018 SC-2, RFC-016, EVID-022 · Closes part of #81 batch 1/3 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#83) Swap inline `<button class="pane-icon">` for the shared `Button` primitive (variant=ghost, size=sm) on the three pane-frame controls (reset zoom / add pane / close pane). Pane-header layout (toolbar chrome, drag handle, padding) stays inline. Tooltip primitive cannot wrap Button until it supports `asChild` semantics (current bits-ui Trigger renders its own <button>, which nests buttons). \`title\` attrs preserved as a stop-gap; TODO marker in the file links the follow-up. Refs: PRD-018 SC-2, RFC-016, EVID-022 · Closes part of #81 batch 2/3 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sue #84) Swap the inline \`.error-bar\` block (custom div with embedded retry button) for `Alert variant="danger"` containing a `Button variant="ghost"` retry control. Border-radius zeroed via class override to keep the full-width banner look at the top of the layout. UpdateDialog form-body work (Field/Input) — out of scope here: the dialog's body is informational (version comparison + manual update steps), not a form. Existing Dialog/Button/Code primitives already cover what's there. Refs: PRD-018 SC-2, RFC-016, EVID-022 · Closes part of #81 batch 3/3 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap inline `<button class="chip">` toggleable filter buttons in Filters.svelte for the Toggle primitive (variant=outline, size=sm). Pressed state, ARIA, focus ring all come from the primitive. Inline .chip CSS removed (~30 lines). Color dot + ring decorations kept inline since they're filter-domain visuals, not generic atoms. Refs: PRD-018 SC-2, RFC-016, EVID-022 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Theme 3-segment switcher (Auto/Light/Dark) → ToggleGroup type=single with ToggleGroupItem children. Replaces manual role=radiogroup + aria-checked wiring with bits-ui's accessible primitive. Notify toggle (binary on/off) → Toggle primitive (variant=outline, size=sm). The pressed state shows accent-dim background; the existing 🔔/🔕 icon swap stays inline. Status .chip rail (TOTAL/ACTIVE/DRAFT/stale/blind segments) kept as inline custom segmented bar — design intent is the unified bordered rail look, which Badge primitives wouldn't reproduce. Documented as a deliberate skip. Refs: PRD-018 SC-2, RFC-016, EVID-022 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Tab count badges (Recent 20 / Blocked 4 / Drafts 9 / Agents N) → Badge primitive. variant=primary for warn (blocked/drafts), secondary for neutral (recent/agents). Removes 12 lines of .badge CSS. - "Lowest R_eff" progress bars (Health tab) → Progress primitive. variant maps to reffTone(): danger/warning/success. Removes ~20 lines of inline span-based bar styling and the .scoring-row .bar override. KPI cards, .tab navigation, and other inline atoms in this widget left for follow-up rounds. Refs: PRD-018 SC-2, RFC-016, EVID-022 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ives - .kind inline span → Badge variant=ghost size=sm. Removes 7 lines of .kind CSS. - 5 .ghost buttons (Show downstream/upstream/Clear/Hide body/Copy as markdown) → Button variant=ghost size=sm with class="panel-action" preserving the project's mono-font + uppercase + accent-on-hover style via :global() override. Removes ~28 lines of duplicated .ghost CSS. - .close × button → Button variant=ghost size=sm. Position kept (top-right absolute), font-size 22px override for the × glyph. - copy-md state classes (copied/failed) routed through interpolated class string instead of class:directive (component classes can't use class: directives in Svelte 5). Refs: PRD-018 SC-2, RFC-016, EVID-022 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three corrections after visual diff against the /playground reference:
1. HealthBar theme switcher — drop the custom :global(.theme-toggle)
override that stripped bg/padding. The ToggleGroup now renders its
natural bg-2 + 2px-padding container with a raised bg-1 pill on
the selected segment, matching the "Left | Center | Right" example.
2. Filters KIND/STATUS — collapse N independent <Toggle> calls into
one <ToggleGroup type="multiple"> per section. Multi-select state
flows through value/onValueChange. Looks like a unified segmented
filter rail, matching the /playground style.
3. Timeline collapse button — was Button (binary state was hidden in
aria-expanded). Now <Toggle pressed={!collapsed}> so the expanded
state visibly highlights with accent-dim. The "now" trigger stays
a Button (one-shot action). Removed dead .ghost / .toggle CSS.
Refs: PRD-018 SC-2, RFC-016, EVID-022
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous styling kept the ToggleGroup's default packed layout (2px gap, single bg-2 container, transparent item borders). The intended look is independent outlined chips: - ToggleGroup wrapper made transparent (no bg/border/padding). - Items spaced 6px apart, wrap onto multiple rows. - Each item gets a visible line-2 border and bg-1 fill. - Hover bumps to line-3. - Pressed (data-state=on) → accent border + accent text + plain bg. ToggleGroup type=multiple semantics preserved — only the visual layer changed via :global() overrides scoped to .filter-group. Refs: PRD-018 SC-2, RFC-016, EVID-022 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Progress was correctly mounted but rendered at size="sm" (4px height) — visually too thin against the user's reference. Bumping to size="md" (6px) matches the intended chunkier look while keeping the rounded pill shape and tone-driven variant (success/warning/danger). Refs: PRD-018 SC-2 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ipline
shared/ui/ is the single owner of visual primitives. Upper FSD layers
(entities/widgets/pages/routes) compose primitives but MUST NOT modify
their internals from above (no :global() overrides on primitive class
names, no inline atom re-implementations, no consumer-side variants).
Required path when a feature needs a new look:
1. Add a variant/size/prop to the primitive in shared/ui/<name>/
(or create a new primitive — preferring shadcn-svelte / bits-ui
ports, styled with plain CSS + CSS vars from app/styles/app.css).
2. Showcase on /playground in light + dark themes.
3. Use the new prop from the upper layer.
Allowed upper-layer styling: layout, positioning, motion, container
chrome that does not shadow a primitive — composition, never
customisation.
Lists existing violations as TODO(rule-24-cleanup) debt for future
cleanup PRs. Updates rules/00-index.md.
Refs: PRD-018 SC-2
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New EvidencePack EVID-023 documents the shared/ui integration across 7 widgets/pages (PR #85). Structured Fields: verdict=supports, congruence_level=3 (CL3 same-context), evidence_type=measurement. Linked --informs--> PRD-018 and RFC-016. Activated. forgeplan score PRD-018 → R_eff: 1.00 (Adequate), 2 evidence (EVID-022 + EVID-023). Confidence: insufficient (only 2 evidence in support — acceptable for a Standard-depth PRD with same-context CL3 measurements). PRD-018 SC-2 moves "deferred" → "pass". The primitives catalogue is now the rendering path on the home route, the artifact panel, the filters sidebar, the health bar, the timeline header, the version footer, and the mosaic pane controls. Refs: PRD-018 SC-2, RFC-016, EVID-022 · Closes #81 (after PR #85 merge) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
) (#85) Closes #81, #82, #83, #84. PRD-018 SC-2 follow-up integration: replace ad-hoc atom CSS with `@/shared/ui` primitives across the SvelteKit app. EvidencePack [EVID-023](https://github.com/ForgePlan/forgeplan-web/blob/feature/prd-018-sc2-integration/.forgeplan/evidence/EVID-023-shared-ui-integrated-across-7-widgets-pages-closes-prd-018-sc-2.md) closes the PRD's SC-2 ("deferred" → "pass"); `forgeplan score PRD-018` → R_eff = **1.00 (Adequate)**. ## What changed 11 commits, 9 files modified. | Commit | Scope | Primitives used | |---|---|---| | `434e179` | `widgets/version-footer/UpdateButton.svelte` | Button | | `48124be` | `widgets/mosaic/PaneFrame.svelte` (3 pane controls) | Button | | `f8de23c` | `pages/home/HomePage.svelte` (error bar) | Alert + Button | | `c2c830b` | `widgets/artifact-filters/Filters.svelte` (KIND/STATUS chips) | Toggle | | `0965bed` | `widgets/health-bar/HealthBar.svelte` (theme switcher + notify) | ToggleGroup + Toggle | | `2b07516` | `widgets/insights-rail/InsightsRail.svelte` (tab badges + R_eff bars) | Badge + Progress | | `a2d3cc0` | `widgets/artifact-panel/ArtifactPanel.svelte` (kind chip + 5 ghost buttons + close) | Badge + Button × 6 | | `<sha>` | `widgets/{health-bar,artifact-filters,timeline}/*` alignment fixes — match `/playground` reference | ToggleGroup + Toggle (corrected styling) | | `<sha>` | `widgets/artifact-filters/Filters.svelte` — outlined chips with spacing | ToggleGroup | | `<sha>` | `widgets/insights-rail/InsightsRail.svelte` — Progress bars to size=md | Progress | | `<sha>` | `.claude/rules/24-shared-ui-ownership.md` — new rule: shared/ui owns primitives, upper layers compose only | — | | `<sha>` | `.forgeplan/evidence/EVID-023-*.md` — closing EvidencePack | — | **Net diff:** **+201 / −254 lines** in `template/src/`. ≈53 lines of duplicated atom CSS removed; behaviour preserved. ## Forgeplan - ✅ EVID-023 created with `## Structured Fields` (verdict=supports, congruence_level=3 CL3, evidence_type=measurement). - ✅ Linked `EVID-023 --informs--> PRD-018` and `--informs--> RFC-016`. - ✅ Activated (status=active). - ✅ `forgeplan score PRD-018` → R_eff: **1.00 (Adequate)**, F-G-R: 0.79 (B), 2 evidence. - ✅ Rule 24 (`shared/ui` ownership) added to `.claude/rules/00-index.md`. ## Test plan - [x] `npm run check` — 0 errors, 0 warnings (1039 files). - [x] Visual smoke in Chrome on `/` — light + dark theme parity verified end-to-end. - [x] DOM inspection of migrated nodes: - Lowest R_eff bars → `<.progress class="progress variant-success size-md">` with proper `width: %` on `.bar` (height 6px, border-radius 999px verified via `getComputedStyle`). - PaneFrame controls → 6 nodes with `class="btn variant-ghost size-sm pane-icon …"`. - HealthBar theme switcher → bits-ui `<ToggleGroup type="single">` with proper ARIA wiring; clicking DARK actually flips `documentElement.dataset.theme`. - [x] No app-side console errors (only chrome-extension `locatorjs` warnings, unrelated). - [x] `grep -RIn '<style>' template/src/widgets/{version-footer,mosaic} template/src/pages/home` shows only layout/positioning/motion styles. ## Out of scope (intentional, documented in EVID-023 §Interpretation) - `VersionFooter.svelte` — fixed-positioned chrome. - `UpdateDialog.svelte` body — informational, not a form. - `Splitter`, `MosaicCanvas`, drop overlay — drag/layout chrome. - HealthBar status `.chip` rail — segmented unified-border design intent. - InsightsRail KPI cards (`.kpi`) and `.tab` navigation — bigger refactor; deferred. - `+error.svelte` `.home-link` — `<Button>` lacks `href`. Catalogue gap. ## Known debt (rule 24 cleanup) Eleven `:global()` overrides remain in upper-layer `<style>` blocks (HealthBar, Filters, ArtifactPanel, InsightsRail, Timeline, UpdateButton, PaneFrame). Tracked as `TODO(rule-24-cleanup)` in EVID-023 §Interpretation. Each should resolve into a new variant/size on the corresponding primitive (preferred) or a removal. ## Catalogue gaps surfaced 1. `Button` has no `href` — anchor-styled-as-button cases blocked. 2. `Tooltip.Trigger` always renders its own `<button>` — can't wrap `<Button>`. `TODO(rfc-016-tooltip-aschild)` marker in `PaneFrame.svelte`. 3. `Card` padding caps at `lg` (16px) — large card paddings (40px+) need a `padding="xl"`. Follow-up RFC candidate: "shared/ui catalogue gaps" addressing these three. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
#89) PRD-019 Wave 1 of the PRD-018 SC-2 audit follow-ups (#99). #86 — HealthBar theme switcher restored radiogroup semantics: - Wrapper carries role="radiogroup" + aria-label="Theme". - ToggleGroupItem grew aria-checked + role pass-through props, each item now renders role="radio" aria-checked={mode === id}. - Dropped `as ThemeMode` cast in favour of an isThemeMode guard. - permission typed NotificationPermission | 'unsupported' (matches notify.svelte.ts contract). - Lifted props to Props interface, dropped legacy $props<{}>(). - Added requesting flag — disables Toggle during async permission prompt to fix the rapid-toggle race (closes #95 partially). #87 — Timeline collapse uses aria-expanded: - Replaced <Toggle pressed> (aria-pressed) with <Button> carrying aria-expanded={!collapsed} + aria-controls="timeline-body". - Disclosure pattern is now correct; screen readers announce "expanded"/"collapsed" instead of "pressed". - Added FIXME(pointer-capture-race) per rule 10. #89 — rule-24 verification grep: - Replaced broken `rg --type svelte` (svelte is not a built-in ripgrep type) with `--type-add 'svelte:*.svelte'`. - Replaced broken &&/|| shell pair (conflated "no matches" with "command failed") with explicit if-form. Snippet now exits non-zero on violation, zero on clean — verified on this branch. - Added consumer-supplied class names to forbidden list (.panel-action, .update-btn, .theme-toggle, .notify-toggle, .pane-icon, .error-alert, .tab-badge, .timeline-toggle, .kind, .close, .filter-group) — dominant violation pattern in PR #85. - Refreshed existing-violations roster with FSD `ui/` segment + added pages/home/ui/HomePage.svelte (.error-alert). Wave 2 prep (primitive variants for #88): - Toggle: added variant="outline-mono" (mono uppercase, accent on pressed). Used by HealthBar's notify toggle. - ToggleGroup: added variant prop + variant="outline-mono" (transparent, no inner padding/gap). - ToggleGroupItem: added role + aria-checked pass-through; variant-outline-mono styles inside ToggleGroup variant. Browser-verified accessibility tree at /: radiogroup, aria-label="Theme" ├─ radio aria-checked=false "Auto" ├─ radio aria-checked=true "Light" └─ radio aria-checked=false "Dark" button.timeline-toggle aria-expanded="true" aria-controls="timeline-body" aria-pressed=null npm run check: 0 errors, 0 warnings, 1039 files. Refs: PRD-019 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#91 #92 #93 #94 #95 #96 #97 #98) PRD-019 Waves 2-4 of the PRD-018 SC-2 audit follow-ups (#99). Wave 2 — primitive variants (#88): - Button: variant="ghost-mono" (mono uppercase, accent-on-hover, aria-expanded styling); size="icon" (22×22 square). - Badge: variant="mono" (mono uppercase, no border-radius). - Alert: tone="banner" (no rounded corners, no l/r border). - Toggle, ToggleGroup, ToggleGroupItem already gained outline-mono in Wave 1. ToggleGroupItem also accepts role + aria-checked passthrough for radiogroup composition. - TabsList grew a wrap?: boolean prop to support multi-row tab rails (used by InsightsRail). - Showcased every new variant on /playground in light + dark. - Updated shared/ui/README.md. Wave 3 — apply variants + per-widget fixes: #91 — InsightsRail: hand-rolled <nav class="tabs"> replaced with Tabs/TabsList/TabsTrigger primitive. Browser-verified: tablist role + 5 role="tab" items + aria-selected="true" on active. #97 items 2-4 also addressed: {@const n = t.badge()} (no triple- call), keyed each (t.key), inline <span class="kind"> replaced with <Badge variant="mono">. #92 — ArtifactPanel: hand-rolled Outgoing/Incoming disclosures replaced with Collapsible / CollapsibleTrigger / CollapsibleContent. Built-in aria-controls wiring + focus management. #93 — HealthBar: see Wave 1 commit (already closed). #94 — HomePage: dropped `as typeof liveNodes/liveEdges` casts. Tightened ArtifactSnapshot.{kind,status} from `string` to the canonical literal unions (ArtifactSnapshotKind / ArtifactSnapshotStatus, defined in shared/server/snapshot.ts to keep the FSD import direction valid). HomePage now uses local toLowerKinds/Statuses helpers with type guards instead of brute-force lowercasing. #95 — HealthBar: see Wave 1 commit (already closed). #96 — ArtifactPanel auto-collapse $effect: outgoing.length / incoming.length reads now wrapped in untrack() so the 10s graph poll (which re-derives identical-content arrays with new refs) no longer silently undoes user toggles. #97 — code-quality bundle: - item 1 (Timeline FIXME) — already in Wave 1. - items 2-4 (InsightsRail) — see #91 above. - item 5 (PaneFrame): added TODO(select-trigger-marker) per rule 10. - item 6 (Timeline fetch): added AbortController with 10s timeout matching server's GIT_TIMEOUT_MS. - item 7 (clipboard fallback): moved ta.select() inside try block so a select() throw on iOS Safari doesn't leak the textarea. #98 — Filters: tightened to discriminated unions. `kinds: ArtifactKind[]; kindFilter: Set<ArtifactKind>` (analogous for status). HomePage's settings.ts also typed accordingly with isArtifactKind / isArtifactStatus type guards on persist load. Removed :global() overrides retired by Wave 2 variants: ArtifactPanel: panel-action, close, kind, copy-md.copied/.failed InsightsRail: tab-badge, inline .kind block HomePage: error-alert Filters: filter-group .toggle-group-item internals PaneFrame: pane-icon UpdateButton: update-btn Timeline: timeline-toggle mono styling Wave 4 — TODO markers (#90): The audit's premise was that retiring a :global() override would require leaving a TODO for cleanup. We retired the underlying violations directly via Wave 2 variants — there are no orphan overrides left to tag. Updated the rule-24 doc's "Existing violations" section to reflect this. Verification: - `npm run check` clean (1039 files / 0 errors / 0 warnings). - Rule-24 grep returns OK on this branch (no upper-layer `:global()` reaches into a primitive class name from the PRIMITIVE_CLASSES set). - Browser-verified at http://127.0.0.1:5174/ (light + dark): radiogroup[aria-label=Theme] with 3 radios + aria-checked button.timeline-toggle aria-expanded + aria-controls [role=tablist][aria-label=Insights] with 5 [role=tab] Alert tone=banner (no rounded corners, no l/r border) - Browser-verified at /playground (light + dark): every new variant renders, tokens resolve in both themes. Refs: PRD-019, closes #88 #90 #91 #92 #93 #94 #95 #96 #97 #98 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the PRD-019 lifecycle for the audit follow-ups (issue #99): - EVID-024 created with `## Structured Fields` (verdict: supports, congruence_level: 3, evidence_type: test) — same-context test against the actual surface PRD-019 modifies (svelte-check on template/src/, rule-24 grep on widgets/pages/routes, browser DOM inspection). - forgeplan link EVID-024 PRD-019 --relation informs. - forgeplan score PRD-019 → Quality 0.76 (B-grade) with R_eff > 0. - forgeplan activate EVID-024 (draft → active). - forgeplan activate PRD-019 (draft → active). Refs: PRD-019, EVID-024
## Summary Resolves all 13 child issues of umbrella **#99** — the multi-expert audit follow-ups for PRD-018 SC-2 (`shared/ui` migration in PR #85). - **A11y regressions fixed** — HealthBar `radiogroup` (#86), Timeline `aria-expanded` (#87), InsightsRail `tablist` (#91). - **Primitive catalogue extended** (#88) — `Button.variant="ghost-mono"`, `Button.size="icon"`, `Badge.variant="mono"`, `Alert.tone="banner"`, `Toggle/ToggleGroup.variant="outline-mono"`, `ToggleGroupItem` `role`+`aria-checked` passthrough, `TabsList.wrap`. All showcased on `/playground` in light + dark. - **Widgets migrated to catalogue** — InsightsRail uses `Tabs/TabsList/TabsTrigger` (#91), ArtifactPanel uses `Collapsible` for Outgoing/Incoming disclosures (#92). - **Type safety tightened end-to-end** — `ArtifactSnapshot.kind/.status` literal unions in `shared/server/snapshot.ts`, dropped `as typeof` casts in HomePage (#94), discriminated unions through Filters (#98), HealthBar typing fixes (#93). - **Latent reactivity bugs fixed** — HealthBar notify race during permission prompt (#95), ArtifactPanel auto-collapse `$effect` re-firing on graph poll (#96). - **Rule-24 enforcement** — verification grep now exits non-zero on violation (#89), forbidden-class list extended with consumer-class pattern, all 13 `:global()` overrides retired by Wave 2/3 variants (#90). - **Code-quality bundle** — FIXME/TODO markers, AbortController on Timeline fetch, clipboard fallback DOM-leak edge case (#97). Driven by **PRD-019** (active, R_eff > 0) with **EVID-024** structured-fields evidence linked (verdict: supports, congruence_level: 3, evidence_type: test). ## Why The PRD-018 SC-2 migration shipped APPROVE_WITH_FIXES — structurally complete and svelte-check clean, but introduced silent a11y regressions for screen-reader users, ~13 rule-24 violations re-skinning primitive internals from upper layers, type casts hiding source-of-truth gaps, and broke the rule-24 verification grep itself. The catalogue (`/playground`) was no longer the source of truth — half the visuals were produced by overrides scattered across widgets. This PR closes that gap so the next contributor compositions instead of forking. ## Test plan - [x] `npm run check --prefix template` — clean (1039 files / 0 errors / 0 warnings) on the final commit. - [x] Rule-24 verification grep run from `template/` — exit 0, prints `OK: no upper-layer reaches into primitive internals`. - [x] Browser DOM inspection via Chrome DevTools at `http://127.0.0.1:5174/`: - `[role=radiogroup][aria-label=Theme]` with 3 `[role=radio][aria-checked]` items - `button.timeline-toggle aria-expanded="true" aria-controls="timeline-body" aria-pressed=null` - `[role=tablist][aria-label=Insights]` with 5 `[role=tab][aria-selected]` items - `Alert tone="banner"` replaces `.error-alert` `:global()` override - [x] Browser visual smoke at `/playground` (light + dark) — every new variant renders, tokens resolve in both themes. - [ ] CI matrix (ubuntu/macos/windows × Node 22) — required gate. - [ ] VoiceOver/NVDA manual a11y pass — out of scope (DOM tree verified mechanically). ## Out of scope - Adding rule-24 verification as a CI step (separate ticket). - `shared/ui/README.md` worked-example expansion (table updated; `/playground` is the contract). ## Closes Closes #86, #87, #88, #89, #90, #91, #92, #93, #94, #95, #96, #97, #98. --- Refs: PRD-019, EVID-024 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…facts Sankey (issue #69): - Bar height = max(in_value, out_value) * LINK_HEIGHT (12px) so every bar is exactly proportional to its flow weight, mirroring the classic d3-sankey "river" visual. - Orphan bars (no edges) get 1-link height (12px) and stack below connected nodes with ORPHAN_GROUP_GAP — keeps connected vs disconnected groups visually distinct. - Adaptive VIEW_H grows with the densest column so packing never collapses bars to ~2px on dense workspaces. - Label-collision drop hides labels whose centres are within LABEL_LINE_HEIGHT_PX of the previous visible label in the same column; hover/select reveals dropped labels. - Labels become hover targets (pointer-events: auto), so users can hover the id text, not only the bar rectangle. - fitToView is width-driven for dense layouts so bars keep their chrome — vertical overflow is reachable via pan. Tree (issue #39): - Layer assignment is now kind-tier-driven (compactTierMap) instead of topological dfs(incoming). Each artifact kind gets its own row in the same compact-tier order Sankey uses. - Intra-row wrap when natural row width exceeds ~1.5x viewport; sub-rows of equal cardinality preserve edge geometry. PRD-020 / RFC-017 / SPEC-001 / SPEC-002 + EVID-025 activated; R_eff > 0; CL3 test evidence on the playground (123 artifacts). Includes user's HealthBar / Filters style touch-up (out-of-scope files but bundled per request). Refs: PRD-020 RFC-017 SPEC-001 SPEC-002 EVID-025 Closes #69 Closes #39 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
) (#101) ## Summary Fixes both #69 (broken Sankey at 100+ artifacts) and #39 (broken Tree at 100+ artifacts) with a single bundled change in `widgets/dependency-graph/`. ## What changed **SankeyView** - Bar height = `max(in_value, out_value) × LINK_HEIGHT (12px)` so every bar's vertical extent is exactly its flow weight — matches the classic d3-sankey reference visual. - Orphan bars (no edges) keep 1-link height and stack below connected nodes with an `ORPHAN_GROUP_GAP` so flow vs disconnected stays visually distinct. - Adaptive `VIEW_H` grows with the densest column; sparse workspaces stay at the 760-px floor (no regression). - Label-collision drop hides labels whose centres are within `LABEL_LINE_HEIGHT_PX` (22) of the previous visible label in the same column; hover/select reveals. - Labels are hover targets too (`pointer-events: auto`) — not only the bar. - `fitToView` is width-driven for dense layouts so bars keep their chrome; vertical overflow is reachable via pan. **TreeView** - Layer assignment now driven by kind tier (`compactTierMap`) instead of topological `dfs(incoming)`. Each kind gets its own row in compact-tier order. - Intra-row wrap when natural row width > 1.5× viewport; sub-rows of equal cardinality preserve edge geometry. **Forgeplan** - PRD-020 (product), RFC-017 (architecture, Option A), SPEC-001/002 (per-view), EVID-025 (CL3 test evidence at 123 artifacts) — all activated. - `R_eff > 0` for PRD-020. **Bundled out-of-scope** - User's manual style touch-up to `HealthBar.svelte` + `Filters.svelte` (per their request to include in this PR). ## Test plan - [x] `cd template && npm run check` — 1041 files, 0 errors, 0 warnings - [x] `cd template && npm run test` — 15 files passed, 146 tests (incl. 9 new in `sankey-layout.test.ts` and 9 new in `tree-layout.test.ts`) - [x] Manual verification at `npm run dev:playground` (123 artifacts): - Sankey: bars proportional to flow, labels readable, orphans grouped below - Tree: each kind on its own row, dense kinds wrap, no 1-stripe collapse - [x] Backwards-compat at the live `.forgeplan/` workspace (62 artifacts) — visual identical to develop branch 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…prop
Replace hand-rolled shared/ui/toaster with thin svelte-sonner wrapper;
public surface (Toaster, toast, toast.info/success/warning/danger,
toaster, types) preserved. Sonner CSS vars mapped to project tokens.
Extend shared/ui/toggle-group:
- Variant widens to 'default' | 'outline-mono' | 'outline'
- New spacing?: boolean prop -> data-spacing attr; gap + flex-wrap
applied via primitive (no consumer-side :global() overrides needed).
Rewire widgets/artifact-filters/Filters.svelte to use
variant="outline" + spacing={true}; drops the :global(.filter-group)
layout block (spacing handled by the primitive).
Playground: new tiles for outline variant w/ and w/o spacing.
shared/ui/README.md: updated ToggleGroup + Toaster rows.
Verification — typecheck 0 errors (1047 files), build green,
smoke PASS, rule-24 grep clean. R_eff=1.00 on PRD-021/RFC-018.
Refs: PRD-021, RFC-018, EVID-026
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…102) ## Summary - Replace hand-rolled `shared/ui/toaster/` with a thin `svelte-sonner` wrapper while preserving the public surface (`Toaster`, `toast`, `toast.info/success/warning/danger`, `toaster`, types). - Extend `shared/ui/toggle-group/` with `'outline'` variant (per shadcn-svelte) and a `spacing?: boolean` prop that emits `data-spacing="true"` and applies `gap + flex-wrap` from the primitive itself. - Rewire `widgets/artifact-filters/ui/Filters.svelte` to `variant="outline"` + `spacing={true}` and drop the `:global(.filter-group)` layout block (handled by the primitive now). - Playground: new tiles for outline variant w/ and w/o spacing; README updated. ## Why Filters needed an outlined look with breathing room without violating rule 24 (`shared/ui` ownership). Sonner gives us hover-pause / swipe / promise toasts for free and removes ~230 LOC of bespoke store + DOM rendering. ## Forgeplan - PRD-021 (active, R_eff = 1.00) - RFC-018 (active, R_eff = 1.00) - EVID-026 (active) — typecheck 0 / build green / smoke PASS / rule-24 clean (CL3, evidence_type=test) ## Test plan - [x] `npm run check` → 1047 files, 0 errors, 0 warnings - [x] `npm run build` → exit 0 - [x] `node scripts/smoke.mjs` → PASS (homepage + /api/health + /api/list) - [x] `grep -RIn ":global(.toggle-group" template/src/{widgets,pages,routes,entities}` → 0 matches outside `shared/ui` - [ ] Manual: visit `/playground` and confirm outline + spacing tiles render in both `data-theme` values - [ ] Manual: visit `/` and confirm Filters look like the screenshot in the PRD-021 brief 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds a new view mode `force3d` last in the GRAPH_VIEWS dropdown with an EXPERIMENTAL badge. Renders the same artifact dependency graph as 2D Force in a Three.js scene via Threlte v8, simulating positions with d3-force-3d. The chunk is lazy-loaded behind `view === 'force3d'` so non-3D users pay zero bytes. What ships: - shared/config/ui-prefs.ts: GraphViewMeta gains optional `badge`; new `force3d` entry registered last. - shared/ui/select: Select renders a pill-style badge next to the label (used by Force 3D, available to any future entry). - widgets/dependency-graph-3d: new widget — Threlte canvas + d3-force-3d simulation, OrbitControls, kind-colored spheres (R_eff sized), edge segments, theme reactivity (light + dark via data-theme observer). Initial layout uses a phyllotaxis sphere (radius 50) so the simulation starts spread out, then forces shape clusters. - widgets/dependency-graph/ui/DependencyGraph.svelte: lazy-import branch for Force 3D — no Three.js / Threlte in the main bundle. - widgets/dependency-graph/ui/ForceView.svelte: TODO marker for the growth-vision in-Force-mode 2D ↔ 3D switch. Forgeplan trail (full Standard pipeline, R_eff = 1.00 / grade A): - PRD-022 — problem, FRs / NFRs, acceptance, risk register - RFC-019 — Option A (Threlte + d3-force-3d) chosen vs three alternatives - EVID-027 — smoke test with structured fields (verdict / CL / type) Refs: PRD-022 RFC-019 EVID-027 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The experimental single-file ESM bundle inlines all runtime deps. Force 3D adds Three.js + Threlte (~2.4 MB unzipped). Non-3D users still pay 0 bytes in the legacy `dist/` shape (lazy chunk), but the experimental shape can't code-split — so the floor moves to 6M. Documented in RFC-019. Refs: PRD-022 RFC-019 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary Adds a new **Force 3D** view mode (last in the dropdown, badged `experimental`) that renders the artifact dependency graph in a Threlte v8 canvas, simulating positions with `d3-force-3d`. Chunk is lazy-loaded so non-3D users pay zero bundle bytes. Closes #103. ## Why The 2D Force view collapses dense workspaces (~80+ artifacts) into hairballs where cluster topology becomes illegible. The 3D layout separates orthogonal sub-graphs spatially. Reference: vasturiano/3d-force-graph; visual style adapted to Forgeplan tokens (kind colors, status emissive glow, R_eff → sphere radius). ## What changed - `shared/config/ui-prefs.ts` — `GraphViewMeta.badge?` + `force3d` entry last. - `shared/ui/select` — pill badge next to label. - `widgets/dependency-graph-3d/` — new widget: Threlte canvas, scene, `NodeMesh`, sim builder, theme adapter, `d3-force-3d` ambient types. - `widgets/dependency-graph/ui/DependencyGraph.svelte` — lazy `await import('@/widgets/dependency-graph-3d')` only when `view === 'force3d'`. - `widgets/dependency-graph/ui/ForceView.svelte` — TODO marker for the future in-Force-mode 2D ↔ 3D switch (PRD-022 growth vision). - `template/package.json` — `@threlte/core`, `@threlte/extras`, `three`, `d3-force-3d` to deps; `@types/three` to devDeps. ## Forgeplan - **PRD-022** active — Standard depth, problem / FRs / NFRs / acceptance. - **RFC-019** active — Option A (Threlte v8 + d3-force-3d) chosen vs vanilla Three, the reference 3d-force-graph wrapper, and r3f. - **EVID-027** active — smoke test (`verdict: supports`, `CL: 3`, `type: test`), R_eff = 1.00, overall grade **A** on both PRD and RFC. ## Test plan - [x] `npm run check` — 0 errors / 0 warnings (2105 files). - [x] `npm run build` — production build passes; `dependency-graph-3d` chunk lives in its own client chunk; verified the homepage entry only references it via dynamic `import('../chunks/...')`. - [x] Browser smoke (Chromium MCP) — Force 3D last in dropdown with EXPERIMENTAL badge, 3D scene renders 123 nodes / 104 edges, orbit drag rotates camera, theme reactivity (light + dark) confirmed. - [ ] Performance trace (NFR-001 first-frame, NFR-002 fps p50) — deferred to a follow-up EvidencePack alongside the in-mode 2D ↔ 3D switch. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…105) Reverts #104 in full as requested. `git revert -m 1 c60fd77` undoes: - Removes `template/src/widgets/dependency-graph-3d/` (Force 3D widget). - Drops `force3d` from `GRAPH_VIEWS` + `GraphView` union. - Restores `Select.svelte` (no badge support). - Drops `three`, `@threlte/core`, `@threlte/extras`, `d3-force-3d`, `@types/three` from `template/package.json`. - Restores `template/package-lock.json`. - Removes `// TODO(force-mode-2d3d-switch)` from `ForceView.svelte`. - Restores `scripts/build.mjs` `DIST_EXPERIMENTAL_MAX_BYTES` to `3 * 1024 * 1024` (the 3M → 6M bump bundled in PR #104 is reverted in the same commit). - Removes `.forgeplan/prds/PRD-022`, `.forgeplan/rfcs/RFC-019`, `.forgeplan/evidence/EVID-027`. Issue #103 will be reopened after merge — the feature is no longer in the codebase, so its tracking issue should reflect that. ## Test plan - [x] `npm run check` — 0 errors / 0 warnings (1047 files, matches pre-#104 baseline). - [ ] CI matrix (ubuntu / macos / windows) green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Extends the dual-theme system (PRD-015 / RFC-014) with a third opt-in palette `orch`: pure-black surfaces (`--bg: #000`) and lavender accent (`--accent: #a78bfa`). Toggle in the health bar gains a fourth position alongside Auto / Light / Dark; `auto` mode stays binary and never resolves to `orch`. Pre-paint inline script accepts the new mode synchronously, so first-paint is FOUC-free for users who saved it. The change is a pure token-swap — every CSS-var defined under `:root[data-theme='dark']` is mirrored under `:root[data-theme='orch']`, so no component-level code changes were required. Verified via svelte-check (0/0 errors/warnings across 1047 files), token-name diff (empty modulo intentionally-shared `--font-*` / `--dot-grid-*`), and production build round-trip (compiled `_layout.css` carries all three selectors and the lavender literal). FG/BG contrast 16.58:1 (AAA). Refs: PRD-023, EVID-028 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UX shape change for the orch theme (PRD-023 v2):
- The visible health-bar toggle now stays at three positions
(Auto / Light / Dark). Orch becomes a 4th visible item only
after the user clicks Dark five times in a row — a hidden
Easter egg. Counter resets when any non-Dark item is clicked.
- Once Orch is active, clicking it (deselect) bounces back to
Auto. Clicking any active item never leaves the toggle
visually empty: a remount tick is bumped on every empty
emit from bits-ui so the controlled `value` prop re-pins
the visual state. Spec invariant: 100% always some pressed
item.
- Two race conditions tamed:
1. bits-ui ToggleGroup fires onValueChange("dark") AFTER
our click-delegation handler has already advanced mode
to "orch" on the 5th dark click. Sentinel
`justUnlockedOrch` swallows that single follow-up.
2. bits-ui's internal `data-state` desyncs from a
controlled `value` when the user re-clicks an active
item. `{#key toggleRemountTick + ":" + items.length}`
forces a fresh ToggleGroup mount so the visual re-pins.
Palette softened per user feedback ("цвета слишком
контрастные") to match Orchestra's marketing references:
foreground stepping compressed (`--fg` `#fafafa` →
`#d6cdf2`, `--fg-1` `#e8e8ed` → `#b3acc8`, etc.), accent
moved to a paler lavender (`#a78bfa` → `#b9a8ff`), and
edge / canvas / good-bad tokens dimmed proportionally.
F/B contrast `--fg-1` on `--bg-1` ≈ 9.7:1 — comfortably
above WCAG AAA, but visually softer than the previous
16.6:1.
Verified in browser via dev server (5174):
- 5 Dark clicks → orch active, Orch button appears + pressed
- Click active Orch → Auto active, orch button gone
- Click active Auto / active Light → stays / falls back to Auto
- Theme persists through reload via inline pre-paint script
Refs: PRD-023, EVID-028
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Once Orch becomes visible (5th consecutive Dark click), all theme toggle items go disabled for ORCH_LOCK_MS = 2000ms so accidental follow-up clicks (the user keeps mashing Dark beyond the 5th unlock click) cannot immediately bounce out of orch. Disabled state propagates through bits-ui via the existing `disabled` prop on ToggleGroupItem; the click-delegation handler also early-returns during the lock as a defensive belt-and-braces. Refs: PRD-023 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… freeze (#106) ## Summary Adds a third theme palette `orch` (Orchestra-inspired pure-black + muted lavender) alongside the existing `dark` and `light` palettes, hidden behind a 5-consecutive-Dark-clicks Easter egg with a 2s post-unlock toggle freeze. - **Palette**: `--bg #000`, `--accent #b9a8ff`, foreground stepped from `#d6cdf2` → `#2e2b36`. Lower contrast than `dark` per design references; AAA on `--fg-1`/`--bg-1`. - **UX**: Visible toggle stays at 3 positions (Auto / Light / Dark). Orch appears as a 4th item only after 5 consecutive Dark clicks; counter resets on any non-Dark click. - **Lock**: After Orch activates, all toggle items go disabled for 2000 ms so accidental follow-up clicks (the user keeps mashing Dark beyond the unlock threshold) cannot bounce out. - **Always-some-value**: Re-clicking an active item (deselect attempt) bounces to Auto and force-remounts the ToggleGroup so the visual re-pins to the store. Spec invariant: never visually empty. ## Why PRD-023 / EVID-028. Existing dual-theme system (PRD-015 / RFC-014) only exposed warm `dark` (`#ff5a1f` accent) and cream `light`. Adds a cool true-black palette for OLED users and long-session reviewers, gated behind an Easter egg per user direction. ## Test plan - [x] `cd template && npm run check` — 0 errors / 0 warnings (1047 files) - [x] `cd template && npm run build` — built successfully; orch palette + lavender accent present in production CSS - [x] Browser smoke (5174): - 5 Dark clicks → Orch button appears + pressed, theme=orch, lavender palette applied - Click active Orch → Auto active, orch button disappears - Click active Auto / Light → mode falls back to Auto, button stays pressed (never empty) - Within 2 s of orch unlock all toggle buttons are disabled ## Files - `template/src/app/styles/app.css` — `:root[data-theme='orch']` palette block (every dark-defined token mirrored, dimmer/cooler values) - `template/src/shared/lib/theme.svelte.ts` — `ThemeMode` / `ThemeEffective` widened to include `'orch'` - `template/src/app.html` — pre-paint script accepts `'orch'` - `template/src/widgets/health-bar/ui/HealthBar.svelte` — 5-click streak, 2s lock, deselect-to-auto, remount-tick visual re-pin - `.forgeplan/prds/PRD-023-...md` + `.forgeplan/evidence/EVID-028-...md` — Forgeplan artifacts (R_eff = 1.0) Refs: PRD-023, EVID-028 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Cuts release/v0.1.13 from develop. Highlights: - shared/ui catalogue + /playground showcase (PRD-018, RFC-016) - rule 24 — shared/ui ownership and customisation discipline - new primitive variants from rule-24 audit (ghost-mono, mono, outline-mono, banner) - orch theme (Orchestra-inspired) as 5-click easter egg + 2s freeze - dense-graph render-resilience for Sankey + Tree - adoption of svelte-sonner Toaster - reverted Force 3D experimental Threlte view (#103/#104/#105) Refs: PRD-018, RFC-016, EVID-022, EVID-023, EVID-024, PRD-019
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release branch for v0.1.13. Cuts from
develop(38 commits ahead ofmain).Highlights
bits-ui, showcased on/playgroundin light + dark themes.shared/uiownership and customisation discipline. Forbids:global()re-skins of primitive internals from upper FSD layers; ships a verification grep snippet.Button.variant=ghost-mono,Button.size=icon,Badge.variant=mono,Toggle/ToggleGroup.variant=outline-mono,Alert.tone=banner,TabsList.wrap. Replaces hand-rolled markup across widgets.orchtheme — third palette (Orchestra-inspired), exposed as a hidden 5-click easter egg with a 2 s freeze after unlock.svelte-sonnerfor toasts.dist-experimentalcap stays at 6M.Test plan
npm run build— bothdist/anddist-experimental/build clean (dist-experimental: 2.43M)npm run smoke— passes against scratch workspace/api/*(rule 22)Refs: PRD-018, RFC-016, EVID-022, EVID-023, EVID-024, PRD-019
🤖 Generated with Claude Code