@k2b/cloud 0.28.0 → 0.29.1
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 +9 -6
- package/src/_internal/cli-plugins.ts +18 -0
- package/src/_internal/define-app.ts +21 -5
- package/src/_internal/registry-validation.ts +30 -1
- package/src/_internal/runtime-context.ts +1 -0
- package/src/access/PermissionEditor.tsx +11 -4
- package/src/ai/assistant-models.ts +25 -4
- package/src/ai/audio-tool.ts +3 -10
- package/src/ai/capabilities.ts +35 -9
- package/src/ai/chat/blocks.tsx +259 -197
- package/src/ai/chat/builtin-tools.tsx +3 -1
- package/src/ai/chat/composer-adapter.ts +8 -0
- package/src/ai/chat/live-turn.browser-harness.tsx +16 -3
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +28 -6
- package/src/ai/chat/messages.ts +20 -24
- package/src/ai/chat/presentation.tsx +1 -8
- package/src/ai/chat/tool-disclosure.tsx +21 -14
- package/src/ai/chat/tool-groups.ts +64 -37
- package/src/ai/chat/tool-names.ts +157 -0
- package/src/ai/chat/turn-layout.ts +14 -2
- package/src/ai/chat/turn-view.tsx +132 -70
- package/src/ai/chat-tasks.ts +16 -3
- package/src/ai/code-mode-skill.ts +4 -4
- package/src/ai/compaction.ts +22 -3
- package/src/ai/default-tools.ts +9 -2
- package/src/ai/executor.ts +127 -40
- package/src/ai/file-media-type.ts +4 -0
- package/src/ai/files-store.ts +17 -10
- package/src/ai/http.ts +11 -10
- package/src/ai/index.ts +1 -0
- package/src/ai/memories.ts +2 -0
- package/src/ai/memory-learning.ts +59 -5
- package/src/ai/memory-workflow-evidence.ts +35 -10
- package/src/ai/projects-routes.ts +5 -1
- package/src/ai/projects.ts +5 -1
- package/src/ai/protocol.ts +43 -5
- package/src/ai/provider.ts +16 -1
- package/src/ai/quota-provider.ts +4 -1
- package/src/ai/routes.ts +20 -10
- package/src/ai/settings.ts +54 -17
- package/src/ai/skill-seeds.ts +11 -3
- package/src/ai/store.ts +76 -33
- package/src/ai/structured.ts +2 -1
- package/src/ai/system-prompt.ts +5 -1
- package/src/ai/transcription.ts +58 -13
- package/src/ai/turn-failure.ts +5 -1
- package/src/ai/turn-guidance.ts +505 -0
- package/src/ai/types.ts +3 -1
- package/src/ai/ui.tsx +1 -0
- package/src/api/admin-core-settings.ts +16 -1
- package/src/api/admin-outgoing-mail.ts +18 -0
- package/src/api/me.ts +42 -1
- package/src/browser/FileChooser.tsx +71 -295
- package/src/browser/FileSaver.tsx +407 -0
- package/src/browser/app-badges.ts +16 -0
- package/src/browser/choose-files.browser-harness.tsx +22 -6
- package/src/browser/choose-files.tsx +23 -36
- package/src/browser/file-chooser-messages.ts +64 -0
- package/src/browser/file-providers.ts +258 -8
- package/src/browser/files.tsx +26 -3
- package/src/browser/notifications.ts +37 -1
- package/src/browser/provider-folder.tsx +301 -0
- package/src/browser/provider-list.ts +39 -0
- package/src/browser/save-files.browser-harness.tsx +35 -0
- package/src/browser/save-files.tsx +107 -0
- package/src/cli/admin/outgoing-mail.ts +30 -2
- package/src/contracts/app.ts +10 -0
- package/src/contracts/commands.ts +3 -1
- package/src/contracts/notification-group.ts +6 -0
- package/src/contracts/notification-types.ts +6 -0
- package/src/contracts/outgoing-mail.ts +56 -11
- package/src/contracts/registry.ts +1 -0
- package/src/contracts/user-notifications.ts +74 -0
- package/src/server/middleware/request-logger.ts +11 -2
- package/src/services/accounts/avatar.ts +5 -2
- package/src/services/image-metadata.ts +438 -0
- package/src/services/index.ts +1 -0
- package/src/services/logging/redaction.ts +50 -0
- package/src/services/mandates/index.ts +4 -2
- package/src/services/mandates/policy.ts +20 -0
- package/src/services/notifications/browser.ts +48 -15
- package/src/services/notifications/dispatcher.ts +31 -1
- package/src/services/notifications/platform.ts +32 -4
- package/src/services/notifications/preview.ts +23 -0
- package/src/services/notifications/quiet.ts +153 -0
- package/src/services/notifications/user.ts +6 -0
- package/src/services/outbox.ts +24 -9
- package/src/services/outgoing-mail/index.ts +33 -5
- package/src/services/outgoing-mail/messages.ts +60 -19
- package/src/services/outgoing-mail/store.ts +63 -4
- package/src/services/pdf/template-preview.ts +2 -1
- package/src/services/settings/app.ts +3 -0
- package/src/services/settings/store.ts +4 -1
- package/src/services/weather/geo.ts +6 -2
- package/src/shared/ai-platform-prompt.ts +2 -2
- package/src/shared/network-address.ts +1 -0
- package/src/shared/redirect.ts +2 -1
- package/src/shared/template-rendering.ts +120 -7
- package/src/ssr/AppBadge.tsx +30 -0
- package/src/ssr/AppLaunchpadPanel.tsx +47 -26
- package/src/ssr/IslandError.tsx +33 -0
- package/src/ssr/Layout.tsx +3 -0
- package/src/ssr/LayoutHelp.tsx +4 -2
- package/src/ssr/RailApps.island.tsx +9 -2
- package/src/ssr/app-badges.ts +93 -0
- package/src/ssr/rail-messages.ts +4 -0
- package/src/ssr/rail-navigation.ts +2 -0
- package/src/styles/effects.css +165 -0
- package/src/styles/file-chooser.css +25 -0
- package/src/styles/utilities-markdown-table.css +5 -1
- package/src/styles/utilities-navigation.css +41 -0
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import { ScrollArea, Tooltip, useLocale } from "@k2b/ui";
|
|
2
2
|
import { createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js";
|
|
3
3
|
import type { RailSnapshot } from "../contracts/rail-preferences";
|
|
4
|
+
import { AppBadge, useAppBadgeDescription } from "./AppBadge";
|
|
4
5
|
import { appAccentStyle } from "./app-appearance";
|
|
6
|
+
import { watchAppBadges } from "./app-badges";
|
|
5
7
|
import { RAIL_PREFERENCES_EVENT, readRailContext } from "./rail-context";
|
|
6
8
|
import { projectRailNavigation, type RailApp, type RailLink, railLinkActive } from "./rail-navigation";
|
|
7
9
|
|
|
8
10
|
function RailIcon(props: { link: RailLink; currentPath: string }) {
|
|
9
11
|
let target: HTMLAnchorElement | undefined;
|
|
10
12
|
const active = () => railLinkActive(props.link, props.currentPath);
|
|
13
|
+
const badge = useAppBadgeDescription(() => props.link.badge);
|
|
11
14
|
return (
|
|
12
15
|
<>
|
|
13
16
|
<a
|
|
14
17
|
ref={target}
|
|
15
18
|
href={props.link.href}
|
|
16
|
-
class={`rail-item shrink-0 ${active() ? "rail-item-active" : ""}`}
|
|
17
|
-
aria-label={props.link.label}
|
|
19
|
+
class={`rail-item relative shrink-0 ${active() ? "rail-item-active" : ""}`}
|
|
20
|
+
aria-label={badge() ? `${props.link.label}, ${badge()}` : props.link.label}
|
|
18
21
|
aria-current={active() ? "page" : undefined}
|
|
19
22
|
style={appAccentStyle(props.link.accent)}
|
|
20
23
|
>
|
|
21
24
|
<i class={`${props.link.iconClass} text-base`} aria-hidden="true" />
|
|
22
25
|
<span class="sr-only">{props.link.label}</span>
|
|
26
|
+
<AppBadge endpoint={props.link.badge} />
|
|
23
27
|
</a>
|
|
24
28
|
<Tooltip target={() => target} content={props.link.label} placement="right" delay={0} />
|
|
25
29
|
</>
|
|
@@ -36,10 +40,13 @@ export default function RailApps(props: { apps: RailApp[]; settings: RailSnapsho
|
|
|
36
40
|
const syncLocation = () => setCurrentPath(`${window.location.pathname}${window.location.search}${window.location.hash}`);
|
|
37
41
|
sync();
|
|
38
42
|
syncLocation();
|
|
43
|
+
// Every visible app, not only the rail's: the app grid shows the same counts.
|
|
44
|
+
const stopBadges = watchAppBadges(props.apps.flatMap((app) => (app.badge ? [app.badge] : [])));
|
|
39
45
|
window.addEventListener(RAIL_PREFERENCES_EVENT, sync);
|
|
40
46
|
window.addEventListener("popstate", syncLocation);
|
|
41
47
|
window.addEventListener("hashchange", syncLocation);
|
|
42
48
|
onCleanup(() => {
|
|
49
|
+
stopBadges();
|
|
43
50
|
window.removeEventListener(RAIL_PREFERENCES_EVENT, sync);
|
|
44
51
|
window.removeEventListener("popstate", syncLocation);
|
|
45
52
|
window.removeEventListener("hashchange", syncLocation);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createSignal } from "solid-js";
|
|
2
|
+
import { APP_BADGES_REFRESH_EVENT } from "../browser/app-badges";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shortest time between two scheduled reads in one tab, and the longest one
|
|
6
|
+
* read may take. Badges are a hint to open an app, not a live view, so one
|
|
7
|
+
* read per minute keeps them current while someone works elsewhere without a
|
|
8
|
+
* steady stream of requests per tab.
|
|
9
|
+
*/
|
|
10
|
+
export const APP_BADGE_INTERVAL_MS = 60_000;
|
|
11
|
+
|
|
12
|
+
// Keyed by endpoint, so a pinned app shortcut and the app's own entry share one
|
|
13
|
+
// count. Only the browser ever writes it; server renders always see no badge.
|
|
14
|
+
const [counts, setCounts] = createSignal<ReadonlyMap<string, number>>(new Map());
|
|
15
|
+
|
|
16
|
+
/** The count for a declared `nav.badge` endpoint; 0 until the browser has read it. */
|
|
17
|
+
export const appBadgeCount = (endpoint: string | undefined): number => (endpoint ? (counts().get(endpoint) ?? 0) : 0);
|
|
18
|
+
|
|
19
|
+
/** A positive safe integer from an `AppNavBadge` body, else 0. */
|
|
20
|
+
export const parseAppBadge = (body: unknown): number => {
|
|
21
|
+
if (typeof body !== "object" || body === null || !("count" in body)) return 0;
|
|
22
|
+
const count = body.count;
|
|
23
|
+
return typeof count === "number" && Number.isSafeInteger(count) && count > 0 ? count : 0;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const readBadge = async (endpoint: string, signal: AbortSignal): Promise<number> => {
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(endpoint, { credentials: "same-origin", headers: { accept: "application/json" }, signal });
|
|
29
|
+
return response.status === 200 ? parseAppBadge(await response.json()) : 0;
|
|
30
|
+
} catch {
|
|
31
|
+
// An unreachable or failing app shows no badge rather than a stale one.
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let watching = false;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Reads the badge endpoints after hydration, every interval while the tab is
|
|
40
|
+
* visible, when a hidden tab becomes visible again after the interval, and on
|
|
41
|
+
* `refreshAppBadges()`. A hidden tab sends nothing and keeps its schedule.
|
|
42
|
+
* Each endpoint has at most one read in flight, and its count shows as soon as
|
|
43
|
+
* that read settles, so a slow app delays only its own badge. A refresh while
|
|
44
|
+
* a read runs queues one more read after it instead of another request, and a
|
|
45
|
+
* read still running after the interval gives up. Returns a disposer.
|
|
46
|
+
*/
|
|
47
|
+
export const watchAppBadges = (endpoints: readonly string[]): (() => void) => {
|
|
48
|
+
const unique = [...new Set(endpoints)];
|
|
49
|
+
if (typeof window === "undefined" || unique.length === 0 || watching) return () => {};
|
|
50
|
+
watching = true;
|
|
51
|
+
const stopped = new AbortController();
|
|
52
|
+
// Endpoints with a read in flight, mapped to whether one more read waits for it.
|
|
53
|
+
const running = new Map<string, boolean>();
|
|
54
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
55
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
56
|
+
|
|
57
|
+
const read = (endpoint: string) => {
|
|
58
|
+
if (running.has(endpoint)) {
|
|
59
|
+
running.set(endpoint, true);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
running.set(endpoint, false);
|
|
63
|
+
void readBadge(endpoint, AbortSignal.any([stopped.signal, AbortSignal.timeout(APP_BADGE_INTERVAL_MS)])).then((count) => {
|
|
64
|
+
if (stopped.signal.aborted) return;
|
|
65
|
+
setCounts((previous) => new Map(previous).set(endpoint, count));
|
|
66
|
+
const queued = running.get(endpoint);
|
|
67
|
+
running.delete(endpoint);
|
|
68
|
+
if (queued && document.visibilityState !== "hidden") read(endpoint);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
const refresh = () => {
|
|
72
|
+
// Leave the pending read in place, so the tab still reads on time once it is visible again.
|
|
73
|
+
if (document.visibilityState === "hidden") return;
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
last = Date.now();
|
|
76
|
+
timer = setTimeout(refresh, APP_BADGE_INTERVAL_MS);
|
|
77
|
+
for (const endpoint of unique) read(endpoint);
|
|
78
|
+
};
|
|
79
|
+
const resume = () => {
|
|
80
|
+
if (document.visibilityState !== "hidden" && Date.now() - last >= APP_BADGE_INTERVAL_MS) refresh();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
document.addEventListener("visibilitychange", resume);
|
|
84
|
+
window.addEventListener(APP_BADGES_REFRESH_EVENT, refresh);
|
|
85
|
+
refresh();
|
|
86
|
+
return () => {
|
|
87
|
+
watching = false;
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
stopped.abort();
|
|
90
|
+
document.removeEventListener("visibilitychange", resume);
|
|
91
|
+
window.removeEventListener(APP_BADGES_REFRESH_EVENT, refresh);
|
|
92
|
+
};
|
|
93
|
+
};
|
package/src/ssr/rail-messages.ts
CHANGED
|
@@ -32,6 +32,8 @@ export const railMessages = i18n.define({
|
|
|
32
32
|
saveFailed: "Could not save app bar settings. Your edits are still here.",
|
|
33
33
|
conflict: "The app bar changed in another editor. Cancel and reopen this editor to load the latest settings.",
|
|
34
34
|
invalid: "Check shortcut titles, links and icons. Each app can be pinned once; settings must fit within 16 KiB.",
|
|
35
|
+
badge: ({ count }: { count: number }) => `${count} new`,
|
|
36
|
+
badgeMany: "more than 99 new",
|
|
35
37
|
},
|
|
36
38
|
de: {
|
|
37
39
|
managed: "Von der Administration bereitgestellt",
|
|
@@ -63,6 +65,8 @@ export const railMessages = i18n.define({
|
|
|
63
65
|
conflict:
|
|
64
66
|
"Die App-Leiste wurde in einem anderen Editor geändert. Brich ab und öffne den Editor erneut, um den aktuellen Stand zu laden.",
|
|
65
67
|
invalid: "Prüfe Titel, Links und Icons. Jede App kann einmal angeheftet werden; die Einstellungen dürfen höchstens 16 KiB belegen.",
|
|
68
|
+
badge: ({ count }) => `${count} neu`,
|
|
69
|
+
badgeMany: "mehr als 99 neu",
|
|
66
70
|
},
|
|
67
71
|
},
|
|
68
72
|
});
|
package/src/styles/effects.css
CHANGED
|
@@ -96,6 +96,148 @@
|
|
|
96
96
|
outline-offset: var(--k2b-focus-offset);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/* A pending approval: a calm tinted card in place 4 that says what will happen. Fields stack on phones and the
|
|
100
|
+
decision buttons share the width there. */
|
|
101
|
+
.k2b-ui .ai-approval {
|
|
102
|
+
display: grid;
|
|
103
|
+
min-width: 0;
|
|
104
|
+
gap: 0.75rem;
|
|
105
|
+
padding: 1rem 1.125rem;
|
|
106
|
+
/* Transparent, so forced colours still draw an outline where the tint disappears. */
|
|
107
|
+
border: 1px solid transparent;
|
|
108
|
+
border-radius: calc(var(--k2b-radius-surface) + 0.25rem);
|
|
109
|
+
color: var(--k2b-text-secondary);
|
|
110
|
+
background: color-mix(in srgb, var(--k2b-warning-surface) 70%, var(--k2b-surface-canvas));
|
|
111
|
+
font-size: 0.8125rem;
|
|
112
|
+
line-height: 1.5;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.k2b-ui.k2b-dark .ai-approval,
|
|
116
|
+
.dark .k2b-ui .ai-approval {
|
|
117
|
+
background: color-mix(in srgb, var(--k2b-warning-surface) 55%, var(--k2b-surface-canvas));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.k2b-ui .ai-approval__head {
|
|
121
|
+
display: flex;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
align-items: center;
|
|
124
|
+
gap: 0.75rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.k2b-ui .ai-approval__icon {
|
|
128
|
+
flex: none;
|
|
129
|
+
color: var(--k2b-warning-text);
|
|
130
|
+
font-size: 1.125rem;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* The title wraps, so it reads in full where the decision button has to shorten it. */
|
|
134
|
+
.k2b-ui .ai-approval__title {
|
|
135
|
+
margin: 0;
|
|
136
|
+
color: var(--k2b-text);
|
|
137
|
+
font-size: 0.875rem;
|
|
138
|
+
font-weight: 600;
|
|
139
|
+
overflow-wrap: anywhere;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.k2b-ui .ai-approval__sub,
|
|
143
|
+
.k2b-ui .ai-approval__state {
|
|
144
|
+
margin: 0;
|
|
145
|
+
color: var(--k2b-text-muted);
|
|
146
|
+
font-size: 0.75rem;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.k2b-ui .ai-approval__state {
|
|
150
|
+
display: inline-flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: 0.25rem;
|
|
153
|
+
font-weight: 500;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.k2b-ui .ai-approval__text {
|
|
157
|
+
min-width: 0;
|
|
158
|
+
color: var(--k2b-text-secondary);
|
|
159
|
+
font-size: 0.75rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.k2b-ui .ai-approval__label,
|
|
163
|
+
.k2b-ui .ai-approval__fields dt {
|
|
164
|
+
color: var(--k2b-text);
|
|
165
|
+
font-size: 0.75rem;
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.k2b-ui .ai-approval__fields {
|
|
170
|
+
display: grid;
|
|
171
|
+
margin: 0;
|
|
172
|
+
gap: 0.3rem 1rem;
|
|
173
|
+
grid-template-columns: max-content minmax(0, 1fr);
|
|
174
|
+
font-size: 0.75rem;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.k2b-ui .ai-approval__fields dd {
|
|
178
|
+
min-width: 0;
|
|
179
|
+
margin: 0;
|
|
180
|
+
color: var(--k2b-text-secondary);
|
|
181
|
+
overflow-wrap: anywhere;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.k2b-ui .ai-approval__preview {
|
|
185
|
+
max-height: 12rem;
|
|
186
|
+
margin: 0;
|
|
187
|
+
overflow: auto;
|
|
188
|
+
color: var(--k2b-text-secondary);
|
|
189
|
+
font-size: 0.75rem;
|
|
190
|
+
line-height: 1.55;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.k2b-ui .ai-approval__foot {
|
|
194
|
+
display: flex;
|
|
195
|
+
min-width: 0;
|
|
196
|
+
flex-wrap: wrap;
|
|
197
|
+
align-items: center;
|
|
198
|
+
gap: 0.5rem;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.k2b-ui .ai-approval__actions {
|
|
202
|
+
display: flex;
|
|
203
|
+
min-width: 0;
|
|
204
|
+
align-items: center;
|
|
205
|
+
gap: 0.375rem;
|
|
206
|
+
margin-left: auto;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@media (max-width: 40rem) {
|
|
210
|
+
.k2b-ui .ai-approval__fields {
|
|
211
|
+
grid-template-columns: minmax(0, 1fr);
|
|
212
|
+
gap: 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.k2b-ui .ai-approval__fields dd {
|
|
216
|
+
margin-bottom: 0.35rem;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.k2b-ui .ai-approval__actions {
|
|
220
|
+
width: 100%;
|
|
221
|
+
margin-left: 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* The buttons share the width and may shrink below their labels: a long action title ends in an ellipsis instead
|
|
225
|
+
of pushing the menu out of the card. The title above shows it in full. */
|
|
226
|
+
.k2b-ui .ai-approval__actions > *,
|
|
227
|
+
.k2b-ui .ai-approval__actions .k2b-split-button__primary {
|
|
228
|
+
min-width: 0;
|
|
229
|
+
flex: 1;
|
|
230
|
+
justify-content: center;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.k2b-ui .ai-approval__actions .k2b-split-button__primary > .k2b-button__label {
|
|
234
|
+
display: block;
|
|
235
|
+
min-width: 0;
|
|
236
|
+
overflow: hidden;
|
|
237
|
+
text-overflow: ellipsis;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
99
241
|
/* Result links sit inside an already padded activity row. Keep them inline so
|
|
100
242
|
they do not make linked capability results taller than neighboring rows. */
|
|
101
243
|
.k2b-ui .k2b-button.ai-chat-result-link[data-size="xs"] {
|
|
@@ -892,3 +1034,26 @@ dialog:has(.launchpad-panel)::backdrop {
|
|
|
892
1034
|
.k2b-ui .ai-response-info__rows dd { min-width: 0; margin: 0; overflow: hidden; color: var(--k2b-text); text-align: end; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
893
1035
|
.k2b-ui .ai-response-info__tools { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; margin: 0; padding: 0; list-style: none; font-size: 0.8125rem; color: var(--k2b-text-secondary); }
|
|
894
1036
|
.k2b-ui .ai-response-info__tools > li { display: inline-flex; align-items: center; gap: 0.375rem; }
|
|
1037
|
+
|
|
1038
|
+
/* The image a step looked at: a small inset thumbnail whose box is fixed before the image loads, so nothing moves. */
|
|
1039
|
+
.k2b-ui .k2b-chat-activity .ai-step-image {
|
|
1040
|
+
display: flex;
|
|
1041
|
+
width: 12rem;
|
|
1042
|
+
max-width: 100%;
|
|
1043
|
+
height: 8rem;
|
|
1044
|
+
flex-shrink: 0;
|
|
1045
|
+
align-items: center;
|
|
1046
|
+
justify-content: center;
|
|
1047
|
+
overflow: hidden;
|
|
1048
|
+
padding: 0;
|
|
1049
|
+
border-radius: var(--k2b-radius-control);
|
|
1050
|
+
background: var(--ui-surface-muted);
|
|
1051
|
+
box-shadow: var(--ui-control-recess);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.k2b-ui .k2b-chat-activity .ai-step-image img {
|
|
1055
|
+
width: 100%;
|
|
1056
|
+
height: 100%;
|
|
1057
|
+
object-fit: cover;
|
|
1058
|
+
object-position: top;
|
|
1059
|
+
}
|
|
@@ -167,3 +167,28 @@
|
|
|
167
167
|
gap: 0.5rem;
|
|
168
168
|
margin-left: auto;
|
|
169
169
|
}
|
|
170
|
+
|
|
171
|
+
/* A saved file's progress, or the field for another name when its name is taken. Every row keeps the field's
|
|
172
|
+
height, so a conflict never moves the rows below. */
|
|
173
|
+
.cloud-file-saver__slot {
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
min-height: 2.25rem;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.cloud-file-saver__slot > .k2b-progress {
|
|
180
|
+
flex: 1 1 auto;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.cloud-file-saver__rename {
|
|
184
|
+
display: flex;
|
|
185
|
+
flex: 1 1 auto;
|
|
186
|
+
align-items: center;
|
|
187
|
+
gap: 0.5rem;
|
|
188
|
+
min-width: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.cloud-file-saver__rename > :first-child {
|
|
192
|
+
flex: 1 1 auto;
|
|
193
|
+
min-width: 0;
|
|
194
|
+
}
|
|
@@ -84,7 +84,11 @@
|
|
|
84
84
|
font-size: inherit;
|
|
85
85
|
line-height: inherit;
|
|
86
86
|
color: inherit;
|
|
87
|
-
|
|
87
|
+
/* break-word, not anywhere: anywhere lowers a column's min-content width to
|
|
88
|
+
one character, so auto table layout squeezes short columns until every
|
|
89
|
+
word breaks. break-word still wraps an overlong word, but only when it
|
|
90
|
+
would overflow. */
|
|
91
|
+
overflow-wrap: break-word;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
.dark .md-table-cell {
|
|
@@ -771,6 +771,47 @@
|
|
|
771
771
|
box-shadow: none;
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
+
/* An app's count on its icon in the rail and the app grid. It is laid over the
|
|
775
|
+
icon's corner and never takes layout space, so a count appearing, growing to
|
|
776
|
+
"99+", or clearing moves nothing. The fill is the solid action colour, the
|
|
777
|
+
same as the sidebar's mention count. */
|
|
778
|
+
.cloud-app-badge {
|
|
779
|
+
position: absolute;
|
|
780
|
+
top: 0;
|
|
781
|
+
right: -0.25rem;
|
|
782
|
+
box-sizing: border-box;
|
|
783
|
+
min-width: 0.875rem;
|
|
784
|
+
height: 0.875rem;
|
|
785
|
+
padding-inline: 0.1875rem;
|
|
786
|
+
border-radius: 999px;
|
|
787
|
+
color: #ffffff;
|
|
788
|
+
background: var(--k2b-action-solid);
|
|
789
|
+
font-size: 0.625rem;
|
|
790
|
+
font-variant-numeric: tabular-nums;
|
|
791
|
+
font-weight: 600;
|
|
792
|
+
line-height: 0.875rem;
|
|
793
|
+
text-align: center;
|
|
794
|
+
white-space: nowrap;
|
|
795
|
+
pointer-events: none;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.app-icon > .cloud-app-badge {
|
|
799
|
+
top: -0.375rem;
|
|
800
|
+
right: -0.375rem;
|
|
801
|
+
min-width: 1.25rem;
|
|
802
|
+
height: 1.25rem;
|
|
803
|
+
padding-inline: 0.3125rem;
|
|
804
|
+
font-size: 0.6875rem;
|
|
805
|
+
line-height: 1.25rem;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
@media (forced-colors: active) {
|
|
809
|
+
.cloud-app-badge {
|
|
810
|
+
outline: 1px solid CanvasText;
|
|
811
|
+
outline-offset: -1px;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
774
815
|
.rail-item-active:hover {
|
|
775
816
|
color: color-mix(in srgb, var(--app-accent, var(--color-blue-500)) 88%, black);
|
|
776
817
|
background: transparent;
|