@live-change/frontend-template 0.9.198 → 0.9.199

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +184 -0
  2. package/.claude/rules/live-change-backend-architecture.md +126 -0
  3. package/.claude/rules/live-change-backend-models-and-relations.md +188 -0
  4. package/.claude/rules/live-change-frontend-vue-primevue.md +291 -0
  5. package/.claude/rules/live-change-service-structure.md +89 -0
  6. package/.claude/skills/create-skills-and-rules.md +196 -0
  7. package/.claude/skills/live-change-design-actions-views-triggers.md +190 -0
  8. package/.claude/skills/live-change-design-models-relations.md +173 -0
  9. package/.claude/skills/live-change-design-service.md +132 -0
  10. package/.claude/skills/live-change-frontend-action-buttons.md +128 -0
  11. package/.claude/skills/live-change-frontend-action-form.md +143 -0
  12. package/.claude/skills/live-change-frontend-analytics.md +146 -0
  13. package/.claude/skills/live-change-frontend-command-forms.md +215 -0
  14. package/.claude/skills/live-change-frontend-data-views.md +182 -0
  15. package/.claude/skills/live-change-frontend-editor-form.md +177 -0
  16. package/.claude/skills/live-change-frontend-locale-time.md +171 -0
  17. package/.claude/skills/live-change-frontend-page-list-detail.md +200 -0
  18. package/.claude/skills/live-change-frontend-range-list.md +128 -0
  19. package/.claude/skills/live-change-frontend-ssr-setup.md +118 -0
  20. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +202 -0
  21. package/.cursor/rules/live-change-backend-architecture.mdc +131 -0
  22. package/.cursor/rules/live-change-backend-models-and-relations.mdc +194 -0
  23. package/.cursor/rules/live-change-frontend-vue-primevue.mdc +290 -0
  24. package/.cursor/rules/live-change-service-structure.mdc +107 -0
  25. package/.cursor/skills/live-change-design-actions-views-triggers.md +197 -0
  26. package/.cursor/skills/live-change-design-models-relations.md +168 -0
  27. package/.cursor/skills/live-change-design-service.md +75 -0
  28. package/.cursor/skills/live-change-frontend-action-buttons.md +128 -0
  29. package/.cursor/skills/live-change-frontend-action-form.md +143 -0
  30. package/.cursor/skills/live-change-frontend-analytics.md +146 -0
  31. package/.cursor/skills/live-change-frontend-command-forms.md +215 -0
  32. package/.cursor/skills/live-change-frontend-data-views.md +182 -0
  33. package/.cursor/skills/live-change-frontend-editor-form.md +177 -0
  34. package/.cursor/skills/live-change-frontend-locale-time.md +171 -0
  35. package/.cursor/skills/live-change-frontend-page-list-detail.md +200 -0
  36. package/.cursor/skills/live-change-frontend-range-list.md +128 -0
  37. package/.cursor/skills/live-change-frontend-ssr-setup.md +119 -0
  38. package/README.md +71 -0
  39. package/package.json +50 -50
  40. package/server/app.config.js +35 -0
  41. package/server/services.list.js +2 -0
  42. package/.nx/workspace-data/file-map.json +0 -195
  43. package/.nx/workspace-data/nx_files.nxt +0 -0
  44. package/.nx/workspace-data/project-graph.json +0 -8
  45. package/.nx/workspace-data/project-graph.lock +0 -0
  46. package/.nx/workspace-data/source-maps.json +0 -1
