Crawler-Factory

Design-Language/02-component-recipes.md

Component Recipes — Crawler Dashboard Design Language

Purpose: copy-paste-ready HTML / JSX skeletons for every pattern in 01-design-language.md. Use these before authoring any new component. If a recipe matches your need, use it.

Convention: examples are framework-agnostic HTML using class names from tokens.css. To use in React, replace class with className and convert attributes to camelCase. State props are noted in comments.


Recipe index

  1. Setting card (read mode)
  2. Setting card (edit mode, inline)
  3. Toggle row
  4. Toggle → conditional fields
  5. Number stepper with unit
  6. Repeating row (Start URLs / Exclusion Rules)
  7. Read-only with copy / Masked secret
  8. Day-picker pills
  9. Create-entity modal
  10. Destructive typed-confirm
  11. Status pill
  12. KPI tile row
  13. Empty state — data / gated / edit-empty
  14. Page header with tabs
  15. Three-section left nav
  16. Persistent CTA top bar
  17. Toolbar above table
  18. Data table
  19. Info banner inline
  20. Suggestion card
  21. Grouped warnings (Data Analysis)
  22. Run-control cluster (workspace top-right)
  23. Run-state pill (list row)
  24. Re-crawl confirm dialog (scoped, not typed)

1. Setting card (read mode)

<article class="card">
  <div style="display: flex; align-items: flex-start; gap: 16px;">
    <div class="card__icon-tile">
      <svg width="18" height="18" aria-hidden="true">…</svg>
    </div>
    <div style="flex: 1;">
      <h3 class="t-card-title">Crawler Schedule</h3>
      <p class="t-description">Tell the crawler how often you want it to crawl your site.</p>
    </div>
    <div style="display: flex; align-items: center; gap: 16px;">
      <span class="t-description">on the 28 day of the month</span>
      <button class="btn btn--ghost btn--small" aria-label="Edit Crawler Schedule">Edit</button>
    </div>
  </div>
</article>

Notes: - Icon tile = 32px square, light grey fill, 8px radius, dark monochrome glyph. - The right-side "value" is muted text — never bold, never colored. - The Edit button is the only interactive element.


2. Setting card (edit mode, inline)

<article class="card" data-state="editing">
  <div style="display: flex; align-items: flex-start; gap: 16px;">
    <div class="card__icon-tile">…</div>
    <div style="flex: 1;">
      <h3 class="t-card-title">Crawler Schedule</h3>
      <p class="t-description">Tell the crawler how often you want it to crawl your site.</p>
    </div>
  </div>

  <!-- Edit body -->
  <div style="margin-top: 20px; display: flex; flex-direction: column; gap: 20px;">
    <!-- ... controls go here (use Recipe 3-8 inside) ... -->
  </div>

  <!-- Save / Cancel row -->
  <div style="display: flex; gap: 12px; margin-top: 24px;">
    <button class="btn btn--primary" id="save" disabled>Save</button>
    <button class="btn btn--ghost" id="cancel">Cancel</button>
  </div>
</article>

Rules: - Save is disabled (greyed) until the form is dirty; becomes solid blue when dirty. - Cancel is always available; reverts state and collapses card back to read mode. - No "delete" button inside the edit body — use the danger zone (Recipe 10) for destructive operations.


3. Toggle row

<div style="display: flex; align-items: center; gap: 12px;">
  <label class="switch">
    <input type="checkbox" checked>
    <span class="switch__track"></span>
  </label>
  <span class="t-body">Enabled</span>
</div>

Rules: - Always pair the switch with a textual label ("Enabled" / "Disabled") — never rely on track color alone.


4. Toggle → conditional fields

<!-- Top toggle -->
<div style="display: flex; align-items: center; gap: 12px;">
  <label class="switch"><input type="checkbox" checked><span class="switch__track"></span></label>
  <span class="t-body">Enabled</span>
</div>

<!-- Conditional dropdown -->
<select class="input">
  <option>Basic Auth: Username & Password</option>
  <option>OAuth 2.0</option>
</select>

