@k2b/cloud 0.39.0 → 0.41.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 (237) hide show
  1. package/README.md +8 -0
  2. package/package.json +11 -4
  3. package/src/_internal/app-favicon.ts +4 -2
  4. package/src/_internal/capabilities.ts +72 -48
  5. package/src/_internal/capability-reviews.ts +81 -0
  6. package/src/_internal/capability-schemas.ts +29 -0
  7. package/src/_internal/capability-sentences.ts +217 -1
  8. package/src/_internal/capability-streams.ts +7 -0
  9. package/src/_internal/define-app.ts +49 -18
  10. package/src/_internal/font-preloads.ts +2 -1
  11. package/src/_internal/heartbeat.ts +203 -14
  12. package/src/_internal/registry.ts +28 -1
  13. package/src/_internal/runtime-watcher.ts +6 -0
  14. package/src/_internal/setup-lock.ts +103 -0
  15. package/src/_internal/static-assets.ts +52 -3
  16. package/src/_internal/status-preserving-ssr.ts +2 -0
  17. package/src/access/PermissionEditor.tsx +2 -2
  18. package/src/account/EntitySearch.tsx +1 -1
  19. package/src/ai/action-review.ts +101 -0
  20. package/src/ai/background-review.ts +72 -0
  21. package/src/ai/capabilities.ts +29 -11
  22. package/src/ai/capability-execution.ts +76 -43
  23. package/src/ai/capability-result.ts +95 -0
  24. package/src/ai/chat/action-card.ts +464 -0
  25. package/src/ai/chat/action-messages.ts +129 -0
  26. package/src/ai/chat/action-question.browser-harness.tsx +245 -0
  27. package/src/ai/chat/action-question.fixtures.ts +733 -0
  28. package/src/ai/chat/action-question.tsx +680 -0
  29. package/src/ai/chat/action-text.ts +288 -0
  30. package/src/ai/chat/blocks.tsx +19 -25
  31. package/src/ai/chat/message-actions.tsx +6 -0
  32. package/src/ai/chat/tool-action-words.ts +31 -0
  33. package/src/ai/chat/tool-action.tsx +456 -0
  34. package/src/ai/chat/turn-view.tsx +10 -160
  35. package/src/ai/chat/view-block.tsx +15 -5
  36. package/src/ai/chat-task-contracts.ts +4 -1
  37. package/src/ai/chat-task-reasons-migrate.ts +7 -0
  38. package/src/ai/chat-task-reasons.ts +1 -1
  39. package/src/ai/chat-tasks.ts +55 -25
  40. package/src/ai/client/controller.ts +59 -0
  41. package/src/ai/default-tools.ts +9 -1
  42. package/src/ai/executor.ts +198 -39
  43. package/src/ai/firecrawl-tools.ts +15 -2
  44. package/src/ai/index.ts +3 -0
  45. package/src/ai/migrate.ts +26 -2
  46. package/src/ai/open-questions.ts +172 -0
  47. package/src/ai/projects-routes.ts +3 -0
  48. package/src/ai/protocol.ts +14 -2
  49. package/src/ai/question-text.ts +8 -0
  50. package/src/ai/recent-resources.ts +240 -0
  51. package/src/ai/recent-sent-refs.ts +38 -0
  52. package/src/ai/routes.ts +195 -5
  53. package/src/ai/runtime.ts +1 -0
  54. package/src/ai/skills-routes.ts +3 -0
  55. package/src/ai/skills.ts +10 -1
  56. package/src/ai/store.ts +245 -24
  57. package/src/ai/testing.ts +36 -0
  58. package/src/ai/tool-audit.ts +8 -0
  59. package/src/ai/tool-sentences.ts +152 -34
  60. package/src/ai/tool-wording.ts +36 -8
  61. package/src/ai/tools.ts +8 -3
  62. package/src/ai/types.ts +43 -4
  63. package/src/api/accounts-entities.ts +2 -1
  64. package/src/api/admin-ai-projects.ts +2 -0
  65. package/src/api/admin-ai-skills.ts +2 -0
  66. package/src/api/admin-ai-usage.ts +2 -0
  67. package/src/api/admin-geoip.ts +80 -0
  68. package/src/api/admin-identity.ts +3 -0
  69. package/src/api/admin-lifecycle.ts +3 -0
  70. package/src/api/admin-outgoing-mail.ts +2 -0
  71. package/src/api/app-approval-actions.ts +31 -7
  72. package/src/api/app-approval.ts +46 -5
  73. package/src/api/approvals-loader.ts +23 -1
  74. package/src/api/approvals.ts +2 -0
  75. package/src/api/auth/schemas.ts +3 -0
  76. package/src/api/auth.ts +67 -12
  77. package/src/api/capabilities.ts +44 -11
  78. package/src/api/capability-streams.ts +12 -0
  79. package/src/api/help.ts +4 -28
  80. package/src/api/index.ts +6 -1
  81. package/src/api/lasting-access-messages.ts +17 -0
  82. package/src/api/mcp.ts +30 -3
  83. package/src/api/me-panel-consent.ts +71 -0
  84. package/src/api/me.ts +159 -36
  85. package/src/api/search.ts +3 -1
  86. package/src/api/sync-ops.ts +2 -0
  87. package/src/api/widgets.ts +3 -0
  88. package/src/approvals/Approvals.tsx +166 -262
  89. package/src/approvals/approval-card.ts +68 -0
  90. package/src/approvals/messages.ts +12 -24
  91. package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
  92. package/src/browser/app-approval.ts +54 -5
  93. package/src/browser/commands.ts +22 -6
  94. package/src/browser/composer-catalog.ts +45 -3
  95. package/src/browser/composer-insert.ts +10 -0
  96. package/src/browser/composer-open-messages.ts +17 -0
  97. package/src/browser/composer-open.ts +110 -0
  98. package/src/browser/discoverable-command.ts +6 -0
  99. package/src/browser/hints.ts +6 -0
  100. package/src/browser/live-connection.ts +26 -2
  101. package/src/browser/navigation-apps.ts +7 -0
  102. package/src/browser/panel-context-bridge.ts +30 -0
  103. package/src/browser/panel-context-receiver.ts +53 -0
  104. package/src/browser/panel-menu.tsx +56 -0
  105. package/src/browser/panel-messages.ts +11 -0
  106. package/src/browser/panel-shortcuts.ts +31 -0
  107. package/src/browser/panel-slot.ts +412 -10
  108. package/src/browser/panel.ts +37 -0
  109. package/src/browser/search-commands.ts +6 -3
  110. package/src/capabilities/client.ts +8 -2
  111. package/src/capabilities/reviews.ts +205 -0
  112. package/src/capabilities/server.ts +10 -3
  113. package/src/capabilities/testing.ts +47 -0
  114. package/src/capabilities/types.ts +2 -2
  115. package/src/cli/account.ts +13 -3
  116. package/src/cli/admin/geoip.ts +115 -0
  117. package/src/cli/admin/index.ts +3 -0
  118. package/src/contracts/app-approval.ts +123 -16
  119. package/src/contracts/approvals.ts +2 -2
  120. package/src/contracts/capabilities.ts +410 -18
  121. package/src/contracts/geoip.ts +42 -0
  122. package/src/contracts/index.ts +3 -0
  123. package/src/contracts/panel-context-plain.ts +99 -0
  124. package/src/contracts/panel-context.ts +93 -0
  125. package/src/contracts/seen-hints.ts +35 -0
  126. package/src/contracts/shared.ts +3 -0
  127. package/src/server/client-ip.ts +5 -0
  128. package/src/server/help-payload.ts +38 -0
  129. package/src/server/middleware/auth.ts +13 -1
  130. package/src/server/middleware/middleware.ts +9 -0
  131. package/src/server/middleware/no-transform.ts +19 -0
  132. package/src/server/middleware/rate-limit.ts +4 -6
  133. package/src/server/request-context/address.ts +45 -0
  134. package/src/server/request-context/geoip.ts +380 -0
  135. package/src/server/request-context/index.ts +33 -0
  136. package/src/server/request-context/user-agent.ts +95 -0
  137. package/src/services/accounts/app.ts +23 -3
  138. package/src/services/accounts/users.ts +12 -1
  139. package/src/services/app-approval.ts +382 -59
  140. package/src/services/approvals/device.ts +49 -31
  141. package/src/services/approvals/index.ts +7 -5
  142. package/src/services/approvals/list.ts +27 -6
  143. package/src/services/approvals/question.ts +68 -0
  144. package/src/services/audit/index.ts +1 -0
  145. package/src/services/auth-flows/ipa.ts +39 -2
  146. package/src/services/auth-flows/notification-sender.ts +3 -0
  147. package/src/services/auth-flows/password-reset.ts +32 -6
  148. package/src/services/geoip/index.ts +164 -0
  149. package/src/services/geoip/store.ts +30 -0
  150. package/src/services/geoip/upload.ts +150 -0
  151. package/src/services/identity/routes.ts +5 -1
  152. package/src/services/index.ts +13 -0
  153. package/src/services/known-browsers.ts +312 -0
  154. package/src/services/login-address-store.ts +51 -0
  155. package/src/services/login-context.ts +118 -0
  156. package/src/services/mandates/index.ts +3 -0
  157. package/src/services/mandates/policy.ts +15 -3
  158. package/src/services/notifications/email-frame.ts +1 -1
  159. package/src/services/oauth-tokens.ts +16 -2
  160. package/src/services/panel-context-consent.ts +58 -0
  161. package/src/services/pdf/markdown.ts +160 -2
  162. package/src/services/service-account-credentials.ts +21 -0
  163. package/src/services/session/index.ts +296 -41
  164. package/src/services/session/recent.ts +13 -0
  165. package/src/services/webauthn.ts +12 -0
  166. package/src/shared/email-html.ts +2 -0
  167. package/src/shared/help.ts +3 -0
  168. package/src/shared-text/cursor.ts +22 -0
  169. package/src/shared-text/documents.ts +142 -0
  170. package/src/shared-text/events.ts +43 -0
  171. package/src/shared-text/index.ts +12 -0
  172. package/src/shared-text/log.ts +48 -0
  173. package/src/shared-text/text-diff.ts +23 -0
  174. package/src/shared-text/types.ts +41 -0
  175. package/src/ssr/AdminLayout.tsx +2 -0
  176. package/src/ssr/AppLaunchpadPanel.tsx +2 -2
  177. package/src/ssr/CloudShell.island.tsx +10 -2
  178. package/src/ssr/HelpPanelArticle.island.tsx +308 -0
  179. package/src/ssr/HelpPanelBack.island.tsx +32 -0
  180. package/src/ssr/HelpPanelMenu.island.tsx +133 -0
  181. package/src/ssr/HelpPanelSearch.island.tsx +123 -0
  182. package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
  183. package/src/ssr/HelpPanelView.island.tsx +27 -0
  184. package/src/ssr/Layout.tsx +25 -2
  185. package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
  186. package/src/ssr/LayoutHelp.tsx +222 -419
  187. package/src/ssr/LayoutHelpPanel.browser-harness.tsx +135 -0
  188. package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
  189. package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
  190. package/src/ssr/LayoutHelpTrigger.tsx +27 -2
  191. package/src/ssr/MobileNavigation.tsx +2 -2
  192. package/src/ssr/PanelLayout.tsx +9 -1
  193. package/src/ssr/PanelRuntime.island.tsx +7 -0
  194. package/src/ssr/PanelSlot.tsx +53 -3
  195. package/src/ssr/ShellRuntime.island.tsx +11 -2
  196. package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
  197. package/src/ssr/app-color.ts +14 -9
  198. package/src/ssr/app-navigation.ts +16 -3
  199. package/src/ssr/compact-shell.browser-harness.ts +30 -22
  200. package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
  201. package/src/ssr/help-messages.ts +4 -0
  202. package/src/ssr/help.ts +21 -0
  203. package/src/ssr/hints.ts +25 -0
  204. package/src/ssr/index.ts +3 -0
  205. package/src/ssr/layout-context.ts +2 -0
  206. package/src/ssr/layout-help-article.server.ts +50 -0
  207. package/src/ssr/layout-help-panel-controls.tsx +43 -0
  208. package/src/ssr/layout-help-panel-document.ts +67 -0
  209. package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
  210. package/src/ssr/layout-help-panel-menu.tsx +113 -0
  211. package/src/ssr/layout-help-panel-modules.ts +7 -0
  212. package/src/ssr/layout-help-panel-reader.tsx +159 -0
  213. package/src/ssr/layout-help-panel-search.tsx +69 -0
  214. package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
  215. package/src/ssr/layout-help-panel-state.ts +141 -0
  216. package/src/ssr/layout-help-panel-types.ts +19 -0
  217. package/src/ssr/layout-help-start.ts +33 -7
  218. package/src/ssr/layout-help-views.tsx +484 -0
  219. package/src/ssr/panel-consent.ts +29 -0
  220. package/src/ssr/panel-context.ts +10 -0
  221. package/src/ssr/panel-idle.ts +9 -0
  222. package/src/ssr/panel-slot.browser-harness.tsx +53 -4
  223. package/src/ssr/platform-messages.ts +18 -0
  224. package/src/ssr/shell-data.tsx +2 -1
  225. package/src/styles/approvals.css +6 -0
  226. package/src/styles/effects.css +213 -9
  227. package/src/styles/file-chooser.css +2 -1
  228. package/src/styles/global.css +49 -12
  229. package/src/styles/panel.css +47 -4
  230. package/src/styles/resource-search.css +6 -2
  231. package/src/styles/tokens.css +17 -11
  232. package/src/styles/utilities-buttons.css +3 -2
  233. package/src/styles/utilities-data.css +2 -1
  234. package/src/styles/utilities-feedback.css +2 -2
  235. package/src/styles/utilities-layout.css +3 -13
  236. package/src/styles/utilities-markdown-editor.css +6 -6
  237. package/src/styles/utilities-navigation.css +17 -22
