@k2b/cloud 0.31.0 → 0.33.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 -6
- package/src/_internal/define-app.ts +20 -9
- package/src/_internal/page-responses.ts +5 -1
- package/src/ai/app-skills.ts +1 -7
- package/src/ai/audio-tool.ts +2 -1
- package/src/ai/browser.ts +109 -0
- package/src/ai/capabilities.ts +6 -2
- package/src/ai/chart-block.ts +431 -81
- package/src/ai/chat/action-sentences.ts +13 -7
- package/src/ai/chat/blocks.tsx +11 -7
- package/src/ai/chat/builtin-tools.tsx +9 -7
- package/src/ai/chat/chart-block.tsx +218 -44
- package/src/ai/chat/explorer-block.tsx +276 -0
- package/src/ai/chat/file-tools.tsx +40 -1
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/message-utils.ts +7 -0
- package/src/ai/chat/messages.ts +16 -0
- package/src/ai/chat/presentation.tsx +13 -3
- package/src/ai/chat/reference-text.tsx +34 -0
- package/src/ai/chat/tool-groups.ts +1 -0
- package/src/ai/chat/tool-names.ts +14 -0
- package/src/ai/chat/turn-view.tsx +17 -7
- package/src/ai/chat/view-block.browser-harness.tsx +35 -0
- package/src/ai/chat/view-block.tsx +417 -0
- package/src/ai/chat/visual-tools.tsx +32 -53
- package/src/ai/client-tool-results.ts +9 -0
- package/src/ai/code-source-contracts.ts +17 -2
- package/src/ai/code-source-tools.ts +1 -0
- package/src/ai/default-tool-contracts.ts +334 -91
- package/src/ai/default-tools.ts +89 -13
- package/src/ai/executor.ts +173 -31
- package/src/ai/file-error.ts +2 -0
- package/src/ai/file-tools.ts +27 -7
- package/src/ai/files-store.ts +60 -0
- package/src/ai/html-pdf-tool.ts +3 -2
- package/src/ai/http.ts +37 -12
- package/src/ai/index.ts +90 -0
- package/src/ai/local-tool-contracts.ts +244 -0
- package/src/ai/malformed-tool-call-repair.ts +144 -0
- package/src/ai/markdown-pdf-tool.ts +2 -1
- package/src/ai/memories.ts +48 -1
- package/src/ai/memory-learning.ts +17 -6
- package/src/ai/memory-tool.ts +1 -0
- package/src/ai/migrate.ts +15 -0
- package/src/ai/openai-api-messages.ts +53 -0
- package/src/ai/openai-api-slots.ts +40 -0
- package/src/ai/openai-api.ts +383 -0
- package/src/ai/protocol.ts +6 -2
- package/src/ai/provider.ts +17 -4
- package/src/ai/request-context.ts +33 -0
- package/src/ai/routes.ts +96 -57
- package/src/ai/runtime.ts +48 -12
- package/src/ai/settings.ts +5 -0
- package/src/ai/skill-catalog.ts +20 -5
- package/src/ai/skill-format.ts +17 -7
- package/src/ai/skill-tool.ts +15 -5
- package/src/ai/skills-routes.ts +20 -6
- package/src/ai/skills.ts +26 -7
- package/src/ai/store.ts +25 -0
- package/src/ai/system-prompt.ts +69 -10
- package/src/ai/timeline.ts +3 -1
- package/src/ai/tools.ts +3 -0
- package/src/ai/types.ts +9 -0
- package/src/ai/view-block.ts +941 -0
- package/src/ai/vision-tool.ts +2 -1
- package/src/api/admin-identity.ts +6 -1
- package/src/api/app-approval-actions.ts +36 -0
- package/src/api/app-approval.ts +10 -2
- package/src/api/approvals-loader.ts +15 -0
- package/src/api/approvals.ts +120 -0
- package/src/api/index.ts +3 -0
- package/src/api/me.ts +12 -3
- package/src/approvals/Approvals.browser-harness.tsx +16 -0
- package/src/approvals/Approvals.tsx +621 -0
- package/src/approvals/ApprovalsNavStatus.tsx +20 -0
- package/src/approvals/messages.ts +95 -0
- package/src/approvals/ui.ts +8 -0
- package/src/browser/app-approval.ts +32 -0
- package/src/browser/choose-files.tsx +26 -13
- package/src/browser/commands.ts +13 -4
- package/src/browser/file-provider-error.ts +11 -0
- package/src/browser/file-providers.ts +3 -12
- package/src/browser/files.tsx +1 -2
- package/src/browser/live-connection.ts +2 -3
- package/src/browser/navigation-search.ts +4 -1
- package/src/browser/provider-list.ts +2 -1
- package/src/browser/save-files.tsx +13 -2
- package/src/cli/account.ts +21 -2
- package/src/cli/commands.ts +4 -2
- package/src/contracts/app-approval.ts +120 -0
- package/src/contracts/approval-paths.ts +4 -0
- package/src/contracts/approvals.ts +81 -0
- package/src/contracts/command-path.ts +17 -0
- package/src/contracts/commands.ts +5 -15
- package/src/contracts/shared.ts +5 -0
- package/src/events/live-protocol.ts +2 -1
- package/src/events/live-server-message.ts +40 -0
- package/src/events/testing.ts +88 -0
- package/src/server/index.ts +2 -0
- package/src/server/middleware/auth.ts +11 -1
- package/src/server/request-timeout.ts +22 -0
- package/src/server/services/access.ts +4 -3
- package/src/services/app-approval.ts +103 -15
- package/src/services/approvals/cursor.ts +15 -0
- package/src/services/approvals/device.ts +112 -0
- package/src/services/approvals/index.ts +27 -11
- package/src/services/approvals/list.ts +120 -0
- package/src/services/approvals/migrate.ts +9 -1
- package/src/services/identity/models-only-auth.ts +25 -0
- package/src/services/notifications/email-frame.ts +16 -3
- package/src/services/outbox.ts +13 -6
- package/src/services/service-account-credentials.ts +22 -10
- package/src/services/settings/core-settings.ts +27 -0
- package/src/shared/ai-platform-prompt.ts +29 -3
- package/src/shared/email-html.ts +4 -0
- package/src/shared/index.ts +6 -1
- package/src/shared/markdown/extensions/code.ts +14 -0
- package/src/shared/markdown/index.ts +4 -4
- package/src/shared/theme.ts +41 -20
- package/src/ssr/AnnouncementBanners.tsx +30 -0
- package/src/ssr/AppBadge.tsx +4 -4
- package/src/ssr/AppLaunchpadPanel.tsx +6 -6
- package/src/ssr/CloudShell.island.tsx +91 -0
- package/src/ssr/GlobalSearchTrigger.tsx +51 -0
- package/src/ssr/Layout.tsx +56 -85
- package/src/ssr/LayoutHeader.tsx +31 -77
- package/src/ssr/LayoutHelp.tsx +2 -57
- package/src/ssr/LayoutHelpTrigger.tsx +51 -0
- package/src/ssr/{LayoutPreferences.island.tsx → LayoutPreferences.tsx} +2 -2
- package/src/ssr/LayoutRail.tsx +15 -69
- package/src/ssr/MinimalLayout.tsx +2 -3
- package/src/ssr/MinimalLayoutPreferences.island.tsx +12 -15
- package/src/ssr/MobileNavigation.tsx +13 -8
- package/src/ssr/MobileProfileActions.tsx +10 -7
- package/src/ssr/PageTrailRow.tsx +14 -0
- package/src/ssr/{ProfilePreferences.island.tsx → ProfilePreferences.tsx} +12 -7
- package/src/ssr/PwaLayout.tsx +0 -2
- package/src/ssr/PwaRuntime.island.tsx +1 -1
- package/src/ssr/{RailApps.island.tsx → RailApps.tsx} +2 -2
- package/src/ssr/ShellRuntime.island.tsx +170 -0
- package/src/ssr/TimezoneCookie.island.tsx +3 -19
- package/src/ssr/WorkspaceNavigationProvider.tsx +20 -1
- package/src/ssr/announcements-dialog.tsx +66 -0
- package/src/ssr/app-badges.ts +50 -6
- package/src/ssr/{AppLaunchpad.island.tsx → app-launchpad.tsx} +42 -62
- package/src/ssr/busy-action.ts +19 -0
- package/src/ssr/islands/index.ts +9 -6
- package/src/ssr/launchpad-surface.tsx +15 -0
- package/src/ssr/layout-help-registry.tsx +60 -0
- package/src/ssr/mobile-navigation.browser-harness.tsx +20 -0
- package/src/ssr/page-trail.browser-harness.tsx +52 -3
- package/src/ssr/preference-controller.ts +30 -21
- package/src/ssr/profile-actions.ts +26 -15
- package/src/ssr/profile-preferences-messages.ts +8 -4
- package/src/ssr/rail-context.ts +8 -12
- package/src/ssr/rail-navigation.ts +5 -3
- package/src/ssr/runtime.ts +6 -1
- package/src/ssr/shell-data.tsx +48 -0
- package/src/ssr/timezone-cookie.ts +22 -0
- package/src/ssr/workspace-navigation.ts +25 -2
- package/src/styles/approvals.css +390 -0
- package/src/styles/global.css +22 -7
- package/src/styles/utilities-navigation.css +13 -0
- package/src/workflows/language/compiler.ts +1 -1
- package/src/ssr/AppWorkspaceController.island.tsx +0 -21
- package/src/ssr/BrowserPushRegistration.island.tsx +0 -12
- package/src/ssr/GlobalAnnouncements.island.tsx +0 -117
- package/src/ssr/GlobalSearchTrigger.island.tsx +0 -75
- package/src/ssr/LayoutHelpTrigger.island.tsx +0 -61
- package/src/ssr/PageTrailRow.island.tsx +0 -20
- package/src/ssr/PageTrailRuntime.island.tsx +0 -26
- package/src/ssr/RegisteredHelpDocuments.island.tsx +0 -3
- /package/src/ssr/{LayoutBreadcrumbs.island.tsx → LayoutBreadcrumbs.tsx} +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createContext, type JSX, useContext } from "solid-js";
|
|
2
|
+
import type { AppSearchLink } from "../contracts/app";
|
|
3
|
+
import type { RailSnapshot } from "../contracts/rail-preferences";
|
|
4
|
+
import type { CloudThemePreference } from "../shared/theme";
|
|
5
|
+
import type { AppLaunchpadLegalLink } from "./app-launchpad";
|
|
6
|
+
import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
|
|
7
|
+
import type { RailApp } from "./rail-navigation";
|
|
8
|
+
|
|
9
|
+
export type ShellData = {
|
|
10
|
+
/** The authorized navigation catalog, sorted for the request locale: the rail, the app grid and the phone menu show it. */
|
|
11
|
+
apps: RailApp[];
|
|
12
|
+
/** Signed-in pages: the person's rail preferences. */
|
|
13
|
+
rail?: RailSnapshot;
|
|
14
|
+
legalLinks: AppLaunchpadLegalLink[];
|
|
15
|
+
/** Signed-in pages: the person, for the profile menu and the phone menu's profile actions. */
|
|
16
|
+
profile?: { name: string; avatarSrc?: string; appHref?: string };
|
|
17
|
+
theme: CloudThemePreference;
|
|
18
|
+
/** The current app's accent, for the Help dialog and the phone menu, which open outside the app canvas. */
|
|
19
|
+
accent?: string;
|
|
20
|
+
/** The search destinations of each app in `apps`, by app id in registry order; the search host expands them into rows. */
|
|
21
|
+
searchLinks: Record<string, readonly AppSearchLink[]>;
|
|
22
|
+
searchHelpApps: GlobalSearchHelpApp[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const ShellDataContext = createContext<() => ShellData>();
|
|
26
|
+
|
|
27
|
+
export function ShellDataProvider(props: { data: ShellData; children: JSX.Element }) {
|
|
28
|
+
return (
|
|
29
|
+
<ShellDataContext.Provider value={() => props.data}>
|
|
30
|
+
<script id="cloud-shell-data" type="application/json" innerHTML={JSON.stringify(props.data).replace(/</g, "\\u003c")} />
|
|
31
|
+
{props.children}
|
|
32
|
+
</ShellDataContext.Provider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function readShellData(): ShellData | undefined {
|
|
37
|
+
if (typeof document === "undefined") return undefined;
|
|
38
|
+
const text = document.getElementById("cloud-shell-data")?.textContent;
|
|
39
|
+
if (!text) return undefined;
|
|
40
|
+
// Framework-generated, escaped SSR data; never loaded from localStorage. The server validates the rail snapshot when
|
|
41
|
+
// it reads it, so the shell does not load the schemas on every page.
|
|
42
|
+
const data: ShellData = JSON.parse(text);
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useShellData(): ShellData | undefined {
|
|
47
|
+
return useContext(ShellDataContext)?.() ?? readShellData();
|
|
48
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cookies } from "@k2b/stdlib/browser";
|
|
2
|
+
import { TIMEZONE_COOKIE } from "../shared/time";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stores the browser's timezone for server-rendered times. With `reload`, a
|
|
6
|
+
* changed timezone reloads the page once, so it shows times in that zone.
|
|
7
|
+
*/
|
|
8
|
+
export const syncTimezoneCookie = (reload: boolean) => {
|
|
9
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
10
|
+
const current = cookies.readCookie(TIMEZONE_COOKIE);
|
|
11
|
+
if (!timeZone || current === timeZone) return;
|
|
12
|
+
cookies.writeCookie(TIMEZONE_COOKIE, timeZone);
|
|
13
|
+
if (!reload) return;
|
|
14
|
+
try {
|
|
15
|
+
if (sessionStorage.getItem("cloud.timezone.reload") !== timeZone) {
|
|
16
|
+
sessionStorage.setItem("cloud.timezone.reload", timeZone);
|
|
17
|
+
window.location.reload();
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// Cookie persistence still succeeds; the next navigation will render with the browser timezone.
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createNavigation, type NavigationController, type NavigationItem } from "@k2b/ui";
|
|
1
|
+
import { type AppWorkspaceSidebarSwitcherProps, createNavigation, type NavigationController, type NavigationItem } from "@k2b/ui";
|
|
2
2
|
import { createEffect, onCleanup, onMount } from "solid-js";
|
|
3
3
|
|
|
4
4
|
export type WorkspaceNavigationState = { navigation: NavigationController; label: string; owner: Element };
|
|
@@ -44,6 +44,28 @@ function validItems(value: unknown): value is NavigationItem[] {
|
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function readSwitcher(value: unknown): AppWorkspaceSidebarSwitcherProps | undefined {
|
|
48
|
+
if (
|
|
49
|
+
!value ||
|
|
50
|
+
typeof value !== "object" ||
|
|
51
|
+
!("currentId" in value) ||
|
|
52
|
+
typeof value.currentId !== "string" ||
|
|
53
|
+
!("label" in value) ||
|
|
54
|
+
typeof value.label !== "string" ||
|
|
55
|
+
!("icon" in value) ||
|
|
56
|
+
typeof value.icon !== "string" ||
|
|
57
|
+
!("switchLabel" in value) ||
|
|
58
|
+
typeof value.switchLabel !== "string"
|
|
59
|
+
)
|
|
60
|
+
return;
|
|
61
|
+
const description = "description" in value ? value.description : undefined;
|
|
62
|
+
const color = "color" in value ? value.color : undefined;
|
|
63
|
+
if (description !== undefined && typeof description !== "string") return;
|
|
64
|
+
if (color !== undefined && typeof color !== "string") return;
|
|
65
|
+
// The snapshot carries only the head; options and actions wait for the owning island.
|
|
66
|
+
return { currentId: value.currentId, label: value.label, icon: value.icon, switchLabel: value.switchLabel, description, color };
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
export function readWorkspaceNavigation(): WorkspaceNavigationState | undefined {
|
|
48
70
|
if (typeof window === "undefined") return;
|
|
49
71
|
const current = window.__cloudWorkspaceNavigation;
|
|
@@ -67,10 +89,11 @@ export function readWorkspaceNavigation(): WorkspaceNavigationState | undefined
|
|
|
67
89
|
...(item.inlineActions ? { inlineActions: item.inlineActions.map((action) => ({ ...action, disabled: true })) } : {}),
|
|
68
90
|
}));
|
|
69
91
|
const items = links(value.items);
|
|
92
|
+
const switcher = "switcher" in value ? readSwitcher(value.switcher) : undefined;
|
|
70
93
|
return {
|
|
71
94
|
owner,
|
|
72
95
|
label: "label" in value && typeof value.label === "string" ? value.label : "",
|
|
73
|
-
navigation: createNavigation({ items: () => items }),
|
|
96
|
+
navigation: createNavigation({ items: () => items, ...(switcher ? { switcher: () => switcher } : {}) }),
|
|
74
97
|
};
|
|
75
98
|
} catch {
|
|
76
99
|
return;
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/* Pending Action approvals (`@k2b/cloud/approvals/ui`). A flat list: rows are set apart by space, not by lines or
|
|
2
|
+
boxes. A decided row keeps its content and height and turns its decision into a receipt in the same slot. */
|
|
3
|
+
.k2b-ui .cloud-approvals {
|
|
4
|
+
display: grid;
|
|
5
|
+
min-width: 0;
|
|
6
|
+
gap: 1rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.k2b-ui .cloud-approvals__toolbar {
|
|
10
|
+
display: flex;
|
|
11
|
+
min-height: 1.75rem;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: space-between;
|
|
14
|
+
gap: 0.75rem;
|
|
15
|
+
color: var(--k2b-text-muted);
|
|
16
|
+
font-size: 0.75rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.k2b-ui .cloud-approvals__list {
|
|
20
|
+
display: grid;
|
|
21
|
+
min-width: 0;
|
|
22
|
+
gap: 2rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.k2b-ui .cloud-approvals__group {
|
|
26
|
+
display: grid;
|
|
27
|
+
min-width: 0;
|
|
28
|
+
gap: 1rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.k2b-ui .cloud-approvals__group + .cloud-approvals__group {
|
|
32
|
+
margin-top: 1rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.k2b-ui .cloud-approvals__group-title {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 0.5rem;
|
|
39
|
+
margin: 0;
|
|
40
|
+
color: var(--k2b-text);
|
|
41
|
+
font-size: 0.8125rem;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.k2b-ui .cloud-approvals__more {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-wrap: wrap;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
gap: 0.5rem 0.75rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.k2b-ui .cloud-approvals__more-error {
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 0.375rem;
|
|
57
|
+
margin: 0;
|
|
58
|
+
color: var(--k2b-danger-text);
|
|
59
|
+
font-size: 0.75rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.k2b-ui .cloud-approvals__elsewhere {
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: space-between;
|
|
67
|
+
gap: 0.5rem 1rem;
|
|
68
|
+
margin: 0.5rem 0 0;
|
|
69
|
+
color: var(--k2b-text-muted);
|
|
70
|
+
font-size: 0.75rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.k2b-ui .cloud-approvals__elsewhere a {
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 0.25rem;
|
|
77
|
+
color: var(--k2b-link-accent);
|
|
78
|
+
font-weight: 500;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.k2b-ui .cloud-approval {
|
|
82
|
+
display: grid;
|
|
83
|
+
min-width: 0;
|
|
84
|
+
gap: 0.875rem;
|
|
85
|
+
grid-template-columns: 2rem minmax(0, 1fr);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.k2b-ui .cloud-approval__icon {
|
|
89
|
+
display: grid;
|
|
90
|
+
width: 2rem;
|
|
91
|
+
height: 2rem;
|
|
92
|
+
place-items: center;
|
|
93
|
+
border-radius: var(--k2b-radius-control);
|
|
94
|
+
color: var(--k2b-text-secondary);
|
|
95
|
+
background: var(--k2b-surface-muted);
|
|
96
|
+
font-size: 1rem;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Only an approval is a success; a rejection or a decision that did not apply keeps the neutral icon tile. */
|
|
100
|
+
.k2b-ui .cloud-approval[data-decided="approved"] .cloud-approval__icon {
|
|
101
|
+
color: var(--k2b-success-text);
|
|
102
|
+
background: var(--k2b-success-surface);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.k2b-ui .cloud-approval__main {
|
|
106
|
+
display: grid;
|
|
107
|
+
min-width: 0;
|
|
108
|
+
gap: 0.75rem;
|
|
109
|
+
container-type: inline-size;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.k2b-ui .cloud-approval__content {
|
|
113
|
+
display: grid;
|
|
114
|
+
min-width: 0;
|
|
115
|
+
gap: 0.5rem;
|
|
116
|
+
transition: opacity 150ms ease-out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.k2b-ui .cloud-approval[data-decided] .cloud-approval__content {
|
|
120
|
+
opacity: 0.55;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.k2b-ui .cloud-approval__title,
|
|
124
|
+
.k2b-ui .cloud-approval__sheet-title {
|
|
125
|
+
margin: 0;
|
|
126
|
+
color: var(--k2b-text);
|
|
127
|
+
font-size: 0.875rem;
|
|
128
|
+
font-weight: 600;
|
|
129
|
+
line-height: 1.4;
|
|
130
|
+
overflow-wrap: anywhere;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.k2b-ui .cloud-approval__sheet-title {
|
|
134
|
+
font-size: 1.0625rem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.k2b-ui .cloud-approval__origin {
|
|
138
|
+
margin: -0.25rem 0 0;
|
|
139
|
+
color: var(--k2b-text-muted);
|
|
140
|
+
font-size: 0.75rem;
|
|
141
|
+
overflow-wrap: anywhere;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.k2b-ui .cloud-approval__origin a {
|
|
145
|
+
color: inherit;
|
|
146
|
+
text-decoration: underline;
|
|
147
|
+
text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
|
|
148
|
+
text-underline-offset: 0.15em;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.k2b-ui .cloud-approval__origin a:hover {
|
|
152
|
+
color: var(--k2b-text);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.k2b-ui .cloud-approval__text {
|
|
156
|
+
margin: 0;
|
|
157
|
+
color: var(--k2b-text-secondary);
|
|
158
|
+
font-size: 0.8125rem;
|
|
159
|
+
white-space: pre-wrap;
|
|
160
|
+
overflow-wrap: anywhere;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.k2b-ui .cloud-approval__fields {
|
|
164
|
+
display: grid;
|
|
165
|
+
margin: 0;
|
|
166
|
+
gap: 0.25rem 1rem;
|
|
167
|
+
grid-template-columns: max-content minmax(0, 1fr);
|
|
168
|
+
font-size: 0.75rem;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.k2b-ui .cloud-approval__fields dt,
|
|
172
|
+
.k2b-ui .cloud-approval__label {
|
|
173
|
+
margin: 0;
|
|
174
|
+
color: var(--k2b-text-muted);
|
|
175
|
+
font-size: 0.75rem;
|
|
176
|
+
font-weight: 500;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.k2b-ui .cloud-approval__fields dd {
|
|
180
|
+
min-width: 0;
|
|
181
|
+
margin: 0;
|
|
182
|
+
color: var(--k2b-text);
|
|
183
|
+
white-space: pre-wrap;
|
|
184
|
+
overflow-wrap: anywhere;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.k2b-ui .cloud-approval__block {
|
|
188
|
+
display: grid;
|
|
189
|
+
min-width: 0;
|
|
190
|
+
gap: 0.25rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Long content sits inset, so it reads as quoted from the app without a frame or a rule. */
|
|
194
|
+
.k2b-ui .cloud-approval__quote,
|
|
195
|
+
.k2b-ui .cloud-approval__markdown {
|
|
196
|
+
margin: 0;
|
|
197
|
+
padding: 0.5rem 0.75rem;
|
|
198
|
+
border-radius: var(--k2b-radius-control);
|
|
199
|
+
color: var(--k2b-text-secondary);
|
|
200
|
+
background: var(--k2b-surface-muted);
|
|
201
|
+
font-size: 0.75rem;
|
|
202
|
+
line-height: 1.55;
|
|
203
|
+
white-space: pre-wrap;
|
|
204
|
+
overflow-wrap: anywhere;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* The inner text clamps, so the quote's padding never shows a cut line. */
|
|
208
|
+
.k2b-ui .cloud-approval__quote[data-clamp] > span {
|
|
209
|
+
display: -webkit-box;
|
|
210
|
+
overflow: hidden;
|
|
211
|
+
-webkit-box-orient: vertical;
|
|
212
|
+
-webkit-line-clamp: 3;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.k2b-ui .cloud-approval__links {
|
|
216
|
+
display: flex;
|
|
217
|
+
flex-wrap: wrap;
|
|
218
|
+
gap: 0.25rem;
|
|
219
|
+
margin-left: -0.625rem;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.k2b-ui .cloud-approval__sheet {
|
|
223
|
+
display: grid;
|
|
224
|
+
min-width: 0;
|
|
225
|
+
gap: 1rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.k2b-ui .cloud-approval__sheet-foot {
|
|
229
|
+
width: 100%;
|
|
230
|
+
container-type: inline-size;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.k2b-ui .cloud-approval__callout {
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: flex-start;
|
|
236
|
+
gap: 0.5rem;
|
|
237
|
+
margin: 0;
|
|
238
|
+
color: var(--k2b-text-muted);
|
|
239
|
+
font-size: 0.75rem;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.k2b-ui .cloud-approval__callout i {
|
|
243
|
+
margin-top: 0.125rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* The foot keeps its height in every state: the note is one line, and the decision slot keeps one button height, so a
|
|
247
|
+
receipt replaces the buttons without moving anything. A narrow row puts the decision on a line of its own. */
|
|
248
|
+
.k2b-ui .cloud-approval__foot {
|
|
249
|
+
display: grid;
|
|
250
|
+
min-width: 0;
|
|
251
|
+
align-items: center;
|
|
252
|
+
gap: 0.5rem 1rem;
|
|
253
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.k2b-ui .cloud-approval__note {
|
|
257
|
+
display: flex;
|
|
258
|
+
min-width: 0;
|
|
259
|
+
align-items: center;
|
|
260
|
+
gap: 0.375rem;
|
|
261
|
+
color: var(--k2b-text-muted);
|
|
262
|
+
font-size: 0.75rem;
|
|
263
|
+
white-space: nowrap;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.k2b-ui .cloud-approval__note > span {
|
|
267
|
+
min-width: 0;
|
|
268
|
+
overflow: hidden;
|
|
269
|
+
text-overflow: ellipsis;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.k2b-ui .cloud-approval__note[data-tone="danger"] {
|
|
273
|
+
color: var(--k2b-danger-text);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.k2b-ui .cloud-approval__actions {
|
|
277
|
+
display: flex;
|
|
278
|
+
min-height: 1.75rem;
|
|
279
|
+
min-width: 0;
|
|
280
|
+
align-items: center;
|
|
281
|
+
justify-content: flex-end;
|
|
282
|
+
gap: 0.375rem;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@container (max-width: 32rem) {
|
|
286
|
+
.k2b-ui .cloud-approval__foot {
|
|
287
|
+
grid-template-columns: minmax(0, 1fr);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* The decision shares the width; a receipt keeps the same line. */
|
|
291
|
+
.k2b-ui .cloud-approval__actions > .k2b-button,
|
|
292
|
+
.k2b-ui .cloud-approval__actions > .k2b-split-button {
|
|
293
|
+
flex: 1;
|
|
294
|
+
min-width: 0;
|
|
295
|
+
justify-content: center;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.k2b-ui .cloud-approval__receipt {
|
|
300
|
+
display: inline-flex;
|
|
301
|
+
align-items: center;
|
|
302
|
+
gap: 0.375rem;
|
|
303
|
+
color: var(--k2b-text-muted);
|
|
304
|
+
font-size: 0.75rem;
|
|
305
|
+
font-weight: 500;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.k2b-ui .cloud-approval__receipt[data-outcome="approved"] {
|
|
309
|
+
color: var(--k2b-success-text);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* The band sits above the resource a request would change, tinted like the approval card in a chat. */
|
|
313
|
+
.k2b-ui .cloud-approval-bands {
|
|
314
|
+
display: grid;
|
|
315
|
+
gap: 0.5rem;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* Two rows in every width: the decision never shares a line with the title, so a receipt cannot rewrap it. */
|
|
319
|
+
.k2b-ui .cloud-approval-band {
|
|
320
|
+
display: grid;
|
|
321
|
+
min-width: 0;
|
|
322
|
+
align-items: center;
|
|
323
|
+
gap: 0.5rem 0.75rem;
|
|
324
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
325
|
+
container-type: inline-size;
|
|
326
|
+
padding: 0.75rem 1rem;
|
|
327
|
+
/* Transparent, so forced colours still draw an outline where the tint disappears. */
|
|
328
|
+
border: 1px solid transparent;
|
|
329
|
+
border-radius: var(--k2b-radius-surface);
|
|
330
|
+
color: var(--k2b-text-secondary);
|
|
331
|
+
background: color-mix(in srgb, var(--k2b-warning-surface) 70%, var(--k2b-surface-canvas));
|
|
332
|
+
font-size: 0.8125rem;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.k2b-ui.k2b-dark .cloud-approval-band,
|
|
336
|
+
.dark .k2b-ui .cloud-approval-band {
|
|
337
|
+
background: color-mix(in srgb, var(--k2b-warning-surface) 55%, var(--k2b-surface-canvas));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.k2b-ui .cloud-approval-band > i {
|
|
341
|
+
color: var(--k2b-warning-text);
|
|
342
|
+
font-size: 1.125rem;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.k2b-ui .cloud-approval-band[data-decided] {
|
|
346
|
+
background: var(--k2b-surface-muted);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.k2b-ui .cloud-approval-band[data-decided] > i {
|
|
350
|
+
color: var(--k2b-text-muted);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.k2b-ui .cloud-approval-band[data-decided="approved"] > i {
|
|
354
|
+
color: var(--k2b-success-text);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.k2b-ui .cloud-approval-band__text {
|
|
358
|
+
display: grid;
|
|
359
|
+
min-width: 0;
|
|
360
|
+
gap: 0.125rem;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.k2b-ui .cloud-approval-band__text strong {
|
|
364
|
+
color: var(--k2b-text);
|
|
365
|
+
font-weight: 600;
|
|
366
|
+
overflow-wrap: anywhere;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.k2b-ui .cloud-approval-band__text .cloud-approval__origin {
|
|
370
|
+
margin: 0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.k2b-ui .cloud-approval-band > .cloud-approval__foot {
|
|
374
|
+
grid-column: 2;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@media (max-width: 40rem) {
|
|
378
|
+
.k2b-ui .cloud-approval {
|
|
379
|
+
gap: 0.75rem;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.k2b-ui .cloud-approval__fields {
|
|
383
|
+
grid-template-columns: minmax(0, 1fr);
|
|
384
|
+
gap: 0;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.k2b-ui .cloud-approval__fields dd {
|
|
388
|
+
margin-bottom: 0.35rem;
|
|
389
|
+
}
|
|
390
|
+
}
|
package/src/styles/global.css
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
@import "./utilities-markdown-editor.css";
|
|
29
29
|
@import "./base-popover.css";
|
|
30
30
|
@import "./effects.css";
|
|
31
|
+
@import "./approvals.css";
|
|
31
32
|
@import "./input.css";
|
|
32
33
|
@import "./resource-search.css";
|
|
33
34
|
@import "./file-chooser.css";
|
|
@@ -123,7 +124,9 @@ html:has(.cloud-app-canvas[data-layout-full-page="true"]) {
|
|
|
123
124
|
height: 100%;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
|
|
127
|
+
/* Cloud's chart palette on every @k2b/ui surface, which sets its own palette on `.k2b-ui` (Cloud's body), so legends
|
|
128
|
+
and series filters, also in popovers, name the colors the chart draws. */
|
|
129
|
+
:root :is(.k2b-ui, .stdlib-chart, .k2b-chart-legend) {
|
|
127
130
|
--stdlib-chart-c1: #14b8a6;
|
|
128
131
|
--stdlib-chart-c2: #f97316;
|
|
129
132
|
--stdlib-chart-c3: #8b5cf6;
|
|
@@ -317,23 +320,34 @@ solid-island {
|
|
|
317
320
|
edges, without padding, radius, shadow, or border. The rail keeps the app
|
|
318
321
|
canvas, so the change of surface alone separates the two; no line does. The
|
|
319
322
|
outermost workspace and the notices above it lose their frame, so nothing
|
|
320
|
-
draws a box inside the app area.
|
|
323
|
+
draws a box inside the app area.
|
|
324
|
+
The rail and the app area reach the window's edges also where the installed
|
|
325
|
+
app reports safe areas, such as the status bar and the home indicator. Their
|
|
326
|
+
own surfaces fill the insets, and their content stays inside them. */
|
|
321
327
|
@media (min-width: 768px) and (max-width: 1535px) {
|
|
322
328
|
.cloud-app-canvas[data-layout-authenticated] {
|
|
323
|
-
padding:
|
|
329
|
+
padding: 0;
|
|
324
330
|
}
|
|
325
331
|
.cloud-app-canvas[data-layout-authenticated] > .layout-rail {
|
|
326
|
-
width: 3rem;
|
|
332
|
+
width: calc(3rem + env(safe-area-inset-left, 0px));
|
|
327
333
|
margin-inline-start: 0;
|
|
328
|
-
padding
|
|
334
|
+
padding: env(safe-area-inset-top, 0px) 0 calc(var(--ui-space-shell) + env(safe-area-inset-bottom, 0px))
|
|
335
|
+
env(safe-area-inset-left, 0px);
|
|
329
336
|
}
|
|
330
337
|
.cloud-app-canvas[data-layout-authenticated] > .layout-shell-content {
|
|
331
338
|
gap: 0;
|
|
339
|
+
padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) 0 0;
|
|
332
340
|
background: var(--k2b-surface);
|
|
333
341
|
}
|
|
342
|
+
/* A regular page scrolls `main`, so its end clears the home indicator. A
|
|
343
|
+
workspace reaches the bottom edge and clears it inside its own surfaces. */
|
|
344
|
+
.cloud-app-canvas[data-layout-authenticated] .layout-content-main:not(:has(.k2b-app-workspace)) {
|
|
345
|
+
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
346
|
+
}
|
|
334
347
|
.cloud-app-canvas[data-layout-authenticated] .layout-content-main .k2b-app-workspace:not(.k2b-app-workspace *) {
|
|
348
|
+
--k2b-workspace-radius: 0px;
|
|
349
|
+
--k2b-workspace-inset-bottom: env(safe-area-inset-bottom, 0px);
|
|
335
350
|
border: 0;
|
|
336
|
-
border-radius: 0;
|
|
337
351
|
box-shadow: none;
|
|
338
352
|
}
|
|
339
353
|
.cloud-app-canvas[data-layout-authenticated] > .layout-shell-content > :not(.layout-content-main) .k2b-notice-card {
|
|
@@ -373,4 +387,5 @@ solid-island {
|
|
|
373
387
|
width: 100%; white-space: normal; overflow-wrap: anywhere; font-size: .8125rem; line-height: 1.3;
|
|
374
388
|
}
|
|
375
389
|
.cloud-mobile-profile { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; border-top: 1px solid var(--ui-border); padding-top: 1rem; }
|
|
376
|
-
.cloud-mobile-profile :is(a, button) { min-height: 2.75rem; white-space: normal; text-align: start; justify-content: flex-start; }
|
|
390
|
+
.cloud-mobile-profile > :is(a, button) { min-height: 2.75rem; white-space: normal; text-align: start; justify-content: flex-start; }
|
|
391
|
+
.cloud-mobile-profile__appearance { grid-column: 1 / -1; }
|
|
@@ -883,6 +883,19 @@
|
|
|
883
883
|
transition: opacity 120ms ease-out;
|
|
884
884
|
}
|
|
885
885
|
|
|
886
|
+
.layout-profile-preferences__appearance {
|
|
887
|
+
padding: 0.125rem 0.125rem 0.25rem;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.layout-profile-preferences__appearance .k2b-segmented-control {
|
|
891
|
+
width: 100%;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/* Content-sized segments: "Automatisch" needs more room than "Hell" in the narrow panel. */
|
|
895
|
+
.layout-profile-preferences__appearance .k2b-segmented-control__option {
|
|
896
|
+
flex: 1 1 auto;
|
|
897
|
+
}
|
|
898
|
+
|
|
886
899
|
.dark .layout-profile-preferences__panel {
|
|
887
900
|
color: var(--color-zinc-300);
|
|
888
901
|
}
|
|
@@ -292,7 +292,7 @@ const compileSteps = (
|
|
|
292
292
|
if (hasOwn(step, "switch")) {
|
|
293
293
|
validateExactKeys(step, ["switch", "cases", "default"], stepPath, context);
|
|
294
294
|
if (!Array.isArray(step.cases) || step.cases.length === 0) {
|
|
295
|
-
addDiagnostic(context, "switch.cases", "switch
|
|
295
|
+
addDiagnostic(context, "switch.cases", "switch cases must be a non-empty list of { when, do } entries", [...stepPath, "cases"]);
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
298
|
const cases: Array<{ when: WorkflowJsonValue; steps: WorkflowIrStep[] }> = [];
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { installAppWorkspaceController, serializeAppWorkspaceLayoutState } from "@k2b/ui";
|
|
2
|
-
import { onCleanup, onMount } from "solid-js";
|
|
3
|
-
import { appWorkspaceCookieName, readAppWorkspaceLayoutCookie } from "../_internal/app-workspace-state";
|
|
4
|
-
|
|
5
|
-
const writeLayoutCookie = (appId: string | null | undefined, state: Parameters<typeof serializeAppWorkspaceLayoutState>[0]) => {
|
|
6
|
-
if (!appId) return;
|
|
7
|
-
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
8
|
-
document.cookie = `${appWorkspaceCookieName(appId)}=${serializeAppWorkspaceLayoutState(state)}; Path=/; Max-Age=31536000; SameSite=Lax${secure}`;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export default function AppWorkspaceController(props: { appId?: string | null }) {
|
|
12
|
-
let dispose = () => {};
|
|
13
|
-
onMount(() => {
|
|
14
|
-
dispose = installAppWorkspaceController({
|
|
15
|
-
readState: () => readAppWorkspaceLayoutCookie(document.cookie, props.appId),
|
|
16
|
-
writeState: (state) => writeLayoutCookie(props.appId, state),
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
onCleanup(() => dispose());
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { onMount } from "solid-js";
|
|
2
|
-
import { browserNotificationClient } from "../browser/notifications";
|
|
3
|
-
|
|
4
|
-
export default function BrowserPushRegistration() {
|
|
5
|
-
onMount(() => {
|
|
6
|
-
void browserNotificationClient.refreshExisting().catch((error) => {
|
|
7
|
-
console.warn("[notifications] Failed to refresh the browser endpoint", error);
|
|
8
|
-
});
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
return null;
|
|
12
|
-
}
|