@jskit-ai/agent-docs 0.1.278 → 0.1.280

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 (114) hide show
  1. package/guide/agent/app-extras/assistant.md +123 -0
  2. package/guide/agent/app-setup/authentication.md +46 -4
  3. package/guide/agent/app-setup/database-layer.md +35 -6
  4. package/guide/agent/app-setup/material-3-migration.md +53 -10
  5. package/guide/agent/app-setup/multi-homing.md +8 -2
  6. package/guide/agent/app-setup/upgrading-jskit.md +30 -0
  7. package/guide/agent/app-setup/users.md +9 -1
  8. package/guide/agent/framework/material-3.md +153 -71
  9. package/guide/agent/framework/sanity-checks.md +180 -0
  10. package/guide/agent/framework/translation.md +4 -0
  11. package/guide/agent/framework/typed-features.md +158 -105
  12. package/guide/agent/framework/ui-operations.md +3 -1
  13. package/guide/agent/index.md +3 -0
  14. package/package.json +2 -2
  15. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  16. package/patterns/minimal-foundation/PATTERN.md +6 -2
  17. package/patterns/minimal-foundation/example/AGENTS.md +10 -0
  18. package/patterns/minimal-foundation/example/package.json +6 -6
  19. package/patterns/minimal-foundation/example/playwright.config.mjs +3 -0
  20. package/patterns/minimal-foundation/example/src/main.js +3 -2
  21. package/patterns/minimal-foundation/example/vite.config.mjs +3 -1
  22. package/patterns/shell-foundation/PATTERN.md +4 -1
  23. package/patterns/shell-foundation/example/AGENTS.md +10 -0
  24. package/patterns/shell-foundation/example/package.json +7 -7
  25. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  26. package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
  27. package/patterns/shell-foundation/example/playwright.config.mjs +3 -0
  28. package/patterns/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  29. package/patterns/shell-foundation/example/src/main.js +3 -2
  30. package/patterns/shell-foundation/example/src/pages/home.vue +1 -1
  31. package/patterns/shell-foundation/example/src/views/NotFound.vue +1 -1
  32. package/patterns/shell-foundation/example/vite.config.mjs +3 -1
  33. package/reference/autogen/PATTERN_INDEX.md +37 -37
  34. package/reference/autogen/packages/agent-docs.md +4 -0
  35. package/reference/autogen/packages/assistant-runtime.md +27 -2
  36. package/reference/autogen/packages/auth-provider-local-core.md +1 -1
  37. package/reference/autogen/packages/auth-web.md +10 -0
  38. package/reference/autogen/packages/crud-core.md +3 -0
  39. package/reference/autogen/packages/http-web.md +0 -6
  40. package/reference/autogen/packages/json-rest-api-core.md +17 -4
  41. package/reference/autogen/packages/kernel.md +10 -1
  42. package/reference/autogen/packages/material-web.md +2 -1
  43. package/reference/autogen/packages/shell-web.md +95 -4
  44. package/reference/autogen/packages/users-web.md +9 -2
  45. package/reference/autogen/packages/workspaces-core.md +2 -0
  46. package/reference/autogen/tooling/jskit-catalog.md +55 -6
  47. package/reference/autogen/tooling/testUtils.md +15 -1
  48. package/skills/jskit/SKILL.md +15 -5
  49. package/skills/jskit/references/heuristics/INDEX.md +3 -1
  50. package/skills/jskit/references/heuristics/row-policies.md +3 -3
  51. package/skills/jskit/references/heuristics/typed-features.md +7 -7
  52. package/skills/jskit/references/heuristics/ui-testing.md +8 -7
  53. package/skills/jskit/references/material-3.md +153 -71
  54. package/skills/jskit/references/multi-homing.md +8 -2
  55. package/skills/jskit/references/pattern-index.md +37 -37
  56. package/skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md +6 -2
  57. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +10 -0
  58. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +6 -6
  59. package/skills/jskit/references/patterns/app/minimal-foundation/example/playwright.config.mjs +3 -0
  60. package/skills/jskit/references/patterns/app/minimal-foundation/example/src/main.js +3 -2
  61. package/skills/jskit/references/patterns/app/minimal-foundation/example/vite.config.mjs +3 -1
  62. package/skills/jskit/references/patterns/app/shell-foundation/PATTERN.md +4 -1
  63. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +10 -0
  64. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +7 -7
  65. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  66. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
  67. package/skills/jskit/references/patterns/app/shell-foundation/example/playwright.config.mjs +3 -0
  68. package/skills/jskit/references/patterns/app/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  69. package/skills/jskit/references/patterns/app/shell-foundation/example/src/main.js +3 -2
  70. package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home.vue +1 -1
  71. package/skills/jskit/references/patterns/app/shell-foundation/example/src/views/NotFound.vue +1 -1
  72. package/skills/jskit/references/patterns/app/shell-foundation/example/vite.config.mjs +3 -1
  73. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
  74. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +18 -5
  75. package/skills/jskit/references/patterns/auth/auth-surface/example/src/components/AppBrand.vue +17 -0
  76. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/login.vue +2 -2
  77. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/reset-password.vue +2 -2
  78. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/signout.vue +2 -2
  79. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/LoginView.vue +6 -1
  80. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/ResetPasswordView.vue +6 -1
  81. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/SignOutView.vue +6 -1
  82. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  83. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  84. package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/package.json +1 -1
  85. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  86. package/skills/jskit/references/patterns/console/console-surface/example/src/pages/console.vue +1 -1
  87. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  88. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  89. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  90. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  91. package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js +5 -2
  92. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  93. package/skills/jskit/references/patterns/shell/application-shell/PATTERN.md +11 -6
  94. package/skills/jskit/references/patterns/shell/application-shell/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  95. package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home.vue +1 -1
  96. package/skills/jskit/references/patterns/shell/application-shell/example/src/views/NotFound.vue +1 -1
  97. package/skills/jskit/references/patterns/ui/page-and-placement/PATTERN.md +6 -0
  98. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/pages/home/reports.vue +9 -4
  99. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/placementTopology.js +5 -3
  100. package/skills/jskit/references/patterns/users/account-settings/PATTERN.md +28 -2
  101. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsNotificationsSection.vue +1 -1
  102. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue +2 -1
  103. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsProfileSection.vue +3 -2
  104. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account/[section].vue +23 -0
  105. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account.vue +23 -0
  106. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  107. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  108. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/packages/main/src/client/components/AccountPendingInvitesCue.vue +3 -3
  109. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/admin/root.vue +1 -1
  110. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/app/root.vue +1 -1
  111. package/skills/jskit/references/sanity-checks.md +180 -0
  112. package/skills/jskit/references/translation.md +4 -0
  113. package/skills/jskit/references/typed-features.md +158 -105
  114. package/skills/jskit/references/ui-operations.md +3 -1
