@k2b/cloud 0.39.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 (194) hide show
  1. package/README.md +8 -0
  2. package/package.json +8 -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 +216 -1
  8. package/src/_internal/capability-streams.ts +7 -0
  9. package/src/_internal/define-app.ts +36 -15
  10. package/src/_internal/font-preloads.ts +2 -1
  11. package/src/_internal/runtime-watcher.ts +6 -0
  12. package/src/_internal/static-assets.ts +52 -3
  13. package/src/_internal/status-preserving-ssr.ts +2 -0
  14. package/src/access/PermissionEditor.tsx +2 -2
  15. package/src/account/EntitySearch.tsx +1 -1
  16. package/src/ai/action-review.ts +101 -0
  17. package/src/ai/background-review.ts +72 -0
  18. package/src/ai/capabilities.ts +29 -11
  19. package/src/ai/capability-execution.ts +76 -43
  20. package/src/ai/capability-result.ts +95 -0
  21. package/src/ai/chat/action-card.ts +398 -0
  22. package/src/ai/chat/action-messages.ts +129 -0
  23. package/src/ai/chat/action-question.browser-harness.tsx +245 -0
  24. package/src/ai/chat/action-question.fixtures.ts +733 -0
  25. package/src/ai/chat/action-question.tsx +669 -0
  26. package/src/ai/chat/blocks.tsx +19 -25
  27. package/src/ai/chat/message-actions.tsx +6 -0
  28. package/src/ai/chat/tool-action.tsx +481 -0
  29. package/src/ai/chat/turn-view.tsx +10 -160
  30. package/src/ai/chat/view-block.tsx +15 -5
  31. package/src/ai/chat-task-contracts.ts +4 -1
  32. package/src/ai/chat-task-reasons-migrate.ts +7 -0
  33. package/src/ai/chat-task-reasons.ts +1 -1
  34. package/src/ai/chat-tasks.ts +55 -25
  35. package/src/ai/client/controller.ts +59 -0
  36. package/src/ai/default-tools.ts +9 -1
  37. package/src/ai/executor.ts +190 -39
  38. package/src/ai/index.ts +2 -0
  39. package/src/ai/migrate.ts +11 -2
  40. package/src/ai/projects-routes.ts +3 -0
  41. package/src/ai/protocol.ts +14 -2
  42. package/src/ai/recent-resources.ts +240 -0
  43. package/src/ai/recent-sent-refs.ts +38 -0
  44. package/src/ai/routes.ts +145 -1
  45. package/src/ai/runtime.ts +1 -0
  46. package/src/ai/skills-routes.ts +3 -0
  47. package/src/ai/skills.ts +10 -1
  48. package/src/ai/store.ts +227 -24
  49. package/src/ai/tool-audit.ts +8 -0
  50. package/src/ai/tool-wording.ts +24 -2
  51. package/src/ai/types.ts +28 -3
  52. package/src/api/accounts-entities.ts +2 -1
  53. package/src/api/admin-ai-projects.ts +2 -0
  54. package/src/api/admin-ai-skills.ts +2 -0
  55. package/src/api/admin-ai-usage.ts +2 -0
  56. package/src/api/admin-geoip.ts +80 -0
  57. package/src/api/admin-identity.ts +3 -0
  58. package/src/api/admin-lifecycle.ts +3 -0
  59. package/src/api/admin-outgoing-mail.ts +2 -0
  60. package/src/api/app-approval.ts +46 -5
  61. package/src/api/approvals-loader.ts +23 -1
  62. package/src/api/approvals.ts +2 -0
  63. package/src/api/auth.ts +25 -6
  64. package/src/api/capabilities.ts +44 -11
  65. package/src/api/capability-streams.ts +12 -0
  66. package/src/api/help.ts +4 -28
  67. package/src/api/index.ts +6 -1
  68. package/src/api/lasting-access-messages.ts +17 -0
  69. package/src/api/mcp.ts +3 -0
  70. package/src/api/me-panel-consent.ts +71 -0
  71. package/src/api/me.ts +80 -33
  72. package/src/api/search.ts +3 -1
  73. package/src/api/sync-ops.ts +2 -0
  74. package/src/api/widgets.ts +3 -0
  75. package/src/approvals/Approvals.tsx +180 -238
  76. package/src/approvals/messages.ts +12 -24
  77. package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
  78. package/src/browser/app-approval.ts +54 -5
  79. package/src/browser/commands.ts +22 -6
  80. package/src/browser/composer-catalog.ts +45 -3
  81. package/src/browser/composer-insert.ts +10 -0
  82. package/src/browser/composer-open-messages.ts +17 -0
  83. package/src/browser/composer-open.ts +110 -0
  84. package/src/browser/discoverable-command.ts +6 -0
  85. package/src/browser/hints.ts +6 -0
  86. package/src/browser/live-connection.ts +26 -2
  87. package/src/browser/navigation-apps.ts +7 -0
  88. package/src/browser/panel-context-bridge.ts +30 -0
  89. package/src/browser/panel-context-receiver.ts +53 -0
  90. package/src/browser/panel-menu.tsx +56 -0
  91. package/src/browser/panel-messages.ts +11 -0
  92. package/src/browser/panel-shortcuts.ts +31 -0
  93. package/src/browser/panel-slot.ts +412 -10
  94. package/src/browser/panel.ts +37 -0
  95. package/src/browser/search-commands.ts +6 -3
  96. package/src/capabilities/client.ts +8 -2
  97. package/src/capabilities/reviews.ts +205 -0
  98. package/src/capabilities/server.ts +10 -3
  99. package/src/capabilities/testing.ts +47 -0
  100. package/src/capabilities/types.ts +2 -2
  101. package/src/cli/account.ts +13 -3
  102. package/src/cli/admin/geoip.ts +115 -0
  103. package/src/cli/admin/index.ts +3 -0
  104. package/src/contracts/app-approval.ts +120 -13
  105. package/src/contracts/approvals.ts +2 -2
  106. package/src/contracts/capabilities.ts +410 -18
  107. package/src/contracts/geoip.ts +42 -0
  108. package/src/contracts/index.ts +3 -0
  109. package/src/contracts/panel-context-plain.ts +99 -0
  110. package/src/contracts/panel-context.ts +93 -0
  111. package/src/contracts/seen-hints.ts +35 -0
  112. package/src/server/client-ip.ts +5 -0
  113. package/src/server/help-payload.ts +38 -0
  114. package/src/server/middleware/auth.ts +11 -1
  115. package/src/server/middleware/middleware.ts +9 -0
  116. package/src/server/middleware/no-transform.ts +19 -0
  117. package/src/server/middleware/rate-limit.ts +4 -6
  118. package/src/server/request-context/address.ts +45 -0
  119. package/src/server/request-context/geoip.ts +380 -0
  120. package/src/server/request-context/index.ts +33 -0
  121. package/src/server/request-context/user-agent.ts +95 -0
  122. package/src/services/app-approval.ts +382 -59
  123. package/src/services/approvals/device.ts +7 -5
  124. package/src/services/approvals/index.ts +6 -5
  125. package/src/services/approvals/list.ts +26 -5
  126. package/src/services/auth-flows/notification-sender.ts +3 -0
  127. package/src/services/geoip/index.ts +164 -0
  128. package/src/services/geoip/store.ts +30 -0
  129. package/src/services/geoip/upload.ts +150 -0
  130. package/src/services/identity/routes.ts +5 -1
  131. package/src/services/index.ts +11 -0
  132. package/src/services/known-browsers.ts +312 -0
  133. package/src/services/login-address-store.ts +51 -0
  134. package/src/services/login-context.ts +118 -0
  135. package/src/services/mandates/index.ts +3 -0
  136. package/src/services/mandates/policy.ts +15 -3
  137. package/src/services/notifications/email-frame.ts +1 -1
  138. package/src/services/oauth-tokens.ts +16 -2
  139. package/src/services/panel-context-consent.ts +58 -0
  140. package/src/services/service-account-credentials.ts +21 -0
  141. package/src/services/session/index.ts +97 -16
  142. package/src/services/session/recent.ts +13 -0
  143. package/src/services/webauthn.ts +12 -0
  144. package/src/shared/email-html.ts +2 -0
  145. package/src/shared/help.ts +3 -0
  146. package/src/shared-text/cursor.ts +22 -0
  147. package/src/shared-text/documents.ts +142 -0
  148. package/src/shared-text/events.ts +43 -0
  149. package/src/shared-text/index.ts +12 -0
  150. package/src/shared-text/log.ts +48 -0
  151. package/src/shared-text/text-diff.ts +23 -0
  152. package/src/shared-text/types.ts +41 -0
  153. package/src/ssr/AdminLayout.tsx +2 -0
  154. package/src/ssr/AppLaunchpadPanel.tsx +2 -2
  155. package/src/ssr/CloudShell.island.tsx +10 -2
  156. package/src/ssr/Layout.tsx +25 -2
  157. package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
  158. package/src/ssr/LayoutHelp.tsx +435 -274
  159. package/src/ssr/LayoutHelpPanel.browser-harness.tsx +136 -0
  160. package/src/ssr/LayoutHelpTrigger.tsx +27 -2
  161. package/src/ssr/MobileNavigation.tsx +2 -2
  162. package/src/ssr/PanelLayout.tsx +9 -1
  163. package/src/ssr/PanelRuntime.island.tsx +7 -0
  164. package/src/ssr/PanelSlot.tsx +53 -3
  165. package/src/ssr/ShellRuntime.island.tsx +11 -2
  166. package/src/ssr/app-color.ts +14 -9
  167. package/src/ssr/app-navigation.ts +16 -3
  168. package/src/ssr/compact-shell.browser-harness.ts +30 -22
  169. package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
  170. package/src/ssr/help-messages.ts +4 -0
  171. package/src/ssr/help.ts +20 -0
  172. package/src/ssr/hints.ts +25 -0
  173. package/src/ssr/index.ts +2 -0
  174. package/src/ssr/islands/index.ts +4 -3
  175. package/src/ssr/layout-context.ts +2 -0
  176. package/src/ssr/layout-help-article.server.ts +50 -0
  177. package/src/ssr/layout-help-start.ts +33 -7
  178. package/src/ssr/panel-consent.ts +29 -0
  179. package/src/ssr/panel-context.ts +10 -0
  180. package/src/ssr/panel-slot.browser-harness.tsx +53 -4
  181. package/src/ssr/platform-messages.ts +18 -0
  182. package/src/ssr/shell-data.tsx +2 -1
  183. package/src/styles/effects.css +198 -9
  184. package/src/styles/file-chooser.css +2 -1
  185. package/src/styles/global.css +49 -12
  186. package/src/styles/panel.css +47 -4
  187. package/src/styles/resource-search.css +6 -2
  188. package/src/styles/tokens.css +17 -11
  189. package/src/styles/utilities-buttons.css +3 -2
  190. package/src/styles/utilities-data.css +2 -1
  191. package/src/styles/utilities-feedback.css +2 -2
  192. package/src/styles/utilities-layout.css +3 -13
  193. package/src/styles/utilities-markdown-editor.css +6 -6
  194. package/src/styles/utilities-navigation.css +17 -22