@@ -19,6 +19,15 @@ export const platformMessages = i18n.define({
19
19
  panelLoading: ({ label }: { label: string }) => `${label} is loading`,
20
20
  enlargePanel: "Enlarge panel",
21
21
  shrinkPanel: "Shrink panel",
22
+ panelConsentTitle: ({ app }: { app: string }) => `May ${app} see where you are?`,
23
+ panelConsentBody: ({ app }: { app: string }) =>
24
+ `${app} only learns which place you are at. It can read only what you may read yourself. You can change this in the ⋯ menu at any time.`,
25
+ panelConsentAllow: "Allow",
26
+ panelConsentDeny: "Don't share",
27
+ panelMenu: "Panel menu",
28
+ panelFrom: ({ app }: { app: string }) => `Panel from ${app}`,
29
+ panelShareContext: "Share context",
30
+ panelConsentSaveError: "Not saved. Try again.",
22
31
  printFallbackTitle: "This view does not print well.",
23
32
  printFallbackHint: "Open the item you want to print, or use the app's export.",
24
33
  admin: "Admin",
@@ -108,6 +117,15 @@ export const platformMessages = i18n.define({
108
117
  panelLoading: ({ label }: { label: string }) => `${label} lädt`,
109
118
  enlargePanel: "Panel vergrößern",
110
119
  shrinkPanel: "Panel verkleinern",
120
+ panelConsentTitle: ({ app }: { app: string }) => `Darf ${app} sehen, wo du gerade bist?`,
121
+ panelConsentBody: ({ app }: { app: string }) =>
122
+ `${app} erfährt nur, an welchem Ort du gerade bist. Lesen kann es nur, was du selbst lesen darfst. Du kannst das im Menü ⋯ jederzeit ändern.`,
123
+ panelConsentAllow: "Erlauben",
124
+ panelConsentDeny: "Nicht teilen",
125
+ panelMenu: "Panel-Menü",
126
+ panelFrom: ({ app }: { app: string }) => `Panel von ${app}`,
127
+ panelShareContext: "Kontext teilen",
128
+ panelConsentSaveError: "Nicht gespeichert. Versuche es erneut.",
111
129
  printFallbackTitle: "Diese Ansicht lässt sich nicht gut drucken.",
112
130
  printFallbackHint: "Öffne den Eintrag, den du drucken möchtest, oder nutze den Export der App.",
113
131
  admin: "Administration",
@@ -1,6 +1,7 @@
1
1
  import type { KeyboardPlatform } from "@k2b/ui";
2
2
  import { createContext, type JSX, useContext } from "solid-js";
3
3
  import type { AppSearchLink } from "../contracts/app";
4
+ import type { PanelConsentSnapshot, PanelPageContext } from "../contracts/panel-context";
4
5
  import type { RailSnapshot } from "../contracts/rail-preferences";
5
6
  import type { CloudThemePreference } from "../shared/theme";
6
7
  import type { AppLaunchpadLegalLink } from "./app-launchpad";
@@ -8,7 +9,7 @@ import type { PanelProvider } from "./app-navigation";
8
9
  import type { RailApp } from "./rail-navigation";
9
10
 
10
11
  export type ShellData = {
11
- panel?: { providers: PanelProvider[]; provider?: string };
12
+ panel?: { providers: PanelProvider[]; provider?: string; context: PanelPageContext | null; consent?: PanelConsentSnapshot };
12
13
  /** The authorized navigation catalog, sorted for the request locale: the rail, the app grid and the phone menu show it. */
13
14
  apps: RailApp[];
14
15
  /** Signed-in pages: the person's rail preferences. */
@@ -366,6 +366,12 @@
366
366
  overflow-wrap: anywhere;
367
367
  }
368
368
 
369
+ .k2b-ui .cloud-approval-band__reason {
370
+ color: var(--k2b-text-secondary);
371
+ font-size: 0.8125rem;
372
+ overflow-wrap: anywhere;
373
+ }
374
+
369
375
  .k2b-ui .cloud-approval-band__text .cloud-approval__origin {
370
376
  margin: 0;
371
377
  }
@@ -104,6 +104,12 @@
104
104
  border-radius: var(--k2b-radius-control);
105
105
  }
106
106
 
107
+ /* An open question stands apart from the place before and after it; consecutive outcome lines stay together. */
108
+ .k2b-ui .ai-turn__action:has(.k2b-chat-decision__card) + .ai-turn__action,
109
+ .k2b-ui .ai-turn__action + .ai-turn__action:has(.k2b-chat-decision__card) {
110
+ margin-top: 1rem;
111
+ }
112
+
107
113
  .k2b-ui .ai-turn__action:focus {
108
114
  outline: none;
109
115
  }
@@ -113,6 +119,60 @@
113
119
  outline-offset: var(--k2b-focus-offset);
114
120
  }
115
121
 
122
+ /* Assistant questions and outcome lines (card v4), built from @k2b/ui Chat.Decision, Chat.Preview, and Chat.Outcome.
123
+ Only what an older review needs lives here: values that keep their line breaks, a Markdown block, and the call's
124
+ raw input on demand. Everything else is the shared primitives' look. */
125
+ .k2b-ui .cloud-action-preview__value {
126
+ white-space: pre-wrap;
127
+ overflow-wrap: anywhere;
128
+ }
129
+
130
+ .k2b-ui .cloud-action-preview__markdown {
131
+ min-width: 0;
132
+ color: var(--k2b-text-secondary);
133
+ font-size: inherit;
134
+ line-height: inherit;
135
+ }
136
+
137
+ .k2b-ui .cloud-action-preview__input {
138
+ display: grid;
139
+ min-width: 0;
140
+ justify-items: start;
141
+ gap: 0.5rem;
142
+ }
143
+
144
+ .k2b-ui .cloud-action-preview__input > [role="region"] {
145
+ width: 100%;
146
+ min-width: 0;
147
+ }
148
+
149
+ .k2b-ui .cloud-action-preview__raw {
150
+ max-height: 13rem;
151
+ margin: 0;
152
+ overflow: auto;
153
+ border-radius: var(--k2b-radius-control);
154
+ padding: 0.5rem;
155
+ color: var(--k2b-text);
156
+ background: var(--k2b-surface-muted);
157
+ box-shadow: var(--ui-control-recess);
158
+ font-size: 0.6875rem;
159
+ line-height: 1.45;
160
+ white-space: pre-wrap;
161
+ }
162
+
163
+ /* The review's other links continue the preview above them instead of starting a new block. */
164
+ .k2b-ui .k2b-chat-decision__card > .k2b-chat-preview + .cloud-action-preview__links {
165
+ margin-top: 0.25rem;
166
+ }
167
+
168
+ /* The approvals page: where a request waits stands above its card, quiet, on the card's edge. */
169
+ .k2b-ui .cloud-approvals .cloud-approval {
170
+ display: grid;
171
+ min-width: 0;
172
+ gap: 0.25rem;
173
+ grid-template-columns: minmax(0, 1fr);
174
+ }
175
+
116
176
  /* A pending approval: a calm tinted card in place 4 that says what will happen. Fields stack on phones and the
117
177
  decision buttons share the width there. */
118
178
  .k2b-ui .ai-approval {
@@ -341,10 +401,6 @@
341
401
  /* App help is a reader, not a generic Markdown preview. Keep the measure,
342
402
  hierarchy, and rhythm close to the task-first Help wireframe while reusing
343
403
  the same safe server-rendered Markdown pipeline as the rest of Cloud. */
344
- .help-topic-icon {
345
- background: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 10%, var(--ui-surface-muted));
346
- }
347
-
348
404
  /* Help shell: one head (app tile, "Help for <App>", the menu, and the search) above the start, an article, or
349
405
  results. The head never changes size between them, so the search stays in one place. The start and the head share
350
406
  the article's measure: wide, the article and its contents; narrower, the article column alone. */
@@ -352,6 +408,21 @@
352
408
  container: help-shell / inline-size;
353
409
  }
354
410
 
411
+ /* The panel keeps its start and its server article in the document while hidden, so Back needs no client render.
412
+ Its flex and grid surfaces would otherwise override the browser's hidden rule. The start's own wrapper adds no box,
413
+ so its sections keep the start's gap. */
414
+ .help-shell[data-surface="panel"] [hidden] {
415
+ display: none;
416
+ }
417
+
418
+ .help-shell[data-surface="panel"] [data-help-start-content] {
419
+ display: contents;
420
+ }
421
+
422
+ .help-shell[data-surface="panel"] [data-help-start-content][hidden] {
423
+ display: none;
424
+ }
425
+
355
426
  .help-measure {
356
427
  width: 100%;
357
428
  max-width: 56.5rem;
@@ -371,6 +442,7 @@
371
442
 
372
443
  /* Above a scroll area the head keeps the same scrollbar gutter, so both centre their measure alike. */
373
444
  .help-shell[data-surface="modal"] .help-head,
445
+ .help-shell[data-surface="panel"] .help-head,
374
446
  .help-shell[data-surface="embedded"] .help-head {
375
447
  overflow: hidden;
376
448
  padding-bottom: 0.25rem;
@@ -384,6 +456,29 @@
384
456
  }
385
457
  }
386
458
 
459
+ /* Help without a panel slot on a phone: the shared bottom sheet at its full height, with its grabber (#858). */
460
+ .k2b-ui .k2b-dialog.cloud-help-sheet {
461
+ height: min(92dvh, var(--k2b-dialog-available-height));
462
+ }
463
+
464
+ .cloud-help-sheet .k2b-bottom-sheet-viewport {
465
+ height: 100%;
466
+ }
467
+
468
+ .cloud-help-sheet .help-shell[data-surface="modal"] {
469
+ height: auto;
470
+ flex: 1 1 auto;
471
+ border: 0;
472
+ border-radius: 0;
473
+ background: transparent;
474
+ box-shadow: none;
475
+ }
476
+
477
+ /* In the panel the slot's own title row sits right above: the Help head follows it closely. */
478
+ .help-shell[data-surface="panel"] .help-head {
479
+ padding-top: 0.25rem;
480
+ }
481
+
387
482
  .help-head-inner {
388
483
  display: flex;
389
484
  flex-direction: column;
@@ -549,7 +644,7 @@
549
644
  }
550
645
 
551
646
  .help-start-tile:hover {
552
- background: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 6%, var(--ui-surface-subtle));
647
+ background: var(--ui-surface-muted);
553
648
  }
