@jskit-ai/agent-docs 0.1.279 → 0.1.281

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 (138) hide show
  1. package/guide/agent/app-extras/assistant-conversation.md +5 -1
  2. package/guide/agent/app-extras/assistant.md +123 -0
  3. package/guide/agent/app-extras/realtime.md +20 -10
  4. package/guide/agent/app-setup/authentication.md +54 -4
  5. package/guide/agent/app-setup/database-layer.md +108 -35
  6. package/guide/agent/app-setup/existing-application-migration.md +7 -1
  7. package/guide/agent/app-setup/material-3-migration.md +38 -9
  8. package/guide/agent/app-setup/multi-homing.md +14 -4
  9. package/guide/agent/app-setup/upgrading-jskit.md +105 -0
  10. package/guide/agent/app-setup/users.md +9 -1
  11. package/guide/agent/framework/client-features.md +35 -1
  12. package/guide/agent/framework/material-3.md +111 -71
  13. package/guide/agent/framework/sanity-checks.md +181 -0
  14. package/guide/agent/framework/translation.md +4 -0
  15. package/guide/agent/framework/typed-features.md +222 -120
  16. package/guide/agent/framework/ui-operations.md +3 -1
  17. package/guide/agent/index.md +3 -0
  18. package/package.json +2 -2
  19. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  20. package/patterns/minimal-foundation/PATTERN.md +6 -2
  21. package/patterns/minimal-foundation/example/AGENTS.md +22 -5
  22. package/patterns/minimal-foundation/example/package.json +6 -6
  23. package/patterns/minimal-foundation/example/playwright.config.mjs +3 -30
  24. package/patterns/minimal-foundation/example/server.js +7 -1
  25. package/patterns/minimal-foundation/example/src/main.js +3 -2
  26. package/patterns/minimal-foundation/example/tests/e2e/startup.spec.ts +13 -0
  27. package/patterns/minimal-foundation/example/vite.config.mjs +3 -1
  28. package/patterns/shell-foundation/PATTERN.md +4 -1
  29. package/patterns/shell-foundation/example/AGENTS.md +22 -5
  30. package/patterns/shell-foundation/example/package.json +7 -7
  31. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  32. package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
  33. package/patterns/shell-foundation/example/playwright.config.mjs +3 -30
  34. package/patterns/shell-foundation/example/server.js +7 -1
  35. package/patterns/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  36. package/patterns/shell-foundation/example/src/main.js +3 -2
  37. package/patterns/shell-foundation/example/src/pages/home.vue +1 -1
  38. package/patterns/shell-foundation/example/src/views/NotFound.vue +1 -1
  39. package/patterns/shell-foundation/example/tests/e2e/startup.spec.ts +13 -0
  40. package/patterns/shell-foundation/example/vite.config.mjs +3 -1
  41. package/reference/autogen/PATTERN_INDEX.md +37 -37
  42. package/reference/autogen/packages/agent-docs.md +4 -0
  43. package/reference/autogen/packages/architecture-inspector.md +1 -0
  44. package/reference/autogen/packages/assistant-runtime.md +28 -3
  45. package/reference/autogen/packages/auth-provider-local-core.md +2 -1
  46. package/reference/autogen/packages/auth-web.md +10 -0
  47. package/reference/autogen/packages/connectors-catalog.md +8 -0
  48. package/reference/autogen/packages/crud-core.md +3 -0
  49. package/reference/autogen/packages/database-runtime.md +33 -0
  50. package/reference/autogen/packages/http-web.md +0 -4
  51. package/reference/autogen/packages/json-rest-api-core.md +17 -4
  52. package/reference/autogen/packages/kernel.md +37 -2
  53. package/reference/autogen/packages/material-web.md +2 -1
  54. package/reference/autogen/packages/oidc-issuer-core.md +0 -1
  55. package/reference/autogen/packages/realtime.md +8 -5
  56. package/reference/autogen/packages/shell-web.md +88 -3
  57. package/reference/autogen/packages/users-web.md +5 -2
  58. package/reference/autogen/packages/workspaces-core.md +3 -1
  59. package/reference/autogen/tooling/jskit-catalog.md +122 -5
  60. package/reference/autogen/tooling/testUtils.md +15 -1
  61. package/skills/jskit/SKILL.md +17 -5
  62. package/skills/jskit/references/client-features.md +35 -1
  63. package/skills/jskit/references/existing-application-migration.md +7 -1
  64. package/skills/jskit/references/heuristics/INDEX.md +3 -1
  65. package/skills/jskit/references/heuristics/row-policies.md +3 -3
  66. package/skills/jskit/references/heuristics/typed-features.md +7 -7
  67. package/skills/jskit/references/heuristics/ui-testing.md +27 -21
  68. package/skills/jskit/references/material-3.md +111 -71
  69. package/skills/jskit/references/multi-homing.md +14 -4
  70. package/skills/jskit/references/pattern-index.md +37 -37
  71. package/skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md +6 -2
  72. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +22 -5
  73. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +6 -6
  74. package/skills/jskit/references/patterns/app/minimal-foundation/example/playwright.config.mjs +3 -30
  75. package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +7 -1
  76. package/skills/jskit/references/patterns/app/minimal-foundation/example/src/main.js +3 -2
  77. package/skills/jskit/references/patterns/app/minimal-foundation/example/tests/e2e/startup.spec.ts +13 -0
  78. package/skills/jskit/references/patterns/app/minimal-foundation/example/vite.config.mjs +3 -1
  79. package/skills/jskit/references/patterns/app/shell-foundation/PATTERN.md +4 -1
  80. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +22 -5
  81. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +7 -7
  82. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  83. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
  84. package/skills/jskit/references/patterns/app/shell-foundation/example/playwright.config.mjs +3 -30
  85. package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +7 -1
  86. package/skills/jskit/references/patterns/app/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  87. package/skills/jskit/references/patterns/app/shell-foundation/example/src/main.js +3 -2
  88. package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home.vue +1 -1
  89. package/skills/jskit/references/patterns/app/shell-foundation/example/src/views/NotFound.vue +1 -1
  90. package/skills/jskit/references/patterns/app/shell-foundation/example/tests/e2e/startup.spec.ts +13 -0
  91. package/skills/jskit/references/patterns/app/shell-foundation/example/vite.config.mjs +3 -1
  92. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
  93. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +18 -5
  94. package/skills/jskit/references/patterns/auth/auth-surface/example/src/components/AppBrand.vue +17 -0
  95. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/login.vue +2 -2
  96. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/reset-password.vue +2 -2
  97. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/signout.vue +2 -2
  98. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/LoginView.vue +6 -1
  99. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/ResetPasswordView.vue +6 -1
  100. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/SignOutView.vue +6 -1
  101. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  102. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  103. package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/package.json +1 -1
  104. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  105. package/skills/jskit/references/patterns/console/console-surface/example/src/pages/console.vue +1 -1
  106. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  107. package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +34 -24
  108. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +3 -2
  109. package/skills/jskit/references/patterns/database/mysql-application/example/tests/e2e/databases.js +25 -0
  110. package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +12 -0
  111. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +3 -2
  112. package/skills/jskit/references/patterns/database/postgres-application/example/tests/e2e/databases.js +25 -0
  113. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  114. package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js +5 -2
  115. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  116. package/skills/jskit/references/patterns/shell/application-shell/PATTERN.md +11 -6
  117. package/skills/jskit/references/patterns/shell/application-shell/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  118. package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home.vue +1 -1
  119. package/skills/jskit/references/patterns/shell/application-shell/example/src/views/NotFound.vue +1 -1
  120. package/skills/jskit/references/patterns/ui/page-and-placement/PATTERN.md +6 -0
  121. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/pages/home/reports.vue +9 -4
  122. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/placementTopology.js +5 -3
  123. package/skills/jskit/references/patterns/users/account-settings/PATTERN.md +24 -3
  124. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsNotificationsSection.vue +1 -1
  125. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue +2 -1
  126. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsProfileSection.vue +3 -2
  127. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account/[section].vue +23 -0
  128. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account.vue +4 -3
  129. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  130. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  131. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/packages/main/src/client/components/AccountPendingInvitesCue.vue +3 -3
  132. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/admin/root.vue +1 -1
  133. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/app/root.vue +1 -1
  134. package/skills/jskit/references/sanity-checks.md +181 -0
  135. package/skills/jskit/references/translation.md +4 -0
  136. package/skills/jskit/references/typed-features.md +222 -120
  137. package/skills/jskit/references/ui-operations.md +3 -1
  138. package/skills/jskit/references/patterns/database/mysql-application/example/tests/browser/database.js +0 -68
