@k2b/cloud 0.38.0 → 0.40.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 (244) hide show
  1. package/README.md +8 -0
  2. package/package.json +12 -5
  3. package/scripts/build-canvas-workers.ts +109 -29
  4. package/scripts/build.ts +3 -5
  5. package/src/_internal/app-favicon.ts +4 -2
  6. package/src/_internal/app-path.ts +25 -0
  7. package/src/_internal/app-presentation.ts +4 -0
  8. package/src/_internal/canvas-worker.ts +5 -3
  9. package/src/_internal/capabilities.ts +72 -48
  10. package/src/_internal/capability-reviews.ts +81 -0
  11. package/src/_internal/capability-schemas.ts +29 -0
  12. package/src/_internal/capability-sentences.ts +216 -1
  13. package/src/_internal/capability-streams.ts +7 -0
  14. package/src/_internal/define-app.ts +63 -16
  15. package/src/_internal/font-preloads.ts +2 -1
  16. package/src/_internal/panel-cookie.ts +39 -0
  17. package/src/_internal/registry-validation.ts +21 -26
  18. package/src/_internal/runtime-context.ts +1 -0
  19. package/src/_internal/runtime-watcher.ts +6 -0
  20. package/src/_internal/static-assets.ts +52 -3
  21. package/src/_internal/status-preserving-ssr.ts +2 -0
  22. package/src/access/PermissionEditor.browser-harness.tsx +57 -38
  23. package/src/access/PermissionEditor.tsx +32 -7
  24. package/src/access/ResourceApiKeys.tsx +5 -1
  25. package/src/account/EntitySearch.tsx +1 -1
  26. package/src/ai/action-review.ts +101 -0
  27. package/src/ai/approval-routes.ts +5 -2
  28. package/src/ai/approvals.ts +139 -28
  29. package/src/ai/background-review.ts +72 -0
  30. package/src/ai/browser.ts +10 -0
  31. package/src/ai/capabilities.ts +29 -11
  32. package/src/ai/capability-execution.ts +76 -43
  33. package/src/ai/capability-result.ts +95 -0
  34. package/src/ai/chat/action-card.ts +398 -0
  35. package/src/ai/chat/action-messages.ts +129 -0
  36. package/src/ai/chat/action-question.browser-harness.tsx +245 -0
  37. package/src/ai/chat/action-question.fixtures.ts +733 -0
  38. package/src/ai/chat/action-question.tsx +669 -0
  39. package/src/ai/chat/action-sentences.ts +1 -44
  40. package/src/ai/chat/blocks.tsx +19 -25
  41. package/src/ai/chat/message-actions.tsx +6 -0
  42. package/src/ai/chat/tool-action.tsx +481 -0
  43. package/src/ai/chat/turn-view.tsx +45 -163
  44. package/src/ai/chat/view-block.tsx +22 -7
  45. package/src/ai/chat-task-contracts.ts +31 -3
  46. package/src/ai/chat-task-reasons-migrate.ts +75 -0
  47. package/src/ai/chat-task-reasons.ts +92 -0
  48. package/src/ai/chat-tasks.ts +438 -78
  49. package/src/ai/client/controller.ts +59 -0
  50. package/src/ai/default-tools.ts +9 -1
  51. package/src/ai/executor.ts +190 -39
  52. package/src/ai/index.ts +14 -0
  53. package/src/ai/migrate.ts +39 -4
  54. package/src/ai/projects-routes.ts +3 -0
  55. package/src/ai/protocol.ts +14 -2
  56. package/src/ai/recent-resources.ts +240 -0
  57. package/src/ai/recent-sent-refs.ts +38 -0
  58. package/src/ai/request-context.ts +7 -1
  59. package/src/ai/routes.ts +172 -2
  60. package/src/ai/runtime.ts +13 -27
  61. package/src/ai/skills-routes.ts +3 -0
  62. package/src/ai/skills.ts +10 -1
  63. package/src/ai/store.ts +232 -27
  64. package/src/ai/timeline.ts +9 -1
  65. package/src/ai/tool-audit.ts +8 -0
  66. package/src/ai/tool-wording.ts +67 -0
  67. package/src/ai/turn-abort.ts +33 -0
  68. package/src/ai/types.ts +40 -4
  69. package/src/api/accounts-entities.ts +2 -1
  70. package/src/api/admin-ai-projects.ts +2 -0
  71. package/src/api/admin-ai-skills.ts +2 -0
  72. package/src/api/admin-ai-usage.ts +2 -0
  73. package/src/api/admin-core-settings.ts +190 -139
  74. package/src/api/admin-geoip.ts +80 -0
  75. package/src/api/admin-identity.ts +3 -0
  76. package/src/api/admin-lifecycle.ts +3 -0
  77. package/src/api/admin-outgoing-mail.ts +6 -2
  78. package/src/api/app-approval.ts +54 -6
  79. package/src/api/approvals-loader.ts +23 -1
  80. package/src/api/approvals.ts +3 -1
  81. package/src/api/auth.ts +25 -6
  82. package/src/api/capabilities.ts +44 -11
  83. package/src/api/capability-streams.ts +12 -0
  84. package/src/api/help.ts +4 -28
  85. package/src/api/index.ts +7 -1
  86. package/src/api/lasting-access-messages.ts +17 -0
  87. package/src/api/mcp.ts +3 -0
  88. package/src/api/me-panel-consent.ts +71 -0
  89. package/src/api/me.ts +80 -33
  90. package/src/api/search.ts +3 -1
  91. package/src/api/sync-ops.ts +2 -0
  92. package/src/api/widgets.ts +3 -0
  93. package/src/approvals/Approvals.tsx +180 -238
  94. package/src/approvals/messages.ts +12 -24
  95. package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
  96. package/src/browser/CloudResourceSearch.tsx +10 -6
  97. package/src/browser/app-approval.ts +60 -6
  98. package/src/browser/commands.ts +22 -6
  99. package/src/browser/composer-catalog.ts +145 -0
  100. package/src/browser/composer-insert-messages.ts +31 -0
  101. package/src/browser/composer-insert.ts +160 -0
  102. package/src/browser/composer-open-messages.ts +17 -0
  103. package/src/browser/composer-open.ts +110 -0
  104. package/src/browser/discoverable-command.ts +6 -0
  105. package/src/browser/hints.ts +6 -0
  106. package/src/browser/live-connection.ts +26 -2
  107. package/src/browser/navigation-apps.ts +7 -0
  108. package/src/browser/panel-context-bridge.ts +30 -0
  109. package/src/browser/panel-context-receiver.ts +53 -0
  110. package/src/browser/panel-menu.tsx +56 -0
  111. package/src/browser/panel-messages.ts +35 -0
  112. package/src/browser/panel-shortcuts.ts +31 -0
  113. package/src/browser/panel-slot.ts +756 -0
  114. package/src/browser/panel.ts +65 -0
  115. package/src/browser/resource-picker.tsx +12 -1
  116. package/src/browser/search-commands.ts +6 -3
  117. package/src/capabilities/client.ts +8 -2
  118. package/src/capabilities/reviews.ts +205 -0
  119. package/src/capabilities/server.ts +10 -3
  120. package/src/capabilities/testing.ts +47 -0
  121. package/src/capabilities/types.ts +2 -2
  122. package/src/cli/account.ts +13 -3
  123. package/src/cli/admin/geoip.ts +115 -0
  124. package/src/cli/admin/index.ts +3 -0
  125. package/src/contracts/app-approval.ts +159 -12
  126. package/src/contracts/app.ts +14 -0
  127. package/src/contracts/approvals.ts +2 -2
  128. package/src/contracts/capabilities.ts +410 -18
  129. package/src/contracts/geoip.ts +42 -0
  130. package/src/contracts/index.ts +3 -0
  131. package/src/contracts/panel-context-plain.ts +99 -0
  132. package/src/contracts/panel-context.ts +93 -0
  133. package/src/contracts/registry.ts +3 -1
  134. package/src/contracts/seen-hints.ts +35 -0
  135. package/src/contracts/shared.ts +2 -0
  136. package/src/server/client-ip.ts +5 -0
  137. package/src/server/help-payload.ts +38 -0
  138. package/src/server/middleware/auth.ts +11 -1
  139. package/src/server/middleware/middleware.ts +9 -0
  140. package/src/server/middleware/no-transform.ts +19 -0
  141. package/src/server/middleware/rate-limit.ts +4 -6
  142. package/src/server/request-context/address.ts +45 -0
  143. package/src/server/request-context/geoip.ts +380 -0
  144. package/src/server/request-context/index.ts +33 -0
  145. package/src/server/request-context/user-agent.ts +95 -0
  146. package/src/server/services/access.ts +5 -1
  147. package/src/services/accounts/posix.ts +2 -1
  148. package/src/services/app-approval.ts +533 -90
  149. package/src/services/approvals/device.ts +9 -5
  150. package/src/services/approvals/index.ts +6 -5
  151. package/src/services/approvals/list.ts +26 -5
  152. package/src/services/auth-flows/notification-sender.ts +3 -0
  153. package/src/services/geoip/index.ts +164 -0
  154. package/src/services/geoip/store.ts +30 -0
  155. package/src/services/geoip/upload.ts +150 -0
  156. package/src/services/identity/routes.ts +5 -1
  157. package/src/services/index.ts +32 -3
  158. package/src/services/known-browsers.ts +312 -0
  159. package/src/services/login-address-store.ts +51 -0
  160. package/src/services/login-context.ts +118 -0
  161. package/src/services/mandates/index.ts +3 -0
  162. package/src/services/mandates/policy.ts +15 -3
  163. package/src/services/notifications/browser.ts +16 -5
  164. package/src/services/notifications/email-frame.ts +1 -1
  165. package/src/services/oauth-tokens.ts +16 -2
  166. package/src/services/panel-context-consent.ts +58 -0
  167. package/src/services/pdf/image-protocol.ts +135 -0
  168. package/src/services/pdf/image-variants.ts +124 -0
  169. package/src/services/pdf/image-worker.ts +31 -0
  170. package/src/services/pdf/image.ts +109 -0
  171. package/src/services/pdf/index.ts +9 -0
  172. package/src/services/service-account-credentials.ts +21 -0
  173. package/src/services/session/index.ts +97 -16
  174. package/src/services/session/recent.ts +13 -0
  175. package/src/services/settings/api.ts +11 -6
  176. package/src/services/settings/app.ts +5 -4
  177. package/src/services/settings/core-settings.ts +4 -3
  178. package/src/services/settings/index.ts +19 -17
  179. package/src/services/settings/namespace.ts +2 -1
  180. package/src/services/settings/store.ts +83 -11
  181. package/src/services/webauthn.ts +12 -0
  182. package/src/shared/ai-platform-prompt.ts +1 -1
  183. package/src/shared/app-presentation.ts +1 -0
  184. package/src/shared/email-html.ts +2 -0
  185. package/src/shared/help.ts +3 -0
  186. package/src/shared/redirect.ts +12 -0
  187. package/src/shared-text/cursor.ts +22 -0
  188. package/src/shared-text/documents.ts +142 -0
  189. package/src/shared-text/events.ts +43 -0
  190. package/src/shared-text/index.ts +12 -0
  191. package/src/shared-text/log.ts +48 -0
  192. package/src/shared-text/text-diff.ts +23 -0
  193. package/src/shared-text/types.ts +41 -0
  194. package/src/ssr/AdminLayout.tsx +2 -0
  195. package/src/ssr/AppLaunchpadPanel.tsx +2 -2
  196. package/src/ssr/CloudShell.island.tsx +95 -10
  197. package/src/ssr/Layout.tsx +66 -10
  198. package/src/ssr/LayoutHeader.tsx +15 -9
  199. package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
  200. package/src/ssr/LayoutHelp.tsx +435 -274
  201. package/src/ssr/LayoutHelpPanel.browser-harness.tsx +136 -0
  202. package/src/ssr/LayoutHelpTrigger.tsx +27 -2
  203. package/src/ssr/LayoutRail.tsx +2 -2
  204. package/src/ssr/LayoutTabBar.tsx +29 -0
  205. package/src/ssr/MobileNavigation.tsx +2 -2
  206. package/src/ssr/PanelLayout.tsx +35 -0
  207. package/src/ssr/PanelRuntime.island.tsx +94 -0
  208. package/src/ssr/PanelSlot.tsx +158 -0
  209. package/src/ssr/PanelTrigger.tsx +49 -0
  210. package/src/ssr/ShellRuntime.island.tsx +69 -5
  211. package/src/ssr/app-appearance.ts +3 -2
  212. package/src/ssr/app-color.ts +105 -0
  213. package/src/ssr/app-launchpad.tsx +18 -1
  214. package/src/ssr/app-navigation.ts +28 -0
  215. package/src/ssr/compact-shell.baseline.json +5270 -0
  216. package/src/ssr/compact-shell.browser-harness.ts +439 -0
  217. package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
  218. package/src/ssr/help-messages.ts +4 -0
  219. package/src/ssr/help.ts +20 -0
  220. package/src/ssr/hints.ts +25 -0
  221. package/src/ssr/index.ts +5 -0
  222. package/src/ssr/islands/index.ts +4 -3
  223. package/src/ssr/layout-context.ts +2 -0
  224. package/src/ssr/layout-help-article.server.ts +50 -0
  225. package/src/ssr/layout-help-start.ts +33 -7
  226. package/src/ssr/panel-consent.ts +29 -0
  227. package/src/ssr/panel-context.ts +10 -0
  228. package/src/ssr/panel-slot.browser-harness.tsx +159 -0
  229. package/src/ssr/platform-messages.ts +48 -0
  230. package/src/ssr/rail-navigation.ts +44 -0
  231. package/src/ssr/shell-data.tsx +5 -0
  232. package/src/ssr/shell-shortcuts.ts +13 -2
  233. package/src/styles/effects.css +201 -11
  234. package/src/styles/file-chooser.css +2 -1
  235. package/src/styles/global.css +314 -63
  236. package/src/styles/panel.css +416 -0
  237. package/src/styles/resource-search.css +6 -2
  238. package/src/styles/tokens.css +36 -13
  239. package/src/styles/utilities-buttons.css +3 -2
  240. package/src/styles/utilities-data.css +2 -1
  241. package/src/styles/utilities-feedback.css +2 -2
  242. package/src/styles/utilities-layout.css +3 -13
  243. package/src/styles/utilities-markdown-editor.css +6 -6
  244. package/src/styles/utilities-navigation.css +22 -43
