@jskit-ai/agent-docs 0.1.131 → 0.1.133
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.
- package/guide/agent/app-extras/assistant.md +29 -605
- package/guide/agent/app-extras/mobile-capacitor.md +29 -362
- package/guide/agent/app-extras/realtime.md +29 -277
- package/guide/agent/app-setup/a-more-interesting-shell.md +44 -815
- package/guide/agent/app-setup/authentication.md +43 -1073
- package/guide/agent/app-setup/console.md +26 -298
- package/guide/agent/app-setup/database-layer.md +110 -790
- package/guide/agent/app-setup/existing-application-migration.md +273 -0
- package/guide/agent/app-setup/initial-scaffolding.md +50 -784
- package/guide/agent/app-setup/multi-homing.md +39 -712
- package/guide/agent/app-setup/quickstart.md +43 -179
- package/guide/agent/app-setup/users.md +34 -353
- package/guide/agent/index.md +17 -23
- package/package.json +2 -2
- package/patterns/INDEX.md +7 -7
- package/patterns/child-cruds.md +3 -3
- package/patterns/client-requests.md +6 -6
- package/patterns/crud-authoring.md +94 -0
- package/patterns/crud-links.md +1 -1
- package/patterns/feature-package/PATTERN.md +108 -0
- package/patterns/feature-package/example/booking-engine/package.json +48 -0
- package/patterns/feature-package/example/booking-engine/src/server/BookingEngineProvider.js +33 -0
- package/patterns/feature-package/example/booking-engine/src/server/actions.js +26 -0
- package/patterns/feature-package/example/booking-engine/src/server/inputSchemas.js +19 -0
- package/patterns/feature-package/example/variations/AvailabilityEngineProvider.js +31 -0
- package/patterns/feature-package/example/variations/InvoiceRollupProvider.js +36 -0
- package/patterns/feature-package/example/variations/customKnexRepository.js +34 -0
- package/patterns/feature-package/example/variations/orchestratorService.js +23 -0
- package/patterns/filters.md +8 -8
- package/patterns/live-actions.md +5 -18
- package/patterns/minimal-foundation/PATTERN.md +98 -0
- package/patterns/minimal-foundation/example/.nvmrc +1 -0
- package/patterns/minimal-foundation/example/AGENTS.md +17 -0
- package/patterns/minimal-foundation/example/Procfile +2 -0
- package/patterns/minimal-foundation/example/app.json +14 -0
- package/patterns/minimal-foundation/example/bin/develop.js +71 -0
- package/patterns/minimal-foundation/example/bin/server.js +8 -0
- package/patterns/minimal-foundation/example/config/public.js +40 -0
- package/patterns/minimal-foundation/example/config/server.js +1 -0
- package/patterns/minimal-foundation/example/config/surfaceAccessPolicies.js +3 -0
- package/patterns/minimal-foundation/example/eslint.config.mjs +19 -0
- package/patterns/minimal-foundation/example/favicon.svg +7 -0
- package/patterns/minimal-foundation/example/gitignore +9 -0
- package/patterns/minimal-foundation/example/index.html +13 -0
- package/patterns/minimal-foundation/example/jsconfig.json +8 -0
- package/patterns/minimal-foundation/example/package.json +57 -0
- package/patterns/minimal-foundation/example/packages/main/package.json +42 -0
- package/patterns/minimal-foundation/example/packages/main/src/shared/index.js +10 -0
- package/patterns/minimal-foundation/example/packages/main/src/shared/schemas/index.js +22 -0
- package/patterns/minimal-foundation/example/playwright.config.mjs +31 -0
- package/patterns/minimal-foundation/example/server/lib/runtimeEnv.js +45 -0
- package/patterns/minimal-foundation/example/server/lib/surfaceRuntime.js +10 -0
- package/patterns/minimal-foundation/example/server.js +195 -0
- package/patterns/minimal-foundation/example/src/App.vue +13 -0
- package/patterns/minimal-foundation/example/src/main.js +85 -0
- package/patterns/minimal-foundation/example/src/pages/home/index.vue +48 -0
- package/patterns/minimal-foundation/example/src/pages/home.vue +13 -0
- package/patterns/minimal-foundation/example/src/views/NotFound.vue +13 -0
- package/patterns/minimal-foundation/example/tests/client/smoke.vitest.js +7 -0
- package/patterns/minimal-foundation/example/tests/e2e/base-shell.spec.ts +23 -0
- package/patterns/minimal-foundation/example/tests/server/smoke.test.js +16 -0
- package/patterns/minimal-foundation/example/vite.config.mjs +81 -0
- package/patterns/page-scaffolding.md +20 -17
- package/patterns/placements.md +17 -15
- package/patterns/row-policies.md +4 -5
- package/patterns/server-search.md +3 -3
- package/patterns/shell-foundation/PATTERN.md +104 -0
- package/patterns/shell-foundation/example/.nvmrc +1 -0
- package/patterns/shell-foundation/example/AGENTS.md +17 -0
- package/patterns/shell-foundation/example/Procfile +2 -0
- package/patterns/shell-foundation/example/app.json +14 -0
- package/patterns/shell-foundation/example/bin/develop.js +71 -0
- package/patterns/shell-foundation/example/bin/server.js +8 -0
- package/patterns/shell-foundation/example/config/public.js +40 -0
- package/patterns/shell-foundation/example/config/server.js +1 -0
- package/patterns/shell-foundation/example/config/surfaceAccessPolicies.js +3 -0
- package/patterns/shell-foundation/example/eslint.config.mjs +19 -0
- package/patterns/shell-foundation/example/favicon.svg +7 -0
- package/patterns/shell-foundation/example/gitignore +9 -0
- package/patterns/shell-foundation/example/index.html +13 -0
- package/patterns/shell-foundation/example/jsconfig.json +8 -0
- package/patterns/shell-foundation/example/package.json +59 -0
- package/patterns/shell-foundation/example/packages/main/package.json +56 -0
- package/patterns/shell-foundation/example/packages/main/src/client/index.js +9 -0
- package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +18 -0
- package/patterns/shell-foundation/example/packages/main/src/shared/index.js +10 -0
- package/patterns/shell-foundation/example/packages/main/src/shared/schemas/index.js +22 -0
- package/patterns/shell-foundation/example/playwright.config.mjs +31 -0
- package/patterns/shell-foundation/example/server/lib/runtimeEnv.js +45 -0
- package/patterns/shell-foundation/example/server/lib/surfaceRuntime.js +10 -0
- package/patterns/shell-foundation/example/server.js +195 -0
- package/patterns/shell-foundation/example/src/App.vue +11 -0
- package/patterns/shell-foundation/example/src/components/ShellLayout.vue +12 -0
- package/patterns/shell-foundation/example/src/components/menus/MenuLinkItem.vue +30 -0
- package/patterns/shell-foundation/example/src/components/menus/SurfaceAwareMenuLinkItem.vue +42 -0
- package/patterns/shell-foundation/example/src/components/menus/TabLinkItem.vue +42 -0
- package/patterns/shell-foundation/example/src/error.js +19 -0
- package/patterns/shell-foundation/example/src/main.js +85 -0
- package/patterns/shell-foundation/example/src/pages/home/index.vue +116 -0
- package/patterns/shell-foundation/example/src/pages/home/settings/general/index.vue +40 -0
- package/patterns/shell-foundation/example/src/pages/home/settings/index.vue +7 -0
- package/patterns/shell-foundation/example/src/pages/home/settings.vue +109 -0
- package/patterns/shell-foundation/example/src/pages/home.vue +20 -0
- package/patterns/shell-foundation/example/src/placement.js +56 -0
- package/patterns/shell-foundation/example/src/placementTopology.js +149 -0
- package/patterns/shell-foundation/example/src/views/NotFound.vue +13 -0
- package/patterns/shell-foundation/example/tests/client/smoke.vitest.js +7 -0
- package/patterns/shell-foundation/example/tests/e2e/adaptive-shell.spec.ts +10 -0
- package/patterns/shell-foundation/example/tests/e2e/base-shell.spec.ts +23 -0
- package/patterns/shell-foundation/example/tests/server/smoke.test.js +16 -0
- package/patterns/shell-foundation/example/vite.config.mjs +81 -0
- package/patterns/ui-contract.md +56 -0
- package/patterns/ui-testing.md +10 -12
- package/reference/autogen/KERNEL_MAP.md +29 -107
- package/reference/autogen/PATTERN_INDEX.md +230 -0
- package/reference/autogen/README.md +4 -8
- package/reference/autogen/packages/agent-docs.md +259 -0
- package/reference/autogen/packages/assistant-core.md +3 -3
- package/reference/autogen/packages/assistant-runtime.md +32 -17
- package/reference/autogen/packages/auth-core.md +31 -33
- package/reference/autogen/packages/auth-provider-local-core.md +4 -12
- package/reference/autogen/packages/auth-provider-local-db-core.md +4 -4
- package/reference/autogen/packages/auth-provider-supabase-core.md +14 -18
- package/reference/autogen/packages/auth-web.md +42 -22
- package/reference/autogen/packages/console-core.md +8 -25
- package/reference/autogen/packages/console-web.md +5 -5
- package/reference/autogen/packages/crud-core.md +61 -17
- package/reference/autogen/packages/database-runtime-mysql.md +12 -2
- package/reference/autogen/packages/database-runtime-postgres.md +12 -2
- package/reference/autogen/packages/database-runtime.md +26 -25
- package/reference/autogen/packages/google-rewarded-core.md +19 -104
- package/reference/autogen/packages/http-runtime.md +4 -8
- package/reference/autogen/packages/http-web.md +32 -0
- package/reference/autogen/packages/json-rest-api-core.md +4 -6
- package/reference/autogen/packages/kernel.md +109 -390
- package/reference/autogen/packages/mobile-capacitor.md +2 -13
- package/reference/autogen/packages/realtime.md +29 -26
- package/reference/autogen/packages/resource-crud-core.md +6 -0
- package/reference/autogen/packages/shell-web.md +69 -54
- package/reference/autogen/packages/storage-runtime.md +3 -3
- package/reference/autogen/packages/uploads-image-web.md +0 -1
- package/reference/autogen/packages/uploads-runtime.md +3 -3
- package/reference/autogen/packages/users-core.md +45 -90
- package/reference/autogen/packages/users-web.md +5 -7
- package/reference/autogen/packages/workspaces-core.md +53 -74
- package/reference/autogen/packages/workspaces-web.md +15 -16
- package/reference/autogen/tooling/jskit-catalog.md +34 -0
- package/reference/autogen/tooling/testUtils.md +4 -4
- package/skills/jskit/SKILL.md +38 -31
- package/skills/jskit/references/app-operations.md +68 -53
- package/skills/jskit/references/crud-operations.md +58 -106
- package/skills/jskit/references/material-3.md +1 -1
- package/skills/jskit/references/ui-operations.md +41 -44
- package/templates/app/AGENTS.md +7 -3
- package/guide/agent/app-setup/upgrade-beta-1-to-final.md +0 -252
- package/guide/agent/app-setup/working-with-the-jskit-cli.md +0 -325
- package/guide/agent/generators/advanced-cruds.md +0 -1935
- package/guide/agent/generators/crud-generators.md +0 -948
- package/guide/agent/generators/intro.md +0 -65
- package/guide/agent/generators/row-policies.md +0 -537
- package/guide/agent/generators/ui-generators.md +0 -690
- package/patterns/crud-scaffolding.md +0 -198
- package/patterns/generated-ui-contract-tracking.md +0 -66
- package/reference/autogen/packages/assistant.md +0 -68
- package/reference/autogen/packages/crud-server-generator.md +0 -215
- package/reference/autogen/packages/crud-ui-generator.md +0 -192
- package/reference/autogen/packages/feature-server-generator.md +0 -65
- package/reference/autogen/packages/ui-generator.md +0 -127
- package/reference/autogen/tooling/create-app.md +0 -317
- package/reference/autogen/tooling/jskit-cli.md +0 -933
- package/reference/autogen/tooling/test-support.md +0 -27
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import Fastify from "fastify";
|
|
2
|
+
import fastifyStatic from "@fastify/static";
|
|
3
|
+
import { resolveRuntimeEnv } from "./server/lib/runtimeEnv.js";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
createInstalledRuntime,
|
|
8
|
+
registerSurfaceRequestConstraint,
|
|
9
|
+
resolveRuntimeProfileFromSurface
|
|
10
|
+
} from "@jskit-ai/kernel/server/platform";
|
|
11
|
+
import { loadAppConfigFromAppRoot } from "@jskit-ai/kernel/server/support";
|
|
12
|
+
import { matchesPathPrefix, normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
|
|
13
|
+
import { surfaceRuntime } from "./server/lib/surfaceRuntime.js";
|
|
14
|
+
|
|
15
|
+
const SPA_INDEX_FILE = "index.html";
|
|
16
|
+
const API_BASE_PATH = "/api";
|
|
17
|
+
const STATIC_GLOBAL_UI_PATHS = Object.freeze([
|
|
18
|
+
"/assets",
|
|
19
|
+
"/favicon.svg",
|
|
20
|
+
"/favicon.ico",
|
|
21
|
+
"/robots.txt",
|
|
22
|
+
"/manifest.webmanifest"
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function toRequestPathname(urlValue) {
|
|
26
|
+
const rawUrl = String(urlValue || "").trim() || "/";
|
|
27
|
+
try {
|
|
28
|
+
return normalizePathname(new URL(rawUrl, "http://localhost").pathname || "/");
|
|
29
|
+
} catch {
|
|
30
|
+
return normalizePathname(rawUrl.split("?")[0] || "/");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isApiPath(pathname) {
|
|
35
|
+
return matchesPathPrefix(pathname, API_BASE_PATH);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function hasFileExtension(pathname) {
|
|
39
|
+
return path.extname(normalizePathname(pathname)) !== "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveGlobalUiPaths(runtimeGlobalUiPaths = []) {
|
|
43
|
+
const paths = new Set(Array.isArray(runtimeGlobalUiPaths) ? runtimeGlobalUiPaths : []);
|
|
44
|
+
for (const staticPath of STATIC_GLOBAL_UI_PATHS) {
|
|
45
|
+
paths.add(staticPath);
|
|
46
|
+
}
|
|
47
|
+
return [...paths];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveStaticFilePath(pathname) {
|
|
51
|
+
const normalizedPathname = normalizePathname(pathname);
|
|
52
|
+
|
|
53
|
+
const relativePath = normalizedPathname.replace(/^\/+/, "");
|
|
54
|
+
if (!relativePath || relativePath.endsWith("/")) {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const normalizedRelativePath = path.posix.normalize(relativePath);
|
|
59
|
+
if (
|
|
60
|
+
!normalizedRelativePath ||
|
|
61
|
+
normalizedRelativePath === "." ||
|
|
62
|
+
normalizedRelativePath === ".." ||
|
|
63
|
+
normalizedRelativePath.startsWith("../") ||
|
|
64
|
+
normalizedRelativePath.includes("/../")
|
|
65
|
+
) {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return normalizedRelativePath;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function canServeStaticFile(distRoot, relativePath) {
|
|
73
|
+
if (!distRoot || !relativePath) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const normalizedDistRoot = path.resolve(distRoot);
|
|
78
|
+
const resolvedPath = path.resolve(normalizedDistRoot, relativePath);
|
|
79
|
+
if (!(resolvedPath === normalizedDistRoot || resolvedPath.startsWith(`${normalizedDistRoot}${path.sep}`))) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return existsSync(resolvedPath);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function createServer({ runtimeEnv = resolveRuntimeEnv() } = {}) {
|
|
87
|
+
const app = Fastify({
|
|
88
|
+
logger: true,
|
|
89
|
+
ajv: {
|
|
90
|
+
customOptions: {
|
|
91
|
+
allowUnionTypes: true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
app.get("/api/health", async () => {
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
app: "reading-room"
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
const appRoot = path.resolve(process.cwd());
|
|
103
|
+
const distRoot = path.resolve(appRoot, "dist");
|
|
104
|
+
const hasWebBuild = existsSync(path.resolve(distRoot, SPA_INDEX_FILE));
|
|
105
|
+
const spaDocument = hasWebBuild ? readFileSync(path.resolve(distRoot, SPA_INDEX_FILE), "utf8") : "";
|
|
106
|
+
const runtime = await createInstalledRuntime({
|
|
107
|
+
appRoot,
|
|
108
|
+
profile: resolveRuntimeProfileFromSurface({
|
|
109
|
+
surfaceRuntime,
|
|
110
|
+
serverSurface: runtimeEnv.SERVER_SURFACE
|
|
111
|
+
}),
|
|
112
|
+
config: await loadAppConfigFromAppRoot({ appRoot }),
|
|
113
|
+
env: runtimeEnv,
|
|
114
|
+
logger: app.log,
|
|
115
|
+
fastify: app
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
registerSurfaceRequestConstraint({
|
|
119
|
+
fastify: app,
|
|
120
|
+
surfaceRuntime,
|
|
121
|
+
serverSurface: runtimeEnv.SERVER_SURFACE,
|
|
122
|
+
globalUiPaths: resolveGlobalUiPaths(runtime?.globalUiPaths || [])
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (hasWebBuild) {
|
|
126
|
+
await app.register(fastifyStatic, {
|
|
127
|
+
root: distRoot,
|
|
128
|
+
index: false,
|
|
129
|
+
serve: false
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
app.log.warn("Frontend build not found (dist/index.html). Page routes will return 404 until `npm run build`.");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
app.setNotFoundHandler(async (request, reply) => {
|
|
136
|
+
const pathname = toRequestPathname(request?.url);
|
|
137
|
+
const method = String(request?.method || "GET")
|
|
138
|
+
.trim()
|
|
139
|
+
.toUpperCase();
|
|
140
|
+
if (isApiPath(pathname) || (method !== "GET" && method !== "HEAD")) {
|
|
141
|
+
return reply.code(404).send({
|
|
142
|
+
message: `Route ${method}:${pathname} not found`,
|
|
143
|
+
error: "Not Found",
|
|
144
|
+
statusCode: 404
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (hasFileExtension(pathname)) {
|
|
148
|
+
const staticFilePath = resolveStaticFilePath(pathname);
|
|
149
|
+
if (hasWebBuild && staticFilePath && canServeStaticFile(distRoot, staticFilePath)) {
|
|
150
|
+
return reply.sendFile(staticFilePath);
|
|
151
|
+
}
|
|
152
|
+
return reply.code(404).send({
|
|
153
|
+
message: `Route ${method}:${pathname} not found`,
|
|
154
|
+
error: "Not Found",
|
|
155
|
+
statusCode: 404
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (!hasWebBuild) {
|
|
159
|
+
return reply.code(404).send({
|
|
160
|
+
error: "Frontend build is not available. Run `npm run build`."
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return reply.type("text/html; charset=utf-8").send(spaDocument);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
app.log.info(
|
|
167
|
+
{
|
|
168
|
+
surface: surfaceRuntime.normalizeSurfaceMode(runtimeEnv.SERVER_SURFACE),
|
|
169
|
+
providerPackages: runtime.providerPackageOrder,
|
|
170
|
+
packageOrder: runtime.packageOrder,
|
|
171
|
+
capabilities: runtime.diagnostics.capabilityIds
|
|
172
|
+
},
|
|
173
|
+
"Started JSKIT installed runtime."
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return app;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function startServer(options = {}) {
|
|
180
|
+
const runtimeEnv = resolveRuntimeEnv();
|
|
181
|
+
const configuredPort = options?.port === undefined ? runtimeEnv.PORT : Number(options.port);
|
|
182
|
+
const port = Number.isInteger(configuredPort) && configuredPort >= 0 ? configuredPort : runtimeEnv.PORT;
|
|
183
|
+
const host = String(options?.host || "").trim() || runtimeEnv.HOST;
|
|
184
|
+
const app = await createServer({
|
|
185
|
+
runtimeEnv: {
|
|
186
|
+
...runtimeEnv,
|
|
187
|
+
HOST: host,
|
|
188
|
+
PORT: port
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
await app.listen({ port, host });
|
|
192
|
+
return app;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export { createServer, startServer };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createApp } from "vue";
|
|
2
|
+
import { createPinia } from "pinia";
|
|
3
|
+
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
|
|
4
|
+
import { createRouter, createWebHistory } from "vue-router/auto";
|
|
5
|
+
import { routes } from "vue-router/auto-routes";
|
|
6
|
+
import "vuetify/styles";
|
|
7
|
+
import { createVuetify } from "vuetify";
|
|
8
|
+
import { aliases as mdiAliases, mdi } from "vuetify/iconsets/mdi-svg";
|
|
9
|
+
import App from "./App.vue";
|
|
10
|
+
import NotFoundView from "./views/NotFound.vue";
|
|
11
|
+
import { bootInstalledClientModules } from "virtual:jskit-client-bootstrap";
|
|
12
|
+
import { createSurfaceRuntime } from "@jskit-ai/kernel/shared/surface/runtime";
|
|
13
|
+
import {
|
|
14
|
+
shouldRetryTransientQueryFailure,
|
|
15
|
+
transientQueryRetryDelay
|
|
16
|
+
} from "@jskit-ai/kernel/shared/support";
|
|
17
|
+
import {
|
|
18
|
+
bootstrapClientShellApp,
|
|
19
|
+
createShellRouter
|
|
20
|
+
} from "@jskit-ai/kernel/client";
|
|
21
|
+
import { config } from "../config/public.js";
|
|
22
|
+
|
|
23
|
+
const surfaceRuntime = createSurfaceRuntime({
|
|
24
|
+
allMode: config.surfaceModeAll,
|
|
25
|
+
surfaces: config.surfaceDefinitions,
|
|
26
|
+
defaultSurfaceId: config.surfaceDefaultId
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const surfaceMode = surfaceRuntime.normalizeSurfaceMode(import.meta.env.VITE_SURFACE);
|
|
30
|
+
const { router, fallbackRoute } = createShellRouter({
|
|
31
|
+
createRouter,
|
|
32
|
+
history: createWebHistory(),
|
|
33
|
+
routes,
|
|
34
|
+
surfaceRuntime,
|
|
35
|
+
surfaceMode,
|
|
36
|
+
notFoundComponent: NotFoundView,
|
|
37
|
+
guard: {
|
|
38
|
+
surfaceDefinitions: config.surfaceDefinitions,
|
|
39
|
+
defaultSurfaceId: config.surfaceDefaultId,
|
|
40
|
+
webRootAllowed: config.webRootAllowed
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const vuetify = createVuetify({
|
|
45
|
+
theme: {
|
|
46
|
+
defaultTheme: "light"
|
|
47
|
+
},
|
|
48
|
+
icons: {
|
|
49
|
+
defaultSet: "mdi",
|
|
50
|
+
aliases: mdiAliases,
|
|
51
|
+
sets: { mdi }
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const pinia = createPinia();
|
|
55
|
+
const queryClient = new QueryClient({
|
|
56
|
+
defaultOptions: {
|
|
57
|
+
queries: {
|
|
58
|
+
refetchOnWindowFocus: false,
|
|
59
|
+
refetchOnReconnect: true,
|
|
60
|
+
retry: shouldRetryTransientQueryFailure,
|
|
61
|
+
retryDelay: transientQueryRetryDelay
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
void bootstrapClientShellApp({
|
|
67
|
+
createApp,
|
|
68
|
+
rootComponent: App,
|
|
69
|
+
appConfig: config,
|
|
70
|
+
appPlugins: [
|
|
71
|
+
pinia,
|
|
72
|
+
[VueQueryPlugin, { queryClient }],
|
|
73
|
+
vuetify
|
|
74
|
+
],
|
|
75
|
+
pinia,
|
|
76
|
+
queryClient,
|
|
77
|
+
router,
|
|
78
|
+
bootClientModules: bootInstalledClientModules,
|
|
79
|
+
surfaceRuntime,
|
|
80
|
+
surfaceMode,
|
|
81
|
+
env: import.meta.env,
|
|
82
|
+
fallbackRoute
|
|
83
|
+
}).catch((error) => {
|
|
84
|
+
console.error("Failed to bootstrap client app.", error);
|
|
85
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="home-start-screen d-flex flex-column ga-4">
|
|
3
|
+
<header>
|
|
4
|
+
<p class="text-overline text-medium-emphasis mb-1">Home</p>
|
|
5
|
+
<h1 class="home-start-screen__title">Home base</h1>
|
|
6
|
+
<p class="text-body-2 text-medium-emphasis mb-0">
|
|
7
|
+
The app runtime is online and ready for the first real workflow.
|
|
8
|
+
</p>
|
|
9
|
+
</header>
|
|
10
|
+
|
|
11
|
+
<v-sheet rounded="lg" border class="home-start-screen__panel">
|
|
12
|
+
<h2 class="text-h6 mb-2">No activity yet</h2>
|
|
13
|
+
<p class="text-body-2 text-medium-emphasis mb-0">
|
|
14
|
+
Recent work, saved records, and next actions will appear here once this app has data.
|
|
15
|
+
</p>
|
|
16
|
+
</v-sheet>
|
|
17
|
+
</section>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<style scoped>
|
|
21
|
+
.home-start-screen {
|
|
22
|
+
--home-start-title-size: 2rem;
|
|
23
|
+
--home-start-panel-padding: 1rem;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.home-start-screen {
|
|
27
|
+
margin-inline: auto;
|
|
28
|
+
max-width: 48rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.home-start-screen__title {
|
|
32
|
+
font-size: var(--home-start-title-size);
|
|
33
|
+
font-weight: 700;
|
|
34
|
+
letter-spacing: 0;
|
|
35
|
+
line-height: 1.08;
|
|
36
|
+
margin: 0 0 0.45rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.home-start-screen__panel {
|
|
40
|
+
padding: var(--home-start-panel-padding);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@media (max-width: 640px) {
|
|
44
|
+
.home-start-screen {
|
|
45
|
+
--home-start-title-size: 1.5rem;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const title = "Not Found";
|
|
3
|
+
const message = "The page you requested does not exist.";
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<v-container class="fill-height d-flex align-center justify-center">
|
|
8
|
+
<v-card class="pa-6 text-center" max-width="560" rounded="lg">
|
|
9
|
+
<v-card-title class="text-h4">{{ title }}</v-card-title>
|
|
10
|
+
<v-card-text class="text-medium-emphasis">{{ message }}</v-card-text>
|
|
11
|
+
</v-card>
|
|
12
|
+
</v-container>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
const VIEWPORTS = Object.freeze([
|
|
4
|
+
Object.freeze({ name: "compact", width: 390, height: 844 }),
|
|
5
|
+
Object.freeze({ name: "medium", width: 768, height: 1024 }),
|
|
6
|
+
Object.freeze({ name: "expanded", width: 1280, height: 900 })
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
test.describe("application responsive smoke", () => {
|
|
10
|
+
for (const viewport of VIEWPORTS) {
|
|
11
|
+
test(`${viewport.name} home route renders without horizontal overflow`, async ({ page }) => {
|
|
12
|
+
await page.setViewportSize(viewport);
|
|
13
|
+
await page.goto("/home");
|
|
14
|
+
await expect(page.getByRole("heading", { name: "Home base" })).toBeVisible();
|
|
15
|
+
|
|
16
|
+
const dimensions = await page.evaluate(() => ({
|
|
17
|
+
clientWidth: document.documentElement.clientWidth,
|
|
18
|
+
scrollWidth: document.documentElement.scrollWidth
|
|
19
|
+
}));
|
|
20
|
+
expect(dimensions.scrollWidth).toBeLessThanOrEqual(dimensions.clientWidth + 1);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createServer } from "../../server.js";
|
|
4
|
+
|
|
5
|
+
test("GET /api/health returns built-in health response", async () => {
|
|
6
|
+
const app = await createServer();
|
|
7
|
+
const response = await app.inject({
|
|
8
|
+
method: "GET",
|
|
9
|
+
url: "/api/health"
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
assert.equal(response.statusCode, 200);
|
|
13
|
+
assert.equal(response.json().ok, true);
|
|
14
|
+
|
|
15
|
+
await app.close();
|
|
16
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { fileURLToPath, URL } from "node:url";
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
import vue from "@vitejs/plugin-vue";
|
|
4
|
+
import vuetify from "vite-plugin-vuetify";
|
|
5
|
+
import VueRouter from "vue-router/vite";
|
|
6
|
+
import { createJskitClientBootstrapPlugin } from "@jskit-ai/kernel/client/vite";
|
|
7
|
+
|
|
8
|
+
const configuredDevPort = Number.parseInt(String(process.env.VITE_DEV_PORT || "").trim(), 10);
|
|
9
|
+
const devPort = Number.isInteger(configuredDevPort) && configuredDevPort > 0 ? configuredDevPort : 5173;
|
|
10
|
+
const apiProxyTarget = String(process.env.VITE_API_PROXY_TARGET || "").trim() || "http://localhost:3000";
|
|
11
|
+
const clientEntry = (() => {
|
|
12
|
+
const normalized = String(process.env.VITE_CLIENT_ENTRY || "").trim();
|
|
13
|
+
if (!normalized) {
|
|
14
|
+
return "/src/main.js";
|
|
15
|
+
}
|
|
16
|
+
if (normalized.startsWith("/")) {
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
if (normalized.startsWith("src/")) {
|
|
20
|
+
return `/${normalized}`;
|
|
21
|
+
}
|
|
22
|
+
return `/src/${normalized}`;
|
|
23
|
+
})();
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
resolve: {
|
|
27
|
+
preserveSymlinks: true,
|
|
28
|
+
alias: {
|
|
29
|
+
"@": fileURLToPath(new URL("./src", import.meta.url))
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
plugins: [
|
|
33
|
+
createJskitClientBootstrapPlugin({
|
|
34
|
+
proxyTarget: apiProxyTarget
|
|
35
|
+
}),
|
|
36
|
+
VueRouter({
|
|
37
|
+
routesFolder: "src/pages",
|
|
38
|
+
// Generated on the first Vite dev/build scan and intentionally gitignored.
|
|
39
|
+
dts: "src/typed-router.d.ts",
|
|
40
|
+
nestedChildren: false
|
|
41
|
+
}),
|
|
42
|
+
vue(),
|
|
43
|
+
vuetify({
|
|
44
|
+
autoImport: true
|
|
45
|
+
}),
|
|
46
|
+
{
|
|
47
|
+
name: "jskit-client-entry",
|
|
48
|
+
transformIndexHtml(source) {
|
|
49
|
+
return String(source || "")
|
|
50
|
+
.replace(/\/src\/%VITE_CLIENT_ENTRY%/g, clientEntry)
|
|
51
|
+
.replace(/\/src\/main\.js/g, clientEntry);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
test: {
|
|
56
|
+
include: ["tests/client/**/*.vitest.js"]
|
|
57
|
+
},
|
|
58
|
+
optimizeDeps: {
|
|
59
|
+
entries: [
|
|
60
|
+
"index.html",
|
|
61
|
+
"src/**/*.{js,ts,vue}"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
server: {
|
|
65
|
+
port: devPort,
|
|
66
|
+
warmup: {
|
|
67
|
+
clientFiles: [
|
|
68
|
+
"src/main.{js,ts}",
|
|
69
|
+
"src/router/**/*.{js,ts}",
|
|
70
|
+
"src/pages/**/*.{js,ts,vue}",
|
|
71
|
+
"src/components/**/*.{js,ts,vue}"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
proxy: {
|
|
75
|
+
"/api": {
|
|
76
|
+
target: apiProxyTarget,
|
|
77
|
+
changeOrigin: true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
@@ -5,44 +5,47 @@ Use when:
|
|
|
5
5
|
- adding a non-CRUD route page
|
|
6
6
|
- adding a placeholder page or screen stub
|
|
7
7
|
- adding a menu-linked page
|
|
8
|
-
- deciding
|
|
8
|
+
- deciding how a page and its link should fit the current application
|
|
9
9
|
|
|
10
10
|
Check first:
|
|
11
11
|
|
|
12
12
|
- the app blueprint route family and chosen surface
|
|
13
|
-
-
|
|
14
|
-
-
|
|
13
|
+
- the page-and-placement source pattern
|
|
14
|
+
- the current placement declarations and topology
|
|
15
15
|
- the nearest existing routed host under `src/pages`
|
|
16
16
|
|
|
17
17
|
Rules:
|
|
18
18
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
- Start from the package-owned page-and-placement pattern for a new app-owned
|
|
20
|
+
non-CRUD route, then adapt the page and placement as ordinary source.
|
|
21
|
+
- Create both the page file and matching `src/placement.js` entry when the page
|
|
22
|
+
belongs in navigation.
|
|
23
|
+
- Choose the page's product navigation role deliberately: primary for main
|
|
24
|
+
destinations, secondary for lower-priority shell links, and detail,
|
|
25
|
+
workflow, or no placement when the page should not appear in navigation.
|
|
26
|
+
- Inspect the installed packages' public placement declarations before using
|
|
27
|
+
a semantic slot. Target concrete outlets only for deliberate infrastructure.
|
|
28
|
+
- If the page sits under an existing routed host, reuse its `page.section-nav`
|
|
29
|
+
ownership rather than creating a parallel navigation structure.
|
|
26
30
|
- For a small placeholder route inside an existing route family, keep the change scoped unless the durable route or surface plan in the app blueprint changed.
|
|
27
|
-
-
|
|
28
|
-
- Prefer a page header plus a direct `v-sheet` working region. Do not wrap every
|
|
31
|
+
- Live pages must be usable screens, not instructional scaffolds. Do not ship copy such as "replace this", "use this area", or "this page is ready".
|
|
32
|
+
- Prefer a page header plus a direct `v-sheet` working region. Do not wrap every page in a generic `v-card`.
|
|
29
33
|
- If the screen is not implemented yet, use a product-shaped empty state with one clear next action or status, not developer instructions.
|
|
30
|
-
- Primary navigation links belong in semantic placements such as `shell.primary-nav` or `page.section-nav`; do not place every
|
|
34
|
+
- Primary navigation links belong in semantic placements such as `shell.primary-nav` or `page.section-nav`; do not place every route into one drawer by default.
|
|
31
35
|
- Compact layouts must be checked first: no horizontal overflow, no unreachable primary action, and tap targets should be at least 48 px.
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
UI contract:
|
|
34
38
|
|
|
35
39
|
- App-facing screens are phone-first and task-first; admin/console screens may be denser but still need responsive controls.
|
|
36
40
|
- Navigation uses semantic placements by default. Raw `host:position` outlets are advanced infrastructure.
|
|
37
41
|
- Page architecture is header plus direct work region, normally `v-sheet`; do not use generic page-level `v-card` shells.
|
|
38
42
|
- Empty/loading/error states are product-shaped and resource-named.
|
|
39
43
|
- Detail and workflow routes are not primary navigation by default.
|
|
40
|
-
-
|
|
44
|
+
- Changed UI must have compact, medium, and expanded browser checks when it changes user-facing behavior.
|
|
41
45
|
|
|
42
46
|
Avoid:
|
|
43
47
|
|
|
44
|
-
-
|
|
48
|
+
- inventing a page and placement shape without checking the existing pattern and neighboring routes
|
|
45
49
|
- treating a small page stub as permission to rewrite marketing copy, route architecture, or app blueprint scope
|
|
46
|
-
- claiming that no generator exists without checking the actual `jskit` generator inventory first
|
|
47
50
|
- adding cards inside cards or repeating the page title inside a card title
|
|
48
51
|
- treating Vuetify component defaults as the product architecture
|
package/patterns/placements.md
CHANGED
|
@@ -45,8 +45,8 @@ Placement topology:
|
|
|
45
45
|
- Every variant needs an `outlet: "host:position"`.
|
|
46
46
|
- Variant `renderers` maps semantic `kind` values to component tokens, for example `renderers: { link: "local.main.ui.surface-aware-menu-link-item" }`.
|
|
47
47
|
- Renderer choice for semantic `kind: "link"` placements belongs in topology, not in each placement entry.
|
|
48
|
-
- `default: true` marks the fallback semantic placement that
|
|
49
|
-
- Package topology is discovered too, but app topology with the same `id` and `owner` overrides package topology
|
|
48
|
+
- `default: true` marks the fallback semantic placement that app-owned pages use when no nearer host applies.
|
|
49
|
+
- Package topology is discovered too, but app topology with the same `id` and `owner` overrides package topology.
|
|
50
50
|
|
|
51
51
|
Runtime behavior:
|
|
52
52
|
|
|
@@ -62,21 +62,23 @@ Runtime behavior:
|
|
|
62
62
|
- On medium and expanded layouts, closing the standard drawer produces an 80px Material navigation rail by default. Expanded and collapsed icons share one centreline. Use `railWidth` to configure an intentionally denser rail, or `desktopDrawerClosedMode="hidden"` only when another discoverable navigation surface exists.
|
|
63
63
|
- The shell link renderers own rail centring, selected-state treatment, and opaque theme-aware tooltips. Applications should keep supplying the same semantic placement records rather than creating rail-only links or tooltip CSS.
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
65
|
+
Authoring and inspection:
|
|
66
|
+
|
|
67
|
+
- Read package placement contributions and the app's `src/placement.js` and
|
|
68
|
+
`src/placementTopology.js` directly; they are the complete current state.
|
|
69
|
+
- Source patterns for pages, CRUD UI, and assistant UI target semantic
|
|
70
|
+
placements rather than concrete outlets.
|
|
71
|
+
- A page under a parent host with a mapped `ShellOutlet` should use the
|
|
72
|
+
topology's semantic placement and owner.
|
|
73
|
+
- Upgrading a page into a routed child-page host includes a
|
|
74
|
+
`page.section-nav` topology entry for its concrete outlet in the same change.
|
|
75
|
+
- Adding a concrete `ShellOutlet` includes its semantic topology mapping in the
|
|
76
|
+
same change.
|
|
77
|
+
- Detail, workflow, and utility routes should not accidentally become primary
|
|
78
|
+
navigation entries.
|
|
77
79
|
- `page.supporting-content` is the default semantic place for supporting detail/content overlays. The default shell maps it to a closed compact bottom sheet and a closed medium/expanded side panel.
|
|
78
80
|
- When adding a public concrete outlet by hand, add its semantic topology mapping in the same change.
|
|
79
|
-
-
|
|
81
|
+
- Keep every public concrete outlet mapped by semantic topology; otherwise keep it private/internal.
|
|
80
82
|
|
|
81
83
|
Rules:
|
|
82
84
|
|
package/patterns/row-policies.md
CHANGED
|
@@ -9,10 +9,9 @@ Use when:
|
|
|
9
9
|
|
|
10
10
|
Read first:
|
|
11
11
|
|
|
12
|
-
- `packages/agent-docs/site/guide/generators/row-policies.md`
|
|
13
12
|
- the owning CRUD provider and shared resource
|
|
14
|
-
- the
|
|
15
|
-
- `packages/crud-
|
|
13
|
+
- the package-owned CRUD server pattern
|
|
14
|
+
- `packages/crud-core/test/defineCrudJsonApiFeature.test.js`
|
|
16
15
|
|
|
17
16
|
Default JSKIT pattern:
|
|
18
17
|
|
|
@@ -60,7 +59,7 @@ Avoid:
|
|
|
60
59
|
|
|
61
60
|
- filtering `document.data` in `service.js`
|
|
62
61
|
- accepting visible ids or hierarchy roots from client query parameters
|
|
63
|
-
- installing `RowPolicyPlugin` in
|
|
64
|
-
- adding a default policy file to every
|
|
62
|
+
- installing `RowPolicyPlugin` in applications
|
|
63
|
+
- adding a default policy file to every CRUD
|
|
65
64
|
- returning a Knex builder or promise from a synchronous grouped contribution
|
|
66
65
|
- swallowing a policy error and retrying without mandatory visibility
|