priv/templates/joby_kit.install/AGENTS.md.section

<!-- jobykit:start -->
## JobyKit guidelines

This project uses [JobyKit](https://github.com/jobycorp/joby_kit) — an
opinionated, agentic-first design system that wraps daisyUI and exposes a
machine-readable manifest of every UI component the app supports.

### Read these first, every session

Before writing **any** UI markup, execute these in order:

1. **`curl http://localhost:PORT/design.json`** — fetch the component
   manifest. Read what's already registered before you reach for raw
   markup.
2. **Open `/design`** in a browser — kit-curated wrappers (`<.button>`,
   `<.input>`, `<.icon>`, `<.card>`, …) with rendered previews and the
   wrapper contract.
3. **Open `/custom-designs`** — this app's generic composites and
   domain components.
4. **After writing UI, run `mix joby_kit.lint`** — confirms the
   contract holds end-to-end.

### Symptoms you skipped step 1

If any of these are true after writing UI, you skipped the manifest. The
fix is mechanical: lift the inline functions into a host component
module, declare the wrapper contract on each, register them in
`design_manifest.ex`, re-run `mix joby_kit.lint`.

- **Private functions in a LiveView's render** (e.g. `avatar/1`,
  `message/1`, `composer/1`, `conversation_row/1`). Those are domain
  composites — they belong in
  `<app>_web/components/<area>_components.ex`, registered with
  `category: :domain`.
- **Raw `<button>`, `<input>`, `<a class="btn">`, `<div class="card">`,
  `<span class="badge">`, `<header class="navbar">`** — the kit ships
  wrappers for all of these (`<.button>`, `<.input>`, `<.card>`, …).
  Reach for the wrapper.
- **New components without `data-component`, `attr :rest, :global`, or
  a manifest entry.** Those three together are the wrapper contract;
  components missing them are invisible to agents and to
  `/design.json`.
- **Class strings duplicated across pages.** Same `class="..."` on the
  same semantic UI object on two pages → lift it into a component.

### Intent

JobyKit is not here to make every app look like generic daisyUI. It is
here to make existing UI contracts discoverable and reusable.

Agents should first ask: "Does this app already have a component for
this job?" The manifest and catalogs answer that question.

### Design freedom is the point

JobyKit wrappers carry the **contract** (typed attrs, `data-component`,
manifest entry). They are deliberately **style-agnostic**. Visual style
— typography rhythm, motion, palette accents, layout, hierarchy,
illustrative touches — is the host app's prerogative, and should be
**distinctive**. Generic AI aesthetic is the failure mode this kit is
meant to prevent.

The build order below tells you *which component to reach for*. It does
**not** tell you what the page should look like. Apply design judgment
(or invoke a frontend-design skill) when building app surfaces.

### Composition vs. creation

- **Composition** — arranging existing wrappers using Tailwind for
  layout, spacing, motion, and visual hierarchy. **No restriction.**
  This is where design happens. A page may use as much creative
  Tailwind as it wants, as long as every primitive flows through a
  wrapper.
- **Creation** — making a new visual primitive (a button family, a
  card variant, a chat bubble). Funnels through the wrapper layer
  (steps 3–4 of the build order) so the manifest stays the source of
  truth.

Components also carry **behavior**, not just visual identity:
accessibility, form integration, LiveView hooks, and testable IDs.
Reaching for raw markup when a wrapper exists drops those guarantees
alongside the contract.

Test: if you wrote `<button class="…">` when `<.button>` exists, that
is creation masquerading as composition. Lift it into the wrapper or
use the existing one. The same rule applies to raw Tailwind: if the
same semantic UI object appears with the same classes on more than one
page, lift it into a component.

### Five-step build order (in order, every time)

1. **Domain composite exists?** Use it. Lives in a domain-scoped
   component module in this app. Surfaces on `/custom-designs`.
2. **Generic composite exists?** Use it. Lives in the host's composite
   module. Surfaces on `/custom-designs`.
3. **Core wrapper exists?** Use it. Lives in
   `<app>_web/components/core_components.ex` or in
   `JobyKit.CoreComponents`. Surfaces on `/design`.
4. **daisyUI primitive exists?** If it will be used in more than one
   place, or carries persistent visual decisions, wrap it as a core
   component first (register it in `<app>_web/design_manifest.ex`)
   and then use the wrapper. For one-off scaffolding inside a single
   page, the primitive can be used directly — but if you catch
   yourself repeating it, lift it. The daisyUI catalogue at the
   bottom of `/design` lists every primitive.
5. **None of the above?** Build from Tailwind + theme tokens. Expose
   the result as a core wrapper (kit page) or generic/domain composite
   (custom-designs page) and register it in the manifest.

**Before adding a new component, check whether an existing one almost
fits.** Extending a wrapper with a new variant, size, or attr is
preferable to creating a parallel one. The manifest's `attrs` and
`slots` fields tell you what's already there; a new variant should be
a deliberate addition, not a duplicate.

### Wrapper contract (every component must satisfy)

1. Declare every prop with `attr` (use `values:` constraints for
   variant/tone/size enums).
2. Carry `data-component="App.Module.function"` on the root element.
3. Accept `:rest, :global` so callers can pass id/class/aria-*/phx-*.
4. **Wrapper internals** compose daisyUI primitives + theme tokens —
   that keeps the wrapper style-coherent everywhere it's used. **Page
   composition** (the file calling the wrapper) is free to use
   Tailwind for layout, motion, gradients, and visual hierarchy. That
   is where style lives.
5. Register every component in `<app>_web/design_manifest.ex`.
   Components missing from the manifest are invisible to agents. Run
   `mix joby_kit.lint` to verify the contract holds before merging.

### Testing

Test what's in the contract — attrs, slots, behavior, observable IDs
— not fragile class strings. The class composition inside a wrapper
is the host's design prerogative and will change; the wrapper's API
is what callers depend on. Use `data-component` attributes and
explicit IDs as selectors instead of CSS classes.

### Where new components go

- **Core wrappers** (one per daisyUI primitive) →
  `core_components.ex`, registered with `category: :core`. Surface on
  `/design`.
- **Generic composites** (multi-primitive patterns reused across
  domains) → a `composite_components.ex` (or similar), registered
  with `category: :composite`. Surface on `/custom-designs`.
- **Domain composites** (tied to a single product area) → a
  domain-scoped component module, registered with `category: :domain`.
  Surface on `/custom-designs`.

**Never** add new components to `/design` directly — it's the
kit-curated surface and stays uniform across every JobyKit consumer.

### daisyUI vs raw Tailwind (for primitives)

This app uses daisyUI as the substrate for **primitives** (buttons,
badges, alerts, cards, stats, progress, etc.). When you need one of
those, reach for the corresponding core wrapper. Roll your own
Tailwind primitive only when nothing in daisyUI fits — and then
expose the result as a wrapper so future markup composes through the
wrapper, not the raw classes.

This rule is about the *primitive layer*. Tailwind utilities for
layout, spacing, motion, gradients, and visual hierarchy on the page
that *composes* those wrappers are encouraged — see "Composition vs.
creation" above.
<!-- jobykit:end -->