@deslop/coding-standards 0.1.1076 → 0.1.1079

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deslop/coding-standards",
3
- "version": "0.1.1076",
3
+ "version": "0.1.1079",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/MP281X/deslop",
@@ -19,7 +19,7 @@ Before code, settle these facts in the current plan, without creating another do
19
19
  - The boundary where untrusted input becomes trusted data.
20
20
  - What remains durable after interruption, and how the caller knows.
21
21
 
22
- For a user-visible direction that remains open, decide it with the user before implementation; use orchestrator when available. Settle technical mechanisms with source, a disposable inline probe or a small executable prototype. Do not ask the user to choose between implementation details that evidence can rank.
22
+ For a user-visible direction that remains open, decide it with the user before implementation; use orchestrator when available. File layout, module boundaries, public interfaces and names are the user's taste: when the Patterns and Taste sections of `CODING_STANDARDS.md` do not settle them, show the shape before code, per orchestrator's Taste gate when available. Settle technical mechanisms with source, a disposable inline probe or a small executable prototype. Do not ask the user to choose between implementation details that evidence can rank.
23
23
 
24
24
  ## Choose the smallest mechanism
25
25
 
@@ -13,11 +13,11 @@ Follow the engineering skill and the repository's `CODING_STANDARDS.md` when pre
13
13
 
14
14
  ## Before the first draft
15
15
 
16
- For an open direction, show a question-sized prototype before polishing it. Use orchestrator when available to settle consequential user choices. Approval is provisional: let the user try a small real journey before expensive expansion. Own routine refinement, but expect the real experience to change the user's direction.
16
+ For a screen that the recorded taste does not settle, agree on a realistic mock with the user before product code. Use orchestrator's Taste gate when available. Approval is provisional: let the user try a small real journey before expensive expansion. Own routine refinement, but expect the real experience to change the user's direction.
17
17
 
18
18
  1. **Task and feeling.** Name the person, their task, their context and the feeling the product gives, such as calm, fast or trustworthy. Name three qualities the person notices, each with what "excellent" looks like.
19
19
  2. **Baseline.** Name the category and platform, and the reference screen for each screen you build: in product mode the canonical screen per engineering's Patterns, otherwise one of Apple's apps on iOS or a reference the user named. Capture the reference in the states you need. Start from the system or established component defaults.
20
- 3. **Range.** Compare structurally different solutions only when the structure has a real unresolved fork, on the same data. Include a simpler solution that removes unnecessary controls. Show the smallest comparison that decides the fork, then develop the chosen direction.
20
+ 3. **Range.** When the reference screen and the Taste section of `CODING_STANDARDS.md` do not settle the structure, compare 2–3 structurally different solutions on the same data. Include a simpler solution that removes unnecessary controls. Develop only the chosen direction.
21
21
  4. **States.** List every screen, its reachable states (empty, loading, partial, error, success, long content) and the events between them, before any code. Group states by concern instead of multiplying every combination.
22
22
  5. **Settled choices.** Carry forward what the user approved and what he removed. Never re-add a removed element, rename a visible term or change an approved detail without a request.
23
23
 
@@ -98,9 +98,9 @@ For an open direction, show a question-sized prototype before polishing it. Use
98
98
 
99
99
  ## Prototype and tune
100
100
 
101
- **Variants.** For a real unresolved choice, show one representative journey or a small comparison on the same realistic data before product code. Use the app's tokens, components, copy and behavior. Add interaction only when manipulating the subject exposes a consequential difference; never add tabs or a state dashboard merely to navigate the explanation. Get the direction approved before expanding its states and polish. For a native app the render settles structure and flow; settle its native appearance in simulator captures.
101
+ **Mocks.** Start the mock from the real screen: copy its rendered markup, theme tokens and fonts from the running app or its source, then change only what the decision needs. Keep its components, copy, spacing, realistic data and main states. For a native app, imitate the platform's system look; the mock settles structure, flow and the rough look. Add interaction only when manipulating the subject exposes a consequential difference; never add tabs or a state dashboard merely to navigate the explanation. Revise the mock until the user accepts the direction, then expand states and polish in code.
102
102
 
