VisualCompiler is a visual-to-CSS layout inference tool. You place rectangles on a canvas and the app infers a likely grid or flex implementation, previews the result, and emits CSS plus responsive snapshots.
Canvas dragging uses soft snapping rather than hard layout enforcement.
Current behavior:
- nearby edges, centers, and repeated gaps still win first for local cleanup
- composition-level candidates can beat slightly closer local snaps when they complete a stronger global pattern
- drag overlays can show inferred ghost rows and columns for emerging grid structure
- sparse shapes such as plus, T, L, and near-complete grids surface multi-item snap targets
- a
Promote to gridbanner can move the dragged item into the single missing grid cell without creating a persistent layout override - holding
Altstill bypasses all drag-time snapping assistance
Requirements:
- Node.js 20+
- npm
Commands:
npm run devstarts Vite athttp://localhost:5173npm run buildruns TypeScript compilation and a production buildnpm run lintruns ESLintnpm run test:unitruns Vitestnpm run test:e2eruns Playwrightnpm testruns unit and end-to-end tests
src/canvas/: interactive canvas, snapping, breakpoint capture, item overridessrc/inference/: layout inference pipeline, candidate scoring, canonicalization, unit testssrc/output/: live preview, CSS panel, ambiguity and override UItests/: fixture-driven inference and browser tests
The main entrypoint is src/inference/index.ts.
Pipeline summary:
- Preprocess input geometry.
- Detect column and row tracks.
- Compute structural features and raw weakness scores.
- Classify the pattern (
uniform-grid,sparse-grid,weak-sparse-grid,flex-wrap,ambiguous) using policy thresholds. - Build the literal grid candidate from detected occupancy.
- Canonicalize weak sparse grids when allowed.
- Score grid vs flex and calibrate candidate confidence.
- Emit CSS and return primary plus alternate candidates.
Some small sparse shapes are bad fits for both flex-wrap and naive grid auto-placement. Examples:
- hollow diamonds
- sparse crosses without a dominant row or column
- small arrangements with weak alignment continuity and no believable wrap story
For these cases, VisualCompiler can replace the literal sparse occupancy with a stronger canonical grid template.
Weak sparse detection is intentionally generic rather than motif-specific. The feature layer computes raw geometry signals such as:
- alignment weakness
- compactness weakness
- narrative weakness
- stability weakness
Those raw weakness scores live in src/inference/features.ts. Policy concepts such as the bad-bucket cutoff and minimum weak-bucket count are applied later in src/inference/classifier.ts, so retuning classifier thresholds does not change the geometry feature contract.
Current behavior:
- Activation is narrow and limited to
weak-sparse-gridcases. - The canonicalizer compares the literal grid against a small template family.
- Current template families are
balanced-grid,anchored-3col, andhero-with-supporting-items. - Template assignment is geometry-driven, not item-array-order-driven.
- Canonicalization only takes effect when the rescored canonical candidate still wins as
grid. - The literal candidate must be below an explicit confidence threshold before rewriting is allowed.
- The selected canonical assignment must stay within explicit canvas-space movement budgets: average item movement in pixels and maximum single-item movement in pixels, both measured from the realized canonical grid layout anchored in the source shape's own frame.
- Canonicalization still needs a material score gain over the literal layout to apply.
- Translation alone should not change the movement gate for the same weak sparse shape.
The implementation lives in src/inference/canonicalize.ts.
Confidence is attached to realized candidates, not just the initial literal geometry.
That means:
- a canonicalized primary grid is calibrated from canonicalized grid structure
- a canonicalized grid alternate keeps its own canonicalized confidence context even when flex remains primary
- ambiguity UI bars are derived from each candidate’s own decision, features, and pattern class
Confidence calibration lives in src/inference/calibration.ts.
User constraints live in src/types.ts and are stored via the canvas store.
Current override support:
forceType: forcegridorflexspans: pin per-item grid spanssizing: pin grid track sizingjustifyContent: pin flex justificationcanonicalization:autoorliteral
canonicalization: 'literal' disables weak sparse rewriting and keeps the literal sparse grid candidate.
The ambiguity/override UI surfaces canonicalization controls whenever a grid candidate carries weak sparse metadata, even if flex is primary.
Deleting an item also prunes item-linked constraints from the store, so stale per-item override badges do not linger after removal.
Responsive snapshots are captured from canvas viewport changes and exported as media-query CSS.
Current behavior:
- Auto-capture records a snapshot when either layout structure changes or orientation bucket changes.
- Snapshots are keyed by
(width, orientation)rather than width alone, so the same width can preserve separate portrait, square, and landscape states. - Orientation buckets match app inference thresholds:
landscapewhenw / h > 6/5,portraitwhenw / h < 5/6, andsquarefor the inclusive middle range. - Responsive CSS generation adds orientation constraints to each breakpoint query:
landscapeuses(aspect-ratio > 6/5),portraituses(aspect-ratio < 5/6), andsquareuses(aspect-ratio >= 5/6) and (aspect-ratio <= 6/5). - This keeps same-width, multi-orientation snapshots from collapsing into conflicting duplicate
@media (min-width: Xpx)blocks.
Relevant coverage for the canonicalization layer:
- tests/inference.spec.ts: fixture coverage and end-to-end inference assertions
- tests/fixtures/weak-sparse.json: weak sparse regression fixtures including translated-shape cases
- src/inference/tests/canonicalize.test.ts: confidence-threshold, movement-budget, and translation-invariant movement gates
- src/inference/tests/index.test.ts: canonicalization merge behavior
- src/inference/tests/calibration.test.ts: per-candidate confidence calibration
- src/inference/tests/classifier.test.ts: weak sparse classification
- src/inference/tests/features.test.ts: raw weakness signal coverage
- src/inference/tests/responsive.test.ts: orientation-aware responsive CSS emission
- tests/canvas.spec.ts: browser capture behavior for orientation field, same-width orientation snapshots, and delete-time override cleanup
Recommended verification before committing:
npm run test:unitnpm run build