<!-- Conditional fieldset, labelled with the selected variant -->
<fieldset style="border: 1px solid var(--line-default); border-radius: 12px; padding: 20px;">
  <legend class="t-label-caps" style="padding: 0 8px;">BASIC AUTH: USERNAME &amp; PASSWORD</legend>
  <div style="display: flex; flex-direction: column; gap: 16px;">
    <div>
      <label class="t-description">URL</label>
      <div style="display: flex; gap: 8px;">
        <select class="input" style="width: 100px;"><option>https://</option></select>
        <input class="input" type="text">
      </div>
    </div>
    <div>
      <label class="t-description">Username</label>
      <input class="input" type="text">
    </div>
    <div>
      <label class="t-description">Password</label>
      <input class="input" type="password">
    </div>
  </div>
</fieldset>

Rules: - Three levels of progressive disclosure max. Don't nest deeper. - Fieldset legend repeats the dropdown selection so user remembers their choice.


5. Number stepper with unit

<div style="display: flex; align-items: center; gap: 8px;">
  <span class="t-body">The crawler will stop when it has crawled</span>
  <input class="input" type="number" value="100" style="width: 100px;">
  <span class="t-description">URLs</span>
</div>

Rule: when a number has a unit, surround the input with natural-language framing ("when it has crawled X URLs"). It's more readable than a labelled input alone.


6. Repeating row

<div style="display: flex; flex-direction: column; gap: 12px;">
  <!-- Header row of column labels -->
  <div style="display: grid; grid-template-columns: 200px 1fr 40px; gap: 12px;">
    <span class="t-description">Condition</span>
    <span class="t-description">URL Pattern</span>
    <span></span>
  </div>

  <!-- Row -->
  <div style="display: grid; grid-template-columns: 200px 1fr 40px; gap: 12px;">
    <select class="input"><option>Select condition</option></select>
    <input class="input" type="text" placeholder="Enter pattern…">
    <button class="btn btn--ghost btn--small" aria-label="Remove rule">
      <svg width="16" height="16">…trash icon…</svg>
    </button>
  </div>

  <!-- Add button (ghost, full-width or left-aligned, with + glyph) -->
  <button class="btn btn--ghost btn--small" style="align-self: flex-start;">
    + URL Exclusion
  </button>
</div>

Rules: - Add button is ghost-style with a + glyph, always below the rows. - Trash is a 40px icon button (icon-only with aria-label). - Removing the last row is allowed; the empty state appears below the add button.


7. Read-only with copy / Masked secret

<!-- Read-only with copy -->
<label class="t-description">Crawler ID</label>
<div style="display: flex; gap: 8px;">
  <input class="input" readonly value="0cd7586b-a2be-4340-bd23-73625b38f52a">
  <button class="btn btn--ghost btn--small" aria-label="Copy Crawler ID">
    <svg>…copy icon…</svg>
  </button>
</div>

<!-- Masked secret with reveal + copy -->
<label class="t-description">Algolia API Key</label>
<div style="display: flex; gap: 8px;">
  <input class="input" type="password" readonly value="••••••••••••••••••••••••••">
  <button class="btn btn--ghost btn--small" aria-label="Reveal API Key"><svg>…eye…</svg></button>
  <button class="btn btn--ghost btn--small" aria-label="Copy API Key"><svg>…copy…</svg></button>
</div>

8. Day-picker pills

<div style="display: flex; gap: 6px;">
  <button class="day-pill" aria-pressed="false">Su</button>
  <button class="day-pill" aria-pressed="true">M</button>
  <button class="day-pill" aria-pressed="false">Tu</button>
  <button class="day-pill" aria-pressed="false">W</button>
  <button class="day-pill" aria-pressed="false">Th</button>
  <button class="day-pill" aria-pressed="false">F</button>
  <button class="day-pill" aria-pressed="false">S</button>
</div>

<style>
.day-pill {
  width: 36px; height: 36px;
  border: 1px solid var(--line-default);
  border-radius: 8px;
  background: var(--surface-card);
  font-size: var(--text-description);
  font-weight: var(--weight-semibold);
  cursor: pointer;
}
.day-pill[aria-pressed="true"] {
  background: var(--accent);
  color: var(--ink-on-accent);
  border-color: var(--accent);
}
</style>