103
- **Real host.** For a consequential behavior or taste choice, build one complete representative journey with realistic data in the real host: the browser preview for the web, the simulator for a native app. Let the user try it before broader implementation and polish. Complete specified fixes and approved-pattern work with focused proof, without a user checkpoint. Carry unaffected choices forward when feedback changes the journey; revise the affected design instead of defending prototype approval. An HTML imitation of a native screen misleads.
103
+ **Real host.** After the mock, build one complete representative journey with realistic data in the real host: the browser preview for the web, the simulator for a native app. Settle the final native appearance there. Let the user try it before broader implementation and polish. Complete specified fixes and approved-pattern work with focused proof, without a user checkpoint. Carry unaffected choices forward when feedback changes the journey; revise the affected design instead of defending the approved mock.
104
104
 
105
105
  **Tune.** Settle values yourself. For values you cannot settle by reasoning, such as spacing, timing, springs or blur, capture candidates side by side, including both extremes, and judge them against the reference. Write the chosen values into the source and recapture.
106
106
 
@@ -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 and test code, including prototypes.'
4
4
  ---
5
5
 
6
- **Scope.** Apply these rules and the repository's `CODING_STANDARDS.md` when present. Aim for a structurally correct first pass with few lint errors, not a promise of zero diagnostics. Oxlint remains the authoritative check; instructions do not replace it. Oxfmt owns whitespace, indentation and other mechanical formatting. Use automatic formatting and available fixes instead of spending reasoning on those details; fix remaining structural diagnostics at their source without a full rewrite or weakened rules. A refactor or cleanup request applies every rule to every line of the owned files. Scratch code outside the repository follows no style rule. Create a missing standards file only to hold a [Patterns](#patterns) row.
6
+ **Scope.** Apply these rules and the repository's `CODING_STANDARDS.md` when present. Aim for a structurally correct first pass with few lint errors, not a promise of zero diagnostics. Oxlint remains the authoritative check; instructions do not replace it. Oxfmt owns whitespace, indentation and other mechanical formatting. Use automatic formatting and available fixes instead of spending reasoning on those details; fix remaining structural diagnostics at their source without a full rewrite or weakened rules. A refactor or cleanup request applies every rule to every line of the owned files. Scratch code outside the repository follows no style rule. Create a missing standards file only to hold a [Patterns](#patterns) or Taste row.
7
7
 
8
8
  **Before checking.** Run Oxfmt and the repository's deterministic lint autofix before manual code analysis, independent review or a check-only lint pass. Use the owned scope and preserve unrelated changes. Run both even when generated code looks correct; automatic fixes need no model iteration. Analyze the remaining diagnostics and behavior only after normalization. For a read-only review, the author prepares the normalized diff; the reviewer never writes the reviewed checkout.
9
9
 
@@ -112,6 +112,8 @@ Keep the repository consistent by default, without a reminder, and change a stan
112
112
 
113
113
  **Register.** When a task settles a shape that two or more consumers share and the Patterns section has no row for it, add one: the pattern, its owner and canonical example, a search that lists its consumers, and what enforces it.
114
114
 
115
+ **Taste.** Read the Taste section of `CODING_STANDARDS.md` before choosing a layout, file organization, interface or name, and follow its rows like Patterns rows. When the user picks a variant, rejects one or corrects a taste choice that no rule covers, add a row in the same change: the area, the choice, the rejected alternative and his reason in his words. A row that recurs across repositories moves into the owning skill.
116
+
115
117
  **Pattern change.** A user change to a pattern, style, name or convention changes the repository standard, unless he limits its scope. In the same change, update its owner: the shared component, template, generator, lint rule and Patterns row. List every consumer with the row's search, migrate each one, delete the superseded variant and its exemptions, then run the search for the old form again. Report each consumer kept on purpose with its reason. A migration that crosses another repository or a published contract needs the user's approval first.
