@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
@@ -0,0 +1,416 @@
1
+ /* Panel slot: one provider page in a same-origin iframe beside the app.
2
+ From 1024 px it is a column after the app card and the app card narrows;
3
+ from 768 to 1023 px a non-modal drawer over the right edge; below 768 px a
4
+ sheet from the bottom. The canvas carries the state from the cloud_panel
5
+ cookie, so the server draws the first frame. Loading, failure, the resize
6
+ shield and the closing hint are stacked in the body and only change their
7
+ visibility, so nothing ever moves inside the reserved box. */
8
+ .cloud-panel {
9
+ --cloud-panel-width: var(--cloud-panel-width-set, 26rem);
10
+ position: relative;
11
+ display: none;
12
+ min-height: 0;
13
+ color: var(--k2b-text);
14
+ }
15
+
16
+ @media not all and (min-width: 1280px) {
17
+ .cloud-panel {
18
+ --cloud-panel-width: var(--cloud-panel-width-set, 21.5rem);
19
+ }
20
+ }
21
+
22
+ /* The same surface, radius, frame and dark border as the app card. */
23
+ .cloud-panel__card {
24
+ display: flex;
25
+ flex-direction: column;
26
+ width: 100%;
27
+ height: 100%;
28
+ min-height: 0;
29
+ overflow: hidden;
30
+ border: 1px solid transparent;
31
+ border-radius: var(--cloud-card-radius, var(--k2b-radius-dialog));
32
+ background: var(--k2b-surface);
33
+ box-shadow: var(--k2b-shadow-frame);
34
+ }
35
+
36
+ .dark .cloud-panel__card {
37
+ border-color: var(--k2b-border);
38
+ }
39
+
40
+ /* One 46 px row, no line below; the provider's page starts right under it. */
41
+ .cloud-panel__head {
42
+ display: flex;
43
+ flex: none;
44
+ align-items: center;
45
+ gap: 0.25rem;
46
+ min-height: 2.875rem;
47
+ padding: 0.375rem 0.375rem 0.375rem 0.5rem;
48
+ }
49
+
50
+ .cloud-panel__head .k2b-icon-button .ti {
51
+ font-size: 1.125rem;
52
+ }
53
+
54
+ .cloud-panel__title {
55
+ display: flex;
56
+ flex: 1;
57
+ min-width: 0;
58
+ align-items: center;
59
+ gap: 0.5rem;
60
+ padding-inline-start: 0.5rem;
61
+ font-size: 0.875rem;
62
+ font-weight: 600;
63
+ }
64
+
65
+ .cloud-panel__title > .ti {
66
+ flex: none;
67
+ font-size: 1rem;
68
+ color: var(--k2b-text-muted);
69
+ }
70
+
71
+ .cloud-panel__title > span {
72
+ overflow: hidden;
73
+ text-overflow: ellipsis;
74
+ white-space: nowrap;
75
+ }
76
+
77
+ .cloud-panel__tabs {
78
+ display: flex;
79
+ flex: 1;
80
+ min-width: 0;
81
+ gap: 0.125rem;
82
+ overflow-x: auto;
83
+ scrollbar-width: none;
84
+ }
85
+
86
+ .cloud-panel__tab {
87
+ display: inline-flex;
88
+ flex: none;
89
+ max-width: 12rem;
90
+ height: 2rem;
91
+ align-items: center;
92
+ gap: 0.375rem;
93
+ padding: 0 0.625rem;
94
+ border: 0;
95
+ border-radius: var(--k2b-radius-control);
96
+ background: transparent;
97
+ color: var(--k2b-text-muted);
98
+ font-size: 0.875rem;
99
+ font-weight: 500;
100
+ cursor: pointer;
101
+ }
102
+
103
+ .cloud-panel__tab > .ti {
104
+ flex: none;
105
+ font-size: 1rem;
106
+ }
107
+
108
+ .cloud-panel__tab:hover {
109
+ background: var(--k2b-hover);
110
+ color: var(--k2b-text);
111
+ }
112
+
113
+ .cloud-panel__tab[aria-selected="true"] {
114
+ background: var(--k2b-surface-muted);
115
+ color: var(--k2b-text);
116
+ }
117
+
118
+ .cloud-panel__tab:focus-visible,
119
+ .cloud-panel__grabber:focus-visible {
120
+ outline: 2px solid var(--k2b-focus-ring);
121
+ outline-offset: -2px;
122
+ }
123
+
124
+ /* Row 1 holds the consent question when the shell asks; the layers share row 2 and only change visibility. */
125
+ .cloud-panel__body {
126
+ position: relative;
127
+ display: grid;
128
+ flex: 1;
129
+ grid-template-columns: minmax(0, 1fr);
130
+ grid-template-rows: auto minmax(0, 1fr);
131
+ min-height: 0;
132
+ }
133
+
134
+ .cloud-panel__body > :is(.cloud-panel__skeleton, .cloud-panel__frame, .cloud-panel__fallback, .cloud-panel__shield, .cloud-panel__closing) {
135
+ grid-area: 2 / 1;
136
+ width: 100%;
137
+ height: 100%;
138
+ min-height: 0;
139
+ visibility: hidden;
140
+ opacity: 0;
141
+ }
142
+
143
+ .cloud-panel__body > .cloud-panel__closing {
144
+ grid-row: 1 / -1;
145
+ }
146
+
147
+ .cloud-panel[data-state="ready"] .cloud-panel__frame,
148
+ .cloud-panel[data-state="failed"] .cloud-panel__fallback,
149
+ .cloud-panel[data-resizing="true"] .cloud-panel__shield,
150
+ .cloud-panel[data-closing="true"] .cloud-panel__closing {
151
+ visibility: visible;
152
+ opacity: 1;
153
+ }
154
+
155
+ /* A loading page already takes focus and keys; the skeleton covers it until it reports ready. */
156
+ .cloud-panel[data-state="loading"] .cloud-panel__frame {
157
+ visibility: visible;
158
+ }
159
+
160
+ .cloud-panel[data-state="loading"] .cloud-panel__skeleton {
161
+ visibility: visible;
162
+ opacity: 1;
163
+ }
164
+
165
+ .cloud-panel__frame {
166
+ display: block;
167
+ border: 0;
168
+ background: var(--k2b-surface);
169
+ }
170
+
171
+ .cloud-panel__skeleton {
172
+ z-index: 1;
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: 0.75rem;
176
+ padding: 0.5rem 1rem;
177
+ background: var(--k2b-surface);
178
+ pointer-events: none;
179
+ }
180
+
181
+ .cloud-panel__skeleton > span {
182
+ display: block;
183
+ height: 0.75rem;
184
+ border-radius: 0.375rem;
185
+ background: var(--k2b-surface-muted);
186
+ }
187
+
188
+ .cloud-panel__skeleton > span:nth-child(2) {
189
+ width: 85%;
190
+ }
191
+
192
+ .cloud-panel__skeleton > span:last-child {
193
+ width: 60%;
194
+ }
195
+
196
+ @media (prefers-reduced-motion: no-preference) {
197
+ html:not([data-k2b-motion="reduced"]) .cloud-panel[data-state="loading"] .cloud-panel__skeleton > span {
198
+ animation: cloud-panel-pulse 1.4s ease-in-out infinite;
199
+ }
200
+ }
201
+
202
+ @keyframes cloud-panel-pulse {
203
+ 50% {
204
+ opacity: 0.55;
205
+ }
206
+ }
207
+
208
+ .cloud-panel__fallback {
209
+ z-index: 1;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ padding: 1.5rem;
214
+ background: var(--k2b-surface);
215
+ }
216
+
217
+ /* Takes the pointer from the iframe while the separator drags. */
218
+ .cloud-panel__shield {
219
+ z-index: 2;
220
+ cursor: col-resize;
221
+ }
222
+
223
+ /* Dragging clearly below 20 rem: the panel fades and says what release does. */
224
+ .cloud-panel__closing {
225
+ z-index: 3;
226
+ display: flex;
227
+ align-items: center;
228
+ justify-content: center;
229
+ padding: 1rem;
230
+ background: color-mix(in srgb, var(--k2b-surface) 82%, transparent);
231
+ pointer-events: none;
232
+ }
233
+
234
+ .cloud-panel__closing > span {
235
+ display: inline-flex;
236
+ align-items: center;
237
+ gap: 0.5rem;
238
+ padding: 0.5rem 0.75rem;
239
+ border-radius: var(--k2b-radius-control);
240
+ background: var(--k2b-surface-muted);
241
+ color: var(--k2b-text);
242
+ font-size: 0.875rem;
243
+ font-weight: 500;
244
+ }
245
+
246
+ .cloud-panel__closing .ti {
247
+ font-size: 1rem;
248
+ color: var(--k2b-text-muted);
249
+ }
250
+
251
+ .cloud-panel__grabber {
252
+ display: none;
253
+ }
254
+
255
+ /* The separator sits in the gap at the panel's start edge. */
256
+ .cloud-panel__resize {
257
+ position: absolute !important;
258
+ inset-block: 0;
259
+ inset-inline-start: calc(-6px - var(--cloud-card-inset, 0.5rem) / 2);
260
+ z-index: 4;
261
+ }
262
+
263
+ /* Column: the app area is the AppWorkspace host, so the app lays out by its narrowed width. */
264
+ @media (min-width: 1024px) {
265
+ .cloud-app-canvas[data-panel="open"] > .cloud-panel {
266
+ display: block;
267
+ flex: none;
268
+ align-self: stretch;
269
+ width: var(--cloud-panel-width);
270
+ margin-inline-start: var(--cloud-card-inset, 0.5rem);
271
+ }
272
+
273
+ .cloud-app-canvas[data-panel="open"] > [data-cloud-app-area] {
274
+ container: k2b-app-area / inline-size;
275
+ }
276
+ }
277
+
278
+ /* Drawer: over the app's right edge; the app keeps its width. Not modal. */
279
+ @media (min-width: 768px) {
280
+ @media not all and (min-width: 1024px) {
281
+ .cloud-app-canvas[data-panel="open"] > .cloud-panel {
282
+ position: absolute;
283
+ z-index: 20;
284
+ inset-block: calc(0.5rem + env(safe-area-inset-top, 0px)) calc(0.5rem + env(safe-area-inset-bottom, 0px));
285
+ inset-inline-end: calc(0.5rem + env(safe-area-inset-right, 0px));
286
+ display: block;
287
+ width: min(var(--cloud-panel-width), calc(100% - 1rem));
288
+ }
289
+
290
+ .cloud-panel__card {
291
+ border-radius: var(--k2b-radius-dialog);
292
+ box-shadow: var(--k2b-shadow-float);
293
+ }
294
+
295
+ .cloud-panel__resize {
296
+ display: none !important;
297
+ }
298
+ }
299
+ }
300
+
301
+ /* Sheet: from the bottom, a little over half the height; the grabber makes it full. */
302
+ @media not all and (min-width: 768px) {
303
+ .cloud-app-canvas[data-panel-sheet="open"] > .cloud-panel {
304
+ position: fixed;
305
+ z-index: 30;
306
+ inset-inline: 0;
307
+ bottom: 0;
308
+ display: block;
309
+ height: 58dvh;
310
+ }
311
+
312
+ /* The page's last row scrolls up above the sheet. Only the end of the page
313
+ grows, so nothing above it moves; full pages own their own scrollers. */
314
+ .cloud-app-canvas[data-layout-authenticated][data-panel-sheet="open"]:not([data-layout-full-page]) .layout-content-main {
315
+ padding-bottom: max(58dvh, var(--cloud-tab-bar-space, 0px));
316
+ scroll-padding-bottom: max(58dvh, var(--cloud-tab-bar-space, 0px));
317
+ }
318
+
319
+ .cloud-app-canvas[data-panel-sheet="open"] > .cloud-panel[data-panel-size="full"] {
320
+ height: calc(100dvh - 2.875rem - env(safe-area-inset-top, 0px));
321
+ }
322
+
323
+ .cloud-panel__card {
324
+ padding-bottom: env(safe-area-inset-bottom, 0px);
325
+ border-radius: 1.125rem 1.125rem 0 0;
326
+ box-shadow: 0 -10px 36px rgb(9 9 11 / 0.18);
327
+ }
328
+
329
+ .dark .cloud-panel__card {
330
+ border-bottom-color: transparent;
331
+ box-shadow: 0 -10px 36px rgb(0 0 0 / 0.5);
332
+ }
333
+
334
+ .cloud-panel__grabber {
335
+ display: flex;
336
+ flex: none;
337
+ align-items: center;
338
+ justify-content: center;
339
+ width: 100%;
340
+ height: 1.25rem;
341
+ padding: 0;
342
+ border: 0;
343
+ background: transparent;
344
+ }
345
+
346
+ .cloud-panel__grabber > span {
347
+ width: 2.375rem;
348
+ height: 0.3125rem;
349
+ border-radius: 999px;
350
+ background: var(--k2b-border-strong);
351
+ }
352
+
353
+ .cloud-panel__head {
354
+ padding-top: 0;
355
+ }
356
+
357
+ .cloud-panel__resize {
358
+ display: none !important;
359
+ }
360
+ }
361
+
362
+ @media print {
363
+ .cloud-panel {
364
+ display: none !important;
365
+ }
366
+ }
367
+
368
+ /* The provider's page inside the iframe: the panel surface from the first paint. */
369
+ .cloud-panel-page {
370
+ display: flex;
371
+ height: 100dvh;
372
+ min-height: 0;
373
+ flex-direction: column;
374
+ background: var(--k2b-surface);
375
+ }
376
+
377
+ html:has([data-cloud-panel-page]),
378
+ html:has([data-cloud-panel-page]) body {
379
+ background: var(--k2b-surface);
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,15 +7,26 @@
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
- --ui-app-accent-text: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 78%, black);
20
+ /* app-color.ts computes these defaults from --color-blue-500; app-color.test.ts pins them. */
21
+ --app-ink-light: #2469d4;
22
+ --app-ink-dark: #589aff;
23
+ --app-selected-light: #f0f6ff;
24
+ --app-selected-dark: #15243b;
25
+ --app-selected-nav-light: #eaf2ff;
26
+ --app-selected-nav-dark: #152742;
27
+ --ui-app-accent-text: var(--app-ink-light);
28
+ --ui-app-selected: var(--app-selected-light);
29
+ --ui-app-selected-nav: var(--app-selected-nav-light);
19
30
  --ui-app-accent-border: var(--app-accent, var(--color-blue-500));
