spyret 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ # Spyret library and IDE ownership
2
+
3
+ Spyret now supplies the reusable browser integration. A Drive-imported native
4
+ module can install its own CPO display adapter; the IDE does **not** need a
5
+ Spytial-specific output branch. See [library setup](BROWSER_LIBRARY.md).
6
+
7
+ | Functionality | Owner |
8
+ | --- | --- |
9
+ | Value capture, relationalization, reconstruction, `_spytial` collection | Spyret |
10
+ | Generated Pyret rule types, YAML section composition | Spyret |
11
+ | Pyret-facing diagram functions and runtime suspension/Stop handling | Spyret |
12
+ | Evaluator/layout setup, opaque handles, source preview and views | Spyret browser entry |
13
+ | CPO renderer compatibility code | Spyret, isolated in `registerCpoOutput` |
14
+ | Layout semantics, parsing, solving, graph controls and diagnostics | Core |
15
+ | Source selection, YAML source writeback, undo and keyboard shortcuts | IDE |
16
+ | Builtin aliases and package/asset delivery for existing programs | IDE build configuration |
17
+
18
+ In the separate companion IDE change, the `spytial` builtin becomes a small
19
+ forwarding wrapper around
20
+ `spyret/browser`. `spytial-rules` is a build-time copy of the generated Pyret
21
+ module. Its old `spytial-view` module and Spytial branch in `output-ui` are removed.
22
+ Existing `SP.diagram(value, yaml)` and `_output` methods continue to work; new
23
+ programs can use `SP.diagram(value)` and typed rules.
24
+
25
+ Stock CPO can instead load `spyret/pyret-module` through its existing `gdrive-js`
26
+ locator, optionally hidden behind one URL-imported `.arr` wrapper. No new native
27
+ URL locator or IDE plugin is required for that route. Drive hosting/access is a
28
+ separate deployment step.
29
+
30
+ The companion IDE change consumes a lockfile-verified vendored Spyret 0.2.0 package
31
+ archive, so this coordinated change builds without an unpublished registry
32
+ version or a sibling source checkout. Once 0.2.0 is published, replace that
33
+ archive dependency with the exact npm version and regenerate the copied assets;
34
+ no runtime code needs to change.
35
+
36
+ Future work should focus on an upstream public display-registration and
37
+ cancellation API, deployment/Drive-access testing, and optional source-editor
38
+ integration. The existing private CPO adapter remains a compatibility dependency.
@@ -62,8 +62,10 @@ inspect its nominal token/display spelling.
62
62
 
63
63
  No printer or arbitrary annotation runs during capture. Declared datatype methods
64
64
  are behavior supplied by declarations and are outside the declared-slot state
65
- contract; this includes `_output`. Non-method extension fields on data values
66
- are rejected instead of dropped. Functions/methods in state-bearing slots,
65
+ contract; this includes `_output`. Callable `_spytial` metadata is also omitted
66
+ on ordinary objects and outside a datatype's declared slots; collecting layout
67
+ rules is a separate, explicit operation. Other non-method extension fields on
68
+ data values are rejected instead of dropped. Functions/methods in state-bearing slots,
67
69
  opaque values, unrecognized branded library objects, standalone table Row
68
70
  values, sparse arrays, unset/frozen references, and arbitrary reference
69
71
  annotations produce explicit diagnostics. This API does not serialize closures
package/docs/RELEASING.md CHANGED
@@ -1,68 +1,65 @@
1
- # Releasing Spyret
1
+ # Releasing Spyret to Google Drive
2
2
 