@@ -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. */
@@ -371,6 +427,7 @@
371
427
 
372
428
  /* Above a scroll area the head keeps the same scrollbar gutter, so both centre their measure alike. */
373
429
  .help-shell[data-surface="modal"] .help-head,
430
+ .help-shell[data-surface="panel"] .help-head,
374
431
  .help-shell[data-surface="embedded"] .help-head {
375
432
  overflow: hidden;
376
433
  padding-bottom: 0.25rem;
@@ -384,6 +441,29 @@
384
441
  }
385
442
  }
386
443
 
444
+ /* Help without a panel slot on a phone: the shared bottom sheet at its full height, with its grabber (#858). */
445
+ .k2b-ui .k2b-dialog.cloud-help-sheet {
446
+ height: min(92dvh, var(--k2b-dialog-available-height));
447
+ }
448
+
449
+ .cloud-help-sheet .k2b-bottom-sheet-viewport {
450
+ height: 100%;
451
+ }
452
+
453
+ .cloud-help-sheet .help-shell[data-surface="modal"] {
454
+ height: auto;
455
+ flex: 1 1 auto;
456
+ border: 0;
457
+ border-radius: 0;
458
+ background: transparent;
459
+ box-shadow: none;
460
+ }
461
+
462
+ /* In the panel the slot's own title row sits right above: the Help head follows it closely. */
463
+ .help-shell[data-surface="panel"] .help-head {
464
+ padding-top: 0.25rem;
465
+ }
466
+
387
467
  .help-head-inner {
388
468
  display: flex;
389
469
  flex-direction: column;
@@ -549,7 +629,7 @@
549
629
  }
