@k2b/cloud 0.6.0-rc.0 → 0.7.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.
Files changed (315) hide show
  1. package/README.md +2 -2
  2. package/package.json +30 -17
  3. package/scripts/browser-performance.ts +14 -0
  4. package/scripts/build-pdf-renderer.ts +39 -0
  5. package/scripts/build.ts +15 -35
  6. package/scripts/preload.ts +9 -12
  7. package/scripts/sync-recovery-smoke.ts +2 -1
  8. package/src/_internal/app-presentation.ts +14 -2
  9. package/src/_internal/capabilities.ts +108 -7
  10. package/src/_internal/capability-streams.ts +81 -0
  11. package/src/_internal/define-app.ts +62 -36
  12. package/src/_internal/heartbeat.ts +4 -0
  13. package/src/_internal/help-catalog.ts +27 -172
  14. package/src/_internal/help.ts +13 -76
  15. package/src/_internal/page-responses.ts +2 -0
  16. package/src/_internal/postgres-application-name.ts +30 -0
  17. package/src/_internal/process-identity.ts +15 -0
  18. package/src/_internal/process-sync.ts +1 -1
  19. package/src/_internal/registry-validation.ts +29 -25
  20. package/src/_internal/registry.ts +2 -53
  21. package/src/_internal/runtime-context.ts +5 -16
  22. package/src/_internal/server-timing.ts +13 -0
  23. package/src/_internal/status-preserving-ssr.ts +18 -6
  24. package/src/_internal/web-vitals-asset.ts +10 -0
  25. package/src/access/PermissionEditor.tsx +14 -153
  26. package/src/access/PrincipalPicker.tsx +104 -0
  27. package/src/access/ui.ts +2 -0
  28. package/src/ai/admin.ts +14 -13
  29. package/src/ai/assistant-models.ts +12 -1
  30. package/src/ai/audio-format.ts +34 -0
  31. package/src/ai/audio-tool.ts +90 -0
  32. package/src/ai/browser-code-contracts.ts +79 -0
  33. package/src/ai/browser.ts +2 -0
  34. package/src/ai/capabilities.ts +55 -36
  35. package/src/ai/capability-execution.ts +47 -12
  36. package/src/ai/chat/blocks.tsx +157 -95
  37. package/src/ai/chat/capability-result.ts +15 -0
  38. package/src/ai/chat/capability-table.tsx +2 -10
  39. package/src/ai/chat/composer-adapter.ts +48 -3
  40. package/src/ai/chat/message-actions.tsx +8 -7
  41. package/src/ai/chat/message-utils.ts +12 -1
  42. package/src/ai/chat/messages.ts +2 -0
  43. package/src/ai/chat/presentation.tsx +19 -48
  44. package/src/ai/chat/primitives.tsx +43 -1
  45. package/src/ai/chat/tool-disclosure.tsx +17 -2
  46. package/src/ai/chat/tool-groups.ts +52 -0
  47. package/src/ai/chat-quotas.ts +25 -0
  48. package/src/ai/chat-task-contracts.ts +9 -1
  49. package/src/ai/chat-tasks.ts +276 -43
  50. package/src/ai/client/controller.ts +110 -33
  51. package/src/ai/client/file-source.ts +12 -6
  52. package/src/ai/client/live-connection.ts +1 -0
  53. package/src/ai/client/projection.ts +22 -11
  54. package/src/ai/client/transport.ts +4 -0
  55. package/src/ai/code-capability-routes.ts +110 -0
  56. package/src/ai/code-capability-transport.ts +38 -0
  57. package/src/ai/code-mode-skill.ts +114 -0
  58. package/src/ai/code-runtime-tools.ts +166 -0
  59. package/src/ai/code-source-contracts.ts +202 -0
  60. package/src/ai/code-source-tools.ts +145 -0
  61. package/src/ai/compaction.ts +27 -6
  62. package/src/ai/data-analysis-skill.ts +10 -0
  63. package/src/ai/default-tools.ts +25 -2
  64. package/src/ai/dictation-runtime.ts +171 -0
  65. package/src/ai/dictations.ts +188 -0
  66. package/src/ai/draft-content.ts +34 -0
  67. package/src/ai/enrich.ts +6 -0
  68. package/src/ai/executor.ts +191 -31
  69. package/src/ai/file-content-version.ts +22 -0
  70. package/src/ai/file-context.ts +2 -1
  71. package/src/ai/file-media-type.ts +12 -0
  72. package/src/ai/file-reference-contracts.ts +15 -0
  73. package/src/ai/file-tools.ts +2 -1
  74. package/src/ai/files-store.ts +174 -71
  75. package/src/ai/fixtures/vision-encrypted.pdf +0 -0
  76. package/src/ai/grids-skill.ts +40 -10
  77. package/src/ai/http.ts +15 -8
  78. package/src/ai/index.ts +26 -2
  79. package/src/ai/inference-calls.ts +198 -0
  80. package/src/ai/live-events.ts +5 -1
  81. package/src/ai/memory-learning.ts +60 -28
  82. package/src/ai/message-queue.ts +167 -0
  83. package/src/ai/migrate.ts +187 -11
  84. package/src/ai/model-pricing.ts +43 -0
  85. package/src/ai/pdf-render-worker.ts +74 -0
  86. package/src/ai/pdf-render.fixture.ts +28 -0
  87. package/src/ai/pdf-render.ts +77 -0
  88. package/src/ai/projects-routes.ts +3 -3
  89. package/src/ai/projects.ts +20 -6
  90. package/src/ai/protocol.ts +3 -0
  91. package/src/ai/provider.ts +1 -8
  92. package/src/ai/quota-provider.ts +191 -0
  93. package/src/ai/quota-report.ts +152 -0
  94. package/src/ai/quotas-migrate.ts +45 -0
  95. package/src/ai/quotas.ts +168 -0
  96. package/src/ai/routes.ts +215 -11
  97. package/src/ai/run-timeout.ts +10 -0
  98. package/src/ai/runtime-tools.ts +3 -0
  99. package/src/ai/runtime.ts +55 -16
  100. package/src/ai/settings.ts +36 -8
  101. package/src/ai/short-id.ts +2 -2
  102. package/src/ai/skill-catalog.ts +0 -10
  103. package/src/ai/skill-search.ts +47 -0
  104. package/src/ai/skill-seeds.ts +105 -20
  105. package/src/ai/skill-tool.ts +16 -5
  106. package/src/ai/skills-routes.ts +32 -4
  107. package/src/ai/skills.ts +324 -155
  108. package/src/ai/store.ts +373 -191
  109. package/src/ai/stream.ts +5 -0
  110. package/src/ai/structured-runs.ts +4 -3
  111. package/src/ai/structured.ts +8 -1
  112. package/src/ai/system-prompt.ts +12 -2
  113. package/src/ai/timeline.ts +2 -5
  114. package/src/ai/todo-contracts.ts +20 -0
  115. package/src/ai/todo-tool.ts +16 -0
  116. package/src/ai/tool-audit.ts +30 -40
  117. package/src/ai/tools.ts +6 -0
  118. package/src/ai/transcription.ts +202 -0
  119. package/src/ai/turn-timing.ts +114 -0
  120. package/src/ai/types.ts +58 -10
  121. package/src/ai/ui.tsx +2 -0
  122. package/src/ai/usage.ts +118 -118
  123. package/src/ai/vision-tool.ts +39 -10
  124. package/src/api/admin-ai-projects.ts +2 -2
  125. package/src/api/admin-ai-quotas.ts +74 -0
  126. package/src/api/admin-ai-skills.ts +48 -5
  127. package/src/api/admin-ai-usage.ts +2 -2
  128. package/src/api/admin-core-settings.ts +25 -0
  129. package/src/api/admin-identity.ts +91 -51
  130. package/src/api/admin-rail.ts +58 -0
  131. package/src/api/announcements.ts +14 -0
  132. package/src/api/capabilities.ts +292 -43
  133. package/src/api/capability-streams.ts +262 -0
  134. package/src/api/help.ts +8 -44
  135. package/src/api/index.ts +6 -0
  136. package/src/api/mcp.ts +44 -26
  137. package/src/api/me-web-vitals.ts +35 -0
  138. package/src/api/me.ts +2 -0
  139. package/src/api/search/schemas.ts +26 -15
  140. package/src/api/search.bench.ts +1 -1
  141. package/src/api/search.ts +54 -17
  142. package/src/browser/CloudResourceSearch.tsx +640 -702
  143. package/src/browser/app-approval-vault.ts +19 -7
  144. package/src/browser/command-bridge.ts +109 -0
  145. package/src/browser/command-shortcuts.ts +76 -0
  146. package/src/browser/commands.ts +66 -0
  147. package/src/browser/navigation-search.ts +48 -0
  148. package/src/browser/notifications.ts +1 -1
  149. package/src/browser/resource-picker.tsx +5 -18
  150. package/src/browser/resource-search-dialog.ts +9 -0
  151. package/src/browser/resource-search-input.ts +46 -0
  152. package/src/browser/resource-search-messages.ts +76 -40
  153. package/src/browser/resource-search.ts +7 -0
  154. package/src/browser/search-bridge.ts +102 -0
  155. package/src/browser/search-commands.ts +59 -0
  156. package/src/browser/search.ts +13 -0
  157. package/src/browser/spotlight-position.ts +208 -0
  158. package/src/browser/testing.ts +5 -0
  159. package/src/browser/web-vitals.ts +25 -0
  160. package/src/capabilities/claims.ts +179 -0
  161. package/src/capabilities/client.ts +34 -0
  162. package/src/capabilities/command-link.ts +16 -0
  163. package/src/capabilities/executions.ts +298 -0
  164. package/src/capabilities/migrate.ts +81 -0
  165. package/src/capabilities/server.ts +114 -34
  166. package/src/capabilities/stream-body.ts +16 -0
  167. package/src/capabilities/streams.ts +28 -0
  168. package/src/cli/admin/ai-quotas.ts +161 -0
  169. package/src/cli/admin/ai-skills.ts +64 -0
  170. package/src/cli/admin/ai-usage.ts +53 -30
  171. package/src/cli/admin/app-credentials.ts +79 -0
  172. package/src/cli/admin/data.ts +17 -0
  173. package/src/cli/admin/index.ts +9 -0
  174. package/src/cli/capabilities.ts +29 -0
  175. package/src/cli/capability-streams.ts +25 -0
  176. package/src/cli/index.ts +3 -2
  177. package/src/config/env.ts +11 -8
  178. package/src/contracts/app.ts +23 -3
  179. package/src/contracts/capabilities.ts +68 -5
  180. package/src/contracts/capability-streams.ts +46 -0
  181. package/src/contracts/commands.ts +49 -0
  182. package/src/contracts/index.ts +6 -1
  183. package/src/contracts/rail-admin.ts +38 -0
  184. package/src/contracts/rail-preferences.ts +16 -0
  185. package/src/contracts/registry.ts +3 -27
  186. package/src/contracts/settings-types.ts +1 -1
  187. package/src/contracts/shared.ts +9 -5
  188. package/src/contracts/web-vitals.ts +27 -0
  189. package/src/desktop/solid.tsx +3 -3
  190. package/src/index.ts +0 -4
  191. package/src/server/index.ts +3 -0
  192. package/src/server/middleware/auth.ts +3 -2
  193. package/src/server/middleware/route-template.ts +9 -3
  194. package/src/server/middleware/runtime.ts +2 -1
  195. package/src/server/middleware/settings.ts +17 -11
  196. package/src/server/services/access-revision.ts +10 -0
  197. package/src/server/services/access.ts +1 -1
  198. package/src/server/services/index.ts +1 -0
  199. package/src/services/account-category-policy.ts +9 -0
  200. package/src/services/account-lifecycle/audit.ts +1 -1
  201. package/src/services/accounts/app.ts +12 -2
  202. package/src/services/accounts/email-write.ts +23 -0
  203. package/src/services/accounts/groups.ts +15 -4
  204. package/src/services/accounts/identities.ts +249 -0
  205. package/src/services/accounts/identity-reconciliation.ts +87 -0
  206. package/src/services/accounts/ipa-data.ts +2 -2
  207. package/src/services/accounts/local-groups.ts +2 -2
  208. package/src/services/accounts/posix.ts +45 -29
  209. package/src/services/accounts/users.ts +2 -2
  210. package/src/services/announcements/index.ts +69 -8
  211. package/src/services/audit/index.ts +1 -1
  212. package/src/services/cache-fill.ts +35 -0
  213. package/src/services/help/index.ts +109 -0
  214. package/src/services/help/maintenance.ts +25 -0
  215. package/src/services/help/store.ts +180 -0
  216. package/src/services/help/types.ts +46 -0
  217. package/src/services/identity/invocation-actor.ts +2 -2
  218. package/src/services/identity/key-config.ts +2 -1
  219. package/src/services/identity/key-ring.ts +1 -1
  220. package/src/services/index.ts +15 -0
  221. package/src/services/ipa/users.ts +268 -221
  222. package/src/services/legal-consent.ts +1 -1
  223. package/src/services/logging/index.ts +15 -13
  224. package/src/services/logging/trace.ts +2 -1
  225. package/src/services/logging/web-vitals.ts +81 -0
  226. package/src/services/mandates/index.ts +18 -4
  227. package/src/services/mandates/policy.ts +65 -2
  228. package/src/services/notifications/batches.ts +124 -57
  229. package/src/services/notifications/browser.ts +12 -0
  230. package/src/services/notifications/index.ts +0 -2
  231. package/src/services/notifications/platform.ts +0 -4
  232. package/src/services/oauth-tokens.ts +3 -3
  233. package/src/services/pdf/gotenberg.ts +107 -84
  234. package/src/services/pdf/index.ts +3 -0
  235. package/src/services/providers/local/users.ts +25 -16
  236. package/src/services/public-http.ts +101 -0
  237. package/src/services/rail-shortcuts.ts +95 -0
  238. package/src/services/rail-snapshot.ts +53 -0
  239. package/src/services/request-cache-redis.ts +28 -0
  240. package/src/services/session/index.ts +15 -2
  241. package/src/services/session/user.ts +62 -37
  242. package/src/services/settings/core-settings.ts +21 -28
  243. package/src/services/settings/defaults.ts +5 -0
  244. package/src/services/settings/store.ts +58 -84
  245. package/src/shared/ai-costs.ts +42 -0
  246. package/src/shared/ai-platform-prompt.ts +1 -1
  247. package/src/shared/ai-quotas.ts +136 -0
  248. package/src/shared/ai-usage.ts +6 -16
  249. package/src/shared/app-presentation.ts +21 -15
  250. package/src/shared/capability-messages.ts +14 -0
  251. package/src/shared/help.ts +6 -42
  252. package/src/shared/index.ts +6 -0
  253. package/src/ssr/AdminLayout.tsx +7 -5
  254. package/src/ssr/AdminSidebar.tsx +29 -15
  255. package/src/ssr/AppLaunchpad.island.tsx +24 -137
  256. package/src/ssr/AppLaunchpadPanel.tsx +181 -0
  257. package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
  258. package/src/ssr/GlobalSearchDialog.tsx +167 -20
  259. package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
  260. package/src/ssr/Layout.tsx +17 -6
  261. package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
  262. package/src/ssr/LayoutFooter.tsx +1 -1
  263. package/src/ssr/LayoutHeader.tsx +46 -14
  264. package/src/ssr/LayoutHelp.tsx +23 -22
  265. package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
  266. package/src/ssr/LayoutRail.tsx +17 -9
  267. package/src/ssr/MobileNavigation.tsx +71 -0
  268. package/src/ssr/MobileProfileActions.tsx +49 -0
  269. package/src/ssr/ProfilePreferences.island.tsx +3 -36
  270. package/src/ssr/RailApps.island.tsx +2 -2
  271. package/src/ssr/RailEditor.tsx +17 -2
  272. package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
  273. package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
  274. package/src/ssr/admin-navigation.ts +7 -1
  275. package/src/ssr/help.ts +29 -0
  276. package/src/ssr/islands/SearchBar.tsx +67 -0
  277. package/src/ssr/islands/index.ts +3 -1
  278. package/src/ssr/layout-context.ts +2 -2
  279. package/src/ssr/layout-help-search.ts +15 -0
  280. package/src/ssr/mobile-menu-history.ts +70 -0
  281. package/src/ssr/platform-messages.ts +16 -8
  282. package/src/ssr/profile-actions.ts +42 -0
  283. package/src/ssr/rail-context.ts +8 -4
  284. package/src/ssr/rail-messages.ts +2 -0
  285. package/src/ssr/rail-navigation.ts +3 -3
  286. package/src/ssr/workspace-navigation.ts +75 -0
  287. package/src/styles/effects.css +6 -6
  288. package/src/styles/global.css +54 -0
  289. package/src/styles/input.css +43 -41
  290. package/src/styles/resource-search.css +505 -0
  291. package/src/styles/utilities-feedback.css +13 -0
  292. package/src/styles/utilities-layout.css +1 -5
  293. package/src/styles/utilities-navigation.css +3 -39
  294. package/src/types/ambient.d.ts +5 -0
  295. package/src/workflows/ai/runtime.ts +5 -1
  296. package/src/workflows/ai/store.ts +2 -0
  297. package/src/workflows/ai-actions.ts +16 -13
  298. package/src/workflows/definition.ts +10 -5
  299. package/src/workflows/store/actions.ts +44 -20
  300. package/src/workflows/store/index.ts +2 -0
  301. package/src/workflows/store/runs.ts +36 -0
  302. package/src/workflows/store/worker-pool.ts +55 -0
  303. package/src/workflows/store/worker-runtime.ts +90 -0
  304. package/scripts/app-favicon.test.ts +0 -49
  305. package/src/ai/chat/blocks.render.test.tsx +0 -1108
  306. package/src/ai/kit-skill.ts +0 -22
  307. package/src/ai/usage-migrate.ts +0 -81
  308. package/src/browser/CloudResourceSearch.behavior.test.tsx +0 -74
  309. package/src/browser/notification-target.ts +0 -27
  310. package/src/contracts/notification-live.ts +0 -66
  311. package/src/services/notifications/live.ts +0 -50
  312. package/src/ssr/BrowserNotifications.island.tsx +0 -135
  313. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +0 -96
  314. package/src/ssr/Layout.render.test.tsx +0 -148
  315. package/src/ssr/islands/SearchBar.island.tsx +0 -80
