@deslop/coding-standards 0.1.871 → 0.1.885

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/dist/install.js CHANGED
@@ -24685,7 +24685,7 @@ const runWith = (command, config) => {
24685
24685
  };
24686
24686
  //#endregion
24687
24687
  //#region package.json
24688
- var version = "0.1.871";
24688
+ var version = "0.1.885";
24689
24689
  //#endregion
24690
24690
  //#region src/install.ts
24691
24691
  const cli = pipe(make("deslop-coding-standards", {}, fnUntraced(function* () {
package/dist/oxlint.d.ts CHANGED
@@ -4020,8 +4020,10 @@ export declare const oxlint: {
4020
4020
  'shadcn/no-restyle'?: undefined;
4021
4021
  'shadcn/require-static-classes'?: undefined;
4022
4022
  '@deslop/coding-standards/no-destructured-parameter'?: undefined;
4023
+ '@deslop/coding-standards/no-native-control'?: undefined;
4023
4024
  '@deslop/coding-standards/no-renamed-import'?: undefined;
4024
4025
  'react/rules-of-hooks'?: undefined;
4026
+ '@deslop/coding-standards/no-discarded-failure'?: undefined;
4025
4027
  'effecttsgo/async-function'?: undefined;
4026
4028
  'typescript/require-await'?: undefined;
4027
4029
  'typescript/strict-void-return'?: undefined;
@@ -4034,8 +4036,10 @@ export declare const oxlint: {
4034
4036
  'shadcn/no-restyle': "off";
4035
4037
  'shadcn/require-static-classes': "off";
4036
4038
  '@deslop/coding-standards/no-destructured-parameter'?: undefined;
4039
+ '@deslop/coding-standards/no-native-control'?: undefined;
4037
4040
  '@deslop/coding-standards/no-renamed-import'?: undefined;
4038
4041
  'react/rules-of-hooks'?: undefined;
4042
+ '@deslop/coding-standards/no-discarded-failure'?: undefined;
4039
4043
  'effecttsgo/async-function'?: undefined;
4040
4044
  'typescript/require-await'?: undefined;
4041
4045
  'typescript/strict-void-return'?: undefined;
@@ -4048,8 +4052,10 @@ export declare const oxlint: {
4048
4052
  'shadcn/no-restyle'?: undefined;
4049
4053
  'shadcn/require-static-classes'?: undefined;
4050
4054
  '@deslop/coding-standards/no-destructured-parameter': "off";
4055
+ '@deslop/coding-standards/no-native-control': "off";
4051
4056
  '@deslop/coding-standards/no-renamed-import': "off";
4052
4057
  'react/rules-of-hooks'?: undefined;
4058
+ '@deslop/coding-standards/no-discarded-failure'?: undefined;
4053
4059
  'effecttsgo/async-function'?: undefined;
4054
4060
  'typescript/require-await'?: undefined;
4055
4061
  'typescript/strict-void-return'?: undefined;
@@ -4062,8 +4068,10 @@ export declare const oxlint: {
4062
4068
  'shadcn/no-restyle'?: undefined;
4063
4069
  'shadcn/require-static-classes'?: undefined;
4064
4070
  '@deslop/coding-standards/no-destructured-parameter'?: undefined;
4071
+ '@deslop/coding-standards/no-native-control'?: undefined;
4065
4072
  '@deslop/coding-standards/no-renamed-import'?: undefined;
4066
4073
  'react/rules-of-hooks': "off";
4074
+ '@deslop/coding-standards/no-discarded-failure'?: undefined;
4067
4075
  'effecttsgo/async-function'?: undefined;
4068
4076
  'typescript/require-await'?: undefined;
4069
4077
  'typescript/strict-void-return'?: undefined;
@@ -4076,8 +4084,26 @@ export declare const oxlint: {
4076
4084
  'shadcn/no-restyle'?: undefined;
4077
4085
  'shadcn/require-static-classes'?: undefined;
4078
4086
  '@deslop/coding-standards/no-destructured-parameter'?: undefined;
4087
+ '@deslop/coding-standards/no-native-control'?: undefined;
4079
4088
  '@deslop/coding-standards/no-renamed-import'?: undefined;
4080
4089
  'react/rules-of-hooks'?: undefined;
4090
+ '@deslop/coding-standards/no-discarded-failure': "off";
4091
+ 'effecttsgo/async-function'?: undefined;
4092
+ 'typescript/require-await'?: undefined;
4093
+ 'typescript/strict-void-return'?: undefined;
4094
+ 'unicorn/no-null'?: undefined;
4095
+ };
4096
+ } | {
4097
+ files: string[];
4098
+ rules: {
4099
+ 'import/no-default-export'?: undefined;
4100
+ 'shadcn/no-restyle'?: undefined;
4101
+ 'shadcn/require-static-classes'?: undefined;
4102
+ '@deslop/coding-standards/no-destructured-parameter'?: undefined;
4103
+ '@deslop/coding-standards/no-native-control'?: undefined;
4104
+ '@deslop/coding-standards/no-renamed-import'?: undefined;
4105
+ 'react/rules-of-hooks'?: undefined;
4106
+ '@deslop/coding-standards/no-discarded-failure'?: undefined;
4081
4107
  'effecttsgo/async-function': "off";
4082
4108
  'typescript/require-await': "error";
4083
4109
  'typescript/strict-void-return': "off";
@@ -4090,12 +4116,14 @@ export declare const oxlint: {
4090
4116
  '@deslop/coding-standards/no-constant-function': "error";
4091
4117
  '@deslop/coding-standards/no-deep-pipe': "error";
4092
4118
  '@deslop/coding-standards/no-destructured-parameter': "error";
4119
+ '@deslop/coding-standards/no-discarded-failure': "error";
4093
4120
  '@deslop/coding-standards/no-double-nullish-check': "error";
4094
4121
  '@deslop/coding-standards/no-effect-property-arrow': "error";
4095
4122
  '@deslop/coding-standards/no-error-message-assertion': "error";
4096
4123
  '@deslop/coding-standards/no-fake-ref-state': "error";
4097
4124
  '@deslop/coding-standards/no-hand-written-guard': "error";
4098
4125
  '@deslop/coding-standards/no-let': "error";
4126
+ '@deslop/coding-standards/no-native-control': "error";
4099
4127
  '@deslop/coding-standards/no-native-emptiness-check': "error";
4100
4128
  '@deslop/coding-standards/no-native-method-call': "error";
4101
4129
  '@deslop/coding-standards/no-nullish-ternary': "error";
@@ -4308,11 +4336,15 @@ export declare const oxlint: {
4308
4336
  }];
4309
4337
  'import/no-relative-parent-imports': "error";
4310
4338
  'no-restricted-imports': ["error", {
4311
- paths: {
4339
+ paths: ({
4312
4340
  importNames: string[];
4313
4341
  message: string;
4314
4342
  name: string;
4315
- }[];
4343
+ } | {
4344
+ importNames?: undefined;
4345
+ message: string;
4346
+ name: string;
4347
+ })[];
4316
4348
  patterns: {
4317
4349
  message: string;
4318
4350
  regex: string;
package/dist/oxlint.js CHANGED
@@ -1021,6 +1021,27 @@ const noDestructuredParameter = defineRule({
1021
1021
  meta: { type: "problem" }
1022
1022
  });
1023
1023
  //#endregion
1024
+ //#region src/rules/no-discarded-failure.ts
1025
+ const noDiscardedFailure = defineRule({
1026
+ create: (context) => ({ MemberExpression: (node) => {
1027
+ if (!importedMember({
1028
+ context,
1029
+ importedName: "Effect",
1030
+ node
1031
+ })) return;
1032
+ if (!exists(memberName(node), (name) => contains$1([
1033
+ "ignore",
1034
+ "orDie",
1035
+ "orDieWith"
1036
+ ], name))) return;
1037
+ context.report({
1038
+ message: "Map the failure to the service's error; an expected failure is never ignored or turned into a defect.",
1039
+ node
1040
+ });
1041
+ } }),
1042
+ meta: { type: "problem" }
1043
+ });
1044
+ //#endregion
1024
1045
  //#region src/rules/no-double-nullish-check.ts
1025
1046
  function nullishOperand(node) {
1026
1047
  if (node.type === "Literal" && node.value === null) return some("null");
@@ -1284,6 +1305,30 @@ const noLet = defineRule({
1284
1305
  meta: { type: "problem" }
1285
1306
  });
1286
1307
  //#endregion
1308
+ //#region src/rules/no-native-control.ts
1309
+ const primitives = {
1310
+ button: "Button",
1311
+ input: "Input",
1312
+ select: "Select",
1313
+ textarea: "Textarea"
1314
+ };
1315
+ const controlStyle = /(?:^|:)(?:hover|focus|focus-visible|active):|(?:^|:)(?:rounded|shadow|ring)(?:-|$)|(?:^|:)p[blrtxy]?-(?!0$)|(?:^|:)bg-(?!transparent$)|(?:^|:)border(?:-[blrtxy])?$|(?:^|:)border-(?!0$|[blrtxy]-0$)/u;
1316
+ const noNativeControl = defineRule({
1317
+ create: (context) => ({ JSXOpeningElement: (node) => {
1318
+ if (node.name.type !== "JSXIdentifier") return;
1319
+ const name = node.name.name;
1320
+ map(get$2(primitives, name), (primitive) => {
1321
+ const styled = pipe(node.attributes, findFirst((attribute) => attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier" && attribute.name.name === "className"), flatMap((attribute) => attribute.type === "JSXAttribute" && attribute.value?.type === "Literal" ? fromNullishOr(attribute.value.value) : none()), exists((value) => some$1(split(value, /\s+/u), matches(controlStyle))));
1322
+ if (name !== "select" && !styled) return;
1323
+ context.report({
1324
+ message: `Use the ${primitive} UI primitive instead of a styled native control.`,
1325
+ node
1326
+ });
1327
+ });
1328
+ } }),
1329
+ meta: { type: "problem" }
1330
+ });
1331
+ //#endregion
1287
1332
  //#region src/rules/no-native-emptiness-check.ts
1288
1333
  function emptinessPredicate(node) {
1289
1334
  const comparesLength = node.left.type === "MemberExpression" && contains(memberName(node.left), "length") && node.right.type === "Literal" && node.right.value === 0;
@@ -2477,6 +2522,7 @@ const oxlint = defineConfig({
2477
2522
  files: ["**/components/ui/**", "**/packages/ui/**"],
2478
2523
  rules: {
2479
2524
  "@deslop/coding-standards/no-destructured-parameter": "off",
2525
+ "@deslop/coding-standards/no-native-control": "off",
2480
2526
  "@deslop/coding-standards/no-renamed-import": "off"
2481
2527
  }
2482
2528
  },
@@ -2484,6 +2530,10 @@ const oxlint = defineConfig({
2484
2530
  files: ["**/*.ts"],
2485
2531
  rules: { "react/rules-of-hooks": "off" }
2486
2532
  },
2533
+ {
2534
+ files: ["**/*.test.ts", "**/*.test.tsx"],
2535
+ rules: { "@deslop/coding-standards/no-discarded-failure": "off" }
2536
+ },
2487
2537
  {
2488
2538
  files: ["**/*.tsx"],
2489
2539
  rules: {
@@ -2508,12 +2558,14 @@ const oxlint = defineConfig({
2508
2558
  "@deslop/coding-standards/no-constant-function": "error",
2509
2559
  "@deslop/coding-standards/no-deep-pipe": "error",
2510
2560
  "@deslop/coding-standards/no-destructured-parameter": "error",
2561
+ "@deslop/coding-standards/no-discarded-failure": "error",
2511
2562
  "@deslop/coding-standards/no-double-nullish-check": "error",
2512
2563
  "@deslop/coding-standards/no-effect-property-arrow": "error",
2513
2564
  "@deslop/coding-standards/no-error-message-assertion": "error",
2514
2565
  "@deslop/coding-standards/no-fake-ref-state": "error",
2515
2566
  "@deslop/coding-standards/no-hand-written-guard": "error",
2516
2567
  "@deslop/coding-standards/no-let": "error",
2568
+ "@deslop/coding-standards/no-native-control": "error",
2517
2569
  "@deslop/coding-standards/no-native-emptiness-check": "error",
2518
2570
  "@deslop/coding-standards/no-native-method-call": "error",
2519
2571
  "@deslop/coding-standards/no-nullish-ternary": "error",
@@ -2730,6 +2782,10 @@ const oxlint = defineConfig({
2730
2782
  message: reactLegacyMessage,
2731
2783
  name: "react"
2732
2784
  },
2785
+ {
2786
+ message: "Read and write data through Effect Atom.",
2787
+ name: "@tanstack/react-query"
2788
+ },
2733
2789
  {
2734
2790
  importNames: ["vi"],
2735
2791
  message: "A Layer is the seam; never use vi.",
@@ -3206,12 +3262,14 @@ var oxlint_default = definePlugin({
3206
3262
  "no-constant-function": noConstantFunction,
3207
3263
  "no-deep-pipe": noDeepPipe,
3208
3264
  "no-destructured-parameter": noDestructuredParameter,
3265
+ "no-discarded-failure": noDiscardedFailure,
3209
3266
  "no-double-nullish-check": noDoubleNullishCheck,
3210
3267
  "no-effect-property-arrow": noEffectPropertyArrow,
3211
3268
  "no-error-message-assertion": noErrorMessageAssertion,
3212
3269
  "no-fake-ref-state": noFakeRefState,
3213
3270
  "no-hand-written-guard": noHandWrittenGuard,
3214
3271
  "no-let": noLet,
3272
+ "no-native-control": noNativeControl,
3215
3273
  "no-native-emptiness-check": noNativeEmptinessCheck,
3216
3274
  "no-native-method-call": noNativeMethodCall,
3217
3275
  "no-nullish-ternary": noNullishTernary,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deslop/coding-standards",
3
- "version": "0.1.871",
3
+ "version": "0.1.885",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/MP281X/deslop",
@@ -3,7 +3,7 @@ name: engineering
3
3
  description: 'TypeScript and Effect code conventions paired with shared lint. Use when designing, implementing or reviewing product/test code, including prototypes.'
4
4
  ---
5
5
 
6
- **Scope.** Apply these rules and repository-specific CODING_STANDARDS.md.
6
+ **Scope.** Apply these rules and repository-specific CODING_STANDARDS.md. Write each line right the first time: the examples exist so lint and review find nothing, and lint is only the fallback.
7
7
 
8
8
  **Dependency APIs.** Match the manifest and lockfile versions. Before adding an API wrapper or workaround, read the upstream source per workflow's research procedure. Library APIs do not define coding policy.
9
9
 
@@ -16,16 +16,16 @@ description: 'TypeScript and Effect code conventions paired with shared lint. Us
16
16
  **Experiments.** Write with these idioms; reserve the full refactor/cleanup pass for retained delivery. Keep disposable source isolated and shared rules intact.
17
17
 
18
18
  - **Reuse first.** Check existing code, installed modules and platform capabilities before adding code. Implement only the missing behavior, without dropping required behavior, safety or accessibility. Workflow owns the scope. A refactor or cleanup request applies every rule to every line of the owned files.
19
- - **Inward layers.** Domain and service code never import HTTP, RPC, or other transport types.
19
+ - **Inward layers.** Domain and service code never import HTTP, RPC, or other transport types, and domain schemas carry no transport annotation such as an HTTP status.
20
20
  - **Building blocks.** For a consequential new interface, start with a realistic caller operation and its ordering, failure and lifetime obligations; hide useful complexity, not forwarded names. Offer services, Layers and functions the caller composes like any Effect module, never a bundle such as `serve(app)`. No mandatory architecture document or alternatives round.
21
21
  - **No machinery.** Add hooks, guards or generators only for a required contract or a recurring defect.
22
- - **Behavior kept.** A refactor keeps observable behavior at every consumer. Report two accepted differences with the change: one no consumer observes, and one a rule here causes, such as an error keeping its cause. Printed diagnostics are not a contract. Any other behavior change follows workflow's scope.
22
+ - **Behavior kept.** A refactor keeps observable behavior at every consumer. Report accepted differences only when they exist: a change no consumer observes, or one a rule here causes, such as an error keeping its cause. Printed diagnostics are not a contract. Any other behavior change follows workflow's scope.
23
23
  - **Reachable bugs.** Fix a bug that real input reaches when it lies inside workflow's scope, and check every consumer of the changed output. For a hard bug, use one reproduction and one prediction per probe. Remove edits based on disproven hypotheses, then rerun the original scenario. Delete handling only for input the contract excludes; keep and report code that looks intentional.
24
- - **Performance.** Optimize only when the request or a required contract needs it, and measure realistic input before claiming a speedup. Diagnose at the layer that owns the symptom: distributed spans for RPC/backend dependencies, React commits for rerenders, a browser performance profile for main-thread work. Correlate one real action before changing code, then repeat it on the fix; neither a screenshot nor a service name proves latency or trace coverage. Time correct completed work under equivalent workloads/configuration; distinguish setup/cache/profiling cost and report run count/variation. Differences within noise are unproved, not speedups. Use existing instrumentation; profiling is not a ritual on every change.
24
+ - **Performance.** Optimize only when the request or a required contract needs it. Measure one real action on realistic input before and after the change, at the layer that owns the symptom: spans for backend calls, React commits for rerenders, a browser profile for main-thread work. Report run count and variation; a difference within noise is unproved.
25
25
  - **Type safety.** A fix never weakens types to make a symptom go away: no widened or erased type, cast, dropped generic, or loosened exported type.
26
26
  - **Domain.** Implement the definition the domain uses, such as a cycle for recursion, never the nearest syntactic proxy.
27
27
 
28
- **Happy path.** Failures flow through the error channel: no catch, retry, fallback, backup, or defensive check unless the request or an existing contract needs it.
28
+ **Happy path.** Failures flow through the error channel: no catch, retry, fallback, backup, or defensive check unless the request or an existing contract needs it. Map an expected failure to the service's error; never `Effect.ignore` it or turn it into a defect with `Effect.orDie`.
29
29
 
30
30
  **Trusted data.** Validate and transform once at the boundary with Effect Schema. Carry narrowed values forward. Never re-check what the schema, the declared type, an earlier filter or every caller guarantees.
31
31
 
@@ -72,7 +72,7 @@ _input => onSubmit(_input.value) // or input2: an outer input is shadowed
72
72
 
73
73
  **Nearest sibling.** Extend the nearest implementation; match permissions, errors, refresh and tests. Reuse its helpers and change only affected state.
74
74
 
75
- **Once.** Extract repeated logic once; one fact has one owner.
75
+ **Once.** Extract repeated logic once; one fact has one owner. A domain enum, such as a status, has one schema that every other schema reuses, and a cache key or atom family key carries the full identity of what it caches.
76
76
 
77
77
  ```ts
78
78
  // good
@@ -96,7 +96,7 @@ pipe(
96
96
 
97
97
  **Present needs.** No speculative option, abstraction, layer, export, file or check. Configure only what callers vary.
98
98
 
99
- **Dead code.** Every superseded or unused file, export, type, branch, config, validation, doc, test, and dependency goes in the same change.
99
+ **Dead code.** Every superseded or unused file, export, type, branch, config, validation, doc, test, and dependency goes in the same change. Docs, guides and generated bundles that describe changed behavior change in the same commit.
100
100
 
101
101
  ## Lint pairs
102
102
 
@@ -149,22 +149,7 @@ Array.appendAll(
149
149
  )
150
150
  ```
151
151
 
152
- **Choices.** `Match` for a union, never `switch`.
153
-
154
- ```ts
155
- // good
156
- pipe(
157
- Match.value(usage.kind),
158
- Match.when('input', () => 'in'),
159
- Match.when('output', () => 'out'),
160
- Match.exhaustive
161
- )
162
- // bad
163
- switch (usage.kind) {
164
- case 'input':
165
- return 'in'
166
- }
167
- ```
152
+ **Choices.** `Match` with `Match.exhaustive` for a union, never `switch`.
168
153
 
169
154
  **Options.** Use Option operators, never .value. Use Option where it composes; keep plain optional values otherwise. This is our convention, not a claim narrowing is unsafe.
170
155
 
@@ -187,7 +172,9 @@ const parsed = Option.getOrUndefined(Number.parse(event.target.value))
187
172
  // good
188
173
  const total = Array.reduce(values, 0, Number.sum)
189
174
  pipe(teamId, String.split(','), Array.map(String.trim), Array.findFirst(String.isNonEmpty))
190
- Array.filterMap(items, item => (supports(item) ? Result.succeed(item.value) : Result.failVoid))
175
+ Array.filterMap(items, item =>
176
+ Boolean.match(supports(item), {onFalse: () => Result.failVoid, onTrue: () => Result.succeed(item.value)})
177
+ )
191
178
  // bad
192
179
  let total = 0
193
180
  for (const value of values) total += value
@@ -399,7 +386,7 @@ const traced = Effect.withSpan('Notes.create')(notes.create(input))
399
386
  Effect.addFinalizer(() => closeSocket(socket)) // acquisition and release with different owners
400
387
  ```
401
388
 
402
- **Runtime.** The owned runtime runs the program; a Promise an external contract demands is bridged once, where it is returned.
389
+ **Runtime.** The owned runtime runs the program; a Promise an external contract demands is bridged once, where it is returned. React code is no such contract: atoms run its Effects.
403
390
 
404
391
  ```ts
405
392
  // good
@@ -559,7 +546,7 @@ const warned = ['sort-keys', 'typescript/no-restricted-types'] // a package rule
559
546
 
560
547
  ## React
561
548
 
562
- **Components.** React Compiler memoizes. Destructure state at its declaration, receive component refs through props, and keep shared logic in atoms.
549
+ **Components.** React Compiler memoizes. Destructure state at its declaration, receive component refs through props, and keep only a component's own interaction state in React.
563
550
 
564
551
  ```tsx
565
552
  // good
@@ -571,6 +558,41 @@ const handle = useCallback(() => setOpen(true), [])
571
558
  const state = useState(false)
572
559
  ```
573
560
 
561
+ **Atoms.** Data, shared state and every Effect a component needs live in atoms; a component reads them with hooks and never runs an Effect. A task that changes a React Query hook, a fetching Effect or an Effect runner in React code moves that code to an atom. Declare atoms at module level or in `Atom.family`, never during render. Import `Atom` and `AsyncResult` from `effect/reactivity` and the hooks from `@effect/atom-react`. Supply services through `Atom.runtime(layer)`, and build every read and write with that runtime's `atom` and `fn`. Tie each write to the reads it changes with reactivity keys; only a runtime's `fn` honors them, so never pass them to `Atom.fn`.
562
+
563
+ ```tsx
564
+ // good
565
+ const appsRuntime = Atom.runtime(DualApps.layer)
566
+ const appAtom = Atom.family((id: AppId) => pipe(appsRuntime.atom(loadApp(id)), Atom.withReactivity(['apps'])))
567
+ const saveAppAtom = appsRuntime.fn((draft: AppDraft) => saveApp(draft), {reactivityKeys: ['apps']})
568
+ const app = useAtomValue(appAtom(appId))
569
+ const [saveResult, save] = useAtom(saveAppAtom)
570
+ // bad
571
+ useQuery({queryFn: () => Effect.runPromiseWith(Context.empty())(loadApp(appId))})
572
+ onClick={() => Effect.runPromise(saveApp(draft))}
573
+ useAtomValue(Atom.make(loadApp(appId))) // a new atom on every render
574
+ ```
575
+
576
+ **Outcomes.** Read data with `useAtomSuspense`. The route or section that owns a screen wraps it in one `Suspense` boundary for loading and one error boundary that shows the failure with a retry. Components never branch on loading or failure, and never turn a failure into an empty value. Take a write's pending and failure state from its atom's result, never from copied React state.
577
+
578
+ ```tsx
579
+ // good
580
+ function AppFrame(props: {appId: AppId}) {
581
+ return <Frame app={useAtomSuspense(appAtom(props.appId)).value} />
582
+ }
583
+ <ErrorBoundary fallback={<LoadError onRetry={refresh} />}>
584
+ <Suspense fallback={<Spinner />}>
585
+ <AppFrame appId={appId} />
586
+ </Suspense>
587
+ </ErrorBoundary>
588
+ <Button disabled={saveResult.waiting} onClick={() => save(draft)}>Save</Button>
589
+ // bad
590
+ app.data ? <AppFrame app={app.data} /> : <Spinner /> // a failed load spins forever
591
+ const members = query.data ?? [] // a failed load looks like no members
592
+ const [pending, setPending] = useState(false) // copies the write atom's waiting state
593
+ ```
594
+
595
+ - **Ownership.** Give a component that owns its own atoms, writes or lifetime its own file; keep small presentational helpers beside their one user. Reset an editable draft through a `key` on its identity, never an Effect that copies props.
574
596
  - **Pure render.** Derive display values from props and subscribed state during render; never mutate them or perform an RPC, write or subscription there. Event handlers own user-triggered actions; Effects synchronize external systems, not copied state or calculations. Reuse the existing router/atom integration rather than fetching in a new Effect.
575
597
  - **State identity.** Keep one owner for each value, stable domain keys for list items and deliberate reset boundaries. For an observed rerender problem, derive the needed value in the existing atom graph instead of subscribing to frequently changing unrelated state. Compiler memoization is a performance optimization, never a correctness or lifetime guarantee. Do not add manual memoization, compiler escape directives or weakened hook rules to hide an ownership problem.
576
598
 
@@ -20,7 +20,7 @@ description: 'Select and run black-box behavior proof for Effect apps/tools. Use
20
20
  ```ts
21
21
  // good — "test files only for the services/packages public interfaces"
22
22
  it.layer(Layer.provideMerge(Ledger.layer, NodeServices.layer))(test => {
23
- test.effect('keeps the previous entries when the file is malformed', () => run(program))
23
+ test.effect('keeps the previous entries when the file is malformed', () => program)
24
24
  })
25
25
  assert.deepStrictEqual(customCodes(result.stdout), ['@deslop/coding-standards(no-typeof)'])
26
26
  assert.containsSubset(error, {_tag: 'SandboxUnavailable', cause: killed})