@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
@@ -24,7 +24,7 @@
24
24
  "build:home": "VITE_SURFACE=home vite build",
25
25
  "preview": "vite preview",
26
26
  "lint": "eslint .",
27
- "test": "node --test --test-concurrency=1",
27
+ "test": "jskit test",
28
28
  "test:client": "vitest run tests/client",
29
29
  "test:e2e": "playwright test tests/e2e",
30
30
  "jskit:update": "npx --yes @jskit-ai/jskit-catalog@latest update",
@@ -34,8 +34,8 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.2.10",
38
- "@jskit-ai/material-web": "0.1.2",
37
+ "@jskit-ai/kernel": "0.2.12",
38
+ "@jskit-ai/material-web": "0.1.3",
39
39
  "@tanstack/vue-query": "^5.101.0",
40
40
  "fastify": "^5.12.7",
41
41
  "pino": "^10.3.1",
@@ -44,13 +44,13 @@
44
44
  "vue": "^3.5.44",
45
45
  "vue-router": "^5.3.1",
46
46
  "vuetify": "^4.3.0",
47
- "@jskit-ai/http-runtime": "0.2.10",
47
+ "@jskit-ai/http-runtime": "0.2.12",
48
48
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
49
49
  "@vue/devtools-api": "^8.2.1"
50
50
  },
51
51
  "devDependencies": {
52
- "@jskit-ai/config-eslint": "0.1.245",
53
- "@jskit-ai/jskit-catalog": "0.2.31",
52
+ "@jskit-ai/config-eslint": "0.1.246",
53
+ "@jskit-ai/jskit-catalog": "0.2.33",
54
54
  "@playwright/test": "1.61.1",
55
55
  "@vitejs/plugin-vue": "^6.0.7",
56
56
  "eslint": "^10.8.0",
@@ -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`,
@@ -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";
@@ -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/**"],
@@ -118,7 +118,10 @@ Read these together before adapting the shell:
118
118
 
119
119
  ## Variation points
120
120
 
121
- Rename the example, replace the home content, add or remove settings routes,
121
+ Rename the example (its name is spelt in `package.json`, `app.json` and
122
+ `index.html`'s `<title>`, and, once the app adds sign-in, in the
123
+ `auth/auth-surface` pattern's `src/components/AppBrand.vue`; rename them
124
+ together), replace the home content, add or remove settings routes,
122
125
  and change placements through their public contracts. Use package-owned
123
126
  patterns for auth, users, databases, CRUD, realtime, and other capabilities.
124
127
  Do not preinstall capabilities solely because they appear in another app.
@@ -29,11 +29,28 @@ Do not install an unrelated runtime package only to read documentation.
29
29
  Before adding or changing browser-test setup, read the browser verification
30
30
  guide at
31
31
  `https://mobily-enterprises.github.io/jskit-ai/guide/framework/ui-operations#browser-verification`.
32
- Routine tests reuse the isolated schema and prepare fixtures once per suite
33
- invocation. Keep `BROWSER_TEST_DB_NAME` distinct from disposable `TEST_DB_NAME`
34
- and prove that disposable cleanup preserves browser migrations. Require
35
- repeat-start regression proof; do not rebuild the database or production
36
- frontend for each focused browser check.
32
+ `playwright.config.mjs` is `browserTestConfig()` from
33
+ `@jskit-ai/jskit-catalog/browser-tests`: every Playwright worker gets its own
34
+ server on the production build, its own port and, through the config's
35
+ `prepare` module, its own copy of the browser database, restored from a schema
36
+ snapshot and seeded. A local run never starts the development server; with
37
+ `PLAYWRIGHT_BASE_URL`, the tests use that managed target as its host serves
38
+ it. Keep `BROWSER_TEST_DB_NAME` distinct from disposable `TEST_DB_NAME`. The build and
39
+ the snapshot are reused while their sources are unchanged; do not rebuild the
40
+ database or production frontend for each focused browser check. Require
41
+ repeat-start regression proof.
42
+
43
+ Tests run in parallel within the machine's test budget: run node's test
44
+ runner through `jskit test` (`npm test`), with `--databases` once the app has a
45
+ database so each file running at once gets its own copy of `TEST_DB_NAME`,
46
+ Playwright through `browserTestConfig()`, and take Vitest's `maxWorkers` from
47
+ `testConcurrency()` of
48
+ `@jskit-ai/jskit-catalog/test-budget`; never one file at a time or a fixed
49
+ count. Isolate files that collide (their own port, temporary directory and
50
+ database) instead of serialising them; a file that truly must run alone says
51
+ why on its first line, `// jskit-test: alone - <reason>`. Plain `jskit check`
52
+ fails anything else
53
+ (`https://mobily-enterprises.github.io/jskit-ai/guide/framework/typed-features#test-files-run-in-parallel`).
37
54
 
38
55
  Keep verification output small. Run the relevant cases together, save complete
39
56
  stdout/stderr to local artifacts, and preserve the exit status. Report counts,
@@ -24,7 +24,7 @@
24
24
  "build:home": "VITE_SURFACE=home vite build",
25
25
  "preview": "vite preview",
26
26
  "lint": "eslint .",
27
- "test": "node --test --test-concurrency=1",
27
+ "test": "jskit test",
28
28
  "test:client": "vitest run tests/client",
29
29
  "test:e2e": "playwright test tests/e2e",
30
30
  "jskit:update": "npx --yes @jskit-ai/jskit-catalog@latest update",
@@ -34,8 +34,8 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.2.10",
38
- "@jskit-ai/material-web": "0.1.2",
37
+ "@jskit-ai/kernel": "0.2.12",
38
+ "@jskit-ai/material-web": "0.1.3",
39
39
  "@tanstack/vue-query": "^5.101.0",
40
40
  "fastify": "^5.12.7",
41
41
  "pino": "^10.3.1",
@@ -44,14 +44,14 @@
44
44
  "vue": "^3.5.44",
45
45
  "vue-router": "^5.3.1",
46
46
  "vuetify": "^4.3.0",
47
- "@jskit-ai/http-runtime": "0.2.10",
48
- "@jskit-ai/shell-web": "0.2.13",
47
+ "@jskit-ai/http-runtime": "0.2.12",
48
+ "@jskit-ai/shell-web": "0.2.15",
49
49
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
50
50
  "@vue/devtools-api": "^8.2.1"
51
51
  },
