@live-change/frontend-template 0.9.201 → 0.9.204

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 (54) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +49 -1
  2. package/.claude/rules/live-change-backend-architecture.md +5 -5
  3. package/.claude/rules/live-change-backend-event-sourcing.md +2 -2
  4. package/.claude/rules/live-change-backend-models-and-relations.md +65 -8
  5. package/.claude/rules/live-change-frontend-e2e-lifecycle.md +42 -0
  6. package/.claude/rules/live-change-frontend-i18n-locales.md +25 -0
  7. package/.claude/rules/live-change-frontend-vue-primevue.md +100 -4
  8. package/.claude/rules/live-change-node-toolchain-fnm.md +39 -0
  9. package/.claude/rules/live-change-service-structure.md +2 -2
  10. package/.claude/settings.json +3 -1
  11. package/.claude/skills/create-skills-and-rules/SKILL.md +23 -0
  12. package/.claude/skills/live-change-backend-change-triggers/SKILL.md +15 -0
  13. package/.claude/skills/live-change-dao-protocol/SKILL.md +46 -0
  14. package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +116 -0
  15. package/.claude/skills/live-change-design-models-relations/SKILL.md +63 -5
  16. package/.claude/skills/live-change-frontend-command-forms/SKILL.md +6 -5
  17. package/.claude/skills/live-change-frontend-data-views/SKILL.md +75 -4
  18. package/.claude/skills/live-change-frontend-e2e-lifecycle/SKILL.md +82 -0
  19. package/.claude/skills/live-change-frontend-range-list/SKILL.md +90 -0
  20. package/.claude/skills/live-change-frontend-synchronized/SKILL.md +101 -0
  21. package/.claude/skills/live-change-node-toolchain-fnm/SKILL.md +44 -0
  22. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +58 -4
  23. package/.cursor/rules/live-change-backend-architecture.mdc +1 -1
  24. package/.cursor/rules/live-change-backend-event-sourcing.mdc +1 -1
  25. package/.cursor/rules/live-change-backend-models-and-relations.mdc +69 -7
  26. package/.cursor/rules/live-change-backend-views-vs-triggers-for-reads-writes.mdc +28 -0
  27. package/.cursor/rules/live-change-dao-protocol.mdc +47 -0
  28. package/.cursor/rules/live-change-frontend-e2e-lifecycle.mdc +15 -0
  29. package/.cursor/rules/live-change-frontend-i18n-locales.mdc +26 -0
  30. package/.cursor/rules/live-change-frontend-views-not-commands-for-reads.mdc +30 -0
  31. package/.cursor/rules/live-change-frontend-vue-primevue.mdc +91 -0
  32. package/.cursor/rules/live-change-node-toolchain-fnm.mdc +40 -0
  33. package/.cursor/rules/live-change-service-structure.mdc +1 -1
  34. package/.cursor/skills/create-skills-and-rules.md +23 -0
  35. package/.cursor/skills/live-change-backend-change-triggers.md +15 -0
  36. package/.cursor/skills/live-change-design-actions-views-triggers.md +57 -0
  37. package/.cursor/skills/live-change-design-models-relations.md +23 -5
  38. package/.cursor/skills/live-change-frontend-command-forms.md +6 -5
  39. package/.cursor/skills/live-change-frontend-data-views.md +17 -0
  40. package/.cursor/skills/live-change-frontend-e2e-lifecycle.md +82 -0
  41. package/.cursor/skills/live-change-frontend-range-list.md +45 -0
  42. package/.cursor/skills/live-change-frontend-synchronized.md +101 -0
  43. package/.cursor/skills/live-change-node-toolchain-fnm.md +44 -0
  44. package/.node-version +1 -0
  45. package/.nvmrc +1 -0
  46. package/README.md +18 -0
  47. package/e2e/client-session.test.ts +17 -0
  48. package/e2e/e2eSuite.ts +12 -0
  49. package/e2e/env.ts +130 -0
  50. package/e2e/homepage.test.ts +17 -0
  51. package/e2e/steps.ts +3 -0
  52. package/e2e/withBrowser.ts +18 -0
  53. package/opencode.json +4 -1
  54. package/package.json +55 -53
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Rules for implementing actions, views, and triggers in LiveChange services
3
- globs: **/services/**/*.js
3
+ globs: **/services/**/*.js, **/server/**/*.js, server/**/*.js
4
4
  ---
5
5
 
6
6
  # LiveChange backend – actions, views, triggers (Claude Code)
