@jskit-ai/agent-docs 0.1.239 → 0.1.240

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 (50) hide show
  1. package/guide/agent/app-extras/assistant-conversation.md +11 -6
  2. package/guide/agent/app-setup/authentication.md +28 -2
  3. package/guide/agent/app-setup/existing-application-migration.md +5 -3
  4. package/guide/agent/app-setup/initial-scaffolding.md +4 -1
  5. package/guide/agent/app-setup/run-an-accounts-hub.md +152 -0
  6. package/guide/agent/app-setup/sign-in-through-oidc-issuer.md +148 -0
  7. package/guide/agent/app-setup/upgrading-jskit.md +13 -0
  8. package/guide/agent/framework/application-operations.md +3 -0
  9. package/guide/agent/framework/ui-operations.md +24 -0
  10. package/guide/agent/index.md +2 -0
  11. package/package.json +1 -1
  12. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  13. package/patterns/minimal-foundation/example/package.json +3 -3
  14. package/patterns/shell-foundation/example/package.json +4 -4
  15. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  16. package/reference/autogen/KERNEL_MAP.md +1 -0
  17. package/reference/autogen/PATTERN_INDEX.md +57 -35
  18. package/reference/autogen/README.md +2 -0
  19. package/reference/autogen/packages/auth-core.md +1 -0
  20. package/reference/autogen/packages/auth-provider-oidc-core.md +144 -0
  21. package/reference/autogen/packages/auth-web.md +16 -0
  22. package/reference/autogen/packages/kernel.md +6 -1
  23. package/reference/autogen/packages/oidc-issuer-core.md +201 -0
  24. package/reference/autogen/packages/shell-web.md +3 -0
  25. package/skills/jskit/references/app-operations.md +3 -0
  26. package/skills/jskit/references/existing-application-migration.md +5 -3
  27. package/skills/jskit/references/pattern-index.md +57 -35
  28. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +3 -3
  29. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +4 -4
  30. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  31. package/skills/jskit/references/patterns/auth/accounts-hub/PATTERN.md +97 -0
  32. package/skills/jskit/references/patterns/auth/accounts-hub/example/.env.example +10 -0
  33. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +8 -0
  34. package/skills/jskit/references/patterns/auth/accounts-hub/example/server/hubPaths.js +7 -0
  35. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +33 -11
  36. package/skills/jskit/references/patterns/auth/oidc-sign-in/PATTERN.md +87 -0
  37. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/.env.example +13 -0
  38. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +8 -0
  39. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  40. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  41. package/skills/jskit/references/patterns/connectors/oauth-connection/PATTERN.md +25 -0
  42. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  43. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  44. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  45. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  46. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  47. package/skills/jskit/references/patterns/shell/application-shell/PATTERN.md +9 -0
  48. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  49. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  50. package/skills/jskit/references/ui-operations.md +24 -0
