FE-769: Add String token dimension type (string interning)#8956
Conversation
PR SummaryMedium Risk Overview Engine: Strings are stored as 8-byte pool IDs ( Worker ↔ UI: Because the pool does not travel inside frame bytes, each Authoring & UI: Schema/LSP/AI cheatsheets accept Type-edit migration: Changing a type’s elements now migrates stored initial state — Reviewed by Cursor Bugbot for commit 213bf32. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
There was a problem hiding this comment.
Pull request overview
Adds string as a new token dimension type across Petrinaut UI + @hashintel/petrinaut-core, implementing per-run string interning via an append-only StringPool and shipping append-only pool deltas alongside interactive worker frame payloads so main-thread frame history remains decodable.
Changes:
- Introduces
StringPooland a newu64physical kind for string pool references in packed token buffers, with encode/decode wired through the pool. - Extends the interactive worker protocol + main-thread frame store to accumulate
newStringsdeltas and decode historical frames against the accumulated pool. - Updates UI editors (type dropdown, spreadsheets, playground) and documentation to support string dimensions end-to-end.
Reviewed changes
Copilot reviewed 48 out of 48 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| libs/@hashintel/petrinaut/src/ui/views/Editor/panels/SimulateView/scenarios/scenario-mapping.ts | Keeps string scenario columns as literal text when mapping rows to spreadsheet values. |
| libs/@hashintel/petrinaut/src/ui/views/Editor/panels/PropertiesPanel/type-properties/subviews/main.tsx | Adds “String” to the dimension type selector in type properties. |
| libs/@hashintel/petrinaut/src/ui/views/Editor/panels/PropertiesPanel/place-properties/subviews/place-initial-state/initial-state-editor.tsx | Preserves string initial-marking values as text in the spreadsheet editor. |
| libs/@hashintel/petrinaut/src/ui/lib/compile-visualizer.ts | Extends visualizer token prop typing to include string values. |
| libs/@hashintel/petrinaut/src/ui/dev/token-encoding-playground/token-memory-view.tsx | Updates playground memory view to display string pool IDs + decoded text round-trip. |
| libs/@hashintel/petrinaut/src/ui/dev/token-encoding-playground/playground-monaco.ts | Updates Monaco defs generation to type string dimensions as string. |
| libs/@hashintel/petrinaut/src/ui/dev/token-encoding-playground/physical-layout.ts | Uses a throwaway StringPool in the playground encoder/decoder path for string fields. |
| libs/@hashintel/petrinaut/src/ui/dev/token-encoding-playground/physical-layout.test.ts | Adds playground tests for string field layout and pool-reference round-trips. |
| libs/@hashintel/petrinaut/src/ui/dev/token-encoding-playground/dimension-editor.tsx | Adds “String” to the playground dimension type selector. |
| libs/@hashintel/petrinaut/src/ui/components/spreadsheet.tsx | Adds string column type support (parsing, tooltips, input type handling). |
| libs/@hashintel/petrinaut/src/ui/components/spreadsheet.stories.tsx | Extends spreadsheet Storybook story with a string column + data. |
| libs/@hashintel/petrinaut/docs/petri-net-extensions.md | Documents the new String dimension type and its discrete semantics. |
| libs/@hashintel/petrinaut-core/src/types/sdcpn.ts | Adds "string" to ColorElementType and includes string in token attribute runtime union. |
| libs/@hashintel/petrinaut-core/src/simulation/worker/simulation.worker.ts | Ships append-only newStrings deltas and resets delta state per init/reset. |
| libs/@hashintel/petrinaut-core/src/simulation/worker/simulation.worker.test.ts | Tests initial-marking delta shipping and omission when no string fields exist. |
| libs/@hashintel/petrinaut-core/src/simulation/worker/frame-payload.ts | Extends worker frame payload type with optional newStrings delta. |
| libs/@hashintel/petrinaut-core/src/simulation/runtime/frame-store.ts | Accumulates main-thread string pool copy and asserts delta ordering before storing frames. |
| libs/@hashintel/petrinaut-core/src/simulation/runtime/frame-store.test.ts | New tests for pool accumulation, ordering assertions, and clear() reset behavior. |
| libs/@hashintel/petrinaut-core/src/simulation/monte-carlo/transition-effect.ts | Ensures MC decode/encode paths use the run’s string pool; adjusts error formatting. |
| libs/@hashintel/petrinaut-core/src/simulation/monte-carlo/monte-carlo-simulator.test.ts | Adds an end-to-end MC test covering string interning + metric decoding. |
| libs/@hashintel/petrinaut-core/src/simulation/monte-carlo/frame-reader.ts | Decodes MC tokens using the run-local string pool. |
| libs/@hashintel/petrinaut-core/src/simulation/frames/frame-reader.ts | Extends frame reader compilation to accept a StringPoolReader for string decoding. |
| libs/@hashintel/petrinaut-core/src/simulation/frames/frame-reader.test.ts | Adds coverage for decoding string fields through a provided pool accessor. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/types.ts | Adds stringPool to SimulationInstance so the pool is owned per run/init. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/token-values.ts | Adds string default/coercion and guards against encoding/decoding strings without a pool. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/token-layout.ts | Adds u64 physical kind, pool reader/writer types, and string pool integration for read/write/encode. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/token-layout.test.ts | Adds layout + round-trip tests for string fields stored as u64 pool references. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/token-layout.test-helpers.ts | Threads stringPool through test helpers so decoding works for string layouts. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/string-pool.ts | New append-only StringPool implementation with max-size guard and delta support. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/string-pool.test.ts | New unit tests for deduping, reserved "", valuesFrom, and max-size guard. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/execute-transitions.test.ts | Ensures test simulation instances include a string pool. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/encode-kernel-token.ts | Interns string outputs in kernel encoding and stores u64 pool IDs into buffers. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/compute-possible-transition.ts | Decodes inputs via the simulation pool and interns outputs via the pool; adjusts error formatting. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/compute-possible-transition.test.ts | Adds kernel output tests for string interning, forwarding, defaults, and Distribution rejection. |
| libs/@hashintel/petrinaut-core/src/simulation/engine/build-simulation.ts | Constructs a per-run StringPool and uses it while packing the initial marking + decoding dynamics input. |
| libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/compile-scenario.test.ts | Adds compile-scenario tests ensuring string columns pass through literally and default correctly. |
| libs/@hashintel/petrinaut-core/src/simulation/api.ts | Clarifies initial marking value semantics for string attributes. |
| libs/@hashintel/petrinaut-core/src/schemas/scenario-schema.ts | Clarifies schema docs: strings are literal for string elements; uuid strings still coerce for uuid elements. |
| libs/@hashintel/petrinaut-core/src/schemas/entity-schemas.ts | Extends element type enum and schema descriptions to include string semantics + interning. |
| libs/@hashintel/petrinaut-core/src/lsp/lib/generate-virtual-files.ts | Types string elements as string in LSP-generated TS defs (incl. metric session token record unions). |
| libs/@hashintel/petrinaut-core/src/lsp/lib/checker.test.ts | Adds LSP checker tests for string typing, kernel output acceptance, and Distribution rejection. |
| libs/@hashintel/petrinaut-core/src/index.ts | Exports StringPool and pool reader/writer types from the package entrypoint. |
| libs/@hashintel/petrinaut-core/src/default-codes.ts | Adds default source literals for string attributes in generated templates. |
| libs/@hashintel/petrinaut-core/src/clipboard/serialize.test.ts | Updates an invalid-type fixture now that "string" is a valid element type. |
| libs/@hashintel/petrinaut-core/src/ai.ts | Updates code-surface guidance to include string typing and scenario semantics. |
| libs/@hashintel/petrinaut-core/docs/string-interning.md | New design/decision doc describing the string interning architecture and trade-offs. |
| libs/@hashintel/petrinaut-core/docs/architecture/engine.html | Updates the format-v2 table and notes the interactive newStrings delta protocol. |
| .changeset/fe-769-string-token-dimension-type.md | Changeset documenting the new string element type and storage semantics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1665496 to
d1f4562
Compare
f0ce1bf to
0dce09f
Compare
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
d1f4562 to
0344187
Compare
0dce09f to
3e10124
Compare
0344187 to
83dabe0
Compare
3e10124 to
50d5adb
Compare
83dabe0 to
dabfc56
Compare
50d5adb to
5a8b38e
Compare
Plain JS strings in user code, stored via per-run interning: frame buffers hold u64 references into an append-only pool owned by the simulation; interactive runs ship newStrings deltas with frame payloads. Editing a type's schema now migrates stored initial state (scenario rows and session marking) with value coercion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5a8b38e to
2ce1044
Compare
dabfc56 to
213bf32
Compare
|
Folded into #8953 (top-to-bottom fold of the remaining stack) — this branch's commit lives there now. |

🌟 What is the purpose of this PR?
Adds
stringas a fifth colour element type. Strings are variable-length, so they cannot live in the fixed-stride packed token structs — instead each frame stores a 64-bit reference into an append-only per-run string intern pool that is owned by the simulation, not the frame. The design rationale (pool ownership, delta protocol, trade-offs) is documented in the architecture pages and module docs.🔗 Related links
🚫 Blocked by
🔍 What does this change?
The pool (
engine/string-pool.ts):init."", so zeroed buffers decode sanely and string-free nets ship zero protocol overhead.maxSizeguard (1M distinct values) fails loudly with a targeted message if a kernel generates unbounded unique strings — the pathology that made us reject interning for UUIDs, contained here by design.Buffers: new
u64physical kind (8 B, align 8) holding the pool ID via the existingBigUint64Arrayview. Stride math, byte-range compaction, and all whole-token moves are untouched — references are just bytes.Pool distribution (the "not part of the frame" consequence):
SimulationFramePayloadcarries an append-onlynewStrings: { baseId, values }delta; the main-thread frame store accumulates its own pool copy (ordered,baseId-asserted) and frame readers decode through it. Delta ordering guarantees every stored frame is decodable on arrival.Semantics: runtime
TokenRecords hold plain JS strings; coercion is total (String(value), missing →""); interning is deterministic (same run ⇒ same IDs) and equal strings always share an ID; kernels/markings/scenarios write, dynamics read-only (?: neverderivative);Distributionon a string field stays an error (LSP + runtime).UI: String option in the dimension type select and the playground; spreadsheet string columns (text editing, identity parse, Delete →
""); the playground memory view shows the pool-reference round-trip (input "hello world" → pool id 1 → "hello world").Docs: dimension-type list + kernel notes in the user guide;
stringrow in the architecture format-v2 table; the design document.Schema-edit state migration (also in this PR): editing a colour type's elements now migrates stored initial state instead of leaving it silently misaligned. Scenario
per_placerows (positional) are migrated inside the same document mutation — add appends defaults, remove drops the column, move permutes, and a type change coerces each cell through the standard codec with total fallback to the new type's default (uuid cells stay canonical strings) — so undo/redo stays atomic. The sessioninitialMarking(name-keyed) is migrated by the simulation provider when type definitions change: renames re-key records, type changes coerce values, removed elements' keys are dropped.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
petri-net-extensions.mdshows a four-option dropdown; the UI now has five — please re-capture🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
maxSizewith a clear error).🐾 Next steps
🛡 What tests cover this?
schema-migration.test.ts(all four edit kinds through the actions API, coercion fallbacks, subnet places, code-mode scenarios untouched, atomic undo) andmigrate-initial-marking.test.ts(rename re-keying, coercion fallbacks, stale-key cleanup, no-op detection)string-pool.test.ts(dedup, reserved"",valuesFrom,maxSizeguard) andframe-store.test.ts(delta accumulation, ordering assertion, reset)String()coercion, missing →"", Distribution-on-string throwslint:tsc+lint:eslintclean❓ How to test this?
yarn devinlibs/@hashintel/petrinaut.state.places.X.tokens[0].label).label: input.Source[0].label) and one producing new strings; confirm equal strings behave identically.input → pool id → valueround-trip.📹 Demo
The playground story shows the wire format: a string field as a u64 pool reference with its round-trip in the hover panel.