Skip to content

Comments

ecalli traces support#476

Merged
tomusdrw merged 15 commits intomainfrom
td-ecall
Feb 19, 2026
Merged

ecalli traces support#476
tomusdrw merged 15 commits intomainfrom
td-ecall

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 c057acf
🔍 Latest deploy log https://app.netlify.com/projects/pvm-debugger/deploys/6997323bf0300400083f3ba4
😎 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.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces comprehensive host-call trace support to the PVM debugger, including a new host-call trace parsing library (JIP-6 format), trace configuration UI, trace-aware program loading, and enhanced memory editing with pending writes visualization. It refactors host-call state management, adds an application-level error boundary, removes service-ID tracking from web-worker infrastructure, and updates all host-call handlers to support restart actions.

Changes

Cohort / File(s) Summary
Host-call Trace Library
src/lib/host-call-trace.ts, src/lib/host-call-trace.test.ts
New comprehensive module for parsing, validating, and serializing JIP-6 host-call traces; includes data model (RegisterDump, MemoryWrite, HostCallEntry, etc.), Zod-based validation schemas, trace parsing with line-by-line interpretation, serialization utilities, and state comparison helpers (collectStateMismatches, findHostCallEntry).
Trace UI & Configuration
src/components/DebuggerSettings/TraceConfigDialog.tsx, src/components/DebuggerSettings/Content.tsx
New TraceConfigDialog component for editing Ecalli traces with CodeMirror editor, drag-and-drop file upload, validation, and auto-continue toggle; Content.tsx integrates trace display with status indicators and configure button, uses selectHostCallsTrace selector.
Program Loading with Trace Integration
src/components/ProgramLoader/loading-utils.ts, src/components/ProgramLoader/loading-utils.test.ts, src/components/ProgramLoader/Loader.tsx, src/components/ProgramLoader/ProgramFileUpload.tsx, src/components/ProgramLoader/Examples.tsx, src/components/ProgramLoader/index.tsx, src/components/ProgramLoader/types.ts, src/components/ProgramLoader/Links.tsx
New loading-utils.ts module with loadFileFromUint8Array orchestrating trace, JSON test, SPI, and generic PVM parsing; memory/register utilities; Loader enhanced to capture and dispatch traces; ProgramFileUpload and Examples updated with disabled prop and trace content handling; ProgramUploadFileOutput extended with optional spiArgs; added JIP-6 spec link in Links.
Host-call Dialog & Memory Visualization
src/components/HostCallDialog/index.tsx, src/components/HostCallDialog/DefaultHostCallContent.tsx, src/components/HostCallDialog/MemoryEditor.tsx, src/components/HostCallDialog/handlers/types.ts, src/components/HostCallDialog/handlers/...*
Dialog enhanced to display and toggle trace entries, compute mismatches, pass trace data to handlers; DefaultHostCallContent adds traceEntry and onRestart props, displays log output for hostCallId=100 with level badges; MemoryEditor adds pendingWrites, initialAddress, initialLength props with per-byte modification detection and visual highlighting; all handlers (Fetch, Gas, Info, Log, Provide, Read, Transfer, Write, Yield) updated to accept and propagate onRestart; HostCallActionButtons reordered with optional restart link and auto-continue toggle.
Debugger State & Host-call Management
src/store/debugger/debuggerSlice.ts, src/store/workers/workersSlice.ts, src/store/utils.ts
debuggerSlice refactored with HostCallTraceState and HostCallPaused interfaces; replaced hasHostCallOpen/pendingHostCallIndex with hostCallsTrace, nextHostCallIndex, pendingHostCall, autoContinueOnHostCalls; new reducers setHostCallsTrace, setPendingHostCall, setAutoContinueOnHostCalls, resetHostCallIndex; workersSlice replaced setAllWorkersServiceId thunk with initAllWorkers, added guard checks for workers availability, rewrote handleHostCall flow with trace lookup and auto-continue paths, enhanced stepAllWorkers with host-call detection; store/utils.ts improved listener cleanup and logging context.
Web-worker Infrastructure Cleanup
src/packages/web-worker/types.ts, src/packages/web-worker/worker.ts, src/packages/web-worker/command-handlers/step.ts
Removed SET_SERVICE_ID command from Commands enum and WorkerResponseParams/CommandWorkerRequestParams unions; removed serviceId propagation from worker state and runStep function signature.
Application-level & Core Utilities
src/main.tsx, src/components/ErrorBoundary.tsx, src/components/DebuggerControlls/index.tsx, src/hooks/useDebuggerActions.ts, src/utils/loggerService.tsx, src/types/type-guards.ts
New ErrorBoundary class component wrapping app tree with error capture and reload capability; DebuggerControlls adds noWorkers derived state and disables controls when no workers exist, renames Reset to Restart; useDebuggerActions introduces recreateWorker helper and expands restartProgram flow with host-call index reset and worker recreation; LoggerService adds group/groupEnd methods; type-guards.ts removes isInstructionError and isOneImmediateArgs utilities.
Test Infrastructure & Fixtures
tests/trace-loading.spec.ts, tests/utils/actions.ts, tests/utils/fixtures.ts
New Playwright test for loading and running io-trace-output.log with error boundary handling; actions.ts updates openDebugger and selectPVM with reload and full iteration logic; new fixtures.ts module centralizes fixture file access with getFixturePath, fixtureExists, readFixture, readFixtureBytes utilities.
Dependencies
package.json
Added baseline-browser-mapping ^2.9.19 and caniuse-lite ^1.0.30001769; added trustedDependencies field with @swc/core.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #455 — Both PRs add and modify HostCallDialog implementations, debugger/workers state management for host-call handling, and web-worker command infrastructure for host-call execution.
  • PR #451 — Both PRs refactor program loading utilities (loading-utils), update SPI/JAM decoding paths, and modify worker initialization with spiArgs and related type changes.
  • PR #468 — Both PRs extend ProgramLoader test hooks and e2e test infrastructure, adding test IDs for load buttons and Playwright trace-loading test scenarios.
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is vague and minimal but does relate to the changeset, mentioning trace file loading and fixes without providing detail. Expand description with specific examples: what trace format is supported, what files changed, or what behavior users should expect.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding support for ecalli traces in the debugger.

