Skip to content

FE-591: Add basic Metrics with native timeline views#8633

Merged
kube merged 9 commits intomainfrom
cf/fe-591-implement-basic-metrics
Apr 28, 2026
Merged

FE-591: Add basic Metrics with native timeline views#8633
kube merged 9 commits intomainfrom
cf/fe-591-implement-basic-metrics

Conversation

@kube
Copy link
Copy Markdown
Collaborator

@kube kube commented Apr 15, 2026

🌟 What is the purpose of this PR?

Adds a first iteration of Metrics — user-authored functions that run over each simulation frame and plot a number on the timeline chart. The model and authoring surface are intentionally simple: this format is expected to change once Stochastic Simulation / Experiments and the Intermediate Representation land, at which point metrics will be re-expressed against the IR rather than raw place state. This iteration unblocks UI and product feedback in the meantime.

Alongside metrics, the timeline gets two built-in views ("Tokens per type", "Transition firings") so the chart is useful out of the box, and the metric code editor is wired to a temporary LSP session for live type-checking.

🔗 Related links

🔍 What does this change?

  • Metric model. New Metric type on SDCPN (Zod-validated) with file-format round-tripping; legacy files default to an empty list.
  • Compiler. compileMetric mirrors compileScenario's hardening (strict mode, shadowed globals, frozen prototype-less state). buildMetricState reshapes a frame buffer into named tokens per place so author code can read e.g. state.places.Infected.count.
  • Timeline picker. "Tokens per place" / "Tokens per type" / each metric, surfaced through a discriminated TimelineView union so native views and user metrics share one state field. useStreamingData drives all three modes off a single { series, extract } config.
  • Native views.
    • Tokens per type aggregates token counts by color (untyped places collapse into a single series).
    • Transition firings interpolates firingCount between firings with a 4 s trailing-window delta and a small EWMA, producing a continuous rate instead of a step function; per-transition state resets on simulation restart.
  • Drawers. CreateMetricDrawer / ViewMetricDrawer with a multi-line code editor, live compile preview, and CRUD wired into MutationContext under the scenarioMutate guard so edits work in simulate mode.
  • Metric LSP session. Each open metric form spawns a temp _temp/metrics/{sessionId}/code.ts virtual file wrapped as function __metric(state: MetricState): number { … }. MetricState is generated from the current SDCPN so place names and colored token shapes are type-checked. The drawer footer surfaces LSP diagnostics alongside form/compile errors and gates Save until they're resolved.
  • SimulateView. Metrics tab enabled with a MetricList; sidebar mode lifted to EditorContext so the Manage button can open the tab.
  • Example. SIR model gains an "Infected Fraction" metric, written against the LSP-typed state.places (no defensive optional chaining).

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

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

📜 Does this require a change to the docs?

  • are internal and do not require a docs change

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

  • do not affect the execution graph

⚠️ Known issues

  • The current Metric shape (raw JS body over state.places) is provisional and will be re-expressed once Stochastic Simulation / Experiments and the Intermediate Representation are in place.

🐾 Next steps

  • Re-target metrics at the IR once it lands.
  • Aggregate metrics across simulation runs (Experiments).

🛡 What tests cover this?

  • New unit tests for compileMetric (hardening, NaN / non-finite rejection, shadowed globals).
  • Existing simulation/timeline tests cover the picker, native views, and the useStreamingData refactor.

❓ How to test this?

  1. Open the Petrinaut demo and load the SIR example.
  2. Switch to Simulate mode and flip the timeline picker between Tokens per place, Tokens per type, Transition firings, and the Infected Fraction metric.
  3. Open the metric drawer and edit the code: typing state.places.Infect should autocomplete; introducing a type error should disable Save and show a red diagnostic in the footer.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 15, 2026

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

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview, Comment Apr 28, 2026 10:24am
hashdotdesign-tokens Ready Ready Preview, Comment Apr 28, 2026 10:24am
petrinaut Ready Ready Preview Apr 28, 2026 10:24am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign Ignored Ignored Preview Apr 28, 2026 10:24am

@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 labels Apr 15, 2026
Copy link
Copy Markdown
Collaborator Author

kube commented Apr 15, 2026

@kube kube changed the title Add Metrics: timeline picker, compiler, and drawer FE-591: Add Metrics: timeline picker, compiler, and drawer Apr 15, 2026
@kube kube force-pushed the cf/fe-591-implement-basic-metrics branch from 9033e93 to 7e958f5 Compare April 16, 2026 09:09
@kube kube force-pushed the cf/fe-591-implement-basic-metrics branch 2 times, most recently from ea7cc42 to 332c07d Compare April 16, 2026 11:13
@kube kube force-pushed the cf/fe-532-implement-basic-experiment branch from 5fe5a8a to 265f689 Compare April 16, 2026 11:13
Base automatically changed from cf/fe-532-implement-basic-experiment to main April 16, 2026 12:31
@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) area/apps > hash.design Affects the `hash.design` design site (app) labels Apr 16, 2026
kube added 4 commits April 27, 2026 14:58
Introduces user-authored metric functions that run over each simulation
frame and plot a single number on the timeline chart.