@@ -32,6 +32,7 @@
32
32
  @import "./input.css";
33
33
  @import "./resource-search.css";
34
34
  @import "./file-chooser.css";
35
+ @import "./panel.css";
35
36
 
36
37
  @theme {
37
38
  --color-dark: rgb(9 9 11);
@@ -52,17 +53,19 @@
52
53
  }
53
54
 
54
55
  .cloud-app-canvas {
56
+ --cloud-canvas-strength: var(--app-canvas-strength, 0%);
57
+ --cloud-canvas-dark-strength: var(--app-canvas-dark-strength, 0%);
55
58
  padding: var(--ui-space-shell);
56
59
  background: linear-gradient(
57
60
  var(--app-canvas-angle, 135deg),
58
- color-mix(in srgb, var(--app-canvas-from, var(--color-zinc-50)) var(--app-canvas-strength, 0%), var(--color-zinc-50)),
61
+ color-mix(in srgb, var(--app-canvas-from, var(--color-zinc-50)) var(--cloud-canvas-strength), var(--color-zinc-50)),
59
62
  color-mix(
60
63
  in srgb,
61
- var(--app-canvas-via, var(--app-canvas-from, var(--color-zinc-50))) var(--app-canvas-strength, 0%),
64
+ var(--app-canvas-via, var(--app-canvas-from, var(--color-zinc-50))) var(--cloud-canvas-strength),
62
65
  var(--color-zinc-50)
63
66
  )
64
67
  50%,
65
- color-mix(in srgb, var(--app-canvas-to, var(--color-zinc-50)) var(--app-canvas-strength, 0%), var(--color-zinc-50))
68
+ color-mix(in srgb, var(--app-canvas-to, var(--color-zinc-50)) var(--cloud-canvas-strength), var(--color-zinc-50))
66
69
  );
67
70
  }
