@podosoft/podokit 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +6 -0
  2. package/dist/add.d.ts +2 -0
  3. package/dist/add.js +6 -1
  4. package/dist/assemble.js +4 -1
  5. package/dist/create.d.ts +2 -0
  6. package/dist/create.js +6 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +6 -1
  9. package/dist/lib.d.ts +14 -0
  10. package/dist/lib.js +34 -0
  11. package/dist/lockfile.js +15 -0
  12. package/dist/templates/base/AGENTS.md +30 -0
  13. package/dist/templates/base/CLAUDE.md +6 -0
  14. package/dist/templates/fullstack-nest-svelte/AGENTS.md +79 -0
  15. package/dist/templates/fullstack-nest-svelte/CLAUDE.md +6 -0
  16. package/dist/templates/fullstack-nest-svelte/Dockerfile.dev +25 -0
  17. package/dist/templates/fullstack-nest-svelte/apps/api/package.json +4 -2
  18. package/dist/templates/fullstack-nest-svelte/apps/web/src/app.html +2 -0
  19. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/index.ts +1 -3
  20. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +1 -1
  21. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +5 -25
  22. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +1 -4
  23. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/tabs/tabs.svelte +1 -1
  24. package/dist/templates/fullstack-nest-svelte/apps/web/static/favicon.svg +13 -0
  25. package/dist/templates/fullstack-nest-svelte/apps/web/vite.config.ts +12 -0
  26. package/dist/templates/fullstack-nest-svelte/compose.dev.yaml +150 -0
  27. package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-add-module/SKILL.md +27 -0
  28. package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-nest-endpoint/SKILL.md +23 -0
  29. package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-sveltekit-route/SKILL.md +23 -0
  30. package/dist/templates/fullstack-nest-svelte/dot-claude/skills/podokit-update/SKILL.md +26 -0
  31. package/dist/templates/fullstack-nest-svelte/dot-cursor/rules/podokit.mdc +11 -0
  32. package/dist/templates/fullstack-nest-svelte/dot-devcontainer/devcontainer.json +20 -0
  33. package/dist/templates/fullstack-nest-svelte/dot-dockerignore +8 -0
  34. package/dist/templates/fullstack-nest-svelte/dot-env.docker +26 -0
  35. package/dist/templates/fullstack-nest-svelte/dot-github/copilot-instructions.md +6 -0
  36. package/dist/templates/fullstack-nest-svelte/dot-mcp.json +8 -0
  37. package/dist/templates/fullstack-nest-svelte/infra/traefik/dynamic.yml +13 -0
  38. package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.controller.ts +87 -0
  39. package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.module.ts +14 -0
  40. package/dist/templates/modules/admin-dashboard/files/apps/api/src/site-settings/site-settings.service.ts +84 -0
  41. package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +2 -0
  42. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +12 -1
  43. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +13 -0
  44. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-footer.svelte +32 -0
  45. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +16 -3
  46. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +46 -2
  47. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/site.svelte.ts +50 -0
  48. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +28 -13
  49. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/general-settings.svelte +203 -0
  50. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +7 -3
  51. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +8 -3
  52. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +24 -4
  53. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +41 -0
  54. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +2 -0
  55. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/maintenance/+page.svelte +25 -0
  56. package/dist/templates/modules/admin-dashboard/files/dot-claude/skills/podokit-data-table/SKILL.md +43 -0
  57. package/dist/templates/modules/admin-dashboard/files/tests/ui/general-effects.ui.spec.ts +81 -0
  58. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +42 -1
  59. package/dist/templates/modules/admin-dashboard/module.manifest.json +20 -2
  60. package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +27 -0
  61. package/dist/templates/modules/auth/module.manifest.json +6 -0
  62. package/dist/templates/modules/bullmq/module.manifest.json +1 -1
  63. package/dist/templates/modules/file-upload/files/apps/api/src/files/files.controller.ts +3 -0
  64. package/dist/templates/todo/AGENTS.md +80 -0
  65. package/dist/templates/todo/CLAUDE.md +6 -0
  66. package/dist/templates/todo/Dockerfile.dev +25 -0
  67. package/dist/templates/todo/apps/api/package.json +4 -2
  68. package/dist/templates/todo/apps/web/src/app.html +2 -0
  69. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/index.ts +1 -3
  70. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-content.svelte +1 -1
  71. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-list.svelte +5 -25
  72. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs-trigger.svelte +1 -4
  73. package/dist/templates/todo/apps/web/src/lib/components/ui/tabs/tabs.svelte +1 -1
  74. package/dist/templates/todo/apps/web/static/favicon.svg +13 -0
  75. package/dist/templates/todo/apps/web/vite.config.ts +12 -0
  76. package/dist/templates/todo/compose.dev.yaml +150 -0
  77. package/dist/templates/todo/dot-claude/skills/podokit-add-module/SKILL.md +27 -0
  78. package/dist/templates/todo/dot-claude/skills/podokit-nest-endpoint/SKILL.md +23 -0
  79. package/dist/templates/todo/dot-claude/skills/podokit-sveltekit-route/SKILL.md +23 -0
  80. package/dist/templates/todo/dot-claude/skills/podokit-update/SKILL.md +26 -0
  81. package/dist/templates/todo/dot-cursor/rules/podokit.mdc +11 -0
  82. package/dist/templates/todo/dot-devcontainer/devcontainer.json +20 -0
  83. package/dist/templates/todo/dot-dockerignore +8 -0
  84. package/dist/templates/todo/dot-env.docker +26 -0
  85. package/dist/templates/todo/dot-github/copilot-instructions.md +6 -0
  86. package/dist/templates/todo/dot-mcp.json +8 -0
  87. package/dist/templates/todo/infra/traefik/dynamic.yml +13 -0
  88. package/package.json +9 -1