@@ -1,32 +1,5 @@
1
1
  import { defineConfig } from "@playwright/test";
2
+ import { browserTestConfig } from "@jskit-ai/jskit-catalog/browser-tests";
2
3
 
3
- const managedBaseUrl = String(process.env.PLAYWRIGHT_BASE_URL || "")
4
- .trim()
5
- .replace(/\/+$/u, "");
6
- const port = String(process.env.PLAYWRIGHT_PORT || "").trim() || "4173";
7
- const baseURL = managedBaseUrl || `http://127.0.0.1:${port}`;
8
- const storageState = String(process.env.PLAYWRIGHT_STORAGE_STATE || "").trim();
9
-
10
- export default defineConfig({
11
- testDir: "./tests/e2e",
12
- timeout: 60_000,
13
- expect: {
14
- timeout: 10_000
15
- },
16
- use: {
17
- baseURL,
18
- headless: true,
19
- ...(storageState ? { storageState } : {})
20
- },
21
- ...(managedBaseUrl ? {} : {
22
- webServer: {
23
- command: "npm run develop",
24
- env: {
25
- PORT: port
26
- },
27
- url: `${baseURL}/api/health`,
28
- reuseExistingServer: true,
29
- timeout: 180_000
30
- }
31
- })
32
- });
4
+ // Each Playwright worker gets its own server on the production build; with PLAYWRIGHT_BASE_URL, the managed target.
5
+ export default defineConfig(browserTestConfig({ testDir: "./tests/e2e" }));
@@ -23,6 +23,10 @@ import { surfaceRuntime } from "./server/lib/surfaceRuntime.js";
23
23
  /** @typedef {import("@jskit-ai/kernel/server/types").RuntimeEnv} RuntimeEnv */
