@stonepandastudio/cairn 0.4.2 → 0.5.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 (68) hide show
  1. package/README.md +33 -22
  2. package/bin/cairn.js +5 -0
  3. package/lib/init.js +10 -0
  4. package/lib/render/cli.js +106 -0
  5. package/lib/render/engine.js +148 -0
  6. package/lib/render/index.js +210 -0
  7. package/package.json +2 -1
  8. package/presets/EXTRACTION.md +210 -0
  9. package/presets/README.md +86 -0
  10. package/presets/angular/code-guidelines.md +197 -0
  11. package/presets/angular/slots/architect-discussion-topics.md +10 -0
  12. package/presets/angular/slots/architect-mandatory-docs.md +5 -0
  13. package/presets/angular/slots/architect-references.md +4 -0
  14. package/presets/angular/slots/implementation-reference.md +34 -0
  15. package/presets/angular/slots/key-patterns.md +11 -0
  16. package/presets/angular/slots/plan-step-ordering.md +11 -0
  17. package/presets/angular/slots/review-checklist.md +16 -0
  18. package/presets/angular/variants/i18n-external-service.md +11 -0
  19. package/presets/angular/variants/i18n-glossr.md +63 -0
  20. package/presets/core/AGENTS.md +49 -0
  21. package/presets/core/README.md +35 -0
  22. package/presets/core/WORKFLOW.md +56 -0
  23. package/presets/core/agents/architect.md +269 -0
  24. package/presets/core/agents/developer.md +145 -0
  25. package/presets/core/agents/reviewer.md +167 -0
  26. package/presets/core/commands/_stub.md +7 -0
  27. package/presets/core/workflow.json +45 -0
  28. package/presets/drizzle/code-guidelines.md +33 -0
  29. package/presets/drizzle/slots/architect-discussion-topics.md +4 -0
  30. package/presets/drizzle/slots/architect-mandatory-docs.md +4 -0
  31. package/presets/drizzle/slots/implementation-reference.md +17 -0
  32. package/presets/drizzle/slots/key-patterns.md +7 -0
  33. package/presets/drizzle/slots/review-checklist.md +10 -0
  34. package/presets/nestjs/code-guidelines.md +273 -0
  35. package/presets/nestjs/slots/architect-discussion-topics.md +4 -0
  36. package/presets/nestjs/slots/architect-mandatory-docs.md +5 -0
  37. package/presets/nestjs/slots/architect-references.md +5 -0
  38. package/presets/nestjs/slots/implementation-reference.md +45 -0
  39. package/presets/nestjs/slots/key-patterns.md +11 -0
  40. package/presets/nestjs/slots/plan-step-ordering.md +12 -0
  41. package/presets/nestjs/slots/review-checklist.md +12 -0
  42. package/presets/nestjs/variants/validation-class-validator.md +120 -0
  43. package/presets/nestjs/variants/validation-zod.md +194 -0
  44. package/presets/nextjs/code-guidelines.md +45 -0
  45. package/presets/nextjs/slots/architect-discussion-topics.md +5 -0
  46. package/presets/nextjs/slots/architect-mandatory-docs.md +3 -0
  47. package/presets/nextjs/slots/architect-references.md +6 -0
  48. package/presets/nextjs/slots/implementation-reference.md +24 -0
  49. package/presets/nextjs/slots/key-patterns.md +8 -0
  50. package/presets/nextjs/slots/plan-step-ordering.md +11 -0
  51. package/presets/nextjs/slots/review-checklist.md +11 -0
  52. package/presets/react/code-guidelines.md +46 -0
  53. package/presets/react/slots/architect-discussion-topics.md +5 -0
  54. package/presets/react/slots/architect-references.md +5 -0
  55. package/presets/react/slots/implementation-reference.md +26 -0
  56. package/presets/react/slots/key-patterns.md +8 -0
  57. package/presets/react/slots/plan-step-ordering.md +9 -0
  58. package/presets/react/slots/review-checklist.md +10 -0
  59. package/presets/tailwind/code-guidelines.md +28 -0
  60. package/presets/tailwind/slots/implementation-reference.md +8 -0
  61. package/presets/tailwind/slots/key-patterns.md +5 -0
  62. package/presets/tailwind/slots/review-checklist.md +8 -0
  63. package/presets/typeorm/code-guidelines.md +329 -0
  64. package/presets/typeorm/slots/architect-discussion-topics.md +4 -0
  65. package/presets/typeorm/slots/architect-mandatory-docs.md +3 -0
  66. package/presets/typeorm/slots/implementation-reference.md +19 -0
  67. package/presets/typeorm/slots/key-patterns.md +8 -0
  68. package/presets/typeorm/slots/review-checklist.md +8 -0