550
630
 
551
631
  .help-start-tile:hover {
552
- background: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 6%, var(--ui-surface-subtle));
632
+ background: var(--ui-surface-muted);
553
633
  }
554
634
 
555
635
  .help-start-tile-icon,
@@ -559,8 +639,6 @@
559
639
  height: 2.25rem;
560
640
  flex: 0 0 auto;
561
641
  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
642
  font-size: 1.125rem;
565
643
  }
566
644
 
@@ -568,6 +646,32 @@
568
646
  margin-bottom: 0.5rem;
569
647
  }
570
648
 
649
+ /* A narrow reader, such as the panel column at 20-23 rem, has no room for two tiles side by side: each task keeps
650
+ its tile, one per row, with the icon beside its title. */
651
+ @container help-shell (max-width: 23rem) {
652
+ .help-start-tiles {
653
+ grid-template-columns: minmax(0, 1fr);
654
+ }
655
+
656
+ .help-start-tile {
657
+ display: grid;
658
+ grid-template-columns: auto minmax(0, 1fr);
659
+ column-gap: 0.75rem;
660
+ row-gap: 0.125rem;
661
+ align-items: start;
662
+ padding: 0.75rem;
663
+ }
664
+
665
+ .help-start-tile-icon {
666
+ grid-row: span 2;
667
+ margin-bottom: 0;
668
+ }
669
+
670
+ .help-start-tile-description {
671
+ -webkit-line-clamp: 2;
672
+ }
673
+ }
674
+
571
675
  .help-start-tile-title {
572
676
  max-width: 100%;
573
677
  color: var(--color-zinc-950);
@@ -750,12 +854,56 @@
750
854
  padding-top: 0.25rem;
751
855
  }
