Skip to content

feat(storage-plugin): add hex-first UI for binary entries#262

Open
burczu wants to merge 7 commits into
callstackincubator:mainfrom
burczu:feat/storage-plugin-hex-first-binary-ui
Open

feat(storage-plugin): add hex-first UI for binary entries#262
burczu wants to merge 7 commits into
callstackincubator:mainfrom
burczu:feat/storage-plugin-hex-first-binary-ui

Conversation

@burczu
Copy link
Copy Markdown

@burczu burczu commented May 11, 2026

Closes #248.

Summary

  • Replaces the decimal-array presentation of buffer entries with a hex-first UI across the storage plugin.
  • Table cell renders a compact hex preview + byte count: 89 50 4E 47 0D 0A 1A 0A … 128 B.
  • Detail dialog renders a standard hexdump with 8-digit lowercase offsets, grouped hex (16/line, double-space gap after byte 8), and an ASCII column (0x20–0x7E printable, others as .).
  • Add / Edit dialogs render a CodeMirror-backed BinaryValueEditor for buffer values. Hex is the default mode; Base64 is available for copy/paste workflows. Bytes are the source of truth — switching modes converts in place when the input parses, or clears to a fresh editor when it doesn't.
  • Paste normalization rewrites pasted content into canonical grouped hex. Accepted shapes: raw continuous hex, grouped hex, multiline grouped hex, hexdump rows with offsets and |ascii| columns, optional 0x prefixes, mixed case.
  • Typing leaves the document alone — single keystrokes pass through unchanged. Trailing nibbles are tolerated; save is disabled until the input is valid.
  • Validation messages match the issue: Enter at least one byte., Hex input contains invalid characters., Hex input must contain complete bytes., Base64 input is invalid.
  • Internal data model unchanged — buffer is still number[]. No changes to StorageEntry, storage-view.ts, adapters, the runtime hook, or the bridge wire format.
  • New CodeMirror devDependencies: @codemirror/state, @codemirror/view, @codemirror/commands. Matches the version pins used by sqlite-plugin.
  • Changeset: minor bump for @rozenite/storage-plugin.

Architecture