@@ -0,0 +1,97 @@
1
+ ---
2
+ id: auth/accounts-hub
3
+ title: Accounts hub
4
+ summary: Run one OpenID Connect sign-in for all of an owner's apps as its own JSKIT project, with production apps approved by the owner.
5
+ keywords: auth, authentication, oidc, openid connect, issuer, single sign-on, sso, accounts hub, master login
6
+ requires: @jskit-ai/oidc-issuer-core
7
+ ---
8
+
9
+ # Accounts hub
10
+
11
+ ## Use when
12
+
13
+ Use this pattern when one person or company owns several apps and wants one
14
+ account to sign in to all of them. The hub is its own project, deployed on a
15
+ domain of its own. Apps sign in through it with
16
+ `@jskit-ai/auth-provider-oidc-core` (pattern `auth/oidc-sign-in`).
17
+
18
+ ## Do not use when
19
+
20
+ Do not use it inside an app that has its own users only: use local accounts.
21
+ Do not add it to an existing app as a second purpose; the hub's cookies and
22
+ pages belong on their own origin.
23
+
24
+ ## Product decisions
25
+
26
+ - The hub's address (`APP_PUBLIC_URL`), on a domain no app shares.
27
+ - The owner (`OIDC_HUB_OWNER_EMAIL`): only this verified account opens Apps,
28
+ People and Setup.
29
+ - The email sender (`OIDC_HUB_EMAIL_FROM` with `RESEND_API_KEY` or `SMTP_URL`).
30
+ - Optional "Continue with Google" with the owner's own Google OAuth client
31
+ (`OIDC_HUB_GOOGLE_CLIENT_ID`, `OIDC_HUB_GOOGLE_CLIENT_SECRET`), redirect URI
32
+ `{APP_PUBLIC_URL}/hub/google/callback`.
33
+ - Development addresses approved automatically
34
+ (`OIDC_HUB_DEV_ORIGIN_PATTERNS`, comma-separated `https://` globs where `*`
35
+ stays inside one DNS label). Loopback is always included.
36
+
37
+ ## Invariants
38
+
39
+ - Secrets (`OIDC_HUB_SECRET`, `OIDC_HUB_REGISTRATION_TOKEN`, sender keys,
40
+ Google secret) live in the host's secret store, never in source.
41
+ - Every production app address waits for one owner click in Apps. Names are
42
+ self-asserted; the full address is what the owner approves.
43
+ - Apps receive the authorization code flow with PKCE only, no refresh tokens,
44
+ exact redirect URIs, and the same `sub` everywhere.
45
+ - The issuer and `/hub` paths are never framed; in production no hub page is.
46
+ (Development lets an editor's preview pane show the hub's own pages.) The
47
+ hub's own sign-in is first-party: apps never see passwords.
48
+ - Passwords use the JSKIT local-account scrypt v1 format, so local accounts
49
+ import unchanged.
50
+
51
+ ## Framework APIs
52
+
53
+ Install `@jskit-ai/oidc-issuer-core` with a database driver
54
+ (`@jskit-ai/database-runtime-mysql`). Its provider mounts on the app's Fastify
55
+ server:
56
+
57
+ - `/.well-known/openid-configuration` and `/oidc/*`: the issuer, built on
58
+ `oidc-provider` with a persistent Knex adapter;
59
+ - `/oidc/reg`: protected self-registration (RFC 7591/7592) with the
60
+ registration token as the initial access token;
61
+ - `/hub/interaction/:uid`, `/hub/google/*`, `/hub/api/*`: the hub's sign-in,
62
+ account and admin API that the app's own pages call.
63
+
64
+ Add `HUB_GLOBAL_PATHS` from `@jskit-ai/oidc-issuer-core/server` to the
65
+ server's global UI paths so a surface-filtered server still serves them.
66
+ Pages send `x-hub-request: 1` on every state-changing call.
67
+
68
+ Commands (`jskit-oidc-issuer`): `keys rotate`, `import-accounts --in <file>
69
+ [--apply]` (dry run by default), `preview-identity`.
70
+
71
+ ## Example files
72
+
73
+ `example/package.json` declares the hub runtime. `example/.env.example` names
74
+ every variable without values. `example/server/hubPaths.js` shows the one
75
+ server change.
76
+
77
+ ## Variation points
78
+
79
+ Page design, wording and branding are the app's. Development origin patterns,
80
+ the email sender and Google are configuration. The registration policy,
81
+ security headers and token rules are not variation points.
82
+
83
+ ## Verification
84
+
85
+ - Start with a missing variable and read the one-sentence error.
86
+ - Register a loopback app: it is approved automatically and signs in.
87
+ - Register an https app on another domain: it waits until Approve.
88
+ - Rotate the signing key: old ID tokens verify for two hours, then fail.
89
+ - Import exported local accounts with a dry run first; an imported account
90
+ signs in with its old password.
91
+
92
+ ## Avoid
93
+
94
+ - running the hub on a domain an app shares
95
+ - approving an app by its name without reading its address
96
+ - copying secrets into source or into the hub's Env defaults
97
+ - turning on development interactions or refresh tokens for apps
@@ -0,0 +1,10 @@
1
+ APP_PUBLIC_URL=https://accounts.example.com
2
+ OIDC_HUB_SECRET=
3
+ OIDC_HUB_REGISTRATION_TOKEN=
4
+ OIDC_HUB_OWNER_EMAIL=
5
+ OIDC_HUB_DEV_ORIGIN_PATTERNS=
6
+ OIDC_HUB_EMAIL_FROM=
7
+ RESEND_API_KEY=
8
+ SMTP_URL=
9
+ OIDC_HUB_GOOGLE_CLIENT_ID=
10
+ OIDC_HUB_GOOGLE_CLIENT_SECRET=
@@ -0,0 +1,8 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "dependencies": {
5
+ "@jskit-ai/database-runtime-mysql": "0.1.243",
6
+ "@jskit-ai/oidc-issuer-core": "0.1.0"
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ import { HUB_GLOBAL_PATHS } from "@jskit-ai/oidc-issuer-core/server";
2
+
3
+ // Pass these to registerSurfaceRequestConstraint({ globalUiPaths }) next to
4
+ // the runtime's own global paths, so every surface server serves the hub.
5
+ export function hubGlobalUiPaths(runtimeGlobalUiPaths = []) {
6
+ return [...new Set([...runtimeGlobalUiPaths, ...HUB_GLOBAL_PATHS])];
7
+ }
@@ -30,16 +30,33 @@ Use the views, auth guard, HTTP client integration, sign-out runtime, providers,
30
30
  and Playwright auth helper exported by `@jskit-ai/auth-web`. Use auth policies
31
31
  from `@jskit-ai/auth-core` for server authorization.
32
32
 
33
- For a managed preview, copy the app-owned `tools/preview-identity` executable
34
- and declare it under `#### Preview identity` in the project's web-presented
35
- Vibe64 `Outputs` target with protocol `vibe64.preview-identity.command.v1`.
36
- Declare `AUTH_DEV_BYPASS_ENABLED` as its Enabled environment and
37
- `AUTH_DEV_BYPASS_SECRET` as its Secret environment so the preview host supplies
38
- fresh output-run-scoped values; users do not set those values by hand. JSKIT
39
- owns this executable/library pattern, Vibe64 owns the `Outputs` grammar and
40
- runtime behavior, and Genesis only composes that consumer-owned section as
41
- opaque text. The executable calls the exported managed-preview identity library
42
- directly; it does not require a JSKIT CLI.
33
+ Every application with accounts includes the managed-preview login. It is
34
+ standard, not optional: adding this pattern adds it, so a managed host's
35
+ preview can sign its browser in as an existing account without anyone asking
36
+ for it. Copy the app-owned `tools/preview-identity` executable (keep it
37
+ executable) and declare it under `#### Preview identity` in the project's
38
+ web-presented Vibe64 `Outputs` target, exactly:
39
+
40
+ ```markdown
41
+ #### Preview identity
42
+
43
+ - Command: `tools/preview-identity`
44
+ - Protocol: `vibe64.preview-identity.command.v1`
45
+ - Identity types: `email` `user-id`
46
+ - Enabled environment: `AUTH_DEV_BYPASS_ENABLED`
47
+ - Secret environment: `AUTH_DEV_BYPASS_SECRET`
48
+ - Runtimes: `nodejs`
49
+ ```
50
+
51
+ The preview host supplies fresh output-run-scoped values for both environment
52
+ names; users do not set them by hand, and the exchange stays disabled without
53
+ them. The person using the host chooses which existing accounts the preview may
54
+ sign in as; the application never creates users for it. JSKIT owns this
55
+ executable/library pattern, Vibe64 owns the `Outputs` grammar and runtime
56
+ behavior, and Genesis only composes that consumer-owned section as opaque text.
57
+ The executable calls the exported managed-preview identity library directly; it
58
+ does not require a JSKIT CLI. A project without Genesis or a managed host keeps
59
+ the executable; it is inert unless both environment values are present.
43
60
 
44
61
  Choose local-auth storage through the installed capability provider. A
45
62
  database-backed application installs `@jskit-ai/auth-provider-local-db-core`,
@@ -52,6 +69,8 @@ a user as a side effect.
52
69
  ## Invariants
53
70
 
54
71
  - Credentials and provider secrets come from environment values.
72
+ - An application with accounts ships the executable `tools/preview-identity`
73
+ and declares it under `#### Preview identity` in its web `Outputs` target.
55
74
  - Auth backend selection comes from the installed provider graph, not an
56
75
  `AUTH_LOCAL_BACKEND` environment value.
57
76
  - The selected auth backend and the storage containing the application's users
@@ -72,7 +91,8 @@ a user as a side effect.
72
91
 
73
92
  `example/` contains concrete route wrappers, editable view wrappers, and the
74
93
  client runtime helpers needed by an authentication surface. It also contains
75
- the optional app-owned managed-preview identity executable. Compose their
94
+ the standard app-owned managed-preview identity executable,
95
+ `tools/preview-identity`. Compose their
76
96
  surface and placement declarations into the application's ordinary config and
77
97
  placement files.
78
98
 
@@ -87,6 +107,8 @@ Exercise failed and successful login, sign-out, reset, protected navigation,
87
107
  keyboard interaction, warm-cache return navigation, and browser refresh.
88
108
  Open the login route with an existing authenticated session and confirm it
89
109
  returns to the requested screen; repeat signed out and confirm the form remains.
110
+ Confirm `tools/preview-identity` is committed as executable and the web
111
+ `Outputs` target declares the `#### Preview identity` block above.
90
112
 
91
113
  ## Avoid
92
114
 
@@ -0,0 +1,87 @@
1
+ ---
2
+ id: auth/oidc-sign-in
3
+ title: Sign in through an OIDC issuer
4
+ summary: Let people sign in with the accounts they already have in an accounts hub or another OpenID Connect issuer, instead of app-local passwords.
5
+ keywords: auth, authentication, oidc, openid connect, single sign-on, sso, accounts hub, master login, issuer, back-channel logout, dynamic client registration
6
+ requires: @jskit-ai/auth-provider-oidc-core, @jskit-ai/auth-web
7
+ ---
8
+
9
+ # Sign in through an OIDC issuer
10
+
11
+ ## Use when
12
+
13
+ Several apps should share one set of accounts: people sign in once at an
14
+ accounts hub (see `auth/accounts-hub`) or at an organisation's OpenID Connect
15
+ issuer, and every app signs them in from there. The app keeps its own `users`
16
+ rows and its own session; the issuer only proves who the person is.
17
+
18
+ ## Do not use when
19
+
20
+ The app needs its own passwords (use `auth-provider-local-core`), or the product
21
+ has deliberately chosen Supabase (`auth/supabase-auth`). Install exactly one
22
+ `auth.service` provider.
23
+
24
+ ## Product decisions
25
+
26
+ - Which issuer. A hub gives each app a registration token; other issuers
27
+ (Google Workspace, Okta) give a fixed client id and secret instead.
28
+ - The button label people see.
29
+ - The production address, and who approves the app in the hub.
30
+ - Whether existing local accounts move to the hub (one-time commands below) or
31
+ the app starts fresh.
32
+
33
+ ## Invariants
34
+
35
+ - One client per app address. With a registration token the app registers
36
+ itself the first time it is used from a new address (or at start when
37
+ `APP_PUBLIC_URL` is set) and stores the client in `auth_oidc_clients`.
38
+ - The redirect URI is auth-web's login route, `AUTH_OIDC_REDIRECT_PATH`
39
+ (`/auth/login`). Where to go afterwards travels in a sealed, ten-minute,
40
+ single-use attempt cookie, never in the redirect URI.
41
+ - Every sign-in uses PKCE, `state`, `nonce` and the RFC 9207 `iss` check; ID
42
+ tokens must name this issuer and this client.
43
+ - The app session is the app's own cookie (30 days), backed by
44
+ `auth_oidc_sessions`. Signing out of the app does not sign out of the issuer
45
+ unless the person asks for it.
46
+ - Secrets stay in Env, never in source.
47
+
48
+ ## Framework APIs
49
+
50
+ - `@jskit-ai/auth-provider-oidc-core` provides `auth.service`, its migration and
51
+ the `jskit-oidc-sign-in` command.
52
+ - `@jskit-ai/auth-provider-oidc-core/shared` exports `AUTH_OIDC_PROVIDER_ID`,
53
+ `AUTH_OIDC_REDIRECT_PATH`, `AUTH_OIDC_LOGOUT_PATH` and
54
+ `AUTH_OIDC_BACKCHANNEL_LOGOUT_PATH`.
55
+ - auth-web's login page shows the one button. Inside a frame (an editor
56
+ Preview) it opens the issuer in a pop-up window and reloads signed in.
57
+
58
+ ## Example files
59
+
60
+ `example/package.json` declares the provider and the login surface.
61
+ `example/.env.example` names every Env value without credentials.
62
+
63
+ ## Variation points
64
+
65
+ - Static client (`AUTH_OIDC_CLIENT_ID`/`AUTH_OIDC_CLIENT_SECRET`) instead of
66
+ the registration token; never both.
67
+ - `AUTH_OIDC_LABEL` for the button.
68
+ - A sign-out-everywhere link: `/api/auth/oidc/logout?everywhere=1` also ends the
69
+ issuer session through its end-session endpoint.
70
+
71
+ ## Verification
72
+
73
+ - Missing `AUTH_OIDC_ISSUER` shows "Sign-in is not set up yet" instead of a
74
+ crash.
75
+ - Sign in unframed and inside the Preview frame; refresh stays signed in.
76
+ - A production address the hub has not approved yet reaches the hub's waiting
77
+ page; a blocked one reads "Your accounts hub has blocked this app's address."
78
+ - Disabling the account in the hub ends the app session (back-channel logout).
79
+ - Preview identity (`tools/preview-identity`) still signs in as an existing
80
+ `users` row.
81
+
82
+ ## Avoid
83
+
84
+ - keeping the local provider installed alongside this one
85
+ - putting the return path or any secret in the redirect URI
86
+ - sharing one registered client between addresses
87
+ - running the account move commands without the person's explicit yes
@@ -0,0 +1,13 @@
1
+ # The accounts hub (or any OpenID Connect issuer). Discovery is used.
2
+ AUTH_OIDC_ISSUER=https://accounts.example.com/oidc
3
+ # Preferred: the app registers itself, once per address, with this token.
4
+ AUTH_OIDC_REGISTRATION_TOKEN=replace-me
5
+ # Instead of the token, for issuers without self-registration:
6
+ # AUTH_OIDC_CLIENT_ID=replace-me
7
+ # AUTH_OIDC_CLIENT_SECRET=replace-me
8
+ # Optional button label.
9
+ # AUTH_OIDC_LABEL=Continue with your account
10
+ # Required in production, at least 32 characters. Generated in development.
11
+ # AUTH_OIDC_SESSION_SECRET=replace-me
12
+ # The address people use. Registers at start; required behind a non-loopback proxy.
13
+ APP_PUBLIC_URL=http://localhost:5173
@@ -0,0 +1,8 @@
1
+ {
2
+ "private": true,
3
+ "type": "module",
4
+ "dependencies": {
5
+ "@jskit-ai/auth-provider-oidc-core": "0.1.0",
6
+ "@jskit-ai/auth-web": "0.1.246"
7
+ }
8
+ }
@@ -2,6 +2,6 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/auth-provider-supabase-core": "0.1.241"
5
+ "@jskit-ai/auth-provider-supabase-core": "0.1.242"
6
6
  }