@@ -4,6 +4,7 @@ import vue from "@vitejs/plugin-vue";
4
4
  import vuetify from "vite-plugin-vuetify";
5
5
  import VueRouter from "vue-router/vite";
6
6
  import { createJskitClientBootstrapPlugin } from "@jskit-ai/kernel/client/vite";
7
+ import { testConcurrency } from "@jskit-ai/jskit-catalog/test-budget";
7
8
 
8
9
  const configuredDevPort = Number.parseInt(String(process.env.VITE_DEV_PORT || "").trim(), 10);
9
10
  const devPort = Number.isInteger(configuredDevPort) && configuredDevPort > 0 ? configuredDevPort : 5173;
@@ -52,7 +53,8 @@ export default defineConfig({
52
53
  }
53
54
  ],
54
55
  test: {
55
- include: ["tests/client/**/*.vitest.js"]
56
+ include: ["tests/client/**/*.vitest.js"],
57
+ maxWorkers: testConcurrency()
56
58
  },
57
59
  optimizeDeps: {
58
60
  include: ["vuetify/components/**", "vuetify/directives/**"],
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/database-runtime-mysql": "0.1.257",
6
- "@jskit-ai/oidc-issuer-core": "0.1.17"
5
+ "@jskit-ai/database-runtime-mysql": "0.1.258",
6
+ "@jskit-ai/oidc-issuer-core": "0.1.19"
7
7
  }
8
8
  }
@@ -27,8 +27,14 @@ Those choices must be known before adapting the example.
27
27
  ## Framework APIs