9. Create-entity modal

<div class="dialog-backdrop" role="presentation">
  <div class="dialog" role="dialog" aria-labelledby="dialog-title" aria-modal="true">
    <div style="display: flex; justify-content: space-between; align-items: center;">
      <h2 class="dialog__title" id="dialog-title">Add Markdown Index</h2>
      <button class="btn btn--ghost btn--small" aria-label="Close">×</button>
    </div>

    <div class="dialog__body">
      <div>
        <label class="t-description">Index Name <span style="color: var(--status-danger-fg);">*</span></label>
        <input class="input" type="text" placeholder="Enter index name (e.g., markdown-index)">
      </div>
      <div>
        <label class="t-description">Content Tag <span style="color: var(--status-danger-fg);">*</span></label>
        <input class="input" type="text" value="main">
      </div>
      <div>
        <label class="t-description">Template <span style="color: var(--status-danger-fg);">*</span></label>
        <select class="input">
          <option>Non-DocSearch (Generic)</option>
          <option>DocSearch</option>
        </select>
        <p class="input-help">Choose the template that matches your documentation framework for optimal indexing</p>
      </div>
    </div>

    <div class="dialog__actions">
      <button class="btn btn--ghost">Cancel</button>
      <button class="btn btn--primary">Add Index</button>
    </div>
  </div>
</div>

10. Destructive typed-confirm

<!-- The danger zone card on the Settings page -->
<article class="card card--danger">
  <h3 class="t-card-title">Delete Crawler</h3>
  <p class="t-description">This crawler and all associated data will be lost</p>
  <button class="btn btn--danger" style="margin-top: 16px;">Delete this crawler</button>
</article>

<!-- The confirm dialog opened by that button -->
<div class="dialog-backdrop">
  <div class="dialog" role="dialog" aria-labelledby="dlg-title">
    <div style="display: flex; justify-content: space-between;">
      <h2 class="dialog__title" id="dlg-title">Delete crawler</h2>
      <button class="btn btn--ghost btn--small" aria-label="Close">×</button>
    </div>

    <p class="t-body">
      Are you sure you want to delete this crawler? This action is irreversible.
      We will delete your crawler, the configuration, all pending operations, all logs.
    </p>
    <p class="t-body" style="margin-top: 12px;">
      <strong>We will not</strong> remove anything from your Algolia account. If you want to delete the
      associated Algolia indices, you will need to do it by hand afterward.
    </p>

    <p class="t-description" style="margin-top: 20px;">Enter "DELETE" to confirm.</p>
    <input class="input" type="text" id="confirm-input" autocomplete="off">

    <div class="dialog__actions">
      <button class="btn btn--danger" disabled id="confirm-delete">Delete</button>
    </div>
  </div>
</div>

<script>
  document.querySelector('#confirm-input').addEventListener('input', e => {
    document.querySelector('#confirm-delete').disabled = e.target.value !== 'DELETE';
  });
</script>

Rules: - The scope statement (what is not affected) is mandatory. - The button stays disabled until the user types "DELETE" exactly. - No second "Cancel" button needed — closing the dialog (X or Escape) is the cancel.


11. Status pill

<span class="pill pill--success">Success</span>
<span class="pill pill--warning">Canceled</span>
<span class="pill pill--neutral">Created</span>
<span class="pill pill--running">
  <span style="width: 6px; height: 6px; border-radius: 50%; background: currentColor;"></span>
  Running
</span>
<span class="pill pill--danger">Failed</span>

12. KPI tile row

