@k2b/cloud 0.37.0 → 0.39.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.
- package/package.json +8 -3
- package/scripts/build-canvas-workers.ts +109 -29
- package/scripts/build.ts +3 -5
- package/src/_internal/app-path.ts +25 -0
- package/src/_internal/app-presentation.ts +4 -0
- package/src/_internal/canvas-worker.ts +5 -3
- package/src/_internal/define-app.ts +28 -2
- package/src/_internal/help-catalog.ts +123 -4
- package/src/_internal/help.ts +27 -6
- package/src/_internal/panel-cookie.ts +39 -0
- package/src/_internal/registry-validation.ts +21 -26
- package/src/_internal/runtime-context.ts +1 -0
- package/src/access/PermissionEditor.browser-harness.tsx +57 -38
- package/src/access/PermissionEditor.tsx +30 -5
- package/src/access/ResourceApiKeys.tsx +5 -1
- package/src/account/Avatar.tsx +20 -2
- package/src/account/ui.ts +1 -1
- package/src/ai/approval-routes.ts +5 -2
- package/src/ai/approvals.ts +139 -28
- package/src/ai/browser.ts +11 -0
- package/src/ai/chart-block.ts +44 -13
- package/src/ai/chat/action-sentences.ts +1 -44
- package/src/ai/chat/chart-block.tsx +2 -11
- package/src/ai/chat/messages.ts +10 -0
- package/src/ai/chat/turn-view.tsx +35 -3
- package/src/ai/chat/view-block.tsx +7 -2
- package/src/ai/chat/web-tools.tsx +23 -6
- package/src/ai/chat-task-contracts.ts +28 -3
- package/src/ai/chat-task-reasons-migrate.ts +68 -0
- package/src/ai/chat-task-reasons.ts +92 -0
- package/src/ai/chat-tasks.ts +460 -66
- package/src/ai/default-tool-contracts.ts +30 -13
- package/src/ai/fetch-file-tool.ts +37 -3
- package/src/ai/index.ts +12 -0
- package/src/ai/migrate.ts +46 -3
- package/src/ai/readable-html.ts +1057 -0
- package/src/ai/request-context.ts +7 -1
- package/src/ai/routes.ts +27 -1
- package/src/ai/runtime.ts +12 -27
- package/src/ai/store.ts +5 -3
- package/src/ai/timeline.ts +9 -1
- package/src/ai/tool-wording.ts +45 -0
- package/src/ai/turn-abort.ts +33 -0
- package/src/ai/types.ts +12 -1
- package/src/api/admin-core-settings.ts +190 -139
- package/src/api/admin-outgoing-mail.ts +4 -2
- package/src/api/app-approval.ts +9 -2
- package/src/api/approvals.ts +1 -1
- package/src/api/help.ts +23 -4
- package/src/api/index.ts +2 -1
- package/src/api/mcp.ts +8 -2
- package/src/browser/CloudResourceSearch.tsx +10 -6
- package/src/browser/FileChooser.tsx +11 -3
- package/src/browser/app-approval.ts +7 -2
- package/src/browser/choose-files.browser-harness.tsx +4 -1
- package/src/browser/composer-catalog.ts +103 -0
- package/src/browser/composer-insert-messages.ts +31 -0
- package/src/browser/composer-insert.ts +150 -0
- package/src/browser/file-chooser-messages.ts +4 -0
- package/src/browser/file-providers.ts +14 -1
- package/src/browser/panel-messages.ts +24 -0
- package/src/browser/panel-slot.ts +354 -0
- package/src/browser/panel.ts +28 -0
- package/src/browser/provider-folder.tsx +58 -5
- package/src/browser/resource-picker.tsx +17 -2
- package/src/cli/admin/workflows.ts +1 -0
- package/src/config/env.ts +14 -0
- package/src/contracts/app-approval.ts +49 -9
- package/src/contracts/app.ts +14 -0
- package/src/contracts/file-provider.ts +48 -1
- package/src/contracts/outgoing-mail.ts +8 -1
- package/src/contracts/registry.ts +3 -1
- package/src/contracts/shared.ts +2 -0
- package/src/server/help.ts +136 -10
- package/src/server/services/access.ts +5 -1
- package/src/server/services/geo.ts +21 -4
- package/src/services/accounts/posix.ts +2 -1
- package/src/services/allowed-network-fetch.ts +189 -0
- package/src/services/app-approval.ts +158 -38
- package/src/services/approvals/device.ts +2 -0
- package/src/services/help/index.ts +46 -5
- package/src/services/help/store.ts +26 -5
- package/src/services/help/types.ts +10 -2
- package/src/services/index.ts +22 -3
- package/src/services/notifications/browser.ts +16 -5
- package/src/services/outgoing-mail/dispatcher.ts +7 -2
- package/src/services/outgoing-mail/resolve-host.ts +39 -0
- package/src/services/outgoing-mail/test-send.ts +18 -3
- package/src/services/outgoing-mail/transport.ts +24 -13
- package/src/services/pdf/image-protocol.ts +135 -0
- package/src/services/pdf/image-variants.ts +124 -0
- package/src/services/pdf/image-worker.ts +31 -0
- package/src/services/pdf/image.ts +109 -0
- package/src/services/pdf/index.ts +9 -0
- package/src/services/settings/api.ts +11 -6
- package/src/services/settings/app.ts +5 -4
- package/src/services/settings/core-settings.ts +4 -3
- package/src/services/settings/index.ts +67 -43
- package/src/services/settings/namespace.ts +2 -1
- package/src/services/settings/store.ts +83 -11
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/app-presentation.ts +1 -0
- package/src/shared/help.ts +41 -0
- package/src/shared/index.ts +9 -1
- package/src/shared/markdown/extensions/guided-help.ts +208 -64
- package/src/shared/markdown/formula.ts +8 -8
- package/src/shared/markdown/index.ts +72 -11
- package/src/shared/redirect.ts +12 -0
- package/src/ssr/CloudShell.island.tsx +86 -9
- package/src/ssr/Layout.tsx +62 -11
- package/src/ssr/LayoutHeader.tsx +15 -9
- package/src/ssr/LayoutHelp.browser-harness.tsx +130 -27
- package/src/ssr/LayoutHelp.tsx +569 -410
- package/src/ssr/LayoutRail.tsx +2 -2
- package/src/ssr/LayoutTabBar.tsx +29 -0
- package/src/ssr/PanelLayout.tsx +27 -0
- package/src/ssr/PanelRuntime.island.tsx +87 -0
- package/src/ssr/PanelSlot.tsx +108 -0
- package/src/ssr/PanelTrigger.tsx +49 -0
- package/src/ssr/ShellRuntime.island.tsx +72 -25
- package/src/ssr/app-appearance.ts +3 -2
- package/src/ssr/app-color.ts +100 -0
- package/src/ssr/app-launchpad.tsx +18 -1
- package/src/ssr/app-navigation.ts +15 -0
- package/src/ssr/compact-shell.baseline.json +5270 -0
- package/src/ssr/compact-shell.browser-harness.ts +431 -0
- package/src/ssr/help-messages.ts +60 -38
- package/src/ssr/help.ts +3 -0
- package/src/ssr/index.ts +3 -0
- package/src/ssr/layout-help-article.ts +41 -0
- package/src/ssr/layout-help-navigation.ts +0 -12
- package/src/ssr/layout-help-registry.tsx +4 -11
- package/src/ssr/layout-help-search.ts +9 -12
- package/src/ssr/layout-help-start.ts +68 -0
- package/src/ssr/panel-slot.browser-harness.tsx +110 -0
- package/src/ssr/platform-messages.ts +34 -0
- package/src/ssr/rail-navigation.ts +44 -0
- package/src/ssr/shell-data.tsx +11 -0
- package/src/ssr/shell-shortcuts.ts +19 -0
- package/src/styles/effects.css +689 -185
- package/src/styles/global.css +269 -55
- package/src/styles/panel.css +373 -0
- package/src/styles/resource-search.css +25 -0
- package/src/styles/tokens.css +23 -6
- package/src/styles/utilities-navigation.css +5 -21
- package/src/workflows/store/events.ts +24 -0
- package/src/workflows/store/index.ts +8 -0
- package/src/workflows/store/observability.ts +6 -1
- package/src/workflows/store/retention.ts +157 -0
|
@@ -0,0 +1,373 @@
|
|
|
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
|
+
.cloud-panel__tab-label {
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
text-overflow: ellipsis;
|
|
75
|
+
white-space: nowrap;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.cloud-panel__tabs {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex: 1;
|
|
81
|
+
min-width: 0;
|
|
82
|
+
gap: 0.125rem;
|
|
83
|
+
overflow-x: auto;
|
|
84
|
+
scrollbar-width: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.cloud-panel__tab {
|
|
88
|
+
display: inline-flex;
|
|
89
|
+
flex: none;
|
|
90
|
+
max-width: 12rem;
|
|
91
|
+
height: 2rem;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 0.375rem;
|
|
94
|
+
padding: 0 0.625rem;
|
|
95
|
+
border: 0;
|
|
96
|
+
border-radius: var(--k2b-radius-control);
|
|
97
|
+
background: transparent;
|
|
98
|
+
color: var(--k2b-text-muted);
|
|
99
|
+
font-size: 0.875rem;
|
|
100
|
+
font-weight: 500;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.cloud-panel__tab > .ti {
|
|
105
|
+
flex: none;
|
|
106
|
+
font-size: 1rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.cloud-panel__tab:hover {
|
|
110
|
+
background: var(--k2b-hover);
|
|
111
|
+
color: var(--k2b-text);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.cloud-panel__tab[aria-selected="true"] {
|
|
115
|
+
background: var(--k2b-surface-muted);
|
|
116
|
+
color: var(--k2b-text);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.cloud-panel__tab:focus-visible,
|
|
120
|
+
.cloud-panel__grabber:focus-visible {
|
|
121
|
+
outline: 2px solid var(--k2b-focus-ring);
|
|
122
|
+
outline-offset: -2px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.cloud-panel__body {
|
|
126
|
+
position: relative;
|
|
127
|
+
flex: 1;
|
|
128
|
+
min-height: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.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;
|
|
134
|
+
width: 100%;
|
|
135
|
+
height: 100%;
|
|
136
|
+
visibility: hidden;
|
|
137
|
+
opacity: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.cloud-panel[data-state="ready"] .cloud-panel__frame,
|
|
141
|
+
.cloud-panel[data-state="failed"] .cloud-panel__fallback,
|
|
142
|
+
.cloud-panel[data-resizing="true"] .cloud-panel__shield,
|
|
143
|
+
.cloud-panel[data-closing="true"] .cloud-panel__closing {
|
|
144
|
+
visibility: visible;
|
|
145
|
+
opacity: 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* A loading page already takes focus and keys; the skeleton covers it until it reports ready. */
|
|
149
|
+
.cloud-panel[data-state="loading"] .cloud-panel__frame {
|
|
150
|
+
visibility: visible;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.cloud-panel[data-state="loading"] .cloud-panel__skeleton {
|
|
154
|
+
visibility: visible;
|
|
155
|
+
opacity: 1;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.cloud-panel__frame {
|
|
159
|
+
display: block;
|
|
160
|
+
border: 0;
|
|
161
|
+
background: var(--k2b-surface);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.cloud-panel__skeleton {
|
|
165
|
+
z-index: 1;
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
gap: 0.75rem;
|
|
169
|
+
padding: 0.5rem 1rem;
|
|
170
|
+
background: var(--k2b-surface);
|
|
171
|
+
pointer-events: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.cloud-panel__skeleton > span {
|
|
175
|
+
display: block;
|
|
176
|
+
height: 0.75rem;
|
|
177
|
+
border-radius: 0.375rem;
|
|
178
|
+
background: var(--k2b-surface-muted);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.cloud-panel__skeleton > span:nth-child(2) {
|
|
182
|
+
width: 85%;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.cloud-panel__skeleton > span:last-child {
|
|
186
|
+
width: 60%;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
190
|
+
html:not([data-k2b-motion="reduced"]) .cloud-panel[data-state="loading"] .cloud-panel__skeleton > span {
|
|
191
|
+
animation: cloud-panel-pulse 1.4s ease-in-out infinite;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@keyframes cloud-panel-pulse {
|
|
196
|
+
50% {
|
|
197
|
+
opacity: 0.55;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.cloud-panel__fallback {
|
|
202
|
+
z-index: 1;
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
justify-content: center;
|
|
206
|
+
padding: 1.5rem;
|
|
207
|
+
background: var(--k2b-surface);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Takes the pointer from the iframe while the separator drags. */
|
|
211
|
+
.cloud-panel__shield {
|
|
212
|
+
z-index: 2;
|
|
213
|
+
cursor: col-resize;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Dragging clearly below 20 rem: the panel fades and says what release does. */
|
|
217
|
+
.cloud-panel__closing {
|
|
218
|
+
z-index: 3;
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
justify-content: center;
|
|
222
|
+
padding: 1rem;
|
|
223
|
+
background: color-mix(in srgb, var(--k2b-surface) 82%, transparent);
|
|
224
|
+
pointer-events: none;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.cloud-panel__closing > span {
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
gap: 0.5rem;
|
|
231
|
+
padding: 0.5rem 0.75rem;
|
|
232
|
+
border-radius: var(--k2b-radius-control);
|
|
233
|
+
background: var(--k2b-surface-muted);
|
|
234
|
+
color: var(--k2b-text);
|
|
235
|
+
font-size: 0.875rem;
|
|
236
|
+
font-weight: 500;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.cloud-panel__closing .ti {
|
|
240
|
+
font-size: 1rem;
|
|
241
|
+
color: var(--k2b-text-muted);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.cloud-panel__grabber {
|
|
245
|
+
display: none;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* The separator sits in the gap at the panel's start edge. */
|
|
249
|
+
.cloud-panel__resize {
|
|
250
|
+
position: absolute !important;
|
|
251
|
+
inset-block: 0;
|
|
252
|
+
inset-inline-start: calc(-6px - var(--cloud-card-inset, 0.5rem) / 2);
|
|
253
|
+
z-index: 4;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Column: the app area is the AppWorkspace host, so the app lays out by its narrowed width. */
|
|
257
|
+
@media (min-width: 1024px) {
|
|
258
|
+
.cloud-app-canvas[data-panel="open"] > .cloud-panel {
|
|
259
|
+
display: block;
|
|
260
|
+
flex: none;
|
|
261
|
+
align-self: stretch;
|
|
262
|
+
width: var(--cloud-panel-width);
|
|
263
|
+
margin-inline-start: var(--cloud-card-inset, 0.5rem);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.cloud-app-canvas[data-panel="open"] > [data-cloud-app-area] {
|
|
267
|
+
container: k2b-app-area / inline-size;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Drawer: over the app's right edge; the app keeps its width. Not modal. */
|
|
272
|
+
@media (min-width: 768px) {
|
|
273
|
+
@media not all and (min-width: 1024px) {
|
|
274
|
+
.cloud-app-canvas[data-panel="open"] > .cloud-panel {
|
|
275
|
+
position: absolute;
|
|
276
|
+
z-index: 20;
|
|
277
|
+
inset-block: calc(0.5rem + env(safe-area-inset-top, 0px)) calc(0.5rem + env(safe-area-inset-bottom, 0px));
|
|
278
|
+
inset-inline-end: calc(0.5rem + env(safe-area-inset-right, 0px));
|
|
279
|
+
display: block;
|
|
280
|
+
width: min(var(--cloud-panel-width), calc(100% - 1rem));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.cloud-panel__card {
|
|
284
|
+
border-radius: var(--k2b-radius-dialog);
|
|
285
|
+
box-shadow: var(--k2b-shadow-float);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.cloud-panel__resize {
|
|
289
|
+
display: none !important;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Sheet: from the bottom, a little over half the height; the grabber makes it full. */
|
|
295
|
+
@media not all and (min-width: 768px) {
|
|
296
|
+
.cloud-app-canvas[data-panel-sheet="open"] > .cloud-panel {
|
|
297
|
+
position: fixed;
|
|
298
|
+
z-index: 30;
|
|
299
|
+
inset-inline: 0;
|
|
300
|
+
bottom: 0;
|
|
301
|
+
display: block;
|
|
302
|
+
height: 58dvh;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* The page's last row scrolls up above the sheet. Only the end of the page
|
|
306
|
+
grows, so nothing above it moves; full pages own their own scrollers. */
|
|
307
|
+
.cloud-app-canvas[data-layout-authenticated][data-panel-sheet="open"]:not([data-layout-full-page]) .layout-content-main {
|
|
308
|
+
padding-bottom: max(58dvh, var(--cloud-tab-bar-space, 0px));
|
|
309
|
+
scroll-padding-bottom: max(58dvh, var(--cloud-tab-bar-space, 0px));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.cloud-app-canvas[data-panel-sheet="open"] > .cloud-panel[data-panel-size="full"] {
|
|
313
|
+
height: calc(100dvh - 2.875rem - env(safe-area-inset-top, 0px));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.cloud-panel__card {
|
|
317
|
+
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
318
|
+
border-radius: 1.125rem 1.125rem 0 0;
|
|
319
|
+
box-shadow: 0 -10px 36px rgb(9 9 11 / 0.18);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.dark .cloud-panel__card {
|
|
323
|
+
border-bottom-color: transparent;
|
|
324
|
+
box-shadow: 0 -10px 36px rgb(0 0 0 / 0.5);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.cloud-panel__grabber {
|
|
328
|
+
display: flex;
|
|
329
|
+
flex: none;
|
|
330
|
+
align-items: center;
|
|
331
|
+
justify-content: center;
|
|
332
|
+
width: 100%;
|
|
333
|
+
height: 1.25rem;
|
|
334
|
+
padding: 0;
|
|
335
|
+
border: 0;
|
|
336
|
+
background: transparent;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.cloud-panel__grabber > span {
|
|
340
|
+
width: 2.375rem;
|
|
341
|
+
height: 0.3125rem;
|
|
342
|
+
border-radius: 999px;
|
|
343
|
+
background: var(--k2b-border-strong);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.cloud-panel__head {
|
|
347
|
+
padding-top: 0;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.cloud-panel__resize {
|
|
351
|
+
display: none !important;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
@media print {
|
|
356
|
+
.cloud-panel {
|
|
357
|
+
display: none !important;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* The provider's page inside the iframe: the panel surface from the first paint. */
|
|
362
|
+
.cloud-panel-page {
|
|
363
|
+
display: flex;
|
|
364
|
+
height: 100dvh;
|
|
365
|
+
min-height: 0;
|
|
366
|
+
flex-direction: column;
|
|
367
|
+
background: var(--k2b-surface);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
html:has([data-cloud-panel-page]),
|
|
371
|
+
html:has([data-cloud-panel-page]) body {
|
|
372
|
+
background: var(--k2b-surface);
|
|
373
|
+
}
|
|
@@ -428,6 +428,31 @@
|
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
/* On a phone the resource picker is a full-screen sheet: the whole width, from
|
|
432
|
+
below the status bar to the bottom edge, rounded at the top. Its results
|
|
433
|
+
take the room the search field and footer leave. Global search keeps its
|
|
434
|
+
floating card. */
|
|
435
|
+
@media (max-width: 47.999rem) {
|
|
436
|
+
.cloud-search-dialog.cloud-search-dialog--picker {
|
|
437
|
+
--search-top: max(0.5rem, env(safe-area-inset-top, 0px));
|
|
438
|
+
--search-max-height: calc(100dvh - var(--search-top));
|
|
439
|
+
inset: var(--search-top) 0 auto;
|
|
440
|
+
transform: none;
|
|
441
|
+
width: 100%;
|
|
442
|
+
height: var(--search-max-height);
|
|
443
|
+
border-width: 1px 0 0;
|
|
444
|
+
border-radius: var(--ui-radius-frame) var(--ui-radius-frame) 0 0;
|
|
445
|
+
}
|
|
446
|
+
.cloud-search-dialog--picker > .cloud-search-dialog__content,
|
|
447
|
+
.cloud-search-dialog--picker .cloud-resource-search {
|
|
448
|
+
height: 100%;
|
|
449
|
+
}
|
|
450
|
+
.cloud-search-dialog--picker .cloud-resource-search {
|
|
451
|
+
--search-body-height: max(0px, calc(var(--search-max-height) - 12rem));
|
|
452
|
+
padding-bottom: max(0.5rem, env(safe-area-inset-bottom, 0px));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
431
456
|
/* Border grips belong only to desktop Spotlight, never the resource picker. */
|
|
432
457
|
.cloud-search-dialog:has(.cloud-global-search) {
|
|
433
458
|
overflow: visible;
|
package/src/styles/tokens.css
CHANGED
|
@@ -15,7 +15,16 @@
|
|
|
15
15
|
--ui-hover: rgb(239 240 239 / 0.9);
|
|
16
16
|
--ui-active: rgb(229 231 230 / 0.94);
|
|
17
17
|
--ui-selected: color-mix(in srgb, var(--color-blue-500) 7%, var(--ui-surface));
|
|
18
|
-
|
|
18
|
+
/* app-color.ts computes these defaults from --color-blue-500; app-color.test.ts pins them. */
|
|
19
|
+
--app-ink-light: #2264c9;
|
|
20
|
+
--app-ink-dark: #589aff;
|
|
21
|
+
--app-selected-light: #f0f6ff;
|
|
22
|
+
--app-selected-dark: #15243b;
|
|
23
|
+
--app-selected-nav-light: #e0e8f6;
|
|
24
|
+
--app-selected-nav-dark: #22334e;
|
|
25
|
+
--ui-app-accent-text: var(--app-ink-light);
|
|
26
|
+
--ui-app-selected: var(--app-selected-light);
|
|
27
|
+
--ui-app-selected-nav: var(--app-selected-nav-light);
|
|
19
28
|
--ui-app-accent-border: var(--app-accent, var(--color-blue-500));
|
|
20
29
|
--ui-focus: inset 0 0 0 2px color-mix(in srgb, var(--color-blue-500) 45%, transparent);
|
|
21
30
|
--ui-action-primary-surface: var(--color-zinc-900);
|
|
@@ -143,7 +152,9 @@
|
|
|
143
152
|
--ui-hover: #252c35;
|
|
144
153
|
--ui-active: color-mix(in srgb, var(--ui-hover) 72%, var(--ui-border-strong));
|
|
145
154
|
--ui-selected: color-mix(in srgb, var(--color-blue-400) 12%, var(--ui-surface));
|
|
146
|
-
--ui-app-accent-text:
|
|
155
|
+
--ui-app-accent-text: var(--app-ink-dark);
|
|
156
|
+
--ui-app-selected: var(--app-selected-dark);
|
|
157
|
+
--ui-app-selected-nav: var(--app-selected-nav-dark);
|
|
147
158
|
--ui-app-accent-border: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
|
|
148
159
|
--ui-focus: inset 0 0 0 2px color-mix(in srgb, var(--color-blue-400) 55%, transparent);
|
|
149
160
|
--ui-action-primary-surface: var(--color-zinc-100);
|
|
@@ -186,23 +197,29 @@
|
|
|
186
197
|
--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
198
|
}
|
|
188
199
|
|
|
189
|
-
/*
|
|
190
|
-
|
|
200
|
+
/* Custom properties resolve where declared. Each app scope must re-read its
|
|
201
|
+
inline per-app values instead of inheriting the resolved platform defaults. */
|
|
191
202
|
.cloud-app-canvas,
|
|
192
203
|
.app-accent-scope {
|
|
193
|
-
--ui-app-accent-text:
|
|
204
|
+
--ui-app-accent-text: var(--app-ink-light);
|
|
205
|
+
--ui-app-selected: var(--app-selected-light);
|
|
206
|
+
--ui-app-selected-nav: var(--app-selected-nav-light);
|
|
194
207
|
--ui-app-accent-border: var(--app-accent, var(--color-blue-500));
|
|
195
208
|
/* Portable UI components consume their own optional hook; Cloud alone maps
|
|
196
209
|
it to the active app appearance. */
|
|
197
210
|
--k2b-app-workspace-active: var(--ui-app-accent-text);
|
|
211
|
+
--k2b-app-workspace-selected: var(--ui-app-selected-nav);
|
|
198
212
|
--k2b-detail-panel-accent: var(--ui-app-accent-text);
|
|
199
213
|
--k2b-link-accent: var(--ui-app-accent-text);
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
.dark :is(.cloud-app-canvas, .app-accent-scope) {
|
|
203
|
-
--ui-app-accent-text:
|
|
217
|
+
--ui-app-accent-text: var(--app-ink-dark);
|
|
218
|
+
--ui-app-selected: var(--app-selected-dark);
|
|
219
|
+
--ui-app-selected-nav: var(--app-selected-nav-dark);
|
|
204
220
|
--ui-app-accent-border: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
|
|
205
221
|
--k2b-app-workspace-active: var(--ui-app-accent-text);
|
|
222
|
+
--k2b-app-workspace-selected: var(--ui-app-selected-nav);
|
|
206
223
|
--k2b-detail-panel-accent: var(--ui-app-accent-text);
|
|
207
224
|
--k2b-link-accent: var(--ui-app-accent-text);
|
|
208
225
|
}
|
|
@@ -226,12 +226,12 @@
|
|
|
226
226
|
@utility sidebar-item-active {
|
|
227
227
|
position: relative;
|
|
228
228
|
background: var(--ui-selected);
|
|
229
|
-
color:
|
|
229
|
+
color: var(--ui-app-accent-text);
|
|
230
230
|
@apply font-medium;
|
|
231
231
|
box-shadow: var(--ui-shadow-surface);
|
|
232
232
|
|
|
233
233
|
&:hover {
|
|
234
|
-
color:
|
|
234
|
+
color: var(--ui-app-accent-text);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
&::before {
|
|
@@ -241,14 +241,6 @@
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
.dark .sidebar-item-active {
|
|
245
|
-
color: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.dark .sidebar-item-active:hover {
|
|
249
|
-
color: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 45%, white);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
244
|
@utility sidebar-item-meta {
|
|
253
245
|
@apply mt-0.5 text-left text-xs leading-tight text-dimmed;
|
|
254
246
|
}
|
|
@@ -723,12 +715,12 @@
|
|
|
723
715
|
border-left: 0;
|
|
724
716
|
position: relative;
|
|
725
717
|
background: var(--ui-selected);
|
|
726
|
-
color:
|
|
718
|
+
color: var(--ui-app-accent-text);
|
|
727
719
|
@apply rounded-lg font-medium;
|
|
728
720
|
box-shadow: var(--ui-shadow-surface);
|
|
729
721
|
|
|
730
722
|
&:hover {
|
|
731
|
-
color:
|
|
723
|
+
color: var(--ui-app-accent-text);
|
|
732
724
|
}
|
|
733
725
|
|
|
734
726
|
&::before {
|
|
@@ -738,14 +730,6 @@
|
|
|
738
730
|
}
|
|
739
731
|
}
|
|
740
732
|
|
|
741
|
-
.dark .list-item-active {
|
|
742
|
-
color: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 58%, white);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
.dark .list-item-active:hover {
|
|
746
|
-
color: color-mix(in srgb, var(--app-accent, var(--color-blue-400)) 45%, white);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
733
|
@utility section-label {
|
|
750
734
|
@apply text-[0.6875rem] font-medium normal-case tracking-normal mb-5 text-dimmed select-none;
|
|
751
735
|
}
|
|
@@ -1115,7 +1099,7 @@
|
|
|
1115
1099
|
}
|
|
1116
1100
|
|
|
1117
1101
|
.sidebar-icon-action-active {
|
|
1118
|
-
color:
|
|
1102
|
+
color: var(--ui-app-accent-text);
|
|
1119
1103
|
background: var(--ui-selected);
|
|
1120
1104
|
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 20%, transparent);
|
|
1121
1105
|
}
|
|
@@ -10,11 +10,28 @@
|
|
|
10
10
|
* A schedule tick is an event. A button press is an event. An inbound message
|
|
11
11
|
* is an event. Nothing needs its own registration path.
|
|
12
12
|
*/
|
|
13
|
+
import { Buffer } from "node:buffer";
|
|
13
14
|
import { type SQL, sql } from "bun";
|
|
14
15
|
import type { WorkflowJsonValue } from "../contracts";
|
|
15
16
|
import { createWorkflowRun } from "./runs";
|
|
16
17
|
import { withTransaction } from "./transaction";
|
|
17
18
|
|
|
19
|
+
/** UTF-8 bytes of serialized JSON, independently for data and context. */
|
|
20
|
+
export const WORKFLOW_EVENT_MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
|
|
21
|
+
|
|
22
|
+
export class WorkflowEventTooLargeError extends Error {
|
|
23
|
+
readonly code = "WORKFLOW_EVENT_TOO_LARGE";
|
|
24
|
+
readonly limitBytes = WORKFLOW_EVENT_MAX_PAYLOAD_BYTES;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
readonly field: "data" | "context",
|
|
28
|
+
readonly bytes: number,
|
|
29
|
+
) {
|
|
30
|
+
super(`Workflow event ${field} is ${bytes} bytes; the limit is ${WORKFLOW_EVENT_MAX_PAYLOAD_BYTES} bytes (16 MiB).`);
|
|
31
|
+
this.name = "WorkflowEventTooLargeError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
export type WorkflowEventInput = {
|
|
19
36
|
appId: string;
|
|
20
37
|
scopeId: string;
|
|
@@ -173,6 +190,13 @@ export const emitWorkflowEvent = async (
|
|
|
173
190
|
const context = event.context ?? {};
|
|
174
191
|
const authorization = event.authorization ?? {};
|
|
175
192
|
|
|
193
|
+
// Validate before even opening a savepoint: callers may catch this inside
|
|
194
|
+
// their own transaction and continue writing domain state.
|
|
195
|
+
for (const field of ["data", "context"] as const) {
|
|
196
|
+
const bytes = Buffer.byteLength(JSON.stringify(field === "data" ? data : context), "utf8");
|
|
197
|
+
if (bytes > WORKFLOW_EVENT_MAX_PAYLOAD_BYTES) throw new WorkflowEventTooLargeError(field, bytes);
|
|
198
|
+
}
|
|
199
|
+
|
|
176
200
|
return withTransaction(options.db, async (tx) => {
|
|
177
201
|
const inserted = await tx<{ id: string }[]>`
|
|
178
202
|
INSERT INTO workflows.event (
|
|
@@ -45,8 +45,10 @@ export {
|
|
|
45
45
|
emitWorkflowEvent,
|
|
46
46
|
listUndispatchedWorkflowEvents,
|
|
47
47
|
type UndispatchedWorkflowEvent,
|
|
48
|
+
WORKFLOW_EVENT_MAX_PAYLOAD_BYTES,
|
|
48
49
|
type WorkflowEmission,
|
|
49
50
|
type WorkflowEventInput,
|
|
51
|
+
WorkflowEventTooLargeError,
|
|
50
52
|
} from "./events";
|
|
51
53
|
export {
|
|
52
54
|
getWorkflowRun,
|
|
@@ -66,6 +68,12 @@ export {
|
|
|
66
68
|
type WorkflowStepSummary,
|
|
67
69
|
workflowHealth,
|
|
68
70
|
} from "./observability";
|
|
71
|
+
export {
|
|
72
|
+
expireWorkflowEventPayloads,
|
|
73
|
+
expireWorkflowEventPayloadsBatch,
|
|
74
|
+
startWorkflowEventRetention,
|
|
75
|
+
WORKFLOW_EVENT_RETENTION_DAYS,
|
|
76
|
+
} from "./retention";
|
|
69
77
|
export {
|
|
70
78
|
beginWorkflowEffect,
|
|
71
79
|
claimWorkflowRun,
|
|
@@ -387,6 +387,7 @@ export type WorkflowRunDetail = WorkflowRunSummary & {
|
|
|
387
387
|
source: string;
|
|
388
388
|
eventId: string | null;
|
|
389
389
|
eventData: Record<string, WorkflowJsonValue> | null;
|
|
390
|
+
eventPayloadExpired: boolean;
|
|
390
391
|
steps: WorkflowStepSummary[];
|
|
391
392
|
children: Record<WorkflowRunState, number>;
|
|
392
393
|
};
|
|
@@ -412,10 +413,13 @@ export const getWorkflowRun = async (runId: string, options: { db?: SQL } = {}):
|
|
|
412
413
|
source: string;
|
|
413
414
|
event_id: string | null;
|
|
414
415
|
event_data: Record<string, WorkflowJsonValue> | null;
|
|
416
|
+
event_payload_expired: boolean;
|
|
415
417
|
})[]
|
|
416
418
|
>`
|
|
417
419
|
SELECT ${db.unsafe(RUN_SELECT)},
|
|
418
|
-
r.inputs, r.result, r.effects_used, v.effect_budget, v.source, r.event_id,
|
|
420
|
+
r.inputs, r.result, r.effects_used, v.effect_budget, v.source, r.event_id,
|
|
421
|
+
CASE WHEN e.payload_expired_at IS NULL THEN e.data END AS event_data,
|
|
422
|
+
(e.payload_expired_at IS NOT NULL) AS event_payload_expired
|
|
419
423
|
${db.unsafe(RUN_FROM)}
|
|
420
424
|
WHERE r.id = ${runId}::uuid
|
|
421
425
|
`;
|
|
@@ -438,6 +442,7 @@ export const getWorkflowRun = async (runId: string, options: { db?: SQL } = {}):
|
|
|
438
442
|
source: row.source,
|
|
439
443
|
eventId: row.event_id,
|
|
440
444
|
eventData: row.event_data,
|
|
445
|
+
eventPayloadExpired: row.event_payload_expired,
|
|
441
446
|
children,
|
|
442
447
|
steps,
|
|
443
448
|
};
|