- Schema: Metric type on SDCPN with Zod validation; round-trips via the
  file format (legacy files default to an empty list).
- Compiler: compileMetric mirrors compileScenario's hardening (strict
  mode, shadowed globals, frozen prototype-less state), plus a
  buildMetricState helper that reshapes a frame's buffer into named
  tokens per place.
- Timeline: metric picker in the header (Default + each metric) with
  Create/Edit/Manage IconButtons matching the scenarios picker pattern.
  When a metric is picked, useStreamingData streams a single series
  computed per frame; runtime errors render as NaN gaps, compile errors
  replace the chart with a message.
- Drawers: CreateMetricDrawer and ViewMetricDrawer with a multi-line
  CodeEditor for the function body and live compile preview in the
  footer. Metrics CRUD added to MutationContext under the scenarioMutate
  guard so edits work in simulate mode.
- SimulateView: Metrics tab enabled with a MetricList; sidebar mode
  lifted to EditorContext so the Manage button can open the tab.
- Example: SIR model gains an "Infected Fraction" metric.
- Add a "Metric" label and widen the timeline picker to match the
  Scenario picker in simulation settings.
- Truncate Select dropdown labels with ellipsis on overflow instead of
  wrapping, by applying min-width:0 on items and an ItemText style that
  clips the default label.
- Dedent the SIR "Infected Fraction" example code so it renders at
  column 0 in the editor.
Introduces a second built-in view alongside the existing per-place
breakdown, aggregating token counts by color type. Places with no color
are collapsed into a single "Untyped" series.

- Replaces `timelineMetricId` with a discriminated `TimelineView` union
  so native views and user metrics share one state field.
- Refactors useStreamingData to drive all three modes off a single
  `{ series, extract }` config, unifying the frame loop.
- Picker now shows "Tokens per place", "Tokens per type", then each
  metric; the Edit icon is only relevant for user-defined metrics.
- Legend is gated on series count (>= 2) rather than mode, so a
  single-series per-type run also hides it.
Shows per-transition firing activity as a smoothed time-series:

- Cumulative firingCount is interpolated between firings using
  timeSinceLastFiringMs and the last observed inter-firing interval,
  producing a continuous piecewise-linear ramp instead of a step function.
- A 4 s trailing-window delta converts the interpolated cumulative into
  a local firing rate.
- An EWMA low-pass (α = 0.15) on the output damps residual stochastic
  noise from variable inter-firing intervals.
- All per-transition state (cumulative histories, interval estimates,
  EWMA accumulators) resets automatically on simulation restart via
  time-regression detection.
@kube kube force-pushed the cf/fe-591-implement-basic-metrics branch from 332c07d to 0ae8dcc Compare April 27, 2026 14:00
@github-actions github-actions Bot removed area/deps Relates to third-party dependencies (area) area/apps > hash.design Affects the `hash.design` design site (app) labels Apr 27, 2026
Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

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

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread libs/@hashintel/petrinaut/src/simulation/compile-metric.ts
- Lift the .constructor-chain block (`runSandboxed`) and shadowed-globals
  list to a shared `simulation/sandbox.ts` and apply them to compileMetric
  so it matches compileScenario's hardening. Adds a regression test that
  `({}).constructor.constructor("…")()` is rejected.
- Reject empty metric code via a form-level validator so the Save button
  surfaces "Metric code is required" instead of letting the form save a
  metric that compileMetric would later reject at runtime.
- Gate ViewMetricDrawer's `open` on the selected metric still existing —
  switching the timeline picker while the edit drawer is open no longer
  leaves an empty overlay behind.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
alex-e-leon
alex-e-leon previously approved these changes Apr 27, 2026
`summarizeMetricLspErrors` previously matched any URI under the global
`_temp/metrics/` prefix, so a sibling drawer's session (e.g. a Create
drawer still mounted during a View drawer's open animation) could surface
diagnostics that block Save in the wrong drawer. The helper now requires
a sessionId and filters on the per-session prefix instead.

To plumb the ID, `useMetricLspSession` is now called in the drawer
parents (CreateMetricDrawer, ViewMetricContent) rather than inside
MetricFormBody, and the session ID flows to both the body (for the
editor `path`) and the footer (for the diagnostics summary).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kube kube requested a review from alex-e-leon April 27, 2026 18:13
Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 d373a64. Configure here.

Comment thread libs/@hashintel/petrinaut/src/state/mutation-provider.tsx
@kube kube added this pull request to the merge queue Apr 28, 2026
Merged via the queue into main with commit fe08932 Apr 28, 2026
43 checks passed
@kube kube deleted the cf/fe-591-implement-basic-metrics branch April 28, 2026 15:12
@hashdotai hashdotai mentioned this pull request Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

2 participants