@jskit-ai/agent-docs 0.1.191 → 0.1.193

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 (32) hide show
  1. package/guide/agent/app-setup/existing-application-migration.md +30 -0
  2. package/guide/agent/app-setup/multi-homing.md +212 -0
  3. package/guide/agent/framework/material-3.md +11 -2
  4. package/guide/agent/framework/ui-operations.md +35 -0
  5. package/package.json +1 -1
  6. package/patterns/INDEX.md +1 -1
  7. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  8. package/patterns/minimal-foundation/example/package.json +4 -4
  9. package/patterns/page-scaffolding.md +2 -0
  10. package/patterns/shell-foundation/example/package.json +5 -5
  11. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  12. package/patterns/ui-contract.md +7 -0
  13. package/reference/autogen/PATTERN_INDEX.md +35 -35
  14. package/skills/jskit/SKILL.md +9 -4
  15. package/skills/jskit/references/existing-application-migration.md +30 -0
  16. package/skills/jskit/references/material-3.md +11 -2
  17. package/skills/jskit/references/multi-homing.md +274 -0
  18. package/skills/jskit/references/pattern-index.md +35 -35
  19. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
  20. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
  21. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  22. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  23. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  24. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  25. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  26. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  27. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  28. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  29. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  30. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  31. package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +49 -0
  32. package/skills/jskit/references/ui-operations.md +35 -0
@@ -222,6 +222,36 @@ not undo the write. Do not turn pending, committed, or unknown write failures
222
222
  into successful missing-record results, or automatically replay a committed
223
223
  or unresolved write.
224
224
 