✏️ 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

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

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
@tomusdrw
Copy link
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 19, 2026

✅ Actions performed

Full review triggered.

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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/DebuggerControlls/index.tsx (1)

163-174: ⚠️ Potential issue | 🟡 Minor

Add noWorkers check to Restart button disabled condition.

The Restart button at line 170 is missing the noWorkers guard that other action buttons (Play, Run, Step) include. While restartProgram has defensive coding with optional chaining on workers[0]?.memoryRanges, it accesses the workers array directly at line 86. For consistency across the component and to prevent edge cases, disable the button when no workers are available.

🔧 Suggested change
-        disabled={!pvmInitialized || isProgramEditMode}
+        disabled={!pvmInitialized || isProgramEditMode || noWorkers}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DebuggerControlls/index.tsx` around lines 163 - 174, The
Restart button's disabled condition needs to include the existing noWorkers
guard like the other controls; update the Button that calls
debuggerActions.restartProgram(initialState) (in DebuggerControlls/index.tsx) to
add the noWorkers boolean to its disabled expression so the button is disabled
when noWorkers is true, keeping the existing pvmInitialized and
isProgramEditMode checks intact.
🧹 Nitpick comments (5)
src/utils/loggerService.tsx (1)

1-58: Consider renaming file to kebab-case for consistency.

The filename loggerService.tsx uses camelCase, but the coding guidelines specify kebab-case for TypeScript files (e.g., logger-service.tsx). This is a pre-existing issue, so it can be deferred.

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

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/loggerService.tsx` around lines 1 - 58, Rename the file from
loggerService.tsx to kebab-case (logger-service.tsx) to match naming guidelines;
perform a git mv to preserve history, update all imports that reference the
module (look for usages of the exported symbols Logger and logger and any
imports that reference "loggerService") to the new filename, run
type-check/build to ensure no broken imports remain, and adjust any
tooling/exports that assume the old name.
src/components/ProgramLoader/Links.tsx (1)

73-98: New JIP-6 trace file link section looks good overall.

The structure follows the existing pattern for other list items, and the main external link (lines 75-79) correctly includes security attributes.

One minor inconsistency: the inner text link at line 87-91 has rel="noopener noreferrer" without target="_blank", while other inner text links (lines 21, 41-46, 62-67) have neither attribute. The rel without target="_blank" is harmless but unnecessary.

🔧 Optional: Remove unnecessary rel or add target="_blank" for consistency

Either remove the unnecessary rel to match other inner links:

                <a
                  className="text-brand-dark dark:text-brand underline"
                  href="https://github.com/polkadot-fellows/JIPs/blob/main/JIP-6.md"
-                  rel="noopener noreferrer"
                >