752
856
 
857
+ /* The article head names the app whose Help this is: the app's own canvas gradient (the same tokens and strength as
858
+ the app background, over the reader's surface), without a border or a shadow, and the article's icon in the flat
859
+ shared tile. Without app colours it stays the plain surface. The embedded readers (Grids, Pulse) sit inside the
860
+ app's own chrome, which already names the app: their head stays flat. Text sits on this gradient, so its tint stays at
861
+ 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
862
  .help-article-head {
863
+ margin-bottom: 2rem;
864
+ }
865
+
866
+ .help-article:not([data-surface="embedded"]) .help-article-head {
867
+ display: grid;
868
+ grid-template-columns: minmax(0, 1fr) auto;
869
+ align-items: center;
870
+ gap: 1.5rem;
871
+ border-radius: var(--ui-radius-surface);
872
+ padding: 1.5rem;
873
+ background: linear-gradient(
874
+ var(--app-canvas-angle, 135deg),
875
+ color-mix(in srgb, var(--app-canvas-from, var(--k2b-surface)) min(var(--app-canvas-strength, 0%), 30%), var(--k2b-surface)),
876
+ color-mix(in srgb, var(--app-canvas-via, var(--k2b-surface)) min(var(--app-canvas-strength, 0%), 30%), var(--k2b-surface)) 50%,
877
+ color-mix(in srgb, var(--app-canvas-to, var(--k2b-surface)) min(var(--app-canvas-strength, 0%), 30%), var(--k2b-surface))
878
+ );
879
+ }
880
+
881
+ .dark .help-article:not([data-surface="embedded"]) .help-article-head {
882
+ background: linear-gradient(
883
+ var(--app-canvas-angle, 135deg),
884
+ color-mix(in srgb, var(--app-canvas-from, var(--k2b-surface)) min(var(--app-canvas-dark-strength, 0%), 15%), var(--k2b-surface)),
885
+ var(--k2b-surface) 50%,
886
+ color-mix(in srgb, var(--app-canvas-to, var(--k2b-surface)) min(var(--app-canvas-dark-strength, 0%), 15%), var(--k2b-surface))
887
+ );
888
+ }
889
+
890
+ .help-article-head-copy {
754
891
  display: flex;
892
+ min-width: 0;
755
893
  flex-direction: column;
756
894
  align-items: flex-start;
757
895
  gap: 0.75rem;
758
- margin-bottom: 2rem;
896
+ }
897
+
898
+ .help-article-icon {
899
+ width: 4.5rem;
900
+ height: 4.5rem;
901
+ font-size: 2.25rem;
902
+ }
903
+
904
+ /* Over the gradient the chips keep their own quiet fill, so they read as facts and not as part of the colour. */
905
+ .help-article:not([data-surface="embedded"]) .help-article-head .help-article-fact {
906
+ background: color-mix(in srgb, var(--k2b-surface) 72%, transparent);
759
907
  }
