claude-toolkit 0.16.0 → 0.17.0

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +54 -13
  3. package/bin/cli.ts +75 -19
  4. package/package.json +6 -7
  5. package/src/generator.ts +64 -26
  6. package/src/refresh-hint.ts +95 -0
  7. package/bin/postinstall.mjs +0 -69
  8. package/docs/README.md +0 -59
  9. package/docs/agents/code-reviewer.md +0 -76
  10. package/docs/agents/github-workflow.md +0 -98
  11. package/docs/best-practices/capacitor/README.md +0 -64
  12. package/docs/best-practices/capacitor/capacitor-8.md +0 -60
  13. package/docs/best-practices/capacitor/capgo-setup.md +0 -151
  14. package/docs/best-practices/capacitor/channels-and-rollouts.md +0 -101
  15. package/docs/best-practices/capacitor/live-updates-ota.md +0 -75
  16. package/docs/best-practices/capacitor/security-encryption.md +0 -68
  17. package/docs/best-practices/solidjs/README.md +0 -43
  18. package/docs/best-practices/solidjs/anti-patterns.md +0 -166
  19. package/docs/best-practices/solidjs/component-patterns.md +0 -131
  20. package/docs/best-practices/solidjs/context-and-global-state.md +0 -131
  21. package/docs/best-practices/solidjs/control-flow.md +0 -124
  22. package/docs/best-practices/solidjs/data-fetching.md +0 -205
  23. package/docs/best-practices/solidjs/effects-and-lifecycle.md +0 -113
  24. package/docs/best-practices/solidjs/performance.md +0 -100
  25. package/docs/best-practices/solidjs/props-patterns.md +0 -100
  26. package/docs/best-practices/solidjs/reactivity-model.md +0 -104
  27. package/docs/best-practices/solidjs/signals-and-state.md +0 -78
  28. package/docs/best-practices/solidjs/stores-and-nested-state.md +0 -111
  29. package/docs/best-practices/solidjs/typescript-integration.md +0 -186
  30. package/docs/best-practices/testing/README.md +0 -84
  31. package/docs/best-practices/testing/playwright-e2e.md +0 -649
  32. package/docs/best-practices/testing/storybook-interaction.md +0 -455
  33. package/docs/best-practices/testing/vitest-unit.md +0 -451
  34. package/docs/best-practices/typescript/README.md +0 -45
  35. package/docs/best-practices/typescript/any-and-unknown.md +0 -73
  36. package/docs/best-practices/typescript/deriving-vs-decoupling.md +0 -83
  37. package/docs/best-practices/typescript/discriminated-unions.md +0 -75
  38. package/docs/best-practices/typescript/enums-alternatives.md +0 -72
  39. package/docs/best-practices/typescript/essential-patterns.md +0 -119
  40. package/docs/best-practices/typescript/generics-patterns.md +0 -105
  41. package/docs/best-practices/typescript/micro-opinions.md +0 -87
  42. package/docs/best-practices/typescript/runtime-validation.md +0 -62
  43. package/docs/best-practices/typescript/satisfies-operator.md +0 -100
  44. package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +0 -160
  45. package/docs/best-practices/typescript/type-organization.md +0 -64
  46. package/docs/best-practices/typescript/type-vs-interface.md +0 -80
  47. package/docs/commands/code-quality.md +0 -36
  48. package/docs/commands/onboard.md +0 -63
  49. package/docs/commands/pr-review.md +0 -91
  50. package/docs/commands/pr-summary.md +0 -50
  51. package/docs/commands/proto-check.md +0 -59
  52. package/docs/commands/ticket.md +0 -56
  53. package/docs/skills/code-style.md +0 -26
  54. package/docs/skills/systematic-debugging.md +0 -70
  55. package/docs/skills/testing-patterns.md +0 -140
  56. package/docs/skills/typescript-conventions.md +0 -283
  57. package/docs/skills/verification-before-completion.md +0 -103
  58. package/docs/stacks/capacitor-ota.md +0 -145
  59. package/docs/stacks/cloudflare-d1-kv.md +0 -144
  60. package/docs/stacks/esnext-idioms.md +0 -25
  61. package/docs/stacks/i18n-typesafe.md +0 -141
  62. package/docs/stacks/playwright-patterns.md +0 -179
  63. package/docs/stacks/protobuf-contracts.md +0 -85
  64. package/docs/stacks/rust-wasm-patterns.md +0 -106
  65. package/docs/stacks/solidjs-patterns.md +0 -110
  66. package/docs/stacks/storybook-patterns.md +0 -177
  67. package/docs/stacks/vanilla-extract-patterns.md +0 -115
  68. package/docs/stacks/vite-vitest-patterns.md +0 -485
