Skip to content

Conversation

@tomusdrw
Copy link
Member

@tomusdrw tomusdrw commented Feb 5, 2026

  • load ecalli trace file
  • fixes
  • more fixes

@netlify
Copy link

netlify bot commented Feb 5, 2026

Deploy Preview for pvm-debugger ready!

Name Link
🔨 Latest commit 49bb143
🔍 Latest deploy log https://app.netlify.com/projects/pvm-debugger/deploys/6989adeedb99230008e491b4
😎 Deploy Preview https://deploy-preview-476--pvm-debugger.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive host call tracing system implementing JIP-6 trace format specification. The changes add trace parsing and validation libraries, integrate trace configuration UI in debugger settings, enhance host call dialogs with trace-aware editing and restart capabilities, implement trace file loading in the program loader, refactor debugger state management to support trace data, add error boundary handling, remove deprecated serviceId propagation from workers, and include end-to-end test coverage for trace loading workflows.

Changes

Cohort / File(s) Summary
Trace Library & Data Model
src/lib/hostCallTrace.ts, src/lib/hostCallTrace.test.ts
New hostCallTrace module provides end-to-end JIP-6 trace parsing, serialization, validation, lookup, and state mismatch detection; comprehensive test coverage validates parsing logic and trace summarization.
Trace Configuration UI
src/components/DebuggerSettings/TraceConfigDialog.tsx, src/components/DebuggerSettings/Content.tsx
Introduces TraceConfigDialog component for editing Ecalli traces with CodeMirror editor, file upload, validation, auto-continue toggle, and helper actions; integrates trace UI into debugger settings with status bar and configure button.
Host Call Dialog Core
src/components/HostCallDialog/index.tsx, src/components/HostCallDialog/DefaultHostCallContent.tsx
Enhanced host call dialog to display and toggle trace entries, compute pending state (registers, gas, memory) from traces, detect state mismatches, propagate restart callback, and force generic UI when trace data is present.
Host Call Editors & Actions
src/components/HostCallDialog/MemoryEditor.tsx, src/components/HostCallDialog/handlers/HostCallActionButtons.tsx, src/components/HostCallDialog/handlers/types.ts
MemoryEditor augmented with pending writes, initial address/length, and modification tracking; HostCallActionButtons restructured to include optional restart link and right-aligned action group; HostCallHandlerProps extended with onRestart callback.
Host Call Handlers
src/components/HostCallDialog/handlers/{FetchHostCall,GasHostCall,InfoHostCall,LogHostCall,ProvideHostCall,ReadHostCall,TransferHostCall,WriteHostCall,YieldHostCall}.tsx
All handler components extended with onRestart prop and wired to HostCallActionButtons; enables restart functionality across all host call types.
Program Loading & Trace Integration
src/components/ProgramLoader/Loader.tsx, src/components/ProgramLoader/loadingUtils.ts, src/components/ProgramLoader/ProgramFileUpload.tsx, src/components/ProgramLoader/loadingUtils.test.ts, src/components/ProgramLoader/Links.tsx
Adds trace file detection and parsing to loader; extends loadFileFromUint8Array to handle trace format with buildSpiArgsFromMemoryWrites helper; updates file upload to accept .trace/.log files; includes trace summary UI and JIP-6 specification link; test validates loading and parsing flow.
Debugger State Management
src/store/debugger/debuggerSlice.ts, src/store/workers/workersSlice.ts
Replaces hasHostCallOpen/pendingHostCallIndex flags with structured HostCallTraceState; adds pendingHostCall, nextHostCallIndex, autoContinueOnHostCalls state; implements setHostCallsTrace, setPendingHostCall, setAutoContinueOnHostCalls, resetHostCallIndex reducers; reworks handleHostCall logic to use findHostCallEntry, apply trace-derived edits, and handle auto-continue.
Error Handling & Infrastructure
src/components/ErrorBoundary.tsx, src/main.tsx, src/utils/loggerService.tsx, src/store/utils.ts
Adds ErrorBoundary class component for capturing render errors with fallback UI and reload button; wraps app in ErrorBoundary; enhances logger with group/groupEnd methods and minimal init message; adds message event listener cleanup in timeout handler.
Web Worker Cleanup
src/packages/web-worker/types.ts, src/packages/web-worker/worker.ts, src/packages/web-worker/command-handlers/step.ts
Removes SET_SERVICE_ID command from Commands enum and request/response unions; eliminates serviceId state and propagation from worker; simplifies runStep signature to remove serviceId parameter.
Debugger Actions & UI
src/hooks/useDebuggerActions.ts, src/components/DebuggerControlls/index.tsx, src/types/type-guards.ts
Refactors restartProgram to use recreateWorker helper and pvmOptions selection; simplifies handlePvmTypeChange to delegate to restart flow; changes Restart button label from "Reset"; removes unused isInstructionError and isOneImmediateArgs type guards.
Configuration & E2E Tests
package.json, tests/trace-loading.spec.ts
Adds trustedDependencies array with @swc/core to package.json; introduces Playwright tests for trace loading workflow including file input, load verification, run execution, and error boundary detection.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Loader as ProgramLoader
    participant Parser as hostCallTrace
    participant Redux as Redux Store
    participant Dialog as HostCallDialog
    participant Worker as Web Worker

    User->>Loader: Upload trace file (.trace/.log)
    Loader->>Parser: parseTrace(content)
    Parser->>Parser: Parse program, start, ecalli, termination
    Parser-->>Loader: ParsedTrace with entries
    Loader->>Redux: dispatch(setHostCallsTrace(content))
    Redux-->>Loader: State updated
    Loader->>User: Display trace summary (host call count)
    
    User->>User: Click Run/Step
    Loader->>Worker: Load program + step
    Worker->>Worker: Execute, hit host call
    Worker-->>Redux: Debugger state update
    
    Redux->>Dialog: selectHostCallsTrace available
    Dialog->>Dialog: findHostCallEntry from trace
    Dialog->>Dialog: Detect state mismatches
    Dialog->>User: Display trace entry + validate state
    
    User->>Dialog: Apply changes or Auto-continue
    Dialog->>Dialog: Apply register/memory edits from trace
    Dialog->>Worker: Resume with applied state
    Worker->>Worker: Continue execution from trace state
    Worker-->>Redux: Updated state
    Redux->>Dialog: Update display
