feat(ui): complete phase 4 Poyraz UI foundation
This commit is contained in:
@@ -145,7 +145,7 @@ try {
|
||||
assert.equal(brandingUpdate.payload.data.applicationName, "Neta Smoke Studio");
|
||||
const brandedLoginHtml = await (await fetch(`${baseUrl}/login`)).text();
|
||||
assert.match(brandedLoginHtml, /Neta Smoke Studio/, "Branding metadata must be server-rendered");
|
||||
assert.match(brandedLoginHtml, /--primary:#336699/, "Brand tokens must be present in first HTML response");
|
||||
assert.match(brandedLoginHtml, /--poyraz-primary:#336699/, "Brand tokens must be present in first HTML response");
|
||||
const dynamicManifest = await (await fetch(`${baseUrl}/manifest.webmanifest`)).json();
|
||||
assert.equal(dynamicManifest.name, "Neta Smoke Studio", "Manifest must use instance branding");
|
||||
const publicLogo = await fetch(`${baseUrl}/api/branding/assets/${logoFileId}`);
|
||||
|
||||
@@ -85,8 +85,8 @@ try {
|
||||
assert.equal(branding.primaryColor, "#336699");
|
||||
assert.equal(branding.darkLogoUrl, branding.lightLogoUrl, "Missing dark logo must fall back to light logo");
|
||||
assert.equal(fileService.readPublicBranding(logo.id).metadata.id, logo.id);
|
||||
assert.ok(contrastRatio(branding.primaryColor, branding.cssVariables["--primary-foreground"]) >= 4.5);
|
||||
assert.ok(contrastRatio(branding.accentColor, branding.cssVariables["--accent-foreground"]) >= 4.5);
|
||||
assert.ok(contrastRatio(branding.primaryColor, branding.cssVariables["--poyraz-primary-foreground"]) >= 4.5);
|
||||
assert.ok(contrastRatio(branding.accentColor, branding.cssVariables["--poyraz-accent-foreground"]) >= 4.5);
|
||||
assertDomainError(() => brandingService.update(clientOne, { applicationName: "Attack" }), "FORBIDDEN");
|
||||
assertDomainError(() => brandingService.update(ownerTwo, { applicationName: "Attack" }), "FORBIDDEN");
|
||||
assertDomainError(() => brandingService.update(ownerOne, { primaryColor: "red" }), "VALIDATION_ERROR");
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const checkedPaths = [
|
||||
"app/layout.tsx",
|
||||
"app/globals.css",
|
||||
"app/login",
|
||||
"app/register",
|
||||
"components/auth",
|
||||
"components/layout",
|
||||
"components/ui",
|
||||
"config/sidebar.ts",
|
||||
"config/portal-sidebar.ts",
|
||||
];
|
||||
|
||||
const requiredInternalPrimitives = [
|
||||
"components/ui/button.tsx",
|
||||
"components/ui/input.tsx",
|
||||
"components/ui/field.tsx",
|
||||
"components/ui/card.tsx",
|
||||
"components/ui/skeleton.tsx",
|
||||
"components/ui/toast.tsx",
|
||||
];
|
||||
|
||||
function walk(targetPath) {
|
||||
const absolutePath = path.join(repoRoot, targetPath);
|
||||
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const stat = fs.statSync(absolutePath);
|
||||
|
||||
if (stat.isFile()) {
|
||||
return [absolutePath];
|
||||
}
|
||||
|
||||
return fs.readdirSync(absolutePath, { withFileTypes: true }).flatMap((entry) => {
|
||||
const entryPath = path.join(absolutePath, entry.name);
|
||||
const relativePath = path.relative(repoRoot, entryPath);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
return walk(relativePath);
|
||||
}
|
||||
|
||||
return /\.(css|tsx?|jsx?)$/.test(entry.name) ? [entryPath] : [];
|
||||
});
|
||||
}
|
||||
|
||||
for (const primitive of requiredInternalPrimitives) {
|
||||
assert.ok(fs.existsSync(path.join(repoRoot, primitive)), `Missing internal primitive: ${primitive}`);
|
||||
}
|
||||
|
||||
const violations = checkedPaths
|
||||
.flatMap(walk)
|
||||
.flatMap((filePath) => {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const relativePath = path.relative(repoRoot, filePath);
|
||||
const matches = [];
|
||||
|
||||
if (content.includes("poyraz-ui")) {
|
||||
matches.push(`${relativePath}: imports poyraz-ui`);
|
||||
}
|
||||
|
||||
if (content.includes("--poyraz")) {
|
||||
matches.push(`${relativePath}: uses --poyraz token`);
|
||||
}
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
assert.deepEqual(violations, [], `UI boundary violations:\n${violations.join("\n")}`);
|
||||
|
||||
console.log("Phase 3 UI boundary passed");
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8"));
|
||||
const globalsCss = fs.readFileSync(path.join(repoRoot, "app/globals.css"), "utf8");
|
||||
const appShell = fs.readFileSync(path.join(repoRoot, "components/layout/app-shell.tsx"), "utf8");
|
||||
|
||||
assert.match(packageJson.dependencies["poyraz-ui"] ?? "", /^\^?3\./, "poyraz-ui must use major v3");
|
||||
assert.match(
|
||||
packageJson.dependencies.mermaid ?? "",
|
||||
/^\^?11\./,
|
||||
"Poyraz molecule bundle requires its Mermaid peer to be resolvable at build time",
|
||||
);
|
||||
assert.ok(
|
||||
globalsCss.includes('@import "poyraz-ui/preset.css";'),
|
||||
"Poyraz UI preset must be imported by app/globals.css",
|
||||
);
|
||||
|
||||
for (const component of [
|
||||
"SidebarProvider",
|
||||
"SidebarPanel",
|
||||
"SidebarHeader",
|
||||
"SidebarBranding",
|
||||
"SidebarContent",
|
||||
"SidebarMenu",
|
||||
"SidebarMenuItem",
|
||||
"SidebarFooter",
|
||||
"SidebarTrigger",
|
||||
"SidebarUserProfile",
|
||||
]) {
|
||||
assert.ok(appShell.includes(component), `App shell must compose the Poyraz ${component} organism`);
|
||||
}
|
||||
|
||||
const requiredSystemCompositions = [
|
||||
"components/system/page-header.tsx",
|
||||
"components/system/feedback-state.tsx",
|
||||
"components/system/status-badge.tsx",
|
||||
"components/system/destructive-confirmation.tsx",
|
||||
];
|
||||
for (const file of requiredSystemCompositions) {
|
||||
assert.ok(fs.existsSync(path.join(repoRoot, file)), `Missing Neta system composition: ${file}`);
|
||||
}
|
||||
|
||||
const allowedLocalUiFiles = new Set([
|
||||
"offline-indicator.tsx",
|
||||
"pending-link.tsx",
|
||||
"pending-submit-button.tsx",
|
||||
]);
|
||||
const localUiFiles = fs
|
||||
.readdirSync(path.join(repoRoot, "components/ui"), { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile())
|
||||
.map((entry) => entry.name);
|
||||
assert.deepEqual(
|
||||
localUiFiles.sort(),
|
||||
[...allowedLocalUiFiles].sort(),
|
||||
"components/ui may contain only Neta-specific behavior compositions",
|
||||
);
|
||||
|
||||
const removedDirectUiDependencies = [
|
||||
"@base-ui/react",
|
||||
"@iconify/react",
|
||||
"@radix-ui/react-alert-dialog",
|
||||
"@radix-ui/react-checkbox",
|
||||
"@radix-ui/react-dialog",
|
||||
"@radix-ui/react-dropdown-menu",
|
||||
"@radix-ui/react-label",
|
||||
"@radix-ui/react-select",
|
||||
"@radix-ui/react-separator",
|
||||
"@radix-ui/react-slot",
|
||||
"@radix-ui/react-toast",
|
||||
"class-variance-authority",
|
||||
"next-themes",
|
||||
"radix-ui",
|
||||
"shadcn",
|
||||
];
|
||||
for (const dependency of removedDirectUiDependencies) {
|
||||
assert.equal(packageJson.dependencies[dependency], undefined, `Duplicate UI dependency remains: ${dependency}`);
|
||||
}
|
||||
|
||||
function walk(targetPath) {
|
||||
const absolutePath = path.join(repoRoot, targetPath);
|
||||
if (!fs.existsSync(absolutePath)) return [];
|
||||
const stat = fs.statSync(absolutePath);
|
||||
if (stat.isFile()) return [absolutePath];
|
||||
|
||||
return fs.readdirSync(absolutePath, { withFileTypes: true }).flatMap((entry) => {
|
||||
const entryPath = path.join(absolutePath, entry.name);
|
||||
if (entry.isDirectory()) return walk(path.relative(repoRoot, entryPath));
|
||||
return /\.(css|tsx?|jsx?)$/.test(entry.name) ? [entryPath] : [];
|
||||
});
|
||||
}
|
||||
|
||||
const violations = ["app", "components"]
|
||||
.flatMap(walk)
|
||||
.flatMap((filePath) => {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
const relativePath = path.relative(repoRoot, filePath);
|
||||
const matches = [];
|
||||
|
||||
if (/from\s+["']poyraz-ui["']/.test(content)) {
|
||||
matches.push(`${relativePath}: imports from the package root instead of atoms/molecules/organisms`);
|
||||
}
|
||||
if (/@\/components\/ui\/(button|card|checkbox|dialog|dropdown-menu|field|form|icon|input|label|select|separator|skeleton|textarea|toast|toaster)/.test(content)) {
|
||||
matches.push(`${relativePath}: imports a removed local generic primitive`);
|
||||
}
|
||||
return matches;
|
||||
});
|
||||
|
||||
assert.deepEqual(violations, [], `Phase 4 UI boundary violations:\n${violations.join("\n")}`);
|
||||
console.log("Phase 4 Poyraz UI boundary passed");
|
||||
Reference in New Issue
Block a user