Skip to content

Rewrite with React & Tailwind CSS - #1

Draft
romeoahmed wants to merge 129 commits into
mainfrom
feat/react-rewrite
Draft

Rewrite with React & Tailwind CSS#1
romeoahmed wants to merge 129 commits into
mainfrom
feat/react-rewrite

Conversation

@romeoahmed

Copy link
Copy Markdown
Owner

No description provided.

romeoahmed and others added 30 commits April 17, 2026 11:52
1. Fix teal color: #7dc4e4 → #8bd5ca (was duplicate of sapphire)
2. Split tsconfig into app (browser, no bun) + node (bun/config files);
   remove sourceMap:false and noUncheckedSideEffectImports
3. Add src/lib/throttle.ts leading+trailing throttle utility
4. Fix useSimulation: throttle slider param Comlink calls at 16ms
   to prevent unbounded Promise queue growth on slider drag (~60Hz)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New branch for complete React + WebGPU rewrite. Removes old PixiJS/SCSS
stack. Includes all production and dev dependencies, strict TypeScript
config split into app (browser) and node (bun) configs, Catppuccin
Macchiato theme via @catppuccin/palette, IBM Plex font pair.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix TS error code reference in global.d.ts (TS2307, not TS2882)
- Remove redundant build.sourcemap:false from vite.config.ts (Vite default)
- Add comment to toCssVars() explaining its JS-context usage
Biome 2.4: organizeImports under assist.actions.source, files.includes
with negations instead of files.ignore, noImportantStyles:off for
accessibility !important in prefers-reduced-motion block.
- css.parser.tailwindDirectives:true to allow @theme syntax
- Folder excludes without trailing /** (Biome 2.2+ convention)
- passWithNoTests:true so vitest exits 0 when no tests exist yet
- Auto-fixed: vite.config.ts import order, globals.css font-feature-settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Defines all shared interfaces (circuit graph, physics config, DI types),
default physics constants, Zod schemas for voltage spec and circuit
definition validation, and leading+trailing throttle for Comlink
rate-limiting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ypes

Explicit interface annotation widened const literal types to number,
making 'as const' inert. 'as const satisfies T' preserves narrow types
(e.g. dashPattern as readonly [number, number]) while still enforcing
the interface shape. Also adds color-sourcing comment.
ClockSource, SignalSource, DFlipFlop now accept ComponentDeps instead of
separate config+rng args, enabling direct registration with ComponentRegistry.
CircuitGraph instantiates components/nets from definition, propagates
voltages, topologically sorts combinational logic. SimulationEngine owns
graph + WaveformBuffer, runs fixed-timestep accumulator at 10kHz.
Worker entry exposes loadCircuit, setParam, setSettings, start/stop
via Comlink. Tick loop at ~120Hz with fixed-timestep accumulator.
Sends frame data to Render Worker via MessageChannel at ~120Hz,
voltage status to main thread at ~20Hz.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…unused interpolants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
romeoahmed and others added 30 commits April 18, 2026 13:28
plugin-react@6 dropped Babel support, leaving the Lingui macro
transform silently disabled. Both dev builds and tests relied on
an accidental no-op. SWC transforms macros at build and test time,
unblocking production builds and letting the macro mocks in
src/test/setup.ts stay a belt-and-suspenders safety net.
Added shadcn token aliases to src/styles/globals.css in both dark and
light theme blocks, mapping their CSS vars (--background, --foreground,
--card, --primary, etc.) onto the project's existing Apple tokens.
Also exposed these aliases to Tailwind via @theme inline with --color-*
prefixes and radius utility variants.

Fixed import order in src/lib/utils.ts to pass Biome linting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…group, dialog, sheet)

Patch slider.tsx for exactOptionalPropertyTypes compatibility
(conditional spread of defaultValue/value) and add a biome-ignore
for the inherent thumb-index key (thumbs have no stable id).
Replace `space-y` wrapper and individual `grid-cols-[1fr_auto]` declarations
with a unified parent grid that defines column tracks once. Child rows use
`col-span-2 grid grid-cols-subgrid` to inherit those exact tracks, ensuring
label columns and value columns align across all rows regardless of content
width.

Updates:
- ControlPanel: parent grid with grid-cols-[1fr_auto] gap-x-3 gap-y-2
- ParamSlider: subgrid row with explicit row-start-2 for full-width slider
- ParamToggle: subgrid row with label + switch
- ParamMomentary: subgrid row with label + momentary button
- SettingsSheet: voltage input section uses same pattern

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add SheetDescription/DialogDescription to SettingsSheet, AboutSheet,
and ShortcutsOverlay so Radix stops warning about missing description
linkage. Wrap atom-driven store.set in act() inside the
ProbeStateAnnouncer test so React stops warning about unwrapped
updates.
…ng Lingui

- CLAUDE.md: tech stack (SWC + shadcn + Lingui), atom inventory, ui/
  directory, dual-theme + shadcn token-alias styling section, Lingui
  caveats (.mjs catalogs, SWC plugin, vitest macro shim), commands
- README.md / README.zh-CN.md: Catppuccin → Apple dual theme; Radix →
  shadcn/ui (Radix + Tailwind); add subgrid + theme + locale toggle UX;
  fix locales path src/i18n/locales; add hooks list; add lingui scripts
…rrectness

- Remove dead duplicate src/components/nav/SettingsSheet.tsx
- Drop 5 redundant @radix-ui/react-* scoped packages (unified radix-ui umbrella already present)
- engine.ts: discard negative dt to prevent accumulator underflow on timer regressions
- clock-source.ts: fix single-step phase wrap to robust modulo formula
- signal.ts: validate ringFreq/zeta are positive finite in applyConfig
- i18n/index.ts: guard against stale locale race — drop late-resolving catalog write if a newer switch is in flight
- ui-atoms.ts: persist shaderStyleAtom and localeAtom via atomWithStorage; use safe localStorage wrapper that gracefully no-ops in test environments with broken Storage stubs
- ProbeStateAnnouncer: prune stale probe entries from messagesRef when active probes change
- ParamMomentary: derive visual active state from atom (keyboard shortcuts now update the button); add onPointerCancel to release stuck hold state
- Toolbar.tsx: fix ShaderStyle type guard — widen array type rather than casting the argument
- CircuitSchematic.test: replace vague toBeTruthy() with not.toBeNull()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e boundaries

- ui-atoms: replace null as unknown as Storage with a typed noopStorage object
  that satisfies StateStorage structurally (no double-cast needed)
- useSimulation: remove dead Comlink.releaseProxy call on local proxy callback
  (Comlink.proxy() returns T & ProxyMarked, not Remote<T>, so [releaseProxy] is
  never present); simplify RenderAPI init cast via named RenderInitArgs alias
- render.worker: replace 5 bare 'as' casts on MessageEvent.data fields with a
  typed FrameMessage interface + isFrameMessage() type guard
- gpu-device: remove incorrect 'as GPUDevice | null' cast (requestDevice()
  returns GPUDevice per spec); keep defensive !device guard for safety

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…k proxies

- Add `if (cancelled) return` after loadCircuit, registerStatusCallback,
  and render.init so a terminated bridge is never restarted
- Clear bridgeInFlightRef on createWorkerBridge() rejection so the hook
  can recover instead of awaiting a rejected promise forever
- Expose releaseStatusCallback() on the physics worker and call it from
  the main thread cleanup. Comlink.proxy() returns T & ProxyMarked (no
  [releaseProxy]), so only the worker's Remote<fn> can release the
  channel — route the call through a dedicated worker method.
- Drop redundant releaseProxy() calls from worker-bridge.terminate() —
  the RELEASE message is lost when the worker is immediately terminated
- Push persisted shaderStyle and voltageSpecs to the bridge right after
  init, so localStorage values actually take effect on first paint
releaseStatusCallback() sent a RELEASE message to the worker, but
terminate() was called immediately after, killing the worker before
it could process the message. Move the release into stop() which
runs synchronously inside the worker; terminate() handles port
cleanup for app teardown automatically.
The previous implementation scheduled activeProbeIdsAtom reset and
clearCircuitAtoms(def) inside a setTimeout(0). React's effect-order
guarantees mean the new effect's synchronous body always runs before
that timeout fires and always clears it, so the reset never executed.

Detect real circuit change by comparing def references across renders
and perform the cleanup synchronously in the new effect body. StrictMode
remount (same def object) correctly skips the clear and preserves probe
selection; user-driven circuit switches get a fresh start.
paramAtomFamily initialized every atom to 0, ignoring the circuit's
declared defaults. Slider UI displayed its fallback value while the
atom held 0, and the physics worker ran on its internal default —
three sources of truth diverged at first paint.

After loadCircuit, walk circuitDef.controls and store.set each atom
to ctrl.defaultValue (or 0). The already-registered store.sub
subscribers forward the initial value to the physics worker, making
UI, atom, and worker consistent from the first frame.
Replacing this.engine while the loop was running could fire a tick
against the new engine in its just-constructed state, before the
caller's start() restored deterministic timing. Halt the loop up
front; the caller resumes with start() after loadCircuit returns.
…applyConfig

The applyConfig runtime guard was bypassed for objects built via the
constructor. Extract validation into a private static helper and call
it from both entry points so every Signal instance has a physically
meaningful second-order oscillator configuration.
…lity

The @theme inline block never forwarded --accent or --accent-foreground
into Tailwind tokens, and --color-accent was claimed by the project's
Apple Blue. Shadcn components using bg-accent / text-accent-foreground
therefore rendered Apple Blue or no colour at all on hover/active states.

Rename the shadcn aliases to --shadcn-accent / --shadcn-accent-foreground,
forward them as --color-shadcn-accent{,-foreground}, and switch button.tsx
outline/ghost variants plus toggle.tsx data-[state=on] styling to the new
utility names. Also fixes dialog.tsx close-button data-[state=open]:bg-accent.
Project intents for Apple Blue via bg-accent remain intact.
settingsOpenAtom and aboutOpenAtom live in src/atoms/ui-atoms.ts;
shortcutsOpenAtom was declared inside the ShortcutsOverlay component
file and re-exported, which made App.tsx import atom state from a
component module. Colocate all three overlay atoms to match CLAUDE.md's
documented architecture and avoid the implicit circular-import risk.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
useKeyboardShortcuts listed the caller-supplied options object in its
effect deps, but the caller passes a fresh literal every render. The
effect was running on every AppInner render, removing and re-adding
window keydown/keyup listeners — a keypress landing in the ~1ms gap
would be silently dropped.

Capture options.onOpenHelp in a ref that's synced by its own tiny
effect, and drop options from the main effect's dep array. Listeners
now attach once per mount.
…element

The real <Plural> from @lingui/react/macro returns a ReactElement and
accepts ReactNode for one/other. The test shim returned a raw string
and typed the branches as string, so any test rendering JSX inside a
Plural branch would fail silently and type-check incorrectly.

Handle both string branches (with Lingui's # substitution) and
ReactNode branches (pass-through) and always return an element.
…orrectly

buildSchematicDescription returned a plain English string joined from
literal fragments, meaning screen readers in non-English locales always
heard English for the SVG description. Split the helper into a pure
structural parts builder; assemble the final string inside the component
using Lingui's t macro so locale switches translate naturally.

The colocated describe.test.ts and CircuitSchematic.test.tsx are
updated to assert against derived counts rather than hardcoded literals
so future circuit changes don't silently break the tests.
… adapter

CLAUDE.md mandates English-only source text. The existing Chinese
describe/it strings and the 'as unknown as { requestDevice: ... }'
casts on mockAdapter were carried over from an earlier draft. Replace
them with English test names and a locally-typed MockAdapter whose
requestDevice is a properly-typed vi.fn — no double casts remain on
the mock-setup path.
CLAUDE.md already states "No ! non-null assertions in production code".
The Biome rule was only a warning, so violations wouldn't fail CI.
Flip it to error so the rule is mechanically enforced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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