# Composure Design Evaluation

A comparative analysis of Composure against CSS, Kotlin Compose, Flutter, and SwiftUI.

---

## Executive Summary

Composure is a minimalist layout runtime (~650 lines) built on a single invariant: **parents compose children**. It outputs real DOM with absolute positioning, keeping browser accessibility and text rendering while taking full control of layout. The design trades flexibility for predictability, resulting in a system that is easy to reason about but requires more manual work for complex layouts.

The library occupies a rare and specific niche: **semantic DOM with runtime-owned geometry**. It is not CSS-driven, not canvas-driven, not framework-heavy—but still semantic and accessible. This combination is the actual differentiator.

---

## Core Design Principles

| Principle | What It Means |
|-----------|---------------|
| Parent authority | Parents measure children, assign frames, resolve conflicts. Children describe themselves but never decide placement. |
| Local reasoning | No global constraint solving. Layout is scoped to parent-child relationships. |
| Bounded iteration | Intended model: at most two passes (measure + optional refinement). Current implementation is mostly single-pass. |
| Real DOM output | Semantic HTML with `position: absolute`. Accessibility, focus, selection preserved. |
| Plain JavaScript | No JSX, no DSL, no build step required. Fluent API with chainable methods. |

---

## Constraint and Authority Model

A precise description of how information flows:

| Flow | Direction | Description |
|------|-----------|-------------|
| Constraint authority | Top-down | Parents decide what constraints to pass to children |
| Measured size | Bottom-up | Children report their measured dimensions back to parents |
| Placement authority | Parent-owned | Parents assign final frames; children never self-position |

This is different from "top-down only"—children do influence layout through measurement. But they never *decide* placement, and they never negotiate indefinitely.

---

## Comparison Matrix

| Aspect | Composure | CSS | Kotlin Compose | Flutter | SwiftUI |
|--------|-----------|-----|----------------|---------|---------|
| Layout ownership | Explicit parent | Implicit cascade | Modifier chain | Widget tree | View hierarchy |
| Constraint flow | Authority down, size up | Bidirectional | Bidirectional | Bidirectional | Bidirectional |
| Iteration | Single-pass (refinement planned) | Unbounded reflow | 2-pass | 2-pass | Multiple |
| Output | Real DOM | Real DOM | Canvas/RenderNode | Canvas | Native views |
| Text handling | Block-measured leaf | Full CSS model | Inline + paragraph | Rich text | AttributedString |
| Intrinsic sizing | Not yet | Supported | Supported | Supported | Supported |
| Flex/weight | Not supported | Flexbox | Modifier.weight | Expanded/Flexible | Spacer/frame |
| Styling | Visual props only | Full CSS | Modifier chain | Widget props | ViewModifier |
| Reconciliation | Manual | Browser-owned | Automatic | Automatic | Automatic |

---

## What Composure Borrows From

### From Kotlin Compose / Flutter
- Constraint-based measurement model
- Parent-owned layout authority
- Explicit layout phases (measure → place → render)

### From jQuery / D3
- Fluent chainable API
- Plain JavaScript, no DSL
- Retained tree manipulation

### From the Web Platform
- Real DOM output
- Native accessibility
- Browser text rendering and input handling

---

## What Composure Deliberately Declines

- **Global constraint solving** — no flexbox-style negotiation
- **Unbounded iteration** — no "keep going until stable"
- **Canvas rendering** — preserves semantic DOM
- **Component framework features** — no state management, lifecycle hooks, data binding
- **CSS layout delegation** — runtime owns placement, browser owns painting

These are not missing features waiting for implementation. They are architectural refusals that protect the core model.

---

## Strengths

### 1. Predictable Layout

CSS struggles with implicit bidirectional dependencies where parent sizing depends on children, children depend on siblings, and rules cascade from multiple sources. Composure eliminates this:

```javascript
// Composure: explicit, local authority
row.layout((children, constraints, measure) => {
  const leftSize = measure(children[0], { maxWidth: 200, maxHeight: constraints.maxHeight })
  const rightSize = measure(children[1], {
    maxWidth: constraints.maxWidth - leftSize.width,
    maxHeight: constraints.maxHeight
  })
  children[0].frame = { x: 0, y: 0, ...leftSize }
  children[1].frame = { x: leftSize.width, y: 0, ...rightSize }
  return { width: constraints.maxWidth, height: Math.max(leftSize.height, rightSize.height) }
})
```

### 2. Semantic DOM with Runtime-Owned Geometry

This is the actual sweet spot:

- Screen readers work natively
- Text selection works
- Form controls work
- Browser events work
- But layout is not delegated to CSS

Unlike Flutter Web's canvas approach (which sacrifices accessibility) or CSS (which owns layout), Composure keeps the semantic web while taking control of positioning.