68
71
 
@@ -88,12 +91,47 @@ html:has(.cloud-app-canvas[data-layout-full-page="true"]) {
88
91
  .dark .cloud-app-canvas {
89
92
  background: linear-gradient(
90
93
  var(--app-canvas-angle, 135deg),
91
- color-mix(in srgb, var(--app-canvas-from, var(--ui-canvas)) var(--app-canvas-dark-strength, 0%), var(--ui-canvas)),
92
- color-mix(in srgb, var(--app-canvas-via-dark, var(--ui-canvas)) var(--app-canvas-dark-strength, 0%), var(--ui-canvas)) 50%,
93
- color-mix(in srgb, var(--app-canvas-to, var(--ui-canvas)) var(--app-canvas-dark-strength, 0%), var(--ui-canvas))
94
+ color-mix(in srgb, var(--app-canvas-from, var(--ui-canvas)) var(--cloud-canvas-dark-strength), var(--ui-canvas)),
95
+ color-mix(in srgb, var(--app-canvas-via-dark, var(--ui-canvas)) var(--cloud-canvas-dark-strength), var(--ui-canvas)) 50%,
96
+ color-mix(in srgb, var(--app-canvas-to, var(--ui-canvas)) var(--cloud-canvas-dark-strength), var(--ui-canvas))
94
97
  );
95
98
  }
96
99
 
100
+ /* The shell's own colored surfaces, the tablet rail's container and the
101
+ phone's bottom bar, carry the app color alone in a narrow shape. A white
102
+ middle, the canvas's default `via`, would make their edge vanish halfway,
103
+ so they mix the middle from the app's two colors and take a stronger tint:
104
+ 34 % light and 22 % dark, with a 16 % and 12 % middle. A page without an app
105
+ color gets a neutral tint. The app declaration stays the same. */
106
+ .cloud-app-canvas {
107
+ --cloud-shell-strength: 34%;
108
+ --cloud-shell-via-strength: 16%;
109
+ --cloud-shell-gradient: linear-gradient(
110
+ 160deg,
111
+ color-mix(in srgb, var(--app-canvas-from, var(--color-zinc-400)) var(--cloud-shell-strength), var(--ui-canvas)),
112
+ color-mix(
113
+ in srgb,
114
+ color-mix(in srgb, var(--app-canvas-from, var(--color-zinc-400)) 45%, var(--app-canvas-to, var(--color-zinc-400)))
115
+ var(--cloud-shell-via-strength),
116
+ var(--ui-canvas)
117
+ )
118
+ 55%,
119
+ color-mix(in srgb, var(--app-canvas-to, var(--color-zinc-400)) var(--cloud-shell-strength), var(--ui-canvas))
120
+ );
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. */
123
+ --cloud-shell-selected: var(--k2b-surface);
124
+ --cloud-shell-selected-text: var(--ui-app-accent-text);
125
+ }
126
+
127
+ .dark .cloud-app-canvas {
128
+ --cloud-shell-strength: 22%;
129
+ --cloud-shell-via-strength: 12%;
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));
133
+ }
134
+
97
135
  :root {
98
136
  color-scheme: light;
99
137
  }