28
28
 
29
29
  Use the views, auth guard, HTTP client integration, sign-out runtime, providers,
30
- and Playwright auth helper exported by `@jskit-ai/auth-web`. Use auth policies
31
- from `@jskit-ai/auth-core` for server authorization.
30
+ and Playwright auth helper exported by `@jskit-ai/auth-web`. The sign-in, reset
31
+ and sign-out screens show the app's identity: the pages render the app's view
32
+ wrappers (`src/views/auth/`), and each wrapper fills the default view's `brand`
33
+ slot (given `{ compact }`) with `src/components/AppBrand.vue`, the app's name
34
+ as `index.html`'s `<title>` spells it. JSKIT lays the slot out as a pane beside
35
+ the card on wide windows and a row above it on phones; never restyle the views'
36
+ own classes. Use auth policies from `@jskit-ai/auth-core` for server
37
+ authorization.
32
38
 
33
39
  Every application with accounts includes the managed-preview login. It is
34
40
  standard, not optional: adding this pattern adds it, so a managed host's
@@ -92,6 +98,10 @@ a user as a side effect.
92
98
  - Public auth routes do not require an existing session.
93
99
  - Protected routes use the framework auth policy rather than page-local checks.
94
100
  - Login, sign-out, and reset views use the public auth components/composables.
101
+ - The sign-in, reset and sign-out pages render the app's view wrappers, which
102
+ fill the `brand` slot with the app's identity (`AppBrand.vue`): a person
103
+ always sees which app they are signing in to. Renaming the app renames
104
+ `AppBrand.vue` with `index.html`'s `<title>`; the brand adds no heading.
95
105
  - A custom login view must preserve the default view's initial session check:
96
106
  already authenticated people return to the validated requested destination.
97
107
  Process incoming login callbacks before that redirect, and keep signed-out
@@ -102,8 +112,9 @@ a user as a side effect.
102
112
 
103
113
  ## Example files
104
114
 
105
- `example/` contains concrete route wrappers, editable view wrappers, and the
106
- client runtime helpers needed by an authentication surface. It also contains
115
+ `example/` contains concrete route wrappers, editable view wrappers that show
116
+ the app's brand (`src/components/AppBrand.vue`), and the client runtime helpers
117
+ needed by an authentication surface. It also contains
107
118
  the standard app-owned managed-preview identity executable,
108
119
  `tools/preview-identity`. Compose their
109
120
  surface and placement declarations into the application's ordinary config and
@@ -116,7 +127,9 @@ placements, and post-auth destinations while retaining the runtime contracts.
116
127
 
117
128
  ## Verification
118
129
 
119
- Exercise failed and successful login, sign-out, reset, protected navigation,
130
+ Check that the sign-in, reset and sign-out screens show the app's name, in the
131
+ pane on a wide window and in the row on a phone. Exercise failed and successful
132
+ login, sign-out, reset, protected navigation,
120
133
  keyboard interaction, warm-cache return navigation, and browser refresh.
121
134
  Open the login route with an existing authenticated session and confirm it
122
135
  returns to the requested screen; repeat signed out and confirm the form remains.
@@ -0,0 +1,17 @@
1
+ <script setup>
2
+ // The app's identity on its sign-in, reset and sign-out screens. The auth views place it in their brand slot: a pane
3
+ // beside the card on wide windows, and a row above the card on phones (`compact`). It is the app's name as
4
+ // index.html's <title> spells it, so renaming the app renames both. It holds no heading: each screen's h1 is its own.
5
+ defineProps({
6
+ compact: {
7
+ type: Boolean,
8
+ default: false
9
+ }
10
+ });
11
+
12
+ const APP_NAME = "Reading Room";
13
+ </script>
14
+
15
+ <template>
16
+ <div :class="compact ? 'text-title-medium' : 'text-title-large'" data-testid="app-brand">{{ APP_NAME }}</div>
17
+ </template>
@@ -9,9 +9,9 @@
9
9
  </route>
10
10
 
11
11
  <script setup>
12
- import DefaultLoginView from "@jskit-ai/auth-web/client/views/DefaultLoginView";
12
+ import LoginView from "@/views/auth/LoginView.vue";
13
13
  </script>
14
14
 
15
15
  <template>
