@live-change/frontend-template 0.9.197 → 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,168 @@
1
+ ---
2
+ description: Design models with userItem, itemOf, propertyOf relations and access control
3
+ ---
4
+
5
+ # Skill: live-change-design-models-relations
6
+
7
+ Ten skill opisuje **krok po kroku**, jak projektować modele i relacje (`userItem`, `itemOf`, `propertyOf`, `foreignModel`) w serwisach LiveChange.
8
+
9
+ ## Kiedy używać
10
+
11
+ Użyj tego skilla, gdy:
12
+
13
+ - dodajesz nowy model do serwisu,
14
+ - przenosisz dane z ręcznego CRUD na relacje,
15
+ - potrzebujesz spójnego wzorca dla access control i indeksów.
16
+
17
+ ## 1. Dobierz typ relacji
18
+
19
+ Zastanów się, jak model jest powiązany z resztą domeny:
20
+
21
+ - **`userItem`** – obiekt należy do zalogowanego użytkownika (np. konto, urządzenie użytkownika).
22
+ - **`itemOf`** – lista elementów należy do innego modelu (np. połączenia urządzenia, pozycje zamówienia).
23
+ - **`propertyOf`** – pojedyncza właściwość/model ze stanem o ID równym rodzicowi (np. stan kursora, ustawienia).
24
+ - **bez relacji** – gdy obiekt jest globalny lub ma inną strukturę (np. globalny config).
25
+
26
+ Wybierz jedną główną relację na model – dodatkowe powiązania możesz odzwierciedlić polami i indeksami.
27
+
28
+ ## 2. Zdefiniuj `properties` w czytelny sposób
29
+
30
+ 1. Utwórz sekcję `properties`:
31
+ - każdą właściwość zapisuj wieloliniowo,
32
+ - jasno określ typ, domyślne wartości, walidację.
33
+
34
+ 2. Przykład:
35
+
36
+ ```js
37
+ properties: {
38
+ name: {
39
+ type: String,
40
+ validation: ['nonEmpty']
41
+ },
42
+ status: {
43
+ type: String,
44
+ default: 'offline'
45
+ },
46
+ capabilities: {
47
+ type: Array,
48
+ of: {
49
+ type: String
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## 3. Skonfiguruj relację
56
+
57
+ ### `userItem`
58
+
59
+ 1. Dodaj blok `userItem` w definicji modelu.
60
+ 2. Ustaw role dla odczytu i zapisu.
61
+ 3. Ogranicz `writeableProperties` do pól, które użytkownik może zmieniać.
62
+
63
+ ```js
64
+ userItem: {
65
+ readAccessControl: { roles: ['owner', 'admin'] },
66
+ writeAccessControl: { roles: ['owner', 'admin'] },
67
+ writeableProperties: ['name']
68
+ }
69
+ ```
70
+
71
+ ### `itemOf`
72
+
73
+ 1. Ustal model rodzica (`what`).
74
+ 2. W razie potrzeby użyj `definition.foreignModel`, jeśli rodzic jest w innym serwisie.
75
+
76
+ ```js
77
+ itemOf: {
78
+ what: Device,
79
+ readAccessControl: { roles: ['owner', 'admin'] },
80
+ writeAccessControl: { roles: ['owner', 'admin'] }
81
+ }
82
+ ```
83
+
84
+ ### `propertyOf`
85
+
86
+ 1. Użyj, gdy chcesz, żeby ID modelu = ID rodzica.
87
+ 2. To ułatwia odczyt (`Model.get(parentId)`).
88
+
89
+ ```js
90
+ propertyOf: {
91
+ what: Device,
92
+ readAccessControl: { roles: ['owner', 'admin'] },
93
+ writeAccessControl: { roles: ['owner', 'admin'] }
94
+ }
95
+ ```
96
+
97
+ ### `propertyOf` z wieloma rodzicami (1:1 relacja do wielu encji)
98
+
99
+ Użyj, gdy model ma być „łącznikiem 1:1” pomiędzy kilkoma encjami (np. faktura ↔ kontrahent w konkretnej roli),
100
+ żeby generator relacji/CRUD rozumiał, że to jest relacja, a nie pole `someId` w `properties`.
101
+
102
+ Uwagi:
103
+
104
+ - Najczęściej jest to 1 lub 2 rodziców, ale lista `propertyOf` może zawierać **dowolnie wiele** modeli (np. relacja łącząca 3+ encje).
105
+ - Jeśli encja jest relacją, **nie dodawaj ręcznie** pól typu `...Id` w `properties` tylko po to, żeby “zrobić join” – generator CRUD nie potraktuje tego jako relacji.
106
+
107
+ Przykład:
108
+
109
+ ```js
110
+ const CostInvoice = definition.foreignModel('invoice', 'CostInvoice')
111
+ const Contractor = definition.foreignModel('company', 'Contractor')
112
+
113
+ definition.model({
114
+ name: 'Supplier',
115
+ properties: {
116
+ // opcjonalne pola dodatkowe
117
+ },
118
+ propertyOf: [
119
+ { what: CostInvoice },
120
+ { what: Contractor }
121
+ ]
122
+ })
123
+ ```
124
+
125
+ ### `foreignModel`
126
+
127
+ 1. Na początku pliku z modelem zadeklaruj:
128
+
129
+ ```js
130
+ const Device = definition.foreignModel('deviceManager', 'Device')
131
+ ```
132
+
133
+ 2. Potem używaj `Device` w `itemOf`/`propertyOf`.
134
+
135
+ ## 4. Dodaj indeksy
136
+
137
+ 1. Zidentyfikuj typowe zapytania (np. po `sessionKey`, po `(device, status)`).
138
+ 2. Dodaj sekcję `indexes`:
139
+
140
+ ```js
141
+ indexes: {
142
+ bySessionKey: {
143
+ property: ['sessionKey']
144
+ },
145
+ byDeviceAndStatus: {
146
+ property: ['device', 'status']
147
+ }
148
+ }
149
+ ```
150
+
151
+ 3. Używaj tych indeksów w widokach i akcjach (`indexObjectGet`, `indexRangeGet`).
152
+
153
+ ## 5. Ustal access control na poziomie modelu/relacji
154
+
155
+ 1. Dla `userItem` / `itemOf` / `propertyOf` ustaw zawsze:
156
+ - `readAccessControl`,
157
+ - `writeAccessControl`.
158
+
159
+ 2. Nie zakładaj domyślnych reguł – wpisz je wprost w definicji modelu.
160
+
161
+ ## 6. Sprawdź wygenerowane widoki/akcje
162
+
163
+ Po dodaniu relacji:
164
+
165
+ 1. Sprawdź, jakie widoki/akcje generuje plugin (np. `myUserDevices`, `createMyUserDevice`, itp.).
166
+ 2. Zastanów się, czy potrzebujesz dodatkowych, niestandardowych widoków/akcji:
167
+ - jeśli tak, dodaj je, ale **nie duplikuj** tego, co generuje relacja.
168
+
@@ -0,0 +1,75 @@
1
+ ---
2
+ description: Create or restructure a LiveChange backend service with proper directory layout
3
+ ---
4
+
5
+ # Skill: live-change-design-service
6
+
7
+ Ten skill opisuje **krok po kroku**, jak zaprojektować nowy serwis w LiveChange / live-change-stack albo sensownie rozbudować istniejący.
8
+
9
+ ## Kiedy używać
10
+
11
+ Użyj tego skilla, gdy:
12
+
13
+ - dodajesz **nowy serwis domenowy** do projektu,
14
+ - przenosisz większy kawałek logiki z innego serwisu,
15
+ - potrzebujesz upewnić się, że struktura plików i rejestracja serwisu są zgodne z konwencją.
16
+
17
+ ## Kroki – nowy serwis
18
+
19
+ 1. **Nazwij serwis**
20
+ - Wybierz zwięzłą, domenową nazwę, np. `payments`, `notifications`, `deviceManager`.
21
+ - Nazwa będzie używana jako `name` w `createServiceDefinition` oraz w `app.config.js`.
22
+
23
+ 2. **Utwórz katalog serwisu**
24
+ - Ścieżka: `server/services/<serviceName>/`.
25
+ - W katalogu utwórz pliki:
26
+ - `definition.js`
27
+ - `index.js`
28
+ - opcjonalnie `config.js`
29
+ - pliki domenowe (np. `models.js`, `authenticator.js`, `actions.js`), jeśli potrzebne.
30
+
31
+ 3. **Zaimplementuj `definition.js`**
32
+ - Importuj `app` z `@live-change/framework`.
33
+ - Jeśli serwis korzysta z relacji lub access control:
34
+ - importuj `relationsPlugin` z `@live-change/relations-plugin`,
35
+ - importuj `accessControlService` z `@live-change/access-control-service`.
36
+ - Wywołaj `app.createServiceDefinition({ name, use })`.
37
+ - **Nie** deklaruj tu modeli, akcji ani widoków.
38
+
39
+ 4. **Zaimplementuj `index.js`**
40
+ - Importuj `definition` z `./definition.js`.
41
+ - Importuj wszystkie pliki domenowe (np. `./models.js`, `./authenticator.js`).
42
+ - Eksportuj `definition` jako `default`.
43
+ - Nie dodawaj innej logiki do `index.js`.
44
+
45
+ 5. **(Opcjonalnie) utwórz `config.js`**
46
+ - Importuj `definition`.
47
+ - Odczytaj `definition.config` i rozwiąż wartości domyślne.
48
+ - Eksportuj plain object z konfiguracją serwisu.
49
+
50
+ 6. **Dodaj serwis do `services.list.js`**
51
+ - Importuj z katalogu serwisu, nie z pojedynczego pliku.
52
+ - Dodaj serwis do eksportowanego obiektu.
53
+
54
+ 7. **Dodaj serwis do `app.config.js`**
55
+ - W sekcji `services` dodaj `{ name: '<serviceName>' }`.
56
+ - Upewnij się, że kolejność jest sensowna:
57
+ - serwisy bazowe/plugins (user, session, accessControl) na początku,
58
+ - serwisy domenowe zależne od nich – dalej.
59
+
60
+ 8. **Sprawdź zależności**
61
+ - Jeśli serwis korzysta z modeli w innych serwisach:
62
+ - użyj `definition.foreignModel` wewnątrz domenowych plików serwisu,
63
+ - nie importuj bezpośrednio ich plików modeli.
64
+ - Upewnij się, że serwisy, od których zależysz, są wcześniejsze w `app.config.js`.
65
+
66
+ ## Kroki – rozbudowa istniejącego serwisu
67
+
68
+ 1. Przejrzyj istniejący katalog `server/services/<serviceName>/`.
69
+ 2. Sprawdź, czy `definition.js` ma poprawne `use` (relacje, accessControl).
70
+ 3. Nowe modele/akcje/widoki/triggery dodaj do **osobnych plików domenowych**:
71
+ - jeśli logika jest powiązana z istniejącym modelem – do jego pliku,
72
+ - jeśli tworzysz większy nowy obszar – do nowego pliku (np. `notifications.js`).
73
+ 4. Upewnij się, że nowy plik jest importowany w `index.js`.
74
+ 5. Nie dodawaj ciężkiej logiki do `definition.js` ani `index.js`.
75
+
@@ -0,0 +1,128 @@
1
+ ---
2
+ description: Build async action buttons with workingZone, toast and confirm dialogs
3
+ ---
4
+
5
+ # Skill: live-change-frontend-action-buttons (Claude Code)
6
+
7
+ Use this skill when you build **buttons that trigger async actions** outside of forms, using `workingZone`, `toast`, and optionally `confirm`.
8
+
9
+ ## When to use
10
+
11
+ - A button triggers a backend action (delete, approve, toggle, etc.) — **no form fields**.
12
+ - You want the global loading spinner to appear while the action runs.
13
+ - Destructive actions need a confirmation dialog before executing.
14
+
15
+ **Need a form with fields?** Use `editorData` (model editing) or `actionData` (one-shot actions) instead.
16
+
17
+ ## Step 1 – Inject workingZone, set up toast/confirm
18
+
19
+ ```javascript
20
+ import { inject } from 'vue'
21
+ import { useToast } from 'primevue/usetoast'
22
+ import { useConfirm } from 'primevue/useconfirm'
23
+ import { useApi, useActions } from '@live-change/vue3-ssr'
24
+
25
+ const workingZone = inject('workingZone')
26
+ const toast = useToast()
27
+ const confirm = useConfirm()
28
+ const api = useApi()
29
+ const actions = useActions()
30
+ ```
31
+
32
+ `workingZone` is provided by `ViewRoot` (which wraps every page in `<WorkingZone>`). When you call `workingZone.addPromise(name, promise)`, the global spinner/blur activates until the promise resolves.
33
+
34
+ ## Step 2 – Simple action button
35
+
36
+ Wrap the async operation in `workingZone.addPromise()`:
37
+
38
+ ```javascript
39
+ function createItem() {
40
+ workingZone.addPromise('createItem', (async () => {
41
+ const result = await actions.blog.createArticle({})
42
+ toast.add({ severity: 'success', summary: 'Article created', life: 2000 })
43
+ router.push({ name: 'article:edit', params: { article: result } })
44
+ })())
45
+ }
46
+ ```
47
+
48
+ **Important:** Note the `(async () => { ... })()` pattern – you must invoke the async IIFE immediately so `addPromise` receives a Promise, not a function.
49
+
50
+ Template:
51
+
52
+ ```vue
53
+ <Button label="Create article" icon="pi pi-plus" @click="createItem" />
54
+ ```
55
+
56
+ ## Step 3 – Destructive action with confirm
57
+
58
+ Use `confirm.require()` before the action:
59
+
60
+ ```javascript
61
+ function deleteItem(item) {
62
+ confirm.require({
63
+ message: 'Are you sure you want to delete this article?',
64
+ header: 'Confirmation',
65
+ icon: 'pi pi-trash',
66
+ acceptClass: 'p-button-danger',
67
+ accept: async () => {
68
+ workingZone.addPromise('deleteArticle', (async () => {
69
+ await actions.blog.deleteArticle({ article: item.id })
70
+ toast.add({ severity: 'success', summary: 'Deleted', life: 2000 })
71
+ })())
72
+ },
73
+ reject: () => {
74
+ toast.add({ severity: 'info', summary: 'Cancelled', life: 1500 })
75
+ }
76
+ })
77
+ }
78
+ ```
79
+
80
+ Template:
81
+
82
+ ```vue
83
+ <Button label="Delete" icon="pi pi-trash" severity="danger" @click="deleteItem(article)" />
84
+ ```
85
+
86
+ ## Step 4 – Error handling
87
+
88
+ Add try/catch inside the async IIFE:
89
+
90
+ ```javascript
91
+ function toggleStatus(item) {
92
+ workingZone.addPromise('toggleStatus', (async () => {
93
+ try {
94
+ await actions.blog.toggleArticleStatus({ article: item.id })
95
+ toast.add({ severity: 'success', summary: 'Status updated', life: 2000 })
96
+ } catch(e) {
97
+ toast.add({ severity: 'error', summary: 'Error', detail: e?.message ?? e, life: 5000 })
98
+ }
99
+ })())
100
+ }
101
+ ```
102
+
103
+ ## Step 5 – Using api.command instead of actions
104
+
105
+ Both work. `actions` is shorthand for typed service actions:
106
+
107
+ ```javascript
108
+ // Using actions (preferred when available):
109
+ await actions.blog.deleteArticle({ article: id })
110
+
111
+ // Using api.command (always works):
112
+ await api.command(['blog', 'deleteArticle'], { article: id })
113
+ ```
114
+
115
+ ## Pattern summary
116
+
117
+ ```
118
+ Button click
119
+ → confirm.require() (if destructive)
120
+ → workingZone.addPromise('name', (async () => {
121
+ try {
122
+ await actions.service.action({ ... })
123
+ toast.add({ severity: 'success', ... })
124
+ } catch(e) {
125
+ toast.add({ severity: 'error', ... })
126
+ }
127
+ })())
128
+ ```
@@ -0,0 +1,143 @@
1
+ ---
2
+ description: Build one-shot action forms with actionData, AutoField and ActionButtons
3
+ ---
4
+
5
+ # Skill: live-change-frontend-action-form (Claude Code)
6
+
7
+ Use this skill when you build **one-shot action forms** with `actionData` and `AutoField` in a LiveChange frontend.
8
+
9
+ ## When to use
10
+
11
+ - You need a form for a command/action (not CRUD model editing).
12
+ - The form submits once, then shows a "done" state.
13
+ - You want draft auto-save while the user fills in the form.
14
+
15
+ **Editing a model record instead?** Use `editorData` (see `live-change-frontend-editor-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 actionData
19
+
20
+ ```javascript
21
+ import { AutoField, actionData, ActionButtons } from '@live-change/frontend-auto-form'
22
+
23
+ const formData = await actionData({
24
+ service: 'blog',
25
+ action: 'publishArticle',
26
+ parameters: { article: props.articleId }, // fixed params (not shown as fields)
27
+ initialValue: { scheduleTime: null }, // initial values for editable fields
28
+ draft: true,
29
+ doneToast: 'Article published!',
30
+ onDone: (result) => {
31
+ router.push({ name: 'articles' })
32
+ },
33
+ })
34
+ ```
35
+
36
+ For reactive parameters, use `computedAsync`:
37
+
38
+ ```javascript
39
+ import { computedAsync } from '@vueuse/core'
40
+
41
+ const formData = computedAsync(() =>
42
+ actionData({
43
+ service: 'blog',
44
+ action: 'publishArticle',
45
+ parameters: { article: props.articleId },
46
+ })
47
+ )
48
+ ```
49
+
50
+ ## Step 2 – Build the template with AutoField
51
+
52
+ Use `formData.action.properties.*` as definitions and `formData.value.*` as v-model:
53
+
54
+ ```vue
55
+ <template>
56
+ <form @submit.prevent="formData.submit()" @reset.prevent="formData.reset()">
57
+ <AutoField
58
+ :definition="formData.action.properties.scheduleTime"
59
+ v-model="formData.value.scheduleTime"
60
+ :error="formData.propertiesErrors?.scheduleTime"
61
+ label="Schedule time"
62
+ />
63
+ <AutoField
64
+ :definition="formData.action.properties.message"
65
+ v-model="formData.value.message"
66
+ :error="formData.propertiesErrors?.message"
67
+ label="Message"
68
+ />
69
+
70
+ <ActionButtons :actionFormData="formData" :resetButton="true" />
71
+ </form>
72
+ </template>
73
+ ```
74
+
75
+ ## Step 3 – Handle done state
76
+
77
+ After successful submission, `formData.done` becomes `true`:
78
+
79
+ ```vue
80
+ <template>
81
+ <div v-if="formData.done" class="text-center">
82
+ <i class="pi pi-check-circle text-4xl text-green-500 mb-2"></i>
83
+ <p>Article published successfully!</p>
84
+ </div>
85
+ <form v-else @submit.prevent="formData.submit()" @reset.prevent="formData.reset()">
86
+ <!-- fields + ActionButtons -->
87
+ </form>
88
+ </template>
89
+ ```
90
+
91
+ ## Step 4 – Manual buttons (alternative to ActionButtons)
92
+
93
+ ```vue
94
+ <div class="flex gap-2 justify-end">
95
+ <Button
96
+ type="submit"
97
+ :label="formData.submitting ? 'Executing...' : 'Execute'"
98
+ :icon="formData.submitting ? 'pi pi-spin pi-spinner' : 'pi pi-play'"
99
+ :disabled="formData.submitting"
100
+ />
101
+ <Button type="reset" label="Reset" :disabled="!formData.changed" icon="pi pi-eraser" />
102
+ </div>
103
+ ```
104
+
105
+ ## editorData vs actionData
106
+
107
+ | Aspect | `editorData` | `actionData` |
108
+ |---|---|---|
109
+ | Purpose | CRUD model editing | One-shot command form |
110
+ | Identifier | `model` + `identifiers` | `action` + `parameters` |
111
+ | Create/update | Detects automatically | Always "execute" |
112
+ | After submit | Record is saved | `done` becomes `true` |
113
+ | Definition source | `editor.model` | `formData.action` |
114
+ | `parameters` | Extra params on save | Fixed fields excluded from form |
115
+
116
+ ## Key options
117
+
118
+ | Option | Default | Description |
119
+ |---|---|---|
120
+ | `service` | required | Service name |
121
+ | `action` | required | Action name |
122
+ | `parameters` | `{}` | Fixed identifier fields (not editable) |
123
+ | `initialValue` | `{}` | Initial values for editable fields |
124
+ | `draft` | `true` | Auto-save draft while filling |
125
+ | `debounce` | `600` | Debounce delay in ms |
126
+ | `doneToast` | `"Action done"` | Toast after success |
127
+ | `onDone` | – | Callback after success |
128
+
129
+ ## Key returned properties
130
+
131
+ | Property | Description |
132
+ |---|---|
133
+ | `value` | Editable form data |
134
+ | `action` | Action definition (`.properties.*` for `AutoField`) |
135
+ | `editableProperties` | Properties not fixed by `parameters` |
136
+ | `changed` | Form differs from initial value |
137
+ | `submit()` | Execute the action |
138
+ | `submitting` | Action call in progress |
139
+ | `done` | `true` after success |
140
+ | `reset()` | Discard draft, restore initial value |
141
+ | `propertiesErrors` | Server validation errors per property |
142
+ | `draftChanged` | Draft auto-saved but not submitted |
143
+ | `savingDraft` | Draft auto-save in progress |
@@ -0,0 +1,146 @@
1
+ ---
2
+ description: Integrate analytics tracking with analytics.emit and provider wiring
3
+ ---
4
+
5
+ # Skill: live-change-frontend-analytics (Claude Code)
6
+
7
+ Use this skill when you add **analytics tracking** to a LiveChange frontend.
8
+
9
+ ## When to use
10
+
11
+ - You need to track user actions, page views, or custom events.
12
+ - You are integrating PostHog, GA4, or another analytics provider.
13
+ - You need consent-aware analytics.
14
+
15
+ ## Step 1 – Emit events with `analytics`
16
+
17
+ Import `analytics` from `@live-change/vue3-components` and emit events:
18
+
19
+ ```javascript
20
+ import { analytics } from '@live-change/vue3-components'
21
+
22
+ // In a component or handler:
23
+ analytics.emit('article:published', { articleId: article.id })
24
+ analytics.emit('button:clicked', { action: 'delete', target: 'article' })
25
+ ```
26
+
27
+ Standard built-in events:
28
+
29
+ | Event | Payload | When emitted |
30
+ |---|---|---|
31
+ | `pageView` | route `to` object | On route change (automatic in App.vue) |
32
+ | `user:identification` | `{ user, session, identification, contacts }` | When user identity is known |
33
+ | `consent` | `{ analytics: boolean }` | When user grants/denies tracking consent |
34
+ | `locale:change` | locale settings object | When language/locale changes |
35
+
36
+ ## Step 2 – Wire user identification in App.vue
37
+
38
+ Emit `user:identification` when the user profile is loaded:
39
+
40
+ ```javascript
41
+ import { analytics } from '@live-change/vue3-components'
42
+ import { usePath, live, useClient } from '@live-change/vue3-ssr'
43
+ import { computed, watch } from 'vue'
44
+
45
+ const client = useClient()
46
+ const path = usePath()
47
+
48
+ if(typeof window !== 'undefined') {
49
+ Promise.all([
50
+ live(path.userIdentification.myIdentification()),
51
+ ]).then(([identification]) => {
52
+ const fullIdentification = computed(() => ({
53
+ user: client.value.user,
54
+ session: client.value.session,
55
+ identification: identification.value,
56
+ }))
57
+ watch(fullIdentification, (newId) => {
58
+ analytics.emit('user:identification', newId)
59
+ }, { immediate: true })
60
+ })
61
+ }
62
+ ```
63
+
64
+ ## Step 3 – Create a provider file (e.g. PostHog)
65
+
66
+ Create a file like `src/analytics/posthog.js`:
67
+
68
+ ```javascript
69
+ import { analytics } from '@live-change/vue3-components'
70
+ import posthog from 'posthog-js'
71
+
72
+ posthog.init('phc_YOUR_KEY', {
73
+ api_host: 'https://eu.i.posthog.com',
74
+ person_profiles: 'always'
75
+ })
76
+
77
+ // Page views
78
+ analytics.on('pageView', (to) => {
79
+ posthog.register({
80
+ route: { name: to.name, params: to.params }
81
+ })
82
+ })
83
+
84
+ // User identification
85
+ analytics.on('user:identification', (identification) => {
86
+ if (!identification.user) {
87
+ posthog.reset()
88
+ return
89
+ }
90
+ posthog.identify(identification.user, {
91
+ firstName: identification.identification?.firstName,
92
+ lastName: identification.identification?.lastName,
93
+ })
94
+ })
95
+
96
+ // Consent
97
+ analytics.on('consent', (payload) => {
98
+ if (payload?.analytics === true) {
99
+ posthog.opt_in_capturing()
100
+ } else {
101
+ posthog.opt_out_capturing()
102
+ }
103
+ })
104
+
105
+ // Catch-all for custom events
106
+ const ignored = ['user:identification', 'pageView', 'consent']
107
+ analytics.on('*', (type, event) => {
108
+ if (ignored.includes(type)) return
109
+ posthog.capture(type, event)
110
+ })
111
+ ```
112
+
113
+ Import it in `App.vue`:
114
+
115
+ ```javascript
116
+ import './analytics/posthog'
117
+ ```
118
+
119
+ ## Step 4 – Emit custom events in components
120
+
121
+ ```javascript
122
+ // Track a form submission
123
+ analytics.emit('form:submitted', { formName: 'contactForm' })
124
+
125
+ // Track a feature usage
126
+ analytics.emit('feature:used', { feature: 'darkMode', enabled: true })
127
+
128
+ // Track navigation
129
+ analytics.emit('navigation:click', { target: 'pricing', source: 'navbar' })
130
+ ```
131
+
132
+ ## Step 5 – Consent handling
133
+
134
+ Emit consent events from your consent banner:
135
+
136
+ ```javascript
137
+ function acceptAnalytics() {
138
+ analytics.emit('consent', { analytics: true })
139
+ }
140
+
141
+ function rejectAnalytics() {
142
+ analytics.emit('consent', { analytics: false })
143
+ }
144
+ ```
145
+
146
+ Provider files listen for `consent` and enable/disable tracking accordingly.