@@ -1,101 +0,0 @@
1
- # Channels & Staged Rollouts
2
-
3
- > A channel is a pointer to one JS bundle. Devices subscribe to a channel; you change which bundle the channel points to. That indirection is what makes rollout and rollback instant and rebuild-free.
4
-
5
- ## The Channel Ladder
6
-
7
- Mirror your release pipeline with channels, and version bundles with semver pre-release tags so relationships are obvious:
8
-
9
- | Channel | Bundle version | Audience |
10
- | ------------- | -------------- | ------------------------- |
11
- | `development` | `1.2.3-dev.1` | dev / emulator builds |
12
- | `qa` | `1.2.3-qa.1` | QA verification |
13
- | `staging` | `1.2.3-rc.1` | production-like sign-off |
14
- | `production` | `1.2.3` | end users (store version) |
15
-
16
- Promote a build up the ladder by uploading (or re-pointing) it to the next channel — no native rebuild between stages.
17
-
18
- ```bash
19
- npx @capgo/cli@latest bundle upload --channel=staging # ship to staging
20
- # ...QA signs off...
21
- npx @capgo/cli@latest bundle upload --channel=production # promote to production
22
- npx @capgo/cli@latest channel set production -s default # ensure it's the cloud default
23
- ```
24
-
25
- ## Channel Precedence
26
-
27
- When a device checks for updates, Capgo resolves the channel in this order (highest wins):
28
-
29
- 1. **Forced device mapping** — pin specific device IDs (testing/exception layer).
30
- 2. **Cloud per-device override** — dashboard/API change for one device (up to ~2 min to propagate).
31
- 3. **Config `defaultChannel`** — set in `capacitor.config` (baked into the binary).
32
- 4. **Cloud Default Channel** — the fallback ~99% of users follow.
33
-
34
- **Production rule:** ship production binaries **without** `defaultChannel`. If you bake a channel into the binary, those users stop following the cloud default — which is exactly the lever you use for instant rollout and rollback. Reserve `defaultChannel` for builds you hand directly to testers.
35
-
36
- ## Runtime Switching with setChannel()
37
-
38
- Let users opt into a beta from inside the app:
39
-
40
- ```typescript
41
- import { CapacitorUpdater } from "@capgo/capacitor-updater";
42
-
43
- await CapacitorUpdater.setChannel({ channel: "beta", triggerAutoUpdate: true });
44
- ```
45
-
46
- - Requires **"Allow device self-assignment"** enabled on the target channel; otherwise the call fails.
47
- - In **plugin v8+**, `setChannel` stores the choice **locally on the device** and takes effect on the next update check — no 2-minute backend replication lag. The backend still validates the self-assignment permission at check time.
48
-
49
- ```bash
50
- npx @capgo/cli@latest channel set beta --self-assign # enable opt-in
51
- ```
52
-
53
- ## Version Gates: Enforce the Native/Web Contract
54
-
55
- Channels can refuse updates that cross a version boundary the native shell can't honor:
56
-
57
- ```bash
58
- # Block cross-major OTA on production (e.g. don't push a 2.x bundle to 1.x shells)
59
- npx @capgo/cli@latest channel set production --disable-auto-update major
60
- ```
61
-
62
- | `--disable-auto-update` | Allows |
63
- | ----------------------- | --------------------------------------------------- |
64
- | `major` | minor + patch within the same major |
65
- | `minor` | patch only within the same major.minor |
66
- | `patch` | patch increments only |
67
- | `metadata` | requires minimum version metadata on bundles |
68
- | `none` | all semver-compatible updates |
69
-
70
- Also available: **disable auto-downgrade** (don't send a bundle older than the device's native version) and per-platform targeting (`--ios`, `--android`). These gates are how you keep a web bundle from landing on a shell that can't run it.
71
-
72
- ## Staged Rollout Playbook
73
-
74
- 1. **Canary** — point the channel at the new bundle for ~10% of users (or a dedicated `canary` channel).
75
- 2. **Observe** — watch the success signal for 1–2 hours (next section).
76
- 3. **Widen** — ramp to 100% over ~24h if healthy.
77
- 4. **Schedule** — time heavy rollouts for **1–4 AM local**, Wi-Fi-preferred, so downloads don't compete with active use.
78
-
79
- **Benchmarks:** aim for **~95% adoption within 24h** and **~82% global success rate**. Falling short is a signal to pause and investigate, not to widen.
80
-
81
- ## Rollback
82
-
83
- Instant and rebuild-free — the core OTA payoff:
84
-
85
- 1. Open the channel in the Capgo dashboard.
86
- 2. Find the previous good build and click the **crown icon**.
87
- 3. Confirm. The channel now points at the old bundle; devices pick it up on next check-in.
88
-
89
- Because rollback is this cheap, bias toward shipping small and reverting fast over holding releases for exhaustive pre-launch testing.
90
-
91
- ## Monitoring
92
-
93
- - **`appReady` vs `downloadFailed`** — the health ratio. A `downloadFailed` spike, or `appReady` events not keeping pace with downloads, means bundles are failing to boot → roll back.
94
- - **Capgo stats dashboard** — adoption curve and per-version success rate. Compare against the 95% / 82% benchmarks.
95
- - **Alert, then act** — wire a threshold alert so a bad canary pages you before you widen.
96
-
97
- ## See Also
98
-
99
- - [Live Updates & OTA Strategy](live-updates-ota.md) — why staged rollout is the whole point
100
- - [Capacitor 8 Platform](capacitor-8.md) — what makes a bundle native-incompatible
101
- - [Security & Encryption](security-encryption.md) — signing the bundles a channel serves
@@ -1,75 +0,0 @@
1
- # Live Updates & OTA Strategy
2
-
3
- > OTA is a delivery mechanism for the **web layer**, not a way around the app stores. The strategy is mostly about discipline: knowing what you can ship over the air, keeping bundles small, and rolling out so a mistake is recoverable.
4
-
5
- ## What You Can — and Cannot — Ship Over the Air
6
-
7
- | Change | OTA? | Why |
8
- | ----------------------------------------------- | ---- | ----------------------------------------------------------- |
9
- | Bug fix in JS/TS | ✅ | Pure web layer |
10
- | Copy, translations, styling | ✅ | Static assets in the bundle |
11
- | New feature built on existing native APIs | ✅ | No new native contract |
12
- | Web-only dependency upgrade | ✅ | Compiles into the bundle |
13
- | New Capacitor **plugin** (native code) | ❌ | Native shell lacks the implementation → crash |
14
- | Native dependency / SDK upgrade | ❌ | Native binary change |
15
- | Capacitor **major** version bump | ❌ | Native runtime change |
16
- | Permission / entitlement / manifest change | ❌ | Declared in the native binary |
17
-
18
- The failure mode is concrete: a bundle that calls `SomePlugin.doThing()` on a shell where that plugin isn't compiled in throws at runtime. **Match every bundle to the native version that can run it.**
19
-
20
- ## Store Compliance
21
-
22
- OTA of interpreted code is explicitly permitted:
23
-
24
- - **Apple** — Developer Agreement **§3.3.2** allows OTA of JavaScript and assets executed by the system WebView (since iOS 4.3).
25
- - **Google Play** — the restriction on non-Play code updates **does not apply** to code running in a VM with limited API access, i.e. JavaScript in a WebView.
26
-
27
- What gets apps pulled is shipping *native* executable code OTA. Stay in the web layer and you stay compliant.
28
-
29
- ## Performance: Keep Bundles Small
30
-
31
- Smaller bundles download faster, fail less, and adopt quicker.
32
-
33
- - **Delta / differential updates.** Capgo transfers only the files that changed between versions (auto-enabled with direct/instant updates). The win depends on file-level stability — see below.
34
- - **Deterministic builds.** Strip build timestamps and pin your build tooling so unchanged source produces byte-identical output. Otherwise every file looks "changed" and the delta degrades to a full download.
35
- - **Compression.** Capgo applies ZSTD compression to full-image updates; you don't configure it, but deterministic builds are what let delta + compression actually shrink the payload.
36
- - **Prioritize the critical path.** Load auth and main navigation eagerly; lazy-load analytics, settings, animations, and heavy media after first paint. A small critical bundle reaches `notifyAppReady()` faster, which matters because of the rollback timeout.
37
-
38
- ## Update Timing & Scheduling
39
-
40
- - **Off-peak rollouts.** Schedule heavy rollouts for **1–4 AM local time** and prefer Wi-Fi so you're not competing with active use or burning cellular data.
41
- - **Background application.** The default `atBackground` mode downloads silently and applies when the app backgrounds, so users never watch a spinner. Reserve `onLaunch` / `always` for updates that must take effect immediately (e.g. a hotfix).
42
- - **Respect `periodCheckDelay`.** Minimum 600s (10 min). Don't try to poll faster — it's clamped.
43
-
44
- ## Roll Out So You Can Roll Back
45
-
46
- The whole point of OTA is that mistakes are cheap to undo. Treat every release as staged:
47
-
48
- 1. **Canary** to ~10% of users.
49
- 2. **Watch** the success signal (`appReady` vs `downloadFailed`, Capgo stats) for 1–2 hours.
50
- 3. **Widen** to 100% over ~24h if healthy.
51
- 4. **Roll back instantly** from the channel dashboard if not — devices pick up the previous bundle on next check-in.
52
-
53
- Healthy benchmarks: **~95% adoption within 24h**, **~82% global success rate**. Below that, investigate before widening.
54
-
55
- ## The notifyAppReady() Safety Net
56
-
57
- This is what makes the above safe. The plugin starts a timer (`appReadyTimeout`, default 10s) when a new bundle loads. If your JS calls `notifyAppReady()` before it fires, the bundle is "confirmed." If it doesn't — because the bundle white-screened or threw on boot — the plugin **automatically reverts** to the last good bundle. A broken OTA release self-heals on the next launch even before you notice.
58
-
59
- Call it as early as possible, before heavy async work, so a slow boot doesn't trip the timeout on an otherwise-fine bundle.
60
-
61
- ## Anti-Patterns
62
-
63
- | Anti-pattern | Consequence |
64
- | ------------------------------------------- | ----------------------------------------------------------------- |
65
- | OTA-ing a native change | Runtime crash on the mismatched shell |
66
- | Non-deterministic builds | Delta updates degrade to full downloads |
67
- | 100% rollout, no canary | A bad bundle hits the entire user base at once |
68
- | Eager-loading everything | Slow boot risks tripping `appReadyTimeout` → false rollback |
69
- | Polling faster than 600s | Silently clamped; wasted assumption |
70
-
71
- ## See Also
72
-
73
- - [Capacitor 8 Platform](capacitor-8.md) — keeping native and OTA in lockstep
74
- - [Capgo Setup & API](capgo-setup.md) — config and the manual update lifecycle
75
- - [Channels & Staged Rollouts](channels-and-rollouts.md) — the mechanics of canarying and rollback
@@ -1,68 +0,0 @@
1
- # Security & Encryption
2
-
3
- > An OTA bundle travels over the network and lands on devices you don't control. End-to-end encryption with code signing makes each update unreadable in transit and verifiable on arrival — only your users can decrypt it, and only your signed bundles are accepted.
4
-
5
- ## Why Encrypt
6
-
7
- A channel controls *who is eligible* for an update; it does **not** keep the bundle secret. An unencrypted bundle should be treated as a public asset. If your web layer contains anything you don't want readable — proprietary logic, embedded config — encryption is the only thing that keeps it private end to end, including from Capgo itself.
8
-
9
- ## How v2 Encryption Works (Hybrid RSA + AES)
10
-
11
- RSA can't efficiently encrypt large payloads, so Capgo uses a hybrid scheme:
12
-
13
- 1. A **random AES-256 key** is generated for every upload and encrypts the bundle.
14
- 2. Your **private RSA-2048 key** encrypts (signs) the AES key and the bundle checksum.
15
- 3. The app holds your **public RSA key** (injected at build) and decrypts the AES key + signature.
16
- 4. The app decrypts the bundle with the AES key, recomputes the checksum, and compares it against the decrypted signature.
17
-
18
- The result: the bundle is confidential (AES) **and** authenticated (RSA signature over the checksum). Tampering breaks the checksum match; a bundle not signed by your private key won't validate.
19
-
20
- > Industry baseline this matches: **AES-256** for payloads, **RSA-2048** for key exchange, **SHA-256** for integrity.
21
-
22
- ## Setup
23
-
24
- ```bash
25
- npx @capgo/cli@latest key create # generate the RSA key pair
26
- npx @capgo/cli@latest bundle upload --key-v2 --channel=production
27
- ```
28
-
29
- `key create` writes two files and injects the public key into your Capacitor config:
30
-
31
- | File | Sensitivity | Rule |
32
- | --------------------- | ----------- | ---------------------------------------------------------- |
33
- | `.capgo_key_v2` | **Private** | **Never commit.** Store as a CI secret. Loss = can't sign. |
34
- | `.capgo_key_v2.pub` | Public | Safe to commit — it's a backup of the embedded public key. |
35
-
36
- Add `.capgo_key_v2` to `.gitignore` immediately. The private key only ever lives on trusted dev machines and in CI secret storage; it's used at upload time to sign, never shipped to devices.
37
-
38
- ## Strict Enforcement
39
-
40
- Once encryption is configured, the plugin **requires** a valid signature on every update — this is enforced, not advisory. An unsigned or tampered bundle is **rejected**, not applied. Practical consequences:
41
-
42
- - Every CI upload must run with `--key-v2` and access to the private key, or devices will reject the bundle.
43
- - Rotating keys requires shipping a new native binary with the new public key; plan it as a store release, not an OTA.
44
- - `--no-key` (unencrypted) and encrypted uploads don't mix on the same channel for the same audience — pick one posture per channel.
45
-
46
- ## Integrity Beyond Encryption
47
-
48
- - **Checksums** verify the decrypted bundle matches what you signed — corruption or tampering fails closed.
49
- - **`notifyAppReady()`** is the runtime backstop: even a validly-signed bundle that crashes on boot rolls back automatically (see [Capgo Setup](capgo-setup.md)). Signing proves *authenticity*; `notifyAppReady` proves *it actually runs*. You want both.
50
-
51
- ## Store Compliance
52
-
53
- Encryption doesn't change what you're allowed to ship — it just protects the payload. The compliance rule is unchanged: OTA of **JavaScript/HTML/CSS** is permitted (Apple Developer Agreement §3.3.2; Google Play's webview/JS-VM exemption). Encrypting that web bundle is fine. Encrypting and shipping *native* executable code OTA is still not allowed — encryption is not a loophole around the native/web boundary.
54
-
55
- ## Checklist
56
-
57
- - [ ] `npx @capgo/cli key create` run; `.capgo_key_v2` added to `.gitignore`.
58
- - [ ] Private key stored as a CI secret; `--key-v2` used on every production upload.
59
- - [ ] `.capgo_key_v2.pub` committed as a backup.
60
- - [ ] `notifyAppReady()` present so signed-but-broken bundles still self-heal.
61
- - [ ] Key rotation planned as a native binary release, not an OTA.
62
- - [ ] Only web-layer code shipped OTA — never native code, encrypted or not.
63
-
64
- ## See Also
65
-
66
- - [Capgo Setup & API](capgo-setup.md) — the upload flow and `publicKey` config
67
- - [Channels & Staged Rollouts](channels-and-rollouts.md) — per-channel encryption posture
68
- - [Live Updates & OTA Strategy](live-updates-ota.md) — what's legal to ship over the air
@@ -1,43 +0,0 @@
1
- # SolidJS Best Practices
2
-
3
- > A curated collection of SolidJS best practices, patterns, and guidance sourced from the [official SolidJS documentation](https://docs.solidjs.com/), [Ryan Carniato](https://dev.to/ryansolid) (SolidJS creator), and the SolidJS community.
4
-
5
- SolidJS uses **fine-grained reactivity** with no virtual DOM. Components run once; only reactive expressions re-execute when dependencies change. This is fundamentally different from virtual DOM frameworks — and most best practices stem from this core difference.
6
-
7
- ## Reactivity
8
-
9
- Understanding reactivity is the foundation of everything in Solid.
10
-
11
- | Guide | Summary |
12
- |---|---|
13
- | [Reactivity Model](reactivity-model.md) | How fine-grained reactivity works — signals, tracking, subscriptions. |
14
- | [Signals & State](signals-and-state.md) | `createSignal`, `createMemo`, derived values, and when to use what. |
15
- | [Stores & Nested State](stores-and-nested-state.md) | `createStore`, path syntax, `produce`, `reconcile` for complex state. |
16
- | [Effects & Lifecycle](effects-and-lifecycle.md) | `createEffect`, `onMount`, `onCleanup` — and when NOT to use effects. |
17
-
18
- ## Components
19
-
20
- Patterns for building and composing components.
21
-
22
- | Guide | Summary |
23
- |---|---|
24
- | [Props Patterns](props-patterns.md) | Never destructure. Use `mergeProps`, `splitProps`, `children` helper. |
25
- | [Component Patterns](component-patterns.md) | Composition, typed components, generic components, refs. |
26
- | [Control Flow](control-flow.md) | `<Show>`, `<For>`, `<Switch>`/`<Match>`, `<ErrorBoundary>`, `<Suspense>`. |
27
-
28
- ## Architecture
29
-
30
- Scaling patterns for real applications.
31
-
32
- | Guide | Summary |
33
- |---|---|
34
- | [Data Fetching](data-fetching.md) | `createResource`, `createAsync`, Suspense, error handling, optimistic updates. |
35
- | [Context & Global State](context-and-global-state.md) | Context + stores pattern, provider factories, avoiding prop drilling. |
36
- | [TypeScript Integration](typescript-integration.md) | Component types, typed signals/stores/context, event handling, TSConfig. |
37
-
38
- ## Performance & Pitfalls
39
-
40
- | Guide | Summary |
41
- |---|---|
42
- | [Performance](performance.md) | `lazy`, `batch`, `untrack`, code splitting, and what Solid optimizes for you. |
43
- | [Anti-Patterns](anti-patterns.md) | The most common mistakes and how to avoid them. |
@@ -1,166 +0,0 @@
1
- # Anti-Patterns
2
-
3
- > Common SolidJS mistakes sourced from the [SolidJS documentation](https://docs.solidjs.com/), community guides, and [Ryan Carniato's articles](https://dev.to/ryansolid).
4
-
5
- ## 1. Destructuring Props
6
-
7
- The single most common Solid mistake. Props use getters for reactivity — destructuring extracts the value once.
8
-
9
- ```typescript
10
- // BROKEN
11
- const Card = ({ title, count }) => <div>{title}: {count}</div>;
12
-
13
- // FIXED
14
- const Card = (props) => <div>{props.title}: {props.count}</div>;
15
- ```
16
-
17
- If you need to separate props, use `splitProps`. If you need defaults, use `mergeProps`.
18
-
19
- ## 2. Forgetting to Call Signals
20
-
21
- Signals are getter functions. Forgetting `()` passes the function itself, not the value.
22
-
23
- ```typescript
24
- // BROKEN — passes the function, not the value
25
- <span>{count}</span>
26
-
27
- // FIXED
28
- <span>{count()}</span>
29
- ```
30
-
31
- ## 3. Reading Signals Outside Reactive Context
32
-
33
- The component body runs once. Reading a signal at the top level captures the initial value — it never updates.
34
-
35
- ```typescript
36
- // BROKEN — layout is assigned once, never reactive
37
- const MyComponent = (props) => {
38
- const layout = props.wide ? "grid" : "list"; // Evaluated once
39
- return <div class={layout}>...</div>;
40
- };
41
-
42
- // FIXED — read in JSX (reactive context)
43
- const MyComponent = (props) => {
44
- return <div class={props.wide ? "grid" : "list"}>...</div>;
45
- };
46
-
47
- // Also fixed — wrap in a function
48
- const MyComponent = (props) => {
49
- const layout = () => props.wide ? "grid" : "list";
50
- return <div class={layout()}>...</div>;
51
- };
52
- ```
53
-
54
- ## 4. Using Effects to Sync State
55
-
56
- Effects that set state from other state create ordering problems and glitchy intermediate renders.
57
-
58
- ```typescript
59
- // BROKEN — effect-based synchronization
60
- const [count, setCount] = createSignal(0);
61
- const [doubled, setDoubled] = createSignal(0);
62
- createEffect(() => setDoubled(count() * 2));
63
-
64
- // FIXED — derivation
65
- const doubled = createMemo(() => count() * 2);
66
- // or: const doubled = () => count() * 2;
67
- ```
68
-
69
- **Rule:** If a value can be computed from reactive sources, derive it.
70
-
71
- ## 5. Using Effects for Data Fetching
72
-
73
- Effects run after render, have no built-in loading/error states, and create race conditions on dependency changes.
74
-
75
- ```typescript
76
- // BROKEN
77
- createEffect(async () => {
78
- const res = await fetch(`/api/${id()}`);
79
- setData(await res.json());
80
- });
81
-
82
- // FIXED
83
- const [data] = createResource(id, fetchData);
84
- ```
85
-
86
- `createResource` integrates with Suspense, handles loading/error, and manages race conditions.
87
-
88
- ## 6. Using `.map()` Instead of `<For>`
89
-
90
- JavaScript `.map()` recreates all DOM nodes whenever the array signal changes. `<For>` tracks items by reference and only updates what changed.
91
-
92
- ```typescript
93
- // INEFFICIENT — full DOM rebuild on any array change
94
- <ul>{items().map(item => <li>{item.name}</li>)}</ul>
95
-
96
- // CORRECT — granular updates
97
- <ul><For each={items()}>{(item) => <li>{item.name}</li>}</For></ul>
98
- ```
99
-
100
- ## 7. Using `&&` for Conditional Rendering
101
-
102
- JavaScript short-circuit evaluation doesn't give Solid the boundaries it needs to optimize.
103
-
104
- ```typescript
105
- // SUBOPTIMAL — no Solid optimization
106
- {isVisible() && <Modal />}
107
-
108
- // CORRECT — Solid can optimize the boundary
109
- <Show when={isVisible()}><Modal /></Show>
110
- ```
111
-
112
- ## 8. Mutating Store State Directly
113
-
114
- Stores use proxies. Direct mutation bypasses the reactive system.
115
-
116
- ```typescript
117
- // BROKEN — Solid doesn't see this change
118
- state.tasks[0].completed = true;
119
-
120
- // FIXED — go through setState
121
- setState("tasks", 0, "completed", true);
122
- ```
123
-
124
- ## 9. Creating Unnecessary Reactivity
125
-
126
- Not everything needs to be a signal. Static configuration, constants, and values that never change don't benefit from reactivity.
127
-
128
- ```typescript
129
- // UNNECESSARY
130
- const [apiUrl] = createSignal("https://api.example.com");
131
-
132
- // JUST USE A CONSTANT
133
- const API_URL = "https://api.example.com";
134
- ```
135
-
136
- ## 10. Missing `onCleanup`
137
-
138
- Effects that create subscriptions, timers, or event listeners without cleanup cause memory leaks.
139
-
140
- ```typescript
141
- // LEAKS — interval is never cleared
142
- createEffect(() => {
143
- setInterval(() => tick(), 1000);
144
- });
145
-
146
- // FIXED
147
- createEffect(() => {
148
- const id = setInterval(() => tick(), 1000);
149
- onCleanup(() => clearInterval(id));
150
- });
151
- ```
152
-
153
- ## Quick Reference
154
-
155
- | Mistake | Fix |
156
- |---|---|
157
- | Destructure props | Use `props.x` directly |
158
- | `count` without `()` | Always call signal getters |
159
- | Signal read at top level | Read in JSX or wrap in `() =>` |
160
- | Effect sets state from state | Use `createMemo` |
161
- | Effect fetches data | Use `createResource` |
162
- | `.map()` for lists | Use `<For>` |
163
- | `&&` for conditionals | Use `<Show>` |
164
- | Direct store mutation | Use `setState()` |
165
- | Signal for constants | Use a plain variable |
166
- | No `onCleanup` | Always clean up side effects |
@@ -1,131 +0,0 @@
1
- # Component Patterns
2
-
3
- > Sources: [TypeScript](https://docs.solidjs.com/configuration/typescript), [Props](https://docs.solidjs.com/concepts/components/props) — SolidJS Docs
4
-
5
- ## Component Type Hierarchy
6
-
7
- SolidJS provides four component types for TypeScript:
8
-
9
- ```typescript
10
- import type { Component, ParentComponent, FlowComponent, VoidComponent } from "solid-js";
11
- ```
12
-
13
- | Type | Children | Use Case |
14
- |---|---|---|
15
- | `Component<P>` | No opinion | Base type, generic components |
16
- | `ParentComponent<P>` | Optional `JSX.Element` | Components that accept children |
17
- | `FlowComponent<P, C>` | Required, typed | Control flow (`Show`, `For`-like) |
18
- | `VoidComponent<P>` | Forbidden | Leaf components (icons, inputs) |
19
-
20
- ```typescript
21
- const Card: ParentComponent<{ title: string }> = (props) => {
22
- return (
23
- <div class="card">
24
- <h2>{props.title}</h2>
25
- {props.children}
26
- </div>
27
- );
28
- };
29
-
30
- const Icon: VoidComponent<{ name: string }> = (props) => {
31
- return <svg class={`icon-${props.name}`} />;
32
- };
33
- ```
34
-
35
- ## Generic Components
36
-
37
- The `Component` types cannot be used for generic components. Use function declarations instead:
38
-
39
- ```typescript
40
- function List<T>(props: { items: T[]; render: (item: T) => JSX.Element }): JSX.Element {
41
- return <For each={props.items}>{props.render}</For>;
42
- }
43
-
44
- // Usage — T is inferred
45
- <List items={users()} render={(user) => <span>{user.name}</span>} />
46
- ```
47
-
48
- ## Refs
49
-
50
- Refs may be `undefined` before mount. Always account for this:
51
-
52
- ```typescript
53
- const MyComponent: VoidComponent = () => {
54
- let inputRef: HTMLInputElement | undefined;
55
-
56
- onMount(() => {
57
- inputRef?.focus(); // Safe access after mount
58
- });
59
-
60
- return <input ref={inputRef} />;
61
- };
62
- ```
63
-
64
- Alternatively, use the definite assignment assertion in JSX: `ref={inputRef!}`.
65
-
66
- ## Composition Over Inheritance
67
-
68
- Solid components are plain functions — compose them by passing components as props or using children:
69
-
70
- ```typescript
71
- const Layout: ParentComponent<{ sidebar: JSX.Element }> = (props) => {
72
- return (
73
- <div class="layout">
74
- <aside>{props.sidebar}</aside>
75
- <main>{props.children}</main>
76
- </div>
77
- );
78
- };
79
-
80
- <Layout sidebar={<Navigation />}>
81
- <PageContent />
82
- </Layout>
83
- ```
84
-
85
- ## Event Handling
86
-
87
- Inline event handlers get automatic type inference. For extracted handlers, use `JSX.EventHandler`:
88
-
89
- ```typescript
90
- const onInput: JSX.EventHandler<HTMLInputElement, InputEvent> = (event) => {
91
- // event.currentTarget is HTMLInputElement
92
- // event.target may be any element within
93
- console.log(event.currentTarget.value);
94
- };
95
- ```
96
-
97
- ### Custom Events
98
-
99
- Extend the JSX namespace:
100
-
101
- ```typescript
102
- declare module "solid-js" {
103
- namespace JSX {
104
- interface CustomEvents {
105
- customClick: CustomEvent<{ id: string }>;
106
- }
107
- }
108
- }
109
- ```
110
-
111
- ## Custom Directives
112
-
113
- Register with `use:` prefix. Declare via the JSX namespace:
114
-
115
- ```typescript
116
- declare module "solid-js" {
117
- namespace JSX {
118
- interface Directives {
119
- tooltip: string;
120
- }
121
- }
122
- }
123
-
124
- function tooltip(el: HTMLElement, accessor: () => string) {
125
- // Set up tooltip on el using accessor()
126
- onCleanup(() => { /* teardown */ });
127
- }
128
-
129
- // Usage
130
- <div use:tooltip={"Hello!"} />
131
- ```