@@ -0,0 +1,136 @@
1
+ import { z } from "zod";
2
+ import { PrincipalSchema } from "../contracts/shared";
3
+ import { AiPriceSchema } from "./ai-costs";
4
+
5
+ export const AiBackgroundBudgetSchema = z
6
+ .object({
7
+ enabled: z.boolean(),
8
+ warnAt: AiPriceSchema.nullable(),
9
+ stopAt: AiPriceSchema.positive(),
10
+ })
11
+ .strict()
12
+ .refine((value) => value.warnAt === null || value.warnAt < value.stopAt, "Warning must be below the stop amount.");
13
+
14
+ export const AiQuotaRuleSchema = z
15
+ .object({
16
+ scope: z.string().min(1).max(128),
17
+ hours: z.number().int().min(1).max(8760),
18
+ anchor: z.iso.datetime(),
19
+ grants: z
20
+ .array(
21
+ z
22
+ .object({
23
+ principal: PrincipalSchema.refine((p) => p.type !== "public", "Chat quotas require an identity."),
24
+ displayName: z.string().max(300).optional(),
25
+ limit: AiPriceSchema.nullable(),
26
+ })
27
+ .strict(),
28
+ )
29
+ .max(1000)
30
+ .refine((grants) => new Set(grants.map((g) => JSON.stringify(g.principal))).size === grants.length, "Duplicate quota principal."),
31
+ })
32
+ .strict();
33
+ export const AiQuotaConfigSchema = z
34
+ .object({
35
+ enabled: z.boolean(),
36
+ revision: z.number().int().nonnegative(),
37
+ rules: z.array(AiQuotaRuleSchema).max(256),
38
+ unit: z.string().trim().min(1).max(16).optional(),
39
+ background: AiBackgroundBudgetSchema.optional(),
40
+ })
41
+ .strict()
42
+ .refine((c) => new Set(c.rules.map((r) => r.scope)).size === c.rules.length, "Duplicate quota scope.");
43
+ export type AiQuotaConfig = z.infer<typeof AiQuotaConfigSchema>;
44
+ export type AiQuotaRule = z.infer<typeof AiQuotaRuleSchema>;
45
+ export type AiQuotaBalance = {
46
+ scope: string;
47
+ limit: number | null;
48
+ input: number;
49
+ output: number;
50
+ used: number;
51
+ unknown: number;
52
+ estimated?: number;
53
+ resetsAt: string;
54
+ sources: string[];
55
+ sourceDetails?: { principal: z.infer<typeof PrincipalSchema>; displayName: string }[];
56
+ bypassed: boolean;
57
+ };
58
+ export type AiQuotaSnapshot = {
59
+ unit?: string;
60
+ enabled: boolean;
61
+ balances: AiQuotaBalance[];
62
+ usage: { model: string; input: number; output: number; unknown: number }[];
63
+ };
64
+ export type AiQuotaIdentity = { type: "user" | "service_account"; id: string; label: string; lastUsed: string | null };
65
+ export const AiQuotaIdentitySchema = z.object({ type: z.enum(["user", "service_account"]), id: z.uuid() });
66
+ export const AiQuotaResetSchema = AiQuotaIdentitySchema.extend({ scope: z.string().min(1).max(128), requestId: z.uuid() }).strict();
67
+ export const AiQuotaUsersQuerySchema = z
68
+ .object({
69
+ search: z.string().max(200).default(""),
70
+ page: z.coerce.number().int().positive().default(1),
71
+ })
72
+ .strict();
73
+
74
+ export type AiChatQuotaSnapshot = {
75
+ unit?: string;
76
+ unlimitedModels?: string[];
77
+ enabled: boolean;
78
+ balances: Omit<AiQuotaBalance, "sources" | "sourceDetails">[];
79
+ };
80
+
81
+ export const AiQuotaReportQuerySchema = z.object({
82
+ view: z.enum(["users", "rules"]).default("users"),
83
+ range: z.enum(["24h", "7d", "30d", "90d"]).default("30d"),
84
+ until: z.iso.datetime({ offset: true }).optional(),
85
+ search: z.string().trim().max(200).default(""),
86
+ model: z.string().max(128).default(""),
87
+ status: z.enum(["all", "available", "exhausted", "unknown", "unlimited", "disabled"]).default("all"),
88
+ sort: z.enum(["label", "cost", "lastUsed"]).default("lastUsed"),
89
+ direction: z.enum(["asc", "desc"]).default("desc"),
90
+ page: z.coerce.number().int().min(1).max(1000000).default(1),
91
+ identity: z.uuid().optional(),
92
+ identityType: z.enum(["user", "service_account"]).default("user"),
93
+ });
94
+ export type AiQuotaReportQuery = z.infer<typeof AiQuotaReportQuerySchema>;
95
+ export type AiQuotaStatus = Exclude<AiQuotaReportQuery["status"], "all">;
96
+ export type AiQuotaReportRow = AiQuotaIdentity & {
97
+ cost: number | null;
98
+ input: number;
99
+ output: number;
100
+ calls: number;
101
+ measured: number;
102
+ estimated: number;
103
+ unknown: number;
104
+ status: AiQuotaStatus;
105
+ scopes: number;
106
+ exhausted: number;
107
+ balances: Pick<AiQuotaBalance, "scope" | "limit" | "used" | "unknown" | "bypassed">[];
108
+ };
109
+ export type AiQuotaReport = {
110
+ query: AiQuotaReportQuery;
111
+ since: string;
112
+ until: string;
113
+ asOf: string;
114
+ overview: {
115
+ cost: number | null;
116
+ accounts: number;
117
+ input: number;
118
+ output: number;
119
+ calls: number;
120
+ measured: number;
121
+ estimated: number;
122
+ unknown: number;
123
+ };
124
+ timeline: { cost: number | null; at: string; input: number; output: number; calls: number; measured: number; unknown: number }[];
125
+ models: { cost: number | null; model: string; input: number; output: number; calls: number; measured: number; unknown: number }[];
126
+ selected: AiQuotaIdentity | null;
127
+ items: AiQuotaReportRow[];
128
+ total: number;
129
+ page: number;
130
+ perPage: number;
131
+ };
132
+ export const aiQuotaHref = (query: Partial<AiQuotaReportQuery>) => {
133
+ const params = new URLSearchParams({ tab: "ai-quotas" });
134
+ for (const [key, value] of Object.entries(query)) if (value !== undefined && value !== "") params.set(key, String(value));
135
+ return `/admin/settings?${params}`;
136
+ };
@@ -11,28 +11,18 @@ export const AiUsageQuerySchema = z.object({
11
11
  modelProfileId: text,
12
12
  providerModel: text,
13
13
  appId: text,
14
+ workflowId: z.uuid().optional(),
15
+ workflowRunId: z.uuid().optional(),
14
16
  view: z.enum(AI_USAGE_VIEWS).default("overview"),
15
- kind: z.enum(["chat", "background", "tool"]).optional(),
16
- status: z
17
- .enum([
18
- "completed",
19
- "failed",
20
- "aborted",
21
- "queued",
22
- "running",
23
- "waiting_for_action",
24
- "rejected",
25
- "pending",
26
- "waiting_for_approval",
27
- "waiting_for_frontend",
28
- ])
29
- .optional(),
17
+ kind: z.enum(["chat", "background"]).optional(),
18
+ status: z.enum(["completed", "failed", "running"]).optional(),
30
19
  task: text,
31
20
  errorCode: text,
32
21
  search: z.string().trim().max(200).optional(),
33
22
  rating: z.enum(["up", "down"]).optional(),
34
23
  reason: z.enum(AI_USAGE_REASONS).optional(),
35
- sort: z.enum(["newest", "runs", "tokens", "credits", "errors", "negative", "negativeRate"]).default("runs"),
24
+ direction: z.enum(["asc", "desc"]).default("desc"),
25
+ sort: z.enum(["newest", "runs", "tokens", "cost", "errors", "negative", "negativeRate"]).default("runs"),
36
26
  page: z.coerce.number().int().min(1).max(1000000).default(1),
37
27
  perPage: z.coerce.number().int().min(1).max(100).default(50),
38
28
  });
