Skip to content

H-6819: Add status views, identities and a Kanban board to Petrinaut (experimental) - #9744

Draft
claude[bot] wants to merge 28 commits into
mainfrom
claude/h-6819-status-views
Draft

claude[bot] wants to merge 28 commits into
mainfrom
claude/h-6819-status-views

Conversation

@claude

@claude claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Requested by Ciaran Morinan · Slack thread

Before: Petrinaut shows where tokens sit in a net, but it cannot say which real-world thing a token is (this ticket, that machine) or what status that thing is in. Following one ticket through a workflow, or asking how long machines spend broken, means reading places and markings by hand.

After: A document can declare identities (which token attribute is the key of an instance) and status views (named labels over sets of places, with optional token conditions). With the experimental Settings → Labs → Modeling → Status views setting on, the editor shows a Status views tab, an identity picker on token type attributes, a Kanban board toggle on the canvas, and a Status changes column in Actual mode's Events tab. Every duration comes from the firing log.

🌟 What is the purpose of this PR?

Adds status views, identities, per-instance addressing, and a Kanban board to Petrinaut, behind an experimental setting.

A status view is a set of labels on place-sets: a token in any of a label's places carries that label, an optional boolean condition on the token narrows it, and at most one exit label covers instances whose token has left the view. An identity names the key attribute that makes tokens the same instance, across token types. Status and time-in-status are derived per instance from execution frames, as intervals with entry counts, in simulated or wall-clock time. The Kanban board projects the selected view onto columns. Actual-mode token recording moves to format v2 (firings carry token values so statuses replay); v1 recordings still load. Everything user-facing is behind the enableStatusViews setting (Settings → Labs → Modeling → Status views), off by default; the core types, actions and file format accept identities and status views regardless so documents round-trip.

🔗 Related links

🚫 Blocked by

  • Nothing.

🔍 What does this change?

  • @hashintel/petrinaut-core: identities and statusViews on the SDCPN schema, actions, normalizer and file format; identityRef key markers on colour elements; a shared status-condition compile pipeline that fails closed on broken conditions; per-instance status and time-in-state derived from execution frames; actual-mode recording format v2 with token values (v1 still parses); a Ticket Processing example; a status view on Production with Machine Failure.
  • @hashintel/petrinaut: Status views authoring panel in the Simulate view; identity picker on token type attributes; status badges and tinting on component-instance nodes; Kanban board view mode and toggle; Status changes column in the Actual-mode Events tab; enableStatusViews user setting with a settings-dialog row; docs (status-views.md, examples.md, visual-settings.md).
  • apps/petrinaut-website: the shared example URL contract names the Status views section and overlay.
  • The Load example menu lists Ticket Processing only while the setting is on.

Notes for reviewers

  • Actual-mode recording format bumps to v2 (token values on firings). v1 recordings are still accepted.
  • Canvas status badges on component-instance nodes are not gated: they show whenever a loaded document declares a status view.
  • The Deployment Pipeline example is unchanged so it stays GPU-eligible; the GPU readiness matrix test still lists it as gpu-ready.
  • Ticket Processing is exported from petrinaut-core (tests use it) but appears in the Load example menu only with the setting on. It is not in the website example catalog, which is a hand-listed static catalog.
  • The Kanban board and the Events column follow the document's status views; the Events column uses the first view.

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

  • None known beyond the notes above.

🐾 Next steps

  • Add Ticket Processing to the website example catalog once status views leave experimental.
  • Gate the canvas status badges behind the setting if wanted.

🛡 What tests cover this?

  • petrinaut-core: schema, normalizer, file-format round-trip (identities and status views), status-view validation and condition compile, per-instance status derivation, actual-mode replay, the exhaustive GPU readiness and metrics matrices, and the examples suite (now including Ticket Processing).
  • petrinaut: settings dialog default and row, Load example menu gating (load-example-menu.test.ts), status-view form and Kanban tests, editor-view lifecycle.
  • petrinaut-website: navigation and example URL contract tests.

