# Unified UI Package Guide

`@rip-lang/ui` is the umbrella package for browser widgets, email components, shared helpers, and Tailwind integration.

## Domain boundaries

- `browser/` owns interactive headless widgets and browser-only DOM behavior
- `email/` owns curated email rendering, serializers, and email-client compatibility
- `shared/` owns only truly cross-domain utilities
- `tailwind/` is the only place where `tailwindcss` and `css-tree` may be imported

## Typing

All exported browser and email components should use explicit Rip types on their public props. Use the existing virtual TypeScript pipeline (`rip check`, LSP, generated virtual TS) during development; do not emit scattered `.d.ts` files during active work.

## Gallery

The root `index.html` / `index.css` / `index.rip` form a showcase layer only. Components stay headless and unstyled. The gallery itself is intentionally polished.

## First Read For Agents

When working in `packages/ui`, do not rely only on automatic context pickup from nested `AGENTS.md` files. For strong results, proactively read these files at the start of the task:

1. `packages/ui/AGENTS.md` — package boundaries, architecture, reuse policy
2. `packages/ui/browser/AGENTS.md` — widget conventions, gotchas, popup/focus patterns
3. `packages/ui/DEBUG.md` — current local-debug workflow, live gallery URL, known-good browser signals

Then, if the task involves a hard popup/input widget, inspect the current reference component before making changes:

- `packages/ui/browser/components/multi-select.rip`

Use `multi-select.rip` as the reference example for:

- layered popup mechanics vs widget-local semantics
- nested interactive affordance isolation
- reopen suppression after pointer-driven closes
- composite input styling with an embedded input inside a larger shell

This startup sequence gets an agent much closer to “current project reality” than `AGENTS.md` inheritance alone.

## Browser Widget Direction

For complex browser widgets, prefer a layered design:

- keep generic popup, focus, keyboard, and timing logic in the shared `ARIA` helper layer in `packages/app/index.rip`
- keep component-specific semantics local to the widget (`chips`, token removal, selection rules, etc.)
- do not solve browser event-ordering bugs independently in every component if the underlying problem is generic

The current model is:

- `ARIA` / `__aria` owns shared primitives such as popup dismissal, popover binding, dialog binding, positioning, roving/list navigation, and short reopen-suppression after pointer-driven closes
- individual components own only the semantics that make that widget unique

**ARIA typing is automatic via the `@rip-lang/app` dependency.** `ARIA` is a
runtime global from `@rip-lang/app`, so this package declares it as a
`peerDependency`. That's all it takes: `rip check` auto-includes the ambient
`.d.ts` (`aria.d.ts`) that `@rip-lang/app` advertises in its
`package.json#rip.ambient`, so the widgets' `ARIA.` calls type-check with **no
`rip.types` line**. If you split this package or add a new one that uses `ARIA.`,
just declare the `@rip-lang/app` dependency (peer/dev/normal) — the ambient
contract comes with it. See `packages/app/AGENTS.md` → "The ambient `ARIA` type
contract" and `docs/RIP-TYPES.md` → "Ambient `.d.ts` includes".

## Reference Component

`browser/components/multi-select.rip` is the reference example for a "hard" composite widget in this package.

Use it as the model for components that combine:

- popup lifecycle
- embedded inputs
- nested interactive affordances
- keyboard navigation
- multiple coordinated state transitions

What it should demonstrate:

- clear separation between shared popup mechanics and local widget semantics
- consistent naming (`on...` for auto-wired root handlers, `_on...` for child handlers, `_...` for private helpers/refs/state)
- explicit isolation of nested controls when `mousedown` / focus ordering would otherwise leak into parent behavior
- behavior that matches high-quality headless UI expectations before adding styling

## Reuse Policy

When building or refactoring browser widgets:

- extract the smallest stable shared primitive into `packages/app/index.rip`
- do not create a giant generic base component prematurely
- reuse `ARIA.popupGuard()` for pointer-driven close/reopen timing problems in popup-style controls
- keep styling fixes in the gallery CSS unless the component itself is shipping opinionated visuals

Good candidates for shared primitives:

- reopen suppression after outside-click dismissal
- popup dismissal wiring
- keyboard navigation helpers
- focus restoration / modal stack handling

Bad candidates for premature abstraction:

- token rendering rules
- chip-specific behaviors
- widget-specific content semantics

## Render-template name resolution

`name = expr` bindings and `for x in ...` loop variables behave as
normal lexical locals — they shadow same-named HTML tags within the
same block factory. Nested loops with mixed explicit / auto indices
stay collision-free at any depth (the compiler pre-scans the body
and avoids any name an inner loop or render-local binds). Compound
assignments (`+=`, `-=`, …) work the same way as `=`.

```coffee
for ex in ep.examples
  code = if ex.curl? then buildCurl(ep, ex.curl) else ex.code
  CodeBlock label: ex.label, code: code   # `code` reads the local

for code in items
  span code                                # → <span>{code}</span>

for item in items
  for v, i in item.enum                    # outer auto-allocates `j`
    span "#{v}@#{i}"                       #   instead of `i`

for ex in items
  sum = 0                                  # `=` declares
  sum += ex.value                          # `+=` mutates the local
  span "#{sum}"
```

Render bindings are creation-time captures, not reactive computeds —
`code = ex.body` evaluates once when the block is built. For values
that should track changes, hoist them to a class-level `:=` / `~=`
member or read the reactive source directly inside the DOM
expression.

The one remaining strict-mode collision is a real source-level
duplicate — e.g. `for x, i in xs / for y, i in ys` both binding `i`.
The compiler will not silently rename a variable you typed.

### Debugging "Duplicate parameter name" and friends quickly

These errors surface at module load with no line number pointing at
your source. Fastest reproduction path:

```bash
# extract the inline <script type="text/rip"> block and compile it
awk '/<script type="text\/rip">/,/<\/script>/' file.html |
  sed '1d; $d' > /tmp/inline.rip
rip -c -q /tmp/inline.rip > /tmp/inline.js

# feed the compiled JS to Node's Function parser — it points right at
# the bad function signature (e.g. `p(ctx, v, i, item, i) { ... }`)
node -e 'try { new Function(require("fs").readFileSync("/tmp/inline.js","utf8")) }
         catch(e) { console.log(e.message) }'
```

The emitted patch function is named `p` and takes every closure
variable as a positional parameter — duplicates there are always the
clue you're looking for.
