@deslop/coding-standards 0.1.866 → 0.1.873
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 +1 -1
- package/package.json +1 -1
- package/skills/engineering/SKILL.md +37 -4
- package/skills/testing/SKILL.md +1 -1
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.
|
|
24688
|
+
var version = "0.1.873";
|
|
24689
24689
|
//#endregion
|
|
24690
24690
|
//#region src/install.ts
|
|
24691
24691
|
const cli = pipe(make("deslop-coding-standards", {}, fnUntraced(function* () {
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@ description: 'TypeScript and Effect code conventions paired with shared lint. Us
|
|
|
19
19
|
- **Inward layers.** Domain and service code never import HTTP, RPC, or other transport types.
|
|
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
|
|
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
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.
|
|
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.
|
|
@@ -187,7 +187,9 @@ const parsed = Option.getOrUndefined(Number.parse(event.target.value))
|
|
|
187
187
|
// good
|
|
188
188
|
const total = Array.reduce(values, 0, Number.sum)
|
|
189
189
|
pipe(teamId, String.split(','), Array.map(String.trim), Array.findFirst(String.isNonEmpty))
|
|
190
|
-
Array.filterMap(items, item =>
|
|
190
|
+
Array.filterMap(items, item =>
|
|
191
|
+
Boolean.match(supports(item), {onFalse: () => Result.failVoid, onTrue: () => Result.succeed(item.value)})
|
|
192
|
+
)
|
|
191
193
|
// bad
|
|
192
194
|
let total = 0
|
|
193
195
|
for (const value of values) total += value
|
|
@@ -399,7 +401,7 @@ const traced = Effect.withSpan('Notes.create')(notes.create(input))
|
|
|
399
401
|
Effect.addFinalizer(() => closeSocket(socket)) // acquisition and release with different owners
|
|
400
402
|
```
|
|
401
403
|
|
|
402
|
-
**Runtime.** The owned runtime runs the program; a Promise an external contract demands is bridged once, where it is returned.
|
|
404
|
+
**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
405
|
|
|
404
406
|
```ts
|
|
405
407
|
// good
|
|
@@ -559,7 +561,7 @@ const warned = ['sort-keys', 'typescript/no-restricted-types'] // a package rule
|
|
|
559
561
|
|
|
560
562
|
## React
|
|
561
563
|
|
|
562
|
-
**Components.** React Compiler memoizes. Destructure state at its declaration, receive component refs through props, and keep
|
|
564
|
+
**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
565
|
|
|
564
566
|
```tsx
|
|
565
567
|
// good
|
|
@@ -571,6 +573,37 @@ const handle = useCallback(() => setOpen(true), [])
|
|
|
571
573
|
const state = useState(false)
|
|
572
574
|
```
|
|
573
575
|
|
|
576
|
+
**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 tie each write to the reads it changes with reactivity keys.
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
// good
|
|
580
|
+
const appsRuntime = Atom.runtime(DualApps.layer)
|
|
581
|
+
const appAtom = Atom.family((id: AppId) => pipe(appsRuntime.atom(loadApp(id)), Atom.withReactivity(['apps'])))
|
|
582
|
+
const saveAppAtom = appsRuntime.fn((draft: AppDraft) => saveApp(draft), {reactivityKeys: ['apps']})
|
|
583
|
+
const app = useAtomValue(appAtom(appId))
|
|
584
|
+
const [saveResult, save] = useAtom(saveAppAtom)
|
|
585
|
+
// bad
|
|
586
|
+
useQuery({queryFn: () => Effect.runPromiseWith(Context.empty())(loadApp(appId))})
|
|
587
|
+
onClick={() => Effect.runPromise(saveApp(draft))}
|
|
588
|
+
useAtomValue(Atom.make(loadApp(appId))) // a new atom on every render
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Outcomes.** Render every `AsyncResult` state: loading, failure with its recovery, and success. Take a write's pending and failure state from its atom's result, never from copied React state.
|
|
592
|
+
|
|
593
|
+
```tsx
|
|
594
|
+
// good
|
|
595
|
+
AsyncResult.match(app, {
|
|
596
|
+
onFailure: failure => <LoadError cause={failure.cause} onRetry={refresh} />,
|
|
597
|
+
onInitial: () => <Spinner />,
|
|
598
|
+
onSuccess: success => <AppFrame app={success.value} />
|
|
599
|
+
})
|
|
600
|
+
<Button disabled={saveResult.waiting} onClick={() => save(draft)}>Save</Button>
|
|
601
|
+
// bad
|
|
602
|
+
app.data ? <AppFrame app={app.data} /> : <Spinner /> // a failed load spins forever
|
|
603
|
+
const [pending, setPending] = useState(false) // copies the write atom's waiting state
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
- **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
607
|
- **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
608
|
- **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
609
|
|
package/skills/testing/SKILL.md
CHANGED
|
@@ -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', () =>
|
|
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})
|