@k2b/cloud 0.6.0-rc.0 → 0.7.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/README.md +2 -2
- package/package.json +30 -17
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +15 -35
- package/scripts/preload.ts +9 -12
- package/scripts/sync-recovery-smoke.ts +2 -1
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/capabilities.ts +108 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +62 -36
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +14 -153
- package/src/access/PrincipalPicker.tsx +104 -0
- package/src/access/ui.ts +2 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +79 -0
- package/src/ai/browser.ts +2 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +157 -95
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +48 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +12 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +19 -48
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +17 -2
- package/src/ai/chat/tool-groups.ts +52 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +9 -1
- package/src/ai/chat-tasks.ts +276 -43
- package/src/ai/client/controller.ts +110 -33
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +22 -11
- package/src/ai/client/transport.ts +4 -0
- package/src/ai/code-capability-routes.ts +110 -0
- package/src/ai/code-capability-transport.ts +38 -0
- package/src/ai/code-mode-skill.ts +114 -0
- package/src/ai/code-runtime-tools.ts +166 -0
- package/src/ai/code-source-contracts.ts +202 -0
- package/src/ai/code-source-tools.ts +145 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +25 -2
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +34 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +191 -31
- package/src/ai/file-content-version.ts +22 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +2 -1
- package/src/ai/files-store.ts +174 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +40 -10
- package/src/ai/http.ts +15 -8
- package/src/ai/index.ts +26 -2
- package/src/ai/inference-calls.ts +198 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/memory-learning.ts +60 -28
- package/src/ai/message-queue.ts +167 -0
- package/src/ai/migrate.ts +187 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +74 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +3 -3
- package/src/ai/projects.ts +20 -6
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +191 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +215 -11
- package/src/ai/run-timeout.ts +10 -0
- package/src/ai/runtime-tools.ts +3 -0
- package/src/ai/runtime.ts +55 -16
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +2 -2
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +47 -0
- package/src/ai/skill-seeds.ts +105 -20
- package/src/ai/skill-tool.ts +16 -5
- package/src/ai/skills-routes.ts +32 -4
- package/src/ai/skills.ts +324 -155
- package/src/ai/store.ts +373 -191
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +8 -1
- package/src/ai/system-prompt.ts +12 -2
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +20 -0
- package/src/ai/todo-tool.ts +16 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +2 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +39 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +74 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/capabilities.ts +292 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +6 -0
- package/src/api/mcp.ts +44 -26
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +66 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +5 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +114 -34
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +79 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +9 -0
- package/src/cli/capabilities.ts +29 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/env.ts +11 -8
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +68 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +6 -1
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +3 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +9 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/solid.tsx +3 -3
- package/src/index.ts +0 -4
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +1 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +2 -1
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/index.ts +15 -0
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +18 -4
- package/src/services/mandates/policy.ts +65 -2
- package/src/services/notifications/batches.ts +124 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +107 -84
- package/src/services/pdf/index.ts +3 -0
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +28 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/core-settings.ts +21 -28
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/store.ts +58 -84
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +6 -0
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +29 -15
- package/src/ssr/AppLaunchpad.island.tsx +24 -137
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +23 -22
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +7 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/runtime.ts +5 -1
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +2 -0
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/scripts/app-favicon.test.ts +0 -49
- package/src/ai/chat/blocks.render.test.tsx +0 -1108
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/CloudResourceSearch.behavior.test.tsx +0 -74
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/services/notifications/live.ts +0 -50
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +0 -96
- package/src/ssr/Layout.render.test.tsx +0 -148
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
package/src/ssr/help.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import { getLocale } from "../server/locale";
|
|
3
|
+
import { createHelpReader } from "../services/help";
|
|
4
|
+
import type { HelpManifestResult } from "../services/help/types";
|
|
5
|
+
import { logger } from "../services/logging";
|
|
6
|
+
import { resolveCurrentApp } from "./app-appearance";
|
|
7
|
+
import { getRuntimeContext } from "./runtime";
|
|
8
|
+
|
|
9
|
+
const manifests = new WeakMap<object, HelpManifestResult | null>();
|
|
10
|
+
const log = logger("help:ssr");
|
|
11
|
+
export const getLayoutHelp = (c: object): HelpManifestResult | null => manifests.get(c) ?? null;
|
|
12
|
+
|
|
13
|
+
/** Explicit Help pages also work anonymously; automatic layout loading requires a user. */
|
|
14
|
+
export const preloadLayoutHelp = async (c: Context, appId?: string): Promise<HelpManifestResult | null> => {
|
|
15
|
+
if (manifests.has(c)) return getLayoutHelp(c);
|
|
16
|
+
if (!c.get("runtime") || (!appId && !c.get("user"))) return null;
|
|
17
|
+
const id = appId ?? resolveCurrentApp(getRuntimeContext(c).apps, c.req.path)?.id;
|
|
18
|
+
if (!id) return null;
|
|
19
|
+
try {
|
|
20
|
+
const manifest = await createHelpReader(getLocale(c)).manifest(id);
|
|
21
|
+
manifests.set(c, manifest);
|
|
22
|
+
return manifest;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// An unavailable Help store must not prevent an unrelated application page from rendering.
|
|
25
|
+
log.warn("Help metadata unavailable", { appId: id, error: error instanceof Error ? error.message : String(error) });
|
|
26
|
+
manifests.set(c, null);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { IconButtonLink, TextInput, useLocale } from "@k2b/ui";
|
|
2
|
+
import { createMemo, For, Show } from "solid-js";
|
|
3
|
+
import { platformMessages } from "../platform-messages";
|
|
4
|
+
|
|
5
|
+
type SearchBarProps = {
|
|
6
|
+
/** Destination URL including every filter to retain. */
|
|
7
|
+
action: string;
|
|
8
|
+
value?: string;
|
|
9
|
+
param?: string;
|
|
10
|
+
pageParam?: string;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Native GET search: works in server HTML and inside hydrated islands. */
|
|
17
|
+
export default function SearchBar(props: SearchBarProps) {
|
|
18
|
+
const locale = useLocale();
|
|
19
|
+
const t = () => platformMessages.resolve([locale()]).t;
|
|
20
|
+
const param = () => props.param ?? "search";
|
|
21
|
+
const target = createMemo(() => {
|
|
22
|
+
// Split without resolving against a browser-only origin; relative URLs stay relative.
|
|
23
|
+
const hashAt = props.action.indexOf("#");
|
|
24
|
+
const hash = hashAt < 0 ? "" : props.action.slice(hashAt);
|
|
25
|
+
const href = hashAt < 0 ? props.action : props.action.slice(0, hashAt);
|
|
26
|
+
const queryAt = href.indexOf("?");
|
|
27
|
+
const path = queryAt < 0 ? href : href.slice(0, queryAt);
|
|
28
|
+
const params = new URLSearchParams(queryAt < 0 ? "" : href.slice(queryAt + 1));
|
|
29
|
+
const value = props.value ?? params.get(param()) ?? "";
|
|
30
|
+
params.delete(param());
|
|
31
|
+
params.delete(props.pageParam ?? "page");
|
|
32
|
+
const query = params.toString();
|
|
33
|
+
return { action: path + hash, clear: path + (query || !path ? `?${query}` : "") + hash, fields: [...params], value };
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<form action={target().action} method="get" role="search" class="group/search w-full">
|
|
38
|
+
<For each={target().fields}>{([name, value]) => <input type="hidden" name={name} value={value} />}</For>
|
|
39
|
+
<TextInput
|
|
40
|
+
name={param()}
|
|
41
|
+
type="search"
|
|
42
|
+
placeholder={props.placeholder || `${t().search}…`}
|
|
43
|
+
aria-label={props.ariaLabel ?? t().search}
|
|
44
|
+
icon="ti ti-search"
|
|
45
|
+
activeIcon="ti ti-search"
|
|
46
|
+
value={() => target().value}
|
|
47
|
+
disabled={props.disabled}
|
|
48
|
+
suffix={
|
|
49
|
+
<Show when={!props.disabled}>
|
|
50
|
+
<IconButtonLink
|
|
51
|
+
href={target().clear}
|
|
52
|
+
label={t().clearSearch}
|
|
53
|
+
size="xs"
|
|
54
|
+
tooltip={false}
|
|
55
|
+
class="group-has-[input:placeholder-shown]/search:invisible"
|
|
56
|
+
>
|
|
57
|
+
<i class="ti ti-x" aria-hidden="true" />
|
|
58
|
+
</IconButtonLink>
|
|
59
|
+
</Show>
|
|
60
|
+
}
|
|
61
|
+
/>
|
|
62
|
+
<button type="submit" class="hidden" disabled={props.disabled}>
|
|
63
|
+
{t().search}
|
|
64
|
+
</button>
|
|
65
|
+
</form>
|
|
66
|
+
);
|
|
67
|
+
}
|
package/src/ssr/islands/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { default as SearchBar } from "./SearchBar
|
|
1
|
+
export { default as SearchBar } from "./SearchBar";
|
|
2
|
+
export { provideWorkspaceNavigation } from "../workspace-navigation";
|
|
3
|
+
export { default as WorkspaceNavigationProvider } from "../WorkspaceNavigationProvider";
|
|
2
4
|
|
|
3
5
|
import { AppLaunchpadButton, AppLaunchpadProvider } from "../AppLaunchpad.island";
|
|
4
6
|
import { LayoutHelpDocuments, LayoutHelpPage } from "../LayoutHelp";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RailSnapshot } from "../contracts/rail-preferences";
|
|
2
2
|
import type { User } from "../contracts/shared";
|
|
3
3
|
import type { LayoutAnnouncementsState } from "../server/middleware/settings";
|
|
4
4
|
import type { RuntimeContext } from "./runtime";
|
|
5
5
|
|
|
6
6
|
export type LayoutContext = {
|
|
7
7
|
get(key: "user"): User | undefined;
|
|
8
|
-
get(key: "railPreferences"):
|
|
8
|
+
get(key: "railPreferences"): RailSnapshot | undefined;
|
|
9
9
|
get(key: "page"): { theme?: "light" | "dark" };
|
|
10
10
|
get(key: "runtime"): RuntimeContext;
|
|
11
11
|
get(key: "announcements"): LayoutAnnouncementsState | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type SearchTopic = { id: string; title: string; description?: string; kind: "document" | "content" };
|
|
2
|
+
|
|
3
|
+
/** Use server ranking for documents; metadata remains usable during loading or outages. */
|
|
4
|
+
export const searchHelpTopics = <T extends SearchTopic>(topics: readonly T[], query: string, remoteIds: readonly string[] | null): T[] => {
|
|
5
|
+
const local = topics.filter((topic) => [topic.title, topic.description].some((part) => part?.toLocaleLowerCase().includes(query)));
|
|
6
|
+
if (remoteIds === null) return local;
|
|
7
|
+
const documents = new Map(topics.filter((topic) => topic.kind === "document").map((topic) => [topic.id, topic]));
|
|
8
|
+
return [
|
|
9
|
+
...remoteIds.flatMap((id) => {
|
|
10
|
+
const topic = documents.get(id);
|
|
11
|
+
return topic ? [topic] : [];
|
|
12
|
+
}),
|
|
13
|
+
...local.filter((topic) => topic.kind === "content"),
|
|
14
|
+
];
|
|
15
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { bottomSheetOptions, dialogCore, type DialogRender } from "@k2b/ui";
|
|
2
|
+
|
|
3
|
+
const marker = "cloudMobileMenu";
|
|
4
|
+
let running: Promise<void> | undefined;
|
|
5
|
+
|
|
6
|
+
/** One transient history entry; complete its Back before a selected operation runs. */
|
|
7
|
+
export function openMobileMenu(view: (beforeSelect: () => Promise<boolean>) => DialogRender<void>) {
|
|
8
|
+
if (running) return running;
|
|
9
|
+
let done: () => void = () => {};
|
|
10
|
+
const cleaned = new Promise<void>((resolve) => {
|
|
11
|
+
done = resolve;
|
|
12
|
+
});
|
|
13
|
+
running = (async () => {
|
|
14
|
+
const previous = { ...history.state };
|
|
15
|
+
delete previous[marker];
|
|
16
|
+
history.replaceState(previous, "");
|
|
17
|
+
const id = crypto.randomUUID();
|
|
18
|
+
history.pushState({ ...previous, [marker]: id }, "");
|
|
19
|
+
const abort = new AbortController();
|
|
20
|
+
let leavingPage = false;
|
|
21
|
+
const mobile = window.matchMedia("(max-width: 1023px)");
|
|
22
|
+
const pop = () => {
|
|
23
|
+
if (history.state?.[marker] !== id) abort.abort();
|
|
24
|
+
};
|
|
25
|
+
const resized = () => {
|
|
26
|
+
if (!mobile.matches) abort.abort();
|
|
27
|
+
};
|
|
28
|
+
const leaving = () => {
|
|
29
|
+
leavingPage = true;
|
|
30
|
+
abort.abort();
|
|
31
|
+
};
|
|
32
|
+
window.addEventListener("popstate", pop);
|
|
33
|
+
window.addEventListener("pagehide", leaving);
|
|
34
|
+
mobile.addEventListener("change", resized);
|
|
35
|
+
try {
|
|
36
|
+
await dialogCore.open<void>(
|
|
37
|
+
(close, context) =>
|
|
38
|
+
view(async () => {
|
|
39
|
+
close();
|
|
40
|
+
await cleaned;
|
|
41
|
+
return !abort.signal.aborted;
|
|
42
|
+
})(close, context),
|
|
43
|
+
{ ...bottomSheetOptions, panelClassName: `${bottomSheetOptions.panelClassName} cloud-mobile-menu`, signal: abort.signal },
|
|
44
|
+
);
|
|
45
|
+
} finally {
|
|
46
|
+
window.removeEventListener("popstate", pop);
|
|
47
|
+
window.removeEventListener("pagehide", leaving);
|
|
48
|
+
mobile.removeEventListener("change", resized);
|
|
49
|
+
if (!leavingPage && history.state?.[marker] === id) {
|
|
50
|
+
await new Promise<void>((resolve) => {
|
|
51
|
+
const returned = () => {
|
|
52
|
+
window.removeEventListener("pagehide", returned);
|
|
53
|
+
window.removeEventListener("popstate", returned);
|
|
54
|
+
resolve();
|
|
55
|
+
};
|
|
56
|
+
window.addEventListener("popstate", returned, { once: true });
|
|
57
|
+
window.addEventListener("pagehide", returned, { once: true });
|
|
58
|
+
history.back();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (!mobile.matches && !leavingPage) {
|
|
62
|
+
const launcher = document.querySelector<HTMLButtonElement>(".layout-rail [data-cloud-launchpad]");
|
|
63
|
+
launcher?.focus({ preventScroll: true });
|
|
64
|
+
}
|
|
65
|
+
done();
|
|
66
|
+
running = undefined;
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
return running;
|
|
70
|
+
}
|
|
@@ -9,7 +9,9 @@ export const platformMessages = i18n.define({
|
|
|
9
9
|
start: "Start",
|
|
10
10
|
overview: "Overview",
|
|
11
11
|
general: "General",
|
|
12
|
+
rail: "App bar",
|
|
12
13
|
announcements: "Announcements",
|
|
14
|
+
appCredentials: "App credentials",
|
|
13
15
|
ai: "AI",
|
|
14
16
|
settings: "Settings",
|
|
15
17
|
appAdmin: "App administration",
|
|
@@ -26,9 +28,15 @@ export const platformMessages = i18n.define({
|
|
|
26
28
|
skills: "Skills",
|
|
27
29
|
projects: "Projects",
|
|
28
30
|
aiUsage: "Usage",
|
|
31
|
+
aiQuotas: "Assistant limits",
|
|
29
32
|
backgroundJobs: "Background jobs",
|
|
30
33
|
apps: "Apps",
|
|
31
34
|
openApps: "Open apps",
|
|
35
|
+
appMenu: "Menu",
|
|
36
|
+
findApps: "Find an app…",
|
|
37
|
+
noAppsFound: "No matching apps.",
|
|
38
|
+
allApps: "All apps",
|
|
39
|
+
navigation: "Navigation",
|
|
32
40
|
home: "Home",
|
|
33
41
|
profile: "Profile",
|
|
34
42
|
displayName: "display name",
|
|
@@ -40,10 +48,6 @@ export const platformMessages = i18n.define({
|
|
|
40
48
|
guestAccountExpires: ({ date }: { date: string }) => `Your guest account expires on ${date}.`,
|
|
41
49
|
passwordExpired: "Your password has expired. Sign out and in again to change it.",
|
|
42
50
|
passwordExpires: ({ date }: { date: string }) => `Your password expires on ${date}.`,
|
|
43
|
-
newNotification: "New notification",
|
|
44
|
-
relatedItemReady: "A related item is ready.",
|
|
45
|
-
open: "Open",
|
|
46
|
-
dismissNotification: "Dismiss notification",
|
|
47
51
|
light: "Light",
|
|
48
52
|
dark: "Dark",
|
|
49
53
|
account: "Account",
|
|
@@ -73,7 +77,9 @@ export const platformMessages = i18n.define({
|
|
|
73
77
|
start: "Start",
|
|
74
78
|
overview: "Übersicht",
|
|
75
79
|
general: "Allgemein",
|
|
80
|
+
rail: "App-Leiste",
|
|
76
81
|
announcements: "Ankündigungen",
|
|
82
|
+
appCredentials: "App-Zugänge",
|
|
77
83
|
ai: "KI",
|
|
78
84
|
settings: "Einstellungen",
|
|
79
85
|
appAdmin: "App-Verwaltung",
|
|
@@ -90,8 +96,14 @@ export const platformMessages = i18n.define({
|
|
|
90
96
|
skills: "Skills",
|
|
91
97
|
projects: "Projekte",
|
|
92
98
|
aiUsage: "Nutzung",
|
|
99
|
+
aiQuotas: "Assistant-Limits",
|
|
93
100
|
backgroundJobs: "Hintergrundaufgaben",
|
|
94
101
|
apps: "Apps",
|
|
102
|
+
appMenu: "Menü",
|
|
103
|
+
findApps: "App finden…",
|
|
104
|
+
noAppsFound: "Keine passenden Apps.",
|
|
105
|
+
allApps: "Alle Apps",
|
|
106
|
+
navigation: "Navigation",
|
|
95
107
|
openApps: "Apps öffnen",
|
|
96
108
|
home: "Start",
|
|
97
109
|
profile: "Profil",
|
|
@@ -104,10 +116,6 @@ export const platformMessages = i18n.define({
|
|
|
104
116
|
guestAccountExpires: ({ date }) => `Dein Gastkonto läuft am ${date} ab.`,
|
|
105
117
|
passwordExpired: "Dein Passwort ist abgelaufen. Melde dich ab und erneut an, um es zu ändern.",
|
|
106
118
|
passwordExpires: ({ date }) => `Dein Passwort läuft am ${date} ab.`,
|
|
107
|
-
newNotification: "Neue Benachrichtigung",
|
|
108
|
-
relatedItemReady: "Ein zugehöriger Inhalt ist verfügbar.",
|
|
109
|
-
open: "Öffnen",
|
|
110
|
-
dismissNotification: "Benachrichtigung ausblenden",
|
|
111
119
|
light: "Hell",
|
|
112
120
|
dark: "Dunkel",
|
|
113
121
|
account: "Konto",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type DropdownItem, toast, useLocale } from "@k2b/ui";
|
|
2
|
+
import { createMemo, createSignal } from "solid-js";
|
|
3
|
+
import { browserNotificationClient } from "../browser/notifications";
|
|
4
|
+
import { apiClient } from "../clients/core";
|
|
5
|
+
import type { CloudTheme } from "../shared/theme";
|
|
6
|
+
import { createPreferenceController } from "./preference-controller";
|
|
7
|
+
|
|
8
|
+
export function createProfileActions(initialTheme: CloudTheme) {
|
|
9
|
+
const locale = useLocale();
|
|
10
|
+
const preferences = createPreferenceController(initialTheme, locale);
|
|
11
|
+
const [signingOut, setSigningOut] = createSignal(false);
|
|
12
|
+
const signOut = async () => {
|
|
13
|
+
if (signingOut()) return;
|
|
14
|
+
setSigningOut(true);
|
|
15
|
+
try {
|
|
16
|
+
await browserNotificationClient.disable().catch(() => undefined);
|
|
17
|
+
const response = await apiClient.auth.logout.$post();
|
|
18
|
+
if (!response.ok) throw new Error(preferences.messages().signOutFailed);
|
|
19
|
+
window.location.href = "/auth/login";
|
|
20
|
+
} catch {
|
|
21
|
+
toast.error(preferences.messages().signOutFailed);
|
|
22
|
+
setSigningOut(false);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const items = createMemo<DropdownItem[]>(() => [
|
|
27
|
+
...preferences.items(),
|
|
28
|
+
{
|
|
29
|
+
href: "/me",
|
|
30
|
+
icon: "ti ti-user-circle",
|
|
31
|
+
label: preferences.messages().profileSettings,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
action: signOut,
|
|
35
|
+
icon: "ti ti-logout",
|
|
36
|
+
label: preferences.messages().signOut,
|
|
37
|
+
disabled: signingOut(),
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
return { preferences, items, signOut, signingOut };
|
|
42
|
+
}
|
package/src/ssr/rail-context.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type RailPreferences,
|
|
1
|
+
import { type RailPreferences, type RailSnapshot, RailSnapshotSchema } from "../contracts/rail-preferences";
|
|
2
2
|
import type { RailApp } from "./rail-navigation";
|
|
3
3
|
|
|
4
|
-
export type RailContext = { apps: RailApp[]; settings:
|
|
4
|
+
export type RailContext = { apps: RailApp[]; settings: RailSnapshot };
|
|
5
5
|
export const RAIL_PREFERENCES_EVENT = "cloud:rail-preferences";
|
|
6
6
|
|
|
7
7
|
export const readRailContext = (): RailContext | undefined => {
|
|
@@ -10,12 +10,16 @@ export const readRailContext = (): RailContext | undefined => {
|
|
|
10
10
|
if (!text) return undefined;
|
|
11
11
|
// Framework-generated, escaped SSR data; never loaded from localStorage.
|
|
12
12
|
const value: RailContext = JSON.parse(text);
|
|
13
|
-
return { apps: value.apps, settings:
|
|
13
|
+
return { apps: value.apps, settings: RailSnapshotSchema.parse(value.settings) };
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export const publishRailPreferences = (settings: RailPreferences) => {
|
|
17
17
|
const context = readRailContext();
|
|
18
18
|
const element = document.getElementById("cloud-rail-data");
|
|
19
|
-
if (context && element)
|
|
19
|
+
if (context && element)
|
|
20
|
+
element.textContent = JSON.stringify({
|
|
21
|
+
...context,
|
|
22
|
+
settings: { ...settings, managedShortcuts: context.settings.managedShortcuts },
|
|
23
|
+
}).replace(/</g, "\\u003c");
|
|
20
24
|
window.dispatchEvent(new CustomEvent(RAIL_PREFERENCES_EVENT));
|
|
21
25
|
};
|
package/src/ssr/rail-messages.ts
CHANGED
|
@@ -4,6 +4,7 @@ export const railMessages = i18n.define({
|
|
|
4
4
|
baseLocale: "en",
|
|
5
5
|
messages: {
|
|
6
6
|
en: {
|
|
7
|
+
managed: "Provided by the administration",
|
|
7
8
|
customize: "Customize app bar",
|
|
8
9
|
apps: "Visible apps",
|
|
9
10
|
shortcuts: "App bar shortcuts",
|
|
@@ -33,6 +34,7 @@ export const railMessages = i18n.define({
|
|
|
33
34
|
invalid: "Check shortcut titles, links and icons. Each app can be pinned once; settings must fit within 16 KiB.",
|
|
34
35
|
},
|
|
35
36
|
de: {
|
|
37
|
+
managed: "Von der Administration bereitgestellt",
|
|
36
38
|
customize: "App-Leiste anpassen",
|
|
37
39
|
apps: "Sichtbare Apps",
|
|
38
40
|
shortcuts: "Shortcuts der App-Leiste",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RailPreferences } from "../contracts/rail-preferences";
|
|
1
|
+
import type { RailPreferences, RailSnapshot } from "../contracts/rail-preferences";
|
|
2
2
|
|
|
3
3
|
export type RailApp = {
|
|
4
4
|
id: string;
|
|
@@ -21,11 +21,11 @@ export const sortRailApps = (apps: readonly RailApp[], locale: string): RailApp[
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/** Input is the authorized navigation catalog, never the unfiltered registry. */
|
|
24
|
-
export const projectRailNavigation = (apps: readonly RailApp[], settings:
|
|
24
|
+
export const projectRailNavigation = (apps: readonly RailApp[], settings: RailSnapshot, locale: string) => {
|
|
25
25
|
const catalog = new Map(apps.map((app) => [app.id, app]));
|
|
26
26
|
const pinned = new Set<string>();
|
|
27
27
|
const shortcuts: RailLink[] = [];
|
|
28
|
-
for (const shortcut of settings.shortcuts) {
|
|
28
|
+
for (const shortcut of [...(settings.managedShortcuts ?? []), ...settings.shortcuts]) {
|
|
29
29
|
if (shortcut.kind === "app") {
|
|
30
30
|
const app = catalog.get(shortcut.appId);
|
|
31
31
|
if (!app || pinned.has(app.id)) continue;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createNavigation, type NavigationController, type NavigationItem } from "@k2b/ui";
|
|
2
|
+
import { createEffect, onCleanup, onMount } from "solid-js";
|
|
3
|
+
|
|
4
|
+
export type WorkspaceNavigationState = { navigation: NavigationController; label: string; owner: Element };
|
|
5
|
+
const changedEvent = "cloud:workspace-navigation";
|
|
6
|
+
|
|
7
|
+
declare global {
|
|
8
|
+
interface Window {
|
|
9
|
+
__cloudWorkspaceNavigation?: WorkspaceNavigationState;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Bind only the outer application's navigation. Nested workspaces do not register. */
|
|
14
|
+
export function provideWorkspaceNavigation(navigation: NavigationController, options: { label: () => string; owner: () => Element }) {
|
|
15
|
+
onMount(() => {
|
|
16
|
+
const state = { navigation, label: options.label(), owner: options.owner() };
|
|
17
|
+
window.__cloudWorkspaceNavigation = state;
|
|
18
|
+
createEffect(() => {
|
|
19
|
+
navigation.items();
|
|
20
|
+
state.label = options.label();
|
|
21
|
+
window.dispatchEvent(new Event(changedEvent));
|
|
22
|
+
});
|
|
23
|
+
onCleanup(() => {
|
|
24
|
+
if (window.__cloudWorkspaceNavigation !== state) return;
|
|
25
|
+
delete window.__cloudWorkspaceNavigation;
|
|
26
|
+
window.dispatchEvent(new Event(changedEvent));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function validItems(value: unknown): value is NavigationItem[] {
|
|
32
|
+
return (
|
|
33
|
+
Array.isArray(value) &&
|
|
34
|
+
value.every((entry) => {
|
|
35
|
+
if (!entry || typeof entry !== "object" || typeof entry.id !== "string" || typeof entry.label !== "string") return false;
|
|
36
|
+
if (entry.href !== undefined && typeof entry.href !== "string") return false;
|
|
37
|
+
if (entry.action !== undefined && typeof entry.action !== "string") return false;
|
|
38
|
+
return (entry.children === undefined || validItems(entry.children)) && (entry.actions === undefined || validItems(entry.actions));
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function readWorkspaceNavigation(): WorkspaceNavigationState | undefined {
|
|
44
|
+
if (typeof window === "undefined") return;
|
|
45
|
+
const current = window.__cloudWorkspaceNavigation;
|
|
46
|
+
if (current?.owner.isConnected) return current;
|
|
47
|
+
const owner = document.querySelector<HTMLScriptElement>("script[data-cloud-workspace-navigation]");
|
|
48
|
+
if (!owner) return;
|
|
49
|
+
try {
|
|
50
|
+
const value: unknown = JSON.parse(owner.textContent ?? "");
|
|
51
|
+
if (!value || typeof value !== "object" || !("items" in value) || !validItems(value.items)) return;
|
|
52
|
+
// SSR links work before the app island loads; action handlers are not yet available.
|
|
53
|
+
const links = (items: readonly NavigationItem[]): NavigationItem[] =>
|
|
54
|
+
items.map((item) => ({
|
|
55
|
+
...item,
|
|
56
|
+
disabled: item.disabled || Boolean(item.action && !item.href),
|
|
57
|
+
...(item.href ? { action: undefined } : {}),
|
|
58
|
+
...(item.children ? { children: links(item.children) } : {}),
|
|
59
|
+
...(item.actions ? { actions: links(item.actions) } : {}),
|
|
60
|
+
}));
|
|
61
|
+
const items = links(value.items);
|
|
62
|
+
return {
|
|
63
|
+
owner,
|
|
64
|
+
label: "label" in value && typeof value.label === "string" ? value.label : "",
|
|
65
|
+
navigation: createNavigation({ items: () => items }),
|
|
66
|
+
};
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function observeWorkspaceNavigation(listener: () => void) {
|
|
73
|
+
window.addEventListener(changedEvent, listener);
|
|
74
|
+
return () => window.removeEventListener(changedEvent, listener);
|
|
75
|
+
}
|
package/src/styles/effects.css
CHANGED
|
@@ -794,7 +794,7 @@ dialog:has(.launchpad-panel)::backdrop {
|
|
|
794
794
|
|
|
795
795
|
/* Dashboard launchpad app icon. One app colour drives the glyph and graded rim;
|
|
796
796
|
the face stays clean so the tile feels crisp instead of plastic. */
|
|
797
|
-
.launchpad-panel .app-icon {
|
|
797
|
+
:is(.launchpad-panel, .cloud-mobile-apps) .app-icon {
|
|
798
798
|
--icon-face: #ffffff;
|
|
799
799
|
--rim-top: color-mix(in oklab, var(--app-icon-color), white 34%);
|
|
800
800
|
--rim-bottom: color-mix(in oklab, var(--app-icon-color), black 12%);
|
|
@@ -817,13 +817,13 @@ dialog:has(.launchpad-panel)::backdrop {
|
|
|
817
817
|
}
|
|
818
818
|
|
|
819
819
|
@supports (color: oklch(from red l c h)) {
|
|
820
|
-
.launchpad-panel .app-icon {
|
|
820
|
+
:is(.launchpad-panel, .cloud-mobile-apps) .app-icon {
|
|
821
821
|
--glyph: oklch(from var(--app-icon-color) min(l, 0.5) c h);
|
|
822
822
|
--glyph-hover: oklch(from var(--app-icon-color) min(l, 0.4) c h);
|
|
823
823
|
}
|
|
824
824
|
}
|
|
825
825
|
|
|
826
|
-
.dark .launchpad-panel .app-icon {
|
|
826
|
+
.dark :is(.launchpad-panel, .cloud-mobile-apps) .app-icon {
|
|
827
827
|
--icon-face: #202026;
|
|
828
828
|
--rim-top: color-mix(in oklab, var(--app-icon-color), white 30%);
|
|
829
829
|
--rim-bottom: color-mix(in oklab, var(--app-icon-color), black 24%);
|
|
@@ -835,15 +835,15 @@ dialog:has(.launchpad-panel)::backdrop {
|
|
|
835
835
|
}
|
|
836
836
|
|
|
837
837
|
@supports (color: oklch(from red l c h)) {
|
|
838
|
-
.dark .launchpad-panel .app-icon {
|
|
838
|
+
.dark :is(.launchpad-panel, .cloud-mobile-apps) .app-icon {
|
|
839
839
|
--glyph: oklch(from var(--app-icon-color) max(l, 0.82) c h);
|
|
840
840
|
--glyph-hover: oklch(from var(--app-icon-color) max(l, 0.96) c h);
|
|
841
841
|
}
|
|
842
842
|
}
|
|
843
843
|
|
|
844
844
|
/* Hover changes only the icon tile, never the whole icon+label item. */
|
|
845
|
-
.launchpad-panel .group:hover > .app-icon,
|
|
846
|
-
.launchpad-panel .app-icon:hover {
|
|
845
|
+
:is(.launchpad-panel, .cloud-mobile-apps) .group:hover > .app-icon,
|
|
846
|
+
:is(.launchpad-panel, .cloud-mobile-apps) .app-icon:hover {
|
|
847
847
|
background:
|
|
848
848
|
linear-gradient(var(--icon-face), var(--icon-face)) padding-box,
|
|
849
849
|
linear-gradient(180deg, var(--rim-top-hover), var(--rim-bottom-hover)) border-box;
|
package/src/styles/global.css
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
@import "./base-popover.css";
|
|
30
30
|
@import "./effects.css";
|
|
31
31
|
@import "./input.css";
|
|
32
|
+
@import "./resource-search.css";
|
|
32
33
|
|
|
33
34
|
@theme {
|
|
34
35
|
--color-dark: rgb(9 9 11);
|
|
@@ -63,6 +64,12 @@
|
|
|
63
64
|
);
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
/* Full-page workspaces own their inner scrolling. Stop iframe scroll gestures
|
|
68
|
+
from rubber-banding the outer viewport or triggering pull-to-refresh. */
|
|
69
|
+
html:has(.cloud-app-canvas[data-layout-full-page="true"]) {
|
|
70
|
+
overscroll-behavior: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
.cloud-app-canvas > :is(.layout-rail, .layout-shell-content) {
|
|
67
74
|
gap: var(--ui-space-shell);
|
|
68
75
|
}
|
|
@@ -266,3 +273,50 @@ solid-island {
|
|
|
266
273
|
transition-duration: 0.01ms !important;
|
|
267
274
|
}
|
|
268
275
|
}
|
|
276
|
+
|
|
277
|
+
/* Cloud has exactly two shell modes: mobile below lg, desktop from lg. */
|
|
278
|
+
@media (max-width: 1023px) {
|
|
279
|
+
.cloud-app-canvas {
|
|
280
|
+
padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
|
|
281
|
+
background: var(--ui-surface);
|
|
282
|
+
}
|
|
283
|
+
.cloud-app-canvas > .layout-shell-content { gap: 0; }
|
|
284
|
+
.cloud-app-canvas .layout-header {
|
|
285
|
+
border-radius: 0; border: 0; border-bottom: 1px solid var(--ui-border);
|
|
286
|
+
box-shadow: none !important;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.cloud-mobile-menu .k2b-panel-dialog__header {
|
|
292
|
+
gap: .5rem;
|
|
293
|
+
padding-inline: 1rem;
|
|
294
|
+
background: var(--ui-surface);
|
|
295
|
+
}
|
|
296
|
+
.cloud-mobile-menu .k2b-bottom-sheet__handle { background: var(--ui-surface); }
|
|
297
|
+
/* The app name is the first segment; retain a heading for the dialog's name. */
|
|
298
|
+
.cloud-mobile-menu .k2b-panel-dialog__heading {
|
|
299
|
+
position: absolute; width: 1px; height: 1px; overflow: hidden;
|
|
300
|
+
clip-path: inset(50%); white-space: nowrap;
|
|
301
|
+
}
|
|
302
|
+
.cloud-mobile-menu .k2b-panel-dialog__actions { flex: 1; min-width: 0; }
|
|
303
|
+
.cloud-mobile-menu__switch { width: 100%; flex-wrap: nowrap; }
|
|
304
|
+
.cloud-mobile-menu__switch button { flex: 1; min-width: 0; min-height: 3rem; white-space: nowrap; }
|
|
305
|
+
.cloud-mobile-menu__switch button > span { overflow: hidden; text-overflow: ellipsis; }
|
|
306
|
+
.cloud-mobile-menu .k2b-dialog__close { width: 2.75rem; height: 2.75rem; flex-shrink: 0; }
|
|
307
|
+
|
|
308
|
+
.cloud-mobile-menu .k2b-panel-dialog__body { padding: .75rem 1rem 1rem; }
|
|
309
|
+
.cloud-mobile-apps { display: flex; flex-direction: column; gap: 1rem; }
|
|
310
|
+
.cloud-mobile-apps .launchpad-tools { display: flex; align-items: center; gap: .5rem; }
|
|
311
|
+
.cloud-mobile-apps .launchpad-tools > :first-child { flex: 1; min-width: 0; }
|
|
312
|
+
.cloud-mobile-apps .launchpad-customize { margin: 0; flex-shrink: 0; }
|
|
313
|
+
.cloud-mobile-apps .launchpad-apps-grid {
|
|
314
|
+
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem .5rem;
|
|
315
|
+
}
|
|
316
|
+
.cloud-mobile-apps .launchpad-app { width: auto; padding: .5rem .125rem; gap: .6rem; }
|
|
317
|
+
.cloud-mobile-apps .app-icon { width: 3.5rem; height: 3.5rem; border-radius: 1.1rem; font-size: 1.75rem; }
|
|
318
|
+
.cloud-mobile-apps .launchpad-app > span:last-child {
|
|
319
|
+
width: 100%; white-space: normal; overflow-wrap: anywhere; font-size: .8125rem; line-height: 1.3;
|
|
320
|
+
}
|
|
321
|
+
.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; }
|
|
322
|
+
.cloud-mobile-profile :is(a, button) { min-height: 2.75rem; white-space: normal; text-align: start; justify-content: flex-start; }
|