design-frontend
Put on the pragmatic front-end hat for the emisar Phoenix UI and marketing HEEx — build correct LiveView/operator UI with CoreComponents and LiveTable, or execute public marketing pages from creative
SKILL.md.claude/skills/design-frontend/SKILL.md
GitHubFront-end hat (pragmatic LiveView)
Ship the smallest thing that works and reads clearly. LiveView-first for the
operator console: the server holds the state, HEEx renders it, JS only when
LiveView genuinely can't. Marketing pages are different: they are public,
server-rendered HEEx and may have a distinctive visual language directed by
design-creative-director, as long as they remain fast, crawlable, accessible, and
honest.
Pick the surface first
- Operator console / LiveView: follow the CoreComponents, LiveTable, and IL-18 rules below. Consistency and calm matter more than novelty.
- Marketing pages (
controllers/marketing_html/**): usedesign-creative-directorfor art direction andcontent-seofor crawlable positioning. Custom section layouts are allowed; generic SaaS templates are not. Keep real content in the initial HTML, avoid unnecessary dependencies, and verify with rendered desktop and mobile screenshots.
Reuse before you build
EmisarWeb.CoreComponentsfirst. It's large and already covers buttons, inputs, tables, modals, flash, etc. Grep it before writing markup; extend it only if the primitive is genuinely missing — and then it's shared, not one-off.EmisarWeb.LiveTablefor any list/table: it's stateless and URL-driven. Feed itLiveTable.params_to_opts(params, Runs.run_filters())(the owning context's<schema>_filters/0) and pass the opts to that context'slist_*function. Don't hand-roll pagination, sorting, or filtering.- Match the existing screens' Tailwind utility patterns. Don't invent spacing/color scales; reuse what layouts and CoreComponents already use.
LiveView Iron Law (IL-18 — Credo won't catch these; you must)
- No unconditional DB/context read in
mount—mountruns twice. Useassign_async, orconnected?(socket)with a cheap disconnected branch. stream/3for any list that can grow (runs, audit events, runners). Neverassign(socket, :events, big_list)— it bloats socket memory per connection.connected?(socket)guard before any PubSubsubscribe(live runner/run status updates), or you double-subscribe.- Never
assign_newfor per-mount values (current_subject,current_account, locale) — useassign/3. - Authorize in every
handle_eventby routing through a context call with the subject (IL-15). The button being hidden is not authorization.
Pragmatic rules
- One component, one job. Pass data in via
attr/slot; don't reach into parent assigns. Function components for stateless UI; a LiveComponent only when it owns state. - Loading / empty / error states are part of the component, not a later pass (the
/design-uxhat will ask for them). - Keep markup readable: no deeply nested conditionals in HEEx — compute in the LV, render flat. Extract a function component when a block repeats.
- Forms use
to_form/2+ CoreComponents inputs; show changeset errors (IL-18's sibling: if a save "silently fails", check{:error, changeset}first).
Fixing a reported UI defect
When the user points at something broken on a rendered surface, the fix ships
with before/after screenshot proof — and the BEFORE shot comes before any edit
(the broken state is unrecoverable once the fix deploys). Shoot the element from
the active workspace with ./run shot (--label before), using the current
task's screenshots/ directory (create and claim a basic task first when none
exists), fix,
let ./run serve reload (no image rebuild), re-shoot (--label after, full page + element crop), verify
the pixels yourself, then hand the user both paths for review. Full workflow:
portal/.agent/kb/rules/design-ui-fix-screenshot-proof.md.
Finish
Run ./run gate portal --changed from the repository root, click-test the happy
path + one error path, and confirm lists stream. Before pushing, run the complete
./run gate portal. Before a rendered surface is done, run the
design-interface-polish micro-craft pass against design-system §3 — calm on the
console, expressive on marketing. Hand UX judgment calls to /design-ux; keep this
hat on the implementation.