Add governed module and interface controls
This commit is contained in:
@@ -40,7 +40,7 @@ function scanStructuralSourcePositions(roots) {
|
||||
}
|
||||
if (ts.isPropertyAssignment(node)) {
|
||||
const name = nodeName(node.name);
|
||||
if ((isStructuralName(name) || isAlgorithmNameProperty(node) || isListOptionValueProperty(node)) && hasI18nLiteral(node.initializer)) report(node, `object ${name}`);
|
||||
if ((isStructuralName(name) || isAlgorithmNameProperty(node) || isListOptionValueProperty(node)) && hasI18nLiteral(node.initializer) && !isPresentationalBlockerProperty(node)) report(node, `object ${name}`);
|
||||
if (hasI18nLiteral(node.name) && !isAllowedStructuralStringPosition(node.name, file)) report(node, "object key");
|
||||
}
|
||||
if (ts.isShorthandPropertyAssignment(node) && node.name.text.includes("i18n:")) report(node, "object key");
|
||||
@@ -221,6 +221,16 @@ function isStructuralJsxValue(node) {
|
||||
return parent.tagName.getText() === "option";
|
||||
}
|
||||
|
||||
function isPresentationalBlockerProperty(node) {
|
||||
if (nodeName(node.name) !== "target" || !ts.isObjectLiteralExpression(node.parent)) return false;
|
||||
const propertyNames = new Set(
|
||||
node.parent.properties
|
||||
.filter((property) => ts.isPropertyAssignment(property))
|
||||
.map((property) => nodeName(property.name))
|
||||
);
|
||||
return propertyNames.has("actor") && propertyNames.has("requiredAction");
|
||||
}
|
||||
|
||||
function hasAncestorVariable(node, variableName) {
|
||||
let current = node.parent;
|
||||
while (current) {
|
||||
|
||||
@@ -9,6 +9,10 @@ const read = (path) => readFileSync(resolve(webuiRoot, path), "utf8");
|
||||
const settings = read("src/features/settings/SettingsPage.tsx");
|
||||
const retention = read("src/features/privacy/RetentionPolicyManagement.tsx");
|
||||
const credentials = read("src/components/CredentialEnvelopeManager.tsx");
|
||||
const iconRail = read("src/layout/IconRail.tsx");
|
||||
const moduleLoadBoundary = read("src/components/ModuleLoadBoundary.tsx");
|
||||
const titlebar = read("src/layout/Titlebar.tsx");
|
||||
const layoutStyles = read("src/styles/layout.css");
|
||||
|
||||
assert.match(settings, /contextId: "core\.settings"/, "settings expose stable contextual documentation");
|
||||
assert.match(settings, /There are no unsaved profile changes\./, "profile save explains its clean state");
|
||||
@@ -24,4 +28,18 @@ assert.match(credentials, /contextId: "access\.credentials"/, "credentials expos
|
||||
assert.match(credentials, /disabledReason: writeDisabledReason/, "credential row actions retain actionable disabled reasons");
|
||||
assert.doesNotMatch(credentials, /<textarea/, "credentials use typed controls rather than a primary JSON editor");
|
||||
|
||||
assert.match(iconRail, /<div className="icon-rail-scroll">\s*<nav className="icon-nav">/, "the module navigation has a dedicated scroll viewport");
|
||||
assert.match(layoutStyles, /\.icon-rail-scroll \{[^}]*min-height: 0;[^}]*flex: 1 1 auto;[^}]*overflow-y: auto;/, "only the middle rail region scrolls");
|
||||
assert.match(layoutStyles, /\.icon-rail-header \{[^}]*flex: 0 0 auto;/, "the rail logo remains fixed");
|
||||
assert.match(layoutStyles, /\.icon-rail-bottom \{[^}]*flex: 0 0 auto;/, "the rail utility controls remain fixed");
|
||||
|
||||
assert.match(titlebar, /className="titlebar-status-pattern"/, "shell state uses a titlebar background pattern");
|
||||
assert.match(titlebar, /className="titlebar-global-search"/, "global search retains its dedicated titlebar grid cell");
|
||||
assert.match(titlebar, /className="account-pill"[\s\S]*aria-label=\{displayUserName\}[\s\S]*aria-haspopup="menu"/, "the compact account menu retains an accessible name and menu state");
|
||||
assert.doesNotMatch(layoutStyles, /\.maintenance-topbar-link[^}]*position: absolute;/, "maintenance state does not occupy the centered search position");
|
||||
assert.match(layoutStyles, /@media \(max-width: 600px\)[\s\S]*\.app-main \{[\s\S]*grid-template-rows: 104px 51px minmax\(0, 1fr\);/, "the narrow shell reserves two non-overlapping titlebar rows");
|
||||
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");
|
||||
|
||||
console.log("Core interface-pattern contracts passed.");
|
||||
|
||||
Reference in New Issue
Block a user