Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0e05519
fix: restore renderer copy parity (voxel paint order, seam bleed, tri…
apresmoi Aug 16, 2026
339a044
ci: enforce package boundaries, mirror discipline, and per-package ty…
apresmoi Aug 16, 2026
ea6ac54
perf: stop camera-frame shadow recompute in react/vue and share caste…
apresmoi Aug 16, 2026
6726ca3
fix: unify seamBleed semantics across renderers (core-owned raw px re…
apresmoi Aug 16, 2026
d266359
refactor: hoist renderStats into core, renderers re-export
apresmoi Aug 16, 2026
b9112bb
refactor: hoist orbit/first-person/gizmo controls math into core
apresmoi Aug 16, 2026
7a39978
ci: drop render-stats mirror group after hoist
apresmoi Aug 16, 2026
7a932d4
fix: reconcile renderer defaults (maxDistance, vue pan parity, shadow…
apresmoi Aug 16, 2026
c7777aa
test: include test files in per-package typecheck and fix latent test…
apresmoi Aug 16, 2026
0c48d27
refactor: decompose createPolyScene into concept-scoped api/scene mod…
apresmoi Aug 16, 2026
d3ea988
refactor: decompose react/vue PolyMesh into mirrored mesh/ hook modules
apresmoi Aug 16, 2026
e1e3c5d
chore: cap vitest worker pools at 8
apresmoi Aug 16, 2026
0c9b5db
fix: truthful public APIs (dead props removed, PolyShadowOptions name…
apresmoi Aug 16, 2026
46718fa
ci: drop colorResolver mirror group after deletion
apresmoi Aug 16, 2026
883628e
feat: shadow phase timers, timing + vox benches, renderStats cost dri…
apresmoi Aug 16, 2026
49d32df
fix: shadow cache invalidation on updatePolygon and followAnimation t…
apresmoi Aug 16, 2026
76286c6
docs: shadow lift docs follow upstream unified POLY_DEFAULT_SHADOW_LIFT
apresmoi Aug 16, 2026
5c1d378
fix(shadow): bleed receiver-shadow clips along interior member seams
apresmoi Aug 16, 2026
cf4e8fa
fix(shadow): union coplanar T-junction faces into one receiver group
apresmoi Aug 16, 2026
6dbf6ac
fix(shadow): bleed crease edges via opaque pre-blended solid shadow f…
apresmoi Aug 16, 2026
763c2b5
fix(shadow): match creases on world vertices; surface oracle capture …
apresmoi Aug 16, 2026
7387377
feat(website): shadow strength and color controls in the gallery ligh…
apresmoi Aug 16, 2026
e07e31b
fix(website): bake gallery ground honestly so shadows match; gate par…
apresmoi Aug 17, 2026
175ada9
chore(website): regenerate gallery domformat corpus for shadow geometry
apresmoi Aug 17, 2026
0a8c8e1
fix(ci): enforce renderer lane parity, validate manifests, correct st…
apresmoi Aug 17, 2026
5e1073d
fix(ci): close mirror-gate bypasses, gate mesh modules, scope waivers
apresmoi Aug 17, 2026
a8f6d8a
fix(shadow): bleed creases only when the neighbouring face is camera-…
apresmoi Aug 17, 2026
ae92399
fix(shadow): decide crease bleed from resolved colours; gate pre-blen…
apresmoi Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
55 changes: 55 additions & 0 deletions .github/mirror-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"atlas-pipeline": {
"packages/polycss/src/render/atlas/emit.ts": "67ee80a028a4b64405511ea36f63f14173ae6963e6a5fd39edea7699b760626b",
"packages/polycss/src/render/atlas/packing.ts": "8742b14b1a5bcb33413d3d28c3f6e0f6844cd572158fc71b00eced2aa9f800a0",
"packages/polycss/src/render/atlas/paintDefaults.ts": "0e2ee4b8ef96cba53eba9a0c2d959111d66c453a99c06ab857768b05180be0f9",
"packages/polycss/src/render/atlas/plan.ts": "6fc385814a2339dbf3001c57ad6c0e8c0edcf5f989fffcebd2e27a1fc0a2ea2c",
"packages/polycss/src/render/atlas/rasterise.ts": "ae5e2b93f994c7c7b1b9c05069b761f955bc146791426a4123d149b3271e76d6",
"packages/polycss/src/render/atlas/renderPolygons.ts": "059a24a0c6470a53d4b102be8ac13322c34746e303ba12d03bce0fbccc94ff91",
"packages/polycss/src/render/atlas/solidTrianglePlan.ts": "386c3cff4a9e6848af2584f9cabef92ba623bf6ef78ab64bed59024add643e95",
"packages/polycss/src/render/atlas/stableTriangle.ts": "ba780fcf1177a8ec338a67e8900ee2d4032cc2a6e731329782b01107e79f1463",
"packages/polycss/src/render/atlas/strategy.ts": "890b9487272c469f3c3dfd8f9a9fc7b66b403a9200ac27ca2698b6c19bf82605",
"packages/react/src/scene/atlas/buildAtlasPages.ts": "5ca0059c936d3948fd91f8888acb3f19534e327075367528517879f85ee4a3e1",
"packages/react/src/scene/atlas/detection.ts": "faf6fd923a246e1cd42b1b1a42e6ee92d9cc637ec4fc8c852672a1fc4deb9331",
"packages/react/src/scene/atlas/filterPlans.ts": "b8004f3c1586a6a9d65553a239c3c4d5c05c5f29ddd19c0278a7c73af776e1a1",
"packages/react/src/scene/atlas/packing.ts": "ec59c5a92abe3caeaf1bf8589e8aab2b62b63c94955b77aabfd74c6ea6326fa3",
"packages/react/src/scene/atlas/paintDefaults.ts": "0300239470371dc4f454c7516ab7505a64f68f94b83f5d72fef31ea900e504e6",
"packages/react/src/scene/atlas/solidTriangleStyle.ts": "d7ee521f08fdc71370f7f5770a30dfa5e11526e98e5e4b351416b389126997cc",
"packages/react/src/scene/atlas/stableTriangleDom.ts": "d482c608c3dd48455f3534fa69e2792550a56e8fb801da7fe07f223ece315920",
"packages/vue/src/scene/atlas/buildAtlasPages.ts": "5ca0059c936d3948fd91f8888acb3f19534e327075367528517879f85ee4a3e1",
"packages/vue/src/scene/atlas/detection.ts": "faf6fd923a246e1cd42b1b1a42e6ee92d9cc637ec4fc8c852672a1fc4deb9331",
"packages/vue/src/scene/atlas/filterPlans.ts": "b8004f3c1586a6a9d65553a239c3c4d5c05c5f29ddd19c0278a7c73af776e1a1",
"packages/vue/src/scene/atlas/packing.ts": "ec59c5a92abe3caeaf1bf8589e8aab2b62b63c94955b77aabfd74c6ea6326fa3",
"packages/vue/src/scene/atlas/paintDefaults.ts": "0300239470371dc4f454c7516ab7505a64f68f94b83f5d72fef31ea900e504e6",
"packages/vue/src/scene/atlas/solidTriangleStyle.ts": "81443b1042b7fd9f8e2002577f684834e5441216a35705497d4690385b983bbe",
"packages/vue/src/scene/atlas/stableTriangleDom.ts": "0e54a1b1b7f258be855b6a7c450c7bb471c20cbd504a6a2adb00f8cf899c1454"
},
"voxel-renderer": {
"packages/polycss/src/render/voxelRenderer.ts": "6a74ebb25fcdd788adb7a7be77d2d0613992bdba7c877a905995e10a85485aee",
"packages/react/src/scene/voxelRenderer.ts": "25ce4ad2e8b6fe8b7892dea95c68de547ce4704113e457ea940c0e130e65f250",
"packages/vue/src/scene/voxelRenderer.ts": "25ce4ad2e8b6fe8b7892dea95c68de547ce4704113e457ea940c0e130e65f250"
},
"mesh-modules": {
"packages/react/src/scene/mesh/useGroundShadow.tsx": "df93a3381a4ee9a8e00c43793d7c0baabec1eb106459aac6b34ce8b3ad0443c2",
"packages/react/src/scene/mesh/useMeshAtlas.ts": "27f9d903a111055acefdbb43653dc247dd87a289e7071ec4eb2457b4ed12d32c",
"packages/react/src/scene/mesh/useMeshEvents.ts": "1c88e709cc3965636b7e983ce9198c090f12e539e75bb5afcc00b593cdb2de9d",
"packages/react/src/scene/mesh/useMeshGeometry.ts": "ce7c66c48e80e7579c068cb9b2c42096a3fde34fc237bed4c4715d274f8874ea",
"packages/react/src/scene/mesh/useMeshLighting.ts": "b36e5e0fbeee9b7429a83b09ebe4249e64ed33f4aea0dfd140c65f9a737af0bb",
"packages/react/src/scene/mesh/useReceiverShadows.tsx": "7201e3f02e90b0358ef71170c23a0a5928b1009adb399ceaf7ae4838b1607dd1",
"packages/react/src/scene/mesh/useStableDom.ts": "29eca9ed7e30bc235b8885b65706fe75b41237bff23bdb689040f98ff3f2a60f",
"packages/react/src/scene/mesh/useVoxelFastPath.ts": "e4fd58487557e67c8576b327452db91b357c947bf18a5761efe60a07d5ef2bd1",
"packages/vue/src/scene/mesh/useGroundShadow.ts": "ec91a425a04e0424653c6c5cc7a3f2ca3f116788c4234d2fdf808c02caf90770",
"packages/vue/src/scene/mesh/useMeshAtlas.ts": "a0f9532f9b770c9d382382b87d546cb7c13086af3c1e6154564af5597015889b",
"packages/vue/src/scene/mesh/useMeshEvents.ts": "81874ccad4827e21cf891fd0b8512b87c1b6f304b7c7476f79539da7f8f103f2",
"packages/vue/src/scene/mesh/useMeshGeometry.ts": "e1518f576fa8724218967996fee89ced0175fa044b1587d8e2d9693e7bac1e52",
"packages/vue/src/scene/mesh/useMeshLighting.ts": "885b8caaba5955d3668135bf4b124a4c32052c5f05f30574f371498470a6646d",
"packages/vue/src/scene/mesh/useReceiverShadows.ts": "92bb8a56a567e65ea25751574c6922ae9bb859d2825eec9159ff679aadd34f6e",
"packages/vue/src/scene/mesh/useStableDom.ts": "cc92fd16d0c9f87d3cc8762004122da26a21ff8b123249ce79b45bbd9a380042",
"packages/vue/src/scene/mesh/useVoxelFastPath.ts": "dc4d5f8c46c90b7232d8a3b665fc281f5f03e4d9ab0c461db541c403a0b474e6"
},
"base-styles": {
"packages/polycss/src/styles/styles.ts": "fcb6f5745de2f4cf86f70186d7f2d176d06814fefa165236d86d5f0bcb95a412",
"packages/react/src/styles/styles.ts": "9f281daa9555b59c431755cd9f32024aa4248c0a9666737939dbcc02494eec34",
"packages/vue/src/styles/styles.ts": "fefd385566d3a778604f4e3f3b2d0252f95b77f82ceed7b689bbb5e0a9e7daf1"
}
}
10 changes: 10 additions & 0 deletions .github/mirror-waivers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"voxel-renderer": {
"reason": "React/Vue caught up to a fix packages/polycss already carried: their camera-cull item ordering still used raw rotateVec3 for the mesh rotation, while the vanilla copy already used rotateVec3InWrapperCssFrame. Only the two lagging lanes changed, so the lanes converged rather than diverged.",
"files": [
"packages/react/src/scene/voxelRenderer.ts",
"packages/vue/src/scene/voxelRenderer.ts"
],
"expectedLanes": ["react", "vue"]
}
}
252 changes: 252 additions & 0 deletions .github/scripts/check-boundaries.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/**
* Enforces the package dependency rules documented in AGENTS.md:
*
* core → (nothing; pure ES2020, no browser or node builtins)
* polycss → core
* react → core (+ react, react-dom) — NEVER polycss
* vue → core (+ vue) — NEVER polycss
* fonts → core + earcut
* morph → polycss public API (+ core); node builtins only under prepare/
* domformat→ (no workspace deps)
* skills → (no workspace deps; a Node CLI that only copies markdown)
*
* The rules are applied to BOTH the source imports and the package manifest:
* a package whose `dependencies` / `optionalDependencies` / `peerDependencies`
* name something outside its allow-list fails even if no source file imports
* it yet. (`devDependencies` are tooling and are not constrained.)
*
* Also forbids, everywhere in the repo, deep imports into another package's
* src (e.g. `@layoutit/polycss-core/src/...` or `../../packages/x/src/...`)
* except for explicitly allowlisted dev-tooling entries.
*
* These rules previously lived only in prose + a PR checklist; this script is
* the CI-enforced version. Run: `node .github/scripts/check-boundaries.mjs`
*/
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { dirname, relative, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";

const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");

/**
* Allowed EXTERNAL specifier prefixes per package (workspace + third-party).
* `dirs` lists the scanned source roots; it defaults to just `src`.
*/
export const PACKAGE_RULES = {
core: { allow: [] },
polycss: { allow: ["@layoutit/polycss-core"] },
react: { allow: ["@layoutit/polycss-core", "react", "react-dom"] },
vue: { allow: ["@layoutit/polycss-core", "vue"] },
fonts: { allow: ["@layoutit/polycss-core", "earcut"] },
morph: { allow: ["@layoutit/polycss", "@layoutit/polycss-core"] },
domformat: { allow: [] },
// The skill installer copies markdown with node builtins only; it must never
// pull a renderer in, or `npx polycss-skills` would install a whole engine.
skills: { allow: [], dirs: ["src", "bin"] },
};

/** Manifest fields that declare shipped dependencies. */
export const MANIFEST_DEPENDENCY_FIELDS = [
"dependencies",
"optionalDependencies",
"peerDependencies",
];

/** Packages whose runtime source must not touch node builtins. */
const NO_NODE_BUILTINS = new Set(["core", "polycss", "react", "vue", "fonts"]);

/** morph is Node-only under prepare/; its browser graph must stay clean. */
const MORPH_NODE_PREFIX = `src${sep}prepare${sep}`;

/**
* Known deep-src imports that are accepted for now. Keep this list SHORT and
* annotated; every entry is debt.
*/
export const DEEP_IMPORT_ALLOWLIST = new Set([
// bench/ is not a workspace member, and the atlas emit surface it measures
// is intentionally not public API. Tracked as F1 in the architecture notes.
"bench/entries/atlasBackground.ts",
]);

const SOURCE_RE = /\.(ts|tsx|mts|cts|mjs|cjs|js)$/;
const IMPORT_RE =
/(?:^|\s)(?:import|export)\s+(?:[^"'`]*?\s+from\s+)?["']([^"']+)["']|(?:^|[^.\w])(?:import|require)\s*\(\s*["']([^"']+)["']\s*\)/gm;

/** Build output and vendored trees are never authored source. */
const SKIPPED_DIRS = new Set([
"node_modules",
"dist",
".generated",
"coverage",
"build",
"vendor",
]);

function* walk(dir) {
for (const entry of readdirSync(dir)) {
if (SKIPPED_DIRS.has(entry)) continue;
const abs = resolve(dir, entry);
if (statSync(abs).isDirectory()) yield* walk(abs);
else if (SOURCE_RE.test(entry)) yield abs;
}
}

export function extractSpecifiers(source) {
const specifiers = [];
for (const match of source.matchAll(IMPORT_RE)) {
const spec = match[1] ?? match[2];
if (spec) specifiers.push(spec);
}
return specifiers;
}

const isRelative = (spec) => spec.startsWith(".");
const isTestFile = (file) => /\.test\.(ts|tsx|mts|cts|mjs|cjs|js)$/.test(file);

/**
* The manifest is the other half of the boundary: an unused-but-declared
* dependency still ships to npm and still puts the package on the wrong graph.
*/
export function checkManifestDependencies(manifest, pkg, rules) {
const violations = [];
for (const field of MANIFEST_DEPENDENCY_FIELDS) {
const declared = manifest?.[field];
if (!declared || typeof declared !== "object") continue;
for (const name of Object.keys(declared)) {
if (!rules.allow.includes(name)) {
violations.push(
`packages/${pkg}/package.json: disallowed ${field} entry "${name}"`,
);
}
}
}
return violations;
}

function checkPackageManifest(root, pkg, rules, violations) {
const manifestPath = resolve(root, "packages", pkg, "package.json");
if (!existsSync(manifestPath)) {
violations.push(`packages/${pkg}/package.json: missing manifest`);
return;
}
let manifest;
try {
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
} catch (error) {
violations.push(
`packages/${pkg}/package.json: unreadable manifest (${error.message})`,
);
return;
}
violations.push(...checkManifestDependencies(manifest, pkg, rules));
}

function checkPackage(root, pkg, rules, violations) {
for (const dir of rules.dirs ?? ["src"]) {
checkPackageDir(root, pkg, dir, rules, violations);
}
}

function checkPackageDir(root, pkg, dir, rules, violations) {
const srcDir = resolve(root, "packages", pkg, dir);
if (!existsSync(srcDir)) return;
for (const abs of walk(srcDir)) {
const rel = relative(root, abs);
const relInPkg = relative(resolve(root, "packages", pkg), abs);
const source = readFileSync(abs, "utf8");
for (const spec of extractSpecifiers(source)) {
if (isRelative(spec)) {
// Same-package escapes above src (e.g. ../../package.json) are fine;
// only crossing into ANOTHER package's tree is a violation.
const pkgDir = resolve(root, "packages", pkg);
const resolved = resolve(dirname(abs), spec);
if (!resolved.startsWith(pkgDir + sep) && resolved !== pkgDir) {
violations.push(
`${rel}: relative import escapes the package: "${spec}"`,
);
}
continue;
}
if (/^@layoutit\/[^"']+\/src(\/|$)/.test(spec)) {
violations.push(`${rel}: deep import into a package's src: "${spec}"`);
continue;
}
// Test files may use dev-only tooling (vitest, happy-dom, node:test…).
if (isTestFile(abs)) continue;
if (spec.startsWith("node:")) {
const nodeAllowed =
!NO_NODE_BUILTINS.has(pkg) &&
(pkg !== "morph" || relInPkg.startsWith(MORPH_NODE_PREFIX));
if (!nodeAllowed) {
violations.push(`${rel}: node builtin not allowed here: "${spec}"`);
}
continue;
}
const base = spec.startsWith("@")
? spec.split("/").slice(0, 2).join("/")
: spec.split("/")[0];
if (!rules.allow.includes(base)) {
violations.push(`${rel}: disallowed dependency "${spec}"`);
}
}
}
}

function checkDeepImportsOutsidePackages(root, violations) {
const scanRoots = ["website/src", "website/scripts", "bench", "examples"]
.map((dir) => resolve(root, dir))
.filter((dir) => {
try {
return statSync(dir).isDirectory();
} catch {
return false;
}
});
for (const scanRoot of scanRoots) {
for (const abs of walk(scanRoot)) {
const rel = relative(root, abs).split(sep).join("/");
if (DEEP_IMPORT_ALLOWLIST.has(rel)) continue;
const source = readFileSync(abs, "utf8");
for (const spec of extractSpecifiers(source)) {
const deepBare = /^@layoutit\/[^"']+\/src(\/|$)/.test(spec);
const deepRelative =
isRelative(spec) &&
/(^|\/)packages\/[^/]+\/src(\/|$)/.test(
relative(root, resolve(dirname(abs), spec)).split(sep).join("/"),
);
if (deepBare || deepRelative) {
violations.push(
`${rel}: deep import into a package's src: "${spec}"`,
);
}
}
}
}
}

export function runChecks(root) {
const violations = [];
for (const [pkg, rules] of Object.entries(PACKAGE_RULES)) {
checkPackageManifest(root, pkg, rules, violations);
checkPackage(root, pkg, rules, violations);
}
checkDeepImportsOutsidePackages(root, violations);
return violations;
}

const isDirectRun =
process.argv[1] &&
resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isDirectRun) {
const violations = runChecks(repoRoot);
if (violations.length > 0) {
console.error("Boundary check failed:\n");
for (const violation of violations) console.error(` ${violation}`);
console.error(
`\n${violations.length} violation(s). The dependency rules live in ` +
"AGENTS.md (monorepo table + PolyCSS Morph/domformat boundaries).",
);
process.exit(1);
}
console.log("Boundary check passed.");
}
Loading
Loading