Or add target="_blank" if you want this link to also open in a new tab:

                <a
                  className="text-brand-dark dark:text-brand underline"
                  href="https://github.com/polkadot-fellows/JIPs/blob/main/JIP-6.md"
+                  target="_blank"
                  rel="noopener noreferrer"
                >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ProgramLoader/Links.tsx` around lines 73 - 98, In Links.tsx
the inner anchor element wrapping the "specification" text includes
rel="noopener noreferrer" but lacks target="_blank", creating an inconsistent
pattern with other inner links; update that anchor (the <a> around
"specification") to either remove the rel attribute to match the other
internal-style links or add target="_blank" alongside rel="noopener noreferrer"
if you want it to open in a new tab—pick the approach consistent with other
anchors in this component and apply the same change to keep behavior uniform.
package.json (1)

41-42: Consider moving build-time dependencies to devDependencies.

caniuse-lite and baseline-browser-mapping are build-time tools used by autoprefixer and browserslist for browser compatibility checks. They are not imported anywhere in the runtime code. Placing them in dependencies rather than devDependencies will include them in production installs.

Move them to devDependencies:

♻️ Suggested change
 "dependencies": {
   ...
-  "baseline-browser-mapping": "^2.9.19",
-  "caniuse-lite": "^1.0.30001769",
   ...
 },
 "devDependencies": {
   ...
+  "baseline-browser-mapping": "^2.9.19",
+  "caniuse-lite": "^1.0.30001769",
   ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 41 - 42, Move the build-only packages
"caniuse-lite" and "baseline-browser-mapping" out of runtime dependencies and
into devDependencies in package.json: remove their entries from the dependencies
object and add equivalent entries under devDependencies so they are installed
only for development/build environments (keep the same version specifiers).
Ensure package.json remains valid JSON and run a quick install (npm/yarn) to
update lockfiles.
tests/utils/actions.ts (1)

25-44: Use includes() for simpler, more readable substring matching.
Case-insensitive substring matching is better expressed with includes() than constructing a regex pattern. This is clearer, faster, and more appropriate for text matching in UI automation.

🔧 Suggested change
-    const text = await option.textContent();
-    const isTarget = text?.match(new RegExp(pvmType, "i"));
+    const text = (await option.textContent()) ?? "";
+    const isTarget = text.toLowerCase().includes(pvmType.toLowerCase());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils/actions.ts` around lines 25 - 44, Replace the regex-based
substring check with a case-insensitive includes() check: when iterating the
locator allPvmOptions and computing isTarget from option.textContent(), convert
the text and the pvmType to the same case (e.g., toLowerCase()) and use
textLower.includes(pvmTypeLower) instead of text?.match(new RegExp(pvmType,
"i")); update the isTarget computation and leave selection logic
(isSelected/option.click()) unchanged so behavior is identical but simpler and
faster.
src/lib/host-call-trace.test.ts (1)

1-2: Rename this test file to use the *.spec.ts suffix.

The repository learnings call for unit specs to use *.spec.ts(x); please rename to host-call-trace.spec.ts and update any references.

Based on learnings: Unit specs should use the *.spec.ts(x) suffix and stay beside their modules; Match test file names to the unit under test (e.g., store/pvmSlice.spec.ts).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/host-call-trace.test.ts` around lines 1 - 2, Rename the test file
from host-call-trace.test.ts to host-call-trace.spec.ts to follow the repository
convention; update any imports or references that import this test filename
(e.g., test runner configs or CI patterns) and ensure the tests still import
parseTrace and getTraceSummary from ./host-call-trace; keep the spec colocated
with the module and run the test suite to confirm there are no import or path
breakages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Around line 106-108: The trustedDependencies array currently lists "@swc/core"
but that package is not present in dependencies or devDependencies and is not in
the active dependency tree; either remove the "@swc/core" entry from the
trustedDependencies array or add a short comment justifying why
trustedDependencies contains "@swc/core" (e.g., required for an external build
step or CI hook) and ensure a corresponding dependency entry exists in
dependencies/devDependencies if it truly is needed; update the package.json by
editing the "trustedDependencies" entry to reflect the chosen action.

In `@src/components/DebuggerSettings/TraceConfigDialog.tsx`:
- Around line 73-88: The current validateCurrentContent callback uses
validateTrace (which only checks parse errors) causing mismatches with the
store's setHostCallsTrace that uses validateTraceContent; update
validateCurrentContent to import and call validateTraceContent instead of
validateTrace, map its returned errors into setValidationErrors the same way
(e.g., `errors.map(e => \`Line ${e.lineNumber}: ${e.message}\`)`), and only call
parseTrace and setSummary(getTraceSummary(...)) when validateTraceContent
returns no errors so the dialog's preview validation matches the store
validation.