<div class="kpi-row">
  <article class="kpi">
    <div class="kpi__value">67</div>
    <div class="kpi__label">URLs contained in this path</div>
    <span class="kpi__icon"><svg>…globe…</svg></span>
  </article>
  <article class="kpi">
    <div class="kpi__value">11</div>
    <div class="kpi__label">Records extracted from this path</div>
    <span class="kpi__icon"><svg>…database…</svg></span>
  </article>
  <article class="kpi">
    <div class="kpi__value">0</div>
    <div class="kpi__label">URLs that failed within this path</div>
    <span class="kpi__icon"><svg>…x-circle…</svg></span>
  </article>
  <article class="kpi">
    <div class="kpi__value">5</div>
    <div class="kpi__label">URLs that were skipped within this path</div>
    <span class="kpi__icon"><svg>…ban…</svg></span>
  </article>
</div>

13. Empty states

Variant: data (table empty)

<div class="empty">
  <div class="t-card-title" style="color: var(--ink-secondary);">Empty</div>
  <p class="t-description">There is no data to display</p>
</div>

Variant: gated (requires-prereq)

<div class="empty empty--gated">
  <svg width="80" height="80" aria-hidden="true">
    <!-- shield illustration -->
  </svg>
  <h2 class="empty__title">You need to enable Crawler Logs first</h2>
  <p class="t-description" style="max-width: 480px;">
    To use Logs Explorer, you must first enable the Crawler Logs, then generate
    data by running a complete crawl of your domain.
  </p>
  <p class="t-description">
    <a href="…">Learn more ↗</a> about what's involved.
  </p>
  <button class="btn btn--primary">Go to Configuration</button>
</div>

Variant: edit-empty (inside an edit-mode card)

<p class="t-description" style="text-align: center; padding: 24px;">
  No markdown indices created yet. Click "Add Index" to create your first one.
</p>
<button class="btn btn--ghost" style="align-self: flex-start;">+ Add Index</button>

14. Page header with tabs

<header style="margin-bottom: 24px;">
  <div style="display: flex; justify-content: space-between; align-items: flex-start;">
    <div>
      <h1 class="t-h1">Crawler Configuration</h1>
      <p class="t-description" style="margin-top: 8px; max-width: 720px;">
        Customise how your crawler navigates and processes your site, ensuring efficient data
        collection while maintaining control over performance and security.
        For more advanced configurations, use the <a href="…">Crawler Code Editor</a> instead.
      </p>
    </div>
    <!-- optional secondary control on the right (e.g., index dropdown for Data Analysis) -->
  </div>

  <nav class="tabs" role="tablist" style="margin-top: 24px;">
    <button class="tab" role="tab" aria-selected="true">Initial Set-up</button>
    <button class="tab" role="tab" aria-selected="false">Markdown for LLMs</button>
    <button class="tab" role="tab" aria-selected="false">Safety Checks &amp; Performance</button>
    <button class="tab" role="tab" aria-selected="false">Security &amp; Data Management</button>
  </nav>
</header>

15. Three-section left nav

<nav class="left-rail" aria-label="Crawler navigation">
  <section class="nav-section">
    <header class="nav-section__header">
      <svg width="14" height="14">…trend…</svg>
      Setup
      <button aria-label="Collapse" style="margin-left: auto;">▾</button>
    </header>
    <ul class="nav-section__list">
      <li><a class="nav-item" href="…">Overview</a></li>
      <li><a class="nav-item" href="…" aria-current="true">Configuration</a></li>
      <li><a class="nav-item" href="…">Editor</a></li>
      <li><a class="nav-item" href="…">Suggestions</a></li>
    </ul>
  </section>

  <section class="nav-section">
    <header class="nav-section__header">
      <svg width="14" height="14">…wrench…</svg>
      Status
      <button aria-label="Collapse" style="margin-left: auto;">▾</button>
    </header>
    <ul class="nav-section__list">
      <li><a class="nav-item" href="…">URL inspector</a></li>
      <li><a class="nav-item" href="…">Monitoring</a></li>
      <li><a class="nav-item" href="…">Data Analysis</a></li>
      <li><a class="nav-item" href="…">Path Explorer</a></li>
      <li><a class="nav-item" href="…">Logs Explorer</a></li>
    </ul>
  </section>

  <section class="nav-section">
    <header class="nav-section__header">
      <svg width="14" height="14">…gear…</svg>
      Configuration
      <button aria-label="Collapse" style="margin-left: auto;">▾</button>
    </header>
    <ul class="nav-section__list">
      <li><a class="nav-item" href="…">External Data</a></li>
      <li><a class="nav-item" href="…">Settings</a></li>
    </ul>
  </section>
