Skip to content

FE-1262: Unbreak array methods in scenario code, add a range() helper, and surface scenario compile errors - #9092

Open
kube wants to merge 4 commits into
mainfrom
cf/fe-1262-scenario-sandbox-range-helper
Open

FE-1262: Unbreak array methods in scenario code, add a range() helper, and surface scenario compile errors#9092
kube wants to merge 4 commits into
mainfrom
cf/fe-1262-scenario-sandbox-range-helper

Conversation

@kube

@kube kube commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

🌟 What is the purpose of this PR?

Scenario "Initial State as code" silently did nothing for everyday code: the evaluation sandbox masked .constructor on built-in prototypes with a throwing getter, which broke Array.prototype.map/filter/slice/concat/flatMap (they read the array's constructor via the spec's ArraySpeciesCreate) — and the resulting compile errors were then discarded by SimulationProvider, so nothing appeared in the UI.

This PR unbreaks those array methods, adds a Python-style range() helper for scenario code, and makes scenario compile errors visible in Simulation Settings.

What scenario authors can now write:

// "Define as code" initial state — previously failed silently:
return {
  Space: range(scenario.number_of_satellites).map((i) => ({
    x: 10 * i, y: 10 * i, direction: 0, velocity: 0,
  })),
};
range(4)          // [0, 1, 2, 3]
range(2, 6)       // [2, 3, 4, 5]   (end-exclusive)
range(0, 10, 2)   // [0, 2, 4, 6, 8]
range(5, 0, -1)   // [5, 4, 3, 2, 1]

And when a scenario fails to compile, a red callout below the Scenario dropdown now lists each error instead of silently falling back to the manual marking.

🔗 Related links

🔍 What does this change?

  • runSandboxed (petrinaut-core): the .constructor mask on built-in prototypes returns undefined instead of throwing. Spec-internal species lookups fall back to the default Array, so array methods work; the ({}).constructor.constructor(...) escape still dead-ends (undefined.constructor is a TypeError). All pre-existing escape tests pass unchanged.
  • New range(end) / range(start, end, step?) helper injected into scenario parameter-override expressions, per-place expressions, and initial-state code. Frozen, validated (finite args, non-zero step), and capped at 1,000,000 elements so oversized ranges fail fast instead of freezing render-time compilation.
  • The helper is declared in the scenario session virtual files, so the Monaco editors autocomplete and type-check it (with hover docs).
  • SimulationProvider no longer discards compileScenario errors: they are exposed as scenarioCompilationErrors on SimulationContext and rendered as an error callout in Simulation Settings.
  • The LSP no longer lints an empty "Define as code" editor: the empty body used to be wrapped in function __check(): InitialState {}, surfacing a cryptic TS2355 ("A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.") the moment the mode was toggled on. Empty code is a runtime no-op, so the virtual file is now skipped — same treatment as empty parameter-override and per-place expressions. Regression-tested end-to-end through the language service, including the satellites-style range(...).map(...) code checking clean.
  • New bundled "Pre-deployed Constellation" scenario on the Probabilistic Satellite Launcher example: initial state authored in code mode, building an evenly-spaced ring of satellites via range(scenario.number_of_satellites).map(...) at planet_radius + scenario.initial_altitude. Serves as an in-product showcase of the new helper; covered by an example-level compile test and verified live (8 satellites at frame 0, scaling with the scenario parameters).
  • User docs (docs/scenarios.md) updated: range documented in both authoring modes, error callout documented under "Running a scenario".

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

⚠️ Known issues

  • Unknown place names returned from initial-state code are still skipped silently at runtime (pre-existing, documented behaviour; the Monaco editor flags them at authoring time).
  • The provider-level error plumbing has no dedicated React test (no existing harness for SimulationProvider); behaviour is covered by the core compileScenario tests plus the rendering being a simple conditional.

🛡 What tests cover this?

  • New helpers.test.ts: range semantics (arities, negative/non-integer steps, empty ranges, validation errors, length cap, works under runSandboxed).
  • compile-scenario.test.ts: regression test for the exact Array.from({ length }).map(...) shape that used to fail; range in code mode, override expressions, and per-place expressions; cap/zero-step error reporting; helper escape-safety (range.constructor === undefined); all 8 pre-existing sandbox escape tests unchanged and passing.

❓ How to test this?

  1. Checkout the branch, run the Petrinaut demo (yarn dev in libs/@hashintel/petrinaut).
  2. Create a scenario with a scenario parameter n and "Define as code" initial state returning { SomePlace: range(n).map((i) => ({ ... })) }.
  3. In Simulation Settings, select the scenario and change n — the compiled initial state updates and the simulation uses it.
  4. Introduce an error (e.g. range(0, 5, 0)) and confirm a red callout appears below the Scenario dropdown listing the error.

🤖 Generated with Claude Code

@kube kube self-assigned this Jul 24, 2026
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview Aug 4, 2026 6:03pm
petrinaut Error Error Aug 4, 2026 6:03pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Aug 4, 2026 6:03pm

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/apps > hash.design Affects the `hash.design` design site (app) labels Jul 24, 2026
@vercel
vercel Bot temporarily deployed to Preview – petrinaut July 24, 2026 01:17 Inactive
@vercel
vercel Bot temporarily deployed to Preview – petrinaut July 24, 2026 02:12 Inactive
@vercel
vercel Bot temporarily deployed to Preview – petrinaut July 24, 2026 09:54 Inactive
kube added 4 commits August 4, 2026 19:52
…surface compile errors

