Skip to content

feat: overhaul with Vite, React 19, Tailwind, Builder, Structure View#77

Merged
wemeetagain merged 37 commits intomasterfrom
feat/complete-overhaul
Mar 19, 2026
Merged

feat: overhaul with Vite, React 19, Tailwind, Builder, Structure View#77
wemeetagain merged 37 commits intomasterfrom
feat/complete-overhaul

Conversation

@wemeetagain
Copy link
Member

Summary

  • Complete rewrite from React 17 + Webpack + Bulma to React 19 + Vite + Tailwind CSS 4
  • New interactive type builder — form-based SSZ data construction with type-appropriate inputs (uint, boolean, bytes, containers, lists, bit grids)
  • New structure view — collapsible tree showing SSZ type schema with color-coded types, values, and generalized indices
  • Live reactive processing — no submit button, debounced updates as you type
  • Side-by-side layout — input left, output + structure right
  • Dark Ethereum dev-tool aesthetic with Lodestar branding
  • Base64 deserialize support alongside hex
  • Round-trip mode switching — data carries across serialize/deserialize tabs

What changed

Removed (20+ packages): Webpack, Babel, Bulma, SCSS, threads, react-alert, react-loading-overlay, react-spinners, eyzy-tree, file-saver, bn.js, core-js, and all associated loaders/plugins

Added (3 packages): Comlink, Sonner, Tailwind CSS

New features:

  • Builder mode with inputs for every SSZ type (containers, lists, vectors, uint, boolean, bytes, BitList/BitVector with interactive bit grid)
  • Structure view with type introspection, color-coded categories, click-to-expand values, generalized indices on hover
  • Format tabs for quick switching (YAML/JSON/hex input, hex/base64 output)
  • Copy/download actions with toast feedback
  • Sticky toolbar with fork, type, and mode selectors

Architecture:

  • Functional components with hooks (no class components)
  • Web Worker via Comlink for SSZ operations (serialize, deserialize, defaultValue)
  • Lifted state in app.tsx, clean component boundaries
  • Code-split build (no 500KB chunks)

Test plan

  • Select different forks — type list updates correctly
  • Select different types — default value loads in builder
  • Builder: edit uint, boolean, bytes, nested container fields
  • Builder: BitList/BitVector bit grid toggles work
  • Builder: list add/remove items
  • Switch Editor/Builder — data syncs between modes
  • Live serialization — output updates as you type/edit
  • Hash tree root displays in serialize mode
  • Switch to Deserialize — serialized hex carries over, deserializes correctly
  • Switch back to Serialize — deserialized value carries back as YAML
  • Base64 deserialize input works
  • Copy button works
  • Download button produces correct file
  • File upload works (text in serialize, binary in deserialize)
  • Structure view renders tree, nodes expand/collapse
  • Long values truncate with click-to-expand
  • Production build succeeds (npm run build)
  • Docker build succeeds

🤖 Generated with Claude Code

wemeetagain and others added 30 commits March 18, 2026 09:24
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address critical gaps: patchSszTypes preservation, worker data
contracts, mode-specific input behavior, structure view data source,
dead YAML schema code, and biome config retention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 tasks covering: Vite scaffold, core lib migration, worker setup,
hooks, UI components, structure view, app wiring, cleanup, and
Dockerfile update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Webpack+Babel+Bulma+SCSS build stack with Vite+React 19+Tailwind CSS 4.
Delete old config files and entry points, add minimal app shell.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Also fixes type errors in lib/types.ts and lib/formats.ts to pass
strict TypeScript checking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- useWorker: switch from useRef to useState so worker initialization
  triggers re-renders and downstream hooks receive the worker
- handleModeChange: clear input and parsed value when switching modes
  to prevent stale YAML being sent as hex in deserialize mode
