Each diagram type in the Sample Diagrams panel is now a split button:
clicking the diagram name loads its default example, and a dropdown
arrow on the right (shown when a diagram has more than one example)
opens a popover listing all examples for that diagram.
- getSampleDiagrams now returns all examples per diagram with the
default example first, instead of flattening to the default's code
- loadSampleDiagram analytics now include the chosen example title
https://claude.ai/code/session_015rUgHChBEkquLu77fMxmQ6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The getter handed out a deep $state proxy, so in-place mutation of a
persisted array/record would update the UI while silently never being
written to localStorage. All consumers already replace .value wholesale;
$state.raw makes that the only semantics and drops the per-read proxy
overhead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The untrack()/persistAndProcess() pair was a per-function convention:
each update function had to remember both, a forgotten untrack would let
calling effects subscribe to input state (the bug fixed in 6a9a306e),
and a forgotten persistAndProcess would silently skip persistence and
re-validation. A single update(mutate) gateway now makes both structural,
and the scattered untrack calls (three of which were dead) are gone.
Also:
- Export inputState as Readonly<State> so writes outside the update
functions are type errors instead of comment violations.
- Share the 'codeStore' key as a constant instead of two literals.
- Reuse validatedStateOf() in processState instead of duplicating the
validated-state defaults.
- Use $state.raw for validatedCurrent: it is only ever replaced
wholesale, so deep proxying every published state was pure overhead.
The new state.svelte.test.ts pins the invariants: update functions never
make a calling effect track input state, and every mutation persists.
Vitest needed resolve.conditions=['browser'] for those tests — it was
loading Svelte's server build, where $effect is a no-op.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
readJSON returned any successfully parsed value, so a localStorage
entry holding the literal "null" came back as null instead of the
fallback — and a null inputState crashes module init on every load
until storage is cleared. The deleted MacFJA persist layer guarded
this with `null !== initialValue`; restore that behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
currentText was $state, and the validated-state sync $effect both reads
and writes it. Every keystroke (which sets currentText in the CodeMirror
updateListener) therefore re-ran the effect while re-validation was
still in flight, dispatching a full-document replacement with the stale
validatedState text — visibly reverting the keystroke and resetting the
cursor until validation caught up.
The effect only needs to react to validatedState publishes, so make
currentText a plain variable, matching DesktopEditor.
No unit test: the repo has no component-test harness, and CodeMirror
cannot mount under jsdom without one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
toggleDarkTheme runs inside the layout $effect; without untrack the
effect starts depending on inputState.mermaid and re-adds the default
theme whenever a loaded config replaces it (caught by the unsafe-config
scrub e2e test).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
History now uses the shared persist.svelte helpers, the MacFJA store
copy is deleted, and the esserializer dependency it required is dropped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the writable+derived store pipeline in state.ts with a
state.svelte.ts module: a deep $state inputState mutated only through
the exported update functions, each of which persists the snapshot and
re-validates it asynchronously into validatedState.current (with
urls.current derived from it). Store subscriptions in components become
$effect blocks, and the rough/grid toggles use function bindings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code-review cleanup:
- Collapse the two mode switches (the historyState.entries getter and
activeSlot) into a single slotFor(mode) helper; entries now reads
`slotFor(mode)?.value ?? loader`.
- Remove the now-unused getStateString export from state.ts (its only
consumer was rewritten off it in this PR).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code-review follow-ups:
- validateEntry no longer rejects entries whose time is 0 (epoch); it now
checks `typeof time === 'number'`, so restoring/uploading such entries
works instead of silently dropping them.
- History.svelte serializes each entry's "open in new tab" URL once via a
$derived list instead of calling serializeState (pako deflate) per row on
every render.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each history entry now has a third action: a real anchor (rendered via
the Button's href, target="_blank") linking to the editor with that
entry's serialized state. Being a normal link, users can also copy it or
open it in a new tab via the context menu.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adding the `svelte-check` CI gate surfaced pre-existing type errors and
warnings under develop's TypeScript 6 / Svelte 5.56 bump. Fixes them so
`pnpm check` reports 0 errors / 0 warnings:
- Add `lang="ts"` to Share/Privacy (and a script to PrivacyPolicyLink) so
importers get real declarations instead of implicit `any`.
- Replace the deprecated `monaco.languages.json` with the new top-level
`monaco.json` namespace (proper API, no casts).
- Navbar: use `resolve('/', {})` instead of the deprecated `base`.
- Type the promo `component` as `Component<{ closeBanner: Snippet }>` and
MainMenu's `renderer` as `Snippet<[Omit<MenuItem, 'renderer'>]>`.
- Index-by-string casts in state.ts / Preset / DiagramDocumentationButton.
- Make Actions' clipboard handler accept an optional event.
- toggle-group: expose variant/size via getters to fix the
state_referenced_locally warning.
- Make the History e2e save/delete cases change state via a sample
diagram (deterministic) instead of editor typing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate the Timeline/Saved history state module to idiomatic Svelte 5
runes and wire up type-checking.
- Rename history.ts -> historyState.svelte.ts and replace the Svelte
stores with $state-backed reactive values, removing the get() calls
(the only remaining one reads the external inputStateStore). A small
localStorage-backed `persisted()` helper keeps the same keys, so user
data is preserved. Consumers read via the reactive `historyState`
getter object instead of store auto-subscriptions.
- The gist loader now replaces the in-memory revisions in one call
(setLoaderEntries) instead of appending, so loading a new gist no
longer accumulates stale revisions.
- Add svelte-check: new `check` script, a "Type check" step in the
unit-tests CI workflow, and skipLibCheck so the check passes on the
dependency .d.ts files. svelte-check reports 0 errors / 0 warnings.
- Add accessible labels to the History toggle and the per-item
Restore/Delete buttons (a11y + testability).
- Unskip tests/history.spec.ts and rewrite it against the new UI:
load-from-localStorage + restore, tab-highlight follows the mode,
save/dedupe, auto-vs-manual isolation, and delete/clear. All pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The auto (Timeline) and manual (Saved) history handling had several bugs.
This rewrites the state module into a small, tested source-of-truth API and
turns History.svelte into a thin view.
Bugs fixed:
- Auto-saves no longer leak into the Saved list. addEntry now writes only to
the store for its own type (previously every entry was also pushed to the
manual store).
- Dedup is keyed on code + config via stateKey() instead of the whole input
state, so volatile/view-only fields (renderCount, updateDiagram, pan/zoom)
no longer cause spurious saves or hide real edits.
- The active-tab highlight tracks the history mode. History binds
activeTabID={$historyModeStore} and Tabs derives the highlight reactively
instead of mutating its prop once, which had frozen it on the first tab.
- Auto-save runs for the whole edit session via startAutoSave() in +page,
with proper cleanup. Previously the interval lived in History.svelte, only
ran while the panel was open, and was never cleared (leaking a new interval
on every open).
- restoreEntries() routes each uploaded entry to the store matching its own
type instead of dumping everything into one store by the first entry's type.
The persisted localStorage keys (autoHistoryStore, manualHistoryStore,
autoHistoryMode) are unchanged, so existing user data is preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guard programmatic editor updates so sample diagrams and URL loads do not re-trigger onUpdate, which caused infinite state/URL churn and failing e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bump the full dependency tree (Vite 8, Vitest 4, ESLint 10, Svelte 5.56,
mode-watcher 1.x, etc.) and adapt code for breaking API changes. Upgrade
Playwright CI image to v1.60 for Node 24 compatibility.
Co-authored-by: Cursor <cursoragent@cursor.com>
Playwright <1.60 hangs on browser extraction under Node 24.16.0; bump
the test dependency and CI container image to unblock e2e.
Co-authored-by: Cursor <cursoragent@cursor.com>