Skip to content

Stepper: add a fixed-text hover popover capability to SyntaxProfile - #92

Merged
martin-henz merged 2 commits into
mainfrom
stepper-builtin-hover-popup
Aug 6, 2026
Merged

Stepper: add a fixed-text hover popover capability to SyntaxProfile#92
martin-henz merged 2 commits into
mainfrom
stepper-builtin-hover-popup

Conversation

@martin-henz

Copy link
Copy Markdown
Member

Summary

  • SyntaxProfile.functionValues already gives a named function value (a def/lambda) a mu-term + hover popover showing its body. There was no equivalent for something that isn't a steppable function value at all — a builtin referenced as a bare value, or a name imported from a module — since there is no body to expand.
  • Adds SyntaxProfile.hoverText: a language declares a node type + the (dotted) property holding an already-formatted, static line of popover text (e.g. "built-in function print"). The host wraps that node's normal inline rendering — unchanged — in a Popover showing the text. Fully generic: any language opts in per node type, no per-language host code, mirroring how functionValues itself is implemented.
  • Needed by py-slang#404 (a builtin used as a bare value has no hover at all today) and the pop-up half of py-slang#406 (imported module functions).
  • @sourceacademy/common-stepper and @sourceacademy/web-stepper both bumped (patch) via changeset — purely additive, backward-compatible.

Test plan

  • yarn build — clean (the two pre-existing TS2322 warnings on renderFunctionArguments are unrelated, reproduce identically on main)
  • yarn eslint src/common/stepper/src/index.ts src/web/stepper/src/SubstVisualizer.tsx — clean
  • yarn test — 181/181 passing

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hv4gVtVgskzLuPpPh3woaE

A named function value (SyntaxProfile.functionValues) already gets a
mu-term + hover popover showing its body, but there was no way to
show a popover for something that isn't a steppable function value at
all -- e.g. a builtin referenced as a bare value, or a name imported
from a module -- since there is no body to expand.

Add SyntaxProfile.hoverText: a node type can declare a property
holding an already-formatted, static line of text (e.g. "built-in
function print"); the host wraps that node's existing inline
rendering in a Popover showing it, without collapsing/replacing the
rendering the way functionValues does. Generic in the host -- any
language opts in per node type, no per-language host code -- exactly
mirroring how functionValues itself works.

Needed by py-slang#404 (a builtin used as a value has no hover at
all today) and the pop-up half of py-slang#406 (imported module
functions).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv4gVtVgskzLuPpPh3woaE
@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 32b2d3a

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
The package or glob expression "@sourceacademy/web-data-visualizer" 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

@martin-henz

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds optional SyntaxProfile.hoverText rules and generic web-stepper rendering for fixed-text hover popovers around matching inline node output. Adds patch-release changeset metadata for both stepper packages.

Changes

Hover text popovers

Layer / File(s) Summary
Profile hover text contract
src/common/stepper/src/index.ts
Adds HoverTextRule and the optional SyntaxProfile.hoverText rule list.
Web hover text rendering
src/web/stepper/src/SubstVisualizer.tsx, .changeset/stepper-hover-text-popover.md
Adds ProfileHoverTextPopover and wraps matching non-empty template output with lazy hover text rendering. Documents patch releases for both stepper packages.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SyntaxProfile
  participant ProfileRenderedNode
  participant ProfileHoverTextPopover
  SyntaxProfile->>ProfileRenderedNode: provide hoverText rules
  ProfileRenderedNode->>ProfileRenderedNode: match node type and textProp
  ProfileRenderedNode->>ProfileHoverTextPopover: wrap non-empty template output
  ProfileHoverTextPopover-->>ProfileRenderedNode: display fixed hover text
Loading

Suggested reviewers: shrey5132

Poem

A rabbit hops where syntax grows,
A hover card now softly shows.
Inline text stays in its place,
With helpful words beside its face.
Patch notes thump: the feature’s bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the addition of fixed-text hover popovers to SyntaxProfile.
Description check ✅ Passed The description accurately explains the new SyntaxProfile.hoverText capability, its use cases, compatibility, and test results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch stepper-builtin-hover-popup

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/web/stepper/src/SubstVisualizer.tsx (1)

960-980: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add focused coverage for the new rendering contract.

Test direct and dotted textProp values, missing or empty text, preserved inline template output, and the overlap behavior with functionValues.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/web/stepper/src/SubstVisualizer.tsx` around lines 960 - 980, Add focused
tests for the hover-text rendering logic around the profile hover rule: cover
direct and dotted textProp lookup, missing or empty text, preservation of the
template-generated inline output, and behavior when the node also matches
functionValues. Reuse the existing SubstVisualizer test setup and assert both
rendered content and Popover presence or absence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/web/stepper/src/SubstVisualizer.tsx`:
- Around line 960-980: Update the rendering flow around the function-value
handling and hoverText lookup so nodes matching both profile rules receive both
independent popover behaviors instead of returning before hoverText is
evaluated. Apply the fixed-text hover wrapper after the function-value branch,
or otherwise preserve both wrappers without early-skipping the hoverText logic;
use the existing hoverRule and ProfileHoverTextPopover symbols.

---