The scenario sandbox masked `.constructor` on built-in prototypes with a
throwing getter, which broke `Array.prototype.map`/`filter`/`slice`/
`concat`/`flatMap` in user-authored scenario code (ArraySpeciesCreate reads
the array's constructor). The getter now returns `undefined` instead — the
spec falls back to the default Array while the constructor-walk escape
still dead-ends.

Adds a Python-style `range(end)` / `range(start, end, step?)` helper to
scenario expressions and initial-state code, typed in the Monaco editors
via the scenario session virtual files, with a length cap so oversized
ranges fail fast instead of freezing render-time compilation.

SimulationProvider previously discarded scenario compilation errors,
making failures invisible; they are now exposed on SimulationContext and
rendered as an error callout in Simulation Settings.
An empty "Define as code" editor produced a cryptic TS2355 ("A function
whose declared type is neither 'undefined', 'void', nor 'any' must
return a value.") the moment the mode was toggled on, because the empty
body was wrapped in `function __check(): InitialState {}`. The runtime
compiler explicitly ignores empty code, so the virtual file is now
skipped for blank content — the same treatment empty parameter-override
and per-place expressions already get.

Also adds language-service regression coverage asserting that the
satellites-style initial-state code (range + scenario + parameters)
type-checks cleanly end-to-end.
…ample

A fifth bundled scenario for the Probabilistic Satellite Launcher whose
initial state is authored in code mode: range(...).map(...) builds an
evenly-spaced ring of satellites from two scenario parameters
(number_of_satellites, initial_altitude), on top of the net's
planet_radius. Doubles as an in-product showcase of the range() helper
and code-mode initial state introduced in this branch.

Covered by an example-level test compiling the scenario end-to-end with
default and user-supplied parameter values.
@kube
kube force-pushed the cf/fe-1262-scenario-sandbox-range-helper branch from 1cec691 to d0d473d Compare August 4, 2026 17:52
@vercel
vercel Bot temporarily deployed to Preview – petrinaut August 4, 2026 17:53 Inactive
@kube
kube requested review from CiaranMn and YannisZa August 4, 2026 17:53
@kube
kube marked this pull request as ready for review August 4, 2026 17:53
Copilot AI balanced review requested due to automatic review settings August 4, 2026 17:53
@cursor

cursor Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches user-code sandboxing and synchronous scenario compilation on the UI thread; behavior changes are well-tested but affect security-sensitive evaluation paths.

Overview
Scenario expressions and Define as code initial state no longer break when authors use everyday array methods (.map, .filter, .slice, .concat, .flatMap): the user-code sandbox still masks .constructor on built-ins for escape hardening, but the getter now returns undefined so spec array “species” lookups keep working instead of throwing.

Authors get a Python-style range() helper in parameter overrides, per-place expressions, and initial-state code (with Monaco typings via scenario virtual files), capped at 1M elements so bad parameters fail fast on the UI thread. Simulation Settings shows a red callout when the selected scenario fails to compile, and overrides/initial state are not applied until fixed; the LSP skips linting an empty code-mode body so toggling the mode does not surface a bogus “must return a value” error.

The Probabilistic Satellite Launcher example adds a Pre-deployed Constellation scenario that builds a ring of satellites with range(...).map(...); docs and changesets are updated accordingly.

Reviewed by Cursor Bugbot for commit d0d473d. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI 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.

Pull request overview

Adds safer scenario authoring, a typed range() helper, visible compilation errors, and a bundled constellation example.

Changes:

  • Restores built-in array methods and adds validated range() support.
  • Exposes scenario compilation failures in Simulation Settings.
  • Updates LSP behavior, tests, documentation, and examples.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
simulation-settings.tsx Displays scenario compilation errors.
playback-settings-menu.stories.tsx Updates mocked simulation context.
simulation/provider.tsx Exposes compilation errors.
simulation/context.ts Adds compilation errors to context.
playback/provider.test.tsx Updates test context fixture.
use-petrinaut-mutations.test.tsx Updates test context fixture.
use-petrinaut-commands.test.tsx Updates test context fixture.
docs/scenarios.md Documents helpers and errors.
docs/examples.md Documents constellation example.
scenario/helpers.ts Implements range().
scenario/helpers.test.ts Tests helper behavior.
compile-scenario.ts Injects scenario helpers.
compile-scenario.test.ts Tests arrays, helpers, and errors.
sandbox.ts Makes constructor masking array-compatible.
scenario-session.test.ts Tests LSP scenario diagnostics.
generate-virtual-files.ts Declares helpers and skips empty code.
satellites-launcher.ts Adds constellation scenario.
satellites-launcher.test.ts Tests constellation compilation.
satellites-pre-deployed-constellation.md Adds example changeset.
fe-1262-scenario-range-helper.md Adds feature changeset.

Comment on lines +57 to +61
const values: number[] = [];
for (let i = 0; i < length; i++) {
values.push(from + i * by);
}
return values;
Comment on lines +511 to +515
if (
session.initialStateAsCode &&
session.initialStateCode !== undefined &&
session.initialStateCode.trim() !== ""
) {
Comment on lines +561 to +562
direction: angle,
velocity: 0,

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d0d473d. Configure here.

y: Math.sin(angle) * distanceToCenter,
direction: angle,
velocity: 0,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Constellation starts with zero velocity

Medium Severity

The new Pre-deployed Constellation scenario seeds Space tokens with velocity: 0. Space runs the satellite orbit ODE, which divides by velocity when updating direction, so pressing Play immediately produces non-finite token state and the showcase constellation falls apart instead of orbiting.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d0d473d. Configure here.

@YannisZa

YannisZa commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@kube the copilot comments look sensible. Would you like to address them before me or Ciaran review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash.design Affects the `hash.design` design site (app) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

3 participants