16
- <DefaultLoginView />
16
+ <LoginView />
17
17
  </template>
@@ -9,9 +9,9 @@
9
9
  </route>
10
10
 
11
11
  <script setup>
12
- import DefaultResetPasswordView from "@jskit-ai/auth-web/client/views/DefaultResetPasswordView";
12
+ import ResetPasswordView from "@/views/auth/ResetPasswordView.vue";
13
13
  </script>
14
14
 
15
15
  <template>
16
- <DefaultResetPasswordView />
16
+ <ResetPasswordView />
17
17
  </template>
@@ -9,9 +9,9 @@
9
9
  </route>
10
10
 
11
11
  <script setup>
12
- import DefaultSignOutView from "@jskit-ai/auth-web/client/views/DefaultSignOutView";
12
+ import SignOutView from "@/views/auth/SignOutView.vue";
13
13
  </script>
14
14
 
15
15
  <template>
16
- <DefaultSignOutView />
16
+ <SignOutView />
17
17
  </template>
@@ -1,7 +1,12 @@
1
1
  <script setup>
2
2
  import DefaultLoginView from "@jskit-ai/auth-web/client/views/DefaultLoginView";
3
+ import AppBrand from "@/components/AppBrand.vue";
3
4
  </script>
4
5
 
5
6
  <template>
6
- <DefaultLoginView />
7
+ <DefaultLoginView>
8
+ <template #brand="{ compact }">
9
+ <AppBrand :compact="compact" />
10
+ </template>
11
+ </DefaultLoginView>
7
12
  </template>
@@ -1,7 +1,12 @@
1
1
  <script setup>
2
2
  import DefaultResetPasswordView from "@jskit-ai/auth-web/client/views/DefaultResetPasswordView";
3
+ import AppBrand from "@/components/AppBrand.vue";
3
4
  </script>
4
5
 
5
6
  <template>
6
- <DefaultResetPasswordView />
7
+ <DefaultResetPasswordView>
8
+ <template #brand="{ compact }">
9
+ <AppBrand :compact="compact" />
10
+ </template>
11
+ </DefaultResetPasswordView>
7
12
  </template>
@@ -1,7 +1,12 @@
1
1
  <script setup>
2
2
  import DefaultSignOutView from "@jskit-ai/auth-web/client/views/DefaultSignOutView";
3
+ import AppBrand from "@/components/AppBrand.vue";
3
4
  </script>
4
5
 
5
6
  <template>
6
- <DefaultSignOutView />
7
+ <DefaultSignOutView>
8
+ <template #brand="{ compact }">
9
+ <AppBrand :compact="compact" />
10
+ </template>
11
+ </DefaultSignOutView>
7
12
  </template>
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/auth-provider-oidc-core": "0.2.16",
6
- "@jskit-ai/auth-web": "0.2.14"
5
+ "@jskit-ai/auth-provider-oidc-core": "0.2.18",
6
+ "@jskit-ai/auth-web": "0.2.16"
7
7
  }
8
8
  }
@@ -2,6 +2,6 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/auth-provider-supabase-core": "0.2.12"
5
+ "@jskit-ai/auth-provider-supabase-core": "0.2.13"
6
6
  }
7
7
  }
@@ -2,7 +2,7 @@
2
2
  "name": "s3-native-format-example",
3
3
  "private": true,
4
4
  "type": "module",
5
- "scripts": { "test": "node --test --test-concurrency=1 verify-formats.mjs" },
5
+ "scripts": { "test": "node verify-formats.mjs" },
6
6
  "dependencies": {
7
7
  "csv-parse": "7.0.2",
8
8
  "csv-stringify": "6.8.3",
@@ -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.2.11",
11
- "@jskit-ai/connector-google-calendar": "0.2.11",
12
- "@jskit-ai/database-runtime": "0.1.259",
13
- "@jskit-ai/database-runtime-mysql": "0.1.257",
10
+ "@jskit-ai/connectors-core": "0.2.12",
11
+ "@jskit-ai/connector-google-calendar": "0.2.12",
12
+ "@jskit-ai/database-runtime": "0.1.260",
13
+ "@jskit-ai/database-runtime-mysql": "0.1.258",
14
14
  "knex": "^3.1.0"
15
15
  }
