feat: consolidate shared UI and harden browser authority for release
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const webuiRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
||||
const read = (path) => readFileSync(resolve(webuiRoot, path), "utf8");
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const settings = read("src/features/settings/SettingsPage.tsx");
|
||||
const retention = read("src/features/privacy/RetentionPolicyManagement.tsx");
|
||||
@@ -12,6 +14,14 @@ const confirmDialog = read("src/components/ConfirmDialog.tsx");
|
||||
const credentials = read("src/components/CredentialEnvelopeManager.tsx");
|
||||
const iconRail = read("src/layout/IconRail.tsx");
|
||||
const moduleLoadBoundary = read("src/components/ModuleLoadBoundary.tsx");
|
||||
const moduleLoading = read("src/platform/modules.ts");
|
||||
const application = read("src/App.tsx");
|
||||
assert.match(moduleLoading, /importModuleWithRetry\(loader\.load\)/, "local descriptor imports use one bounded retry");
|
||||
assert.match(moduleLoading, /failedLocalModules\.add\(loader\.packageName\)/, "final local import failures retain an explicit unavailable state");
|
||||
assert.match(moduleLoading, /onLoadFailure\?\.\(info\.id\)/, "enabled module import failures are reported to the shell");
|
||||
assert.match(application, /webModuleLoadFailures\.length > 0[\s\S]*optional_module_load_failed/, "an import failure is not silently presented as an uninstalled optional module");
|
||||
assert.match(application, /function WebModuleLoadFailureNotice[\s\S]*useUnsavedChanges\(\)[\s\S]*requestNavigation\(\(\) => window\.location\.reload\(\)\)/, "module recovery reload uses the shared unsaved-draft navigation guard");
|
||||
const wysiwygEditor = read("src/components/WysiwygEditor.tsx");
|
||||
const titlebar = read("src/layout/Titlebar.tsx");
|
||||
const temporalDataMenu = read("src/layout/TemporalDataMenu.tsx");
|
||||
const helpMenu = read("src/layout/HelpMenu.tsx");
|
||||
@@ -27,6 +37,43 @@ const dialogAnatomy = read("src/components/DialogAnatomy.tsx");
|
||||
const adminPageLayout = read("src/components/admin/AdminPageLayout.tsx");
|
||||
const layoutStyles = read("src/styles/layout.css");
|
||||
const authGateStyles = read("src/styles/auth-gate.css");
|
||||
const viteConfig = read("vite.config.ts");
|
||||
const packageManifest = JSON.parse(read("package.json"));
|
||||
|
||||
// Component presence alone cannot detect a collection toolbar in a left pane.
|
||||
await import("./test-workspace-collection-actions.mjs");
|
||||
|
||||
const listSelectionFilter = read("src/components/ListSelectionFilter.tsx");
|
||||
const dataGrid = read("src/components/table/DataGrid.tsx");
|
||||
const multiSelectFilter = read("src/components/MultiSelectFilter.tsx");
|
||||
assert.match(dataGrid, /<ListSelectionFilter\b/, "DataGrid consumes the shared checkbox filter body");
|
||||
assert.match(multiSelectFilter, /<ListSelectionFilter\b/, "standalone list filters consume the same checkbox body");
|
||||
assert.match(listSelectionFilter, /onChange\(null\)/, "Select all explicitly removes the restriction");
|
||||
assert.match(listSelectionFilter, /onChange\(\[\]\)/, "Deselect all explicitly matches nothing");
|
||||
assert.match(read("src/components/Card.tsx"), /bodyLayout\?: "content" \| "table"/, "table cards explicitly own their inset contract");
|
||||
|
||||
// A package in resolve.dedupe is not necessarily prebundled. Check include
|
||||
// specifically: lazy Campaign imports previously triggered dependency churn
|
||||
// after the UI was already open, leaving module-load errors on first visits.
|
||||
const optimization = viteConfig.match(/optimizeDeps:\s*\{([\s\S]*?)\n\s*\},/)?.[1];
|
||||
assert.ok(optimization, "the development dependency optimizer is configured");
|
||||
const optimizedVendors = [...(optimization.match(/include:\s*\[([\s\S]*?)\]/)?.[1] ?? "").matchAll(/"([^"]+)"/g)].map((match) => match[1]);
|
||||
for (const specifier of [
|
||||
"@xyflow/react",
|
||||
"read-excel-file/browser",
|
||||
"read-excel-file/universal",
|
||||
"@tiptap/core",
|
||||
"@tiptap/extension-image",
|
||||
"@tiptap/react",
|
||||
"@tiptap/starter-kit"
|
||||
]) {
|
||||
assert.ok(optimizedVendors.includes(specifier), `${specifier} is prebundled before lazy routes open`);
|
||||
const packageName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
|
||||
assert.ok(packageManifest.dependencies[packageName] ?? packageManifest.devDependencies[packageName], `${specifier} is a direct dependency of the development shell`);
|
||||
assert.doesNotThrow(() => require.resolve(specifier), `${specifier} has an installed resolvable entry point`);
|
||||
}
|
||||
assert.match(optimization, /exclude:\s*availableWebModuleSpecifiers\(\)/, "optional module packages stay outside dependency prebundling");
|
||||
assert.ok(!optimizedVendors.some((specifier) => specifier.startsWith("@govoplan/")), "vendor prebundling must not eagerly import optional module packages");
|
||||
|
||||
assert.match(settings, /contextId: "core\.settings"/, "settings expose stable contextual documentation");
|
||||
assert.match(settings, /archetype=\{editorSection \? "editor" : "workspace"\}/, "settings declare editor intent only for draft-owning sections");
|
||||
@@ -71,6 +118,8 @@ assert.match(pageActionBar, /refreshable: true;\s*reloadAction: PageReloadAction
|
||||
assert.match(pageActionBar, /state: PageEditorState;/, "editors require an explicit persistence lifecycle state");
|
||||
assert.match(pageActionBar, /data-page-dirty-state=/, "editors announce clean, dirty, and saving states");
|
||||
assert.match(pageActionBar, /<ActionSlot name="reload">/, "page action bars keep reload in a named stable slot");
|
||||
assert.match(pageActionBar, /data-page-action-group="trailing"[\s\S]*<ActionSlot name="reload">/, "Reload belongs to the trailing group beside primary actions");
|
||||
assert.doesNotMatch(pageActionBar, /data-page-action-group="leading"(?:(?!<\/ToolbarGroup>)[\s\S])*name="reload"/, "module-independent reload placement must not drift back to the leading group");
|
||||
assert.match(pageActionBar, /data-page-action-separation="destructive"/, "destructive page actions expose a separate semantic group");
|
||||
assert.match(pageActionBar, /<ActionSlot name="discard">[\s\S]*<ActionSlot name="save">/, "editor save follows discard in the trailing group");
|
||||
assert.match(workspaceActionBar, /actionScope=\{scope\}/, "workspace and pane action bars project their semantic scope centrally");
|
||||
@@ -102,5 +151,8 @@ assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.app-main \{[\s\S
|
||||
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.titlebar-context-selectors \{[\s\S]*overflow-x: auto;/, "narrow context selectors remain reachable without covering titlebar actions");
|
||||
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.account-pill span \{[\s\S]*display: none;/, "narrow account controls retain the icon while removing collision-prone text");
|
||||
assert.match(moduleLoadBoundary, /<DismissibleAlert tone="danger" compact/, "module failures use the compact shared alert");
|
||||
assert.match(wysiwygEditor, /editor\.setEditable\(!disabled, false\)/, "mounting and permission changes never emit rich-text changes");
|
||||
assert.match(wysiwygEditor, /if \(!isWysiwygDocumentUpdate\(transaction, appendedTransactions\)\) return;/, "rich-text editors ignore non-document update events");
|
||||
assert.match(wysiwygEditor, /if \(nextValue === valueRef\.current\) return;/, "rich-text editors do not report identical content as an edit");
|
||||
|
||||
console.log("Core interface-pattern contracts passed.");
|
||||
|
||||
Reference in New Issue
Block a user