@@ -0,0 +1,210 @@
1
+ # Extraction record
2
+
3
+ What was pulled into `presets/`, from where, and — more importantly — what was
4
+ deliberately left in the repos. Measured 2026-08-01 by splitting each
5
+ `code-guidelines.md` on `## ` headings and running cairn's own line differ over
6
+ matching sections.
7
+
8
+ The file-level numbers in `DESIGN.md` (backend 43% same, frontend 76%) turned out
9
+ to be the wrong resolution. Per section the picture is much sharper.
10
+
11
+ ## Backend — glossr-api (828 ln) vs snap-backend (404 ln)
12
+
13
+ | Section | Same | Disposition |
14
+ |---|---|---|
15
+ | General TypeScript Guidelines | 100% | → `nestjs` |
16
+ | NestJS Module Structure | 100% | → `nestjs` |
17
+ | Services | 100% | → `nestjs` |
18
+ | Error Handling | 100% | → `nestjs` |
19
+ | Security | 100% | → `nestjs` |
20
+ | Constants | 100% | → `nestjs` |
21
+ | Enums | 100% | → `nestjs` |
22
+ | Code Quality | 100% | → `nestjs` |
23
+ | File Organization | 93% | → `nestjs` (union; glossr's `interfaces/` kept) |
24
+ | File Naming Conventions | 80% | → `nestjs`, DTO lines to `dto-naming` slot |
25
+ | TypeORM Entities | 100% | → `typeorm` |
26
+ | Database Query Decomposition Pattern | 100% | → `typeorm` |
27
+ | Mappers | 100% | → `typeorm` — **but see finding 1** |
28
+ | Database Operations | 85% | → `typeorm` (glossr's stronger wording) |
29
+ | Query Configuration | 75% | → `typeorm` (glossr is a strict superset) |
30
+ | Validation | 43% | spine → `nestjs`, rest → `validation-stack` slot |
31
+ | DTOs | 28% | spine → `nestjs`, rest → `dto-definition` slot |
32
+ | Controllers | 16% | spine → `nestjs`, rest → `controller-response` slot |
33
+ | Response Transformation | disjoint | entirely `response-transformation` slot |
34
+ | **Testing** | **7%** | **stays project-owned** |
35
+ | Interfaces, Error Codes, No Magic Strings, Pagination | glossr only | → `nestjs` (promoted, generic) |
36
+ | QueryBuilder Field Selection, Migrations | glossr only | → `typeorm` (promoted, generic) |
37
+ | Redis Key Naming | glossr only | **stays project-owned** — names a specific app prefix |
38
+
39
+ ### The backend divergence is one axis, not entropy
40
+
41
+ Controllers 16%, DTOs 28%, Validation 43% and the two mutually exclusive Response
42
+ Transformation sections are all the same disagreement:
43
+
44
+ ```
45
+ glossr-api Zod nestjs-zod, createZodDto, @ZodResponse
46
+ snap-backend class-* class-validator, class-transformer, @ResponseMapper
47
+ ```
48
+
49
+ That is a **choice with two known values**, not accumulated rot — so it is a slot
50
+ with two variants, not a reason to fork the file. The file-level 43% reading
51
+ suggested "leave it alone"; the section-level reading says most of the document is
52
+ shareable and one decision is not.
53
+
54
+ ## Frontend — glossr-frontend (231 ln) vs snap-frontend (191 ln)
55
+
56
+ | Section | Same | Disposition |
57
+ |---|---|---|
58
+ | Preamble (Angular rules) | 100% | → `angular` |
59
+ | Theming and Colors (+ 4 subsections, 94 ln) | 100% | → `angular` |
60
+ | Component Selectors | 100% | → `angular` |
61
+ | Constants | 100% | → `angular` |
62
+ | Enums | 100% | → `angular` |
63
+ | Translation Key Naming / Examples | 100% | → `angular` |
64
+ | Translations — sourcing | 48% | → `translation-source` slot |
65
+ | Change detection, Comments | snap only | → `angular` (promoted, generic) |
66
+
67
+ Cleaner than the backend: after the sourcing slot is carved out, essentially the
68
+ whole document is shared.
69
+
70
+ ## CLI — glossr-cli (203 ln)
71
+
72
+ Not extracted. It shares no section headings with either pair, and per
73
+ `DESIGN.md` it is a stack of one, so nothing can be measured against it. A
74
+ `presets/cli/` should wait for a second CLI repo or a deliberate hand-authored
75
+ decision — not be reverse-engineered from a sample of one.
76
+
77
+ ## Deliberately not extracted
78
+
79
+ - **Testing (7% same).** Two genuinely different regimes: glossr keeps all tests
80
+ under `test/` with integration helpers and factories; snap colocates `.spec.ts`
81
+ and targets >80% coverage. Extracting this would force one project onto the
82
+ other's layout.
83
+ - **Redis Key Naming.** Correct guidance, but it hardcodes `REDIS_APP_PREFIX =
84
+ 'glossr'`. Generalising it would leave a rule with no concrete value in it.
85
+ - **`code-guidelines.md` as a whole remains `fork` mode** per `DESIGN.md`. These
86
+ presets are seeds. A repo that takes one owns its copy; cairn does not plan to
87
+ rewrite these files on upgrade.
88
+
89
+ ## Findings
90
+
91
+ Two things surfaced only because the content was read section by section.
92
+
93
+ ### 1. glossr-api's Mappers section documents a decorator it does not have
94
+
95
+ The section is byte-identical to snap-backend's and says:
96
+
97
+ > Basic entity-to-DTO conversion (use `@ResponseMapper` decorator)
98
+
99
+ `grep -rl ResponseMapper glossr-api/src` returns **nothing**. glossr moved to Zod;
100
+ the Mappers section was copied and never updated. It has been instructing agents to
101
+ reach for a decorator that does not exist in that codebase.
102
+
103
+ **A 100%-identical section is not automatically a correct one.** Identical means
104
+ "nobody has diverged", which includes "nobody has noticed". The preset version now
105
+ points at the response-transformation slot instead of naming a decorator.
106
+
107
+ ### 2. Stale line-number cross-references
108
+
109
+ `glossr-api` contains *"See **Query Configuration** section below (lines 232-325)"*
110
+ — the section actually starts at line 323. Both repos contain *"Create Maps for O(1)
111
+ lookup (per guideline line 122)"*. Line numbers in a document that gets edited are
112
+ wrong the moment it is edited. Replaced with section-name references.
113
+
114
+ Also fixed in passing, present identically in both frontend repos: the typos
115
+ `cosntants` and `inrastructure`. Identical typos across two repos are the clearest
116
+ possible proof of copy-paste, and are a decent cheap signal for finding more.
117
+
118
+ ## Agents, workflow, commands — 2026-09-09
119
+
120
+ Second pass, `snap-proof/{backend,frontend}` again. The 2026-07-31 survey scored the
121
+ reviewer contract at 85–92% and treated that as a ceiling. Reading them in full, the
122
+ **structure** is closer to identical — the divergence is confined to stack-specific
123
+ rule tables and reference lists, which are exactly slot-shaped.
124
+
125
+ | artefact | disposition | measurement |
126
+ |---|---|---|
127
+ | `.claude/commands/*.md` | → `core/commands/_stub.md` | every stub byte-identical modulo `{{ step.agentName }}` + `{{ step.agentDoc }}` (frontend copies carry a stray BOM) |
128
+ | `ai/agents/{node,angular}-reviewer.md` | contract → `core/agents/reviewer.md`; Dimension-2 table → `{nestjs,angular}/slots/review-checklist.md` | `diff` shows ~15 changed lines out of 160, all in the rule table, the model-check string, and agent names |
129
+ | `ai/agents/{node,angular}-developer.md` | spine → `core/agents/developer.md`; "Implementation reference" → `{nestjs,angular}/slots/implementation-reference.md` | E.1–E.5 identical; frontend carried 3 safety notes the backend lacked (folded into core) |
130
+ | `ai/agents/{node-architect,architect-lead}.md` | Mode A/B + file-roles + Plan format → `core/agents/architect.md`; the rest → 5 small `{nestjs,angular}/slots/architect-*.md` | whole scaffold shared; slots are the mandatory-doc list, discussion topics, plan-step ordering, A.1 references |
131
+ | `ai/WORKFLOW.md` | → `core/WORKFLOW.md`, rendered from `workflow.steps[].tracker` + `tracker.*` | same document both sides; only the provider block, stage names, and sync-point actions differ — all config |
132
+ | `ai/AGENTS.md` | → `core/AGENTS.md` skeleton | same structure; rendering it removes live drift (frontend's still says "Jira" while its `.env` and commands are on YouTrack) |
133
+
134
+ ### `react` + `drizzle` seeded from charityiq-app — 2026-09-09
135
+
136
+ `charityiq-app` is a separate product (single founder, UK charity SaaS), Next.js 16
137
+ App Router + Drizzle + Supabase + Inngest, at Sprint 0 — a scaffold with documented
138
+ intent (`CLAUDE.md` hard rules, `docs/adr/0001-stack.md`) and almost no code
139
+ (everything under `lib/` is a `TODO(Sprint N)` stub).
140
+
141
+ So the seed is **conventions and structure, not code patterns** — and there is no
142
+ second repo to diff against, so what is "React-generic" vs "charityiq-specific" is
143
+ hand-judged, not measured. Same limit as `preset-cli`.
144
+
145
+ - `presets/react/` — components (folder-by-role: `ui`/`feature`/`brand`), no
146
+ `localStorage`, Tailwind v4 `@theme` tokens (no `tailwind.config.ts`), Vitest +
147
+ Testing Library + MSW + Playwright, Biome (tabs, double quotes). **Not** in it:
148
+ RSC, Server Actions, route handlers, the App Router — those are Next.js-specific
149
+ and belong in a future `presets/nextjs` that composes on top.
150
+ - `presets/drizzle/` — one schema module (`lib/db/schema.ts`), `drizzle-kit
151
+ generate` for mechanical migrations, hand-SQL only in `supabase/migrations/`, RLS
152
+ policies in the migration not the schema, server client never imported from a
153
+ `"use client"` module. Parallel to `presets/typeorm`.
154
+ - `presets/nextjs/` — the App-Router framework layer, added on top of `react`
155
+ (`--stack nextjs,react,…`, nextjs first so its fullstack plan ordering wins). Owns
156
+ the server/client boundary (`"use client"` discipline, no server-only imports
157
+ across it), Server Actions (`'use server'`, validate args, revalidate after a
158
+ write), Route Handlers (webhooks, verify caller), the App-Router file conventions,
159
+ `next/font` / `next/image` / `Metadata`, and a "the framework moves fast, read the
160
+ installed docs" note. **Not** in it: `requireTier()` + `audit.log()` on every
161
+ mutation — that is charityiq's business rule, tier-3 fork, not Next.js-generic.
162
+ - `presets/tailwind/` — styling is a third composable axis, orthogonal to the
163
+ framework (proof: Angular repos use SCSS `theme.$variable`, charityiq uses
164
+ Tailwind `@theme`, either could use either). The generic rules: tokens in a
165
+ `@theme` block not a `tailwind.config.ts`, no raw hex in markup, dark mode by
166
+ re-mapping semantic tokens. The React preset was made styling-agnostic to match.
167
+ Angular's SCSS theming stays inline in `presets/angular` for now — one system
168
+ across its two repos, not worth its own preset until a second SCSS-theming repo
169
+ appears (then: `presets/scss-theme`).
170
+
171
+ Stays charityiq-owned (tier 3, `fork`): the hard business rules — `requireTier()` +
172
+ `audit.log()` on every mutation, RAG-grounded AI through `lib/ai/client.ts`, the
173
+ audit row in the same transaction as the state change — plus `docs/adr/`, the build
174
+ guide, and the versioned-prompt registry (`docs/prompts/<name>/<version>.md`). Those
175
+ are the project's constitution, not shareable scaffolding.
176
+
177
+ ### The nestjs slots were split nestjs / typeorm — 2026-09-09
178
+
179
+ The first cut of `presets/nestjs/slots/*` (review-checklist, implementation-reference,
180
+ key-patterns, architect-mandatory-docs, architect-discussion-topics) mixed framework
181
+ rules with TypeORM rules. Split: the framework half stays in `nestjs/slots/`, the ORM
182
+ half moved to `presets/typeorm/slots/` under the same filenames. The renderer appends
183
+ one from each `stack[]` entry, so `--stack nestjs,typeorm` reconstitutes the original
184
+ and `--stack nestjs,drizzle` would pair the framework rules with a Drizzle slot set
185
+ instead. `plan-step-ordering` and `architect-references` are primary-stack-only (not
186
+ concatenated) — noted in their headers.
187
+
188
+ ### QA agents — preset-level, not extracted
189
+
190
+ Backend runs three (`qa-architect` / `qa-developer` / `qa-orchestrator`, ~630 ln
191
+ of Jest + factories + `src/feature/` layout + `yarn test:db` scripts); frontend runs
192
+ one (`angular-qa`, Playwright + `data-testid` + page objects). 33% similar at best,
193
+ confirming the 2026-07-31 read. The bodies are one project's test setup, not
194
+ shareable content — vendoring them as a "preset" would over-claim. They belong under
195
+ `{nestjs,angular}/agents/` once a second repo on the same stack shows what is
196
+ actually shared, the same "no peer" limit `preset-cli` has.
197
+
198
+ ### Rot the renderer removes
199
+
200
+ - backend `node-reviewer.md` still requires `claude-sonnet-4-6` — frontend already says `claude-sonnet-5`
201
+ - frontend `AGENTS.md` + `WORKFLOW.md` are entirely Jira; the repo is on YouTrack (`.env`, `.claude/commands/youtrack-*`)
202
+ - same review step is `code_review` in backend, `review_plan` in frontend — one verb, rendered from `workflow.steps[].command`
203
+ - backend reviewer/developer hard-code an `ai/tasks/PROOF-[task]/` path — breaks on `LOCAL-` folders; the core versions use `ai/tasks/[task]/`
204
+
205
+ ## Not wired up yet
206
+
207
+ There is no renderer. Nothing consumes `{{> slot: … }}` or `{{> stack/… }}`, and no
208
+ repo reads these files. They are the input for the agent-preset roadmap step
209
+ (`DESIGN.md`, "### Sequencing" step 4), and are already useful as the canonical
210
+ version of scaffolding that currently exists as two drifting copies.
@@ -0,0 +1,86 @@
1
+ # Presets
2
+
3
+ Extracted guideline content shared across the repos. Internal only — an in-repo
4
+ folder, deliberately package-shaped so promotion to `@stonepandastudio/cairn-preset-*` is
5
+ adding a `package.json` rather than a refactor.
6
+
7
+ Nothing here is wired into a renderer yet. These are the *inputs* the renderer will
8
+ consume once the projects layer and extension mechanisms land (see `DESIGN.md`,
9
+ "Multi-repo projects"). Until then they are seeds: readable, diffable, and already
10
+ useful as the canonical version of guidance that currently exists in two drifting
11
+ copies.
12
+
13
+ ## Layout
14
+
15
+ ```
16
+ presets/
17
+ core/ stack-agnostic — agent role contracts, command stub, WORKFLOW/AGENTS skeletons
18
+ agents/ architect.md, developer.md, reviewer.md (85–92% identical across stacks)
19
+ commands/ _stub.md (renders one file per workflow step)
20
+ nestjs/ NestJS + TypeScript framework spine, validation-stack + ORM agnostic
21
+ variants/ validation-zod.md, validation-class-validator.md
22
+ slots/ the framework side of each {{> stack/… }} point
23
+ typeorm/ ORM add-on — entities, queries, migrations, decomposition
24
+ slots/ the ORM side of the same {{> stack/… }} points (appended to nestjs)
25
+ drizzle/ ORM add-on — Drizzle schema, drizzle-kit migrations, RLS in migrations
26
+ slots/ the ORM side, for a Postgres/Supabase stack
27
+ angular/ Angular spine + SCSS theming + i18n (styling still inline here — one system, two repos)
28
+ variants/ i18n-glossr.md, i18n-external-service.md
29
+ slots/ the Angular side of the same {{> stack/… }} points
30
+ react/ React frontend spine — components, hooks, state, testing (styling-agnostic)
31
+ slots/ the React side of the same {{> stack/… }} points
32
+ nextjs/ App-Router layer over `react` — server/client boundary, actions, route handlers
33
+ slots/ the Next.js side of the same {{> stack/… }} points
34
+ tailwind/ styling add-on — @theme tokens, no tailwind.config.ts, dark mode via token re-map
35
+ slots/ the styling side of the same {{> stack/… }} points
36
+ EXTRACTION.md what was extracted from where, and what deliberately was not
37
+ ```
38
+
39
+ Compose the slots a repo needs. Rough axes — **framework** (`nestjs`, `react`,
40
+ `nextjs` on top of `react`), **ORM** (`typeorm` / `drizzle`), **styling** (`tailwind`):
41
+
42
+ ```
43
+ --stack nestjs,typeorm snap-proof / glossr backend
44
+ --stack angular snap-proof / glossr frontend (SCSS theming inline for now)
45
+ --stack nextjs,react,tailwind,drizzle charityiq — nextjs primary (owns plan ordering + fullstack refs)
46
+ --stack react,tailwind a Vite SPA against a separate API
47
+ ```
48
+
49
+ `typeorm` and `angular` are seeded from two repos; `drizzle`, `react`, `nextjs`,
50
+ `tailwind` from one (charityiq). A single-consumer preset is hand-judged, not
51
+ measured, and gets refined when a second repo on that axis appears — the
52
+ `preset-cli` "no peer" limit.
53
+
54
+ `nestjs` and `typeorm` are separate on purpose. Every backend repo composes both
55
+ today, but the split is what proves the composition model works — a future backend
56
+ on Prisma or Drizzle takes `nestjs` without `typeorm`, and a repo declares the
57
+ composition as `--stack nestjs,typeorm`.
58
+
59
+ ## Slots
60
+
61
+ A slot marks a point where a shared template knows a decision exists but not which
62
+ way a repo (or a stack) went. Two flavours:
63
+
64
+ - **`{{> slot: validation-stack }}`** in a `code-guidelines.md` — the repo picks a
65
+ file from `variants/` by name. Where a repo needs something no variant covers, it
66
+ supplies its own partial — the signal that a third variant may be warranted.
67
+ - **`{{> stack/review-checklist }}`** in a `core/` agent contract — filled from each
68
+ stack entry's `slots/` folder. Two fill modes:
69
+ - **concatenated** — the renderer appends the slot from every entry in `stack[]`,
70
+ in order. `--stack nestjs,typeorm` → `review-checklist` is the nestjs framework
71
+ rows followed by the typeorm ORM rows. Used for checklists, doc lists, reference
72
+ lists.
73
+ - **primary-only** — the slot is taken from `stack[0]` alone, because it is one
74
+ coherent thing that cannot be concatenated. `plan-step-ordering` (a numbered
75
+ sequence) and `architect-references` are primary-only; their file header says so.
76
+
77
+ Slots exist to keep the merge cheap. A template update that does not touch a slot
78
+ never conflicts with a repo's choice, which is the whole reason the extension
79
+ mechanisms are three rather than one.
80
+
81
+ ## What is deliberately absent
82
+
83
+ Sections that are genuinely per-project stay per-project — they are listed in
84
+ `EXTRACTION.md` with the measurement that justified leaving them out. Extracting
85
+ content that *should* differ is the failure mode this whole exercise is designed to
86
+ avoid: it would report every legitimate project difference as drift, forever.
@@ -0,0 +1,197 @@
1
+ # Code Guidelines
2
+
3
+ <!-- cairn preset: angular -->
4
+
5
+ * In angular templates use new template syntax instead of old `*ng` syntax.
6
+ * Unsubscribe from observables in `ngOnDestroy`
7
+ * Use standalone Angular components by default, refactor component to standalone if it's not standalone component
8
+ * Every utility function, model, constant or mapping function should stay in separated file.
9
+ * For file names use dash-case (Example: `my-long-module.module.ts`)
10
+ * Use Reactive Forms for any new forms.
11
+ * All data access is handled through dedicated services.
12
+ * Complex data manipulation is placed in utils files
13
+
14
+ ## Theming and Colors
15
+
16
+ The application uses a CSS variables-based theming system with the following architecture:
17
+
18
+ **Chain**: Global CSS Variables → SCSS Palette Variables → Abstract SCSS Variables → Component `.scss` files
19
+
20
+ ### Architecture Layers
21
+
22
+ 1. **Global CSS Variables** (`src/scss/themes/default/colors.scss`)
23
+ - Defines actual color values as CSS custom properties
24
+ - Example: `--fotofixer-color-primary: #00aabc;`
25
+ - Changing these allows dynamic theme switching without page refresh
26
+
27
+ 2. **SCSS Palette Variables** (`src/scss/themes/colors.scss`)
28
+ - Maps CSS variables to SCSS variables using `var()` function
29
+ - Example: `$fotofixer-color-primary: var(--fotofixer-color-primary);`
30
+ - **DO NOT import this file directly in component SCSS**
31
+
32
+ 3. **Abstract SCSS Variables** (`src/scss/themes/theme.scss`)
33
+ - Provides semantic/abstract names for colors
34
+ - Example: `$font-color-secondary: colors.$main-color-main-10;`
35
+ - **This is the only file you should import in component SCSS files**
36
+
37
+ ### Rules for Using Colors in SCSS Files
38
+
39
+ * **DO NOT** use hardcoded colors (e.g., `#ffffff`, `white`, `rgb(...)`)
40
+ * **DO NOT** use CSS variables directly (e.g., `var(--main-color-main-11)`)
41
+ * **DO NOT** import `colors.scss` directly
42
+ * **DO** import `theme.scss` using `@use` directive
43
+ * **DO** use abstract SCSS variables from `theme.scss`
44
+ * **DO** add new abstract variables to `theme.scss` if needed
45
+
46
+ ### Example Usage
47
+
48
+ ```scss
49
+ @use "themes/theme" as theme;
50
+
51
+ .my-component {
52
+ background-color: theme.$background-color-white;
53
+ border: 1px solid theme.$border-color-secondary;
54
+ color: theme.$font-color;
55
+
56
+ .secondary-text {
57
+ color: theme.$font-color-secondary;
58
+ }
59
+
60
+ .muted-text {
61
+ color: theme.$font-color-muted;
62
+ }
63
+
64
+ .primary-action {
65
+ background-color: theme.$fotofixer-color-primary;
66
+
67
+ &:hover {
68
+ background-color: theme.$fotofixer-color-hover;
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Available Abstract Variables (from `theme.scss`)
75
+
76
+ **Primary Colors:**
77
+ - `$fotofixer-color-primary` - Main brand color (cyan)
78
+ - `$fotofixer-color-secondary` - Light brand color
79
+ - `$fotofixer-color-tetriary` - Very light brand color
80
+ - `$fotofixer-color-hover` - Brand hover state
81
+
82
+ **Background Colors:**
83
+ - `$background-color-main` - Main background (light gray)
84
+ - `$background-color-secondary` - Secondary background
85
+ - `$background-color-secondary-hover` - Secondary hover state
86
+ - `$background-color-white` - White background
87
+
88
+ **Font Colors:**
89
+ - `$font-color` - Main text color (black)
90
+ - `$font-color-navbar` - Navbar text
91
+ - `$font-color-secondary` - Secondary text (dark gray)
92
+ - `$font-color-muted` - Muted/disabled text (medium gray)
93
+ - `$invert-font-color` - Inverted text (white)
94
+
95
+ **Border Colors:**
96
+ - `$border-color` - Main border color
97
+ - `$border-color-secondary` - Secondary border (light gray)
98
+ - `$divider-color` - Divider lines (very light gray)
99
+
100
+ **Message Colors:**
101
+ - `$error-message-color` - Error text
102
+ - `$error-message-background-color` - Error background
103
+ - `$success-message-color` - Success text
104
+
105
+ **Other:**
106
+ - `$disabled-color` - Disabled state
107
+ - `$link-hover-color` - Link hover state
108
+
109
+ ## Component Selectors
110
+
111
+ * Do NOT use `app-` prefix for component selectors in new components
112
+ * Use descriptive, kebab-case selector names (Example: `popover`, `project-status-icon`, `connected-projects-popover`)
113
+ * Selector should match the component's purpose without generic prefixes
114
+
115
+ ## Translations and Internationalization
116
+
117
+ ### General Rules
118
+
119
+ * Always use translation keys for static text in templates.
120
+ * For dynamic text in templates use translation keys when possible. If not possible, add a comment explaining why.
121
+ * **Locale files are generated artifacts.** Never hand-edit them and never add a key to one to make a feature render — any manual change is destroyed by the next sync.
122
+
123
+ Where translations come from, and how new keys are submitted, is project-specific:
124
+
125
+ {{> slot: translation-source }}
126
+
127
+ ### Translation Key Naming Conventions
128
+
129
+ * Use PascalCase for naming translation keys.
130
+ * Key structure depends on context:
131
+ * **Common text (no specific context)**: Use 2 parts - `"Common.SaveButton"`, `"Common.CancelButton"`
132
+ * **Context-specific text**: Use nested structure - `"[PageName].[Subcontext].[TextDescription]"`
133
+
134
+ ### Examples
135
+
136
+ **Common keys:**
137
+ ```json
138
+ "Common": {
139
+ "SaveButton": "Save",
140
+ "CancelButton": "Cancel",
141
+ "Loading": "Loading..."
142
+ }
143
+ ```
144
+
145
+ **Page-specific keys:**
146
+ ```json
147
+ "DevicesTable": {
148
+ "Title": "Devices Table",
149
+ "Header": {
150
+ "CityColumn": "City",
151
+ "DeviceColumn": "Device"
152
+ },
153
+ "Button": {
154
+ "DownloadReport": "Download Report"
155
+ }
156
+ }
157
+ ```
158
+
159
+ **Message keys:**
160
+ ```json
161
+ "Messages": {
162
+ "Devices": {
163
+ "FailedLoadDevices": "Failed to load devices",
164
+ "FailedDownloadReport": "Failed to download report"
165
+ }
166
+ }
167
+ ```
168
+
169
+ ### Checking Existing Keys
170
+
171
+ Before creating new translation keys, check the existing English locale file for examples and to avoid duplication. Read it only — see the generated-artifact rule above.
172
+
173
+ ## Change detection
174
+
175
+ * **Match the strategy used by nearby/similar components.** The codebase mixes `Default` and `OnPush`; don't default to `OnPush` unless the surrounding components use it. Most page-level components currently use `ChangeDetectionStrategy.Default`.
176
+ * If you're adding a component to an area where everything is `Default`, use `Default`. If you're adding one next to `OnPush` components, use `OnPush`.
177
+ * Don't "upgrade" an existing component from `Default` to `OnPush` as a drive-by change — that's a focused refactor, not part of a feature plan.
178
+
179
+ ## Comments
180
+
181
+ * **Default to no comments.** Good names carry meaning; small functions are self-documenting.
182
+ * Only write a comment when the *why* is non-obvious: a hidden constraint, a subtle invariant, a workaround for a specific bug, behavior that would surprise a reader.
183
+ * Don't explain *what* the code does — identifiers and structure already do that.
184
+ * Don't reference task IDs, PR flows, or "added for feature X" in code comments — that context belongs in commit messages and PR descriptions, and rots as the codebase evolves.
185
+ * No multi-paragraph docstrings or multi-line comment blocks — one short line is the ceiling.
186
+
187
+ ## Constants
188
+
189
+ File name template: `xx-yy.constant.ts` for `XxYy` constant
190
+
191
+ Files with constants should be placed in the `constants/` folder within the corresponding module (see `ai/infrastructure/project-structure.md` for module details)
192
+
193
+ ## Enums
194
+
195
+ File name template: `xx-yy.enum.ts` for `XxYy` enum
196
+
197
+ Files with enums should be placed in the `enums/` folder within the corresponding module (see `ai/infrastructure/project-structure.md` for module details)
@@ -0,0 +1,10 @@
1
+ <!-- cairn preset: angular — fills {{> stack/architect-discussion-topics }} (A.3). -->
2
+ - **Ambiguities** — anything in the description that admits multiple reasonable interpretations
3
+ - **UX / design tradeoffs** — popup vs. page, tabs vs. stacked sections, inline vs. separate component
4
+ - **Integration tensions** — e.g. global company state vs. route-scoped company id
5
+
6
+ **Optional aid — `frontend-design` skill.** When the discussion hinges on a
7
+ visual/UX tradeoff, `frontend-design` can explore mockups. **Treat its output as
8
+ inspiration only** — it does not know this project's conventions (Material UI,
9
+ `theme.scss` variables, base classes, new template syntax, no `app-` prefix,
10
+ translation keys). Never feed its generated code into the plan.
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: angular — fills {{> stack/architect-mandatory-docs }} (A.2). -->
2
+ - `ai/infrastructure/code-guidelines.md`
3
+ - `ai/infrastructure/project-structure.md`
4
+ - `ai/infrastructure/patterns.md`
5
+ - `ai/infrastructure/mappers.md`
@@ -0,0 +1,4 @@
1
+ <!-- cairn preset: angular — fills {{> stack/architect-references }} (A.1). -->
2
+ - Frontend source files (components, services, base classes)
3
+ - The backend repo's context docs — entity definitions, business logic, controllers
4
+ - Backend source files, for API-shape confirmation
@@ -0,0 +1,34 @@
1
+ <!-- cairn preset: angular — fills {{> stack/implementation-reference }} in the core
2
+ developer. snap-proof/frontend/ai/agents/angular-developer.md has no block like
3
+ the nestjs one — it points at patterns.md + code-guidelines.md and stops.
4
+ This is a minimal synthesis from angular-developer + angular-reviewer; extend
5
+ it when a second Angular repo shows what is actually shared. -->
6
+
7
+ The authoritative source is `ai/infrastructure/code-guidelines.md`; the base-class
8
+ and header-flow catalogue is `ai/infrastructure/patterns.md`. Reminders:
9
+
10
+ ### Components
11
+ - Standalone components — no `NgModule` declarations block
12
+ - No `app-` prefix in the selector
13
+ - Match the change-detection strategy of nearby components
14
+ - Subscriptions collected in a `Subscription`, unsubscribed in `ngOnDestroy`
15
+ - Complex data manipulation goes in a `utils/` file, not inlined
16
+
17
+ ### Templates
18
+ - Control flow: `@if` / `@for` / `@switch` — never `*ngIf` / `*ngFor`
19
+ - Every `@for` carries a `track` expression
20
+ - Every static string is a translation key — no bare literals
21
+
22
+ ### Styles
23
+ - Only `theme.$variable` from `@use "themes/theme" as theme` — no `#hex`, `rgb()`, or CSS custom properties
24
+
25
+ ### Forms
26
+ - Reactive forms only — no `ngModel`
27
+
28
+ ### Files
29
+ - Models, enums, constants, and mapper functions each in their own kebab-case file
30
+ - `enums/`, `constants/`, `mappers/` folders per module
31
+ - Mapper shapes: `EntityResponse → Entity`, `Entity ↔ EntityForm`, `Entity → EntityPayload`
32
+
33
+ ### Build
34
+ - Run the project's production build before reporting completion; a failing build blocks the report
@@ -0,0 +1,11 @@
1
+ <!-- cairn preset: angular — fills {{> stack/key-patterns }} in the core architect
2
+ and developer. From architect-lead.md / angular-developer.md. -->
3
+
4
+ Canonical catalogue: **`ai/infrastructure/patterns.md`** — base classes, the
5
+ `x-admin-profile` header flow, routing. Plans should lean on these instead of
6
+ reinventing them.
7
+
8
+ Micro-level conventions (theming, translations, template syntax, change detection,
9
+ file naming): **`ai/infrastructure/code-guidelines.md`**.
10
+
11
+ Mapper conventions: **`ai/infrastructure/mappers.md`**.
@@ -0,0 +1,11 @@
1
+ <!-- cairn preset: angular — fills {{> stack/plan-step-ordering }} in the core
2
+ architect's Plan format. architect-lead.md gives no explicit ordering beyond
3
+ "each step leaves the app working"; this is a synthesis, refine against a
4
+ second Angular repo. -->
5
+ 1. Models, enums, constants, mapper functions
6
+ 2. Service methods (API calls, state)
7
+ 3. Component logic (`.ts`)
8
+ 4. Template (`.html`) and styles (`.scss`)
9
+ 5. Routing and module wiring
10
+ 6. Translation keys
11
+ 7. Context doc updates
@@ -0,0 +1,16 @@
1
+ <!-- cairn preset: angular — fills {{> stack/review-checklist }} in the core reviewer.
2
+ Verbatim from snap-proof/frontend/ai/agents/angular-reviewer.md § Dimension 2. -->
3
+
4
+ | New template syntax (`@if`, `@for`, `@switch`) — no `*ngIf`/`*ngFor` | `.html` files |
5
+ | `@for` always includes a `track` expression — missing `track` is a compile error in Angular 17+ | `.html` files |
6
+ | Standalone components — no `NgModule` declarations block | Component `.ts` files |
7
+ | Observables subscribed inside components are collected in a `Subscription` and unsubscribed in `ngOnDestroy` | Component `.ts` files |
8
+ | No hardcoded colors (`#hex`, `rgb()`, CSS custom properties) — only `theme.$variable` from `@use "themes/theme" as theme` | `.scss` files |
9
+ | No `app-` prefix in component selectors | Component `.ts` files |
10
+ | All static text in templates uses translation keys — no bare string literals | `.html` files |
11
+ | `src/assets/i18n/*.json` never hand-edited — autogenerated, replaced during localization — **blocking** if the diff touches these files directly | `src/assets/i18n/*.json` |
12
+ | Reactive forms only — no template-driven forms (`ngModel`) | Component `.ts` files |
13
+ | Complex data manipulation goes in `utils/` files, not inlined in components | Component `.ts` files |
14
+ | Mapper pattern respected: `EntityResponse → Entity`, `Entity ↔ EntityForm`, `Entity → EntityPayload` | `mappers/` files |
15
+ | Models, enums, constants, and mapper functions each in their own kebab-case file | Module folder structure |
16
+ | Enums in `enums/` folder, constants in `constants/` folder, mappers in `mappers/` folder | Module structure |
@@ -0,0 +1,11 @@
1
+ <!-- cairn variant: translation-source = external-service. Fills the
2
+ translation-source slot in presets/angular/code-guidelines.md.
3
+ In use by: snap-frontend. A third-party localization vendor owns the values. -->
4
+
5
+ ## slot: translation-source
6
+
7
+ Never add or update translation values for translation keys in `src/assets/i18n/*.json` files directly. These are autogenerated files that will be replaced by a third-party service during the localization process.
8
+
9
+ ### Checking Existing Keys
10
+
11
+ Before creating new translation keys, check existing keys in `src/assets/i18n/en-us.json` for examples and to avoid duplication.