Loading
sequenceDiagram
    actor User
    participant Dialog as DebuggerSettings
    participant Config as TraceConfigDialog
    participant Editor as CodeMirror
    participant Validator as hostCallTrace
    participant Redux as Redux Store

    User->>Dialog: Open Debugger Settings
    Dialog->>Dialog: Display trace status (if loaded)
    
    User->>Dialog: Click Configure button
    Dialog->>Config: open=true
    Config->>Redux: selectHostCallsTrace, selectAutoContinueOnHostCalls
    Redux-->>Config: Current trace state
    Config->>Editor: Load trace content + validation
    
    User->>Config: Edit trace / Upload file
    Config->>Validator: validateTrace(content)
    Validator->>Validator: Parse lines, check format
    Validator-->>Config: errors[] or success
    
    alt Validation Success
        Config->>Config: Display summary (host calls, termination)
    else Validation Failed
        Config->>Config: Show error messages
    end
    
    User->>Config: Toggle Auto-continue
    Config->>Redux: dispatch(setAutoContinueOnHostCalls(bool))
    
    User->>Config: Click Apply
    Config->>Redux: dispatch(setHostCallsTrace(content))
    Redux-->>Config: State persisted
    Config->>Dialog: onOpenChange(false)
    Dialog->>Dialog: Update trace status display
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • host calls wip #455 — Host calls WIP is a direct architectural precursor; the main PR adds trace support and restart integration on top of the enhanced host-call framework introduced in #455.
  • JAM entrypoints #463 — Modifies ProgramLoader/Loader and DebuggerSettings for SPI/entrypoint behavior; intersects with the main PR's removal of setAllWorkersServiceId from the SPI load flow.
  • Fix loading SPI #451 — Refactors program-loading pipeline in loadingUtils.ts and Loader.tsx; related through shared parsing and payload changes in the loader.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ecalli traces support' is clearly related to the primary change: extensive implementation of JIP-6 host call trace (Ecalli) format support across the debugger, including parsing, validation, UI integration, and file loading capabilities.
Description check ✅ Passed The description mentions 'load ecalli trace file' and 'fixes', which relate to the changeset's core functionality of loading and processing trace files, though the description is quite minimal and vague.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch td-ecall

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🤖 Fix all issues with AI agents
In `@src/components/HostCallDialog/handlers/HostCallActionButtons.tsx`:
- Around line 15-20: The restart button in HostCallActionButtons lacks an
explicit type, which can cause unintended form submission when this component is
used inside a form; update the <button> element that calls onRestart (the
"restart" control) to include type="button" so it does not act as a submit
button, keeping the existing className, onClick handler, and disabled prop
unchanged.

In `@src/components/HostCallDialog/index.tsx`:
- Around line 106-108: currentState may be undefined so accessing
currentState.regs and currentState.gas can throw; update the expressions that
compute finalRegs and currentGas/finalGas (the lines assigning finalRegs,
currentGas, finalGas in HostCallDialog/index.tsx) to use optional chaining or
nullish coalescing when reading currentState (e.g., currentState?.regs and
currentState?.gas) so you fall back to DEFAULT_REGS and DEFAULT_GAS safely, and
keep the pendingRegs/pendingGas precedence logic intact.

In `@src/components/ProgramLoader/Links.tsx`:
- Around line 68-81: The new external links in ProgramLoader's Links.tsx (the
<a> elements that use target="_blank" around the ExternalLink icon and the inner
JIP-6 text) are missing rel attributes; update both anchor tags to include
rel="noopener noreferrer" to prevent reverse-tabnabbing and ensure safe external
navigation. Locate the anchors in the Links.tsx component (the <a
href="https://...JIP-6.md" target="_blank"> wrapping ExternalLink and the inner
<a className="text-brand-dark..." href="https://...JIP-6.md">) and add
rel="noopener noreferrer" to each.

In `@src/components/ProgramLoader/Loader.tsx`:
- Around line 64-74: The handleFileUpload callback currently calls parseTrace
and getTraceSummary without protection; wrap the parseTrace/getTraceSummary
calls in a try-catch inside handleFileUpload so malformed trace data doesn't
crash the component — on success keep the existing setTraceContent(trace) and
setTraceSummary(getTraceSummary(...)), and on error call setTraceContent(null)
and setTraceSummary(null) (and optionally log the error or surface a
user-friendly message) so the component remains stable; reference
handleFileUpload, parseTrace, getTraceSummary, setTraceContent, setTraceSummary,
and setProgramLoad when applying the change.

In `@src/components/ProgramLoader/loadingUtils.test.ts`:
- Line 5: Rename the test file from loadingUtils.test.ts to
loading-utils.test.ts and update all references/imports to that file throughout
the repo (e.g., any import statements, test index files, or CI/test-glob
patterns that explicitly reference the old name); ensure the test suite name
("describe('loadingUtils', ...)") can remain unchanged or be adjusted for
clarity but is not required to match the filename.