52
52
  "devDependencies": {
53
- "@jskit-ai/config-eslint": "0.1.245",
54
- "@jskit-ai/jskit-catalog": "0.2.31",
53
+ "@jskit-ai/config-eslint": "0.1.246",
54
+ "@jskit-ai/jskit-catalog": "0.2.33",
55
55
  "@playwright/test": "1.61.1",
56
56
  "@vitejs/plugin-vue": "^6.0.7",
57
57
  "eslint": "^10.8.0",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "description": "App-local runtime composition and lightweight glue.",
11
11
  "dependencies": {
12
- "@jskit-ai/kernel": "0.2.10"
12
+ "@jskit-ai/kernel": "0.2.12"
13
13
  },
14
14
  "jskit": {
15
15
  "kind": "runtime",
@@ -1,5 +1,6 @@
1
1
  import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
2
2
  import MenuLinkItem from "/src/components/menus/MenuLinkItem.vue";
3
+ import SectionTabLinkItem from "/src/components/menus/SectionTabLinkItem.vue";
3
4
  import SurfaceAwareMenuLinkItem from "/src/components/menus/SurfaceAwareMenuLinkItem.vue";
4
5
  import TabLinkItem from "/src/components/menus/TabLinkItem.vue";
5
6
 
@@ -13,6 +14,8 @@ const MainClientProvider = defineProvider({
13
14
  components.register("local.main.ui.menu-link-item", MenuLinkItem);
14
15
  components.register("local.main.ui.surface-aware-menu-link-item", SurfaceAwareMenuLinkItem);
15
16
  components.register("local.main.ui.tab-link-item", TabLinkItem);
17
+ // A page's section tabs (page.section-nav): Material 3 primary tabs, not navigation bar destinations.
18
+ components.register("local.main.ui.section-tab-link-item", SectionTabLinkItem);
16
19
  }
17
20
  });
18
21
 
@@ -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/**"],