@@ -9,21 +9,22 @@ const mergedTranslation = (catalog: AppPresentationCatalog, requestedLocale: str
9
9
  return canonical ? [[canonical, translation] as const] : [];
10
10
  }),
11
11
  );
12
- const overlays = localeFallbackChain(requestedLocale, baseLocale).filter((locale) => locale !== baseLocale).reverse();
13
- return overlays.reduce<AppPresentationTranslation>(
14
- (current, locale) => {
15
- const next = byLocale.get(locale);
16
- if (!next) return current;
17
- return {
18
- ...current,
19
- ...next,
20
- adminGroups: { ...current.adminGroups, ...next.adminGroups },
21
- adminLinks: { ...current.adminLinks, ...next.adminLinks },
22
- legalLinks: { ...current.legalLinks, ...next.legalLinks },
23
- };
24
- },
25
- {},
26
- );
12
+ const overlays = localeFallbackChain(requestedLocale, baseLocale)
13
+ .filter((locale) => locale !== baseLocale)
14
+ .reverse();
15
+ return overlays.reduce<AppPresentationTranslation>((current, locale) => {
16
+ const next = byLocale.get(locale);
17
+ if (!next) return current;
18
+ return {
19
+ ...current,
20
+ ...next,
21
+ adminGroups: { ...current.adminGroups, ...next.adminGroups },
22
+ adminLinks: { ...current.adminLinks, ...next.adminLinks },
23
+ legalLinks: { ...current.legalLinks, ...next.legalLinks },
24
+ searchLinks: { ...current.searchLinks, ...next.searchLinks },
25
+ searchLinkDescriptions: { ...current.searchLinkDescriptions, ...next.searchLinkDescriptions },
26
+ };
27
+ }, {});
27
28
  };