In `@src/components/ErrorBoundary.tsx`:
- Line 1: Rename the component file from ErrorBoundary.tsx to kebab-case (e.g.,
error-boundary.tsx) and update all imports that reference the module (e.g., any
import of ErrorBoundary or from "src/components/ErrorBoundary" to
"src/components/error-boundary"); inside the file ensure the exported symbol
(class or component named ErrorBoundary) remains unchanged, update any barrel
exports or index.ts under src/components to re-export from "./error-boundary",
and run a quick global search for "ErrorBoundary" and "components/ErrorBoundary"
to update remaining import paths accordingly.

In `@src/components/HostCallDialog/DefaultHostCallContent.tsx`:
- Around line 184-188: pendingMemoryWrites is recreated each render causing
MemoryEditor (which depends on pendingWrites and calls loadMemory) to reload
memory unnecessarily; import useMemo and wrap the pendingMemoryWrites assignment
in useMemo using traceEntry?.memoryWrites as the dependency so the array
reference only changes when the underlying memoryWrites change, e.g., memoize
the mapping that builds pendingMemoryWrites (referencing pendingMemoryWrites,
traceEntry?.memoryWrites, MemoryEditor, and loadMemory) to prevent redundant I/O
and avoid overwriting in-progress edits.
- Around line 111-145: The loadLogMessage async in the useEffect reads untrusted
targetLength and msgLength directly from currentState.regs; clamp both lengths
to a safe maximum (e.g., 4096 bytes) and ensure they are non-negative before
calling readMemory, then use the clamped values for readMemory calls and any
length checks so setLogTarget/setLogMessage only receive at-most 4KB strings;
update the variables targetLength and msgLength (or introduce
clampedTargetLength/clampedMsgLength) inside loadLogMessage and use those in the
existing readMemory and decode code paths.

In `@src/components/HostCallDialog/index.tsx`:
- Around line 70-99: When traceEntry changes in HostCallDialog, rebase the
pending state onto the current Redux state to avoid leaking manual edits: in the
useEffect that depends on traceEntry, first initialize pendingRegs from
currentState?.regs (or a copy of it) via setPendingRegs and pendingGas from
currentState?.gas via setPendingGas (and clear pendingMemoryEdits via
setPendingMemoryEdits([])) before applying traceEntry.registerWrites and
traceEntry.gasAfter and mapping traceEntry.memoryWrites; use the existing
setters (setPendingRegs, setPendingGas, setPendingMemoryEdits) and update the
code that currently applies registerWrites/memoryWrites so it mutates a fresh
copy based on currentState instead of the previous pending state.

In `@src/components/HostCallDialog/MemoryEditor.tsx`:
- Around line 57-67: The useEffect that syncs initialAddress/initialLength
doesn't handle when those props become undefined, leaving stale values; update
the effect in MemoryEditor (the useEffect that calls setAddressInput,
setAddress, setLengthInput, setLength) to explicitly reset to your default
values when initialAddress or initialLength are undefined (e.g., setAddressInput
to an empty string or default hex, setAddress to null/0 as appropriate, and
setLengthInput/ setLength to their defaults) so the editor clears when the trace
is cleared.

In `@src/components/ProgramLoader/loading-utils.test.ts`:
- Around line 8-13: The test currently no-ops if tracePath (constructed with
path.join and checked via fs.existsSync) is missing; either add and commit a
stable fixture file io-trace-output.log into the test resources so the
assertions that exercise loadFileFromUint8Array always run, or change the test
to throw/assert failure when fs.existsSync(tracePath) is false (e.g., replace
the early return with a failing assertion) so CI fails and alerts you that the
fixture is absent; update references in loading-utils.test.ts around tracePath,
fs.existsSync, and the code that calls loadFileFromUint8Array accordingly.

In `@src/components/ProgramLoader/loading-utils.ts`:
- Around line 74-138: Add Zod validation for the output of parseTrace at the
start of tryParseTraceFile: define a Zod schema that requires trace.prelude with
program (string), optional start with pc and gas (numbers), and
initialMemoryWrites (array or expected shape) and validate parseTrace(content)
against it before accessing trace.prelude.*; in tryParseTraceFile call
schema.parse or safeParse on the parse result, log or return null on validation
failure, and then continue using trace.prelude.program,
trace.prelude.initialMemoryWrites and trace.prelude.start as typed values
(update any subsequent error messages to reference the validated structure).