@@ -0,0 +1,215 @@
1
+ ---
2
+ description: Build forms with api.command, command-form, workingZone, confirm and toast
3
+ ---
4
+
5
+ # Skill: live-change-frontend-command-forms (Claude Code)
6
+
7
+ Use this skill when you build **forms and actions** for a LiveChange frontend:
8
+
9
+ - calling `api.command`,
10
+ - using `<command-form>`,
11
+ - handling destructive actions with confirm + toast.
12
+
13
+ ## Choosing the right pattern
14
+
15
+ Before using this skill, pick the right approach:
16
+
17
+ | Pattern | When to use |
18
+ |---|---|
19
+ | `editorData` | **Editing model records** (create/update). Drafts, validation, `AutoField`. See `live-change-frontend-editor-form` skill. |
20
+ | `actionData` | **One-shot action forms** (not CRUD). Submit once → done. See `live-change-frontend-action-form` skill. |
21
+ | `api.command` | **Single button or programmatic calls** (no form fields). This skill, Step 1. |
22
+ | `<command-form>` | **Avoid.** Legacy. Only for trivial prototypes without drafts or `AutoField`. This skill, Step 2. |
23
+
24
+ **Decision flow:**
25
+
26
+ 1. Does the user fill in form fields? → **No**: use `api.command` (this skill).
27
+ 2. Is it editing a model record? → **Yes**: use `editorData`.
28
+ 3. Is it a one-shot action? → **Yes**: use `actionData`.
29
+ 4. Only use `<command-form>` for the simplest throwaway cases.
30
+
31
+ ## Step 1 – Use `api.command` directly
32
+
33
+ 1. Import and create `api`:
34
+
35
+ ```js
36
+ import { api as useApi } from '@live-change/vue3-ssr'
37
+
38
+ const api = useApi()
39
+ ```
40
+
41
+ 2. Call commands as:
42
+
43
+ ```js
44
+ await api.command(['deviceManager', 'createMyUserDevice'], {
45
+ name: 'My device'
46
+ })
47
+
48
+ await api.command(['deviceManager', 'deleteMyUserDevice'], {
49
+ device: id
50
+ })
51
+ ```
52
+
53
+ 3. Wrap in `try/catch` if you need custom error handling.
54
+
55
+ ## Step 2 – `<command-form>` (legacy – prefer editorData/actionData)
56
+
57
+ > **Note:** `<command-form>` is the oldest pattern. For new code, prefer `editorData` (model editing) or `actionData` (one-shot actions) – they support drafts, `AutoField`, and richer state management.
58
+
59
+ 1. Use `<command-form>` only for trivial forms without drafts or `AutoField`.
60
+ 2. Provide:
61
+ - `service` – service name,
62
+ - `action` – action name,
63
+ - `fields` – constant/hidden fields (e.g. ids).
64
+
65
+ Example:
66
+
67
+ ```vue
68
+ <command-form
69
+ service="deviceManager"
70
+ action="updateMyUserDevice"
71
+ :fields="{ device: deviceId }"
72
+ >
73
+ <template #default="{ fieldProps, submit, busy }">
74
+ <div class="space-y-4">
75
+ <div>
76
+ <label class="block text-sm font-medium mb-1">
77
+ Name
78
+ </label>
79
+ <InputText v-bind="fieldProps('name')" class="w-full" />
80
+ </div>
81
+ <div class="flex justify-end gap-2">
82
+ <Button
83
+ label="Save"
84
+ icon="pi pi-check"
85
+ :loading="busy"
86
+ @click="submit"
87
+ />
88
+ </div>
89
+ </div>
90
+ </template>
91
+ </command-form>
92
+ ```
93
+
94
+ ## Step 3 – Confirm + Toast for destructive actions
95
+
96
+ 1. Use PrimeVue `useConfirm` and `useToast`.
97
+ 2. Put the `api.command` call in `accept`.
98
+
99
+ Example:
100
+
101
+ ```js
102
+ import { useConfirm } from 'primevue/useconfirm'
103
+ import { useToast } from 'primevue/usetoast'
104
+ import { api as useApi } from '@live-change/vue3-ssr'
105
+
106
+ const api = useApi()
107
+ const confirm = useConfirm()
108
+ const toast = useToast()
109
+
110
+ function deleteDevice(id) {
111
+ confirm.require({
112
+ message: 'Are you sure you want to delete this device?',
113
+ header: 'Confirmation',
114
+ icon: 'pi pi-exclamation-triangle',
115
+ accept: async () => {
116
+ await api.command(['deviceManager', 'deleteMyUserDevice'], { device: id })
117
+ toast.add({
118
+ severity: 'success',
119
+ summary: 'Deleted',
120
+ life: 2000
121
+ })
122
+ }
123
+ })
124
+ }
125
+ ```
126
+
127
+ ## Step 3b – WorkingZone for button actions (outside forms)
128
+
129
+ When a button triggers an async action outside a form, wrap it with `workingZone.addPromise()` so the global loading spinner activates:
130
+
131
+ ```js
132
+ import { inject } from 'vue'
133
+ import { useToast } from 'primevue/usetoast'
134
+ import { useActions } from '@live-change/vue3-ssr'
135
+
136
+ const workingZone = inject('workingZone')
137
+ const toast = useToast()
138
+ const actions = useActions()
139
+
140
+ function createDevice() {
141
+ workingZone.addPromise('createDevice', (async () => {
142
+ const result = await actions.deviceManager.createMyUserDevice({ name: 'New device' })
143
+ toast.add({ severity: 'success', summary: 'Created', life: 2000 })
144
+ router.push({ name: 'device', params: { device: result } })
145
+ })())
146
+ }
147
+ ```
148
+
149
+ Combine with `confirm.require()` for destructive actions:
150
+
151
+ ```js
152
+ function deleteDevice(id) {
153
+ confirm.require({
154
+ message: 'Are you sure?',
155
+ header: 'Confirmation',
156
+ icon: 'pi pi-trash',
157
+ acceptClass: 'p-button-danger',
158
+ accept: async () => {
159
+ workingZone.addPromise('deleteDevice', (async () => {
160
+ await actions.deviceManager.deleteMyUserDevice({ device: id })
161
+ toast.add({ severity: 'success', summary: 'Deleted', life: 2000 })
162
+ })())
163
+ }
164
+ })
165
+ }
166
+ ```
167
+
168
+ For a detailed guide, see the `live-change-frontend-action-buttons` skill.
169
+
170
+ ## Step 4 – Pattern for sensitive values (toggle + copy)
171
+
172
+ 1. By default, hide sensitive values (keys, tokens, etc.).
173
+ 2. Add:
174
+ - a toggle button (eye / eye-slash),
175
+ - a copy button with a toast.
176
+
177
+ Example:
178
+
179
+ ```vue
180
+ <template>
181
+ <div class="flex items-center gap-2">
182
+ <code>
183
+ {{ revealed ? item.pairingKey : '••••••••••••' }}
184
+ </code>
185
+ <Button
186
+ :icon="revealed ? 'pi pi-eye-slash' : 'pi pi-eye'"
187
+ text
188
+ @click="revealed = !revealed"
189
+ />
190
+ <Button
191
+ icon="pi pi-copy"
192
+ text
193
+ @click="copyToClipboard(item.pairingKey)"
194
+ />
195
+ </div>
196
+ </template>
197
+
198
+ <script setup>
199
+ import { ref } from 'vue'
200
+ import { useToast } from 'primevue/usetoast'
201
+
202
+ const toast = useToast()
203
+ const revealed = ref(false)
204
+
205
+ async function copyToClipboard(text) {
206
+ await navigator.clipboard.writeText(text)
207
+ toast.add({
208
+ severity: 'info',
209
+ summary: 'Copied',
210
+ life: 1500
211
+ })
212
+ }
213
+ </script>
214
+ ```
215
+
@@ -0,0 +1,182 @@
1
+ ---
2
+ description: Build reactive data views with usePath, live, .with() and useClient auth guards
3
+ ---
4
+
5
+ # Skill: live-change-frontend-data-views (Claude Code)
6
+
7
+ Use this skill when you build **reactive data views** using `usePath`, `live`, `.with()`, and `useClient` in a LiveChange frontend.
8
+
9
+ ## When to use
10
+
11
+ - You are loading data from backend views.
12
+ - Paths depend on reactive values (route params, props, client state).
13
+ - You need to load related objects alongside the main data.
14
+ - You need to restrict data loading for unauthenticated users.
15
+
16
+ ## Step 1 – Basic data loading with computed paths
17
+
18
+ When paths depend on reactive values (route params, props), wrap them in `computed()`:
19
+
20
+ ```javascript
21
+ import { computed, unref } from 'vue'
22
+ import { usePath, live } from '@live-change/vue3-ssr'
23
+ import { useRoute } from 'vue-router'
24
+
25
+ const path = usePath()
26
+ const route = useRoute()
27
+ const articleId = route.params.article
28
+
29
+ const articlePath = computed(() => path.blog.article({ article: unref(articleId) }))
30
+ const commentsPath = computed(() => path.blog.articleComments({ article: unref(articleId) }))
31
+
32
+ const [article, comments] = await Promise.all([
33
+ live(articlePath),
34
+ live(commentsPath),
35
+ ])
36
+ ```
37
+
38
+ In templates access `.value`:
39
+
40
+ ```vue
41
+ <h1>{{ article.value?.title }}</h1>
42
+ <div v-for="comment in comments.value" :key="comment.id">
43
+ {{ comment.text }}
44
+ </div>
45
+ ```
46
+
47
+ ## Step 2 – Load related objects with `.with()`
48
+
49
+ Chain `.with()` to attach related data to each item:
50
+
51
+ ```javascript
52
+ const articlesPath = computed(() =>
53
+ path.blog.articlesByCreatedAt({ limit: 20 })
54
+ .with(article => path.userIdentification.identification({
55
+ sessionOrUserType: article.authorType,
56
+ sessionOrUser: article.author
57
+ }).bind('authorProfile'))
58
+ .with(article => path.blog.articleCategory({ category: article.category }).bind('categoryData'))
59
+ )
60
+
61
+ const [articles] = await Promise.all([live(articlesPath)])
62
+ ```
63
+
64
+ Access in template:
65
+
66
+ ```vue
67
+ <div v-for="article in articles.value" :key="article.id">
68
+ <h3>{{ article.title }}</h3>
69
+ <span>by {{ article.authorProfile?.firstName }}</span>
70
+ <Tag :value="article.categoryData?.name" />
71
+ </div>
72
+ ```
73
+
74
+ ### Nested `.with()` (with inside with)
75
+
76
+ ```javascript
77
+ const eventPath = computed(() =>
78
+ path.myService.event({ event: eventId })
79
+ .with(event => path.myService.eventState({ event: event.id }).bind('state')
80
+ .with(state => path.myService.roundPairs({
81
+ event: event.id,
82
+ round: state.round
83
+ }).bind('roundPairs'))
84
+ )
85
+ )
86
+ ```
87
+
88
+ ## Step 3 – Conditional loading with `useClient`
89
+
90
+ Use `useClient()` to check authentication state and conditionally build paths:
91
+
92
+ ```javascript
93
+ import { useClient } from '@live-change/vue3-ssr'
94
+
95
+ const client = useClient()
96
+
97
+ // Path that only loads for logged-in users (null path = no fetch)
98
+ const myDataPath = computed(() =>
99
+ client.value.user && path.blog.myArticles({})
100
+ )
101
+
102
+ // Path that only loads for admins
103
+ const adminPath = computed(() =>
104
+ client.value.roles.includes('admin') && path.blog.allArticles({})
105
+ )
106
+
107
+ const [myData, adminData] = await Promise.all([
108
+ live(myDataPath),
109
+ live(adminPath),
110
+ ])
111
+ ```
112
+
113
+ When the path is `null` / `false` / `undefined`, `live()` returns a ref with `null` value and does not subscribe.
114
+
115
+ ### Conditional rendering in templates
116
+
117
+ ```vue
118
+ <template>
119
+ <!-- Admin-only button -->
120
+ <Button v-if="client.roles.includes('admin')"
121
+ label="Create" icon="pi pi-plus" @click="create" />
122
+
123
+ <!-- Show different content based on auth -->
124
+ <div v-if="client.user">
125
+ <!-- Authenticated content -->
126
+ </div>
127
+ <div v-else>
128
+ <p>Please sign in to see your articles.</p>
129
+ <router-link :to="{ name: 'user:signIn' }">Sign in</router-link>
130
+ </div>
131
+ </template>
132
+ ```
133
+
134
+ ## Step 4 – Dependent paths
135
+
136
+ When one path depends on data from another, load them sequentially:
137
+
138
+ ```javascript
139
+ // First load
140
+ const [article] = await Promise.all([live(articlePath)])
141
+
142
+ // Dependent path using data from first load
143
+ const authorPath = computed(() =>
144
+ article.value && path.userIdentification.identification({
145
+ sessionOrUserType: article.value.authorType,
146
+ sessionOrUser: article.value.author
147
+ })
148
+ )
149
+ const [author] = await Promise.all([live(authorPath)])
150
+ ```
151
+
152
+ Or use `.with()` to combine them in a single query (preferred when possible).
153
+
154
+ ## Step 5 – Props-based paths in components
155
+
156
+ When building reusable components that receive IDs as props:
157
+
158
+ ```vue
159
+ <script setup>
160
+ import { computed } from 'vue'
161
+ import { usePath, live } from '@live-change/vue3-ssr'
162
+
163
+ const props = defineProps({
164
+ articleId: { type: String, required: true }
165
+ })
166
+
167
+ const path = usePath()
168
+
169
+ const articlePath = computed(() => path.blog.article({ article: props.articleId }))
170
+ const [article] = await Promise.all([live(articlePath)])
171
+ </script>
172
+ ```
173
+
174
+ ## Summary
175
+
176
+ | Pattern | When to use |
177
+ |---|---|
178
+ | `computed(() => path.xxx(...))` | Path depends on reactive values |
179
+ | `.with(item => path.yyy(...).bind('field'))` | Attach related objects |
180
+ | `client.value.user && path.xxx(...)` | Load only when authenticated |
181
+ | `client.value.roles.includes('admin')` | Load/show only for specific roles |
182
+ | Sequential `live()` calls | Path depends on data from previous load |
@@ -0,0 +1,177 @@
1
+ ---
2
+ description: Build CRUD editing forms with editorData and AutoField components
3
+ ---
4
+
5
+ # Skill: live-change-frontend-editor-form (Claude Code)
6
+
7
+ Use this skill when you build **CRUD editing forms** with `editorData` and `AutoField` in a LiveChange frontend.
8
+
9
+ ## When to use
10
+
11
+ - You need a create/edit form for a model record.
12
+ - You want draft auto-saving while the user types.
13
+ - You prefer individual `<AutoField>` components over `<AutoEditor>` for layout control.
14
+
15
+ **Not editing a model?** Use `actionData` instead (see `live-change-frontend-action-form` skill).
16
+ **No form fields, just a button?** Use `api.command` (see `live-change-frontend-command-forms` skill).
17
+
18
+ ## Step 1 – Set up editorData
19
+
20
+ ```javascript
21
+ import { ref, getCurrentInstance } from 'vue'
22
+ import { AutoField, editorData } from '@live-change/frontend-auto-form'
23
+ import { useToast } from 'primevue/usetoast'
24
+ import { useRouter } from 'vue-router'
25
+
26
+ const toast = useToast()
27
+ const router = useRouter()
28
+ const appContext = getCurrentInstance().appContext
29
+
30
+ const editor = ref(null)
31
+
32
+ async function loadEditor() {
33
+ editor.value = await editorData({
34
+ service: 'blog',
35
+ model: 'Article',
36
+ identifiers: { article: props.articleId },
37
+ draft: true,
38
+ appContext,
39
+ toast,
40
+ onSaved: () => {
41
+ toast.add({ severity: 'success', summary: 'Saved', life: 1500 })
42
+ },
43
+ onCreated: (result) => {
44
+ router.push({ name: 'article', params: { article: result } })
45
+ },
46
+ })
47
+ }
48
+
49
+ loadEditor()
50
+ ```
51
+
52
+ For new records, pass an empty or missing identifier:
53
+
54
+ ```javascript
55
+ identifiers: { article: props.articleId } // props.articleId can be undefined for new
56
+ ```
57
+
58
+ ## Step 2 – Build the template with AutoField
59
+
60
+ Use `editor.model.properties.*` as definitions and `editor.value.*` as v-model:
61
+
62
+ ```vue
63
+ <template>
64
+ <div v-if="editor" class="space-y-4">
65
+ <AutoField
66
+ :definition="editor.model.properties.title"
67
+ v-model="editor.value.title"
68
+ :error="editor.propertiesErrors?.title"
69
+ label="Title"
70
+ />
71
+ <AutoField
72
+ :definition="editor.model.properties.body"
73
+ v-model="editor.value.body"
74
+ :error="editor.propertiesErrors?.body"
75
+ label="Body"
76
+ />
77
+
78
+ <!-- Manual field alongside AutoField -->
79
+ <div>
80
+ <label class="block text-sm font-medium mb-1">Category</label>
81
+ <Dropdown
82
+ v-model="editor.value.category"
83
+ :options="categories"
84
+ optionLabel="name"
85
+ optionValue="id"
86
+ class="w-full"
87
+ />
88
+ </div>
89
+
90
+ <!-- Buttons -->
91
+ <div class="flex gap-2 justify-end">
92
+ <Button v-if="!editor.isNew" @click="editor.save()"
93
+ label="Save" icon="pi pi-save"
94
+ :disabled="!editor.changed" />
95
+ <Button v-else @click="editor.save()"
96
+ label="Create" icon="pi pi-plus" severity="success"
97
+ :disabled="!editor.changed" />
98
+ <Button @click="editor.reset()"
99
+ label="Reset" icon="pi pi-eraser"
100
+ :disabled="!editor.changed" />
101
+ </div>
102
+ </div>
103
+ </template>
104
+ ```
105
+
106
+ ## Step 3 – Use EditorButtons (alternative)
107
+
108
+ Instead of manual buttons, use the `EditorButtons` component:
109
+
110
+ ```vue
111
+ <template>
112
+ <div v-if="editor">
113
+ <!-- fields... -->
114
+ <EditorButtons :editor="editor" :resetButton="true" />
115
+ </div>
116
+ </template>
117
+
118
+ <script setup>
119
+ import { EditorButtons } from '@live-change/frontend-auto-form'
120
+ </script>
121
+ ```
122
+
123
+ `EditorButtons` automatically handles:
124
+ - "Saving draft..." spinner,
125
+ - "Draft changed" hint,
126
+ - validation error message,
127
+ - Save/Create button (disabled when nothing changed),
128
+ - optional Reset button.
129
+
130
+ ## Step 4 – Reactive identifiers with computedAsync
131
+
132
+ When identifiers come from reactive sources (route params, props):
133
+
134
+ ```javascript
135
+ import { computedAsync } from '@vueuse/core'
136
+
137
+ const editor = computedAsync(() =>
138
+ editorData({
139
+ service: 'blog',
140
+ model: 'Article',
141
+ identifiers: { article: props.articleId },
142
+ draft: true,
143
+ appContext,
144
+ toast,
145
+ })
146
+ )
147
+ ```
148
+
149
+ ## Key options reference
150
+
151
+ | Option | Default | Description |
152
+ |---|---|---|
153
+ | `service` | required | Service name |
154
+ | `model` | required | Model name |
155
+ | `identifiers` | required | e.g. `{ article: id }` |
156
+ | `draft` | `true` | Auto-save draft while editing |
157
+ | `autoSave` | `false` | Auto-save directly (when `draft: false`) |
158
+ | `debounce` | `600` | Debounce delay in ms |
159
+ | `initialData` | `{}` | Default values for new records |
160
+ | `parameters` | `{}` | Extra params sent with every action |
161
+ | `onSaved` | – | Callback after save |
162
+ | `onCreated` | – | Callback after create (receives result) |
163
+
164
+ ## Key returned properties
165
+
166
+ | Property | Description |
167
+ |---|---|
168
+ | `value` | Editable data (use with `v-model`) |
169
+ | `model` | Model definition (use `.properties.*` for `AutoField`) |
170
+ | `changed` | `true` when there are unsaved changes |
171
+ | `isNew` | `true` when creating (no existing record) |
172
+ | `save()` | Submit to backend |
173
+ | `reset()` | Discard draft, restore saved state |
174
+ | `propertiesErrors` | Server validation errors per property |
175
+ | `saving` | Save in progress |
176
+ | `draftChanged` | Draft auto-saved but not submitted |
177
+ | `savingDraft` | Draft auto-save in progress |