Skip to content

Add breakpointSteps to the CSE snapshot protocol - #55

Merged
Akshay-2007-1 merged 3 commits into
mainfrom
feat/cse-breakpoint-steps
Jul 9, 2026
Merged

Add breakpointSteps to the CSE snapshot protocol#55
Akshay-2007-1 merged 3 commits into
mainfrom
feat/cse-breakpoint-steps

Conversation

@Akshay-2007-1

Copy link
Copy Markdown
Contributor

Summary

  • Adds breakpointSteps: number[] to CseSnapshotMessage in @sourceacademy/common-cse-machine: a run-level array of 0-based step indices where a breakpoint (e.g. Python's breakpoint(), JS's debugger;) sits on top of the control.
  • CseMachinePlugin.sendSnapshots (runner) takes it as an optional second argument (defaults to []) and forwards it in the message.
  • CseMachineHostPlugin.receiveSnapshots (web) now receives it as a second parameter, passed through from the message (?? [] if absent).
  • Adds a changeset (minor bump for all three affected packages — additive, non-breaking).

Why

Mirrors the stepper protocol's existing redexNodeType marker field, which host apps use to drive breakpoint-navigation (double-chevron) controls. The CSE machine protocol had no equivalent, so a CSE-machine-based evaluator (e.g. py-slang, for source-academy/py-slang#189) had no way to tell the host which steps to jump to. This is a companion change to a py-slang change that detects breakpoint() in the CSE machine and populates this array, and a frontend change that wires it into the existing updateBreakpointSteps Redux action.

Test plan

  • Updated/extended unit tests in all three touched packages (common.test.ts, runner.test.ts, web.test.ts) — 66 tests passing
  • tsc --noEmit clean on all three packages
  • Rebuilt each package's dist/ and confirmed the new field/param is present in the emitted bundle