In `@tests/trace-loading.spec.ts`:
- Around line 2-12: The failing test occurs because the fixture file referenced
by FIXTURES.IO_TRACE_OUTPUT (resolved via getFixturePath) does not exist when
setInputFiles(tracePath) is called; add the missing tests/io-trace-output.log
fixture into the repo (or generate it during test setup) and ensure it is not
ignored by git — either commit it with git add -f, update .gitignore to allow
this specific fixture, or implement fixture creation in the test harness so
getFixturePath and page.locator('input[type="file"]') / setInputFiles(tracePath)
receive a valid path at runtime.

---

Outside diff comments:
In `@src/components/DebuggerControlls/index.tsx`:
- Around line 163-174: The Restart button's disabled condition needs to include
the existing noWorkers guard like the other controls; update the Button that
calls debuggerActions.restartProgram(initialState) (in
DebuggerControlls/index.tsx) to add the noWorkers boolean to its disabled
expression so the button is disabled when noWorkers is true, keeping the
existing pvmInitialized and isProgramEditMode checks intact.

---

Duplicate comments:
In `@src/hooks/useDebuggerActions.ts`:
- Around line 85-90: The looped destroy path causes N× UNLOAD broadcasts—modify
the worker teardown so UNLOAD targets only the specific worker or provide a
non-broadcast option and centralize full teardown: update destroyWorker in
workersSlice (and any caller usages like restartProgram and handlePvmTypeChange)
so it sends UNLOAD only to the worker id passed (or accept a flag
broadcast:boolean default false), then implement a single destroyAllWorkers
routine that iterates workers and calls destroyWorker for each (or toggles
broadcast=true once) so restartProgram and handlePvmTypeChange call that single
destroy-all path instead of destroying twice; ensure recreateWorker usage
remains unchanged.

---

Nitpick comments:
In `@package.json`:
- Around line 41-42: Move the build-only packages "caniuse-lite" and
"baseline-browser-mapping" out of runtime dependencies and into devDependencies
in package.json: remove their entries from the dependencies object and add
equivalent entries under devDependencies so they are installed only for
development/build environments (keep the same version specifiers). Ensure
package.json remains valid JSON and run a quick install (npm/yarn) to update
lockfiles.

In `@src/components/ProgramLoader/Links.tsx`:
- Around line 73-98: In Links.tsx the inner anchor element wrapping the
"specification" text includes rel="noopener noreferrer" but lacks
target="_blank", creating an inconsistent pattern with other inner links; update
that anchor (the <a> around "specification") to either remove the rel attribute
to match the other internal-style links or add target="_blank" alongside
rel="noopener noreferrer" if you want it to open in a new tab—pick the approach
consistent with other anchors in this component and apply the same change to
keep behavior uniform.

In `@src/lib/host-call-trace.test.ts`:
- Around line 1-2: Rename the test file from host-call-trace.test.ts to
host-call-trace.spec.ts to follow the repository convention; update any imports
or references that import this test filename (e.g., test runner configs or CI
patterns) and ensure the tests still import parseTrace and getTraceSummary from
./host-call-trace; keep the spec colocated with the module and run the test
suite to confirm there are no import or path breakages.

In `@src/utils/loggerService.tsx`:
- Around line 1-58: Rename the file from loggerService.tsx to kebab-case
(logger-service.tsx) to match naming guidelines; perform a git mv to preserve
history, update all imports that reference the module (look for usages of the
exported symbols Logger and logger and any imports that reference
"loggerService") to the new filename, run type-check/build to ensure no broken
imports remain, and adjust any tooling/exports that assume the old name.

In `@tests/utils/actions.ts`:
- Around line 25-44: Replace the regex-based substring check with a
case-insensitive includes() check: when iterating the locator allPvmOptions and
computing isTarget from option.textContent(), convert the text and the pvmType
to the same case (e.g., toLowerCase()) and use textLower.includes(pvmTypeLower)
instead of text?.match(new RegExp(pvmType, "i")); update the isTarget
computation and leave selection logic (isSelected/option.click()) unchanged so
behavior is identical but simpler and faster.

@tomusdrw tomusdrw merged commit 9708c64 into main Feb 19, 2026
7 checks passed
@tomusdrw tomusdrw deleted the td-ecall branch February 19, 2026 16:03
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