@danieljvdm/dev-kit 0.11.1 → 0.11.3

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
@@ -376,8 +376,8 @@ install, runs `dev-kit apply --locked`, and only then runs custom preparation,
376
376
  formatting, linting, tests, and typechecking. Its default typecheck command is
377
377
  `vp run typecheck`; `workflow.typecheck` replaces it. Vite+ maps install flags
378
378
  to the detected package manager. The template installs the consumer's declared
379
- Bun version and pins exact `setup-bun` and `setup-vp` releases; the latter's
380
- `v1` tag is frozen. Keep both current with Renovate or Dependabot. Existing
379
+ Bun version, follows the maintained `setup-bun@v2` tag, and names the current
380
+ `setup-vp` release because that action's `v1` tag is frozen. Existing
381
381
  workflows remain user-owned until their rendered content matches exactly—Dev
382
382
  Kit never merges YAML. See the primary
383
383
  [`setup-vp` versioning guidance](https://github.com/voidzero-dev/setup-vp#versioning),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danieljvdm/dev-kit",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "private": false,
5
5
  "description": "Declarative project development toolkit with portable agent skills.",
6
6
  "license": "MIT",
@@ -146,9 +146,9 @@ and `workflow.typecheck`; treat these commands as trusted manifest input.
146
146
 
147
147
  The workflow must use one frozen, script-suppressed install, then locked Dev Kit
148
148
  convergence before preparation or checks. Set up Bun from the consumer's
149
- `packageManager` or `engines.bun` declaration before Vite+ setup. Keep both
150
- `setup-bun` and `setup-vp` pinned to reviewed release commits—the `setup-vp`
151
- `v1` tag is frozenand let Vite+ resolve the consumer's compatible locked
149
+ `packageManager` or `engines.bun` declaration before Vite+ setup. Follow the
150
+ maintained `setup-bun` major tag, name an explicit `setup-vp` release because
151
+ its `v1` tag is frozen, and let Vite+ resolve the consumer's compatible locked
152
152
  version.
153
153
 
154
154
  ## Ownership and conflicts
@@ -110,7 +110,7 @@ When the task touches one of these areas, consult the matching guide before impl
110
110
  values, time zones, DST-safe arithmetic, formatting, Date interoperability,
111
111
  and deterministic `TestClock` tests
112
112
  - `./references/guide-atom-data-fetching.md` for the core Effect Atom HTTP
113
- data-fetching workflow and ownership rules
113
+ data-fetching workflow, React hook choice, and action-lifetime ownership rules
114
114
  - `./references/atom-cache-lifecycle.md` for Effect Atom registry scope,
115
115
  runtime memoization, families, TTL, SWR, polling, and aggregation resets
116
116
  - `./references/atom-http-and-invalidation.md` for `AtomHttpApi.Service`,
@@ -6,6 +6,12 @@
6
6
 
7
7
  Place one provider around the client application subtree that should share data. Nested or route-local providers create separate caches.
8
8
 
9
+ ### React mounts and registry disposal
10
+
11
+ `useAtomValue` keeps an atom active through its value subscription. `useAtomSet` and `useAtomRefresh` mount an atom through a React effect, while `useAtom` combines one value subscription with a setter. Use the combined hook when one component reads and writes the same atom; composing `useAtomValue` and `useAtomSet` adds a second mount and obscures which lifetime owns the work.
12
+
13
+ Unmount releases only that hook's subscription or mount. The registry removes a node only after it has no remaining consumers and its idle TTL permits removal. Node disposal runs registered finalizers, including cancellation of interruptible Effect work. Therefore component unmount, registry eviction, and `Atom.Interrupt` are distinct events; do not use an unconditional interrupt write as a substitute for releasing a React mount.
14
+
9
15
  An atom runtime and a registry solve different problems:
10
16
 
11
17
  - the registry stores atom nodes, values, subscriptions, idle timers, and finalizers;
@@ -51,6 +51,10 @@ await mutate({
51
51
 
52
52
  The mutation's `reactivityKeys` are invalidated only after the request succeeds. Failed mutations do not invalidate. Keep navigation, toasts, dialog closure, and optimistic UI at the action owner; keep shared server-state refresh in reactivity keys.
53
53
 
54
+ If the component also renders the mutation result, use `useAtom(updateProject)` instead of pairing `useAtomValue` with `useAtomSet`. A module-scoped mutation atom is shared registry state, so an unconditional cleanup write of `Atom.Interrupt` can cancel work owned by another consumer and publishes an interrupted failure.
55
+
56
+ Keep a multi-step mutation sequence in an owner that lives for the whole sequence. When navigation can unmount the initiating route after one request succeeds, a component-owned sequential fan-out can leave a partially completed operation. Use one stable workflow atom or service when client ownership is sufficient; use one server-side command or durable workflow when completion must survive browser navigation or disconnects.
57
+
54
58
  ## Use one key vocabulary
55
59
 
56
60
  Array keys represent independent keys. Record keys support hierarchical broad-plus-entity invalidation:
@@ -56,6 +56,10 @@ Prefer the installed test APIs over invented helpers or matchers. The upstream E
56
56
  3. Run a failed mutation; assert no invalidation.
57
57
  4. Assert cleanup removes invalidation handlers after query disposal.
58
58
 
59
+ ### React Strict Mode and action ownership
60
+
61
+ Use a React integration test when hook cleanup or action ownership is part of the behavior. Render the owner inside `StrictMode` and assert the development setup-cleanup replay does not write `Atom.Interrupt` or publish an interrupted failure before an explicit cancellation. When an action spans multiple requests, unmount during the sequence and prove the chosen policy: either the stable owner completes every step, or an explicit cancellation interrupts it intentionally. If the atom is shared, also prove unmounting one consumer does not cancel work still owned by another.
62
+
59
63
  ### Aggregate stability
60
64
 
61
65
  Mount a route atom that uses `AsyncResult.all`, resolve every input, unmount, and remount inside the retention window. Assert the aggregate never returns to `Initial`. Then let one input expire and prove the aggregate reset is caused by that input, not the retained queries.
@@ -23,6 +23,22 @@ Model server data as stable atoms owned outside React renders. Give the applicat
23
23
  - Ensure every input to a route-level `AsyncResult.all` has stable atom identity and compatible retention. One newly allocated or immediately evicted input returns to `Initial` and makes the whole aggregate appear reset even when the other inputs remain cached.
24
24
  - Do not describe manual refresh or polling as freshness caching. Refresh is forceful; `staleTime` only gates SWR's automatic mount/focus decisions.
25
25
 
26
+ ## React hooks and action lifetime
27
+
28
+ Choose the hook that expresses the component's ownership:
29
+
30
+ - `useAtomValue(atom)` subscribes for rendering without returning a setter;
31
+ - `useAtomSet(atom)` mounts a writable atom and returns a setter without rendering from its value;
32
+ - `useAtom(atom)` subscribes and returns a setter when the same component both renders and writes the atom.
33
+
34
+ Do not pair `useAtomValue(atom)` with `useAtomSet(atom)` for the same atom in one component. The split form creates both a value subscription and a separate mount. Use `useAtom(atom)` instead. Calling an `Atom.family` directly with a stable primitive during render already returns the stable family member; a surrounding `useMemo` is not needed merely to preserve atom identity.
35
+
36
+ The React hooks release their subscriptions and mounts when their component lifetime ends. Registry node removal then follows the atom's idle TTL and remaining consumers, and node disposal runs the atom lifetime finalizers. Do not confuse hook cleanup, idle retention, and operation cancellation: another consumer or a nonzero TTL can intentionally keep the node and its work alive after one component unmounts.
37
+
38
+ Do not add a cleanup-only effect such as `useEffect(() => () => set(Atom.Interrupt), [set])` merely to mirror component unmount. `Atom.Interrupt` is a write that publishes an interrupted `AsyncResult.Failure`, not a passive release function. The cleanup also runs on dependency changes and during React Strict Mode's development effect replay, and it can interrupt work still owned by another consumer. Use `Atom.Interrupt` for an explicit user cancellation or a deliberately scoped cancellation policy, and test that ownership under Strict Mode.
39
+
40
+ Decide whether an action must survive its initiating component before choosing its owner. A sequential client fan-out can partially complete if route unmount disposes its owner after earlier mutations succeed but before later ones start. If the whole sequence must be durable, move it to a stable workflow owner or one server-side command instead of relying on a route component's hook lifetime.
41
+
26
42
  ## Completion check
27
43
 
28
- Confirm one registry boundary, stable atom identity, deliberate TTL/staleness/polling values, matching query and mutation keys, SSR-safe browser access, and tests for every lifecycle behavior changed.
44
+ Confirm one registry boundary, stable atom identity, deliberate TTL/staleness/polling values, matching query and mutation keys, an intentional React hook and action lifetime, SSR-safe browser access, and tests for every lifecycle behavior changed.
package/src/oxlint.js CHANGED
@@ -24,7 +24,7 @@ export const recommendedOxlintConfig = {
24
24
  "import/default": "off",
25
25
  "import/namespace": "off",
26
26
  "import/no-cycle": "error",
27
- "import/no-duplicates": "error",
27
+ "import/no-duplicates": ["error", { preferInline: true }],
28
28
  "import/no-self-import": "error",
29
29
  "react/exhaustive-deps": "error",
30
30
  "react/rules-of-hooks": "error",
@@ -38,7 +38,10 @@ export const recommendedOxlintConfig = {
38
38
  },
39
39
  { blankLine: "always", prev: "*", next: "return" },
40
40
  ],
41
- "typescript/consistent-type-imports": "error",
41
+ "typescript/consistent-type-imports": [
42
+ "error",
43
+ { fixStyle: "inline-type-imports", prefer: "type-imports" },
44
+ ],
42
45
  "typescript/no-floating-promises": "off",
43
46
  "typescript/no-explicit-any": "error",
44
47
  "typescript/no-misused-spread": "off",
package/src/oxlint.ts CHANGED
@@ -27,7 +27,7 @@ export const recommendedOxlintConfig = {
27
27
  "import/default": "off",
28
28
  "import/namespace": "off",
29
29
  "import/no-cycle": "error",
30
- "import/no-duplicates": "error",
30
+ "import/no-duplicates": ["error", { preferInline: true }],
31
31
  "import/no-self-import": "error",
32
32
  "react/exhaustive-deps": "error",
33
33
  "react/rules-of-hooks": "error",
@@ -41,7 +41,10 @@ export const recommendedOxlintConfig = {
41
41
  },
42
42
  { blankLine: "always", prev: "*", next: "return" },
43
43
  ],
44
- "typescript/consistent-type-imports": "error",
44
+ "typescript/consistent-type-imports": [
45
+ "error",
46
+ { fixStyle: "inline-type-imports", prefer: "type-imports" },
47
+ ],
45
48
  "typescript/no-floating-promises": "off",
46
49
  "typescript/no-explicit-any": "error",
47
50
  "typescript/no-misused-spread": "off",
@@ -24,12 +24,10 @@ jobs:
24
24
 
25
25
  # setup-bun resolves the consumer's packageManager or engines.bun version.
26
26
  - name: Set up Bun
27
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # setup-bun action v2.2.0
27
+ uses: oven-sh/setup-bun@v2
28
28
 
29
- # setup-vp's v1 tag is frozen. Keep this v1.16.1 commit current with
30
- # Renovate or Dependabot so fixes arrive through reviewed pull requests.
31
29
  - name: Set up Vite+ and install dependencies
32
- uses: voidzero-dev/setup-vp@143f5f385f39b1b753ffed1a01ad443811855c8b # v1.16.1
30
+ uses: voidzero-dev/setup-vp@v1.16.1
33
31
  with:
34
32
  # Vite+ version intentionally resolves from the consumer manifest/lock.
35
33
  node-version: "24"