20
31
  --ui-focus: inset 0 0 0 2px color-mix(in srgb, var(--color-blue-500) 45%, transparent);
21
32
  --ui-action-primary-surface: var(--color-zinc-900);
@@ -42,7 +53,6 @@
42
53
  --ui-data-divider: var(--ui-border);
43
54
  --ui-data-row-divider: color-mix(in srgb, var(--ui-border) 72%, transparent);
44
55
  --ui-data-row-hover: rgb(9 9 11 / 0.025);
45
- --ui-data-row-selected: var(--ui-selected);
46
56
  --ui-data-column-hover: rgb(9 9 11 / 0.012);
47
57
  --ui-dialog-surface: var(--ui-surface-raised);
48
58
  --ui-dialog-border: var(--ui-border-strong);
@@ -137,13 +147,16 @@
137
147
  --ui-surface-subtle: color-mix(in srgb, var(--ui-surface) 68%, var(--ui-surface-muted));
138
148
  --ui-surface-muted: #20242a;
139
149
  --ui-surface-raised: #181d24;
140
- --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;
141
152
  --ui-border: #29313a;
142
153
  --ui-border-strong: #3a4552;
143
154
  --ui-hover: #252c35;
144
155
  --ui-active: color-mix(in srgb, var(--ui-hover) 72%, var(--ui-border-strong));