225
+ ### Workspace invitation transaction compatibility (workspaces-core 0.1.198)
226
+
227
+ Workspace invitation creation, acceptance and refusal now own managed
228
+ transactions. Existing calls to `members.createInvite(..., { trx })` must become
229
+ `members.prepareInvite(..., { trx })`, application association/receipt writes,
230
+ confirmed commit, then `members.sendInvite(...)` without `trx`. Move writes that
231
+ must accompany built-in acceptance into
232
+ `pendingInvitations.registerAcceptanceParticipant(fn)` during feature setup.
233
+ Acceptance/refusal reject borrowed transactions; revocation can still participate
234
+ in a managed transaction. Ordinary invitation actions retain their IDs and
235
+ response shapes. Terminal repository transitions now reject instead of
236
+ rewriting already accepted/revoked invitations. Mailer exceptions return a fixed
237
+ message to avoid leaking invitation tokens from provider diagnostics.
238
+
239
+ Required participant writes roll back together. Preserve transaction outcomes:
240
+ a failure after commit is not rollback, and an unresolved commit must not be
241
+ blindly replayed. Matching-recipient expiry revocation deliberately commits
242
+ before its 409 rejection. Review the
243
+ [workspace invitation contract and example](./multi-homing.md#transactional-invitation-participation)
244
+ when migrating. No schema migration is required for these framework APIs.
245
+
246
+ The first coordinated release containing this contract includes
247
+ `@jskit-ai/workspaces-core@0.1.198`, `@jskit-ai/jskit-catalog@0.1.245` and
248
+ `@jskit-ai/agent-docs@0.1.192`. Once that release is published, use
249
+ `npm run jskit:update` in the application to upgrade the coordinated graph,
250
+ restart the backend, check the actual installed versions, and assert
251
+ that `prepareInvite`, `sendInvite` and `registerAcceptanceParticipant` are present
252
+ on the resolved capability. Application domain integration and real email
253
+ activation remain separate work.
254
+
225
255
  ## 4. Adopt migrations without breaking either database history
226
256
 
227
257
  Every immutable migration belongs to the package that owns its tables. Declare
@@ -39,6 +39,218 @@ before an operation discloses records or record existence.
39
39
  membership semantics exactly; do not synthesize memberships to compensate for
40
40
  a policy/runtime mismatch.
41
41
 
42
+ ## Transactional invitation participation
43
+
44
+ Use the existing `workspaces.core` capability when accepting an invitation must
45
+ also connect an application-owned identity, training history, access scopes or
46
+ required audit records. JSKIT owns tokens, invitation state and membership; the
47
+ application owns its reviewed association and domain policy. The built-in
48
+ `workspace.invite.redeem` action runs the participant. No replacement route,
49
+ client follow-up save or after-action event is needed.
50
+
51
+ Saving intended access without sending remains an application draft. Do not call
52
+ preparation until an administrator deliberately issues an invitation: preparation
53
+ creates a real token and pending invitation, but does not activate membership.
54
+
55
+ ### Public server methods
56
+
57
+ These methods are trusted server composition APIs, not new public actions. The
58
+ caller must authorize creation, delivery and revocation in the requested workspace.
59
+ The ordinary invitation routes retain their existing authorization and role
60
+ validation. Do not forward arbitrary HTTP input as server composition options.
61
+
62
+ | Capability method | Contract |
63
+ | --- | --- |
64
+ | `workspaces.repositories.workspaceInvites.withTransaction(work)` | Delegates to the same JSON REST API's managed `transaction(work)`; awaits commit and completion hooks before resolving. |
65
+ | `workspaces.repositories.workspaceInvites.lockWorkspaceForInvitations(workspaceId, { trx })` | Locks the workspace before application receipt/draft rows when composing preparation. Requires an active transaction. |
66
+ | `workspaces.services.members.prepareInvite(workspace, actor, payload, options)` | Requires a managed `options.trx`. Validates the invitation role, replaces pending invitations for that workspace/email, and returns `{ createdInviteId, inviteTokenPreview }`. Never sends. |
67
+ | `workspaces.services.members.sendInvite(workspace, actor, prepared, options)` | Requires no `options.trx`. Reloads the committed pending invitation and workspace display data, validates expiry and token correspondence, and sends that invitation without replacement. |
68
+ | `workspaces.services.members.createInvite(workspace, actor, payload, options)` | Owns preparation and commit, then calls `sendInvite`; retains the ordinary response shape. Rejects `options.trx`. |
69
+ | `workspaces.services.pendingInvitations.registerAcceptanceParticipant(fn)` | Registers one application callback receiving `({ invite, user }, options)`. Invalid, duplicate and late registration reject. |
70
+
71
+ `sendInvite` accepts `{ createdInviteId, inviteTokenPreview }` from preparation or
72
+ only `{ createdInviteId }` for a later attempt. The latter uses JSKIT's existing
73
+ opaque token-hash encoding without rotating the token. Supplying an empty or
74
+ mismatched token rejects. Email, role, expiry and workspace display fields come
75
+ from the stored invitation/workspace, not replacement input. Its response keeps
76
+ `createdInviteId`, `inviteTokenPreview`, `inviteUrl` and `inviteDelivery` alongside
77
+ the ordinary workspace/invitation-list payload. Treat tokens and URLs as secrets;
78
+ do not log them or put them in diagnostic messages or administrative receipts.
79
+ A receipt normally needs only the exact invitation ID.
80
+
81
+ ### Preparation, association, commit and controlled delivery
82
+
83
+ Inside application feature setup, declare `workspaces: "workspaces.core"` in
84
+ `requires`, then use that capability. The `reviewedPeople`, `people`, `scopes`
85
+ and `audit` names below represent application-owned services, not JSKIT APIs.
86
+ Their writes must forward the provided transaction to their repositories.
87
+
88
+ ```js
89
+ // During application Feature.setup, before serving requests:
90
+ const { members, pendingInvitations } = workspaces.services;
91
+ const invites = workspaces.repositories.workspaceInvites;
92
+
93
+ pendingInvitations.registerAcceptanceParticipant(async ({ invite, user }, options) => {
94
+ // Resolve from the exact validated invitation, never a client person ID.
95
+ const association = await reviewedPeople.resolveInvitation(invite.id, options);
96
+ if (association.kind === "ordinary") return;
97
+ await reviewedPeople.requireCurrentReview(association, {
98
+ workspaceId: invite.workspaceId,
99
+ user,
100
+ roleSid: invite.roleSid
101
+ }, options);
102
+ await people.linkAccountAndTraining(association.personId, user.id, options);
103
+ await scopes.applyReviewedAccess(association, user.id, options);
104
+ await audit.recordInvitationAcceptance(association, user.id, options);
105
+ });
106
+
107
+ // Inside an authorized application command:
108
+ const prepared = await invites.withTransaction(async (trx) => {
109
+ const options = { trx, context };
110
+ // Match acceptance's order: workspace, then application draft/receipt rows.
111
+ await invites.lockWorkspaceForInvitations(workspace.id, options);
112
+ const reviewed = await reviewedPeople.lockAuthorizedDraft(draftId, workspace.id, actor, options);
113
+ const receipt = await reviewedPeople.findMatchingReceipt(reviewed, requestId, options);
114
+ if (receipt) return { createdInviteId: receipt.inviteId };
115
+ const created = await members.prepareInvite(workspace, actor, {
116
+ email: reviewed.email,
117
+ roleSid: reviewed.roleSid
118
+ }, options);
119
+ await reviewedPeople.associateInvitationAndSaveReceipt(reviewed, requestId, created.createdInviteId, options);
120
+ return { createdInviteId: created.createdInviteId };
121
+ }); // Confirmed commit; a rejection here must not fall through to delivery.
122
+
123
+ const result = await members.sendInvite(workspace, actor, prepared, { context });
124
+ // Persist delivery feedback through the application owner. Avoid logging result:
125
+ // it contains a secret token and URL as well as the nonsecret delivery outcome.
126
+ await reviewedPeople.recordDeliveryOutcome(prepared.createdInviteId, result.inviteDelivery, { context });
127
+ ```
128
+
129
+ The application must lock its reviewed draft/receipt, enforce request identity,
130
+ and reject changed input on a reused request ID. A retry after losing the
131
+ preparation response reads that receipt instead of preparing another invitation.
132
+ Association failure rolls back the new invitation, replacement of an older
133
+ invitation, receipt and application writes. This is not generic framework
134
+ idempotency, and does not imply exactly-once email.
135
+
136
+ Real SMTP is unnecessary for implementation and verification. With no
137
+ `workspaceInviteMailer`, delivery returns `status: "mailer_unconfigured"`, an
138
+ explanatory `message` and an empty `providerMessageId`; the invitation remains
139
+ committed. A test application can configure a controlled mailer:
140
+
141
+ ```js
142
+ workspaceInviteMailer: {
143
+ async sendWorkspaceInvite(message) {
144
+ capturedInvites.push(message); // Test-local memory; contains the secret link.
145
+ return { status: "sent", message: "Captured by test mailer.", providerMessageId: "fixture-1" };
146
+ }
147
+ }
148
+ ```
149
+
150
+ A thrown renderer/mailer error returns `inviteDelivery.status: "failed"` and a
151
+ fixed message, avoiding accidental token leakage through provider exceptions.
152
+ It does not delete the invitation/association or activate membership. Mailer
153
+ results retain their normalized status, message and provider message ID; a
154
+ successful default is `sent`. A provider exception or interrupted response may
155
+ occur after a provider accepted the message: `failed` is not proof of nondelivery.
156
+ There is no exactly-once delivery guarantee. Applications own durable delivery
157
+ attempts and safe retry policy. Validation checks committed invitation state
158
+ before sending; revocation after sending starts cannot recall the message.
159
+
160
+ ### Acceptance and failure semantics
161
+
162
+ Register one callback during feature setup. It composes any application domain
163
+ owners explicitly. Non-functions throw `TypeError`; a second registration
164
+ throws, and registration closes when acceptance/refusal first begins. The
165
+ callback's invite, user and options envelope are shallow frozen. It receives the
166
+ same managed `trx` and request `context`; transaction ownership stays with JSKIT.
167
+
168
+ Acceptance authenticates the user, checks email, pending state and expiry, then
169
+ revalidates the invitation under workspace/invitation locks. Membership is
170
+ available as active inside that transaction before the callback runs. Existing
171
+ role-upsert semantics remain: the stored invitation role becomes the membership
172
+ role, including for an existing membership. The callback cannot authorize role
173
+ or scope overrides from client input. It must recheck the reviewed version,
174
+ workspace, current inviter authority, person eligibility, existing account link,
175
+ and scope eligibility using application-owned data.
176
+
177
+ An ordinary invitation with no application association may pass through. A
178
+ stale, revoked, conflicting or expected-but-missing association must fail closed.
179
+ The application needs enough durable association/review state to distinguish
180
+ these cases; absence alone cannot identify a deleted reviewed association.
181
+
182
+ Await every owner write and forward the same transaction. Do not commit,
183
+ roll back, use savepoints, start a new transaction, or perform external side
184
+ effects in the callback. Do not swallow failures. Participant exceptions and
185
+ later invitation-status failures roll back membership (including its prior role
186
+ and status), person/training links, scopes, required audit and accepted status.
187
+ The callback is not invoked for an invalid invitation or on refusal. Acceptance
188
+ and refusal reject borrowed `options.trx`; revocation can join a managed
189
+ transaction or own one when none is supplied.
190
+
191
+ A missing/already-handled invitation rejects with 404; mismatched authenticated
192
+ email with 403; unauthenticated access with 401. An expired invitation for its
193
+ matching recipient is revoked in a committed transaction and then rejects with
194
+ 409 and `transactionOutcome: "committed"`. It never activates membership or
195
+ runs the participant. A failure to commit that revocation preserves the managed
196
+ transaction error instead. Delivery rejects missing/foreign/handled invitations
197
+ with 404, expired invitations with 409, and mismatched tokens with 400.
198
+
199
+ Keep `error.transactionOutcome` and `error.cause` when translating failures.
200
+ `rolledBack` confirms rollback; `committed` can indicate a completion-hook error
201
+ after successful persistence; `unknown` leaves the outcome unresolved. A thrown
202
+ error alone does not prove rollback. After-commit failures and external effects
203
+ cannot be included in the atomic guarantee. See
204
+ [managed transaction guidance](./existing-application-migration.md#json-rest-v2-integration).
205
+
206
+ Invitation mutations lock workspace before invitation rows, and use current
207
+ locking reads before terminal transitions. Accepted/revoked state cannot be
208
+ overwritten by a losing acceptance, refusal, revocation or replacement. A
209
+ replacement after acceptance may create a new pending invitation while retaining
210
+ the older accepted invitation. Keep callbacks short: invitation changes in the
211
+ same workspace serialize. Database deadlocks/serialization failures remain
212
+ transaction errors; callbacks are not automatically replayed.
213
+
214
+ ### Compatibility and verification
215
+
216
+ This server-composition contract starts with `@jskit-ai/workspaces-core@0.1.198`.
217
+ Transactional callers of
218
+ `createInvite` must migrate to preparation plus post-commit delivery. Related
219
+ acceptance writes must move into the participant. `markAcceptedById` and
220
+ `revokeById` now reject terminal/missing invitations rather than overwriting their
221
+ state. No route/action ID, package export, table or migration is added.
222
+
223
+ Verify ordinary invitations, rollback after each domain write, late framework
224
+ failure, existing membership restoration, recipient checks, stale associations,
225
+ controlled delivery failures and lost-response receipts. Run the normal redeem
226
+ action in tests. Use independent connections to race acceptance against acceptance,
227
+ refusal, revocation and replacement in both winning orders. The package fixture
228
+ runs these races on native MySQL/MariaDB and PostgreSQL; SQLite skips those races
229
+ and is not evidence of native locking correctness.
230
+
231
+ Use only a verified empty disposable database named
232
+ `jskit_repository_test_*` for native tests. The runner verifies the actual target,
233
+ reports its engine version, applies package migrations and drops fixture tables.
234
+
235
+ ```sh
236
+ node --test packages/workspaces-core/test/*.test.js
237
+ # Set JSKIT_REPOSITORY_TEST_DATABASE_URL to the verified empty disposable target.
238
+ node --test packages/workspaces-core/test/repositories.integration.test.js
239
+ npm run verify
240
+ ```
241
+
242
+ A passing framework fixture does not complete the application's People, training,
243
+ permissions, privacy or browser integration. Publication remains a separate
244
+ maintainer operation. After a coordinated release is published, run the
245
+ application's `npm run jskit:update`, restart its backend and verify that
246
+ `members.prepareInvite`, `members.sendInvite` and
247
+ `pendingInvitations.registerAcceptanceParticipant` are functions on the installed
248
+ `workspaces.core` capability. Check the installed package/catalogue versions
249
+ against that release. The first coordinated release containing this contract pairs
250
+ `@jskit-ai/workspaces-core@0.1.198`, `@jskit-ai/jskit-catalog@0.1.245` and
251
+ `@jskit-ai/agent-docs@0.1.192`. Upgrade the coordinated dependency graph rather
252
+ than pinning only `workspaces-core`. Do not patch installed `node_modules`.
253
+
42
254
  ## Browser composition
43
255
 
44
256
  Use `workspaces/workspace-surfaces` for workspace selection, invitation landing,
@@ -102,9 +102,18 @@ Run this behavior-preserving audit over every affected screen:
102
102
  3. Check hierarchy, token use, component purpose, responsive presentation,
103
103
  interaction states, keyboard/focus behavior, labels, targets, contrast, and
104
104
  reduced motion.
105
- 4. Correct issues at the narrowest established owner. Consolidate only truly
105
+ 4. Audit Back on every affected page using [Back navigation](./ui-operations.md#back-navigation):
106
+ verify its presence or justified omission, actual previous-page destination,
107
+ safe direct-entry fallback, and retained origin context. Inspect handlers and
108
+ route bindings; a hardcoded parent/list link is an explicit exception, not
109
+ the default. Check label, accessible name, consistent leading placement,
110
+ keyboard order, responsive reachability, duplicates, and distinction from
111
+ Cancel, Close, or Previous step. Report misleading navigation as a finding;
112
+ fix behavior only within the requested scope, otherwise report it separately
113
+ from behavior-preserving cleanup.
114
+ 5. Correct issues at the narrowest established owner. Consolidate only truly
106
115
  repeated policy; avoid pass-through wrappers and speculative abstractions.
107
- 5. Run focused tests and Playwright at compact, medium, and expanded widths.
116
+ 6. Run focused tests and Playwright at compact, medium, and expanded widths.
108
117
  Navigate away and back with warm query data when the screen persists state.
109
118
 
110
119
  Do not declare Material 3 compliance from visual resemblance alone. Report any
@@ -23,6 +23,38 @@ only when defining or diagnosing topology. Resolve current dynamic parameters
23
23
  to an absolute URL or route object for sibling/child links—never bind a route
24
24
  template or relative string raw to Vue Router `to`.
25
25
 
26
+ ## Back navigation
27
+
28
+ Check every page for a visible Back button that returns to the previous page.
29
+ Omit it only when returning makes no product sense, such as the app's root
30
+ landing page or a completed flow that must not reopen an expired step. State
31
+ the reason in the implementation or review result; a drawer link is not Back.
32
+
33
+ Plain **Back** normally follows the person's actual route history, retaining
34
+ the originating page's query, filters, tab, and pagination. Reuse the existing
35
+ JSKIT screen/navigation owner and its public seams. For history traversal use
36
+ the app's Vue Router instance (`useRouter()` from `vue-router`, then
37
+ [`router.back()`](https://router.vuejs.org/guide/essentials/navigation.html#traverse-history));
38
+ `usePaths().page()` from `@jskit-ai/shell-web/client/navigation/usePaths`
39
+ resolves a destination, not history. Do not invent a second navigation stack.
40
+
41
+ For direct links, new tabs, or no usable in-app history, use a deliberate safe
42
+ fallback resolved through the existing surface/workspace-aware path helpers.
43
+ Do not infer a usable previous page from `window.history.length` alone or send
44
+ people outside the app unexpectedly. Keep existing unsaved-change guards.
45
+
46
+ A fixed parent/list destination is valid when the task calls for it; name it
47
+ explicitly, for example **Back to appointments**. It must not silently replace
48
+ ordinary Back. Shared CRUD `listLocation`/`listUrlTemplate` links are fixed
49
+ destinations, not proof of previous-page behavior. Keep Back distinct from
50
+ Cancel, Close, and a wizard's Previous step.
51
+
52
+ Place Back consistently near the start of the page's existing action region,
53
+ before forward/primary actions in reading and keyboard order. Reuse shell or
54
+ shared-screen controls; avoid duplicates, drawer-only access, or adding a page
55
+ heading just to house it. Keep its meaning and accessible name clear at every
56
+ supported width.
57
+
26
58
  ## Application-owned files
27
59
 
28
60
  Pattern source is ordinary customizable application source. When replacing a
@@ -67,6 +99,9 @@ Exercise user-facing changes with Playwright at compact, medium, and expanded
67
99
  widths. Check overflow, clipped text, duplicate navigation, route placement,
68
100
  actions, skeleton replacement, error feedback, and target sizes. Use relative
69
101
  URLs. When `PLAYWRIGHT_BASE_URL` is provided, start no duplicate server.
102
+ For Back, enter the same page from two different origins and verify each
103
+ returns to its own origin with context intact. Also check direct entry, reload,
104
+ browser back/forward, fixed-destination exceptions, and any unsaved-change guard.
70
105
 
71
106
  Vibe64 owns its managed browser and may provide
72
107
  `VIBE64_PLAYWRIGHT_STORAGE_STATE`; never print or commit that value, bypass it,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.191",
3
+ "version": "0.1.193",
4
4
  "description": "Distributed JSKIT agent references, prompts, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
package/patterns/INDEX.md CHANGED
@@ -30,7 +30,7 @@ How to use it:
30
30
  - `client-requests.md`
31
31
  - playwright, browser test, e2e, ui verification, baseline test, authenticated ui test, test auth, dev login as, dev auth bypass
32
32
  - `ui-testing.md`
33
- - UI contract, design contract, navigation roles, density, placeholder copy, card shells, shared CRUD screens, row actions, synthetic rows, detail slots
33
+ - UI contract, design contract, navigation roles, Back button, previous page, history, hardcoded destination, Deslop, density, placeholder copy, card shells, shared CRUD screens, row actions, synthetic rows, detail slots
34
34
  - `ui-contract.md`
35
35
  - filter, filters, search facets, chips, date range, enum filter, lookup filter, `useCrudListFilters`, `createCrudListFilters`
36
36
  - `filters.md`
@@ -5,7 +5,7 @@
5
5
  "private": true,
6
6
  "type": "module",
7
7
  "dependencies": {
8
- "@jskit-ai/kernel": "0.1.219",
8
+ "@jskit-ai/kernel": "0.1.221",
9
9
  "json-rest-schema": "^1.0.17"
10
10
  },
11
11
  "exports": {
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.1.219",
37
+ "@jskit-ai/kernel": "0.1.221",
38
38
  "@tanstack/vue-query": "^5.101.0",
39
39
  "fastify": "^5.8.5",
40
40
  "json-rest-schema": "^1.0.17",
@@ -42,13 +42,13 @@
42
42
  "vue": "^3.5.43",
43
43
  "vue-router": "^5.3.1",
44
44
  "vuetify": "^4.1.2",
45
- "@jskit-ai/http-runtime": "0.1.217",
45
+ "@jskit-ai/http-runtime": "0.1.219",
46
46
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
47
47
  "@vue/devtools-api": "^8.2.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@jskit-ai/config-eslint": "0.1.216",
51
- "@jskit-ai/jskit-catalog": "0.1.244",
50
+ "@jskit-ai/config-eslint": "0.1.218",
51
+ "@jskit-ai/jskit-catalog": "0.1.246",
52
52
  "@playwright/test": "1.61.1",
53
53
  "@vitejs/plugin-vue": "^6.0.7",
54
54
  "eslint": "^10.8.0",
@@ -34,6 +34,8 @@ Rules:
34
34
  - If the screen is not implemented yet, use a product-shaped empty state with one clear next action or status, not developer instructions.
35
35
  - Primary navigation links belong in semantic placements such as `shell.primary-nav` or `page.section-nav`; do not place every route into one drawer by default.
36
36
  - Compact layouts must be checked first: no horizontal overflow, no unreachable primary action, and tap targets should be at least 48 px.
37
+ - Check Back's presence, previous-page behavior, and position using the
38
+ [UI contract](./ui-contract.md); explain any omission or fixed destination.
37
39
 
38
40
  UI contract:
39
41
 
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.1.219",
37
+ "@jskit-ai/kernel": "0.1.221",
38
38
  "@tanstack/vue-query": "^5.101.0",
39
39
  "fastify": "^5.8.5",
40
40
  "json-rest-schema": "^1.0.17",
@@ -42,15 +42,15 @@
42
42
  "vue": "^3.5.43",
43
43
  "vue-router": "^5.3.1",
44
44
  "vuetify": "^4.1.2",
45
- "@jskit-ai/http-runtime": "0.1.217",
45
+ "@jskit-ai/http-runtime": "0.1.219",
46
46
  "@mdi/js": "^7.4.47",
47
- "@jskit-ai/shell-web": "0.1.223",
47
+ "@jskit-ai/shell-web": "0.1.225",
48
48
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
49
49
  "@vue/devtools-api": "^8.2.1"
50
50
  },
51
51
  "devDependencies": {
52
- "@jskit-ai/config-eslint": "0.1.216",
53
- "@jskit-ai/jskit-catalog": "0.1.244",
52
+ "@jskit-ai/config-eslint": "0.1.218",
53
+ "@jskit-ai/jskit-catalog": "0.1.246",
54
54
  "@playwright/test": "1.61.1",
55
55
  "@vitejs/plugin-vue": "^6.0.7",
56
56
  "eslint": "^10.8.0",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "description": "App-local runtime composition and lightweight glue.",
11
11
  "dependencies": {
12
- "@jskit-ai/kernel": "0.1.219"
12
+ "@jskit-ai/kernel": "0.1.221"
13
13
  },
14
14
  "jskit": {
15
15
  "kind": "runtime",
@@ -11,6 +11,11 @@ and action UI.
11
11
  overflow.
12
12
  - Compact interactive targets are at least 48 CSS pixels.
13
13
  - Navigation uses semantic placements and explicit navigation roles.
14
+ - Check every page for Back to the previous page unless returning makes no
15
+ product sense. Use the existing navigation owner; fixed destinations are
16
+ deliberate, explicitly labelled exceptions. Follow the
17
+ [Back navigation contract](../guide/agent/framework/ui-operations.md#back-navigation)
18
+ for history, direct-entry fallback, retained context, and consistent placement.
14
19
  - Pages do not stack decorative cards inside structural cards.
15
20
  - Pages do not include a header, title block, welcome heading, or standalone
16
21
  heading copy unless the user explicitly requests one.
@@ -50,6 +55,8 @@ it as a closed sheet and wider layouts as a closed side panel.
50
55
 
51
56
  - test skeleton, empty, loaded, local error, retry, and mutation feedback states
52
57
  - navigate away and back with warm query cache, including browser back/forward
58
+ - during review or Deslop, audit Back's presence or justified omission, leading
59
+ position, label, and actual destination; verify two origins and direct entry
53
60
  - exercise compact, medium, and expanded viewports
54
61
  - verify accessible names, keyboard operation, target size, and no overflow
55
62
  - assert that no spinner or layout-shifting transient error banner appears