7
7
  }
@@ -7,10 +7,10 @@
7
7
  "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
8
8
  },
9
9
  "dependencies": {
10
- "@jskit-ai/connectors-core": "0.1.61",
11
- "@jskit-ai/connector-google-calendar": "0.1.61",
12
- "@jskit-ai/database-runtime": "0.1.244",
13
- "@jskit-ai/database-runtime-mysql": "0.1.242",
10
+ "@jskit-ai/connectors-core": "0.1.62",
11
+ "@jskit-ai/connector-google-calendar": "0.1.62",
12
+ "@jskit-ai/database-runtime": "0.1.245",
13
+ "@jskit-ai/database-runtime-mysql": "0.1.243",
14
14
  "knex": "^3.1.0"
15
15
  }
16
16
  }
@@ -181,6 +181,31 @@ comma-separated permissions. Oura handles reduced callback scopes and rotated
181
181
  refresh tokens. Some providers do not return a refresh token; expired access
182
182
  then needs consent again. Provider PKCE enforcement needs provider evidence.
183
183
 
184
+ ## Shared connection from an Env refresh token
185
+
186
+ When the application's host already holds a long-lived grant for the shared
187
+ account (the owner connected it once with the same OAuth client), skip consent
188
+ in the app: add `refreshTokenRef` to a `shared` oauth2 integration whose
189
+ registration uses the authorization-code flow.
190
+
191
+ ```json
192
+ "authentication": {
193
+ "method": "oauth2",
194
+ "registrationRef": "google",
195
+ "refreshTokenRef": "env:GOOGLE_REFRESH_TOKEN"
196
+ }
197
+ ```
198
+
199
+ The Env value is a seed. On first use, and whenever the value or the OAuth
200
+ client changes, the runtime replaces the shared connection with one built from
201
+ that refresh token. From then on it refreshes and stores rotated tokens in the
202
+ application's own store as usual. `status()` reports `source: "env"` (show it as
203
+ "Connected from Env"). `beginAuthorization()` and `disconnect()` refuse with
204
+ `connector_env_managed`: "This connection comes from an Env value; unlink or
205
+ replace that value instead." A revoked token fails with "The refresh token in
206
+ Env no longer works; replace that Env value." The refresh token must come from
207
+ the same client as `registrationRef`; non-JSKIT apps read the variable directly.
208
+
184
209
  ## Twitch composition
