fix(dashboard): repair undefined spacing tokens, settings-search gaps, and census/browser-lane test drift

Dashboard bare-run repair, census/token cluster. Real product bugs: the
Command Center activity panel (FN-8866) and structural-mail badge
(FN-8872) referenced undefined --space-* tokens, zeroing their
gaps/padding — mapped to the defined named scale; the settings search
index lagged FN-8829/FN-9021 additions and FN-8855's requiredChecks
entry had no scroll anchor (now a SettingsTextRow). Test-side: the
theme census learns FN-8730's intentional midnight theme, and the
Chromium touch-resize suite self-gates with describe.runIf per the
sibling browser-lane convention (CI/FUSION_BROWSER_SMOKE_REQUIRE still
fail loudly; all 62 tests still run where Chromium exists).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-08-15 22:13:17 -07:00
parent 9f5f981e33
commit 9673f15c11
14 changed files with 82 additions and 22 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix collapsed Command Center activity spacing and mailbox badge padding; make recommendation and required-checks settings searchable.
category: fix
dev: AgentActivityPanel.css used an undefined numeric `--space-1/2/3` scale (FN-8866) and MailboxStructuralItem.css referenced undefined `--space-2xs` (FN-8872), zeroing gaps/padding — mapped to the defined named token scale. Settings search index gains `maxRecommendationsPerTask` (FN-8829) and `recommendationMailboxNoticeEnabled` (FN-9021); MergeSection's `requiredChecks` row now uses the `SettingsTextRow` primitive so the FN-8855 search entry actually scroll-anchors.

View File

