Skip to content

Open .play files on the event model board in VS Code - #323

Merged
einari merged 21 commits into
mainfrom
claude/focused-allen-u3y1wy
Oct 1, 2026
Merged

einari merged 21 commits into
mainfrom
claude/focused-allen-u3y1wy

Conversation

@einari

@einari einari commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

The Screenplay VS Code extension now opens .play files on the Cratis event model board, the board Cratis Studio draws. A new TypeScript compiler for Screenplay draws it.

Added

  • A .play file opens on the event model board by default and redraws as you edit. Show Source opens the text beside the board, Open Event Model Board goes back, and Reopen Editor With… still offers the text editor. See VS Code extension. (Open .play files on the event model board in the VS Code extension #322)
  • A file inside a folder application, the folder holding an application.play, shows the whole application on the board. This includes unsaved edits to any of its files.
  • Errors in the model are listed above the board and link to the line, while the board still draws everything that could be read.
  • A TypeScript compiler for Screenplay, shipped with the extension. It parses .play documents and folders into the same syntax tree as the C# compiler, with the same visitors and walker. It reports the same diagnostics as the C# compiler for the constructs it reads. See TypeScript compiler.

Introduce @cratis/screenplay-compiler, the start of a TypeScript port of the
C# Screenplay parser so the VS Code extension can compile .play documents
itself. This first step carries the offside-rule foundations - the line
splitter, the line reader and the parser context - mirroring their C#
counterparts, plus a fence-aware skip for constructs the TypeScript
compiler deliberately does not model.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
claude added 9 commits October 1, 2026 08:21
Port the C# ScreenplayParser for what the event model board draws first:
domain, import, concept, type, module, nested features, slices with their
type, events and read models, and descriptions including fenced ones.

Nodes carry the C# record's kind and its camelCase member names, and
toSyntaxJson writes the C# SyntaxJson form narrowed to the members this
compiler models. Constructs it does not model are skipped whole rather
than reported, so the C# compiler and the editor diagnostics stay the
authority on their validity. C# patterns are copied through a helper that
keeps .NET's Unicode meaning of \w.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Port the slice members the event model board draws: a command's
properties and declarative validation rules, queries with their
parameters, the events each projection block consumes, reaction trigger
sources (when, every, at) and unique constraints. What these constructs
do beyond that - produces mappings, handlers, projection keys and
mappings, trigger bodies - is recognized and skipped rather than modeled.

A spec reads every .play document the repository ships and expects no
errors, since each of them compiles with the C# compiler.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Port the specification parser for what the board shows of a
specification: given events and read models, the command or appended
event it acts with, and the events, read models and errors it expects,
each with the values it states. Caller fixtures, denial, query and
absence assertions are recognized and skipped.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Give consumers the same hooks the C# compiler offers for turning a syntax
tree into artifacts: ApplicationSyntaxVisitor and its module, feature,
slice, constraint and specification siblings, a compile(source, visitor)
entry point, and a ScreenplaySyntaxWalker that visits every modeled node
in the order the C# walker does.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Port PlayFolderMerge for what the TypeScript compiler models: a folder's
documents are read in ordinal path order and combined, modules and
features with the same name merging, and a concept, type or slice
declared in two files reported and dropped. File reading stays with the
caller, so the compiler runs wherever its text comes from.

The canonical corpus folder merges into the same syntax as its single
document, slice order aside.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
The TypeScript compiler writes the syntax it reads for a shared corpus of
.play documents as golden SyntaxJson, narrowed to the members it models,
and a spec holds it to those files. A C# spec holds the C# compiler to the
same files: every member present in a golden file must read the same in
the C# syntax, so the two compilers can only drift apart by failing one
side. The corpus is the canonical corpus documents, both invoicing
samples and a document exercising every modeled construct.

Run the TypeScript specs with SCREENPLAY_REGENERATE_SYNTAX_VECTORS=1 to
rewrite the golden files.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Nothing checked the JavaScript packages before a merge to main. Build the
compiler, the language service and the extension, then lint and test
every workspace. The .NET build now also runs when the golden syntax
changes, since the C# compiler is held to it.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Add @cratis/screenplay-event-models, an ApplicationSyntaxVisitor that turns
the TypeScript compiler's syntax into the JSON document @cratis/event-models
draws - the counterpart of Studio's EventModelSyntaxVisitor. Every module
goes into one collection, a State View shows the read model its projection
builds, and a slice consuming an event points back at the State Change
slice that produces it and carries its shape. Commands carry the
validation rules the board has an equivalent for, events their unique
constraints, automations what sets them off, and slices their
specifications with literal values.

Ids are derived from where each element sits in the model, so recompiling
after an edit leaves untouched elements with the ids the board remembers
their state by. Every repository sample maps into a document the published
board schema accepts.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
@einari einari added the minor New features, non-breaking additions label Oct 1, 2026 — with Claude
claude added 5 commits October 1, 2026 08:49
Register a custom editor that is the default for *.play: a webview drawing
the document on @cratis/event-models' board, compiled with the TypeScript
compiler and the event model mapping. The board redraws when the text
changes, wherever it is edited, and lists the compiler's errors above the
board with a link to the line, while still drawing everything that could
be read. Show Source and Open Event Model Board move between the board and
the text, which stays the source of truth.

The webview bundles React, the board and its stylesheets and fonts, and
runs under a nonce-only content security policy; Pixi compiles its shaders
through pixi.js/unsafe-eval so the page never has to allow eval. The board
is drawn in its dark theme whatever the editor theme is: its labels are
colored for a dark surface, which is also why Studio's viewer is dark only.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
The declarations were written as a .d.ts, which the repository ignores, so
the previous commit left the webview without them and its typecheck would
fail on a clean checkout.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
A document in a folder application is one file's share of the model, so
the board now compiles the application it belongs to: every .play file
beneath the nearest folder holding an application.play, which is what the
C# compiler writes when it expands an application into folders. Unsaved
text is compiled for every open file of the folder, the board redraws
when any of them changes, is saved, created or deleted, and a problem in
another file opens that file. A document outside a folder application is
shown on its own, as before.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
The extension now bundles the TypeScript compiler and the event model
mapping, so the Marketplace job builds both before bundling it. Both
packages are private for now: the extension carries them, and publishing
them to npm needs trusted publishing set up for each new package first.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Add a page for the extension's board - moving between the board and the
text, folder applications, errors and theme - and one for the TypeScript
compiler: parsing a document and a folder, its visitors and walker, what
it reads and skips, and how it is held to the C# compiler.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc

einari commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Notes for reviewers

What was verified, and how:

  • TypeScript specs: yarn build, yarn lint:ci and yarn test pass for the compiler (72 specs), the event model mapping (18), the language service (66) and the extension (63). They were also run from a clean worktree.
  • C# against TypeScript: the new spec Syntax/Serialization/for_SyntaxJson/when_holding_the_typescript_compiler_to_it holds the C# compiler's SyntaxJson to the golden syntax the TypeScript compiler writes for six corpus documents. It passed in .NET Build (4,841 specs, 0 failed) on 42eba1c.
  • Board schema: every .play file in the repository maps into a document that the published @cratis/event-models 0.106.0 JSON Schema accepts. The check is not vacuous: a planted bad id and a bad slice type were both reported.
  • Rendering: the webview bundle was rendered in headless Chromium under the real content security policy. It draws the invoicing sample with one canvas and no page errors. Pixi needs pixi.js/unsafe-eval to run without 'unsafe-eval' in the CSP.
  • Packaging: the VSIX packages at 673 KB.

Not verified here:

  • The custom editor inside a real VS Code. The container cannot download VS Code. Please try it with F5, or by installing the VSIX: open a .play file, edit it beside the board, and open a file of a folder application.

Decisions worth a look:

  • Dark only. The board is always dark. Its header labels are colored for a dark surface (for example #ffffffb3) rather than drawn from the theme tokens, so on a light background they are unreadable. Studio's viewer is dark only for the same reason. Following the editor theme needs that change in @cratis/event-models.
  • Private packages. @cratis/screenplay-compiler and @cratis/screenplay-event-models are private. The extension bundles them, and publishing them to npm needs trusted publishing configured for each new package first.
  • Pre-existing failure. The root yarn build fails in the screenplay-editor demo app on a monaco-editor type mismatch between workspaces. This PR does not touch that app. The new JavaScript Build workflow builds the packages it depends on explicitly, and lints and tests every workspace.

Generated by Claude Code

claude added 6 commits October 1, 2026 09:00
The editor demo pinned monaco-editor 0.52 while the language service it
consumes builds against 0.55, so its typecheck saw two incompatible monaco
APIs and the root yarn build failed. Align the editor on 0.55. A language
service spec asserted on a possibly undefined value, which failed the root
yarn compile.

The JavaScript workflow now runs the root build, compile, lint and test
scripts, so every workspace is checked on every pull request.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
…reports

Event tags are now part of the syntax, and an inline '{...}' or '[...]'
value is read into object and list expressions as the C# compiler reads
it, keeping the first of two members with the same name. The parser also
reports what the C# parser reports inside the constructs it reads: a
reason for a missing or already explained concept attribute, an empty
validate in an enumeration, an entering event claimed by two variants,
invalid tags, invalid structured values and unknown $context paths.

The walker visits tags and the items and members of structured values.
The conformance document exercises tags and structured values, and the
golden syntax is regenerated with them.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Add 112 invalid documents with the diagnostics they are reported with -
code, line and order - covering every error and warning the TypeScript
parser can report. A TypeScript spec and a C# spec each hold their
compiler to the same file, so an error path cannot drift on one side
without failing.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Add specs for what the compiler skips at every level, every trigger and
validation form, structured values valid and invalid, folder merges of
domains, imports and agreeing descriptions, string-aware splitting and
unescaping, and the JSON writer. The walker is held to visiting exactly
the nodes the tree holds, kind by kind.

Remove what no input can reach: the unknown validation rule branch, whose
operands all have a kind, a default in trigger parsing the caller never
takes, and an unused location helper.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Cover every trigger form an automation shows - event, custom, every
interval unit and schedule - slices missing their command, read model or
trigger, an application without modules, and the shape of every kind of
property type, recursive types included.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
Both packages now run their specs with coverage and fail below their
thresholds - 99% of lines and 96% of branches for the compiler, every
line for the mapping - so new code arrives with its specs. The compiler's
documentation says how its diagnostics are held to the C# compiler and
how coverage is enforced.

Part of #322.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrnFQW3Zx9PqPJgwE4b7Kc
@einari
einari marked this pull request as ready for review October 1, 2026 09:13

einari commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Update for reviewers

Root workspace fixed. yarn build and yarn compile both pass from the root now, and the JavaScript Build workflow runs the root build, compile, lint:ci and test on every pull request. Two failures predated this PR:

  • the editor demo pinned monaco-editor 0.52 against the language service's 0.55
  • a language service spec asserted on a possibly undefined value

Diagnostics held to C#. Conformance/diagnostics.json lists 112 invalid documents with the code, line and order of every diagnostic. The TypeScript spec and a new C# spec (when_holding_the_typescript_compiler_to_its_diagnostics) hold both compilers to it, and it covers every code the TypeScript parser can report. Writing it led to porting the C# checks the first version skipped inside modeled constructs:

  • concept attribute reasons
  • an empty validate in an enumeration
  • variant entering events
  • tags
  • $context paths
  • structured values

Syntax, tags and structured values. Event tags and {...} / [...] values are now read into the tree. The golden syntax was regenerated, and the diff only adds those.

Coverage, enforced. Both packages run their specs with coverage thresholds, and a planted uncovered file fails the run.

Package Lines Branches Specs
Compiler 99.9% 96.9% 240
Event model mapping 100% 96.3% 37

The walker spec checks that the walker visits exactly the nodes the tree holds, kind by kind.

Still untested by specs: the extension's VS Code glue (BoardRefresh, boardSources, the editor provider) and the React webview. The pure parts of the extension (findApplicationRoot, boardFor, boardHtml) are covered, and the webview was rendered in headless Chromium.


Generated by Claude Code

@einari
einari merged commit 7d0bfa6 into main Oct 1, 2026
5 of 6 checks passed
@einari
einari deleted the claude/focused-allen-u3y1wy branch October 1, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor New features, non-breaking additions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants