@live-change/frontend-template 0.9.197 → 0.9.199

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 (46) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +184 -0
  2. package/.claude/rules/live-change-backend-architecture.md +126 -0
  3. package/.claude/rules/live-change-backend-models-and-relations.md +188 -0
  4. package/.claude/rules/live-change-frontend-vue-primevue.md +291 -0
  5. package/.claude/rules/live-change-service-structure.md +89 -0
  6. package/.claude/skills/create-skills-and-rules.md +196 -0
  7. package/.claude/skills/live-change-design-actions-views-triggers.md +190 -0
  8. package/.claude/skills/live-change-design-models-relations.md +173 -0
  9. package/.claude/skills/live-change-design-service.md +132 -0
  10. package/.claude/skills/live-change-frontend-action-buttons.md +128 -0
  11. package/.claude/skills/live-change-frontend-action-form.md +143 -0
  12. package/.claude/skills/live-change-frontend-analytics.md +146 -0
  13. package/.claude/skills/live-change-frontend-command-forms.md +215 -0
  14. package/.claude/skills/live-change-frontend-data-views.md +182 -0
  15. package/.claude/skills/live-change-frontend-editor-form.md +177 -0
  16. package/.claude/skills/live-change-frontend-locale-time.md +171 -0
  17. package/.claude/skills/live-change-frontend-page-list-detail.md +200 -0
  18. package/.claude/skills/live-change-frontend-range-list.md +128 -0
  19. package/.claude/skills/live-change-frontend-ssr-setup.md +118 -0
  20. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +202 -0
  21. package/.cursor/rules/live-change-backend-architecture.mdc +131 -0
  22. package/.cursor/rules/live-change-backend-models-and-relations.mdc +194 -0
  23. package/.cursor/rules/live-change-frontend-vue-primevue.mdc +290 -0
  24. package/.cursor/rules/live-change-service-structure.mdc +107 -0
  25. package/.cursor/skills/live-change-design-actions-views-triggers.md +197 -0
  26. package/.cursor/skills/live-change-design-models-relations.md +168 -0
  27. package/.cursor/skills/live-change-design-service.md +75 -0
  28. package/.cursor/skills/live-change-frontend-action-buttons.md +128 -0
  29. package/.cursor/skills/live-change-frontend-action-form.md +143 -0
  30. package/.cursor/skills/live-change-frontend-analytics.md +146 -0
  31. package/.cursor/skills/live-change-frontend-command-forms.md +215 -0
  32. package/.cursor/skills/live-change-frontend-data-views.md +182 -0
  33. package/.cursor/skills/live-change-frontend-editor-form.md +177 -0
  34. package/.cursor/skills/live-change-frontend-locale-time.md +171 -0
  35. package/.cursor/skills/live-change-frontend-page-list-detail.md +200 -0
  36. package/.cursor/skills/live-change-frontend-range-list.md +128 -0
  37. package/.cursor/skills/live-change-frontend-ssr-setup.md +119 -0
  38. package/README.md +71 -0
  39. package/package.json +50 -50
  40. package/server/app.config.js +35 -0
  41. package/server/services.list.js +2 -0
  42. package/.nx/workspace-data/file-map.json +0 -195
  43. package/.nx/workspace-data/nx_files.nxt +0 -0
  44. package/.nx/workspace-data/project-graph.json +0 -8
  45. package/.nx/workspace-data/project-graph.lock +0 -0
  46. package/.nx/workspace-data/source-maps.json +0 -1
@@ -0,0 +1,291 @@
1
+ ---
2
+ description: Rules for Vue 3, PrimeVue, Tailwind frontend development on LiveChange
3
+ globs: **/front/src/**/*.{vue,js,ts}
4
+ ---
5
+
6
+ # Frontend on live-change-stack – Vue 3 + PrimeVue + Tailwind (Claude Code)
7
+
8
+ Use these rules when working on frontends that talk to LiveChange backends.
9
+
10
+ ## Stack
11
+
12
+ - Vue 3 + TypeScript
13
+ - PrimeVue 4 for UI components
14
+ - Tailwind CSS for styling (prefer utility classes, avoid unnecessary custom CSS)
15
+ - vite-plugin-pages for file-based routing in `src/pages/`
16
+ - `@live-change/vue3-ssr` for integration with the backend
17
+
18
+ ## Data loading – `live` + `Promise.all` (Suspense)
19
+
20
+ - **Do not** use `ref(null)` + `onMounted` to fetch data.
21
+ - Always fetch data using `await Promise.all([...])` and `live(path()...)` in `script setup`.
22
+ - The root app should wrap pages with `<Suspense>` (usually handled by `ViewRoot` in live-change frontends).
23
+
24
+ Example:
25
+
26
+ ```js
27
+ import { path, live, api as useApi } from '@live-change/vue3-ssr'
28
+
29
+ const api = useApi()
30
+
31
+ const [devices] = await Promise.all([
32
+ live(path().deviceManager.myUserDevices({}))
33
+ ])
34
+
35
+ const [device, connections] = await Promise.all([
36
+ live(path().deviceManager.myUserDevice({ device: deviceId })),
37
+ live(path().deviceManager.deviceOwnedDeviceConnections({ device: deviceId }))
38
+ ])
39
+ ```
40
+
41
+ In templates, use the `.value` of these refs:
42
+
43
+ ```vue
44
+ <template>
45
+ <div v-if="device.value">
46
+ {{ device.value.name }}
47
+ </div>
48
+ </template>
49
+ ```
50
+
51
+ ## Commands and forms – choosing the right pattern
52
+
53
+ There are 4 ways to execute backend actions. Use the right one:
54
+
55
+ | Pattern | When to use |
56
+ |---|---|
57
+ | `editorData` | **Editing model records** (create/update). Drafts, validation, `AutoField`. Use for settings, editors, profiles. |
58
+ | `actionData` | **One-shot action forms** (not CRUD). Submit once → done. Use for publish, invite, import. |
59
+ | `api.command` | **Single button or programmatic calls** (no form fields). Use for delete, toggle, code-triggered actions. |
60
+ | `<command-form>` | **Avoid.** Legacy, only for trivial prototypes. Prefer `editorData` or `actionData`. |
61
+
62
+ Decision flow:
63
+
64
+ 1. Does the user fill in form fields? → **No**: use `api.command` (wrap in `workingZone.addPromise` for buttons).
65
+ 2. Is it editing a model record (create/update)? → **Yes**: use `editorData`. **No**: use `actionData`.
66
+ 3. Only use `<command-form>` for the simplest throwaway cases.
67
+
68
+ ### `api.command`
69
+
70
+ ```js
71
+ await api.command(['deviceManager', 'createMyUserDevice'], {
72
+ name: 'My device'
73
+ })
74
+
75
+ await api.command(['deviceManager', 'deleteMyUserDevice'], {
76
+ device: id
77
+ })
78
+ ```
79
+
80
+ Format:
81
+
82
+ - `['serviceName', 'actionName']` as the first argument,
83
+ - payload object as the second argument.
84
+
85
+ ## Routing – `<route>` block and `meta.signedIn`
86
+
87
+ - Each page in `src/pages/` can declare its route meta in a `<route>` block.
88
+ - Use `meta.signedIn` for pages that require authentication.
89
+
90
+ ```vue
91
+ <route>
92
+ { "name": "devices", "meta": { "signedIn": true } }
93
+ </route>
94
+ ```
95
+
96
+ Dynamic routes:
97
+
98
+ - `[id].vue` corresponds to `/devices/:id`.
99
+
100
+ ```js
101
+ import { useRoute } from 'vue-router'
102
+
103
+ const route = useRoute()
104
+ const deviceId = route.params.id
105
+ ```
106
+
107
+ ## Confirm + Toast for destructive actions
108
+
109
+ ```js
110
+ import { useConfirm } from 'primevue/useconfirm'
111
+ import { useToast } from 'primevue/usetoast'
112
+
113
+ const confirm = useConfirm()
114
+ const toast = useToast()
115
+
116
+ function deleteDevice(id) {
117
+ confirm.require({
118
+ message: 'Are you sure you want to delete this device?',
119
+ header: 'Confirmation',
120
+ icon: 'pi pi-exclamation-triangle',
121
+ accept: async () => {
122
+ await api.command(['deviceManager', 'deleteMyUserDevice'], { device: id })
123
+ toast.add({
124
+ severity: 'success',
125
+ summary: 'Deleted',
126
+ life: 2000
127
+ })
128
+ }
129
+ })
130
+ }
131
+ ```
132
+
133
+ ## Common UI patterns – list and detail
134
+
135
+ ### List page
136
+
137
+ ```vue
138
+ <template>
139
+ <div class="container mx-auto p-4">
140
+ <div class="flex items-center justify-between mb-6">
141
+ <h1 class="text-2xl font-bold">Devices</h1>
142
+ <Button label="Add" icon="pi pi-plus" @click="openDialog" />
143
+ </div>
144
+
145
+ <Card v-if="devices.value?.length === 0">
146
+ <template #content>
147
+ <p class="text-center text-gray-500">
148
+ No devices yet
149
+ </p>
150
+ </template>
151
+ </Card>
152
+
153
+ <div class="grid gap-4">
154
+ <Card v-for="device in devices.value" :key="device.id">
155
+ <template #content>
156
+ <!-- content -->
157
+ </template>
158
+ </Card>
159
+ </div>
160
+ </div>
161
+ </template>
162
+ ```
163
+
164
+ ### Status tag
165
+
166
+ ```vue
167
+ <Tag
168
+ :value="conn.status"
169
+ :severity="conn.status === 'online' ? 'success' : 'secondary'"
170
+ />
171
+ ```
172
+
173
+ ## Computed paths – reactive parameters
174
+
175
+ When paths depend on reactive values (route params, props), wrap them in `computed()`:
176
+
177
+ ```js
178
+ import { computed, unref } from 'vue'
179
+ import { usePath, live } from '@live-change/vue3-ssr'
180
+
181
+ const path = usePath()
182
+
183
+ const articlePath = computed(() => path.blog.article({ article: unref(articleId) }))
184
+ const [article] = await Promise.all([live(articlePath)])
185
+ ```
186
+
187
+ For conditional loading (e.g. only when logged in), return a falsy value:
188
+
189
+ ```js
190
+ import { useClient } from '@live-change/vue3-ssr'
191
+ const client = useClient()
192
+
193
+ const myDataPath = computed(() => client.value.user && path.blog.myArticles({}))
194
+ const [myData] = await Promise.all([live(myDataPath)])
195
+ ```
196
+
197
+ ## Related data – `.with()`
198
+
199
+ Attach related objects to items in a single reactive query:
200
+
201
+ ```js
202
+ path.blog.articles({})
203
+ .with(article => path.userIdentification.identification({
204
+ sessionOrUserType: article.authorType,
205
+ sessionOrUser: article.author
206
+ }).bind('authorProfile'))
207
+ ```
208
+
209
+ Access: `article.authorProfile?.firstName`. Works with both `live()` and `RangeViewer`.
210
+
211
+ ## WorkingZone for async actions
212
+
213
+ `ViewRoot` wraps every page in `<WorkingZone>`. Use `inject('workingZone')` for non-form button actions:
214
+
215
+ ```js
216
+ import { inject } from 'vue'
217
+ const workingZone = inject('workingZone')
218
+
219
+ function doAction() {
220
+ workingZone.addPromise('actionName', (async () => {
221
+ await actions.blog.publishArticle({ article: id })
222
+ toast.add({ severity: 'success', summary: 'Published', life: 2000 })
223
+ })())
224
+ }
225
+ ```
226
+
227
+ This activates the global loading spinner/blur while the promise is pending.
228
+
229
+ ## Auth guards with `useClient`
230
+
231
+ ```js
232
+ import { useClient } from '@live-change/vue3-ssr'
233
+ const client = useClient()
234
+ ```
235
+
236
+ - `client.value.user` – truthy when logged in
237
+ - `client.value.roles` – array of roles (e.g. `['admin', 'owner']`)
238
+
239
+ Use in templates:
240
+
241
+ ```vue
242
+ <Button v-if="client.roles.includes('admin')" label="Admin" />
243
+ <div v-if="!client.user">Please sign in</div>
244
+ ```
245
+
246
+ ## Locale and time
247
+
248
+ - Call `useLocale().captureLocale()` in `App.vue` to save browser locale to backend.
249
+ - Use `locale.localTime(date)` with vue-i18n's `d()` for SSR-safe date display.
250
+ - `currentTime` from `@live-change/frontend-base` is a reactive ref that ticks every 500ms.
251
+ - `useTimeSynchronization()` from `@live-change/vue3-ssr` corrects clock skew – use when timing is critical (countdowns, real-time events).
252
+
253
+ ## Analytics
254
+
255
+ Use `analytics` from `@live-change/vue3-components`:
256
+
257
+ ```js
258
+ import { analytics } from '@live-change/vue3-components'
259
+ analytics.emit('article:published', { articleId: id })
260
+ ```
261
+
262
+ Wire providers (PostHog, GA4) in a separate file imported from `App.vue`.
263
+
264
+ ## SSR and frontend configuration
265
+
266
+ - Keep separate entry points for client and server:
267
+
268
+ ```js
269
+ // entry-client.js
270
+ import { clientEntry } from '@live-change/frontend-base/client-entry.js'
271
+ export default clientEntry(App, createRouter, config)
272
+
273
+ // entry-server.js
274
+ import { serverEntry, sitemapEntry } from '@live-change/frontend-base/server-entry.js'
275
+ export const render = serverEntry(App, createRouter, config)
276
+ export const sitemap = sitemapEntry(App, createRouter, routerSitemap, config)
277
+ ```
278
+
279
+ - Configure PrimeVue theme in one place (e.g. `config.js`) using `definePreset` and keep dark mode options consistent.
280
+
281
+ ## Discovering views and actions with `describe`
282
+
283
+ Use the CLI `describe` command to find available views (for `live()`) and actions (for `api.command` / `editorData` / `actionData`):
284
+
285
+ ```bash
286
+ node server/start.js describe --service blog
287
+ node server/start.js describe --service blog --view articlesByCreatedAt --output yaml
288
+ node server/start.js describe --service blog --action createMyUserArticle --output yaml
289
+ ```
290
+
291
+ This is the fastest way to discover what paths and actions are available, including those auto-generated by relations.
@@ -0,0 +1,89 @@
1
+ ---
2
+ description: Rules for LiveChange service directory structure and file organization
3
+ globs: **/services/**/*.js
4
+ ---
5
+
6
+ # LiveChange Service Structure
7
+
8
+ Every LiveChange service **must** be a directory, not a single file.
9
+
10
+ ## Required structure
11
+
12
+ ```
13
+ server/services/<serviceName>/
14
+ definition.js # creates app.createServiceDefinition({ name }) – nothing else
15
+ index.js # imports definition, imports all domain files, exports definition
16
+ config.js # optional – reads definition.config, exports resolved config object
17
+ <domain>.js # one file per domain area (models, views, actions, triggers)
18
+ ```
19
+
20
+ ## definition.js
21
+
22
+ Only creates and exports the definition. No models, no actions here.
23
+
24
+ ```js
25
+ import App from '@live-change/framework'
26
+ const app = App.app()
27
+
28
+ const definition = app.createServiceDefinition({ name: 'myService' })
29
+
30
+ export default definition
31
+ ```
32
+
33
+ ## index.js
34
+
35
+ Imports definition and all domain files (side-effect imports), then re-exports definition.
36
+
37
+ ```js
38
+ import App from '@live-change/framework'
39
+ const app = App.app()
40
+
41
+ import definition from './definition.js'
42
+
43
+ import './authenticator.js'
44
+ import './myModel.js'
45
+ import './otherModel.js'
46
+
47
+ export default definition
48
+ ```
49
+
50
+ ## config.js (if needed)
51
+
52
+ Reads `definition.config` set from `app.config.js`, resolves defaults, exports plain object.
53
+
54
+ ```js
55
+ import definition from './definition.js'
56
+
57
+ const { someOption = 'default' } = definition.config
58
+
59
+ export default { someOption }
60
+ ```
61
+
62
+ ## Domain files (e.g. myModel.js)
63
+
64
+ Each file imports `definition` (and `config` if needed) and registers models/views/actions/triggers.
65
+
66
+ ```js
67
+ import App from '@live-change/framework'
68
+ const app = App.app()
69
+
70
+ import definition from './definition.js'
71
+
72
+ export const MyModel = definition.model({ name: 'MyModel', ... })
73
+
74
+ definition.view({ name: 'myList', ... })
75
+ definition.action({ name: 'doThing', ... })
76
+ ```
77
+
78
+ ## services.list.js import
79
+
80
+ Always import from the directory index, not a flat file:
81
+
82
+ ```js
83
+ import myService from './services/myService/index.js' // correct
84
+ import myService from './services/myService.js' // wrong
85
+ ```
86
+
87
+ ## Reference implementation
88
+
89
+ See `/home/m8/IdeaProjects/live-change/live-change-stack/services/stripe-service/` as the canonical example.
@@ -0,0 +1,196 @@
1
+ ---
2
+ description: Create or update Claude Code skills and Cursor rules/skills with proper format and frontmatter
3
+ ---
4
+
5
+ # Skill: create-skills-and-rules
6
+
7
+ Use this skill when you need to **create or update skills and rules** for Claude Code (`.claude/`) and Cursor (`.cursor/`).
8
+
9
+ ## Directory structure
10
+
11
+ ```
12
+ .claude/
13
+ rules/ *.md – always-loaded project instructions
14
+ skills/ *.md – step-by-step guides, invocable or auto-matched by description
15
+ .cursor/
16
+ rules/ *.mdc – Cursor rules with description/globs/alwaysApply frontmatter
17
+ skills/ *.md – same content as .claude/skills/ (Cursor reads them as reference)
18
+ ```
19
+
20
+ ## Step 1 – Decide: rule or skill
21
+
22
+ | Type | Purpose | When loaded |
23
+ |---|---|---|
24
+ | **Rule** | Constraints, conventions, best practices | Automatically, based on `globs` or `alwaysApply` |
25
+ | **Skill** | Step-by-step implementation guide | When description matches task, or invoked via `/skill-name` |
26
+
27
+ Rules say **what to do / not do**. Skills say **how to do it step by step**.
28
+
29
+ ## Step 2 – Create a Claude Code skill (`.claude/skills/<name>.md`)
30
+
31
+ ### Frontmatter
32
+
33
+ ```yaml
34
+ ---
35
+ description: Short description of what this skill does and when to use it
36
+ ---
37
+ ```
38
+
39
+ The `description` field is used by Claude to decide when to automatically apply the skill. Write it as a concise action phrase.
40
+
41
+ ### Optional frontmatter fields (for directory-based skills)
42
+
43
+ If you use the directory format `.claude/skills/<name>/SKILL.md`:
44
+
45
+ ```yaml
46
+ ---
47
+ name: my-skill
48
+ description: What this skill does
49
+ user-invocable: true
50
+ disable-model-invocation: false
51
+ allowed-tools: Read, Grep, Bash
52
+ context: fork
53
+ agent: Explore
54
+ argument-hint: [filename]
55
+ model: sonnet
56
+ ---
57
+ ```
58
+
59
+ | Field | Default | Description |
60
+ |---|---|---|
61
+ | `name` | directory name | Slash command name (lowercase, hyphens, max 64 chars) |
62
+ | `description` | – | When to use (Claude matches this to decide auto-invocation) |
63
+ | `user-invocable` | `true` | Show in `/` menu |
64
+ | `disable-model-invocation` | `false` | Prevent auto-loading by Claude |
65
+ | `allowed-tools` | – | Tools allowed without asking (e.g. `Read, Grep, Bash`) |
66
+ | `context` | – | Set to `fork` to run in isolated subagent |
67
+ | `agent` | – | Subagent type when `context: fork` (`Explore`, `Plan`, etc.) |
68
+ | `argument-hint` | – | Hint for autocomplete (e.g. `[issue-number]`) |
69
+ | `model` | inherited | Model override (`sonnet`, `opus`, `haiku`) |
70
+
71
+ ### Dynamic substitutions in skill content
72
+
73
+ - `$ARGUMENTS` – all arguments passed when invoking
74
+ - `$ARGUMENTS[0]`, `$1` – specific argument by index
75
+ - `${CLAUDE_SESSION_ID}` – current session ID
76
+ - `${CLAUDE_SKILL_DIR}` – directory containing SKILL.md
77
+
78
+ ### Body structure
79
+
80
+ ```markdown
81
+ ---
82
+ description: Build X with Y and Z
83
+ ---
84
+
85
+ # Skill: my-skill-name (Claude Code)
86
+
87
+ Use this skill when you build **X** using Y and Z.
88
+
89
+ ## When to use
90
+
91
+ - Bullet list of scenarios
92
+
93
+ ## Step 1 – First step
94
+
95
+ Explanation + code example.
96
+
97
+ ## Step 2 – Second step
98
+
99
+ Explanation + code example.
100
+ ```
101
+
102
+ Keep skills under 500 lines. Use clear step numbering. Include real code examples.
103
+
104
+ ## Step 3 – Create a Claude Code rule (`.claude/rules/<name>.md`)
105
+
106
+ ### Frontmatter
107
+
108
+ ```yaml
109
+ ---
110
+ description: Short description of this rule's purpose
111
+ globs: **/front/src/**/*.{vue,js,ts}
112
+ ---
113
+ ```
114
+
115
+ | Field | Description |
116
+ |---|---|
117
+ | `description` | What this rule covers (used for matching) |
118
+ | `globs` | File patterns that trigger this rule (e.g. `**/*.js`, `**/services/**/*.js`) |
119
+
120
+ ### Body structure
121
+
122
+ ```markdown
123
+ ---
124
+ description: Rules for X development
125
+ globs: **/*.js
126
+ ---
127
+
128
+ # Title
129
+
130
+ ## Section 1
131
+
132
+ - Rule bullet points
133
+ - Code examples
134
+
135
+ ## Section 2
136
+
137
+ - More rules
138
+ ```
139
+
140
+ Rules are concise. Lead with the constraint, then show a code example.
141
+
142
+ ## Step 4 – Create Cursor rule (`.cursor/rules/<name>.mdc`)
143
+
144
+ Same content as the Claude rule, but with Cursor-specific frontmatter:
145
+
146
+ ```yaml
147
+ ---
148
+ description: Short description of this rule's purpose
149
+ globs: **/front/src/**/*.{vue,js,ts}
150
+ alwaysApply: false
151
+ ---
152
+ ```
153
+
154
+ | Field | Default | Description |
155
+ |---|---|---|
156
+ | `description` | – | What the rule does; Cursor uses this to decide when to apply |
157
+ | `globs` | – | Comma-separated file patterns (e.g. `**/*.tsx, src/**/*.js`) |
158
+ | `alwaysApply` | `false` | If `true`, applies to every session regardless of context |
159
+
160
+ **Notes:**
161
+ - Do NOT quote glob patterns in frontmatter
162
+ - Keep rules short (target 25 lines, max 50 lines for best Cursor performance)
163
+ - The `.mdc` extension is required for Cursor
164
+
165
+ ## Step 5 – Mirror Cursor skills (`.cursor/skills/<name>.md`)
166
+
167
+ Copy the `.claude/skills/*.md` file directly to `.cursor/skills/`. Same content, same filename. Cursor reads these as reference documents.
168
+
169
+ ## Step 6 – Mirror to sub-projects
170
+
171
+ If the project has sub-projects with their own `.claude/` and `.cursor/` directories, copy the files there too:
172
+
173
+ ```bash
174
+ for dir in automation auto-firma; do
175
+ cp .claude/skills/my-skill.md "$dir/.claude/skills/"
176
+ cp .claude/skills/my-skill.md "$dir/.cursor/skills/"
177
+ cp .claude/rules/my-rule.md "$dir/.claude/rules/"
178
+ cp .cursor/rules/my-rule.mdc "$dir/.cursor/rules/"
179
+ done
180
+ ```
181
+
182
+ ## Naming conventions
183
+
184
+ - Use lowercase with hyphens: `live-change-frontend-editor-form.md`
185
+ - Prefix with domain: `live-change-frontend-*`, `live-change-backend-*`
186
+ - Skills describe actions: `*-editor-form`, `*-range-list`, `*-action-buttons`
187
+ - Rules describe scope: `*-vue-primevue`, `*-models-and-relations`
188
+
189
+ ## Checklist
190
+
191
+ - [ ] Frontmatter with `description` in every file
192
+ - [ ] `.claude/skills/*.md` created
193
+ - [ ] `.cursor/skills/*.md` mirrored (same content)
194
+ - [ ] `.claude/rules/*.md` created (if rule)
195
+ - [ ] `.cursor/rules/*.mdc` created with `globs` + `alwaysApply` (if rule)
196
+ - [ ] Sub-projects updated (automation, auto-firma)