185
210
 
186
211
  Use `twitchProvider` from `@jskit-ai/connectors-catalog/server/twitch` in the
@@ -8,8 +8,8 @@
8
8
  "./shared": "./src/shared/index.js"
9
9
  },
10
10
  "dependencies": {
11
- "@jskit-ai/crud-core": "0.1.255",
12
- "@jskit-ai/resource-crud-core": "0.1.186"
11
+ "@jskit-ai/crud-core": "0.1.256",
12
+ "@jskit-ai/resource-crud-core": "0.1.187"
13
13
  },
14
14
  "jskit": {
15
15
  "kind": "runtime",
@@ -8,6 +8,6 @@
8
8
  "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
9
9
  },
10
10
  "dependencies": {
11
- "@jskit-ai/database-runtime-mysql": "0.1.242"
11
+ "@jskit-ai/database-runtime-mysql": "0.1.243"
12
12
  }
13
13
  }
@@ -8,6 +8,6 @@
8
8
  "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
9
9
  },
10
10
  "dependencies": {
11
- "@jskit-ai/database-runtime-postgres": "0.1.241"
11
+ "@jskit-ai/database-runtime-postgres": "0.1.242"
12
12
  }
13
13
  }
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/realtime": "0.1.242",
6
- "@jskit-ai/shell-web": "0.1.249"
5
+ "@jskit-ai/realtime": "0.1.243",
6
+ "@jskit-ai/shell-web": "0.1.250"
7
7
  }