</nav>

16. Persistent CTA top bar

<div class="workspace-topbar">
  <!-- Left: module wordmark -->
  <div style="display: flex; align-items: center; gap: 12px;">
    <span style="width: 12px; height: 12px; border-radius: 50%; background: #0FBE6E;"></span>
    <span style="font-weight: 700; letter-spacing: 0.18em;">CRAWLER</span>
  </div>

  <!-- Center: scope indicator -->
  <div style="display: flex; flex-direction: column; align-items: center; line-height: 1.2;">
    <span class="t-description">Crawler</span>
    <button style="display: flex; align-items: center; gap: 8px; background: none; border: none;">
      <span class="avatar-square" style="background: orange; width: 20px; height: 20px; border-radius: 4px;"></span>
      <span style="font-weight: 600;">Wellington</span>
      <svg width="12" height="12">▾</svg>
    </button>
  </div>

  <!-- Right: persistent CTA + close -->
  <div style="display: flex; gap: 8px; align-items: center;">
    <button class="btn btn--primary">Start Crawling</button>
    <button class="btn btn--ghost btn--small" aria-label="Close and return to Algolia">×</button>
  </div>
</div>

17. Toolbar above table

<div style="display: flex; gap: 12px; align-items: center; margin-bottom: 16px;">
  <input class="input" type="search" placeholder="Search…" style="max-width: 320px;">
  <select class="input" style="width: 160px;"><option>All Status</option></select>

  <div style="margin-left: auto; display: flex; gap: 8px;">
    <button class="btn btn--ghost btn--small">Recrawl failed URLs</button>
    <button class="btn btn--primary btn--small">Add new crawler</button>
  </div>
</div>

18. Data table

<table style="width: 100%; border-collapse: separate; border-spacing: 0;">
  <thead>
    <tr style="border-bottom: 1px solid var(--line-default);">
      <th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;"><input type="checkbox"></th>
      <th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;">Name</th>
      <th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;">Status</th>
      <th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;">Author</th>
      <th style="text-align: left; padding: 12px 16px; font-size: 13px; color: var(--ink-secondary); font-weight: 400;">Last indexed</th>
    </tr>
  </thead>
  <tbody>
    <tr style="border-bottom: 1px solid var(--line-default);">
      <td style="padding: 14px 16px;"><input type="checkbox"></td>
      <td style="padding: 14px 16px;"><a href="…">Wellington</a></td>
      <td style="padding: 14px 16px;"><span class="pill pill--warning">Canceled</span></td>
      <td style="padding: 14px 16px; display: flex; align-items: center; gap: 8px;">
        <span class="avatar-circle">SM</span>
        sajid.momin@algolia.com
      </td>
      <td style="padding: 14px 16px;">3 days ago</td>
    </tr>
  </tbody>
</table>

19. Info banner inline

<div class="info-banner">
  <svg width="16" height="16" aria-hidden="true">…info…</svg>
  <div>
    The crawler won't update your records until you check and confirm the change is okay.
    <a href="…">Learn more ↗</a> about what's involved.
  </div>
</div>

Use for: safety rails on consequential actions, prerequisite reminders, scope clarifications.


20. Suggestion card

<article class="card">
  <header style="display: flex; justify-content: space-between; align-items: center;">
    <h3 class="t-card-title">Website HTML contains redirect to a different domain</h3>
    <div style="display: flex; gap: 8px;">
      <button class="btn btn--ghost btn--small">Snooze</button>
      <button class="btn btn--ghost btn--small">Dismiss</button>
      <a class="btn btn--ghost btn--small" href="…">Docs ↗</a>
    </div>
  </header>

  <dl style="margin-top: 16px; display: grid; grid-template-columns: 120px 1fr; gap: 12px 16px;">
    <dt class="t-description"><strong>Description</strong></dt>
    <dd class="t-body">A crawler might follow a URL redirect but won't crawl the new page. <a href="…">Add any missing domains ↗</a> and update <code>pathsToMatch</code> in the crawler configuration.</dd>

    <dt class="t-description"><strong>Solution</strong></dt>
    <dd><a href="…">View in Monitoring</a></dd>
  </dl>
