spyret 0.1.0 → 0.2.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,69 +1,26 @@
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:
12
-
13
- ```sh
14
- npm login
15
- npm install --global npm@11.20.0
16
- npm ci
17
- npm run typecheck
18
- npm test
19
- npm run test:package -- --out release
20
- npm publish ./release/spyret-0.1.0.tgz --access public
21
- ```
22
-
23
- Also require green upstream PBT CI for this commit before publishing. The package
24
- check installs the actual tarball into an empty project and exercises
25
- CJS, ESM, browser and TypeScript consumers.
26
-
27
- After the package exists, configure its npm **Trusted Publisher** settings:
28
-
29
- | Setting | Value |
30
- | --- | --- |
31
- | Provider | GitHub Actions |
32
- | Organization/user | `sidprasad` |
33
- | Repository | `spyret` |
34
- | Workflow filename | `release.yml` |
35
- | Environment | Leave empty |
36
- | Allowed action | Enable direct `npm publish` |
37
-
38
- With npm 11.15 or later, the equivalent command is:
3
+ With Node 22+, [GitHub CLI](https://cli.github.com/), and `tar` installed, run
4
+ from this checkout (no `npm install` or build needed):
39
5
 
40
6
  ```sh
41
- npm trust github spyret --repo sidprasad/spyret --file release.yml --allow-publish
7
+ npm run release:drive
42
8
  ```
43
9
 
44
- See [npm's trusted publishing documentation](https://docs.npmjs.com/trusted-publishers/).
45
- No npm token needs to be stored in GitHub. Publishing uses OIDC and provenance.
46
-
47
- ## Later releases
48
-
49
- 1. On a release branch, run `npm version patch --no-git-tag-version` (or choose a
50
- minor/major bump), commit both manifests, and merge after CI passes.
51
- 2. On the updated `main`, tag that exact commit and push the tag:
52
-
53
- ```sh
54
- git tag v0.1.1
55
- git push origin v0.1.1
56
- ```
10
+ The script downloads the latest GitHub release and walks you through:
57
11
 
58
- 3. `release.yml` verifies the tag matches `package.json` and belongs to `main`,
59
- runs the unit/package suite and both standard-Pyret PBT seeds, tests the packed
60
- artifact, publishes it to npm, and attaches it to a GitHub release.
12
+ 1. Upload the prepared `.js` file to your Google Drive. Set **Anyone with the
13
+ link / Viewer**, then paste its Drive link into the script.
14
+ 2. Upload the generated `.arr` file with the same sharing setting and paste
15
+ its Drive link into the script.
16
+ 3. Copy the printed import into CPO and run the provided diagram example.
17
+ Share that import line with users: it includes typed constructors and diagrams.
61
18
 
62
- Prerelease versions publish under `next`; stable versions use `latest`. A failed
63
- workflow can be rerun or manually dispatched against the same **tag**. An existing
64
- npm version is accepted only when its integrity matches the tested tarball;
65
- different contents require a version bump.
19
+ Keep both filenames unchanged and upload them as files without conversion.
20
+ The two upload files are in `release/vVERSION/upload/`; the CPO example is
21
+ saved separately as `release/vVERSION/import.arr`. Keep published Drive files
22
+ unchanged so existing imports continue to work.
66
23
 
67
- For the manually bootstrapped 0.1.0 release, push `v0.1.0` after configuring
68
- trusted publishing. The workflow verifies that identical package and creates
69
- the corresponding GitHub release.
24
+ To select a specific release, use `npm run release:drive -- --tag v0.2.0`.
25
+ The release must contain the browser module and Pyret rules; `v0.1.1` is headless
26
+ and cannot be used. No Google Cloud project, service account, or API setup is needed.
@@ -0,0 +1,73 @@
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.direction-below]),
17
+ S.align("siblings", S.alignment-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. Optional fields and
26
+ style blocks have typed, immutable setters:
27
+
28
+ ```pyret
29
+ S.atom-style-with(S.default-atom-style-options
30
+ .with-selector("leaf")
31
+ .with-fill-style(S.default-fill-style.with-color("red")))
32
+ ```
33
+
34
+ From the JavaScript host, after evaluating the program:
35
+
36
+ ```js
37
+ import { toDataInstance, getSpytialSpec } from 'spyret';
38
+
39
+ const instance = toDataInstance(pyretValue, runtime);
40
+ const specs = await getSpytialSpec(pyretValue, runtime); // string[]
41
+ ```
42
+
43
+ The collector walks reachable values in breadth-first order, including lists,
44
+ objects, tuples, arrays, references, dictionaries and table cells. It handles
45
+ cycles and invokes each distinct `_spytial` function or method once per call,
46
+ with methods bound to their first encountered owner. Repeated hooks never stop
47
+ the traversal of an instance's children. Hooks should describe types, independent
48
+ of the particular instance; unobserved datatype variants cannot be discovered.
49
+
50
+ Each hook returns `List<SpytialRule>`, serialized as one YAML document. A raw YAML
51
+ string is also accepted as an escape hatch, unchanged and unvalidated. No hooks
52
+ yields `[]`; a hook returning an empty list yields one empty spec. Identical
53
+ documents from different hooks remain separate. Hook failures, malformed rules
54
+ and unsupported values reject the promise with a `SpytialSpecError` and path.
55
+
56
+ Hooks execute user code. Call the async collector while its owning runtime is
57
+ idle or paused; a native Pyret module can use `runtime.pauseStack` around the
58
+ promise. `spytialRulesToYaml(rules, runtime)` also serializes an already evaluated
59
+ rule list directly. Capture and `toDataInstance` never invoke hooks, and omit
60
+ callable `_spytial` metadata on ordinary objects or outside a datatype's declared
61
+ slots. Other capture restrictions still apply.
62
+
63
+ See the [generated rule reference](SPYTIAL_RULES_REFERENCE.md) for every
64
+ constructor, enum and option. Pyret annotations check field types; serialization
65
+ checks numeric bounds, string patterns and incompatible directions. Selector
66
+ meaning and result arity remain Core's responsibility.
67
+
68
+ To update the API, pin the desired Core release in `package.json` and the lockfile,
69
+ then run `npm run generate:spytial`. Commit the generated Pyret source, serializer
70
+ schema and reference together. `npm run check:spytial` and the tests detect drift;
71
+ unknown field types or enum vocabularies fail generation for explicit handling.
72
+ No Core runtime dependency is added to the published package.
73
+
@@ -0,0 +1,51 @@
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
+ Optional fields use typed options. Start with `S.default-<rule>-options`,
7
+ chain `.with-<field>(value)`, then pass it to `<rule>-with` after the required arguments.
8
+ Style blocks similarly offer `default-<block>` and `.with-<field>(value)`.
9
+
10
+ Numeric bounds, patterns and incompatible direction combinations are checked during serialization.
11
+
12
+ | Constructor | Optional fields |
13
+ | --- | --- |
14
+ | `orientation(selector :: String, directions :: List<Direction>)` | `hold: Hold`, `source: RuleSource` |
15
+ | `cyclic(selector :: String)` | `direction: Rotation`, `hold: Hold`, `source: RuleSource` |
16
+ | `align(selector :: String, direction :: Alignment)` | `hold: Hold`, `source: RuleSource` |
17
+ | `group(selector :: String, name :: String)` | `add-edge: GroupAddEdge`, `show-label: Boolean`, `text-style: TextStyle`, `hold: Hold`, `source: RuleSource` |
18
+ | `size(width :: Number, height :: Number)` | `selector: String`, `source: RuleSource` |
19
+ | `hide-atom(selector :: String)` | `source: RuleSource` |
20
+ | `flag(name :: LayoutFlag)` | |
21
+ | `atom-style()` | `selector: String`, `fill-style: FillStyle`, `border-style: BorderStyle`, `icon-style: IconStyle`, `text-style: TextStyle`, `show-label: Boolean`, `source: RuleSource` |
22
+ | `edge-style(field :: String)` | `selector: String`, `filter: String`, `line-style: LineStyle`, `text-style: TextStyle`, `show-label: Boolean`, `hidden: Boolean`, `source: RuleSource` |
23
+ | `attribute(field :: String)` | `selector: String`, `filter: String`, `text-style: TextStyle`, `source: RuleSource` |
24
+ | `tag(to-tag :: String, name :: String, value :: String)` | `text-style: TextStyle`, `source: RuleSource` |
25
+ | `hide-field(field :: String)` | `selector: String`, `filter: String`, `source: RuleSource` |
26
+ | `inferred-edge(name :: String, selector :: String)` | `draw: String`, `line-style: LineStyle`, `text-style: TextStyle`, `color: String`, `style: LinePattern`, `weight: Number`, `highlight: String`, `source: RuleSource` |
27
+ | `icon(selector :: String, path :: String)` (deprecated; use atom-style) | `show-labels: Boolean`, `source: RuleSource` |
28
+ | `atom-color(value :: String, selector :: String)` (deprecated; use atom-style) | `source: RuleSource` |
29
+ | `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` |
30
+
31
+ ## Enum values
32
+
33
+ - `TextSize`: `text-size-small`, `text-size-normal`, `text-size-large`
34
+ - `LinePattern`: `line-pattern-solid`, `line-pattern-dashed`, `line-pattern-dotted`
35
+ - `IconPlacement`: `icon-placement-full`, `icon-placement-badge`
36
+ - `Direction`: `direction-above`, `direction-below`, `direction-left`, `direction-right`, `direction-directly-above`, `direction-directly-below`, `direction-directly-left`, `direction-directly-right`
37
+ - `Hold`: `hold-always`, `hold-never`
38
+ - `Rotation`: `rotation-clockwise`, `rotation-counterclockwise`
39
+ - `Alignment`: `alignment-horizontal`, `alignment-vertical`
40
+ - `GroupEdgeDirection`: `group-edge-direction-none`, `group-edge-direction-togroup`, `group-edge-direction-fromgroup`
41
+ - `LayoutFlag`: `layout-flag-hide-disconnected`, `layout-flag-hide-disconnected-built-ins`
42
+
43
+ ## Blocks
44
+
45
+ - `text-style(size: Option<TextSize>, color: Option<String>)`
46
+ - `line-style(color: Option<String>, pattern: Option<LinePattern>, weight: Option<Number>, highlight: Option<String>)`
47
+ - `fill-style(color: Option<String>)`
48
+ - `border-style(color: Option<String>, width: Option<Number>)`
49
+ - `icon-style(path: Option<String>, placement: Option<IconPlacement>, opacity: Option<Number>)`
50
+ - `rule-source(text: String, location: Option<String>)`
51
+ - `group-add-edge(points: Option<GroupEdgeDirection>, line-style: Option<LineStyle>, text-style: Option<TextStyle>)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spyret",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",