145
156
  --ui-selected: color-mix(in srgb, var(--color-blue-400) 12%, var(--ui-surface));
146
- --ui-app-accent-text: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
157
+ --ui-app-accent-text: var(--app-ink-dark);
158
+ --ui-app-selected: var(--app-selected-dark);
159
+ --ui-app-selected-nav: var(--app-selected-nav-dark);
147
160
  --ui-app-accent-border: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
148
161
  --ui-focus: inset 0 0 0 2px color-mix(in srgb, var(--color-blue-400) 55%, transparent);
149
162
  --ui-action-primary-surface: var(--color-zinc-100);
@@ -186,23 +199,32 @@
186
199
  --ui-shadow-workspace-edge: -3px 0 6px -5px rgb(0 0 0 / 0.28), 3px 0 6px -5px rgb(0 0 0 / 0.28);
187
200
  }
188
201
 
189
- /* App appearance is applied on the canvas rather than :root. Recompute the
190
- derived accent roles here so an app never inherits the platform fallback. */
202
+ /* Custom properties resolve where declared. Each app scope must re-read its
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. */
191
205
  .cloud-app-canvas,
192
206
  .app-accent-scope {
193
- --ui-app-accent-text: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 78%, black);
207
+ --ui-app-accent-text: var(--app-ink-light);
208
+ --ui-app-selected: var(--app-selected-light);
209
+ --ui-app-selected-nav: var(--app-selected-nav-light);
194
210
  --ui-app-accent-border: var(--app-accent, var(--color-blue-500));
