Agent pitfalls (Dialup)

Copy Markdown View Source

This guide records common misunderstandings coding agents make when working on Dialup apps. Add a new section here whenever a review or production issue reveals a pattern agents get wrong.


1. layout.css classes already apply on every page

The mistake

An agent sees page-specific CSS such as agent_demo/page.css and assumes that each page only has access to its own page.css. It then reimplements shared UI (buttons, code blocks, shadows) with new selectors like .mcp-handoff-btn or .mcp-add button.

The reality

Dialup colocation CSS works like this:

  1. layout.css (next to layout.ex) defines styles shared by all pages under that layout.
  2. page.css (next to page.ex) adds page-specific rules only.
  3. At render time, the framework injects both into one <style data-dialup-css> block.

DOM structure:

<div class="d-layout">      <!-- layout.css scope -->
  <nav>...</nav>
  <main>
    <div class="d-page">  <!-- page.css scope -->
      ... page content ...
    </div>
  </main>
</div>

Layout wraps the page. Classes from layout.css — for example .btn, .btn-primary, .btn-ghost — match elements inside any page as long as you put the class on the element.

There is no separate import step and no UI component module required. Reuse is class-based:

<.dialup_action navigate="/docs" class="btn btn-ghost">Get Started</.dialup_action>
<button type="submit" class="btn btn-ghost">Submit</button>
<button type="button" class="btn btn-ghost btn-block" data-mcp="handoff">Hand off</button>

What belongs where

FileUse for
layout.cssSite-wide tokens, buttons (.btn*), nav, docs chrome, shared utilities (.btn-block, .btn-sm)
page.cssLayout and content unique to that page (grids, cards, domain-specific regions) — not another copy of button styles

How to avoid it

Before adding button CSS to page.css, check layout.css for existing .btn variants. Prefer:

  • .btn.btn-ghost — purple CTA
  • .btn.btn-primary — light background
  • .btn.btn-accent — accent2 highlight
  • .btn.btn-sm — compact control
  • .btn.btn-block — full width

Add a new variant in layout.css only when the design system genuinely needs one, not per page.

Reference implementation

See site/lib/app/layout.css (definitions) and site/lib/app/agent_demo/page.ex (usage on MCP demo buttons).


2. ws-change inputs should not return {:update, assigns}

The mistake

A text field uses ws-change + ws-debounce, but handle_event/3 returns {:update, assigns}. Every debounced keystroke triggers a full #dialup-root morph. The input has value={@field} from the server, so the cursor jumps and characters disappear — especially on deployed sites with higher WebSocket latency.

The reality

For live typing, follow Events — ws-change:

  • Human input over ws-change{:noreply, assigns} (state only, no DOM replace)
  • Optional feedback elsewhere → {:patch, id, html, assigns} on a different element (see /demo draft_change)
  • Agent-only updates that must refresh the field → {:patch, input_id, render_input(assigns), assigns}

Reference implementation

site/lib/app/agent_demo/page.exset_project/3
site/lib/app/demo/page.exdraft_change/3