554
649
 
555
650
  .help-start-tile-icon,
@@ -559,8 +654,6 @@
559
654
  height: 2.25rem;
560
655
  flex: 0 0 auto;
561
656
  place-items: center;
562
- border-radius: var(--ui-radius-control);
563
- background: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 14%, var(--ui-surface-muted));
564
657
  font-size: 1.125rem;
565
658
  }
566
659
 
@@ -568,6 +661,32 @@
568
661
  margin-bottom: 0.5rem;
569
662
  }
570
663
 
664
+ /* A narrow reader, such as the panel column at 20-23 rem, has no room for two tiles side by side: each task keeps
665
+ its tile, one per row, with the icon beside its title. */
666
+ @container help-shell (max-width: 23rem) {
667
+ .help-start-tiles {
668
+ grid-template-columns: minmax(0, 1fr);
669
+ }
670
+
671
+ .help-start-tile {
672
+ display: grid;
673
+ grid-template-columns: auto minmax(0, 1fr);
674
+ column-gap: 0.75rem;
675
+ row-gap: 0.125rem;
676
+ align-items: start;
677
+ padding: 0.75rem;
678
+ }
679
+
680
+ .help-start-tile-icon {
681
+ grid-row: span 2;
682
+ margin-bottom: 0;
683
+ }
684
+
685
+ .help-start-tile-description {
686
+ -webkit-line-clamp: 2;
687
+ }
688
+ }
689
+
571
690
  .help-start-tile-title {
572
691
  max-width: 100%;
573
692
  color: var(--color-zinc-950);
@@ -750,12 +869,56 @@
750
869
  padding-top: 0.25rem;
751
870
  }