195
211
  /* Portable UI components consume their own optional hook; Cloud alone maps
196
212
  it to the active app appearance. */
197
213
  --k2b-app-workspace-active: var(--ui-app-accent-text);
214
+ --k2b-app-workspace-selected: var(--ui-app-selected-nav);
215
+ --k2b-row-selected: var(--ui-app-selected);
198
216
  --k2b-detail-panel-accent: var(--ui-app-accent-text);
199
217
  --k2b-link-accent: var(--ui-app-accent-text);
200
218
  }
201
219
 
202
220
  .dark :is(.cloud-app-canvas, .app-accent-scope) {
203
- --ui-app-accent-text: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
221
+ --ui-app-accent-text: var(--app-ink-dark);
222
+ --ui-app-selected: var(--app-selected-dark);
223
+ --ui-app-selected-nav: var(--app-selected-nav-dark);
204
224
  --ui-app-accent-border: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
205
225
  --k2b-app-workspace-active: var(--ui-app-accent-text);
226
+ --k2b-app-workspace-selected: var(--ui-app-selected-nav);
227
+ --k2b-row-selected: var(--ui-app-selected);
206
228
  --k2b-detail-panel-accent: var(--ui-app-accent-text);
207
229
  --k2b-link-accent: var(--ui-app-accent-text);
208
230
  }
