@k2b/cloud 0.6.0-rc.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 (540) hide show
  1. package/README.md +98 -0
  2. package/package.json +128 -0
  3. package/public/logo.svg +1 -0
  4. package/scripts/app-favicon.test.ts +49 -0
  5. package/scripts/app-favicon.ts +51 -0
  6. package/scripts/build.ts +205 -0
  7. package/scripts/preload.ts +164 -0
  8. package/scripts/sync-recovery-smoke.ts +200 -0
  9. package/src/_internal/app-favicon.ts +2 -0
  10. package/src/_internal/app-presentation.ts +61 -0
  11. package/src/_internal/app-workspace-state.ts +24 -0
  12. package/src/_internal/bounded-json.ts +47 -0
  13. package/src/_internal/build-metadata.ts +9 -0
  14. package/src/_internal/capabilities.ts +1118 -0
  15. package/src/_internal/define-app.ts +801 -0
  16. package/src/_internal/heartbeat.ts +167 -0
  17. package/src/_internal/help-catalog.ts +241 -0
  18. package/src/_internal/help.ts +95 -0
  19. package/src/_internal/nats-connection.ts +34 -0
  20. package/src/_internal/page-responses.ts +40 -0
  21. package/src/_internal/process-sync.ts +117 -0
  22. package/src/_internal/readiness.ts +3 -0
  23. package/src/_internal/registry-validation.ts +136 -0
  24. package/src/_internal/registry-watch.ts +52 -0
  25. package/src/_internal/registry.ts +338 -0
  26. package/src/_internal/runtime-compatibility.ts +64 -0
  27. package/src/_internal/runtime-context.ts +84 -0
  28. package/src/_internal/runtime-watcher.ts +78 -0
  29. package/src/_internal/static-assets.ts +86 -0
  30. package/src/_internal/status-preserving-ssr.ts +49 -0
  31. package/src/access/PermissionEditor.tsx +486 -0
  32. package/src/access/ResourceApiKeys.tsx +271 -0
  33. package/src/access/messages.ts +121 -0
  34. package/src/access/ui.ts +16 -0
  35. package/src/account/Avatar.tsx +28 -0
  36. package/src/account/EntitySearch.tsx +363 -0
  37. package/src/account/avatar-upload-dialog.tsx +197 -0
  38. package/src/account/avatar-upload.ts +149 -0
  39. package/src/account/ui.ts +3 -0
  40. package/src/ai/admin.ts +47 -0
  41. package/src/ai/approval-routes.ts +130 -0
  42. package/src/ai/approvals.ts +139 -0
  43. package/src/ai/assistant-models.ts +61 -0
  44. package/src/ai/attachments.ts +40 -0
  45. package/src/ai/browser.ts +92 -0
  46. package/src/ai/capabilities.ts +714 -0
  47. package/src/ai/capability-execution.ts +151 -0
  48. package/src/ai/chat/blocks.render.test.tsx +1108 -0
  49. package/src/ai/chat/blocks.tsx +730 -0
  50. package/src/ai/chat/builtin-tools.tsx +428 -0
  51. package/src/ai/chat/capability-table.tsx +87 -0
  52. package/src/ai/chat/composer-adapter.ts +169 -0
  53. package/src/ai/chat/file-tools.tsx +81 -0
  54. package/src/ai/chat/message-actions.tsx +370 -0
  55. package/src/ai/chat/message-utils.ts +479 -0
  56. package/src/ai/chat/messages.ts +43 -0
  57. package/src/ai/chat/presentation.tsx +395 -0
  58. package/src/ai/chat/primitives.tsx +3 -0
  59. package/src/ai/chat/tool-disclosure.tsx +41 -0
  60. package/src/ai/chat/turn-navigator-utils.ts +28 -0
  61. package/src/ai/chat/turn-navigator.tsx +164 -0
  62. package/src/ai/chat/user-message.tsx +149 -0
  63. package/src/ai/chat/visual-tools.tsx +435 -0
  64. package/src/ai/chat/web-tools.tsx +202 -0
  65. package/src/ai/chat-task-contracts.ts +113 -0
  66. package/src/ai/chat-tasks.ts +952 -0
  67. package/src/ai/client/controller.ts +1211 -0
  68. package/src/ai/client/file-source.ts +95 -0
  69. package/src/ai/client/live-connection.ts +140 -0
  70. package/src/ai/client/projection.ts +178 -0
  71. package/src/ai/client/transport.ts +111 -0
  72. package/src/ai/compaction.ts +182 -0
  73. package/src/ai/credentials.ts +124 -0
  74. package/src/ai/default-tool-contracts.ts +108 -0
  75. package/src/ai/default-tools.ts +127 -0
  76. package/src/ai/enrich.ts +280 -0
  77. package/src/ai/executor.ts +1624 -0
  78. package/src/ai/fetch-file-tool.ts +313 -0
  79. package/src/ai/file-context.ts +92 -0
  80. package/src/ai/file-media-type.ts +28 -0
  81. package/src/ai/file-mount.ts +16 -0
  82. package/src/ai/file-tools.ts +566 -0
  83. package/src/ai/files-store.ts +390 -0
  84. package/src/ai/firecrawl-tools.ts +305 -0
  85. package/src/ai/grids-skill.ts +126 -0
  86. package/src/ai/http.ts +265 -0
  87. package/src/ai/index.ts +337 -0
  88. package/src/ai/kit-skill.ts +22 -0
  89. package/src/ai/limits.ts +5 -0
  90. package/src/ai/live-events.ts +289 -0
  91. package/src/ai/live-messages.ts +45 -0
  92. package/src/ai/live-outbox.ts +102 -0
  93. package/src/ai/live-routes.ts +412 -0
  94. package/src/ai/live.ts +2 -0
  95. package/src/ai/maintenance.ts +302 -0
  96. package/src/ai/markdown-pdf-tool.ts +95 -0
  97. package/src/ai/memories.ts +605 -0
  98. package/src/ai/memory-learning-runs.ts +222 -0
  99. package/src/ai/memory-learning.ts +619 -0
  100. package/src/ai/memory-tool.ts +120 -0
  101. package/src/ai/memory-workflow-evidence.ts +158 -0
  102. package/src/ai/migrate.ts +2082 -0
  103. package/src/ai/model-access-migrate.ts +30 -0
  104. package/src/ai/model-access.ts +191 -0
  105. package/src/ai/personal-agent.ts +6 -0
  106. package/src/ai/prefs.ts +72 -0
  107. package/src/ai/project-tool.ts +94 -0
  108. package/src/ai/projects-routes.ts +277 -0
  109. package/src/ai/projects.ts +902 -0
  110. package/src/ai/protocol.ts +279 -0
  111. package/src/ai/provider.ts +73 -0
  112. package/src/ai/public-projection.ts +25 -0
  113. package/src/ai/resource-markers.ts +31 -0
  114. package/src/ai/resource-refs.ts +70 -0
  115. package/src/ai/routes.ts +1131 -0
  116. package/src/ai/runtime-tools.ts +39 -0
  117. package/src/ai/runtime.ts +544 -0
  118. package/src/ai/settings.ts +529 -0
  119. package/src/ai/short-id.ts +45 -0
  120. package/src/ai/skill-catalog.ts +73 -0
  121. package/src/ai/skill-format.ts +145 -0
  122. package/src/ai/skill-seeds.ts +430 -0
  123. package/src/ai/skill-tool.ts +81 -0
  124. package/src/ai/skills-routes.ts +143 -0
  125. package/src/ai/skills.ts +934 -0
  126. package/src/ai/solid.ts +27 -0
  127. package/src/ai/store.ts +3113 -0
  128. package/src/ai/stream.ts +308 -0
  129. package/src/ai/structured-runs.ts +66 -0
  130. package/src/ai/structured.ts +186 -0
  131. package/src/ai/system-prompt.ts +170 -0
  132. package/src/ai/task-prompt.ts +22 -0
  133. package/src/ai/timeline.ts +148 -0
  134. package/src/ai/tool-audit.ts +177 -0
  135. package/src/ai/tool-result-budget.ts +16 -0
  136. package/src/ai/tools.ts +155 -0
  137. package/src/ai/types.ts +967 -0
  138. package/src/ai/ui.tsx +39 -0
  139. package/src/ai/usage-migrate.ts +81 -0
  140. package/src/ai/usage.ts +315 -0
  141. package/src/ai/validate.ts +54 -0
  142. package/src/ai/vision-tool.ts +79 -0
  143. package/src/api/accounts-entities.ts +174 -0
  144. package/src/api/admin-ai-projects.ts +74 -0
  145. package/src/api/admin-ai-skills.ts +76 -0
  146. package/src/api/admin-ai-usage.ts +42 -0
  147. package/src/api/admin-core-settings.ts +508 -0
  148. package/src/api/admin-identity.ts +148 -0
  149. package/src/api/admin-lifecycle.ts +206 -0
  150. package/src/api/admin-linux-identities.ts +66 -0
  151. package/src/api/announcements.ts +131 -0
  152. package/src/api/app-approval.ts +129 -0
  153. package/src/api/apps.ts +51 -0
  154. package/src/api/auth/schemas.ts +54 -0
  155. package/src/api/auth.ts +370 -0
  156. package/src/api/capabilities.ts +658 -0
  157. package/src/api/help-messages.ts +19 -0
  158. package/src/api/help.ts +78 -0
  159. package/src/api/index.ts +108 -0
  160. package/src/api/mcp.ts +744 -0
  161. package/src/api/me-rail.ts +45 -0
  162. package/src/api/me.ts +753 -0
  163. package/src/api/search/schemas.ts +79 -0
  164. package/src/api/search.bench.ts +185 -0
  165. package/src/api/search.ts +407 -0
  166. package/src/api/sync-ops.ts +79 -0
  167. package/src/api/widgets.ts +143 -0
  168. package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
  169. package/src/browser/CloudResourceSearch.tsx +791 -0
  170. package/src/browser/app-approval-vault.ts +305 -0
  171. package/src/browser/app-approval.ts +234 -0
  172. package/src/browser/live-websocket.ts +268 -0
  173. package/src/browser/locale-preference.ts +25 -0
  174. package/src/browser/notification-target.ts +27 -0
  175. package/src/browser/notification-timeout.ts +14 -0
  176. package/src/browser/notifications.ts +181 -0
  177. package/src/browser/resource-clipboard.ts +70 -0
  178. package/src/browser/resource-picker.tsx +39 -0
  179. package/src/browser/resource-search-messages.ts +55 -0
  180. package/src/browser/resource-search.ts +27 -0
  181. package/src/capabilities/client.ts +149 -0
  182. package/src/capabilities/response.ts +39 -0
  183. package/src/capabilities/server.ts +215 -0
  184. package/src/capabilities/signals.ts +4 -0
  185. package/src/capabilities/testing.ts +12 -0
  186. package/src/capabilities/types.ts +32 -0
  187. package/src/cli/access.ts +499 -0
  188. package/src/cli/account.ts +382 -0
  189. package/src/cli/admin/account-administration.ts +45 -0
  190. package/src/cli/admin/account-categories.ts +49 -0
  191. package/src/cli/admin/ai-usage.ts +118 -0
  192. package/src/cli/admin/app-sign-in.ts +49 -0
  193. package/src/cli/admin/data.ts +343 -0
  194. package/src/cli/admin/documentation.ts +34 -0
  195. package/src/cli/admin/gateway.ts +155 -0
  196. package/src/cli/admin/index.ts +61 -0
  197. package/src/cli/admin/instance.ts +250 -0
  198. package/src/cli/admin/jobs.ts +223 -0
  199. package/src/cli/admin/legal.ts +122 -0
  200. package/src/cli/admin/linux.ts +53 -0
  201. package/src/cli/admin/logs.ts +324 -0
  202. package/src/cli/admin/metrics.ts +142 -0
  203. package/src/cli/admin/nats.ts +167 -0
  204. package/src/cli/admin/notifications.ts +516 -0
  205. package/src/cli/admin/shared.ts +113 -0
  206. package/src/cli/admin/sync.ts +188 -0
  207. package/src/cli/admin/telemetry.ts +314 -0
  208. package/src/cli/admin/webhooks.ts +222 -0
  209. package/src/cli/admin/workflows.ts +367 -0
  210. package/src/cli/apps.ts +40 -0
  211. package/src/cli/capabilities.ts +239 -0
  212. package/src/cli/commands.ts +604 -0
  213. package/src/cli/index.ts +61 -0
  214. package/src/cli/locale.ts +31 -0
  215. package/src/cli/output.ts +58 -0
  216. package/src/clients/core.ts +20 -0
  217. package/src/config/env.ts +54 -0
  218. package/src/config/index.ts +6 -0
  219. package/src/contracts/account-categories.ts +62 -0
  220. package/src/contracts/announcements.ts +121 -0
  221. package/src/contracts/app-approval.ts +197 -0
  222. package/src/contracts/app.ts +162 -0
  223. package/src/contracts/capabilities.ts +484 -0
  224. package/src/contracts/index.ts +14 -0
  225. package/src/contracts/notification-live.ts +66 -0
  226. package/src/contracts/notification-types.ts +235 -0
  227. package/src/contracts/posix.ts +69 -0
  228. package/src/contracts/profile.ts +85 -0
  229. package/src/contracts/rail-preferences.ts +53 -0
  230. package/src/contracts/registry.ts +110 -0
  231. package/src/contracts/resource-clipboard.ts +33 -0
  232. package/src/contracts/settings-types.ts +98 -0
  233. package/src/contracts/shared.ts +408 -0
  234. package/src/contracts/user-notifications.ts +101 -0
  235. package/src/contracts/widgets.ts +183 -0
  236. package/src/desktop/index.ts +704 -0
  237. package/src/desktop/solid.tsx +938 -0
  238. package/src/index.ts +58 -0
  239. package/src/server/actor.ts +41 -0
  240. package/src/server/api/index.ts +2 -0
  241. package/src/server/api/respond.ts +96 -0
  242. package/src/server/api-client.ts +54 -0
  243. package/src/server/app-context.ts +39 -0
  244. package/src/server/help.ts +187 -0
  245. package/src/server/index.ts +89 -0
  246. package/src/server/locale.ts +55 -0
  247. package/src/server/middleware/auth.ts +386 -0
  248. package/src/server/middleware/index.ts +28 -0
  249. package/src/server/middleware/invocation.ts +55 -0
  250. package/src/server/middleware/middleware.ts +65 -0
  251. package/src/server/middleware/openapi.ts +126 -0
  252. package/src/server/middleware/rate-limit.ts +125 -0
  253. package/src/server/middleware/request-logger.ts +41 -0
  254. package/src/server/middleware/route-template.ts +46 -0
  255. package/src/server/middleware/runtime.ts +22 -0
  256. package/src/server/middleware/settings.ts +55 -0
  257. package/src/server/middleware/validator.ts +49 -0
  258. package/src/server/middleware/workload.ts +22 -0
  259. package/src/server/ratelimit.ts +151 -0
  260. package/src/server/request-cookies.ts +18 -0
  261. package/src/server/services/access.ts +644 -0
  262. package/src/server/services/freeipa/client.ts +137 -0
  263. package/src/server/services/freeipa/index.ts +9 -0
  264. package/src/server/services/freeipa/session.ts +123 -0
  265. package/src/server/services/freeipa/test-certificates.ts +44 -0
  266. package/src/server/services/freeipa/tls.ts +44 -0
  267. package/src/server/services/freeipa/transport.ts +116 -0
  268. package/src/server/services/freeipa/util.ts +59 -0
  269. package/src/server/services/geo.ts +198 -0
  270. package/src/server/services/index.ts +48 -0
  271. package/src/server/services/pagination.ts +22 -0
  272. package/src/server/services/services.ts +13 -0
  273. package/src/server/time.ts +33 -0
  274. package/src/services/account-category-policy.ts +51 -0
  275. package/src/services/account-lifecycle/audit.ts +41 -0
  276. package/src/services/account-lifecycle/index.ts +940 -0
  277. package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
  278. package/src/services/account-lifecycle/notification-sender.ts +13 -0
  279. package/src/services/account-lifecycle/scheduler.ts +506 -0
  280. package/src/services/account-model.ts +22 -0
  281. package/src/services/accounts/app.ts +1671 -0
  282. package/src/services/accounts/authz.ts +44 -0
  283. package/src/services/accounts/avatar.ts +120 -0
  284. package/src/services/accounts/base-group.ts +11 -0
  285. package/src/services/accounts/base-user.ts +46 -0
  286. package/src/services/accounts/duplicate-emails.ts +82 -0
  287. package/src/services/accounts/entities.ts +622 -0
  288. package/src/services/accounts/group-sql.ts +106 -0
  289. package/src/services/accounts/groups.ts +272 -0
  290. package/src/services/accounts/index.ts +14 -0
  291. package/src/services/accounts/ipa-data.ts +66 -0
  292. package/src/services/accounts/lifecycle.ts +2 -0
  293. package/src/services/accounts/local-groups.ts +499 -0
  294. package/src/services/accounts/model.ts +129 -0
  295. package/src/services/accounts/notification-sender.ts +31 -0
  296. package/src/services/accounts/posix.ts +386 -0
  297. package/src/services/accounts/request-policy.ts +11 -0
  298. package/src/services/accounts/switching.ts +126 -0
  299. package/src/services/accounts/users.ts +745 -0
  300. package/src/services/announcements/index.ts +224 -0
  301. package/src/services/app-approval.ts +498 -0
  302. package/src/services/audit/index.ts +438 -0
  303. package/src/services/auth-flows/index.ts +15 -0
  304. package/src/services/auth-flows/ipa.ts +197 -0
  305. package/src/services/auth-flows/magic-link.ts +185 -0
  306. package/src/services/auth-flows/notification-sender.ts +10 -0
  307. package/src/services/auth-flows/password-reset.ts +268 -0
  308. package/src/services/auth-flows/proxy-return.ts +49 -0
  309. package/src/services/document-extraction.ts +148 -0
  310. package/src/services/freeipa-config.ts +179 -0
  311. package/src/services/gateway.ts +186 -0
  312. package/src/services/identity/constants.ts +18 -0
  313. package/src/services/identity/index.ts +52 -0
  314. package/src/services/identity/invocation-actor.ts +120 -0
  315. package/src/services/identity/invocation-authority.ts +53 -0
  316. package/src/services/identity/invocation-operations.ts +15 -0
  317. package/src/services/identity/invocation-token.ts +276 -0
  318. package/src/services/identity/key-config.ts +41 -0
  319. package/src/services/identity/key-ring.ts +530 -0
  320. package/src/services/identity/metrics.ts +29 -0
  321. package/src/services/identity/routes.ts +18 -0
  322. package/src/services/identity/runtime-config.ts +88 -0
  323. package/src/services/identity/session-token.ts +152 -0
  324. package/src/services/identity/signer-refresh.ts +44 -0
  325. package/src/services/identity/workload-auth.ts +64 -0
  326. package/src/services/index.ts +187 -0
  327. package/src/services/ipa/auth.ts +216 -0
  328. package/src/services/ipa/connection.ts +57 -0
  329. package/src/services/ipa/effective-groups.ts +70 -0
  330. package/src/services/ipa/groups.ts +722 -0
  331. package/src/services/ipa/guard.ts +17 -0
  332. package/src/services/ipa/index.ts +17 -0
  333. package/src/services/ipa/posix.ts +19 -0
  334. package/src/services/ipa/profile.ts +132 -0
  335. package/src/services/ipa/runtime.ts +28 -0
  336. package/src/services/ipa/search.ts +154 -0
  337. package/src/services/ipa/service-account.ts +15 -0
  338. package/src/services/ipa/sync-planning.ts +100 -0
  339. package/src/services/ipa/sync.ts +862 -0
  340. package/src/services/ipa/users.ts +806 -0
  341. package/src/services/legal-consent.ts +76 -0
  342. package/src/services/logging/index.ts +477 -0
  343. package/src/services/logging/redaction.ts +20 -0
  344. package/src/services/logging/trace.ts +1231 -0
  345. package/src/services/mandates/index.ts +855 -0
  346. package/src/services/mandates/policy.ts +85 -0
  347. package/src/services/network-security.ts +31 -0
  348. package/src/services/notifications/batches.ts +722 -0
  349. package/src/services/notifications/browser.ts +252 -0
  350. package/src/services/notifications/catalog.ts +113 -0
  351. package/src/services/notifications/channels.ts +104 -0
  352. package/src/services/notifications/dispatcher.ts +139 -0
  353. package/src/services/notifications/email.ts +118 -0
  354. package/src/services/notifications/index.ts +633 -0
  355. package/src/services/notifications/live.ts +50 -0
  356. package/src/services/notifications/observability.ts +407 -0
  357. package/src/services/notifications/platform.ts +421 -0
  358. package/src/services/notifications/runtime.ts +120 -0
  359. package/src/services/notifications/user.ts +248 -0
  360. package/src/services/notifications/web-push-transport.ts +135 -0
  361. package/src/services/oauth-tokens.ts +209 -0
  362. package/src/services/outbox.ts +187 -0
  363. package/src/services/pdf/gotenberg.ts +290 -0
  364. package/src/services/pdf/index.ts +43 -0
  365. package/src/services/pdf/markdown.ts +209 -0
  366. package/src/services/pdf/template-preview.ts +122 -0
  367. package/src/services/postgres.ts +76 -0
  368. package/src/services/providers/index.ts +27 -0
  369. package/src/services/providers/local/auth.ts +51 -0
  370. package/src/services/providers/local/index.ts +4 -0
  371. package/src/services/providers/local/users.ts +263 -0
  372. package/src/services/rail-preferences.ts +27 -0
  373. package/src/services/runtime-lifecycle.ts +191 -0
  374. package/src/services/secrets.ts +10 -0
  375. package/src/services/service-account-credentials.ts +891 -0
  376. package/src/services/service-accounts.ts +184 -0
  377. package/src/services/session/index.ts +236 -0
  378. package/src/services/session/test-fixture.ts +18 -0
  379. package/src/services/session/user.ts +144 -0
  380. package/src/services/settings/api.ts +61 -0
  381. package/src/services/settings/app.ts +86 -0
  382. package/src/services/settings/core-settings.ts +796 -0
  383. package/src/services/settings/crypto.ts +69 -0
  384. package/src/services/settings/defaults.ts +374 -0
  385. package/src/services/settings/index.ts +215 -0
  386. package/src/services/settings/namespace.ts +9 -0
  387. package/src/services/settings/snapshot.ts +49 -0
  388. package/src/services/settings/store.ts +241 -0
  389. package/src/services/settings/templates.ts +4 -0
  390. package/src/services/sync-ops.ts +403 -0
  391. package/src/services/topic-cursor.ts +22 -0
  392. package/src/services/weather/forecast.ts +323 -0
  393. package/src/services/weather/geo.ts +119 -0
  394. package/src/services/weather/index.ts +67 -0
  395. package/src/services/weather/location.ts +24 -0
  396. package/src/services/weather/locations.ts +133 -0
  397. package/src/services/weather/migrate.ts +45 -0
  398. package/src/services/weather/settings.ts +66 -0
  399. package/src/services/weather/short-id.ts +57 -0
  400. package/src/services/weather/types.ts +61 -0
  401. package/src/services/weather/ui.ts +50 -0
  402. package/src/services/webauthn.ts +385 -0
  403. package/src/shared/account-action-notice.ts +70 -0
  404. package/src/shared/account-display.ts +15 -0
  405. package/src/shared/account-session.ts +15 -0
  406. package/src/shared/ai-platform-prompt.ts +134 -0
  407. package/src/shared/ai-usage.ts +49 -0
  408. package/src/shared/app-presentation.ts +48 -0
  409. package/src/shared/app-url.ts +12 -0
  410. package/src/shared/branding.ts +14 -0
  411. package/src/shared/capability-messages.ts +70 -0
  412. package/src/shared/email-html.ts +54 -0
  413. package/src/shared/format.ts +91 -0
  414. package/src/shared/help.ts +68 -0
  415. package/src/shared/icons.ts +400 -0
  416. package/src/shared/index.ts +54 -0
  417. package/src/shared/locale.ts +48 -0
  418. package/src/shared/login-method.ts +44 -0
  419. package/src/shared/markdown/client.ts +130 -0
  420. package/src/shared/markdown/extensions/code.ts +105 -0
  421. package/src/shared/markdown/extensions/guided-help.ts +100 -0
  422. package/src/shared/markdown/extensions/images.ts +79 -0
  423. package/src/shared/markdown/extensions/info-blocks.ts +97 -0
  424. package/src/shared/markdown/extensions/katex.ts +120 -0
  425. package/src/shared/markdown/extensions/links.ts +43 -0
  426. package/src/shared/markdown/extensions/mark.ts +48 -0
  427. package/src/shared/markdown/extensions/sub-sup.ts +60 -0
  428. package/src/shared/markdown/extensions/tables.ts +109 -0
  429. package/src/shared/markdown/extensions/task-list.ts +53 -0
  430. package/src/shared/markdown/formula.ts +1187 -0
  431. package/src/shared/markdown/index.ts +193 -0
  432. package/src/shared/markdown/shared.ts +36 -0
  433. package/src/shared/mock-cover.ts +130 -0
  434. package/src/shared/network-address.ts +106 -0
  435. package/src/shared/redirect.ts +56 -0
  436. package/src/shared/template-rendering.ts +140 -0
  437. package/src/shared/theme.ts +68 -0
  438. package/src/shared/time.ts +13 -0
  439. package/src/ssr/AdminLayout.tsx +53 -0
  440. package/src/ssr/AdminSidebar.tsx +50 -0
  441. package/src/ssr/AppLaunchpad.island.tsx +236 -0
  442. package/src/ssr/AppWorkspaceController.island.tsx +21 -0
  443. package/src/ssr/BrowserNotifications.island.tsx +135 -0
  444. package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
  445. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
  446. package/src/ssr/GlobalSearchDialog.tsx +35 -0
  447. package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
  448. package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
  449. package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
  450. package/src/ssr/Layout.render.test.tsx +148 -0
  451. package/src/ssr/Layout.tsx +292 -0
  452. package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
  453. package/src/ssr/LayoutFooter.tsx +22 -0
  454. package/src/ssr/LayoutHeader.tsx +81 -0
  455. package/src/ssr/LayoutHelp.tsx +869 -0
  456. package/src/ssr/LayoutPreferences.island.tsx +36 -0
  457. package/src/ssr/LayoutRail.tsx +58 -0
  458. package/src/ssr/MinimalLayout.tsx +46 -0
  459. package/src/ssr/PageError.tsx +29 -0
  460. package/src/ssr/ProfilePreferences.island.tsx +92 -0
  461. package/src/ssr/RailApps.island.tsx +61 -0
  462. package/src/ssr/RailEditor.tsx +293 -0
  463. package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
  464. package/src/ssr/TimezoneCookie.island.tsx +23 -0
  465. package/src/ssr/admin-active-link.ts +47 -0
  466. package/src/ssr/admin-navigation.ts +78 -0
  467. package/src/ssr/app-appearance.ts +23 -0
  468. package/src/ssr/app-navigation.ts +41 -0
  469. package/src/ssr/help-messages.ts +104 -0
  470. package/src/ssr/index.ts +14 -0
  471. package/src/ssr/islands/SearchBar.island.tsx +80 -0
  472. package/src/ssr/islands/index.ts +16 -0
  473. package/src/ssr/layout-context.ts +17 -0
  474. package/src/ssr/layout-help-markdown.ts +9 -0
  475. package/src/ssr/layout-help-navigation.ts +24 -0
  476. package/src/ssr/layout-help-url.ts +5 -0
  477. package/src/ssr/layout-runtime.ts +22 -0
  478. package/src/ssr/page-error-messages.ts +25 -0
  479. package/src/ssr/platform-messages.ts +135 -0
  480. package/src/ssr/preference-controller.ts +44 -0
  481. package/src/ssr/profile-preferences-messages.ts +29 -0
  482. package/src/ssr/rail-context.ts +21 -0
  483. package/src/ssr/rail-messages.ts +66 -0
  484. package/src/ssr/rail-navigation.ts +60 -0
  485. package/src/ssr/runtime.ts +31 -0
  486. package/src/ssr/url-filter.ts +127 -0
  487. package/src/styles/base-popover.css +36 -0
  488. package/src/styles/effects.css +851 -0
  489. package/src/styles/global.css +268 -0
  490. package/src/styles/input.css +58 -0
  491. package/src/styles/tokens.css +230 -0
  492. package/src/styles/utilities-buttons.css +405 -0
  493. package/src/styles/utilities-data.css +33 -0
  494. package/src/styles/utilities-feedback.css +232 -0
  495. package/src/styles/utilities-layout.css +603 -0
  496. package/src/styles/utilities-markdown-editor.css +525 -0
  497. package/src/styles/utilities-markdown-table.css +234 -0
  498. package/src/styles/utilities-navigation.css +1054 -0
  499. package/src/types/ambient.d.ts +24 -0
  500. package/src/workflows/ai/index.ts +17 -0
  501. package/src/workflows/ai/runtime.ts +286 -0
  502. package/src/workflows/ai/store.ts +314 -0
  503. package/src/workflows/ai/types.ts +111 -0
  504. package/src/workflows/ai-actions.ts +269 -0
  505. package/src/workflows/authoring.ts +88 -0
  506. package/src/workflows/builtins.ts +193 -0
  507. package/src/workflows/contracts.ts +280 -0
  508. package/src/workflows/definition.ts +334 -0
  509. package/src/workflows/editor.ts +113 -0
  510. package/src/workflows/index.ts +71 -0
  511. package/src/workflows/language/binder.ts +45 -0
  512. package/src/workflows/language/canonical.ts +29 -0
  513. package/src/workflows/language/compiler.ts +396 -0
  514. package/src/workflows/language/expressions.ts +34 -0
  515. package/src/workflows/language/index.ts +20 -0
  516. package/src/workflows/language/references.ts +55 -0
  517. package/src/workflows/language/schema.ts +134 -0
  518. package/src/workflows/language/strict-yaml.ts +131 -0
  519. package/src/workflows/manifest.ts +24 -0
  520. package/src/workflows/module.ts +12 -0
  521. package/src/workflows/runtime/coordinator.ts +115 -0
  522. package/src/workflows/runtime/executor.ts +1003 -0
  523. package/src/workflows/runtime/index.ts +60 -0
  524. package/src/workflows/runtime/instant.ts +8 -0
  525. package/src/workflows/runtime/ports.ts +213 -0
  526. package/src/workflows/runtime/schedule.ts +203 -0
  527. package/src/workflows/runtime/trigger.ts +37 -0
  528. package/src/workflows/runtime/wake.ts +13 -0
  529. package/src/workflows/store/actions.ts +438 -0
  530. package/src/workflows/store/budget.ts +145 -0
  531. package/src/workflows/store/definitions.ts +345 -0
  532. package/src/workflows/store/events.ts +383 -0
  533. package/src/workflows/store/index.ts +107 -0
  534. package/src/workflows/store/observability.ts +594 -0
  535. package/src/workflows/store/runs.ts +975 -0
  536. package/src/workflows/store/transaction.ts +23 -0
  537. package/src/workflows/store/worker.ts +439 -0
  538. package/src/workflows/testing/conformance.ts +72 -0
  539. package/src/workflows/testing/index.ts +11 -0
  540. package/src/workflows/testing/process-fixtures.ts +275 -0