760
908
 
761
909
  .help-article-path {
@@ -1104,7 +1252,7 @@
1104
1252
  }
1105
1253
 
1106
1254
  .help-document :where(ul > li)::marker {
1107
- color: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 75%, var(--color-zinc-400));
1255
+ color: var(--ui-text-secondary);
1108
1256
  }
1109
1257
 
1110
1258
  .help-document :where(ol > li)::marker {
@@ -1460,11 +1608,52 @@
1460
1608
  }
1461
1609
  }
1462
1610
 
1611
+ /* Narrow: the tile stands before the title, and the path, the short answer, and the chips use the full width. */
1463
1612
  @container help-article (max-width: 40rem) {
1464
1613
  .help-article-head {
1465
1614
  margin-bottom: 1.5rem;
1466
1615
  }
1467
1616
 
1617
+ .help-article:not([data-surface="embedded"]) .help-article-head {
1618
+ grid-template-columns: auto minmax(0, 1fr);
1619
+ column-gap: 0.75rem;
1620
+ row-gap: 0;
1621
+ padding: 1.25rem 1rem;
1622
+ }
1623
+
1624
+ .help-article:not([data-surface="embedded"]) .help-article-head-copy {
1625
+ display: contents;
1626
+ }
1627
+
1628
+ .help-article:not([data-surface="embedded"]) .help-article-path {
1629
+ grid-column: 1 / -1;
1630
+ grid-row: 1;
1631
+ margin-bottom: 0.75rem;
1632
+ }
1633
+
1634
+ .help-article-icon {
1635
+ grid-column: 1;
1636
+ grid-row: 2;
1637
+ width: 2.75rem;
1638
+ height: 2.75rem;
1639
+ font-size: 1.375rem;
1640
+ }
1641
+
1642
+ .help-article:not([data-surface="embedded"]) .help-article-title {
1643
+ grid-column: 2;
1644
+ grid-row: 2;
1645
+ }
1646
+
1647
+ .help-article:not([data-surface="embedded"]) .help-article-head-copy > :not(.help-article-path, .help-article-title) {
1648
+ grid-column: 1 / -1;
1649
+ margin-top: 0.75rem;
1650
+ }
1651
+
1652
+ /* The panel article has no path row (the panel head names the app), so the tile and the title open the head. */
1653
+ .help-article[data-surface="panel"] :is(.help-article-icon, .help-article-title) {
1654
+ grid-row: 1;
1655
+ }
1656
+
1468
1657
  /* Two columns leave a phone about 140 px per alternative. */
1469
1658
  .help-document .help-compare > ul {
1470
1659
  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
  }
@@ -246,7 +246,7 @@
246
246
  @utility icon-btn {
247
247
  border-radius: var(--ui-radius-control);
248
248
  @apply inline-flex h-8 w-8 items-center justify-center p-1;
249
- @apply text-zinc-400 dark:text-zinc-500;
249
+ @apply text-dimmed;
250
250
  @apply transition-[color,background-color] duration-150 ease-out;
251
251
  @apply bg-transparent border-0 shadow-none;
252
252
  @apply hover:text-zinc-700 hover:bg-zinc-100;
@@ -398,8 +398,9 @@
398
398
  background: var(--ui-hover);
399
399
  }
400
400
 
401
+ /* An active filter chip shares the navigation fill, as the @k2b/ui FilterChip does. */
401
402
  .filter-chip-trigger[data-state="active"] {
402
403
  border-color: transparent;
403
- background: var(--ui-selected);
404
+ background: var(--ui-app-selected-nav);
404
405
  box-shadow: none;
405
406
  }
@@ -19,8 +19,9 @@
19
19
  }
