@plutoframework/frontend 0.1.0 → 0.1.2

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.
package/README.md CHANGED
@@ -165,5 +165,44 @@ Tests cover transition rejection, duplicate actions, stale callbacks, reset,
165
165
  disposal, executor failures, React Strict Mode/SSR and 1,500 deterministic property
166
166
  runs over arbitrary event sequences. Mutation smoke is a bounded set of seeded
167
167
  mutations, not a comprehensive mutation engine. CI tests React 18.3.1 and 19.3.1.
168
- The package is published manually after inspecting its tarball; no npm credentials
169
- are stored in this repository or in GitHub Actions.
168
+ Releases publish through GitHub Actions using npm trusted publishing (OIDC);
169
+ no npm credentials are stored in this repository or in GitHub Actions.
170
+
171
+ ## Publish a release
172
+
173
+ The `publish.yml` workflow publishes stable `vMAJOR.MINOR.PATCH` tags using npm
174
+ trusted publishing and provenance. It checks tag/version agreement and main-branch
175
+ ancestry, runs tests and mutation checks, and verifies packed consumers before
176
+ publishing. Prereleases are rejected. Manual dispatch accepts an existing tag for
177
+ retrying a failed publish; npm never overwrites an already published version.
178
+
179
+ After merging the intended changes into main:
180
+
181
+ ```sh
182
+ npm version patch
183
+ git push origin main --follow-tags
184
+ ```
185
+
186
+ The one-time npm trusted publisher is scoped to:
187
+
188
+ | Setting | Value |
189
+ | -------------- | ---------------- |
190
+ | Organization | `plutoframework` |
191
+ | Repository | `pluto-frontend` |
192
+ | Workflow | `publish.yml` |
193
+ | Environment | None |
194
+ | Allowed action | `npm publish` |
195
+
196
+ The workflow uses GitHub-hosted runners with `id-token: write`; npm exchanges the
197
+ workflow identity for a short-lived publish credential. No `NPM_TOKEN` secret is
198
+ needed. See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
199
+
200
+ ## Source readability
201
+
202
+ Development tooling uses ESLint Stylistic with an 80-column expanded convention.
203
+ Run `npm run fmt` to apply the canonical Prettier + ESLint result. `npm run check`
204
+ checks formatting without writing, then lint, types, behavior tests and builds.
205
+ Braces, multiline blocks, separated object/type fields and logical blank lines
206
+ are enforced; nested ternaries require explicit branches or named mappings.
207
+ The policy is visible in `scripts/readability.mjs`. These are dev-only tools and
208
+ add no runtime dependencies to state or React entrypoints.
@@ -3,5 +3,8 @@ import { useSyncExternalStore } from "react";
3
3
  /** Subscribe only. The host owns the controller's reset/disposal lifecycle. */
4
4
  export function useMachine(machine) {
5
5
  const state = useSyncExternalStore(machine.subscribe, machine.getSnapshot, machine.getInitialSnapshot);
6
- return { state, send: machine.send };
6
+ return {
7
+ state,
8
+ send: machine.send,
9
+ };
7
10
  }
@@ -8,21 +8,25 @@ export function createMachine(options) {
8
8
  const listeners = new Set();
9
9
  const executions = new Set();
10
10
  function notify() {
11
- for (const listener of [...listeners])
11
+ for (const listener of [...listeners]) {
12
12
  listener();
13
+ }
13
14
  }
14
15
  function cancel() {
15
16
  // Fence callbacks BEFORE synchronous abort handlers can send events.
16
17
  epoch++;
17
- for (const controller of executions)
18
+ for (const controller of executions) {
18
19
  controller.abort();
20
+ }
19
21
  executions.clear();
20
22
  }
21
23
  async function send(event) {
22
- if (disposed)
24
+ if (disposed) {
23
25
  return false;
24
- if (evaluating)
26
+ }
27
+ if (evaluating) {
25
28
  throw new Error("Transitions must not send events");
29
+ }
26
30
  const generation = epoch;
27
31
  evaluating = true;
28
32
  let decision;
@@ -32,8 +36,9 @@ export function createMachine(options) {
32
36
  finally {
33
37
  evaluating = false;
34
38
  }
35
- if (!decision.accepted)
39
+ if (!decision.accepted) {
36
40
  return false;
41
+ }
37
42
  if (decision.effect !== undefined && !execute) {
38
43
  throw new Error("An effect requires an executor");
39
44
  }
@@ -44,8 +49,9 @@ export function createMachine(options) {
44
49
  try {
45
50
  notify();
46
51
  // Subscribers may reset/dispose the flow during notification.
47
- if (generation !== epoch || disposed)
52
+ if (generation !== epoch || disposed) {
48
53
  return true;
54
+ }
49
55
  if (decision.effect !== undefined && execute) {
50
56
  await execute(decision.effect, {
51
57
  signal: controller.signal,
@@ -68,8 +74,9 @@ export function createMachine(options) {
68
74
  getSnapshot: () => current,
69
75
  getInitialSnapshot: () => initial,
70
76
  subscribe(listener) {
71
- if (disposed)
77
+ if (disposed) {
72
78
  return () => { };
79
+ }
73
80
  listeners.add(listener);
74
81
  return () => {
75
82
  listeners.delete(listener);
@@ -77,15 +84,17 @@ export function createMachine(options) {
77
84
  },
78
85
  send,
79
86
  reset() {
80
- if (disposed)
87
+ if (disposed) {
81
88
  return;
89
+ }
82
90
  cancel();
83
91
  current = initial;
84
92
  notify();
85
93
  },
86
94
  dispose() {
87
- if (disposed)
95
+ if (disposed) {
88
96
  return;
97
+ }
89
98
  disposed = true;
90
99
  cancel();
91
100
  listeners.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plutoframework/frontend",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Explicit, typed frontend state transitions and React subscriptions for Pluto applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,22 +38,27 @@
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc -p tsconfig.build.json",
41
- "fmt": "prettier --write .",
42
- "check": "prettier --check . && tsc --noEmit && npm test && npm run build",
43
- "test": "vitest run",
41
+ "fmt": "node scripts/format.mjs && eslint .",
42
+ "check": "node scripts/format.mjs --check && eslint . && tsc --noEmit && npm test && npm run build",
43
+ "test": "vitest run && node --test scripts/format.test.mjs",
44
44
  "test:mutation": "python3 scripts/mutation.py",
45
45
  "prepack": "npm run build"
46
46
  },
47
47
  "devDependencies": {
48
- "typescript": "5.9.3",
49
- "vitest": "5.0.5",
50
- "fast-check": "4.10.2",
51
- "react": "19.3.1",
52
- "react-dom": "19.3.1",
48
+ "@eslint/js": "10.0.1",
49
+ "@stylistic/eslint-plugin": "5.10.0",
50
+ "@testing-library/react": "16.3.3",
53
51
  "@types/react": "19.3.0",
54
52
  "@types/react-dom": "19.3.0",
55
- "@testing-library/react": "16.3.3",
53
+ "eslint": "10.13.0",
54
+ "eslint-plugin-react-hooks": "7.1.1",
55
+ "fast-check": "4.10.2",
56
56
  "jsdom": "30.1.3",
57
- "prettier": "3.9.10"
57
+ "prettier": "3.9.10",
58
+ "react": "19.3.1",
59
+ "react-dom": "19.3.1",
60
+ "typescript": "5.9.3",
61
+ "typescript-eslint": "8.72.1",
62
+ "vitest": "5.0.5"
58
63
  }
59
64
  }