@live-change/frontend-template 0.9.198 → 0.9.200

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 (71) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +246 -0
  2. package/.claude/rules/live-change-backend-architecture.md +126 -0
  3. package/.claude/rules/live-change-backend-event-sourcing.md +186 -0
  4. package/.claude/rules/live-change-backend-models-and-relations.md +260 -0
  5. package/.claude/rules/live-change-frontend-vue-primevue.md +317 -0
  6. package/.claude/rules/live-change-service-structure.md +89 -0
  7. package/.claude/settings.json +32 -0
  8. package/.claude/skills/create-skills-and-rules/SKILL.md +248 -0
  9. package/.claude/skills/create-skills-and-rules.md +196 -0
  10. package/.claude/skills/live-change-backend-change-triggers/SKILL.md +186 -0
  11. package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +462 -0
  12. package/.claude/skills/live-change-design-actions-views-triggers.md +190 -0
  13. package/.claude/skills/live-change-design-models-relations/SKILL.md +230 -0
  14. package/.claude/skills/live-change-design-models-relations.md +173 -0
  15. package/.claude/skills/live-change-design-service/SKILL.md +133 -0
  16. package/.claude/skills/live-change-design-service.md +132 -0
  17. package/.claude/skills/live-change-frontend-accessible-objects/SKILL.md +384 -0
  18. package/.claude/skills/live-change-frontend-accessible-objects.md +383 -0
  19. package/.claude/skills/live-change-frontend-action-buttons/SKILL.md +129 -0
  20. package/.claude/skills/live-change-frontend-action-buttons.md +128 -0
  21. package/.claude/skills/live-change-frontend-action-form/SKILL.md +149 -0
  22. package/.claude/skills/live-change-frontend-action-form.md +143 -0
  23. package/.claude/skills/live-change-frontend-analytics/SKILL.md +147 -0
  24. package/.claude/skills/live-change-frontend-analytics.md +146 -0
  25. package/.claude/skills/live-change-frontend-command-forms/SKILL.md +216 -0
  26. package/.claude/skills/live-change-frontend-command-forms.md +215 -0
  27. package/.claude/skills/live-change-frontend-data-views/SKILL.md +183 -0
  28. package/.claude/skills/live-change-frontend-data-views.md +182 -0
  29. package/.claude/skills/live-change-frontend-editor-form/SKILL.md +240 -0
  30. package/.claude/skills/live-change-frontend-editor-form.md +177 -0
  31. package/.claude/skills/live-change-frontend-locale-time/SKILL.md +172 -0
  32. package/.claude/skills/live-change-frontend-locale-time.md +171 -0
  33. package/.claude/skills/live-change-frontend-page-list-detail/SKILL.md +201 -0
  34. package/.claude/skills/live-change-frontend-page-list-detail.md +200 -0
  35. package/.claude/skills/live-change-frontend-range-list/SKILL.md +129 -0
  36. package/.claude/skills/live-change-frontend-range-list.md +128 -0
  37. package/.claude/skills/live-change-frontend-ssr-setup/SKILL.md +119 -0
  38. package/.claude/skills/live-change-frontend-ssr-setup.md +118 -0
  39. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +290 -0
  40. package/.cursor/rules/live-change-backend-architecture.mdc +131 -0
  41. package/.cursor/rules/live-change-backend-event-sourcing.mdc +185 -0
  42. package/.cursor/rules/live-change-backend-models-and-relations.mdc +256 -0
  43. package/.cursor/rules/live-change-frontend-vue-primevue.mdc +290 -0
  44. package/.cursor/rules/live-change-service-structure.mdc +107 -0
  45. package/.cursor/skills/create-skills-and-rules.md +248 -0
  46. package/.cursor/skills/live-change-backend-change-triggers.md +186 -0
  47. package/.cursor/skills/live-change-design-actions-views-triggers.md +296 -0
  48. package/.cursor/skills/live-change-design-models-relations.md +230 -0
  49. package/.cursor/skills/live-change-design-service.md +76 -0
  50. package/.cursor/skills/live-change-frontend-accessible-objects.md +384 -0
  51. package/.cursor/skills/live-change-frontend-action-buttons.md +129 -0
  52. package/.cursor/skills/live-change-frontend-action-form.md +149 -0
  53. package/.cursor/skills/live-change-frontend-analytics.md +147 -0
  54. package/.cursor/skills/live-change-frontend-command-forms.md +216 -0
  55. package/.cursor/skills/live-change-frontend-data-views.md +183 -0
  56. package/.cursor/skills/live-change-frontend-editor-form.md +240 -0
  57. package/.cursor/skills/live-change-frontend-locale-time.md +172 -0
  58. package/.cursor/skills/live-change-frontend-page-list-detail.md +201 -0
  59. package/.cursor/skills/live-change-frontend-range-list.md +129 -0
  60. package/.cursor/skills/live-change-frontend-ssr-setup.md +120 -0
  61. package/README.md +71 -0
  62. package/front/src/router.js +2 -1
  63. package/opencode.json +10 -0
  64. package/package.json +52 -50
  65. package/server/app.config.js +35 -0
  66. package/server/services.list.js +2 -0
  67. package/.nx/workspace-data/file-map.json +0 -195
  68. package/.nx/workspace-data/nx_files.nxt +0 -0
  69. package/.nx/workspace-data/project-graph.json +0 -8
  70. package/.nx/workspace-data/project-graph.lock +0 -0
  71. package/.nx/workspace-data/source-maps.json +0 -1
