@live-change/frontend-template 0.9.199 → 0.9.201

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 (44) hide show
  1. package/.claude/rules/live-change-backend-actions-views-triggers.md +62 -0
  2. package/.claude/rules/live-change-backend-event-sourcing.md +186 -0
  3. package/.claude/rules/live-change-backend-models-and-relations.md +72 -0
  4. package/.claude/rules/live-change-frontend-vue-primevue.md +26 -0
  5. package/.claude/settings.json +32 -0
  6. package/.claude/skills/create-skills-and-rules/SKILL.md +248 -0
  7. package/.claude/skills/live-change-backend-change-triggers/SKILL.md +186 -0
  8. package/.claude/skills/live-change-design-actions-views-triggers/SKILL.md +462 -0
  9. package/.claude/skills/live-change-design-models-relations/SKILL.md +230 -0
  10. package/.claude/skills/live-change-design-service/SKILL.md +133 -0
  11. package/.claude/skills/live-change-frontend-accessible-objects/SKILL.md +384 -0
  12. package/.claude/skills/live-change-frontend-accessible-objects.md +383 -0
  13. package/.claude/skills/live-change-frontend-action-buttons/SKILL.md +129 -0
  14. package/.claude/skills/live-change-frontend-action-form/SKILL.md +149 -0
  15. package/.claude/skills/live-change-frontend-analytics/SKILL.md +147 -0
  16. package/.claude/skills/live-change-frontend-command-forms/SKILL.md +216 -0
  17. package/.claude/skills/live-change-frontend-data-views/SKILL.md +183 -0
  18. package/.claude/skills/live-change-frontend-editor-form/SKILL.md +240 -0
  19. package/.claude/skills/live-change-frontend-locale-time/SKILL.md +172 -0
  20. package/.claude/skills/live-change-frontend-page-list-detail/SKILL.md +201 -0
  21. package/.claude/skills/live-change-frontend-range-list/SKILL.md +129 -0
  22. package/.claude/skills/live-change-frontend-ssr-setup/SKILL.md +119 -0
  23. package/.cursor/rules/live-change-backend-actions-views-triggers.mdc +88 -0
  24. package/.cursor/rules/live-change-backend-event-sourcing.mdc +185 -0
  25. package/.cursor/rules/live-change-backend-models-and-relations.mdc +62 -0
  26. package/.cursor/skills/create-skills-and-rules.md +248 -0
  27. package/.cursor/skills/live-change-backend-change-triggers.md +186 -0
  28. package/.cursor/skills/live-change-design-actions-views-triggers.md +178 -79
  29. package/.cursor/skills/live-change-design-models-relations.md +112 -50
  30. package/.cursor/skills/live-change-design-service.md +1 -0
  31. package/.cursor/skills/live-change-frontend-accessible-objects.md +384 -0
  32. package/.cursor/skills/live-change-frontend-action-buttons.md +1 -0
  33. package/.cursor/skills/live-change-frontend-action-form.md +9 -3
  34. package/.cursor/skills/live-change-frontend-analytics.md +1 -0
  35. package/.cursor/skills/live-change-frontend-command-forms.md +1 -0
  36. package/.cursor/skills/live-change-frontend-data-views.md +1 -0
  37. package/.cursor/skills/live-change-frontend-editor-form.md +135 -72
  38. package/.cursor/skills/live-change-frontend-locale-time.md +1 -0
  39. package/.cursor/skills/live-change-frontend-page-list-detail.md +1 -0
  40. package/.cursor/skills/live-change-frontend-range-list.md +1 -0
  41. package/.cursor/skills/live-change-frontend-ssr-setup.md +1 -0
  42. package/front/src/router.js +2 -1
  43. package/opencode.json +10 -0
  44. package/package.json +52 -50
