@akanjs/cli 3.0.0-alpha.5 → 3.0.0-alpha.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-e5yq60qz.js → abstractCompactor-20e0cwbs.js} +2 -2
  5. package/{agent.command-b0fcx3r2.js → agent.command-nqs3bw0g.js} +8 -7
  6. package/{application.command-cgmy00gd.js → application.command-k2v46pxf.js} +7 -6
  7. package/{applicationBuildRunner-n1svrwgt.js → applicationBuildRunner-t5v6tcrm.js} +6 -7
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-93nzdfsy.js} +1 -1
  9. package/buildBatch.proc.js +6 -5
  10. package/{capacitorApp-yyw35vg4.js → capacitorApp-927ahhz2.js} +29 -26
  11. package/{cloud.command-1m7hfdpk.js → cloud.command-7jwmxtez.js} +11 -10
  12. package/{context.command-b5heat2a.js → context.command-6kn3tady.js} +16 -15
  13. package/{dependencyScanner-f6grrep3.js → dependencyScanner-rbs2407b.js} +1 -1
  14. package/getDirname-ka11hm7e.js +8 -0
  15. package/{guideline.command-9ew6aqhm.js → guideline.command-pjdrndqk.js} +7 -6
  16. package/guidelines/agentRule/agentRule.generate.json +31 -0
  17. package/guidelines/agentRule/agentRule.instruction.md +457 -0
  18. package/guidelines/conventions/conventions.generate.json +25 -0
  19. package/guidelines/conventions/conventions.instruction.md +773 -0
  20. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  21. package/guidelines/framework/framework.instruction.md +1 -0
  22. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  23. package/guidelines/mcpRule/mcpRule.instruction.md +176 -0
  24. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  25. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +638 -0
  26. package/incrementalBuilder.proc.js +7 -6
  27. package/index-1577bej2.js +7 -0
  28. package/{index-jn9vfpwz.js → index-24h5bqpj.js} +54 -15
  29. package/{index-hrwzz2wf.js → index-4z05haak.js} +1 -1
  30. package/{index-m4kpmmtj.js → index-5em412h5.js} +1 -1
  31. package/{index-6npsfpxr.js → index-5fe016p2.js} +3 -3
  32. package/{index-bjpxzr6s.js → index-5vy2jzar.js} +1 -1
  33. package/{index-exhpp31y.js → index-6bs24rb4.js} +15 -7
  34. package/{index-nx1vbtsc.js → index-7etegaht.js} +14 -14
  35. package/{index-wrrb8rxk.js → index-7w5vv8jk.js} +4 -4
  36. package/{index-meh7mmz9.js → index-7zd96bng.js} +8 -4
  37. package/{index-p69fcwe1.js → index-866yqz3y.js} +13 -13
  38. package/{index-jkynkyfk.js → index-8rr621r2.js} +5 -3
  39. package/{index-47qxw1tz.js → index-90zyjrby.js} +170 -65
  40. package/{index-pfrt7qwh.js → index-97cw8sg1.js} +4 -4
  41. package/{index-p6txr1ss.js → index-bwt525ne.js} +9 -11
  42. package/{index-qp089jp0.js → index-j37qq1f2.js} +1 -1
  43. package/{index-c94nq3c1.js → index-k7sm38v6.js} +3 -3
  44. package/{index-j9cxndc0.js → index-qnv0aqmm.js} +9 -6
  45. package/{index-0cj2zxbm.js → index-sh01r3pm.js} +1 -1
  46. package/{index-0fj4yg7k.js → index-wr999y03.js} +105 -227
  47. package/{index-nfxq39kj.js → index-xe3ychw5.js} +37 -6
  48. package/{index-60a3jfcz.js → index-xrdrttjb.js} +8 -8
  49. package/{index-mn96y1nk.js → index-y59x51f1.js} +6 -6
  50. package/{index-mterpj70.js → index-zgkey4d0.js} +86 -17
  51. package/index.js +20 -19
  52. package/{library.command-aa1g8nsa.js → library.command-7h3yezwc.js} +5 -4
  53. package/{localRegistry.command-kshva1ck.js → localRegistry.command-vnkc41gj.js} +10 -9
  54. package/{module.command-b1zzgqde.js → module.command-4g11k3xd.js} +9 -8
  55. package/{package.command-9qv6vr9s.js → package.command-z7dd6vy2.js} +5 -4
  56. package/package.json +3 -4
  57. package/{page.command-k16pw244.js → page.command-y996708m.js} +5 -4
  58. package/{primitive.command-s60dn1ae.js → primitive.command-4fcn8k83.js} +10 -9
  59. package/{quality.command-p3vaze52.js → quality.command-hx5af2yn.js} +193 -112
  60. package/{recipeScanner-tghkzrrt.js → recipeScanner-zrzev25x.js} +3 -3
  61. package/{repair.command-mfv749ha.js → repair.command-ysxd8pef.js} +7 -6
  62. package/{routeSourceValidator-cncd92fv.js → routeSourceValidator-7fycdqyv.js} +3 -39
  63. package/{scalar.command-hs3nwzv1.js → scalar.command-3chgxybs.js} +8 -7
  64. package/templates/app/env/env.server.debug.ts.template +1 -5
  65. package/templates/app/env/env.server.develop.ts.template +1 -5
  66. package/templates/app/env/env.server.local.ts.template +1 -5
  67. package/templates/app/env/env.server.main.ts.template +1 -5
  68. package/templates/app/page/styles.css.template +3 -3
  69. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  70. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  71. package/templates/crudPages/[__model__Id]/edit/page.tsx +1 -1
  72. package/templates/crudPages/new/page.tsx +1 -1
  73. package/templates/crudSinglePage/page.tsx +1 -1
  74. package/templates/env/_env.server.type.ts +7 -5
  75. package/templates/lib/db.ts +1 -1
  76. package/templates/lib/dict.ts +1 -1
  77. package/templates/lib/sig.ts +1 -1
  78. package/templates/lib/srv.ts +6 -2
  79. package/templates/module/__Model__.Util.tsx +1 -1
  80. package/templates/module/__Model__.Zone.tsx +3 -5
  81. package/templates/workspaceRoot/AGENTS.md.template +0 -967
  82. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  83. package/templates/workspaceRoot/biome.json.template +4 -220
  84. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +15 -16
  85. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  86. package/{typeChecker-kravn7ns.js → typeChecker-g6bh0xfv.js} +1 -1
  87. package/typecheck.proc.js +1 -1
  88. package/{workflow.command-z0e0fk0z.js → workflow.command-endxfzv5.js} +15 -14
  89. package/{workspace.command-3q1d6kk4.js → workspace.command-5f2nmmd5.js} +70 -52
  90. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  91. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
  92. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  93. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  94. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
  95. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  96. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  97. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  98. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  99. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -18
  100. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -61
  101. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  102. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  103. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  104. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -58
  105. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  106. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  107. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  108. package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
  109. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  110. /package/{index-r24hmh0q.js → index-yb2wsc7f.js} +0 -0