@@ -222,7 +244,7 @@
222
244
  }
223
245
 
224
246
  @utility text-secondary {
225
- @apply text-zinc-700 dark:text-zinc-300;
247
+ color: var(--ui-text-secondary);
226
248
  }
227
249
 
228
250
  @utility text-dimmed {
@@ -230,5 +252,6 @@
230
252
  }
231
253
 
232
254
  @utility text-label {
233
- @apply text-sm font-medium text-zinc-700 dark:text-zinc-300;
255
+ @apply text-sm font-medium;
256
+ color: var(--ui-text-secondary);
234
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);
@@ -95,7 +95,7 @@
95
95
  .md-editor-tool {
96
96
  @apply inline-flex items-center justify-center;
97
97
  @apply w-7 h-7 rounded;
98
- @apply text-zinc-500 dark:text-zinc-400;
98
+ @apply text-dimmed;
99
99
  @apply hover:bg-zinc-200 hover:text-zinc-800;
100
100
  @apply dark:hover:bg-zinc-700 dark:hover:text-zinc-100;
101
101
  @apply transition-colors;
@@ -339,7 +339,7 @@
339
339
  pointer-events: none !important;
340
340
  user-select: none !important;
341
341
  z-index: 0 !important;
342
- @apply text-zinc-400 dark:text-zinc-500;
342
+ @apply text-dimmed;
343
343
  white-space: pre-wrap !important;
344
344
  word-wrap: break-word !important;
345
345
  word-break: normal !important;
@@ -354,7 +354,7 @@
354
354
  on-screen but dimmed so prose stands out. */
355
355
  .md-syntax,
356
356
  .md-marker {
357
- @apply text-zinc-400 dark:text-zinc-500;
357
+ @apply text-dimmed;
358
358
  }
359
359
 
360
360
  /* Bold + headers via `text-shadow` instead of `font-weight`.
@@ -422,7 +422,7 @@
422
422
  doesn't affect layout, unlike border-left or padding-left). The bar
423
423
  sits visually outside the text column edge so prose stays aligned. */
424
424
  .md-quote {
425
- @apply text-zinc-600 dark:text-zinc-400;
425
+ @apply text-dimmed;
426
426
  box-shadow: inset 3px 0 0 var(--color-zinc-300);
427
427
  }
428
428
 
@@ -475,7 +475,7 @@
475
475
  /* Horizontal rule (---) — just dim the marker; can't draw an actual
476
476
  rule line without inserting layout-shifting elements. */
477
477
  .md-hr {
478
- @apply text-zinc-400 dark:text-zinc-500;
478
+ @apply text-dimmed;
479
479
  }
480
480
 
481
481
  /* Completion match — every occurrence of a registered completion
@@ -498,7 +498,7 @@
498
498
  .md-editor-stats {
499
499
  @apply flex justify-start items-center gap-3;
500
500
  @apply px-3 py-1;
501
- @apply text-[10px] text-zinc-400 dark:text-zinc-500;
501
+ @apply text-[10px] text-dimmed;
502
502
  @apply font-mono select-none;
503
503
  letter-spacing: 0.02em;
504
504
  }