3
- Spyret is published as the public npm package `spyret`. The CJS, ESM, browser and
4
- TypeScript entry points contain Spyret's adapters and bundled data contract
5
- declarations, with no Core runtime dependency. The host passes the returned
6
- `IDataInstance` to Core for layout/rendering; compatibility tests use Core 6.3.1.
7
-
8
- ## One-time npm setup
9
-
10
- The first package publication requires an npm maintainer login. From a checked,
11
- merged release commit, run:
3
+ With Node 22+, [GitHub CLI](https://cli.github.com/), and `tar` installed, run
4
+ from this checkout (no `npm install` or build needed):
12
5
 
13
6
  ```sh
14
- npm login
15
- npm ci
16
- npm run typecheck
17
- npm test
18
- npx --yes --package npm@11.20.0 -c 'npm run test:package -- --out release'
19
- npx --yes --package npm@11.20.0 npm publish ./release/spyret-0.1.0.tgz --access public
7
+ npm run release:drive
20
8
  ```
21
9
 
22
- Also require green upstream PBT CI for this commit before publishing. The package
23
- check installs the actual tarball into an empty project and exercises
24
- CJS, ESM, browser and TypeScript consumers.
25
-
26
- After the package exists, configure its npm **Trusted Publisher** settings:
27
-
28
- | Setting | Value |
29
- | --- | --- |
30
- | Provider | GitHub Actions |
31
- | Organization/user | `sidprasad` |
32
- | Repository | `spyret` |
33
- | Workflow filename | `release.yml` |
34
- | Environment | Leave empty |
35
- | Allowed action | Enable direct `npm publish` |
36
-
37
- With npm 11.15 or later, the equivalent command is:
38
-
39
- ```sh
40
- npm trust github spyret --repo sidprasad/spyret --file release.yml --allow-publish
41
- ```
42
-
43
- See [npm's trusted publishing documentation](https://docs.npmjs.com/trusted-publishers/).
44
- No npm token needs to be stored in GitHub. Publishing uses OIDC and provenance.
45
-
46
- ## Later releases
47
-
48
- 1. On a release branch, run `npm version patch --no-git-tag-version` (or choose a
49
- minor/major bump), commit both manifests, and merge after CI passes.
50
- 2. On the updated `main`, tag that exact commit and push the tag:
51
-
52
- ```sh
53
- git tag v0.1.2
54
- git push origin v0.1.2
55
- ```
56
-
57
- 3. `release.yml` verifies the tag matches `package.json` and belongs to `main`,
58
- runs the unit/package suite and both standard-Pyret PBT seeds, tests the packed
59
- artifact, publishes it to npm, and attaches it to a GitHub release.
60
-
61
- Prerelease versions publish under `next`; stable versions use `latest`. A failed
62
- workflow can be rerun or manually dispatched against the same **tag**. An existing
63
- npm version is accepted only when its integrity matches the tested tarball;
64
- different contents require a version bump.
65
-
66
- Version 0.1.0 was bootstrapped manually. Subsequent versions use the tagged
67
- workflow. It installs its pinned npm into a separate tools directory so npm
68
- does not replace itself while running.
10
+ The script downloads the latest GitHub release and walks you through:
11
+
12
+ 1. Open a new [CPO editor](https://code.pyret.org/editor) with normal Google login.
13
+ Replace **all** editor text (including the initial `use context` line) with the
14
+ contents of the prepared `.js` file. Name it `spyret-vVERSION.js`, **Save**, and
15
+ wait for saving to finish. **Do not Run** this document: it contains JavaScript.
16
+ In Google Drive, set the saved file to **Anyone with the link / Viewer** and
17
+ keep it under **My Drive**. Paste its CPO editor URL (`#program=...`), Drive
18
+ link, or file ID into the script. Use the saved file, not a CPO Share copy.
19
+ 2. Upload the generated `.arr` wrapper to My Drive with the same sharing setting
20
+ and paste its Drive link into the script.
21
+ 3. Copy the printed import into CPO and run the provided diagram example.
22
+ 4. Test that same import with a **second account using normal CPO login** before
23
+ distributing it. The script prepares artifacts; it does not verify Google
24
+ permissions or perform either browser test.
25
+
26
+ ### Why save the JavaScript through CPO?
27
+
28
+ CPO's normal login requests `drive.file`. Its `gdrive-js` loader uses an
29
+ authenticated request, so uploading a public JavaScript file directly to Drive
30
+ does not by itself authorize CPO to read it. Saving through CPO creates the file
31
+ with CPO's authorization. CPO saves the editor text without compiling it.
32
+ The outer `.arr` wrapper uses `shared-gdrive`, which has a public-file retrieval
33
+ path, so that file can still be uploaded directly.
34
+
35
+ For an existing JavaScript upload, **Open with → Code Pyret** in Google Drive,
36
+ if offered, is another way to grant per-file access without changing its ID.
37
+ Google documents this mechanism in its
38
+ [Drive authorization guidance](https://developers.google.com/workspace/drive/api/guides/handle-errors#appNotAuthorizedToFile).
39
+ Opening an arbitrary `editor#program=ID` link alone is not the same authorization
40
+ action. Pasting a link into this script only selects an ID; it grants no access.
41
+
42
+ This workflow still needs end-to-end verification with real CPO accounts.
43
+ Success as the publisher does not establish access for other users. If another
44
+ user gets a 403, they may need to authorize the JavaScript through Drive's
45
+ **Open with → Code Pyret** too. Do not advertise a setup-free import until the
46
+ second-account test passes. The old full-access login flow is not a prerequisite
47
+ or a recommended workaround: Google may block it.
48
+
49
+ Keep both filenames unchanged and preserve the file contents without conversion.
50
+ Use **My Drive**, not a Google **Shared drive**: CPO’s Drive loader omits the
51
+ shared-drive API flag, so files in Shared drives return 404 even when public.
52
+ A shortcut under My Drive does not change where a file lives.
53
+ The two upload files are in `release/vVERSION/upload/`; the CPO example is
54
+ saved separately as `release/vVERSION/import.arr`. Keep published Drive files
55
+ unchanged so existing imports continue to work.
56
+
57
+ If you previously uploaded directly to Drive (or used a Shared drive), rerun with
58
+ `npm run release:drive -- --tag v0.2.0 --out release/cpo-authorized` and follow
59
+ the CPO-save workflow above. Paste the new links when prompted so the wrapper
60
+ uses the new JavaScript file ID. The fresh output directory preserves the old
61
+ release artifacts; existing wrappers are never overwritten with a different ID.
62
+
63
+ To select a specific release, use `npm run release:drive -- --tag v0.2.0`.
64
+ The release must contain the browser module and Pyret rules; `v0.1.1` is headless
65
+ and cannot be used. No Google Cloud project, service account, or API setup is needed.
@@ -0,0 +1,71 @@
1
+ # Layout hooks and typed rules
2
+
3
+ The package includes `pyret/spytial.arr`, a typed rule library generated from
4
+ Core's language manifest. Copy or serve that file where your Pyret program can
5
+ import it (Node hosts can locate it with `require.resolve('spyret/spytial.arr')`).
6
+
7
+ ```pyret
8
+ import file("spytial.arr") as S
9
+
10
+ data Tree:
11
+ | leaf(value)
12
+ | branch(children)
13
+ sharing:
14
+ method _spytial(self) -> List<S.SpytialRule>:
15
+ [list:
16
+ S.orientation("children", [list: S.below]),
17
+ S.align("siblings", S.horizontal)
18
+ ]
19
+ end
20
+ end
21
+ ```
22
+
23
+ Public constructors wrap their results as `constraint(Constraint)` or
24
+ `directive(Directive)` automatically. Compose rules with ordinary Pyret lists;
25
+ the serializer puts each rule in its proper YAML section. Use named fields in
26
+ records for optional properties and typed constructors for nested style blocks:
27
+
28
+ ```pyret
29
+ S.atom-style("leaf", {fill-style: S.fill-style({color: "red"})})
30
+ ```
31
+
32
+ From the JavaScript host, after evaluating the program:
33
+
34
+ ```js
35
+ import { toDataInstance, getSpytialSpec } from 'spyret';
36
+
37
+ const instance = toDataInstance(pyretValue, runtime);
38
+ const specs = await getSpytialSpec(pyretValue, runtime); // string[]
39
+ ```
40
+
41
+ The collector walks reachable values in breadth-first order, including lists,
42
+ objects, tuples, arrays, references, dictionaries and table cells. It handles
43
+ cycles and invokes each distinct `_spytial` function or method once per call,
44
+ with methods bound to their first encountered owner. Repeated hooks never stop
45
+ the traversal of an instance's children. Hooks should describe types, independent
46
+ of the particular instance; unobserved datatype variants cannot be discovered.
47
+
48
+ Each hook returns `List<SpytialRule>`, serialized as one YAML document. A raw YAML
49
+ string is also accepted as an escape hatch, unchanged and unvalidated. No hooks
50
+ yields `[]`; a hook returning an empty list yields one empty spec. Identical
51
+ documents from different hooks remain separate. Hook failures, malformed rules
52
+ and unsupported values reject the promise with a `SpytialSpecError` and path.
53
+
54
+ Hooks execute user code. Call the async collector while its owning runtime is
55
+ idle or paused; a native Pyret module can use `runtime.pauseStack` around the
56
+ promise. `spytialRulesToYaml(rules, runtime)` also serializes an already evaluated
57
+ rule list directly. Capture and `toDataInstance` never invoke hooks, and omit
58
+ callable `_spytial` metadata on ordinary objects or outside a datatype's declared
59
+ slots. Other capture restrictions still apply.
60
+
61
+ See the [layout rule guide](SPYTIAL_LANGUAGE.md) for examples and the
62
+ [generated rule reference](SPYTIAL_RULES_REFERENCE.md) for every constructor,
63
+ enum and option. Pyret annotations check field types; serialization
64
+ checks numeric bounds, string patterns and incompatible directions. Selector
65
+ meaning and result arity remain Core's responsibility.
66
+
67
+ To update the API, pin the desired Core release in `package.json` and the lockfile,
68
+ then run `npm run generate:spytial`. Commit the generated Pyret source, serializer
69
+ schema and reference together. `npm run check:spytial` and the tests detect drift;
70
+ unknown field types or enum vocabularies fail generation for explicit handling.
71
+ No Core runtime dependency is added to the published package.
@@ -0,0 +1,194 @@
1
+ # Spyret's layout rule language
2
+
3
+ Spyret lets a Pyret program describe how its data should be drawn. The
4
+ `pyret/spytial.arr` library, imported as `S` below, provides typed constructors
5
+ for Spytial constraints and directives. Each constructor returns an
6
+ `S.SpytialRule`; put rules in an ordinary Pyret list. `S.diagram(value)` finds
7
+ `_spytial` hooks on reachable values, collects their rules, and displays the
8
+ diagram. See [layout hooks](SPYTIAL_HOOKS.md) for the hook contract and
9
+ [the README](../README.md#pyret-import-describe-display) for the CPO import.
10
+ This page describes the 0.3.0 source API. The published v0.2.0 wrapper uses
11
+ prefixed enum names and `atom-style-with`; update those calls when moving to
12
+ 0.3.0. Style properties now use named records and typed blocks instead of
13
+ `.with-*` chains.
14
+
15
+ ## A first diagram
16
+
17
+ ```pyret
18
+ # Use the versioned wrapper import supplied by the library maintainer.
19
+ import shared-gdrive("spyret-vVERSION.arr", "WRAPPER_DRIVE_FILE_ID") as S
20
+
21
+ data Tree:
22
+ | leaf(value)
23
+ | branch(left, right)
24
+ sharing:
25
+ method _spytial(self) -> List<S.SpytialRule>:
26
+ [list:
27
+ S.orientation("left + right", [list: S.below]),
28
+ S.orientation("left", [list: S.left]),
29
+ S.orientation("right", [list: S.right]),
30
+ S.atom-style("leaf", {fill-style: S.fill-style({color: "#e0f2ff"})})
31
+ ]
32
+ end
33
+ end
34
+
35
+ S.diagram(branch(leaf(1), leaf(2)))
36
+ ```
37
+
38
+ The `left + right` selector finds parent-to-child pairs. For each pair,
39
+ `below` places the child below the parent. The next two rules put
40
+ left children to the left and right children to the right. The style rule fills
41
+ leaf nodes. Rule order does not turn later rules into overrides; Spytial applies
42
+ them together and reports conflicting constraints.
43
+
44
+ The same constructors work in a local `import file("spytial.arr") as S` if the
45
+ library is available as a file. That import supplies rule constructors; the
46
+ versioned wrapper also supplies `diagram` and `diagram-with-rules`.
47
+
48
+ ## Selectors and direction
49
+
50
+ A selector is a **string containing a Spytial relational expression**, not a
51
+ Pyret function. A unary selector such as `"leaf"` selects nodes. A binary
52
+ selector such as `"left"` or `"left + right"` selects `(source, target)` pairs.
53
+ The expected arity depends on the rule. For orientation, the direction names
54
+ describe where the **target** goes relative to the source. Thus, if `left`
55
+ contains `(parent, child)`, `S.below` puts the child below its parent.
56
+
57
+ Common expressions are a type name (`"leaf"`), a field name (`"left"`), a
58
+ union (`"left + right"`), or a transitive closure (`"^(left + right)"`). Keep
59
+ operators inside the quoted Pyret string. See [Spytial Core's selector
60
+ documentation](https://github.com/sidprasad/spytial-core/blob/main/site/selectors.md)
61
+ for the full expression language and arity rules.
62
+
63
+ | Pyret direction | Meaning for the target |
64
+ | --- | --- |
65
+ | `S.above`, `S.below` | Strictly above or below; sideways offset is allowed. |
66
+ | `S.left`, `S.right` | Strictly left or right; vertical offset is allowed. |
67
+ | `S.directly-above`, `S.directly-below` | Above or below and horizontally centered. |
68
+ | `S.directly-left`, `S.directly-right` | Left or right and vertically centered. |
69
+
70
+ You can combine compatible directions, for example
71
+ `[list: S.below, S.left]`. Opposites such as `above` and
72
+ `below` cannot be combined. A `directly-*` direction can only be combined with
73
+ its matching plain direction. Spyret checks these combinations when it
74
+ serializes the rule list.
75
+
76
+ ## Constraints: layout and visibility
77
+
78
+ | Constructor | What it does |
79
+ | --- | --- |
80
+ | `S.orientation(selector, directions)` | Places targets of a binary selector relative to sources. |
81
+ | `S.cyclic(selector)` | Arranges nodes in the order of a binary selector around a circle. |
82
+ | `S.align(selector, direction)` | Gives selected nodes or node pairs a common row (`S.horizontal`) or column (`S.vertical`). |
83
+ | `S.group(selector, name)` | Draws a box around selected nodes; a binary selector makes a group per first-column key. |
84
+ | `S.size(width, height)` | Sets node dimensions in pixels; select the affected nodes with `S.size-with(width, height, {selector: "..."})`. Both numbers must be positive. |
85
+ | `S.hide-atom(selector)` | Removes selected nodes and their edges from the diagram. |
86
+
87
+ For example:
88
+
89
+ ```pyret
90
+ [list:
91
+ S.cyclic-with("next", {direction: S.counterclockwise}),
92
+ S.align("siblings", S.horizontal),
93
+ S.group("children", "Family"),
94
+ S.size-with(150, 80, {selector: "branch"}),
95
+ S.hide-atom("InternalNode")
96
+ ]
97
+ ```
98
+
99
+ Constraints can also use `{hold: S.never}` to require that a relation
100
+ **does not** hold. For instance, negating `S.above` means the target is
101
+ not strictly above the source; it does not require the target to be below.
102
+ `S.always` expresses the normal positive constraint.
103
+
104
+ ## Directives: appearance and labels
105
+
106
+ | Constructor | What it does |
107
+ | --- | --- |
108
+ | `S.flag(name)` | Applies a global display flag such as `S.hide-disconnected` or `S.hide-disconnected-built-ins`. |
109
+ | `S.atom-style(selector, style)` | Styles the selected nodes' fill, border, icon, label, and label visibility. |
110
+ | `S.edge-style(field, style)` | Styles a relation's edge line and label, or hides the edge. |
111
+ | `S.attribute(field)` | Shows a field as text inside its source node instead of as an edge. |
112
+ | `S.tag(to-tag, name, value)` | Adds computed text to matching nodes while retaining the original edges. |
113
+ | `S.hide-field(field)` | Hides a field's drawn edges. |
114
+ | `S.inferred-edge(name, selector)` | Draws additional edges computed from a selector. |
115
+
116
+ `field` names a relation; optional `selector` and `filter` fields narrow where
117
+ field-based directives apply. `tag` takes a unary `to-tag` selector and a
118
+ `value` selector whose first column identifies the node receiving the tag.
119
+
120
+ ```pyret
121
+ [list:
122
+ S.flag(S.hide-disconnected-built-ins),
123
+ S.atom-style("leaf", {border-style: S.border-style({
124
+ color: "#2563eb", width: 2
125
+ })}),
126
+ S.edge-style("left", {line-style: S.line-style({
127
+ color: "#64748b", pattern: S.dashed
128
+ })}),
129
+ S.attribute("value"),
130
+ S.hide-field("internal"),
131
+ S.inferred-edge("descendant", "^(left + right)")
132
+ ]
133
+ ```
134
+
135
+ Use `atom-style`, `edge-style`, and `inferred-edge` for new styles. The exported
136
+ `icon`, `atom-color`, and `edge-color` constructors are older forms retained for
137
+ compatibility.
138
+
139
+ ## Optional fields and reusable style blocks
140
+
141
+ Rules with optional fields have a short constructor for their required
142
+ arguments and a `-with` constructor that takes a Pyret record of named fields.
143
+ `atom-style` takes a selector and a style record; `edge-style` takes a field
144
+ and a style record. Within those records, nested styles use the named types
145
+ `S.TextStyle`, `S.LineStyle`, `S.FillStyle`, `S.BorderStyle`, and `S.IconStyle`.
146
+ For example, `S.text-style({color: "navy"})` makes a `TextStyle`. Leave any
147
+ field out to let Spytial Core supply its display default.
148
+
149
+ ```pyret
150
+ S.orientation-with("children", [list: S.below], {hold: S.always})
151
+
152
+ S.group-with("children", "Family", {
153
+ add-edge: S.group-add-edge({
154
+ points: S.togroup,
155
+ line-style: S.line-style({weight: 2})
156
+ }),
157
+ text-style: S.text-style({color: "#7c3aed"})
158
+ })
159
+ ```
160
+
161
+ The group example draws a connector from each group key to its box. Its
162
+ `line-style` styles that connector; the group's own `text-style` styles its
163
+ caption. Most rule records also accept a `source: S.rule-source({...})` field;
164
+ this records the originating rule text and optional location
165
+ for error reports without changing layout.
166
+
167
+ The [generated rule reference](SPYTIAL_RULES_REFERENCE.md) lists every
168
+ constructor, option, enum, and style block for the currently pinned Core
169
+ language version. Spyret checks Pyret types through annotations, then checks
170
+ numeric bounds, string patterns, and direction compatibility while serializing.
171
+
172
+ ## How rules become a diagram
173
+
174
+ `_spytial` returns `List<S.SpytialRule>`. Spyret turns each list into one YAML
175
+ document with `constraints` and `directives` sections; constructors choose the
176
+ correct section automatically. `S.diagram(value)` collects the hooks reachable
177
+ from `value`. To bypass hooks, call `S.diagram-with-rules(value, rules)`; an
178
+ existing YAML document can be passed as `S.diagram(value, yaml)`.
179
+
180
+ For example, `S.orientation("left", [list: S.below])` represents the
181
+ following constraint (the serializer quotes YAML keys and strings):
182
+
183
+ ```yaml
184
+ constraints:
185
+ - orientation:
186
+ selector: left
187
+ directions: [below]
188
+ directives: []
189
+ ```
190
+
191
+ For host code, `getSpytialSpec(value, runtime)` returns the collected YAML
192
+ documents as `string[]`, and `spytialRulesToYaml(rules, runtime)` serializes an
193
+ already evaluated list. See [layout hooks](SPYTIAL_HOOKS.md) for collection
194
+ order, error behavior, and runtime requirements.
@@ -0,0 +1,53 @@
1
+ # Generated Pyret rule reference
2
+
3
+ Core 6.3.2; language 2026-09-18.
4
+
5
+ Import `pyret/spytial.arr` as `S`. Constructors return `S.SpytialRule` automatically.
6
+ Enum values are qualified through `S`, for example `S.below` and `S.horizontal`.
7
+ Optional fields use Pyret records with named keys. Pass the record to `<rule>-with`;
8
+ `atom-style(selector, style)` and `edge-style(field, style)` take one directly.
9
+ Nested style blocks are typed constructors such as `S.fill-style({color: "red"})`.
10
+ Unset fields are omitted from YAML; Spytial Core supplies their defaults.
11
+
12
+ Numeric bounds, patterns and incompatible direction combinations are checked during serialization.
13
+
14
+ | Constructor | Optional fields |
15
+ | --- | --- |
16
+ | `orientation(selector :: String, directions :: List<Direction>)` | `hold: Hold`, `source: RuleSource` |
17
+ | `cyclic(selector :: String)` | `direction: Rotation`, `hold: Hold`, `source: RuleSource` |
18
+ | `align(selector :: String, direction :: Alignment)` | `hold: Hold`, `source: RuleSource` |
19
+ | `group(selector :: String, name :: String)` | `add-edge: GroupAddEdge`, `show-label: Boolean`, `text-style: TextStyle`, `hold: Hold`, `source: RuleSource` |
20
+ | `size(width :: Number, height :: Number)` | `selector: String`, `source: RuleSource` |
21
+ | `hide-atom(selector :: String)` | `source: RuleSource` |
22
+ | `flag(name :: LayoutFlag)` | |
23
+ | `atom-style(selector :: String, style :: Any)` | A record with optional `fill-style`, `border-style`, `icon-style`, `text-style`, `show-label`, and `source` keys. |
24
+ | `edge-style(field :: String, style :: Any)` | A record with optional `selector`, `filter`, `line-style`, `text-style`, `show-label`, `hidden`, and `source` keys. |
25
+ | `attribute(field :: String)` | `selector: String`, `filter: String`, `text-style: TextStyle`, `source: RuleSource` |
26
+ | `tag(to-tag :: String, name :: String, value :: String)` | `text-style: TextStyle`, `source: RuleSource` |
27
+ | `hide-field(field :: String)` | `selector: String`, `filter: String`, `source: RuleSource` |
28
+ | `inferred-edge(name :: String, selector :: String)` | `draw: String`, `line-style: LineStyle`, `text-style: TextStyle`, `color: String`, `style: LinePattern`, `weight: Number`, `highlight: String`, `source: RuleSource` |
29
+ | `icon(selector :: String, path :: String)` (deprecated; use atom-style) | `show-labels: Boolean`, `source: RuleSource` |
30
+ | `atom-color(value :: String, selector :: String)` (deprecated; use atom-style) | `source: RuleSource` |
31
+ | `edge-color(field :: String, value :: String)` (deprecated; use edge-style) | `selector: String`, `filter: String`, `style: LinePattern`, `weight: Number`, `highlight: String`, `show-label: Boolean`, `hidden: Boolean`, `source: RuleSource` |
32
+
33
+ ## Enum values
34
+
35
+ - `TextSize`: `small`, `normal`, `large`
36
+ - `LinePattern`: `solid`, `dashed`, `dotted`
37
+ - `IconPlacement`: `full`, `badge`
38
+ - `Direction`: `above`, `below`, `left`, `right`, `directly-above`, `directly-below`, `directly-left`, `directly-right`
39
+ - `Hold`: `always`, `never`
40
+ - `Rotation`: `clockwise`, `counterclockwise`
41
+ - `Alignment`: `horizontal`, `vertical`
42
+ - `GroupEdgeDirection`: `no-group-edge`, `togroup`, `fromgroup`
43
+ - `LayoutFlag`: `hide-disconnected`, `hide-disconnected-built-ins`
44
+
45
+ ## Blocks
46
+
47
+ - `text-style({size: TextSize?, color: String?})`
48
+ - `line-style({color: String?, pattern: LinePattern?, weight: Number?, highlight: String?})`
49
+ - `fill-style({color: String?})`
50
+ - `border-style({color: String?, width: Number?})`
51
+ - `icon-style({path: String?, placement: IconPlacement?, opacity: Number?})`
52
+ - `rule-source({text: String, location: String?})`
53
+ - `group-add-edge({points: GroupEdgeDirection?, line-style: LineStyle?, text-style: TextStyle?})`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spyret",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Headless interfaces between standard Pyret and Spytial",
5
5
  "license": "MIT",
6
6
  "author": "Siddhartha Prasad",
@@ -14,7 +14,8 @@
14
14
  "files": [
15
15
  "dist",
16
16
  "docs",
17
- "THIRD_PARTY_NOTICES.txt"
17
+ "THIRD_PARTY_NOTICES.txt",
18
+ "pyret"
18
19
  ],
19
20
  "exports": {
20
21
  ".": {
@@ -27,31 +28,51 @@
27
28
  "default": "./dist/spyret.js"
28
29
  }
29
30
  },
30
- "./global": "./dist/spyret.global.js"
31
+ "./global": "./dist/spyret.global.js",
32
+ "./spytial.arr": "./pyret/spytial.arr",
33
+ "./browser": {
34
+ "import": {
35
+ "types": "./dist/spyret-browser.d.mts",
36
+ "default": "./dist/spyret-browser.mjs"
37
+ },
38
+ "require": {
39
+ "types": "./dist/spyret-browser.d.ts",
40
+ "default": "./dist/spyret-browser.js"
41
+ }
42
+ },
43
+ "./browser/amd": "./dist/spyret-browser.amd.js",
44
+ "./pyret-module": "./dist/spyret.pyret.js"
31
45
  },
32
46
  "scripts": {
33
- "build": "tsup && node scripts/write-notices.mjs",
47
+ "build": "npm run check:spytial && tsup && node scripts/write-pyret-module.mjs && node scripts/write-notices.mjs",
34
48
  "typecheck": "tsc --noEmit",
35
49
  "test": "vitest run",
36
50
  "test:upstream": "node scripts/check-pyret-capture.mjs",
37
51
  "test:program": "node scripts/check-pyret-capture-program.mjs",
38
52
  "test:pbt": "node scripts/check-pyret-pbt.mjs",
39
53
  "prepack": "npm run typecheck && npm run build",
40
- "test:package": "npm run build && node scripts/check-package.mjs"
54
+ "test:package": "npm run build && node scripts/check-package.mjs",
55
+ "generate:spytial": "node scripts/generate-spytial.mjs",
56
+ "check:spytial": "node scripts/generate-spytial.mjs --check",
57
+ "test:spytial": "node scripts/check-spytial-program.mjs",
58
+ "make:drive-wrapper": "node scripts/make-drive-wrapper.mjs",
59
+ "release:drive": "node scripts/prepare-drive-release.mjs"
41
60
  },
42
61
  "engines": {
43
62
  "node": ">=22"
44
63
  },
45
64
  "devDependencies": {
65
+ "@types/js-yaml": "4.0.9",
46
66
  "@types/node": "^24.0.3",
47
67
  "fast-check": "^4.6.0",
68
+ "graphlib": "^2.1.8",
69
+ "js-yaml": "4.1.0",
48
70
  "jsdom": "^26.1.0",
71
+ "spytial-core": "6.3.2",
49
72
  "tsup": "^8.5.0",
50
73
  "tsx": "^4.21.0",
51
74
  "typescript": "^5.8.3",
52
- "vitest": "^3.2.4",
53
- "spytial-core": "6.3.1",
54
- "graphlib": "^2.1.8"
75
+ "vitest": "^3.2.4"
55
76
  },
56
77
  "publishConfig": {
57
78
  "access": "public",