### 3. Debuggability

Every node has a resolved `.frame` object readable at any time. The debug overlay shows exact bounds with color-coded layout types. No hunting through computed styles.

### 4. Minimal API Surface

Five constructors (`vstack`, `hstack`, `zstack`, `box`, `text`) plus a handful of methods. Compare to CSS's 500+ properties or Flutter's hundreds of widget types.

### 5. Custom Layouts Are First-Class

The `.layout()` API makes custom layouts trivial compared to implementing a custom `RenderBox` in Flutter or a custom `Layout` in Compose.

---

## Weaknesses

### 1. No Reconciliation or Identity System

This is the most significant gap. Without keyed child diffing:

- Interactivity stays toy-like
- Append/remove/reorder are fragile
- Performance conclusions are premature
- Dynamic updates require manual DOM management

The current `_prevChildEls` tracking is minimal. Real reconciliation with stable identity is needed.

### 2. No Dirty Tracking

Currently the runtime tends toward whole-tree layout/render on every update. Without dirty subtree tracking:

- Small changes trigger full relayout
- Caching benefits are limited
- Large trees will have performance problems

### 3. Measurement Fidelity

Composure's correctness depends heavily on the measurement environment matching the render environment. Current issues:

- Text measurement creates temporary DOM elements (overhead)
- Style properties like `textTransform` may not be applied during measurement
- Border/padding interactions can cause mismatches

This should be a named design concern, not just a bug to fix.

### 4. Render-Layer Mixing

The `node.el` pattern mixes retained model with render state. This affects:

- Reuse potential
- Incremental patching
- Future animation/layer hints
- Server-side rendering feasibility

### 5. No Flexible Space Distribution

Composure lacks flex-grow/weight concepts. Distributing space proportionally requires manual calculation:

```javascript
// Composure: manual calculation for 1:2 split
box().layout((children, constraints, measure) => {
  const total = constraints.maxWidth
  children[0].frame = { x: 0, y: 0, width: total / 3, height: constraints.maxHeight }
  children[1].frame = { x: total / 3, y: 0, width: total * 2 / 3, height: constraints.maxHeight }
  return { width: total, height: constraints.maxHeight }
})
```

### 6. No Intrinsic Size Queries

Children cannot report minimum/maximum sizes—only measured size given constraints. This makes some layouts harder, though adding this risks dragging Composure toward more complicated negotiation.

---

## Comparison: Where Each System Wins

### vs. CSS

**Composure wins on:** explicit layout authority, debugging clarity, no cascade confusion

**CSS wins on:** flexible distribution, cross-element alignment, responsive breakpoints, text flow, browser optimization, ecosystem

**Verdict:** Composure for complex custom layouts needing explicit control. CSS for document-style content and standard patterns.

### vs. Kotlin Compose / Flutter / SwiftUI

**Composure wins on:** mental model simplicity, runtime weight, web-native semantics, no build tooling

**These systems win on:** ecosystem, animation, platform integration, reconciliation, state management, production scale

**Framing:** Composure is not trying to compete feature-for-feature. It borrows the constraint model while declining the framework scope.

---

## Improvement Recommendations — IMPLEMENTED

The following improvements have been implemented:

### Architecture (Complete)

1. **Keyed reconciliation and DOM patching** ✓

   Stable identity via `_key`. `_prevChildKeys` Map tracks children. DOM elements reused when keys match.

2. **Dirty tracking for layout/render** ✓

   `_dirty` and `_renderDirty` flags. Mutations propagate dirty state up. Clean subtrees skip layout when constraints match.

3. **Measurement fidelity and cache invalidation** ✓

   All styles applied during measurement. Cache includes style hash and text content for proper invalidation.

4. **Style patch cleanup** ✓

   `_prevStyleKeys` and `_prevAttrKeys` track applied properties. Stale ones removed on render.

### Usability (Complete)

5. **Flexible space distribution** ✓

   ```javascript
   box().grow(1)  // take 1 share of remaining space
   box().grow(2)  // take 2 shares
   ```
   Works in vstack and hstack. Simple two-pass layout.

6. **Debug tooling enhancements** ✓

   - `debug.logConstraints`, `debug.logMeasure`, `debug.trackMeasured`
   - `exportTree()` for JSON serialization
   - `flashMeasuredNodes()`, `logTree()` in debug.js

7. **Event API cleanup** ✓

   Added `.off(event)` for handler removal.

### Polish (Complete)

8. **Limited intrinsic queries** ✓

   `measureIntrinsic(node, 'min' | 'max')` for min/max-content sizing.

9. **Animation helpers** ✓

   `.transition({ duration, easing })` and `.noTransition()` for frame animation.

### Not Implemented

