@3lineas/grooveskills 1.0.0 → 1.0.3
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/README.md +4 -0
- package/dist/{chunk-75476IDS.js → chunk-6FZFZCBX.js} +156 -59
- package/dist/chunk-6FZFZCBX.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/main.js +1 -1
- package/package.json +1 -1
- package/skills/react-advanced/SKILL.md +147 -0
- package/skills/react-advanced/references/component-patterns.md +117 -0
- package/skills/react-advanced/references/concurrent-rendering.md +130 -0
- package/skills/react-advanced/references/performance.md +99 -0
- package/skills/react-advanced/references/react-compiler.md +90 -0
- package/skills/react-advanced/references/react19-actions-and-apis.md +145 -0
- package/skills/react-advanced/references/rsc-boundary.md +129 -0
- package/skills/react-core/SKILL.md +168 -0
- package/skills/react-create-page/SKILL.md +187 -0
- package/dist/chunk-75476IDS.js.map +0 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- **Cursor IDE**: `.cursor/rules/<skill-name>.mdc` (auto-adapted to Cursor rules format)
|
|
16
16
|
- **Gemini Code Assist**: `.gemini/skills/<skill-name>/`
|
|
17
17
|
- **✨ Interactive & Friendly CLI**: Clean terminal experience built with `@clack/prompts` and courteous, persuasive feedback.
|
|
18
|
+
- **🔗 Centralized Global Store & Symlinks**: Skills are synced globally to `~/.grooveskills/` and linked into local projects via symlinks. Updates to global skills instantly reflect across all your projects with zero file duplication.
|
|
18
19
|
- **🛡️ Battle-tested Architectural Standards**:
|
|
19
20
|
- **Universal Best Practices**: Multi-language clean code (TypeScript, PHP, Dart, Rust, etc.), early returns, zero barrel files, strict typing, <200 lines per file.
|
|
20
21
|
- **Hono API Architecture**: Independent routes by domain, isolated Actions layer, database queries exclusively inside Actions, strict Valibot validation, and Cloudflare/Hono RPC.
|
|
@@ -69,6 +70,9 @@ npx @3lineas/grooveskills --skills best-practices hono --agents antigravity clau
|
|
|
69
70
|
| :--- | :--- | :--- | :--- |
|
|
70
71
|
| `best-practices` | **Universal Best Practices** | Multi-language clean code, early returns, no barrel files, strict typing, <200 lines. | **Always suggested by default** (Any language/file) |
|
|
71
72
|
| `hono` | **Hono Architecture (Valibot)** | Enterprise Hono standard: Valibot validation, Actions layer, RPC bindings, AppError. | Detected `hono`, `@hono/valibot-validator`, etc. |
|
|
73
|
+
| `react-core` | **React Core Fundamentals** | Opinionated React fundamentals: function components, JSX, immutable state, core hooks, lists, and controlled inputs. | Detected `react`, `react-dom` |
|
|
74
|
+
| `react-advanced` | **React 19 Advanced & Compiler Architecture** | React 19 platform features: React Compiler 1.0, concurrent rendering, `use()`, Actions, virtualization, and RSC boundaries. | Detected `react`, `react-dom` |
|
|
75
|
+
| `react-create-page` | **React Page & View Architecture** | Declarative page coordinators, domain hooks, isolated API actions, Valibot + Formisch forms, and atomic co-location. | Detected `react`, `react-dom` |
|
|
72
76
|
|
|
73
77
|
---
|
|
74
78
|
|
|
@@ -56,6 +56,27 @@ var SKILL_CATALOG = [
|
|
|
56
56
|
description: "Enterprise Hono API standard with Valibot validation, isolated Actions layer, and RPC bindings",
|
|
57
57
|
dependencies: ["hono", "@hono/valibot-validator", "@hono/standard-validator"],
|
|
58
58
|
directoryName: "hono"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "react-core",
|
|
62
|
+
name: "React Core Fundamentals",
|
|
63
|
+
description: "Opinionated React fundamentals: function components, JSX, immutable state, core hooks, lists, and controlled inputs",
|
|
64
|
+
dependencies: ["react", "react-dom"],
|
|
65
|
+
directoryName: "react-core"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "react-advanced",
|
|
69
|
+
name: "React 19 Advanced & Compiler Architecture",
|
|
70
|
+
description: "Modern React 19 platform features: React Compiler auto-memoization, concurrent rendering, use(), Actions, virtualization, and RSC boundaries",
|
|
71
|
+
dependencies: ["react", "react-dom"],
|
|
72
|
+
directoryName: "react-advanced"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "react-create-page",
|
|
76
|
+
name: "React Page & View Architecture",
|
|
77
|
+
description: "Clean architecture for React pages: declarative page coordinators, domain hooks, isolated API actions, Valibot + Formisch forms, and atomic co-location",
|
|
78
|
+
dependencies: ["react", "react-dom"],
|
|
79
|
+
directoryName: "react-create-page"
|
|
59
80
|
}
|
|
60
81
|
];
|
|
61
82
|
|
|
@@ -145,7 +166,7 @@ async function promptConfirmOverwrite() {
|
|
|
145
166
|
}
|
|
146
167
|
|
|
147
168
|
// src/domains/installer/verify-skill-exists.ts
|
|
148
|
-
import {
|
|
169
|
+
import { lstatSync } from "fs";
|
|
149
170
|
import { join as join2 } from "path";
|
|
150
171
|
var AGENT_DESTINATION_MAP = {
|
|
151
172
|
antigravity: (dir) => join2(".agents", "skills", dir),
|
|
@@ -157,118 +178,193 @@ function verifySkillExists(targetPath, skillDirectoryName, agents) {
|
|
|
157
178
|
for (const agent of agents) {
|
|
158
179
|
const relativePathResolver = AGENT_DESTINATION_MAP[agent];
|
|
159
180
|
const destination = join2(targetPath, relativePathResolver(skillDirectoryName));
|
|
160
|
-
|
|
181
|
+
try {
|
|
182
|
+
lstatSync(destination);
|
|
161
183
|
return true;
|
|
184
|
+
} catch {
|
|
162
185
|
}
|
|
163
186
|
}
|
|
164
187
|
return false;
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
// src/domains/installer/install-orchestrator.ts
|
|
168
|
-
import { join as
|
|
191
|
+
import { join as join10 } from "path";
|
|
169
192
|
|
|
170
193
|
// src/domains/adapters/antigravity.adapter.ts
|
|
171
|
-
import {
|
|
194
|
+
import { mkdirSync, existsSync as existsSync2 } from "fs";
|
|
195
|
+
import { join as join4 } from "path";
|
|
196
|
+
|
|
197
|
+
// src/domains/global-store/global-paths.ts
|
|
198
|
+
import { homedir } from "os";
|
|
172
199
|
import { join as join3 } from "path";
|
|
200
|
+
function getGlobalStoreDir() {
|
|
201
|
+
return join3(homedir(), ".grooveskills");
|
|
202
|
+
}
|
|
203
|
+
function getGlobalSkillsDir() {
|
|
204
|
+
return join3(getGlobalStoreDir(), "skills");
|
|
205
|
+
}
|
|
206
|
+
function getGlobalCursorRulesDir() {
|
|
207
|
+
return join3(getGlobalStoreDir(), "cursor-rules");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/domains/adapters/link-helper.ts
|
|
211
|
+
import { lstatSync as lstatSync2, unlinkSync, rmSync, symlinkSync } from "fs";
|
|
212
|
+
function removeExistingLinkPath(path) {
|
|
213
|
+
try {
|
|
214
|
+
const lstat = lstatSync2(path);
|
|
215
|
+
if (lstat.isDirectory() && !lstat.isSymbolicLink()) {
|
|
216
|
+
rmSync(path, { recursive: true, force: true });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
unlinkSync(path);
|
|
220
|
+
} catch {
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function createOrReplaceSymlink(target, linkPath, type) {
|
|
224
|
+
removeExistingLinkPath(linkPath);
|
|
225
|
+
symlinkSync(target, linkPath, type);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/domains/adapters/antigravity.adapter.ts
|
|
173
229
|
var AntigravityAdapter = class {
|
|
174
230
|
id = "antigravity";
|
|
175
231
|
name = "Antigravity";
|
|
176
232
|
targetRelativeDir = ".agents/skills";
|
|
177
|
-
async installSkill(targetPath, skill,
|
|
178
|
-
const
|
|
179
|
-
if (!
|
|
180
|
-
mkdirSync(
|
|
233
|
+
async installSkill(targetPath, skill, _skillSourceDir) {
|
|
234
|
+
const parentDir = join4(targetPath, this.targetRelativeDir);
|
|
235
|
+
if (!existsSync2(parentDir)) {
|
|
236
|
+
mkdirSync(parentDir, { recursive: true });
|
|
181
237
|
}
|
|
182
|
-
|
|
238
|
+
const destination = join4(parentDir, skill.directoryName);
|
|
239
|
+
const globalSkillDir = join4(getGlobalSkillsDir(), skill.directoryName);
|
|
240
|
+
createOrReplaceSymlink(globalSkillDir, destination, "dir");
|
|
183
241
|
return [destination];
|
|
184
242
|
}
|
|
185
243
|
};
|
|
186
244
|
|
|
187
245
|
// src/domains/adapters/claude.adapter.ts
|
|
188
|
-
import {
|
|
189
|
-
import { join as
|
|
246
|
+
import { mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
247
|
+
import { join as join5 } from "path";
|
|
190
248
|
var ClaudeAdapter = class {
|
|
191
249
|
id = "claude";
|
|
192
250
|
name = "Claude Code";
|
|
193
251
|
targetRelativeDir = ".claude/skills";
|
|
194
|
-
async installSkill(targetPath, skill,
|
|
195
|
-
const
|
|
196
|
-
if (!
|
|
197
|
-
mkdirSync2(
|
|
252
|
+
async installSkill(targetPath, skill, _skillSourceDir) {
|
|
253
|
+
const parentDir = join5(targetPath, this.targetRelativeDir);
|
|
254
|
+
if (!existsSync3(parentDir)) {
|
|
255
|
+
mkdirSync2(parentDir, { recursive: true });
|
|
198
256
|
}
|
|
199
|
-
|
|
257
|
+
const destination = join5(parentDir, skill.directoryName);
|
|
258
|
+
const globalSkillDir = join5(getGlobalSkillsDir(), skill.directoryName);
|
|
259
|
+
createOrReplaceSymlink(globalSkillDir, destination, "dir");
|
|
200
260
|
return [destination];
|
|
201
261
|
}
|
|
202
262
|
};
|
|
203
263
|
|
|
204
264
|
// src/domains/adapters/cursor.adapter.ts
|
|
205
|
-
import {
|
|
206
|
-
import { join as
|
|
265
|
+
import { mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
266
|
+
import { join as join6 } from "path";
|
|
207
267
|
var CursorAdapter = class {
|
|
208
268
|
id = "cursor";
|
|
209
269
|
name = "Cursor";
|
|
210
270
|
targetRelativeDir = ".cursor/rules";
|
|
211
|
-
async installSkill(targetPath, skill,
|
|
212
|
-
const rulesDir =
|
|
213
|
-
if (!
|
|
271
|
+
async installSkill(targetPath, skill, _skillSourceDir) {
|
|
272
|
+
const rulesDir = join6(targetPath, this.targetRelativeDir);
|
|
273
|
+
if (!existsSync4(rulesDir)) {
|
|
214
274
|
mkdirSync3(rulesDir, { recursive: true });
|
|
215
275
|
}
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const mdcHeader = `---
|
|
222
|
-
description: ${skill.description}
|
|
223
|
-
globs: ["**/*"]
|
|
224
|
-
alwaysApply: false
|
|
225
|
-
---
|
|
226
|
-
|
|
227
|
-
`;
|
|
228
|
-
const cleanedBody = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
229
|
-
const targetFile = join5(rulesDir, `${skill.id}.mdc`);
|
|
230
|
-
writeFileSync(targetFile, `${mdcHeader}${cleanedBody}`, "utf-8");
|
|
231
|
-
return [targetFile];
|
|
276
|
+
const filename = `${skill.id}.mdc`;
|
|
277
|
+
const destination = join6(rulesDir, filename);
|
|
278
|
+
const globalRuleFile = join6(getGlobalCursorRulesDir(), filename);
|
|
279
|
+
createOrReplaceSymlink(globalRuleFile, destination, "file");
|
|
280
|
+
return [destination];
|
|
232
281
|
}
|
|
233
282
|
};
|
|
234
283
|
|
|
235
284
|
// src/domains/adapters/gemini.adapter.ts
|
|
236
|
-
import {
|
|
237
|
-
import { join as
|
|
285
|
+
import { mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
|
|
286
|
+
import { join as join7 } from "path";
|
|
238
287
|
var GeminiAdapter = class {
|
|
239
288
|
id = "gemini";
|
|
240
289
|
name = "Gemini Code Assist";
|
|
241
290
|
targetRelativeDir = ".gemini/skills";
|
|
242
|
-
async installSkill(targetPath, skill,
|
|
243
|
-
const
|
|
244
|
-
if (!
|
|
245
|
-
mkdirSync4(
|
|
291
|
+
async installSkill(targetPath, skill, _skillSourceDir) {
|
|
292
|
+
const parentDir = join7(targetPath, this.targetRelativeDir);
|
|
293
|
+
if (!existsSync5(parentDir)) {
|
|
294
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
246
295
|
}
|
|
247
|
-
|
|
296
|
+
const destination = join7(parentDir, skill.directoryName);
|
|
297
|
+
const globalSkillDir = join7(getGlobalSkillsDir(), skill.directoryName);
|
|
298
|
+
createOrReplaceSymlink(globalSkillDir, destination, "dir");
|
|
248
299
|
return [destination];
|
|
249
300
|
}
|
|
250
301
|
};
|
|
251
302
|
|
|
303
|
+
// src/domains/global-store/sync-global-store.ts
|
|
304
|
+
import { cpSync, mkdirSync as mkdirSync5, existsSync as existsSync7, writeFileSync, readFileSync as readFileSync2 } from "fs";
|
|
305
|
+
import { join as join9 } from "path";
|
|
306
|
+
|
|
252
307
|
// src/domains/installer/resolve-skills-source.ts
|
|
253
308
|
import { fileURLToPath } from "url";
|
|
254
|
-
import { dirname, resolve, join as
|
|
255
|
-
import { existsSync as
|
|
309
|
+
import { dirname, resolve, join as join8 } from "path";
|
|
310
|
+
import { existsSync as existsSync6 } from "fs";
|
|
256
311
|
function resolveSkillsSourceRoot() {
|
|
257
312
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
258
313
|
const currentDir = dirname(currentFilePath);
|
|
259
|
-
const
|
|
260
|
-
if (
|
|
261
|
-
return
|
|
314
|
+
const candidateFromDist = resolve(currentDir, "../skills");
|
|
315
|
+
if (existsSync6(candidateFromDist)) {
|
|
316
|
+
return candidateFromDist;
|
|
317
|
+
}
|
|
318
|
+
const candidateFromSrc = resolve(currentDir, "../../../skills");
|
|
319
|
+
if (existsSync6(candidateFromSrc)) {
|
|
320
|
+
return candidateFromSrc;
|
|
262
321
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
322
|
+
let searchDir = currentDir;
|
|
323
|
+
for (let i = 0; i < 5; i++) {
|
|
324
|
+
const candidate = join8(searchDir, "skills");
|
|
325
|
+
if (existsSync6(candidate)) {
|
|
326
|
+
return candidate;
|
|
327
|
+
}
|
|
328
|
+
const parent = dirname(searchDir);
|
|
329
|
+
if (parent === searchDir) {
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
searchDir = parent;
|
|
266
333
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
334
|
+
throw new Error(
|
|
335
|
+
`GrooveSkills internal error: Unable to locate bundled skills directory from ${currentDir}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/domains/global-store/sync-global-store.ts
|
|
340
|
+
function syncGlobalStore() {
|
|
341
|
+
const bundledSkillsDir = resolveSkillsSourceRoot();
|
|
342
|
+
const globalSkillsDir = getGlobalSkillsDir();
|
|
343
|
+
const globalCursorRulesDir = getGlobalCursorRulesDir();
|
|
344
|
+
if (!existsSync7(globalSkillsDir)) {
|
|
345
|
+
mkdirSync5(globalSkillsDir, { recursive: true });
|
|
346
|
+
}
|
|
347
|
+
if (!existsSync7(globalCursorRulesDir)) {
|
|
348
|
+
mkdirSync5(globalCursorRulesDir, { recursive: true });
|
|
349
|
+
}
|
|
350
|
+
cpSync(bundledSkillsDir, globalSkillsDir, { recursive: true });
|
|
351
|
+
for (const skill of SKILL_CATALOG) {
|
|
352
|
+
const skillMdPath = join9(globalSkillsDir, skill.directoryName, "SKILL.md");
|
|
353
|
+
if (!existsSync7(skillMdPath)) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const content = readFileSync2(skillMdPath, "utf-8");
|
|
357
|
+
const mdcHeader = `---
|
|
358
|
+
description: ${skill.description}
|
|
359
|
+
globs: ["**/*"]
|
|
360
|
+
alwaysApply: false
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
`;
|
|
364
|
+
const cleanedBody = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
365
|
+
const targetMdc = join9(globalCursorRulesDir, `${skill.id}.mdc`);
|
|
366
|
+
writeFileSync(targetMdc, `${mdcHeader}${cleanedBody}`, "utf-8");
|
|
270
367
|
}
|
|
271
|
-
return join7(process.cwd(), "skills");
|
|
272
368
|
}
|
|
273
369
|
|
|
274
370
|
// src/domains/installer/install-orchestrator.ts
|
|
@@ -279,14 +375,15 @@ var ADAPTER_REGISTRY = {
|
|
|
279
375
|
gemini: new GeminiAdapter()
|
|
280
376
|
};
|
|
281
377
|
async function installSkills(options) {
|
|
282
|
-
|
|
378
|
+
syncGlobalStore();
|
|
379
|
+
const globalSkillsDir = getGlobalSkillsDir();
|
|
283
380
|
const createdFiles = [];
|
|
284
381
|
for (const skillId of options.selectedSkillIds) {
|
|
285
382
|
const skill = SKILL_CATALOG.find((item) => item.id === skillId);
|
|
286
383
|
if (!skill) {
|
|
287
384
|
continue;
|
|
288
385
|
}
|
|
289
|
-
const skillSourceDir =
|
|
386
|
+
const skillSourceDir = join10(globalSkillsDir, skill.directoryName);
|
|
290
387
|
for (const agentId of options.selectedAgents) {
|
|
291
388
|
const adapter = ADAPTER_REGISTRY[agentId];
|
|
292
389
|
if (!adapter) {
|
|
@@ -373,4 +470,4 @@ async function runGrooveSkills(options = {}) {
|
|
|
373
470
|
export {
|
|
374
471
|
runGrooveSkills
|
|
375
472
|
};
|
|
376
|
-
//# sourceMappingURL=chunk-
|
|
473
|
+
//# sourceMappingURL=chunk-6FZFZCBX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domains/detector/detect-agents.ts","../src/domains/detector/detect-dependencies.ts","../src/domains/registry/skill-catalog.ts","../src/domains/detector/project-detector.ts","../src/domains/ui/ui-orchestrator.ts","../src/domains/ui/banners.ts","../src/domains/ui/prompt-skills.ts","../src/domains/ui/prompt-agents.ts","../src/domains/ui/prompt-confirm-overwrite.ts","../src/domains/installer/verify-skill-exists.ts","../src/domains/installer/install-orchestrator.ts","../src/domains/adapters/antigravity.adapter.ts","../src/domains/global-store/global-paths.ts","../src/domains/adapters/link-helper.ts","../src/domains/adapters/claude.adapter.ts","../src/domains/adapters/cursor.adapter.ts","../src/domains/adapters/gemini.adapter.ts","../src/domains/global-store/sync-global-store.ts","../src/domains/installer/resolve-skills-source.ts","../src/main.ts"],"sourcesContent":["import { readdirSync } from \"node:fs\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nexport function detectAgents(targetPath: string): AgentId[] {\n const detected: AgentId[] = [];\n\n try {\n const entries = new Set(readdirSync(targetPath));\n\n if (entries.has(\".agents\")) {\n detected.push(\"antigravity\");\n }\n if (entries.has(\".claude\")) {\n detected.push(\"claude\");\n }\n if (entries.has(\".cursor\")) {\n detected.push(\"cursor\");\n }\n if (entries.has(\".gemini\")) {\n detected.push(\"gemini\");\n }\n } catch {\n // If directory cannot be read, return empty list gracefully\n }\n\n return detected;\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface PackageJsonDetection {\n dependencies: string[];\n exists: boolean;\n}\n\nexport function detectDependencies(targetPath: string): PackageJsonDetection {\n const packageJsonPath = join(targetPath, \"package.json\");\n\n if (!existsSync(packageJsonPath)) {\n return { dependencies: [], exists: false };\n }\n\n try {\n const raw = readFileSync(packageJsonPath, \"utf-8\");\n const parsed = JSON.parse(raw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n const direct = Object.keys(parsed.dependencies ?? {});\n const dev = Object.keys(parsed.devDependencies ?? {});\n const all = Array.from(new Set([...direct, ...dev]));\n\n return { dependencies: all, exists: true };\n } catch {\n return { dependencies: [], exists: true };\n }\n}\n","import type { SkillMetadata } from \"@/types/skill.types\";\n\nexport const SKILL_CATALOG: SkillMetadata[] = [\n {\n id: \"best-practices\",\n name: \"Universal Best Practices\",\n description: \"Multi-language clean code standards, early returns, no barrel files, strict typing, <200 lines\",\n dependencies: [],\n directoryName: \"best-practices\",\n },\n {\n id: \"hono\",\n name: \"Hono Architecture (Valibot)\",\n description: \"Enterprise Hono API standard with Valibot validation, isolated Actions layer, and RPC bindings\",\n dependencies: [\"hono\", \"@hono/valibot-validator\", \"@hono/standard-validator\"],\n directoryName: \"hono\",\n },\n {\n id: \"react-core\",\n name: \"React Core Fundamentals\",\n description: \"Opinionated React fundamentals: function components, JSX, immutable state, core hooks, lists, and controlled inputs\",\n dependencies: [\"react\", \"react-dom\"],\n directoryName: \"react-core\",\n },\n {\n id: \"react-advanced\",\n name: \"React 19 Advanced & Compiler Architecture\",\n description: \"Modern React 19 platform features: React Compiler auto-memoization, concurrent rendering, use(), Actions, virtualization, and RSC boundaries\",\n dependencies: [\"react\", \"react-dom\"],\n directoryName: \"react-advanced\",\n },\n {\n id: \"react-create-page\",\n name: \"React Page & View Architecture\",\n description: \"Clean architecture for React pages: declarative page coordinators, domain hooks, isolated API actions, Valibot + Formisch forms, and atomic co-location\",\n dependencies: [\"react\", \"react-dom\"],\n directoryName: \"react-create-page\",\n },\n];\n","import { detectAgents } from \"@/domains/detector/detect-agents\";\nimport { detectDependencies } from \"@/domains/detector/detect-dependencies\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\nimport type { ProjectContext } from \"@/types/context.types\";\n\nexport function detectProjectContext(targetPath: string): ProjectContext {\n const detectedAgents = detectAgents(targetPath);\n const { dependencies, exists } = detectDependencies(targetPath);\n\n const suggestedSkillIds: string[] = [\"best-practices\"];\n\n for (const skill of SKILL_CATALOG) {\n if (skill.dependencies.length === 0) {\n continue;\n }\n const hasMatch = skill.dependencies.some((dep) => dependencies.includes(dep));\n if (hasMatch && !suggestedSkillIds.includes(skill.id)) {\n suggestedSkillIds.push(skill.id);\n }\n }\n\n return {\n targetPath,\n detectedDependencies: dependencies,\n suggestedSkillIds,\n detectedAgents,\n packageJsonExists: exists,\n };\n}\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { renderBanner } from \"@/domains/ui/banners\";\nimport { promptSkills } from \"@/domains/ui/prompt-skills\";\nimport { promptAgents } from \"@/domains/ui/prompt-agents\";\nimport { promptConfirmOverwrite } from \"@/domains/ui/prompt-confirm-overwrite\";\nimport { verifySkillExists } from \"@/domains/installer/verify-skill-exists\";\nimport { installSkills } from \"@/domains/installer/install-orchestrator\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\nimport type { ProjectContext } from \"@/types/context.types\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nexport async function runInteractiveUi(context: ProjectContext): Promise<void> {\n console.log(renderBanner());\n p.intro(pc.magenta(\"Welcome to GrooveSkills! Let's tailor your AI agents with clean architectural standards.\"));\n\n if (context.detectedDependencies.length > 0) {\n p.note(\n `Detected dependencies: ${context.detectedDependencies.map((d) => pc.bold(d)).join(\", \")}\\n` +\n `Active AI folders: ${context.detectedAgents.map((a) => pc.green(a)).join(\", \") || \"None (will be created)\"}`,\n \"Project Environment\"\n );\n }\n\n const selectedSkills = await promptSkills(context.suggestedSkillIds);\n if (p.isCancel(selectedSkills)) {\n p.cancel(\"Installation cancelled. Have a wonderful day!\");\n process.exit(0);\n }\n\n const selectedAgents = await promptAgents(context.detectedAgents);\n if (p.isCancel(selectedAgents)) {\n p.cancel(\"Installation cancelled. Have a wonderful day!\");\n process.exit(0);\n }\n\n let forceOverwrite = true;\n const anyExists = (selectedSkills as string[]).some((skillId) => {\n const meta = SKILL_CATALOG.find((s) => s.id === skillId);\n return meta ? verifySkillExists(context.targetPath, meta.directoryName, selectedAgents as AgentId[]) : false;\n });\n\n if (anyExists) {\n const confirm = await promptConfirmOverwrite();\n if (p.isCancel(confirm) || !confirm) {\n p.cancel(\"Installation stopped to protect existing skills.\");\n process.exit(0);\n }\n forceOverwrite = Boolean(confirm);\n }\n\n const s = p.spinner();\n s.start(\"Installing and adapting skills across your configured AI agents...\");\n\n const result = await installSkills({\n targetPath: context.targetPath,\n selectedSkillIds: selectedSkills as string[],\n selectedAgents: selectedAgents as AgentId[],\n forceOverwrite,\n });\n\n s.stop(pc.green(\"All skills have been successfully installed and configured!\"));\n\n p.note(\n `Configured skills: ${result.installedSkills.map((s) => pc.bold(s)).join(\", \")}\\n` +\n `Target agents: ${result.configuredAgents.map((a) => pc.cyan(a)).join(\", \")}`,\n \"Installation Summary\"\n );\n\n p.outro(pc.green(\"Your AI programming pair is now empowered with your team's architectural habits! 🚀\"));\n}\n","import pc from \"picocolors\";\n\nexport function renderBanner(): string {\n const title = pc.bold(pc.white(\" GROOVE SKILLS \"));\n const badge = pc.bgMagenta(pc.black(title));\n const subtitle = pc.dim(\"Multi-Agent AI Skills & Architectural Standards\");\n\n return `${badge} ${subtitle}\\n`;\n}\n","import * as p from \"@clack/prompts\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\n\nexport async function promptSkills(suggestedSkillIds: string[]): Promise<string[] | symbol> {\n const options = SKILL_CATALOG.map((skill) => {\n const isSuggested = suggestedSkillIds.includes(skill.id);\n return {\n value: skill.id,\n label: skill.name,\n hint: isSuggested ? \"Suggested based on your project stack\" : skill.description,\n };\n });\n\n const selected = await p.multiselect({\n message: \"Which skills would you like to install in this project?\",\n options,\n initialValues: suggestedSkillIds,\n required: true,\n });\n\n return selected;\n}\n","import * as p from \"@clack/prompts\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nexport async function promptAgents(detectedAgents: AgentId[]): Promise<AgentId[] | symbol> {\n const options: Array<{ value: AgentId; label: string; hint: string }> = [\n { value: \"antigravity\", label: \"Antigravity\", hint: \".agents/skills (Google Agentic IDE)\" },\n { value: \"claude\", label: \"Claude Code\", hint: \".claude/skills (Anthropic Claude Code)\" },\n { value: \"cursor\", label: \"Cursor\", hint: \".cursor/rules/*.mdc (Cursor IDE Rules)\" },\n { value: \"gemini\", label: \"Gemini Code Assist\", hint: \".gemini/skills (Google Gemini)\" },\n ];\n\n const initialValues: AgentId[] = detectedAgents.length > 0 ? detectedAgents : [\"antigravity\"];\n\n const selected = await p.multiselect({\n message: \"Which AI agents should be configured?\",\n options,\n initialValues,\n required: true,\n });\n\n return selected as AgentId[] | symbol;\n}\n","import * as p from \"@clack/prompts\";\n\nexport async function promptConfirmOverwrite(): Promise<boolean | symbol> {\n return await p.confirm({\n message: \"Some skills already exist in this project. Would you like to overwrite them with the latest version?\",\n initialValue: true,\n });\n}\n","import { lstatSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nconst AGENT_DESTINATION_MAP: Record<AgentId, (dir: string) => string> = {\n antigravity: (dir) => join(\".agents\", \"skills\", dir),\n claude: (dir) => join(\".claude\", \"skills\", dir),\n cursor: (dir) => join(\".cursor\", \"rules\", `${dir}.mdc`),\n gemini: (dir) => join(\".gemini\", \"skills\", dir),\n};\n\nexport function verifySkillExists(\n targetPath: string,\n skillDirectoryName: string,\n agents: AgentId[]\n): boolean {\n for (const agent of agents) {\n const relativePathResolver = AGENT_DESTINATION_MAP[agent];\n const destination = join(targetPath, relativePathResolver(skillDirectoryName));\n\n try {\n lstatSync(destination);\n return true;\n } catch {\n // Path does not exist\n }\n }\n\n return false;\n}\n","import { join } from \"node:path\";\nimport { AntigravityAdapter } from \"@/domains/adapters/antigravity.adapter\";\nimport { ClaudeAdapter } from \"@/domains/adapters/claude.adapter\";\nimport { CursorAdapter } from \"@/domains/adapters/cursor.adapter\";\nimport { GeminiAdapter } from \"@/domains/adapters/gemini.adapter\";\nimport { syncGlobalStore } from \"@/domains/global-store/sync-global-store\";\nimport { getGlobalSkillsDir } from \"@/domains/global-store/global-paths\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { InstallOptions, InstallResult } from \"@/types/installer.types\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nconst ADAPTER_REGISTRY: Record<AgentId, AgentAdapter> = {\n antigravity: new AntigravityAdapter(),\n claude: new ClaudeAdapter(),\n cursor: new CursorAdapter(),\n gemini: new GeminiAdapter(),\n};\n\nexport async function installSkills(options: InstallOptions): Promise<InstallResult> {\n syncGlobalStore();\n\n const globalSkillsDir = getGlobalSkillsDir();\n const createdFiles: string[] = [];\n\n for (const skillId of options.selectedSkillIds) {\n const skill = SKILL_CATALOG.find((item) => item.id === skillId);\n if (!skill) {\n continue;\n }\n\n const skillSourceDir = join(globalSkillsDir, skill.directoryName);\n\n for (const agentId of options.selectedAgents) {\n const adapter = ADAPTER_REGISTRY[agentId];\n if (!adapter) {\n continue;\n }\n\n const files = await adapter.installSkill(options.targetPath, skill, skillSourceDir);\n createdFiles.push(...files);\n }\n }\n\n return {\n installedSkills: options.selectedSkillIds,\n configuredAgents: options.selectedAgents,\n createdFiles,\n };\n}\n","import { mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getGlobalSkillsDir } from \"@/domains/global-store/global-paths\";\nimport { createOrReplaceSymlink } from \"@/domains/adapters/link-helper\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { SkillMetadata } from \"@/types/skill.types\";\n\nexport class AntigravityAdapter implements AgentAdapter {\n readonly id = \"antigravity\" as const;\n readonly name = \"Antigravity\";\n readonly targetRelativeDir = \".agents/skills\";\n\n async installSkill(\n targetPath: string,\n skill: SkillMetadata,\n _skillSourceDir: string\n ): Promise<string[]> {\n const parentDir = join(targetPath, this.targetRelativeDir);\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true });\n }\n\n const destination = join(parentDir, skill.directoryName);\n const globalSkillDir = join(getGlobalSkillsDir(), skill.directoryName);\n\n createOrReplaceSymlink(globalSkillDir, destination, \"dir\");\n\n return [destination];\n }\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function getGlobalStoreDir(): string {\n return join(homedir(), \".grooveskills\");\n}\n\nexport function getGlobalSkillsDir(): string {\n return join(getGlobalStoreDir(), \"skills\");\n}\n\nexport function getGlobalCursorRulesDir(): string {\n return join(getGlobalStoreDir(), \"cursor-rules\");\n}\n","import { lstatSync, unlinkSync, rmSync, symlinkSync } from \"node:fs\";\n\nfunction removeExistingLinkPath(path: string): void {\n try {\n const lstat = lstatSync(path);\n\n if (lstat.isDirectory() && !lstat.isSymbolicLink()) {\n rmSync(path, { recursive: true, force: true });\n return;\n }\n\n unlinkSync(path);\n } catch {\n // If path does not exist, exit early\n }\n}\n\nexport function createOrReplaceSymlink(\n target: string,\n linkPath: string,\n type: \"dir\" | \"file\"\n): void {\n removeExistingLinkPath(linkPath);\n symlinkSync(target, linkPath, type);\n}\n\n","import { mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getGlobalSkillsDir } from \"@/domains/global-store/global-paths\";\nimport { createOrReplaceSymlink } from \"@/domains/adapters/link-helper\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { SkillMetadata } from \"@/types/skill.types\";\n\nexport class ClaudeAdapter implements AgentAdapter {\n readonly id = \"claude\" as const;\n readonly name = \"Claude Code\";\n readonly targetRelativeDir = \".claude/skills\";\n\n async installSkill(\n targetPath: string,\n skill: SkillMetadata,\n _skillSourceDir: string\n ): Promise<string[]> {\n const parentDir = join(targetPath, this.targetRelativeDir);\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true });\n }\n\n const destination = join(parentDir, skill.directoryName);\n const globalSkillDir = join(getGlobalSkillsDir(), skill.directoryName);\n\n createOrReplaceSymlink(globalSkillDir, destination, \"dir\");\n\n return [destination];\n }\n}\n","import { mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getGlobalCursorRulesDir } from \"@/domains/global-store/global-paths\";\nimport { createOrReplaceSymlink } from \"@/domains/adapters/link-helper\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { SkillMetadata } from \"@/types/skill.types\";\n\nexport class CursorAdapter implements AgentAdapter {\n readonly id = \"cursor\" as const;\n readonly name = \"Cursor\";\n readonly targetRelativeDir = \".cursor/rules\";\n\n async installSkill(\n targetPath: string,\n skill: SkillMetadata,\n _skillSourceDir: string\n ): Promise<string[]> {\n const rulesDir = join(targetPath, this.targetRelativeDir);\n if (!existsSync(rulesDir)) {\n mkdirSync(rulesDir, { recursive: true });\n }\n\n const filename = `${skill.id}.mdc`;\n const destination = join(rulesDir, filename);\n const globalRuleFile = join(getGlobalCursorRulesDir(), filename);\n\n createOrReplaceSymlink(globalRuleFile, destination, \"file\");\n\n return [destination];\n }\n}\n","import { mkdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getGlobalSkillsDir } from \"@/domains/global-store/global-paths\";\nimport { createOrReplaceSymlink } from \"@/domains/adapters/link-helper\";\nimport type { AgentAdapter } from \"@/domains/adapters/adapter.interface\";\nimport type { SkillMetadata } from \"@/types/skill.types\";\n\nexport class GeminiAdapter implements AgentAdapter {\n readonly id = \"gemini\" as const;\n readonly name = \"Gemini Code Assist\";\n readonly targetRelativeDir = \".gemini/skills\";\n\n async installSkill(\n targetPath: string,\n skill: SkillMetadata,\n _skillSourceDir: string\n ): Promise<string[]> {\n const parentDir = join(targetPath, this.targetRelativeDir);\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true });\n }\n\n const destination = join(parentDir, skill.directoryName);\n const globalSkillDir = join(getGlobalSkillsDir(), skill.directoryName);\n\n createOrReplaceSymlink(globalSkillDir, destination, \"dir\");\n\n return [destination];\n }\n}\n","import { cpSync, mkdirSync, existsSync, writeFileSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getGlobalSkillsDir, getGlobalCursorRulesDir } from \"@/domains/global-store/global-paths\";\nimport { resolveSkillsSourceRoot } from \"@/domains/installer/resolve-skills-source\";\nimport { SKILL_CATALOG } from \"@/domains/registry/skill-catalog\";\n\nexport function syncGlobalStore(): void {\n const bundledSkillsDir = resolveSkillsSourceRoot();\n const globalSkillsDir = getGlobalSkillsDir();\n const globalCursorRulesDir = getGlobalCursorRulesDir();\n\n if (!existsSync(globalSkillsDir)) {\n mkdirSync(globalSkillsDir, { recursive: true });\n }\n if (!existsSync(globalCursorRulesDir)) {\n mkdirSync(globalCursorRulesDir, { recursive: true });\n }\n\n cpSync(bundledSkillsDir, globalSkillsDir, { recursive: true });\n\n for (const skill of SKILL_CATALOG) {\n const skillMdPath = join(globalSkillsDir, skill.directoryName, \"SKILL.md\");\n if (!existsSync(skillMdPath)) {\n continue;\n }\n\n const content = readFileSync(skillMdPath, \"utf-8\");\n const mdcHeader = `---\ndescription: ${skill.description}\nglobs: [\"**/*\"]\nalwaysApply: false\n---\n\n`;\n const cleanedBody = content.replace(/^---[\\s\\S]*?---\\s*/, \"\");\n const targetMdc = join(globalCursorRulesDir, `${skill.id}.mdc`);\n writeFileSync(targetMdc, `${mdcHeader}${cleanedBody}`, \"utf-8\");\n }\n}\n","import { fileURLToPath } from \"node:url\";\nimport { dirname, resolve, join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\nexport function resolveSkillsSourceRoot(): string {\n const currentFilePath = fileURLToPath(import.meta.url);\n const currentDir = dirname(currentFilePath);\n\n // 1. From dist/ in published npm package or local build: dist/../skills\n const candidateFromDist = resolve(currentDir, \"../skills\");\n if (existsSync(candidateFromDist)) {\n return candidateFromDist;\n }\n\n // 2. From src/domains/installer/ during development: ../../../skills\n const candidateFromSrc = resolve(currentDir, \"../../../skills\");\n if (existsSync(candidateFromSrc)) {\n return candidateFromSrc;\n }\n\n // 3. Search upward for the package root containing a skills directory\n let searchDir = currentDir;\n for (let i = 0; i < 5; i++) {\n const candidate = join(searchDir, \"skills\");\n if (existsSync(candidate)) {\n return candidate;\n }\n const parent = dirname(searchDir);\n if (parent === searchDir) {\n break;\n }\n searchDir = parent;\n }\n\n throw new Error(\n `GrooveSkills internal error: Unable to locate bundled skills directory from ${currentDir}`\n );\n}\n","import { detectProjectContext } from \"@/domains/detector/project-detector\";\nimport { runInteractiveUi } from \"@/domains/ui/ui-orchestrator\";\nimport { installSkills } from \"@/domains/installer/install-orchestrator\";\nimport type { AgentId } from \"@/types/agent.types\";\n\nexport interface CliOptions {\n skills?: string[];\n agents?: AgentId[];\n yes?: boolean;\n}\n\nexport async function runGrooveSkills(options: CliOptions = {}): Promise<void> {\n const targetPath = process.cwd();\n const context = detectProjectContext(targetPath);\n\n if (options.skills && options.skills.length > 0 && options.agents && options.agents.length > 0) {\n await installSkills({\n targetPath,\n selectedSkillIds: options.skills,\n selectedAgents: options.agents,\n forceOverwrite: Boolean(options.yes),\n });\n return;\n }\n\n await runInteractiveUi(context);\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;AAGrB,SAAS,aAAa,YAA+B;AAC1D,QAAM,WAAsB,CAAC;AAE7B,MAAI;AACF,UAAM,UAAU,IAAI,IAAI,YAAY,UAAU,CAAC;AAE/C,QAAI,QAAQ,IAAI,SAAS,GAAG;AAC1B,eAAS,KAAK,aAAa;AAAA,IAC7B;AACA,QAAI,QAAQ,IAAI,SAAS,GAAG;AAC1B,eAAS,KAAK,QAAQ;AAAA,IACxB;AACA,QAAI,QAAQ,IAAI,SAAS,GAAG;AAC1B,eAAS,KAAK,QAAQ;AAAA,IACxB;AACA,QAAI,QAAQ,IAAI,SAAS,GAAG;AAC1B,eAAS,KAAK,QAAQ;AAAA,IACxB;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;;;AC1BA,SAAS,cAAc,kBAAkB;AACzC,SAAS,YAAY;AAOd,SAAS,mBAAmB,YAA0C;AAC3E,QAAM,kBAAkB,KAAK,YAAY,cAAc;AAEvD,MAAI,CAAC,WAAW,eAAe,GAAG;AAChC,WAAO,EAAE,cAAc,CAAC,GAAG,QAAQ,MAAM;AAAA,EAC3C;AAEA,MAAI;AACF,UAAM,MAAM,aAAa,iBAAiB,OAAO;AACjD,UAAM,SAAS,KAAK,MAAM,GAAG;AAK7B,UAAM,SAAS,OAAO,KAAK,OAAO,gBAAgB,CAAC,CAAC;AACpD,UAAM,MAAM,OAAO,KAAK,OAAO,mBAAmB,CAAC,CAAC;AACpD,UAAM,MAAM,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,QAAQ,GAAG,GAAG,CAAC,CAAC;AAEnD,WAAO,EAAE,cAAc,KAAK,QAAQ,KAAK;AAAA,EAC3C,QAAQ;AACN,WAAO,EAAE,cAAc,CAAC,GAAG,QAAQ,KAAK;AAAA,EAC1C;AACF;;;AC5BO,IAAM,gBAAiC;AAAA,EAC5C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,cAAc,CAAC;AAAA,IACf,eAAe;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,cAAc,CAAC,QAAQ,2BAA2B,0BAA0B;AAAA,IAC5E,eAAe;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,cAAc,CAAC,SAAS,WAAW;AAAA,IACnC,eAAe;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,cAAc,CAAC,SAAS,WAAW;AAAA,IACnC,eAAe;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,cAAc,CAAC,SAAS,WAAW;AAAA,IACnC,eAAe;AAAA,EACjB;AACF;;;ACjCO,SAAS,qBAAqB,YAAoC;AACvE,QAAM,iBAAiB,aAAa,UAAU;AAC9C,QAAM,EAAE,cAAc,OAAO,IAAI,mBAAmB,UAAU;AAE9D,QAAM,oBAA8B,CAAC,gBAAgB;AAErD,aAAW,SAAS,eAAe;AACjC,QAAI,MAAM,aAAa,WAAW,GAAG;AACnC;AAAA,IACF;AACA,UAAM,WAAW,MAAM,aAAa,KAAK,CAAC,QAAQ,aAAa,SAAS,GAAG,CAAC;AAC5E,QAAI,YAAY,CAAC,kBAAkB,SAAS,MAAM,EAAE,GAAG;AACrD,wBAAkB,KAAK,MAAM,EAAE;AAAA,IACjC;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,sBAAsB;AAAA,IACtB;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,EACrB;AACF;;;AC5BA,YAAYA,QAAO;AACnB,OAAOC,SAAQ;;;ACDf,OAAO,QAAQ;AAER,SAAS,eAAuB;AACrC,QAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,iBAAiB,CAAC;AACjD,QAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,KAAK,CAAC;AAC1C,QAAM,WAAW,GAAG,IAAI,iDAAiD;AAEzE,SAAO,GAAG,KAAK,KAAK,QAAQ;AAAA;AAC9B;;;ACRA,YAAY,OAAO;AAGnB,eAAsB,aAAa,mBAAyD;AAC1F,QAAM,UAAU,cAAc,IAAI,CAAC,UAAU;AAC3C,UAAM,cAAc,kBAAkB,SAAS,MAAM,EAAE;AACvD,WAAO;AAAA,MACL,OAAO,MAAM;AAAA,MACb,OAAO,MAAM;AAAA,MACb,MAAM,cAAc,0CAA0C,MAAM;AAAA,IACtE;AAAA,EACF,CAAC;AAED,QAAM,WAAW,MAAQ,cAAY;AAAA,IACnC,SAAS;AAAA,IACT;AAAA,IACA,eAAe;AAAA,IACf,UAAU;AAAA,EACZ,CAAC;AAED,SAAO;AACT;;;ACrBA,YAAYC,QAAO;AAGnB,eAAsB,aAAa,gBAAwD;AACzF,QAAM,UAAkE;AAAA,IACtE,EAAE,OAAO,eAAe,OAAO,eAAe,MAAM,sCAAsC;AAAA,IAC1F,EAAE,OAAO,UAAU,OAAO,eAAe,MAAM,yCAAyC;AAAA,IACxF,EAAE,OAAO,UAAU,OAAO,UAAU,MAAM,yCAAyC;AAAA,IACnF,EAAE,OAAO,UAAU,OAAO,sBAAsB,MAAM,iCAAiC;AAAA,EACzF;AAEA,QAAM,gBAA2B,eAAe,SAAS,IAAI,iBAAiB,CAAC,aAAa;AAE5F,QAAM,WAAW,MAAQ,eAAY;AAAA,IACnC,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU;AAAA,EACZ,CAAC;AAED,SAAO;AACT;;;ACrBA,YAAYC,QAAO;AAEnB,eAAsB,yBAAoD;AACxE,SAAO,MAAQ,WAAQ;AAAA,IACrB,SAAS;AAAA,IACT,cAAc;AAAA,EAChB,CAAC;AACH;;;ACPA,SAAS,iBAAiB;AAC1B,SAAS,QAAAC,aAAY;AAGrB,IAAM,wBAAkE;AAAA,EACtE,aAAa,CAAC,QAAQA,MAAK,WAAW,UAAU,GAAG;AAAA,EACnD,QAAQ,CAAC,QAAQA,MAAK,WAAW,UAAU,GAAG;AAAA,EAC9C,QAAQ,CAAC,QAAQA,MAAK,WAAW,SAAS,GAAG,GAAG,MAAM;AAAA,EACtD,QAAQ,CAAC,QAAQA,MAAK,WAAW,UAAU,GAAG;AAChD;AAEO,SAAS,kBACd,YACA,oBACA,QACS;AACT,aAAW,SAAS,QAAQ;AAC1B,UAAM,uBAAuB,sBAAsB,KAAK;AACxD,UAAM,cAAcA,MAAK,YAAY,qBAAqB,kBAAkB,CAAC;AAE7E,QAAI;AACF,gBAAU,WAAW;AACrB,aAAO;AAAA,IACT,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;;;AC7BA,SAAS,QAAAC,cAAY;;;ACArB,SAAS,WAAW,cAAAC,mBAAkB;AACtC,SAAS,QAAAC,aAAY;;;ACDrB,SAAS,eAAe;AACxB,SAAS,QAAAC,aAAY;AAEd,SAAS,oBAA4B;AAC1C,SAAOA,MAAK,QAAQ,GAAG,eAAe;AACxC;AAEO,SAAS,qBAA6B;AAC3C,SAAOA,MAAK,kBAAkB,GAAG,QAAQ;AAC3C;AAEO,SAAS,0BAAkC;AAChD,SAAOA,MAAK,kBAAkB,GAAG,cAAc;AACjD;;;ACbA,SAAS,aAAAC,YAAW,YAAY,QAAQ,mBAAmB;AAE3D,SAAS,uBAAuB,MAAoB;AAClD,MAAI;AACF,UAAM,QAAQA,WAAU,IAAI;AAE5B,QAAI,MAAM,YAAY,KAAK,CAAC,MAAM,eAAe,GAAG;AAClD,aAAO,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC7C;AAAA,IACF;AAEA,eAAW,IAAI;AAAA,EACjB,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,uBACd,QACA,UACA,MACM;AACN,yBAAuB,QAAQ;AAC/B,cAAY,QAAQ,UAAU,IAAI;AACpC;;;AFjBO,IAAM,qBAAN,MAAiD;AAAA,EAC7C,KAAK;AAAA,EACL,OAAO;AAAA,EACP,oBAAoB;AAAA,EAE7B,MAAM,aACJ,YACA,OACA,iBACmB;AACnB,UAAM,YAAYC,MAAK,YAAY,KAAK,iBAAiB;AACzD,QAAI,CAACC,YAAW,SAAS,GAAG;AAC1B,gBAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,IAC1C;AAEA,UAAM,cAAcD,MAAK,WAAW,MAAM,aAAa;AACvD,UAAM,iBAAiBA,MAAK,mBAAmB,GAAG,MAAM,aAAa;AAErE,2BAAuB,gBAAgB,aAAa,KAAK;AAEzD,WAAO,CAAC,WAAW;AAAA,EACrB;AACF;;;AG7BA,SAAS,aAAAE,YAAW,cAAAC,mBAAkB;AACtC,SAAS,QAAAC,aAAY;AAMd,IAAM,gBAAN,MAA4C;AAAA,EACxC,KAAK;AAAA,EACL,OAAO;AAAA,EACP,oBAAoB;AAAA,EAE7B,MAAM,aACJ,YACA,OACA,iBACmB;AACnB,UAAM,YAAYC,MAAK,YAAY,KAAK,iBAAiB;AACzD,QAAI,CAACC,YAAW,SAAS,GAAG;AAC1B,MAAAC,WAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,IAC1C;AAEA,UAAM,cAAcF,MAAK,WAAW,MAAM,aAAa;AACvD,UAAM,iBAAiBA,MAAK,mBAAmB,GAAG,MAAM,aAAa;AAErE,2BAAuB,gBAAgB,aAAa,KAAK;AAEzD,WAAO,CAAC,WAAW;AAAA,EACrB;AACF;;;AC7BA,SAAS,aAAAG,YAAW,cAAAC,mBAAkB;AACtC,SAAS,QAAAC,aAAY;AAMd,IAAM,gBAAN,MAA4C;AAAA,EACxC,KAAK;AAAA,EACL,OAAO;AAAA,EACP,oBAAoB;AAAA,EAE7B,MAAM,aACJ,YACA,OACA,iBACmB;AACnB,UAAM,WAAWC,MAAK,YAAY,KAAK,iBAAiB;AACxD,QAAI,CAACC,YAAW,QAAQ,GAAG;AACzB,MAAAC,WAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,IACzC;AAEA,UAAM,WAAW,GAAG,MAAM,EAAE;AAC5B,UAAM,cAAcF,MAAK,UAAU,QAAQ;AAC3C,UAAM,iBAAiBA,MAAK,wBAAwB,GAAG,QAAQ;AAE/D,2BAAuB,gBAAgB,aAAa,MAAM;AAE1D,WAAO,CAAC,WAAW;AAAA,EACrB;AACF;;;AC9BA,SAAS,aAAAG,YAAW,cAAAC,mBAAkB;AACtC,SAAS,QAAAC,aAAY;AAMd,IAAM,gBAAN,MAA4C;AAAA,EACxC,KAAK;AAAA,EACL,OAAO;AAAA,EACP,oBAAoB;AAAA,EAE7B,MAAM,aACJ,YACA,OACA,iBACmB;AACnB,UAAM,YAAYC,MAAK,YAAY,KAAK,iBAAiB;AACzD,QAAI,CAACC,YAAW,SAAS,GAAG;AAC1B,MAAAC,WAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,IAC1C;AAEA,UAAM,cAAcF,MAAK,WAAW,MAAM,aAAa;AACvD,UAAM,iBAAiBA,MAAK,mBAAmB,GAAG,MAAM,aAAa;AAErE,2BAAuB,gBAAgB,aAAa,KAAK;AAEzD,WAAO,CAAC,WAAW;AAAA,EACrB;AACF;;;AC7BA,SAAS,QAAQ,aAAAG,YAAW,cAAAC,aAAY,eAAe,gBAAAC,qBAAoB;AAC3E,SAAS,QAAAC,aAAY;;;ACDrB,SAAS,qBAAqB;AAC9B,SAAS,SAAS,SAAS,QAAAC,aAAY;AACvC,SAAS,cAAAC,mBAAkB;AAEpB,SAAS,0BAAkC;AAChD,QAAM,kBAAkB,cAAc,YAAY,GAAG;AACrD,QAAM,aAAa,QAAQ,eAAe;AAG1C,QAAM,oBAAoB,QAAQ,YAAY,WAAW;AACzD,MAAIA,YAAW,iBAAiB,GAAG;AACjC,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB,QAAQ,YAAY,iBAAiB;AAC9D,MAAIA,YAAW,gBAAgB,GAAG;AAChC,WAAO;AAAA,EACT;AAGA,MAAI,YAAY;AAChB,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,YAAYD,MAAK,WAAW,QAAQ;AAC1C,QAAIC,YAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,SAAS,QAAQ,SAAS;AAChC,QAAI,WAAW,WAAW;AACxB;AAAA,IACF;AACA,gBAAY;AAAA,EACd;AAEA,QAAM,IAAI;AAAA,IACR,+EAA+E,UAAU;AAAA,EAC3F;AACF;;;AD/BO,SAAS,kBAAwB;AACtC,QAAM,mBAAmB,wBAAwB;AACjD,QAAM,kBAAkB,mBAAmB;AAC3C,QAAM,uBAAuB,wBAAwB;AAErD,MAAI,CAACC,YAAW,eAAe,GAAG;AAChC,IAAAC,WAAU,iBAAiB,EAAE,WAAW,KAAK,CAAC;AAAA,EAChD;AACA,MAAI,CAACD,YAAW,oBAAoB,GAAG;AACrC,IAAAC,WAAU,sBAAsB,EAAE,WAAW,KAAK,CAAC;AAAA,EACrD;AAEA,SAAO,kBAAkB,iBAAiB,EAAE,WAAW,KAAK,CAAC;AAE7D,aAAW,SAAS,eAAe;AACjC,UAAM,cAAcC,MAAK,iBAAiB,MAAM,eAAe,UAAU;AACzE,QAAI,CAACF,YAAW,WAAW,GAAG;AAC5B;AAAA,IACF;AAEA,UAAM,UAAUG,cAAa,aAAa,OAAO;AACjD,UAAM,YAAY;AAAA,eACP,MAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAM5B,UAAM,cAAc,QAAQ,QAAQ,sBAAsB,EAAE;AAC5D,UAAM,YAAYD,MAAK,sBAAsB,GAAG,MAAM,EAAE,MAAM;AAC9D,kBAAc,WAAW,GAAG,SAAS,GAAG,WAAW,IAAI,OAAO;AAAA,EAChE;AACF;;;AP1BA,IAAM,mBAAkD;AAAA,EACtD,aAAa,IAAI,mBAAmB;AAAA,EACpC,QAAQ,IAAI,cAAc;AAAA,EAC1B,QAAQ,IAAI,cAAc;AAAA,EAC1B,QAAQ,IAAI,cAAc;AAC5B;AAEA,eAAsB,cAAc,SAAiD;AACnF,kBAAgB;AAEhB,QAAM,kBAAkB,mBAAmB;AAC3C,QAAM,eAAyB,CAAC;AAEhC,aAAW,WAAW,QAAQ,kBAAkB;AAC9C,UAAM,QAAQ,cAAc,KAAK,CAAC,SAAS,KAAK,OAAO,OAAO;AAC9D,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AAEA,UAAM,iBAAiBE,OAAK,iBAAiB,MAAM,aAAa;AAEhE,eAAW,WAAW,QAAQ,gBAAgB;AAC5C,YAAM,UAAU,iBAAiB,OAAO;AACxC,UAAI,CAAC,SAAS;AACZ;AAAA,MACF;AAEA,YAAM,QAAQ,MAAM,QAAQ,aAAa,QAAQ,YAAY,OAAO,cAAc;AAClF,mBAAa,KAAK,GAAG,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,iBAAiB,QAAQ;AAAA,IACzB,kBAAkB,QAAQ;AAAA,IAC1B;AAAA,EACF;AACF;;;ANrCA,eAAsB,iBAAiB,SAAwC;AAC7E,UAAQ,IAAI,aAAa,CAAC;AAC1B,EAAE,SAAMC,IAAG,QAAQ,0FAA0F,CAAC;AAE9G,MAAI,QAAQ,qBAAqB,SAAS,GAAG;AAC3C,IAAE;AAAA,MACA,0BAA0B,QAAQ,qBAAqB,IAAI,CAAC,MAAMA,IAAG,KAAK,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,qBAClE,QAAQ,eAAe,IAAI,CAAC,MAAMA,IAAG,MAAM,CAAC,CAAC,EAAE,KAAK,IAAI,KAAK,wBAAwB;AAAA,MAC3G;AAAA,IACF;AAAA,EACF;AAEA,QAAM,iBAAiB,MAAM,aAAa,QAAQ,iBAAiB;AACnE,MAAM,YAAS,cAAc,GAAG;AAC9B,IAAE,UAAO,+CAA+C;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,iBAAiB,MAAM,aAAa,QAAQ,cAAc;AAChE,MAAM,YAAS,cAAc,GAAG;AAC9B,IAAE,UAAO,+CAA+C;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,iBAAiB;AACrB,QAAM,YAAa,eAA4B,KAAK,CAAC,YAAY;AAC/D,UAAM,OAAO,cAAc,KAAK,CAACC,OAAMA,GAAE,OAAO,OAAO;AACvD,WAAO,OAAO,kBAAkB,QAAQ,YAAY,KAAK,eAAe,cAA2B,IAAI;AAAA,EACzG,CAAC;AAED,MAAI,WAAW;AACb,UAAMC,WAAU,MAAM,uBAAuB;AAC7C,QAAM,YAASA,QAAO,KAAK,CAACA,UAAS;AACnC,MAAE,UAAO,kDAAkD;AAC3D,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,qBAAiB,QAAQA,QAAO;AAAA,EAClC;AAEA,QAAM,IAAM,WAAQ;AACpB,IAAE,MAAM,oEAAoE;AAE5E,QAAM,SAAS,MAAM,cAAc;AAAA,IACjC,YAAY,QAAQ;AAAA,IACpB,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,EACF,CAAC;AAED,IAAE,KAAKF,IAAG,MAAM,6DAA6D,CAAC;AAE9E,EAAE;AAAA,IACA,sBAAsB,OAAO,gBAAgB,IAAI,CAACC,OAAMD,IAAG,KAAKC,EAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,iBAC5D,OAAO,iBAAiB,IAAI,CAAC,MAAMD,IAAG,KAAK,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF;AAEA,EAAE,SAAMA,IAAG,MAAM,4FAAqF,CAAC;AACzG;;;Ae3DA,eAAsB,gBAAgB,UAAsB,CAAC,GAAkB;AAC7E,QAAM,aAAa,QAAQ,IAAI;AAC/B,QAAM,UAAU,qBAAqB,UAAU;AAE/C,MAAI,QAAQ,UAAU,QAAQ,OAAO,SAAS,KAAK,QAAQ,UAAU,QAAQ,OAAO,SAAS,GAAG;AAC9F,UAAM,cAAc;AAAA,MAClB;AAAA,MACA,kBAAkB,QAAQ;AAAA,MAC1B,gBAAgB,QAAQ;AAAA,MACxB,gBAAgB,QAAQ,QAAQ,GAAG;AAAA,IACrC,CAAC;AACD;AAAA,EACF;AAEA,QAAM,iBAAiB,OAAO;AAChC;","names":["p","pc","p","p","join","join","existsSync","join","join","lstatSync","join","existsSync","mkdirSync","existsSync","join","join","existsSync","mkdirSync","mkdirSync","existsSync","join","join","existsSync","mkdirSync","mkdirSync","existsSync","join","join","existsSync","mkdirSync","mkdirSync","existsSync","readFileSync","join","join","existsSync","existsSync","mkdirSync","join","readFileSync","join","pc","s","confirm"]}
|
package/dist/cli.js
CHANGED
package/dist/main.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-advanced
|
|
3
|
+
description: Use when building with React 19 platform features and rendering architecture: React Compiler (auto-memoization), concurrent rendering (useTransition, useDeferredValue, Suspense), the use() hook, Actions (useActionState, useOptimistic, useFormStatus), ref-as-prop, <Context value>, compound components, context splitting, error boundaries, portals, virtualization (@tanstack/react-virtual), code-splitting, and RSC / 'use client' boundaries. TRIGGER when optimizing React performance, adopting React 19 APIs, working with forms/mutations, or architecting complex React component trees. Always adhere to 'best-practices' and 'react-core'.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React Advanced: React 19 Platform & Rendering Architecture
|
|
7
|
+
|
|
8
|
+
Opinionated reference for **React 19** and **React Compiler 1.0** capabilities, concurrent rendering primitives, async Actions, advanced component composition, performance engineering, and React Server Component (RSC) boundaries.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Golden Rules (Non-negotiable)
|
|
13
|
+
|
|
14
|
+
1. **React Compiler First — Stop Reflexive Manual Memoization**:
|
|
15
|
+
- Do not wrap every derived value or handler in `useMemo`, `useCallback`, or `React.memo` by reflex in new code.
|
|
16
|
+
- Reserve manual `useMemo`/`useCallback` strictly for **effect dependency stability** (`useEffect` identity contracts) or code skipped by the compiler.
|
|
17
|
+
- Never strip existing manual memoization blindly; respect `preserve-manual-memoization` lint warnings.
|
|
18
|
+
2. **React 19 Idioms Only (No Legacy Wrappers)**:
|
|
19
|
+
- Receive `ref` as a plain prop on function components (`ComponentProps<"input">`)—never use `forwardRef` in new code.
|
|
20
|
+
- Render `<MyContext value={val}>` directly—never use legacy `<MyContext.Provider>`.
|
|
21
|
+
- Render native `<title>`, `<meta>`, and `<link>` tags directly in components—never add `react-helmet`.
|
|
22
|
+
3. **Actions for Async Mutations & Forms**:
|
|
23
|
+
- Use `useActionState`, `useOptimistic`, `useFormStatus`, and `<form action={fn}>` for form submissions and async mutations instead of manual `isLoading`/`error` `useState` boilerplate.
|
|
24
|
+
4. **Concurrent Responsiveness & Post-`await` Transitions**:
|
|
25
|
+
- Use `useTransition` for non-urgent state updates and `useDeferredValue` instead of fixed `setTimeout` debouncing for expensive derived renders.
|
|
26
|
+
- Inside `startTransition(async () => { ... })`, always re-wrap state updates that happen **after an `await`** in a nested `startTransition(() => setState(...))`.
|
|
27
|
+
5. **Safe `use()` Hook Usage**:
|
|
28
|
+
- `use(promiseOrContext)` may be called conditionally after early returns, but **never inside `try / catch`**. Handle rejected promises with an **Error Boundary**.
|
|
29
|
+
- Never create a new promise inline during client render (`use(fetchData())`); pass a stable promise created on the server, in a parent, or in an event/transition.
|
|
30
|
+
6. **Context Architecture by Change Frequency**:
|
|
31
|
+
- Native Context has no selector and bypasses `React.memo`. Never combine high-frequency state (e.g. cursor/scroll) and low-frequency state (e.g. theme/auth) in a single Context. Split contexts by update rate and memoize provider values.
|
|
32
|
+
7. **Virtualize Large Lists & Split by Route**:
|
|
33
|
+
- Virtualize lists exceeding ~100 items using `@tanstack/react-virtual`.
|
|
34
|
+
- Code-split with `React.lazy` + `Suspense` at **route or heavy-feature boundaries**, avoiding micro-chunks (<10KB).
|
|
35
|
+
8. **Strict RSC & `'use server'` Security Boundary**:
|
|
36
|
+
- Treat `'use client'` as a **module dependency graph** boundary: props crossing into client modules must be serializable.
|
|
37
|
+
- Treat every `'use server'` function as a public HTTP endpoint: validate inputs with **Valibot** (`* as v from "valibot"`) and verify authorization inside the function before executing mutations.
|
|
38
|
+
9. **GrooveSkills Architecture Compliance**:
|
|
39
|
+
- Enforce `< 200` lines per file, mandatory `@/...` path aliases, zero barrel files (`index.ts`), and early returns across all components and hooks.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. Compiler, Concurrency & React 19 Actions
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { startTransition, useActionState, useDeferredValue, useState } from "react";
|
|
47
|
+
import { updateFilterPreferenceAction } from "@/actions/catalog/update-filter-preference.action";
|
|
48
|
+
import type { Product } from "@/types/product.types";
|
|
49
|
+
|
|
50
|
+
export function ProductExplorer({ catalog }: { catalog: Product[] }) {
|
|
51
|
+
const [query, setQuery] = useState<string>("");
|
|
52
|
+
const deferredQuery = useDeferredValue(query, "");
|
|
53
|
+
|
|
54
|
+
const [state, saveAction, isPending] = useActionState(
|
|
55
|
+
async (_prev: { error?: string }, formData: FormData) => {
|
|
56
|
+
const savedQuery = String(formData.get("query") ?? "");
|
|
57
|
+
return await updateFilterPreferenceAction({ query: savedQuery });
|
|
58
|
+
},
|
|
59
|
+
{}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
function handleAsyncRefresh(fetchNext: () => Promise<string>): void {
|
|
63
|
+
startTransition(async () => {
|
|
64
|
+
const nextQuery = await fetchNext();
|
|
65
|
+
// Always re-wrap state updates after await in startTransition:
|
|
66
|
+
startTransition(() => setQuery(nextQuery));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const matches = catalog.filter((item) =>
|
|
71
|
+
item.name.toLowerCase().includes(deferredQuery.toLowerCase())
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<section>
|
|
76
|
+
<form action={saveAction}>
|
|
77
|
+
<input name="query" value={query} onChange={(e) => setQuery(e.target.value)} />
|
|
78
|
+
<button type="submit" disabled={isPending}>Save Filter</button>
|
|
79
|
+
{state.error && <p role="alert">{state.error}</p>}
|
|
80
|
+
</form>
|
|
81
|
+
<ul style={{ opacity: query !== deferredQuery ? 0.6 : 1 }}>
|
|
82
|
+
{matches.map((p) => <li key={p.id}>{p.name}</li>)}
|
|
83
|
+
</ul>
|
|
84
|
+
</section>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 3. RSC & `'use server'` Boundary with Valibot
|
|
92
|
+
|
|
93
|
+
Server Functions (`'use server'`) are public-facing boundaries. Always validate inputs with **Valibot** and authorize the caller before executing business logic.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
"use server";
|
|
97
|
+
|
|
98
|
+
import * as v from "valibot";
|
|
99
|
+
import { requireAuthenticatedUser } from "@/actions/auth/require-authenticated-user.action";
|
|
100
|
+
|
|
101
|
+
const commentSchema = v.object({
|
|
102
|
+
articleId: v.pipe(v.string(), v.nonEmpty("Article ID is required.")),
|
|
103
|
+
body: v.pipe(
|
|
104
|
+
v.string(),
|
|
105
|
+
v.trim(),
|
|
106
|
+
v.minLength(3, "Please write at least 3 characters before posting.")
|
|
107
|
+
),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export async function publishCommentServerAction(formData: FormData) {
|
|
111
|
+
const parsed = v.safeParse(commentSchema, {
|
|
112
|
+
articleId: formData.get("articleId"),
|
|
113
|
+
body: formData.get("body"),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (!parsed.success) {
|
|
117
|
+
return { success: false, error: parsed.issues[0].message };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const user = await requireAuthenticatedUser();
|
|
121
|
+
return { success: true, userId: user.id, message: "Thank you! Your comment has been published." };
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. Anti-Patterns to Avoid
|
|
128
|
+
|
|
129
|
+
| Anti-Pattern | Why It Fails | Fix |
|
|
130
|
+
| :--- | :--- | :--- |
|
|
131
|
+
| Hand-memoizing everything with React Compiler active | Adds visual noise and redundant cache overhead | Write plain code; use `useMemo` only as an effect-dependency escape hatch |
|
|
132
|
+
| Stripping existing `useMemo` blindly | Can break effect dependency stability contracts | Respect `preserve-manual-memoization` lint warnings |
|
|
133
|
+
| Updating state after `await` in `startTransition` without re-wrapping | Post-`await` update escapes the transition; `isPending` ends prematurely | Wrap post-`await` state updates in `startTransition(() => ...)` |
|
|
134
|
+
| Calling `use(promise)` inside `try / catch` or creating promises in client render | Suspense throws internal signals; inline promises recreate every render | Use Error Boundaries for rejections; create promises above the component |
|
|
135
|
+
| Using `forwardRef`, `<Context.Provider>`, or `react-helmet` | Deprecated/superseded in React 19 | Pass `ref` as prop, render `<Context value>`, use native `<title>`/`<meta>` |
|
|
136
|
+
| Unvalidated `'use server'` arguments | `'use server'` endpoints are public and untrusted | Validate with Valibot (`* as v from "valibot"`) and check auth first |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 5. Reference Navigation
|
|
141
|
+
|
|
142
|
+
- **[react-compiler.md](references/react-compiler.md)**: Compiler adoption, ESLint v6+, escape hatches, and detection checklist.
|
|
143
|
+
- **[concurrent-rendering.md](references/concurrent-rendering.md)**: `useTransition`, post-`await` rule, `useDeferredValue`, `Suspense`, and streaming SSR.
|
|
144
|
+
- **[react19-actions-and-apis.md](references/react19-actions-and-apis.md)**: `use()`, `useActionState`, `useOptimistic`, `useFormStatus`, `ref` prop, `<Context value>`, and native metadata.
|
|
145
|
+
- **[component-patterns.md](references/component-patterns.md)**: Compound components, context splitting, `use-context-selector`, error boundaries, and portals.
|
|
146
|
+
- **[performance.md](references/performance.md)**: `@tanstack/react-virtual`, route-first `React.lazy`, Profiler workflow, and anti-pattern table.
|
|
147
|
+
- **[rsc-boundary.md](references/rsc-boundary.md)**: Module-graph `'use client'` rules, serializable props, `'use server'` Valibot security, and React version pinning.
|