@@ -1,55 +1,55 @@
1
1
  // @bun
2
2
  import {
3
3
  ContextScript
4
- } from "./index-p69fcwe1.js";
5
- import"./index-6npsfpxr.js";
6
- import"./index-c94nq3c1.js";
7
- import"./index-pfrt7qwh.js";
8
- import"./index-60a3jfcz.js";
9
- import"./index-hrwzz2wf.js";
4
+ } from "./index-866yqz3y.js";
5
+ import"./index-5fe016p2.js";
6
+ import"./index-k7sm38v6.js";
7
+ import"./index-7w5vv8jk.js";
8
+ import"./index-97cw8sg1.js";
9
+ import"./index-xrdrttjb.js";
10
+ import"./index-ss469dec.js";
11
+ import"./index-4z05haak.js";
10
12
  import {
11
13
  CloudScript
12
- } from "./index-mn96y1nk.js";
14
+ } from "./index-y59x51f1.js";
13
15
  import {
14
16
  getLatestPackageVersion,
15
17
  getNpmRegistryUrl
16
- } from "./index-p6txr1ss.js";
18
+ } from "./index-bwt525ne.js";
17
19
  import {
18
20
  PackageScript
19
- } from "./index-meh7mmz9.js";
20
- import"./index-wrrb8rxk.js";
21
- import"./index-ss469dec.js";
22
- import"./index-mterpj70.js";
21
+ } from "./index-7zd96bng.js";
22
+ import"./index-zgkey4d0.js";
23
23
  import {
24
24
  GlobalConfig
25
- } from "./index-0cj2zxbm.js";
25
+ } from "./index-sh01r3pm.js";
26
26
  import {
27
27
  AgentScript
28
- } from "./index-nfxq39kj.js";
29
- import"./index-jn9vfpwz.js";
30
- import"./index-m4kpmmtj.js";
31
- import"./index-qp089jp0.js";
28
+ } from "./index-xe3ychw5.js";
29
+ import"./index-24h5bqpj.js";
30
+ import"./index-5em412h5.js";
31
+ import"./index-j37qq1f2.js";
32
32
  import {
33
33
  ApplicationScript
34
- } from "./index-nx1vbtsc.js";
35
- import"./index-76rn3g2c.js";
34
+ } from "./index-7etegaht.js";
36
35
  import {
37
36
  LibraryScript
38
- } from "./index-exhpp31y.js";
37
+ } from "./index-6bs24rb4.js";
38
+ import"./index-76rn3g2c.js";
39
39
  import {
40
40
  Exec,
41
41
  Workspace,
42
42
  command,
43
43
  runner,
44
44
  script
45
- } from "./index-jkynkyfk.js";
45
+ } from "./index-8rr621r2.js";
46
46
  import"./index-fgc8r6dj.js";
47
47
  import {
48
48
  ThemeValidator,
49
49
  countBlocking,
50
50
  formatStyleContract
51
51
  } from "./index-xjd3rqzj.js";
52
- import"./index-bjpxzr6s.js";
52
+ import"./index-5vy2jzar.js";
53
53
  import {
54
54
  AppExecutor,
55
55
  LibExecutor,
@@ -59,29 +59,30 @@ import {
59
59
  collectScopeRecipeSources,
60
60
  extractAgentBlock,
61
61
  renderScopeAgentBlock
62
- } from "./index-47qxw1tz.js";
62
+ } from "./index-90zyjrby.js";
63
63
  import"./index-mxvakhsm.js";
64
64
  import {
65
65
  collectRecipeSources,
66
66
  scanRecipes
67
67
  } from "./index-xys926f2.js";
68
+ import"./index-1577bej2.js";
68
69
  import"./index-46tjzh6s.js";
69
70
  import {
70
71
  FileSys
71
72
  } from "./index-67546d0j.js";
72
- import"./index-r24hmh0q.js";
73
-
74
- // pkgs/@akanjs/cli/workspace/workspace.script.ts
75
- import path2 from "path";
76
- import { Logger } from "akanjs/common";
73
+ import"./index-yb2wsc7f.js";
77
74
 
78
75
  // pkgs/@akanjs/cli/workspace/workspace.runner.ts
79
76
  import path from "path";