package/README.md CHANGED
@@ -47,10 +47,16 @@ Options:
47
47
  - base : minimal npm workspace
48
48
  --dir <path> Target directory (default: ./<name>)
49
49
  --pm <name> Package manager: npm | pnpm | yarn (default: npm)
50
+ --no-ai Skip AI agent guidance (AGENTS.md, CLAUDE.md, editor rules)
50
51
  -y, --yes Skip prompts and accept defaults
51
52
  -h, --help Show help
52
53
  ```
53
54
 
55
+ Generated projects include agent guidance for AI coding tools — an
56
+ [`AGENTS.md`](https://agents.md) (the open standard), a `CLAUDE.md` that imports
57
+ it, `.cursor`/`.github` pointers, and Claude Code skills under `.claude/skills/`.
58
+ Modules extend `AGENTS.md` as you add them. Use `--no-ai` to skip.
59
+
54
60
  Examples:
55
61
 
56
62
  ```bash
package/dist/add.d.ts CHANGED
@@ -2,6 +2,8 @@ interface Injection {
2
2
  file: string;
3
3
  marker: string;
4
4
  text: string;
5
+ /** Skip (instead of failing) if the target file or marker is absent. */
6
+ optional?: boolean;
5
7
  }
6
8
  export interface ModuleManifest {
7
9
  name: string;
package/dist/add.js CHANGED
@@ -109,9 +109,14 @@ function applyModule(projectRoot, module, modulesDir, applied) {
109
109
  for (const injection of manifest.inject ?? []) {
110
110
  const target = (0, node_path_1.join)(projectRoot, injection.file);
111
111
  if (!(0, node_fs_1.existsSync)(target)) {
112
+ if (injection.optional)
113
+ continue;
112
114
  throw new Error(`Cannot wire module: ${injection.file} not found.`);
113
115
  }
114
- (0, node_fs_1.writeFileSync)(target, (0, podokit_template_engine_1.insertAtMarker)((0, node_fs_1.readFileSync)(target, "utf8"), injection.marker, injection.text));
116
+ const content = (0, node_fs_1.readFileSync)(target, "utf8");
117
+ if (injection.optional && !content.includes(injection.marker))
118
+ continue;
119
+ (0, node_fs_1.writeFileSync)(target, (0, podokit_template_engine_1.insertAtMarker)(content, injection.marker, injection.text));
115
120
  }
116
121
  const instructions = (manifest.instructions ?? []).map((line) => line.replace(/<app>/g, appName));
117
122
  return { module, instructions, added };
package/dist/assemble.js CHANGED
@@ -47,7 +47,10 @@ function applyModuleToTree(tree, moduleDir, vars) {
47
47
  // 4) inject wiring at markers
48
48
  for (const injection of manifest.inject ?? []) {
49
49
  const target = injection.file;
50
- setText(tree, target, (0, podokit_template_engine_1.insertAtMarker)(textOf(tree, target), injection.marker, injection.text));
50
+ const content = textOf(tree, target);
51
+ if (injection.optional && !content.includes(injection.marker))
52
+ continue;
53
+ setText(tree, target, (0, podokit_template_engine_1.insertAtMarker)(content, injection.marker, injection.text));
51
54
  }
52
55
  }
53
56
  /** Assemble the project tree for a template + ordered modules, in memory. */
package/dist/create.d.ts CHANGED
@@ -14,6 +14,8 @@ export interface CreateOptions {
14
14
  packageManager?: PackageManager;
15
15
  /** PodoKit version stamped into the lockfile. Defaults to the CLI version. */
16
16
  podokitVersion?: string;
17
+ /** Include AI agent guidance (AGENTS.md, CLAUDE.md, editor rules). Defaults to true. */
18
+ ai?: boolean;
17
19
  }
18
20
  export interface CreateResult {
19
21
  projectDir: string;
package/dist/create.js CHANGED
@@ -9,6 +9,8 @@ const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
9
9
  const templates_1 = require("./templates");
10
10
  Object.defineProperty(exports, "DEFAULT_TEMPLATE", { enumerable: true, get: function () { return templates_1.DEFAULT_TEMPLATE; } });
11
11
  const lockfile_1 = require("./lockfile");
12
+ /** AI agent guidance files, removed when scaffolding with `--no-ai`. */
13
+ const AI_ARTIFACTS = ["AGENTS.md", "CLAUDE.md", ".claude", ".cursor", ".github/copilot-instructions.md"];
12
14
  const NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-._]*[a-z0-9])?$/i;
13
15
  /** Validate a project name: no path separators, npm-friendly characters. */
14
16
  function assertValidName(name) {
@@ -42,6 +44,10 @@ function create(options) {
42
44
  }
43
45
  const vars = { projectName: name, packageManager };
44
46
  (0, podokit_template_engine_1.copyTemplate)(templateDir, projectDir, vars);
47
+ if (options.ai === false) {
48
+ for (const artifact of AI_ARTIFACTS)
49
+ (0, node_fs_1.rmSync)((0, node_path_1.join)(projectDir, artifact), { recursive: true, force: true });
50
+ }
45
51
  (0, lockfile_1.initLockfile)(projectDir, {
46
52
  template,
47
53
  packageManager,
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ interface ParsedArgs {
10
10
  apply: boolean;
11
11
  yes: boolean;
12
12
  help: boolean;
13
+ ai: boolean;
13
14
  }
14
15
  export declare function parseArgs(argv: string[]): ParsedArgs;
15
16
  export {};
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ Options:
26
26
  --template <t> Template to scaffold (see below)
27
27
  --dir <path> Target directory (default: ./<name>)
28
28
  --pm <name> Package manager: npm | pnpm | yarn (default: npm)
29
+ --no-ai Skip AI agent guidance (AGENTS.md, CLAUDE.md, editor rules)
29
30
  -y, --yes Skip prompts and accept defaults
30
31
  -h, --help Show this help
31
32
 
@@ -38,7 +39,7 @@ Example:
38
39
  cd my-app && npx @podosoft/podokit add auth
39
40
  `;
40
41
  function parseArgs(argv) {
41
- const parsed = { help: false, yes: false, apply: false };
42
+ const parsed = { help: false, yes: false, apply: false, ai: true };
42
43
  const positionals = [];
43
44
  for (let i = 0; i < argv.length; i += 1) {
44
45
  const arg = argv[i];
@@ -48,6 +49,9 @@ function parseArgs(argv) {
48
49
  else if (arg === "-y" || arg === "--yes") {
49
50
  parsed.yes = true;
50
51
  }
52
+ else if (arg === "--no-ai") {
53
+ parsed.ai = false;
54
+ }
51
55
  else if (arg === "--apply") {
52
56
  parsed.apply = true;
53
57
  }
@@ -242,6 +246,7 @@ async function main(argv) {
242
246
  template: resolved.template,
243
247
  targetDir: args.dir,
244
248
  packageManager: resolved.packageManager,
249
+ ai: args.ai,
245
250
  });
246
251
  const relPath = (0, node_path_1.relative)(process.cwd(), result.projectDir) || ".";
247
252
  const rel = relPath.startsWith("..") ? result.projectDir : relPath;
package/dist/lib.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export { create } from "./create";
2
+ export type { CreateOptions, CreateResult } from "./create";
3
+ export { TEMPLATES, DEFAULT_TEMPLATE, TEMPLATE_NAMES, isKnownTemplate } from "./templates";
4
+ export type { TemplateInfo } from "./templates";
5
+ export { listModules, addModule } from "./add";
6
+ export type { ModuleManifest, AddOptions, AddResult } from "./add";
7
+ export { status, diff, doctor, SUPPORTED_FRAMEWORKS } from "./inspect";
8
+ export type { StatusReport, DoctorFinding } from "./inspect";
9
+ export { planUpdate, summarize } from "./update";
10
+ export type { UpdatePlan, FileChange } from "./update";
11
+ /** Absolute path to the templates bundled in this installed package. */
12
+ export declare function builtinTemplatesDir(): string;
13
+ /** Absolute path to the modules bundled in this installed package. */
14
+ export declare function builtinModulesDir(): string;
package/dist/lib.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summarize = exports.planUpdate = exports.SUPPORTED_FRAMEWORKS = exports.doctor = exports.diff = exports.status = exports.addModule = exports.listModules = exports.isKnownTemplate = exports.TEMPLATE_NAMES = exports.DEFAULT_TEMPLATE = exports.TEMPLATES = exports.create = void 0;
4
+ exports.builtinTemplatesDir = builtinTemplatesDir;
5
+ exports.builtinModulesDir = builtinModulesDir;
6
+ // Library entry point: the reusable functions behind the `podo` CLI, for other
7
+ // packages (e.g. @podosoft/podokit-mcp) to call without spawning the binary.
8
+ const node_path_1 = require("node:path");
9
+ var create_1 = require("./create");
10
+ Object.defineProperty(exports, "create", { enumerable: true, get: function () { return create_1.create; } });
11
+ var templates_1 = require("./templates");
12
+ Object.defineProperty(exports, "TEMPLATES", { enumerable: true, get: function () { return templates_1.TEMPLATES; } });
13
+ Object.defineProperty(exports, "DEFAULT_TEMPLATE", { enumerable: true, get: function () { return templates_1.DEFAULT_TEMPLATE; } });
14
+ Object.defineProperty(exports, "TEMPLATE_NAMES", { enumerable: true, get: function () { return templates_1.TEMPLATE_NAMES; } });
15
+ Object.defineProperty(exports, "isKnownTemplate", { enumerable: true, get: function () { return templates_1.isKnownTemplate; } });
16
+ var add_1 = require("./add");
17
+ Object.defineProperty(exports, "listModules", { enumerable: true, get: function () { return add_1.listModules; } });
18
+ Object.defineProperty(exports, "addModule", { enumerable: true, get: function () { return add_1.addModule; } });
19
+ var inspect_1 = require("./inspect");
20
+ Object.defineProperty(exports, "status", { enumerable: true, get: function () { return inspect_1.status; } });
21
+ Object.defineProperty(exports, "diff", { enumerable: true, get: function () { return inspect_1.diff; } });
22
+ Object.defineProperty(exports, "doctor", { enumerable: true, get: function () { return inspect_1.doctor; } });
23
+ Object.defineProperty(exports, "SUPPORTED_FRAMEWORKS", { enumerable: true, get: function () { return inspect_1.SUPPORTED_FRAMEWORKS; } });
24
+ var update_1 = require("./update");
25
+ Object.defineProperty(exports, "planUpdate", { enumerable: true, get: function () { return update_1.planUpdate; } });
26
+ Object.defineProperty(exports, "summarize", { enumerable: true, get: function () { return update_1.summarize; } });
27
+ /** Absolute path to the templates bundled in this installed package. */
28
+ function builtinTemplatesDir() {
29
+ return (0, node_path_1.join)(__dirname, "templates");
30
+ }
31
+ /** Absolute path to the modules bundled in this installed package. */
32
+ function builtinModulesDir() {
33
+ return (0, node_path_1.join)(__dirname, "templates", "modules");
34
+ }
package/dist/lockfile.js CHANGED
@@ -44,6 +44,21 @@ const WALK_IGNORE = new Set([
44
44
  exports.DEFAULT_OWNED_GLOBS = [
45
45
  "apps/web/src/routes/**",
46
46
  "apps/web/src/lib/components/ui/**",
47
+ // AI agent guidance — yours to customize; update never touches it.
48
+ "AGENTS.md",
49
+ "CLAUDE.md",
50
+ ".claude/**",
51
+ ".cursor/**",
52
+ ".github/copilot-instructions.md",
53
+ ".mcp.json",
54
+ // Containerized dev-environment scaffolding: users tweak ports/services, so
55
+ // updates never overwrite these.
56
+ "compose.dev.yaml",
57
+ "Dockerfile.dev",
58
+ ".dockerignore",
59
+ ".devcontainer/**",
60
+ ".env.docker",
61
+ "infra/traefik/**",
47
62
  ];
48
63
  const TEXT_EXT = /\.(json|md|ts|tsx|js|mjs|cjs|css|html|yml|yaml|txt|env|svelte|gitignore|example)$/i;
49
64
  function podokitDir(projectRoot) {
@@ -0,0 +1,30 @@
1
+ # AGENTS.md — {{projectName}}
2
+
3
+ Guidance for AI coding agents (Claude Code, Codex, Cursor, Copilot, …). Generated
4
+ by [PodoKit](https://github.com/podosoft-dev/podokit).
5
+
6
+ ## Project overview
7
+
8
+ A minimal npm-workspace starter. Build it up from here — add features with the
9
+ `podo` CLI (`podo add <module>`; run `podo add` with no argument to list them).
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ npm install
15
+ npm run dev # runs workspace dev scripts
16
+ npm run build
17
+ npm run lint # type-check
18
+ npm test
19
+ ```
20
+
21
+ ## Code style
22
+
23
+ - TypeScript `strict`. **No `any`** (use `unknown` + narrowing), no `@ts-ignore`.
24
+ - Explicit function return types. Conventional Commits, imperative mood, no emojis.
25
+
26
+ ## PodoKit tooling
27
+
28
+ This project is managed by the `podo` CLI; `.podokit/` records how it was
29
+ assembled (do not edit by hand). Use `podo status`/`podo diff` to see your local
30
+ edits and `podo update` to pull in improvements without losing your work.
@@ -0,0 +1,6 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this project lives in [AGENTS.md](./AGENTS.md). It is imported
4
+ below so Claude Code loads the same rules the other AI tools read.
5
+
6
+ @AGENTS.md
@@ -0,0 +1,79 @@
1
+ # AGENTS.md — {{projectName}}
2
+
3
+ Guidance for AI coding agents (Claude Code, Codex, Cursor, Copilot, …) working in
4
+ this repository. It was generated by [PodoKit](https://github.com/podosoft-dev/podokit).
5
+ Follow it so your changes match the project's conventions.
6
+
7
+ ## Project overview
8
+
9
+ A full-stack TypeScript monorepo (npm workspaces):
10
+
11
+ - `apps/api` — **NestJS** backend (`{{projectName}}-api`), TypeORM + PostgreSQL, Swagger at `/api-docs`.
12
+ - `apps/web` — **SvelteKit** frontend (`{{projectName}}-web`), Tailwind v4 + shadcn-svelte + typesafe-i18n.
13
+ - `infra/` — Docker Compose (PostgreSQL/Redis) and example k3s manifests.
14
+ - `.podokit/` — PodoKit's generation lockfile (see "PodoKit tooling" below). Do not edit by hand.
15
+
16
+ ## Setup & commands
17
+
18
+ ```bash
19
+ npm install
20
+ cp .env.example .env
21
+ docker compose -f infra/docker/docker-compose.yml up -d # PostgreSQL (+ Redis)
22
+ npm run dev # api on http://localhost:5002, web on http://localhost:5001
23
+ ```
24
+
25
+ - **Ports: web = 5001, api = 5002.** The web app reaches the API only through its
26
+ SvelteKit server proxy (`/api/*`), never with a direct browser `fetch`.
27
+ - Build: `npm run build` · Type-check/lint: `npm run lint` · Unit tests: `npm test`
28
+ - Per workspace: `npm run build -w {{projectName}}-api` / `-w {{projectName}}-web`.
29
+ - DB migrations (TypeORM): `npm run migration:run -w {{projectName}}-api`.
30
+ - e2e (Playwright, ships in `tests/`): `npm run test:e2e`.
31
+
32
+ **Always** verify a change by building the affected workspace (and `svelte-check`
33
+ for web) before considering it done. UI changes need a Playwright test; API
34
+ changes need an e2e/unit test.
35
+
36
+ ## Code style
37
+
38
+ ### TypeScript (both apps)
39
+ - `strict` mode. **No `any`** — use `unknown` + narrowing. **No `@ts-ignore`/`@ts-expect-error`** and no `eslint-disable`.
40
+ - Explicit function return types. DTOs use `class-validator` decorators.
41
+ - Keep `noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noUncheckedIndexedAccess`.
42
+
43
+ ### Svelte 5 (web)
44
+ - **Runes only**: `$state()`, `$derived()`, `$effect()`, `$props()`. **Never** use `$:` (deprecated).
45
+ - Reactive stores go in `.svelte.ts` files; don't destructure a getter-exported rune (it loses reactivity) — wrap in `$derived`.
46
+
47
+ ### UI — Tailwind + shadcn-svelte (web)
48
+ - Prefer **shadcn-svelte components** over raw HTML (`<button>`, `<input>`, `<select>`, …).
49
+ - **Do not edit `src/lib/components/ui/**`** (the shadcn primitives). Customize in a separate wrapper.
50
+ - Tailwind v4 (CSS-first, no `tailwind.config`). Use semantic classes: `bg-background`, `text-foreground`, `border-border`. Dark mode via `mode-watcher`. Responsive (`sm:`/`md:`/`lg:`).
51
+
52
+ ### i18n & errors
53
+ - **Every user-facing string goes through i18n** (`src/lib/i18n/messages.ts`) — no hardcoded text. Add keys to **all** locales at once.
54
+ - The backend returns a stable **error envelope**: `{ success: false, error: { code, message, statusCode, path, timestamp, details? } }`. Throw `AppException("SOME_CODE", ...)` on the backend; **branch on `error.code`, not the message,** on the frontend.
55
+ - Logging is structured; no emojis/icons. Start messages with an action verb.
56
+
57
+ ## PodoKit tooling & file ownership
58
+
59
+ This project is managed by the `podo` CLI. Files have an ownership tier recorded
60
+ in `.podokit/files.lock` — respect it:
61
+
62
+ - **owned** (your code: `apps/web/src/routes/**`, your components/services, and this `AGENTS.md`) — edit freely.
63
+ - **assembled** (e.g. `apps/api/src/app.module.ts`, `auth.ts`) — contain `// podokit:begin:… / …:end` fenced regions that the toolkit recomputes. Edit outside the fences only; **never hand-edit inside the fences**.
64
+ - **managed** (bootstrap/config like `main.ts`) — the toolkit may update these; avoid gratuitous edits.
65
+
66
+ Useful commands:
67
+
68
+ - `podo add <module>` — add a feature (auth, admin-dashboard, redis, bullmq, …). It wires itself into the app; run `podo add` with no argument to list modules.
69
+ - `podo status` / `podo diff` — see your local edits vs. what PodoKit generated.
70
+ - `podo update` — pull in template/module improvements (3-way merges your edits; never touches owned files).
71
+
72
+ ## Commit conventions
73
+
74
+ Conventional Commits (`feat|fix|docs|refactor|test|chore`), imperative mood,
75
+ ≤50-char subject, no trailing period, no emojis. One logical change per commit.
76
+
77
+ <!-- podokit:begin:agents-modules -->
78
+ <!-- Module-specific guidance is injected here by `podo add`. -->
79
+ <!-- podokit:end:agents-modules -->
@@ -0,0 +1,6 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this project lives in [AGENTS.md](./AGENTS.md). It is imported
4
+ below so Claude Code loads the same rules the other AI tools read.
5
+
6
+ @AGENTS.md
@@ -0,0 +1,25 @@
1
+ # Development image for the PodoKit monorepo (web + api).
2
+ # Dependencies and source are baked in; `docker compose watch` syncs your
3
+ # edits into the running container so the native file watchers fire.
4
+ FROM node:22-bookworm-slim
5
+
6
+ # git for the toolchain (editors, AI agents); build tools for native modules.
7
+ RUN apt-get update \
8
+ && apt-get install -y --no-install-recommends git python3 make g++ ca-certificates \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ WORKDIR /app
12
+
13
+ # Install workspace dependencies (cached unless a package.json changes).
14
+ # NB: the host package-lock.json is intentionally NOT copied — a lockfile
15
+ # generated on macOS/Windows prunes platform-specific optional deps and can drop
16
+ # packages (e.g. @swc/cli) on linux. Resolving fresh in-container avoids that.
17
+ COPY package.json ./
18
+ COPY apps/api/package.json ./apps/api/package.json
19
+ COPY apps/web/package.json ./apps/web/package.json
20
+ RUN npm install
21
+
22
+ # Baked source (kept in sync during development by `docker compose watch`).
23
+ COPY . .
24
+
25
+ EXPOSE 5001 5002
@@ -3,7 +3,7 @@
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "nest start --watch",
6
+ "dev": "nest start --watch --builder swc",
7
7
  "build": "nest build",
8
8
  "start": "node dist/main",
9
9
  "lint": "tsc -p tsconfig.json --noEmit",
@@ -40,7 +40,9 @@
40
40
  "supertest": "^7.0.0",
41
41
  "ts-jest": "^29.2.5",
42
42
  "ts-node": "^10.9.2",
43
- "typescript": "^6.0.3"
43
+ "typescript": "^6.0.3",
44
+ "@swc/core": "^1.7.0",
45
+ "@swc/cli": "^0.4.0"
44
46
  },
45
47
  "jest": {
46
48
  "moduleFileExtensions": [
@@ -3,6 +3,8 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{projectName}}</title>
7
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
6
8
  %sveltekit.head%
7
9
  </head>
8
10
  <body data-sveltekit-preload-data="hover" class="h-full">
@@ -1,6 +1,6 @@
1
1
  import Root from "./tabs.svelte";
2
2
  import Content from "./tabs-content.svelte";
3
- import List, { tabsListVariants, type TabsListVariant } from "./tabs-list.svelte";
3
+ import List from "./tabs-list.svelte";
4
4
  import Trigger from "./tabs-trigger.svelte";
5
5
 
6
6
  export {
@@ -8,8 +8,6 @@ export {
8
8
  Content,
9
9
  List,
10
10
  Trigger,
11
- tabsListVariants,
12
- type TabsListVariant,
13
11
  //
14
12
  Root as Tabs,
15
13
  Content as TabsContent,
@@ -12,6 +12,6 @@
12
12
  <TabsPrimitive.Content
13
13
  bind:ref
14
14
  data-slot="tabs-content"
15
- class={cn("text-sm flex-1 outline-none", className)}
15
+ class={cn("flex-1 outline-none", className)}
16
16
  {...restProps}
17
17
  />
@@ -1,40 +1,20 @@
1
- <script lang="ts" module>
2
- import { tv, type VariantProps } from "tailwind-variants";
3
-
4
- export const tabsListVariants = tv({
5
- base: "rounded-lg p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
6
- variants: {
7
- variant: {
8
- default: "cn-tabs-list-variant-default bg-muted",
9
- line: "cn-tabs-list-variant-line gap-1 bg-transparent",
10
- },
11
- },
12
- defaultVariants: {
13
- variant: "default",
14
- },
15
- });
16
-
17
- export type TabsListVariant = VariantProps<typeof tabsListVariants>["variant"];
18
- </script>
19
-
20
1
  <script lang="ts">
21
2
  import { Tabs as TabsPrimitive } from "bits-ui";
22
3
  import { cn } from "$lib/utils.js";
23
4
 
24
5
  let {
25
6
  ref = $bindable(null),
26
- variant = "default",
27
7
  class: className,
28
8
  ...restProps
29
- }: TabsPrimitive.ListProps & {
30
- variant?: TabsListVariant;
31
- } = $props();
9
+ }: TabsPrimitive.ListProps = $props();
32
10
  </script>
33
11
 
34
12
  <TabsPrimitive.List
35
13
  bind:ref
36
14
  data-slot="tabs-list"
37
- data-variant={variant}
38
- class={cn(tabsListVariants({ variant }), className)}
15
+ class={cn(
16
+ "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
17
+ className
18
+ )}
39
19
  {...restProps}
40
20
  />
@@ -13,10 +13,7 @@
13
13
  bind:ref
14
14
  data-slot="tabs-trigger"
15
15
  class={cn(
16
- "gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
17
- "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
18
- "data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
19
- "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
16
+ "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
20
17
  className
21
18
  )}
22
19
  {...restProps}
@@ -14,6 +14,6 @@
14
14
  bind:ref
15
15
  bind:value
16
16
  data-slot="tabs"
17
- class={cn("gap-2 group/tabs flex data-[orientation=horizontal]:flex-col", className)}
17
+ class={cn("flex flex-col gap-2", className)}
18
18
  {...restProps}
19
19
  />
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="PodoKit">
2
+ <defs>
3
+ <linearGradient id="pk" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#8b5cf6"/>
5
+ <stop offset="1" stop-color="#4f46e5"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="64" height="64" rx="15" fill="url(#pk)"/>
9
+ <!-- layered stack: full-stack toolkit -->
10
+ <path d="M32 14 50 25 32 36 14 25Z" fill="#fff" fill-opacity="0.96"/>
11
+ <path d="M16 31 32 40.5 48 31" fill="none" stroke="#fff" stroke-opacity="0.78" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"/>
12
+ <path d="M16 38 32 47.5 48 38" fill="none" stroke="#fff" stroke-opacity="0.5" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"/>
13
+ </svg>
@@ -2,6 +2,18 @@ import { sveltekit } from "@sveltejs/kit/vite";
2
2
  import tailwindcss from "@tailwindcss/vite";
3
3
  import { defineConfig } from "vite";
4
4
 
5
+ // Inside the dev container (compose.dev.yaml sets VITE_DOCKER=1): bind to all
6
+ // interfaces, allow the Traefik hostname, and route HMR through Traefik on :80.
7
+ const inDocker = process.env.VITE_DOCKER === "1";
8
+
5
9
  export default defineConfig({
6
10
  plugins: [tailwindcss(), sveltekit()],
11
+ server: inDocker
12
+ ? {
13
+ host: true,
14
+ port: 5001,
15
+ allowedHosts: true,
16
+ hmr: { clientPort: 80 },
17
+ }
18
+ : undefined,
7
19
  });