116
118
 
117
119
  **Enforce.** When the same correction happens twice, make the mistake impossible at the highest level that works: an owner component, export or type that removes the wrong form, then a lint rule whose message names the replacement, then a test. Keep a prose rule only for a judgment that no check can make. Prove a new check against the real past mistake and an accepted control.
@@ -134,13 +136,13 @@ const root = options?.root !== undefined ? options.root : '.'
134
136
  ```tsx
135
137
  // good
136
138
  if (enabled) return
137
- aria-current={props.selected === true ? 'page' : undefined}
139
+ aria-current={Boolean.match(props.selected === true, {onFalse: () => undefined, onTrue: () => 'page'})}
138
140
  // bad
139
141
  if (enabled === true) return // enabled is a boolean
140
142
  if (props.selected) return // props.selected can be undefined
141
143
  ```
142
144
 
143
- **Flat.** Use early-return statements in Effect.fn. No nested ternaries or combinators deeper than equivalent statements. Match and combinators serve single expressions; Boolean.match selects a value, except one-line JSX-attribute ternaries.
145
+ **Flat.** Use early-return statements in Effect.fn. No ternaries, in JSX too, and no combinators deeper than equivalent statements. An early return, `Boolean.match`, `Option.match`, `Array.match`, `Match` or a lookup record selects a value.
144
146
 
145
147
  ```ts
146
148
  // good
@@ -153,6 +155,7 @@ Boolean.match(ignoreCase, {onFalse: () => undefined, onTrue: () => 'i'})
153
155
  // bad
154
156
  const type = event.type === 'text-delta' ? 'text' : event.type === 'reasoning-delta' ? 'reasoning' : 'unknown'
155
157
  const flags = ignoreCase ? 'i' : undefined // "instead of a ternary the Boolean.match would be cleaner"
158
+ {props.children ? props.children(entry) : <Label entry={entry} />} // "the ternaries are impossible to read"
156
159
  // bad
157
160
  Array.appendAll(
158
161
  base,
@@ -180,6 +183,17 @@ if (Option.isSome(open)) open.value.append(event.delta)
180
183
  const parsed = Option.getOrUndefined(Number.parse(event.target.value))
181
184
  ```
182
185
 
186
+ **Absence.** `undefined` marks an absent value. Use `null` only where an outside contract, such as a stored schema or a library callback, demands it, with an inline disable that names the contract.
187
+
188
+ ```ts
189
+ // good
190
+ const last = Option.getOrUndefined(Array.last(sessions))
191
+ // oxlint-disable-next-line no-restricted-properties -- The schema stores a missing link as null.
192
+ url: Option.getOrNull(url)
193
+ // bad
194
+ failed: Option.getOrNull(Option.none()) // "i don't like null, i prefer undefined"
195
+ ```
196
+
183
197
  **Handlers.** A handler used once is an arrow in place; a handler used twice is a `function`.
184
198
 
185
199
  **Accumulators.** No `let`: a fold or an Effect collection operation that says what it selects.
@@ -207,6 +221,7 @@ items.filter(item => supports(item)).map(item => item.value)
207
221
  // good
208
222
  Number.parse(input.count)
209
223
  Predicate.isString(value)
224
+ Predicate.hasProperty(node, 'tagName')
210
225
  Predicate.isNotNullish(annotation)
211
226
  Array.isReadonlyArrayEmpty(node.arguments)
212
227
  String.isNonEmpty(event.delta)
