@live-change/frontend-template 0.9.201 → 0.9.203

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 (37) hide show
  1. package/.claude/rules/live-change-backend-architecture.md +5 -5
  2. package/.claude/rules/live-change-backend-event-sourcing.md +2 -2
  3. package/.claude/rules/live-change-backend-models-and-relations.md +41 -2
  4. package/.claude/rules/live-change-frontend-e2e-lifecycle.md +42 -0
  5. package/.claude/rules/live-change-frontend-i18n-locales.md +25 -0
  6. package/.claude/rules/live-change-frontend-vue-primevue.md +100 -4
  7. package/.claude/rules/live-change-node-toolchain-fnm.md +39 -0
  8. package/.claude/skills/create-skills-and-rules/SKILL.md +23 -0
  9. package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +6 -0
  10. package/.claude/skills/live-change-frontend-command-forms/SKILL.md +6 -5
  11. package/.claude/skills/live-change-frontend-data-views/SKILL.md +2 -0
  12. package/.claude/skills/live-change-frontend-e2e-lifecycle/SKILL.md +82 -0
  13. package/.claude/skills/live-change-node-toolchain-fnm/SKILL.md +44 -0
  14. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +35 -0
  15. package/.cursor/rules/live-change-backend-models-and-relations.mdc +33 -0
  16. package/.cursor/rules/live-change-frontend-e2e-lifecycle.mdc +15 -0
  17. package/.cursor/rules/live-change-frontend-i18n-locales.mdc +26 -0
  18. package/.cursor/rules/live-change-frontend-vue-primevue.mdc +25 -0
  19. package/.cursor/rules/live-change-node-toolchain-fnm.mdc +40 -0
  20. package/.cursor/skills/create-skills-and-rules.md +23 -0
  21. package/.cursor/skills/live-change-design-actions-views-triggers.md +6 -0
  22. package/.cursor/skills/live-change-frontend-command-forms.md +6 -5
  23. package/.cursor/skills/live-change-frontend-data-views.md +2 -0
  24. package/.cursor/skills/live-change-frontend-e2e-lifecycle.md +82 -0
  25. package/.cursor/skills/live-change-frontend-range-list.md +24 -0
  26. package/.cursor/skills/live-change-node-toolchain-fnm.md +44 -0
  27. package/.node-version +1 -0
  28. package/.nvmrc +1 -0
  29. package/README.md +18 -0
  30. package/e2e/client-session.test.ts +17 -0
  31. package/e2e/e2eSuite.ts +12 -0
  32. package/e2e/env.ts +130 -0
  33. package/e2e/homepage.test.ts +17 -0
  34. package/e2e/steps.ts +3 -0
  35. package/e2e/withBrowser.ts +18 -0
  36. package/opencode.json +4 -1
  37. package/package.json +55 -53
@@ -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)
@@ -203,7 +203,7 @@ definition.model({
203
203
  })