</article>

21. Grouped warnings (Data Analysis post-run)

<!-- Header banner with index name + Restart action -->
<div class="card" style="display: flex; justify-content: space-between; align-items: center; background: var(--surface-subtle); border: none;">
  <div>
    <h3 class="t-card-title">crawler_Wellington-Funds</h3>
    <p class="t-description">Last analyzed less than a minute ago</p>
  </div>
  <button class="btn btn--ghost btn--small">↻ Restart analysis</button>
</div>

<!-- Tabs -->
<nav class="tabs" style="margin-top: 24px;">
  <button class="tab" aria-selected="true">Warnings <span class="tab__count">21</span></button>
  <button class="tab" aria-selected="false">Dismissed warnings <span class="tab__count">0</span></button>
</nav>

<!-- Group: per-attribute -->
<section style="margin-top: 24px;">
  <h4 class="t-card-title">description</h4>
  <p class="t-description">
    <a href="…">Affects 3 records out of 13 records (23.08%)</a>
  </p>

  <table style="width: 100%; margin-top: 12px;">
    <thead>
      <tr><th>Warning</th><th>Records affected</th><th>Preview</th><th>Dismiss Warning</th></tr>
    </thead>
    <tbody>
      <tr>
        <td>Missing Data</td>
        <td>3</td>
        <td><button class="btn btn--ghost btn--small">View URLs</button></td>
        <td><a href="#">× Dismiss</a></td>
      </tr>
      <tr>
        <td>Healthy Records</td>
        <td>10</td>
        <td><button class="btn btn--ghost btn--small">View URLs</button></td>
        <td></td>
      </tr>
    </tbody>
  </table>
</section>

<!-- Repeat per attribute group -->

22. Run-control cluster

The persistent top-right control of the focused workspace. State-aware: button label and overflow menu both change based on the crawler's current run state. This is our value-add over Algolia's single static CTA.

<!-- State: running -->
<div class="run-control" data-state="running" style="display: flex; gap: 8px; align-items: center;">
  <button class="btn btn--ghost" id="pause-btn">
    <svg width="14" height="14" aria-hidden="true">…pause icon…</svg>
    Pause
  </button>

  <div class="run-control__menu" style="position: relative;">
    <button class="btn btn--ghost btn--small" aria-haspopup="menu" aria-expanded="false" aria-label="More run actions">
      <svg width="14" height="14">⋯</svg>
    </button>

    <div role="menu" hidden style="position: absolute; right: 0; top: 100%; margin-top: 4px; background: var(--surface-elevated); border: 1px solid var(--line-default); border-radius: 8px; box-shadow: var(--shadow-popover); min-width: 200px; padding: 4px;">
      <button role="menuitem" class="run-control__item">Stop crawl</button>
      <button role="menuitem" class="run-control__item">View live log</button>
      <hr style="border: 0; border-top: 1px solid var(--line-default); margin: 4px 0;">
      <button role="menuitem" class="run-control__item">Settings</button>
    </div>
  </div>

  <button class="btn btn--ghost btn--small" aria-label="Close and return to dashboard">×</button>
</div>

<style>
.run-control__item {
  display: block; width: 100%; text-align: left;
  padding: 8px 12px; font-size: var(--text-body);
  background: none; border: 0; border-radius: 6px;
  cursor: pointer; color: var(--ink-primary);
}
.run-control__item:hover { background: var(--surface-subtle); }
.run-control__item--danger { color: var(--status-danger-fg); }
</style>

Stateful labels:

State Primary button Overflow items
idle Crawl now (filled) Edit configuration · Re-crawl from scratch · Settings
running Pause (ghost) Stop crawl · View live log · Settings
paused Resume (filled) Stop crawl · Edit configuration · Re-crawl from scratch
stopped Crawl now (filled) Re-crawl from scratch · Edit configuration · Settings
failed Retry (filled) View error log · Edit configuration · Settings