28
29
 
29
30
  /** Resolve only human presentation; stable app identity, routes, icons, and authorization stay untouched. */
@@ -40,6 +41,11 @@ export const resolveAppPresentation = <T extends AppMeta>(app: T, requestedLocal
40
41
  label: (group.id && translation.adminGroups?.[group.id]) || group.label,
41
42
  links: group.links.map((link) => ({ ...link, label: translation.adminLinks?.[link.href] ?? link.label })),
42
43
  })),
44
+ searchLinks: app.searchLinks?.map((link) => ({
45
+ ...link,
46
+ label: translation.searchLinks?.[link.href] ?? link.label,
47
+ description: translation.searchLinkDescriptions?.[link.href] ?? link.description,
48
+ })),
43
49
  legalLinks: app.legalLinks?.map((link) => ({ ...link, label: translation.legalLinks?.[link.href] ?? link.label })),
44
50
  };
45
51
  };
@@ -11,10 +11,17 @@ const catalog = i18n.define({
11
11
  catalogLimit: ({ max }: { max: number }) => `Capability catalog limit must be between 1 and ${max}`,
12
12
  cloudUnavailable: "Cloud is unavailable",
13
13
  deadlineExceeded: "The capability deadline was exceeded",
14
+ idempotencyConflict: "This Idempotency-Key was already used with different input",
15
+ idempotencyInProgress:
16
+ "An identical call with this Idempotency-Key is still running; verify the outcome with a Query before retrying",
14
17
  idempotencyInvalid: "Idempotency-Key is invalid",
15
18
  idempotencyNotAllowed: "This Action does not support idempotent retries; omit Idempotency-Key",
16
19
  idempotencyOnlyActions: "Idempotency-Key is only valid for Actions that require it",
17
20
  idempotencyRequired: "This Action requires an Idempotency-Key",
21
+ idempotencyResultNotRetained:
22
+ "This Action already succeeded, but its result was too large to retain; read the current state with a Query",
23
+ idempotencyUncertain:
24
+ "An earlier call with this Idempotency-Key ended with an unknown outcome; verify the current state with a Query",
18
25
  inputNotSerializable: "Capability input must be JSON-serializable",
19
26
  inputSchemaMismatch: "Capability input did not match the registered schema",
20
27
  invalidCapabilityError: ({ appId }: { appId: string }) => `App ${appId} returned an invalid capability error`,
@@ -39,10 +46,17 @@ const catalog = i18n.define({
39
46
  catalogLimit: ({ max }) => `Das Limit des Capability-Katalogs muss zwischen 1 und ${max} liegen`,
40
47
  cloudUnavailable: "Cloud ist nicht verfügbar",
41
48
  deadlineExceeded: "Das Zeitlimit der Capability wurde überschritten",
49
+ idempotencyConflict: "Dieser Idempotency-Key wurde bereits mit anderen Eingaben verwendet",
50
+ idempotencyInProgress:
51
+ "Ein identischer Aufruf mit diesem Idempotency-Key läuft noch; prüfe das Ergebnis mit einer Query, bevor du erneut aufrufst",
42
52
  idempotencyInvalid: "Der Idempotency-Key ist ungültig",
43
53
  idempotencyNotAllowed: "Diese Action unterstützt keine idempotenten Wiederholungen; Idempotency-Key weglassen",
44
54
  idempotencyOnlyActions: "Ein Idempotency-Key ist nur für Actions gültig, die ihn voraussetzen",
45
55
  idempotencyRequired: "Diese Action benötigt einen Idempotency-Key",
56
+ idempotencyResultNotRetained:
57
+ "Diese Action war bereits erfolgreich, ihr Ergebnis war jedoch zu groß zum Aufbewahren; lies den aktuellen Stand mit einer Query",
58
+ idempotencyUncertain:
59
+ "Ein früherer Aufruf mit diesem Idempotency-Key endete mit unbekanntem Ergebnis; prüfe den aktuellen Stand mit einer Query",
46
60
  inputNotSerializable: "Die Capability-Eingabe muss als JSON serialisierbar sein",
47
61
  inputSchemaMismatch: "Die Capability-Eingabe entspricht nicht dem registrierten Schema",
48
62
  invalidCapabilityError: ({ appId }) => `App ${appId} hat einen ungültigen Capability-Fehler zurückgegeben`,
@@ -1,7 +1,7 @@
1
- import { canonicalLocale, localeFallbackChain } from "./locale";
1
+ import { localeFallbackChain } from "./locale";
2
2
 
3
3
  export type HelpDocumentManifest = {
4
- /** Resolved content locale. Present on request-scoped manifests. */
4
+ /** Resolved content locale on central Help manifests. */
5
5
  locale?: string;
6
6
  id: string;
7
7
  title: string;
@@ -13,56 +13,20 @@ export type HelpDocumentManifest = {
13
13
  url: string;
14
14
  };
15
15
 
16
- export type HelpManifest = {
17
- manifestHash: string;
18
- pageBase: string;
19
- /** Base language that owns the complete logical article set. Absent on legacy registrations. */
20
- baseLocale?: string;
21
- /** Localized metadata variants keyed by canonical BCP 47 locale. */
22
- documentsByLocale?: Readonly<Record<string, readonly HelpDocumentManifest[]>>;
23
- documents: readonly HelpDocumentManifest[];
24
- };
25
-
26
16
  export type HelpSearchPayload = {
27
- /** Resolved request locale. Optional only for rolling compatibility with older Cloud hosts. */
28
- locale?: string;
17
+ /** Resolved request locale. */
18
+ locale: string;
29
19
  ids: string[];
30
20
  };
31
21
 
32
22
  export type HelpDocumentPayload = {
33
- /** Resolved request locale. Optional only for rolling compatibility with older Cloud hosts. */
34
- locale?: string;
23
+ /** Resolved request locale. */
24
+ locale: string;
35
25
  id: string;
36
26
  title: string;
37
27
  markdown: string;
38
28
  html: string;
39
29
  };
40
30
 
41
- export type ResolvedHelpManifest = Omit<HelpManifest, "documentsByLocale" | "documents"> & {
42
- locale: string;
43
- documents: readonly HelpDocumentManifest[];
44
- };
45
-
46
31
  /** Exact locale, then BCP 47 ancestors, then the corpus base locale. */
47
32
  export const helpLocaleChain = localeFallbackChain;
48
-
49
- /** Resolve localized Help metadata per article while preserving the base article set and order. */
50
- export const resolveHelpManifest = (manifest: HelpManifest, requestedLocale: string): ResolvedHelpManifest => {
51
- const baseLocale = canonicalLocale(manifest.baseLocale ?? "en") ?? "en";
52
- const variants = manifest.documentsByLocale ?? {};
53
- const chain = helpLocaleChain(requestedLocale, baseLocale);
54
- const selectedLocale = chain.find((locale) => locale === baseLocale || variants[locale] !== undefined) ?? baseLocale;
55
- const overlays = [...chain.slice(0, chain.indexOf(baseLocale)), baseLocale].reverse();
56
- const byId = new Map(manifest.documents.map((document) => [document.id, document]));
57
- for (const locale of overlays) {
58
- if (locale === baseLocale) continue;
59
- for (const document of variants[locale] ?? []) byId.set(document.id, { ...byId.get(document.id), ...document });
60
- }
61
- return {
62
- manifestHash: manifest.manifestHash,
63
- pageBase: manifest.pageBase,
64
- baseLocale,
65
- locale: selectedLocale,
66
- documents: manifest.documents.map((document) => ({ ...(byId.get(document.id) ?? document), locale: selectedLocale })),
67
- };
68
- };
@@ -52,3 +52,9 @@ export {
52
52
  } from "./template-rendering";
53
53
  export * from "./theme";
54
54
  export * from "./time";
55
+
56
+ export * from "./ai-quotas";
57
+
58
+ export type { AiChatQuotaSnapshot } from "./ai-quotas";
59
+
60
+ export * from "./ai-costs";
@@ -1,6 +1,6 @@
1
1
  import { AppWorkspace } from "@k2b/ui";
2
2
  import type { JSX } from "solid-js/jsx-runtime";
3
- import type { RailPreferences } from "../contracts/rail-preferences";
3
+ import type { RailSnapshot } from "../contracts/rail-preferences";
4
4
  import { getLocale } from "../server/locale";
5
5
  import type { LayoutAnnouncementsState } from "../server/middleware/settings";
6
6
  import AdminSidebar from "./AdminSidebar";
@@ -10,7 +10,7 @@ import { getLocalizedRuntimeContext, type RuntimeContext } from "./runtime";
10
10
 
11
11
  type Breadcrumb = { title: string; href?: string };
12
12
  type AdminLayoutContext = {
13
- get(key: "railPreferences"): RailPreferences | undefined;
13
+ get(key: "railPreferences"): RailSnapshot | undefined;
14
14
  get(key: "user"): any;
15
15
  get(key: "page"): any;
16
16
  get(key: "runtime"): RuntimeContext;
@@ -25,7 +25,9 @@ type Props = {
25
25
  /** Disable when a bounded child such as SettingsPage owns scrolling. */
26
26
  scroll?: boolean;
27
27
  };
28
- export default function AdminLayout({ children, c, title, scroll = true }: Props) {
28
+ export default function AdminLayout(props: Props) {
29
+ // Keep children lazy so Layout installs the request locale before rendering them.
30
+ const { c, title, scroll = true } = props;
29
31
  const locale = getLocale(c);
30
32
  const t = platformMessages.resolve([locale]).t;
31
33
  const url = new URL(c.req.raw.url);
@@ -40,11 +42,11 @@ export default function AdminLayout({ children, c, title, scroll = true }: Props
40
42
  }
41
43
  return (
42
44
  <Layout c={c} fullWidth fullPage title={breadcrumbs}>
43
- <AppWorkspace class="min-h-0 flex-1" resizable={false}>
45
+ <AppWorkspace mobileSurface="flush" class="min-h-0 flex-1" resizable={false}>
44
46
  <AdminSidebar currentPath={currentPath} apps={runtime.apps} />
45
47
  <AppWorkspace.Content>
46
48
  <AppWorkspace.Main scroll={scroll} class={scroll ? "p-[var(--ui-space-shell)]" : undefined}>
47
- {children}
49
+ {props.children}
48
50
  </AppWorkspace.Main>
49
51
  </AppWorkspace.Content>
50
52
  </AppWorkspace>
@@ -1,3 +1,4 @@
1
+ import WorkspaceNavigation from "./WorkspaceNavigation.island";
1
2
  import { AppWorkspace, useLocale } from "@k2b/ui";
2
3
  import { activeAdminHref } from "./admin-active-link";
3
4
  import { type AdminLink, buildAdminGroups } from "./admin-navigation";
@@ -31,20 +32,33 @@ export default function AdminSidebar({ currentPath, apps }: { currentPath: strin
31
32
  const groups = () => buildAdminGroups(apps, locale());
32
33
 
33
34
  return (
34
- <AppWorkspace.Sidebar resizable={false}>
35
- <AppWorkspace.SidebarMobileTrigger label={t().admin} />
36
-
37
- <AppWorkspace.SidebarMobile>
38
- <AppWorkspace.SidebarMobileBody scrollPreserveKey="admin-sidebar-mobile">
39
- <AdminNavigation currentPath={currentPath} groups={groups()} />
40
- </AppWorkspace.SidebarMobileBody>
41
- </AppWorkspace.SidebarMobile>
42
-
43
- <AppWorkspace.SidebarDesktop>
44
- <AppWorkspace.SidebarBody scrollPreserveKey="admin-sidebar">
45
- <AdminNavigation currentPath={currentPath} groups={groups()} />
46
- </AppWorkspace.SidebarBody>
47
- </AppWorkspace.SidebarDesktop>
48
- </AppWorkspace.Sidebar>
35
+ <>
36
+ <WorkspaceNavigation
37
+ label={t().admin}
38
+ items={groups().map((group) => ({
39
+ id: group.label,
40
+ label: group.label,
41
+ children: group.links.map((link) => ({
42
+ id: link.href,
43
+ label: link.label,
44
+ href: link.href,
45
+ icon: `ti ${link.icon}`,
46
+ active:
47
+ link.href ===
48
+ activeAdminHref(
49
+ currentPath,
50
+ groups().flatMap((entry) => entry.links.map((entry) => entry.href)),
51
+ ),
52
+ })),
53
+ }))}
54
+ />
55
+ <AppWorkspace.Sidebar resizable={false}>
56
+ <AppWorkspace.SidebarDesktop>
57
+ <AppWorkspace.SidebarBody scrollPreserveKey="admin-sidebar">
58
+ <AdminNavigation currentPath={currentPath} groups={groups()} />
59
+ </AppWorkspace.SidebarBody>
60
+ </AppWorkspace.SidebarDesktop>
61
+ </AppWorkspace.Sidebar>
62
+ </>
49
63
  );
50
64
  }
@@ -1,42 +1,9 @@
1
- import { dialogCore, IconButton, ScrollArea, Tooltip, useLocale } from "@k2b/ui";
2
- import { createEffect, For, Show } from "solid-js";
1
+ import { AppLaunchpadPanel, type AppLaunchpadApp, type AppLaunchpadLegalLink, type AppLaunchpadContext } from "./AppLaunchpadPanel";
2
+ import { openCloudMobileMenu } from "./MobileNavigation";
3
+ export type { AppLaunchpadApp, AppLaunchpadLegalLink } from "./AppLaunchpadPanel";
4
+ import { dialogCore, IconButton, Tooltip, useLocale } from "@k2b/ui";
5
+ import { createEffect } from "solid-js";
3
6
  import { platformMessages } from "./platform-messages";
4
- import { openRailEditor } from "./RailEditor";
5
- import { readRailContext } from "./rail-context";
6
- import { railMessages } from "./rail-messages";
7
- import { projectRailNavigation } from "./rail-navigation";
8
-
9
- export type AppLaunchpadApp = {
10
- id: string;
11
- iconClass: string;
12
- label: string;
13
- href: string;
14
- description?: string;
15
- accent?: string;
16
- };
17
-
18
- export type AppLaunchpadLegalLink = {
19
- label: string;
20
- href: string;
21
- icon?: string;
22
- };
23
-
24
- type AppLaunchpadContext = {
25
- apps: AppLaunchpadApp[];
26
- legalLinks: AppLaunchpadLegalLink[];
27
- };
28
-
29
- type AppIconPaletteEntry = { from: string };
30
-
31
- const appIconPalette: readonly [AppIconPaletteEntry, ...AppIconPaletteEntry[]] = [
32
- { from: "#2563eb" },
33
- { from: "#059669" },
34
- { from: "#7c3aed" },
35
- { from: "#d97706" },
36
- { from: "#e11d48" },
37
- { from: "#0891b2" },
38
- { from: "#52525b" },
39
- ];
40
7
 
41
8
  declare global {
42
9
  interface Window {
@@ -47,17 +14,6 @@ declare global {
47
14
  }
48
15
  }
49
16
 
50
- const paletteForId = (id: string) => {
51
- let hash = 0;
52
- for (let i = 0; i < id.length; i++) hash = (hash + id.charCodeAt(i)) % appIconPalette.length;
53
- return appIconPalette[hash] ?? appIconPalette[0];
54
- };
55
-
56
- const appIconStyle = (app: AppLaunchpadApp) => {
57
- const tone = /^#[0-9a-f]{6}$/i.test(app.accent ?? "") ? app.accent! : paletteForId(app.id).from;
58
- return `--app-icon-color:${tone}`;
59
- };
60
-
61
17
  const readEmbeddedContext = (): AppLaunchpadContext | undefined => {
62
18
  if (typeof document === "undefined") return undefined;
63
19
  const element = document.getElementById("cloud-app-launchpad-data");
@@ -69,6 +25,7 @@ const readEmbeddedContext = (): AppLaunchpadContext | undefined => {
69
25
  if (!Array.isArray(parsed.apps)) return undefined;
70
26
  return {
71
27
  apps: parsed.apps,
28
+ profile: parsed.profile?.name && (parsed.profile.theme === "dark" || parsed.profile.theme === "light") ? parsed.profile : undefined,
72
29
  legalLinks: Array.isArray(parsed.legalLinks) ? parsed.legalLinks : [],
73
30
  };
74
31
  } catch {
@@ -76,99 +33,28 @@ const readEmbeddedContext = (): AppLaunchpadContext | undefined => {
76
33
  }
77
34
  };
78
35
 
79
- const AppLaunchpadPanel = (props: AppLaunchpadContext & { close: () => void }) => {
80
- const locale = useLocale();
81
- const t = () => railMessages.resolve([locale()]).t;
82
- const rail = readRailContext();
83
- const shortcuts = () => (rail ? projectRailNavigation(rail.apps, rail.settings, locale()).shortcuts : []);
84
- return (
85
- <ScrollArea class="launchpad-panel mx-auto max-h-[min(86vh,var(--ui-dialog-available-height))] w-[var(--ui-dialog-available-width)] max-w-[var(--ui-dialog-available-width)] overscroll-contain p-4 text-primary sm:w-fit sm:p-6 md:p-7 dark:text-white">
86
- <Show when={rail}>
87
- <div class="mb-2 flex justify-end">
88
- <IconButton
89
- variant="ghost"
90
- size="sm"
91
- label={t().customize}
92
- tooltip={t().customize}
93
- tooltipDelay={0}
94
- onClick={() => {
95
- props.close();
96
- openRailEditor(locale());
97
- }}
98
- >
99
- <i class="ti ti-adjustments-horizontal" aria-hidden="true" />
100
- </IconButton>
101
- </div>
102
- </Show>
103
- <Show when={shortcuts().length > 0}>
104
- <section class="mb-5 flex flex-col gap-3" aria-label={t().shortcuts}>
105
- <h3 class="text-center text-sm font-medium">{t().shortcuts}</h3>
106
- <div class="flex flex-wrap justify-center gap-2">
107
- <For each={shortcuts()}>
108
- {(shortcut) => (
109
- <a
110
- href={shortcut.href}
111
- class="inline-flex items-center gap-2 rounded-lg border border-[var(--ui-border)] px-3 py-2 text-sm"
112
- >
113
- <i class={shortcut.iconClass} aria-hidden="true" />
114
- {shortcut.label}
115
- </a>
116
- )}
117
- </For>
118
- </div>
119
- </section>
120
- </Show>
121
- <div class="flex flex-wrap justify-center gap-x-4 gap-y-4 sm:gap-x-7 sm:gap-y-6">
122
- <For each={props.apps}>
123
- {(app) => (
124
- <a
125
- href={app.href}
126
- class="group flex w-[4.75rem] min-w-0 flex-col items-center gap-1.5 rounded-2xl p-1 text-center outline-none focus-visible:ring-2 focus-visible:ring-white/60 sm:w-[6.25rem] sm:gap-2 sm:p-2"
127
- >
128
- <span
129
- class="app-icon grid h-12 w-12 place-items-center rounded-[0.95rem] text-[1.25rem] sm:h-16 sm:w-16 sm:rounded-[1.25rem] sm:text-[1.7rem]"
130
- style={appIconStyle(app)}
131
- >
132
- <i class={app.iconClass} />
133
- </span>
134
- <span class="max-w-full truncate text-[11px] font-medium text-primary sm:text-xs dark:text-white">{app.label}</span>
135
- </a>
136
- )}
137
- </For>
138
- </div>
139
- <Show when={props.legalLinks.length > 0}>
140
- <div class="mt-7 flex flex-wrap justify-center text-[11px] text-dimmed dark:text-white/56">
141
- <For each={props.legalLinks}>
142
- {(link) => (
143
- <a
144
- href={link.href}
145
- class="inline-flex items-center gap-1.5 rounded-md px-2 py-1 transition-colors hover:text-primary dark:hover:text-white"
146
- >
147
- <i class={link.icon ?? "ti ti-file-text"} />
148
- {link.label}
149
- </a>
150
- )}
151
- </For>
152
- </div>
153
- </Show>
154
- </ScrollArea>
155
- );
156
- };
157
-
158
- export function setAppLaunchpadContext(apps: AppLaunchpadApp[], legalLinks: AppLaunchpadLegalLink[] = []) {
36
+ export function setAppLaunchpadContext(
37
+ apps: AppLaunchpadApp[],
38
+ legalLinks: AppLaunchpadLegalLink[] = [],
39
+ profile?: AppLaunchpadContext["profile"],
40
+ ) {
159
41
  if (typeof window === "undefined") return;
160
- window.__cloudAppLaunchpad = { apps, legalLinks };
42
+ window.__cloudAppLaunchpad = { apps, legalLinks, profile };
161
43
  window.cloud ??= {};
162
44
  window.cloud.openAppLaunchpad = () => {
163
45
  openAppLaunchpad();
164
46
  };
165
47
  }
166
48
 
167
- export function openAppLaunchpad(apps?: AppLaunchpadApp[], legalLinks?: AppLaunchpadLegalLink[]) {
49
+ export function openAppLaunchpad(apps?: AppLaunchpadApp[], legalLinks?: AppLaunchpadLegalLink[], profile?: AppLaunchpadContext["profile"]) {
168
50
  if (typeof window === "undefined") return;
169
- const context = apps ? { apps, legalLinks: legalLinks ?? [] } : (window.__cloudAppLaunchpad ?? readEmbeddedContext());
170
- if (!context || context.apps.length === 0) return;
51
+ const context = apps ? { apps, legalLinks: legalLinks ?? [], profile } : (window.__cloudAppLaunchpad ?? readEmbeddedContext());
52
+ if (!context) return;
171
53
  window.__cloudAppLaunchpad = context;
54
+ if (window.matchMedia("(max-width: 1023px)").matches) {
55
+ void openCloudMobileMenu(context);
56
+ return;
57
+ }
172
58
  void dialogCore.open<void>((close) => <AppLaunchpadPanel apps={context.apps} legalLinks={context.legalLinks} close={close} />, {
173
59
  panelClassName: "k2b-dialog k2b-dialog--large is-bare",
174
60
  contentClassName: "k2b-dialog__viewport is-bare",
@@ -178,7 +64,7 @@ export function openAppLaunchpad(apps?: AppLaunchpadApp[], legalLinks?: AppLaunc
178
64
 
179
65
  export function AppLaunchpadProvider(props: AppLaunchpadContext) {
180
66
  createEffect(() => {
181
- setAppLaunchpadContext(props.apps, props.legalLinks);
67
+ setAppLaunchpadContext(props.apps, props.legalLinks, props.profile);
182
68
  });
183
69
 
184
70
  return <span class="hidden" data-cloud-app-launchpad-provider />;
@@ -187,13 +73,14 @@ export function AppLaunchpadProvider(props: AppLaunchpadContext) {
187
73
  export function AppLaunchpadButton(props: AppLaunchpadContext & { variant: "rail" | "header" | "menu"; label?: string }) {
188
74
  const locale = useLocale();
189
75
  const t = () => platformMessages.resolve([locale()]).t;
190
- const open = () => openAppLaunchpad(props.apps, props.legalLinks);
76
+ const open = () => openAppLaunchpad(props.apps, props.legalLinks, props.profile);
191
77
 
192
78
  if (props.variant === "rail") {
193
79
  return (
194
80
  <Tooltip.Trigger
195
81
  type="button"
196
82
  class="rail-item shrink-0"
83
+ data-cloud-launchpad
197
84
  content={props.label ?? t().apps}
198
85
  placement="right"
199
86
  delay={0}
@@ -227,10 +114,10 @@ export function AppLaunchpadButton(props: AppLaunchpadContext & { variant: "rail
227
114
 
228
115
  export function AppLaunchpad(props: AppLaunchpadContext & { variant?: "provider" | "rail" | "header" | "menu"; label?: string }) {
229
116
  if (!props.variant || props.variant === "provider") {
230
- return <AppLaunchpadProvider apps={props.apps} legalLinks={props.legalLinks} />;
117
+ return <AppLaunchpadProvider apps={props.apps} legalLinks={props.legalLinks} profile={props.profile} />;
231
118
  }
232
119
 
233
- return <AppLaunchpadButton apps={props.apps} legalLinks={props.legalLinks} variant={props.variant} label={props.label} />;
120
+ return <AppLaunchpadButton apps={props.apps} legalLinks={props.legalLinks} profile={props.profile} variant={props.variant} label={props.label} />;
234
121
  }
235
122
 
236
123
  export default AppLaunchpad;