- **Render-layer separation** — Deferred until SSR or testing becomes priority
- **Server-side rendering** — Future consideration

---

## When to Use Composure

**Good fit:**
- Custom expressive layouts (like the Persona messaging demo)
- Visualizations with precise positioning requirements
- Embedded widgets where you want explicit control
- Learning layout concepts without CSS complexity

**Poor fit:**
- Document-heavy content (use CSS)
- Standard app UIs with forms, tables, lists (use CSS or a framework)
- Cross-platform native apps (use Flutter/Compose)
- Apps needing rich state management (use a framework)

---

## Persona-IM Demo: Implementation Status

The Persona-IM demo ports Chris Horner's Kotlin Compose implementation of the Persona 5 messaging UI to Composure. This serves as a comprehensive test of the library's capabilities for expressive, non-trivial layouts.

### Implemented Features

#### Layout & Structure
- **Message plates** — Parallelogram text boxes with stems connecting to avatars
- **Entry layout** — Avatar + text box with 18dp overlap, proper anchoring based on height
- **Reply layout** — Right-aligned player messages with inverted color scheme
- **Sticky header** — Logo and portraits overlay content when scrolling
- **Message grouping** — VStack groups with proper spacing

#### Visual Elements
- **Avatar shapes** — Black outer box, white middle box, colored inner box with image
- **Text box shapes** — Outer/inner parallelograms with stems (7-point polygons)
- **Portraits bar** — Three character portraits with irregular quadrilateral borders
- **Question mark punctuation** — Decorative "?" on messages ending with question marks
- **Typing indicator** — Animated dots with scale-in animation matching Kotlin timing
- **Vector assets** — Logo, typing bubble, dots, next button, background splatter

#### Zigzag Connecting Lines
- **Container-level drawing** — Single combined path avoids shadow overlap artifacts
- **Random width** — 44-60px per message
- **Alternating shift** — 16-48px left/right for zigzag effect
- **Ren handling** — Player messages use different center point, no shift applied
- **Last message** — No shift (matches Kotlin's finalizeEntryState timing)
- **Shadow** — 16px offset, 50% opacity, 4px blur

#### Portrait Transformations
- **Rotation** — Outer (-12° to +2°), middle (outer + -2° to 0°), inner (-2° to 0°)
- **Offsets** — Horizontal overlap (-14 to -12px), vertical jitter (-4 to +4px)
- **Irregular shapes** — Each corner uses fresh random inset (6-7px mid, 10-11px clip)
- **Dark avatar** — Random portrait gets black background behind image
- **Image offset** — Ann's portrait offset 8px right for better framing

#### Styling
- **Optima Nova font** — Custom font loaded before render
- **Persona red background** — #C41001
- **Character colors** — Ann (pink), Ryuji (yellow), Yusuke (blue), Kasumi (red)

### Not Yet Implemented

#### Animations
- **Message entry animation** — Scale/alpha animation when messages appear
- **Line progress animation** — Connecting lines animate into view
- **Punctuation scale** — Question mark scales in with animation

#### Visual Effects
- **Background particles** — Sakura petals / snowflakes (seasonal)
- **Season menu** — UI for switching visual themes

#### Interaction
- **Next button** — Tap to advance conversation
- **Dynamic message addition** — Currently static seed data

### Code Organization

```
demo/
├── persona.html      — Entry point, font loading
├── persona.js        — Main UI: messages, avatars, portraits, lines
├── personaPlate.js   — Shape generators, coordinate math
└── personaAssets.js  — Vector paths (logo, typing, buttons)
```

### Lessons Learned

1. **Decoration API works well** — The `decorate()` callback handles complex SVG overlays cleanly
2. **Custom layouts are straightforward** — Entry/reply layouts required ~30 lines each
3. **Container-level effects need care** — Shadow overlap required drawing all segments as one path
4. **Font loading matters** — Text measurement before fonts load causes layout shifts
5. **Seeded RNG enables determinism** — Portrait/line randomness is reproducible

---

## Conclusion

Composure succeeds at its stated goal: a small, predictable layout runtime where parents own layout authority. The Persona demo proves it can handle expressive, non-trivial layouts.

The library's actual differentiator is **semantic DOM with runtime-owned geometry**—a rare combination that preserves web accessibility while escaping CSS layout complexity.

The transition from proof-of-concept to production-ready runtime depends on:

1. Reconciliation with stable identity
2. Dirty tracking for incremental updates
3. Measurement fidelity under dynamic changes

These are more urgent than feature additions like flex weights or intrinsic sizing. The architecture must support real interactivity before the API surface expands.

The design trades flexibility for predictability. This is a valid tradeoff for specific use cases, and the clear documentation of non-goals prevents scope creep into framework territory.