In `@src/components/ProgramLoader/loadingUtils.ts`:
- Around line 48-66: The buildSpiArgsFromMemoryWrites function can allocate a
huge Uint8Array for sparse far-apart addresses; add a hard cap (e.g. const
MAX_SPI_ARG_SIZE = 10 * 1024 * 1024) and check totalSize after computing
lastAddr - firstAddr, and if totalSize > MAX_SPI_ARG_SIZE throw a clear
RangeError/ValidationError (include totalSize and MAX in the message) to fail
fast instead of allocating; apply the same guard to the other similar routine
handling memoryWrites (the other block around the 83-90 logic) and use the same
MAX constant and error handling so both places consistently refuse oversized
spans.
- Around line 68-73: The function tryParseTraceFile uses parseTrace but doesn't
check for parse errors before reading prelude fields; update tryParseTraceFile
to detect parse failures from parseTrace (e.g., an errors or success flag on the
returned trace) and short‑circuit (return null or a LoadFileResult error) when
parseTrace indicates errors so you never access trace.prelude.program with an
invalid parse; locate tryParseTraceFile and parseTrace usages and add a guard
that rejects traces with parse errors before any further processing of
ExpectedState or building the LoadFileResult.
- Around line 134-142: The current mapping uses memory.map to create one
PageMapItem per chunk which misses pages when a chunk spans multiple 4KiB pages;
change the construction of pageMap so that for each chunk (from the memory
array) you compute the firstPage = Math.floor(chunk.address / 4096) and lastPage
= Math.floor((chunk.address + (chunk.length ?? 0) - 1) / 4096) and push a
PageMapItem for every page index in that range with address = (pageIndex << 12)
(or pageIndex*4096) & ~0xfff, length = 4096 and "is-writable" = true; then keep
the existing dedupe into uniquePageMap (filter by address) to remove duplicates.

In `@src/hooks/useDebuggerActions.ts`:
- Around line 81-83: The code reads workers[0]?.memoryRanges into memoryRanges
and calls syncMemoryRangeAllWorkers which will throw if undefined; change to
default to an empty array and only call syncMemoryRangeAllWorkers when the array
is non-empty (e.g., const memoryRanges = workers[0]?.memoryRanges ?? []; if
(memoryRanges.length) syncMemoryRangeAllWorkers(...)). Apply the same
guard/default pattern wherever memoryRanges is read and passed to
syncMemoryRangeAllWorkers in this module (the other occurrences near the other
syncMemoryRangeAllWorkers calls).
- Around line 84-90: The current loop calls destroyWorker for each WorkerState
which broadcasts Commands.UNLOAD to all workers repeatedly; change this by
adding a single "destroyAllWorkers" flow or modify destroyWorker so it targets
only the specific worker id: update destroyWorker (or add destroyAllWorkers) to
accept a flag or separate path that sends Commands.UNLOAD only to the intended
worker socket instead of broadcasting to all, then replace the
Promise.all(workers.map(...dispatch(destroyWorker...))) calls with a single call
to the new destroy-all helper or call destroyWorker per-id in its non-broadcast
mode; ensure recreateWorker usage remains unchanged and keep references to
WorkerState, recreateWorker, destroyWorker and Commands.UNLOAD when making the
change.

In `@src/lib/hostCallTrace.test.ts`:
- Line 6: The test file is named in camelCase; rename the file from
src/lib/hostCallTrace.test.ts to kebab-case host-call-trace.test.ts and update
any imports or references (e.g., test runner configs or other files that import
this test) to point to host-call-trace.test.ts; also update any describe/title
strings if you want consistency (the test's describe("hostCallTrace", ...) can
remain or be changed to "host-call-trace" if desired) and run the test suite to
ensure no broken references remain.

In `@src/lib/hostCallTrace.ts`:
- Around line 1-9: The file name hostCallTrace.ts uses camelCase but the repo
requires kebab-case; rename the file to host-call-trace.ts and update all
imports referencing hostCallTrace (e.g., any import statements that import the
module providing "ecalli / PVM IO Trace Parser and Types" or types from that
module) to use the new kebab-case name; ensure build and tests are updated to
reference host-call-trace.ts and run a full repo search/replace for
"hostCallTrace" to catch any remaining references.
- Around line 205-235: The parseMemreadLine and parseMemwriteLine functions
currently ignore mismatches between the declared len and the actual decoded byte
count; update both functions to call parseHexBytes(dataHex), verify that the
returned byte array length equals parseInt(lenStr, 10), and throw a descriptive
Error (including the parsed address, declared len, and actual data length) if
they differ so callers cannot silently proceed with inconsistent memory sizes;
keep the existing parseAddress/parseHexBytes usage and ensure the thrown error
is clear for debugging.
- Around line 618-636: findHostCallEntry currently picks the first host call
with a matching pc (from trace.hostCalls.slice(indexInTrace)) which can return a
false mismatch when a later entry has the correct hostCallIndex and acceptable
gas; change the selection logic so after building matchingEntries (pc match and
hc.gas <= gas) you first try to find an entry whose hostCallIndex equals the
provided hostCallIndex and gas <= gas (e.g. matchingEntries.find(hc =>
hc.hostCallIndex === hostCallIndex && hc.gas <= gas)), use that as entry if
found, otherwise fall back to matchingEntries[0]; keep the rest of the
mismatches handling unchanged but reference the chosen entry variable (entry) as
before.
- Around line 337-468: parseTrace currently consumes raw trace text without Zod
validation; add a Zod schema for the overall ParsedTrace shape (including
prelude.program, prelude.start, initialMemoryWrites, hostCalls entries with
memoryReads/writes/registerWrites/gasAfter, termination, and errors) and
validate the input structure before doing line-by-line parsing. Implement the
schema near the top of this module, call schema.safeParse or parse inside
parseTrace at the start (or validate a preliminary parsed object after minimal
parsing) and on validation failure return or populate result.errors with the zod
error messages (or throw a clear error) so downstream code gets a typed,
validated ParsedTrace; reference parseTrace, ParsedTrace, and the
result.hostCalls/currentHostCall shapes when adding the schema and handling
failures.

