Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .clack/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Claude Code & Clack Agent Guidelines for Liquid DOM

This file provides system instructions for console-based coding agents (like Claude Code) operating in the Liquid DOM monorepo.

---

## 1. Environment & Monorepo Operations

The codebase is built using `pnpm` monorepo workspaces. Always run packages builds and tests using the filters:

- **Install Workspace**: `pnpm install`
- **Build All**: `pnpm -r build`
- **Layout Tests**: `pnpm --filter @liquid-dom/layout test`
- **Core Tests**: `pnpm --filter @liquid-dom/core test`
- **React Tests**: `pnpm --filter @liquid-dom/react test`

When building, executing scripts, or resolving typescript compilation issues, make sure to compile dependencies (`@liquid-dom/layout` and `@liquid-dom/core` first) before testing higher-level packages (`@liquid-dom/react`, `@liquid-dom/three`, `@liquid-dom/r3f`).

---

## 2. Code Refactoring & Contribution Rules

When editing files in `packages/`:

### A. Core Physics & Layout Calculations
- Do NOT rewrite or simplify the spring Euler substepping code in `@liquid-dom/react` or `@liquid-dom/core` unless requested. The substepping ($dt_{\text{spring}}$ split into 60Hz slices) is critical to prevent numerical integration explosion.
- Do NOT alter the bilinear tap-pairing coordinates `[1.4584295, 3.4039848, 5.3518057]` or weights `[0.23933733, 0.1394403, 0.052710965]`. These are mathematically tuned to match a standard Gaussian distribution ($\sigma = 3$) at 7 taps.

### B. Interface Separations
- **Layout engine (`@liquid-dom/layout`)**: Maintain zero DOM references, zero CSS references, and zero WebGPU resources.
- **Core rendering (`@liquid-dom/core`)**: Maintain strict hierarchy validations inside scene node additions (`Scene.add`, `Container.add`, `Glass.add`). Do NOT allow `Glass` components to accept other `Glass` children.
- **Three adapter (`@liquid-dom/three`)**: Require and assert the presence of `WebGPURenderer`. Guard with features checkers.

---

## 3. Pull Request & Verification Standards
- Before completing a task, run the TypeScript compiler `tsc` or package bundlers to verify that type signatures match across dependencies.
- Ensure all tests pass.
- Maintain existing docstrings and comments. Do not delete them.
63 changes: 63 additions & 0 deletions .cursor/rules/adaptive-tint-blur.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
description: Performance models for adaptive blurs, downsampling math, bilinear tap-pairing, backdrop luminance, settle debouncing, and WeakMap state isolation.
globs: "packages/core/**/*, packages/react/**/*"
alwaysApply: true
---

# Adaptive Blur & Adaptive Tinting Specs

To keep frame rates high under large blur and tint updates, the WebGPU glass renderer implements mathematical decimation and backdrop analysis pipelines.

## 1. Adaptive Gaussian Blur Model

Applying standard Gaussian blurs over wide pixel radii is expensive. The engine optimizes this by downsampling the input texture before running a separable 1D pass.

### Downsample Level Selection ($L$)
The downsample factor is computed using the base dense radius constant (6.0px):
$$L = \min\left(\max\left( \left\lceil \log_2\left(\frac{\text{radiusPx}}{6.0}\right) \right\rceil, 0 \right), L_{\text{max}}\right)$$

### Bilinear Tap-Pairing Optimization
Instead of sampling a standard 13-tap horizontal and vertical Gaussian filter, the shader utilizes linear hardware filtering to fetch midpoints between offset nodes. This reduces the pass to **7 taps**, saving roughly 46% of texture bandwidth:
- **Center offset/weight**: `0.0` / `0.13702282`
- **Bilinear offsets**: `[1.4584295, 3.4039848, 5.3518057]`
- **Bilinear weights**: `[0.23933733, 0.1394403, 0.052710965]`

---

## 2. Adaptive Tinting Backdrop Analysis