Implementation notes: - The button visually morphs (label + icon) between states, never a layout shift. - Overflow menu is keyboard-navigable: ↑/↓ between items, Escape to close, Enter to activate. - Menu items that lead to destructive actions (Stop crawl, Re-crawl from scratch) get the --danger modifier on hover state. - Re-crawl from scratch always opens Recipe 24 — never fires immediately.


23. Run-state pill (list row)

Used in the crawler list table to show the current state of each crawler at a glance. Different from the static "last status" pills (Recipe 11): this one is live and it animates only the dot, never the layout.

<!-- Idle -->
<span class="pill pill--neutral">
  <svg width="6" height="6"><circle cx="3" cy="3" r="3" fill="currentColor"/></svg>
  Idle
</span>

<!-- Running -->
<span class="pill pill--running run-state-pill--live">
  <svg width="6" height="6"><circle cx="3" cy="3" r="3" fill="currentColor"/></svg>
  Running
</span>

<!-- Paused -->
<span class="pill pill--warning">
  <svg width="10" height="10" aria-hidden="true">…pause-bars…</svg>
  Paused
</span>

<!-- Stopped (user stopped) -->
<span class="pill pill--neutral">Stopped</span>

<!-- Failed -->
<span class="pill pill--danger">Failed</span>

<!-- Done (last run completed) -->
<span class="pill pill--success">
  <svg width="10" height="10" aria-hidden="true">…check…</svg>
  Done
</span>

<style>
/* The only animation in the entire pack — the live dot.
   Restraint: 1.6s ease-in-out, no scale, opacity only. */
.run-state-pill--live svg circle {
  animation: livePulse 1.6s ease-in-out infinite;
}
@keyframes livePulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}
</style>

Rules: - Only Running gets the live-dot pulse. All other states are static. - Pulse is opacity-only; never use scale, glow, or color shifts. - The pill is paired with a 3-dot overflow menu in the row to expose actions (Pause / Resume / Stop / Open / Delete).


24. Re-crawl confirm dialog

A scoped confirmation — less heavy than the typed-confirm (Recipe 10) because the action is destructive of records only, not the asset itself. Records get re-built on the next crawl. The crawler config, schedule, and index name all survive.

<div class="dialog-backdrop">
  <div class="dialog" role="dialog" aria-labelledby="recrawl-title">
    <div style="display: flex; justify-content: space-between;">
      <h2 class="dialog__title" id="recrawl-title">Re-crawl from scratch?</h2>
      <button class="btn btn--ghost btn--small" aria-label="Close">×</button>
    </div>

    <p class="t-body">
      We'll wipe the current index records and start a fresh crawl. The crawler's
      configuration, schedule, and index name will not change.
    </p>

    <div class="info-banner" style="margin-top: 16px;">
      <svg width="16" height="16">…info…</svg>
      <div>
        Search will return <strong>no results</strong> for this crawler until the
        first records are indexed (typically within a minute for small sites).
      </div>
    </div>

    <div class="dialog__actions">
      <button class="btn btn--ghost">Cancel</button>
      <button class="btn btn--primary">Re-crawl now</button>
    </div>
  </div>
</div>

Why not typed-confirm? Re-crawl is recoverable — the next crawl rebuilds the records. Typed-confirm (Recipe 10) is reserved for irreversible actions (Delete crawler). Reserving it for true irreversibility is what makes it credible.


How to extend this file

When you encounter a pattern in the dashboard not covered here:

  1. Take a screenshot, save it under dashboard_screenshots/ with a numbered name.
  2. Add a finding to 00-findings.md describing what's new.
  3. Add a recipe to this file with the copy-paste skeleton.
  4. If the pattern requires a new token, add it to tokens.css and reference it in 01-design-language.md §1.

The pack stays portable as long as all four files travel together: 00-findings.md, 01-design-language.md, 02-component-recipes.md, tokens.css.