@@ -0,0 +1,869 @@
1
+ import { clipboard, hotkeys } from "@k2b/stdlib/solid";
2
+ import { Button, IconButton, IconButtonLink, MarkdownView, NoticeCard, Placeholder, prompts, ScrollArea, useLocale } from "@k2b/ui";
3
+ import type { HelpDocumentManifest, HelpDocumentPayload, HelpSearchPayload } from "@k2b/cloud/shared";
4
+ import { createEffect, createMemo, createSignal, For, type JSX, onCleanup, onMount, Show } from "solid-js";
5
+ import { appAccentStyle } from "./app-appearance";
6
+ import { type GlobalSearchHelpApp, openGlobalSearchHelpDialog } from "./GlobalSearchHelpDialog";
7
+ import { helpMessages } from "./help-messages";
8
+ import { formatHelpBundleMarkdown, formatHelpDocumentMarkdown } from "./layout-help-markdown";
9
+ import { adjacentHelpDocuments, focusHelpArticleHeading, resetHelpArticleScroll } from "./layout-help-navigation";
10
+ import { layoutHelpTopicHref } from "./layout-help-url";
11
+
12
+ type HelpTopicBase = {
13
+ id: string;
14
+ title: string;
15
+ icon?: string;
16
+ description?: string;
17
+ order?: number;
18
+ };
19
+
20
+ export type LayoutHelpDocumentsProps = {
21
+ documents: readonly HelpDocumentManifest[];
22
+ /** Canonical standalone Help route owned by the current app. */
23
+ pageBase: string;
24
+ };
25
+ export type LayoutHelpPageProps = {
26
+ documents: readonly HelpDocumentManifest[];
27
+ initialTopic?: string;
28
+ includeShortcuts?: boolean;
29
+ accent?: string;
30
+ /** Render inside an app-owned pane instead of occupying a standalone page. */
31
+ embedded?: boolean;
32
+ /** Canonical standalone Help route owned by the current app. */
33
+ pageBase?: string;
34
+ };
35
+
36
+ type HelpTopic = (HelpTopicBase & { kind: "content"; children: JSX.Element }) | (HelpDocumentManifest & { kind: "document" });
37
+ type HelpView = "hub" | "search" | "article";
38
+ type HelpSession = {
39
+ view: HelpView;
40
+ query: string;
41
+ activeId: string | null;
42
+ articleScrollTop: number;
43
+ articleCache: Map<string, HelpDocumentPayload>;
44
+ };
45
+ type HelpSection = {
46
+ element: HTMLHeadingElement;
47
+ icon: string;
48
+ id: string;
49
+ title: string;
50
+ };
51
+
52
+ const HELP_TOPICS_EVENT = "cloud:layout-help-topics";
53
+ const LAST_TOPIC_KEY = "cloud.layoutHelp.activeTopic";
54
+
55
+ declare global {
56
+ interface Window {
57
+ __cloudLayoutHelpTopics?: Map<string, HelpTopic>;
58
+ __cloudLayoutHelpPageBase?: string;
59
+ }
60
+ }
61
+
62
+ const registry = () => {
63
+ if (typeof window === "undefined") return null;
64
+ window.__cloudLayoutHelpTopics ??= new Map<string, HelpTopic>();
65
+ return window.__cloudLayoutHelpTopics;
66
+ };
67
+
68
+ const emitTopicsChanged = () => window.dispatchEvent(new Event(HELP_TOPICS_EVENT));
69
+ const iconClass = (icon?: string) => (icon?.startsWith("ti ") ? icon : `ti ${icon ?? "ti-circle"}`);
70
+ const legacyTopicContent = (topic: HelpTopic) => (topic.kind === "content" ? topic.children : null);
71
+ const sortedTopics = () =>
72
+ [...(registry()?.values() ?? [])].sort((a, b) => (a.order ?? 100) - (b.order ?? 100) || a.title.localeCompare(b.title));
73
+ const documentTopics = (documents: readonly HelpDocumentManifest[] = []): HelpTopic[] =>
74
+ documents.map((document) => ({ ...document, kind: "document" }));
75
+ const mergeTopics = (registered: readonly HelpTopic[], documents: readonly HelpDocumentManifest[] = []): HelpTopic[] => {
76
+ const topics = new Map(registered.map((topic) => [topic.id, topic]));
77
+ for (const topic of documentTopics(documents)) topics.set(topic.id, topic);
78
+ return [...topics.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100) || a.title.localeCompare(b.title));
79
+ };
80
+
81
+ const registerTopic = (topic: HelpTopic) => {
82
+ const topics = registry();
83
+ if (!topics) return () => {};
84
+ topics.set(topic.id, topic);
85
+ emitTopicsChanged();
86
+ return () => {
87
+ if (topics.get(topic.id) !== topic) return;
88
+ topics.delete(topic.id);
89
+ emitTopicsChanged();
90
+ };
91
+ };
92
+
93
+ /** Register an app's server-prepared Markdown manifest with the shared help UI. */
94
+ export function LayoutHelpDocuments(props: LayoutHelpDocumentsProps) {
95
+ onMount(() => {
96
+ window.__cloudLayoutHelpPageBase = props.pageBase;
97
+ const disposers = props.documents.map((document) => registerTopic({ ...document, kind: "document" }));
98
+ onCleanup(() => {
99
+ disposers.forEach((dispose) => dispose());
100
+ if (window.__cloudLayoutHelpPageBase === props.pageBase) delete window.__cloudLayoutHelpPageBase;
101
+ });
102
+ });
103
+ return null;
104
+ }
105
+
106
+ const Shortcuts = (props: { openSearchHelp: () => void }) => {
107
+ const locale = useLocale();
108
+ const t = () => helpMessages.resolve([locale()]).t;
109
+ const entries = createMemo(() => [...hotkeys.entries()].sort((a, b) => a.label.localeCompare(b.label) || a.keys.localeCompare(b.keys)));
110
+ return (
111
+ <div class="flex flex-col gap-3">
112
+ <p class="text-sm leading-relaxed text-dimmed">{t().shortcutsIntro}</p>
113
+ <Button size="sm" variant="secondary" class="self-start" onClick={props.openSearchHelp}>
114
+ <i class="ti ti-search" /> {t().searchHelp}
115
+ </Button>
116
+ <div class="flex flex-col gap-2">
117
+ <For each={entries()}>
118
+ {(entry) => (
119
+ <div class="flex items-start justify-between gap-4 rounded-[var(--ui-radius-surface)] bg-[var(--ui-surface-subtle)] px-3 py-2.5">
120
+ <div class="min-w-0">
121
+ <p class="text-sm font-medium text-primary">{entry.label}</p>
122
+ <Show when={entry.desc}>
123
+ <p class="mt-0.5 text-xs text-dimmed">{entry.desc}</p>
124
+ </Show>
125
+ </div>
126
+ <div class="flex shrink-0 gap-1" role="group" aria-label={entry.keysPretty.map((part) => part.ariaLabel).join(" + ")}>
127
+ <For each={entry.keysPretty}>
128
+ {(part) => (
129
+ <kbd class="rounded bg-[var(--ui-surface-raised)] px-1.5 py-1 text-[11px] ring-1 ring-black/10 dark:ring-white/10">
130
+ {part.key}
131
+ </kbd>
132
+ )}
133
+ </For>
134
+ </div>
135
+ </div>
136
+ )}
137
+ </For>
138
+ </div>
139
+ </div>
140
+ );
141
+ };
142
+
143
+ const HelpShell = (props: {
144
+ session: HelpSession;
145
+ close: () => void;
146
+ pageBase?: string;
147
+ searchHelpApps: GlobalSearchHelpApp[];
148
+ documents?: readonly HelpDocumentManifest[];
149
+ includeShortcuts?: boolean;
150
+ accent?: string;
151
+ surface?: "modal" | "page" | "embedded";
152
+ syncPageUrl?: boolean;
153
+ }) => {
154
+ const locale = useLocale();
155
+ const t = () => helpMessages.resolve([locale()]).t;
156
+ const [externalTopics, setExternalTopics] = createSignal(mergeTopics(sortedTopics(), props.documents));
157
+ const [view, setView] = createSignal<HelpView>(props.session.view);
158
+ const [query, setQuery] = createSignal(props.session.query);
159
+ const [activeId, setActiveId] = createSignal<string | null>(props.session.activeId);
160
+ const [payload, setPayload] = createSignal<HelpDocumentPayload | null>(null);
161
+ const [loading, setLoading] = createSignal(props.session.view === "article");
162
+ const [loadError, setLoadError] = createSignal<string | null>(null);
163
+ const [loadAttempt, setLoadAttempt] = createSignal(0);
164
+ const [remoteMatches, setRemoteMatches] = createSignal<ReadonlySet<string>>(new Set());
165
+ const [searching, setSearching] = createSignal(false);
166
+ const [copyingAll, setCopyingAll] = createSignal(false);
167
+ const [copyAllError, setCopyAllError] = createSignal<string | null>(null);
168
+ const [articleSections, setArticleSections] = createSignal<HelpSection[]>([]);
169
+ const [activeSectionId, setActiveSectionId] = createSignal<string | null>(null);
170
+ const articleClipboard = clipboard.create(1800);
171
+ const allHelpClipboard = clipboard.create(1800);
172
+ let root: HTMLDivElement | undefined;
173
+ let scrollArea: HTMLDivElement | undefined;
174
+ let articleContent: HTMLDivElement | undefined;
175
+ let articleHeading: HTMLHeadingElement | undefined;
176
+ let pendingArticleFocus = false;
177
+ let requestVersion = 0;
178
+ let searchVersion = 0;
179
+ let copyAllController: AbortController | undefined;
180
+
181
+ const restoreArticleScroll = () =>
182
+ requestAnimationFrame(() => {
183
+ if (scrollArea && view() === "article") scrollArea.scrollTop = props.session.articleScrollTop;
184
+ });
185
+
186
+ const syncActiveSection = () => {
187
+ const sections = articleSections();
188
+ if (!scrollArea || sections.length === 0) return;
189
+ const readingLine = scrollArea.getBoundingClientRect().top + Math.min(160, scrollArea.clientHeight * 0.22);
190
+ let active = sections[0];
191
+ for (const section of sections) {
192
+ if (section.element.getBoundingClientRect().top <= readingLine) active = section;
193
+ else break;
194
+ }
195
+ setActiveSectionId(active?.id ?? null);
196
+ };
197
+
198
+ const collectArticleSections = () => {
199
+ const sections = Array.from(articleContent?.querySelectorAll<HTMLHeadingElement>("h2[id]") ?? []).map((element) => ({
200
+ element,
201
+ icon: element.dataset.helpIcon ?? "ti ti-point",
202
+ id: element.id,
203
+ title: element.textContent?.trim() || t().section,
204
+ }));
205
+ setArticleSections(sections);
206
+ setActiveSectionId(sections[0]?.id ?? null);
207
+ requestAnimationFrame(syncActiveSection);
208
+ };
209
+
210
+ const openArticleSection = (section: HelpSection) => {
211
+ setActiveSectionId(section.id);
212
+ section.element.scrollIntoView({
213
+ behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth",
214
+ block: "start",
215
+ });
216
+ };
217
+
218
+ const shortcutsTopic = createMemo<HelpTopic>(() => ({
219
+ id: "shortcuts",
220
+ title: t().shortcuts,
221
+ icon: "ti ti-keyboard",
222
+ description: t().shortcutsDescription,
223
+ order: 0,
224
+ kind: "content",
225
+ children: <Shortcuts openSearchHelp={() => openGlobalSearchHelpDialog(props.searchHelpApps, locale())} />,
226
+ }));
227
+ const topics = createMemo(() => (props.includeShortcuts === false ? externalTopics() : [shortcutsTopic(), ...externalTopics()]));
228
+ const documentTopics = createMemo(() =>
229
+ externalTopics().filter((topic): topic is HelpDocumentManifest & { kind: "document" } => topic.kind === "document"),
230
+ );
231
+ const activeTopic = createMemo(() => topics().find((topic) => topic.id === activeId()) ?? null);
232
+ const adjacentTopics = createMemo(() => adjacentHelpDocuments(documentTopics(), activeId()));
233
+ const normalizedQuery = createMemo(() => query().trim().toLocaleLowerCase());
234
+ const results = createMemo(() => {
235
+ const value = normalizedQuery();
236
+ if (!value) return topics();
237
+ const matches = remoteMatches();
238
+ return topics().filter(
239
+ (topic) => [topic.title, topic.description].some((part) => part?.toLocaleLowerCase().includes(value)) || matches.has(topic.id),
240
+ );
241
+ });
242
+
243
+ createEffect(() => {
244
+ const value = normalizedQuery();
245
+ const urls = [
246
+ ...new Set(
247
+ externalTopics()
248
+ .filter((topic): topic is HelpDocumentManifest & { kind: "document" } => topic.kind === "document")
249
+ .map((topic) => topic.searchUrl),
250
+ ),
251
+ ];
252
+ const version = ++searchVersion;
253
+ setRemoteMatches(new Set<string>());
254
+ setSearching(false);
255
+ if (!value || urls.length === 0) return;
256
+
257
+ const controller = new AbortController();
258
+ const timer = window.setTimeout(() => {
259
+ setSearching(true);
260
+ void Promise.all(
261
+ urls.map(async (url) => {
262
+ const response = await fetch(`${url}?q=${encodeURIComponent(value)}`, {
263
+ signal: controller.signal,
264
+ headers: { Accept: "application/json" },
265
+ });
266
+ if (!response.ok) throw new Error(t().searchFailed);
267
+ const payload = (await response.json()) as Partial<HelpSearchPayload>;
268
+ return Array.isArray(payload.ids) ? payload.ids.filter((id): id is string => typeof id === "string") : [];
269
+ }),
270
+ )
271
+ .then((groups) => {
272
+ if (version === searchVersion) setRemoteMatches(new Set<string>(groups.flat()));
273
+ })
274
+ .catch((error) => {
275
+ if (error instanceof DOMException && error.name === "AbortError") return;
276
+ // Metadata matches stay usable if full-text search is temporarily unavailable.
277
+ })
278
+ .finally(() => {
279
+ if (version === searchVersion) setSearching(false);
280
+ });
281
+ }, 120);
282
+
283
+ onCleanup(() => {
284
+ window.clearTimeout(timer);
285
+ controller.abort();
286
+ if (version === searchVersion) searchVersion++;
287
+ });
288
+ });
289
+
290
+ createEffect(() => {
291
+ const html = payload()?.html;
292
+ if (!html || view() !== "article") {
293
+ setArticleSections([]);
294
+ setActiveSectionId(null);
295
+ return;
296
+ }
297
+ const frame = requestAnimationFrame(collectArticleSections);
298
+ onCleanup(() => cancelAnimationFrame(frame));
299
+ });
300
+
301
+ createEffect(() => {
302
+ activeId();
303
+ if (!pendingArticleFocus || view() !== "article") return;
304
+ const frame = requestAnimationFrame(() => {
305
+ focusHelpArticleHeading(articleHeading);
306
+ pendingArticleFocus = false;
307
+ });
308
+ onCleanup(() => cancelAnimationFrame(frame));
309
+ });
310
+
311
+ const syncSession = () => {
312
+ props.session.view = view();
313
+ props.session.query = query();
314
+ props.session.activeId = activeId();
315
+ };
316
+ createEffect(syncSession);
317
+
318
+ onMount(() => {
319
+ const dialog = root?.closest("dialog");
320
+ if (dialog) {
321
+ dialog.setAttribute("aria-labelledby", "layout-help-title");
322
+ dialog.setAttribute("aria-describedby", "layout-help-subtitle");
323
+ }
324
+ const update = () => setExternalTopics(mergeTopics(sortedTopics(), props.documents));
325
+ window.addEventListener(HELP_TOPICS_EVENT, update);
326
+ update();
327
+ onCleanup(() => window.removeEventListener(HELP_TOPICS_EVENT, update));
328
+ restoreArticleScroll();
329
+ });
330
+ onCleanup(() => copyAllController?.abort());
331
+
332
+ const loadDocument = async (topic: HelpDocumentManifest, signal: AbortSignal) => {
333
+ const cacheKey = `${topic.locale ?? "en"}:${topic.url}`;
334
+ const cached = props.session.articleCache.get(cacheKey);
335
+ if (cached) return cached;
336
+
337
+ const response = await fetch(topic.url, { signal, headers: { Accept: "application/json" } });
338
+ if (!response.ok) throw new Error(t().requestFailed);
339
+ const value = (await response.json()) as Partial<HelpDocumentPayload>;
340
+ if (
341
+ value.id !== topic.id ||
342
+ (value.locale !== undefined && typeof value.locale !== "string") ||
343
+ (topic.locale !== undefined && value.locale !== undefined && value.locale !== topic.locale) ||
344
+ typeof value.html !== "string" ||
345
+ typeof value.markdown !== "string" ||
346
+ typeof value.title !== "string"
347
+ ) {
348
+ throw new Error(t().invalidDocument);
349
+ }
350
+ const document: HelpDocumentPayload = { ...(value as HelpDocumentPayload), locale: value.locale ?? topic.locale ?? "en" };
351
+ props.session.articleCache.set(cacheKey, document);
352
+ return document;
353
+ };
354
+
355
+ createEffect(() => {
356
+ loadAttempt();
357
+ const topic = activeTopic();
358
+ const version = ++requestVersion;
359
+ setPayload(null);
360
+ setLoadError(null);
361
+ setLoading(false);
362
+ if (!topic || topic.kind !== "document" || view() !== "article") return;
363
+ const cached = props.session.articleCache.get(`${topic.locale ?? "en"}:${topic.url}`);
364
+ if (cached) {
365
+ setPayload(cached);
366
+ restoreArticleScroll();
367
+ return;
368
+ }
369
+
370
+ const controller = new AbortController();
371
+ setLoading(true);
372
+ void loadDocument(topic, controller.signal)
373
+ .then((document) => {
374
+ if (version === requestVersion) {
375
+ setPayload(document);
376
+ restoreArticleScroll();
377
+ }
378
+ })
379
+ .catch((error) => {
380
+ if (error instanceof DOMException && error.name === "AbortError") return;
381
+ if (version === requestVersion) setLoadError(error instanceof Error ? error.message : t().loadFallback);
382
+ })
383
+ .finally(() => {
384
+ if (version === requestVersion) setLoading(false);
385
+ });
386
+ onCleanup(() => {
387
+ controller.abort();
388
+ if (version === requestVersion) requestVersion++;
389
+ });
390
+ });
391
+
392
+ const copyArticle = (topic: HelpTopic, document: HelpDocumentPayload | null) => {
393
+ if (topic.kind !== "document" || !document) return;
394
+ return articleClipboard.copy(formatHelpDocumentMarkdown({ ...topic, markdown: document.markdown }));
395
+ };
396
+
397
+ const copyAllHelp = async () => {
398
+ const manifests = documentTopics();
399
+ if (manifests.length === 0 || copyingAll()) return;
400
+
401
+ copyAllController?.abort();
402
+ const controller = new AbortController();
403
+ copyAllController = controller;
404
+ setCopyingAll(true);
405
+ setCopyAllError(null);
406
+ try {
407
+ const documents = await Promise.all(
408
+ manifests.map(async (topic) => {
409
+ const document = await loadDocument(topic, controller.signal);
410
+ return { ...topic, markdown: document.markdown };
411
+ }),
412
+ );
413
+ await allHelpClipboard.copy(formatHelpBundleMarkdown(documents));
414
+ } catch (error) {
415
+ if (!(error instanceof DOMException && error.name === "AbortError")) {
416
+ setCopyAllError(error instanceof Error ? error.message : t().copyFallback);
417
+ }
418
+ } finally {
419
+ if (copyAllController === controller) {
420
+ copyAllController = undefined;
421
+ setCopyingAll(false);
422
+ }
423
+ }
424
+ };
425
+
426
+ const openTopic = (id: string) => {
427
+ resetHelpArticleScroll(props.session, scrollArea);
428
+ pendingArticleFocus = true;
429
+ setActiveId(id);
430
+ setView("article");
431
+ if (props.syncPageUrl) {
432
+ if (props.pageBase) history.replaceState(history.state, "", layoutHelpTopicHref(props.pageBase, id));
433
+ }
434
+ try {
435
+ localStorage.setItem(LAST_TOPIC_KEY, id);
436
+ } catch {
437
+ /* Help does not depend on storage. */
438
+ }
439
+ };
440
+ const goBack = () => {
441
+ setView(query().trim() ? "search" : "hub");
442
+ if (props.syncPageUrl) {
443
+ if (props.pageBase) history.replaceState(history.state, "", layoutHelpTopicHref(props.pageBase, null));
444
+ }
445
+ };
446
+ const showHub = () => {
447
+ setQuery("");
448
+ setRemoteMatches(new Set<string>());
449
+ setView("hub");
450
+ };
451
+ const modalTitle = createMemo(() => {
452
+ if (view() === "article") return activeTopic()?.title ?? t().help;
453
+ if (view() === "search") return t().searchHelp;
454
+ return t().help;
455
+ });
456
+ const modalDescription = createMemo(() => {
457
+ if (view() === "article") return activeTopic()?.description ?? t().description;
458
+ if (view() === "search") return t().searchDescription;
459
+ return t().description;
460
+ });
461
+ const modalIcon = createMemo(() => {
462
+ if (view() === "article") return iconClass(activeTopic()?.icon);
463
+ if (view() === "search") return "ti ti-search";
464
+ return "ti ti-help";
465
+ });
466
+
467
+ const TopicList = (listProps: { items: HelpTopic[] }) => (
468
+ <div class="flex flex-col gap-1 rounded-[var(--ui-radius-surface)] bg-[var(--ui-surface-subtle)] p-1.5">
469
+ <For each={listProps.items}>
470
+ {(topic) => (
471
+ <button
472
+ type="button"
473
+ class="group flex w-full items-center gap-3 rounded-[var(--ui-radius-control)] px-3 py-2.5 text-left hover:bg-[var(--ui-surface-raised)] focus-ui"
474
+ onClick={() => openTopic(topic.id)}
475
+ >
476
+ <span class="help-topic-icon flex h-8 w-8 shrink-0 items-center justify-center rounded-[var(--ui-radius-control)] app-accent-text">
477
+ <i class={`${iconClass(topic.icon)} text-base`} />
478
+ </span>
479
+ <span class="min-w-0 flex-1">
480
+ <span class="block truncate text-sm font-medium text-primary group-hover:app-accent-text">{topic.title}</span>
481
+ <Show when={topic.description}>
482
+ {(description) => <span class="mt-0.5 block truncate text-xs text-dimmed">{description()}</span>}
483
+ </Show>
484
+ </span>
485
+ <i class="ti ti-chevron-right shrink-0 text-xs text-dimmed" />
486
+ </button>
487
+ )}
488
+ </For>
489
+ </div>
490
+ );
491
+
492
+ const TopicNavigation = () => (
493
+ <Show when={adjacentTopics().previous || adjacentTopics().next}>
494
+ <nav class="mt-12 grid gap-3 sm:grid-cols-2" aria-label={t().topicNavigation}>
495
+ <Show when={adjacentTopics().previous}>
496
+ {(previous) => (
497
+ <button
498
+ type="button"
499
+ class="group flex min-w-0 flex-col items-start gap-1 rounded-[var(--ui-radius-surface)] bg-[var(--ui-surface-subtle)] p-4 text-left transition-colors hover:bg-[var(--ui-surface-muted)] focus-ui"
500
+ onClick={() => openTopic(previous().id)}
501
+ >
502
+ <span class="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-dimmed">
503
+ <i class="ti ti-arrow-left" aria-hidden="true" />
504
+ {t().previousTopic}
505
+ </span>
506
+ <span class="text-sm font-medium text-primary group-hover:app-accent-text">{previous().title}</span>
507
+ </button>
508
+ )}
509
+ </Show>
510
+ <Show when={adjacentTopics().next}>
511
+ {(next) => (
512
+ <button
513
+ type="button"
514
+ class="group flex min-w-0 flex-col items-end gap-1 rounded-[var(--ui-radius-surface)] bg-[var(--ui-surface-subtle)] p-4 text-right transition-colors hover:bg-[var(--ui-surface-muted)] focus-ui sm:col-start-2"
515
+ onClick={() => openTopic(next().id)}
516
+ >
517
+ <span class="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-dimmed">
518
+ {t().nextTopic}
519
+ <i class="ti ti-arrow-right" aria-hidden="true" />
520
+ </span>
521
+ <span class="text-sm font-medium text-primary group-hover:app-accent-text">{next().title}</span>
522
+ </button>
523
+ )}
524
+ </Show>
525
+ </nav>
526
+ </Show>
527
+ );
528
+
529
+ return (
530
+ <div
531
+ ref={root}
532
+ class={`app-accent-scope flex min-h-0 flex-col bg-[var(--ui-surface-raised)] ${
533
+ props.surface === "modal"
534
+ ? "h-[min(48rem,var(--ui-dialog-available-height))] overflow-hidden panel-dialog-shell [box-shadow:var(--ui-shadow-float)]"
535
+ : props.surface === "page"
536
+ ? "min-h-screen"
537
+ : "h-full"
538
+ }`}
539
+ style={appAccentStyle(props.accent)}
540
+ >
541
+ <Show when={props.surface === "modal"}>
542
+ <header class="flex min-h-16 shrink-0 items-center gap-2 px-5 py-3">
543
+ <Show when={view() !== "hub"}>
544
+ <IconButton
545
+ class="shrink-0"
546
+ label={view() === "article" ? t().backToTopics : t().backToHelp}
547
+ title={t().back}
548
+ onClick={() => (view() === "article" ? goBack() : showHub())}
549
+ >
550
+ <i class="ti ti-arrow-left" />
551
+ </IconButton>
552
+ </Show>
553
+ <span class="help-topic-icon flex h-9 w-9 shrink-0 items-center justify-center rounded-[var(--ui-radius-control)] app-accent-text">
554
+ <i class={`${modalIcon()} text-lg`} />
555
+ </span>
556
+ <div class="min-w-0 flex-1">
557
+ <h2 ref={articleHeading} id="layout-help-title" tabindex="-1" class="truncate rounded-sm font-semibold text-primary focus-ui">
558
+ {modalTitle()}
559
+ </h2>
560
+ <p id="layout-help-subtitle" class="truncate text-xs text-dimmed" title={modalDescription()}>
561
+ {modalDescription()}
562
+ </p>
563
+ </div>
564
+ <div class="flex shrink-0 items-center gap-1" role="group" aria-label={t().actions}>
565
+ <Show when={view() === "hub" && documentTopics().length > 0}>
566
+ <IconButton
567
+ disabled={copyingAll()}
568
+ label={allHelpClipboard.wasCopied() ? t().helpCopied : t().copyAllHelp}
569
+ title={allHelpClipboard.wasCopied() ? t().copied : t().copyAllHelp}
570
+ onClick={() => void copyAllHelp()}
571
+ >
572
+ <i class={copyingAll() ? "ti ti-loader-2 animate-spin" : allHelpClipboard.wasCopied() ? "ti ti-check" : "ti ti-markdown"} />
573
+ </IconButton>
574
+ </Show>
575
+ <Show when={view() === "article" && activeTopic()?.kind === "document"}>
576
+ <IconButton
577
+ disabled={!payload()}
578
+ label={articleClipboard.wasCopied() ? t().articleCopied : t().copyArticle}
579
+ title={articleClipboard.wasCopied() ? t().copied : t().copyArticle}
580
+ onClick={() => {
581
+ const topic = activeTopic();
582
+ if (topic) void copyArticle(topic, payload());
583
+ }}
584
+ >
585
+ <i class={articleClipboard.wasCopied() ? "ti ti-check" : "ti ti-markdown"} />
586
+ </IconButton>
587
+ </Show>
588
+ <Show when={props.pageBase}>
589
+ {(pageBase) => (
590
+ <IconButtonLink
591
+ href={layoutHelpTopicHref(pageBase(), view() === "article" ? activeId() : null)}
592
+ target="_blank"
593
+ rel="noopener noreferrer"
594
+ label={t().openWindow}
595
+ title={t().openFullPage}
596
+ onClick={(event) => {
597
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
598
+
599
+ const popup = window.open(
600
+ event.currentTarget.href,
601
+ "cloud-layout-help",
602
+ "popup,width=1120,height=820,resizable=yes,scrollbars=yes",
603
+ );
604
+ if (!popup) return;
605
+
606
+ event.preventDefault();
607
+ popup.opener = null;
608
+ popup.focus();
609
+ }}
610
+ >
611
+ <i class="ti ti-app-window" />
612
+ </IconButtonLink>
613
+ )}
614
+ </Show>
615
+ <IconButton label={t().closeHelp} title={t().close} onClick={props.close}>
616
+ <i class="ti ti-x" />
617
+ </IconButton>
618
+ </div>
619
+ </header>
620
+ </Show>
621
+
622
+ <ScrollArea
623
+ ref={scrollArea}
624
+ class="min-h-0 flex-1 p-4 sm:p-5"
625
+ onScroll={(event) => {
626
+ if (view() === "article") {
627
+ props.session.articleScrollTop = event.currentTarget.scrollTop;
628
+ syncActiveSection();
629
+ }
630
+ }}
631
+ >
632
+ <Show when={view() === "hub"}>
633
+ <div class="mx-auto flex max-w-3xl flex-col gap-6">
634
+ <div class="flex flex-col items-start justify-between gap-3 sm:flex-row">
635
+ <div>
636
+ <h3 class="text-xl font-semibold text-primary">{t().heading}</h3>
637
+ <p class="mt-1 text-sm text-dimmed">{t().headingDescription}</p>
638
+ </div>
639
+ <Show when={props.surface !== "modal" && documentTopics().length > 0}>
640
+ <Button size="sm" variant="secondary" class="shrink-0" disabled={copyingAll()} onClick={() => void copyAllHelp()}>
641
+ <i class={copyingAll() ? "ti ti-loader-2 animate-spin" : allHelpClipboard.wasCopied() ? "ti ti-check" : "ti ti-copy"} />
642
+ {copyingAll() ? t().preparing : allHelpClipboard.wasCopied() ? t().copied : t().copyAllShort}
643
+ </Button>
644
+ </Show>
645
+ </div>
646
+ <Show when={copyAllError()}>
647
+ {(message) => (
648
+ <p class="text-xs text-danger" role="alert">
649
+ {t().copyAllFailed({ message: message() })}
650
+ </p>
651
+ )}
652
+ </Show>
653
+ <label class="field flex h-11 items-center gap-2 px-3">
654
+ <i class="ti ti-search text-dimmed" />
655
+ <input
656
+ class="min-w-0 flex-1 bg-transparent text-sm outline-none"
657
+ value={query()}
658
+ placeholder={t().searchPlaceholder}
659
+ onInput={(event) => {
660
+ setQuery(event.currentTarget.value);
661
+ if (event.currentTarget.value.trim()) setView("search");
662
+ }}
663
+ />
664
+ </label>
665
+ <section aria-labelledby="help-start-title">
666
+ <h4 id="help-start-title" class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
667
+ {t().startHere}
668
+ </h4>
669
+ <TopicList items={topics().slice(0, 5)} />
670
+ </section>
671
+ <Show when={topics().length > 5}>
672
+ <section aria-labelledby="help-all-title">
673
+ <h4 id="help-all-title" class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
674
+ {t().allTopics}
675
+ </h4>
676
+ <TopicList items={topics().slice(5)} />
677
+ </section>
678
+ </Show>
679
+ </div>
680
+ </Show>
681
+
682
+ <Show when={view() === "search"}>
683
+ <div class="mx-auto flex max-w-3xl flex-col gap-4">
684
+ <div class="flex items-center gap-2">
685
+ <Show when={props.surface !== "modal"}>
686
+ <IconButton label={t().backToHelp} onClick={showHub}>
687
+ <i class="ti ti-arrow-left" />
688
+ </IconButton>
689
+ </Show>
690
+ <label class="field flex h-11 flex-1 items-center gap-2 px-3">
691
+ <i class="ti ti-search text-dimmed" />
692
+ <input
693
+ autofocus
694
+ class="min-w-0 flex-1 bg-transparent text-sm outline-none"
695
+ value={query()}
696
+ placeholder={t().searchPlaceholder}
697
+ onInput={(event) => setQuery(event.currentTarget.value)}
698
+ />
699
+ </label>
700
+ </div>
701
+ <p class="flex items-center gap-1.5 text-xs text-dimmed" aria-live="polite">
702
+ <Show when={searching()}>
703
+ <i class="ti ti-loader-2 animate-spin" aria-hidden="true" />
704
+ </Show>
705
+ {t().results({ count: results().length })}
706
+ </p>
707
+ <TopicList items={results()} />
708
+ <Show when={results().length === 0 && !searching()}>
709
+ <Placeholder icon="ti ti-search-off" title={t().noResults} />
710
+ </Show>
711
+ </div>
712
+ </Show>
713
+
714
+ <Show when={view() === "article" && activeTopic()}>
715
+ {(topic) => (
716
+ <article class="mx-auto flex min-h-full w-full max-w-7xl flex-col">
717
+ <Show when={props.surface !== "modal"}>
718
+ <button
719
+ type="button"
720
+ class="mb-6 inline-flex items-center gap-1.5 text-xs font-medium text-dimmed hover:app-accent-text focus-ui"
721
+ onClick={goBack}
722
+ >
723
+ <i class="ti ti-arrow-left" /> {t().back}
724
+ </button>
725
+ <header class="mb-8 grid grid-cols-[auto_minmax(0,1fr)] items-start gap-x-3 gap-y-3 sm:grid-cols-[auto_minmax(0,1fr)_auto]">
726
+ <span class="help-topic-icon flex h-10 w-10 shrink-0 items-center justify-center rounded-[var(--ui-radius-control)] app-accent-text">
727
+ <i class={`${iconClass(topic().icon)} text-lg`} />
728
+ </span>
729
+ <div class="min-w-0 flex-1">
730
+ <h3 ref={articleHeading} tabindex="-1" class="rounded-sm text-2xl font-semibold tracking-tight text-primary focus-ui">
731
+ {topic().title}
732
+ </h3>
733
+ <Show when={topic().description}>
734
+ {(description) => <p class="mt-1.5 text-sm leading-relaxed text-dimmed">{description()}</p>}
735
+ </Show>
736
+ </div>
737
+ <Show when={topic().kind === "document" && payload()}>
738
+ <Button
739
+ size="sm"
740
+ variant="secondary"
741
+ class="col-start-2 shrink-0 justify-self-start sm:col-start-3 sm:row-start-1 sm:justify-self-auto"
742
+ onClick={() => void copyArticle(topic(), payload())}
743
+ >
744
+ <i class={articleClipboard.wasCopied() ? "ti ti-check" : "ti ti-markdown"} />
745
+ {articleClipboard.wasCopied() ? t().copied : t().copyMarkdown}
746
+ </Button>
747
+ </Show>
748
+ </header>
749
+ </Show>
750
+ <Show when={topic().kind === "content"}>{legacyTopicContent(topic())}</Show>
751
+ <Show when={topic().kind === "document"}>
752
+ <Show when={loading()}>
753
+ <Placeholder state="loading" variant="panel" title={t().loading} />
754
+ </Show>
755
+ <Show when={loadError()}>
756
+ {(message) => (
757
+ <NoticeCard tone="danger" icon={false}>
758
+ <p class="font-medium">{t().loadFailed}</p>
759
+ <p class="mt-1 text-sm">{message()}</p>
760
+ <Button size="sm" variant="secondary" class="mt-3" onClick={() => setLoadAttempt((value) => value + 1)}>
761
+ {t().tryAgain}
762
+ </Button>
763
+ </NoticeCard>
764
+ )}
765
+ </Show>
766
+ <Show when={payload()}>
767
+ {(document) => (
768
+ <div class="help-article-layout">
769
+ <Show when={articleSections().length > 0}>
770
+ <nav class="help-article-toc" aria-label={t().onThisPage}>
771
+ <p class="help-article-toc-label">{t().onThisPage}</p>
772
+ <ol>
773
+ <For each={articleSections()}>
774
+ {(section) => (
775
+ <li>
776
+ <button
777
+ type="button"
778
+ aria-current={activeSectionId() === section.id ? "location" : undefined}
779
+ title={section.title}
780
+ onClick={() => openArticleSection(section)}
781
+ >
782
+ <span class="help-article-toc-icon" aria-hidden="true">
783
+ <i class={section.icon} />
784
+ </span>
785
+ <span class="help-article-toc-title">{section.title}</span>
786
+ </button>
787
+ </li>
788
+ )}
789
+ </For>
790
+ </ol>
791
+ </nav>
792
+ </Show>
793
+ <div class="help-article-copy">
794
+ <div ref={articleContent}>
795
+ <MarkdownView trustedHtml={document().html} class="help-document" />
796
+ </div>
797
+ <TopicNavigation />
798
+ </div>
799
+ </div>
800
+ )}
801
+ </Show>
802
+ </Show>
803
+ </article>
804
+ )}
805
+ </Show>
806
+ <span class="sr-only" aria-live="polite">
807
+ {articleClipboard.wasCopied() ? t().articleCopiedAnnouncement : allHelpClipboard.wasCopied() ? t().allCopiedAnnouncement : ""}
808
+ </span>
809
+ </ScrollArea>
810
+ </div>
811
+ );
812
+ };
813
+
814
+ const createSession = (initialTopic?: string): HelpSession => {
815
+ let activeId: string | null = initialTopic ?? null;
816
+ if (!activeId && typeof window !== "undefined") {
817
+ try {
818
+ activeId = localStorage.getItem(LAST_TOPIC_KEY);
819
+ } catch {
820
+ /* Storage is optional. */
821
+ }
822
+ }
823
+ return {
824
+ view: initialTopic ? "article" : "hub",
825
+ query: "",
826
+ activeId,
827
+ articleScrollTop: 0,
828
+ articleCache: new Map(),
829
+ };
830
+ };
831
+
832
+ /**
833
+ * Render the shared Help experience as a full page. Apps pass the same
834
+ * manifest used by `Layout.HelpDocuments`; article bodies still load lazily
835
+ * from the central Help API.
836
+ */
837
+ export function LayoutHelpPage(props: LayoutHelpPageProps) {
838
+ const session = createSession(props.initialTopic);
839
+ return (
840
+ <HelpShell
841
+ session={session}
842
+ close={() => {}}
843
+ searchHelpApps={[]}
844
+ documents={props.documents}
845
+ includeShortcuts={props.includeShortcuts}
846
+ accent={props.accent}
847
+ surface={props.embedded ? "embedded" : "page"}
848
+ pageBase={props.pageBase}
849
+ syncPageUrl={!props.embedded && !!props.pageBase}
850
+ />
851
+ );
852
+ }
853
+
854
+ export function openLayoutHelpDialog(searchHelpApps: GlobalSearchHelpApp[] = [], accent?: string) {
855
+ const session = createSession();
856
+ void prompts.dialog<void>(
857
+ (close) => (
858
+ <HelpShell
859
+ session={session}
860
+ close={close}
861
+ searchHelpApps={searchHelpApps}
862
+ accent={accent}
863
+ surface="modal"
864
+ pageBase={window.__cloudLayoutHelpPageBase}
865
+ />
866
+ ),
867
+ { surface: "bare", header: false, size: "wide" },
868
+ );
869
+ }