16
16
  }
@@ -14,7 +14,7 @@ import { RouterView } from "vue-router";
14
14
  </script>
15
15
 
16
16
  <template>
17
- <ShellLayout title="" subtitle="">
17
+ <ShellLayout>
18
18
  <RouterView />
19
19
  </ShellLayout>
20
20
  </template>
@@ -9,8 +9,8 @@
9
9
  "./shared": "./src/shared/index.js"
10
10
  },
11
11
  "dependencies": {
12
- "@jskit-ai/crud-core": "0.2.11",
13
- "@jskit-ai/resource-crud-core": "0.1.201"
12
+ "@jskit-ai/crud-core": "0.2.12",
13
+ "@jskit-ai/resource-crud-core": "0.1.202"
14
14
  },
15
15
  "jskit": {
16
16
  "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.257"
11
+ "@jskit-ai/database-runtime-mysql": "0.1.258"
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.256"
11
+ "@jskit-ai/database-runtime-postgres": "0.1.257"
12
12
  }
13
13
  }
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/realtime": "0.2.15",
6
- "@jskit-ai/shell-web": "0.2.12"
5
+ "@jskit-ai/realtime": "0.2.17",
6
+ "@jskit-ai/shell-web": "0.2.14"
7
7
  }
8
8
  }
@@ -72,8 +72,10 @@ function launchGoogleRewardedAd({
72
72
 
73
73
  let readySeen = false;
74
74
  let settled = false;
75
+ let availabilityTimer = null;
75
76
 
76
77
  const cleanup = () => {
78
+ window.clearTimeout(availabilityTimer);
77
79
  pubads.removeEventListener("rewardedSlotReady", handleReady);
78
80
  pubads.removeEventListener("rewardedSlotGranted", handleGranted);
79
81
  pubads.removeEventListener("rewardedSlotClosed", handleClosed);
@@ -120,8 +122,8 @@ function launchGoogleRewardedAd({
120
122
  gptServicesEnabled = true;
121
123
  }
122
124
 
123
- googletag.display(slot);
124
- window.setTimeout(() => {
125
+ // Started before display, so a slot that settles at once still clears it.
126
+ availabilityTimer = window.setTimeout(() => {
125
127
  if (settled || readySeen) {
126
128
  return;
127
129
  }
@@ -131,6 +133,7 @@ function launchGoogleRewardedAd({
131
133
  reject(new Error("No rewarded ad was available."));
132
134
  });
133
135
  }, 10000);
136
+ googletag.display(slot);
134
137
  });
135
138
  })
136
139
  );
@@ -5,7 +5,7 @@
5
5
  "private": true,
6
6
  "type": "module",
7
7
  "dependencies": {
8
- "@jskit-ai/kernel": "0.2.10",
8
+ "@jskit-ai/kernel": "0.2.11",
9
9
  "json-rest-schema": "^1.0.17"
10
10
  },
11
11
  "exports": {
@@ -55,12 +55,17 @@ placement registry, topology, and adaptive smoke helper exported by
55
55
  border widths to `0` and gives navigation a tonal surface, for example
56
56
  `rgb(var(--v-theme-surface-container-low))` when the theme defines it.
57
57
  - `createShellRouter()` from `@jskit-ai/kernel/client` installs
58
- `shellScrollBehavior`: a new screen opens at the top (or its hash target),
59
- Back/Forward restore the saved position, and query, hash, tab or sub-view
60
- changes within the same page keep it. A tab or sub-view is the same page when
61
- both locations share a parent page record below the surface root. Pass
62
- `scrollBehavior` to replace it, or `scrollBehavior: false` to disable it; do
63
- not wrap the router or reset scroll in page components.
58
+ `shellScrollBehavior`: a new screen opens at the top (or its hash target,
59
+ below the target's `scroll-margin-top`), Back/Forward restore the saved
60
+ position, and query, hash, tab or sub-view changes within the same page keep
61
+ it. A tab or sub-view is the same page when both locations share a parent
62
+ page record below the surface root that draws a component, a layout hosting
63
+ the tabs; a folder's component-less record, as file routing creates for
64
+ `bookings/`, makes its pages different pages. Pass `scrollBehavior` to replace it
65
+ (calling `shellScrollBehavior` for what it leaves to the shell), or
66
+ `scrollBehavior: false` to disable it. A `createRouter` that replaces the
67
+ behaviour it is given throws, so never set one there; do not wrap the router
68
+ or reset scroll in page components.
64
69
  - The adaptive browser check closes navigation through its public close control.
65
70
 
66
71
  ## Example files
@@ -0,0 +1,42 @@
1
+ <script setup>
2
+ import ShellSectionTabLinkItem from "@jskit-ai/shell-web/client/components/ShellSectionTabLinkItem";
3
+
4
+ const props = defineProps({
5
+ label: {
6
+ type: String,
7
+ default: ""
8
+ },
9
+ to: {
10
+ type: String,
11
+ default: ""
12
+ },
13
+ icon: {
14
+ type: String,
15
+ default: ""
16
+ },
17
+ surface: {
18
+ type: String,
19
+ default: ""
20
+ },
21
+ scopedSuffix: {
22
+ type: String,
23
+ default: "/"
24
+ },
25
+ unscopedSuffix: {
26
+ type: String,
27
+ default: "/"
28
+ },
29
+ disabled: {
30
+ type: Boolean,
31
+ default: false
32
+ },
33
+ exact: {
34
+ type: Boolean,
35
+ default: false
36
+ }
37
+ });
38
+ </script>
39
+
40
+ <template>
41
+ <ShellSectionTabLinkItem v-bind="props" />
42
+ </template>
@@ -14,7 +14,7 @@ import { RouterView } from "vue-router";
14
14
  </script>
15
15
 
16
16
  <template>
17
- <ShellLayout title="" subtitle="">
17
+ <ShellLayout>
18
18
  <RouterView />
19
19
  </ShellLayout>
20
20
  </template>
@@ -6,7 +6,7 @@ const message = "The page you requested does not exist.";
6
6
 
7
7
  <template>
8
8
  <!-- The shell's top app bar carries the title, and its navigation leads back into the app. -->
9
- <ShellLayout title="Not Found" subtitle="">
9
+ <ShellLayout title="Not Found">
10
10
  <section class="d-flex flex-column align-center text-center on-surface-variant py-16">
11
11
  <v-icon icon="$searchOff" size="48" />
12
12
  <p class="text-body-large mt-4 mb-0">{{ message }}</p>
@@ -35,6 +35,12 @@ a filename alone.
35
35
  - Child links share an explicit section owner.
36
36
  - Topology maps each semantic placement to concrete responsive outlets.
37
37
  - Placed components are registered by token through a client provider.
38
+ - Section navigation is Material 3 primary tabs: the section's `page.section-nav`
39
+ topology names a section-tab renderer (`ShellSectionTabLinkItem`, registered as
40
+ `local.main.ui.section-tab-link-item`), and the page draws the tab row (one
41
+ row over a 1dp `outline-variant` divider that scrolls sideways). A navigation
42
+ bar destination (`ShellTabLinkItem`) belongs to the shell's navigation bar
43
+ only; placed in a page it draws a section tab.
38
44
  - Compact navigation controls remain accessible and at least 48 CSS pixels.
39
45
  - Application source has no generated-file marker, receipt, or overwrite state.
40
46
 
@@ -8,10 +8,9 @@ definePage({ meta: { jskit: { title: "Reports" } } });
8
8
 
9
9
  <template>
10
10
  <section class="reports-section d-flex flex-column ga-4">
11
- <nav aria-label="Report sections">
12
- <v-tabs>
13
- <ShellOutlet target="reports:section-nav" />
14
- </v-tabs>
11
+ <!-- Material 3 primary tabs: one row over a divider, scrolling sideways when the tabs don't fit. -->
12
+ <nav class="reports-section__tabs" aria-label="Report sections">
13
+ <ShellOutlet target="reports:section-nav" />
15
14
  </nav>
16
15
 
17
16
  <div class="reports-section__content">
@@ -25,4 +24,10 @@ definePage({ meta: { jskit: { title: "Reports" } } });
25
24
  .reports-section__content {
26
25
  min-width: 0;
27
26
  }
27
+
28
+ .reports-section__tabs {
29
+ border-bottom: 1px solid rgb(var(--v-theme-outline-variant));
30
+ display: flex;
31
+ overflow-x: auto;
32
+ }
28
33
  </style>
@@ -4,6 +4,8 @@ function addPlacementTopology(value = {}) {
4
4
  placements.push(value);
5
5
  }
6
6
 
7
+ // The section's tabs: Material 3 primary tabs at every width (the shell's section-tab renderer), never navigation bar
8
+ // destinations, which belong to the shell's navigation bar alone.
7
9
  addPlacementTopology({
8
10
  id: "page.section-nav",
9
11
  owner: "reports",
@@ -12,15 +14,15 @@ addPlacementTopology({
12
14
  variants: {
13
15
  compact: {
14
16
  outlet: "reports:section-nav",
15
- renderers: { link: "local.main.ui.tab-link-item" }
17
+ renderers: { link: "local.main.ui.section-tab-link-item" }
16
18
  },
17
19
  medium: {
18
20
  outlet: "reports:section-nav",
19
- renderers: { link: "local.main.ui.tab-link-item" }
21
+ renderers: { link: "local.main.ui.section-tab-link-item" }
20
22
  },
21
23
  expanded: {
22
24
  outlet: "reports:section-nav",
23
- renderers: { link: "local.main.ui.tab-link-item" }
25
+ renderers: { link: "local.main.ui.section-tab-link-item" }
24
26
  }
25
27
  }
26
28
  });
@@ -38,6 +38,18 @@ app's language; the app does, for example
38
38
  `watch(useAccountLocale(), (code) => { if (code) i18n.global.locale.value = code; }, { immediate: true })`.
39
39
  Section titles may be plain text, a message key or `{ key, fallback }`.
40
40
 
41
+ Sections are pages of the account surface, in Material 3's list-detail layout
42
+ (shell-web's `ShellListDetail`): `/account` is the section list
43
+ (`src/pages/account/index.vue`) and `/account/<section>` one section
44
+ (`src/pages/account/[section].vue`, its `value`, such as
45
+ `/account/preferences`), both rendering `AccountSettingsClientElement`, which
46
+ reads the section from the route's `section` param. The section page's parent
47
+ is the list, the surface's `"/"`. A phone or medium window shows the list or one
48
+ section, and Up returns from the section to the list; an expanded or large
49
+ window shows the list beside the open section, or beside the first section on
50
+ the list's own page. A link to a section is its path, `/account/invites`, with
51
+ `returnTo` in the query for Done.
52
+
41
53
  `runtime.signInMethods.methods` lists the auth provider's OAuth sign-in
42
54
  methods as `{ provider, label, linked, canLink, canUnlink }`, read from the
43
55
  settings security status. `link(provider)` leaves for the provider through
@@ -57,10 +69,22 @@ provider offers provider linking (`providerLinking` in its capabilities).
57
69
  - Loading uses layout-stable skeletons, never an indeterminate spinner.
58
70
  - Pending controls keep stable labels and prevent duplicate submission.
59
71
  - The route and placement owner agree.
72
+ - The account surface renders in its own layout, `src/pages/account.vue`
73
+ (`ShellLayout` around `<RouterView />`, `meta.jskit.surface: "account"`), so
74
+ the settings page's title and Up arrow are in the top app bar. Up is the
75
+ page's only way out; `AccountSettingsClientElement` adds a Done action only
76
+ where Up can't go back (no parent, a section beside the list on a wide
77
+ window, or a returnTo on another origin).
78
+ With no navigation items, the surface shows no navigation bar, rail or drawer.
79
+ - Each section has its own address, `/account/<section>`, served by
80
+ `src/pages/account/[section].vue` with parent `"/"`; nothing selects a
81
+ section through the query.
60
82
 
61
83
  ## Example files
62
84
 
63
- `example/` contains an account route and four concrete settings sections.
85
+ `example/` contains the account surface's layout, the section list's route
86
+ (`account/index.vue`), a section's route (`account/[section].vue`) and four
87
+ concrete settings sections.
64
88
  Adapt their fields and copy while retaining the shared account runtime.
65
89
 
66
90
  ## Variation points
@@ -72,7 +96,9 @@ notification vocabulary while keeping server-owned validation authoritative.
72
96
 
73
97
  Navigate away and back with warm cache, exercise browser back/forward, save and
74
98
  clear fields, verify toast errors, inspect skeleton geometry, and test keyboard
75
- operation at compact and expanded widths.
99
+ operation at compact and expanded widths. At 390 px check that the list and a
100
+ section show one at a time and Up returns from the section to the list; at
101
+ 1280 px that the list shows beside the open section, with no Up.
76
102
 
77
103
  ## Avoid
78
104
 
@@ -35,11 +35,11 @@ const notifications = props.runtime.notifications;
35
35
  color="primary"
36
36
  hide-details
37
37
  disabled
38
- class="mb-4"
39
38
  />
40
39
  <v-btn
41
40
  type="submit"
42
41
  color="primary"
42
+ class="mt-4"
43
43
  :loading="notifications.isSaving.value"
44
44
  :disabled="notifications.isRefreshing.value"
45
45
  >
@@ -94,7 +94,7 @@ const preferences = props.runtime.preferences;
94
94
  />
95
95
  </v-col>
96
96
 
97
- <v-col cols="12" md="3">
97
+ <v-col cols="12" md="4">
98
98
  <v-select
99
99
  v-model.number="preferences.form.avatarSize"
100
100
  label="Avatar size"
@@ -110,6 +110,7 @@ const preferences = props.runtime.preferences;
110
110
  <v-btn
111
111
  type="submit"
112
112
  color="primary"
113
+ class="mt-4"
113
114
  :loading="preferences.isSaving.value"
114
115
  :disabled="preferences.isRefreshing.value"
115
116
  >
@@ -51,7 +51,7 @@ const profile = props.runtime.profile;
51
51
  </v-row>
52
52
 
53
53
  <v-row>
54
- <v-col cols="12" md="6">
54
+ <v-col cols="12">
55
55
  <v-text-field
56
56
  v-model="profile.form.displayName"
57
57
  label="Display name"
@@ -63,7 +63,7 @@ const profile = props.runtime.profile;
63
63
  />
64
64
  </v-col>
65
65
 
66
- <v-col cols="12" md="6">
66
+ <v-col cols="12">
67
67
  <v-text-field
68
68
  v-model="profile.form.email"
69
69
  label="Email"
@@ -79,6 +79,7 @@ const profile = props.runtime.profile;
79
79
  <v-btn
80
80
  type="submit"
81
81
  color="primary"
82
+ class="mt-4"
82
83
  :loading="profile.isSaving.value"
83
84
  :disabled="profile.isDeletingAvatar.value || profile.isRefreshing.value"
84
85
  >
@@ -0,0 +1,23 @@
1
+ <route lang="json">
2
+ {
3
+ "meta": {
4
+ "guard": {
5
+ "policy": "authenticated"
6
+ },
7
+ "jskit": {
8
+ "title": "Account settings",
9
+ "parent": "/"
10
+ }
11
+ }
12
+ }
13
+ </route>
14
+
15
+ <template>
16
+ <AccountSettingsClientElement />
17
+ </template>
18
+
19
+ <script setup>
20
+ // One section of account settings (/account/preferences). Its parent is the section list, the surface's "/", so on a
21
+ // phone Up returns to the list; a wide window shows the list beside the section.
22
+ import AccountSettingsClientElement from "@jskit-ai/users-web/client/components/AccountSettingsClientElement";
23
+ </script>
@@ -0,0 +1,23 @@
1
+ <route lang="json">
2
+ {
3
+ "meta": {
4
+ "jskit": {
5
+ "surface": "account"
6
+ }
7
+ }
8
+ }
9
+ </route>
10
+
11
+ <script setup>
12
+ // The account surface's layout: the shell's top app bar carries the settings page's title and its Up arrow back to
13
+ // home, the page's only way out (a Done action shows only where Up can't go back, such as a returnTo on another of
14
+ // the app's origins). The surface has no navigation items, so the shell draws no navigation bar, rail or drawer.
15
+ import ShellLayout from "@/components/ShellLayout.vue";
16
+ import { RouterView } from "vue-router";
17
+ </script>
18
+
19
+ <template>
20
+ <ShellLayout>
21
+ <RouterView />
22
+ </ShellLayout>
23
+ </template>