❓ How to test this?

  1. Checkout the branch and run the Petrinaut app or website.
  2. Open Settings (gear) → Labs → Modeling and turn on Status views.
  3. Load example → Ticket Processing; the item is hidden while the setting is off.
  4. Press Play in Edit mode and switch the canvas to the Kanban board; cards move through Todo / In Progress / In Review / Blocked / Done and show time in status.
  5. Open Simulate → Status views to edit labels and token conditions; a broken condition shows a notice and matches nothing.
  6. In Actual mode, stream firings and check the Events tab's Status changes column.

📹 Demo

(none yet)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NfgLsCh68QZ1jWeq1ZFNoB


Generated by Claude Code

@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
hash Ready Ready Preview Sep 15, 2026 9:38pm UTC
petrinaut Ready Ready Preview Sep 15, 2026 9:38pm UTC
petrinaut-docs Ready Ready Preview Sep 15, 2026 9:38pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 15, 2026 9:38pm UTC

Request Review

@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 15.35%. Comparing base (fb728e4) to head (341110b).
⚠️ Report is 64 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #9744       +/-   ##
===========================================
- Coverage   65.90%   15.35%   -50.55%     
===========================================
  Files        1887      210     -1677     
  Lines      198443     6677   -191766     
  Branches     8248     1154     -7094     
===========================================
- Hits       130774     1025   -129749     
+ Misses      66139     5551    -60588     
+ Partials     1530      101     -1429     
Flag Coverage Δ
apps.hash-ai-worker-py ?
apps.hash-ai-worker-ts ?
apps.hash-api 15.35% <ø> (ø)
apps.hash-graph ?
backend-integration-tests ?
blockprotocol.type-system ?
deer ?
error-stack ?
local.claude-hooks ?
local.harpc-client ?
local.hash-backend-utils ?
local.hash-graph-sdk ?
local.hash-isomorphic-utils ?
local.hash-subgraph ?
rust.antsi ?
rust.deer ?
rust.error-stack ?
rust.harpc-codec ?
rust.harpc-net ?
rust.harpc-tower ?
rust.harpc-types ?
rust.harpc-wire-protocol ?
rust.hash-codec ?
rust.hash-config ?
rust.hash-graph-api ?
rust.hash-graph-atlas ?
rust.hash-graph-authentication ?
rust.hash-graph-authorization ?
rust.hash-graph-embeddings ?
rust.hash-graph-postgres-store ?
rust.hash-graph-store ?
rust.hash-graph-temporal-versioning ?
rust.hash-graph-types ?
rust.hash-graph-validation ?
rust.hash-middleware ?
rust.hashql-ast ?
rust.hashql-compiletest ?
rust.hashql-core ?
rust.hashql-diagnostics ?
rust.hashql-eval ?
rust.hashql-hir ?
rust.hashql-mir ?
rust.hashql-syntax-jexpr ?
rust.sarif ?
sarif ?
tests.hash-backend-integration ?
unit-tests ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

StatusView is a first-class SDCPN collection next to metrics: an ordered
list of labels, each mapping a set of places (optionally instance-scoped)
to a named status with a display colour, an optional token-attribute
condition, and an optional exit label for instances whose token left the
view's places. Label order is the array position; a moveStatusViewLabel
mutation reorders labels. The instanceId::placeId convention moves from
the simulation engine into a shared scoped-ids module.
…ments

Identity is its own root concept: `identities` on the SDCPN root declares
what a status view tracks (e.g. "ticket") and the key's element types,
with two or more entries forming a compound key correlated by tuple
equality. A colour element referencing an identity via `identityRef` is
that identity's key element — there is no separate key flag — so keys
correlate across colours without relying on element-name equality.
Removing an identity clears element references and dependent status views
atomically. The type properties panel gains a per-element identity picker
that can also create a new identity.
…chine Failure