@@ -1,11 +1,12 @@
/* FNXC:StructuralMail 2026-08-09-09:09: Reports and approval references need a compact structural treatment without changing ordinary mail. */ /* FNXC:StructuralMail 2026-08-09-09:09: Reports and approval references need a compact structural treatment without changing ordinary mail. */
/* FNXC:StructuralMail 2026-08-16-05:07: FN-8872 referenced undefined --space-2xs, which invalidated the badge padding to 0. --space-2xs is intentionally undefined (see space-token-defined.test.ts); use the established calc(var(--space-xs) / 2) half-step for the compact block padding instead. */
.mailbox-kind-badge { .mailbox-kind-badge {
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
display: inline-flex; display: inline-flex;
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
margin-inline-start: var(--space-xs); margin-inline-start: var(--space-xs);
padding: var(--space-2xs) var(--space-xs); padding: calc(var(--space-xs) / 2) var(--space-xs);
} }
.mailbox-kind-badge--report { .mailbox-kind-badge--report {

View File

@@ -4,7 +4,8 @@ import type { ColorTheme } from "@fusion/core";
import { ThemeSelector } from "../ThemeSelector"; import { ThemeSelector } from "../ThemeSelector";
// FNXC:Theme 2026-07-16-14:30: FN-8146 pins the historical Settings-grid set, including restored shadcn-mono, so a removal from COLOR_THEMES cannot make the all-themes checks pass circularly. // FNXC:Theme 2026-07-16-14:30: FN-8146 pins the historical Settings-grid set, including restored shadcn-mono, so a removal from COLOR_THEMES cannot make the all-themes checks pass circularly.
const EXPECTED_THEME_IDS = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'aurora', 'calm', 'dawn', 'sage', 'factory-dark', 'factory-light', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'] as const; // FNXC:Theme 2026-08-16-05:07: FN-8730 (5931e10345) added the Midnight preset; the census gains "midnight" between sage and factory-dark to match COLOR_THEMES order.
const EXPECTED_THEME_IDS = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'aurora', 'calm', 'dawn', 'sage', 'midnight', 'factory-dark', 'factory-light', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'] as const;
function renderedThemeIds(listbox: HTMLElement) { function renderedThemeIds(listbox: HTMLElement) {
return within(listbox).getAllByRole("option").map((option) => { return within(listbox).getAllByRole("option").map((option) => {

View File

@@ -1,10 +1,13 @@
/* /*
FNXC:CommandCenterAgentActivity 2026-08-10-01:54: FNXC:CommandCenterAgentActivity 2026-08-10-01:54:
The activity panel keeps live and timeline controls touch-usable at desktop and mobile widths with existing design tokens. Timeline rows are manual windows, so this stylesheet deliberately adds no nested scroll owner. The activity panel keeps live and timeline controls touch-usable at desktop and mobile widths with existing design tokens. Timeline rows are manual windows, so this stylesheet deliberately adds no nested scroll owner.
FNXC:CommandCenterAgentActivity 2026-08-16-05:07:
FN-8866 shipped with the undefined numeric --space-1/2/3 scale, which invalidated every gap/padding to 0 and collapsed the panel's spacing. This design system defines only the named --space-xs/sm/md/lg/xl/2xl scale (styles.css), so the numeric references map to xs=4px, sm=8px, md=12px per CommandCenter.token-validity.css.test.ts.
*/ */
.cc-agent-activity { .cc-agent-activity {
display: grid; display: grid;
gap: var(--space-3); gap: var(--space-md);
min-inline-size: 0; min-inline-size: 0;
} }
@@ -13,7 +16,7 @@ The activity panel keeps live and timeline controls touch-usable at desktop and
.cc-agent-activity-target { .cc-agent-activity-target {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-2); gap: var(--space-sm);
min-inline-size: 0; min-inline-size: 0;
} }
@@ -35,15 +38,15 @@ The activity panel keeps live and timeline controls touch-usable at desktop and
.cc-agent-activity-list { .cc-agent-activity-list {
display: grid; display: grid;
gap: var(--space-2); gap: var(--space-sm);
} }
.cc-agent-activity-row { .cc-agent-activity-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--space-2); gap: var(--space-sm);
min-inline-size: 0; min-inline-size: 0;
padding: var(--space-2); padding: var(--space-sm);
border: 0; border: 0;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
background: transparent; background: transparent;
@@ -63,7 +66,7 @@ button.cc-agent-activity-row:focus-visible {
.cc-agent-activity-copy { .cc-agent-activity-copy {
display: grid; display: grid;
gap: var(--space-1); gap: var(--space-xs);
min-inline-size: 0; min-inline-size: 0;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
@@ -93,6 +96,6 @@ button.cc-agent-activity-row:focus-visible {
@media (max-height: 480px) and (max-width: 768px) { @media (max-height: 480px) and (max-width: 768px) {
.cc-agent-activity { .cc-agent-activity {
gap: var(--space-2); gap: var(--space-sm);
} }
} }

View File

@@ -76,7 +76,8 @@ function cssRule(css: string, selector: string) {
} }
// FNXC:Theme 2026-07-16-14:30: FN-8146 pins the historical Settings-grid set, including restored shadcn-mono, so a removal from COLOR_THEMES cannot make the all-themes checks pass circularly. // FNXC:Theme 2026-07-16-14:30: FN-8146 pins the historical Settings-grid set, including restored shadcn-mono, so a removal from COLOR_THEMES cannot make the all-themes checks pass circularly.
const EXPECTED_THEME_IDS = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'aurora', 'calm', 'dawn', 'sage', 'factory-dark', 'factory-light', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'] as const; // FNXC:Theme 2026-08-16-05:07: FN-8730 (5931e10345) added the Midnight preset; the census gains "midnight" between sage and factory-dark to match COLOR_THEMES order.
const EXPECTED_THEME_IDS = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'aurora', 'calm', 'dawn', 'sage', 'midnight', 'factory-dark', 'factory-light', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'] as const;
function renderedThemeIds(listbox: HTMLElement) { function renderedThemeIds(listbox: HTMLElement) {
return within(listbox).getAllByRole("option").map((option) => { return within(listbox).getAllByRole("option").map((option) => {

View File

@@ -23,6 +23,35 @@ export const generalSearchEntries: SettingsSearchEntry[] = [
helpFallback: "Report a bug, send feedback, share an idea, or get help from Fusion.", helpFallback: "Report a bug, send feedback, share an idea, or get help from Fusion.",
keywords: ["bug", "feedback", "idea", "help", "support"], keywords: ["bug", "feedback", "idea", "help", "support"],
}, },
{
/*
FNXC:SettingsSearch 2026-08-16-05:07:
FN-8829 (d450dbe971) added the recommendations cap descriptor row without an
index entry; the search-index drift guard caught the gap. Labels/help mirror
the section's t() copy verbatim.
*/
sectionId: "general",
key: "maxRecommendationsPerTask",
labelKey: "settings.general.maxRecommendationsPerTask",
labelFallback: "Maximum recommendations per task",
helpKey: "settings.general.maxRecommendationsPerTaskHelp",
helpFallback: "Default: 3. Set 0 to disable recommendations; choose a whole number from 1 to 20 to cap each completed task.",
keywords: ["suggestions", "follow-up", "cap", "limit"],
},
{
/*
FNXC:SettingsSearch 2026-08-16-05:07:
FN-9021 (ea53cbd4ff) added the mailbox-notice toggle without an index entry;
same drift-guard gap as above.
*/
sectionId: "general",
key: "recommendationMailboxNoticeEnabled",
labelKey: "settings.general.recommendationMailboxNoticeEnabled",
labelFallback: "Recommendation mailbox notices",
helpKey: "settings.general.recommendationMailboxNoticeEnabledHelp",
helpFallback: "Default: enabled. When a completed task captures recommendations, send a summary to your mailbox. Turning this off does not change whether recommendations are captured.",
keywords: ["notifications", "mail", "inbox", "suggestions"],
},
{ {
sectionId: "general", sectionId: "general",
key: "taskPrefix", key: "taskPrefix",

View File

@@ -5,6 +5,7 @@ import { fetchGitRemoteBranches } from "../../../api";
import { MovedSettingsStub } from "./MovedSettingsStub"; import { MovedSettingsStub } from "./MovedSettingsStub";
import { SettingsSelectRow } from "../SettingsSelectRow"; import { SettingsSelectRow } from "../SettingsSelectRow";
import { SettingsNumberRow } from "../SettingsNumberRow"; import { SettingsNumberRow } from "../SettingsNumberRow";
import { SettingsTextRow } from "../SettingsTextRow";
import { SettingsHelpTip } from "../SettingsHelpTip"; import { SettingsHelpTip } from "../SettingsHelpTip";
import type { SectionBaseProps } from "./context"; import type { SectionBaseProps } from "./context";
/* /*
@@ -279,19 +280,28 @@ export function MergeSection({ form, setForm, integrationBranchOptions, integrat
</select> </select>
</div> </div>
{form.mergeStrategy === "pull-request" && <> {form.mergeStrategy === "pull-request" && <>
<div className="form-group"> {/*
<div className="settings-field-label-row"> FNXC:SettingsSearch 2026-08-16-05:07:
<label htmlFor="requiredChecks">{t("settings.merge.requiredChecks", "Required pull-request checks")}</label> FN-8855 (d59c1b162f) indexed requiredChecks in MergeSection.search.ts but rendered it as a
<SettingsHelpTip settingKey="requiredChecks">{t("settings.merge.requiredChecksHelp", "No default — unset. Comma-separated check names match GitHub exactly (case-sensitive). Leaving this empty uses GitHub required-status checks only; a named check that never reports blocks the merge.")}</SettingsHelpTip> bespoke label/input, so the search result had no `data-settings-key` anchor to scroll to and
</div> the drift guard flagged the entry as stale. The row composes the typed SettingsTextRow like
<input id="requiredChecks" className="input" value={requiredChecksInput} onChange={(event) => { its descriptor-row neighbors: same label/help copy, same comma-list parsing on change.
const rawValue = event.target.value; */}
<SettingsTextRow
descriptor={{
key: "requiredChecks",
label: t("settings.merge.requiredChecks", "Required pull-request checks"),
help: t("settings.merge.requiredChecksHelp", "No default — unset. Comma-separated check names match GitHub exactly (case-sensitive). Leaving this empty uses GitHub required-status checks only; a named check that never reports blocks the merge."),
}}
value={requiredChecksInput}
onChange={(nextValue) => {
const rawValue = nextValue ?? "";
setRequiredChecksInput(rawValue); setRequiredChecksInput(rawValue);
const requiredChecks = resolveRequiredCheckNames({ requiredChecks: rawValue.split(",") }); const requiredChecks = resolveRequiredCheckNames({ requiredChecks: rawValue.split(",") });
emittedRequiredCheckNames.current = requiredChecks; emittedRequiredCheckNames.current = requiredChecks;
setForm((current) => ({ ...current, requiredChecks: requiredChecks.length > 0 ? requiredChecks : undefined })); setForm((current) => ({ ...current, requiredChecks: requiredChecks.length > 0 ? requiredChecks : undefined }));
}}/> }}
</div> />
<div className="form-group"> <div className="form-group">
<div className="settings-field-label-row"> <div className="settings-field-label-row">
<label htmlFor="githubNativeAutoMerge" className="checkbox-label"> <label htmlFor="githubNativeAutoMerge" className="checkbox-label">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -23,8 +23,16 @@ FNXC:TaskDetailTitle 2026-08-05-19:18:
FN-8806's acceptance evidence is real Chromium geometry across production hosts. A skipped browser FN-8806's acceptance evidence is real Chromium geometry across production hosts. A skipped browser
lane can neither observe ResizeObserver delivery nor reject the original flicker, so fail discovery lane can neither observe ResizeObserver delivery nor reject the original flicker, so fail discovery
explicitly instead of allowing a green suite without this required rendering-engine regression. explicitly instead of allowing a green suite without this required rendering-engine regression.
FNXC:TaskDetailTitle 2026-08-16-05:07:
The hard failure applies only where the dedicated dashboard-browser-touch lane is required to run:
CI, or an explicit FUSION_BROWSER_SMOKE_REQUIRE=1 opt-in (the same flag scripts/browser-layout-smoke.mjs
uses for its --require-browser mode). A bare local run on a Chromium-less machine follows the sibling
planning-browser-e2e.test.ts convention and self-gates via describe.runIf(executablePath) instead of
failing discovery — the module-load throw was breaking unrelated local runs that merely collected this file.
*/ */
if (!executablePath) { const browserRequired = Boolean(process.env.CI) || process.env.FUSION_BROWSER_SMOKE_REQUIRE === "1";
if (!executablePath && browserRequired) {
throw new Error( throw new Error(
"[task-modal-touch-resize] Chromium is required for task-title stability coverage; set FUSION_BROWSER_SMOKE_BROWSER or CHROME_BIN.", "[task-modal-touch-resize] Chromium is required for task-title stability coverage; set FUSION_BROWSER_SMOKE_BROWSER or CHROME_BIN.",
); );
@@ -164,12 +172,12 @@ Browser CDP gestures are required because jsdom cannot resolve CSS hit targets.
both production resize paths and sends CSS-pixel touch input through Chromium so elementFromPoint, both production resize paths and sends CSS-pixel touch input through Chromium so elementFromPoint,
pointer capture, persistence, and header-drag isolation use the same browser input path. pointer capture, persistence, and header-drag isolation use the same browser input path.
*/ */
describe("Task modal tablet touch resize browser regression", () => { describe.runIf(executablePath)("Task modal tablet touch resize browser regression", () => {
let server: ViteDevServer; let browser: Browser; let baseUrl = ""; let server: ViteDevServer; let browser: Browser; let baseUrl = "";
beforeAll(async () => { beforeAll(async () => {
server = await createServer({ root: process.cwd(), server: { host: "127.0.0.1", port: 0, watch: null }, logLevel: "error" }); server = await createServer({ root: process.cwd(), server: { host: "127.0.0.1", port: 0, watch: null }, logLevel: "error" });
await server.listen(); baseUrl = server.resolvedUrls?.local[0] ?? ""; await server.listen(); baseUrl = server.resolvedUrls?.local[0] ?? "";
browser = await chromium.launch({ executablePath, headless: true, ...(process.env.CI ? { args: ["--no-sandbox", "--disable-dev-shm-usage"] } : {}) }); browser = await chromium.launch({ executablePath: executablePath as string, headless: true, ...(process.env.CI ? { args: ["--no-sandbox", "--disable-dev-shm-usage"] } : {}) });
}, 30_000); }, 30_000);
afterAll(async () => { afterAll(async () => {
await browser?.close(); await browser?.close();