Skip to content

feat: GPU Lab — simulation worksheet cells + public gallery (roadmap #6)#81

Merged
ABCrimson merged 12 commits into
mainfrom
feat/wave1-gpu-lab
Jul 11, 2026
Merged

feat: GPU Lab — simulation worksheet cells + public gallery (roadmap #6)#81
ABCrimson merged 12 commits into
mainfrom
feat/wave1-gpu-lab

Conversation

@ABCrimson

Copy link
Copy Markdown
Owner

Summary

  • simulation worksheet cell: new cell kind mounting the existing WebGPU renderers (PDE heat/wave/laplace heatmap, Lorenz attractor, direction fields) with registry-driven param sliders and presets; state persists through worksheet autosave/share codes
  • /gpu-lab public gallery: RSC page over the server-filtered publicWorksheets query showing only worksheets containing a simulation cell; worksheet PUBLIC/PRIVATE visibility action (atomic ownership check) + fork-on-open for non-owners
  • slider animation: Desmos-style play/pause/speed on plot variable sliders (loop/bounce/once), extracted as a pure tested helper
  • 77 i18n keys × 8 locales; GIF export deliberately deferred (documented captureStream/MediaRecorder design)

Adversarial-review fix included

  • stored simulation params are clamped to spec bounds before any compute — forked/public worksheet content is author-controlled JSON, and a crafted lorenz steps previously drove an unbounded CPU loop on open

Verification

web 350 tests (73 new for registry/sim-cell/slider-animation/gallery filter) — green; typecheck + biome clean. Wave-1 roadmap item #6 + unranked slider-animation polish.

🤖 Generated with Claude Code

ABCrimson and others added 11 commits July 10, 2026 23:08
Move the 11-preset InitialConditionType union and grid generator out of
the PDE studio page into a pure shared module so the worksheet simulation
cell can reuse the exact same preset library. No behavior change; the
relocated numeric hot-path keeps its scoped biome noNonNullAssertion
override (decision-#21a pattern).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SimulationCell { sim, preset, params } — serializable presets/params
  ONLY (plain numbers/strings, never GPU buffers); runtime state stays
  component-local
- SIM_REGISTRY param specs/presets for pde-heat/wave/laplace, lorenz,
  and direction-field (per-preset param specs + CPU f/g mirroring the
  WGSL evalSystem cases)
- updateSimulation action (kind switch resets preset/params to registry
  defaults), importFromJSON accepts 'simulation'
- hydrate({ fork }) detaches non-owner loads (worksheetId null,
  version 0) so edits create a new worksheet instead of 409-spamming
- visibility metadata in store for the publish toggle

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Additive readonly [number, number] override for the built-in eqParams
defaults so worksheet sliders can bind Lotka-Volterra a/b, Van der Pol mu,
spiral alpha, pendulum gamma. 32-byte uniform layout unchanged (f32
offsets 6/7); effect deps use the destructured numbers to avoid tuple
identity churn. Existing ode page call sites unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SimulationCellContent mounts the three EXISTING renderers as-is:
- pde-* -> WebGPUHeatmap (internal WebGPU compute solver + WebGL
  fallback), IC grids from the shared preset library, gpuSolverEnabled
  bound to local play/pause state
- lorenz -> Lorenz3DRenderer fed by LorenzAttractor.simulate through
  useDeferredValue(params) so slider drags stay responsive
- direction-field -> GpuDirectionField with the new params prop;
  mounted only while playing so GPU resources init/dispose cleanly
detectBestBackend() drives the WebGL2 fallback banner and the no-GPU
message card (lorenz + canvas2d). Cell wired into cell.tsx kindMeta +
body (dynamic import) and all three add-cell surfaces; toolbar gains
the Publish-to-Gallery toggle (useActionState). No manual memoization
(React Compiler); strings via worksheet.simulation i18n namespace.
Documented the deferred GIF/clip export follow-up design
(canvas.captureStream + MediaRecorder WebM, zero deps).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SetWorksheetVisibilitySchema (PUBLIC|PRIVATE) in lib/validations
- setWorksheetVisibility server action: auth-gated atomic ownership
  update (same P2025 shape as saveWorksheet), revalidates /gpu-lab
- loadWorksheet result gains isOwner + visibility
- client-wrapper hydrates with fork: true for non-owners so a shared
  worksheet's first edit autosaves as a NEW worksheet owned by the
  viewer (no version conflicts against the owner's row)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-slider Play/Pause with loop -> bounce -> once mode cycling and
0.5x/1x/2x/4x speed cycling. ONE component-level rAF driver whose tick
is a React 19.3 useEffectEvent reading the latest sliders/anims without
re-subscribing; values advance (max-min)/4000ms * dt * speed through the
existing handleValueChange path so onChange -> re-evaluate keeps firing.
loop wraps to min, bounce flips direction at bounds, once stops at max
and clears its entry (rAF loop stops when no sliders animate).
prefers-reduced-motion: nothing auto-plays and flipping the OS setting
mid-animation pauses all sliders. All user-visible strings migrated to
the plots.sliders i18n namespace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- GPU_LAB_WORKSHEETS_QUERY typed document (content JSON included) +
  regenerated codegen artifacts; resolver already serves a 60s PUBLIC
  cache hint
- RSC gallery page via the in-process SchemaLink client: filters nodes
  whose content contains a simulation cell, renders glass-morphism cards
  with sim-kind badges, author (UserSummary fragment unmask), views and
  relative updated time via next-intl getFormatter
- loading.tsx skeleton; command-palette GPU Lab entry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 new keys (plots.sliders.*, worksheet.simulation.* incl. params/presets,
worksheet.publish*, gpuLab.*, nav.gpuLab) with real translations in
en/ru/es/uk/de/fr/ja/zh — correct plural categories per locale (ru/uk
one/few/many, ja/zh other-only). No English placeholders; remaining
identical strings are cognates/proper nouns (Sigma, Van der Pol, de/fr
'Simulation'/'Pause', fr min/max).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The <span> wrapping the Eye icon + view count had aria-label set, but a
plain span's implicit "generic" role does not support ARIA naming
(biome lint/a11y/useAriaPropsSupportedByRole). Move the accessible name
into a visually-hidden sr-only sibling instead of naming the generic
container, and mark the visible count aria-hidden so screen readers
read the sr-only text exactly once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… gallery filter

Fills the test gap on the GPU Lab feature branch — all four Wave 1
review items get real vitest coverage instead of shipping untested:

- lib/simulation/registry.ts: structural invariants over SIM_REGISTRY
  and DIRECTION_FIELD_PRESETS (min<=default<=max, presets/params
  resolve real en.json label keys, direction-field f/g never reference
  an undeclared param key, getSimParams/DEFAULT_PARAMS/DEFAULT_PRESET
  behavior).
- components/worksheet/simulation-cell.tsx: renders the right sliders
  per SIM_REGISTRY entry, propagates a slider change through the real
  worksheet store into the mounted (mocked) GPU renderer, atomic
  preset+params reset on direction-field preset switch, hides the
  preset select for single-preset kinds, and the unknown-sim-kind
  fallback renders instead of crashing. GPU renderers, ui/slider, and
  detectBestBackend are mocked — jsdom has no WebGPU/WebGL.
- components/plot/variable-sliders.tsx: extracted the rAF tick's
  per-frame math into a pure, exported `stepSliderValue()` (loop wrap /
  bounce-and-flip / once-and-finish) so the animation behavior is
  covered without driving a fake requestAnimationFrame loop through a
  real component. No behavior change — the component's tick effect
  event now just calls the extracted function.
- app/[locale]/gpu-lab/page.tsx: extracted the "only show worksheets
  with a simulation cell" rule into lib/simulation/gallery.ts
  (isSimulationCellData / simKindsOf / filterGalleryWorksheets) so it's
  unit-testable without a database or RSC rendering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Worksheet content is untrusted input — forked/public gallery worksheets
arrive as author-controlled JSON, and the sliders only clamp values
written locally. A crafted lorenz `steps` (e.g. 1e9) previously drove an
unbounded CPU trajectory loop on open; NaN/out-of-range params reached
the GPU renderers unchecked. sanitizeSimParams() clamps every declared
spec key to [min, max] and replaces non-finite values with the spec
default before any compute or render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nextcalc-pro Ready Ready Preview, Comment Jul 11, 2026 7:09pm

… feat/wave1-gpu-lab

Union-merged the 8 locale files (disjoint key sets across the five
wave-1 namespaces). Verified: typecheck clean, 486 web tests green,
biome clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ABCrimson
ABCrimson merged commit 44e83d6 into main Jul 11, 2026
7 checks passed
@ABCrimson
ABCrimson deleted the feat/wave1-gpu-lab branch July 11, 2026 19:10
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.

1 participant