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,145 +0,0 @@
|
|
|
1
|
-
# Capacitor OTA (Capgo) Patterns
|
|
2
|
-
|
|
3
|
-
> Capacitor 8 native runtime and Capgo over-the-air live updates: channels, encryption, staged rollouts, and store-safe delivery.
|
|
4
|
-
|
|
5
|
-
**Type:** Stack Skill (requires `capacitor` stack)
|
|
6
|
-
**Source:** [`stacks/capacitor/skills/ct-capacitor-ota/SKILL.md`](../../stacks/capacitor/skills/ct-capacitor-ota/SKILL.md)
|
|
7
|
-
**Directory Mappings:** `capacitor.config.ts`, `capacitor.config.json`, `ios/`, `android/`
|
|
8
|
-
**File Extensions:** _(none — keyed on config + content)_
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
Capacitor wraps a web app in a native iOS/Android shell. Capgo's `@capgo/capacitor-updater` lets you replace the web bundle that shell loads, shipping JS/HTML/CSS fixes in minutes instead of waiting on store review.
|
|
13
|
-
|
|
14
|
-
**Golden rule:** OTA updates the **web layer only**. Native code changes (new plugin, native dependency, Capacitor major bump) require a new store binary. Never OTA a bundle whose native contract differs from the installed shell.
|
|
15
|
-
|
|
16
|
-
## Versions (as of June 2026)
|
|
17
|
-
|
|
18
|
-
| Package | Version | Notes |
|
|
19
|
-
| -------------------------- | ------- | ---------------------------------------------------------------- |
|
|
20
|
-
| `@capacitor/core` & CLI | ^8.3.x | 8.3.1 (2026-04-16); Node 22+, SPM default for new iOS apps |
|
|
21
|
-
| `@capgo/capacitor-updater` | ^8.x | Major version tracks Capacitor major; v8 stores channels locally |
|
|
22
|
-
| `@capgo/cli` | latest | `npx @capgo/cli@latest …` — pin in CI |
|
|
23
|
-
|
|
24
|
-
## Setup
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm i @capgo/capacitor-updater && npx cap sync
|
|
28
|
-
npx @capgo/cli@latest init <API_KEY> # adds app, injects notifyAppReady, builds, uploads, tests
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Critical: notifyAppReady()
|
|
32
|
-
|
|
33
|
-
Call it as early as possible after the bundle boots. If the plugin doesn't hear it within `appReadyTimeout` (default 10s), it assumes the bundle is broken and **auto-rolls back** to the previous one — the safety net that makes OTA reversible.
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
import { CapacitorUpdater } from "@capgo/capacitor-updater";
|
|
37
|
-
|
|
38
|
-
CapacitorUpdater.notifyAppReady(); // top of bootstrap, before heavy async work
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Configuration
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
// capacitor.config.ts
|
|
45
|
-
import type { CapacitorConfig } from "@capacitor/cli";
|
|
46
|
-
|
|
47
|
-
const config: CapacitorConfig = {
|
|
48
|
-
appId: "com.example.app",
|
|
49
|
-
appName: "Example",
|
|
50
|
-
webDir: "dist",
|
|
51
|
-
plugins: {
|
|
52
|
-
CapacitorUpdater: {
|
|
53
|
-
autoUpdate: "atBackground", // apply when app backgrounds
|
|
54
|
-
appReadyTimeout: 10000,
|
|
55
|
-
autoDeleteFailed: true,
|
|
56
|
-
autoDeletePrevious: true,
|
|
57
|
-
resetWhenUpdate: true, // wipe OTA bundles on native app update
|
|
58
|
-
// defaultChannel: "Development", // TEST/QA BUILDS ONLY — omit in production
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export default config;
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### autoUpdate modes
|
|
67
|
-
|
|
68
|
-
| Value | Behavior |
|
|
69
|
-
| ---------------------------- | ------------------------------------------------------------ |
|
|
70
|
-
| `false` / `"off"` | No automatic updates — drive everything from JS |
|
|
71
|
-
| `"atBackground"` *(default)* | Download silently, apply when app moves to background |
|
|
72
|
-
| `"atInstall"` | Apply right after a fresh install / native update |
|
|
73
|
-
| `"onLaunch"` | Apply on next launch, then background |
|
|
74
|
-
| `"always"` | Apply immediately whenever a check runs |
|
|
75
|
-
| `"onlyDownload"` | Download only; emit `updateAvailable` for you to act |
|
|
76
|
-
|
|
77
|
-
`periodCheckDelay` sets poll frequency (seconds, **minimum 600** / 10 min).
|
|
78
|
-
|
|
79
|
-
## Channels
|
|
80
|
-
|
|
81
|
-
A channel points to one JS bundle. Swap which bundle a channel points to for instant rollout/rollback without rebuilding.
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
npx @capgo/cli@latest bundle upload --channel=production # build → upload → assign
|
|
85
|
-
npx @capgo/cli@latest channel set production -s default # make it the cloud default
|
|
86
|
-
npx @capgo/cli@latest channel set beta --self-assign # allow in-app setChannel()
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**Precedence** (highest first): forced device mapping → cloud per-device override → config `defaultChannel` → cloud Default Channel.
|
|
90
|
-
|
|
91
|
-
Ship production binaries **without** `defaultChannel` so users follow the cloud default. Recommended ladder:
|
|
92
|
-
|
|
93
|
-
| Channel | Bundle version | Audience |
|
|
94
|
-
| ------------- | -------------- | ------------------------- |
|
|
95
|
-
| `development` | `1.2.3-dev.1` | dev / emulator builds |
|
|
96
|
-
| `qa` | `1.2.3-qa.1` | QA verification |
|
|
97
|
-
| `staging` | `1.2.3-rc.1` | production-like sign-off |
|
|
98
|
-
| `production` | `1.2.3` | end users (store version) |
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// Runtime switch — needs "Allow device self-assignment" on the channel (v8+).
|
|
102
|
-
await CapacitorUpdater.setChannel({ channel: "beta", triggerAutoUpdate: true });
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## End-to-end encryption (v2)
|
|
106
|
-
|
|
107
|
-
Hybrid RSA-2048 + AES-256: a random AES key encrypts each bundle; your private RSA key signs the key + checksum; the app decrypts with the embedded public key.
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
npx @capgo/cli@latest key create
|
|
111
|
-
npx @capgo/cli@latest bundle upload --key-v2 --channel=production
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
- `.capgo_key_v2` (private) — **never commit**; store as a CI secret.
|
|
115
|
-
- `.capgo_key_v2.pub` (public) — safe to commit.
|
|
116
|
-
- With encryption configured, the plugin **strictly enforces** a valid signature; tampered/unsigned bundles are rejected.
|
|
117
|
-
|
|
118
|
-
## Staged rollouts & monitoring
|
|
119
|
-
|
|
120
|
-
- Start at ~10% of users, widen over 24h; schedule heavy rollouts 1–4 AM local, Wi-Fi-preferred.
|
|
121
|
-
- Targets: ~95% adoption within 24h, ~82% global success rate.
|
|
122
|
-
- Roll back instantly from the channel dashboard (crown icon on the previous build).
|
|
123
|
-
- Watch the `downloadFailed` / `appReady` ratio in Capgo stats; a spike means roll back.
|
|
124
|
-
|
|
125
|
-
## Store compliance
|
|
126
|
-
|
|
127
|
-
OTA of JS/HTML/CSS is permitted — Apple §3.3.2 (since iOS 4.3) and Google Play (webview/JS VM code is exempt). Shipping native code OTA is not.
|
|
128
|
-
|
|
129
|
-
## Anti-Patterns
|
|
130
|
-
|
|
131
|
-
| Anti-pattern | Why it's wrong |
|
|
132
|
-
| ----------------------------------------- | ------------------------------------------------------------------------ |
|
|
133
|
-
| **Not calling `notifyAppReady()`** | Every bundle auto-rolls back after `appReadyTimeout`. The #1 OTA failure. |
|
|
134
|
-
| **OTA-ing a native change** | Bundle needs a native API the shell lacks → crash. Bump the binary first. |
|
|
135
|
-
| **`defaultChannel` in production builds** | Pins users off the cloud default; breaks instant rollback. |
|
|
136
|
-
| **Committing `.capgo_key_v2`** | Leaks your signing key. Commit only the `.pub`. |
|
|
137
|
-
| **`periodCheckDelay` < 600** | Silently clamped; never polls faster than 10 min. |
|
|
138
|
-
| **100% rollout with no canary** | A bad bundle hits everyone at once. Stage it. |
|
|
139
|
-
| **Unpinned `@capgo/cli@latest` in CI** | A CLI minor can change upload behavior mid-pipeline. Pin it. |
|
|
140
|
-
|
|
141
|
-
## See Also
|
|
142
|
-
|
|
143
|
-
- [Capacitor & OTA Best Practices](../best-practices/capacitor/README.md) — full reference set
|
|
144
|
-
- [ct-vite-vitest-patterns](vite-vitest-patterns.md) — the build that produces `webDir`
|
|
145
|
-
- [ct-playwright-patterns](playwright-patterns.md) — E2E-verify a bundle before upload
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
# Cloudflare D1 and KV Patterns
|
|
2
|
-
|
|
3
|
-
> Cloudflare D1 SQL database and KV cache patterns.
|
|
4
|
-
|
|
5
|
-
**Type:** Stack Skill (requires `cloudflare` stack)
|
|
6
|
-
**Source:** [`stacks/cloudflare/skills/ct-cloudflare-d1-kv/SKILL.md`](../stacks/cloudflare/skills/ct-cloudflare-d1-kv/SKILL.md)
|
|
7
|
-
**Directory Mappings:** `src/db/`, `migrations/`
|
|
8
|
-
**File Extensions:** `.sql`
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
D1 is Cloudflare's serverless SQLite database, and KV is an eventually-consistent key-value store. Together they provide primary data storage and read-heavy caching for Workers.
|
|
13
|
-
|
|
14
|
-
## D1 Database
|
|
15
|
-
|
|
16
|
-
### Prepared Statements
|
|
17
|
-
|
|
18
|
-
Always use prepared statements with `.bind()` -- never concatenate SQL strings.
|
|
19
|
-
|
|
20
|
-
```rust
|
|
21
|
-
let stmt = db.prepare("SELECT * FROM users WHERE id = ?1")
|
|
22
|
-
.bind(&[id.into()])?;
|
|
23
|
-
let user = stmt.first::<User>(None).await?;
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### Batch Operations
|
|
27
|
-
|
|
28
|
-
Execute multiple statements in a single round trip. Batch operations are transactional -- all succeed or all fail.
|
|
29
|
-
|
|
30
|
-
```rust
|
|
31
|
-
let results = db.batch(vec![
|
|
32
|
-
db.prepare("INSERT INTO events ...").bind(&[...])?,
|
|
33
|
-
db.prepare("INSERT INTO event_members ...").bind(&[...])?,
|
|
34
|
-
]).await?;
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Migration Workflow
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
wrangler d1 migrations create DB "add_users_table" # Create migration
|
|
41
|
-
wrangler d1 migrations apply DB --local # Apply locally
|
|
42
|
-
wrangler d1 migrations apply DB --remote # Apply to remote
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Schema Conventions
|
|
46
|
-
|
|
47
|
-
- Use `TEXT` for IDs (UUIDs/nanoids, not auto-increment integers)
|
|
48
|
-
- Use `TEXT` for timestamps in ISO 8601 format
|
|
49
|
-
- Always add `created_at` and `updated_at` columns
|
|
50
|
-
- Create indexes for columns used in WHERE clauses and JOINs
|
|
51
|
-
- Use `IF NOT EXISTS` / `IF EXISTS` for idempotent migrations
|
|
52
|
-
- D1 has a **5MB response size limit** -- always use LIMIT
|
|
53
|
-
|
|
54
|
-
## KV Namespace
|
|
55
|
-
|
|
56
|
-
### Read-Through Caching Pattern
|
|
57
|
-
|
|
58
|
-
```rust
|
|
59
|
-
async fn get_user_cached(kv: &KvStore, db: &D1Database, user_id: &str) -> Result<User> {
|
|
60
|
-
let cache_key = format!("user:{}", user_id);
|
|
61
|
-
|
|
62
|
-
// Try cache first
|
|
63
|
-
if let Some(cached) = kv.get(&cache_key).text().await? {
|
|
64
|
-
if let Ok(user) = serde_json::from_str::<User>(&cached) {
|
|
65
|
-
return Ok(user);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Cache miss -- query DB, then populate cache with TTL
|
|
70
|
-
let user = /* query D1 */;
|
|
71
|
-
kv.put(&cache_key, &json)?.expiration_ttl(3600).execute().await?;
|
|
72
|
-
Ok(user)
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### TTL Strategies
|
|
77
|
-
|
|
78
|
-
| Data Type | TTL | Rationale |
|
|
79
|
-
|---|---|---|
|
|
80
|
-
| User profile | 1 hour | Changes infrequently |
|
|
81
|
-
| Session data | 24 hours | Matches session lifetime |
|
|
82
|
-
| Config/settings | 5 minutes | Needs faster propagation |
|
|
83
|
-
| Public listings | 15 minutes | Balances freshness and load |
|
|
84
|
-
|
|
85
|
-
### Key Naming Conventions
|
|
86
|
-
|
|
87
|
-
Use colon-separated hierarchical keys:
|
|
88
|
-
```
|
|
89
|
-
user:{userId}
|
|
90
|
-
user:{userId}:profile
|
|
91
|
-
event:{eventId}
|
|
92
|
-
event:{eventId}:members
|
|
93
|
-
session:{sessionToken}
|
|
94
|
-
cache:feed:{userId}:page:{pageNum}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Cache Invalidation
|
|
98
|
-
|
|
99
|
-
Invalidate on write operations by deleting the corresponding cache key after updating D1.
|
|
100
|
-
|
|
101
|
-
## Hyperdrive (External Database Acceleration)
|
|
102
|
-
|
|
103
|
-
Hyperdrive accelerates connections to external PostgreSQL/MySQL databases from Workers by maintaining regional connection pools close to your database, eliminating per-request TCP/TLS handshake overhead.
|
|
104
|
-
|
|
105
|
-
### Setup
|
|
106
|
-
|
|
107
|
-
```toml
|
|
108
|
-
# wrangler.toml
|
|
109
|
-
[[hyperdrive]]
|
|
110
|
-
binding = "HYPERDRIVE"
|
|
111
|
-
id = "<hyperdrive-config-id>"
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
// Access the connection string in your Worker
|
|
116
|
-
const sql = env.HYPERDRIVE.connectionString;
|
|
117
|
-
// Pass to your Postgres/MySQL client (e.g., node-postgres, drizzle)
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### When to Use
|
|
121
|
-
|
|
122
|
-
| Scenario | Use |
|
|
123
|
-
|---|---|
|
|
124
|
-
| Simple data, edge-native | **D1** (serverless SQLite) |
|
|
125
|
-
| Read-heavy caching layer | **KV** (eventually consistent) |
|
|
126
|
-
| Full relational DB (Postgres/MySQL) | **Hyperdrive** + external DB |
|
|
127
|
-
|
|
128
|
-
### Key Points
|
|
129
|
-
|
|
130
|
-
- Always use Hyperdrive when connecting to external databases from Workers
|
|
131
|
-
- Caches common read queries automatically, reducing origin load
|
|
132
|
-
- Connection pool size is configurable per Hyperdrive config (minimum 5 connections)
|
|
133
|
-
- Understands read vs write queries for intelligent caching
|
|
134
|
-
|
|
135
|
-
## Anti-patterns
|
|
136
|
-
|
|
137
|
-
| Anti-pattern | Why it's wrong |
|
|
138
|
-
|---|---|
|
|
139
|
-
| **Unbounded queries** | Always use LIMIT. D1 has a 5MB response limit. |
|
|
140
|
-
| **Missing indexes** | D1 is SQLite; without indexes, every query is a full table scan. |
|
|
141
|
-
| **KV as primary data store** | KV is eventually consistent with no query capability. Use D1 for primary data. |
|
|
142
|
-
| **Ignoring batch operations** | Each D1 call is a network round trip. Batch for performance. |
|
|
143
|
-
| **KV cache without TTL** | Stale data without TTL persists indefinitely. |
|
|
144
|
-
| **Not testing migrations locally** | Always apply with `--local` before `--remote`. |
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# ESNext Idioms
|
|
2
|
-
|
|
3
|
-
> Modern ECMAScript runtime idioms — Temporal, iterator helpers, `using`, structuredClone.
|
|
4
|
-
|
|
5
|
-
**Type:** Stack Skill (requires `esnext` stack)
|
|
6
|
-
**Source:** [`stacks/esnext/skills/ct-esnext-idioms/SKILL.md`](../../stacks/esnext/skills/ct-esnext-idioms/SKILL.md)
|
|
7
|
-
**Detected by:** tsconfig `target`/`module`/`lib` = `ESNext`/`ES2022+`, package.json `"type":"module"`, `engines.node >= 20`, or a `.mjs` file
|
|
8
|
-
**File Extensions:** `.mjs`, `.cjs`
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
Guidance for preferring current ECMAScript **runtime features** over legacy patterns and hand-rolled polyfills. It is availability-aware: each newer feature notes the runtime/polyfill it needs, so the recommendation never generates code the target can't run. Types are covered by `ct-typescript-conventions`; general code structure by `ct-code-style`.
|
|
13
|
-
|
|
14
|
-
## Topics
|
|
15
|
-
|
|
16
|
-
- **Dates → `Temporal`** — immutable, timezone-correct calendar/clock math (polyfill until baseline).
|
|
17
|
-
- **Resource management → `using` / `await using`** — deterministic cleanup via `Symbol.dispose`/`asyncDispose`.
|
|
18
|
-
- **Iterator helpers** — lazy `map`/`filter`/`take`, `Iterator.concat`, `Array.fromAsync`.
|
|
19
|
-
- **Clone & group** — `structuredClone`, `Object.groupBy` / `Map.groupBy`.
|
|
20
|
-
- **Async ergonomics** — `Promise.withResolvers`, `AbortSignal.timeout`/`any`, top-level await.
|
|
21
|
-
- **Immutable array ops** — `at`, `findLast`, `toSorted`/`toReversed`/`with`.
|
|
22
|
-
- **Errors** — `Error({ cause })`, `AggregateError`.
|
|
23
|
-
- **ESM hygiene** — `import.meta.dirname` over `__dirname`, dynamic `import()`.
|
|
24
|
-
|
|
25
|
-
See the [skill source](../../stacks/esnext/skills/ct-esnext-idioms/SKILL.md) for the full patterns, availability notes, and anti-patterns table.
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# Typesafe-i18n Patterns
|
|
2
|
-
|
|
3
|
-
> Typesafe-i18n internationalization patterns for SolidJS.
|
|
4
|
-
|
|
5
|
-
**Type:** Stack Skill (requires `i18n-typesafe` stack)
|
|
6
|
-
**Source:** [`stacks/i18n-typesafe/skills/ct-i18n-typesafe/SKILL.md`](../stacks/i18n-typesafe/skills/ct-i18n-typesafe/SKILL.md)
|
|
7
|
-
**Directory Mappings:** `src/locales/`, `src/i18n/`
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
typesafe-i18n provides fully type-safe translations with autocompletion. Translation keys are checked at compile time -- missing or misspelled keys cause TypeScript errors.
|
|
12
|
-
|
|
13
|
-
## Setup
|
|
14
|
-
|
|
15
|
-
### Directory Structure
|
|
16
|
-
|
|
17
|
-
```text
|
|
18
|
-
src/i18n/
|
|
19
|
-
i18n-types.ts # Auto-generated types
|
|
20
|
-
i18n-util.ts # Auto-generated utilities
|
|
21
|
-
i18n-solid.tsx # SolidJS adapter
|
|
22
|
-
en/index.ts # Base locale (source of truth)
|
|
23
|
-
fr/index.ts # French translations
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### Configuration (`.typesafe-i18n.json`)
|
|
27
|
-
|
|
28
|
-
```json
|
|
29
|
-
{
|
|
30
|
-
"baseLocale": "en",
|
|
31
|
-
"adapter": "solid",
|
|
32
|
-
"outputPath": "src/i18n/{locale}/"
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### Run the Generator
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx typesafe-i18n # Watches base locale and regenerates types
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Base Locale
|
|
43
|
-
|
|
44
|
-
The base locale is the source of truth. All types are derived from it.
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
// src/i18n/en/index.ts
|
|
48
|
-
const en = {
|
|
49
|
-
common: {
|
|
50
|
-
welcome: 'Welcome, {name:string}!',
|
|
51
|
-
itemCount: '{count:number} {{item|items}}',
|
|
52
|
-
save: 'Save',
|
|
53
|
-
},
|
|
54
|
-
auth: {
|
|
55
|
-
signIn: 'Sign in',
|
|
56
|
-
signOut: 'Sign out',
|
|
57
|
-
},
|
|
58
|
-
} satisfies BaseTranslation;
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Adding Translations
|
|
62
|
-
|
|
63
|
-
Other locales use the `Translation` type derived from the base locale. Missing keys or wrong parameter types cause compile errors.
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
// src/i18n/fr/index.ts
|
|
67
|
-
const fr = {
|
|
68
|
-
common: {
|
|
69
|
-
welcome: 'Bienvenue, {name} !',
|
|
70
|
-
itemCount: '{count} {{article|articles}}',
|
|
71
|
-
save: 'Enregistrer',
|
|
72
|
-
},
|
|
73
|
-
auth: {
|
|
74
|
-
signIn: 'Se connecter',
|
|
75
|
-
signOut: 'Se deconnecter',
|
|
76
|
-
},
|
|
77
|
-
} satisfies Translation;
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Usage in Components
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
const { LL } = useI18nContext();
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<div>
|
|
87
|
-
<h1>{LL().events.title()}</h1>
|
|
88
|
-
<span>{LL().events.attendees({ count: event.memberCount })}</span>
|
|
89
|
-
</div>
|
|
90
|
-
);
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Provider Setup
|
|
94
|
-
|
|
95
|
-
```tsx
|
|
96
|
-
<I18nProvider locale="en">
|
|
97
|
-
<Router />
|
|
98
|
-
</I18nProvider>
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Parameterized Translations
|
|
102
|
-
|
|
103
|
-
| Syntax | Example | Description |
|
|
104
|
-
| ------------------------ | ---------------------------- | ------------------------ |
|
|
105
|
-
| `{name:string}` | `'Hello {name:string}'` | Named string parameter |
|
|
106
|
-
| `{count:number}` | `'{count:number} items'` | Named number parameter |
|
|
107
|
-
| `{{singular\|plural}}` | `'{count} {{item\|items}}'` | Plural forms |
|
|
108
|
-
| `{0:string}` | `'Click {0:string}'` | Positional parameter |
|
|
109
|
-
|
|
110
|
-
## Namespace Organization
|
|
111
|
-
|
|
112
|
-
Organize translations by feature/domain. Keep namespaces flat (one level deep):
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
const en = {
|
|
116
|
-
common: { ... }, // Global
|
|
117
|
-
auth: { ... }, // Authentication
|
|
118
|
-
events: { ... }, // Events feature
|
|
119
|
-
profile: { ... }, // User profile
|
|
120
|
-
settings: { ... }, // Settings
|
|
121
|
-
admin: { ... }, // Admin panel
|
|
122
|
-
};
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Anti-patterns
|
|
126
|
-
|
|
127
|
-
| Anti-pattern | Why it's wrong |
|
|
128
|
-
| ------------------------------ | ------------------------------------------------------------------------------- |
|
|
129
|
-
| **Hardcoded strings** | Every user-visible string must go through `LL()`. |
|
|
130
|
-
| **Missing translations** | `Translation` type catches these at compile time -- run `tsc --noEmit` in CI. |
|
|
131
|
-
| **Interpolating HTML** | Do not embed HTML in translation strings. Use component composition. |
|
|
132
|
-
| **Dynamic key access** | `LL()[dynamicKey]()` bypasses type safety. Use conditional rendering. |
|
|
133
|
-
| **Forgetting to load locale** | Call `loadLocale()` before rendering. |
|
|
134
|
-
| **Over-splitting namespaces** | Too many small namespaces add overhead. Group by feature, not component. |
|
|
135
|
-
|
|
136
|
-
## Best Practices Reference
|
|
137
|
-
|
|
138
|
-
| Topic | Guide |
|
|
139
|
-
| ---------------------------------------- | ------------------------------------------------------------------------------- |
|
|
140
|
-
| `satisfies` pattern used in translations | [The satisfies Operator](../best-practices/typescript/satisfies-operator.md) |
|
|
141
|
-
| i18n provider and context pattern | [Context & Global State](../best-practices/solidjs/context-and-global-state.md) |
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# Playwright E2E Patterns
|
|
2
|
-
|
|
3
|
-
> Playwright E2E testing with Page Objects, fixtures, auth, network mocking, and CI/CD.
|
|
4
|
-
|
|
5
|
-
**Type:** Stack Skill (requires `playwright` stack)
|
|
6
|
-
**Source:** [`stacks/playwright/skills/ct-playwright-patterns/SKILL.md`](../../stacks/playwright/skills/ct-playwright-patterns/SKILL.md)
|
|
7
|
-
**Directory Mappings:** `e2e/`, `playwright.config.ts`
|
|
8
|
-
**File Extensions:** `.spec.ts`
|
|
9
|
-
|
|
10
|
-
## Overview
|
|
11
|
-
|
|
12
|
-
E2E tests sit at the top of the testing pyramid. They exercise full user journeys in real browsers. Write fewer, make each one count.
|
|
13
|
-
|
|
14
|
-
## Project Structure
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
e2e/
|
|
18
|
-
fixtures/ # Custom Playwright fixtures
|
|
19
|
-
index.ts # Extended test with page objects
|
|
20
|
-
pages/ # Page Object classes
|
|
21
|
-
components/ # Component Object classes (reusable fragments)
|
|
22
|
-
specs/ # Test files organized by feature
|
|
23
|
-
helpers/ # Utilities (data factories, API helpers)
|
|
24
|
-
.auth/ # storageState files (gitignored)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Page Object Model + Fixtures
|
|
28
|
-
|
|
29
|
-
Combine Page Objects with custom fixtures. Tests receive pre-built page objects:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
// e2e/pages/LoginPage.ts
|
|
33
|
-
export class LoginPage {
|
|
34
|
-
private readonly emailInput: Locator;
|
|
35
|
-
private readonly submitButton: Locator;
|
|
36
|
-
|
|
37
|
-
constructor(private readonly page: Page) {
|
|
38
|
-
this.emailInput = page.getByLabel("Email");
|
|
39
|
-
this.submitButton = page.getByRole("button", { name: "Sign in" });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async goto() { await this.page.goto("/login"); }
|
|
43
|
-
async login(email: string, password: string) {
|
|
44
|
-
await this.emailInput.fill(email);
|
|
45
|
-
await this.submitButton.click();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// e2e/fixtures/index.ts
|
|
50
|
-
export const test = base.extend<Fixtures>({
|
|
51
|
-
loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
|
|
52
|
-
});
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Fixtures are lazy (created on demand) and composable (can depend on each other).
|
|
56
|
-
|
|
57
|
-
## Authentication
|
|
58
|
-
|
|
59
|
-
Use project dependencies with `storageState` to authenticate once and reuse:
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
// e2e/auth.setup.ts
|
|
63
|
-
setup("authenticate", async ({ page }) => {
|
|
64
|
-
await page.goto("/login");
|
|
65
|
-
await page.getByLabel("Email").fill("test@example.com");
|
|
66
|
-
await page.getByRole("button", { name: "Sign in" }).click();
|
|
67
|
-
await page.waitForURL("/dashboard");
|
|
68
|
-
await page.context().storageState({ path: "e2e/.auth/user.json" });
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Config: `dependencies: ["setup"]` + `storageState: "e2e/.auth/user.json"`. Add `e2e/.auth/` to `.gitignore`.
|
|
73
|
-
|
|
74
|
-
For multi-role testing, create separate setup files per role.
|
|
75
|
-
|
|
76
|
-
## Locator Priority
|
|
77
|
-
|
|
78
|
-
1. `getByRole()` -- accessibility semantics, survives refactors
|
|
79
|
-
2. `getByLabel()` -- form elements
|
|
80
|
-
3. `getByPlaceholder()` -- input hints
|
|
81
|
-
4. `getByText()` -- visible text
|
|
82
|
-
5. `getByTestId()` -- stable but less semantic
|
|
83
|
-
6. CSS/XPath -- **avoid**
|
|
84
|
-
|
|
85
|
-
## Web-First Assertions
|
|
86
|
-
|
|
87
|
-
Always use auto-retrying assertions:
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
// GOOD
|
|
91
|
-
await expect(page.getByRole("alert")).toBeVisible();
|
|
92
|
-
await expect(page).toHaveURL("/dashboard");
|
|
93
|
-
|
|
94
|
-
// BAD -- checks once, no retry
|
|
95
|
-
expect(await page.getByRole("alert").isVisible()).toBeTruthy();
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Key assertions: `toBeVisible()`, `toBeEnabled()`, `toHaveText()`, `toHaveURL()`, `toHaveCount()`, `toHaveScreenshot()`, `toMatchAriaSnapshot()`.
|
|
99
|
-
|
|
100
|
-
## Network Mocking
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
// Mock API
|
|
104
|
-
await page.route("**/api/users", async (route) => {
|
|
105
|
-
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ name: "Alice" }]) });
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// Block analytics
|
|
109
|
-
await page.route("**/analytics/**", (route) => route.abort());
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Mock ~80% of API calls for speed; keep ~20% hitting real endpoints. Always call `route.continue()`, `route.fulfill()`, or `route.abort()`. Use `page.unrouteAll()` in cleanup.
|
|
113
|
-
|
|
114
|
-
## WebSocket Mocking (v1.53+)
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
await page.routeWebSocket("wss://example.com/ws", (ws) => {
|
|
118
|
-
ws.onMessage((message) => { ws.send("mocked response"); });
|
|
119
|
-
});
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Accessibility Testing
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import AxeBuilder from "@axe-core/playwright";
|
|
126
|
-
|
|
127
|
-
test("WCAG 2.1 AA", async ({ page }) => {
|
|
128
|
-
await page.goto("/");
|
|
129
|
-
const results = await new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21aa"]).analyze();
|
|
130
|
-
expect(results.violations).toEqual([]);
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Aria snapshots (v1.52+): `await expect(nav).toMatchAriaSnapshot(...)`.
|
|
135
|
-
|
|
136
|
-
## Parallelization
|
|
137
|
-
|
|
138
|
-
- File-level parallel (default) -- start here
|
|
139
|
-
- In-file: `test.describe.configure({ mode: "parallel" })`
|
|
140
|
-
- Full: `fullyParallel: true` in config
|
|
141
|
-
|
|
142
|
-
Each worker gets its own `BrowserContext` (isolated cookies/storage). Use `mode: "serial"` sparingly.
|
|
143
|
-
|
|
144
|
-
## CI/CD
|
|
145
|
-
|
|
146
|
-
- Use official Playwright Docker image (`mcr.microsoft.com/playwright:v1.58.0-noble`)
|
|
147
|
-
- Upload reports + traces as artifacts
|
|
148
|
-
- Use `--shard` for suites exceeding 5 minutes
|
|
149
|
-
- `retries: process.env.CI ? 2 : 0`
|
|
150
|
-
- Traces: `trace: "on-first-retry"`
|
|
151
|
-
|
|
152
|
-
## Flaky Test Prevention
|
|
153
|
-
|
|
154
|
-
| Cause | Fix |
|
|
155
|
-
| ---------------------- | ------------------------------------------------ |
|
|
156
|
-
| `waitForTimeout()` | Web-first assertions or `waitForResponse` |
|
|
157
|
-
| Brittle selectors | `getByRole`, `getByLabel`, `getByTestId` |
|
|
158
|
-
| Shared state | Fresh `BrowserContext` per test |
|
|
159
|
-
| External API flakiness | Mock with `page.route()` |
|
|
160
|
-
| Animation timing | `--force-prefers-reduced-motion` |
|
|
161
|
-
| Missing `await` | #1 source of false-passing tests. Lint for it. |
|
|
162
|
-
|
|
163
|
-
## Anti-Patterns
|
|
164
|
-
|
|
165
|
-
1. **Using `waitForTimeout()`** -- Use web-first assertions or explicit wait conditions.
|
|
166
|
-
2. **Fragile CSS/XPath selectors** -- Use role-based and semantic locators.
|
|
167
|
-
3. **Using `{ force: true }`** -- Masks real actionability problems.
|
|
168
|
-
4. **Missing `await` on assertions** -- #1 source of false-passing tests.
|
|
169
|
-
5. **Tests without assertions** -- A test that clicks without asserting is just a script.
|
|
170
|
-
6. **Using Playwright for unit tests** -- Use Vitest for pure logic.
|
|
171
|
-
7. **Committing raw codegen output** -- Always refactor into Page Objects.
|
|
172
|
-
8. **Not cleaning up route handlers** -- Use `page.unrouteAll()`.
|
|
173
|
-
|
|
174
|
-
## See Also
|
|
175
|
-
|
|
176
|
-
- [ct-testing-patterns](../skills/testing-patterns.md) -- Framework-agnostic TDD practices
|
|
177
|
-
- [Playwright E2E Best Practices](../best-practices/testing/playwright-e2e.md) -- SolidJS-specific E2E patterns
|
|
178
|
-
- [ct-vite-vitest-patterns](vite-vitest-patterns.md) -- Unit testing layer
|
|
179
|
-
- [ct-storybook-patterns](storybook-patterns.md) -- Interaction testing layer
|