8
8
  }
@@ -5,7 +5,7 @@
5
5
  "private": true,
6
6
  "type": "module",
7
7
  "dependencies": {
8
- "@jskit-ai/kernel": "0.1.244",
8
+ "@jskit-ai/kernel": "0.1.245",
9
9
  "json-rest-schema": "^1.0.17"
10
10
  },
11
11
  "exports": {
@@ -38,6 +38,15 @@ placement registry, topology, and adaptive smoke helper exported by
38
38
  - Cached route/resource state hydrates writable UI state synchronously or with
39
39
  immediate watchers.
40
40
  - Loading uses stable skeletons, not spinners or shifting status banners.
41
+ - `ShellErrorHost` stays directly inside `v-app`. Its banners float below a top
42
+ app bar; on a surface without one (such as sign-in) they join the page flow
43
+ above the content so they never cover it. A full-viewport fixed screen on
44
+ such a surface starts at `var(--shell-error-banners-inline-height, 0px)`, as
45
+ `@jskit-ai/auth-web`'s login and reset views do.
46
+ - Restyle shell chrome through its `--shell-chrome-*`, `--shell-app-bar-*`,
47
+ `--shell-navigation-*` and `--shell-bottom-navigation-*` custom properties,
48
+ not by overriding Vuetify internals; a Material 3 restyle removes the dividers
49
+ and separates surfaces tonally.
41
50
  - The adaptive browser check closes navigation through its public close control.
42
51
 
43
52
  ## Example files
@@ -9,8 +9,8 @@
9
9
  "./shared": "./src/shared/index.js"
10
10
  },