In `@src/store/debugger/debuggerSlice.ts`:
- Around line 225-237: The reducer setHostCallsTrace currently clears
state.hostCallsTrace when action.payload is null/empty but leaves
state.nextHostCallIndex and state.pendingHostCall unchanged, causing stale UI
state; update setHostCallsTrace to also reset state.nextHostCallIndex to 0 and
state.pendingHostCall to null whenever action.payload is null or an
empty/whitespace string so that hostCallsTrace, nextHostCallIndex, and
pendingHostCall are consistent.
- Around line 225-233: The reducer setHostCallsTrace currently stores parsed
traces without validating them; call the existing validateTrace(action.payload)
(or run the payload through the shared Zod schema) before calling parseTrace,
and if validation reports errors throw an Error (or reject the payload) so
invalid trace content is not persisted; only after validateTrace succeeds
proceed to parseTrace and set state.hostCallsTrace with rawContent and parsed.
Ensure this change applies to the setHostCallsTrace reducer so Loader.tsx
dispatches cannot write invalid traces, keeping TraceConfigDialog.tsx validation
redundant but harmless.

In `@src/store/workers/workersSlice.ts`:
- Around line 296-342: In the auto-continue path you need to advance each
worker's host-call index and actually await resuming execution instead of
fire-and-forget; after computing newRegs/newGas/memoryEdits and after
successfully applying SET_STATE/SET_MEMORY via asyncWorkerPostMessage, increment
the host-call index on the state you pass to
dispatch(setWorkerCurrentState(...)) (or dispatch a dedicated advance-host-call
action) so the worker's currentState reflects the progressed host-call, and then
await runAllWorkers() (use await dispatch(runAllWorkers()) if it's a thunk)
rather than calling it without awaiting, so trace matching and resume errors are
not lost.

In `@src/utils/loggerService.tsx`:
- Around line 19-20: The startup log uses a template literal with embedded
newline/indentation and awkward phrasing; replace the template literal in the
console.info call in src/utils/loggerService.tsx with a plain single-line string
and fix grammar (e.g., "Logger initialized — watch console for logs."), removing
the extra whitespace/newline so the message is concise and properly punctuated.

In `@tests/trace-loading.spec.ts`:
- Around line 13-16: The test uses a hard-coded tracePath and directly calls
fileInput.setInputFiles, which bypasses shared Playwright fixtures; replace the
direct path usage with the shared fixture from tests/utils (the fixture that
supplies the deterministic file or FilePayload) and pass that fixture into the
test instead of constructing tracePath, then use that fixture value with the
existing fileInput locator and setInputFiles call (update the test signature to
accept the fixture and substitute tracePath in the setInputFiles invocation);
apply the same change to the other occurrence around lines 45-48.
🧹 Nitpick comments (12)
tests/trace-loading.spec.ts (1)

18-27: Replace fixed sleeps with condition-based waits.

waitForTimeout adds flakiness; the expect(...).toBeVisible/Enabled calls already include waits. At lines 18–27, remove the 2000ms sleep before checking button visibility. At lines 49–65, remove both the 2000ms and 3000ms sleeps, and replace the 10000ms sleep after Run with a condition-based wait for either the host-call dialog or program-status visibility.

Additionally, reuse the openDebugger fixture from tests/utils/actions.ts instead of duplicating the page.goto and localStorage.clear calls.

♻️ Suggested cleanup
-    await page.waitForTimeout(2000);
...
-    await page.waitForTimeout(3000);
...
-    await page.waitForTimeout(10000);

Replace the 10000ms sleep with:

await Promise.race([
  page.locator('[role="dialog"]').waitFor({ state: 'visible' }),
  expect(page.getByTestId("program-status")).toBeVisible()
]).catch(() => {});
src/lib/hostCallTrace.test.ts (2)

1-4: Avoid __dirname if Vitest runs in ESM mode.

If tests execute as ESM, __dirname can be undefined. Consider using import.meta.url + fileURLToPath instead.

🔧 Safer ESM path resolution
-import * as path from "path";
+import { fileURLToPath } from "url";
@@
-      const tracePath = path.join(__dirname, "../../io-trace-output.log");
+      const tracePath = fileURLToPath(new URL("../../io-trace-output.log", import.meta.url));

Also applies to: 67-68


69-72: Use a committed fixture to keep this test deterministic.

Right now the test silently skips when the file is missing. Consider adding a small fixture trace under version control so the test always exercises the parsing path.

src/components/ProgramLoader/loadingUtils.test.ts (2)

1-3: Avoid __dirname if Vitest runs in ESM mode.

If tests execute as ESM, __dirname can be undefined. Prefer import.meta.url + fileURLToPath.

🔧 Safer ESM path resolution
-import * as path from "path";
+import { fileURLToPath } from "url";
@@
-      const tracePath = path.join(__dirname, "../../../io-trace-output.log");
+      const tracePath = fileURLToPath(new URL("../../../io-trace-output.log", import.meta.url));

Also applies to: 8-9


10-13: Use a committed fixture to keep this test deterministic.

Right now the test silently skips when the file is missing. Consider checking in a small fixture trace instead so the test always exercises the parsing path.

src/components/ErrorBoundary.tsx (2)

60-65: Unnecessary state reset before page reload.

The setState call on line 62 has no effect since window.location.reload() immediately follows on line 64, discarding any in-memory state changes.

🔧 Suggested simplification
             <button
               onClick={() => {
-                this.setState({ hasError: false, error: null, errorInfo: null });
                 window.location.hash = "#/load";
                 window.location.reload();
               }}

24-28: Consider using the existing logger service for consistency.

The codebase has a logger.error service (see src/utils/loggerService.tsx) that provides centralized error handling. Using it here would maintain consistency across the application.

src/components/ProgramLoader/Loader.tsx (1)

195-195: Misleading variable name.

hasTraceWithEntrypoint simply checks if traceContent !== null, but the name suggests it verifies that the trace contains entrypoint information. Consider renaming to hasTrace for clarity.

✏️ Suggested rename
-  const hasTraceWithEntrypoint = traceContent !== null;
+  const hasTrace = traceContent !== null;

Then update usages on lines 198 and 311.

src/components/DebuggerSettings/TraceConfigDialog.tsx (2)

176-192: Consider using dropzone's open() method instead of creating a manual input element.

The dropzone hook provides an open() function that can trigger the file picker. This would be more consistent and avoid duplicating the file accept logic.

♻️ Suggested refactor using dropzone open()
-  const { getRootProps, getInputProps, isDragActive } = useDropzone({
+  const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
     onDrop: handleFileDrop,
     accept: { "text/plain": [".txt", ".trace", ".log"] },
     noClick: true,
     multiple: false,
   });

Then update the button:

               <Button
                 variant="outline"
                 size="sm"
-                onClick={() => {
-                  const input = document.createElement("input");
-                  input.type = "file";
-                  input.accept = ".txt,.trace,.log";
-                  input.onchange = (e) => {
-                    const file = (e.target as HTMLInputElement).files?.[0];
-                    if (file) handleFileDrop([file]);
-                  };
-                  input.click();
-                }}
+                onClick={open}
               >

128-129: Unsafe type assertion on FileReader result.

The e.target?.result is cast to string without verification. While readAsText should return a string, defensive coding would check the type.

🛡️ Suggested defensive check
         reader.onload = (e) => {
-          const content = e.target?.result as string;
+          const content = e.target?.result;
+          if (typeof content !== "string") return;
           const { stripped, programLine: pl } = stripProgramLine(content);
src/components/HostCallDialog/MemoryEditor.tsx (1)

130-130: Potential unnecessary memory reloads when pendingWrites reference changes.

Adding pendingWrites to the dependency array causes loadMemory to re-run whenever the array reference changes, even if the content is identical. For large memory regions, this could cause noticeable re-fetching.

Consider memoizing pendingWrites in the parent component or using a stable reference comparison if this becomes a performance concern.

src/components/HostCallDialog/DefaultHostCallContent.tsx (1)

36-53: Cap log memory reads to avoid oversized allocations.
Lengths come from registers; a defensive maximum prevents large reads or UI stalls if the target/message lengths are unexpectedly huge.

Suggested guard
 const decoder = new TextDecoder("utf8");
+const MAX_LOG_BYTES = 16_384;

 ...
-        const targetLength = Number(currentRegs[9] ?? 0n);
+        const targetLength = Math.max(0, Math.min(Number(currentRegs[9] ?? 0n), MAX_LOG_BYTES));
 ...
-        const msgLength = Number(currentRegs[11] ?? 0n);
+        const msgLength = Math.max(0, Math.min(Number(currentRegs[11] ?? 0n), MAX_LOG_BYTES));

Also applies to: 111-154, 251-269

Comment on lines 15 to 20
<button
className="text-sm text-muted-foreground hover:text-foreground hover:underline disabled:opacity-50"
onClick={onRestart}
disabled={disabled}
>
restart
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Set type="button" on the restart control.
Prevents unintended form submission if this component is rendered inside a form.

🔧 Proposed fix
-        <button
+        <button type="button"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
className="text-sm text-muted-foreground hover:text-foreground hover:underline disabled:opacity-50"
onClick={onRestart}
disabled={disabled}
>
restart
<button type="button"
className="text-sm text-muted-foreground hover:text-foreground hover:underline disabled:opacity-50"
onClick={onRestart}
disabled={disabled}
>
restart
🤖 Prompt for AI Agents
In `@src/components/HostCallDialog/handlers/HostCallActionButtons.tsx` around
lines 15 - 20, The restart button in HostCallActionButtons lacks an explicit
type, which can cause unintended form submission when this component is used
inside a form; update the <button> element that calls onRestart (the "restart"
control) to include type="button" so it does not act as a submit button, keeping
the existing className, onClick handler, and disabled prop unchanged.

Comment on lines 106 to 108
const finalRegs = pendingRegs ?? currentState.regs ?? DEFAULT_REGS;
const currentGas = currentState.gas ?? DEFAULT_GAS;
const finalGas = gas ?? pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n);

// Combine memory edits from host calls with pending memory from default UI
const finalMemoryEdits: MemoryEdit[] = [];
if (memoryEdits && memoryEdits.length > 0) {
finalMemoryEdits.push(...memoryEdits);
}
if (pendingMemoryEdits.length > 0) {
finalMemoryEdits.push(...pendingMemoryEdits);
}
const finalGas = pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential null reference when accessing currentState properties.

currentState is typed as ExpectedState | undefined (line 60), but lines 106-108 access currentState.regs and currentState.gas without null checks, which would throw if currentState is undefined.

🛡️ Proposed fix with optional chaining
         // Use provided values or fall back to pending/current state
-        const finalRegs = pendingRegs ?? currentState.regs ?? DEFAULT_REGS;
-        const currentGas = currentState.gas ?? DEFAULT_GAS;
+        const finalRegs = pendingRegs ?? currentState?.regs ?? DEFAULT_REGS;
+        const currentGas = currentState?.gas ?? DEFAULT_GAS;
         const finalGas = pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const finalRegs = pendingRegs ?? currentState.regs ?? DEFAULT_REGS;
const currentGas = currentState.gas ?? DEFAULT_GAS;
const finalGas = gas ?? pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n);
// Combine memory edits from host calls with pending memory from default UI
const finalMemoryEdits: MemoryEdit[] = [];
if (memoryEdits && memoryEdits.length > 0) {
finalMemoryEdits.push(...memoryEdits);
}
if (pendingMemoryEdits.length > 0) {
finalMemoryEdits.push(...pendingMemoryEdits);
}
const finalGas = pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n);
const finalRegs = pendingRegs ?? currentState?.regs ?? DEFAULT_REGS;
const currentGas = currentState?.gas ?? DEFAULT_GAS;
const finalGas = pendingGas ?? (currentGas > 10n ? currentGas - 10n : 0n);
🤖 Prompt for AI Agents
In `@src/components/HostCallDialog/index.tsx` around lines 106 - 108, currentState
may be undefined so accessing currentState.regs and currentState.gas can throw;
update the expressions that compute finalRegs and currentGas/finalGas (the lines
assigning finalRegs, currentGas, finalGas in HostCallDialog/index.tsx) to use
optional chaining or nullish coalescing when reading currentState (e.g.,
currentState?.regs and currentState?.gas) so you fall back to DEFAULT_REGS and
DEFAULT_GAS safely, and keep the pendingRegs/pendingGas precedence logic intact.

Comment on lines 64 to 74
const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => {
setProgramLoad(output);
if (trace) {
setTraceContent(trace);
const parsed = parseTrace(trace);
setTraceSummary(getTraceSummary(parsed));
} else {
setTraceContent(null);
setTraceSummary(null);
}
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing error handling for trace parsing.

If parseTrace or getTraceSummary throws an exception (e.g., malformed trace content), it will crash the component. Consider wrapping in try-catch.

🛡️ Proposed fix with error handling
   const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => {
     setProgramLoad(output);
     if (trace) {
-      setTraceContent(trace);
-      const parsed = parseTrace(trace);
-      setTraceSummary(getTraceSummary(parsed));
+      try {
+        setTraceContent(trace);
+        const parsed = parseTrace(trace);
+        setTraceSummary(getTraceSummary(parsed));
+      } catch (e) {
+        console.error("Failed to parse trace:", e);
+        setTraceContent(null);
+        setTraceSummary(null);
+      }
     } else {
       setTraceContent(null);
       setTraceSummary(null);
     }
   }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => {
setProgramLoad(output);
if (trace) {
setTraceContent(trace);
const parsed = parseTrace(trace);
setTraceSummary(getTraceSummary(parsed));
} else {
setTraceContent(null);
setTraceSummary(null);
}
}, []);
const handleFileUpload = useCallback((output: ProgramUploadFileOutput, trace?: string) => {
setProgramLoad(output);
if (trace) {
try {
setTraceContent(trace);
const parsed = parseTrace(trace);
setTraceSummary(getTraceSummary(parsed));
} catch (e) {
console.error("Failed to parse trace:", e);
setTraceContent(null);
setTraceSummary(null);
}
} else {
setTraceContent(null);
setTraceSummary(null);
}
}, []);
🤖 Prompt for AI Agents
In `@src/components/ProgramLoader/Loader.tsx` around lines 64 - 74, The
handleFileUpload callback currently calls parseTrace and getTraceSummary without
protection; wrap the parseTrace/getTraceSummary calls in a try-catch inside
handleFileUpload so malformed trace data doesn't crash the component — on
success keep the existing setTraceContent(trace) and
setTraceSummary(getTraceSummary(...)), and on error call setTraceContent(null)
and setTraceSummary(null) (and optionally log the error or surface a
user-friendly message) so the component remains stable; reference
handleFileUpload, parseTrace, getTraceSummary, setTraceContent, setTraceSummary,
and setProgramLoad when applying the change.