- CopyButton: wrap clipboard API call in try/catch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SSZ hashing library (as-sha256) uses Buffer.allocUnsafe which
doesn't exist in browser environments. Add the buffer polyfill to
both main thread and worker entry points.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
React's setState treats callable values as updater functions.
Comlink.wrap() returns a Proxy that React tries to invoke, causing
"rawValue.apply is not a function". Wrap in arrow function so
React calls the arrow (updater) which returns the proxy (value).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Deep black surface palette (#0c0e14 / #12151e / #181c28)
- Ethereum blue (#627eea) as accent color throughout
- ETH diamond logo in header
- Google Fonts: Inter for UI, JetBrains Mono for data
- Custom scrollbar styling, consistent focus rings
- Smaller, denser typography for dev-tool feel
- Polished controls: ghost buttons, inset shadow tabs
- Hash tree root highlighted in eth-blue
- Sticky header with backdrop blur
- Better spacing and max-width constraint
- Copy button with "Copied!" feedback state
- Download toast notification

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Form-based builder that introspects SSZ types and generates
appropriate input controls:
- Containers: collapsible field groups with labeled inputs
- uint types: number/text inputs (bigint for 8-byte)
- boolean: toggle buttons
- bytes: hex input with length validation
- lists: add/remove items with element type inputs
- vectors: fixed-length element inputs

Toggle between Editor (text) and Builder (form) modes on the
input panel. Builder syncs values to the text editor and
serialization output in real-time. Available in serialize mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clickable bit grid where each bit is a toggle (0/1). Shows bit
count and set count in the header. Controls for add/remove bit
(BitList only), set all 1, set all 0. Caps visual grid at 256
bits with overflow indicator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSZ bit types use BitArray objects (Uint8Array + bitLen), not plain
boolean arrays. Convert to bool[] for the UI grid, and back to
BitArray via BitArray.fromBoolArray() when emitting changes.
BitVector now initializes with the correct fixed length.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When switching serialize→deserialize, the serialized hex bytes now
flow into the deserialize input for seamless round-trip testing.
When switching deserialize→serialize, the deserialized value flows
back as YAML. Eliminates the brief error from the empty
intermediate state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
result.serialized is already a Uint8Array — don't pass it through
inputFormats.hex.dump which tries to type.serialize() it again.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
toHexString may not handle Comlink-transferred Uint8Array correctly.
Use the same hex dump path that the output panel uses successfully.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 300ms debounce means the first deserialize attempt after a mode
switch still has the old YAML text. Guard against this by checking
that input starts with 0x before calling worker.deserialize.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e mode

Mirror of the deserialize guard: when in serialize mode with a
non-hex format (yaml/json), skip if the debounced input is still
hex data from the previous deserialize mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The auto-generate effect was firing on every mode switch (because
serializeMode and inputFormat were in the dependency chain),
overwriting the carefully placed round-trip data. Now the auto-
trigger only depends on worker/typeName/forkName — mode switches
preserve the user's data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BitArray objects were falling through to String() giving
[object Object]. Now shows "N bits, M set" summary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
wemeetagain and others added 6 commits March 18, 2026 13:05
- BitArray: renders as binary string (e.g., "0000101100...")
- Bytes: renders full hex without truncation
- Long values (>64 chars): truncated with "... (N chars)" suffix,
  click to expand/collapse the full value

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Truncated values now show meaningful context instead of character
count: bytes show "N bytes", bits show "N bits, M set". Uses a
valueSuffix field on TreeNodeData to carry the semantic description
from formatValue to the tree node renderer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rows of 32 bits with the starting index (0, 32, 64...) displayed
on the left of each row. Bumped max visible bits to 512. Makes it
much easier to count and locate specific bit positions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Header: slim branding bar (logo, title, spec/GitHub links)
- New toolbar: fork, type, and serialize/deserialize mode toggle
  in a dedicated row below the header — always visible, not cramped
- Mode toggle moved from output panel to toolbar (it's a global
  control, not an output concern)
- Main area: CSS grid layout for better proportions
- Footer: minimal single-line with versions
- Output panel simplified (no more mode tabs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Favicon: Lodestar icon (favicon.ico, icon-192.png, icon-512.png)
- Title: "SSZ Playground | Lodestar"
- Meta description for SEO
- Header: "Lodestar" label linking to lodestar.chainsafe.io,
  subtitle with spec link, GitHub icon
- Footer: "A Lodestar tool by ChainSafe Systems"
- Matches enr-app branding pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add base64 as a deserialize input format alongside hex. The input
panel now shows hex/base64 tabs in deserialize mode. The worker
decodes base64 via atob before deserializing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wemeetagain wemeetagain changed the title Complete overhaul: Vite, React 19, Tailwind, Builder, Structure View refactor: overhaul: Vite, React 19, Tailwind, Builder, Structure View Mar 18, 2026
- Remove unused fromBase64 function
- Add biome-ignore for intentionally incomplete useEffect deps
- Fix undeclared React type with import() syntax
- Disable noArrayIndexKey rule (SSZ list items have no stable key)
- Auto-format fixes applied

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wemeetagain wemeetagain changed the title refactor: overhaul: Vite, React 19, Tailwind, Builder, Structure View feat: overhaul with Vite, React 19, Tailwind, Builder, Structure View Mar 19, 2026
@wemeetagain wemeetagain merged commit acd6171 into master Mar 19, 2026
3 of 4 checks passed
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