Adds breakpointSteps: number[] to CseSnapshotMessage — a run-level array
of 0-based step indices where a breakpoint (e.g. Python's breakpoint())
sits on top of the control. CseMachinePlugin.sendSnapshots takes it as an
optional second argument; CseMachineHostPlugin.receiveSnapshots now
receives it as a second parameter.

Lets host apps wire up breakpoint-navigation controls for CSE-machine
evaluators, matching the stepper's existing redexNodeType contract.
Paired with a py-slang change that detects breakpoint() in the CSE
machine and populates this array (source-academy/py-slang issue #189).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 55bc989

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

💥 An error occurred when fetching the changed packages and changesets in this PR
Some errors occurred when validating the changesets config:
The package or glob expression "@sourceacademy/web-stepper" is specified in the `ignore` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch

@Akshay-2007-1 Akshay-2007-1 self-assigned this Jul 9, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces breakpointSteps to the CSE snapshot protocol, allowing host applications to wire breakpoint-navigation controls for CSE-machine-based evaluators. The changes update CseMachinePlugin.sendSnapshots and CseMachineHostPlugin.receiveSnapshots to support and propagate this new array of step indices. The feedback suggests making breakpointSteps optional in the CseSnapshotMessage interface to ensure backward compatibility, and defensively verifying that message.breakpointSteps is an array at runtime before passing it to receiveSnapshots.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/common/cse-machine/src/index.ts Outdated
Comment thread src/web/cse-machine/src/index.ts Outdated
Make CseSnapshotMessage.breakpointSteps optional for backward compatibility
with runners that predate this field, and defensively check
Array.isArray(message.breakpointSteps) in the web plugin before passing it
to receiveSnapshots, since messages cross a conduit channel boundary and
could come from an older or malformed runner.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Akshay-2007-1

Copy link
Copy Markdown
Contributor Author

Addressed both suggestions in 5c35f18: breakpointSteps is now optional on CseSnapshotMessage, and the web plugin defensively checks Array.isArray(message.breakpointSteps) before passing it through.

@Akshay-2007-1

Copy link
Copy Markdown
Contributor Author

@coderabbitai, any review comments?

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 55 --repo source-academy/plugins --json title,body,files,commits -q '.'
echo "---FILES---"
gh pr diff 55 --repo source-academy/plugins

Length of output: 14705


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,80p' src/web/cse-machine/src/index.ts

Length of output: 2465


The changes look solid overall — I checked the current diff (commits 668a454 and 5c35f18) and confirm both previously flagged points are addressed correctly:

  • breakpointSteps?: number[] is now optional on CseSnapshotMessage in src/common/cse-machine/src/index.ts.
  • receiveSnapshots in src/web/cse-machine/src/index.ts guards with Array.isArray(message.breakpointSteps) ? message.breakpointSteps : [] before forwarding, which correctly handles messages from older runners or malformed payloads.

A couple of minor, non-blocking observations for your consideration:

  1. No validation of breakpointSteps contents. The subscribe handler in src/web/cse-machine/src/index.ts (Line 51-61) only checks Array.isArray(message.breakpointSteps) but doesn't verify the array elements are actually numbers, or that each index falls within [0, totalSteps). Since this crosses a conduit channel boundary from a potentially malformed/older runner, a malicious or buggy payload could contain non-numeric entries or out-of-range indices, which downstream host code (e.g. the Redux updateBreakpointSteps wiring) may not expect. Given message.snapshots and message.totalSteps aren't deeply validated either, this may be an accepted tradeoff for this PR's scope — just flagging it in case you'd like tighter validation now or as a follow-up.

  2. Ordering not enforced. The doc comment states breakpointSteps should be "in ascending order," but this is documentation-only — nothing enforces it in CseMachinePlugin.sendSnapshots (src/runner/cse-machine/src/index.ts). Since the runner and the py-slang breakpoint detector are the sole producers today, this is likely fine, but consumers on the host side should probably not assume sortedness without an explicit guarantee/test.

Neither of these blocks the PR — the core protocol addition, optional-field backward compatibility, and defensive parsing are all in good shape, and the test coverage across common.test.ts, runner.test.ts, and web.test.ts looks appropriately thorough for the new field.

[resolve]

@Akshay-2007-1
Akshay-2007-1 requested a review from Shrey5132 July 9, 2026 12:47
@Akshay-2007-1
Akshay-2007-1 merged commit 6672299 into main Jul 9, 2026
1 check passed
@Akshay-2007-1
Akshay-2007-1 deleted the feat/cse-breakpoint-steps branch July 9, 2026 12:58
Akshay-2007-1 added a commit that referenced this pull request Jul 9, 2026
changesets/action execs the `version` input directly instead of through a
shell, splitting it on whitespace and running the first token with the
rest as literal argv. `yarn changeset version && yarn install` therefore
sent `&&`, `yarn`, `install` to the changeset CLI itself as extra
positional arguments, which it rejected ("Too many arguments passed to
changesets — we only accept the command name as an argument"). That
aborted the version step before it could write any changes, so the
resulting "no commits between main and changeset-release/main" made the
action fail to open a Version Packages PR (seen on the #55 merge).

Wrap the compound command in `bash -c "..."` so bash — not changesets/action
— parses the `&&`.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Akshay-2007-1 added a commit to source-academy/frontend that referenced this pull request Jul 10, 2026
…tSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Akshay-2007-1 added a commit to Akshay-2007-1/py-slang-local that referenced this pull request Jul 10, 2026
…tSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/runner-cse-machine ^1.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus web-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachinePlugin.sendSnapshots(snapshots, breakpointSteps?) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
martin-henz added a commit to source-academy/frontend that referenced this pull request Jul 10, 2026
…channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>
martin-henz added a commit to source-academy/py-slang that referenced this pull request Jul 10, 2026
…rker (#256)

* feat(cse-machine): support breakpoint() as a breakpoint-navigation marker

Registers breakpoint() as a no-op MISC builtin (chapters 1-4) and, in the
CSE machine's step generator, detects a zero-arg call resolving (by
identity, so aliases like `bp = breakpoint; bp()` are caught too) to that
builtin, recording the step where it sits on top of the control into
context.runtime.breakpointSteps. Threads that array out through
collectSnapshots and PyCseEvaluator's sendSnapshots call so the host's
breakpoint-navigation controls can consume it (paired with a protocol
change in source-academy/plugins to carry it across the Conductor
__cse channel). Mirrors PR #197's substitution-stepper support of the
same feature (issue #189).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/runner-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and runner-cse-machine can't be pinned to a plugins branch via
Yarn's git+workspace protocol either — like web-cse-machine, it depends on
the sibling common-cse-machine workspace via workspace:*, and Yarn's
single-workspace git fetch doesn't build sibling workspaces first, so the
prepack/rollup step can't find its dist.

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) publishing a new npm
version before it will typecheck/build against real installs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/runner-cse-machine ^1.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus web-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachinePlugin.sendSnapshots(snapshots, breakpointSteps?) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(cse-machine): address Gemini review on #256

Also reset context.runtime.changepointSteps and .break in evaluateChunk,
alongside breakpointSteps -- same per-run leaking-state class, since
this.context persists across evaluateChunk calls but only breakpointSteps
was being reset.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

* Fix yarn.lock entry ordering for CI's hardened-mode install check

The root workspace's dependency block had @sourceacademy/conductor
listed after @sourceacademy/runner-cse-machine instead of before it
alphabetically -- yarn.lock wasn't re-normalized when
common-cse-machine/runner-cse-machine were bumped. This alone was
enough to fail `yarn install --frozen-lockfile` under Yarn's hardened
mode (auto-enabled for fork PRs), which treats any lockfile diff,
including pure reordering, as forbidden. Regenerated via a plain
`yarn install`; no dependency versions changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Martin Henz <henz@nus.edu.sg>
Co-authored-by: henz <henz@comp.nus.edu.sg>
Isha-Sovasaria pushed a commit to source-academy/frontend that referenced this pull request Jul 14, 2026
…channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>
Isha-Sovasaria pushed a commit to source-academy/frontend that referenced this pull request Jul 19, 2026
…channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>
RichDom2185 pushed a commit to source-academy/frontend that referenced this pull request Jul 19, 2026
…channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>
RichDom2185 pushed a commit to source-academy/frontend that referenced this pull request Jul 19, 2026
…channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>
martin-henz added a commit to source-academy/frontend that referenced this pull request Jul 29, 2026
* Add Louis bot rendering for sicpy

* Pass language to backend

* Update @sourceacademy/autocomplete digest to d476416 (#4049)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency react-simple-keyboard to v3.8.231 (#4064)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency @sentry/react to v10.63.0 (#4067)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency @rsbuild/core to v2.1.3 (#4059)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Fix crash when stored playground language state is invalid (#3646)

* Fix crash when stored playground language state is invalid

Validate the stored chapter+variant combination against ALL_LANGUAGES
when loading from localStorage. Falls back to the default language config
if the combination is invalid (e.g. chapter 1 + explicit-control).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fall back to defaultLanguageConfig for invalid stored language state

When the stored chapter+variant combination is not in ALL_LANGUAGES,
reset to `defaultLanguageConfig` (a known-valid pair) rather than to the
individual default context fields, so the fallback is explicitly "the
default language config" as the issue describes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com>

* docs: add local backend setup and py-slang testing instructions (#4070)

* docs: add local backend setup steps to README

Fills the gap between "point REACT_APP_BACKEND_URL at localhost:4000"
and an actually-running backend, which otherwise just shows the
"under maintenance" page. Also corrects the postgres docker command
from the backend repo's own README, which has a stray -e flag before
-p 5432:5432 that prevents the port from actually being published.

* docs: add "Running your own py-slang" section

Mirrors the existing "Running your own js-slang" section, linking
out to py-slang's own README (added in a companion PR there) rather
than duplicating the instructions here.

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* Update README.md

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Shrey Jain <101659971+Shrey5132@users.noreply.github.com>
Co-authored-by: Martin Henz <henz@comp.nus.edu.sg>

* Update README.md

* feat(cseMachine): rename global/program frames and annotate globals (#4068)

* feat(cseMachine): rename global/program frames and annotate globals

- Rename the top two CSE Machine frame labels to match Python's LEGB
  terminology: "Global" -> "Built-in functions", "Program" -> "Globals"
  (#4042).
- Thread the new optional CseSerializedEnvFrame.globalNames field
  (companion source-academy/plugins + source-academy/py-slang PRs)
  through CseSnapshotAdapter onto the fake Environment, and render it
  as a "globals: x, y" annotation at the top of a call frame the
  moment it's created, so it's clear those names resolve straight to
  the global frame instead of the usual enclosing-scope chain (#4041).
- Add @sourceacademy/common-cse-machine as a direct dependency: it's a
  peerDependency of @sourceacademy/web-cse-machine whose types
  frontend re-exports and uses directly, but frontend never installed
  it itself, so those types were silently resolving to `any` under
  skipLibCheck.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(cseMachine): stack globals annotation below frame name, fix overflow, and occlude arrows behind header text

- Move the globals annotation to its own row stacked below the frame
  name (rather than beside it or inside the box as a binding), since
  frame names are arbitrary function names of unpredictable length
  and can't safely share a row with anything else.
- Widen the frame box to fit the annotation label the same way binding
  text already widens it (capped at the same FrameDefaultWidth), so it
  no longer gets ellipsis-truncated or rendered left of the frame's
  own border when the label is longer than the box's other content.
- Add an opaque background rect behind the frame name and the globals
  annotation so the parent/tail arrow passing through that header gap
  is fully hidden behind the text, instead of showing through the
  empty space between glyphs (arrows are already layered behind
  content, but plain Konva text has no opaque fill of its own).

Closes #4069.

* fix(cseMachine): fix assignment animation crash for statement-only-assignment languages

InstrType.ASSIGNMENT constructed AssignmentAnimation with the
*current* (post-assignment) top-of-stash item, which is only valid
because a JS assignment *expression* re-pushes its value. A Python
assignment *statement* pushes nothing back, so the stash is genuinely
empty afterwards, the non-null assertion lied, and the constructor
threw reading `stashItem.index` on undefined — silently, since the
try/catch around CseAnimation.updateAnimation() had no logging.

Use the *previous* stash's top item instead, matching how
InstrType.POP (also a pure consumer) already does this correctly.
Also add the missing console.error so future animation regressions
in this path aren't invisible again.

Closes #4072.

* fix(cseMachine): stop the arrow-occlusion patch from being wider than the text it backs

Frame.tsx's header-text backing rects used Text.width(), which floors
at Config.TextMinWidth (30px) — a minimum meant for binding key/value
alignment, unrelated to how wide the text visually is. For a short
name like a one-letter function ("f", 9px of actual ink), that floor
is wider than Config.FramePaddingX (20px, the arrow's x-offset), so
the padded-out rect covered the arrow well past where the glyph
itself ends — an oversized, unnecessary gap in the arrow line even
when the text was nowhere near wide enough to actually cross it.

Use getTextWidth(text) directly instead, sized to what's actually
rendered.

* chore: bump common-cse-machine/web-cse-machine, drop published-type workaround casts

Now that @sourceacademy/common-cse-machine@0.2.0 (declaring
globalNames) and @sourceacademy/web-cse-machine@2.0.0 are actually
published, bump both dependencies and simplify the read sites that
were casting around the not-yet-published type.

Frame.tsx still needs one cast: `Env` (js-slang's real Environment
type) has no globalNames field regardless of any package version,
since it's an ad-hoc property CseSnapshotAdapter.ts stashes onto the
fake Environment object — updated that comment to say so accurately.

Also adds @sourceacademy/common-cse-machine to .yarnrc.yml's
npmPreapprovedPackages, matching the existing entries for
web-cse-machine/conductor/js-slang: yarn's npmMinimalAgeGate (3-day
quarantine on freshly published versions) was blocking install of the
version just published in the companion plugins release.

* fix(cseMachine): scope the Python LEGB frame rename to the Conductor snapshot path only

The #4042 rename ("Global" -> "Built-in functions", "Program" ->
"Globals") was motivated by Python's LEGB terminology, but Frame.tsx's
frameNames lookup applied unconditionally, so the legacy non-conductor
path (conductor.enable off, real js-slang interpreter) picked it up
too — a real regression against js-slang's own established naming,
confirmed live and flagged by Gemini's review on this PR.

Gate the rename on Layout.snapshotMode, which is true only while
rendering a Conductor snapshot (currently Python-only) and false for
the entire legacy path — evalCodeSaga never calls
CseMachine.renderSnapshot() when conductor.enable is off, so this is
a reliable, already-existing signal rather than a new one.

Other changes in this PR are left as-is: the globalNames annotation
is already inert for legacy js-slang Environments (they never carry
that ad-hoc field), the arrow-occlusion background-rect fix is a
general visual correctness fix unrelated to any language, and the
assignment-animation and frame-transition-timing fixes don't touch
frame naming at all.

Adds regression coverage on both sides of the gate: a real js-slang
EnvTree rendered outside snapshot mode keeps "Global"/"Program", and
a Python snapshot still gets "Built-in functions"/"Globals".

* fix(cseMachine): apply center-alignment offset to the globals annotation too

reassignCoordinatesX applied the center-alignment textOffset to the
frame name but not to _globalNamesText, leaving the annotation
flush-left under a centered name whenever center-alignment is on.
Flagged independently by CodeRabbit and Shrey's review on #4068.

Also extracts the headerRowHeight formula (duplicated between the
constructor and reassignCoordinatesY) into a single module-level
constant, per CodeRabbit's nitpick.

Added a regression test; confirmed it fails without the fix (30 vs
80) and passes with it.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Shrey Jain <101659971+Shrey5132@users.noreply.github.com>

* Show a popup for input() requests instead of hanging (py-slang#190) (#4084)

* Show a popup for input() requests instead of hanging (py-slang#190)

Wires up Conductor's new receiveInputRequest hook: a handleInputRequest
saga listens for input requests from the runner, shows the existing
PromptDialog popup, and answers via hostPlugin.sendInput. The home tab
and the viz CSE Machine tab share the same evalCodeConductorSaga run,
so this single fix covers both surfaces from the issue.

Also adds isWaitingForInput workspace state, used to suspend the
execTime watchdog while a popup is open so a user typing an answer
doesn't get their run force-stopped mid-prompt.

Points @sourceacademy/conductor at the local checkout (portal:) for
dev while the protocol change there is unreleased; repin to a real
published version once that lands upstream.

* chore: deps update

* Fix uncaught rejection when the input popup is dismissed via onClose

showSimplePromptDialog's underlying promise rejects if the dialog is
dismissed via its onClose path (e.g. clicking outside) rather than a
button response. The inner try/finally in handleInputRequest reset
isWaitingForInput on that path but didn't catch the rejection, so it
escaped the saga's while(true) loop and killed the task permanently -
leaving the runner blocked forever with no way to answer this or any
future input() call. Catch it and send an empty string, same as an
explicit Cancel.

Caught by gemini-code-assist review on PR #4084.

* Repin @sourceacademy/conductor to the published npm 0.6.0

The npm publish for conductor 0.6.0 landed, so the git-tag stopgap
pin isn't needed anymore - back to the normal semver-via-npm-registry
pin this repo used before. Verified: tsc --noEmit clean against the
real published package.

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>

* Updated the format in which language is sent to backend

* Remove chatbot from JS layout; stop sending language in chat APIs

* Update README.md

adding conductor info

* Remove unused scrollRefIntoView; inline scroll, clean unused Sicp layout symbols; add host plugin callbacks; relax CseMachineHostPlugin signature

* Fix language semantics

* Fix conflict

* Update README.md

* Fix conflict

* Fix conflict

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>

* Updated the format in which language is sent to backend

* Remove chatbot from JS layout; stop sending language in chat APIs

* Update blueprintjs to v6.17.2 (#4060)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency @rsbuild/core to v2.1.5 (#4077)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency react-simple-keyboard to v3.8.233 (#4081)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency @rsbuild/plugin-sass to v2.0.1 (#4075)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency @rsbuild/plugin-svgr to v2.0.5 (#4076)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update vitest monorepo to v4.1.10 (#4082)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency prettier to v3.9.5 (#4087)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Follow Redux Toolkit conventions (#4089)

* Rename useTypedSelector to useAppSelector

* Create useAppDispatch hook

* Update ESLint import rule

* Migrate some components to use useAppDispatch

* Migrate more components to use useAppDispatch

* Migrate even more files to use useAppDispatch

* Fix imports

* Fix errors post-rebase

* Clean up tests

* Remove duplicated code

* Fix remaining errors

* Migrate conflicting files

* Undo incorrect change post-rebase

* Fix remaining error

* Fix ESLint errors

* Set up Knip for codebase maintenance (#4052)

* Install knip

* Deduplicate dependencies

* Add knip config

* Delete some unused files

* Delete FaceAPI side content

* Delete editable test case side content

* Remove unused export

* Clean up config warnings

* Address phantom dependencies

* Add knip docs in README

* Fix lockfile post-rebase

* Reformat README

* Resolve README discrepancies

* Fix README confusion

* Migrate from Prettier to oxfmt (#4090)

* Update .gitattributes for LF normalization

* Install oxfmt

* Migrate Prettier config to oxfmt

* Add ignore patterns

* Update VSCode configuration

* Reformat files

* Update package scripts and hooks

* Reformat Renovate config

* Group rsbuild ecosystem updates together

* Refactor SICP/SICPy layout hooks and add caching (#4092)

* Move useLocalStorageState to separate file

* Move local storage helpers to hook file

* Fix util duplication and match for parity

* Fix typo

* Standardize file layout

* Improve hook safety

* Use hook instead of low level effects

* Create SICP query keys

* Create SICP query hooks

* Use new hooks to reduce duplication

* Add special handling for index page

* Rename hook to be textbook agnostic

* Use global object to avoid stale references

* Fix loading edge case

* Prefer supplier function for performance

* feat: move Chatbot from JS layout to Python layout

* refactor(sicp): move Chatbot from JS layout to Python layout

* style(sicp): format Python layout after move

* Fix link issues

* Fix react-router useParams typing in sicpjs layout

* Fix react-router useParams typing and import formatting in sicpy layout

* Format sicpy layout with oxfmt

* Fix crash when stored playground language state is invalid (#3646)

* Fix crash when stored playground language state is invalid

Validate the stored chapter+variant combination against ALL_LANGUAGES
when loading from localStorage. Falls back to the default language config
if the combination is invalid (e.g. chapter 1 + explicit-control).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fall back to defaultLanguageConfig for invalid stored language state

When the stored chapter+variant combination is not in ALL_LANGUAGES,
reset to `defaultLanguageConfig` (a known-valid pair) rather than to the
individual default context fields, so the fallback is explicitly "the
default language config" as the issue describes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com>

* docs: add local backend setup and py-slang testing instructions (#4070)

* docs: add local backend setup steps to README

Fills the gap between "point REACT_APP_BACKEND_URL at localhost:4000"
and an actually-running backend, which otherwise just shows the
"under maintenance" page. Also corrects the postgres docker command
from the backend repo's own README, which has a stray -e flag before
-p 5432:5432 that prevents the port from actually being published.

* docs: add "Running your own py-slang" section

Mirrors the existing "Running your own js-slang" section, linking
out to py-slang's own README (added in a companion PR there) rather
than duplicating the instructions here.

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* Update README.md

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Shrey Jain <101659971+Shrey5132@users.noreply.github.com>
Co-authored-by: Martin Henz <henz@comp.nus.edu.sg>

* Update README.md

* feat(cseMachine): rename global/program frames and annotate globals (#4068)

* feat(cseMachine): rename global/program frames and annotate globals

- Rename the top two CSE Machine frame labels to match Python's LEGB
  terminology: "Global" -> "Built-in functions", "Program" -> "Globals"
  (#4042).
- Thread the new optional CseSerializedEnvFrame.globalNames field
  (companion source-academy/plugins + source-academy/py-slang PRs)
  through CseSnapshotAdapter onto the fake Environment, and render it
  as a "globals: x, y" annotation at the top of a call frame the
  moment it's created, so it's clear those names resolve straight to
  the global frame instead of the usual enclosing-scope chain (#4041).
- Add @sourceacademy/common-cse-machine as a direct dependency: it's a
  peerDependency of @sourceacademy/web-cse-machine whose types
  frontend re-exports and uses directly, but frontend never installed
  it itself, so those types were silently resolving to `any` under
  skipLibCheck.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(cseMachine): stack globals annotation below frame name, fix overflow, and occlude arrows behind header text

- Move the globals annotation to its own row stacked below the frame
  name (rather than beside it or inside the box as a binding), since
  frame names are arbitrary function names of unpredictable length
  and can't safely share a row with anything else.
- Widen the frame box to fit the annotation label the same way binding
  text already widens it (capped at the same FrameDefaultWidth), so it
  no longer gets ellipsis-truncated or rendered left of the frame's
  own border when the label is longer than the box's other content.
- Add an opaque background rect behind the frame name and the globals
  annotation so the parent/tail arrow passing through that header gap
  is fully hidden behind the text, instead of showing through the
  empty space between glyphs (arrows are already layered behind
  content, but plain Konva text has no opaque fill of its own).

Closes #4069.

* fix(cseMachine): fix assignment animation crash for statement-only-assignment languages

InstrType.ASSIGNMENT constructed AssignmentAnimation with the
*current* (post-assignment) top-of-stash item, which is only valid
because a JS assignment *expression* re-pushes its value. A Python
assignment *statement* pushes nothing back, so the stash is genuinely
empty afterwards, the non-null assertion lied, and the constructor
threw reading `stashItem.index` on undefined — silently, since the
try/catch around CseAnimation.updateAnimation() had no logging.

Use the *previous* stash's top item instead, matching how
InstrType.POP (also a pure consumer) already does this correctly.
Also add the missing console.error so future animation regressions
in this path aren't invisible again.

Closes #4072.

* fix(cseMachine): stop the arrow-occlusion patch from being wider than the text it backs

Frame.tsx's header-text backing rects used Text.width(), which floors
at Config.TextMinWidth (30px) — a minimum meant for binding key/value
alignment, unrelated to how wide the text visually is. For a short
name like a one-letter function ("f", 9px of actual ink), that floor
is wider than Config.FramePaddingX (20px, the arrow's x-offset), so
the padded-out rect covered the arrow well past where the glyph
itself ends — an oversized, unnecessary gap in the arrow line even
when the text was nowhere near wide enough to actually cross it.

Use getTextWidth(text) directly instead, sized to what's actually
rendered.

* chore: bump common-cse-machine/web-cse-machine, drop published-type workaround casts

Now that @sourceacademy/common-cse-machine@0.2.0 (declaring
globalNames) and @sourceacademy/web-cse-machine@2.0.0 are actually
published, bump both dependencies and simplify the read sites that
were casting around the not-yet-published type.

Frame.tsx still needs one cast: `Env` (js-slang's real Environment
type) has no globalNames field regardless of any package version,
since it's an ad-hoc property CseSnapshotAdapter.ts stashes onto the
fake Environment object — updated that comment to say so accurately.

Also adds @sourceacademy/common-cse-machine to .yarnrc.yml's
npmPreapprovedPackages, matching the existing entries for
web-cse-machine/conductor/js-slang: yarn's npmMinimalAgeGate (3-day
quarantine on freshly published versions) was blocking install of the
version just published in the companion plugins release.

* fix(cseMachine): scope the Python LEGB frame rename to the Conductor snapshot path only

The #4042 rename ("Global" -> "Built-in functions", "Program" ->
"Globals") was motivated by Python's LEGB terminology, but Frame.tsx's
frameNames lookup applied unconditionally, so the legacy non-conductor
path (conductor.enable off, real js-slang interpreter) picked it up
too — a real regression against js-slang's own established naming,
confirmed live and flagged by Gemini's review on this PR.

Gate the rename on Layout.snapshotMode, which is true only while
rendering a Conductor snapshot (currently Python-only) and false for
the entire legacy path — evalCodeSaga never calls
CseMachine.renderSnapshot() when conductor.enable is off, so this is
a reliable, already-existing signal rather than a new one.

Other changes in this PR are left as-is: the globalNames annotation
is already inert for legacy js-slang Environments (they never carry
that ad-hoc field), the arrow-occlusion background-rect fix is a
general visual correctness fix unrelated to any language, and the
assignment-animation and frame-transition-timing fixes don't touch
frame naming at all.

Adds regression coverage on both sides of the gate: a real js-slang
EnvTree rendered outside snapshot mode keeps "Global"/"Program", and
a Python snapshot still gets "Built-in functions"/"Globals".

* fix(cseMachine): apply center-alignment offset to the globals annotation too

reassignCoordinatesX applied the center-alignment textOffset to the
frame name but not to _globalNamesText, leaving the annotation
flush-left under a centered name whenever center-alignment is on.
Flagged independently by CodeRabbit and Shrey's review on #4068.

Also extracts the headerRowHeight formula (duplicated between the
constructor and reassignCoordinatesY) into a single module-level
constant, per CodeRabbit's nitpick.

Added a regression test; confirmed it fails without the fix (30 vs
80) and passes with it.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Shrey Jain <101659971+Shrey5132@users.noreply.github.com>

* Show a popup for input() requests instead of hanging (py-slang#190) (#4084)

* Show a popup for input() requests instead of hanging (py-slang#190)

Wires up Conductor's new receiveInputRequest hook: a handleInputRequest
saga listens for input requests from the runner, shows the existing
PromptDialog popup, and answers via hostPlugin.sendInput. The home tab
and the viz CSE Machine tab share the same evalCodeConductorSaga run,
so this single fix covers both surfaces from the issue.

Also adds isWaitingForInput workspace state, used to suspend the
execTime watchdog while a popup is open so a user typing an answer
doesn't get their run force-stopped mid-prompt.

Points @sourceacademy/conductor at the local checkout (portal:) for
dev while the protocol change there is unreleased; repin to a real
published version once that lands upstream.

* chore: deps update

* Fix uncaught rejection when the input popup is dismissed via onClose

showSimplePromptDialog's underlying promise rejects if the dialog is
dismissed via its onClose path (e.g. clicking outside) rather than a
button response. The inner try/finally in handleInputRequest reset
isWaitingForInput on that path but didn't catch the rejection, so it
escaped the saga's while(true) loop and killed the task permanently -
leaving the runner blocked forever with no way to answer this or any
future input() call. Catch it and send an empty string, same as an
explicit Cancel.

Caught by gemini-code-assist review on PR #4084.

* Repin @sourceacademy/conductor to the published npm 0.6.0

The npm publish for conductor 0.6.0 landed, so the git-tag stopgap
pin isn't needed anymore - back to the normal semver-via-npm-registry
pin this repo used before. Verified: tsc --noEmit clean against the
real published package.

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>

* Updated the format in which language is sent to backend

* Remove chatbot from JS layout; stop sending language in chat APIs

* Remove unused scrollRefIntoView; inline scroll, clean unused Sicp layout symbols; add host plugin callbacks; relax CseMachineHostPlugin signature

* Fix language semantics

* Fix conflict

* Update README.md

* Fix conflict

* Fix conflict

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel (#4083)

* feat(cseMachine): wire breakpoint step indices from the CSE snapshot channel

CseMachineHostPlugin.receiveSnapshots now takes breakpointSteps as a second
argument (paired with the @sourceacademy/web-cse-machine protocol change),
and handleCseSnapshots dispatches WorkspaceActions.updateBreakpointSteps
with it alongside the existing snapshot/stepsTotal updates. The double-
chevron breakpoint-navigation buttons in SideContentCseMachine already
read props.breakpointSteps and are rendered unconditionally for the
conductor/Python flow — they just needed real data instead of an empty
array. No new UI. Depends on a py-slang change that detects breakpoint()
in the CSE machine (source-academy/py-slang issue #189).

package.json/yarn.lock currently point @sourceacademy/common-cse-machine
and @sourceacademy/web-cse-machine at a local yarn-link portal to
/home/vakshay/plugins for development; swap for a real published semver
range once that plugins PR merges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: revert local yarn-link dev scaffolding for cse-machine packages

package.json/yarn.lock were pointing @sourceacademy/common-cse-machine and
@sourceacademy/web-cse-machine at a local yarn-link portal
(/home/vakshay/plugins) for development. That doesn't resolve for anyone
else or CI, and web-cse-machine can't be pinned to the plugins branch via
Yarn's git+workspace protocol either (it depends on the sibling
common-cse-machine workspace via workspace:*, and Yarn's single-workspace
git fetch doesn't build sibling workspaces first, so the prepack/rollup
step can't find its dist).

Reverting to the original published semver ranges so this PR's dependency
diff is clean. This PR depends on source-academy/plugins#55 (adds
breakpointSteps to the CSE snapshot protocol) merging and publishing to
npm before it will typecheck/build — opening as a draft until then.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: bump cse-machine deps to the published versions with breakpointSteps

@sourceacademy/common-cse-machine ^0.2.0 -> ^0.3.0 and
@sourceacademy/web-cse-machine ^2.0.0 -> ^3.0.0, now that
source-academy/plugins#55 has published (bumping the same two packages,
plus runner-cse-machine, to carry breakpointSteps over the CSE snapshot
channel). Unblocks this PR: tsc --noEmit passes against the real published
CseMachineHostPlugin.receiveSnapshots(snapshots, breakpointSteps) signature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore: formattin

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: henz <henz@comp.nus.edu.sg>

* Updated the format in which language is sent to backend

* Remove chatbot from JS layout; stop sending language in chat APIs

* feat: move Chatbot from JS layout to Python layout

# Conflicts:
#	src/new_routes/sicpjs/_layout.tsx
#	src/new_routes/sicpy/_layout.tsx

* refactor(sicp): move Chatbot from JS layout to Python layout

* style(sicp): format Python layout after move

* Fix link issues

* Fix react-router useParams typing in sicpjs layout

* Fix react-router useParams typing and import formatting in sicpy layout

* Format sicpy layout with oxfmt

* Changes as requested

* Configure Louis chatbot per course

* Format SICP JavaScript layout

* Remove unrelated PR artifacts

* fixed code review comment

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Martin Henz <henz@comp.nus.edu.sg>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com>
Co-authored-by: Akshay <131676168+Akshay-2007-1@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Shrey Jain <101659971+Shrey5132@users.noreply.github.com>
Co-authored-by: Zhang Yilin <yilinzh54@gmail.com>
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.

2 participants