@live-change/frontend-template 0.9.203 → 0.9.205
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-actions-views-triggers.md +49 -1
- package/.claude/rules/live-change-backend-models-and-relations.md +24 -6
- package/.claude/rules/live-change-service-structure.md +2 -2
- package/.claude/settings.json +3 -1
- package/.claude/skills/live-change-backend-change-triggers/SKILL.md +15 -0
- package/.claude/skills/live-change-dao-protocol/SKILL.md +46 -0
- package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +110 -0
- package/.claude/skills/live-change-design-models-relations/SKILL.md +63 -5
- package/.claude/skills/live-change-frontend-data-views/SKILL.md +73 -4
- package/.claude/skills/live-change-frontend-range-list/SKILL.md +90 -0
- package/.claude/skills/live-change-frontend-synchronized/SKILL.md +101 -0
- package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +23 -4
- package/.cursor/rules/live-change-backend-architecture.mdc +1 -1
- package/.cursor/rules/live-change-backend-event-sourcing.mdc +1 -1
- package/.cursor/rules/live-change-backend-models-and-relations.mdc +36 -7
- package/.cursor/rules/live-change-backend-views-vs-triggers-for-reads-writes.mdc +28 -0
- package/.cursor/rules/live-change-dao-protocol.mdc +47 -0
- package/.cursor/rules/live-change-frontend-views-not-commands-for-reads.mdc +30 -0
- package/.cursor/rules/live-change-frontend-vue-primevue.mdc +70 -4
- package/.cursor/rules/live-change-service-structure.mdc +1 -1
- package/.cursor/skills/live-change-backend-change-triggers.md +15 -0
- package/.cursor/skills/live-change-design-actions-views-triggers.md +51 -0
- package/.cursor/skills/live-change-design-models-relations.md +23 -5
- package/.cursor/skills/live-change-frontend-data-views.md +15 -0
- package/.cursor/skills/live-change-frontend-range-list.md +21 -0
- package/.cursor/skills/live-change-frontend-synchronized.md +101 -0
- package/.node-version +1 -1
- package/.nvmrc +1 -1
- package/front/src/pages/index.vue +1 -1
- package/package.json +55 -55
|
@@ -27,6 +27,19 @@ function articlesPathRange(range) {
|
|
|
27
27
|
}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
## Step 1a – Hard rules for index-backed ranges
|
|
31
|
+
|
|
32
|
+
For lists loaded with `RangeViewer` / `rangeBuckets`:
|
|
33
|
+
|
|
34
|
+
- backend views should use `sortedIndexRangePath`, not `indexRangePath`,
|
|
35
|
+
- keep `range.gt/gte/lt/lte` for pagination cursor only,
|
|
36
|
+
- never override `gt/lt` in frontend `pathFunction` with ad-hoc filters.
|
|
37
|
+
|
|
38
|
+
Why:
|
|
39
|
+
|
|
40
|
+
- RangeViewer computes next buckets from previous cursor boundaries,
|
|
41
|
+
- replacing cursor fields causes repeated slices and broken infinite loading.
|
|
42
|
+
|
|
30
43
|
## Step 2 – Attach related objects with `.with()`
|
|
31
44
|
|
|
32
45
|
Chain `.with()` calls to load related data for each item:
|
|
@@ -151,3 +164,11 @@ Why:
|
|
|
151
164
|
</template>
|
|
152
165
|
</ReactiveRangeViewer>
|
|
153
166
|
```
|
|
167
|
+
|
|
168
|
+
## Checklist – range pagination safety
|
|
169
|
+
|
|
170
|
+
- [ ] backend index view is based on `sortedIndexRangePath`
|
|
171
|
+
- [ ] frontend `pathFunction` forwards `range` unchanged (`...range` or `...reverseRange(range)`)
|
|
172
|
+
- [ ] domain filters (`month`, `year`, `status`) are separate view params
|
|
173
|
+
- [ ] no manual cursor overrides (`gt/gte/lt/lte`) in frontend code
|
|
174
|
+
- [ ] if narrowing is needed, backend uses index prefix design first, `prefixRange` only as fallback
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: live-change-frontend-synchronized
|
|
3
|
+
description: Use synchronized and synchronizedList for autosave editing in LiveChange Vue frontends, including object vs list decision flow, identifiers mapping, and save/delete patterns
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: live-change-frontend-synchronized (Cursor)
|
|
7
|
+
|
|
8
|
+
Use this skill when implementing or refactoring frontend editing flows that should keep local form state synchronized with backend data.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
- Editing one object loaded from `live(...)` with autosave or manual save.
|
|
13
|
+
- Editing list items inline (for example access roles) with per-row identifiers.
|
|
14
|
+
- Replacing custom `watch + api.command` autosave logic with a standard helper.
|
|
15
|
+
- Choosing between `synchronized`, `synchronizedList`, and `editorData`.
|
|
16
|
+
|
|
17
|
+
## Decision flow
|
|
18
|
+
|
|
19
|
+
1. **One editable object** (`profile`, `note`, `settings`) -> use `synchronized`.
|
|
20
|
+
2. **Editable list rows** (`accesses`, `invitations`, `requests`) -> use `synchronizedList`.
|
|
21
|
+
3. **Definition-driven CRUD form with validation UI** -> use `editorData` from auto-form stack.
|
|
22
|
+
|
|
23
|
+
## What counts as "editable list rows"
|
|
24
|
+
|
|
25
|
+
Treat the feature as a list flow (`synchronizedList`) when most of these are true:
|
|
26
|
+
|
|
27
|
+
- The UI renders rows with `v-for` and each row has editable fields.
|
|
28
|
+
- Users can edit many rows inline in one screen (table/config/admin list).
|
|
29
|
+
- Autosave should happen per row while the list stays reactive.
|
|
30
|
+
- Each row needs its own action payload keys in addition to shared list context.
|
|
31
|
+
|
|
32
|
+
In this case, wire one `synchronizedList(...)` and edit fields directly on `syncList.value`.
|
|
33
|
+
Do not build a parallel `id -> synchronized(...)` map for rows.
|
|
34
|
+
|
|
35
|
+
## `synchronized` pattern (object)
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
import { synchronized } from '@live-change/vue3-components'
|
|
39
|
+
|
|
40
|
+
const sync = synchronized({
|
|
41
|
+
source: sourceRef,
|
|
42
|
+
update: actions.service.updateThing,
|
|
43
|
+
identifiers: computed(() => ({ thing: thingId.value })),
|
|
44
|
+
recursive: true,
|
|
45
|
+
autoSave: true,
|
|
46
|
+
debounce: 600
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const { value: editable, changed, saving, save } = sync
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Rules
|
|
53
|
+
|
|
54
|
+
- `source` should come from `live(...)` or a computed source.
|
|
55
|
+
- Keep identifiers stable and pass them through `identifiers` (plain object or `computed`).
|
|
56
|
+
- Use `updateDataProperty: 'data'` for draft-like payloads where backend expects nested data.
|
|
57
|
+
- Use `autoSave: false` when save must happen only after explicit confirmation.
|
|
58
|
+
|
|
59
|
+
## `synchronizedList` pattern (list)
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
import { synchronizedList } from '@live-change/vue3-components'
|
|
63
|
+
|
|
64
|
+
const syncList = synchronizedList({
|
|
65
|
+
source: accesses,
|
|
66
|
+
update: actions.accessControl.updateSessionOrUserAndObjectOwnedAccess,
|
|
67
|
+
delete: actions.accessControl.resetSessionOrUserAndObjectOwnedAccess,
|
|
68
|
+
identifiers: { object, objectType },
|
|
69
|
+
objectIdentifiers: ({ to, sessionOrUser, sessionOrUserType }) => ({
|
|
70
|
+
access: to, sessionOrUser, sessionOrUserType, object, objectType
|
|
71
|
+
}),
|
|
72
|
+
recursive: true
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const editableRows = syncList.value
|
|
76
|
+
await syncList.delete(editableRows.value[0])
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Rules
|
|
80
|
+
|
|
81
|
+
- `source` should be a list from `live(...)`.
|
|
82
|
+
- Every item should have stable `id`.
|
|
83
|
+
- Put shared context in `identifiers`, and row-specific keys in `objectIdentifiers`.
|
|
84
|
+
- Edit rows through `syncList.value`; call `syncList.delete(...)` / `syncList.insert(...)` on the helper object.
|
|
85
|
+
- Prefer this pattern for configuration lists, permission tables, dictionaries, and other multi-row settings editors.
|
|
86
|
+
|
|
87
|
+
## Project examples to follow
|
|
88
|
+
|
|
89
|
+
- `speed-dating/front/src/components/notes/NoteEditor.vue` (`synchronized` with autosave)
|
|
90
|
+
- `speed-dating/front/src/components/profile/ProfileSettings.vue` (`synchronized` + `updateDataProperty: 'data'`)
|
|
91
|
+
- `family-tree/front/src/components/AgreementDialog.vue` (`synchronized` with manual save)
|
|
92
|
+
- `rcstreamer/front/src/configuration/AccessRequests.vue` (`synchronizedList`)
|
|
93
|
+
- `rcstreamer/front/src/configuration/AccessInvitations.vue` (`synchronizedList`)
|
|
94
|
+
- `rcstreamer/front/src/configuration/AccessList.vue` (`synchronizedList`)
|
|
95
|
+
|
|
96
|
+
## Common mistakes
|
|
97
|
+
|
|
98
|
+
- Using `synchronized` for a list of rows instead of `synchronizedList`.
|
|
99
|
+
- Forgetting `objectIdentifiers` when backend update/delete actions need per-row keys.
|
|
100
|
+
- Mutating raw `live(...)` list data instead of `synchronizedList(...).value`.
|
|
101
|
+
- Mixing autosave helper patterns with unrelated one-shot action form patterns.
|
package/.node-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
20.
|
|
1
|
+
20.20.2
|
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
20.
|
|
1
|
+
20.20.2
|
|
@@ -250,7 +250,7 @@ await api.create('user', {
|
|
|
250
250
|
</div>
|
|
251
251
|
<h4 class="font-semibold text-surface-900 dark:text-surface-100 mb-2">Install Framework</h4>
|
|
252
252
|
<p class="text-surface-600 dark:text-surface-300 text-sm">
|
|
253
|
-
npm install
|
|
253
|
+
npm install @live-change/framework
|
|
254
254
|
</p>
|
|
255
255
|
</div>
|
|
256
256
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/frontend-template",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.205",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"memDev": "tsx --inspect --expose-gc server/start.js memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
6
|
"localDevInit": "tsx server/start.js localDev --enableSessions --initScript ./init.js --dbAccess",
|
|
@@ -44,59 +44,59 @@
|
|
|
44
44
|
},
|
|
45
45
|
"type": "module",
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@codemirror/language": "6.
|
|
47
|
+
"@codemirror/language": "6.12.3",
|
|
48
48
|
"@dotenvx/dotenvx": "0.27.0",
|
|
49
49
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
|
50
|
-
"@live-change/access-control-frontend": "^0.9.
|
|
51
|
-
"@live-change/access-control-service": "^0.9.
|
|
52
|
-
"@live-change/agreement-service": "^0.9.
|
|
53
|
-
"@live-change/backup-service": "^0.9.
|
|
54
|
-
"@live-change/blog-frontend": "^0.9.
|
|
55
|
-
"@live-change/blog-service": "^0.9.
|
|
56
|
-
"@live-change/cli": "^0.9.
|
|
57
|
-
"@live-change/content-frontend": "^0.9.
|
|
58
|
-
"@live-change/content-service": "^0.9.
|
|
59
|
-
"@live-change/cron-service": "^0.9.
|
|
60
|
-
"@live-change/dao": "^0.9.
|
|
61
|
-
"@live-change/dao-vue3": "^0.9.
|
|
62
|
-
"@live-change/dao-websocket": "^0.9.
|
|
63
|
-
"@live-change/db-client": "^0.9.
|
|
64
|
-
"@live-change/draft-service": "^0.9.
|
|
65
|
-
"@live-change/email-service": "^0.9.
|
|
66
|
-
"@live-change/framework": "^0.9.
|
|
67
|
-
"@live-change/frontend-auto-form": "^0.9.
|
|
68
|
-
"@live-change/frontend-base": "^0.9.
|
|
69
|
-
"@live-change/geoip-service": "^0.9.
|
|
70
|
-
"@live-change/google-authentication-service": "^0.9.
|
|
71
|
-
"@live-change/image-frontend": "^0.9.
|
|
72
|
-
"@live-change/linkedin-authentication-service": "^0.9.
|
|
73
|
-
"@live-change/locale-settings-service": "^0.9.
|
|
74
|
-
"@live-change/notification-service": "^0.9.
|
|
75
|
-
"@live-change/password-authentication-service": "^0.9.
|
|
76
|
-
"@live-change/peer-connection-frontend": "^0.9.
|
|
77
|
-
"@live-change/peer-connection-service": "^0.9.
|
|
78
|
-
"@live-change/prosemirror-service": "^0.9.
|
|
79
|
-
"@live-change/scope-service": "^0.9.
|
|
80
|
-
"@live-change/secret-code-service": "^0.9.
|
|
81
|
-
"@live-change/secret-link-service": "^0.9.
|
|
82
|
-
"@live-change/security-service": "^0.9.
|
|
83
|
-
"@live-change/session-service": "^0.9.
|
|
84
|
-
"@live-change/task-frontend": "^0.9.
|
|
85
|
-
"@live-change/task-service": "^0.9.
|
|
86
|
-
"@live-change/timer-service": "^0.9.
|
|
87
|
-
"@live-change/upload-frontend": "^0.9.
|
|
88
|
-
"@live-change/upload-service": "^0.9.
|
|
89
|
-
"@live-change/url-frontend": "^0.9.
|
|
90
|
-
"@live-change/url-service": "^0.9.
|
|
91
|
-
"@live-change/user-frontend": "^0.9.
|
|
92
|
-
"@live-change/user-identification-service": "^0.9.
|
|
93
|
-
"@live-change/user-service": "^0.9.
|
|
94
|
-
"@live-change/video-call-frontend": "^0.9.
|
|
95
|
-
"@live-change/video-call-service": "^0.9.
|
|
96
|
-
"@live-change/vote-service": "^0.9.
|
|
97
|
-
"@live-change/vue3-components": "^0.9.
|
|
98
|
-
"@live-change/vue3-ssr": "^0.9.
|
|
99
|
-
"@live-change/wysiwyg-frontend": "^0.9.
|
|
50
|
+
"@live-change/access-control-frontend": "^0.9.205",
|
|
51
|
+
"@live-change/access-control-service": "^0.9.205",
|
|
52
|
+
"@live-change/agreement-service": "^0.9.205",
|
|
53
|
+
"@live-change/backup-service": "^0.9.205",
|
|
54
|
+
"@live-change/blog-frontend": "^0.9.205",
|
|
55
|
+
"@live-change/blog-service": "^0.9.205",
|
|
56
|
+
"@live-change/cli": "^0.9.205",
|
|
57
|
+
"@live-change/content-frontend": "^0.9.205",
|
|
58
|
+
"@live-change/content-service": "^0.9.205",
|
|
59
|
+
"@live-change/cron-service": "^0.9.205",
|
|
60
|
+
"@live-change/dao": "^0.9.205",
|
|
61
|
+
"@live-change/dao-vue3": "^0.9.205",
|
|
62
|
+
"@live-change/dao-websocket": "^0.9.205",
|
|
63
|
+
"@live-change/db-client": "^0.9.205",
|
|
64
|
+
"@live-change/draft-service": "^0.9.205",
|
|
65
|
+
"@live-change/email-service": "^0.9.205",
|
|
66
|
+
"@live-change/framework": "^0.9.205",
|
|
67
|
+
"@live-change/frontend-auto-form": "^0.9.205",
|
|
68
|
+
"@live-change/frontend-base": "^0.9.205",
|
|
69
|
+
"@live-change/geoip-service": "^0.9.205",
|
|
70
|
+
"@live-change/google-authentication-service": "^0.9.205",
|
|
71
|
+
"@live-change/image-frontend": "^0.9.205",
|
|
72
|
+
"@live-change/linkedin-authentication-service": "^0.9.205",
|
|
73
|
+
"@live-change/locale-settings-service": "^0.9.205",
|
|
74
|
+
"@live-change/notification-service": "^0.9.205",
|
|
75
|
+
"@live-change/password-authentication-service": "^0.9.205",
|
|
76
|
+
"@live-change/peer-connection-frontend": "^0.9.205",
|
|
77
|
+
"@live-change/peer-connection-service": "^0.9.205",
|
|
78
|
+
"@live-change/prosemirror-service": "^0.9.205",
|
|
79
|
+
"@live-change/scope-service": "^0.9.205",
|
|
80
|
+
"@live-change/secret-code-service": "^0.9.205",
|
|
81
|
+
"@live-change/secret-link-service": "^0.9.205",
|
|
82
|
+
"@live-change/security-service": "^0.9.205",
|
|
83
|
+
"@live-change/session-service": "^0.9.205",
|
|
84
|
+
"@live-change/task-frontend": "^0.9.205",
|
|
85
|
+
"@live-change/task-service": "^0.9.205",
|
|
86
|
+
"@live-change/timer-service": "^0.9.205",
|
|
87
|
+
"@live-change/upload-frontend": "^0.9.205",
|
|
88
|
+
"@live-change/upload-service": "^0.9.205",
|
|
89
|
+
"@live-change/url-frontend": "^0.9.205",
|
|
90
|
+
"@live-change/url-service": "^0.9.205",
|
|
91
|
+
"@live-change/user-frontend": "^0.9.205",
|
|
92
|
+
"@live-change/user-identification-service": "^0.9.205",
|
|
93
|
+
"@live-change/user-service": "^0.9.205",
|
|
94
|
+
"@live-change/video-call-frontend": "^0.9.205",
|
|
95
|
+
"@live-change/video-call-service": "^0.9.205",
|
|
96
|
+
"@live-change/vote-service": "^0.9.205",
|
|
97
|
+
"@live-change/vue3-components": "^0.9.205",
|
|
98
|
+
"@live-change/vue3-ssr": "^0.9.205",
|
|
99
|
+
"@live-change/wysiwyg-frontend": "^0.9.205",
|
|
100
100
|
"@vueuse/core": "^12.3.0",
|
|
101
101
|
"codeceptjs-assert": "^0.0.5",
|
|
102
102
|
"compression": "^1.7.5",
|
|
@@ -119,8 +119,8 @@
|
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"copyfiles": "^2.4.1",
|
|
122
|
-
"generate-password": "1.7.1",
|
|
123
|
-
"playwright": "1.49.1",
|
|
122
|
+
"generate-password": "^1.7.1",
|
|
123
|
+
"playwright": "^1.49.1",
|
|
124
124
|
"random-profile-generator": "^2.3.0",
|
|
125
125
|
"tsx": "^4.21.0",
|
|
126
126
|
"txtgen": "^3.0.7",
|
|
@@ -129,5 +129,5 @@
|
|
|
129
129
|
"author": "Michał Łaszczewski <michal@laszczewski.pl>",
|
|
130
130
|
"license": "ISC",
|
|
131
131
|
"description": "",
|
|
132
|
-
"gitHead": "
|
|
132
|
+
"gitHead": "ef195e51ea283e56d891b11da5d5f586691507db"
|
|
133
133
|
}
|