77
+
78
+ // pkgs/@akanjs/devkit/biomeBase.ts
79
+ var biomeVersion = "2.5.8";
80
+
81
+ // pkgs/@akanjs/cli/workspace/workspace.runner.ts
80
82
  var defaultWorkspacePeerDependencies = new Set([
81
83
  "@react-spring/web",
82
84
  "@use-gesture/react",
83
85
  "chance",
84
- "croner",
85
86
  "react",
86
87
  "react-dom",
87
88
  "react-icons",
@@ -93,6 +94,7 @@ var defaultWorkspacePeerDependencies = new Set([
93
94
  "tailwindcss",
94
95
  "typescript"
95
96
  ]);
97
+ var defaultMaxDiagnostics = 200;
96
98
 
97
99
  class WorkspaceRunner extends runner("workspace") {
98
100
  async generateAgentRules(workspace, { overwrite = false, cursorRules = true } = {}) {
@@ -113,17 +115,14 @@ class WorkspaceRunner extends runner("workspace") {
113
115
  dict,
114
116
  overwrite
115
117
  }));
116
- if (!cursorRules)
117
- return created;
118
- return [
119
- ...created,
120
- ...await workspace.applyTemplate({
118
+ if (cursorRules)
119
+ created.push(...await workspace.applyTemplate({
121
120
  basePath: ".cursor/rules",
122
121
  template: "workspaceRoot/.cursor/rules/akan.mdc.template",
123
122
  dict,
124
123
  overwrite
125
- })
126
- ];
124
+ }));
125
+ return created;
127
126
  }
128
127
  async createWorkspace(repoName, appName, {
129
128
  dirname = ".",
@@ -137,10 +136,7 @@ class WorkspaceRunner extends runner("workspace") {
137
136
  const normalizedRegistryUrl = registryUrl ? getNpmRegistryUrl(registryUrl) : undefined;
138
137
  const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot, repoName });
139
138
  const templateSpinner = workspace.spinning(`Creating workspace template files in ${dirname}/${repoName}...`);
140
- const [latestBiomeVersion, latestTypesBunVersion] = await Promise.all([
141
- getLatestPackageVersion("@biomejs/biome", "latest", normalizedRegistryUrl),
142
- getLatestPackageVersion("@types/bun", "latest", normalizedRegistryUrl)
143
- ]);
139
+ const latestTypesBunVersion = await getLatestPackageVersion("@types/bun", "latest", normalizedRegistryUrl);
144
140
  await workspace.applyTemplate({
145
141
  basePath: ".",
146
142
  template: "workspaceRoot",
@@ -164,7 +160,7 @@ class WorkspaceRunner extends runner("workspace") {
164
160
  },
165
161
  devDependencies: {
166
162
  ...rootPackageJson.devDependencies,
167
- "@biomejs/biome": latestBiomeVersion,
163
+ "@biomejs/biome": biomeVersion,
168
164
  "@types/bun": latestTypesBunVersion,
169
165
  "@akanjs/devkit": akanVersion,
170
166
  ...typescript ? { typescript } : {}
@@ -236,17 +232,28 @@ class WorkspaceRunner extends runner("workspace") {
236
232
  }
237
233
  throw new Error(`[workspace] failed to locate akanjs package.json from ${import.meta.dir}`);
238
234
  }
239
- async lint(exec, workspace, { fix = true } = {}) {
235
+ async lint(exec, workspace, { fix = true, maxDiagnostics = defaultMaxDiagnostics } = {}) {
236
+ const configPath = await WorkspaceRunner.resolveBiomeConfigPath(workspace);
240
237
  await workspace.spawn("./node_modules/.bin/biome", [
241
238
  "check",
242
239
  ...fix ? ["--write"] : [],
243
240
  "--no-errors-on-unmatched",
241
+ `--max-diagnostics=${maxDiagnostics > 0 ? maxDiagnostics : "none"}`,
242
+ ...configPath ? ["--config-path", configPath] : [],
244
243
  exec.cwdPath
245
244
  ]);
246
245
  await this.#enforceStyleContract(exec);
247
246
  await this.#enforceRecipeGate(exec);
248
247
  await this.#enforceAgentsIndex(exec);
249
248
  }
249
+ static async resolveBiomeConfigPath(workspace) {
250
+ for (const fileName of ["biome.json", "biome.jsonc"]) {
251
+ const configPath = path.join(workspace.workspaceRoot, fileName);
252
+ if (await Bun.file(configPath).exists())
253
+ return configPath;
254
+ }
255
+ return null;
256
+ }
250
257
  async#enforceAgentsIndex(exec) {
251
258
  if (!(exec instanceof SysExecutor))
252
259
  return;
@@ -314,6 +321,8 @@ AKAN_PUBLIC_LOG_LEVEL=debug
314
321
  }
315
322
 
316
323
  // pkgs/@akanjs/cli/workspace/workspace.script.ts
324
+ import path2 from "path";
325
+ import { Logger } from "akanjs/common";
317
326
  class WorkspaceScript extends script("workspace", [
318
327
  WorkspaceRunner,
319
328
  ApplicationScript,
@@ -372,30 +381,33 @@ class WorkspaceScript extends script("workspace", [
372
381
  async generateAgentRules(workspace, { overwrite = false, cursorRules = true } = {}) {
373
382
  const spinner = workspace.spinning("Generating agent rules...");
374
383
  const files = await this.workspaceRunner.generateAgentRules(workspace, { overwrite, cursorRules });
384
+ if (files.some((file) => file.filePath.endsWith("AGENTS.md")))
385
+ await this.agentScript.agent(workspace, "install", "agents-md", { force: overwrite });
375
386
  spinner.succeed(`Agent rules ready (${files.length} file${files.length === 1 ? "" : "s"})`);
376
387
  }
377
- async lint(exec, workspace, { fix = true } = {}) {
388
+ async lint(exec, workspace, { fix = true, maxDiagnostics } = {}) {
378
389
  if (exec instanceof AppExecutor)
379
390
  await this.applicationScript.sync(exec);
380
391
  else if (exec instanceof LibExecutor)
381
392
  await this.libraryScript.syncLibrary(exec);
382
393
  const spinner = workspace.spinning(`Linting${fix ? " with fix" : ""}...`);
383
394
  try {
384
- await this.workspaceRunner.lint(exec, workspace, { fix });
395
+ await this.workspaceRunner.lint(exec, workspace, { fix, maxDiagnostics });
385
396
  spinner.succeed("Lint completed with no errors");
386
397
  } catch (error) {
387
398
  spinner.fail("Lint failed with errors");
388
399
  throw error;
389
400
  }
390
401
  }
391
- async lintAll(workspace, { fix = true } = {}) {
402
+ async lintAll(workspace, { fix = true, maxDiagnostics } = {}) {
392
403
  const [appNames, libNames, pkgNames] = await workspace.getExecs();
404
+ const options = { fix, maxDiagnostics };
393
405
  await Promise.all(appNames.map((appName) => this.applicationScript.sync(AppExecutor.from(workspace, appName))));
394
406
  await Promise.all(libNames.map((libName) => this.libraryScript.syncLibrary(LibExecutor.from(workspace, libName))));
395
407
  await Promise.all([
396
- ...appNames.map((appName) => this.workspaceRunner.lint(AppExecutor.from(workspace, appName), workspace, { fix })),
397
- ...libNames.map((libName) => this.workspaceRunner.lint(LibExecutor.from(workspace, libName), workspace, { fix })),
398
- ...pkgNames.filter((pkgName) => pkgName !== "contract").map((pkgName) => this.workspaceRunner.lint(PkgExecutor.from(workspace, pkgName), workspace, { fix }))
408
+ ...appNames.map((appName) => this.workspaceRunner.lint(AppExecutor.from(workspace, appName), workspace, options)),
409
+ ...libNames.map((libName) => this.workspaceRunner.lint(LibExecutor.from(workspace, libName), workspace, options)),
410
+ ...pkgNames.filter((pkgName) => pkgName !== "contract").map((pkgName) => this.workspaceRunner.lint(PkgExecutor.from(workspace, pkgName), workspace, options))
399
411
  ]);
400
412
  }
401
413
  async syncAll(workspace) {
@@ -477,11 +489,17 @@ class WorkspaceCommand extends command("workspace", [WorkspaceScript], ({ public
477
489
  }).option("overwrite", Boolean, { desc: "Overwrite existing agent rule files", default: false }).option("cursorRules", Boolean, { desc: "Generate .cursor/rules/akan.mdc", default: true }).with(Workspace).exec(async function(overwrite, cursorRules, workspace) {
478
490
  await this.workspaceScript.generateAgentRules(workspace, { overwrite, cursorRules });
479
491
  }),
480
- lint: target({ desc: "Lint and fix code in a specific app/lib/pkg" }).with(Exec).option("fix", Boolean, { default: true }).with(Workspace).exec(async function(exec, fix, workspace) {
481
- await this.workspaceScript.lint(exec, workspace, { fix });
492
+ lint: target({ desc: "Lint and fix code in a specific app/lib/pkg" }).with(Exec).option("fix", Boolean, { default: true }).option("maxDiagnostics", Number, {
493
+ desc: "How many diagnostics Biome prints before truncating (0 for no limit)",
494
+ default: defaultMaxDiagnostics
495
+ }).with(Workspace).exec(async function(exec, fix, maxDiagnostics, workspace) {
496
+ await this.workspaceScript.lint(exec, workspace, { fix, maxDiagnostics });
482
497
  }),
483
- lintAll: target({ desc: "Lint and fix code in all apps and libraries" }).option("fix", Boolean, { default: true }).with(Workspace).exec(async function(fix, workspace) {
484
- await this.workspaceScript.lintAll(workspace, { fix });
498
+ lintAll: target({ desc: "Lint and fix code in all apps and libraries" }).option("fix", Boolean, { default: true }).option("maxDiagnostics", Number, {
499
+ desc: "How many diagnostics Biome prints before truncating (0 for no limit)",
500
+ default: defaultMaxDiagnostics
501
+ }).with(Workspace).exec(async function(fix, maxDiagnostics, workspace) {
502
+ await this.workspaceScript.lintAll(workspace, { fix, maxDiagnostics });
485
503
  }),
486
504
  syncAll: target({ desc: "Sync dependencies and configuration for all apps and libraries" }).with(Workspace).exec(async function(workspace) {
487
505
  await this.workspaceScript.syncAll(workspace);
@@ -1,43 +0,0 @@
1
- ---
2
- description: Akan page routing conventions
3
- globs: apps/**/page/**/*.{ts,tsx,js,jsx}
4
- alwaysApply: false
5
- ---
6
-
7
- # Akan Page Routing
8
-
9
- - `apps/<app>/page` may contain route modules only. Do not add helper logic or component-only files there.
10
- - Route source files under `page/` must use `.tsx`. Do not add `logic.ts`, `.js`, or `.jsx` files under `page/`.
11
- - Route pages use `_index.tsx`; layouts use `_layout.tsx`.
12
- - Reserved `_*.tsx` route filenames are limited to `_index.tsx` and `_layout.tsx`; do not add files like `_Component.tsx` or `_helper.tsx`.
13
- - Page filenames must not start with an uppercase letter. Move helper components like `Component.tsx` to app `ui`, `common`, or `lib` instead.
14
- - Dynamic segments use `[id]`; route groups use directories like `(user)`, `(public)`, `(tab)`, or `(detail)`.
15
- - Page modules should usually export `default`, `pageConfig`, `head`, `generateHead`, or `Loading`.
16
- - Prefer `export default function Page` or `export default async function Page` for page components.
17
- - Before changing route behavior, check nearby routes for the expected pattern.
18
-
19
- ## Page Body Shape
20
-
21
- ```tsx
22
- interface PageProps {
23
- params: { taskId: string };
24
- }
25
-
26
- export default async function Page({ params }: PageProps) {
27
- const { l } = usePage();
28
- getSelf({ unauthorize: "/signin" });
29
- const { taskId } = params;
30
- const [{ task }] = await Promise.all([fetch.viewTask(taskId)]);
31
- return <Task.View.General task={task} />;
32
- }
33
-
34
- export const pageConfig = { transition: "stack" } satisfies PageConfig;
35
- ```
36
-
37
- - There is no `loader=` / `render=` page prop. Pages are `export default async function Page`.
38
- - Declare `interface PageProps { params: {...}; searchParams?: {...} }` immediately above the default export.
39
- - Body order: `usePage()`, auth, destructure params, fetch, return.
40
- - Run independent fetches through `Promise.all`, even when there is only one.
41
- - Gate auth at `_layout.tsx`; repeating `getSelf({ unauthorize: "/signin" })` in the page is fine and common.
42
- - Keep `async` even when nothing is awaited — it marks a real server page.
43
- - No `useState`, no `useEffect`, and no comments in page files.
@@ -1,34 +0,0 @@
1
- ---
2
- description: sync-enforced Akan app/lib layout and generated index conventions
3
- globs: apps/**/*,libs/**/*
4
- alwaysApply: false
5
- ---
6
-
7
- # Akan Sync Conventions
8
-
9
- - `apps/<appName>` root may only contain these files: `AGENTS.md`, `CLAUDE.md`, `akan.app.json`, `akan.config.ts`, `capacitor.config.ts`, `client.ts`, `main.ts`, `package.json`, `server.ts`, `tsconfig.json`.
10
- - `apps/<appName>` root may only contain these folders: `.akan`, `android`, `common`, `env`, `ios`, `lib`, `mobile`, `page`, `plugin`, `private`, `public`, `script`, `secrets`, `srvkit`, `ui`, `webkit`.
11
- - `akan sync` maintains a scoped agent guide per app/lib (`apps/<app>/AGENTS.md` / `libs/<lib>/AGENTS.md`); the `akan:agent` block in it is generated, content outside the markers is hand-editable, and `akan lint` fails when the block is stale.
12
- - Do not add `apps/*/base`; place shared app utilities under `apps/*/common`.
13
- - `apps/*/lib` and `libs/*/lib` root files are limited to generated/support files: `cnst.ts`, `db.ts`, `dict.ts`, `option.ts`, `sig.ts`, `srv.ts`, `st.ts`, `useClient.ts`, `useServer.ts`.
14
- - Domain module folders are `lib/<model>` for database modules, `lib/_<service>` for service modules, and `lib/__scalar/<scalar>` for scalar modules.
15
- - Database module UI files are limited to `<Model>.Template.tsx`, `<Model>.Unit.tsx`, `<Model>.Util.tsx`, `<Model>.View.tsx`, and `<Model>.Zone.tsx`.
16
- - Service module UI files are limited to `<Service>.Util.tsx` and `<Service>.Zone.tsx`.
17
- - Scalar module UI files are limited to `<Scalar>.Template.tsx` and `<Scalar>.Unit.tsx`.
18
- - Module `*.test.ts`, `*.test.tsx`, `*.spec.ts`, and `*.spec.tsx` files are allowed.
19
- - `ui/index.ts`, `webkit/index.ts`, `srvkit/index.ts`, `common/index.ts`, and module `lib/**/index.ts` files are generated by `akan sync`; do not hand-edit or track them.
20
- - Generated facet indexes export only 1-depth files/folders with `export * from "./name";`.
21
-
22
- ## Layer Placement
23
-
24
- | Folder | Admission test | Naming |
25
- |---|---|---|
26
- | `common/` | pure, isomorphic, zero-dependency; may import only sibling `common/*` and `akanjs/base`. Cannot import `Err`, so keep throwing code out of it. | camelCase file, filename equals the single export |
27
- | `webkit/` | touches `window` / `navigator` / Capacitor, or is a React hook | `use<Thing>.tsx` — `.tsx` even with no JSX |
28
- | `srvkit/` | touches `node:*`, `Bun`, `process.env`, a secret, or a server SDK | camelCase file, PascalCase class |
29
- | `ui/` | renders JSX and is not bound to one model | PascalCase component, camelCase sidecar (`swipeCard.util.ts`) |
30
-
31
- - Hooks return a named object of async closures, never a tuple.
32
- - Only layer-root `index.ts` barrels are generated. A `ui/<Folder>/index.tsx` that builds a namespace is hand-written source; the distinguishing test is that a generated barrel contains nothing but `export * from "./X";` lines.
33
- - `ui/<Folder>/index_.tsx` (trailing underscore) is the `"use client"` + `lazy()` boundary, with a server-safe `index.tsx` beside it. Collapsing the pair into one file breaks RSC.
34
- - Never add a sibling helper file inside `lib/<model>/`; helpers belong in one of the folders above.
@@ -1,16 +0,0 @@
1
- ---
2
- description: Application test commands after source changes
3
- alwaysApply: true
4
- ---
5
-
6
- # Application Test Commands
7
-
8
- - After changing application source code, test the app with `bun run akan start <appName>`.
9
- - Test production build generation with `bun run akan build <appName>`.
10
- - To test a built artifact locally, run it from the generated app directory with the required Akan runtime environment variables.
11
-
12
- ```bash
13
- cd dist/apps/<appName> && USE_AKANJS_PKGS=true AKAN_PUBLIC_REPO_NAME=akansoft AKAN_PUBLIC_SERVE_DOMAIN="akanjs.com" AKAN_PUBLIC_APP_NAME=seon AKAN_PUBLIC_ENV=local AKAN_PUBLIC_OPERATION_MODE=local SERVER_MODE=federation AKAN_PUBLIC_BASE_PATHS=operator,office,homepage bun main.js
14
- ```
15
-
16
- - Adjust `<appName>`, `AKAN_PUBLIC_APP_NAME`, and `AKAN_PUBLIC_BASE_PATHS` to match the app being tested.
@@ -1,12 +0,0 @@
1
- ---
2
- description: Keep edits scoped and avoid unrelated churn
3
- alwaysApply: true
4
- ---
5
-
6
- # Change Scope
7
-
8
- - Keep edits scoped to the requested task and the directly related files.
9
- - Do not reformat unrelated files or rewrite nearby code just for style.
10
- - Do not revert or overwrite unrelated user changes in a dirty working tree.
11
- - Prefer established nearby patterns over introducing a new abstraction.
12
- - Add new abstractions only when they remove real duplication or match an existing project pattern.
@@ -1,18 +0,0 @@
1
- ---
2
- description: Client/server boundary conventions for Akan apps
3
- globs: apps/**/*.{ts,tsx},libs/**/*.{ts,tsx},pkgs/akanjs/**/*.{ts,tsx}
4
- alwaysApply: false
5
- ---
6
-
7
- # Client Server Boundaries
8
-
9
- - Use `"use client";` at the top of client component files.
10
- - Be careful when importing client-only code from page or layout modules.
11
- - Keep page props serializable unless the existing route pattern clearly allows otherwise.
12
- - Let server page/layout files own route-level concerns: `usePage`, headers, static layout, and `akanjs/ui` `Tab` composition.
13
- - Keep interactive loading, submit/import actions, local form inputs, and `router.push` inside client `Util`, `Template`, or `Zone` components.
14
- - Prefer server-rendered `Tab` shells for static mode selection; put one client component inside each `Tab.Panel` instead of managing the selected mode with extra `useState`.
15
- - In domain UI the boundary is mechanical, not a judgment call: `Template`, `Zone`, and `Util` are always client components with `"use client"` on line 1; `Unit` and `View` are always server components and never carry the directive. `usePage()` is still legal in server files.
16
- - Preserve established domain file roles such as `.document.ts`, `.service.ts`, `.store.ts`, `.constant.ts`, and `.client.ts`.
17
- - When unsure, inspect nearby files in the same app or package before introducing a new boundary pattern.
18
- - The boundary is only half the rule: how *little* ends up on the client is the other half. See `ssr-first.mdc`.
@@ -1,25 +0,0 @@
1
- ---
2
- description: Do not narrate code; document only what the code cannot say
3
- alwaysApply: true
4
- ---
5
-
6
- # Comments
7
-
8
- Do not narrate code. Do document the thing the code cannot say. Both halves are the rule.
9
-
10
- - Never add a comment that restates the identifier, the signature, or the control flow.
11
- - Prefer clear names and structure so ordinary logic needs no explanation.
12
- - Do not add JSDoc, section banners, or "why/how" comments for ordinary logic.
13
- - Comment density tracks the layer, not the author: pages carry none, product `lib/` and `ui/` code stays under 1 %, and `srvkit/` adapters and `guards.ts` carry as much as the external constraints require.
14
- - A comment is warranted for: a vendor spec or protocol quirk; an infrastructure constraint; a third-party library gotcha; security reasoning; why a rule that looks arbitrary is correct; a math derivation; a domain field's business meaning; a state transition above a document chain method; why an obvious alternative was rejected.
15
- - In-code markers:
16
- 1. `TODO` — unfinished work that must be tracked in-code
17
- 2. `FIXME` — known broken or incorrect behavior that must be fixed
18
- 3. `XXX` — dangerous / surprising hazard that a reader must not miss
19
- 4. `//!` — disabled or must-fix code
20
- 5. `//?` — an explanatory aside
21
- 6. `//*` — a design note
22
- 7. Deletion caution — warn why removing a line or block would break something non-obvious
23
- - Keep allowed comments one short line when possible.
24
- - Every suppression carries a reason: `// biome-ignore lint/<rule>: <why>`. Never a bare disable block.
25
- - Match nearby file style: if the surrounding code has few comments, keep it that way.
@@ -1,44 +0,0 @@
1
- ---
2
- description: Coding style conventions for classes, functions, files, and types
3
- globs: **/*.{ts,tsx}
4
- alwaysApply: false
5
- ---
6
-
7
- # Coding Style
8
-
9
- - For large units of work, prefer declaring a class and running the flow through an instance instead of scattering many standalone functions.
10
- - Prefer class methods or `static` methods over unrelated top-level helper functions when the logic belongs to a class-level workflow.
11
- - Prefer ECMAScript `#private` fields and methods over TypeScript `private`, except in the four suffixes where `#private` is lint-banned (`*.constant.ts`, `*.document.ts`, `*.service.ts`, `*.store.ts`) — those use TypeScript `private`. `#private` is the house style under `srvkit/`, including `adapt()` classes.
12
- - In files that declare a class, avoid top-level functions or variables when they can reasonably live inside the class.
13
- - Prefer `const` function expressions over `function` declarations unless hoisting, overloads, generators, or framework conventions make `function` the better fit.
14
- - Prefer declaring only one class per file; split the file when two or more class declarations are needed.
15
- - For class-centered modules, prefer noun-style filenames that match the primary class name, such as `RouteClientBuilder.ts`, instead of verb-style wrapper filenames like `buildRouteClient.ts`.
16
- - Avoid keeping exported functions that only instantiate a class and immediately call one method. Prefer migrating callers to instantiate the class directly.
17
- - Except for React component files or convention files, TypeScript filenames should use camelCase.
18
- - In React components, keep one-off `className` strings inline. Only extract class name constants when the class is reused, conditionally composed, or too large to read comfortably in JSX.
19
-
20
- # File Size And Duplication
21
-
22
- - Keep files small. The house median is well under 50 lines; split a component before it reaches ~150 lines rather than adding section comments.
23
- - Ship every scaffold file even when it is empty — `export class XInternal extends internal(srv.x, () => ({})) {}`, empty dictionary stages, the `// state` / `// action` markers in an empty store. They mark where things go.
24
- - Never add a sibling helper file inside `lib/<model>/`. Helpers go to `common/`, `webkit/`, `srvkit/`, or `ui/`.
25
- - Prefer duplication to premature abstraction at the leaf. Near-identical sibling modules and per-vendor pages are deliberately copied, not parameterised. Copy the file and change the literals; share enums by import only.
26
-
27
- # TypeScript Shape
28
-
29
- - Use `interface` for object shapes and `type` only for unions and aliases.
30
- - Declare `interface <ComponentName>Props` immediately above the component with no blank line between, and put `className?: string` first. Name it for the component (`CardProps`, `WorldProps`), never for the model. Do not export it unless a sibling imports it.
31
- - Never use a non-null assertion. Narrow with `?.`, an early return, or a type predicate such as `.filter((id): id is string => !!id)`.
32
- - Escape with `as unknown as T`. Never `as any`.
33
- - Never annotate a component's return type. Annotate a helper only when the return is a union, a tuple, or a type predicate.
34
- - Use `as const` on every `enumOf(...)` array, every `via(Model, [...] as const, …)` Light tuple, and every module-scope lookup map. Never use the TypeScript `enum` keyword.
35
- - Async functions carry no `Async` suffix.
36
-
37
- # Test Code
38
-
39
- - Write TypeScript tests with Bun's test runner and import `describe`, `expect`, and `test` from `bun:test`.
40
- - Keep tests colocated with the source they cover using `*.test.ts` or `*.spec.ts`, following the existing nearby pattern.
41
- - Prefer focused behavior tests for public contracts and edge cases over implementation-detail assertions.
42
- - Split signal tests in two. `<model>.signal.spec.ts` holds reusable fixtures built on `sampleOf(cnst.XInput)` with explicit `Promise<cnst.X>` return types and **no assertions**. `<model>.signal.test.ts` holds the assertions: `describe("<Model> Signal")`, `let` fixtures at describe scope, one `beforeAll`, story-ordered `it`s, and negatives via `await expect(p).rejects.toThrow()`.
43
- - `lib/user/user.signal.spec.ts` is the one place agent types are re-exported and re-typed; import `UserAgent` / `AdminAgent` from there rather than from the shared lib directly.
44
- - A placeholder `it` with a descriptive title is an acceptable floor. Write a real suite when the behaviour is security-relevant.
@@ -1,17 +0,0 @@
1
- ---
2
- description: Akan domain module file roles and naming conventions
3
- globs: apps/**/lib/**/*.{ts,tsx},libs/**/lib/**/*.{ts,tsx}
4
- alwaysApply: false
5
- ---
6
-
7
- # Domain Module Conventions
8
-
9
- - Organize business concepts as domain folders under `lib/`; keep related schema, service, signal, store, and UI files together.
10
- - Use lowercase logic files such as `<model>.constant.ts`, `<model>.document.ts`, `<model>.service.ts`, `<model>.signal.ts`, `<model>.dictionary.ts`, and `<model>.store.ts`.
11
- - Use PascalCase React component files such as `<Model>.Template.tsx`, `<Model>.Unit.tsx`, `<Model>.View.tsx`, `<Model>.Zone.tsx`, and `<Model>.Util.tsx`.
12
- - Treat `constant.ts`, `dictionary.ts`, and `signal.ts` as shared contract files that should avoid platform-specific dependencies.
13
- - Keep backend persistence/query logic in `.document.ts` and domain business orchestration in `.service.ts`.
14
- - Keep frontend state in `.store.ts`; use `Template` for forms, `Unit` for list/card items, `View` for details, `Zone` for composed page sections, and `Util` for domain-specific UI helpers.
15
- - Move reusable workflow state and actions from client UI into `<model>.store.ts` when they span loading state, selected lists, messages, or multi-step actions; consume them as `st.use.someState()` and `st.do.someAction()`.
16
- - Keep `Util` components thin when a store action exists: render controls, call `st.do.*`, read `st.use.*`, and leave orchestration in the store.
17
- - When adding or formalizing a public workflow such as importing an existing project, update the module `*.abstract.md` with the invariant or behavior change.
@@ -1,12 +0,0 @@
1
- ---
2
- description: Formatting and linting conventions
3
- alwaysApply: true
4
- ---
5
-
6
- # Formatting And Linting
7
-
8
- - Use Biome as the formatter and linter.
9
- - Format with `bun run akan lint <appName>` from the repo root.
10
- - Keep formatting consistent with `biome.json`: 2-space indentation, 120 line width, and double quotes for JS/TS.
11
- - Avoid adding new `console` usage except accepted methods such as `console.error`, `console.info`, and `console.warn`.
12
- - Do not make broad formatting-only changes in unrelated files.
@@ -1,18 +0,0 @@
1
- ---
2
- description: Lint rules that break the build, including several whose correct output looks wrong
3
- alwaysApply: true
4
- ---
5
-
6
- # Lint-Enforced Rules (These Break The Build)
7
-
8
- Enforced by `biome.json` and the Akan grit plugins registered in `biome.json`. Several of them produce output that looks wrong; do not "fix" it back.
9
-
10
- - **Never hand-order Tailwind classes.** `nursery/useSortedClasses` is an error and also sorts the string arguments to `clsx()` and `cva()`. Sorter output such as `font-bold text-2xl text-base-content` or `border-base-content/5 border-t` is correct. Write the classes in any order, run the formatter, leave the result.
11
- - **Never `throw new Error`.** Throw `new Err("<module>.error.<key>")` and register the key as `[en, ko]` in that module's dictionary `.error({})`. Import `Err` from `"../dict"` on the server and from `"@libs/<lib>/client"` or `"@apps/<app>/client"` in UI. `no-throw-raw-error.grit` exempts `*.test.ts`, `*.spec.ts`, `*.constant.ts`, and `common/**` — `common/` has no legal `Err` import path, so keep throwing code out of it.
12
- - **Never import a third-party package** from `page/**`, from any barrel, or from any `*.{constant,dictionary,document,service,signal,store}.ts` / `*.{Template,Unit,Util,View,Zone}.tsx` (`no-import-external-library.grit`). Re-export the symbol through a lib first. One-line re-export shims such as `libs/<lib>/base/<pkg>.ts` and `libs/<lib>/webkit/<hook>.ts` exist for exactly this reason — they are load-bearing, not cruft. Do not delete them.
13
- - **`#private` is banned in exactly four file suffixes:** `*.constant.ts`, `*.document.ts`, `*.service.ts`, and `*.store.ts` (`no-js-private-class-method.grit`). The rule is scoped by file path, not by class shape, so `#private` remains the house style everywhere under `srvkit/`, including `adapt()` adapter classes.
14
- - **No `console.log` / `console.debug`.** Only `assert`, `error`, `info`, and `warn` are allowed. Server code uses the injected `this.logger.*` or `new Logger("ClassName")`.
15
- - **Never redeclare a generated CRUD endpoint name** in `*.signal.ts` (`no-redeclare-predefined-endpoint.grit`).
16
- - **No deep imports past a barrel** (`no-deep-internal-import.grit`). Cross-module constant references such as `../map/map.constant` are the sanctioned exception.
17
- - **Server-component discipline** is enforced on `page/**`, `*.Unit.tsx`, and `*.View.tsx` (`no-import-client-functions.grit`, `no-use-client-in-server.grit`, `non-scalar-props-restricted.grit`).
18
- - `noArrayIndexKey` and `useExhaustiveDependencies` are **off** on purpose: `key={idx}` for embedded scalars and short dependency arrays are intentional, not oversights.
@@ -1,61 +0,0 @@
1
- ---
2
- description: Per-file shape for constant, document, service, signal, store, dictionary, and abstract files
3
- globs: apps/**/lib/**/*.{ts,tsx,md},libs/**/lib/**/*.{ts,tsx,md}
4
- alwaysApply: false
5
- ---
6
-
7
- # Module File Playbook
8
-
9
- ## `<model>.constant.ts`
10
-
11
- Five classes in order with one blank line between them and `enumOf("camelName", [...] as const)` classes above: `XInput → XObject → LightX → X → XInsight`. Write `XInsight` even when it is empty.
12
-
13
- - Put display and predicate logic on `LightX` (`isNew()`, `canWrite(user?)`, `formatTimes()`, `isCancellable()`) — the Light class is the one both server and client hold, so shared logic belongs there instead of in a util module. This is the most commonly missed rule in the codebase.
14
- - Collection-level helpers go `static` on the full model.
15
- - Defaults are a literal for scalars and a thunk for anything constructed. Arrays are `field([T])`; optional is the postfix `.optional()`.
16
- - Give any field whose business meaning is not obvious a short trailing comment.
17
-
18
- ## `<model>.document.ts`
19
-
20
- Fixed order: `XFilter extends from(...)` → `X extends by(...)` → `XModel extends into(...)`, with `sort: {}` always present.
21
-
22
- - Chain methods validate → mutate → `return this`, and never `save()`; the caller saves, so chains compose (`org.removeUser(id).removeInvite(id).save()`).
23
- - Put a one-line comment above each chain method stating the transition.
24
- - Atomic counters live on the Model class with the updater-callback form, returning `!!modifiedCount`.
25
- - Indexes and derived totals go in `static override _onSchema`, not in the service.
26
-
27
- ## `<model>.service.ts`
28
-
29
- - Keep methods to a few lines: load → chain → `return await ….save()`. Write `return await` explicitly in tail position; do not "optimize" it away.
30
- - Side effects belong in `override async _preUpdate` / `_postCreate`, not inline.
31
- - Fire-and-forget is explicitly `void`-ed.
32
- - Order deliberately: load every referenced document, then save, then notify.
33
- - Return `null` / `false` for "not allowed" or "not found" and let the signal decide whether that is an error.
34
-
35
- ## `<model>.signal.ts`
36
-
37
- - `XInternal` → `XSlice` → `XEndpoint`, all three declared even when empty. `exec` is a one-liner delegating to the service.
38
- - Every `slice()` takes an explicit `{ guards: {…} }` second argument, and `root:` is always `Admin`.
39
- - Every custom `mutation` / `query` / `message` names its own `guards: [...]` array. `Public` belongs on a slice `get:`, never on a mutation.
40
- - The acting user arrives via `.with(Self)` / `.with(CurrentUserId)` / `.with(Me)`. Never trust a client-supplied id.
41
-
42
- ## `<model>.store.ts`
43
-
44
- - Write a custom action only for a toast, an optimistic update, or a multi-field write; most stores need none, because state and CRUD actions are generated.
45
- - The body is three lines: `await fetch.X` → `this.setX(...)` → toast. The optimistic shape is mutate the client model, `void fetch.*`, then commit.
46
- - Use `this.pick(...)` when the value must exist, `this.get()` when it may not, and `this.set({...})` to write.
47
- - Mutate lists through the collection API (`this.set({ xList: xList.set(x).save() })`), not array spread.
48
- - **Never `import type { RootStore } from "../st"`** — it crashes `akan build` with a Bun SSR segfault.
49
- - Store actions do not `try/catch`; let the framework toast the `Err`.
50
-
51
- ## `<model>.dictionary.ts`
52
-
53
- Fixed chain, with empty stages still written: `.of() → .model() → .insight() → .query() → .sort() → .enum() → .slice() → .endpoint() → .error() → .translate()`.
54
-
55
- - Every label is `t(["English", "한국어"])`, and nearly every one also carries `.desc([en, ko])`.
56
- - Name every argument in `.arg()`, including framework-supplied `skip` / `limit` / `sort`.
57
- - Use `modelDictionary`, `scalarDictionary`, or `serviceDictionary` to match the module kind.
58
-
59
- ## `<module>.abstract.md`
60
-
61
- A title line, one declarative sentence naming what the module owns, a `## Rules` list of two to five invariants the code cannot show, and an optional workflow arrow chain (`draft -> signed -> active -> completed`). Never restate field lists or types. Update it whenever an invariant or workflow changes.
@@ -1,15 +0,0 @@
1
- ---
2
- description: Naming conventions for components, methods, slices, booleans, and language choice
3
- alwaysApply: true
4
- ---
5
-
6
- # Naming And Language
7
-
8
- - Component exports are role names — `Card`, `Sticker`, `General`, `Preview`, `Admin`, `World`, `Mesh`, `Remove`. The model comes from the namespace (`<Floor.Unit.Card>`), so never write `FloorCard`. `Util` exports are the endpoint verb minus the model noun (`Serve`, `Refund`, `Complete`, `Terminate`).
9
- - Layer the verbs: the document chain method drops the model (`sign()`, `approve()`), the service keeps the bare verb, and the signal, store, and dictionary re-add it (`signScContract`). This keeps custom endpoints clear of generated CRUD and makes `st.do.X` read the same as `fetch.X`.
10
- - Slice and filter names are prepositional: `inOrg`, `inProject`, `inPeriod`, `byStatuses`, `ofPortfolio`. Never `getXInY`, never `listX`.
11
- - Handlers are `onX` props taking inline arrows. Do not extract a `handleX`.
12
- - Booleans are `is*` / `has*` / `can*` / `show*` / `disable*`. Counters are `*Num`, indices are `idx`, collections are `*List` or plural. `SCREAMING_SNAKE` is unused; module-scope tables are camelCase + `as const`.
13
- - Keep existing domain vocabulary and its typos. Transliterated domain terms and misspelled identifiers already in use are load-bearing — renaming them silently breaks callers that match on the name.
14
- - Identifiers, type names, endpoint names, and log messages are **English, always**. Everything a user reads goes through `l("model.field")` or `l.trans({ en, ko })` — never a hard-coded string in JSX, never `window.alert`.
15
- - Dictionary entries are `[en, ko]` pairs, and nearly every label also carries a `.desc([en, ko])` even when it repeats the label. English labels are Title Case, Korean is the plain domain term, and `.error()` Korean ends in `다.`.
@@ -1,21 +0,0 @@
1
- ---
2
- description: React component, form, and Tailwind/daisyUI styling conventions
3
- globs: **/*.tsx
4
- alwaysApply: false
5
- ---
6
-
7
- # React Components And Styling
8
-
9
- - Components are `export const X = ({ … }: XProps) => { return (…); };` — arrow const with a block body. `export default` is reserved for pages, layouts, and `lazy()` targets.
10
- - Never `React.FC`, never `defaultProps`, never `PropsWithChildren`. Defaults go in the destructuring (`prefix = ""`); children are typed `children: ReactNode`.
11
- - `"use client"` on line 1 above the imports is mechanical by file role: every `.Zone.tsx`, `.Template.tsx`, and `.Util.tsx` has it; no `.Unit.tsx` or `.View.tsx` ever does. `usePage()` is legal in server files.
12
- - Conditional render is `cond ? <X/> : null`. Never `{cond && <X/>}` — in a `className` context it renders the literal string `"false"`. Early `return null` is for guard clauses only.
13
- - Never hand-roll loading, empty, or list states. Use `Load.Units` / `Load.View` / `Load.Edit` with `renderItem`, `renderList`, `renderView`, and `renderEmpty`; `<Empty />` for a bare placeholder; and `Model.New` / `Model.Edit` / `Model.SureToRemove` for CRUD modals.
14
- - Avoid hooks. `useState` is for modal-open, tab, draft-input, and drag state only — never for server data. `useEffect` must be a genuine effect such as subscribe-with-cleanup or one-shot init. Prefer `Tab` over a `useState` mode switch. `.Template.tsx` files contain zero `useState`.
15
- - Forms are entirely store-driven: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`, the setter passed by reference. Always use `Field.*`, never a bare `<input>` for a model field. Nested rows use `st.do.writeOnX("payments.3.name", v)` plus the generated `add<Field>OnX` / `sub<Field>OnX`.
16
- - Read with `st.use.*` and write with `st.do.*`. Client components do not call `fetch.*`.
17
- - Static class strings stay plain strings. Reach for `clsx` only for a conditional or to merge an incoming `className`, and merge the caller last: `clsx("base classes", conditional, className)`. `clsx` comes from `akanjs/client`. No `twMerge`, no `cn()`.
18
- - Multi-slot components take extra named props (`wrapperClassName`, `bodyClassName`), never a `classNames` object.
19
- - Use daisyUI semantic tokens with opacity modifiers (`text-base-content/60`, `border-base-content/10`, `bg-base-100/70`, `bg-primary/10`). Never use `dark:` — theming is the daisyUI theme block in `page/*/styles.css`. Raw hex belongs only in marketing surfaces; match the neighbouring files.
20
- - Hoist enum→class lookups to a module-scope `as const` map typed `{ [key in cnst.XStatus["value"]]: string }`. Do not use `Record<...>`. Escalate the map to `webkit/` when a second module needs it.
21
- - Use `<Link>` from `akanjs/ui` for internal navigation; `<a>` only for `mailto:` and external links.