752
871
 
872
+ /* The article head names the app whose Help this is: the app's own canvas gradient (the same tokens and strength as
873
+ the app background, over the reader's surface), without a border or a shadow, and the article's icon in the flat
874
+ shared tile. Without app colours it stays the plain surface. The embedded readers (Grids, Pulse) sit inside the
875
+ app's own chrome, which already names the app: their head stays flat. Text sits on this gradient, so its tint stays at
876
+ most 30 % light and 15 % dark, as on the phone shell: then the title and the lead read at 4.5:1 on any app colour. */
753
877
  .help-article-head {
878
+ margin-bottom: 2rem;
879
+ }
880
+
881
+ .help-article:not([data-surface="embedded"]) .help-article-head {
882
+ display: grid;
883
+ grid-template-columns: minmax(0, 1fr) auto;
884
+ align-items: center;
885
+ gap: 1.5rem;
886
+ border-radius: var(--ui-radius-surface);
887
+ padding: 1.5rem;
888
+ background: linear-gradient(
889
+ var(--app-canvas-angle, 135deg),
890
+ color-mix(in srgb, var(--app-canvas-from, var(--k2b-surface)) min(var(--app-canvas-strength, 0%), 30%), var(--k2b-surface)),
891
+ color-mix(in srgb, var(--app-canvas-via, var(--k2b-surface)) min(var(--app-canvas-strength, 0%), 30%), var(--k2b-surface)) 50%,
892
+ color-mix(in srgb, var(--app-canvas-to, var(--k2b-surface)) min(var(--app-canvas-strength, 0%), 30%), var(--k2b-surface))
893
+ );
894
+ }
895
+
896
+ .dark .help-article:not([data-surface="embedded"]) .help-article-head {
897
+ background: linear-gradient(
898
+ var(--app-canvas-angle, 135deg),
899
+ color-mix(in srgb, var(--app-canvas-from, var(--k2b-surface)) min(var(--app-canvas-dark-strength, 0%), 15%), var(--k2b-surface)),
900
+ var(--k2b-surface) 50%,
901
+ color-mix(in srgb, var(--app-canvas-to, var(--k2b-surface)) min(var(--app-canvas-dark-strength, 0%), 15%), var(--k2b-surface))
902
+ );
903
+ }
904
+
905
+ .help-article-head-copy {
754
906
  display: flex;
907
+ min-width: 0;
755
908
  flex-direction: column;
756
909
  align-items: flex-start;
757
910
  gap: 0.75rem;
758
- margin-bottom: 2rem;
911
+ }
912
+
913
+ .help-article-icon {
914
+ width: 4.5rem;
915
+ height: 4.5rem;
916
+ font-size: 2.25rem;
917
+ }
918
+
919
+ /* Over the gradient the chips keep their own quiet fill, so they read as facts and not as part of the colour. */
920
+ .help-article:not([data-surface="embedded"]) .help-article-head .help-article-fact {
921
+ background: color-mix(in srgb, var(--k2b-surface) 72%, transparent);
759
922
  }
