{"id":48819150,"url":"https://github.com/leanprover/illuminate","last_synced_at":"2026-04-14T14:00:57.890Z","repository":{"id":351102149,"uuid":"1183419468","full_name":"leanprover/illuminate","owner":"leanprover","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-13T15:18:35.000Z","size":6511,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-13T17:34:32.235Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Lean","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leanprover.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-16T15:33:59.000Z","updated_at":"2026-04-09T18:41:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/leanprover/illuminate","commit_stats":null,"previous_names":["leanprover/illuminate"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/leanprover/illuminate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover%2Filluminate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover%2Filluminate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover%2Filluminate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover%2Filluminate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leanprover","download_url":"https://codeload.github.com/leanprover/illuminate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leanprover%2Filluminate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31799411,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T11:13:53.975Z","status":"ssl_error","status_checked_at":"2026-04-14T11:13:53.299Z","response_time":153,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-14T14:00:27.666Z","updated_at":"2026-04-14T14:00:57.881Z","avatar_url":"https://github.com/leanprover.png","language":"Lean","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Illuminate\n\nIlluminate is a diagramming library for Lean. It provides a\ncompositional approach to constructing two-dimensional diagrams,\nrendering them to SVG, and previewing them interactively in the Lean\nInfoView.\n\n**This library is experimental and subject to rapid change.** The API\nis under active development. Types, function signatures, and module\nstructure may change without notice between versions.\n\n## Core Concepts\n\n### Composing Diagrams\n\nDiagrams are built by combining smaller diagrams. Primitive shapes,\ntext, and images serve as the basic building blocks; composition\noperators such as `hcat`, `beside`, and `pinOver` assemble them into\nlarger structures. Transformations, styling, and naming are applied to\nany diagram regardless of how it was constructed.\n\nThe type `Diagram β` is parameterized by a foreign primitive type `β`,\nwhich allows backends to embed their own rendering objects alongside\nthe built-in primitives. Pure geometric diagrams use `Empty` for this\nparameter.\n\n### Envelopes, Not Bounding Boxes\n\nMost diagram libraries represent the spatial extent of a shape as an\naxis-aligned bounding box. Illuminate uses _envelopes_ instead. An\nenvelope is a function from a unit direction vector to a scalar\nextent:\n\n```lean\ndef Envelope := Vec2 → Float\n```\n\nGiven a direction `v`, an envelope returns the largest value `t` such\nthat the shape fits within the half-plane `{ p | p · v ≤ t }`. A\ncircle of radius 5 has the constant envelope `fun _ =\u003e 5`. A rectangle\nhas the envelope `fun v =\u003e |v.x| * halfWidth + |v.y| * halfHeight`.\n\nThis representation is more expressive than a bounding box. When two\nshapes are placed side by side, the library queries each envelope in\nthe direction of composition to compute the exact displacement,\nproducing tighter layouts for nonrectangular shapes. Envelopes compose\nnaturally: the union of two envelopes is their pointwise maximum, and\naffine transformations act on envelopes through the adjugate matrix.\n\n### Names and Anchors\n\nAny subdiagram can be given a hierarchical name with `Diagram.named`.\nA named diagram automatically receives cardinal anchor points (north,\nsouth, east, west, and the four intermediates) derived from its\nenvelope. Other parts of the diagram can refer to these anchors by\nqualified name. The `connect` function, for instance, draws an arrow\nbetween two named anchors, and `pinOver` places an overlay at a named\nanchor's position.\n\n`Diagram.scopeNames` wraps a diagram in a name scope. Names inside the\nscope are resolvable by arrows and connections within the scope, but\ninvisible from outside. This prevents name collisions when composing\nindependent diagram fragments that use the same internal names.\n\n### Cascading Configuration\n\nStyle properties such as stroke width, fill color, font size, and\narrowhead type propagate downward through the diagram via a cascading\nconfiguration system. Each property is independently set to one of\nthree states: _inherit_ from the parent, _reset_ to the global\ndefault, or _set_ to an explicit value.\n\nWrapping a subdiagram with `withFillColor Color.blue` sets the fill\nfor that subtree. Children inherit the setting unless they override\nit. Resetting allows a subtree to escape an ancestor's styling and\nreturn to the global default. This design means that reusable diagram\ncomponents can be inserted into any context without inheriting\nunwanted styles.\n\n### Deferred Layout\n\nThe final positions of named anchors are not known until the entire\ndiagram has been assembled. Operations that depend on anchor\npositions, such as `connect` and `pinOver`, insert _deferred_ nodes\ninto the diagram. During rendering, the layout engine resolves these\nnodes through fixed-point iteration: it repeatedly collects anchor\npositions and evaluates deferred callbacks until all positions\nconverge. This means that authors can connect and annotate named\nsubdiagrams without manually computing coordinates.\n\n### Validation\n\nEvery diagram renders to _something_. There are no partial failures or\nexceptions during rendering. However, certain structural issues cause\ndegraded behavior: duplicate names make anchor resolution ambiguous,\nreferences to missing names leave deferred nodes unresolved, and empty\npaths produce invisible geometry. The resulting output in these cases\nis unpredictable and unlikely to be what the author intended.\n\nThe `validate` function checks a diagram for these issues before\nrendering, returning a list of errors such as duplicate names, empty\npaths, and missing fallbacks for foreign primitives. Warnings embedded\nin the diagram can be collected separately with `collectWarnings`.\n\n## Building Diagrams\n\n### Shapes\n\nIlluminate provides a library of common shapes, each producing a\ndiagram with an appropriate envelope:\n\n- `Diagram.rect` and `Diagram.roundedRect` produce axis-aligned\n  rectangles.\n- `Diagram.circle` and `Diagram.ellipse` produce circular and\n  elliptical shapes.\n- `Diagram.polygon` produces a regular polygon with a given number of\n  sides.\n- `Diagram.star` produces a star with alternating outer and inner\n  vertices.\n- `Diagram.text` produces a text label.\n- `Diagram.styledText` produces a text label with mixed fonts and\n  colors.\n- `Diagram.line` produces a straight line segment.\n\nAll shapes are centered at the origin.\n\n### Extended Shapes\n\nThe `Illuminate.Shapes` module provides additional shapes organized\ninto five categories.\n\n#### Flowchart Shapes\n\n`Diagram.diamond` produces a rhombus with independent width and height\ncontrol, suitable for decision nodes. `Diagram.parallelogram` produces\na skewed rectangle for input/output nodes, with a configurable skew\nparameter. `Diagram.trapezoid` produces a shape with independent top\nand bottom widths. `Diagram.document` produces a rectangle with a wavy\nbottom edge.\n\nAll four accept an optional `label` parameter. When a label is\nprovided, the shape grows to ensure the label fits inside, and the\nlabel is centered within the shape.\n\n#### Arrow and Chevron Shapes\n\n`Diagram.blockArrow` produces a pentagonal block arrow pointing right.\n`Diagram.doubleArrow` produces a bidirectional block arrow with\narrowheads on both ends. `Diagram.chevron` produces a shape with a\npointed right side and a V-notch on the left, designed to chain\ntogether as pipeline steps.\n\nThree bent arrow shapes are available for routing indicators.\n`Diagram.bentArrow180` makes a U-turn with a semicircular arc.\n`Diagram.bentArrow90` makes a rounded 90-degree turn.\n`Diagram.squareBentArrow90` makes a sharp right-angle turn. All three\naccept `headWidth` and `headLength` parameters for the arrowhead, with\ndefaults derived from the shaft width.\n\n#### Decorative Shapes\n\n`Diagram.heart` produces a heart shape. Its `width`, `height`, and\n`cleft` parameters control the overall proportions and the depth of\nthe center indentation. The `cleft` parameter ranges from 0 (flush\nwith the lobe tops) to 1 (reaching the bottom point).\n\n`Diagram.plus` produces an n-armed cross. The `arms` parameter\n(default 4) controls the number of arms; for three or more arms, a\nregular polygon at the center connects rectangular arms extending\noutward. Numbered anchors (`tip0`, `tip1`, etc.) are placed at each\narm tip.\n\n`Diagram.stadium` produces a pill or capsule shape. It delegates to\n`roundedRect` with a corner radius equal to half the height.\n\n`Diagram.cylinder` produces a database symbol with separate `topFill`\nand `sideFill` parameters. The top cap is a full ellipse drawn on top\nof the cylindrical body, creating a three-dimensional appearance.\n\n`Diagram.cloud` produces a cloud shape made of circular arcs arranged\naround an elliptical perimeter. The `puffs` parameter controls the\nnumber of bumps and the `puffSize` parameter (0 to 1) controls how far\nthey bulge outward.\n\n#### Bubble Shapes\n\n`Diagram.speechBubble` produces a rounded rectangle with a triangular\ntail. The tail's position along the edge and which edge it extends\nfrom are configurable via `tailPosition` and `tailSide`.\n`Diagram.thoughtBubble` produces an elliptical body with trailing\ncircles that decrease in size toward the tail direction.\n\nTwo placement operators compose bubbles with existing diagrams.\n`Diagram.addSpeechBubble` and `Diagram.addThoughtBubble` take a named\nanchor and a `position` for the bubble center (relative to the diagram\norigin). They automatically compute the tail direction to point at the\nanchor. If the anchor falls inside the bubble body, a warning is\nemitted.\n\n#### Math Operator Shapes\n\nEight filled operator symbols are available: `opPlus`, `opMinus`,\n`opTimes`, `opDivide`, `opEquals`, `opNotEquals`, `opLessThan`, and\n`opGreaterThan`. Each takes a `size` parameter (the overall height)\nand a `lineWidth` parameter (the thickness of the filled regions).\nWhen all operators are given the same `size`, they are proportioned to\nlook visually consistent side by side. Each operator is drawn as a\nsingle closed path so that stroking does not produce internal lines.\n\n### Styled Text\n\n`Diagram.styledText` renders text where different segments can have\ndifferent fonts, sizes, weights, and colors. It takes a `StyledText`\nvalue and a base font style. Bare strings inherit the base style;\ncombinators modify it for their contents. Newline characters are\nrespected:\n\n```lean\nDiagram.styledText (base := { fontSize := 12 }) \u003c|\n  \"The \" ++ family \"monospace\" \"List\" ++ \" type is \" ++ bold \"polymorphic\" ++ \".\"\n```\n\nThe available combinators are:\n\n- `bold` — renders the inner fragment in bold.\n- `italic` — renders the inner fragment in italic.\n- `family` — sets the font family (e.g., `family \"monospace\"`).\n- `colored` — sets the text color (e.g., `colored Color.red`).\n- `styled` — applies an arbitrary `FontStyle → FontStyle` modifier.\n\nCombinators compose naturally: `bold (family \"monospace\" \"code\")`\nproduces bold monospace text. Newlines in any string fragment start a\nnew line:\n\n```lean\nDiagram.styledText (base := { fontSize := 10 }) \u003c|\n  \"First line\\n\" ++ bold \"Second\" ++ \" line\"\n```\n\nThe lower-level function `styledLines` accepts\n`List (List (FontStyle × String))` directly.\n\n### Spatial Composition\n\nThe primary composition operations place diagrams adjacent to one\nanother, using envelopes to compute the necessary displacement:\n\n- `Diagram.beside` places one diagram next to another in an arbitrary\n  direction, with an optional gap.\n- `Diagram.hcat` and `Diagram.vcat` concatenate a list of diagrams\n  horizontally or vertically.\n- `Diagram.hsep` and `Diagram.vsep` do the same with uniform spacing\n  between elements.\n- `Diagram.grid` lays out a two-dimensional array of diagrams with\n  uniform cell sizes and configurable spacing.\n\nEach of these accepts an optional alignment parameter controlling\ncross-axis positioning.\n\n### Overlay and Deferred Positioning\n\nTwo diagrams can be overlaid at the same origin with\n`Diagram.compose`. For positioning relative to named anchors,\n`Diagram.pinOver` and `Diagram.pinUnder` place an overlay or underlay\nat the position of a named anchor within another diagram. Because\nanchor positions are not known until layout, these operations are\nresolved during the layout pass.\n\n### Transforms and Modifiers\n\nStandard affine transformations are available through `Diagram.scale`,\n`Diagram.scaleXY`, `Diagram.rotate` (counterclockwise, in radians),\n`Diagram.hflip`, and `Diagram.vflip`.\n\nPadding operations expand the envelope without changing the visual\ncontent. `Diagram.pad` adds uniform padding on all sides,\n`Diagram.padXY` adds horizontal and vertical padding independently,\nand `Diagram.padLRTB` controls each side separately.\n\nThe `frame` function draws a stroked rectangle around a diagram's\nenvelope, with optional corner rounding and padding.\n\n### Envelope Manipulation\n\nSeveral operations adjust a diagram's envelope without changing its\nappearance:\n\n- `floating` renders the diagram but contributes zero extent to\n  layout, making it invisible to surrounding composition.\n- `ghost` contributes the diagram's envelope to layout but renders\n  nothing.\n- `refocus` uses one subdiagram's envelope for a combined diagram.\n- `strut` creates an invisible spacer with a given envelope.\n- `hGap` and `vGap` create invisible spacers of a given width or\n  height.\n\n### Arrows and Connections\n\n`Diagram.connect` draws a curved arrow between two named anchor\npoints. Each endpoint is described by a `LineEnd`, which specifies the\ntarget anchor name, an optional departure or arrival angle, a pull\nfactor controlling the curvature of the Bezier control points, and an\noptional arrowhead. Four arrowhead types are available: `latex` (open\ntwo-line), `stealth` (filled), `triangle`, and `circle`.\n\n`Diagram.connectEdge` uses stroke traces to find the boundary of named\nshapes automatically, so arrows terminate at the shape edge rather\nthan its center. `Diagram.connectL` draws an L-shaped (single bend)\nconnector between two anchor points. `Diagram.connectU` draws a\nU-shaped (double bend) connector, with an `offset` parameter\ncontrolling the position of the middle segment. All connection\nfunctions accept optional labels.\n\n### Curly Brace Annotations\n\n`Diagram.curlyBrace` draws a curly brace centered at the origin,\nspanning a given width. The `angle` parameter (in radians) controls\nwhich direction the tip points; the default points downward. An\noptional `label` is placed beyond the tip.\n\nConvenience functions attach a brace to an existing diagram's\nenvelope: `braceBelow`, `braceAbove`, `braceLeftOf`, and\n`braceRightOf` span the full extent of a diagram along the\ncorresponding edge. The general `braceBy` places a brace along any\ndirection.\n\n### Tree Layout\n\n`treeLayout` arranges a rose tree of diagrams automatically using the\n[Buchheim-Junger-Leipert](https://doi.org/10.1007/3-540-36151-0_32)\nalgorithm, which runs in linear time and produces aesthetically\nbalanced layouts: nodes at the same depth are aligned, parents are\ncentered over their children, and isomorphic subtrees are drawn\nidentically.\n\nThe input is a `Tree (Diagram β)`, a rose tree where each node carries\na diagram. Convenience constructors `Tree.leaf`, `Tree.binary`, and\nthe general `Tree.node` build trees.\n\nA `TreeConfig` controls the layout:\n\n- `siblingGap` — minimum spacing between adjacent subtrees (default\n  20).\n- `levelGap` — distance between depth levels (default 40).\n- `orientation` — direction from root to children in radians. The\n  default `3 * pi / 2` gives a top-down tree; `0` gives left-to-right.\n  Any angle works.\n- `siblingAlign` — cross-axis alignment when nodes differ in size (0 =\n  near edge, 0.5 = center, 1 = far edge).\n- `drawEdge` — callback to draw each parent-child edge. Defaults to\n  `connectEdge`; set to `none` to suppress edges.\n\nNodes are named by their path in the tree: `node_0` for the root,\n`node_0_0` for the first child, `node_0_1_5` for the sixth child of\nthe second child, etc. These names have cardinal anchors, so custom\nedge-drawing callbacks can use any connection function (`connect`,\n`connectEdge`, `connectL`).\n\nBy default, the result is wrapped in a name scope so that internal\nnode names are invisible from outside and multiple trees can be\ncomposed without conflicts. Pass `name` to make the node names\naccessible under that namespace instead.\n\nThe `proofTree` function provides a dedicated layout for natural\ndeduction and sequent calculus proof trees, with horizontal inference\nlines and optional rule labels.\n\n### Style Helpers\n\nConvenience functions such as `withFillColor`, `withStrokeColor`,\n`withStrokeWidth`, `withFontSize`, `withFontFamily`, `withTextColor`,\nand `withArrowhead` wrap a subdiagram in a configuration override.\nEach has a corresponding `reset` variant (e.g., `resetFillColor`) that\nreverts the property to the global default for that subtree.\n\n## Domain-Specific Languages\n\nAs a demonstration of one way to use Illuminate, two domain-specific\ndiagram languages are included. These are in their infancy and are\nprobably not suitable for real use yet.\n\n### Commutative Diagrams\n\nThe `CommDiag` module provides a monadic DSL for building commutative\ndiagrams of the kind used in algebra and category theory:\n\n```lean\ndef mySquare : Diagram Empty :=\n  commDiag do\n    let a ← CommDiagM.node \"A\"\n    let b ← CommDiagM.node \"B\"\n    let c ← CommDiagM.node \"C\"\n    let d ← CommDiagM.node \"D\"\n    CommDiagM.grid #[#[some a, some b], #[some c, some d]]\n    CommDiagM.arrowWith a b { label := some \"f\" }\n    CommDiagM.arrowWith a c { label := some \"g\", side := .left }\n    CommDiagM.arrowWith b d { label := some \"h\" }\n    CommDiagM.arrowWith c d { label := some \"k\", side := .below }\n```\n\nNodes are created with labels, arranged in a grid, and connected by\nmorphism arrows with optional labels and curvature.\n\n### State Diagrams\n\nThe `StateDiagram` module builds DFA and NFA state diagrams with\nconfigurable radius, spacing, colors, and font sizes. States are laid\nout on a horizontal line and connected by straight or curved edges\nwith labeled transitions.\n\n## Interactive Previews\n\n### The `#diagram` Command\n\nThe `#diagram` command renders a diagram inline in the Lean 4\ninfoview. Hover over a `#diagram` line to see a live SVG preview:\n\n```lean\n#diagram Diagram.circle 30\n```\n\n### Parameterized Diagrams\n\nDiagram functions can accept interactive parameters using gadget\ntypes. The infoview renders appropriate controls (sliders, text\ninputs, checkboxes) and re-evaluates the diagram when the user adjusts\nthem:\n\n```lean\n#diagram fun (n : Slider \"Points\" 3 12) (r : Slider \"Radius\" 10 50) =\u003e\n  Diagram.star n.toNat r (r * 0.4)\n```\n\nThe `Slider` type produces a floating-point slider with a label,\nminimum, and maximum. `TextInput` and `Checkbox` provide string and\nboolean inputs, respectively. Each gadget type reduces to its\nunderlying value type (`Float`, `String`, or `Bool`), so the function\nbody uses the parameter as an ordinary value.\n\n### The `#animate` Command\n\nThe `#animate` command builds a step-based animation and plays it in\nthe infoview with a play/pause button and scrub bar:\n\n```lean\n#animate\n  [{ duration := 1.5 }, { duration := 2.0 }]\n  (fun progress =\u003e\n    let t := Easing.easeInOut progress[0]\n    let radius := Interpolate.interpolate 10.0 50.0 t\n    Diagram.circle radius (fill := .solid { color := Color.red }))\n```\n\nEach step has a duration in seconds and optional flags:\n`pause := true` stops playback until the user clicks, and\n`loop := true` repeats the step continuously. The render function\nreceives a vector of per-step progress values in `[0, 1]`. Built-in\neasing functions (`easeIn`, `easeOut`, `easeInOut`, `sineInOut`,\n`backOut`) and interpolation (`Interpolate.interpolate` for `Float`,\n`Vec2`, `Color`, `Matrix`) produce smooth transitions. Helper effects\nlike `fadeIn`, `fadeOut`, `crossFade`, `slide`, `animScale`, and\n`animRotate` compose common animation patterns.\n\nCompiled animations can also be rendered to standalone HTML files\n(`CompiledAnimation.renderHTML`) or embedded in reveal.js\npresentations (`CompiledAnimation.renderRevealHTML`).\n\n## Module Overview\n\n- `Illuminate.Diagram` contains the `Diagram` type, core shapes,\n  spatial algebra, arrow routing, and tree layout.\n- `Illuminate.Shapes` provides extended shapes: flowchart nodes, block\n  arrows, hearts, cylinders, clouds, speech and thought bubbles, and\n  math operator symbols.\n- `Illuminate.Widget` provides the `#diagram` command, interactive\n  parameter gadgets, and infoview integration.\n- `Illuminate.Animation` provides the `#animate` command, step-based\n  timeline with looping and pause steps, easing functions,\n  interpolation, and compilation to frame-based playback.\n- `Illuminate.DSL` includes the commutative diagram and state diagram\n  builders.\n- `Illuminate.Style` defines `Color`, `Fill`, `Stroke`, `TextStyle`,\n  `FontStyle`, `StyledText`, and arrowhead types.\n- `Illuminate.Geometry` provides `Vec2`, `Point`, `Matrix` (3x3 affine\n  transforms), `Envelope`, and `PathData`.\n- `Illuminate.Render` contains the `DrawCmd` display list and SVG\n  backend.\n\nThe root import `import Illuminate` re-exports all modules.\n\n## Development\n\nBuilding requires [elan](https://github.com/leanprover/elan), which\nmanages Lean toolchains automatically via the `lean-toolchain` file.\nThe tests additionally require [uv](https://docs.astral.sh/uv/) and\n[Docker](https://docs.docker.com/get-docker/) (on macOS,\n[colima](https://github.com/abiosoft/colima) works).\n\nBuild the library with `lake build --wfail`. The project enables the\n`missingDocs` linter, so all public declarations require docstrings.\nThe `--wfail` flag ensures warnings are treated as errors.\n\nThe test suite has two layers. `lake test --wfail` runs Lean unit\ntests and writes SVG output files. `uv run test_playwright.py` then\nruns two kinds of checks on those SVGs: structural DOM tests via\nPlaywright (headless Chromium), and pixel-level visual regression\ntests that render each SVG via Inkscape inside a Docker container with\npinned fonts (`visual_tests/Dockerfile`). Both should pass before\nsubmitting changes:\n\n```sh\nlake test --wfail \u0026\u0026 uv run test_playwright.py\n```\n\n### Type Checking Player JavaScript\n\nThe animation player and widget JavaScript in `player_js/` uses JSDoc\ntype annotations checked by TypeScript. React type definitions are\nvendored in `vendored_js/`. To run the type checker:\n\n```sh\nnpx tsc --noEmit -p player_js/jsconfig.json\n```\n\n### Updating Visual Baselines\n\nExpected images live in `visual_tests/` as `*.expected.png` files.\nWhen a visual test fails, compare the `*.actual.png` output against\nthe baseline. If the difference reflects an intentional change,\nregenerate the baselines with:\n\n```sh\nUPDATE_BASELINES=1 uv run test_playwright.py\n```\n\nReview the updated baselines in the diff before committing.\n\n## Acknowledgments\n\nThe design of Illuminate draws on three libraries in particular:\n[Racket pict](https://docs.racket-lang.org/pict/),\n[Haskell diagrams](https://diagrams.github.io/), and\n[TikZ/PGF](https://github.com/pgf-tikz/pgf).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleanprover%2Filluminate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleanprover%2Filluminate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleanprover%2Filluminate/lists"}