Adaptive tinting reads pixels behind the glass container, analyzing colors and brightness to adjust the glass overlay automatically.

### Metrics Retrieval
Activate metrics tracking on the renderer and fetch them inside a frame loop:
```ts
renderer.setBackdropMetricsTracking(container, true);
const metrics = renderer.getBackdropMetrics(container);
// Exposes averageLinearColor, averageLuminance, luminanceP10, luminanceP50, luminanceP90
```

### Median Luminance ($P_{50}$) & S-Curve Mapping
The engine uses **median luminance ($P_{50}$)** instead of the average to ignore outlier specular glares.
1. The luminance is mapped using a `smoothstep` S-curve over a `[0.08..0.92]` window:
$$\text{normalized} = \text{smoothstep}(0.08, 0.92, \text{luminanceP50})$$
2. Clamps and shifts the target brightness:
$$\text{mappedBrightness} = 0.1 + \text{normalized} \times 0.75$$
3. Brightness floor rule (preventing glass from looking dark on bright backdrops):
$$\text{targetBrightness} = \max(\text{mappedBrightness}, \text{luminanceP50})$$

---

## 3. Debounce & Smoothing Rules

To prevent subpixel rendering noise or scroll updates from causing visual jitter, follow these rules:

1. **Epsilon Gate**: Do not trigger target updates unless:
$$\lvert \text{nextObserved} - \text{observed} \rvert > 0.01$$
2. **Settle Timer**: Changes queue a pending target scheduled for $300$ms in the future. If a new change crosses the epsilon before the timer fires, the settle timer resets.
3. **Exponential Smoothing**: Once promoted, transition the current brightness smoothly using a frame-rate independent easing function:
$$\text{current} = \text{current} + (\text{target} - \text{current}) \times \left(1 - e^{-dt / \tau}\right)$$
- Default time constant $\tau$ (`easingDurationMs`): `500`
4. **WeakMap Isolation**: Store all states indexed by container to prevent cross-container leaks and support multi-glass overlays:
```ts
const adaptiveTintStates = new WeakMap<Container, AdaptiveTintState>();
```
91 changes: 91 additions & 0 deletions .cursor/rules/animation-system.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
description: Custom animation systems, spring physics equations, cubic bezier curves, timeScale configuration, and hook/timeline APIs.
globs: "packages/react/**/*, packages/r3f/**/*"
alwaysApply: true
---

# Animation System & Physics Integration

Liquid DOM manages high-performance animations outside of standard React renders, editing scene node positions directly on each frame tick.

## 1. Physics-Based Spring System

The animation loop utilizes a substepped integrator to guarantee stability under frame drops and low performance.

### Spring Configurations
- **`stiffness`**: Restoration force constant $k$ (default `300`).
- **`damping`**: Friction decay $c$ (default `30`).
- **`mass`**: Inertia constant $m$ (default `1`).
- **`restSpeed`**: Minimum velocity threshold (default `0.01`).
- **`restDelta`**: Minimum error distance (default `0.01`).

### Integration Loop Rules
1. Every frame delta $dt$ is scaled:
$$dt_{\text{scaled}} = dt \times \text{timeScale}$$
2. The step size is clamped to a maximum of 64ms:
$$dt_{\text{spring}} = \min(0.064, dt_{\text{scaled}})$$
3. The delta is divided into 60Hz intervals:
$$\text{stepCount} = \max\left(1, \left\lceil \frac{dt_{\text{spring}}}{1/60} \right\rceil\right)$$
$$\text{stepSeconds} = \frac{dt_{\text{spring}}}{\text{stepCount}}$$
4. Substep iteration equations:
$$F_s = -k \cdot (x - x_{\text{target}})$$
$$F_d = -c \cdot v$$
$$a = \frac{F_s + F_d}{m}$$
$$v_{\text{next}} = v + a \cdot \text{stepSeconds}$$
$$x_{\text{next}} = x + v_{\text{next}} \cdot \text{stepSeconds}$$

