@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.
- package/.claude/rules/live-change-backend-architecture.md +5 -5
- package/.claude/rules/live-change-backend-event-sourcing.md +2 -2
- package/.claude/rules/live-change-backend-models-and-relations.md +41 -2
- package/.claude/rules/live-change-frontend-e2e-lifecycle.md +42 -0
- package/.claude/rules/live-change-frontend-i18n-locales.md +25 -0
- package/.claude/rules/live-change-frontend-vue-primevue.md +100 -4
- package/.claude/rules/live-change-node-toolchain-fnm.md +39 -0
- package/.claude/skills/create-skills-and-rules/SKILL.md +23 -0
- package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +6 -0
- package/.claude/skills/live-change-frontend-command-forms/SKILL.md +6 -5
- package/.claude/skills/live-change-frontend-data-views/SKILL.md +2 -0
- package/.claude/skills/live-change-frontend-e2e-lifecycle/SKILL.md +82 -0
- package/.claude/skills/live-change-node-toolchain-fnm/SKILL.md +44 -0
- package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +35 -0
- package/.cursor/rules/live-change-backend-models-and-relations.mdc +33 -0
- package/.cursor/rules/live-change-frontend-e2e-lifecycle.mdc +15 -0
- package/.cursor/rules/live-change-frontend-i18n-locales.mdc +26 -0
- package/.cursor/rules/live-change-frontend-vue-primevue.mdc +25 -0
- package/.cursor/rules/live-change-node-toolchain-fnm.mdc +40 -0
- package/.cursor/skills/create-skills-and-rules.md +23 -0
- package/.cursor/skills/live-change-design-actions-views-triggers.md +6 -0
- package/.cursor/skills/live-change-frontend-command-forms.md +6 -5
- package/.cursor/skills/live-change-frontend-data-views.md +2 -0
- package/.cursor/skills/live-change-frontend-e2e-lifecycle.md +82 -0
- package/.cursor/skills/live-change-frontend-range-list.md +24 -0
- package/.cursor/skills/live-change-node-toolchain-fnm.md +44 -0
- package/.node-version +1 -0
- package/.nvmrc +1 -0
- package/README.md +18 -0
- package/e2e/client-session.test.ts +17 -0
- package/e2e/e2eSuite.ts +12 -0
- package/e2e/env.ts +130 -0
- package/e2e/homepage.test.ts +17 -0
- package/e2e/steps.ts +3 -0
- package/e2e/withBrowser.ts +18 -0
- package/opencode.json +4 -1
- 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` | **
|
|
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.
|
|
28
|
-
2.
|
|
29
|
-
3. Is it a
|
|
30
|
-
4.
|
|
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).
|