24
24
 
25
25
  const SPA_INDEX_FILE = "index.html";
26
+ // Vite names each built file under assets/ after its content (`index-BxY3kz9a.js`), so a browser may keep one for good:
27
+ // a new build references new names. index.html and unhashed files are revalidated on every load.
28
+ const HASHED_ASSET = /^assets\/(?:.+\/)?[^/]+-[\w-]{8}\.[a-z0-9]+$/u;
29
+ const HASHED_ASSET_CACHE = Object.freeze({ maxAge: 365 * 24 * 60 * 60 * 1000, immutable: true });
26
30
  const STATIC_GLOBAL_UI_PATHS = Object.freeze([
27
31
  "/assets",
28
32
  "/favicon.svg",
@@ -157,7 +161,9 @@ async function createServer({ runtimeEnv }) {
157
161
  if (hasFileExtension(pathname)) {
158
162
  const staticFilePath = toStaticFilePath(pathname);
159
163
  if (hasWebBuild && staticFilePath && canServeStaticFile(distRoot, staticFilePath)) {
160
- return reply.sendFile(staticFilePath);
164
+ return HASHED_ASSET.test(staticFilePath)
165
+ ? reply.sendFile(staticFilePath, HASHED_ASSET_CACHE)
166
+ : reply.sendFile(staticFilePath);
161
167
  }
162
168
  return reply.code(404).send({
163
169
  message: `Route ${method}:${pathname} not found`,
@@ -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>
@@ -1,10 +1,11 @@
1
+ // Vuetify's styles first, so its stylesheet comes before any component chunk's.
2
+ import "vuetify/styles";
3
+ import "@jskit-ai/material-web/client/material.css";
1
4
  import { createApp } from "vue";
2
5
  import { createPinia } from "pinia";
3
6
  import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
4
7
  import { createRouter, createWebHistory } from "vue-router/auto";
5
8
  import { routes } from "vue-router/auto-routes";
6
- import "vuetify/styles";
7
- import "@jskit-ai/material-web/client/material.css";
8
9
  import { createVuetify } from "vuetify";
9
10
  import { createMaterialVuetifyOptions } from "@jskit-ai/material-web/client";
10
11
  import App from "./App.vue";
@@ -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>
@@ -33,3 +33,16 @@ for (const width of [390, 768, 1280]) {
33
33
  await expect(page.locator("#startup-shell")).toHaveCount(0);
34
34
  });
35
35
  }
36
+
37
+ test("built assets are kept for good and the page itself is revalidated", async ({ page }) => {
38
+ test.skip(Boolean(process.env.PLAYWRIGHT_BASE_URL), "A managed target's server and proxy set its own caching.");
39
+ const assets: string[] = [];
40
+ page.on("response", response => {
41
+ if (new URL(response.url()).pathname.startsWith("/assets/")) assets.push(response.headers()["cache-control"] ?? "");
42
+ });
43
+ const document = await page.goto("/home");
44
+ await page.waitForLoadState("load");
45
+ expect(document?.headers()["cache-control"] ?? "").not.toContain("immutable");
46
+ expect(assets.length).toBeGreaterThan(0);
47
+ expect(new Set(assets)).toEqual(new Set(["public, max-age=31536000, immutable"]));
48
+ });
@@ -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.18"
5
+ "@jskit-ai/database-runtime-mysql": "0.1.259",
6
+ "@jskit-ai/oidc-issuer-core": "0.1.20"
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.17",
6
- "@jskit-ai/auth-web": "0.2.15"
5
+ "@jskit-ai/auth-provider-oidc-core": "0.2.19",
6
+ "@jskit-ai/auth-web": "0.2.17"
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.14"
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.13",
11
+ "@jskit-ai/connector-google-calendar": "0.2.13",
12
+ "@jskit-ai/database-runtime": "0.1.261",
13
+ "@jskit-ai/database-runtime-mysql": "0.1.259",
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.13",
13
+ "@jskit-ai/resource-crud-core": "0.1.203"
14
14
  },
15
15
  "jskit": {
16
16
  "kind": "runtime",
@@ -61,27 +61,38 @@ metadata. Do not combine it with the PostgreSQL pattern's workflow: a MySQL
61
61
  application's workflow has no `postgres` service, `POSTGRES_*` variables, port
62
62
  `5432`, or `pg_isready` option.
63
63
 
64
- `example/tests/browser/database.js` is the application-owned browser preparation
65
- helper. It requires the exact `BROWSER_TEST_DB_NAME`, rejects the ordinary and
66
- disposable `TEST_DB_NAME` databases,
67
- retains the schema and migration ledger, applies pending migrations, then clears
68
- test rows and calls one explicit fixture seed transaction. The seed must restore
69
- any migration-owned baseline rows the app needs. Call it once before starting
70
- the isolated test server, never in `beforeEach`. It closes its connections without
71
- dropping the database. The launcher selects that database only for its own server
72
- and identity process, disables external effects, and proves actual server identity
73
- before the suite writes data. It must not edit the normal environment. Provision
74
- both test databases with exact grants for the application account. A disposable
75
- integration or migration test must never drop the browser database.
76
-
77
- Startup reports whether the schema exists, the number of migrations applied,
78
- and the migration, fixture-reset and seed durations. Use those measurements to
79
- diagnose a slow start before rerunning a browser case.
64
+ Tests get their own databases from
65
+ `@jskit-ai/database-runtime/server/testDatabases`. The `test` script runs
66
+ `jskit test --databases`: each Node test file running at once gets its own
67
+ copy of `TEST_DB_NAME` (`<name>`, `<name>_1`, ...), restored from a stored
68
+ snapshot of the current migrations' schema and the rows they write, in
69
+ `node_modules/.cache/jskit-test-databases/`. The snapshot is replayed once, and
70
+ again only when a migration's name or contents or `package-lock.json` change;
71
+ a file that migrates or drops tables leaves its copy to be restored before the
72
+ next file takes it.
73
+
74
+ `example/tests/e2e/databases.js` is the browser run's `prepare` module for
75
+ `browserTestConfig({ prepare: "tests/e2e/databases.js" })` from
76
+ `@jskit-ai/jskit-catalog/browser-tests`. It gives each Playwright worker's
77
+ server its own copy of `BROWSER_TEST_DB_NAME`, restored from the same kind of
78
+ snapshot and seeded in one transaction by the app's fixture `seed`, which
79
+ restores any migration-owned baseline rows the app needs. Every server serves
80
+ the production build. Both refuse the application's database (`DB_NAME`,
81
+ `DATABASE_URL`'s, the Knex connection's), `PROD_DB_NAME` and the other test
82
+ database and its copies. Provision `TEST_DB_NAME`, `BROWSER_TEST_DB_NAME` and
83
+ their numbered copies with grants for the application account (a `<app>_%`
84
+ grant covers them); a user who may create no copies runs one file or worker at
85
+ a time. Two test runs at once need different database names. Test-only server
86
+ settings that disable external effects (email, payments) go in
87
+ `browserTestConfig({ env })`; a suite that writes data proves the server's
88
+ identity first.
89
+
90
+ Each preparation reports how many databases it made, whether it replayed the
91
+ migrations for a new snapshot, and the restore and seed durations. Use those
92
+ measurements to diagnose a slow start before rerunning a browser case.
80
93
 
81
94
  Keep fixtures small for the selected test scope. Run related cases in one suite
82
- invocation. Use the development launcher for ordinary browser checks; test the
83
- production build separately when needed. Fresh-schema migration proofs are
84
- separate from browser startup.
95
+ invocation. Fresh-schema migration proofs are separate from browser startup.
85
96
 
86
97
  ## Variation points
87
98
 
@@ -99,11 +110,10 @@ it as `seed` to `prepareDatabaseFromApp()`.
99
110
  - Run `npm run db:migrate:status` and the application verification command.
100
111
  - Exercise one transaction and one negative connection case.
101
112
  - When a seed exists, run `db:prepare` twice and require the second run to be safe.
102
- - For browser preparation, run a disposable test between two starts: the second applies no
103
- old migrations, stale fixture data is removed, baseline rows are restored,
104
- foreign-key checks remain enabled, and the normal database is unchanged.
105
- Also prove that a new pending migration is applied. Keep this as a regression
106
- test when adapting the example; reject a harness that recreates the schema.
113
+ - For the test databases, run the suite twice: the second run restores the
114
+ stored snapshot instead of replaying the migrations, stale rows are gone,
115
+ baseline rows are restored, foreign keys are enforced, and the normal
116
+ database is unchanged. A new migration takes a new snapshot.
107
117
  - Confirm `.github/workflows/verify.yml` parses as YAML and declares only the
108
118
  `mariadb` service. GitHub reports an unparseable workflow only as a failed
109
119
  run with "no jobs were run".
@@ -5,9 +5,10 @@
5
5
  "db:prepare": "node ./scripts/prepare-database.js",
6
6
  "db:migrate": "knex --knexfile ./knexfile.js migrate:latest",
7
7
  "db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
8
- "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
8
+ "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list",
9
+ "test": "jskit test --databases"
9
10
  },
10
11
  "dependencies": {
11
- "@jskit-ai/database-runtime-mysql": "0.1.257"
12
+ "@jskit-ai/database-runtime-mysql": "0.1.259"
12
13
  }
13
14
  }
@@ -0,0 +1,25 @@
1
+ import { prepareTestDatabases } from "@jskit-ai/database-runtime/server/testDatabases";
2
+ import knexConfig from "../../knexfile.js";
3
+
4
+ // The browser run's `prepare` module: `browserTestConfig({ prepare: "tests/e2e/databases.js" })` in
5
+ // playwright.config.mjs runs it once, before the servers start. Every Playwright worker gets its own copy of
6
+ // BROWSER_TEST_DB_NAME (`<name>`, `<name>_1`, ...), restored from the schema snapshot of the current migrations, then
7
+ // seeded; the normal and disposable TEST_DB_NAME databases are refused. It is also given each worker's `baseUrls`,
8
+ // for a server setting that names its own address.
9
+
10
+ /** The fixtures the specs rely on, including any migration-owned baseline rows the app needs. */
11
+ async function seed(transaction) {
12
+ await transaction("fixtures").insert({ id: 1, label: "Browser fixture" });
13
+ }
14
+
15
+ export default async function prepareBrowserDatabases({ workers }) {
16
+ const { databases, summary } = await prepareTestDatabases({
17
+ knexConfig,
18
+ environment: process.env,
19
+ variable: "BROWSER_TEST_DB_NAME",
20
+ count: workers,
21
+ seed
22
+ });
23
+ console.log(`[browser-tests] ${summary}.`);
24
+ return databases.map((database) => ({ DB_NAME: database }));
25
+ }
@@ -53,6 +53,18 @@ dialect. `example/scripts/prepare-database.js` is the portable
53
53
  migrate-then-seed entrypoint for managed sessions and deployments.
54
54
  `example/.env.example` names the five connection values without a secret.
55
55
 
56
+ Tests get their own databases from
57
+ `@jskit-ai/database-runtime/server/testDatabases`. The `test` script runs
58
+ `jskit test --databases`: each Node test file running at once gets a copy of
59
+ `TEST_DB_NAME`, created from `<TEST_DB_NAME>_snapshot`, a database the
60
+ migrations are replayed into once and again only when a migration changes.
61
+ `example/tests/e2e/databases.js` is the browser run's `prepare` module for
62
+ `browserTestConfig({ prepare: "tests/e2e/databases.js" })` from
63
+ `@jskit-ai/jskit-catalog/browser-tests`: each Playwright worker's server gets
64
+ its own seeded copy of `BROWSER_TEST_DB_NAME`. The database user needs
65
+ `CREATEDB`; without it, Node test files run one at a time on the connected
66
+ database.
67
+
56
68
  `example/.github/workflows/verify.yml` is a normal app-owned CI workflow with
57
69
  exactly one database service: `postgres` (`postgres:17`, `POSTGRES_*`
58
70
  variables, port `5432`, a `pg_isready` health check). Copy it whole from this
@@ -5,9 +5,10 @@
5
5
  "db:prepare": "node ./scripts/prepare-database.js",
6
6
  "db:migrate": "knex --knexfile ./knexfile.js migrate:latest",
7
7
  "db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
8
- "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
8
+ "db:migrate:status": "knex --knexfile ./knexfile.js migrate:list",
9
+ "test": "jskit test --databases"
9
10
  },