A pure-function-first posture keeps the spec behavior tests exercisable without RTL/jsdom (which the repo doesn't have set up):

  • src/ui/binary.ts — pure helpers: bytesToGroupedHex, bytesToHexdump, bytesToAsciiPreview, bytesToBase64, base64ToBytes, compactBufferPreview, hexInputToBytes (lenient parser stripping hexdump offsets, ASCII columns, 0x prefixes, whitespace).
  • src/ui/binary-value-editor-state.ts — pure reducer: initialState, reduce, validate. Handles set-text (preserves user text, re-parses), normalize-paste (rewrites text on success), switch-mode (convert in place / clear on invalid).
  • src/ui/binary-value-editor.tsx — thin React wrapper: useReducer + CodeMirror wiring. The update listener short-circuits on round-trips (comparing doc to stateRef.current.text) to avoid loops between the reducer and the editor.

Test plan

Automated (49 tests covering the spec bullets):

  • binary.test.ts (30 tests): grouped hex spacing including post-byte-8 gap, hexdump offsets + ASCII column + trailing-line padding, ASCII printable boundary checks, base64 round-trip with arbitrary bytes, compact preview with/without ellipsis, hex parser accepting raw / grouped / multiline / hexdump rows / colon offsets / 0x prefixes / mixed case, hex parser rejecting empty / non-hex / odd-length.
  • binary-value-editor-state.test.ts (19 tests): initialState for all input shapes; set-text for valid / invalid chars / partial nibble / cleared / non-canonical-but-valid (verifies text is not rewritten); normalize-paste rewrites hexdump → canonical, base64 → canonical, keeps raw on invalid; switch-mode converts valid in place, clears on invalid, no-op on same mode; validate for empty / invalid / valid.
  • pnpm --filter @rozenite/storage-plugin test — 53/53 passing.
  • pnpm --filter @rozenite/storage-plugin typecheck — clean.
  • pnpm --filter @rozenite/storage-plugin lint — 0 errors (1 pre-existing unrelated warning).
  • pnpm --filter @rozenite/docs build — website builds.

Manual (playground, MMKV — others are string-only):

  • Table cell shows 89 50 4E 47 0D 0A 1A 0A … N B for buffer entries.
  • Detail dialog shows a hexdump with offsets and ASCII column for buffer entries.
  • Edit dialog opens populated with canonical grouped hex; editing one byte saves correctly.
  • Pasting a hexdump (with offsets and |ascii|) normalizes immediately into grouped hex.
  • Typing a single nibble at the end disables save and shows "Hex input must contain complete bytes."
  • Toggling to Base64 converts the bytes; toggling back to Hex re-encodes.
  • Invalid hex + mode toggle clears the editor.
  • Add dialog allows creating a new buffer entry from hex input.

Out of scope

  • A full byte-grid hex editor.
  • Adapter-specific native dump formats.
  • Changes to the StorageEntry contract, adapter APIs, or the storage write pipeline.
  • UI test infrastructure (RTL / jsdom) — covered by pure-function and reducer tests instead.

burczu added 6 commits May 12, 2026 06:47
Adds pure helpers in src/ui/binary.ts for the upcoming hex-first
buffer UI: grouped hex (16/line, gap after byte 8), full hexdump
with offsets and an ASCII column, ASCII previews, base64 round-trip
via btoa/atob, a compact table-cell preview, and a lenient hex parser
that strips hexdump offsets, trailing ASCII columns, "0x" prefixes,
and whitespace before validating even-length hex.

All helpers are unit-tested with the spec error messages
("Enter at least one byte.", "Hex input contains invalid characters.",
"Hex input must contain complete bytes.", "Base64 input is invalid.").

Refs callstackincubator#248
Pure reducer for the binary editor: initialState encodes initial
bytes into the chosen mode, set-text re-parses without rewriting,
normalize-paste replaces user text with the canonical encoding on
success, and switch-mode converts in place when bytes are valid or
clears to a fresh editor otherwise.

The reducer is fully testable without React. Validate surfaces the
parse error, the canonical "Enter at least one byte." for empty input,
or returns the parsed bytes when ready to save.

Refs callstackincubator#248
CodeMirror-backed React component that wraps the editor reducer.
Detects paste transactions and dispatches normalize-paste so canonical
grouped hex (or base64) replaces the raw paste, while typed input
passes through unchanged. A doc-sync effect pushes reducer text back
into CodeMirror only when they diverge (after paste normalization or
mode switch), with the update listener short-circuiting on round-trips
to avoid loops.

Adds @codemirror/state, view, and commands as devDependencies.
Theme matches the panel's dark UI; gutters hidden, line-wrapping on.
Component is binding-only; behavior tests live on the reducer.

Refs callstackincubator#248
Switches the table cell, detail dialog, edit dialog, and add dialog
to the new hex-first surfaces:

- Table: compact preview "89 50 4E 47 0D 0A 1A 0A ... 128 B" replaces
  the decimal array.
- Detail dialog: hexdump view with offsets and ASCII column, byte count
  in the header.
- Edit/Add dialogs: BinaryValueEditor (CodeMirror, paste-normalized,
  Hex/Base64 toggle) replaces the JSON-array textarea when type is
  buffer. Save is gated on pendingBytes; the editor's onChange routes
  parsed bytes directly to the existing number[] write path. Enter-to-
  save is excluded for buffer so editor newlines work as expected.
  Modals widened to w-[32rem] to fit the editor comfortably.

No changes to runtime, adapters, or the StorageEntry contract.

Refs callstackincubator#248
Adds a "Binary entries (buffer type)" section to the storage plugin
docs covering the new hex-first surfaces: compact table preview,
hexdump detail view, the CodeMirror-backed editor (Hex and Base64
modes), the canonical 16-byte-per-line format, paste normalization
(raw hex, grouped, multiline, hexdump rows with offsets/ASCII columns,
optional 0x prefixes), incomplete-input semantics, validation
messages, and a cross-adapter buffer-support matrix.

Refs callstackincubator#248
Bumps @rozenite/storage-plugin as minor for the hex-first UI for
binary (buffer) storage entries.

Refs callstackincubator#248
@burczu burczu force-pushed the feat/storage-plugin-hex-first-binary-ui branch from 6239231 to bb10330 Compare May 12, 2026 04:48
@burczu burczu marked this pull request as ready for review May 12, 2026 04:54
…preview

Two small fixes around buffer editing surfaced by manual testing on
the hex-first UI:

- MMKV stores raw bytes per key without a type tag. After setting a
  buffer whose bytes decode to valid printable ASCII (e.g. hex
  68 65 6c 6c 6f = "hello"), the existing getEntry heuristic called
  getString first and classified the result as a string, flipping
  the type field in the panel. Each MMKV storage now keeps an
  in-memory map of last-known types for keys it has set; getEntry
  consults the override before falling back to the heuristic. Scoped
  to the round-trip (edit -> save -> re-read) case within a session.

- The binary editor's ASCII preview was rendered unlabeled, so for an
  all-ASCII buffer it visually read as the entry's value rather than
  a derived preview. Now prefixed with "ASCII:" to make the role
  explicit.

Refs callstackincubator#248
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.

Improve storage-plugin binary entry UI with hex-first read and edit flows

1 participant