Ticket Processing is a new example net: tickets keyed by a Ticket identity
flow Todo / In Progress / In Review / Blocked / Done under one status view,
with a review loop for multi-interval dwell, Done as an explicit sink
place, and an Archive transition whose consumed tickets fall to the view's
exit label. Production with Machine Failure gains one machine identity
carried by both machine colours — the cross-colour case — with an
Available / Producing / Broken / Under repair view.
A recorded firing may carry the attribute values of the tokens it
consumed and produced (`inputTokens`/`outputTokens`, keyed like
`input`/`output`, with scoped instanceId::placeId keys valid), so replay
can correlate per-instance state. Replay removes consumed tokens by
recorded value with a FIFO fallback, and the frame reader coerces
recorded values into runtime token records so HIR evaluation sees them.
The recording version is bumped to 2 with version 1 still accepted:
old recordings parse and simply carry no token values, and an
unsupported version fails explicitly rather than as a nested field error.
Status-label token conditions become a seventh HIR surface: a single
boolean expression over `token`, lowered and type-checked against the
attributes of the label's places' colours and evaluated by the HIR
interpreter (like scenario surfaces) rather than emitted as a buffer
program. compileHirArtifacts compiles every label condition and reports
failures per label. A frame evaluator assigns each tracked instance its
current label — labels apply in array order, keyed by the identity's key
elements — and a tracker walks frames to build per-instance interval
sets, falling back to the view's exit label when a token leaves the
view's places. The same code runs on quick-sim and actual-mode frames.
A new Status views tab clones the metrics panel pattern: a table list,
create and edit drawers, and a form that picks the identity the view
tracks and edits its ordered labels — name, display colour, place
multi-select including componentInstances' scoped copies of subnet
places, an optional token condition, and the exit-label toggle — with
row moves for reordering, since label order is the array position.
Token conditions are compiled through the HIR on submit and failures
block saving with the label's diagnostics. ColorSelect moves to the
panels' shared folder so the type properties and status view forms use
one picker.
Frame assignments now carry the place holding each instance's token, so
the canvas can attribute tracked instances to component-instance nodes:
a hook evaluates the net's status views against the currently viewed
frame — simulation or actual, over scoped instanceId::placeId ids — and
each instance node shows per-label counts as badges, with the dominant
label's display colour tinting the card. Places keep their type
colouring untouched.
The production-with-machine-failure scenario seeded every machine without
a machine_id, so uuid coercion collapsed the whole fleet into one
nil-uuid instance on the Machine status view. Seed each machine with a
distinct index-derived key instead.

Deleting the last status view hid the canvas/Kanban toggle while the
canvas view mode stayed "kanban", leaving the net canvas unreachable.
The canvas now always renders when the net has no status views.
…wrong token

Recorded input tokens are removed by value with the firing's input count
as the cap: records beyond the count are ignored, consumption beyond the
records falls back to FIFO, and a record that matches nothing removes
nothing instead of splicing out an arbitrary token of another instance.
Recorded output tokens now pad up to the output count so partial
recordings keep counts consistent. The protocol README documents the
semantics and names the unbounded firing-log growth as a known follow-up.
…rences

Token conditions no longer fail open: a declared condition without a
compiled artifact matches nothing, per-place satisfiability of the
condition's attribute reads is decided statically against the colour, and
a genuine interpreter failure is reported through a new onConditionError
callback instead of silently counting as no match. Tokens whose key
element holds the type default (nil uuid, empty string) are untracked
rather than merged into one phantom instance.

Status-view label places and identity references are validated in
addStatusView/updateStatusView, removePlace prunes deleted places out of
labels, addIdentity rejects duplicate ids and names, and identity key
elements must match the identity's keyElementTypes in order wherever a
mutation can break that. The file format now enforces the label
invariants on import, and status-condition contexts resolve scoped place
ids through the component-instance path instead of a bare-id map that
collided across nets.

The scoped-place walker, evaluation scope, and label-place resolver move
into core as status-view-scope.ts. The tracker sweeps only instances
that can still exit, the evaluator decodes each place's tokens once per
frame and reuses one locals map, and interpretHir reads the locals map
without copying it.
…e pipeline

Status-condition artifacts move into a single provider that the canvas
badges and the Kanban board share, ending duplicate LSP compiles per
edit; it reports pending and failure states instead of leaving a
rejected compile as an unhandled rejection with conditions silently
dropped. The Kanban board replays incrementally through a serialized
board-replay queue (full replay only on backward scrubs or a reshaped
timeline), shows replay and condition errors instead of freezing on
stale data, and counts instances that match no label.