11
11
  "dependencies": {
12
- "@jskit-ai/crud-core": "0.1.255",
13
- "@jskit-ai/resource-crud-core": "0.1.186"
12
+ "@jskit-ai/crud-core": "0.1.256",
13
+ "@jskit-ai/resource-crud-core": "0.1.187"
14
14
  },
15
15
  "jskit": {
16
16
  "kind": "runtime",
@@ -9,9 +9,9 @@
9
9
  "./shared": "./src/shared/index.js"
10
10
  },
11
11
  "dependencies": {
12
- "@jskit-ai/crud-core": "0.1.255",
13
- "@jskit-ai/resource-crud-core": "0.1.186",
14
- "@jskit-ai/workspaces-core": "0.1.222"
12
+ "@jskit-ai/crud-core": "0.1.256",
13
+ "@jskit-ai/resource-crud-core": "0.1.187",
14
+ "@jskit-ai/workspaces-core": "0.1.223"
15
15
  },
16
16
  "jskit": {
17
17
  "kind": "runtime",
@@ -50,6 +50,17 @@ shared-screen controls; avoid duplicates, drawer-only access, or adding a page
50
50
  heading just to house it. Keep its meaning and accessible name clear at every
51
51
  supported width.
52
52
 
53
+ ### Scroll position
54
+
55
+ `createShellRouter()` from `@jskit-ai/kernel/client` installs
56
+ `shellScrollBehavior` by default: a new screen opens at the top (or at its
57
+ hash target), Back/Forward restore the saved position, and query, hash, tab or
58
+ sub-view changes within the same page keep the current position. A tab or
59
+ sub-view counts as the same page when both locations share a parent page record
60
+ below the surface root. Pass `scrollBehavior` to `createShellRouter()` to
61
+ replace it with your own Vue Router function, or `scrollBehavior: false` to
62
+ disable it. Do not wrap the router or reset scroll in page components.
63
+
53
64
  ## Application-owned files
54
65
 
55
66
  Pattern source is ordinary customizable application source. When replacing a
@@ -88,6 +99,19 @@ icon/label and label/end gaps. The normal 80px rail centres 48px targets;
88
99
  `railWidth` and `drawerWidth` are public density/width controls. Do not override
89
100
  the shell's private implementation CSS.
90
101
 
102
+ The app bar, drawer/rail and compact bottom navigation follow the Vuetify theme
103
+ and expose restyling tokens instead of hard-coded chrome: `--shell-chrome-border-width`
104
+ and `--shell-chrome-border-color` for the shared dividers,
105
+ `--shell-app-bar-border-width`, `--shell-navigation-border-width` and
106
+ `--shell-bottom-navigation-border-width` per edge, and
107
+ `--shell-app-bar-background`, `--shell-navigation-background` and
108
+ `--shell-bottom-navigation-background` for the surfaces. The defaults keep one
109
+ thin divider on each separating edge, because Vuetify's default themes define
110
+ no Material 3 surface-container roles to separate surfaces tonally. A Material 3
111
+ restyle sets the border widths to `0` and gives navigation a tonal surface, for
112
+ example `--shell-navigation-background: rgb(var(--v-theme-surface-container-low))`
113
+ when the theme defines that role.
114
+
91
115
  ## Browser verification
92
116
 
93
117
  Exercise user-facing changes with Playwright at compact, medium, and expanded