Nitpick comments:
In `@src/web/stepper/src/SubstVisualizer.tsx`:
- Around line 960-980: Add focused tests for the hover-text rendering logic
around the profile hover rule: cover direct and dotted textProp lookup, missing
or empty text, preservation of the template-generated inline output, and
behavior when the node also matches functionValues. Reuse the existing
SubstVisualizer test setup and assert both rendered content and Popover presence
or absence.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dfc1ca17-92f2-4d4c-95f8-d45851dc0c85

📥 Commits

Reviewing files that changed from the base of the PR and between 065da98 and 7233304.

📒 Files selected for processing (3)
  • .changeset/stepper-hover-text-popover.md
  • src/common/stepper/src/index.ts
  • src/web/stepper/src/SubstVisualizer.tsx

Comment thread src/web/stepper/src/SubstVisualizer.tsx Outdated
CodeRabbit caught this on #92: the hoverText popover check only ran
in the *else* branch (the plain-template render), so a node type
listed in both functionValues and hoverText would silently get only
the mu-term treatment -- contradicting the doc comment's own claim
that the two are independent. Move the hoverText wrap after the
if/else so it applies to whichever result either branch produced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv4gVtVgskzLuPpPh3woaE
@martin-henz

Copy link
Copy Markdown
Member Author

Fixed the actionable comment in 32b2d3a — the `hoverText` check now runs after the function-value if/else rather than only in the `else` branch, so a node type listed in both `functionValues` and `hoverText` gets both behaviors instead of silently losing the popover. Skipping the nitpick about adding unit tests for now — there's no existing test harness for web-stepper's React rendering to extend (would need to stand one up from scratch for this one suggestion).

@martin-henz
martin-henz merged commit 7226fba into main Aug 6, 2026
2 of 3 checks passed
@martin-henz
martin-henz deleted the stepper-builtin-hover-popup branch August 6, 2026 16:23
martin-henz added a commit to source-academy/py-slang that referenced this pull request Aug 7, 2026
* Stepper: give a built-in used as a value a hover popup

is_function(print) had no way to show what `print` (unapplied) is:
a bare built-in-named Identifier rendered like any other name, with
no popup, unlike a user-defined function's mu-term hover.

The obvious "hack" -- checking whether an Identifier's name matches a
builtin -- already exists throughout reduce.ts/builtins.ts for actual
evaluation (contractCall's built-in dispatch), but that check can't
be baked into a real node type at translation time: a builtin's name
can be locally shadowed (e.g. `def print(x): return is_function(print)`
is valid, if confusing, Python -- see the extended discussion on
py-slang#404), and py-slang's substitution model only resolves that
shadowing as reduction proceeds, not statically up front.

Add ast.ts's markBuiltins: a display-only pass getSteps.ts runs on
each step's tree, after reduction, right before serialization. A
bare Identifier surviving to that point genuinely refers to the
built-in of that name -- any shadowing binding has already
substituted the occurrence away by then, the same guarantee
contractCall already relies on -- except inside a not-yet-invoked
function value's own body, which is opaque cargo substitution hasn't
reached yet, so the walk tracks bound names there exactly like
substitute() does, to avoid mislabelling a future recursive
self-reference as a builtin.

Since this walk runs after a contraction's preRedex/postRedex are
already captured by identity from the pre-relabeling tree, it also
returns a correspondence map so getSteps.ts can translate a marker's
redex into its (possibly relabeled) counterpart -- needed whenever
the redex itself is what gets relabeled, e.g. breakpoint()'s own
callee.

Renders via the new Builtin node type + syntaxProfile.ts's hoverText
rule, added to @sourceacademy/common-stepper/web-stepper in
source-academy/plugins#92 (not yet published, hence the
`as unknown as SyntaxProfile` cast, dropped once that version ships).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv4gVtVgskzLuPpPh3woaE

* Fix markBuiltins: sibling/pre-declaration shadowing at un-reduced steps

CodeRabbit caught this on #409: markBuiltins walked a Program's or
BlockStatement's sibling statements independently, all sharing the
same `bound` set. At the very first ("Start of evaluation") step --
before any reduction/substitution has touched the tree -- this meant
a later statement referencing a name a not-yet-processed earlier
statement is about to declare (def print(x): ...\nprint(1)) got
wrongly relabeled Builtin, since nothing had "removed" that forward
reference from view yet the way live substitution eventually will.

Special-case Program/BlockStatement to walk their body left to right,
adding each statement's own declared name (a def's name, or a
`x = ...` target) to `bound` before walking the statements after it
-- simulating, for display, the same shadowing stepHead's actual
declare-and-substitute step will produce once reduction gets there.

Surfaced a second, related bug while fixing the first: a
VariableDeclaration wasn't special-cased at all, so its own `id`
(the declaration target itself, not a reference) got walked
generically by the default mapChildren fallback and could be
mislabelled too (e.g. a local `print = 5` inside a function body,
checked before that statement is even reached). Added a
VariableDeclaration case that only walks `init`, mirroring how
substitute() already treats VariableDeclarator's `id` the same way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv4gVtVgskzLuPpPh3woaE

* Fix prettier formatting in getSteps.test.ts

CI's format:ci caught two lines over the print width, left over from
an earlier commit. Applied prettier --write.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv4gVtVgskzLuPpPh3woaE

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.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.

1 participant