import * as fs from "fs";
import * as path from "path";

describe("loadingUtils", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Rename this test file to kebab-case.

Please rename to loading-utils.test.ts to align with repo naming rules and update any references.

As per coding guidelines: Use descriptive filenames in kebab-case (e.g., memory-range-panel.tsx).

🤖 Prompt for AI Agents
In `@src/components/ProgramLoader/loadingUtils.test.ts` at line 5, Rename the test
file from loadingUtils.test.ts to loading-utils.test.ts and update all
references/imports to that file throughout the repo (e.g., any import
statements, test index files, or CI/test-glob patterns that explicitly reference
the old name); ensure the test suite name ("describe('loadingUtils', ...)") can
remain unchanged or be adjusted for clarity but is not required to match the
filename.

Comment on lines 225 to 237
setHostCallsTrace(state, action: { payload: string | null }) {
if (action.payload === null || action.payload.trim() === "") {
state.hostCallsTrace = null;
} else {
const parsed = parseTrace(action.payload);
state.hostCallsTrace = {
rawContent: action.payload,
parsed,
};
// reset host call index
state.nextHostCallIndex = 0;
state.pendingHostCall = null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset host-call progress when the trace is cleared.
When payload is null/empty, pendingHostCall and nextHostCallIndex keep their previous values, which can leave the UI in a stale host-call state.

Suggested fix
     if (action.payload === null || action.payload.trim() === "") {
       state.hostCallsTrace = null;
+      state.nextHostCallIndex = 0;
+      state.pendingHostCall = null;
     } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setHostCallsTrace(state, action: { payload: string | null }) {
if (action.payload === null || action.payload.trim() === "") {
state.hostCallsTrace = null;
} else {
const parsed = parseTrace(action.payload);
state.hostCallsTrace = {
rawContent: action.payload,
parsed,
};
// reset host call index
state.nextHostCallIndex = 0;
state.pendingHostCall = null;
}
setHostCallsTrace(state, action: { payload: string | null }) {
if (action.payload === null || action.payload.trim() === "") {
state.hostCallsTrace = null;
state.nextHostCallIndex = 0;
state.pendingHostCall = null;
} else {
const parsed = parseTrace(action.payload);
state.hostCallsTrace = {
rawContent: action.payload,
parsed,
};
// reset host call index
state.nextHostCallIndex = 0;
state.pendingHostCall = null;
}
🤖 Prompt for AI Agents
In `@src/store/debugger/debuggerSlice.ts` around lines 225 - 237, The reducer
setHostCallsTrace currently clears state.hostCallsTrace when action.payload is
null/empty but leaves state.nextHostCallIndex and state.pendingHostCall
unchanged, causing stale UI state; update setHostCallsTrace to also reset
state.nextHostCallIndex to 0 and state.pendingHostCall to null whenever
action.payload is null or an empty/whitespace string so that hostCallsTrace,
nextHostCallIndex, and pendingHostCall are consistent.

Comment on lines 296 to 342
logger.debug(" [handleHostCall] AUTO-CONTINUING with trace data");
// TODO [ToDr] This should be a separate method?
const newRegs = [...regs];
for (const rw of result.entry.registerWrites) {
newRegs[rw.index] = rw.value;
}
const newGas = result.entry.gasAfter ?? gas;
const memoryEdits = result.entry.memoryWrites.map((mw) => ({
address: mw.address,
data: mw.data,
}));

logger.debug(` [handleHostCall] Setting state: gas=${newGas}, memEdits=${memoryEdits.length}`);
await Promise.all(
state.workers
.filter(({ id }) => {
// Allow to call it for a single worker
return workerId ? workerId === id : true;
})
.map(async (worker) => {
const resp = await asyncWorkerPostMessage(worker.id, worker.worker, {
command: Commands.HOST_CALL,
payload: { hostCallIdentifier: worker.exitArg ?? -1 },
command: Commands.SET_STATE,
payload: { regs: newRegs, gas: newGas },
});

// TODO [ToDr] Handle host call response?

if ((getState() as RootState).debugger.isRunMode) {
dispatch(continueAllWorkers());
}

if (hasCommandStatusError(resp)) {
throw new Error(resp.error.message);
}

logger.info(` [handleHostCall] dispatching setWorkerCurrentState for ${worker.id}`);
dispatch(setWorkerCurrentState({ id: worker.id, currentState: resp.payload.state }));

for (const mem of memoryEdits) {
const memResp = await asyncWorkerPostMessage(worker.id, worker.worker, {
command: Commands.SET_MEMORY,
payload: { address: mem.address, data: mem.data },
});

if (hasCommandStatusError(memResp)) {
throw new Error(memResp.error.message);
}
}
}),
);

if (selectShouldContinueRunning(state)) {
dispatch(continueAllWorkers());
}

logger.info(" [handleHostCall] Done - returning without opening dialog");
dispatch(runAllWorkers());
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Advance host-call index and await resume in auto-continue path.
Right now the host-call index isn’t incremented and runAllWorkers() is fire-and-forget, so trace matching can repeat and resume failures are swallowed.

Suggested fix
-    logger.info("  [handleHostCall] Done - returning without opening dialog");
-    dispatch(runAllWorkers());
+    logger.info("  [handleHostCall] Done - advancing host-call index and resuming");
+    dispatch(
+      setPendingHostCall({
+        pendingHostCall: null,
+        nextHostCallIndex: nextHostCallIndex + 1,
+      }),
+    );
+    await dispatch(runAllWorkers()).unwrap();
     return;
🤖 Prompt for AI Agents
In `@src/store/workers/workersSlice.ts` around lines 296 - 342, In the
auto-continue path you need to advance each worker's host-call index and
actually await resuming execution instead of fire-and-forget; after computing
newRegs/newGas/memoryEdits and after successfully applying SET_STATE/SET_MEMORY
via asyncWorkerPostMessage, increment the host-call index on the state you pass
to dispatch(setWorkerCurrentState(...)) (or dispatch a dedicated
advance-host-call action) so the worker's currentState reflects the progressed
host-call, and then await runAllWorkers() (use await dispatch(runAllWorkers())
if it's a thunk) rather than calling it without awaiting, so trace matching and
resume errors are not lost.

Comment on lines 19 to 20
console.info(`Logger initialized watch console for logs
`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten the startup log string to avoid extra whitespace and fix grammar.

The template literal inserts a newline/indentation, and the message reads awkwardly. Use a plain string.

Proposed fix
-      console.info(`Logger initialized watch console for logs
-    `);
+      console.info("Logger initialized; watch console for logs.");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.info(`Logger initialized watch console for logs
`);
console.info("Logger initialized; watch console for logs.");
🤖 Prompt for AI Agents
In `@src/utils/loggerService.tsx` around lines 19 - 20, The startup log uses a
template literal with embedded newline/indentation and awkward phrasing; replace
the template literal in the console.info call in src/utils/loggerService.tsx
with a plain single-line string and fix grammar (e.g., "Logger initialized —
watch console for logs."), removing the extra whitespace/newline so the message
is concise and properly punctuated.

Fixes per CodeRabbit review:

- Rename hostCallTrace files to kebab-case

- Rename loadingUtils files to kebab-case

- Add type="button" to restart button

- Add optional chaining for currentState access

- Add rel="noopener noreferrer" to external links

- Add try-catch for trace parsing

- Add SPI args size guard (16MB limit)

- Reject traces with parse errors

- Fix pageMap to handle multi-page memory chunks

- Guard against undefined memoryRanges

- Fix destroyWorker broadcast issue

- Validate memread/memwrite length

- Improve findHostCallEntry matching logic

- Reset state when clearing trace

- Validate trace before parsing

- Await auto-continue execution

- Fix logger startup message

- Replace fixed waits in tests with condition-based waits
- Fix destroyWorker to always return ID for cleanup

- Add retry loop to initAllWorkers for race condition robustness

- Disable UI (Loader, Examples, DebuggerControls) when PVM is loading

- Improve selectPVM test helper to avoid unnecessary toggling

- Reload page in openDebugger to ensure clean state
@tomusdrw
Copy link
Member Author

tomusdrw commented Feb 8, 2026

@coderabbitai please review whether your previous comments are addressed and resolve if that's the case.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@tomusdrw, I'll check my previous review comments on this PR and resolve those that have been addressed.


🏁 Script executed:

#!/bin/bash
# List all review comments made by coderabbitai on PR `#476`
echo "=== Fetching review comments from PR `#476` ==="
gh pr view 476 --json reviews --jq '.reviews[] | select(.author.login == "coderabbitai") | {state: .state, body: .body, createdAt: .createdAt}'

echo -e "\n=== Fetching review comment threads ==="
gh api repos/FluffyLabs/pvm-debugger/pulls/476/comments --jq '.[] | select(.user.login == "coderabbitai") | {id: .id, path: .path, line: .line, body: .body, created_at: .created_at, in_reply_to_id: .in_reply_to_id}'

Length of output: 21839


Let me check for unresolved conversations more thoroughly:


🏁 Script executed:

#!/bin/bash
# Check for review comments with more detail
echo "=== Checking PR review threads and their resolution status ==="
gh api graphql -f query='
query {
  repository(owner: "FluffyLabs", name: "pvm-debugger") {
    pullRequest(number: 476) {
      reviewThreads(first: 100) {
        nodes {
          id
          isResolved
          isOutdated
          comments(first: 10) {
            nodes {
              author {
                login
              }
              body
              createdAt
            }
          }
        }
      }
    }
  }
}' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.comments.nodes[0].author.login == "coderabbitai") | {isResolved: .isResolved, isOutdated: .isOutdated, commentBody: .comments.nodes[0].body}'

echo -e "\n=== Checking for any open review threads ==="
gh api graphql -f query='
query {
  repository(owner: "FluffyLabs", name: "pvm-debugger") {
    pullRequest(number: 476) {
      reviewThreads(first: 100) {
        totalCount
        nodes {
          isResolved
        }
      }
    }
  }
}' --jq '.data.repository.pullRequest.reviewThreads | {total: .totalCount, resolved: [.nodes[] | select(.isResolved)] | length, unresolved: [.nodes[] | select(.isResolved | not)] | length}'

Length of output: 91091


@tomusdrw, I've reviewed my previous comments on this PR. Currently, all 20 review comments remain unresolved. Here's a summary:

🟠 Major Issues (10 unresolved)

  1. Links.tsx - Missing rel="noopener noreferrer" on external links (security risk)
  2. loadingUtils.ts - No guard against huge sparse memory allocations (OOM risk)
  3. loadingUtils.ts - Missing validation for trace parse errors before using prelude
  4. useDebuggerActions.ts - Repeated UNLOAD broadcasts when destroying workers
  5. hostCallTrace.ts & .test.ts - Files need renaming to kebab-case per repo convention
  6. loadingUtils.test.ts - File needs renaming to kebab-case
  7. hostCallTrace.ts - Missing Zod validation for trace structure
  8. debuggerSlice.ts - Missing input validation in setHostCallsTrace reducer
  9. workersSlice.ts - Auto-continue path doesn't advance host-call index or await resume

🟡 Minor Issues (8 unresolved)

  1. HostCallActionButtons.tsx - Missing type="button" on restart button
  2. Loader.tsx - Missing error handling for trace parsing
  3. useDebuggerActions.ts - Need guard for undefined memoryRanges
  4. hostCallTrace.ts - Should validate memread/memwrite length matches actual data
  5. hostCallTrace.ts - findHostCallEntry should prefer entries matching the host call index
  6. debuggerSlice.ts - Should reset nextHostCallIndex and pendingHostCall when clearing trace
  7. trace-loading.spec.ts - Should use shared Playwright fixtures instead of hard-coded paths

✅ Outdated (2 comments - code changed)

  1. HostCallDialog/index.tsx - Optional chaining issue (code may have changed)
  2. loadingUtils.ts - PageMap generation issue (code may have changed)

Would you like me to help address any of these issues, or would you prefer to tackle them yourself first before I resolve the comments?

Major fixes:

- Links.tsx: Add rel="noopener noreferrer" to external links for security

- host-call-trace.ts: Add Zod validation for trace structure

- debuggerSlice.ts: Add input validation in setHostCallsTrace reducer

- workersSlice.ts: Fix auto-continue to advance host-call index and await resume

- useDebuggerActions.ts: Add guard for undefined memoryRanges

- findHostCallEntry: Prefer exact host call index matches with early return

Minor fixes:

- trace-loading.spec.ts: Use shared Playwright fixtures

- Create tests/utils/fixtures.ts for shared test utilities
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