From 64661c3ae1005a2ee2cb3840f650af8a8ab3e65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Can=C3=B4?= Date: Thu, 23 Jul 2026 03:04:33 -0300 Subject: [PATCH] feat(dashboard): stable theme token contract and plugin overlay layering (#2415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Gives dashboard integrators (plugin views, embedded panels, theming tools) a supported way to match the dashboard's look and to layer overlay UI correctly — instead of scraping computed styles and guessing z-index values. This implements the CSS-token bridge slice of `docs/proposals/2026-07-01-dashboard-theme-plugin-system.md`. Two additions, both inert unless used: 1. **Documented theme-token contract.** A "Theme tokens" section in `docs/dashboard-guide.md` (referenced from `docs/PLUGIN_AUTHORING.md`) declares the stable set of CSS custom properties — colors, surfaces, status colors — that integrators may read. Tokens resolve to raw color strings (e.g. `#161b22`) in every theme, including the newer ones. A sync test (`theme-token-contract-docs.test.ts`) parses the doc's token table and asserts each documented token has a real definition in the dashboard CSS, so the contract cannot silently drift from the code. 2. **Overlay layering surface.** Overlay-style UI (palettes, pickers, floating panels) currently has no supported way to sit above the floating-window stack — the effective max z-index is runtime state inside `floatingWindowStack.ts`. This PR exposes it: - `--fusion-max-z` on `:root` — kept in sync by `floatingWindowStack` (written at module load and after every `nextFloatingZ()` claim), so it always reflects the true top of the dashboard-managed stack. Boot/floor value is `11001`, chosen to clear the highest statically-declared layer (the body-portaled model-combobox dropdown at `z-index: 11000`). - `#plugin-overlay-root` — an empty, `pointer-events: none` sibling of `#root` stacked at `calc(var(--fusion-max-z) + 1)`. React never renders into it, so it is hydration-safe; integrators portal into it and re-enable pointer events on their own elements. - The layer bands (base UI / floating windows / toasts / dropdown / overlay root) are documented in `styles.css` and the guide, and a guard test (`dashboard-max-z-guard.test.ts`) scans the structural + component CSS and fails if any static `z-index` is ever introduced above the floor — keeping the contract honest as the codebase evolves. ## Behavior No visual or behavioral change for existing users: `floatingWindowStack` still returns the same values from `nextFloatingZ()`; the overlay root is empty and click-through; tokens were already defined — this only documents and guards them. ## Tests - `theme-token-contract-docs.test.ts` — docs ↔ CSS sync (non-tautological: anchored matching against real definitions). - `floatingWindowStack.max-z.test.ts` — `--fusion-max-z` boot value and live tracking as the stack claims z-indexes. - `dashboard-max-z-guard.test.ts` — no static dashboard z-index above the floor (decorative `public/theme-data.css` INT_MAX scanline overlay deliberately excluded; it's non-interactive grain, documented in the test). - Changeset included (`minor`, `category: feature`). Typecheck clean. ## Open question for maintainers The token is named `--fusion-max-z`. The existing scale uses `--z-*` names (`--z-dropdown`, `--z-modal`) on a lower band — happy to rename to `--z-max` / `--z-plugin-overlay` or anything that fits your convention; the name is the only bikeshed here, the sync mechanism is independent of it. ## AI assistance disclosure Parts of this change were authored with AI assistance (Anthropic's Claude); the commit carries a `Co-authored-by` trailer accordingly. Everything was human-reviewed before submission, and the test suite and typecheck were run locally against the current `main`. If squash-merging with a rewritten message, please keep the attribution: ``` Co-authored-by: Claude ``` ## Summary by CodeRabbit - **New Features** - Added stable dashboard theme tokens for consistent plugin/integration styling. - Introduced a dedicated plugin overlay mount point with click-through defaults and an overlay stacking ceiling. - Overlay z-index now stays in sync with floating window layering automatically. - **Documentation** - Added an explicit stable “theme token contract” and “overlay layering contract,” including interaction and z-index usage rules and deprecation expectations. - **Bug Fixes** - Improved reliability of plugin overlay stacking so overlay content renders above intended dashboard layers. - **Tests** - Added guards validating CSS z-index ceilings and enforcing the documented theme token contract. Co-authored-by: Claude --- .changeset/theme-token-contract.md | 7 ++ docs/PLUGIN_AUTHORING.md | 28 +++++++ docs/dashboard-guide.md | 73 +++++++++++++++++++ .../__tests__/dashboard-max-z-guard.test.ts | 40 ++++++++++ .../theme-token-contract-docs.test.ts | 65 +++++++++++++++++ .../floatingWindowStack.max-z.test.ts | 47 ++++++++++++ .../app/components/floatingWindowStack.ts | 29 +++++++- packages/dashboard/app/index.html | 2 + packages/dashboard/app/styles.css | 17 +++++ 9 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 .changeset/theme-token-contract.md create mode 100644 packages/dashboard/app/__tests__/dashboard-max-z-guard.test.ts create mode 100644 packages/dashboard/app/__tests__/theme-token-contract-docs.test.ts create mode 100644 packages/dashboard/app/components/__tests__/floatingWindowStack.max-z.test.ts diff --git a/.changeset/theme-token-contract.md b/.changeset/theme-token-contract.md new file mode 100644 index 0000000000..cf5616a84b --- /dev/null +++ b/.changeset/theme-token-contract.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add stable dashboard theme tokens and plugin overlay layering with --fusion-max-z. +category: feature +dev: `--fusion-max-z` is synced from `floatingWindowStack.ts` with an 11001 floor; `#plugin-overlay-root` is a click-through fixed mount point; the contract is documented and guarded by a docs-to-CSS sync test. diff --git a/docs/PLUGIN_AUTHORING.md b/docs/PLUGIN_AUTHORING.md index d646c74dbf..f9a80dfe7f 100644 --- a/docs/PLUGIN_AUTHORING.md +++ b/docs/PLUGIN_AUTHORING.md @@ -12,6 +12,7 @@ A comprehensive guide to creating Fusion plugins that extend the task board with 6. [Registering Routes](#6-registering-routes) 7. [Registering UI Slots](#7-registering-ui-slots) 8. [Registering Top-Level Dashboard Views](#8-registering-top-level-dashboard-views) + - [Theming & Overlay Layering for Dashboard Views](#theming--overlay-layering-for-dashboard-views) 9. [Registering Agent Runtimes](#9-registering-agent-runtimes) 10. [Plugin Context API Reference](#10-plugin-context-api-reference) 11. [Plugin Lifecycle States](#11-plugin-lifecycle-states) @@ -815,6 +816,33 @@ Project-scoped UI state guidance: - For dependency graph layout, the canonical base key is `fusion-plugin-dependency-graph:positions`. - Do not persist plugin UI state in task metadata or server-side task records. +### Theming & Overlay Layering for Dashboard Views + +Use only the [stable theme token contract](./dashboard-guide.md#stable-theme-token-contract-integrators--plugins) for plugin UI. It provides supported surface, text, spacing, status, motion, and layering variables; internal CSS names may change without notice. + +```css +.my-plugin-panel { + background: var(--surface); + color: var(--text); +} + +.my-plugin-owned-overlay { + position: fixed; + inset: 0; + z-index: calc(var(--fusion-max-z) + 1); + pointer-events: auto; +} +``` + +For overlays that should share Fusion's root stacking context, append the plugin element to the supported `#plugin-overlay-root` mount point: + +```ts +const overlayRoot = document.querySelector("#plugin-overlay-root"); +overlayRoot?.append(pluginOverlayElement); +``` + +The mount point is fixed and click-through; set `pointer-events: auto` on interactive plugin children. Its layer follows `--fusion-max-z` as Fusion's monotonic floating-window stack rises, so plugins do not need to track dashboard window focus in JavaScript. + --- ## 9. Registering Agent Runtimes diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index da054af655..1731e427a1 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -1963,6 +1963,79 @@ Command Center chart surfaces are a stricter token-only zone: `CommandCenter.css Non-Command-Center dashboard CSS uses `--text` as the canonical primary text token. The undefined `--text-primary` alias is forbidden outside `components/command-center/**` and guarded by `packages/dashboard/app/__tests__/text-token-canonicalization.test.ts`. +### Stable theme token contract (integrators & plugins) + +The following curated tokens are the supported dashboard theming contract for integrations and plugin-rendered UI. Each token is defined by `styles.css`; use these names rather than depending on internal or theme-data-only variables. + + +| Token | Stable meaning | +|---|---| +| `--space-xs` | Extra-small spacing step | +| `--space-sm` | Small spacing step | +| `--space-md` | Medium spacing step | +| `--space-lg` | Large spacing step | +| `--space-xl` | Extra-large spacing step | +| `--space-2xl` | Largest shared spacing step | +| `--radius-sm` | Small corner radius | +| `--radius-md` | Medium corner radius | +| `--radius-lg` | Large corner radius | +| `--radius-xl` | Extra-large corner radius | +| `--radius-pill` | Pill-shaped corner radius | +| `--font-primary` | Dashboard UI font stack | +| `--font-mono` | Dashboard monospace font stack | +| `--font-size-xs` | Caption and help text size | +| `--font-size-base` | Default body text size | +| `--shadow-sm` | Subtle elevation shadow | +| `--shadow-md` | Standard elevation shadow | +| `--shadow-lg` | High elevation shadow | +| `--focus-ring` | Subtle focus indicator shadow | +| `--focus-ring-strong` | Emphasized focus indicator shadow | +| `--duration-instant` | Instant motion duration | +| `--duration-fast` | Fast motion duration | +| `--duration-normal` | Standard motion duration | +| `--duration-slow` | Slow motion duration | +| `--transition-instant` | Instant duration and easing shorthand | +| `--transition-fast` | Fast duration and easing shorthand | +| `--transition-normal` | Standard duration and easing shorthand | +| `--transition-slow` | Slow duration and easing shorthand | +| `--bg` | Primary application background | +| `--surface` | Primary raised surface | +| `--card` | Card surface | +| `--card-hover` | Hovered card surface | +| `--surface-hover` | Neutral hovered surface | +| `--bg-secondary` | Secondary application background | +| `--bg-tertiary` | Tertiary application background | +| `--border` | Default border color | +| `--border-subtle` | Low-contrast border color | +| `--border-strong` | High-contrast border color | +| `--text` | Primary text color | +| `--text-muted` | Secondary text color | +| `--text-dim` | De-emphasized text color | +| `--triage` | Triage workflow status color | +| `--todo` | To-do workflow status color | +| `--in-progress` | In-progress workflow status color | +| `--in-review` | In-review workflow status color | +| `--done` | Done workflow status color | +| `--color-success` | Semantic success color | +| `--color-error` | Semantic error color | +| `--color-warning` | Semantic warning color | +| `--color-info` | Semantic informational color | +| `--color-muted` | Semantic muted color | +| `--fusion-max-z` | Live dashboard floating-layer ceiling | + + +Color tokens resolve to raw color strings (e.g. `#161b22`), not shadcn-style HSL triples, so a token can be used directly as a `color`, `background`, or `border` value without wrapping it in `hsl(...)`. + +#### Overlay layering contract + +`--fusion-max-z` is always at least as high as the dashboard-managed floating layers covered by this contract: the page overlay/popover band at 10000–10001, the session-monotonic floating-utility stack starting at 10100, the reserved toast/feedback ceiling at 10500, and the body-portaled model-combobox dropdown at 11000. Its CSS boot value is 11001, one above the tallest static layer. `floatingWindowStack.ts` raises the inline value on `document.documentElement` whenever the utility stack grows beyond that floor, and CSS `var()` references re-resolve automatically. The separate task-detail popup band starting at 220 is intentionally not a source for updates because it remains below the utility band. + +For the simplest integration, append overlay content to `#plugin-overlay-root`. This fixed, viewport-sized mount point uses `z-index: calc(var(--fusion-max-z) + 1)` and is click-through by default; interactive children must set `pointer-events: auto`. A plugin that owns another root stacking context can apply the same z-index expression directly. + +A static mount-point z-index would eventually be overtaken by the unbounded, session-monotonic utility counter. The live custom property is therefore the layering primitive; the mount point is an inert convenience consumer. When empty, it does not alter layout, scrolling, or pointer behavior. + +Tokens in the table are stable. Renaming or removing one requires a deprecation note and a changeset; `theme-token-contract-docs.test.ts` guards that every documented token still has a CSS definition. + ### Theme system diff --git a/packages/dashboard/app/__tests__/dashboard-max-z-guard.test.ts b/packages/dashboard/app/__tests__/dashboard-max-z-guard.test.ts new file mode 100644 index 0000000000..de2699308f --- /dev/null +++ b/packages/dashboard/app/__tests__/dashboard-max-z-guard.test.ts @@ -0,0 +1,40 @@ +/* +FNXC:PluginOverlayLayering 2026-07-23-01:21: +The plugin overlay ceiling only holds if no dashboard-managed surface is painted above it with a +static z-index. Scan the structural and component stylesheets (styles.css plus every +components/*.css, via loadAllAppCss) and assert every literal z-index stays at or below +FUSION_MAX_Z_FLOOR, so `calc(var(--fusion-max-z) + 1)` overlays always win. Custom-property +definitions and var()-driven values are intentionally out of scope: the numeric-literal regex +never matches them. The per-color-theme decorative layers in public/theme-data.css are likewise not +part of this interactive stacking contract and are excluded, consistent with loadAllAppCss. +*/ +import { describe, expect, it } from "vitest"; +import { FUSION_MAX_Z_FLOOR } from "../components/floatingWindowStack"; +import { loadAllAppCss } from "../test/cssFixture"; + +describe("dashboard static z-index ceiling", () => { + it("keeps every literal z-index at or below the plugin overlay floor", () => { + const css = loadAllAppCss(); + + const offenders: Array<{ value: number; declaration: string }> = []; + for (const match of css.matchAll(/z-index\s*:\s*(-?\d+)/g)) { + const value = Number(match[1]); + if (value > FUSION_MAX_Z_FLOOR) { + offenders.push({ value, declaration: match[0] }); + } + } + + expect( + offenders, + `static z-index declarations above FUSION_MAX_Z_FLOOR (${FUSION_MAX_Z_FLOOR}): ${ + offenders.map((o) => o.declaration).join(", ") || "none" + }. Raise --fusion-max-z and FUSION_MAX_Z_FLOOR above the tallest static layer, or route the surface through --fusion-max-z.`, + ).toEqual([]); + }); + + it("finds at least one literal z-index so the guard cannot silently pass on an empty scan", () => { + const css = loadAllAppCss(); + const literals = [...css.matchAll(/z-index\s*:\s*(-?\d+)/g)]; + expect(literals.length).toBeGreaterThan(0); + }); +}); diff --git a/packages/dashboard/app/__tests__/theme-token-contract-docs.test.ts b/packages/dashboard/app/__tests__/theme-token-contract-docs.test.ts new file mode 100644 index 0000000000..23cff1e09d --- /dev/null +++ b/packages/dashboard/app/__tests__/theme-token-contract-docs.test.ts @@ -0,0 +1,65 @@ +/* +FNXC:PluginThemeContract 2026-07-23-01:21: +The integrator-facing token inventory is a curated stability promise, not an informal example list. +Guard its marker block against missing CSS definitions and keep the documented overlay primitive, +stack synchronizer, HTML mount point, and plugin-authoring cross-reference wired together. +*/ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { loadAllAppCss } from "../test/cssFixture"; + +const CONTRACT_START = ""; +const CONTRACT_END = ""; + +/** Slice the marker-delimited token table out of the dashboard guide, failing loudly if either marker is missing. */ +function extractContractBlock(guide: string): string { + const startIndex = guide.indexOf(CONTRACT_START); + const endIndex = guide.indexOf(CONTRACT_END); + + expect(startIndex, "dashboard guide is missing the theme-token contract start marker").toBeGreaterThanOrEqual(0); + expect(endIndex, "dashboard guide is missing the theme-token contract end marker").toBeGreaterThan(startIndex); + + return guide.slice(startIndex + CONTRACT_START.length, endIndex); +} + +/** Escape a token name for use inside a RegExp so `--border` cannot match `--border-subtle`. */ +function escapeRegex(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +describe("stable dashboard theme token contract", () => { + it("keeps every documented token backed by dashboard CSS", () => { + const guide = readFileSync(resolve(__dirname, "../../../../docs/dashboard-guide.md"), "utf-8"); + const contractBlock = extractContractBlock(guide); + const documentedTokens = [...contractBlock.matchAll(/`(--[a-z0-9-]+)`/g)].map((match) => match[1]); + const uniqueTokens = new Set(documentedTokens); + + expect(uniqueTokens.size, "theme-token contract inventory must contain at least 30 distinct tokens").toBeGreaterThanOrEqual(30); + expect(documentedTokens, "theme-token contract must not document the same token twice").toHaveLength(uniqueTokens.size); + + const css = loadAllAppCss(); + const missingDefinitions = [...uniqueTokens].filter((token) => { + const definition = new RegExp(`(^|[^-\\w])${escapeRegex(token)}\\s*:`, "m"); + return !definition.test(css); + }); + + expect( + missingDefinitions, + `documented stable tokens without CSS definitions: ${missingDefinitions.join(", ") || "none"}`, + ).toEqual([]); + expect(uniqueTokens.has("--fusion-max-z"), "layering token must remain part of the stable contract").toBe(true); + }); + + it("keeps the live layering implementation and plugin documentation connected", () => { + const stackSource = readFileSync(resolve(__dirname, "../components/floatingWindowStack.ts"), "utf-8"); + const indexHtml = readFileSync(resolve(__dirname, "../index.html"), "utf-8"); + const pluginGuide = readFileSync(resolve(__dirname, "../../../../docs/PLUGIN_AUTHORING.md"), "utf-8"); + + expect(stackSource).toContain("--fusion-max-z"); + expect(indexHtml).toContain('id="plugin-overlay-root"'); + expect(pluginGuide).toContain("--fusion-max-z"); + expect(pluginGuide).toContain("plugin-overlay-root"); + expect(pluginGuide).toContain("dashboard-guide.md"); + }); +}); diff --git a/packages/dashboard/app/components/__tests__/floatingWindowStack.max-z.test.ts b/packages/dashboard/app/components/__tests__/floatingWindowStack.max-z.test.ts new file mode 100644 index 0000000000..4635708e35 --- /dev/null +++ b/packages/dashboard/app/components/__tests__/floatingWindowStack.max-z.test.ts @@ -0,0 +1,47 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +/* +FNXC:PluginOverlayLayering 2026-07-23-01:21: +The plugin overlay ceiling must exist before any floating window is claimed, remain at its boot +floor while dashboard layers are lower, and follow the session-monotonic utility stack once that +stack exceeds the floor. Module evaluation must remain safe in non-DOM runtimes. +*/ + +afterEach(() => { + vi.unstubAllGlobals(); + document.documentElement.style.removeProperty("--fusion-max-z"); +}); + +describe("floatingWindowStack --fusion-max-z synchronization", () => { + it("publishes the boot floor when the module loads", async () => { + vi.resetModules(); + + await import("../floatingWindowStack"); + + expect(document.documentElement.style.getPropertyValue("--fusion-max-z")).toBe("11001"); + }); + + it("keeps the floor until the utility stack exceeds it, then follows every claim", async () => { + vi.resetModules(); + const { FUSION_MAX_Z_FLOOR, currentFloatingZ, nextFloatingZ } = await import("../floatingWindowStack"); + + for (let claim = currentFloatingZ(); claim < FUSION_MAX_Z_FLOOR; claim += 1) { + nextFloatingZ(); + } + expect(currentFloatingZ()).toBe(FUSION_MAX_Z_FLOOR); + expect(document.documentElement.style.getPropertyValue("--fusion-max-z")).toBe(String(FUSION_MAX_Z_FLOOR)); + + nextFloatingZ(); + expect(document.documentElement.style.getPropertyValue("--fusion-max-z")).toBe(String(currentFloatingZ())); + + nextFloatingZ(); + expect(document.documentElement.style.getPropertyValue("--fusion-max-z")).toBe(String(currentFloatingZ())); + }); + + it("loads without writing when document is unavailable", async () => { + vi.resetModules(); + vi.stubGlobal("document", undefined); + + await expect(import("../floatingWindowStack")).resolves.toBeDefined(); + }); +}); diff --git a/packages/dashboard/app/components/floatingWindowStack.ts b/packages/dashboard/app/components/floatingWindowStack.ts index 02fdadc9ba..d2b5004b75 100644 --- a/packages/dashboard/app/components/floatingWindowStack.ts +++ b/packages/dashboard/app/components/floatingWindowStack.ts @@ -9,13 +9,40 @@ FNXC:TaskPopupLayer 2026-07-17-15:55: Task-detail popups and Quick Chat are interaction-stack peers in this lower board-layer band: the most recently mounted or pointer/focus-interacted peer is on top. Terminal, right-dock expand, Files, New Task, and other utility surfaces continue to use the separate 10100+ utility band. + +FNXC:PluginOverlayLayering 2026-07-23-01:21: +Plugins need a stable layer above every dashboard-managed utility window even though this stack is +session-monotonic and unbounded. Keep `--fusion-max-z` at the 11001 boot floor until this utility +counter exceeds it, then raise the inline root value after each claim. The floor sits one above the +tallest static dashboard overlay — the body-portaled model-combobox dropdown at 11000 — so it +dominates every fixed layer. The lower 220+ task-detail band is intentionally excluded; only +utility claims can grow past the dashboard's static layers. */ +/** Boot value for `--fusion-max-z`: one above the tallest static dashboard layer (the body-portaled model-combobox dropdown at 11000). */ +export const FUSION_MAX_Z_FLOOR = 11001; + let topZ = 10100; let taskDetailTopZ = 220; +let lastSyncedFusionMaxZ: number | undefined; + +/** Publish the current dashboard-managed z-index ceiling to `--fusion-max-z` on `:root`, skipping redundant writes. No-op outside a DOM. */ +function syncFusionMaxZ(): void { + if (typeof document === "undefined") return; + + const value = Math.max(topZ, FUSION_MAX_Z_FLOOR); + if (value === lastSyncedFusionMaxZ) return; + + document.documentElement.style.setProperty("--fusion-max-z", String(value)); + lastSyncedFusionMaxZ = value; +} + +syncFusionMaxZ(); /** Claim the front of the shared floating-utility stack. Monotonic, session-length. */ export function nextFloatingZ(): number { - return ++topZ; + const nextZ = ++topZ; + syncFusionMaxZ(); + return nextZ; } /** Current top of the floating-utility stack (read-only). Lets a utility window skip a needless bump when already on top. */ diff --git a/packages/dashboard/app/index.html b/packages/dashboard/app/index.html index 467535aafb..89f1dda0b2 100644 --- a/packages/dashboard/app/index.html +++ b/packages/dashboard/app/index.html @@ -239,5 +239,7 @@
+ +
diff --git a/packages/dashboard/app/styles.css b/packages/dashboard/app/styles.css index 27ada48709..fa63466adf 100644 --- a/packages/dashboard/app/styles.css +++ b/packages/dashboard/app/styles.css @@ -223,6 +223,12 @@ html { --z-dropdown: 1000; --z-modal: 1100; + /* + FNXC:PluginOverlayLayering 2026-07-23-01:21: + Plugin overlays need a stable ceiling above the dashboard's 10000–10001 page-overlay band, 10100+ session-monotonic utility-window stack, the 10500 toast/feedback layer, and the 11000 body-portaled model-combobox dropdown. The 11001 boot floor is available before JavaScript runs; floatingWindowStack.ts raises an inline documentElement value when its utility counter grows past this floor. + */ + --fusion-max-z: 11001; + --shadow-glow: 0 0 8px color-mix(in srgb, var(--todo) 30%, transparent); --glow-success: 0 0 8px color-mix(in srgb, var(--cta-border) 30%, transparent); --glow-warning: 0 0 8px color-mix(in srgb, #e3b541 30%, transparent); @@ -2833,6 +2839,17 @@ Task cards reuse the compact badge chip dimensions for GitLab links, but stale G padding: 12px 0; } +/* +FNXC:PluginOverlayLayering 2026-07-23-01:21: +The supported plugin overlay mount spans the viewport in the root stacking context and follows the live dashboard ceiling through --fusion-max-z. It is empty and click-through by default, so existing layout and interactions remain unchanged; interactive plugin children must opt back into pointer events. +*/ +#plugin-overlay-root { + position: fixed; + inset: 0; + z-index: calc(var(--fusion-max-z, 11001) + 1); + pointer-events: none; +} + /* === Toasts === */ /* FNXC:ToastTheming 2026-06-21-00:00: