@k2b/cloud 0.22.0 → 0.24.0
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/package.json +4 -3
- package/scripts/README.md +1 -1
- package/scripts/build-canvas-workers.ts +58 -0
- package/scripts/build.ts +4 -5
- package/src/_internal/canvas-worker.ts +24 -0
- package/src/_internal/capabilities.ts +230 -75
- package/src/_internal/define-app.ts +90 -7
- package/src/_internal/fixtures/filesv2-manifest-cloud-v0.29.0.json +887 -0
- package/src/_internal/page-responses.ts +20 -4
- package/src/_internal/registry-validation.ts +6 -0
- package/src/_internal/registry.ts +78 -3
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/static-assets.ts +18 -3
- package/src/access/ResourceApiKeys.tsx +30 -30
- package/src/ai/capability-execution.ts +5 -0
- package/src/ai/chat/blocks.tsx +2 -1
- package/src/ai/chat/messages.ts +6 -0
- package/src/ai/client/controller.ts +80 -21
- package/src/ai/client/transport.ts +47 -3
- package/src/ai/code-mode-skill.ts +2 -2
- package/src/ai/index.ts +0 -7
- package/src/ai/live-events.ts +1 -262
- package/src/ai/live.ts +31 -2
- package/src/ai/migrate.ts +35 -16
- package/src/ai/pdf-render.ts +2 -22
- package/src/ai/routes.ts +5 -4
- package/src/ai/runtime.ts +1 -6
- package/src/ai/solid.ts +0 -5
- package/src/ai/stream.ts +89 -65
- package/src/api/app-approval.ts +5 -1
- package/src/api/help.ts +2 -2
- package/src/api/index.ts +3 -0
- package/src/api/me.ts +19 -5
- package/src/api/pwa-phone.ts +168 -0
- package/src/api/pwa.ts +179 -0
- package/src/api/search/schemas.ts +34 -0
- package/src/api/search.ts +118 -50
- package/src/browser/CloudResourceSearch.browser-harness.tsx +11 -0
- package/src/browser/CloudResourceSearch.tsx +208 -80
- package/src/browser/app-session.ts +69 -0
- package/src/browser/live-connection.ts +211 -0
- package/src/browser/live-websocket.ts +31 -11
- package/src/browser/live.ts +2 -0
- package/src/browser/resource-search-messages.ts +15 -2
- package/src/browser/search-stream.ts +151 -0
- package/src/contracts/app.ts +10 -0
- package/src/contracts/capabilities.ts +89 -24
- package/src/contracts/capability-compatibility.ts +28 -14
- package/src/contracts/file-provider.ts +158 -0
- package/src/contracts/index.ts +2 -0
- package/src/contracts/pwa-paths.ts +6 -0
- package/src/contracts/pwa.ts +156 -0
- package/src/contracts/registry.ts +7 -0
- package/src/contracts/shared.ts +7 -1
- package/src/events/index.ts +2 -0
- package/src/events/live-engine.ts +830 -0
- package/src/events/live-protocol.ts +31 -0
- package/src/events/live.ts +298 -0
- package/src/server/actor.ts +7 -0
- package/src/server/api-client.ts +23 -2
- package/src/server/help.ts +3 -3
- package/src/server/index.ts +3 -1
- package/src/server/middleware/auth.ts +80 -8
- package/src/server/middleware/openapi.ts +2 -1
- package/src/server/services/access.ts +49 -0
- package/src/server/services/index.ts +1 -0
- package/src/services/app-approval.ts +6 -25
- package/src/services/audit/index.ts +2 -0
- package/src/services/branding/app-icon-source.ts +40 -0
- package/src/services/branding/app-icons.ts +112 -0
- package/src/services/branding/icon-render-worker.ts +69 -0
- package/src/services/branding/icon-svg.ts +22 -0
- package/src/services/identity/invocation-actor.ts +8 -5
- package/src/services/identity/invocation-authority.ts +1 -0
- package/src/services/identity/invocation-token.ts +6 -0
- package/src/services/index.ts +1 -0
- package/src/services/outbox.ts +114 -80
- package/src/services/pairing-secret.ts +16 -0
- package/src/services/pdf/markdown.ts +22 -2
- package/src/services/pwa-devices.ts +640 -0
- package/src/services/session/index.ts +212 -57
- package/src/services/session/recent.ts +42 -0
- package/src/services/session/user.ts +15 -5
- package/src/shared/index.ts +1 -0
- package/src/{browser → shared}/locale-preference.ts +1 -1
- package/src/shared/markdown/formula.ts +122 -26
- package/src/shared/markdown/index.ts +27 -23
- package/src/ssr/AppLaunchpad.island.tsx +8 -1
- package/src/ssr/AppLaunchpadPanel.tsx +1 -1
- package/src/ssr/GlobalAnnouncements.island.tsx +1 -1
- package/src/ssr/Layout.tsx +10 -12
- package/src/ssr/LayoutHeader.tsx +14 -2
- package/src/ssr/LayoutHelp.tsx +2 -4
- package/src/ssr/LayoutRail.tsx +13 -4
- package/src/ssr/MinimalLayoutPreferences.island.tsx +1 -1
- package/src/ssr/MobileProfileActions.tsx +19 -13
- package/src/ssr/PageError.tsx +23 -1
- package/src/ssr/ProfilePreferences.island.tsx +9 -1
- package/src/ssr/PwaLayout.tsx +70 -0
- package/src/ssr/PwaRuntime.island.tsx +105 -0
- package/src/ssr/TimezoneCookie.island.tsx +7 -1
- package/src/ssr/app-navigation.ts +25 -10
- package/src/ssr/index.ts +8 -1
- package/src/ssr/layout-context.ts +1 -1
- package/src/ssr/preference-controller.ts +2 -2
- package/src/ssr/profile-actions.ts +3 -1
- package/src/ssr/profile-preferences-messages.ts +2 -0
- package/src/ssr/pwa-messages.ts +24 -0
- package/src/styles/global.css +6 -0
- package/src/styles/resource-search.css +19 -0
- package/src/styles/utilities-feedback.css +2 -2
- package/scripts/build-pdf-renderer.ts +0 -39
- package/src/ai/client/live-connection.ts +0 -141
- package/src/ai/live-messages.ts +0 -45
- package/src/ai/live-outbox.ts +0 -102
- package/src/ai/live-routes.ts +0 -412
- package/src/shared/markdown/extensions/info-blocks.ts +0 -108
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { startLiveOutbox } from "../events/live";
|
|
1
2
|
import { registerHelp } from "../services/help";
|
|
2
3
|
import { preloadLayoutHelp } from "../ssr/help";
|
|
3
4
|
import { invokeCapabilityStream } from "./capability-streams";
|
|
@@ -26,6 +27,7 @@ import type {
|
|
|
26
27
|
AppLifecycle,
|
|
27
28
|
AppMeta,
|
|
28
29
|
AppPresentationCatalog,
|
|
30
|
+
AppPwaPart,
|
|
29
31
|
AppSearchLink,
|
|
30
32
|
CloudLifecycleContext,
|
|
31
33
|
WidgetEndpoint,
|
|
@@ -38,6 +40,7 @@ import {
|
|
|
38
40
|
CapabilityOriginSchema,
|
|
39
41
|
} from "../contracts/capabilities";
|
|
40
42
|
import { type BoundNotificationMap, bindNotificationDefinitions, type NotificationDefinitionMap } from "../contracts/notification-types";
|
|
43
|
+
import { isPwaPartId, PWA_AUTH_PATH, PWA_CANVAS_COLORS, PWA_MANIFEST_PATH, PWA_SHELL_APP_ID } from "../contracts/pwa";
|
|
41
44
|
import type { AppRegistryEntry } from "../contracts/registry";
|
|
42
45
|
import type { AppSettingsMap, KindToType } from "../contracts/settings-types";
|
|
43
46
|
import type { Role } from "../contracts/shared";
|
|
@@ -59,6 +62,7 @@ import { normalizeInvocationRequestId } from "../services/identity/invocation-to
|
|
|
59
62
|
import { logger } from "../services/logging";
|
|
60
63
|
import { startNotificationDefinitionRegistration } from "../services/notifications/catalog";
|
|
61
64
|
import { readRailSnapshot } from "../services/rail-snapshot";
|
|
65
|
+
import { isAppPagePath } from "../services/session";
|
|
62
66
|
import { get, loadCache as loadSettingsCache, set } from "../services/settings";
|
|
63
67
|
import { createSettingsAPI, type SettingsAPI } from "../services/settings/api";
|
|
64
68
|
import { registerSettings, toLegacySettingDefs } from "../services/settings/defaults";
|
|
@@ -95,6 +99,8 @@ export type PageOptions = {
|
|
|
95
99
|
theme?: "light" | "dark";
|
|
96
100
|
/** Framework-resolved BCP 47 document language — always the canonical request locale, never a page override. */
|
|
97
101
|
lang?: string;
|
|
102
|
+
/** Renders the document as a page of the installable mobile app; set by `PwaLayout`. */
|
|
103
|
+
pwa?: { name: string };
|
|
98
104
|
};
|
|
99
105
|
|
|
100
106
|
type ResolvedPageOptions = PageOptions & { performanceRoute?: string };
|
|
@@ -162,6 +168,16 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
|
|
|
162
168
|
legalLinks?: ReadonlyArray<{ label: string; href: string; icon?: string }>;
|
|
163
169
|
/** Static destinations for the global search UI; no capabilities are created. */
|
|
164
170
|
searchLinks?: readonly AppSearchLink[];
|
|
171
|
+
/**
|
|
172
|
+
* Preview: pages of this app in the installable mobile app. Declaring it adds
|
|
173
|
+
* `/pwa/<id>` to the gateway routes and lists the app in the mobile app,
|
|
174
|
+
* with its name, icon and description. Serve the pages below `/pwa/<id>`
|
|
175
|
+
* with `PwaLayout` and `ssr.pwaAccess`. `routes` never lists `/pwa` paths.
|
|
176
|
+
*/
|
|
177
|
+
pwa?: {
|
|
178
|
+
/** Coarse visibility in the mobile app, like `nav.requiresRoles`. Routes and services still authorize. */
|
|
179
|
+
requiresRoles?: Role[];
|
|
180
|
+
};
|
|
165
181
|
/**
|
|
166
182
|
* Dashboard widget endpoints this app exposes. Each entry references an
|
|
167
183
|
* HTTP path on this app that returns a `WidgetResponse`. The dashboard
|
|
@@ -312,6 +328,32 @@ export type AppDefinition<S extends AppSettingsMap = {}, N extends NotificationD
|
|
|
312
328
|
|
|
313
329
|
// ── Implementation ──────────────────────────────────────────────────────────
|
|
314
330
|
|
|
331
|
+
const isPwaPath = (route: string): boolean => route === "/pwa" || route.startsWith("/pwa/");
|
|
332
|
+
/** A route as the gateway reads it: trimmed and without empty segments, so `//pwa/x/` is `/pwa/x`. */
|
|
333
|
+
const canonicalRoute = (route: string): string => {
|
|
334
|
+
const trimmed = route.trim();
|
|
335
|
+
return trimmed.startsWith("/") ? `/${trimmed.split("/").filter(Boolean).join("/")}` : trimmed;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* `/pwa` belongs to the mobile app shell, `/pwa/_auth` to Core, and `/pwa/<id>`
|
|
340
|
+
* to the app that declares `pwa`. A part never claims a path by hand, so no app
|
|
341
|
+
* can take the shell's pages or another app's part.
|
|
342
|
+
*/
|
|
343
|
+
const validateAppPwaPart = (opts: Pick<AppOptions, "id" | "routes" | "pwa">): AppPwaPart | undefined => {
|
|
344
|
+
for (const route of opts.routes) {
|
|
345
|
+
const path = canonicalRoute(route);
|
|
346
|
+
if (!isPwaPath(path)) continue;
|
|
347
|
+
if ((path === "/pwa" && opts.id === PWA_SHELL_APP_ID) || (path === PWA_AUTH_PATH && opts.id === "core")) continue;
|
|
348
|
+
throw new Error(`App "${opts.id}" lists route "${route}"; /pwa is reserved for the mobile app, declare pwa: {} to get /pwa/${opts.id}`);
|
|
349
|
+
}
|
|
350
|
+
if (!opts.pwa) return undefined;
|
|
351
|
+
if (!isPwaPartId(opts.id)) {
|
|
352
|
+
throw new Error(`App "${opts.id}" cannot declare pwa; a part needs a plain id other than pwa, settings and offline`);
|
|
353
|
+
}
|
|
354
|
+
return { href: `/pwa/${opts.id}`, requiresRoles: opts.pwa.requiresRoles ? [...opts.pwa.requiresRoles] : undefined };
|
|
355
|
+
};
|
|
356
|
+
|
|
315
357
|
export const defineApp = <
|
|
316
358
|
const S extends AppSettingsMap = {},
|
|
317
359
|
const N extends NotificationDefinitionMap = {},
|
|
@@ -323,6 +365,7 @@ export const defineApp = <
|
|
|
323
365
|
const isDevelopment = env.IS_DEVELOPMENT;
|
|
324
366
|
const notifications = bindNotificationDefinitions(opts.id, opts.notifications);
|
|
325
367
|
const cliModules = validateAppCliModules(opts.id, opts.cli);
|
|
368
|
+
const pwaPart = validateAppPwaPart(opts);
|
|
326
369
|
|
|
327
370
|
// ── 0. Register declared settings into the runtime registry ──────────
|
|
328
371
|
// SETTINGS_MAP is the single source of truth for validation in store.ts
|
|
@@ -341,19 +384,43 @@ export const defineApp = <
|
|
|
341
384
|
rootDir,
|
|
342
385
|
componentRoots: [resolve(env.APP_DIR ?? rootDir, "src"), fileURLToPath(new URL("../", import.meta.url))],
|
|
343
386
|
basePath: opts.basePath,
|
|
344
|
-
template: ({ body, scripts, title, description, theme, lang, performanceRoute }) => {
|
|
387
|
+
template: ({ body, scripts, title, description, theme, lang, performanceRoute, pwa }) => {
|
|
345
388
|
const themeFixed = theme !== undefined;
|
|
389
|
+
// App pages paint the canvas before any stylesheet and give it to the
|
|
390
|
+
// status bar. No status-bar style: `black-translucent` lays a blur band
|
|
391
|
+
// over the top edge on iOS 26.
|
|
392
|
+
const canvas = PWA_CANVAS_COLORS[theme ?? "light"];
|
|
393
|
+
const head = pwa
|
|
394
|
+
? {
|
|
395
|
+
html: ` style="background-color:${canvas}"`,
|
|
396
|
+
viewport: "width=device-width, initial-scale=1, viewport-fit=cover",
|
|
397
|
+
app: `<meta name="theme-color" content="${canvas}">
|
|
398
|
+
<link rel="manifest" href="${PWA_MANIFEST_PATH}">
|
|
399
|
+
<link rel="apple-touch-icon" href="/branding/apple-touch-icon.png">
|
|
400
|
+
<meta name="apple-mobile-web-app-title" content="${escapeHtml(pwa.name)}">`,
|
|
401
|
+
// The web cross-fades between pages; an app page swaps at once. The browser ignores taps while a
|
|
402
|
+
// cross-document transition runs, so a tab tap right after the last one would be lost. After
|
|
403
|
+
// global.css, whose `navigation: auto` it overrides.
|
|
404
|
+
styles: `
|
|
405
|
+
<style data-cloud-app-navigation>@view-transition{navigation:none}</style>`,
|
|
406
|
+
}
|
|
407
|
+
: {
|
|
408
|
+
html: "",
|
|
409
|
+
viewport: "width=device-width, initial-scale=1.0",
|
|
410
|
+
app: `<meta name="theme-color" content="#09090b">
|
|
411
|
+
<meta name="mobile-web-app-capable" content="yes">`,
|
|
412
|
+
styles: "",
|
|
413
|
+
};
|
|
346
414
|
// The inline layer statement fixes the cascade order before any
|
|
347
415
|
// stylesheet declares a layer. Tailwind's `properties` layer resets
|
|
348
416
|
// `--tw-*` in browsers without `@property` and must stay lowest.
|
|
349
417
|
return `<!DOCTYPE html>
|
|
350
|
-
<html lang="${normalizeLocale(lang)}" class="${theme ?? "light"}"${themeFixed ? " data-theme-fixed" : ""}>
|
|
351
|
-
<head><meta charset="UTF-8"><meta name="viewport" content="
|
|
418
|
+
<html lang="${normalizeLocale(lang)}" class="${theme ?? "light"}"${themeFixed ? " data-theme-fixed" : ""}${head.html}>
|
|
419
|
+
<head><meta charset="UTF-8"><meta name="viewport" content="${head.viewport}">
|
|
352
420
|
<meta name="view-transition" content="same-origin">
|
|
353
421
|
<title>${escapeHtml(title ?? "Cloud")}</title>
|
|
354
422
|
<meta name="description" content="${escapeHtml(description ?? "Cloud workspace")}">
|
|
355
|
-
|
|
356
|
-
<meta name="mobile-web-app-capable" content="yes">
|
|
423
|
+
${head.app}
|
|
357
424
|
<link rel="icon" href="${appFaviconHref(opts.id, v)}">
|
|
358
425
|
<style data-cloud-css-layers>@layer properties, theme, base, components, utilities;</style>
|
|
359
426
|
<link rel="preload" href="/public/tabler-icons.woff2" as="font" type="font/woff2" crossorigin>
|
|
@@ -361,7 +428,7 @@ export const defineApp = <
|
|
|
361
428
|
<link rel="stylesheet" href="/public/fonts.css?v=${v}">
|
|
362
429
|
<link rel="stylesheet" href="/public/tabler-icons.css?v=${v}">
|
|
363
430
|
<link rel="stylesheet" href="/public/${opts.id}/app.css?v=${v}">
|
|
364
|
-
<link rel="stylesheet" href="/public/global.css?v=${v}"
|
|
431
|
+
<link rel="stylesheet" href="/public/global.css?v=${v}">${head.styles}
|
|
365
432
|
<script>${themeBootstrapScript}</script>
|
|
366
433
|
</head>
|
|
367
434
|
<body class="k2b-ui" data-k2b-app-workspace-controller="global">
|
|
@@ -389,6 +456,9 @@ export const defineApp = <
|
|
|
389
456
|
const user = c.get("user");
|
|
390
457
|
c.get("page").performanceRoute =
|
|
391
458
|
user && c.get("settings")?.observability?.web_vitals?.enabled === true ? (matchedRouteTemplate(c) ?? undefined) : undefined;
|
|
459
|
+
// Pages of the mobile app render in PwaLayout, which shows no announcements, Help, or rail; reading them would
|
|
460
|
+
// only delay every tab switch. Core's pages below /pwa/_auth, such as its not-found page, keep the web layout.
|
|
461
|
+
if (isAppPagePath(c.req.path)) return;
|
|
392
462
|
await Promise.all([
|
|
393
463
|
preloadLayoutAnnouncements(c),
|
|
394
464
|
preloadLayoutHelp(c),
|
|
@@ -414,10 +484,11 @@ export const defineApp = <
|
|
|
414
484
|
links: group.links.map((link) => ({ ...link })),
|
|
415
485
|
}),
|
|
416
486
|
),
|
|
417
|
-
routes: [...opts.routes, ...cliPluginRoutePrefixes(cliModules)],
|
|
487
|
+
routes: [...opts.routes, ...cliPluginRoutePrefixes(cliModules), ...(pwaPart ? [pwaPart.href] : [])],
|
|
418
488
|
nav: opts.nav,
|
|
419
489
|
legalLinks: opts.legalLinks ? [...opts.legalLinks] : undefined,
|
|
420
490
|
searchLinks: opts.searchLinks?.map((link) => ({ ...link, keywords: link.keywords ? [...link.keywords] : undefined })),
|
|
491
|
+
pwa: pwaPart,
|
|
421
492
|
widgets: opts.widgets ? opts.widgets.map((w) => ({ ...w })) : undefined,
|
|
422
493
|
settingKeys: opts.settings ? Object.keys(opts.settings) : undefined,
|
|
423
494
|
openapi: opts.openapi,
|
|
@@ -512,6 +583,9 @@ export const defineApp = <
|
|
|
512
583
|
help: compiledHelp?.summary,
|
|
513
584
|
legalLinks: meta.legalLinks ? meta.legalLinks.map((l) => ({ ...l })) : undefined,
|
|
514
585
|
searchLinks: meta.searchLinks,
|
|
586
|
+
pwa: meta.pwa
|
|
587
|
+
? { href: meta.pwa.href, requiresRoles: meta.pwa.requiresRoles ? [...meta.pwa.requiresRoles] : undefined }
|
|
588
|
+
: undefined,
|
|
515
589
|
widgets: meta.widgets ? meta.widgets.map((w) => ({ ...w })) : undefined,
|
|
516
590
|
settingKeys: meta.settingKeys ? [...meta.settingKeys] : undefined,
|
|
517
591
|
openapi: advertiseOpenapi ? opts.openapi : undefined,
|
|
@@ -812,10 +886,19 @@ export const defineApp = <
|
|
|
812
886
|
const stopLifecycle = startOpts.lifecycle?.stop;
|
|
813
887
|
if (stopLifecycle) cleanup.push(() => stopLifecycle(cloudCtx));
|
|
814
888
|
|
|
889
|
+
const startOutbox = async () => {
|
|
890
|
+
const stopLiveOutbox = await startLiveOutbox(meta.id);
|
|
891
|
+
if (stopLiveOutbox) cleanup.push(stopLiveOutbox);
|
|
892
|
+
};
|
|
893
|
+
// An application fails before any setup when Core has not created the platform outbox yet.
|
|
894
|
+
// Core creates it in its own setup, so it starts publishing after that.
|
|
895
|
+
if (meta.id !== "core") await startOutbox();
|
|
896
|
+
|
|
815
897
|
if (!startOpts.skipSetup && startOpts.lifecycle?.setup) {
|
|
816
898
|
log.info(`Setup: ${meta.id}`);
|
|
817
899
|
await startOpts.lifecycle.setup(cloudCtx);
|
|
818
900
|
}
|
|
901
|
+
if (meta.id === "core") await startOutbox();
|
|
819
902
|
|
|
820
903
|
stopNotificationRegistration = await startNotificationDefinitionRegistration(meta.id, notifications);
|
|
821
904
|
|