## 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 <noreply@anthropic.com> ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude <noreply@anthropic.com>
66 lines
3.2 KiB
TypeScript
66 lines
3.2 KiB
TypeScript
/*
|
|
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 = "<!-- fusion-theme-token-contract:start -->";
|
|
const CONTRACT_END = "<!-- fusion-theme-token-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");
|
|
});
|
|
});
|