Initial commit

This commit is contained in:
Samraaj Bath
2026-06-29 15:11:48 -07:00
commit 66dfdcc58d
404 changed files with 45970 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
import type { IR, IRNode } from "../normalize/ir.js";
import { isTextChild } from "../normalize/ir.js";
import type { MenuCapture } from "../capture/interactions.js";
/**
* M4b — mount-on-open menus (Radix-style portals). The panel is added to the DOM on open
* and removed on close, so the IR never sees it and the IR-based disclosure path can't
* reproduce it. Instead the panel is captured as a self-contained inline-styled fragment
* and reproduced CLIENT-SIDE: `DropdownMenu` renders null on mount and only injects the panel
* (under its trigger) on interaction — so the server-rendered base that gates 06 grade is
* structurally untouched (same safety model as DittoWire). A menu that doesn't reproduce is
* pruned by the interaction gate, so a broken menu is never shipped.
*/
const TRANSPARENT_GIF = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
export type RTMenu = { trigger: string; hoverOpen: boolean; gap: number; align: "left" | "right"; html: string };
function capToNode(ir: IR): Map<string, IRNode> {
const m = new Map<string, IRNode>();
const walk = (n: IRNode): void => {
const cap = n.attrs["data-cid-cap"];
if (cap !== undefined) m.set(cap, n);
for (const c of n.children) if (!isTextChild(c)) walk(c);
};
walk(ir.root);
return m;
}
function textContent(n: IRNode, max = 120): string {
let out = "";
const walk = (node: IRNode): void => {
if (out.length >= max) return;
for (const c of node.children) {
if (isTextChild(c)) out += " " + c.text;
else walk(c);
if (out.length >= max) return;
}
};
walk(n);
return out.replace(/\s+/g, " ").trim().slice(0, max);
}
function isLikelySkipTrigger(n: IRNode, menu: MenuCapture): boolean {
const label = `${textContent(n, 120)} ${n.attrs["aria-label"] ?? ""}`.toLowerCase();
const href = (n.attrs.href ?? "").trim();
if (/\bskip\s+(?:to\s+)?(?:content|main|navigation)\b/.test(label)) return true;
return href.startsWith("#") && menu.gap > 360;
}
/** Rewrite the captured panel HTML so the clone stays self-contained: same-origin links
* become app-relative (via the page's linkRewrite), and every image src resolves to a
* local asset or a transparent placeholder — never a remote origin (rubric Gate 2). */
function rewriteHtml(html: string, assetMap: Map<string, string>, sourceUrl: string, linkRewrite?: (href: string) => string): string {
const resolve = (u: string): string => { try { return new URL(u, sourceUrl).href; } catch { return u; } };
let out = html.replace(/\bsrc="([^"]*)"/g, (_m, u: string) => {
const local = assetMap.get(resolve(u));
return `src="${local ?? TRANSPARENT_GIF}"`;
});
if (linkRewrite) out = out.replace(/\bhref="([^"]*)"/g, (_m, u: string) => `href="${linkRewrite(u).replace(/"/g, "&quot;")}"`);
return out;
}
export function buildMenuSpecs(
ir: IR,
menus: MenuCapture[] | undefined,
assetMap: Map<string, string>,
sourceUrl: string,
linkRewrite?: (href: string) => string,
include?: (cid: string) => boolean,
): RTMenu[] {
if (!menus || !menus.length) return [];
const map = capToNode(ir);
const out: RTMenu[] = [];
for (const m of menus) {
const trigger = map.get(m.triggerCap);
if (!trigger) continue; // trigger pruned from the IR → drop
if (isLikelySkipTrigger(trigger, m)) continue;
if (include && !include(trigger.id)) continue;
out.push({ trigger: trigger.id, hoverOpen: m.hoverOpen, gap: m.gap, align: m.align, html: rewriteHtml(m.html, assetMap, sourceUrl, linkRewrite) });
}
return out;
}
export function menusJsx(menus: RTMenu[], indent: number): string {
if (!menus.length) return "";
const pad = " ".repeat(indent);
return `${pad}<DropdownMenu menus={${JSON.stringify(menus)}} />`;
}
export function dropdownMenuImportPath(depth: number): string {
return (depth === 0 ? "./" : "../".repeat(depth)) + "ditto/DropdownMenu";
}
/** The fixed DropdownMenu client component, written once per generated app. */
export const DROPDOWN_MENU_TSX = `"use client";
import { useEffect, useRef } from "react";
type RTMenu = { trigger: string; hoverOpen: boolean; gap: number; align: "left" | "right"; html: string };
const byCid = (cid: string): HTMLElement | null => document.querySelector('[data-cid="' + cid + '"]');
/** Reproduces mount-on-open dropdown/nav menus: renders nothing and applies NOTHING on mount; only on
* user interaction does it inject the captured panel fragment under its trigger. The base
* render is therefore unchanged. */
export default function DropdownMenu({ menus }: { menus: RTMenu[] }) {
const wired = useRef(false);
useEffect(() => {
if (wired.current) return;
wired.current = true;
for (const m of menus) {
const trig = byCid(m.trigger);
if (!trig) continue;
let panel: HTMLElement | null = null;
const place = () => {
if (!panel) return;
const r = trig.getBoundingClientRect();
panel.style.position = "absolute";
panel.style.top = (r.bottom + window.scrollY + m.gap) + "px";
if (m.align === "right") { panel.style.left = ""; panel.style.right = (document.documentElement.clientWidth - (r.right + window.scrollX)) + "px"; }
else { panel.style.right = ""; panel.style.left = (r.left + window.scrollX) + "px"; }
panel.style.zIndex = "9999";
};
const open = () => {
if (panel) return;
const wrap = document.createElement("div");
wrap.innerHTML = m.html;
panel = wrap.firstElementChild as HTMLElement | null;
if (!panel) return;
document.body.appendChild(panel);
place();
trig.setAttribute("aria-expanded", "true");
};
const close = () => { if (panel) { panel.remove(); panel = null; } trig.setAttribute("aria-expanded", "false"); };
const toggle = () => (panel ? close() : open());
if (m.hoverOpen) {
const root = trig.parentElement ?? trig;
root.addEventListener("mouseenter", open);
root.addEventListener("mouseleave", close);
} else {
trig.addEventListener("click", (e) => { e.preventDefault(); toggle(); });
}
document.addEventListener("keydown", (e) => { if (e.key === "Escape") close(); });
document.addEventListener("click", (e) => {
const t = e.target as Node;
if (panel && !trig.contains(t) && !panel.contains(t)) close();
});
window.addEventListener("resize", place);
window.addEventListener("scroll", place, { passive: true });
}
(window as any).__dittoMenuReady = true; // wiring done — lets the gate drive deterministically
}, [menus]);
return null;
}
`;