760
923
 
761
924
  .help-article-path {
@@ -1104,7 +1267,7 @@
1104
1267
  }
1105
1268
 
1106
1269
  .help-document :where(ul > li)::marker {
1107
- color: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 75%, var(--color-zinc-400));
1270
+ color: var(--ui-text-secondary);
1108
1271
  }
1109
1272
 
1110
1273
  .help-document :where(ol > li)::marker {
@@ -1460,11 +1623,52 @@
1460
1623
  }
1461
1624
  }
1462
1625
 
1626
+ /* Narrow: the tile stands before the title, and the path, the short answer, and the chips use the full width. */
1463
1627
  @container help-article (max-width: 40rem) {
1464
1628
  .help-article-head {
1465
1629
  margin-bottom: 1.5rem;
1466
1630
  }
1467
1631
 
1632
+ .help-article:not([data-surface="embedded"]) .help-article-head {
1633
+ grid-template-columns: auto minmax(0, 1fr);
1634
+ column-gap: 0.75rem;
1635
+ row-gap: 0;
1636
+ padding: 1.25rem 1rem;
1637
+ }
1638
+
1639
+ .help-article:not([data-surface="embedded"]) .help-article-head-copy {
1640
+ display: contents;
1641
+ }
1642
+
1643
+ .help-article:not([data-surface="embedded"]) .help-article-path {
1644
+ grid-column: 1 / -1;
1645
+ grid-row: 1;
1646
+ margin-bottom: 0.75rem;
1647
+ }
1648
+
1649
+ .help-article-icon {
1650
+ grid-column: 1;
1651
+ grid-row: 2;
1652
+ width: 2.75rem;
1653
+ height: 2.75rem;
1654
+ font-size: 1.375rem;
1655
+ }
1656
+
1657
+ .help-article:not([data-surface="embedded"]) .help-article-title {
1658
+ grid-column: 2;
1659
+ grid-row: 2;
1660
+ }
1661
+
1662
+ .help-article:not([data-surface="embedded"]) .help-article-head-copy > :not(.help-article-path, .help-article-title) {
1663
+ grid-column: 1 / -1;
1664
+ margin-top: 0.75rem;
1665
+ }
1666
+
1667
+ /* The panel article has no path row (the panel head names the app), so the tile and the title open the head. */
1668
+ .help-article[data-surface="panel"] :is(.help-article-icon, .help-article-title) {
1669
+ grid-row: 1;
1670
+ }
1671
+
1468
1672
  /* Two columns leave a phone about 140 px per alternative. */
1469
1673
  .help-document .help-compare > ul {
1470
1674
  grid-template-columns: minmax(0, 1fr);
@@ -68,8 +68,9 @@
68
68
  color: var(--k2b-text-secondary);
69
69
  }
70
70
 
71
+ /* A picked file is an object row: its check carries the app ink. */
71
72
  .cloud-file-chooser__icon[data-selected="true"] {
72
- color: var(--k2b-action);
73
+ color: var(--ui-app-accent-text);
73
74
  }
74
75
 
75
76
  .cloud-file-chooser__meta {
@@ -118,9 +118,8 @@ html:has(.cloud-app-canvas[data-layout-full-page="true"]) {
118
118
  55%,
119
119
  color-mix(in srgb, var(--app-canvas-to, var(--color-zinc-400)) var(--cloud-shell-strength), var(--ui-canvas))
120
120
  );
121
- /* On color the open item is the surface; on the surface it is tinted. Its
122
- label is the app ink, which app-color.ts keeps at AA on the surface and on
123
- the app's selection fills. */
121
+ /* On color the open item is the surface in light and tinted in dark. Its
122
+ label is the app ink, which app-color.ts keeps at AA on both fills. */
124
123
  --cloud-shell-selected: var(--k2b-surface);
125
124
  --cloud-shell-selected-text: var(--ui-app-accent-text);
126
125
  }
@@ -128,7 +127,9 @@ html:has(.cloud-app-canvas[data-layout-full-page="true"]) {
128
127
  .dark .cloud-app-canvas {
129
128
  --cloud-shell-strength: 22%;
130
129
  --cloud-shell-via-strength: 12%;
131
- --cloud-shell-selected: var(--ui-app-selected-nav);
130
+ /* The navigation share over the muted surface, which stays apart from the
131
+ dark bar; app-color.ts derives the same fill as shellSelected. */
132
+ --cloud-shell-selected: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 17%, var(--ui-surface-muted));
132
133
  }
133
134
 
134
135
  :root {
@@ -493,6 +494,35 @@ solid-island {
493
494
  background: linear-gradient(to bottom, color-mix(in srgb, var(--k2b-surface) 0%, transparent), var(--k2b-surface) 0.75rem);
494
495
  pointer-events: none;
495
496
  }
497
+ /* The app's one primary action (the primary of `AppWorkspace.Head`) is the
498
+ round button in that free place beside the bar: the bar's height, its end
499
+ edge, and its shadow. The card keeps the bar's space free at its end and in
500
+ its scroll padding, so the button never covers the last row or a focused
501
+ control either. Only the outermost workspace's head offers it. */
502
+ .cloud-app-canvas[data-layout-authenticated]:has(> .cloud-tab-bar)
503
+ .k2b-app-workspace:not(.k2b-app-workspace *)
504
+ > .k2b-app-workspace__head
505
+ .k2b-app-workspace__head-primary {
506
+ position: fixed;
507
+ z-index: 11;
508
+ right: max(calc(env(safe-area-inset-right, 0px) + 0.75rem), calc((100vw - 33.75rem) / 2));
509
+ bottom: var(--cloud-tab-bar-end);
510
+ width: var(--cloud-tab-bar-height);
511
+ min-width: var(--cloud-tab-bar-height);
512
+ height: var(--cloud-tab-bar-height);
513
+ min-height: var(--cloud-tab-bar-height);
514
+ box-shadow:
515
+ 0 10px 28px -14px rgb(9 9 11 / 0.34),
516
+ 0 0 0 1px rgb(9 9 11 / 0.06);
517
+ }
518
+ .dark .cloud-app-canvas[data-layout-authenticated]:has(> .cloud-tab-bar)
519
+ .k2b-app-workspace:not(.k2b-app-workspace *)
520
+ > .k2b-app-workspace__head
521
+ .k2b-app-workspace__head-primary {
522
+ box-shadow:
523
+ 0 12px 30px -14px rgb(0 0 0 / 0.7),
524
+ 0 0 0 1px var(--k2b-border);
525
+ }
496
526
  }
497
527
 
498
528
  /* The bar exists only on signed-in phones. Its row keeps the place of the
@@ -644,9 +674,10 @@ solid-island {
644
674
  Phones ignore it. The app area takes the app bar's place and starts at the
645
675
  card spacing. The button that
646
676
  shows them again floats at the app area's bottom start corner; on touch
647
- screens a drag from the 18 px strip at the left edge does the same. From
648
- 768 to 1023 px the navigation is already an overlay behind the
649
- workspace head's button, so there only the app bar hides. */
677
+ screens a drag from the 18 px strip at the left edge does the same. In an
678
+ app host below 64 rem (a window from 768 to 1023 px, or the app beside the
679
+ panel column) the navigation is already an overlay behind the workspace
680
+ head's button, so there only the app bar hides. */
650
681
  .cloud-shell-show,
651
682
  .cloud-shell-reveal {
652
683
  display: none;
@@ -698,12 +729,18 @@ solid-island {
698
729
  left: calc(max(var(--cloud-card-inset), env(safe-area-inset-left, 0px)) + 0.75rem);
699
730
  bottom: calc(var(--cloud-card-inset) + var(--cloud-card-inset-bottom) + 0.75rem);
700
731
  }
701
- /* The navigation leaves like a hidden sidebar: its track, its resize
702
- handle, and navigation panes go, and the work area rounds its start
703
- corners with the card. An island may wrap the sidebar or Main, as in
704
- Grids. */
732
+ }
733
+
734
+ /* The navigation leaves like a hidden sidebar: its track, its resize handle,
735
+ and navigation panes go, and the work area rounds its start corners with
736
+ the card. An island may wrap the sidebar or Main, as in Grids. This follows
737
+ the app's host, as the sidebar column does: in a host below 64 rem, such as
738
+ the app beside the panel column, the navigation is an overlay behind the
739
+ workspace head's button and stays there. */
740
+ @container k2b-app-area (min-width: 64rem) {
705
741
  .cloud-app-canvas[data-shell-focus] .k2b-app-workspace:not(.k2b-app-workspace *) {
706
742
  --k2b-workspace-start-radius: var(--k2b-workspace-inner-radius);
743
+ --k2b-workspace-sidebar-space: 0px;
707
744
  }
708
745
  .cloud-app-canvas[data-shell-focus]
709
746
  .k2b-app-workspace:not(.k2b-app-workspace *)
@@ -749,7 +786,7 @@ solid-island {
749
786
  .cloud-mobile-menu .k2b-panel-dialog__actions { flex: 1; min-width: 0; }
750
787
  .cloud-mobile-menu__switch { width: 100%; flex-wrap: nowrap; }
751
788
  .cloud-mobile-menu__switch button { flex: 1; min-width: 0; min-height: 3rem; white-space: nowrap; }
752
- .cloud-mobile-menu__switch button > span { overflow: hidden; text-overflow: ellipsis; }
789
+ .cloud-mobile-menu__switch button > span { min-width: 0; overflow: hidden; }
753
790
  .cloud-mobile-menu .k2b-dialog__close { width: 2.75rem; height: 2.75rem; flex-shrink: 0; }
754
791
 
755
792
  .cloud-mobile-menu .k2b-panel-dialog__body { padding: .75rem 1rem 1rem; }
@@ -68,8 +68,7 @@
68
68
  color: var(--k2b-text-muted);
69
69
  }
70
70
 
71
- .cloud-panel__title > span,
72
- .cloud-panel__tab-label {
71
+ .cloud-panel__title > span {
73
72
  overflow: hidden;
74
73
  text-overflow: ellipsis;
75
74
  white-space: nowrap;
@@ -122,21 +121,29 @@
122
121
  outline-offset: -2px;
123
122
  }
124
123
 
124
+ /* Row 1 holds the consent question when the shell asks; the layers share row 2 and only change visibility. */
125
125
  .cloud-panel__body {
126
126
  position: relative;
127
+ display: grid;
127
128
  flex: 1;
129
+ grid-template-columns: minmax(0, 1fr);
130
+ grid-template-rows: auto minmax(0, 1fr);
128
131
  min-height: 0;
129
132
  }
130
133
 
131
134
  .cloud-panel__body > :is(.cloud-panel__skeleton, .cloud-panel__frame, .cloud-panel__fallback, .cloud-panel__shield, .cloud-panel__closing) {
132
- position: absolute;
133
- inset: 0;
135
+ grid-area: 2 / 1;
134
136
  width: 100%;
135
137
  height: 100%;
138
+ min-height: 0;
136
139
  visibility: hidden;
137
140
  opacity: 0;
138
141
  }
139
142
 
143
+ .cloud-panel__body > .cloud-panel__closing {
144
+ grid-row: 1 / -1;
145
+ }
146
+
140
147
  .cloud-panel[data-state="ready"] .cloud-panel__frame,
141
148
  .cloud-panel[data-state="failed"] .cloud-panel__fallback,
142
149
  .cloud-panel[data-resizing="true"] .cloud-panel__shield,
@@ -371,3 +378,39 @@ html:has([data-cloud-panel-page]),
371
378
  html:has([data-cloud-panel-page]) body {
372
379
  background: var(--k2b-surface);
373
380
  }
381
+
382
+ /* The shell's consent question: one calm notice above the provider page, inset from the card edge like the
383
+ tabs above it. The server draws it when the answer is missing, so hydration never moves it; the error line is
384
+ reserved and fills in place. */
385
+ .cloud-panel .cloud-panel__consent.k2b-notice-card {
386
+ display: none;
387
+ grid-area: 1 / 1;
388
+ margin: 0 0.5rem 0.5rem;
389
+ padding: 0.75rem 0.875rem 0;
390
+ outline: none;
391
+ }
392
+
393
+ .cloud-panel[data-consent="ask"] .cloud-panel__consent.k2b-notice-card {
394
+ display: block;
395
+ }
396
+
397
+ .cloud-panel__consent-actions {
398
+ display: flex;
399
+ flex-wrap: wrap;
400
+ gap: 0.5rem;
401
+ }
402
+
403
+ /* The reserved line doubles as the notice's bottom padding until a failed save fills it. */
404
+ .cloud-panel__consent-error {
405
+ min-height: 1.75rem;
406
+ margin: 0;
407
+ padding-block: 0.25rem;
408
+ color: var(--k2b-danger-text);
409
+ font-size: 0.8125rem;
410
+ line-height: 1.25rem;
411
+ }
412
+
413
+ .cloud-panel__menu {
414
+ display: inline-flex;
415
+ flex: none;
416
+ }
@@ -184,10 +184,14 @@
184
184
  .cloud-resource-search__result:hover {
185
185
  background: var(--k2b-hover);
186
186
  }
187
- .cloud-resource-search__result.is-active,
188
- .cloud-resource-search__result.is-selected {
187
+ /* The keyboard-active option is a state of the listbox and keeps the platform
188
+ selection; a picked resource is an object row and carries the app fill. */
189
+ .cloud-resource-search__result.is-active {
189
190
  background: var(--k2b-selected);
190
191
  }
192
+ .cloud-resource-search__result.is-selected {
193
+ background: var(--ui-app-selected);
194
+ }
191
195
  .cloud-resource-search__result-copy {
192
196
  display: flex;
193
197
  flex: 1;
@@ -7,21 +7,23 @@
7
7
  --ui-surface-subtle: rgb(248 248 248 / 0.96);
8
8
  --ui-surface-muted: rgb(243 244 243 / 0.96);
9
9
  --ui-surface-raised: rgb(255 255 255 / 0.99);
10
- /* Dimmed text stays at WCAG AA on the muted surfaces it often sits on, such
11
- as dialog section groups, not only on the plain surface. */
12
- --ui-text-dimmed: color-mix(in srgb, var(--color-zinc-500) 80%, var(--color-zinc-600));
10
+ /* The ink hierarchy matches @k2b/ui: secondary is its text-secondary, and
11
+ dimmed sits one step below its text-muted. Dimmed keeps at least 6.5:1
12
+ on the muted surfaces it often sits on, such as dialog section groups. */
13
+ --ui-text-secondary: #27272a;
14
+ --ui-text-dimmed: #55555e;
13
15
  --ui-border: rgb(218 220 219 / 0.68);
14
16
  --ui-border-strong: rgb(207 210 208 / 0.9);
15
17
  --ui-hover: rgb(239 240 239 / 0.9);
16
18
  --ui-active: rgb(229 231 230 / 0.94);
17
19
  --ui-selected: color-mix(in srgb, var(--color-blue-500) 7%, var(--ui-surface));
18
20
  /* app-color.ts computes these defaults from --color-blue-500; app-color.test.ts pins them. */
19
- --app-ink-light: #2264c9;
21
+ --app-ink-light: #2469d4;
20
22
  --app-ink-dark: #589aff;
21
23
  --app-selected-light: #f0f6ff;
22
24
  --app-selected-dark: #15243b;
23
- --app-selected-nav-light: #e0e8f6;
24
- --app-selected-nav-dark: #22334e;
25
+ --app-selected-nav-light: #eaf2ff;
26
+ --app-selected-nav-dark: #152742;
25
27
  --ui-app-accent-text: var(--app-ink-light);
26
28
  --ui-app-selected: var(--app-selected-light);
27
29
  --ui-app-selected-nav: var(--app-selected-nav-light);
@@ -51,7 +53,6 @@
51
53
  --ui-data-divider: var(--ui-border);
52
54
  --ui-data-row-divider: color-mix(in srgb, var(--ui-border) 72%, transparent);
53
55
  --ui-data-row-hover: rgb(9 9 11 / 0.025);
54
- --ui-data-row-selected: var(--ui-selected);
55
56
  --ui-data-column-hover: rgb(9 9 11 / 0.012);
56
57
  --ui-dialog-surface: var(--ui-surface-raised);
57
58
  --ui-dialog-border: var(--ui-border-strong);
@@ -146,7 +147,8 @@
146
147
  --ui-surface-subtle: color-mix(in srgb, var(--ui-surface) 68%, var(--ui-surface-muted));
147
148
  --ui-surface-muted: #20242a;
148
149
  --ui-surface-raised: #181d24;
149
- --ui-text-dimmed: color-mix(in srgb, var(--color-zinc-500) 40%, var(--color-zinc-400));
150
+ --ui-text-secondary: #f1f1f3;
151
+ --ui-text-dimmed: #a8a8b2;
150
152
  --ui-border: #29313a;
151
153
  --ui-border-strong: #3a4552;
152
154
  --ui-hover: #252c35;
@@ -198,7 +200,8 @@
198
200
  }
199
201
 
200
202
  /* Custom properties resolve where declared. Each app scope must re-read its
201
- inline per-app values instead of inheriting the resolved platform defaults. */
203
+ inline per-app values instead of inheriting the resolved platform defaults.
204
+ The page shell's body carries the current app's scope so portalled dialogs inherit it. */
202
205
  .cloud-app-canvas,
203
206
  .app-accent-scope {
204
207
  --ui-app-accent-text: var(--app-ink-light);
@@ -209,6 +212,7 @@
209
212
  it to the active app appearance. */
210
213
  --k2b-app-workspace-active: var(--ui-app-accent-text);
211
214
  --k2b-app-workspace-selected: var(--ui-app-selected-nav);
215
+ --k2b-row-selected: var(--ui-app-selected);
212
216
  --k2b-detail-panel-accent: var(--ui-app-accent-text);
213
217
  --k2b-link-accent: var(--ui-app-accent-text);
214
218
  }
@@ -220,6 +224,7 @@
220
224
  --ui-app-accent-border: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
221
225
  --k2b-app-workspace-active: var(--ui-app-accent-text);
222
226
  --k2b-app-workspace-selected: var(--ui-app-selected-nav);
227
+ --k2b-row-selected: var(--ui-app-selected);
223
228
  --k2b-detail-panel-accent: var(--ui-app-accent-text);
224
229
  --k2b-link-accent: var(--ui-app-accent-text);
225
230
  }
@@ -239,7 +244,7 @@
239
244
  }
240
245
 
241
246
  @utility text-secondary {
242
- @apply text-zinc-700 dark:text-zinc-300;
247
+ color: var(--ui-text-secondary);
243
248
  }
244
249
 
245
250
  @utility text-dimmed {
@@ -247,5 +252,6 @@
247
252
  }
248
253
 
249
254
  @utility text-label {
250
- @apply text-sm font-medium text-zinc-700 dark:text-zinc-300;
255
+ @apply text-sm font-medium;
256
+ color: var(--ui-text-secondary);
251
257
  }