10
11
  "dependencies": {
11
- "@jskit-ai/database-runtime-postgres": "0.1.256"
12
+ "@jskit-ai/database-runtime-postgres": "0.1.258"
12
13
  }
13
14
  }
@@ -0,0 +1,25 @@
1
+ import { prepareTestDatabases } from "@jskit-ai/database-runtime/server/testDatabases";
2
+ import knexConfig from "../../knexfile.js";
3
+
4
+ // The browser run's `prepare` module: `browserTestConfig({ prepare: "tests/e2e/databases.js" })` in
5
+ // playwright.config.mjs runs it once, before the servers start. Every Playwright worker gets its own copy of
6
+ // BROWSER_TEST_DB_NAME (`<name>`, `<name>_1`, ...), created from the snapshot database of the current migrations with
7
+ // CREATE DATABASE ... TEMPLATE, then seeded; the normal and disposable TEST_DB_NAME databases are refused. The database
8
+ // user needs CREATEDB.
9
+
10
+ /** The fixtures the specs rely on, including any migration-owned baseline rows the app needs. */
11
+ async function seed(transaction) {
12
+ await transaction("fixtures").insert({ id: 1, label: "Browser fixture" });
13
+ }
14
+
15
+ export default async function prepareBrowserDatabases({ workers }) {
16
+ const { databases, summary } = await prepareTestDatabases({
17
+ knexConfig,
18
+ environment: process.env,
19
+ variable: "BROWSER_TEST_DB_NAME",
20
+ count: workers,
21
+ seed
22
+ });
23
+ console.log(`[browser-tests] ${summary}.`);
24
+ return databases.map((database) => ({ DB_NAME: database }));
25
+ }
@@ -2,7 +2,7 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "dependencies": {
5
- "@jskit-ai/realtime": "0.2.16",
6
- "@jskit-ai/shell-web": "0.2.13"
5
+ "@jskit-ai/realtime": "0.2.18",
6
+ "@jskit-ai/shell-web": "0.2.15"
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
  );