204
204
  ```
205
205
 
206
- Use `node server/start.js describe --service myService --model MyModel --output yaml` to see all fields including auto-added ones.
206
+ 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
207
 
208
208
  ## Indexes
209
209
 
@@ -223,6 +223,45 @@ indexes: {
223
223
 
224
224
  In other services, the same index may be visible with a prefixed name such as `myService_Model_byDeviceAndStatus`.
225
225
 
226
+ ### When index should be outside a model
227
+
228
+ If an index is a union/projection over multiple peer tables (no single natural owner model), do not force it into `model.indexes`.
229
+
230
+ - Use service-level `definition.index(...)` (prefer a dedicated `indexes.js` file).
231
+ - Keep `model.indexes` for indexes semantically owned by one model.
232
+
233
+ ### Function index serialization constraint
234
+
235
+ 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.
236
+
237
+ ```js
238
+ // ❌ BROKEN — mapper defined outside, invisible after serialization
239
+ const mapper = obj => ({
240
+ id: obj.name + '_' + obj.id, to: obj.id
241
+ })
242
+ indexes: {
243
+ byDerived: {
244
+ function: async (input, output, { tableName }) => {
245
+ const table = await input.table(tableName)
246
+ await table.map(mapper).to(output) // mapper is undefined!
247
+ }
248
+ }
249
+ }
250
+
251
+ // ✅ CORRECT — mapper defined inside the function
252
+ indexes: {
253
+ byDerived: {
254
+ function: async (input, output, { tableName }) => {
255
+ const mapper = obj => ({
256
+ id: obj.name + '_' + obj.id, to: obj.id
257
+ })
258
+ const table = await input.table(tableName)
259
+ await table.map(mapper).to(output)
260
+ }
261
+ }
262
+ }
263
+ ```
264
+
226
265
  ## Access control on relations
227
266
 
228
267
  - 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.
@@ -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)
@@ -7,6 +7,12 @@ description: Design actions, views, triggers with indexes and batch processing p
7
7
 
8
8
  Use this skill to design **actions, views, and triggers** in LiveChange services while making good use of indexes and avoiding full-table scans.
9
9
 
10
+ ## Reads vs writes (CQRS-like)
11
+
12
+ **Frontend (Vue):** load data with `usePath` + `live` / `useFetch` on **views**. Do **not** use `api.command` or `useActions()` only to fetch, preview, or compute display-only values — add a `definition.view` on the server and read it on the client.
13
+
14
+ **Backend (services):** **`definition.view`** is the read surface (including computed or preview data). From triggers/actions use **`app.viewGet`** / **`app.serviceViewGet`** when you need the same view layer as the client, or direct model/index reads where appropriate. **Actions and triggers** change state via **`emit`** / **`trigger`** / **`triggerService`**, not via fake “read-only actions.”
15
+
10
16
  ## When to use
11
17
 
12
18
  - You add or change actions on existing models.
@@ -19,15 +19,16 @@ Before using this skill, pick the right approach:
19
19
  |---|---|
20
20
  | `editorData` | **Editing model records** (create/update). Drafts, validation, `AutoField`. See `live-change-frontend-editor-form` skill. |
21
21
  | `actionData` | **One-shot action forms** (not CRUD). Submit once → done. See `live-change-frontend-action-form` skill. |
22
- | `api.command` | **Single button or programmatic calls** (no form fields). This skill, Step 1. |
22
+ | `api.command` | **Mutations only** — single button or programmatic calls that **change persisted state** (no form fields). This skill, Step 1. Not for loading or previewing data (use views + `live` / `useFetch`; see `live-change-frontend-data-views`). |
23
23
  | `<command-form>` | **Avoid.** Legacy. Only for trivial prototypes without drafts or `AutoField`. This skill, Step 2. |
24
24
 
25
25
  **Decision flow:**
26
26
 
27
- 1. Does the user fill in form fields? → **No**: use `api.command` (this skill).
28
- 2. Is it editing a model record? → **Yes**: use `editorData`.
29
- 3. Is it a one-shot action? → **Yes**: use `actionData`.
30
- 4. Only use `<command-form>` for the simplest throwaway cases.
27
+ 1. Do you only need to **read** data (including previews)? → **Use views** (`live` / `useFetch`), not this skill.
28
+ 2. Does the user fill in form fields? → **No**: use `api.command` (this skill) **only if** the operation **mutates** state.
29
+ 3. Is it editing a model record? → **Yes**: use `editorData`.
30
+ 4. Is it a one-shot action? **Yes**: use `actionData`.
31
+ 5. Only use `<command-form>` for the simplest throwaway cases.
31
32
 
32
33
  ## Step 1 – Use `api.command` directly
33
34
 
@@ -14,6 +14,8 @@ Use this skill when you build **reactive data views** using `usePath`, `live`, `
14
14
  - You need to load related objects alongside the main data.
15
15
  - You need to restrict data loading for unauthenticated users.
16
16
 
17
+ **Do not use `api.command` / `useActions()` to load data.** Commands are for **mutations**. For every read (lists, details, previews, “next number”), use **views** with `live` or `useFetch` as in this skill.
18
+
17
19
  ## Step 1 – Basic data loading with computed paths
18
20
 