@@ -0,0 +1,260 @@
1
+ ---
2
+ description: Rules for defining models, relations, indexes and access control in LiveChange
3
+ globs: **/services/**/*.js
4
+ ---
5
+
6
+ # LiveChange backend – models and relations (Claude Code)
7
+
8
+ Use these rules when defining or editing models and relations in LiveChange services.
9
+
10
+ ## General style for models
11
+
12
+ - Put models in domain files imported from the service `index.js`.
13
+ - Prefer **readable, multi-line** property definitions.
14
+ - Avoid squeezing `type`, `default`, `validation` into a single unreadable line.
15
+
16
+ ```js
17
+ properties: {
18
+ name: {
19
+ type: String,
20
+ validation: ['nonEmpty']
21
+ },
22
+ status: {
23
+ type: String,
24
+ default: 'offline'
25
+ },
26
+ capabilities: {
27
+ type: Array,
28
+ of: {
29
+ type: String
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## `userItem` – belongs to the signed-in user
36
+
37
+ Use when the model is owned by the currently signed-in user.
38
+
39
+ ```js
40
+ definition.model({
41
+ name: 'Device',
42
+ properties: {
43
+ name: {
44
+ type: String
45
+ }
46
+ },
47
+ userItem: {
48
+ readAccessControl: { roles: ['owner', 'admin'] },
49
+ writeAccessControl: { roles: ['owner', 'admin'] },
50
+ writeableProperties: ['name']
51
+ }
52
+ })
53
+ ```
54
+
55
+ This automatically generates:
56
+
57
+ - “my X” views (list + single),
58
+ - basic CRUD actions for the owner.
59
+
60
+ ## `itemOf` – child belongs to a parent
61
+
62
+ Use for lists of items related to another model.
63
+
64
+ ```js
65
+ definition.model({
66
+ name: 'DeviceConnection',
67
+ properties: {
68
+ connectionType: {
69
+ type: String
70
+ },
71
+ status: {
72
+ type: String,
73
+ default: 'offline'
74
+ }
75
+ },
76
+ itemOf: {
77
+ what: Device,
78
+ readAccessControl: { roles: ['owner', 'admin'] },
79
+ writeAccessControl: { roles: ['owner', 'admin'] }
80
+ }
81
+ })
82
+ ```
83
+
84
+ Guidelines:
85
+
86
+ - `what` must point to a model defined earlier (in this or another service).
87
+ - The relation generates standard views/actions for listing and managing children.
88
+
89
+ ## `propertyOf` – one-to-one property, id = parent id
90
+
91
+ Use when the model represents a single “state object” for a parent, with the same id.
92
+
93
+ ```js
94
+ definition.model({
95
+ name: 'DeviceCursorState',
96
+ properties: {
97
+ x: { type: Number },
98
+ y: { type: Number }
99
+ },
100
+ propertyOf: {
101
+ what: Device,
102
+ readAccessControl: { roles: ['owner', 'admin'] },
103
+ writeAccessControl: { roles: ['owner', 'admin'] }
104
+ }
105
+ })
106
+ ```
107
+
108
+ Effects:
109
+
110
+ - You can fetch it directly as `DeviceCursorState.get(deviceId)`.
111
+ - No need for an extra `device` field or index for lookups by parent id.
112
+
113
+ ## `propertyOf` with multiple parents (1:1 link to each)
114
+
115
+ 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.
117
+
118
+ In that case:
119
+
120
+ - avoid storing the “other side id” as a plain `contractorId` / `someId` property
121
+ - avoid adding ad-hoc `...Id` fields in a relation model just to “join” entities — CRUD/relations generators won’t treat it as a relation
122
+ - instead, define the relation as `propertyOf` to **each** parent so the relations/CRUD generator understands the model is connecting entities.
123
+
124
+ Example (schematic):
125
+
126
+ ```js
127
+ const CostInvoice = definition.foreignModel('invoice', 'CostInvoice')
128
+ const Contractor = definition.foreignModel('company', 'Contractor')
129
+
130
+ definition.model({
131
+ name: 'Supplier',
132
+ properties: {
133
+ // optional extra fields
134
+ },
135
+ propertyOf: [
136
+ { what: CostInvoice },
137
+ { what: Contractor }
138
+ ]
139
+ })
140
+ ```
141
+
142
+ ## `foreignModel` – parent from another service
143
+
144
+ Use when `itemOf` / `propertyOf` refers to a model from a different service.
145
+
146
+ ```js
147
+ const Device = definition.foreignModel('deviceManager', 'Device')
148
+
149
+ definition.model({
150
+ name: 'BotSession',
151
+ properties: {
152
+ // ...
153
+ },
154
+ itemOf: {
155
+ what: Device,
156
+ readAccessControl: { roles: ['owner', 'admin'] }
157
+ }
158
+ })
159
+ ```
160
+
161
+ Notes:
162
+
163
+ - First argument is the service name.
164
+ - Second is the model name in that service.
165
+
166
+ ## Auto-added fields from relations
167
+
168
+ Relations automatically add **identifier fields** and **indexes** to the model. Do **not** re-declare these in `properties`.
169
+
170
+ **Naming convention:** field name = parent model name with first letter lowercased (`Device` → `device`, `CostInvoice` → `costInvoice`).
171
+
172
+ | Relation | Field(s) auto-added | Index(es) auto-added |
173
+ |---|---|---|
174
+ | `itemOf: { what: Device }` | `device` | `byDevice` |
175
+ | `propertyOf: { what: Device }` | `device` | `byDevice` |
176
+ | `userItem` | `user` | `byUser` |
177
+ | `userProperty` | `user` | `byUser` |
178
+ | `sessionOrUserProperty` | `sessionOrUserType`, `sessionOrUser` | `bySessionOrUser` (hash) |
179
+ | `sessionOrUserProperty: { extendedWith: ['object'] }` | + `objectType`, `object` | composite indexes |
180
+ | `propertyOfAny: { to: ['owner'] }` | `ownerType`, `owner` | `byOwner` (hash) |
181
+ | `boundTo: { what: Device }` | `device` | `byDevice` (hash) |
182
+
183
+ For multi-parent relations (e.g. `propertyOf: [{ what: A }, { what: B }]`), all index combinations are created (`byA`, `byB`, `byAAndB`).
184
+
185
+ ```js
186
+ // ✅ Correct — only define YOUR fields
187
+ definition.model({
188
+ name: 'Connection',
189
+ properties: {
190
+ status: { type: String } // 'device' is NOT here — auto-added by itemOf
191
+ },
192
+ itemOf: { what: Device } // adds 'device' field + 'byDevice' index
193
+ })
194
+
195
+ // ❌ Wrong — redundant field
196
+ definition.model({
197
+ name: 'Connection',
198
+ properties: {
199
+ device: { type: String }, // ❌ already added by itemOf
200
+ status: { type: String }
201
+ },
202
+ itemOf: { what: Device }
203
+ })
204
+ ```
205
+
206
+ Use `node server/start.js describe --service myService --model MyModel --output yaml` to see all fields including auto-added ones.
207
+
208
+ ## Indexes
209
+
210
+ - Declare indexes explicitly when you frequently query by a field or field combination.
211
+ - Use descriptive names like `bySessionKey`, `byDeviceAndStatus`, etc.
212
+
213
+ ```js
214
+ indexes: {
215
+ bySessionKey: {
216
+ property: ['sessionKey']
217
+ },
218
+ byDeviceAndStatus: {
219
+ property: ['device', 'status']
220
+ }
221
+ }
222
+ ```
223
+
224
+ In other services, the same index may be visible with a prefixed name such as `myService_Model_byDeviceAndStatus`.
225
+
226
+ ## Access control on relations
227
+
228
+ - Always set `readAccessControl` and `writeAccessControl` on relations (`userItem`, `itemOf`, `propertyOf`).
229
+ - Treat access control as part of the model definition, not an afterthought.
230
+
231
+ ## `entity` models – granting access on creation
232
+
233
+ Models with `entity` and `writeAccessControl` / `readAccessControl` check roles on every CRUD operation, but do **not** auto-grant roles to the creator. You must add a change trigger to grant the creator `'owner'` (or other roles) after creation:
234
+
235
+ ```js
236
+ definition.trigger({
237
+ name: 'changeMyService_MyModel',
238
+ properties: {
239
+ object: { type: MyModel, validation: ['nonEmpty'] },
240
+ data: { type: Object },
241
+ oldData: { type: Object }
242
+ },
243
+ async execute({ object, data, oldData }, { client, triggerService }) {
244
+ if (!data || oldData) return // only on create
245
+ if (!client?.user) return
246
+
247
+ await triggerService({ service: 'accessControl', type: 'accessControl_setAccess' }, {
248
+ objectType: 'myService_MyModel',
249
+ object,
250
+ roles: ['owner'],
251
+ sessionOrUserType: 'user_User',
252
+ sessionOrUser: client.user,
253
+ lastUpdate: new Date()
254
+ })
255
+ }
256
+ })
257
+ ```
258
+
259
+ Without this trigger, the creator cannot read or modify their own object. The `objectType` format is `serviceName_ModelName`.
260
+
@@ -0,0 +1,317 @@
1
+ ---
2
+ description: Rules for Vue 3, PrimeVue, Tailwind frontend development on LiveChange
3
+ globs: **/front/src/**/*.{vue,js,ts}
4
+ ---
5
+
6
+ # Frontend on live-change-stack – Vue 3 + PrimeVue + Tailwind (Claude Code)
7
+
8
+ Use these rules when working on frontends that talk to LiveChange backends.
9
+
10
+ ## Stack
11
+
12
+ - Vue 3 + TypeScript
13
+ - PrimeVue 4 for UI components
14
+ - Tailwind CSS for styling (prefer utility classes, avoid unnecessary custom CSS)
15
+ - vite-plugin-pages for file-based routing in `src/pages/`
16
+ - `@live-change/vue3-ssr` for integration with the backend
17
+
18
+ ## Data loading – `live` + `Promise.all` (Suspense)
19
+
20
+ - **Do not** use `ref(null)` + `onMounted` to fetch data.
21
+ - Always fetch data using `await Promise.all([...])` and `live(path()...)` in `script setup`.
22
+ - The root app should wrap pages with `<Suspense>` (usually handled by `ViewRoot` in live-change frontends).
23
+
24
+ Example:
25
+
26
+ ```js
27
+ import { path, live, api as useApi } from '@live-change/vue3-ssr'
28
+
29
+ const api = useApi()
30
+
31
+ const [devices] = await Promise.all([
32
+ live(path().deviceManager.myUserDevices({}))
33
+ ])
34
+
35
+ const [device, connections] = await Promise.all([
36
+ live(path().deviceManager.myUserDevice({ device: deviceId })),
37
+ live(path().deviceManager.deviceOwnedDeviceConnections({ device: deviceId }))
38
+ ])
39
+ ```
40
+
41
+ In templates, use the `.value` of these refs:
42
+
43
+ ```vue
44
+ <template>
45
+ <div v-if="device.value">
46
+ {{ device.value.name }}
47
+ </div>
48
+ </template>
49
+ ```
50
+
51
+ ## Commands and forms – choosing the right pattern
52
+
53
+ There are 4 ways to execute backend actions. Use the right one:
54
+
55
+ | Pattern | When to use |
56
+ |---|---|
57
+ | `editorData` | **Editing model records** (create/update). Drafts, validation, `AutoField`. Use for settings, editors, profiles. |
58
+ | `actionData` | **One-shot action forms** (not CRUD). Submit once → done. Use for publish, invite, import. |
59
+ | `api.command` | **Single button or programmatic calls** (no form fields). Use for delete, toggle, code-triggered actions. |
60
+ | `<command-form>` | **Avoid.** Legacy, only for trivial prototypes. Prefer `editorData` or `actionData`. |
61
+
62
+ Decision flow:
63
+
64
+ 1. Does the user fill in form fields? → **No**: use `api.command` (wrap in `workingZone.addPromise` for buttons).
65
+ 2. Is it editing a model record (create/update)? → **Yes**: use `editorData`. **No**: use `actionData`.
66
+ 3. Only use `<command-form>` for the simplest throwaway cases.
67
+
68
+ ## Form validation feedback
69
+
70
+ 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.
71
+
72
+ Three approaches (pick whichever fits the layout):
73
+
74
+ 1. **AutoField without slot** — auto-picks input and shows errors. Simplest, use by default.
75
+ 2. **AutoField with slot** — wrap a custom input inside `<AutoField>`. Still renders label + error automatically.
76
+ 3. **Manual `Message`** — add `<Message v-if="editor.propertiesErrors?.field" severity="error" variant="simple" size="small">` below the input. Use when AutoField wrapper doesn't fit.
77
+
78
+ Always pass `:error="editor.propertiesErrors?.fieldName"` (or `formData.propertiesErrors?.fieldName` for `actionData`).
79
+
80
+ ## Form element requirement
81
+
82
+ Forms using `editorData` or `actionData` with `EditorButtons` or `ActionButtons` **must** be wrapped in a `<form>` element with submit/reset handlers:
83
+
84
+ ```vue
85
+ <!-- editorData -->
86
+ <form @submit.prevent="editor.save()" @reset.prevent="editor.reset()">
87
+
88
+ <!-- actionData -->
89
+ <form @submit.prevent="formData.submit()" @reset.prevent="formData.reset()">
90
+ ```
91
+
92
+ `EditorButtons` and `ActionButtons` use `type="submit"` / `type="reset"` on their internal buttons. Without a `<form>` parent, these buttons do nothing.
93
+
94
+ ### `api.command`
95
+
96
+ ```js
97
+ await api.command(['deviceManager', 'createMyUserDevice'], {
98
+ name: 'My device'
99
+ })
100
+
101
+ await api.command(['deviceManager', 'deleteMyUserDevice'], {
102
+ device: id
103
+ })
104
+ ```
105
+
106
+ Format:
107
+
108
+ - `['serviceName', 'actionName']` as the first argument,
109
+ - payload object as the second argument.
110
+
111
+ ## Routing – `<route>` block and `meta.signedIn`
112
+
113
+ - Each page in `src/pages/` can declare its route meta in a `<route>` block.
114
+ - Use `meta.signedIn` for pages that require authentication.
115
+
116
+ ```vue
117
+ <route>
118
+ { "name": "devices", "meta": { "signedIn": true } }
119
+ </route>
120
+ ```
121
+
122
+ Dynamic routes:
123
+
124
+ - `[id].vue` corresponds to `/devices/:id`.
125
+
126
+ ```js
127
+ import { useRoute } from 'vue-router'
128
+
129
+ const route = useRoute()
130
+ const deviceId = route.params.id
131
+ ```
132
+
133
+ ## Confirm + Toast for destructive actions
134
+
135
+ ```js
136
+ import { useConfirm } from 'primevue/useconfirm'
137
+ import { useToast } from 'primevue/usetoast'
138
+
139
+ const confirm = useConfirm()
140
+ const toast = useToast()
141
+
142
+ function deleteDevice(id) {
143
+ confirm.require({
144
+ message: 'Are you sure you want to delete this device?',
145
+ header: 'Confirmation',
146
+ icon: 'pi pi-exclamation-triangle',
147
+ accept: async () => {
148
+ await api.command(['deviceManager', 'deleteMyUserDevice'], { device: id })
149
+ toast.add({
150
+ severity: 'success',
151
+ summary: 'Deleted',
152
+ life: 2000
153
+ })
154
+ }
155
+ })
156
+ }
157
+ ```
158
+
159
+ ## Common UI patterns – list and detail
160
+
161
+ ### List page
162
+
163
+ ```vue
164
+ <template>
165
+ <div class="container mx-auto p-4">
166
+ <div class="flex items-center justify-between mb-6">
167
+ <h1 class="text-2xl font-bold">Devices</h1>
168
+ <Button label="Add" icon="pi pi-plus" @click="openDialog" />
169
+ </div>
170
+
171
+ <Card v-if="devices.value?.length === 0">
172
+ <template #content>
173
+ <p class="text-center text-gray-500">
174
+ No devices yet
175
+ </p>
176
+ </template>
177
+ </Card>
178
+
179
+ <div class="grid gap-4">
180
+ <Card v-for="device in devices.value" :key="device.id">
181
+ <template #content>
182
+ <!-- content -->
183
+ </template>
184
+ </Card>
185
+ </div>
186
+ </div>
187
+ </template>
188
+ ```
189
+
190
+ ### Status tag
191
+
192
+ ```vue
193
+ <Tag
194
+ :value="conn.status"
195
+ :severity="conn.status === 'online' ? 'success' : 'secondary'"
196
+ />
197
+ ```
198
+
199
+ ## Computed paths – reactive parameters
200
+
201
+ When paths depend on reactive values (route params, props), wrap them in `computed()`:
202
+
203
+ ```js
204
+ import { computed, unref } from 'vue'
205
+ import { usePath, live } from '@live-change/vue3-ssr'
206
+
207
+ const path = usePath()
208
+
209
+ const articlePath = computed(() => path.blog.article({ article: unref(articleId) }))
210
+ const [article] = await Promise.all([live(articlePath)])
211
+ ```
212
+
213
+ For conditional loading (e.g. only when logged in), return a falsy value:
214
+
215
+ ```js
216
+ import { useClient } from '@live-change/vue3-ssr'
217
+ const client = useClient()
218
+
219
+ const myDataPath = computed(() => client.value.user && path.blog.myArticles({}))
220
+ const [myData] = await Promise.all([live(myDataPath)])
221
+ ```
222
+
223
+ ## Related data – `.with()`
224
+
225
+ Attach related objects to items in a single reactive query:
226
+
227
+ ```js
228
+ path.blog.articles({})
229
+ .with(article => path.userIdentification.identification({
230
+ sessionOrUserType: article.authorType,
231
+ sessionOrUser: article.author
232
+ }).bind('authorProfile'))
233
+ ```
234
+
235
+ Access: `article.authorProfile?.firstName`. Works with both `live()` and `RangeViewer`.
236
+
237
+ ## WorkingZone for async actions
238
+
239
+ `ViewRoot` wraps every page in `<WorkingZone>`. Use `inject('workingZone')` for non-form button actions:
240
+
241
+ ```js
242
+ import { inject } from 'vue'
243
+ const workingZone = inject('workingZone')
244
+
245
+ function doAction() {
246
+ workingZone.addPromise('actionName', (async () => {
247
+ await actions.blog.publishArticle({ article: id })
248
+ toast.add({ severity: 'success', summary: 'Published', life: 2000 })
249
+ })())
250
+ }
251
+ ```
252
+
253
+ This activates the global loading spinner/blur while the promise is pending.
254
+
255
+ ## Auth guards with `useClient`
256
+
257
+ ```js
258
+ import { useClient } from '@live-change/vue3-ssr'
259
+ const client = useClient()
260
+ ```
261
+
262
+ - `client.value.user` – truthy when logged in
263
+ - `client.value.roles` – array of roles (e.g. `['admin', 'owner']`)
264
+
265
+ Use in templates:
266
+
267
+ ```vue
268
+ <Button v-if="client.roles.includes('admin')" label="Admin" />
269
+ <div v-if="!client.user">Please sign in</div>
270
+ ```
271
+
272
+ ## Locale and time
273
+
274
+ - Call `useLocale().captureLocale()` in `App.vue` to save browser locale to backend.
275
+ - Use `locale.localTime(date)` with vue-i18n's `d()` for SSR-safe date display.
276
+ - `currentTime` from `@live-change/frontend-base` is a reactive ref that ticks every 500ms.
277
+ - `useTimeSynchronization()` from `@live-change/vue3-ssr` corrects clock skew – use when timing is critical (countdowns, real-time events).
278
+
279
+ ## Analytics
280
+
281
+ Use `analytics` from `@live-change/vue3-components`:
282
+
283
+ ```js
284
+ import { analytics } from '@live-change/vue3-components'
285
+ analytics.emit('article:published', { articleId: id })
286
+ ```
287
+
288
+ Wire providers (PostHog, GA4) in a separate file imported from `App.vue`.
289
+
290
+ ## SSR and frontend configuration
291
+
292
+ - Keep separate entry points for client and server:
293
+
294
+ ```js
295
+ // entry-client.js
296
+ import { clientEntry } from '@live-change/frontend-base/client-entry.js'
297
+ export default clientEntry(App, createRouter, config)
298
+
299
+ // entry-server.js
300
+ import { serverEntry, sitemapEntry } from '@live-change/frontend-base/server-entry.js'
301
+ export const render = serverEntry(App, createRouter, config)
302
+ export const sitemap = sitemapEntry(App, createRouter, routerSitemap, config)
303
+ ```
304
+
305
+ - Configure PrimeVue theme in one place (e.g. `config.js`) using `definePreset` and keep dark mode options consistent.
306
+
307
+ ## Discovering views and actions with `describe`
308
+
309
+ Use the CLI `describe` command to find available views (for `live()`) and actions (for `api.command` / `editorData` / `actionData`):
310
+
311
+ ```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
315
+ ```
316
+
317
+ This is the fastest way to discover what paths and actions are available, including those auto-generated by relations.
@@ -0,0 +1,89 @@
1
+ ---
2
+ description: Rules for LiveChange service directory structure and file organization
3
+ globs: **/services/**/*.js
4
+ ---
5
+
6
+ # LiveChange Service Structure
7
+
8
+ Every LiveChange service **must** be a directory, not a single file.
9
+
10
+ ## Required structure
11
+
12
+ ```
13
+ server/services/<serviceName>/
14
+ definition.js # creates app.createServiceDefinition({ name }) – nothing else
15
+ index.js # imports definition, imports all domain files, exports definition
16
+ config.js # optional – reads definition.config, exports resolved config object
17
+ <domain>.js # one file per domain area (models, views, actions, triggers)
18
+ ```
19
+
20
+ ## definition.js
21
+
22
+ Only creates and exports the definition. No models, no actions here.
23
+
24
+ ```js
25
+ import App from '@live-change/framework'
26
+ const app = App.app()
27
+
28
+ const definition = app.createServiceDefinition({ name: 'myService' })
29
+
30
+ export default definition
31
+ ```
32
+
33
+ ## index.js
34
+
35
+ Imports definition and all domain files (side-effect imports), then re-exports definition.
36
+
37
+ ```js
38
+ import App from '@live-change/framework'
39
+ const app = App.app()
40
+
41
+ import definition from './definition.js'
42
+
43
+ import './authenticator.js'
44
+ import './myModel.js'
45
+ import './otherModel.js'
46
+
47
+ export default definition
48
+ ```
49
+
50
+ ## config.js (if needed)
51
+
52
+ Reads `definition.config` set from `app.config.js`, resolves defaults, exports plain object.
53
+
54
+ ```js
55
+ import definition from './definition.js'
56
+
57
+ const { someOption = 'default' } = definition.config
58
+
59
+ export default { someOption }
60
+ ```
61
+
62
+ ## Domain files (e.g. myModel.js)
63
+
64
+ Each file imports `definition` (and `config` if needed) and registers models/views/actions/triggers.
65
+
66
+ ```js
67
+ import App from '@live-change/framework'
68
+ const app = App.app()
69
+
70
+ import definition from './definition.js'
71
+
72
+ export const MyModel = definition.model({ name: 'MyModel', ... })
73
+
74
+ definition.view({ name: 'myList', ... })
75
+ definition.action({ name: 'doThing', ... })
76
+ ```
77
+
78
+ ## services.list.js import
79
+
80
+ Always import from the directory index, not a flat file:
81
+
82
+ ```js
83
+ import myService from './services/myService/index.js' // correct
84
+ import myService from './services/myService.js' // wrong
85
+ ```
86
+
87
+ ## Reference implementation
88
+
89
+ See `/home/m8/IdeaProjects/live-change/live-change-stack/services/stripe-service/` as the canonical example.
@@ -0,0 +1,32 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(ls -d /home/m8/IdeaProjects/live-change/*/.claude/skills/)",
5
+ "Bash(ls -d /home/m8/IdeaProjects/live-change/*/.cursor/skills/)",
6
+ "Bash(node -e \"const p = require.resolve\\(''''@live-change/vue3-components''''\\); console.log\\(p\\)\")",
7
+ "Bash(find /home/m8/IdeaProjects/live-change/live-change-stack -path *vue3-components* -name *.js)",
8
+ "Read(//home/m8/IdeaProjects/**)",
9
+ "Bash(find /home/m8/IdeaProjects/live-change -name \"*.vue\" -type f ! -path \"*/node_modules/*\" ! -path \"*/.history/*\" -exec grep -l \"AutoField\" {})",
10
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/skills/live-change-frontend-editor-form/SKILL.md /home/m8/IdeaProjects/live-change/.cursor/skills/live-change-frontend-editor-form.md)",
11
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/skills/live-change-frontend-action-form/SKILL.md /home/m8/IdeaProjects/live-change/.cursor/skills/live-change-frontend-action-form.md)",
12
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-frontend-vue-primevue.md /home/m8/IdeaProjects/live-change/auto-firma/.claude/rules/live-change-frontend-vue-primevue.md)",
13
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-frontend-vue-primevue.md /home/m8/IdeaProjects/live-change/automation/.claude/rules/live-change-frontend-vue-primevue.md)",
14
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-frontend-vue-primevue.md /home/m8/IdeaProjects/live-change/live-change-stack/frontend/frontend-template/.claude/rules/live-change-frontend-vue-primevue.md)",
15
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/skills/live-change-design-models-relations/SKILL.md /home/m8/IdeaProjects/live-change/.cursor/skills/live-change-design-models-relations.md)",
16
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/skills/live-change-design-actions-views-triggers/SKILL.md /home/m8/IdeaProjects/live-change/.cursor/skills/live-change-design-actions-views-triggers.md)",
17
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-actions-views-triggers.md /home/m8/IdeaProjects/live-change/auto-firma/.claude/rules/live-change-backend-actions-views-triggers.md)",
18
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-models-and-relations.md /home/m8/IdeaProjects/live-change/auto-firma/.claude/rules/live-change-backend-models-and-relations.md)",
19
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-actions-views-triggers.md /home/m8/IdeaProjects/live-change/automation/.claude/rules/live-change-backend-actions-views-triggers.md)",
20
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-models-and-relations.md /home/m8/IdeaProjects/live-change/automation/.claude/rules/live-change-backend-models-and-relations.md)",
21
+ "Bash(cp /home/m8/IdeaProjects/live-change/.claude/rules/live-change-backend-event-sourcing.md /home/m8/IdeaProjects/live-change/auto-firma/.claude/rules/live-change-backend-event-sourcing.md)",
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
+ "Bash(find /home/m8/IdeaProjects/live-change/live-change-stack/framework -type f \\\\\\(-name *.ts -o -name *.js \\\\\\))",
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)"
26
+ ],
27
+ "additionalDirectories": [
28
+ "/home/m8/IdeaProjects/live-change/.claude/skills/create-skills-and-rules",
29
+ "/home/m8/IdeaProjects/live-change/.claude/rules"
30
+ ]
31
+ }
32
+ }