Guard Soma DOM contract boundaries

active-uix
dev 5 months ago
parent 8b7e5cbab7
commit 0074a6cbb7

@ -71,6 +71,46 @@ function grepSources(root: string, pattern: RegExp): string[] {
return listSourceFiles(root).filter((file) => pattern.test(readFileSync(file, 'utf8')));
}
function stripComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1');
}
function collectKnownMorfoDataAttrs(): Set<string> {
const attrs = new Set<string>();
for (const file of listSourceFiles(join(HERE, 'morfo', 'components'))) {
const source = stripComments(readFileSync(file, 'utf8'));
const component = /kebab:\s*'([^']+)'/.exec(source)?.[1];
if (!component) continue;
const partRe = /kebab:\s*'([^']+)'/g;
let partMatch: RegExpExecArray | null;
while ((partMatch = partRe.exec(source))) {
const part = partMatch[1];
attrs.add(part === 'provider' ? `data-${component}` : `data-${component}-${part}`);
}
const dataRe = /attr:\s*'([^']+)'/g;
let dataMatch: RegExpExecArray | null;
while ((dataMatch = dataRe.exec(source))) {
if (dataMatch[1].startsWith('data-')) attrs.add(dataMatch[1]);
}
}
return attrs;
}
function collectDataAttrs(source: string): Set<string> {
const clean = stripComments(source);
const attrs = new Set<string>();
const stringRe = /['"`](data-[a-zA-Z0-9_-]+)['"`]/g;
const inlineRe = /\b(data-[a-zA-Z0-9_-]+)(?=(?:=|\s|>|\/|}|\]))/g;
let match: RegExpExecArray | null;
while ((match = stringRe.exec(clean))) attrs.add(match[1]);
while ((match = inlineRe.exec(clean))) attrs.add(match[1]);
return attrs;
}
describe('UIX layer contracts', () => {
it('pins ActiveUix public service names and rejects legacy aliases', () => {
const uix = createActiveUix({ langs: minimalLang });
@ -205,6 +245,27 @@ describe('UIX layer contracts', () => {
expect(violations).toEqual([]);
});
it('guards Soma from direct mutable DOM writes', () => {
const violations = grepSources(
join(HERE, 'soma'),
/\.setAttribute\(|\.removeAttribute\(|\.toggleAttribute\(|\.classList\.|\.style\.setProperty\(|\.style\.[a-zA-Z]+\s*=/
);
expect(violations).toEqual([]);
});
it('guards hardcoded Soma component data attrs with morfo contracts', () => {
const knownAttrs = collectKnownMorfoDataAttrs();
const violations = listSourceFiles(join(HERE, 'soma', 'components')).flatMap((file) => {
const attrs = collectDataAttrs(readFileSync(file, 'utf8'));
return [...attrs]
.filter((attr) => !knownAttrs.has(attr))
.map((attr) => `${file.replace(REPO_ROOT, '')}: ${attr}`);
});
expect(violations).toEqual([]);
});
it('events/sema uses events as service name and requires dom or projector for visual projection', () => {
const factory = defineEngineSemantic({ visual: false });
expect(factory.name).toBe(UIX_LAYER_CONTRACTS.events.publicServiceName);

@ -203,7 +203,7 @@
stroke: none;
}
[data-popover-arrow] [data-arrow-outline] {
[data-popover-arrow] path {
stroke: var(--popover-border);
stroke-width: var(--popover-border-width);
stroke-linecap: round;

@ -29,15 +29,9 @@
{#if children}
{@render children?.()}
{:else}
<svg {width} {height} viewBox="0 0 30 10" preserveAspectRatio="none" data-arrow="">
<polygon points="0,0 30,0 15,10" fill="currentColor" data-arrow-fill="" />
<path
d="M0 0 L15 10 L30 0"
fill="none"
stroke="currentColor"
stroke-linejoin="round"
data-arrow-outline=""
/>
<svg {width} {height} viewBox="0 0 30 10" preserveAspectRatio="none">
<polygon points="0,0 30,0 15,10" fill="currentColor" />
<path d="M0 0 L15 10 L30 0" fill="none" stroke="currentColor" stroke-linejoin="round" />
</svg>
{/if}
</span>

Loading…
Cancel
Save

Powered by TurnKey Linux.