20
20
  }
21
21
 
22
+ /* Selected rows carry the active app's selection fill, as @k2b/ui rows do. */
22
23
  @utility data-table-row-selected {
23
- background: var(--ui-data-row-selected);
24
+ background: var(--ui-app-selected);
24
25
  }
25
26
 
26
27
  @utility data-table-column-hover {
@@ -88,11 +88,11 @@
88
88
  }
89
89
 
90
90
  @utility menu-label {
91
- @apply px-4 pt-3 pb-1 text-xs font-medium uppercase tracking-wider text-zinc-500;
91
+ @apply px-4 pt-3 pb-1 text-xs font-medium uppercase tracking-wider text-dimmed;
92
92
  }
93
93
 
94
94
  @utility state-placeholder-icon {
95
- @apply flex shrink-0 items-center justify-center text-zinc-400 dark:text-zinc-500;
95
+ @apply flex shrink-0 items-center justify-center text-dimmed;
96
96
  }
97
97
 
98
98
  @utility state-placeholder-icon-panel {
@@ -319,23 +319,17 @@
319
319
  gap: 0.625rem;
320
320
  }
321
321
 
322
+ /* The box and the app ink; the chip comes from the shared `k2b-icon-tile` on the same element. */
322
323
  .app-overview-icon {
323
324
  width: 2.5rem;
324
325
  height: 2.5rem;
325
- border-radius: var(--ui-radius-control);
326
- background: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 9%, var(--ui-surface)) !important;
327
- color: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 80%, black) !important;
328
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 16%, transparent);
326
+ color: var(--ui-app-accent-text) !important;
329
327
  }
330
328
 
331
329
  .app-overview-icon > i {
332
330
  color: inherit !important;
333
331
  }
334
332
 
335
- .dark .app-overview-icon {
336
- color: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white) !important;
337
- }
338
-
339
333
  .app-overview-title {
340
334
  font-size: 1.125rem;
341
335
  letter-spacing: -0.015em;
@@ -507,15 +501,11 @@
507
501
  }
508
502
 
509
503
  .floating-window {
510
- border: 1px solid color-mix(in srgb, var(--app-accent, var(--ui-border-strong)) 28%, var(--ui-border-strong));
504
+ border: 1px solid var(--ui-border-strong);
511
505
  border-radius: var(--ui-dialog-radius);
512
506
  box-shadow: var(--ui-shadow-float);
513
507
  }
514
508
 
515
- .dark .floating-window {
516
- border-color: color-mix(in srgb, var(--app-accent, var(--ui-border-strong)) 34%, var(--ui-border-strong));
517
- }
518
-
519
509
  .panel-dialog-layout[data-surface="floating"] {
520
510
  gap: 0;
521
511
  border: 1px solid var(--ui-dialog-border);