The status-view drawers surface schema failures through the submit
validator instead of a silent dead Save, and flag label references to
places that no longer exist. Component-instance badges now count tokens
for every instance along the scope path, so nested instances keep their
badges when their parent subnet is opened, and the evaluation bails out
before any work when the net has no instances. Identity pickers offer
only identities the element can take without breaking key coherence and
new identities get unique names.
…ay quadratics

The Events tab's status-changes column now derives from the same frame
evaluator and tracker as the Kanban board and canvas badges, so token
conditions, scoped component-instance places, and the exit label behave
identically across surfaces. The deriver folds in only newly appended
firings, keeps a shared marking cursor so each firing is applied once,
and the column header names the driving view when several exist. Dwell
formatting is the shared helper on both surfaces.

Actual-mode getFramesInRange replays the firing log once across the
requested range through a new precomputed-marking parameter on the
timeline frame reader, replacing the per-reader from-zero replay that
made a Kanban render O(firings squared). The tracker exposes per-instance
label states without copying interval history for consumers that diff
consecutive frames, and the flattening module drops a dead local alias
of formatScopedId.
normalizeSDCPN rebuilds the document from an explicit list of root
collections and left out identities and statusViews, so every document
opened through createJsonDocHandle lost both. Loading the Ticket
Processing or Deployment Pipeline example in the website therefore
showed an empty Status views panel and no canvas/Kanban toggle.
The left sidebar, properties panel, and bottom panel float over the
canvas container. The net canvas can be panned out from under them, but
the Kanban board has a fixed layout, so its first column and status-view
selector were hidden behind the open sidebar.

The board root is now absolutely positioned like the other canvas
overlays, with its left, right, and bottom edges set from the panels'
current sizes in EditorContext, so it occupies only the visible region
and scrolls horizontally within it when columns overflow.
Clicking empty board background clears the selection, as clicking the
empty net canvas does, so a properties panel opened from a sidebar list
can be dismissed from the Kanban view. Cards and the status-view
selector are marked interactive and keep the selection.

Card keys render in the UI font instead of monospace, cards lose their
coloured left border (the column header swatch already carries the label
colour), and columns get a subtle border and a slightly darker surface so
they read as distinct containers.
Ticket Processing keyed its identity on a uuid element the engine
generated per token, so Kanban cards showed UUIDs. Kernel code cannot
build strings, so the example gains a seeded backlog place instead: a
TicketRequest token type carries a string id without the identity, every
scenario seeds the backlog with a fixed list of ids (FE-1045...), and the
create transition consumes one request per new ticket, copying its id
onto the tracked Ticket. Ids stay unique and deterministic per run, and
arrivals continue at the configured rate until the backlog is drained.
…xample matrix

The status-view examples key their tokens on a uuid or string identity
element, which WebGPU cannot represent, so the exhaustive GPU-readiness
matrix now declines Production Machines and Ticket Processing on
`unsupported-attribute-type`. Eligibility declines Production Machines
before shader emission, so the multi-place consumer assertion no longer
applies.

normalizeSDCPN gains a regression test that description and metadata
pass through alongside identities and status views, and stay absent when
omitted.
Status views follow the other experimental surfaces: an `enableStatusViews`
user setting, off by default, with a Status views row in the settings
dialog. Off, the Simulate panel offers no Status views tab (a stored
"status-views" mode falls back to Experiments), token type attributes show
no identity picker, the canvas shows no Kanban toggle, and Actual mode's
Events tab has no status changes column. The core types, actions,
serialization and the canvas status badges are unaffected, so a document
with identities and status views still loads and round-trips.
The website's example pages spell the editor's Simulate section and open
overlay into the URL through an exhaustive contract, so the new
`status-views` section and `create-status-view` overlay have to be named
there for the projection to type-check.
The Load example menu offered Ticket Processing regardless of the status
views setting, so a user with the setting off could load a net whose main
feature the editor hides. The menu's examples now come from one list in
load-example-menu.ts, where an entry can require the setting; Ticket
Processing is the only one that does. The example stays exported from
petrinaut-core for tests and for the website.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash* Affects HASH (a `hash-*` app) area/apps > hash-api Affects the HASH API (app) area/apps area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

2 participants