Add a Monaco-based debugger toolbar to the JavaScript playground, aligned with shared toolbar patterns but implementing JavaScript-specific stepping via an Acorn AST strategy (transform or instrument user code so execution can pause at semantic steps / statements mapping back to editor lines).
Goals — debugger UX
Breakpoints, Continue, Step over / Step into / Step out where feasible.
Variable inspection (locals / scope chain) surfaced in the UI aligned with Monaco’s current execution context.
Execution line highlighting in Monaco (decorations) mapped from VM position → source line.
Technical direction — JavaScript playground
Use Acorn (AST) analysis to:
Identify step boundaries suitable for pedagogical stepping (statement/expression granularity as designed).
Preserve source maps or line attribution so AST span ↔ Monaco line mapping stays coherent.
Bridge debugger commands ↔ worker/sandbox iframe (or whichever JS execution isolate we use today) so the toolbar drives real pause/resume rather than simulated-only UI unless unavoidable.
Document fallback behavior where full “step into” across async/bundlers is impractical.
Acceptance criteria
Dedicated issue/PR checklist: toolbar persists across lesson runs; no regressions to non-debug Run/Submit.
Breakpoints respected or clearly documented exceptions (e.g. eval-only paths).
Line highlight follows the active paused line in Monaco.
Broader Monaco editor migration tracked separately; this ticket is the debugger toolbar + JS stepping story atop that work.
See also #24 (app-language-runner Monaco migration umbrella).
## Summary
Add a **Monaco-based debugger toolbar** to the **JavaScript playground**, aligned with shared toolbar patterns but implementing **JavaScript-specific** stepping via an **Acorn AST** strategy (transform or instrument user code so execution can pause at semantic steps / statements mapping back to editor lines).
## Goals — debugger UX
- **Breakpoints**, **Continue**, **Step over / Step into / Step out** where feasible.
- **Variable inspection** (locals / scope chain) surfaced in the UI aligned with Monaco’s current execution context.
- **Execution line highlighting** in Monaco (decorations) mapped from VM position → source line.
## Technical direction — JavaScript playground
- Use **Acorn** (AST) analysis to:
- Identify step boundaries suitable for pedagogical stepping (statement/expression granularity as designed).
- Preserve source maps or line attribution so AST span ↔ **Monaco** line mapping stays coherent.
- Bridge **debugger commands** ↔ worker/sandbox iframe (or whichever JS execution isolate we use today) so the toolbar drives real pause/resume rather than simulated-only UI unless unavoidable.
- Document fallback behavior where full “step into” across async/bundlers is impractical.
## Acceptance criteria
- [ ] Dedicated issue/PR checklist: toolbar persists across lesson runs; **no regressions** to non-debug Run/Submit.
- [ ] Breakpoints respected or clearly documented exceptions (e.g. eval-only paths).
- [ ] Line highlight follows the active paused line in Monaco.
- [ ] Security/sandbox constraints respected (lesson runner remains isolated).
## Related
Broader Monaco editor migration tracked separately; **this ticket is the debugger toolbar + JS stepping story** atop that work.
- See also `#24` (`app-language-runner` Monaco migration umbrella).
Shipped a Visual-Studio-style debugger toolbar for the JavaScript playground built on the existing global js-interpreter / acorn / serialize libraries. The execution engine is a new shared service so Blockly's stepper rides on the same core (no duplicate state machines).
Architecture
JsStepDebuggerService (libraries/tools/src/services/js-step-debugger/) — single source of truth for run/pause/resume/stop, statement-boundary stepping, breakpoints, scope, output capture, and history-based step-back. Reactive via Angular Signals: state, currentLine, currentScope, output, error, historySize, capabilities.
Statement-boundary stepping — instead of tracking raw line numbers (which spuriously back-track when js-interpreter pops up to the Program node), we track the topmost Statement-/Declaration-typed AST node identity. Stable, correct, and naturally maps to the user's notion of "one source-level statement."
BreakpointStore — Set<number>-backed signal store with localStorage persistence so breakpoints survive route navigation within a session.
scope-snapshot — converts js-interpreter's pseudo-objects into a flat, UI-friendly ScopeBinding[] tree. Cycle-safe.
JsDebuggerMonacoBridge — wires service signals to Monaco: instruction-pointer line decoration, breakpoint glyph dots, glyph-margin click → breakpoints.toggle(line). Plain function (not a service) so it stays out of the DI tree.
JavascriptInterpreterService (Blockly) — refactored to delegate execution to JsStepDebuggerService. Kept as a thin shim that orchestrates workspaceToCode → core.prepare and translates core.stepInto loops into block-granularity highlighting.
UI
JsDebuggerToolbarComponent — Angular CDK Menu menubar. After UX feedback, redesigned to a flat Visual-Studio layout: dynamic Play / Pause / Continue primary button (state-driven label + icon + handler), Stop, Step Over, Step Into, Step Out, Step Back as direct icon buttons, Breakpoints as the only remaining dropdown (because it carries a dynamic line list).
JsDebugVarsPanelComponent — collapsible scope tree with click-to-expand for object handles via service.expandHandle.
JsDebugConsolePanelComponent — bottom-of-side-rail Console showing console.log / console.error / alert output streamed live as the program steps. Added so students don't need to crack open Chrome DevTools to see their output. Auto-scrolls to newest line; clear button resets via service.clearOutput().
LanguageRunner content-projection slots — [debug-toolbar] (above the editor) and [debug-side-panel] (right rail). The slot is a single <ng-content> lifted out of the advanced/beginner branches so Angular's content projection always lands on the visible editor layout. editorReady is re-emitted from both editor instances so the bridge re-attaches when toggling sandbox levels.
JavaScriptRunner shell — provides JsStepDebuggerService (route-scoped, not root, so navigation tears down breakpoints + interpreter cleanly), projects toolbar + Variables + Console into the new slots, attaches the Monaco bridge on editorReady, registers a source provider so service.run() can lazily prepare from the live editor without the shell intercepting clicks.
hideSandboxRunButton: true on JAVASCRIPT_LANGUAGE_RUNNER_CONFIG removes the redundant legacy "Run Code" button below the editor — the toolbar's Play/Stop is now the canonical run path. Other languages keep the button untouched.
Acceptance criteria
✅Breakpoints, Continue, Step over / into / out — all wired through the toolbar with capability-driven disabled states. Step Back also implemented (snapshot + deserialize via the global serialize.js).
✅Variable inspection — Variables panel shows the live scope chain at the paused statement; expanding an object handle drills into its children.
✅Execution line highlighting — js-debug-current-line row + js-debug-current-line-glyph margin arrow track currentLine(). Late-stage fix: advanceToFirstStatement now seeds _currentLine on prepare so line 1 actually highlights (regression covered by spec).
✅Toolbar persists across lesson runs / no regressions to non-debug Run/Submit — toolbar lives in a sandbox-mode-only slot above the editor; lesson mode flow is untouched. Spikersoft prod build is clean, all 2033 spikersoft tests + 350 tools tests pass.
✅Sandbox/security constraints respected — js-interpreter runs in a pure JS sandbox (no DOM access, no XHR), independent of the lesson runner's QuickJS isolate.
ES5 fallback documentation
js-interpreter is ES5-only; let / const / arrow functions / template literals fail to parse. Documented in detail at libraries/tools/src/services/js-step-debugger/README.md along with the rationale for not adopting Babel pre-transpilation (line-mapping fidelity loss for source-level stepping is the wrong trade-off for an educational debugger).
The service surfaces parse errors via the error signal, and the toolbar's status chip renders them so the student sees "Unexpected token" with the offending construct rather than a blank pause.
Late fixes during review
Line-1 highlight + line-1 breakpoints — advanceToFirstStatement set lastStatementNode but never _currentLine, so line 1 was never reported and BPs on line 1 walked straight past. Added a parking-line BP check in tick() plus a one-shot skip-flag on resume so resume-from-BP doesn't ping-pong.
Toolbar/vars panel disabled or missing in advanced sandbox mode — capabilities now factor in hasSourceProvider, and the projection slots were hoisted out of the advanced/beginner branch into a single shared wrapper.
Pre-existing build issues unblocked along the way — .ttf loader for Monaco's codicon font, anyScript budget bump for the worker chunk.
## Implementation summary
Shipped a Visual-Studio-style debugger toolbar for the JavaScript playground built on the existing global `js-interpreter` / `acorn` / `serialize` libraries. The execution engine is a new shared service so Blockly's stepper rides on the same core (no duplicate state machines).
### Architecture
- **`JsStepDebuggerService`** (`libraries/tools/src/services/js-step-debugger/`) — single source of truth for run/pause/resume/stop, statement-boundary stepping, breakpoints, scope, output capture, and history-based step-back. Reactive via Angular Signals: `state`, `currentLine`, `currentScope`, `output`, `error`, `historySize`, `capabilities`.
- **Statement-boundary stepping** — instead of tracking raw line numbers (which spuriously back-track when js-interpreter pops up to the `Program` node), we track the topmost Statement-/Declaration-typed AST node identity. Stable, correct, and naturally maps to the user's notion of "one source-level statement."
- **`BreakpointStore`** — `Set<number>`-backed signal store with `localStorage` persistence so breakpoints survive route navigation within a session.
- **`scope-snapshot`** — converts js-interpreter's pseudo-objects into a flat, UI-friendly `ScopeBinding[]` tree. Cycle-safe.
- **`JsDebuggerMonacoBridge`** — wires service signals to Monaco: instruction-pointer line decoration, breakpoint glyph dots, glyph-margin click → `breakpoints.toggle(line)`. Plain function (not a service) so it stays out of the DI tree.
- **`JavascriptInterpreterService`** (Blockly) — refactored to delegate execution to `JsStepDebuggerService`. Kept as a thin shim that orchestrates `workspaceToCode` → `core.prepare` and translates `core.stepInto` loops into block-granularity highlighting.
### UI
- **`JsDebuggerToolbarComponent`** — Angular CDK Menu menubar. After UX feedback, redesigned to a flat Visual-Studio layout: dynamic Play / Pause / Continue primary button (state-driven label + icon + handler), Stop, Step Over, Step Into, Step Out, Step Back as direct icon buttons, Breakpoints as the only remaining dropdown (because it carries a dynamic line list).
- **`JsDebugVarsPanelComponent`** — collapsible scope tree with click-to-expand for object handles via `service.expandHandle`.
- **`JsDebugConsolePanelComponent`** — bottom-of-side-rail Console showing `console.log` / `console.error` / `alert` output streamed live as the program steps. Added so students don't need to crack open Chrome DevTools to see their output. Auto-scrolls to newest line; clear button resets via `service.clearOutput()`.
- **`LanguageRunner` content-projection slots** — `[debug-toolbar]` (above the editor) and `[debug-side-panel]` (right rail). The slot is a single `<ng-content>` lifted out of the advanced/beginner branches so Angular's content projection always lands on the visible editor layout. `editorReady` is re-emitted from both editor instances so the bridge re-attaches when toggling sandbox levels.
- **`JavaScriptRunner` shell** — provides `JsStepDebuggerService` (route-scoped, not root, so navigation tears down breakpoints + interpreter cleanly), projects toolbar + Variables + Console into the new slots, attaches the Monaco bridge on `editorReady`, registers a source provider so `service.run()` can lazily prepare from the live editor without the shell intercepting clicks.
- **`hideSandboxRunButton: true`** on `JAVASCRIPT_LANGUAGE_RUNNER_CONFIG` removes the redundant legacy "Run Code" button below the editor — the toolbar's Play/Stop is now the canonical run path. Other languages keep the button untouched.
### Acceptance criteria
- ✅ **Breakpoints, Continue, Step over / into / out** — all wired through the toolbar with capability-driven disabled states. Step Back also implemented (snapshot + deserialize via the global `serialize.js`).
- ✅ **Variable inspection** — Variables panel shows the live scope chain at the paused statement; expanding an object handle drills into its children.
- ✅ **Execution line highlighting** — `js-debug-current-line` row + `js-debug-current-line-glyph` margin arrow track `currentLine()`. Late-stage fix: `advanceToFirstStatement` now seeds `_currentLine` on prepare so line 1 actually highlights (regression covered by spec).
- ✅ **Toolbar persists across lesson runs / no regressions to non-debug Run/Submit** — toolbar lives in a sandbox-mode-only slot above the editor; lesson mode flow is untouched. Spikersoft prod build is clean, all 2033 spikersoft tests + 350 tools tests pass.
- ✅ **Sandbox/security constraints respected** — js-interpreter runs in a pure JS sandbox (no DOM access, no XHR), independent of the lesson runner's QuickJS isolate.
### ES5 fallback documentation
`js-interpreter` is ES5-only; `let` / `const` / arrow functions / template literals fail to parse. Documented in detail at `libraries/tools/src/services/js-step-debugger/README.md` along with the rationale for not adopting Babel pre-transpilation (line-mapping fidelity loss for source-level stepping is the wrong trade-off for an educational debugger).
The service surfaces parse errors via the `error` signal, and the toolbar's status chip renders them so the student sees "Unexpected token" with the offending construct rather than a blank pause.
### Late fixes during review
- **Line-1 highlight + line-1 breakpoints** — `advanceToFirstStatement` set `lastStatementNode` but never `_currentLine`, so line 1 was never reported and BPs on line 1 walked straight past. Added a parking-line BP check in `tick()` plus a one-shot skip-flag on resume so resume-from-BP doesn't ping-pong.
- **Toolbar/vars panel disabled or missing in advanced sandbox mode** — capabilities now factor in `hasSourceProvider`, and the projection slots were hoisted out of the advanced/beginner branch into a single shared wrapper.
- **Pre-existing build issues unblocked along the way** — `.ttf` loader for Monaco's codicon font, `anyScript` budget bump for the worker chunk.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Add a Monaco-based debugger toolbar to the JavaScript playground, aligned with shared toolbar patterns but implementing JavaScript-specific stepping via an Acorn AST strategy (transform or instrument user code so execution can pause at semantic steps / statements mapping back to editor lines).
Goals — debugger UX
Technical direction — JavaScript playground
Acceptance criteria
Related
Broader Monaco editor migration tracked separately; this ticket is the debugger toolbar + JS stepping story atop that work.
#24(app-language-runnerMonaco migration umbrella).Implementation summary
Shipped a Visual-Studio-style debugger toolbar for the JavaScript playground built on the existing global
js-interpreter/acorn/serializelibraries. The execution engine is a new shared service so Blockly's stepper rides on the same core (no duplicate state machines).Architecture
JsStepDebuggerService(libraries/tools/src/services/js-step-debugger/) — single source of truth for run/pause/resume/stop, statement-boundary stepping, breakpoints, scope, output capture, and history-based step-back. Reactive via Angular Signals:state,currentLine,currentScope,output,error,historySize,capabilities.Programnode), we track the topmost Statement-/Declaration-typed AST node identity. Stable, correct, and naturally maps to the user's notion of "one source-level statement."BreakpointStore—Set<number>-backed signal store withlocalStoragepersistence so breakpoints survive route navigation within a session.scope-snapshot— converts js-interpreter's pseudo-objects into a flat, UI-friendlyScopeBinding[]tree. Cycle-safe.JsDebuggerMonacoBridge— wires service signals to Monaco: instruction-pointer line decoration, breakpoint glyph dots, glyph-margin click →breakpoints.toggle(line). Plain function (not a service) so it stays out of the DI tree.JavascriptInterpreterService(Blockly) — refactored to delegate execution toJsStepDebuggerService. Kept as a thin shim that orchestratesworkspaceToCode→core.prepareand translatescore.stepIntoloops into block-granularity highlighting.UI
JsDebuggerToolbarComponent— Angular CDK Menu menubar. After UX feedback, redesigned to a flat Visual-Studio layout: dynamic Play / Pause / Continue primary button (state-driven label + icon + handler), Stop, Step Over, Step Into, Step Out, Step Back as direct icon buttons, Breakpoints as the only remaining dropdown (because it carries a dynamic line list).JsDebugVarsPanelComponent— collapsible scope tree with click-to-expand for object handles viaservice.expandHandle.JsDebugConsolePanelComponent— bottom-of-side-rail Console showingconsole.log/console.error/alertoutput streamed live as the program steps. Added so students don't need to crack open Chrome DevTools to see their output. Auto-scrolls to newest line; clear button resets viaservice.clearOutput().LanguageRunnercontent-projection slots —[debug-toolbar](above the editor) and[debug-side-panel](right rail). The slot is a single<ng-content>lifted out of the advanced/beginner branches so Angular's content projection always lands on the visible editor layout.editorReadyis re-emitted from both editor instances so the bridge re-attaches when toggling sandbox levels.JavaScriptRunnershell — providesJsStepDebuggerService(route-scoped, not root, so navigation tears down breakpoints + interpreter cleanly), projects toolbar + Variables + Console into the new slots, attaches the Monaco bridge oneditorReady, registers a source provider soservice.run()can lazily prepare from the live editor without the shell intercepting clicks.hideSandboxRunButton: trueonJAVASCRIPT_LANGUAGE_RUNNER_CONFIGremoves the redundant legacy "Run Code" button below the editor — the toolbar's Play/Stop is now the canonical run path. Other languages keep the button untouched.Acceptance criteria
serialize.js).js-debug-current-linerow +js-debug-current-line-glyphmargin arrow trackcurrentLine(). Late-stage fix:advanceToFirstStatementnow seeds_currentLineon prepare so line 1 actually highlights (regression covered by spec).ES5 fallback documentation
js-interpreteris ES5-only;let/const/ arrow functions / template literals fail to parse. Documented in detail atlibraries/tools/src/services/js-step-debugger/README.mdalong with the rationale for not adopting Babel pre-transpilation (line-mapping fidelity loss for source-level stepping is the wrong trade-off for an educational debugger).The service surfaces parse errors via the
errorsignal, and the toolbar's status chip renders them so the student sees "Unexpected token" with the offending construct rather than a blank pause.Late fixes during review
advanceToFirstStatementsetlastStatementNodebut never_currentLine, so line 1 was never reported and BPs on line 1 walked straight past. Added a parking-line BP check intick()plus a one-shot skip-flag on resume so resume-from-BP doesn't ping-pong.hasSourceProvider, and the projection slots were hoisted out of the advanced/beginner branch into a single shared wrapper..ttfloader for Monaco's codicon font,anyScriptbudget bump for the worker chunk.