@@ -243,6 +281,21 @@ body {
243
281
  color: rgb(244 244 245); /* zinc-100 */
244
282
  }
245
283
 
284
+ /* The root paints the screen's page color as well. @k2b/ui makes it the
285
+ default `k2b-app-area` container, and containment stops a body background
286
+ from reaching the canvas that overscroll and short pages show. Zero
287
+ specificity, so the PWA canvas style and syncThemeColor still win; screen
288
+ only, so paper stays white. */
289
+ @media screen {
290
+ :where(html) {
291
+ background-color: rgb(250 250 250); /* zinc-50 */
292
+ }
293
+
294
+ :where(html.dark) {
295
+ background-color: rgb(9 9 11); /* zinc-950 */
296
+ }
297
+ }
298
+
246
299
  /* Pages of the mobile app paint the shell canvas, which their theme-color and
247
300
  html background repeat for the status bar. */
248
301
  html:has(.k2b-mobile-shell) body {
@@ -295,31 +348,37 @@ solid-island {
295
348
 
296
349
  /* Cloud has three shell modes. Phones, below md, get the header and no rail.
297
350
  From md, signed-in people get the 56 px rail instead of the header: up to
298
- 1023 px the app area meets the window's top, right and bottom edges, and
299
- from 1024 px it is a card beside the rail. Anonymous pages keep the header
300
- and the card from md. Each query is the exact opposite of the next one, so
301
- every width, also a fractional one under browser zoom, falls into exactly
302
- one mode. */
351
+ 1023 px the rail sits in a rounded container beside the app as a rounded
352
+ card, and from 1024 px the app is a card beside the rail. Anonymous pages
353
+ keep the header and the card from md. Each query is the exact opposite of
354
+ the next one, so every width, also a fractional one under browser zoom,
355
+ falls into exactly one mode. */
303
356
  @media not all and (min-width: 768px) {
304
357
  .cloud-app-canvas {
305
358
  padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
306
359
  }
307
- /* The flat surface replaces the app gradient in both themes. The dark
308
- selector matches the specificity of the dark gradient above. */
309
- .cloud-app-canvas,
310
- .dark .cloud-app-canvas {
360
+ /* Anonymous pages: the flat surface replaces the app gradient in both
361
+ themes, and the document is the page scroller. The dark selector matches
362
+ the specificity of the dark gradient above. */
363
+ .cloud-app-canvas:not([data-layout-authenticated]),
364
+ .dark .cloud-app-canvas:not([data-layout-authenticated]) {
311
365
  background: var(--ui-surface);
312
366
  }
313
- /* Below lg the document is the page scroller; reserve its scrollbar gutter
314
- so short and long pages keep the same width. Full-page surfaces own their
315
- inner scrolling. */
316
- html:has(.cloud-app-canvas:not([data-layout-full-page])) { scrollbar-gutter: stable; }
367
+ /* Reserve the page scroller's gutter so short and long pages keep the same
368
+ width: the document's, or the card's for signed-in people. Full-page
369
+ surfaces, workspaces, and scroll areas own their inner scrolling, so the
370
+ card reserves no second gutter beside theirs. */
371
+ html:has(.cloud-app-canvas:not([data-layout-full-page], [data-layout-authenticated])) { scrollbar-gutter: stable; }
372
+ .cloud-app-canvas[data-layout-authenticated]:not([data-layout-full-page])
373
+ .layout-content-main:not(:has(> .k2b-scroll-area, > .k2b-app-workspace, > :is(solid-island, solid-client) > .k2b-app-workspace)) {
374
+ scrollbar-gutter: stable;
375
+ }
317
376
  /* A page that scrolls its content in a ScrollArea grows with it here, so
318
377
  the area does not scroll and reserves no second gutter beside the
319
378
  document's. From md, `main` bounds its height and it scrolls. */
320
- .cloud-app-canvas:not([data-layout-full-page]) .layout-content-main > .k2b-scroll-area { scrollbar-gutter: auto; }
379
+ .cloud-app-canvas:not([data-layout-full-page], [data-layout-authenticated]) .layout-content-main > .k2b-scroll-area { scrollbar-gutter: auto; }
321
380
  .cloud-app-canvas > .layout-shell-content { gap: 0; }
322
- .cloud-app-canvas .layout-header {
381
+ .cloud-app-canvas:not([data-layout-authenticated]) .layout-header {
323
382
  border-radius: 0; border: 0; border-bottom: 1px solid var(--ui-border);
324
383
  box-shadow: none !important;
325
384
  }
@@ -329,6 +388,174 @@ solid-island {
329
388
  --k2b-toast-offset-top: 2.875rem;
330
389
  }
331
390
 
391
+ /* Signed-in phones: the app gradient is the page. The title row sits on it
392
+ without a line, and the content is one soft card with 28 px top corners
393
+ that scrolls itself, so its rounded edge stays while it scrolls. The bar
394
+ with the person's apps floats over the card's end; the card keeps that
395
+ space free at its end and in its scroll padding, so the bar never covers
396
+ the last row or a focused control. The window never scrolls. */
397
+ .cloud-app-canvas[data-layout-authenticated] {
398
+ /* Text sits on the gradient here, so its tint stays at most 30 % light
399
+ and 15 % dark: then the title and controls read at 4.5:1 on any app
400
+ color. Built-in apps declare less. */
401
+ --cloud-canvas-strength: min(var(--app-canvas-strength, 0%), 30%);
402
+ --cloud-canvas-dark-strength: min(var(--app-canvas-dark-strength, 0%), 15%);
403
+ height: 100dvh;
404
+ overflow: hidden;
405
+ padding-bottom: 0;
406
+ }
407
+ .cloud-app-canvas[data-layout-authenticated] .layout-header {
408
+ border: 0;
409
+ border-radius: 0;
410
+ background: transparent;
411
+ box-shadow: none !important;
412
+ }
413
+ .cloud-app-canvas[data-layout-authenticated] .layout-header .font-semibold {
414
+ font-size: 1.125rem;
415
+ }
416
+ /* Content wider than the card scrolls sideways in it, as from md; the
417
+ rounded corners still clip. */
418
+ .cloud-app-canvas[data-layout-authenticated] .layout-content-main {
419
+ overflow: auto;
420
+ border-top: 1px solid transparent;
421
+ border-radius: 1.75rem 1.75rem 0 0;
422
+ background: var(--k2b-surface);
423
+ box-shadow: 0 -10px 30px -24px rgb(15 23 19 / 0.3);
424
+ }
425
+ /* Dark: a faint top edge keeps the card apart from the dark gradient. */
426
+ .dark .cloud-app-canvas[data-layout-authenticated] .layout-content-main {
427
+ border-top-color: var(--k2b-border);
428
+ }
429
+ }
430
+
431
+ /* The bar and the room it takes switch with `md`, as the header and the rail
432
+ do, so Home stays reachable also where a larger default font size moves
433
+ that switch above 768 px. */
434
+ .cloud-app-canvas[data-layout-authenticated] {
435
+ --cloud-tab-bar-height: 3.875rem;
436
+ --cloud-tab-bar-end: calc(env(safe-area-inset-bottom, 0px) + 0.5rem);
437
+ --cloud-tab-bar-space: calc(var(--cloud-tab-bar-end) + var(--cloud-tab-bar-height) + 0.75rem);
438
+ }
439
+
440
+ /* A page whose app has its own navigation (a mailbox, a Space, a book) makes
441
+ its title the menu "Name ⌄" on phones. It opens the same app menu as the
442
+ menu button beside it. The navigation's server-rendered snapshot decides,
443
+ so the first frame already shows the right title. */
444
+ .layout-header-title-menu {
445
+ display: none;
446
+ min-width: 0;
447
+ min-height: 2rem;
448
+ align-items: center;
449
+ gap: 0.25rem;
450
+ margin-inline-start: -0.5rem;
451
+ padding: 0 0.5rem;
452
+ border-radius: var(--k2b-radius-control);
453
+ color: inherit;
454
+ background: transparent;
455
+ cursor: pointer;
456
+ }
457
+ .layout-header-title-menu:hover {
458
+ background: color-mix(in srgb, currentColor 8%, transparent);
459
+ }
460
+ .layout-header-title-menu:focus-visible {
461
+ outline: var(--k2b-focus-width) solid var(--k2b-focus-ring);
462
+ outline-offset: 1px;
463
+ }
464
+ .layout-header-title-menu > i {
465
+ flex: none;
466
+ font-size: 1rem;
467
+ opacity: 0.7;
468
+ }
469
+
470
+ @media not all and (min-width: 48rem) {
471
+ :root:has(script[data-cloud-workspace-navigation]) .layout-header-title {
472
+ display: none;
473
+ }
474
+ :root:has(script[data-cloud-workspace-navigation]) .layout-header-title-menu {
475
+ display: inline-flex;
476
+ }
477
+ .cloud-app-canvas[data-layout-authenticated] .layout-content-main {
478
+ padding-bottom: var(--cloud-tab-bar-space);
479
+ scroll-padding-bottom: var(--cloud-tab-bar-space);
480
+ }
481
+ .cloud-app-canvas[data-layout-authenticated] > .cloud-tab-bar {
482
+ position: fixed;
483
+ right: 0;
484
+ bottom: 0;
485
+ left: 0;
486
+ z-index: 10;
487
+ display: flex;
488
+ justify-content: center;
489
+ align-items: flex-end;
490
+ height: var(--cloud-tab-bar-space);
491
+ padding: 0 calc(env(safe-area-inset-right, 0px) + 0.75rem) var(--cloud-tab-bar-end) calc(env(safe-area-inset-left, 0px) + 0.75rem);
492
+ /* Content that scrolls past fades flat into the card above the bar and is
493
+ gone where the bar begins: no glass, and no text beside or under it. */
494
+ background: linear-gradient(to bottom, color-mix(in srgb, var(--k2b-surface) 0%, transparent), var(--k2b-surface) 0.75rem);
495
+ pointer-events: none;
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
+ }
526
+ }
527
+
528
+ /* The bar exists only on signed-in phones. Its row keeps the place of the
529
+ app's primary action free at its end. */
530
+ .cloud-tab-bar {
531
+ display: none;
532
+ }
533
+
534
+ .cloud-tab-bar__row {
535
+ display: flex;
536
+ width: 100%;
537
+ max-width: 33.75rem;
538
+ padding-inline-end: calc(var(--cloud-tab-bar-height) + 0.625rem);
539
+ }
540
+
541
+ /* The shell's live bar replaces the server's once the bar's content changes
542
+ in the browser. */
543
+ .cloud-tab-bar__row:has(.cloud-tab-bar__live) > .k2b-tab-bar {
544
+ display: none;
545
+ }
546
+
547
+ .cloud-tab-bar__row > div:has(> .cloud-tab-bar__live) {
548
+ display: contents;
549
+ }
550
+
551
+ .cloud-tab-bar .k2b-tab-bar {
552
+ flex: 1;
553
+ min-width: 0;
554
+ pointer-events: auto;
555
+ --k2b-tab-bar-background: var(--cloud-shell-gradient);
556
+ --k2b-tab-bar-color: var(--k2b-text);
557
+ --k2b-tab-bar-selected-background: var(--cloud-shell-selected);
558
+ --k2b-tab-bar-selected-color: var(--cloud-shell-selected-text);
332
559
  }
333
560
 
334
561
  /* Card: the outermost workspace is the app card, so it gets the card's radius
@@ -337,9 +564,7 @@ solid-island {
337
564
  child fills `main` and scrolls inside itself, so `main` neither scrolls nor
338
565
  clips: it keeps no scrollbar gutter beside the card, the card keeps the
339
566
  shell spacing on every side, and its frame shadow shows in that spacing. A
340
- workspace nested in other page content leaves `main` the page scroller. For
341
- signed-in people, the flush range below removes the frame again; it comes
342
- later at equal specificity. */
567
+ workspace nested in other page content leaves `main` the page scroller. */
343
568
  @media (min-width: 768px) {
344
569
  .cloud-app-canvas .layout-content-main:has(> .k2b-app-workspace, > :is(solid-island, solid-client) > .k2b-app-workspace) {
345
570
  overflow: visible;
@@ -354,42 +579,60 @@ solid-island {
354
579
  }
355
580
  }
356
581
 
357
- /* Flush: the app area is one opaque surface from the rail to the window's
358
- edges, without padding, radius, shadow, or border. The rail keeps the app
359
- canvas, so the change of surface alone separates the two; no line does. The
360
- outermost workspace and the notices above it lose their frame, so nothing
361
- draws a box inside the app area.
362
- The rail and the app area reach the window's edges also where the installed
363
- app reports safe areas, such as the status bar and the home indicator. Their
364
- own surfaces fill the insets, and their content stays inside them. */
582
+ /* Tablet (I5): the rail sits in a rounded container filled with the app
583
+ color, and the app is a rounded card beside it, both 20 px round with 8 px
584
+ of neutral canvas on every side. The installed app's safe areas, such as
585
+ the status bar and the home indicator, add to that spacing, so nothing sits
586
+ under them. The outermost workspace is the card, as from 1024 px; a page
587
+ without a workspace shows its content on the canvas. On color the open app
588
+ is the surface color, and no separator divides the rail. */
365
589
  @media (min-width: 768px) {
366
590
  @media not all and (min-width: 1024px) {
367
591
  .cloud-app-canvas[data-layout-authenticated] {
368
- padding: 0;
592
+ padding: calc(env(safe-area-inset-top, 0px) + 0.5rem) calc(env(safe-area-inset-right, 0px) + 0.5rem)
593
+ calc(env(safe-area-inset-bottom, 0px) + 0.5rem) calc(env(safe-area-inset-left, 0px) + 0.5rem);
594
+ background: var(--ui-canvas);
369
595
  }
370
596
  .cloud-app-canvas[data-layout-authenticated] > .layout-rail {
371
- padding-top: env(safe-area-inset-top, 0px);
372
- padding-bottom: calc(var(--ui-space-shell) + env(safe-area-inset-bottom, 0px));
597
+ width: 3.75rem;
598
+ margin-inline-end: 0.5rem;
599
+ padding: 0 0 0.5rem;
600
+ border-radius: 1.25rem;
601
+ background-image: var(--cloud-shell-gradient) !important;
373
602
  }
374
- .cloud-app-canvas[data-layout-authenticated] > .layout-shell-content {
375
- gap: 0;
376
- padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) 0 0;
377
- background: var(--k2b-surface);
603
+ .dark .cloud-app-canvas[data-layout-authenticated] > .layout-rail {
604
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--app-accent, var(--k2b-border)) 22%, var(--k2b-border));
378
605
  }
379
- /* A regular page scrolls `main`, so its end clears the home indicator. A
380
- workspace reaches the bottom edge and clears it inside its own surfaces. */
381
- .cloud-app-canvas[data-layout-authenticated] .layout-content-main:not(:has(.k2b-app-workspace)) {
382
- padding-bottom: env(safe-area-inset-bottom, 0px);
606
+ .cloud-app-canvas[data-layout-authenticated] > .layout-rail .rail-item {
607
+ color: var(--k2b-text-secondary);
383
608
  }
384
- .cloud-app-canvas[data-layout-authenticated] .layout-content-main .k2b-app-workspace:not(.k2b-app-workspace *) {
385
- --k2b-workspace-radius: 0px;
386
- --k2b-workspace-inset-bottom: env(safe-area-inset-bottom, 0px);
387
- border: 0;
388
- box-shadow: none;
609
+ .cloud-app-canvas[data-layout-authenticated] > .layout-rail .rail-item:hover {
610
+ background: color-mix(in srgb, var(--k2b-surface) 45%, transparent);
389
611
  }
390
- .cloud-app-canvas[data-layout-authenticated] > .layout-shell-content > :not(.layout-content-main) .k2b-notice-card {
391
- border: 0;
392
- border-radius: 0;
612
+ .cloud-app-canvas[data-layout-authenticated] > .layout-rail .rail-item-active {
613
+ box-shadow: 0 1px 2px rgb(9 9 11 / 0.06);
614
+ }
615
+ .cloud-app-canvas[data-layout-authenticated] > .layout-rail .rail-item-active,
616
+ .cloud-app-canvas[data-layout-authenticated] > .layout-rail .rail-item-active:hover {
617
+ border-color: transparent;
618
+ background: var(--cloud-shell-selected);
619
+ color: var(--cloud-shell-selected-text);
620
+ }
621
+ .cloud-app-canvas[data-layout-authenticated] > .layout-rail [role="separator"] {
622
+ display: none;
623
+ }
624
+ /* The apps scroll in their own area; room above and below the first and
625
+ last app keeps a focus ring whole, also WebKit's wider one, without
626
+ moving an app. */
627
+ .cloud-app-canvas[data-layout-authenticated] > .layout-rail [data-cloud-rail-apps] {
628
+ margin-block: -0.375rem;
629
+ padding-block: 0.375rem;
630
+ }
631
+ .cloud-app-canvas[data-layout-authenticated] > .layout-shell-content {
632
+ gap: 0.5rem;
633
+ }
634
+ .cloud-app-canvas[data-layout-authenticated] .layout-content-main .k2b-app-workspace:not(.k2b-app-workspace *) {
635
+ --k2b-workspace-radius: 1.25rem;
393
636
  }
394
637
  }
395
638
  }
@@ -401,7 +644,7 @@ solid-island {
401
644
  rail starts at the window's left edge and moves down with the card. In the
402
645
  installed app the card and the rail stay below the status bar; the card keeps
403
646
  its spacing at the bottom, and its surfaces keep their content above the
404
- home indicator, as in the flush range. */
647
+ home indicator. */
405
648
  @media (min-width: 1024px) {
406
649
  .cloud-app-canvas[data-layout-authenticated] {
407
650
  --cloud-card-inset: round(clamp(0.25rem, calc(0.25rem + (100vw - 64rem) / 104), 0.5rem), 1px);
@@ -429,11 +672,12 @@ solid-island {
429
672
  (Cmd/Ctrl+\ or the sidebar buttons). The canvas carries the state from the
430
673
  `cloud_shell_focus` cookie, so the server renders the same first frame.
431
674
  Phones ignore it. The app area takes the app bar's place and starts at the
432
- card spacing, or at the window's edge where it is flush. The button that
675
+ card spacing. The button that
433
676
  shows them again floats at the app area's bottom start corner; on touch
434
- screens a drag from the 18 px strip at the left edge does the same. From
435
- 768 to 1023 px the navigation is already an overlay behind the
436
- 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. */
437
681
  .cloud-shell-show,
438
682
  .cloud-shell-reveal {
439
683
  display: none;
@@ -470,8 +714,9 @@ solid-island {
470
714
 
471
715
  @media (min-width: 768px) {
472
716
  @media not all and (min-width: 1024px) {
473
- .cloud-app-canvas[data-layout-authenticated][data-shell-focus] > .layout-shell-content {
474
- padding-left: env(safe-area-inset-left, 0px);
717
+ .cloud-app-canvas[data-layout-authenticated][data-shell-focus] .cloud-shell-show {
718
+ left: calc(env(safe-area-inset-left, 0px) + 1.25rem);
719
+ bottom: calc(env(safe-area-inset-bottom, 0px) + 1.25rem);
475
720
  }
476
721
  }
477
722
  }
@@ -484,12 +729,18 @@ solid-island {
484
729
  left: calc(max(var(--cloud-card-inset), env(safe-area-inset-left, 0px)) + 0.75rem);
485
730
  bottom: calc(var(--cloud-card-inset) + var(--cloud-card-inset-bottom) + 0.75rem);
486
731
  }
487
- /* The navigation leaves like a hidden sidebar: its track, its resize
488
- handle, and navigation panes go, and the work area rounds its start
489
- corners with the card. An island may wrap the sidebar or Main, as in
490
- 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) {
491
741
  .cloud-app-canvas[data-shell-focus] .k2b-app-workspace:not(.k2b-app-workspace *) {
492
742
  --k2b-workspace-start-radius: var(--k2b-workspace-inner-radius);
743
+ --k2b-workspace-sidebar-space: 0px;
493
744
  }
494
745
  .cloud-app-canvas[data-shell-focus]
495
746
  .k2b-app-workspace:not(.k2b-app-workspace *)
@@ -535,7 +786,7 @@ solid-island {
535
786
  .cloud-mobile-menu .k2b-panel-dialog__actions { flex: 1; min-width: 0; }
536
787
  .cloud-mobile-menu__switch { width: 100%; flex-wrap: nowrap; }
537
788
  .cloud-mobile-menu__switch button { flex: 1; min-width: 0; min-height: 3rem; white-space: nowrap; }
538
- .cloud-mobile-menu__switch button > span { overflow: hidden; text-overflow: ellipsis; }
789
+ .cloud-mobile-menu__switch button > span { min-width: 0; overflow: hidden; }
539
790
  .cloud-mobile-menu .k2b-dialog__close { width: 2.75rem; height: 2.75rem; flex-shrink: 0; }
540
791
 
541
792
  .cloud-mobile-menu .k2b-panel-dialog__body { padding: .75rem 1rem 1rem; }