@k2b/cloud 0.40.0 → 0.42.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 +9 -5
- package/src/_internal/bounded-json.ts +14 -2
- package/src/_internal/capabilities.ts +87 -8
- package/src/_internal/capability-reviews.ts +6 -0
- package/src/_internal/capability-sentences.ts +2 -1
- package/src/_internal/capability-timeout.ts +71 -0
- package/src/_internal/define-app.ts +158 -92
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/http-drain.ts +56 -0
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +16 -17
- package/src/access/PrincipalPicker.tsx +45 -10
- package/src/access/messages.ts +8 -0
- package/src/access/principal-label.ts +18 -0
- package/src/access/ui.ts +1 -0
- package/src/account/EntitySearch.tsx +5 -4
- package/src/ai/action-review.ts +2 -1
- package/src/ai/app-skill-store.ts +1 -1
- package/src/ai/capabilities.ts +21 -3
- package/src/ai/capability-execution.ts +14 -12
- package/src/ai/chat/action-card.ts +76 -4
- package/src/ai/chat/action-messages.ts +14 -0
- package/src/ai/chat/action-question.browser-harness.tsx +12 -0
- package/src/ai/chat/action-question.tsx +214 -23
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +1 -26
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/client/controller.ts +150 -32
- package/src/ai/client/transport.ts +14 -2
- package/src/ai/code-capability-routes.ts +27 -23
- package/src/ai/code-runtime-tools.ts +14 -16
- package/src/ai/code-source-tools.ts +16 -18
- package/src/ai/executor.ts +64 -21
- package/src/ai/fetch-file-tool.ts +67 -76
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/http.ts +1 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/migrate.ts +28 -0
- package/src/ai/open-questions.ts +168 -0
- package/src/ai/projects.ts +1 -2
- package/src/ai/protocol.ts +2 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +12 -5
- package/src/ai/review-content.ts +77 -0
- package/src/ai/routes.ts +119 -35
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skill-tool.ts +17 -6
- package/src/ai/skills-routes.ts +72 -27
- package/src/ai/skills.ts +139 -60
- package/src/ai/store.ts +184 -31
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +12 -6
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +25 -4
- package/src/ai/usage.ts +2 -2
- package/src/api/admin-core-settings.ts +15 -2
- package/src/api/admin-geo.ts +54 -0
- package/src/api/admin-linux-identities.ts +6 -3
- package/src/api/admin-outgoing-mail.ts +29 -15
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/approvals-loader.ts +3 -3
- package/src/api/approvals.ts +51 -0
- package/src/api/apps.ts +4 -2
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +54 -18
- package/src/api/capabilities.ts +258 -181
- package/src/api/capability-streams.ts +21 -13
- package/src/api/cli-plugins.ts +4 -1
- package/src/api/geo-glyphs.ts +11 -0
- package/src/api/geo.ts +128 -0
- package/src/api/help.ts +12 -4
- package/src/api/index.ts +5 -0
- package/src/api/mcp.ts +73 -41
- package/src/api/me-panel-consent.ts +8 -5
- package/src/api/me.ts +79 -3
- package/src/api/search.ts +21 -14
- package/src/api/sync-ops.ts +19 -13
- package/src/api/widgets.ts +42 -18
- package/src/approvals/Approvals.tsx +30 -66
- package/src/approvals/approval-card.ts +68 -0
- package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
- package/src/browser/CloudResourceSearch.tsx +3 -13
- package/src/browser/PanelLeaveNotice.tsx +18 -0
- package/src/browser/navigation-search.ts +78 -25
- package/src/browser/panel-leave.ts +69 -0
- package/src/browser/panel-messages.ts +12 -0
- package/src/browser/panel-slot.ts +173 -24
- package/src/browser/panel.ts +3 -0
- package/src/browser/resource-search-messages.ts +2 -0
- package/src/capabilities/client.ts +28 -1
- package/src/capabilities/executions.ts +22 -3
- package/src/capabilities/geo-client.ts +57 -0
- package/src/capabilities/response.ts +2 -1
- package/src/capabilities/review-storage.ts +24 -0
- package/src/capabilities/reviews.ts +17 -3
- package/src/capabilities/server.ts +36 -16
- package/src/cli/access.ts +15 -23
- package/src/cli/admin/linux.ts +13 -2
- package/src/cli/admin/outgoing-mail.ts +26 -4
- package/src/cli/capabilities.ts +19 -0
- package/src/config/env.ts +0 -6
- package/src/contracts/app-access.ts +48 -0
- package/src/contracts/app-approval.ts +3 -3
- package/src/contracts/app.ts +4 -1
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/avatar.ts +186 -0
- package/src/contracts/capabilities.ts +149 -55
- package/src/contracts/geo-admin.ts +127 -0
- package/src/contracts/geo-map.ts +19 -0
- package/src/contracts/geo-route.ts +168 -0
- package/src/contracts/geo.ts +248 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/outgoing-mail.ts +17 -4
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +3 -0
- package/src/events/app-access.ts +68 -0
- package/src/events/live-delivery.ts +128 -0
- package/src/events/live.ts +14 -130
- package/src/geo/CloudMap.tsx +95 -0
- package/src/geo/map-messages.ts +17 -0
- package/src/geo/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server/client-ip.ts +36 -0
- package/src/server/index.ts +3 -4
- package/src/server/middleware/app-access.ts +312 -0
- package/src/server/middleware/auth.ts +5 -3
- package/src/server/middleware/invocation.ts +11 -0
- package/src/server/middleware/rate-limit.ts +4 -4
- package/src/server/services/access.ts +13 -9
- package/src/server/services/index.ts +0 -2
- package/src/server/services/services.ts +0 -2
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/posix.ts +21 -7
- package/src/services/accounts/users.ts +12 -1
- package/src/services/allowed-network-fetch.ts +16 -10
- package/src/services/app-access-metrics.ts +24 -0
- package/src/services/app-access.ts +123 -0
- package/src/services/app-discovery.ts +27 -0
- package/src/services/approvals/content.ts +40 -0
- package/src/services/approvals/device.ts +49 -33
- package/src/services/approvals/index.ts +15 -6
- package/src/services/approvals/list.ts +6 -11
- package/src/services/approvals/question.ts +72 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/downstream-budget.ts +87 -0
- package/src/services/geo/admin.ts +262 -0
- package/src/services/geo/bucket.ts +67 -0
- package/src/services/geo/cache.ts +57 -0
- package/src/services/geo/costs.ts +5 -0
- package/src/services/geo/errors.ts +41 -0
- package/src/services/geo/fixtures/README.md +17 -0
- package/src/services/geo/fixtures/along.json +355 -0
- package/src/services/geo/fixtures/autocomplete.json +218 -0
- package/src/services/geo/fixtures/geocode.json +129 -0
- package/src/services/geo/fixtures/places.json +355 -0
- package/src/services/geo/fixtures/reverse.json +61 -0
- package/src/services/geo/fixtures/routing.json +57 -0
- package/src/services/geo/geoapify-route.ts +93 -0
- package/src/services/geo/geoapify.ts +281 -0
- package/src/services/geo/ledger.ts +122 -0
- package/src/services/geo/map.ts +160 -0
- package/src/services/geo/migrate.ts +25 -0
- package/src/services/geo/polyline.ts +184 -0
- package/src/services/geo/route-chain.ts +145 -0
- package/src/services/geo/route-costs.ts +9 -0
- package/src/services/geo/route-geometry.ts +39 -0
- package/src/services/geo/route-plan.ts +172 -0
- package/src/services/geo/route-service.ts +433 -0
- package/src/services/geo/service.ts +300 -0
- package/src/services/geo/settings.ts +49 -0
- package/src/services/help/index.ts +16 -7
- package/src/services/help/types.ts +6 -1
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/issue-invocation.ts +67 -0
- package/src/services/identity/key-ring.ts +5 -3
- package/src/services/identity/runtime-config.ts +23 -0
- package/src/services/index.ts +11 -0
- package/src/services/mandates/index.ts +40 -3
- package/src/services/network-security.ts +6 -3
- package/src/services/network-transport.ts +99 -0
- package/src/services/notifications/catalog.ts +3 -2
- package/src/services/notifications/runtime.ts +1 -0
- package/src/services/notifications/web-push-transport.ts +45 -38
- package/src/services/outgoing-mail/dispatcher.ts +6 -1
- package/src/services/outgoing-mail/index.ts +55 -1
- package/src/services/outgoing-mail/messages.ts +82 -3
- package/src/services/outgoing-mail/resolve-host.ts +2 -2
- package/src/services/outgoing-mail/retention.ts +13 -1
- package/src/services/outgoing-mail/store.ts +36 -6
- package/src/services/outgoing-mail/test-send.ts +34 -4
- package/src/services/outgoing-mail/transport.ts +1 -1
- package/src/services/pdf/gotenberg.ts +11 -38
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/pdf/template-preview.ts +1 -1
- package/src/services/public-http.ts +28 -40
- package/src/services/session/index.ts +203 -29
- package/src/services/settings/app.ts +4 -1
- package/src/services/settings/core-settings.ts +88 -0
- package/src/services/settings/defaults.ts +8 -2
- package/src/services/settings/errors.ts +11 -0
- package/src/services/settings/geo-budget.ts +40 -0
- package/src/services/settings/index.ts +4 -3
- package/src/services/settings/store.ts +25 -17
- package/src/services/weather/geo.ts +29 -111
- package/src/services/weather/index.ts +4 -3
- package/src/services/weather/locations.ts +8 -5
- package/src/services/weather/migrate.ts +1 -0
- package/src/services/weather/settings.ts +0 -16
- package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
- package/src/shared/avatar-art.ts +400 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/network-address.ts +22 -3
- package/src/shared/template-rendering.ts +36 -4
- package/src/shared-text/documents.ts +1 -1
- package/src/shared-text/index.ts +2 -0
- package/src/shared-text/log.ts +51 -3
- package/src/shared-text/snapshots.ts +340 -0
- package/src/ssr/AdminLayout.tsx +4 -2
- package/src/ssr/CloudShell.island.tsx +6 -0
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +65 -15
- package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +185 -542
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/PanelRuntime.island.tsx +11 -7
- package/src/ssr/PwaLayout.tsx +2 -2
- package/src/ssr/RailApps.tsx +6 -0
- package/src/ssr/ShellRuntime.island.tsx +23 -7
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/admin-navigation.ts +1 -0
- package/src/ssr/app-badges.ts +6 -1
- package/src/ssr/compact-shell.browser-harness.ts +44 -5
- package/src/ssr/help.ts +7 -5
- package/src/ssr/index.ts +2 -1
- package/src/ssr/islands/index.ts +3 -4
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/mobile-menu-history.ts +4 -0
- package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
- package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
- package/src/ssr/page-swap.browser-harness.tsx +177 -0
- package/src/ssr/page-swap.ts +28 -0
- package/src/ssr/page-trail-runtime.ts +32 -13
- package/src/ssr/panel-consent.ts +2 -2
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +3 -0
- package/src/ssr/platform-messages.ts +8 -0
- package/src/ssr/runtime.ts +17 -0
- package/src/ssr/shell-data.tsx +10 -2
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +15 -0
- package/src/styles/global.css +70 -6
- package/src/server/services/geo.ts +0 -215
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { onCleanup, onMount } from "solid-js";
|
|
2
|
+
|
|
3
|
+
/** A real column island deliberately using raw history, to exercise install order. */
|
|
4
|
+
export default function Column(props: { page: string; historyProbe?: boolean }) {
|
|
5
|
+
onMount(() => {
|
|
6
|
+
const raw = () => {
|
|
7
|
+
document.documentElement.dataset.rawPops = String(Number(document.documentElement.dataset.rawPops ?? 0) + 1);
|
|
8
|
+
};
|
|
9
|
+
window.addEventListener("popstate", raw);
|
|
10
|
+
onCleanup(() => window.removeEventListener("popstate", raw));
|
|
11
|
+
if (props.historyProbe) history.pushState({ fixtureColumn: true }, "", `${location.pathname}?entry=column`);
|
|
12
|
+
document.documentElement.dataset.columnMounted = props.page;
|
|
13
|
+
});
|
|
14
|
+
return <output data-column-mounted>{props.page}</output>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { onCleanup, onMount } from "solid-js";
|
|
2
|
+
import { getContext, onContextChange } from "../../browser/panel";
|
|
3
|
+
|
|
4
|
+
export default function ProviderProbe() {
|
|
5
|
+
onMount(() => {
|
|
6
|
+
const show = () => {
|
|
7
|
+
const output = document.getElementById("provider-context");
|
|
8
|
+
if (output) output.textContent = JSON.stringify(getContext());
|
|
9
|
+
};
|
|
10
|
+
show();
|
|
11
|
+
onCleanup(onContextChange(show));
|
|
12
|
+
// This token belongs to the provider window, independently of the host DOM.
|
|
13
|
+
Reflect.set(window, "fixtureProviderToken", crypto.randomUUID());
|
|
14
|
+
document.documentElement.dataset.providerMounted = "true";
|
|
15
|
+
const abort = new AbortController();
|
|
16
|
+
void fetch("/fixture/pending", { signal: abort.signal })
|
|
17
|
+
.then((response) => response.text())
|
|
18
|
+
.then((text) => {
|
|
19
|
+
const output = document.getElementById("pending-result");
|
|
20
|
+
if (output) output.textContent = text;
|
|
21
|
+
})
|
|
22
|
+
.catch((error: unknown) => {
|
|
23
|
+
if (!abort.signal.aborted) {
|
|
24
|
+
const output = document.getElementById("pending-result");
|
|
25
|
+
if (output) output.textContent = String(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
onCleanup(() => abort.abort());
|
|
29
|
+
});
|
|
30
|
+
return <output data-provider-probe />;
|
|
31
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { defineApp } from "../_internal/define-app";
|
|
3
|
+
import { person } from "../_internal/define-app.test-fixture";
|
|
4
|
+
import type { RuntimeAppMeta } from "../contracts/app";
|
|
5
|
+
import type { User } from "../contracts/shared";
|
|
6
|
+
import { type AuthContext, auth } from "../server/middleware/auth";
|
|
7
|
+
import Layout from "./Layout";
|
|
8
|
+
import PanelLayout from "./PanelLayout";
|
|
9
|
+
import Column from "./page-swap-fixture/Column.island";
|
|
10
|
+
import ProviderProbe from "./page-swap-fixture/ProviderProbe.island";
|
|
11
|
+
import type { RuntimeContext } from "./runtime";
|
|
12
|
+
|
|
13
|
+
const probe = defineApp({
|
|
14
|
+
id: "fixture",
|
|
15
|
+
name: "Fixture",
|
|
16
|
+
icon: "ti ti-box",
|
|
17
|
+
description: "Page swap browser fixture",
|
|
18
|
+
baseUrl: "http://fixture:3000",
|
|
19
|
+
routes: ["/app/fixture"],
|
|
20
|
+
pageSwap: true,
|
|
21
|
+
});
|
|
22
|
+
const apps: RuntimeAppMeta[] = [
|
|
23
|
+
{
|
|
24
|
+
id: "fixture",
|
|
25
|
+
name: "Fixture",
|
|
26
|
+
icon: "ti ti-box",
|
|
27
|
+
description: "Fixture",
|
|
28
|
+
routes: ["/app/fixture"],
|
|
29
|
+
nav: { href: "/app/fixture", section: "primary" },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "core",
|
|
33
|
+
name: "Provider",
|
|
34
|
+
icon: "ti ti-box",
|
|
35
|
+
description: "Provider",
|
|
36
|
+
routes: ["/app/provider"],
|
|
37
|
+
panel: { href: "/app/provider/panel", label: "Provider", icon: "ti ti-box" },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "other",
|
|
41
|
+
name: "Other",
|
|
42
|
+
icon: "ti ti-box",
|
|
43
|
+
description: "Other",
|
|
44
|
+
routes: ["/app/other"],
|
|
45
|
+
nav: { href: "/app/other", section: "primary" },
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
// The route policy decides alone; these pages never render their content for the fixture person.
|
|
49
|
+
const policies = new Hono<AuthContext>()
|
|
50
|
+
.get("/protected-login", auth.requireRole("user", probe.ssr.access), ...probe.ssr(() => () => <p>Unreachable</p>))
|
|
51
|
+
.get("/forbidden", auth.requireRole("admin", probe.ssr.access), ...probe.ssr(() => () => <p>Unreachable</p>))
|
|
52
|
+
.get("/revoked", auth.requireRole("admin", probe.ssr.access), ...probe.ssr(() => () => <p>Unreachable</p>));
|
|
53
|
+
export type FixtureEnvironment = { Variables: AuthContext["Variables"] & { runtime: RuntimeContext } };
|
|
54
|
+
export const server = new Hono<FixtureEnvironment>()
|
|
55
|
+
.use(async (c, next) => {
|
|
56
|
+
c.set("runtime", { apps });
|
|
57
|
+
if (!c.req.path.endsWith("/protected-login")) {
|
|
58
|
+
const id = c.req.header("cookie")?.match(/(?:^|;\s*)fixture_person=([^;]+)/)?.[1];
|
|
59
|
+
const user: User = { ...person, ...(id ? { id } : {}) };
|
|
60
|
+
c.set("user", user);
|
|
61
|
+
}
|
|
62
|
+
await next();
|
|
63
|
+
})
|
|
64
|
+
.get(
|
|
65
|
+
"/app/provider/panel",
|
|
66
|
+
...probe.ssr<FixtureEnvironment>((c) => () => (
|
|
67
|
+
<PanelLayout c={c} title="Provider">
|
|
68
|
+
<label>
|
|
69
|
+
Draft
|
|
70
|
+
<textarea aria-label="Provider draft" />
|
|
71
|
+
</label>
|
|
72
|
+
<output id="provider-context" />
|
|
73
|
+
<output id="pending-result" />
|
|
74
|
+
<ProviderProbe />
|
|
75
|
+
</PanelLayout>
|
|
76
|
+
)),
|
|
77
|
+
)
|
|
78
|
+
.get("/app/fixture/file", (c) => c.body("Fixture file", 200, { "content-type": "text/plain" }))
|
|
79
|
+
.get("/api/fixture", (c) => c.json({ fixture: true }))
|
|
80
|
+
.get("/app/fixture/download", (c) =>
|
|
81
|
+
c.body("Download", 200, { "content-type": "application/octet-stream", "content-disposition": 'attachment; filename="fixture.txt"' }),
|
|
82
|
+
)
|
|
83
|
+
.route("/app/fixture", policies)
|
|
84
|
+
.get(
|
|
85
|
+
"/auth/login",
|
|
86
|
+
...probe.ssr<FixtureEnvironment>((c) => () => (
|
|
87
|
+
<Layout c={c} title="Sign in" panel={false}>
|
|
88
|
+
<h1>Sign in to continue</h1>
|
|
89
|
+
</Layout>
|
|
90
|
+
)),
|
|
91
|
+
)
|
|
92
|
+
.get(
|
|
93
|
+
"/app/other",
|
|
94
|
+
...probe.ssr<FixtureEnvironment>((c) => () => (
|
|
95
|
+
<Layout c={c} title="Other">
|
|
96
|
+
<h1>Other app</h1>
|
|
97
|
+
</Layout>
|
|
98
|
+
)),
|
|
99
|
+
)
|
|
100
|
+
.get(
|
|
101
|
+
"/app/fixture/:page?",
|
|
102
|
+
...probe.ssr<FixtureEnvironment>((c) => {
|
|
103
|
+
const page = c.req.param("page") ?? "A";
|
|
104
|
+
const label =
|
|
105
|
+
page === "B"
|
|
106
|
+
? "Full page B"
|
|
107
|
+
: page === "focus"
|
|
108
|
+
? "Focus editor"
|
|
109
|
+
: page === "order"
|
|
110
|
+
? "Order probe"
|
|
111
|
+
: page === "C"
|
|
112
|
+
? "Regular page C"
|
|
113
|
+
: "Regular page A";
|
|
114
|
+
return () => (
|
|
115
|
+
<Layout
|
|
116
|
+
c={c}
|
|
117
|
+
title={page === "A" ? label : [{ title: "Regular page A", href: "/app/fixture/A" }, { title: label }]}
|
|
118
|
+
fullPage={page === "B"}
|
|
119
|
+
fullWidth={page === "B"}
|
|
120
|
+
focusMode={page === "focus"}
|
|
121
|
+
help={[`help-${page.toLowerCase()}`]}
|
|
122
|
+
context={{ scope: { type: "fixture.page", id: page } }}
|
|
123
|
+
>
|
|
124
|
+
<h1>{label}</h1>
|
|
125
|
+
<Column page={page} historyProbe={page === "order"} />
|
|
126
|
+
<a id="to-A" href="/app/fixture/A">
|
|
127
|
+
A
|
|
128
|
+
</a>
|
|
129
|
+
<a id="to-B" href="/app/fixture/B">
|
|
130
|
+
B
|
|
131
|
+
</a>
|
|
132
|
+
<a id="to-C" href="/app/fixture/C">
|
|
133
|
+
C
|
|
134
|
+
</a>
|
|
135
|
+
<a id="to-order" href="/app/fixture/order">
|
|
136
|
+
Order
|
|
137
|
+
</a>
|
|
138
|
+
<a id="to-focus" href="/app/fixture/focus">
|
|
139
|
+
Focus
|
|
140
|
+
</a>
|
|
141
|
+
<a id="to-other" href="/app/other">
|
|
142
|
+
Other app
|
|
143
|
+
</a>
|
|
144
|
+
<a id="to-api" href="/api/fixture">
|
|
145
|
+
API
|
|
146
|
+
</a>
|
|
147
|
+
<a id="to-file" href="/app/fixture/file">
|
|
148
|
+
File
|
|
149
|
+
</a>
|
|
150
|
+
<a id="to-download" href="/app/fixture/download" download="">
|
|
151
|
+
Download
|
|
152
|
+
</a>
|
|
153
|
+
<a id="to-protected-login" href="/app/fixture/protected-login">
|
|
154
|
+
Sign-in policy
|
|
155
|
+
</a>
|
|
156
|
+
<a id="to-forbidden" href="/app/fixture/forbidden">
|
|
157
|
+
Forbidden policy
|
|
158
|
+
</a>
|
|
159
|
+
<a id="to-revoked" href="/app/fixture/revoked">
|
|
160
|
+
Revoked access
|
|
161
|
+
</a>
|
|
162
|
+
<form action="/app/fixture/A" method="get">
|
|
163
|
+
<input name="query" value="submitted" />
|
|
164
|
+
<button id="submit" type="submit">
|
|
165
|
+
Submit
|
|
166
|
+
</button>
|
|
167
|
+
</form>
|
|
168
|
+
<div style="height:1600px" />
|
|
169
|
+
<ul>
|
|
170
|
+
<li id="row-B">
|
|
171
|
+
<a href="/app/fixture/B">B row</a>
|
|
172
|
+
</li>
|
|
173
|
+
</ul>
|
|
174
|
+
</Layout>
|
|
175
|
+
);
|
|
176
|
+
}),
|
|
177
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const builds = new WeakMap<object, string>();
|
|
4
|
+
|
|
5
|
+
/** Register only requests of applications that opted into page swaps. */
|
|
6
|
+
export const registerPageSwap = (context: object, build: string): void => {
|
|
7
|
+
builds.set(context, build);
|
|
8
|
+
};
|
|
9
|
+
export const getPageSwapBuild = (context: object): string | undefined => builds.get(context);
|
|
10
|
+
|
|
11
|
+
export type PageSwapParts = {
|
|
12
|
+
app: string | undefined;
|
|
13
|
+
kind: "app" | "anonymous";
|
|
14
|
+
user: string | null;
|
|
15
|
+
appSession: boolean;
|
|
16
|
+
locale: string;
|
|
17
|
+
theme: "light" | "dark" | undefined;
|
|
18
|
+
build: string;
|
|
19
|
+
/** The request-scoped inputs of the shell that stays: navigation catalogs, profile, panel providers, announcements and Help. */
|
|
20
|
+
shell: unknown;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Opaque identity of the document that stays around the replaced content column. */
|
|
24
|
+
export const pageSwapSignature = (parts: PageSwapParts): string =>
|
|
25
|
+
createHash("sha256")
|
|
26
|
+
.update(JSON.stringify([parts.app, parts.kind, parts.user, parts.appSession, parts.locale, parts.theme, parts.build, parts.shell]))
|
|
27
|
+
.digest("hex")
|
|
28
|
+
.slice(0, 32);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* to the page itself. After returning, the row that led down is marked.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { swapPage } from "@k2b/ssr/nav";
|
|
10
11
|
import { PAGE_TRAIL_EVENT, readPageTrail } from "@k2b/ui";
|
|
11
12
|
import { registerContextAwareCommand } from "../browser/command-bridge";
|
|
12
13
|
import { PAGE_UP_EVENT, type PageUpDetail } from "./layout-runtime";
|
|
@@ -168,7 +169,10 @@ export const goUp = (href: string): boolean => {
|
|
|
168
169
|
const memory = read();
|
|
169
170
|
memory.returning = { url: destination, from: here() };
|
|
170
171
|
write(memory);
|
|
171
|
-
|
|
172
|
+
const canvas = document.querySelector<HTMLElement>(".cloud-app-canvas[data-nav-shell]");
|
|
173
|
+
const scope = canvas?.dataset.navScope;
|
|
174
|
+
if (scope && url.origin === location.origin && (url.pathname === scope || url.pathname.startsWith(`${scope}/`))) void swapPage(url.href);
|
|
175
|
+
else location.assign(url.href);
|
|
172
176
|
return true;
|
|
173
177
|
};
|
|
174
178
|
|
|
@@ -208,7 +212,10 @@ const restoreReturn = (visit: Visit | undefined, from: URL) => {
|
|
|
208
212
|
const pending = new Map((visit?.regions ?? []).map((region) => [region.key, region]));
|
|
209
213
|
let marked: HTMLElement | null = null;
|
|
210
214
|
let frames = 0;
|
|
215
|
+
// A page swap that replaces this column ends its restore; the next page is not the one returned to.
|
|
216
|
+
const column = document.querySelector("[data-nav-root]");
|
|
211
217
|
const step = () => {
|
|
218
|
+
if (column && !column.isConnected) return;
|
|
212
219
|
for (const [key, region] of pending) {
|
|
213
220
|
// A region is restored once both axes hold; content that renders late can still widen or lengthen it.
|
|
214
221
|
if (key === "@window") {
|
|
@@ -263,8 +270,10 @@ const offerPageUp = (labels: PageUpLabels): (() => void) => {
|
|
|
263
270
|
};
|
|
264
271
|
update();
|
|
265
272
|
window.addEventListener(PAGE_TRAIL_EVENT, update);
|
|
273
|
+
window.addEventListener("ssr:nav-swapped", update);
|
|
266
274
|
return () => {
|
|
267
275
|
window.removeEventListener(PAGE_TRAIL_EVENT, update);
|
|
276
|
+
window.removeEventListener("ssr:nav-swapped", update);
|
|
268
277
|
unregister();
|
|
269
278
|
};
|
|
270
279
|
};
|
|
@@ -277,19 +286,26 @@ const offerPageUp = (labels: PageUpLabels): (() => void) => {
|
|
|
277
286
|
*/
|
|
278
287
|
export const installPageTrailRuntime = (labels: PageUpLabels): (() => void) => {
|
|
279
288
|
const stopPageUp = offerPageUp(labels);
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
const restoreArrivingPage = () => {
|
|
290
|
+
const memory = read();
|
|
291
|
+
const returning = memory.returning;
|
|
292
|
+
if (returning) {
|
|
293
|
+
delete memory.returning;
|
|
294
|
+
write(memory);
|
|
295
|
+
const current = resolve(here());
|
|
296
|
+
if (pathOf(resolve(returning.url)) === pathOf(current) && resolve(returning.url).search === current.search)
|
|
297
|
+
restoreReturn(
|
|
298
|
+
memory.visits.find((visit) => visit.url === returning.url),
|
|
299
|
+
resolve(returning.from),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
restoreArrivingPage();
|
|
304
|
+
window.addEventListener("ssr:nav-swapped", restoreArrivingPage);
|
|
292
305
|
const onCapture = () => rememberPage();
|
|
306
|
+
const onNavigate = (event: NavigateEvent) => {
|
|
307
|
+
if (event.navigationType === "traverse") rememberPage();
|
|
308
|
+
};
|
|
293
309
|
const onKey = (event: KeyboardEvent) => {
|
|
294
310
|
if (event.key === "Enter") rememberPage();
|
|
295
311
|
};
|
|
@@ -303,11 +319,14 @@ export const installPageTrailRuntime = (labels: PageUpLabels): (() => void) => {
|
|
|
303
319
|
document.addEventListener("keydown", onKey, true);
|
|
304
320
|
document.addEventListener("click", onClick);
|
|
305
321
|
window.addEventListener("pagehide", onCapture);
|
|
322
|
+
window.navigation?.addEventListener("navigate", onNavigate);
|
|
306
323
|
return () => {
|
|
324
|
+
window.removeEventListener("ssr:nav-swapped", restoreArrivingPage);
|
|
307
325
|
stopPageUp();
|
|
308
326
|
document.removeEventListener("click", onCapture, true);
|
|
309
327
|
document.removeEventListener("keydown", onKey, true);
|
|
310
328
|
document.removeEventListener("click", onClick);
|
|
311
329
|
window.removeEventListener("pagehide", onCapture);
|
|
330
|
+
window.navigation?.removeEventListener("navigate", onNavigate);
|
|
312
331
|
};
|
|
313
332
|
};
|
package/src/ssr/panel-consent.ts
CHANGED
|
@@ -3,13 +3,13 @@ import { readPanelCookie } from "../_internal/panel-cookie";
|
|
|
3
3
|
import { getLocale } from "../server/locale";
|
|
4
4
|
import { panelContextConsent } from "../services/panel-context-consent";
|
|
5
5
|
import { visiblePanelProviders } from "./app-navigation";
|
|
6
|
-
import {
|
|
6
|
+
import { getDisplayRuntimeContext } from "./runtime";
|
|
7
7
|
|
|
8
8
|
/** One read, only for a host page with the slot open on a consent provider. Fail closed when the store is unavailable. */
|
|
9
9
|
export async function preloadPanelConsent(c: Context): Promise<void> {
|
|
10
10
|
const user = c.get("user");
|
|
11
11
|
if (!user || !c.get("runtime")) return;
|
|
12
|
-
const providers = visiblePanelProviders(
|
|
12
|
+
const providers = visiblePanelProviders(getDisplayRuntimeContext(c).apps, user, getLocale(c));
|
|
13
13
|
const state = readPanelCookie(
|
|
14
14
|
c.req.header("Cookie"),
|
|
15
15
|
providers.map((provider) => provider.id),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Warm action code after mounting; WebKit has no requestIdleCallback. */
|
|
2
|
+
export function panelIdle(work: () => void): () => void {
|
|
3
|
+
if (typeof requestIdleCallback === "function") {
|
|
4
|
+
const id = requestIdleCallback(work);
|
|
5
|
+
return () => cancelIdleCallback(id);
|
|
6
|
+
}
|
|
7
|
+
const id = setTimeout(work, 200);
|
|
8
|
+
return () => clearTimeout(id);
|
|
9
|
+
}
|
|
@@ -77,6 +77,9 @@ export const server = new Hono<Environment>()
|
|
|
77
77
|
<a href="/app/fixture/next" id="leave-panel">
|
|
78
78
|
Open in the app
|
|
79
79
|
</a>
|
|
80
|
+
<a href="/app/fixture/panel?guard=multiple&mode=manual&step=2" id="internal-panel">
|
|
81
|
+
Another panel document
|
|
82
|
+
</a>
|
|
80
83
|
</PanelLayout>
|
|
81
84
|
)),
|
|
82
85
|
)
|
|
@@ -16,6 +16,9 @@ export const platformMessages = i18n.define({
|
|
|
16
16
|
panelLoadFailed: "The panel did not load",
|
|
17
17
|
panelNotResponding: ({ label }: { label: string }) => `${label} is not responding.`,
|
|
18
18
|
retry: "Try again",
|
|
19
|
+
panelLeaveResume: "Back to the panel",
|
|
20
|
+
panelLeaveBlocked: "The panel is still open",
|
|
21
|
+
panelLeaveDetail: "Finish or save your work in the panel, then try switching or closing again.",
|
|
19
22
|
panelLoading: ({ label }: { label: string }) => `${label} is loading`,
|
|
20
23
|
enlargePanel: "Enlarge panel",
|
|
21
24
|
shrinkPanel: "Shrink panel",
|
|
@@ -47,6 +50,7 @@ export const platformMessages = i18n.define({
|
|
|
47
50
|
accountOperations: "Operations",
|
|
48
51
|
linuxAccess: "Linux identities",
|
|
49
52
|
pdfRendering: "PDF rendering",
|
|
53
|
+
placesAndMaps: "Places and maps",
|
|
50
54
|
emailTemplates: "Email templates",
|
|
51
55
|
outgoingMail: "Outgoing mail",
|
|
52
56
|
security: "Security",
|
|
@@ -114,6 +118,9 @@ export const platformMessages = i18n.define({
|
|
|
114
118
|
panelLoadFailed: "Das Panel lädt nicht",
|
|
115
119
|
panelNotResponding: ({ label }: { label: string }) => `${label} antwortet nicht.`,
|
|
116
120
|
retry: "Erneut versuchen",
|
|
121
|
+
panelLeaveResume: "Zurück zum Panel",
|
|
122
|
+
panelLeaveBlocked: "Das Panel bleibt geöffnet",
|
|
123
|
+
panelLeaveDetail: "Beende oder speichere deine Arbeit im Panel. Danach kannst du es erneut wechseln oder schließen.",
|
|
117
124
|
panelLoading: ({ label }: { label: string }) => `${label} lädt`,
|
|
118
125
|
enlargePanel: "Panel vergrößern",
|
|
119
126
|
shrinkPanel: "Panel verkleinern",
|
|
@@ -145,6 +152,7 @@ export const platformMessages = i18n.define({
|
|
|
145
152
|
accountOperations: "Betrieb",
|
|
146
153
|
linuxAccess: "Linux-Identitäten",
|
|
147
154
|
pdfRendering: "PDF-Erstellung",
|
|
155
|
+
placesAndMaps: "Orte und Karten",
|
|
148
156
|
emailTemplates: "E-Mail-Vorlagen",
|
|
149
157
|
outgoingMail: "Ausgehende Mail",
|
|
150
158
|
security: "Sicherheit",
|
package/src/ssr/runtime.ts
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Runtime context helpers for SSR components.
|
|
3
3
|
* Extracted from _core/runtime-helpers.ts — only the generic parts.
|
|
4
4
|
*/
|
|
5
|
+
import type { Context } from "hono";
|
|
5
6
|
import type { CloudRuntime } from "../contracts/app";
|
|
6
7
|
import { getLocale } from "../server/locale";
|
|
8
|
+
import { appAccessAuthority } from "../services/app-access";
|
|
9
|
+
import { discoverableApps } from "../services/app-discovery";
|
|
7
10
|
import { resolveAppPresentation, resolveAppPresentations } from "../shared/app-presentation";
|
|
8
11
|
|
|
9
12
|
export type RuntimeContext = CloudRuntime;
|
|
@@ -47,3 +50,17 @@ export const appNameIn = (carrier: RuntimeCarrier, appId: string, locale: string
|
|
|
47
50
|
const app = apps.find((entry) => entry.id === appId);
|
|
48
51
|
return app ? resolveAppPresentation(app, locale).name : fallback;
|
|
49
52
|
};
|
|
53
|
+
|
|
54
|
+
const displayApps = new WeakMap<object, RuntimeContext["apps"]>();
|
|
55
|
+
|
|
56
|
+
/** Called after route authentication, before page data and synchronous layouts render. Routing keeps the full runtime. */
|
|
57
|
+
export const preloadDisplayApps = async (c: Context): Promise<void> => {
|
|
58
|
+
if (!c.get("runtime") || displayApps.has(c)) return;
|
|
59
|
+
displayApps.set(c, await discoverableApps(appAccessAuthority(c), getRuntimeContext(c).apps));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Display-only projection. A caller outside the SSR adapter must preload declared app access first. */
|
|
63
|
+
export const getDisplayRuntimeContext = (carrier: RuntimeCarrier): RuntimeContext => {
|
|
64
|
+
const runtime = getRuntimeContext(carrier);
|
|
65
|
+
return { ...runtime, apps: displayApps.get(carrier) ?? runtime.apps.filter((app) => app.access === undefined) };
|
|
66
|
+
};
|
package/src/ssr/shell-data.tsx
CHANGED
|
@@ -36,15 +36,23 @@ export type ShellData = {
|
|
|
36
36
|
|
|
37
37
|
const ShellDataContext = createContext<() => ShellData>();
|
|
38
38
|
|
|
39
|
-
export function ShellDataProvider(props: { data: ShellData; children: JSX.Element }) {
|
|
39
|
+
export function ShellDataProvider(props: { data: ShellData; skipScript?: boolean; children: JSX.Element }) {
|
|
40
40
|
return (
|
|
41
41
|
<ShellDataContext.Provider value={() => props.data}>
|
|
42
|
-
|
|
42
|
+
{/* Inline, not a component: an extra component would shift the hydration keys of every page. */}
|
|
43
|
+
{!props.skipScript && <script id="cloud-shell-data" type="application/json" innerHTML={shellDataJson(props.data)} />}
|
|
43
44
|
{props.children}
|
|
44
45
|
</ShellDataContext.Provider>
|
|
45
46
|
);
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
const shellDataJson = (data: ShellData) => JSON.stringify(data).replace(/</g, "\\u003c");
|
|
50
|
+
|
|
51
|
+
/** The shell data inside a swapped column, so each page brings its own with it. */
|
|
52
|
+
export function ShellDataScript(props: { data: ShellData }) {
|
|
53
|
+
return <script id="cloud-shell-data" type="application/json" innerHTML={shellDataJson(props.data)} />;
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
export function readShellData(): ShellData | undefined {
|
|
49
57
|
if (typeof document === "undefined") return undefined;
|
|
50
58
|
const text = document.getElementById("cloud-shell-data")?.textContent;
|
package/src/styles/approvals.css
CHANGED
|
@@ -366,6 +366,12 @@
|
|
|
366
366
|
overflow-wrap: anywhere;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
.k2b-ui .cloud-approval-band__reason {
|
|
370
|
+
color: var(--k2b-text-secondary);
|
|
371
|
+
font-size: 0.8125rem;
|
|
372
|
+
overflow-wrap: anywhere;
|
|
373
|
+
}
|
|
374
|
+
|
|
369
375
|
.k2b-ui .cloud-approval-band__text .cloud-approval__origin {
|
|
370
376
|
margin: 0;
|
|
371
377
|
}
|
package/src/styles/effects.css
CHANGED
|
@@ -408,6 +408,21 @@
|
|
|
408
408
|
container: help-shell / inline-size;
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
/* The panel keeps its start and its server article in the document while hidden, so Back needs no client render.
|
|
412
|
+
Its flex and grid surfaces would otherwise override the browser's hidden rule. The start's own wrapper adds no box,
|
|
413
|
+
so its sections keep the start's gap. */
|
|
414
|
+
.help-shell[data-surface="panel"] [hidden] {
|
|
415
|
+
display: none;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.help-shell[data-surface="panel"] [data-help-start-content] {
|
|
419
|
+
display: contents;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.help-shell[data-surface="panel"] [data-help-start-content][hidden] {
|
|
423
|
+
display: none;
|
|
424
|
+
}
|
|
425
|
+
|
|
411
426
|
.help-measure {
|
|
412
427
|
width: 100%;
|
|
413
428
|
max-width: 56.5rem;
|
package/src/styles/global.css
CHANGED
|
@@ -75,6 +75,14 @@ html:has(.cloud-app-canvas[data-layout-full-page="true"]) {
|
|
|
75
75
|
overscroll-behavior: none;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/* A full page binds the canvas to the window at every width. The attribute
|
|
79
|
+
carries it, not a class: a page swap copies the attribute and keeps the
|
|
80
|
+
canvas element, so a regular page and a full page can follow each other. */
|
|
81
|
+
.cloud-app-canvas[data-layout-full-page="true"] {
|
|
82
|
+
height: 100dvh;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
.cloud-app-canvas > :is(.layout-rail, .layout-shell-content) {
|
|
79
87
|
gap: var(--ui-space-shell);
|
|
80
88
|
}
|
|
@@ -428,6 +436,62 @@ solid-island {
|
|
|
428
436
|
}
|
|
429
437
|
}
|
|
430
438
|
|
|
439
|
+
/* Phone overviews: object navigation sits on the shell before the card.
|
|
440
|
+
Only an outer flush workspace with visible stacked navigation hands the
|
|
441
|
+
host's paint to its head/content tracks. The pseudo-element is the first
|
|
442
|
+
grid item, behind the existing regions, without a new stacking context:
|
|
443
|
+
the fixed primary action can still sit above the app bar. */
|
|
444
|
+
/* Negated min-width queries keep the exact boundary after CSS compilation;
|
|
445
|
+
fractional max-width approximations can round back up to 768 px. */
|
|
446
|
+
@media not all and (min-width: 768px) {
|
|
447
|
+
@media not all and (min-width: 48rem) {
|
|
448
|
+
.cloud-app-canvas[data-layout-authenticated] .layout-content-main:has(
|
|
449
|
+
.k2b-app-workspace[data-mobile-surface="flush"]:not(.k2b-app-workspace *, [hidden], [hidden] *)
|
|
450
|
+
> .k2b-app-workspace__sidebar[data-mobile="stacked"]:not([hidden]),
|
|
451
|
+
.k2b-app-workspace[data-mobile-surface="flush"]:not(.k2b-app-workspace *, [hidden], [hidden] *)
|
|
452
|
+
> :is(solid-island, solid-client) > .k2b-app-workspace__sidebar[data-mobile="stacked"]:not([hidden])
|
|
453
|
+
) {
|
|
454
|
+
padding-bottom: 0;
|
|
455
|
+
border: 0;
|
|
456
|
+
border-radius: 0;
|
|
457
|
+
background: transparent;
|
|
458
|
+
box-shadow: none;
|
|
459
|
+
|
|
460
|
+
& .k2b-app-workspace[data-mobile-surface="flush"]:not(.k2b-app-workspace *) {
|
|
461
|
+
--k2b-workspace-inset-bottom: var(--cloud-tab-bar-space);
|
|
462
|
+
|
|
463
|
+
&::before {
|
|
464
|
+
content: "";
|
|
465
|
+
grid-column: 1 / -1;
|
|
466
|
+
grid-row: head-start / -1;
|
|
467
|
+
min-height: 0;
|
|
468
|
+
pointer-events: none;
|
|
469
|
+
border-top: 1px solid transparent;
|
|
470
|
+
border-radius: 1.75rem 1.75rem 0 0;
|
|
471
|
+
background: var(--k2b-surface);
|
|
472
|
+
box-shadow: 0 -10px 30px -24px rgb(15 23 19 / 0.3);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
& > .k2b-app-workspace__head {
|
|
476
|
+
border-radius: 1.75rem 1.75rem 0 0;
|
|
477
|
+
background: transparent;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/* Cloud's page title hides the automatic head on phones. With no
|
|
481
|
+
explicit head, Content itself owns the card's rounded clip. */
|
|
482
|
+
&:not(:has(> .k2b-app-workspace__head[data-row])) > .k2b-app-workspace__content {
|
|
483
|
+
border-radius: 1.75rem 1.75rem 0 0;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.dark .cloud-app-canvas[data-layout-authenticated] .layout-content-main
|
|
489
|
+
.k2b-app-workspace[data-mobile-surface="flush"]:not(.k2b-app-workspace *)::before {
|
|
490
|
+
border-top-color: var(--k2b-border);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
431
495
|
/* The bar and the room it takes switch with `md`, as the header and the rail
|
|
432
496
|
do, so Home stays reachable also where a larger default font size moves
|
|
433
497
|
that switch above 768 px. */
|
|
@@ -732,14 +796,14 @@ solid-island {
|
|
|
732
796
|
}
|
|
733
797
|
|
|
734
798
|
/* The navigation leaves like a hidden sidebar: its track, its resize handle,
|
|
735
|
-
and navigation panes go
|
|
736
|
-
the
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
workspace head's button and
|
|
799
|
+
and navigation panes go. With the sidebar space at 0, the workspace rounds
|
|
800
|
+
the work area's start corners with the card itself. An island may wrap the
|
|
801
|
+
sidebar or Main, as in Grids. This follows the app's host, as the sidebar
|
|
802
|
+
column does: in a host below 64 rem, such as the app beside the panel
|
|
803
|
+
column, the navigation is an overlay behind the workspace head's button and
|
|
804
|
+
stays there. */
|
|
740
805
|
@container k2b-app-area (min-width: 64rem) {
|
|
741
806
|
.cloud-app-canvas[data-shell-focus] .k2b-app-workspace:not(.k2b-app-workspace *) {
|
|
742
|
-
--k2b-workspace-start-radius: var(--k2b-workspace-inner-radius);
|
|
743
807
|
--k2b-workspace-sidebar-space: 0px;
|
|
744
808
|
}
|
|
745
809
|
.cloud-app-canvas[data-shell-focus]
|