19
21
  When paths depend on reactive values (route params, props), wrap them in `computed()`:
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: live-change-frontend-e2e-lifecycle
3
+ description: Set up stable node:test E2E lifecycle with env, withBrowser and e2eSuite
4
+ ---
5
+
6
+ # Skill: live-change-frontend-e2e-lifecycle
7
+
8
+ Use this skill when creating or refactoring LiveChange frontend E2E tests.
9
+
10
+ ## Goal
11
+
12
+ Avoid flaky teardown where only the first test file runs because shared env teardown calls `process.exit`.
13
+
14
+ ## Step 1 - Build shared env helper
15
+
16
+ Create `e2e/env.ts` with:
17
+
18
+ - `getTestEnv()` that starts `TestServer` once and memoizes with `envPromise`
19
+ - `disposeTestEnv()` that resets memoized state and disposes server
20
+ - fallback cleanup:
21
+
22
+ ```ts
23
+ process.on('beforeExit', () => {
24
+ void disposeTestEnv()
25
+ })
26
+ ```
27
+
28
+ Keep startup failure `process.exit(1)` inside `getTestEnv()` catch.
29
+
30
+ Do not add `after(...process.exit(...))` to this file.
31
+
32
+ ## Step 2 - Isolate browser per test
33
+
34
+ Create `e2e/withBrowser.ts` with Playwright setup/teardown in `try/finally`:
35
+
36
+ - call `getTestEnv()` once per test execution
37
+ - open browser/context/page
38
+ - close context and browser in `finally`
39
+
40
+ ## Step 3 - Add suite-level teardown
41
+
42
+ Create `e2e/e2eSuite.ts`:
43
+
44
+ ```ts
45
+ import { after, describe } from 'node:test'
46
+ import { disposeTestEnv } from './env.js'
47
+
48
+ export function e2eSuite(name: string, define: () => void): void {
49
+ describe(name, () => {
50
+ after(async () => {
51
+ await disposeTestEnv()
52
+ process.exit(0)
53
+ })
54
+ define()
55
+ })
56
+ }
57
+ ```
58
+
59
+ ## Step 4 - Wrap tests
60
+
61
+ Each `e2e/*.test.ts` file should use one wrapper:
62
+
63
+ ```ts
64
+ import test from 'node:test'
65
+ import { e2eSuite } from './e2eSuite.js'
66
+
67
+ e2eSuite('example-suite', () => {
68
+ test('renders page', async () => {
69
+ // test body
70
+ })
71
+ })
72
+ ```
73
+
74
+ ## Step 5 - Verify
75
+
76
+ Run from project root with `fnm exec`:
77
+
78
+ ```bash
79
+ fnm exec -- npm run e2e
80
+ ```
81
+
82
+ Confirm multiple test files execute and process exits only after full suite teardown.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: live-change-node-toolchain-fnm
3
+ description: Run node, npm, npx, tsx and framework CLI with fnm exec so .node-version and .nvmrc are respected
4
+ ---
5
+
6
+ # Skill: Node toolchain with fnm
7
+
8
+ Use this skill whenever you run **Node**, **npm**, **npx**, **tsx**, or **corepack** in this repo (tests, `describe`, dev servers, scripts). Agents must not use the default sandbox Node; use **fnm exec** so the version from dotfiles applies.
9
+
10
+ ## When to use
11
+
12
+ - Running `npm test`, `npm run …`, linters, builds
13
+ - `node server/start.js describe` or any framework entry
14
+ - `tsx` for TypeScript scripts
15
+ - Any subprocess that would invoke `node` or `npm` for a project with `.node-version` / `.nvmrc`
16
+
17
+ ## Step 1 – Find the right directory
18
+
19
+ Locate the nearest project root that has `.node-version` or `.nvmrc` for the task (often the app folder, e.g. `auto-firma/app/`).
20
+
21
+ `cd` to that directory before running commands so fnm reads the correct file.
22
+
23
+ ## Step 2 – Prefix with fnm exec
24
+
25
+ ```bash
26
+ fnm exec -- node server/start.js describe --service myService --output yaml
27
+ fnm exec -- npm test
28
+ fnm exec -- npx vitest
29
+ fnm exec -- tsx ./tools/something.ts
30
+ ```
31
+
32
+ The part after `--` is the same command you would run manually with the correct Node active.
33
+
34
+ ## Step 3 – Nested monorepo paths
35
+
36
+ If you are in the repo root but the dotfile is only under `some-app/`:
37
+
38
+ ```bash
39
+ cd some-app && fnm exec -- npm test
40
+ ```
41
+
42
+ ## If fnm is missing
43
+
44
+ Do not fall back to bare `node` / `npm` for framework work. Report that fnm is required (or document a one-off alternative the user approved).
@@ -138,6 +138,41 @@ definition.view({
138
138
  })
139
139
  ```
140
140
 
141
+ ### Widoki zakresowe: prefiksy indeksów i opcjonalne filtry
142
+
143
+ W `sortedIndexRangePath(indexName, keyPrefix, range)` najpierw działa `keyPrefix`, a dopiero potem `range` (`gt/gte/lt/lte`) na pełnym kluczu indeksu.
144
+
145
+ To oznacza:
146
+
147
+ - nie przekazuj surowych wartości pola (np. samej daty miesiąca) do `gt/lt`, jeśli klucz zaczyna się od innych części
148
+ - filtr domenowy (`month`, `state`, `company`) przekazuj jako osobny parametr widoku
149
+ - `range` zostaw do paginacji/cursora (RangeViewer i podobne mechanizmy)
150
+
151
+ ```js
152
+ definition.view({
153
+ name: 'bankTransactionsByBankAccountAndDate',
154
+ properties: {
155
+ bankAccount: { type: String },
156
+ month: { type: String },
157
+ ...App.rangeProperties
158
+ },
159
+ async daoPath({ bankAccount, month, ...props }) {
160
+ const range = App.extractRange(props)
161
+ if(month) {
162
+ const prefix = [bankAccount, month].map(v => JSON.stringify(v)).join(':')
163
+ return BankTransaction.rangePath(App.utils.prefixRange(range, prefix, prefix + ':'))
164
+ }
165
+ return BankTransaction.sortedIndexRangePath('byBankAccountAndDate', [bankAccount], range)
166
+ }
167
+ })
168
+ ```
169
+
170
+ Jeśli filtr po miesiącu jest częsty, lepiej dodać indeks z bucketem miesiąca (`byBankAccountAndMonthAndDate`) i użyć:
171
+
172
+ ```js
173
+ BankTransaction.sortedIndexRangePath('byBankAccountAndMonthAndDate', [bankAccount, month], range)
174
+ ```
175
+
141
176
  ## Triggery – online/offline i batchowanie
142
177
 
143
178
  - Triggery są do reakcji na zdarzenia (np. zmiana stanu sesji, start serwera).