@@ -0,0 +1,384 @@
1
+ ---
2
+ name: live-change-frontend-accessible-objects
3
+ description: List entities accessible via access control views (myAccessibleObjects, myAccessesByObjectType, invitations, roles)
4
+ ---
5
+
6
+ # Skill: live-change-frontend-accessible-objects (Claude Code)
7
+
8
+ Use this skill when you need to **list entities accessible to the current user** using the access-control service:
9
+
10
+ - objects with `entity` access control (not simple `userItem` relations),
11
+ - objects the user has roles on via `accessControl_setAccess` / `accessControl_setPublicAccess`,
12
+ - invitations to objects,
13
+ - role-filtered lists of accessible objects.
14
+
15
+ This skill complements:
16
+
17
+ - backend skill `live-change-design-actions-views-triggers` (how to grant access and enable indexes),
18
+ - frontend skills `live-change-frontend-data-views` and `live-change-frontend-page-list-detail`.
19
+
20
+ ## When to use
21
+
22
+ - You want **all objects of a given type** that the current user can access (not just those they created).
23
+ - You are building a list like “My companies”, “My projects”, “Events I can join”.
24
+ - You already grant access through access-control triggers (owner / member / reader roles).
25
+ - The objects are **entities** with access control, not simple `userItem` / `itemOf` relations.
26
+
27
+ If you simply need “records owned by user”, prefer the `userItem` / `itemOf` relation patterns from `live-change-design-models-relations`. Use this skill when access is controlled by roles in the access-control service.
28
+
29
+ ---
30
+
31
+ ## Step 1 – Understand the views you can use
32
+
33
+ There are two main ways to list accessible objects on the frontend.
34
+
35
+ ### 1. Indexed pipeline (requires `indexed: true` on access-control)
36
+
37
+ Enabled when the access-control service is configured with:
38
+
39
+ ```js
40
+ // app.server/app.config.js (service config)
41
+ {
42
+ name: 'accessControl',
43
+ createSessionOnUpdate: true,
44
+ contactTypes,
45
+ indexed: true
46
+ }
47
+ ```
48
+
49
+ Then the following views become available (from `access-control-service/indexes.js`):
50
+
51
+ - `myAccessibleObjects({ objectType?, ...range })`
52
+ - `myAccessibleObjectsByRole({ role, objectType?, ...range })`
53
+ - `accessibleObjects(...)` / `accessibleObjectsByRole(...)` – admin-only variants with explicit `sessionOrUserType` + `sessionOrUser`
54
+ - `objectAccesses({ objectType, object, role?, ...range })` – list owners/roles for a specific object
55
+
56
+ Use these when you want:
57
+
58
+ - “all my accessible objects of type X” (`myAccessibleObjects` with `objectType`),
59
+ - “all my objects where I have role Y” (`myAccessibleObjectsByRole` with `role`),
60
+ - admin tools for inspecting who has access to what (`accessibleObjects`, `objectAccesses`).
61
+
62
+ ### 2. Non-indexed views (always available)
63
+
64
+ Even without `indexed: true`, `view.js` provides:
65
+
66
+ - `myAccessesByObjectType({ objectType, ...range })`
67
+ - `myAccessesByObjectTypeAndRole({ objectType, role, ...range })`
68
+ - `myAccessInvitationsByObjectType({ objectType, ...range })`
69
+ - `myAccessInvitationsByObjectTypeAndRole({ objectType, role, ...range })`
70
+
71
+ Use these when:
72
+
73
+ - you need invitations in addition to accepted accesses,
74
+ - the project has not enabled `indexed: true` yet,
75
+ - you are building paginated lists with `<RangeViewer>` based on access entries.
76
+
77
+ **Object type format:** always `serviceName_ModelName` (for example `company_Company`, `speedDating_Event`).
78
+
79
+ ---
80
+
81
+ ## Step 2 – Simple list with `myAccessibleObjects` (indexed)
82
+
83
+ This is the most convenient way to list all entities of one type accessible to the current user, when indexes are enabled.
84
+
85
+ Example: list companies the user can access (similar to auto-firma `companies/index.vue`):
86
+
87
+ ```vue
88
+ <route>
89
+ { "name": "companies", "meta": { "signedIn": true } }
90
+ </route>
91
+
92
+ <template>
93
+ <div class="container mx-auto p-4">
94
+ <div class="flex items-center justify-between mb-6">
95
+ <h1 class="text-2xl font-bold">{{ t('companies.list.title') }}</h1>
96
+ <Button :label="t('companies.list.addButton')" icon="pi pi-plus"
97
+ @click="router.push({ name: 'companiesNew' })" />
98
+ </div>
99
+
100
+ <Card v-if="!accessibleCompanies?.length">
101
+ <template #content>
102
+ <p class="text-center text-gray-500">
103
+ {{ t('companies.list.empty') }}
104
+ </p>
105
+ </template>
106
+ </Card>
107
+
108
+ <div class="grid gap-4">
109
+ <Card v-for="accessible in accessibleCompanies" :key="accessible.id"
110
+ class="cursor-pointer hover:shadow-md transition-shadow">
111
+ <template #content>
112
+ <div class="flex items-center justify-between">
113
+ <div>
114
+ <div class="text-lg font-semibold">{{ accessible.company.name }}</div>
115
+ <div class="text-sm text-gray-500">
116
+ {{ t('companies.list.nipLabel') }} {{ accessible.company.nip }}
117
+ </div>
118
+ </div>
119
+ <router-link :to="{ name: 'company', params: { company: accessible.company.id } }">
120
+ <Button :label="t('companies.list.details')"
121
+ icon="pi pi-arrow-right" severity="secondary" />
122
+ </router-link>
123
+ </div>
124
+ </template>
125
+ </Card>
126
+ </div>
127
+ </div>
128
+ </template>
129
+
130
+ <script setup>
131
+ import { live, usePath, useClient } from '@live-change/vue3-ssr'
132
+ import { useRouter } from 'vue-router'
133
+ import { computed } from 'vue'
134
+ import { useI18n } from 'vue-i18n'
135
+ import Button from 'primevue/button'
136
+ import Card from 'primevue/card'
137
+
138
+ const router = useRouter()
139
+ const path = usePath()
140
+ const client = useClient()
141
+ const { t } = useI18n()
142
+
143
+ const accessibleCompaniesPath = computed(() =>
144
+ client.value.user
145
+ ? path.accessControl.myAccessibleObjects({ objectType: 'company_Company' })
146
+ .with(accessible =>
147
+ path.company.company({ company: accessible.object }).bind('company')
148
+ )
149
+ : null
150
+ )
151
+
152
+ const [accessibleCompanies] = await Promise.all([
153
+ live(accessibleCompaniesPath)
154
+ ])
155
+ </script>
156
+ ```
157
+
158
+ Key points:
159
+
160
+ - **Guard with `client.value.user`** so that unauthenticated users do not try to load the view.
161
+ - Pass `objectType: 'service_Model'`, e.g. `'company_Company'`.
162
+ - Use `.with()` to hydrate each access entry with the full entity (`accessible.object` → `company`).
163
+
164
+ ---
165
+
166
+ ## Step 3 – Paginated list with `myAccessesByObjectType` + `RangeViewer`
167
+
168
+ When you want an infinite scroll or very large lists, use the non-indexed range views and `<RangeViewer>` (see also `live-change-frontend-range-list` and `speed-dating/front/src/pages/events/index.vue`).
169
+
170
+ Example: “My events” with invitations and accepted accesses:
171
+
172
+ ```vue
173
+ <template>
174
+ <div class="w-full max-w-6xl">
175
+ <div class="w-full" v-if="anyInvitations?.length">
176
+ <div class="bg-surface-0 dark:bg-surface-900 rounded-border shadow-sm p-4 mt-4">
177
+ <div class="text-2xl font-medium text-surface-800 dark:text-surface-50">
178
+ {{ t('event.invitations.title') }}
179
+ </div>
180
+ </div>
181
+ <range-viewer :pathFunction="myEventsInvitationsPathRange" key="invitations"
182
+ :canLoadTop="false" canDropBottom
183
+ loadBottomSensorSize="4000px" dropBottomSensorSize="3000px">
184
+ <template #empty>
185
+ <div class="text-xl text-surface-800 dark:text-surface-50 my-4 mx-4">
186
+ {{ t('event.invitations.noEventsFound') }}
187
+ </div>
188
+ </template>
189
+ <template #default="{ item: invitation }">
190
+ <EventCard :event="invitation.event" class="my-2" />
191
+ </template>
192
+ </range-viewer>
193
+ </div>
194
+
195
+ <div class="w-full">
196
+ <div class="bg-surface-0 dark:bg-surface-900 rounded-border shadow-sm p-4 mt-4">
197
+ <div class="text-2xl font-medium text-surface-800 dark:text-surface-50">
198
+ {{ t('event.yourEvents') }}
199
+ </div>
200
+ </div>
201
+ <range-viewer :pathFunction="myEventsAccessesPathRange" key="myEvents"
202
+ :canLoadTop="false" canDropBottom
203
+ loadBottomSensorSize="4000px" dropBottomSensorSize="3000px">
204
+ <template #empty>
205
+ <div class="text-xl text-surface-800 dark:text-surface-50 my-4 mx-4">
206
+ {{ t('event.invitations.noEventsFound') }}
207
+ </div>
208
+ </template>
209
+ <template #default="{ item: access }">
210
+ <EventCard :event="access.event" class="my-2" />
211
+ </template>
212
+ </range-viewer>
213
+ </div>
214
+ </div>
215
+ </template>
216
+
217
+ <script setup>
218
+ import EventCard from '../../components/events/EventCard.vue'
219
+ import { RangeViewer } from '@live-change/vue3-components'
220
+
221
+ import { usePath, live, useClient, reverseRange } from '@live-change/vue3-ssr'
222
+ import { useI18n } from 'vue-i18n'
223
+
224
+ const path = usePath()
225
+ const client = useClient()
226
+ const { t } = useI18n()
227
+
228
+ function myEventsAccessesPathRange(range) {
229
+ return path.accessControl.myAccessesByObjectType({
230
+ objectType: 'speedDating_Event',
231
+ ...reverseRange(range)
232
+ }).with(access =>
233
+ path.speedDating.event({ event: access.object }).bind('event')
234
+ )
235
+ }
236
+
237
+ function myEventsInvitationsPathRange(range) {
238
+ return path.accessControl.myAccessInvitationsByObjectType({
239
+ objectType: 'speedDating_Event',
240
+ ...reverseRange(range)
241
+ }).with(invitation =>
242
+ path.speedDating.event({ event: invitation.object }).bind('event')
243
+ )
244
+ }
245
+
246
+ const anyInvitationsPath = myEventsInvitationsPathRange({ limit: 1 })
247
+
248
+ const [anyInvitations] = await Promise.all([
249
+ live(anyInvitationsPath)
250
+ ])
251
+ </script>
252
+ ```
253
+
254
+ Notes:
255
+
256
+ - `myAccessesByObjectType` returns access entries with `objectType`, `object`, `role`, etc.
257
+ - You hydrate each access with the real entity using `.with(...)`.
258
+ - `<RangeViewer>` takes a `pathFunction(range)` that must handle `gt` / `lt` / `limit` (here we use `reverseRange(range)` from `@live-change/vue3-ssr`).
259
+
260
+ ---
261
+
262
+ ## Step 4 – Add invitations next to accepted accesses
263
+
264
+ Invitations are separate records that share the same `objectType` / `object` fields. To show invitations together with accepted accesses:
265
+
266
+ 1. Use `myAccessInvitationsByObjectType` (and optionally `...ByObjectTypeAndRole`) for pending invites.
267
+ 2. Use `myAccessesByObjectType` (and optionally `...ByObjectTypeAndRole`) for accepted access.
268
+ 3. Display them in separate sections or merge in the UI.
269
+
270
+ Example outline:
271
+
272
+ ```js
273
+ function myObjectsAccessesPathRange(range) {
274
+ return path.accessControl.myAccessesByObjectType({
275
+ objectType: 'myService_MyEntity',
276
+ ...reverseRange(range)
277
+ }).with(access =>
278
+ path.myService.myEntity({ entity: access.object }).bind('entity')
279
+ )
280
+ }
281
+
282
+ function myObjectsInvitationsPathRange(range) {
283
+ return path.accessControl.myAccessInvitationsByObjectType({
284
+ objectType: 'myService_MyEntity',
285
+ ...reverseRange(range)
286
+ }).with(invitation =>
287
+ path.myService.myEntity({ entity: invitation.object }).bind('entity')
288
+ )
289
+ }
290
+ ```
291
+
292
+ Use two `<RangeViewer>` blocks or one section with two lists, depending on UX.
293
+
294
+ ---
295
+
296
+ ## Step 5 – Filter by role with `myAccessibleObjectsByRole`
297
+
298
+ When indexes are enabled, you can query only objects where the current user has a specific role (for example `owner`, `admin`, `member`).
299
+
300
+ Example:
301
+
302
+ ```js
303
+ import { computed } from 'vue'
304
+ import { usePath, useClient, live } from '@live-change/vue3-ssr'
305
+
306
+ const path = usePath()
307
+ const client = useClient()
308
+
309
+ const ownerProjectsPath = computed(() =>
310
+ client.value.user
311
+ ? path.accessControl.myAccessibleObjectsByRole({
312
+ role: 'owner',
313
+ objectType: 'project_Project'
314
+ }).with(accessible =>
315
+ path.project.project({ project: accessible.object }).bind('project')
316
+ )
317
+ : null
318
+ )
319
+
320
+ const [ownerProjects] = await Promise.all([
321
+ live(ownerProjectsPath)
322
+ ])
323
+ ```
324
+
325
+ Use this pattern for:
326
+
327
+ - “Projects I own” vs. “Projects where I am a member”.
328
+ - “Companies where I am accountant” vs. general access.
329
+
330
+ ---
331
+
332
+ ## Step 6 – Admin tools with `accessibleObjects` and `objectAccesses`
333
+
334
+ For administrative UIs you may need to:
335
+
336
+ - list all objects accessible to a given user,
337
+ - list all users who have access to a specific object and their roles.
338
+
339
+ Use these views (available when `indexed: true`):
340
+
341
+ - `accessibleObjects({ sessionOrUserType, sessionOrUser, objectType?, ...range })`
342
+ - `accessibleObjectsByRole({ sessionOrUserType, sessionOrUser, role, objectType?, ...range })`
343
+ - `objectAccesses({ objectType, object, role?, ...range })`
344
+
345
+ Important:
346
+
347
+ - These views require **admin** role (checked in `indexes.js`).
348
+ - `sessionOrUserType` is `'user_User'` for users or `'session_Session'` for anonymous sessions.
349
+ - `sessionOrUser` is the user id or session id.
350
+
351
+ Example skeleton for an admin panel:
352
+
353
+ ```js
354
+ const usersProjectsPath = computed(() =>
355
+ client.value.roles.includes('admin')
356
+ ? path.accessControl.accessibleObjectsByRole({
357
+ sessionOrUserType: 'user_User',
358
+ sessionOrUser: inspectedUserId,
359
+ role: 'member',
360
+ objectType: 'project_Project'
361
+ }).with(accessible =>
362
+ path.project.project({ project: accessible.object }).bind('project')
363
+ )
364
+ : null
365
+ )
366
+ ```
367
+
368
+ ---
369
+
370
+ ## Summary
371
+
372
+ | Need | View | Notes |
373
+ |---|---|---|
374
+ | All entities of one type I can access | `myAccessibleObjects({ objectType })` | Requires `indexed: true`, simplest pattern |
375
+ | All entities where I have specific role | `myAccessibleObjectsByRole({ role, objectType? })` | Use for “owner”, “member”, etc. |
376
+ | Paginated “my X” list without indexes | `myAccessesByObjectType` + `<RangeViewer>` | Always available, hydrate with `.with()` |
377
+ | Pending invitations | `myAccessInvitationsByObjectType` | Often shown in a separate section |
378
+ | Admin: what objects a user can access | `accessibleObjects` / `accessibleObjectsByRole` | Requires admin role, explicit user id |
379
+ | Admin: who has access to object | `objectAccesses` | Good for audit / sharing dialogs |
380
+
381
+ Remember:
382
+
383
+ - `objectType` is always `service_Model`.
384
+ - For relations like `userItem`, use relation-based views instead; this skill is for **access-control based entities**.