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.
- package/CHANGELOG.md +20 -0
- package/README.md +54 -13
- package/bin/cli.ts +75 -19
- package/package.json +6 -7
- package/src/generator.ts +64 -26
- package/src/refresh-hint.ts +95 -0
- package/bin/postinstall.mjs +0 -69
- package/docs/README.md +0 -59
- package/docs/agents/code-reviewer.md +0 -76
- package/docs/agents/github-workflow.md +0 -98
- package/docs/best-practices/capacitor/README.md +0 -64
- package/docs/best-practices/capacitor/capacitor-8.md +0 -60
- package/docs/best-practices/capacitor/capgo-setup.md +0 -151
- package/docs/best-practices/capacitor/channels-and-rollouts.md +0 -101
- package/docs/best-practices/capacitor/live-updates-ota.md +0 -75
- package/docs/best-practices/capacitor/security-encryption.md +0 -68
- package/docs/best-practices/solidjs/README.md +0 -43
- package/docs/best-practices/solidjs/anti-patterns.md +0 -166
- package/docs/best-practices/solidjs/component-patterns.md +0 -131
- package/docs/best-practices/solidjs/context-and-global-state.md +0 -131
- package/docs/best-practices/solidjs/control-flow.md +0 -124
- package/docs/best-practices/solidjs/data-fetching.md +0 -205
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +0 -113
- package/docs/best-practices/solidjs/performance.md +0 -100
- package/docs/best-practices/solidjs/props-patterns.md +0 -100
- package/docs/best-practices/solidjs/reactivity-model.md +0 -104
- package/docs/best-practices/solidjs/signals-and-state.md +0 -78
- package/docs/best-practices/solidjs/stores-and-nested-state.md +0 -111
- package/docs/best-practices/solidjs/typescript-integration.md +0 -186
- package/docs/best-practices/testing/README.md +0 -84
- package/docs/best-practices/testing/playwright-e2e.md +0 -649
- package/docs/best-practices/testing/storybook-interaction.md +0 -455
- package/docs/best-practices/testing/vitest-unit.md +0 -451
- package/docs/best-practices/typescript/README.md +0 -45
- package/docs/best-practices/typescript/any-and-unknown.md +0 -73
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +0 -83
- package/docs/best-practices/typescript/discriminated-unions.md +0 -75
- package/docs/best-practices/typescript/enums-alternatives.md +0 -72
- package/docs/best-practices/typescript/essential-patterns.md +0 -119
- package/docs/best-practices/typescript/generics-patterns.md +0 -105
- package/docs/best-practices/typescript/micro-opinions.md +0 -87
- package/docs/best-practices/typescript/runtime-validation.md +0 -62
- package/docs/best-practices/typescript/satisfies-operator.md +0 -100
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +0 -160
- package/docs/best-practices/typescript/type-organization.md +0 -64
- package/docs/best-practices/typescript/type-vs-interface.md +0 -80
- package/docs/commands/code-quality.md +0 -36
- package/docs/commands/onboard.md +0 -63
- package/docs/commands/pr-review.md +0 -91
- package/docs/commands/pr-summary.md +0 -50
- package/docs/commands/proto-check.md +0 -59
- package/docs/commands/ticket.md +0 -56
- package/docs/skills/code-style.md +0 -26
- package/docs/skills/systematic-debugging.md +0 -70
- package/docs/skills/testing-patterns.md +0 -140
- package/docs/skills/typescript-conventions.md +0 -283
- package/docs/skills/verification-before-completion.md +0 -103
- package/docs/stacks/capacitor-ota.md +0 -145
- package/docs/stacks/cloudflare-d1-kv.md +0 -144
- package/docs/stacks/esnext-idioms.md +0 -25
- package/docs/stacks/i18n-typesafe.md +0 -141
- package/docs/stacks/playwright-patterns.md +0 -179
- package/docs/stacks/protobuf-contracts.md +0 -85
- package/docs/stacks/rust-wasm-patterns.md +0 -106
- package/docs/stacks/solidjs-patterns.md +0 -110
- package/docs/stacks/storybook-patterns.md +0 -177
- package/docs/stacks/vanilla-extract-patterns.md +0 -115
- 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
|
-
```
|