@@ -52,6 +52,48 @@ definition.action({
52
52
  - Views should be simple query endpoints over models.
53
53
  - Prefer `indexObjectGet` / `indexRangeGet` instead of scanning whole tables.
54
54
 
55
+ ### Range view guardrails (for RangeViewer/rangeBuckets consumers)
56
+
57
+ - For index-backed paginated lists, prefer `Model.sortedIndexRangePath(indexName, keyPrefix, App.extractRange(props))`.
58
+ - Do not use `indexRangePath` semantics for views consumed by bucket-based range UI.
59
+ - Keep `gt/gte/lt/lte` as cursor pagination fields, not domain filter fields.
60
+ - If you need filtering by month/year/status, design index prefix for it first.
61
+ - Use `App.utils.prefixRange` as backend fallback only when index redesign is not feasible.
62
+
63
+ ### Standalone index guardrail
64
+
65
+ - If an index combines peer data streams (union of multiple tables), define it as service-level `definition.index(...)` (prefer separate `indexes.js`), not as `model.indexes` inside one arbitrary model.
66
+ - Use model `indexes` only when one model is the clear owner of index semantics.
67
+
68
+ ### Index function serialization constraint
69
+
70
+ Index functions (`definition.index({ function })` and model-level `indexes: { name: { function } }`) are **serialized via `toString()`** and executed on a remote server. They **cannot reference anything outside their own function body** — no outer variables, no imported functions, no module-scope helpers.
71
+
72
+ ```js
73
+ // ❌ BROKEN — helper is outside the function, undefined at runtime
74
+ function mapRow(obj) { return { id: obj.name + '_' + obj.id, to: obj.id } }
75
+
76
+ definition.index({
77
+ name: 'myIndex',
78
+ function: async (input, output, { tableName }) => {
79
+ const table = await input.table(tableName)
80
+ await table.map(mapRow).to(output) // mapRow is undefined!
81
+ },
82
+ parameters: { tableName: definition.name + '_MyModel' }
83
+ })
84
+
85
+ // ✅ CORRECT — helper is inside the function body
86
+ definition.index({
87
+ name: 'myIndex',
88
+ function: async (input, output, { tableName }) => {
89
+ const mapRow = obj => ({ id: obj.name + '_' + obj.id, to: obj.id })
90
+ const table = await input.table(tableName)
91
+ await table.map(mapRow).to(output)
92
+ },
93
+ parameters: { tableName: definition.name + '_MyModel' }
94
+ })
95
+ ```
96
+
55
97
  Example of a range view:
56
98
 
57
99
  ```js
@@ -159,6 +201,12 @@ definition.trigger({
159
201
 
160
202
  Check `data`/`oldData`: both present = update, only `data` = create, only `oldData` = delete.
161
203
 
204
+ ## Cron-service — schedules, intervals, and admin UI
205
+
206
+ - For **cron-like** or **repeating-interval** execution of a **trigger**, use **`@live-change/cron-service`** (**Schedule** / **Interval**) plus **task-service** triggers — do not sketch “only a timer” without considering cron models and **`changeCron_Schedule`** / **`changeCron_Interval`** timer lifecycle.
207
+ - Reference admin flow (see **task-frontend**): **`setSchedule`** / **`setInterval`** via **`ActionForm`**, lists via **`path.cron.schedules`** / **`path.cron.intervals`**, enrich rows with **`.with()`** for **`scheduleInfo`** / **`intervalInfo`**, **`runState`** (`jobType` **`cron_Schedule`** or **`cron_Interval`**), and **`task.tasksByCauseAndCreatedAt`**; delete with **`deleteSchedule`** / **`deleteInterval`**.
208
+ - **Schedule** time fields (**minute**, **hour**, **day**, **dayOfWeek**, **month**): use **`NaN`** for “every” at that granularity; see **`15-cron-and-intervals.md`** (section **API used by task-frontend**).
209
+
162
210
  ## Granting access on object creation
163
211
 
164
212
  When a model uses `entity` with `writeAccessControl` / `readAccessControl`, the auto-generated CRUD checks roles but does **not** grant them automatically. The creator must be explicitly granted roles — typically `'owner'` — otherwise they cannot access their own object.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Rules for LiveChange backend service architecture and directory structure
3
- globs: **/services/**/*.js
3
+ globs: **/services/**/*.js, **/server/**/*.js, server/**/*.js
4
4
  ---
5
5
 
6
6
  # LiveChange backend – service architecture (Claude Code)
@@ -104,17 +104,17 @@ services: [
104
104
 
105
105
  ## Inspecting services with `describe`
106
106
 
107
- Use the `describe` CLI command to see what the framework generated from your definitions (models, views, actions, triggers, indexes, events):
107
+ Use the `describe` CLI command to see what the framework generated from your definitions (models, views, actions, triggers, indexes, events). From the app root (directory with `.node-version` / `.nvmrc`), run Node via **fnm** so the toolchain matches the project — see rule `live-change-node-toolchain-fnm`.
108
108
 
109
109
  ```bash
110
110
  # All services overview
111
- node server/start.js describe
111
+ fnm exec -- node server/start.js describe
112
112
 
113
113
  # One service in YAML (shows all generated code)
114
- node server/start.js describe --service myService --output yaml
114
+ fnm exec -- node server/start.js describe --service myService --output yaml
115
115
 
116
116
  # Specific entity
117
- node server/start.js describe --service myService --model MyModel --output yaml
117
+ fnm exec -- node server/start.js describe --service myService --model MyModel --output yaml
118
118
  ```
119
119
 
120
120
  This is especially useful after using relations (`userItem`, `itemOf`, `propertyOf`) — `describe` shows all the auto-generated views, actions, triggers, and indexes.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Event-sourcing data flow rules — emit events for DB writes, use triggerService for cross-service writes
3
- globs: **/services/**/*.js
3
+ globs: **/services/**/*.js, **/server/**/*.js, server/**/*.js
4
4
  ---
5
5
 
6
6
  # LiveChange backend – event-sourcing data flow (Claude Code)
@@ -155,7 +155,7 @@ Without `waitForEvents: true`, the action returns immediately and events are pro
155
155
  When a model uses relations (`userItem`, `itemOf`, `propertyOf`, etc.), the relations plugin auto-generates CRUD triggers. Use `describe` to discover them:
156
156
 
157
157
  ```bash
158
- node server/start.js describe --service myService --output yaml
158
+ fnm exec -- node server/start.js describe --service myService --output yaml
159
159
  ```
160
160
 
161
161
  Common auto-generated trigger patterns:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Rules for defining models, relations, indexes and access control in LiveChange
3
- globs: **/services/**/*.js
3
+ globs: **/services/**/*.js, **/server/**/*.js, server/**/*.js
4
4
  ---
5
5
 
6
6
  # LiveChange backend – models and relations (Claude Code)
@@ -32,6 +32,25 @@ properties: {
32
32
  }
33
33
  ```
34
34
 
35
+ ## Relation arity (critical)
36
+
37
+ Always separate:
38
+
39
+ - **annotation arity** — can the annotation be a list of configs
40
+ - **parent tuple arity** — can one config include multiple parents/dimensions
41
+
42
+ | Relation | Annotation arity | Parent tuple arity |
43
+ |---|---|---|
44
+ | `propertyOf`, `itemOf`, `boundTo` | single config only | `what` can be one model or `[A, B, ...]` |
45
+ | `relatedTo` | single config or config list | each config uses `what` with one model or `[A, B, ...]` |
46
+ | `propertyOfAny`, `itemOfAny`, `boundToAny` | single config only | `to` can contain one or many names |
47
+ | `relatedToAny` | single config or config list | each config uses `to` with one or many names |
48
+
49
+ Guardrail:
50
+
51
+ - valid: `propertyOf: { what: [A, B] }`
52
+ - invalid: `propertyOf: [configA, configB]`
53
+
35
54
  ## `userItem` – belongs to the signed-in user
36
55
 
37
56
  Use when the model is owned by the currently signed-in user.
@@ -113,7 +132,7 @@ Effects:
113
132
  ## `propertyOf` with multiple parents (1:1 link to each)
114
133
 
115
134
  Sometimes a model is a dedicated 1:1 link between entities (for example: invoice ↔ contractor in a specific role).
116
- Most commonly this is 1–2 parents, but `propertyOf` can point to **any number** of parent models (including 3+), if that matches the domain semantics.
135
+ Most commonly this is 1–2 parents, but `what` can point to **any number** of parent models (including 3+), if that matches the domain semantics.
117
136
 
118
137
  In that case:
119
138
 
@@ -132,10 +151,9 @@ definition.model({
132
151
  properties: {
133
152
  // optional extra fields
134
153
  },
135
- propertyOf: [
136
- { what: CostInvoice },
137
- { what: Contractor }
138
- ]
154
+ propertyOf: {
155
+ what: [CostInvoice, Contractor]
156
+ }
139
157
  })
140
158
  ```
141
159
 
@@ -180,7 +198,7 @@ Relations automatically add **identifier fields** and **indexes** to the model.
180
198
  | `propertyOfAny: { to: ['owner'] }` | `ownerType`, `owner` | `byOwner` (hash) |
181
199
  | `boundTo: { what: Device }` | `device` | `byDevice` (hash) |
182
200
 
183
- For multi-parent relations (e.g. `propertyOf: [{ what: A }, { what: B }]`), all index combinations are created (`byA`, `byB`, `byAAndB`).
201
+ For multi-parent relations (e.g. `propertyOf: { what: [A, B] }`), all index combinations are created (`byA`, `byB`, `byAAndB`).
184
202
 
185
203
  ```js
186
204
  // ✅ Correct — only define YOUR fields
@@ -203,7 +221,7 @@ definition.model({
203
221
  })
204
222
  ```
205
223
 
206
- Use `node server/start.js describe --service myService --model MyModel --output yaml` to see all fields including auto-added ones.
224
+ Use `fnm exec -- node server/start.js describe --service myService --model MyModel --output yaml` (from the app root, with fnm — see `live-change-node-toolchain-fnm`) to see all fields including auto-added ones.
207
225
 
208
226
  ## Indexes
209
227
 
@@ -223,6 +241,45 @@ indexes: {
223
241
 
224
242
  In other services, the same index may be visible with a prefixed name such as `myService_Model_byDeviceAndStatus`.
225
243
 
244
+ ### When index should be outside a model
245
+
246
+ If an index is a union/projection over multiple peer tables (no single natural owner model), do not force it into `model.indexes`.
247
+
248
+ - Use service-level `definition.index(...)` (prefer a dedicated `indexes.js` file).
249
+ - Keep `model.indexes` for indexes semantically owned by one model.
250
+
251
+ ### Function index serialization constraint
252
+
253
+ Function indexes (both `definition.index({ function })` and model `indexes: { name: { function } }`) are **serialized via `toString()`** and run on a remote server. All helpers, mappers, and variables used inside the function **must be defined inside the function body**. References to outer scope (module-level functions, imports, closures) will be `undefined` at runtime.
254
+
255
+ ```js
256
+ // ❌ BROKEN — mapper defined outside, invisible after serialization
257
+ const mapper = obj => ({
258
+ id: obj.name + '_' + obj.id, to: obj.id
259
+ })
260
+ indexes: {
261
+ byDerived: {
262
+ function: async (input, output, { tableName }) => {
263
+ const table = await input.table(tableName)
264
+ await table.map(mapper).to(output) // mapper is undefined!
265
+ }
266
+ }
267
+ }
268
+
269
+ // ✅ CORRECT — mapper defined inside the function
270
+ indexes: {
271
+ byDerived: {
272
+ function: async (input, output, { tableName }) => {
273
+ const mapper = obj => ({
274
+ id: obj.name + '_' + obj.id, to: obj.id
275
+ })
276
+ const table = await input.table(tableName)
277
+ await table.map(mapper).to(output)
278
+ }
279
+ }
280
+ }
281
+ ```
282
+
226
283
  ## Access control on relations
227
284
 
228
285
  - Always set `readAccessControl` and `writeAccessControl` on relations (`userItem`, `itemOf`, `propertyOf`).
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: Stable node:test E2E lifecycle with env, withBrowser and e2eSuite
3
+ globs: **/e2e/**/*.{js,ts}
4
+ ---
5
+
6
+ # Frontend E2E lifecycle
7
+
8
+ Use this pattern in LiveChange E2E tests based on `node:test`.
9
+
10
+ ## Required files
11
+
12
+ - `e2e/env.ts` with `getTestEnv()` and `disposeTestEnv()`
13
+ - `e2e/withBrowser.ts` that closes context/browser in `finally`
14
+ - `e2e/e2eSuite.ts` that wraps each test file in `describe(...)` + `after(...)`
15
+ - `e2e/*.test.ts` wrapped in exactly one `e2eSuite(...)`
16
+
17
+ ## Teardown rule
18
+
19
+ - Never register `after(...process.exit(...))` inside shared `e2e/env.ts`.
20
+ - Register final `process.exit(0)` only in `e2eSuite.ts` after calling `disposeTestEnv()`.
21
+ - Keep startup failure `process.exit(1)` in `getTestEnv()` catch block.
22
+
23
+ ## Minimal suite wrapper
24
+
25
+ ```ts
26
+ import { after, describe } from 'node:test'
27
+ import { disposeTestEnv } from './env.js'
28
+
29
+ export function e2eSuite(name: string, define: () => void): void {
30
+ describe(name, () => {
31
+ after(async () => {
32
+ await disposeTestEnv()
33
+ process.exit(0)
34
+ })
35
+ define()
36
+ })
37
+ }
38
+ ```
39
+
40
+ ## Running commands
41
+
42
+ When running E2E commands, use `fnm exec -- ...` so Node matches `.node-version` / `.nvmrc`.
@@ -0,0 +1,25 @@
1
+ ---
2
+ description: Keep all locale files under front/locales in sync when adding or changing i18n strings
3
+ globs: **/front/locales/**/*.{json,js,ts}, **/front/src/**/*.{vue,js,ts}
4
+ ---
5
+
6
+ # Frontend i18n – every file in `front/locales`
7
+
8
+ When you add or change **user-visible strings** (labels, messages, buttons, errors, page titles, etc.) that go through **vue-i18n** or the project’s locale files, you must update **every** locale resource in that app’s **`front/locales/`** directory — not only one language.
9
+
10
+ ## Required workflow
11
+
12
+ 1. **Identify the app root** for the frontend you are editing (the folder whose tree contains `front/src` and `front/locales`).
13
+ 2. **List all locale files** in `front/locales/` for that app (`en.json`, `pl.json`, `en.js`, `pl.js`, `landing-en.json`, … — whatever exists).
14
+ 3. **Add or update the same keys** in **each** of those files. Structure and nesting must stay consistent across languages (same key paths).
15
+ 4. **Do not** add a new key to a single locale file and leave others missing — that breaks builds or shows raw keys at runtime.
16
+
17
+ ## Translations you are unsure about
18
+
19
+ - Prefer a correct string in the primary locales (e.g. `en` + `pl`) when the product supports them.
20
+ - For other files, use a sensible placeholder, copy from English, or add a clearly marked temporary string — but **still add the key** everywhere so nothing is omitted.
21
+
22
+ ## Scope
23
+
24
+ - Applies to any work under `front/src` that introduces or changes translated text, and to direct edits in `front/locales`.
25
+ - If a project uses multiple locale formats (`.json` and `.js`), update **all** files that participate in i18n for that app, not only one extension.
@@ -7,6 +7,10 @@ globs: **/front/src/**/*.{vue,js,ts}
7
7
 
8
8
  Use these rules when working on frontends that talk to LiveChange backends.
9
9
 
10
+ ## i18n and `front/locales`
11
+
12
+ Whenever you introduce or change translated strings, follow **`live-change-frontend-i18n-locales`**: add the same keys to **all** files in that app’s **`front/locales/`** directory (every language / variant — not only one file).
13
+
10
14
  ## Stack
11
15
 
12
16
  - Vue 3 + TypeScript
@@ -48,6 +52,25 @@ In templates, use the `.value` of these refs:
48
52
  </template>
49
53
  ```
50
54
 
55
+ ## One-time fetches – `useFetch` (not `api.get` with Path)
56
+
57
+ When you need a **one-time fetch** (not a live subscription), use `useFetch`:
58
+
59
+ ```js
60
+ import { usePath, useFetch } from '@live-change/vue3-ssr'
61
+ const path = usePath()
62
+
63
+ const data = await useFetch(path.paperInvoice.invoiceFileInfo({ invoiceFile: fileId }))
64
+ ```
65
+
66
+ **WARNING:** `path.service.view({ params })` returns a **Path object** (with `.what`, `.more`, `.to` properties), not a raw array. Do NOT pass it to `api.get()` — it will silently fail or produce wrong results.
67
+
68
+ | Pattern | Correct? |
69
+ |---|---|
70
+ | `useFetch(path.svc.view({ ... }))` | **Yes** — handles Path objects |
71
+ | `api.get(['svc', 'view', { ... }])` | **Yes** — raw array, low-level |
72
+ | `api.get(path.svc.view({ ... }))` | **NO** — Path object, will break |
73
+
51
74
  ## Commands and forms – choosing the right pattern
52
75
 
53
76
  There are 4 ways to execute backend actions. Use the right one:
@@ -65,6 +88,51 @@ Decision flow:
65
88
  2. Is it editing a model record (create/update)? → **Yes**: use `editorData`. **No**: use `actionData`.
66
89
  3. Only use `<command-form>` for the simplest throwaway cases.
67
90
 
91
+ ## Autosave helpers – `synchronized` and `synchronizedList`
92
+
93
+ Use these helpers when editing reactive data from `live(...)`:
94
+
95
+ - Use `synchronized` for a single editable object.
96
+ - Use `synchronizedList` for editable list rows.
97
+ - Use shared context in `identifiers` and row-level keys in `objectIdentifiers`.
98
+ - For draft payloads, use `updateDataProperty: 'data'`.
99
+
100
+ Treat a screen as an editable list when:
101
+
102
+ - The UI uses `v-for` and each row has editable fields.
103
+ - Users edit many rows inline in one table/config/admin view.
104
+ - Autosave should persist changes row-by-row.
105
+ - Backend actions require shared list context plus row-specific keys.
106
+
107
+ For these cases, create one `synchronizedList(...)` and edit row fields through `syncList.value`.
108
+ Do not maintain a separate `id -> synchronized(...)` map for list rows.
109
+
110
+ ```js
111
+ const sync = synchronized({
112
+ source: sourceRef,
113
+ update: actions.service.updateThing,
114
+ identifiers: { thing: thingId },
115
+ recursive: true,
116
+ autoSave: true,
117
+ debounce: 600
118
+ })
119
+
120
+ const syncList = synchronizedList({
121
+ source: rowsRef,
122
+ update: actions.service.updateRow,
123
+ delete: actions.service.deleteRow,
124
+ identifiers: { object, objectType },
125
+ objectIdentifiers: row => ({ row: row.to, object, objectType }),
126
+ recursive: true
127
+ })
128
+ ```
129
+
130
+ Typical list flows:
131
+
132
+ - role/permission editors,
133
+ - dictionary/configuration tables,
134
+ - multi-row settings pages with inline edits.
135
+
68
136
  ## Form validation feedback
69
137
 
70
138
  Every field in a form using `editorData` or `actionData` **must** show validation errors. Never use bare `InputText`, `Dropdown`, or other PrimeVue inputs without error feedback.
@@ -234,6 +302,34 @@ path.blog.articles({})
234
302
 
235
303
  Access: `article.authorProfile?.firstName`. Works with both `live()` and `RangeViewer`.
236
304
 
305
+ ## Range lists with reactive filters
306
+
307
+ If range list `pathFunction` depends on reactive filters (month/status/search/company), prefer `ReactiveRangeViewer`.
308
+
309
+ Guidelines:
310
+
311
+ - do not rely on mutating `pathFunction` in `RangeViewer`
312
+ - avoid page-local workaround patterns with ad-hoc `:key`
313
+ - use `sourceKey` as an explicit reload trigger
314
+ - if layout stability matters, set `preserveHeightOnReload`
315
+
316
+ ```vue
317
+ <ReactiveRangeViewer
318
+ :pathFunction="transactionsPathRange"
319
+ :sourceKey="JSON.stringify({ accountId, month: filterByMonth ? month : null })"
320
+ :preserveHeightOnReload="true"
321
+ :canLoadTop="false"
322
+ canDropBottom
323
+ />
324
+ ```
325
+
326
+ ## Range cursor guardrails (`RangeViewer` / `rangeBuckets`)
327
+
328
+ - For index-backed list views, backend should expose `sortedIndexRangePath`-based range views.
329
+ - Never override `range.gt/gte/lt/lte` inside frontend `pathFunction`.
330
+ - Keep cursor fields from RangeViewer intact; pass domain filters (`month`, `year`, `status`) as separate params.
331
+ - If filtering seems to require manual cursor rewriting, move logic to backend index design (or `prefixRange` fallback), not frontend hacks.
332
+
237
333
  ## WorkingZone for async actions
238
334
 
239
335
  `ViewRoot` wraps every page in `<WorkingZone>`. Use `inject('workingZone')` for non-form button actions:
@@ -306,12 +402,12 @@ export const sitemap = sitemapEntry(App, createRouter, routerSitemap, config)
306
402
 
307
403
  ## Discovering views and actions with `describe`
308
404
 
309
- Use the CLI `describe` command to find available views (for `live()`) and actions (for `api.command` / `editorData` / `actionData`):
405
+ Use the CLI `describe` command to find available views (for `live()`) and actions (for `api.command` / `editorData` / `actionData`). From the app root, use **fnm exec** so Node matches `.node-version` / `.nvmrc` (see `live-change-node-toolchain-fnm`).
310
406
 
311
407
  ```bash
312
- node server/start.js describe --service blog
313
- node server/start.js describe --service blog --view articlesByCreatedAt --output yaml
314
- node server/start.js describe --service blog --action createMyUserArticle --output yaml
408
+ fnm exec -- node server/start.js describe --service blog
409
+ fnm exec -- node server/start.js describe --service blog --view articlesByCreatedAt --output yaml
410
+ fnm exec -- node server/start.js describe --service blog --action createMyUserArticle --output yaml
315
411
  ```
316
412
 
317
413
  This is the fastest way to discover what paths and actions are available, including those auto-generated by relations.
@@ -0,0 +1,39 @@
1
+ ---
2
+ description: Run Node, npm, npx, tsx with fnm exec using project .node-version or .nvmrc
3
+ ---
4
+
5
+ # Node.js toolchain – use `fnm exec`
6
+
7
+ Do **not** rely on whatever Node.js version the agent sandbox or shell happens to use. That often differs from the project and breaks the LiveChange stack, tests, and `describe`.
8
+
9
+ Use **fnm** so the version from the project dotfiles (`.node-version` or `.nvmrc`) is selected.
10
+
11
+ ## Required pattern
12
+
13
+ Work in the directory that contains the relevant `.node-version` or `.nvmrc` (app or package root). Prefix **every** `node`, `npm`, `npx`, `corepack`, or `tsx` invocation with:
14
+
15
+ ```bash
16
+ fnm exec -- <command and arguments>
17
+ ```
18
+
19
+ Examples:
20
+
21
+ ```bash
22
+ fnm exec -- node server/start.js describe --service myService --output yaml
23
+ fnm exec -- npm test
24
+ fnm exec -- npm run build
25
+ fnm exec -- npx eslint .
26
+ fnm exec -- tsx scripts/example.ts
27
+ ```
28
+
29
+ If the dotfile lives only under a subfolder (e.g. `auto-firma/app/`), `cd` there first, then run `fnm exec -- ...`.
30
+
31
+ ## When this applies
32
+
33
+ - Tests, CI scripts, linters, builds
34
+ - Framework CLI: `describe`, dev servers, migrations, anything touching `@live-change/*`
35
+ - Any `npm` / `node` / `npx` / `tsx` for this monorepo or its subprojects
36
+
37
+ ## If `fnm` is unavailable
38
+
39
+ Tell the user to install fnm or align the environment; do not proceed assuming an arbitrary `node` on `PATH` is correct.
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  description: Rules for LiveChange service directory structure and file organization
3
- globs: **/services/**/*.js
3
+ globs: **/services/**/*.js, **/server/**/*.js, server/**/*.js
4
4
  ---
5
5
 
6
6
  # LiveChange Service Structure
7
7
 
8
8
  Every LiveChange service **must** be a directory, not a single file.
9
9
 
10
- ## Required structure
10
+ ## Required structureW
11
11
 
12
12
  ```
13
13
  server/services/<serviceName>/
@@ -22,7 +22,9 @@
22
22
  "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-event-sourcing.md /home/m8/IdeaProjects/live-change/automation/.claude/rules/live-change-backend-event-sourcing.md)",
23
23
  "Bash(find /home/m8/IdeaProjects/live-change/live-change-stack/framework -type f \\\\\\(-name *.ts -o -name *.js \\\\\\))",
24
24
  "Bash(find /home/m8/IdeaProjects/live-change/live-change-stack/services -type f -name *.js)",
25
- "Bash(grep -r \"userItem\\\\|userProperty\" /home/m8/IdeaProjects/live-change/live-change-stack/services/user-service --include=*.js)"
25
+ "Bash(grep -r \"userItem\\\\|userProperty\" /home/m8/IdeaProjects/live-change/live-change-stack/services/user-service --include=*.js)",
26
+ "Bash(find /home/m8/IdeaProjects/live-change/auto-firma/app -maxdepth 2 -type f \\\\\\(-name jest.config.* -o -name vitest.config.* -o -name *.test.js -o -name *.test.ts \\\\\\))",
27
+ "Bash(find /home/m8/IdeaProjects/live-change/auto-firma/app -maxdepth 3 -type d -not -path */node_modules* -not -path */dist* -not -path */backups* -not -path */dev_docker_home* -not -path */storage* -not -path */tmp.db*)"
26
28
  ],
27
29
  "additionalDirectories": [
28
30
  "/home/m8/IdeaProjects/live-change/.claude/skills/create-skills-and-rules",
@@ -137,6 +137,22 @@ globs: **/front/src/**/*.{vue,js,ts}
137
137
  | `description` | What this rule covers (used for matching) |
138
138
  | `globs` | File patterns that trigger this rule (e.g. `**/*.js`, `**/services/**/*.js`) |
139
139
 
140
+ ### Backend / LiveChange service rules — standard `globs`
141
+
142
+ Rules for **server-side** LiveChange code (models, actions, triggers, service layout) should use a **comma-separated** `globs` line so they still match when a project stores services under different folder names (many teams copy `.cursor/rules` into other repos):
143
+
144
+ ```yaml
145
+ globs: **/services/**/*.js, **/server/**/*.js, server/**/*.js
146
+ ```
147
+
148
+ | Pattern | Covers |
149
+ |---|---|
150
+ | `**/services/**/*.js` | Trees such as `live-change-stack/services/<service>/` |
151
+ | `**/server/**/*.js` | Any nested `server/` directory (e.g. `app/server/`, `packages/foo/server/`) |
152
+ | `server/**/*.js` | Backend rooted at top-level `server/` |
153
+
154
+ Frontend rules keep their own globs (e.g. `**/front/src/**/*.{vue,js,ts}`). Do not drop the `server` variants for backend-only rules — otherwise Cursor will miss files after a layout change.
155
+
140
156
  ### Body structure
141
157
 
142
158
  ```markdown
@@ -181,6 +197,7 @@ alwaysApply: false
181
197
  - Do NOT quote glob patterns in frontmatter
182
198
  - Keep rules short (target 25 lines, max 50 lines for best Cursor performance)
183
199
  - The `.mdc` extension is required for Cursor
200
+ - For **backend** LiveChange rules, use the same **`globs`** standard as in Claude rules: `**/services/**/*.js, **/server/**/*.js, server/**/*.js`
184
201
 
185
202
  ## Step 5 – Register rules in OpenCode (`opencode.json`)
186
203
 
@@ -198,8 +215,12 @@ OpenCode reads `.claude/skills/<name>/SKILL.md` natively for skills (no extra st
198
215
 
199
216
  When you **create a new rule**, add its path to the `instructions` array in `opencode.json`.
200
217
 
218
+ Project rule **`live-change-node-toolchain-fnm`** should stay **first** (or early) in `instructions` so agents always load the requirement to run `node`, `npm`, `npx`, and `tsx` via `fnm exec` using `.node-version` / `.nvmrc`.
219
+
201
220
  When you **create a new skill**, no `opencode.json` change is needed — OpenCode discovers skills from `.claude/skills/<name>/SKILL.md` automatically.
202
221
 
222
+ When a skill or rule shows shell examples that invoke **`node`**, **`npm`**, **`npx`**, or **`tsx`**, use the **`fnm exec -- …`** form (see `.claude/rules/live-change-node-toolchain-fnm.md` and skill `live-change-node-toolchain-fnm`).
223
+
203
224
  **Important:** OpenCode ignores the `globs` frontmatter from Claude Code rules. All instructions listed in `opencode.json` are always loaded.
204
225
 
205
226
  ## Step 6 – Mirror Cursor skills (`.cursor/skills/<name>.md`)
@@ -239,10 +260,12 @@ done
239
260
 
240
261
  ## Checklist
241
262
 
263
+ - [ ] Shell examples for Node/npm use `fnm exec --` per `live-change-node-toolchain-fnm`
242
264
  - [ ] Directory created: `.claude/skills/<name>/SKILL.md`
243
265
  - [ ] Frontmatter has both `name` (matching dir) and `description`
244
266
  - [ ] `.cursor/skills/<name>.md` mirrored (flat file, same content)
245
267
  - [ ] `.claude/rules/*.md` created (if rule)
246
268
  - [ ] `.cursor/rules/*.mdc` created with `globs` + `alwaysApply` (if rule)
269
+ - [ ] Backend rules use standard `globs`: `**/services/**/*.js, **/server/**/*.js, server/**/*.js` (when the rule targets LiveChange server code)
247
270
  - [ ] `opencode.json` `instructions` array updated (if new rule)
248
271
  - [ ] Sub-projects updated (automation, auto-firma)
@@ -112,6 +112,21 @@ definition.trigger({
112
112
 
113
113
  This means: when a user creates a Schedule via the UI or API, the timer is automatically set up. When they update it, the old timer is canceled and a new one created. When they delete it, the timer is canceled.
114
114
 
115
+ ## Cron-service — planning and admin UI guardrails
116
+
117
+ When the domain needs **wall-clock schedules** or **fixed repeating intervals** that run a **trigger**, default to **`@live-change/cron-service`** (models **Schedule** / **Interval**, internal **timer** + **changeCron_*** lifecycle), not ad-hoc timers only.
118
+
119
+ **Backend:** define the **target `definition.trigger`** in your service; put **Schedule** / **Interval** rows in **cron** with **`trigger: { name, service, properties, returnTask }`**. Rely on **`changeCron_Schedule`** / **`changeCron_Interval`** for timer repair (already implemented in cron-service).
120
+
121
+ **Admin / task-frontend-style UI:** use the same integration as the reference pages:
122
+
123
+ - **Create:** `ActionForm` with `service="cron"` and `action="setSchedule"` or `action="setInterval"` (relations-driven forms).
124
+ - **List:** `RangeViewer` + `path.cron.schedules` / `path.cron.intervals` with **`reverseRange(range)`** as needed.
125
+ - **Per row:** `.with()` → `scheduleInfo` / `intervalInfo`, `runState` (`jobType` **`cron_Schedule`** or **`cron_Interval`**, **`job`** = id), and `task.tasksByCauseAndCreatedAt` for recent runs.
126
+ - **Delete:** `api.actions.cron.deleteSchedule` / `deleteInterval`.
127
+
128
+ See **server doc** `15-cron-and-intervals.md` → section **“API used by task-frontend”** for path examples and **Schedule** field semantics (**`NaN`** = “every” for that field).
129
+
115
130
  ## Step 4 – Specific lifecycle triggers (alternative)
116
131
 
117
132
  If you only care about one lifecycle event, use the specific variant:
@@ -0,0 +1,46 @@
1
+ ---
2
+ description: Jak poprawnie wywoływać akcje i widoki frameworka LiveChange przez surowy protokół DAO.
3
+ ---
4
+
5
+ # LiveChange DAO Protocol Arguments
6
+
7
+ Kiedy komunikujesz się z frameworkiem LiveChange używając surowego protokołu `@live-change/dao` (np. z C++, Pythona, Rusta, Go lub dowolnego innego klienta nie-JS), MUSISZ ZAWSZE przekazywać argumenty jako **tablicę**.
8
+
9
+ Framework traktuje argumenty żądań (request) i obserwabli (observable) DAO jak argumenty funkcji i używa operatora spread (`...args`), aby przekazać je do bazowych funkcji akcji lub widoków. Jeśli przekażesz obiekt zamiast tablicy, serwer rzuci błąd `TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function`.
10
+
11
+ Nawet jeśli akcja lub widok oczekuje pojedynczego obiektu jako parametru, ten obiekt MUSI być opakowany w jednoelementową tablicę.
12
+
13
+ ## Przykład w C++ (używając nlohmann/json)
14
+
15
+ ### Niepoprawnie ❌
16
+ ```cpp
17
+ nlohmann::json args = {
18
+ {"pairingKey", "123"},
19
+ {"connectionType", "device"}
20
+ };
21
+ connection->request({"serviceName", "actionName"}, args, settings);
22
+ ```
23
+
24
+ ### Poprawnie ✅
25
+ ```cpp
26
+ // Wrap the object in an array
27
+ auto args = {
28
+ nlohmann::json::object({
29
+ {"pairingKey", "123"},
30
+ {"connectionType", "device"}
31
+ })
32
+ };
33
+ connection->request({"serviceName", "actionName"}, args, settings);
34
+ ```
35
+
36
+ Lub jawnie:
37
+ ```cpp
38
+ nlohmann::json args = nlohmann::json::array({
39
+ nlohmann::json::object({
40
+ {"pairingKey", "123"},
41
+ {"connectionType", "device"}
42
+ })
43
+ });
44
+ ```
45
+
46
+ Zawsze upewnij się, że Twój payload `args` jest tablicą przed wysłaniem go przez połączenie DAO.