From 5f3f436e37131534114c5542f3792a70df2b4731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 1 Apr 2026 15:14:42 +0200 Subject: [PATCH 01/15] refactor: auto-scan specs and add --last/--dry CLI flags Replace hardcoded spec list with directory scanning so new specs are picked up automatically. Remove per-spec log file plumbing. Co-Authored-By: Claude Opus 4.6 (1M context) --- run-specs.sh | 115 ++++++++++++++++++--------------------------------- 1 file changed, 41 insertions(+), 74 deletions(-) diff --git a/run-specs.sh b/run-specs.sh index 1d1decf..a7242c8 100755 --- a/run-specs.sh +++ b/run-specs.sh @@ -1,6 +1,25 @@ #!/bin/bash set -euo pipefail +# ── CLI flags ───────────────────────────────────────────────────────────── +for arg in "$@"; do + case "$arg" in + --last) LAST_ONLY=1 ;; + --dry) DRY_RUN=1 ;; + --help|-h) + echo "Usage: ./run-specs.sh [--last] [--dry]" + echo " --last Run only the last (most recent) spec" + echo " --dry Print prompts without running Claude" + echo "" + echo "Environment variables:" + echo " START_FROM=spec/... Skip specs before this one" + echo " MODEL=opus Override Claude model" + + exit 0 + ;; + esac +done + # ── Signal handling ─────────────────────────────────────────────────────── # Use a temp file for child output so the child runs as a direct child of # this script (not inside a command-substitution subshell). This lets @@ -26,70 +45,8 @@ MODEL="${MODEL:-}" # e.g. MODEL=opus ./run-specs.sh SKIP_PERMISSIONS="${SKIP_PERMISSIONS:-1}" # set to 0 to use normal permission mode START_FROM="${START_FROM:-}" # e.g. START_FROM=spec/004-content-pipeline.md DRY_RUN="${DRY_RUN:-}" # set to 1 to print prompts without running -LOG_DIR="${LOG_DIR:-logs}" # directory for per-spec log files - -# ── Spec list (order matters) ────────────────────────────────────────────── -SPECS=( - # Core package specs (sequential) - spec/001-workspace-bootstrap.md - spec/002-types-and-contracts.md - spec/003-trace-parser.md - spec/004-content-pipeline.md - spec/005-runtime-worker-and-adapters.md - spec/006-orchestrator.md - spec/007-cli-trace-replay.md - - # UI sprints — Phase 1: Minimal Viable Debugger - spec/ui/sprint-01-app-shell-and-routing.md - spec/ui/sprint-02-load-bundled-example.md - spec/ui/sprint-03-flat-instruction-list.md - spec/ui/sprint-04-registers-and-status.md - spec/ui/sprint-05-single-step.md - spec/ui/sprint-06-memory-panel.md - spec/ui/sprint-07-debugger-layout.md - spec/ui/sprint-08-run-pause-reset-load.md - - # UI sprints — Phase 2: Complete Load Wizard - spec/ui/sprint-09-full-example-browser.md - spec/ui/sprint-10-file-upload.md - spec/ui/sprint-11-url-and-manual-hex.md - spec/ui/sprint-12-detection-summary.md - spec/ui/sprint-13-spi-entrypoint-config.md - - # UI sprints — Phase 3: Drawer + Settings - spec/ui/sprint-14-bottom-drawer-shell.md - spec/ui/sprint-15-settings-tab.md - spec/ui/sprint-16-stepping-modes.md - spec/ui/sprint-17-keyboard-shortcuts.md - - # UI sprints — Phase 4: Host Calls + Traces - spec/ui/sprint-18-host-call-resume.md - spec/ui/sprint-19-host-call-drawer-tab.md - spec/ui/sprint-20-host-call-storage.md - spec/ui/sprint-21-ecalli-trace-tab.md - spec/ui/sprint-22-ecalli-trace-raw-and-download.md - spec/ui/sprint-23-logs-tab.md - - # UI sprints — Phase 5: Multi-PVM - spec/ui/sprint-24-multi-pvm-tabs.md - spec/ui/sprint-25-divergence-detection.md - - # UI sprints — Phase 6: Panel Polish - spec/ui/sprint-26-instructions-breakpoints.md - spec/ui/sprint-27-instructions-blocks-and-virtualization.md - spec/ui/sprint-28-instructions-asm-raw-and-popover.md - spec/ui/sprint-29-registers-inline-editing.md - spec/ui/sprint-30-registers-change-highlighting.md - spec/ui/sprint-31-memory-spi-labels-and-editing.md - spec/ui/sprint-32-memory-change-highlighting.md - - # UI sprints — Phase 7: Cross-Cutting + Wrap-Up - spec/ui/sprint-33-block-stepping.md - spec/ui/sprint-34-persistence-and-reload.md - spec/ui/sprint-35-mobile-responsive.md - spec/ui/sprint-36-integration-smoke-test.md - spec/ui/sprint-37-user-documentation.md -) + +LAST_ONLY="${LAST_ONLY:-}" # set to 1 to run only the last spec # ── Helpers ──────────────────────────────────────────────────────────────── cl_args=(-p) @@ -100,8 +57,26 @@ log() { printf "\n\033[1;36m>>> %s\033[0m\n" "$*"; } warn() { printf "\033[1;33m!!! %s\033[0m\n" "$*"; } err() { printf "\033[1;31mERR %s\033[0m\n" "$*"; } -# current_log is set per-spec in the main loop -current_log="" +# ── Spec list (auto-scanned, sorted by filename) ───────────────────────── +SPECS=() +while IFS= read -r f; do SPECS+=("$f"); done < <( + # Core specs: spec/NNN-*.md (exclude README and non-spec files) + find spec -maxdepth 1 -name '[0-9]*-*.md' -type f | sort + # UI sprint specs: spec/ui/sprint-*.md + find spec/ui -maxdepth 1 -name 'sprint-*.md' -type f | sort -t- -k2,2 -V +) + +if [[ -z "${SPECS[*]}" ]]; then + err "No spec files found in spec/ or spec/ui/" + exit 1 +fi + +# ── --last flag: keep only the last spec ────────────────────────────────── +if [[ -n "$LAST_ONLY" ]]; then + SPECS=("${SPECS[${#SPECS[@]}-1]}") + log "Running last spec only: ${SPECS[0]}" +fi + run_cl() { # $1 = prompt, rest = extra args. Writes JSON output to $CL_OUTPUT. @@ -115,7 +90,6 @@ run_cl() { # propagates SIGINT directly to both the script and the child. "$CL" "${cl_args[@]}" "$@" --output-format json "$prompt" \ > "$CL_OUTPUT" \ - 2> >(tee -a "$current_log" >&2) \ || true # don't let set -e kill us on non-zero exit } @@ -141,14 +115,7 @@ for spec in "${SPECS[@]}"; do spec_title=$(head -1 "$spec" | sed 's/^#* *//') - # Set up per-spec log file - spec_slug=$(echo "$spec" | sed 's|/|_|g; s|\.md$||') - mkdir -p "$LOG_DIR" - current_log="$LOG_DIR/${spec_slug}.log" - : > "$current_log" # truncate - log "Starting: $spec — $spec_title" - log "Log file: $current_log (tail -f $current_log)" # ── Phase 1: Implementation ───────────────────────────────────────────── impl_prompt="$(cat < Date: Thu, 2 Apr 2026 17:59:35 +0200 Subject: [PATCH 02/15] docs: split sprint-42 into two sprints and add all-ecalli fixture Split the oversized sprint-42 spec into: - Sprint 42: Host Call UX Redesign, Handler Improvements, GP 0.7.2 - Sprint 43: Fetch Handler, JAM Codec Add all-ecalli.jam fixture and example entries for both sprints. Co-Authored-By: Claude Opus 4.6 (1M context) --- fixtures/all-ecalli.jam | Bin 0 -> 93163 bytes fixtures/examples.json | 14 ++ ...print-42-fetch-handler-and-host-call-ux.md | 6 + ...rint-42-host-call-ux-redesign-and-gp072.md | 196 ++++++++++++++++++ .../sprint-43-fetch-handler-and-jam-codec.md | 183 ++++++++++++++++ 5 files changed, 399 insertions(+) create mode 100644 fixtures/all-ecalli.jam create mode 100644 spec/ui/sprint-42-fetch-handler-and-host-call-ux.md create mode 100644 spec/ui/sprint-42-host-call-ux-redesign-and-gp072.md create mode 100644 spec/ui/sprint-43-fetch-handler-and-jam-codec.md diff --git a/fixtures/all-ecalli.jam b/fixtures/all-ecalli.jam new file mode 100644 index 0000000000000000000000000000000000000000..0826db7edc26ddee3d6fbd37d7e9a318f87b7235 GIT binary patch literal 93163 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 literal 0 HcmV?d00001 diff --git a/fixtures/examples.json b/fixtures/examples.json index fdb973e..868d990 100644 --- a/fixtures/examples.json +++ b/fixtures/examples.json @@ -130,6 +130,20 @@ "file": "as-factorial.jam", "format": "jam_spi_with_metadata", "entrypoint": { "type": "accumulate", "params": { "slot": "42", "id": "0", "results": "0" } } + }, + { + "id": "all-ecalli-refine", + "name": "All Ecalli (Refine)", + "file": "all-ecalli.jam", + "format": "jam_spi_with_metadata", + "entrypoint": { "type": "refine", "params": { "core": "0", "index": "0", "id": "0" } } + }, + { + "id": "all-ecalli-accumulate", + "name": "All Ecalli (Accumulate)", + "file": "all-ecalli.jam", + "format": "jam_spi_with_metadata", + "entrypoint": { "type": "accumulate", "params": { "slot": "42", "id": "0", "results": "0" } } } ] }, diff --git a/spec/ui/sprint-42-fetch-handler-and-host-call-ux.md b/spec/ui/sprint-42-fetch-handler-and-host-call-ux.md new file mode 100644 index 0000000..eb60a29 --- /dev/null +++ b/spec/ui/sprint-42-fetch-handler-and-host-call-ux.md @@ -0,0 +1,6 @@ +# Sprint 42 — SUPERSEDED + +This sprint was split into two independent sprints: + +- **[Sprint 42](sprint-42-host-call-ux-redesign-and-gp072.md)** — Host Call UX Redesign, Handler Improvements, and GP 0.7.2 Alignment +- **[Sprint 43](sprint-43-fetch-handler-and-jam-codec.md)** — Fetch Handler, JAM Codec, and All-Ecalli diff --git a/spec/ui/sprint-42-host-call-ux-redesign-and-gp072.md b/spec/ui/sprint-42-host-call-ux-redesign-and-gp072.md new file mode 100644 index 0000000..9ed5156 --- /dev/null +++ b/spec/ui/sprint-42-host-call-ux-redesign-and-gp072.md @@ -0,0 +1,196 @@ +# Sprint 42 — Host Call UX Redesign, Handler Improvements, and GP 0.7.2 Alignment + +Status: Implemented + +## Goal + +Redesign the host call tab to a two-column layout with auto-apply (removing the manual Apply button). Improve existing handlers: storage read with correct offset/maxLen slicing, generic handler with comments and line numbers, gas handler simplification. Update host call index mapping to GP 0.7.2. Fix refine entrypoint encoding. Add pending changes coalescing. Highlight the active host call in the reference trace. Add the all-ecalli example program. + +## Prior Sprint Dependencies + +- Sprint 41: host-call editing and pending changes +- Sprint 40: drawer polish and trace fixes +- Sprint 19: host-call drawer tab +- Sprint 20: host-call storage table + +## What Works After This Sprint + +### Host Call Tab UX Redesign (`HostCallTab.tsx`) + +1. **Two-column layout.** Left sidebar (fixed 192px) shows host call badge, input registers with labels (only relevant ones per handler type), output register preview, and **memory write count** (e.g., `"+ 3 memory write(s)"`). Right content area (flex-1, scrollable) shows the handler-specific editor. Sidebar and StickyBar are extracted into named sub-components with typed props for testability. + +2. **Sticky bottom bar** with NONE toggle, "Use Trace Data" button (styled as a visually distinct amber pill: `bg-amber-500/20 text-amber-300 hover:bg-amber-500/30`), **"Changes auto-applied" passive confirmation text** (visible when no error), and error display. The old Apply button is removed. + +3. **Auto-apply.** Changes are applied to the pending changeset immediately via `onEffectsReady` callback. Each handler reports effects reactively — no manual Apply step. + +4. **"Use Trace Data" button.** Resets the handler to trace state when the user has made local modifications. Uses a `traceVersion` counter to trigger reset in child components. + +5. **Register metadata** (`host-call-registers.ts`). Per-handler declaration of which registers are inputs (with labels like "dest", "offset", "kind") and what the output register means. Each register has a **`format` field** (`"hex" | "decimal" | "custom"`) controlling display: pointers and hashes in hex, counts and sizes in decimal. A reusable `formatRegValue(value, format)` utility is exported. Read/info handlers must include offset/maxLen registers. + +6. **Output preview** in sidebar shows the computed output register value (e.g., "ω₇ ← 134 (0x86)") updating live as the user edits. + +7. **NONE toggle** in the sticky bar for handlers that support it (lookup, read, info). When checked, returns ω₇ = 2^64-1 with no memory writes. Cleans up previously-applied memory writes from the pending changeset. + +### Handler Simplification + +8. **Content-only handlers.** All handlers (Gas, Generic, Storage, Log) no longer include headers, register grids, or Apply buttons. They communicate effects via `onEffectsReady` callback. + +9. **GasHostCall** auto-reports ω₇ = currentGas on mount. Minimal display. + +10. **GenericHostCall** parses trace commands live and reports effects on every keystroke. **Command format** uses human-readable arrow syntax: `setreg r07 <- 0x2a`, `memwrite 0x00001000 len=4 <- 0xaabbccdd`, `setgas <- 500000`. **Comment lines** starting with `#` are ignored. **Error messages include line numbers** (e.g., `"Line 3: Malformed setreg: ..."`). **Register writes are sorted by index** before serializing to produce consistent output. + +11. **`setgas` fully wired in GenericHostCall.** `HostCallEffects` now includes an optional `gasAfter` field. The generic handler's `parseAllCommands` collects `setgas` commands and includes them in effects. `HostCallTab.applyEffects` applies gas via `pendingChanges.setGas()` and `orchestrator.setGas()` for all PVMs. + +### Storage Read/Write Improvements + +12. **Storage read produces effects with correct offset/maxLen slicing.** When paused on a read host call (index 3), the handler looks up the scoped key in the storage table. If found, reads `offset` from register 11 and `maxLen` from register 12, slices the value as `value.slice(min(offset, len), min(offset + maxLen, len))`, sets ω₇ = total value length, and writes the sliced data to memory. If not found, returns NONE. Must NOT ignore offset and always slice from byte 0. + +13. **Service ID scoping.** Storage keys are scoped as `"serviceId:keyHex"` (e.g., `"self:0x74657374"`). Service ID `0xffffffffffffffff` maps to `"self"`. Storage lookup uses **O(1) Map-based `store.get(fullKey)`**, not linear scan via `.find()`. + +14. **Trace data seeds the storage table.** On mount, trace proposal data is inserted into the storage table so it becomes the single editable source of truth. Must **check for existing entry before seeding** to avoid overwriting user edits. + +15. **Key display with ASCII.** Shows the decoded key hex and, if all bytes are printable ASCII, the decoded string (e.g., `0x74657374 "test"`). + +16. **Live status indicator.** Shows "Key found in storage table" (green) or "Key not found — will return NONE" (amber), updating reactively when the table is edited. + +17. **`safeFromHex` defensive hex parsing.** A helper that tolerates odd-length hex strings by padding (prepending `"0"`) before decoding, preventing crashes from user typos. Uses try/catch fallback to empty `Uint8Array(0)`. + +18. **Responsive grid layout** for storage host call: `grid grid-cols-1 md:grid-cols-2`, placing key info and storage table side-by-side on wider screens instead of stacking vertically. + +### Pending Changes Improvements + +19. **Consecutive memory writes coalesced in display.** `PendingChanges.tsx` merges adjacent/overlapping memory writes into contiguous ranges for display (e.g., 32 individual byte writes shown as one `[0x100] ← ... (32B)`). + +20. **Scrollable pending changes.** The pending changes area scrolls when too tall, with a sticky header. Registers panel maintains minimum height for 3 rows (Tailwind `min-h-[4.5rem]`, not inline style). + +21. **`removeMemoryWrite` method.** Added to `usePendingChanges` so the host call tab can clean up stale writes when effects change (e.g., NONE toggled on). Only removes host-call-originated writes; user edits from the Memory pane are preserved. + +### GP 0.7.2 Alignment + +22. **Host call index mapping updated.** `HOST_CALL_NAMES` in `packages/trace/src/host-call-names.ts` now matches GP 0.7.2: General (0-5), Refine (6-13: historical_lookup, export, machine, peek, poke, pages, invoke, expunge), Accumulate (14-26: bless, assign, designate, checkpoint, new_service, upgrade, transfer, eject, query, solicit, forget, yield_result, provide), JIP (100: log). + +23. **Refine entrypoint encoding fixed.** `workPackageHash` is now encoded as fixed 32 bytes (no length prefix), replacing the old `package` field that was incorrectly length-prefixed. + +### Reference Trace Active Entry Highlight + +24. **Active host call highlighted in reference trace.** When paused on a host call, the corresponding reference trace entry gets a blue highlight (`bg-blue-500/20 ring-1 ring-inset ring-blue-500/40`). The `activeEntryIndex` is computed inside `EcalliTraceTab` from `activeHostCall` and `recorded.entries.length` — not in `BottomDrawer`. + +### Shared Utilities + +25. **`HostCallEffects` interface** defined in `apps/web/src/lib/fetch-utils.ts` (canonical location) and re-exported from `HostCallTab.tsx`. Has three fields: `registerWrites: Map`, `memoryWrites: Array<{address, data}>`, `gasAfter?: bigint`. + +26. **`NONE` constant** (`(1n << 64n) - 1n`) defined once in `fetch-utils.ts` and imported everywhere. + +27. **`useStableCallback` hook** (`apps/web/src/hooks/useStableCallback.ts`). Extracts the repeated `useRef(fn); ref.current = fn` pattern into a reusable hook that returns a stable function identity. Used in GasHostCall, GenericHostCall, and StorageHostCall. + +### SPI Entrypoint Config + +28. **Defensive 32-byte hash handling** (`SpiEntrypointConfig.tsx`). The `workPackageHash` field always initializes a 32-byte buffer and safely limits input via `.subarray(0, 32)` with try/catch. All-zero hashes are suppressed in display (show empty string). Must NOT produce 0-byte hashes or show 66-character zero strings. + +### All-Ecalli Example Program + +29. **New fixture** `fixtures/all-ecalli.jam` (93KB). Compiled from `as-lan/examples/all-ecalli/`. Invokes every host call in both refine (gas, 14 fetch variants, lookup, read, write, info, log, historical_lookup, export, machine, peek, poke, pages, invoke, expunge) and accumulate (gas, 4 fetch variants, lookup, read, write, info, log, bless, assign, designate, checkpoint, new_service, upgrade, transfer, eject, query, solicit, forget, yield_result, provide) contexts. + +30. **Two example entries** in the AssemblyScript → PVM category: "All Ecalli (Refine)" and "All Ecalli (Accumulate)". + +### E2E Tests + +31. **E2E test** (`sprint-42-host-call-ux.spec.ts`). Playwright tests using `io-trace` (storage read/write, generic host calls) and `all-ecalli` (GP 0.7.2 name coverage). Must: + - Load the io-trace example and step to a host call. + - Verify the **two-column layout** renders: sidebar (`data-testid="host-call-sidebar"`) with register labels and output preview, content area with handler editor. + - Verify **"Changes auto-applied"** text appears in the sticky bottom bar when no error. + - Verify **memory write count** appears in the sidebar (e.g., `"+ N memory write(s)"`). + - Step to a **storage read** host call and verify the storage handler shows key info and status indicator ("Key found" / "Key not found"). + - Verify **NONE toggle** for a read/lookup host call: checking the box changes the output preview to show the NONE sentinel value (2^64-1). + - Verify **GP 0.7.2 host call names** appear correctly in the trace column badges (e.g., "gas", "fetch", "write", "log" — not numeric indices). + - Verify the **active trace entry highlight** is visible (blue ring) when paused on a host call. + - Verify **pending changes coalescing**: after stepping through host calls that produce memory writes, the pending changes panel shows coalesced ranges (not individual byte writes). + - Load **all-ecalli-refine** and verify both example cards are visible on the load page. + - Step through the all-ecalli-refine example and verify GP 0.7.2 host call names appear in trace badges (e.g., "gas", "fetch", "write", "log"). + +## Key Implementation Details + +**`NONE_SUPPORTED` set** in `HostCallTab.tsx` controls which host call indices show the NONE checkbox: `new Set([2, 3, 5])` (lookup, read, info). Fetch (index 1) NONE is handled in Sprint 43. + +**Auto-apply state machine** in `HostCallTab`: `initialEffectsApplied` ref tracks whether the first effects report (from trace/defaults) has been applied. `userModified` state becomes true after the first auto-apply. `traceVersion` counter increments on "Use Trace Data" click, resetting `userModified` and `initialEffectsApplied`. + +**`appliedMemAddrsRef`** in `HostCallTab` tracks which memory write addresses the tab has applied. On each `applyEffects`, stale addresses not in the new effects are removed via `pendingChanges.removeMemoryWrite()`. + +**`useKeyFromMemory` hook** in `StorageHostCall` reads key bytes from PVM memory via `orchestrator.getMemory()` with async cancellation. Falls back to null if orchestrator unavailable. + +**`scopedKey` function** in `StorageHostCall` builds storage table keys as `"serviceId:keyHex"`. Service ID `0xffffffffffffffff` (u64 max = "self") maps to `"self"`. + +**Trace seeding** in `StorageHostCall` uses a `seededRef` guard to insert trace data into the storage table exactly once. Must check `if (!storageTable.store.get(fullKey))` before seeding. + +**Two-column layout** uses `-mx-3 -my-2` negative margins to reclaim the drawer content area's `px-3 py-2` padding, allowing the sidebar and sticky bar to extend edge-to-edge. + +**`RegistersPanel.test.tsx`** mock updated to include `removeMemoryWrite: noop` in `makePendingChanges`. + +## Verification / Acceptance Criteria + +### Unit Tests +- `coalesceMemoryWrites` merges adjacent/overlapping writes correctly (7 tests) +- `removeMemoryWrite` removes by address, no-op for non-existent/null (3 tests) +- `HOST_CALL_NAMES` matches GP 0.7.2 (index 14 = "bless", index 100 = "log", etc.) +- Refine entrypoint encoding produces fixed 32-byte `workPackageHash` (no length prefix) +- All existing tests continue to pass + +### E2E Tests +- Two-column layout renders with sidebar and content area +- "Changes auto-applied" visible in sticky bar +- Memory write count visible in sidebar +- Storage read handler shows key info and status indicator +- NONE toggle changes output to sentinel value +- GP 0.7.2 host call names in trace badges +- Active trace entry has blue highlight ring +- Pending changes shows coalesced memory write ranges + +### Build +```bash +npm install && npm run build && npm test +cd apps/web && npx playwright test sprint-42 +cd apps/web && npx vite build +``` + +## Files Created + +- `apps/web/src/lib/fetch-utils.ts` — HostCallEffects interface, NONE constant +- `apps/web/src/hooks/useStableCallback.ts` +- `apps/web/src/components/drawer/hostcalls/host-call-registers.ts` +- `apps/web/e2e/sprint-42-host-call-ux.spec.ts` +- `fixtures/all-ecalli.jam` + +## Files Modified + +- `packages/trace/src/host-call-names.ts` — GP 0.7.2 index mapping +- `packages/content/src/spi-entrypoint.ts` — refine encoding fix (workPackageHash) +- `packages/content/src/examples-manifest.ts` — workPackageHash default +- `apps/web/src/components/drawer/HostCallTab.tsx` — two-column layout + auto-apply +- `apps/web/src/components/drawer/hostcalls/GasHostCall.tsx` — content-only + auto-apply +- `apps/web/src/components/drawer/hostcalls/GenericHostCall.tsx` — content-only + live effects + comments + line numbers +- `apps/web/src/components/drawer/hostcalls/StorageHostCall.tsx` — storage table effects + offset/maxLen + responsive grid +- `apps/web/src/components/drawer/EcalliTraceTab.tsx` — active entry highlight (self-contained computation) +- `apps/web/src/components/drawer/TraceColumn.tsx` — active entry prop +- `apps/web/src/components/drawer/TraceEntryRow.tsx` — active entry styling +- `apps/web/src/components/debugger/BottomDrawer.tsx` — thread pendingChanges + activeHostCall +- `apps/web/src/components/debugger/RegistersPanel.tsx` — min-height (Tailwind class) +- `apps/web/src/components/debugger/PendingChanges.tsx` — coalesced display + scrollable +- `apps/web/src/hooks/usePendingChanges.ts` — removeMemoryWrite +- `apps/web/src/pages/DebuggerPage.tsx` — thread pendingChanges to drawer +- `apps/web/src/components/load/SpiEntrypointConfig.tsx` — workPackageHash field + defensive 32-byte hash +- `apps/web/src/hooks/usePersistence.ts` — workPackageHash serialization +- `fixtures/examples.json` — all-ecalli examples in AssemblyScript category + +## Edge Cases and Pitfalls + +1. **Storage read offset/maxLen registers.** Read host call uses r11 = offset and r12 = maxLen. Ignoring the offset and always slicing from byte 0 produces incorrect effects for reads that specify a non-zero offset. + +2. **`package` → `workPackageHash` rename is pervasive.** Touches 6+ files across 3 packages. String-keyed field references like `fields["package"]` must also be updated. + +3. **HOST_CALL_NAMES test assertions must match GP 0.7.2.** Index 14 was previously unmapped but is now `"bless"`. + +4. **RegistersPanel.test.tsx mock must include `removeMemoryWrite`.** Adding a new method to `UsePendingChanges` interface requires updating all test mocks. + +5. **`activeEntryIndex` for trace highlight.** The active host call's sequential index = `recorded.entries.length` (the current host call hasn't been recorded yet). Compute this inside `EcalliTraceTab`, not `BottomDrawer`. + +6. **GPG signing may fail in CI.** Use `-c commit.gpgsign=false` as a fallback if signing is unavailable. diff --git a/spec/ui/sprint-43-fetch-handler-and-jam-codec.md b/spec/ui/sprint-43-fetch-handler-and-jam-codec.md new file mode 100644 index 0000000..5810667 --- /dev/null +++ b/spec/ui/sprint-43-fetch-handler-and-jam-codec.md @@ -0,0 +1,183 @@ +# Sprint 43 — Fetch Handler, JAM Codec, and All-Ecalli + +Status: Implemented + +## Goal + +Add a dedicated fetch host call handler with structured editing for all 16 GP 0.7.2 fetch variants. Build the JAM codec library for variable-length encoding. Add E2E tests using the all-ecalli example (added in Sprint 42). + +## Prior Sprint Dependencies + +- Sprint 42: host call UX redesign, auto-apply, register metadata, HostCallEffects interface, all-ecalli fixture +- Sprint 41: host-call editing and pending changes +- Sprint 20: host-call storage table + +## What Works After This Sprint + +### JAM Codec (`packages/types/src/jam-codec.ts`) + +1. **VarU64 encode/decode.** Progressive prefix encoding matching GP Appendix A. Values 0-127 use 1 byte, up to 9 bytes for full u64. Implementation must use a generic loop algorithm (count leading 1-bits, mask lead byte, read trailing bytes LE), not per-width hardcoded branches — the loop form is shorter, more maintainable, and less prone to off-by-one errors. + +2. **LE integer primitives.** `encodeU8/U16LE/U32LE/U64LE` and decode counterparts. All little-endian. Must use `DataView` with `bytes.byteOffset + offset` for decoding to correctly handle subarray views (a plain `bytes[offset]` approach is buggy when the Uint8Array is a slice of a larger ArrayBuffer). + +3. **Variable-length containers.** `encodeBytesVarLen` (VarU64 length prefix + data), `encodeSequenceVarLen` (VarU64 count + items). `encodeSequenceVarLen` should accept a callback `(item: T) => Uint8Array` rather than a pre-encoded array to avoid intermediate allocation and prevent count/length mismatches. With corresponding decoders. + +4. **33 unit tests** covering boundary values (0, 127, 128, 2^14, 2^21, 2^28, 2^32, 2^40, 2^48, 2^56, 2^63), roundtrips, and edge cases. + +### Fetch Struct Codecs (`apps/web/src/lib/fetch-codec.ts`) + +5. **All 16 fetch variant structs** with TypeScript interfaces, encode, and decode (best-effort, returns null on failure): + - ProtocolConstants (134 bytes fixed, 33 fields) + - WorkItemInfo (62 bytes fixed, 8 fields) + - RefinementContext (variable: 4 hashes + timeslot + prerequisites list) + - WorkPackage (variable: fully decoded — authToken, authServiceId, authCodeHash, authConfig, nested RefinementContext + WorkItem list). Must NOT punt context/workItems to opaque hex blobs. + - WorkItem: serviceId (u32), codeHash (32B), payload (var-len), **two separate gas fields** `gasRefine` and `gasAccumulate` (both u64, matching GP spec), exportCount (var-u64), imports (ImportRef[]), extrinsics (ExtrinsicRef[]) + - ImportRef: uses `isWorkPackageHash` boolean discriminant (1-byte tag: 1=WP hash, 0=normal) + 32-byte hash + var-u64 index. Must NOT use a fixed 36-byte encoding that ignores the discriminant. + - ExtrinsicRef: hash (32B) + VarU64 length (not fixed U32) + - AuthorizerInfo (hash + config blob) + - TransferOrOperand (union: Operand tag=0 or Transfer tag=1). Operand must include `resultBlob` (var-len, present when `resultKind === 0`) and `authOutput` (var-len). + - Sequence wrappers for AllWorkItems and AllTransfersAndOperands + +6. **FetchKind enum and FETCH_KIND_INFO** map with human-readable names **and descriptions** for all 16 variants. The description text is displayed in the fetch handler UI. + +7. **Defaults** (`fetch-defaults.ts`) using small testnet values from pvm-debugger (2 cores, epoch=12, etc). Must provide defaults for **all editor types**: `DEFAULT_PROTOCOL_CONSTANTS`, `DEFAULT_WORK_ITEM_INFO`, `DEFAULT_REFINEMENT_CONTEXT`, `DEFAULT_AUTHORIZER_INFO`, `DEFAULT_OPERAND`, `DEFAULT_TRANSFER`, `DEFAULT_WORK_ITEM`, `DEFAULT_WORK_PACKAGE`. + +8. **`computeFetchEffects` utility** (`fetch-utils.ts`). A pure function `computeFetchEffects(fullBlob, isNone, destAddr, offset, maxLen) → HostCallEffects` that implements the full fetch host call effect computation: slicing by offset/maxLen, computing ω₇ = totalLength (or NONE), and producing the memory write at destAddr. All fetch effect computation flows through this single function. + +9. **`tryDecode` DRY helper.** A reusable decode wrapper that catches exceptions and returns `null | { value, bytesRead }`, avoiding repetitive try/catch in every decode function. + +10. **Strict validation for fixed-size fields.** `encodeBytes32` must fail fast on wrong-length input. Transfer memo encoding must truncate to 128 bytes (not silently replace wrong-length with zeros, which masks bugs). + +11. **14+ unit tests** covering size assertions, roundtrips, mixed-type sequences, and **WorkPackage encode/decode roundtrip** (verifying nested fields like `workItems.length`, `authServiceId`). + +### Fetch Host Call Handler (`FetchHostCall.tsx` + `fetch/` sub-components) + +12. **Three editing modes:** + - **Trace**: Read-only display of trace memory write data. Available when resume proposal exists. + - **Raw**: Hex textarea for the full response blob. + - **Struct**: Per-variant structured field editors. + +13. **Mode switching preserves data in all directions.** Struct→Raw copies the encoded hex. Raw→Struct decodes hex into struct fields (via `structInitialBlob`). Trace→Raw copies trace data. **Trace→Struct decodes trace data into struct fields.** All four transitions must carry data — never discard user work. + +14. **Per-variant struct editors:** + - ProtocolConstantsEditor: 33 fields in collapsible grouped sections (Balances, Gas, Capacity, Timing, Sizes), multi-column grid layout (2-3 columns). + - WorkItemInfoEditor: 8 labeled fields. + - RefinementContextEditor: 4 hash fields + timeslot + add/remove prerequisites list. + - AuthorizerInfoEditor: hash + config hex blob (config uses `