@@ -221,6 +236,7 @@ const serve = Effect.gen(function* () {
221
236
  // bad
222
237
  parseInt(input.count)
223
238
  typeof value === 'string'
239
+ 'tagName' in node // "typeof and in shouldn't be allowed, probably this should use effect schema"
224
240
  annotation !== null && annotation !== undefined
225
241
  node.arguments.length === 0
226
242
  Effect.promise(() => new Promise(resolve => setTimeout(resolve, delay)))
@@ -304,11 +320,11 @@ files: ['packages/components-web/src/form.tsx', 'packages/components-web/src/ric
304
320
 
305
321
  ## Effect
306
322
 
307
- **Functions.** Use Effect.fn('Name.method') for a traced function with arguments and Effect.fnUntraced for an internal helper. Use Effect.gen for an effect with several statements and no arguments, and a single effect directly. Use Effect.fn.Return for an explicit return type.
323
+ **Functions.** Use Effect.fn('Name.method') for a traced function with arguments and Effect.fnUntraced for an internal helper. Use Effect.gen for an effect with several statements and no arguments, and a single effect directly. Use Effect.fn.Return only for a generator that calls itself.
308
324
 
309
325
  ```ts
310
326
  // good
311
- const prompt = Effect.fn('Ai.prompt')(function* (message: Prompt.UserMessage): Effect.fn.Return<void, AiError> {
327
+ const prompt = Effect.fn('Ai.prompt')(function* (message: Prompt.UserMessage) {
312
328
  const input = yield* piUserMessage(message)
313
329
  yield* send(input)
314
330
  })
@@ -403,15 +419,17 @@ const traced = Effect.withSpan('Notes.create')(notes.create(input))
403
419
  Effect.addFinalizer(() => closeSocket(socket)) // acquisition and release with different owners
404
420
  ```
405
421
 
406
- **Runtime.** The owned runtime runs the program. Bridge to a Promise once, where an external contract demands it. React is no such contract: atoms run its Effects.
422
+ **Runtime.** The entry point runs the program with `NodeRuntime.runMain`. Bridge to a Promise once, where an external contract demands it, with `Effect.runPromise` and an inline disable that names the contract. React is no such contract: atoms run its Effects.
407
423
 
408
424
  ```ts
409
425
  // good
410
426
  NodeRuntime.runMain(program)
411
- health: flow(rpc.health, Effect.runPromiseWith(Context.empty())),
427
+ // oxlint-disable-next-line no-restricted-properties -- Bingo's produce contract demands a Promise.
428
+ produce: flow(generate, Effect.runPromise),
412
429
  // bad
413
430
  const runtime = ManagedRuntime.make(AppLayer)
414
431
  const run = <A, E>(effect: Effect.Effect<A, E>) => Effect.runPromise(effect)
432
+ Effect.runPromiseWith(Context.empty()) // "what's the point of using the runPromiseWith if i pass an empty context?"
415
433
  ```
416
434
 
417
435
  **Sorting.** `Order` for sorting, `Result` for a fallible pure value.
@@ -429,7 +447,7 @@ entries.sort((left, right) => left.tokens - right.tokens)
429
447
 
430
448
  ## Types and Schema
431
449
 
432
- **Inference.** Infer types. Annotate recursion or branches TypeScript cannot unite; use schema types and indexed members in signatures. Keep intentional widening, not repeated inferred generics.
450
+ **Inference.** Infer types. Annotate a return type only on a type guard, an assertion function, a recursive function or the thunk of a recursive `Schema.suspend`. Check a returned object with `satisfies`, and type an empty or one-element array with `Array.empty<T>()` or `Array.of<T>(...)` instead of its callback. Use schema types and indexed members in signatures. Keep intentional widening, not repeated inferred generics.
433
451
 
434
452
  ```ts
435
453
  // good
@@ -442,8 +460,14 @@ function visit(node: Node): Result {
442
460
  function settle(amount: LedgerEntry['amount']) {
443
461
  return BigDecimal.format(amount)
444
462
  }
463
+ function still(bytes: Uint8Array) {
464
+ return {bytes, type: 'image/jpeg'} satisfies Image
465
+ }
445
466
  export type Sandbox = ReturnType<typeof fromSdk>
446
467
  // bad
468
+ function still(bytes: Uint8Array): Image {
469
+ return {bytes, type: 'image/jpeg'}
470
+ } // "not a fan of assigning the return type, i would have preferred a satisfies"
447
471
  const Input = Schema.Struct({value: Schema.String}) satisfies Schema.Schema<Input>
448
472
  const label = MutableRef.make<string>('') // repeats the inferred type
449
473
  export type SandboxLike = {id: string; renew: (seconds: number) => Promise<unknown>} // a copy of an inferred shape
@@ -511,7 +535,7 @@ function isPathNotFound(error: unknown) {
511
535
 
512
536
  ## Shape
513
537
 
514
- **Inline.** Inline forwarding wrappers, temporaries and constants. Keep a helper only for logic that is reused or deserves its own name. Argument-free functions are named values.
538
+ **Inline.** Inline forwarding wrappers, temporaries, constants and property aliases; keep an alias only where a callback needs its narrowed type. Inline a small helper with one caller. A helper that several files share lives in the package's `lib/utils`. Argument-free functions are named values.
515
539
 
516
540
  ```ts
517
541
  // good
@@ -528,6 +552,10 @@ function isBackendRequest(request: IncomingMessage) {
528
552
  } // "this is just a wrapper / factory function"
529
553
  Ref.set(entries, Array.copy(decoded)) // decoded is already an array
530
554
  const runPromise = Effect.runPromiseWith(Context.empty()) // "wrapper function, tmp variable ... that can be inlined"
555
+ const parent = read.parent // "inline the property access since it add no value"
556
+ function isVideo(type: FileType) {
557
+ return String.startsWith('video/')(type)
558
+ } // String.startsWith('video/') is already the function
531
559
  ```
532
560
 
533
561
  **Linked state.** Linked state moves in one pure updater, never a setter that calls another.
@@ -563,7 +591,7 @@ const warned = ['sort-keys', 'typescript/no-restricted-types'] // a package rule
563
591
 
564
592
  ## React
565
593
 
566
- **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.
594
+ **Components.** React Compiler memoizes. Import hooks by name from `react`. Destructure state at its declaration, receive component refs through props, and keep only a component's own interaction state in React.
567
595
 
568
596
  ```tsx
569
597
  // good
@@ -573,6 +601,7 @@ const moveRpc = useAtomSet(RpcClient.mutation('portfolio.move'))
573
601
  // bad
574
602
  const handle = useCallback(() => setOpen(true), [])
575
603
  const state = useState(false)
604
+ const [open, setOpen] = React.useState(false) // "why are you using the React.useState and not importing useState"
576
605
  ```
577
606
 
578
607
  **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`.
@@ -42,7 +42,7 @@ assert.strictEqual(Array.flatMap(attioProvider.groups, group => group.actions).l
42
42
  - **Branches.** Exercise the reachable distinctions: empty, duplicate, concurrent and dependency failure. Write no Cartesian product, and no random or repeated happy paths without a hypothesis.
43
43
  - **Failure.** Use `Effect.flip` for an expected failure and `Effect.exit` for both outcomes. Assert domain identity and preserved state and resources: no partial write, lost value, extra retry or leak. Wording or rejection alone is not enough, and incidental order is no assertion.
44
44
  - **Control.** Run the regression test against the unfixed implementation, and confirm that the intended assertion fails, not setup, imports or compilation. Before a refactor, cover the old behavior.
45
- - **Audit.** Remove duplicate and tautological cases in the touched scope, but retain independent protocol, package, validation, authorization and persistence contracts. Mock call counts do not prove an external boundary. Do not replace a useful test with a disposable script just to reduce the count.
45
+ - **Audit.** Delete a case that no plausible wrong implementation fails, such as an assertion on a constant placeholder. Remove duplicate and tautological cases in the touched scope, but retain independent protocol, package, validation, authorization and persistence contracts. Mock call counts do not prove an external boundary. Do not replace a useful test with a disposable script just to reduce the count.
46
46
 
47
47
  ## Effect and fixtures
48
48