---

## 2. Bezier Easing Solver

For traditional duration transitions, the solver maps linear progress $p = \text{clamp01}(t_{\text{elapsed}} / \text{duration})$ using a 2D Cubic Bezier Solver:
1. Runs **Newton-Raphson iteration** up to 8 times:
$$t_{n+1} = t_n - \frac{B(t_n, cx_1, cx_2) - x}{B'(t_n, cx_1, cx_2)}$$
2. Falls back to **Binary Search** up to 16 times if Newton-Raphson fails to converge.
3. Evaluates and maps the coordinate $y = B(t, cy_1, cy_2)$.

---

## 3. Hook & Timeline APIs

- **`useFrame(callback, priority)`**: Runs a custom callback inside the canvas frame tick loop. State parameters include `layoutScene`, `renderer`, `scene`, `canvas`, `time`, `delta`, `invalidateLayout`, and `invalidateFrame`.
- **`useAnimate()`**: Triggers direct, imperative property transitions:
```ts
const animate = useAnimate();
const controls = animate(node, { x: 100 }, spring({ stiffness: 200 }));
// controls exposes .stop() and .finished Promise
```
- **`useTimeline()`**: Chains multi-node sequences:
```ts
const createTimeline = useTimeline();
const tl = createTimeline()
.to(node1, { scaleX: 1.2 }, spring())
.to(node2, { y: 50 }, easing({ duration: 0.4 }))
.call(() => console.log('Sequence done!'))
.play();
```
- **`<AnimationConfigProvider>`**:
- Prop: `timeScale?: number`. Set `timeScale={0.5}` to trigger half-speed slow-motion effects.

---

## 4. Code Pattern Examples

### Declarative Animations
```tsx
import { Frame, spring, easing, Easing } from '@liquid-dom/react';

function AnimatedCard({ active }: { active: boolean }) {
return (
<Frame
width={active ? 300 : 150}
height={active ? 200 : 100}
transition={{
width: spring({ stiffness: 220, damping: 18 }),
height: easing({ duration: 0.35, ease: Easing.easeInOut }),
default: spring()
}}
/>
);
}
```
63 changes: 63 additions & 0 deletions .cursor/rules/core-architecture.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
description: Structural design rules, package dependencies, WebGPU constraints, and scene graph nesting invariants for Liquid DOM.
globs: "packages/core/**/*, packages/layout/**/*, packages/three/**/*"
alwaysApply: true
---

# Core Architecture & WebGPU Constraints

This file defines the structural constraints, package relations, and scene invariants for Liquid DOM.

## 1. Package Dependencies & Import Hierarchy

- **`@liquid-dom/layout`**: Core layout algorithms. Has zero external dependencies and does not know about the DOM, CSS, or rendering. Do NOT import renderers or browser APIs in layout.
- **`@liquid-dom/core`**: The main rendering engine. Imports `@liquid-dom/layout` nodes inside `@liquid-dom/core/layout` subpaths to bind them with WebGPU elements.
- **`@liquid-dom/three`**: The adapter for standard Three.js render loops. Wraps `core` elements. It is ONLY compatible with `WebGPURenderer`. It does NOT support WebGL.

---

## 2. Hard Rendering Constraints

1. **WebGPU ONLY**: The canvas rendering requires `navigator.gpu`. In Three.js integrations, always use `THREE.WebGPURenderer`. Guard all WebGPU setups with features checks.
2. **HTML-in-Canvas Chrome Flag**: Drawing DOM elements onto WebGPU glass textures requires enabling the Canvas Draw Element flag: `chrome://flags/#canvas-draw-element`.
- The engine spawns a `<canvas layoutsubtree="true">` to synchronize repaint hooks. Without this flag enabled in the client browser, HTML textures will be blank.

---

## 3. Strict Scene Graph Invariant Nesting Rules

When building or modifying scene graph elements (`Scene`, `Container`, `Glass`, `Html`, `Group`), you MUST follow these absolute parenting rules:

- **`Scene`** accepts ONLY `Container` | `Html` | `Group`.
- **`Container`** accepts ONLY `Glass` | `Group`.
- All sibling `Glass` children in a container are blended via SDF calculations and share the same container's optical properties.
- **`Glass`** accepts ONLY `Html` | `Group`.
- `<Html>` elements are sampled through the bounds of the host glass node.
- **`Group`** and **`StackingContext`** are layout-neutral and can wrap elements to apply transforms. They do NOT disrupt stacking or validate constraints of their children.

> [!CAUTION]
> **No Nested Glass**: You cannot nest a `Glass` node inside another `Glass` node. Doing so will raise a runtime validation error. If you need stacked layers, put them in a `ZStack` as siblings under separate layout bounds.

---

## 4. Code Pattern Examples

### Imperative Scene Graph Setup (Core)
```ts
import { Scene, Container, Glass, Html, StackingContext } from '@liquid-dom/core';

const scene = new Scene();

// OK: Scene -> Container
const container = new Container({ blur: 10, thickness: 80 });
scene.add(container);

// OK: Container -> Glass
const card = new Glass({ cornerRadius: 16 });
container.add(card);

// OK: Glass -> Html
const label = new Html({ width: 100, height: 40 });
label.setElement(document.getElementById('my-label'));
card.add(label);
```
78 changes: 78 additions & 0 deletions .cursor/rules/react-components.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
description: Sizing modes, layout container tags, child limits, and React Three Fiber bridging for Liquid DOM's React bindings.
globs: "packages/react/**/*, packages/r3f/**/*"
alwaysApply: true
---

# React Layout Components & R3F Integration

This file covers the declarative React 19 component library bindings, sizing modes, layout restrictions, and React Three Fiber (R3F) structures.

## 1. SwiftUI-Style Layout Elements

- **`<HStack>` / `<VStack>`**: Horizontal and vertical stacking.
- Props: `spacing?: number`, `alignment?: string`.
- **`<ZStack>`**: Layers children front-to-back.
- Props: `alignment?: string`.
- **`<Frame>`**: Clamps child sizing.
- Props: `width`, `height`, `minWidth`, `minHeight`, `idealWidth`, `idealHeight`, `maxWidth`, `maxHeight`, `alignment`.
- **Restriction**: Accepts exactly ONE layout child.
- **`<Padding>`**: Insets the child layout.
- Props: `insets?: number | { top?, right?, bottom?, left? } | { horizontal?, vertical? }`.
- **Restriction**: Accepts exactly ONE layout child.
- **`<Spacer>`**: Fills available axis space.
- Props: `minLength?: number`.
- **`<Transform>`**: Translates, rotates, and scales.
- Props: `x`, `y`, `scaleX`, `scaleY`, `rotation` (radians), `origin` (`{ x, y }` normalized coordinates, e.g., `{ x: 0.5, y: 0.5 }` is center).
- **Restriction**: Accepts exactly ONE layout child.

---

## 2. `<Html>` Sizing Modes

When hosting DOM portals inside `<Html>` nodes, you must explicitly declare the `sizing` prop:

- **`'intrinsic'`**: The node sizes itself automatically to match the browser's bounding box calculation of the DOM subtree.
- **`'constrained-width'`**: The parent layout dictates the width proposal, and the DOM subtree determines its height based on wrapping behavior.
- **`'fill'`**: The parent layout dictates both the width and height. The DOM subtree expands to fill this container size exactly.

---

## 3. React Three Fiber (R3F) Integration

To overlay glass layouts atop standard R3F 3D viewports:

1. Wrap the Three Canvas elements inside `<LiquidGlassR3F.Root>`.
2. Wrap your layout stack inside `<LiquidGlassR3F.Scene>` (nested as a child of the Root).
3. Place `<LiquidGlassR3F.Render>` inside the Canvas to trigger the WebGPU backdrop compositor. Always pass `renderPriority` (defaults to `1`) to composite glass in the correct order.
4. Ensure the React Three Fiber Canvas is configured to use Three's `WebGPURenderer`.

---

## 4. Code Pattern Examples

### Correct Layout Hierarchy
```tsx
import { LiquidCanvas, GlassContainer, Glass, VStack, HStack, Padding, Html } from '@liquid-dom/react';

function LayoutApp() {
return (
<LiquidCanvas style={{ width: '100%', height: '100%' }}>
<GlassContainer blur={8} spacing={10}>
<Glass cornerRadius={12}>
{/* OK: Padding wraps exactly one VStack */}
<Padding insets={16}>
<VStack spacing={12}>
<HStack spacing={8}>
<Html sizing="intrinsic"><div>Icon</div></Html>
<Html sizing="intrinsic"><h2>Title</h2></Html>
</HStack>
<Html sizing="fill"><p>Body text fitting frame bounds.</p></Html>
</VStack>
</Padding>
</Glass>
</GlassContainer>
</LiquidCanvas>
);
}
```
43 changes: 43 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# GitHub Copilot System Prompts for Liquid DOM

These guidelines are loaded by GitHub Copilot to assist developers writing code for Liquid DOM.

---

## 1. Context Summary
- **Liquid DOM** is a monorepo containing five WebGPU glass-rendering and SwiftUI-like layout packages:
- `@liquid-dom/layout` (pure math layout)
- `@liquid-dom/core` (imperative WebGPU scene graph)
- `@liquid-dom/react` (React 19 bindings)
- `@liquid-dom/three` (Three.js WebGPU post-compositing)
- `@liquid-dom/r3f` (React Three Fiber canvas bridging)

---

## 2. Code Generation Guidelines

### A. General Requirements
- **WebGPU Target**: Always assume the rendering context supports `navigator.gpu`.
- **No legacy WebGL**: When generating Three.js scenes, always write `THREE.WebGPURenderer`. Do NOT generate `THREE.WebGLRenderer`.
- **HTML-in-Canvas**: Always mention that DOM rendering in the canvas requires setting the experimental Chrome flag: `chrome://flags/#canvas-draw-element`.

### B. SwiftUI-Style Layout Rules
- Layout structures follow: parent proposes $\rightarrow$ child measures $\rightarrow$ parent places.
- **Child Clamp Restrictions**: `<Frame>`, `<Padding>`, and `<Transform>` accept exactly **ONE** child. Stacks (`<HStack>`, `<VStack>`, `<ZStack>`) accept multiple children.
- When generating `<Html>` components, always specify the `sizing` prop as `'intrinsic'`, `'constrained-width'`, or `'fill'`.

### C. Scene Graph Nesting Invariants
Validate all imperative scene graph constructions to match these relationships:
- `Scene` accepts ONLY `Container` | `Html` | `Group`.
- `Container` accepts ONLY `Glass` | `Group`.
- `Glass` accepts ONLY `Html` | `Group`.
- **Absolute Constraint**: Never nest a `Glass` inside another `Glass` node directly or indirectly. It will raise a runtime exception.

---

## 3. Reference Math
- **Spring Integration**: Semi-implicit Euler loop using substepped iteration scaled by `timeScale` and capped at 64ms.
- **Adaptive Blur Downsampling Factor ($L$)**:
$$L = \min\left(\max\left( \left\lceil \log_2\left(\frac{\text{radiusPx}}{6.0}\right) \right\rceil, 0 \right), L_{\text{max}}\right)$$
- **Bilinear Gaussian Taps**: Separable 1D blurs use bilinear pairing interpolation to reduce texture sampling to **7 taps** (center + 3 symmetrical pairs).
- **Adaptive Tinting**: Mapped from the median backdrop luminance ($P_{50}$) through a soft `smoothstep(0.08, 0.92, P50)` S-curve, debounced with a $0.01$ epsilon limit and a $300$ms settlement delay.
Loading