/arc:harden

Production resilience

What it does

Harden reviews a component or page for production resilience. It checks for missing error states, text overflow handling, internationalization readiness, edge cases (empty data, huge datasets, slow networks), and input validation. All fixes use Tailwind utilities and HTML patterns. Designs that only work with perfect data aren't production-ready.

Why it exists

The demo looks great. Then a user pastes a 500-character name, the API times out, the German translation is 40% longer than English, and there are zero results to display. Harden catches all of these before your users do.

Design decisions

  • User-interactive, not agent-delegated. Each hardening decision needs context.
  • Focuses on UI resilience, not backend hardening (use security-engineer for that).
  • All fixes expressed in Tailwind utilities and semantic HTML.

Source document

Harden Workflow

Strengthen UI against real-world usage. Designs that only work with perfect data aren't production-ready.

Announce at start: "I'm using the harden skill to make this production-resilient."

This skill is user-interactive. Do NOT spawn agents. Hardening decisions need context — what's likely vs. paranoid, what's worth the complexity.


Phase 0: Load References


Phase 1: Read The Code

Read all files for the target component/page. Identify:

  • What data does it display?
  • What user input does it accept?
  • What async operations does it perform?
  • What states can the UI be in?

Phase 2: Systematic Audit

Work through each hardening dimension:

2.1 Text Overflow

Every text element needs an overflow strategy:

<!-- Single line — truncate -->
<p class="truncate">Long text gets ellipsis...</p>

<!-- Multi-line — clamp -->
<p class="line-clamp-3">Shows 3 lines then ellipsis...</p>

<!-- Headings — balance wrapping -->
<h1 class="text-balance">Heading wraps elegantly</h1>

<!-- Body — pretty wrapping -->
<p class="text-pretty">Body text avoids orphans</p>

<!-- Flex children — prevent overflow -->
<div class="min-w-0"><!-- Required in flex to allow truncation --></div>

<!-- URLs and long words -->
<p class="break-all">superlongdomainname.com/path/to/thing</p>

Check:

  • Every text element has an overflow strategy
  • Flex/grid children use min-w-0 where needed
  • Long user-generated content won't break layout
  • URLs and email addresses handled (break-all or break-words)

2.2 Empty States

Every data-driven view needs an empty state:

<!-- Not just "No items" — provide context and action -->
<div class="flex flex-col items-center gap-4 py-12 text-center">
  <p class="text-gray-500">No projects yet</p>
  <p class="text-sm text-gray-400">Create your first project to get started.</p>
  <button class="...">Create project</button>
</div>

Check:

  • Every list/table/grid has an empty state
  • Empty states explain what would be here and why
  • Empty states provide a next action (CTA)
  • Empty states don't show irrelevant UI (hide filters, sorting when empty)

2.3 Loading States

Every async operation needs loading feedback:

<!-- Skeleton loading — preferred over spinners -->
<div class="animate-pulse space-y-4">
  <div class="h-4 w-3/4 rounded bg-gray-200"></div>
  <div class="h-4 w-1/2 rounded bg-gray-200"></div>
</div>

<!-- Button loading — disable + spinner + keep label -->
<button disabled class="disabled:opacity-50" aria-busy="true">
  <Spinner class="size-4 animate-spin" />
  Save changes
</button>

<!-- Inline loading -->
<div aria-busy="true" aria-live="polite">Loading...</div>

Check:

  • Every async fetch has a loading state (skeleton preferred)
  • Buttons disable during submission (disabled, aria-busy)
  • Loading states match the shape of loaded content (skeleton)
  • aria-busy and aria-live for screen readers

2.4 Error States

Every operation that can fail needs error handling:

<!-- Inline field error -->
<input aria-invalid="true" class="border-red-500 focus:ring-red-500" />
<p class="mt-1 text-sm text-red-500" role="alert">Email address is required</p>

<!-- Page-level error with retry -->
<div class="flex flex-col items-center gap-4 py-12 text-center" role="alert">
  <p class="text-red-500">Something went wrong loading your data.</p>
  <button onclick="retry()">Try again</button>
</div>

Error messages must:

  • Say what happened (not "Error")
  • Say why if possible ("Your session expired")
  • Say how to fix it ("Sign in again" with link)

Check:

  • Every fetch/mutation has error handling
  • Error messages are specific and actionable
  • Inline errors use aria-invalid and role="alert"
  • Retry option provided where possible
  • Errors don't lose user's input (preserve form state)

2.5 Internationalization Readiness

Even if not translating yet, prepare the UI:

<!-- Use logical properties (RTL-safe) -->
<div class="ms-4 me-2 ps-3 pe-3">  <!-- Not ml-4 mr-2 pl-3 pr-3 -->

<!-- Budget 30-40% more space for translations -->
<!-- "Save" (EN) → "Speichern" (DE) → "Enregistrer" (FR) -->
<button class="min-w-[120px]">Save</button>  <!-- Don't constrain to exact content width -->

Use the Intl API for dates, numbers, currency:

// Not: "March 5, 2026" or toLocaleDateString()
new Intl.DateTimeFormat('en', { dateStyle: 'medium' }).format(date)
new Intl.NumberFormat('en', { style: 'currency', currency: 'USD' }).format(price)

Check:

  • Logical CSS properties used (ms-*, me-*, ps-*, pe-* not ml-*, mr-*)
  • Buttons/labels have room to grow (not pixel-exact to content)
  • Dates and numbers use Intl API
  • No text in images
  • Icons don't rely on cultural assumptions

2.6 Edge Cases

Test mentally with extreme inputs:

ScenarioWhat breaks?
0 itemsEmpty state needed
1 itemSingular/plural text? Layout with single child?
1,000+ itemsPagination or virtual scroll needed?
500-char user nameText overflow? Layout break?
Slow network (3G)Loading state needed? Optimistic UI?
OfflineError handling? Cache?
Double-click submitDuplicate prevention?
Paste into inputAllowed? Sanitized?
Browser backState preserved? Scroll restored?

Check:

  • Pagination or virtual scroll for large datasets
  • Double-submit prevention (disabled after click, idempotency keys)
  • Back/forward restores state (URL reflects state — use nuqs)
  • Paste always allowed (never block paste)
  • Unsaved changes warned before navigation

2.7 Input Validation

Validate client-side for UX, server-side for security:

<!-- Set constraints with HTML attributes -->
<input
  type="email"
  required
  maxlength="255"
  autocomplete="email"
  class="..."
/>

<!-- Accept free text, validate after -->
<!-- NEVER block typing -->
<!-- MUST allow submitting incomplete forms to surface validation -->

Check:

  • Correct type for keyboard (email, tel, url, number)
  • autocomplete and name for login/address forms
  • maxlength on text inputs
  • Validation on blur, not on keystroke (except password strength)
  • Errors below fields with aria-describedby

Phase 3: Report & Fix

Present findings grouped by impact:

Critical (will break for real users)

  • Missing error handling on async operations
  • Text overflow breaking layout
  • No loading states
  • Double-submit possible

High (degraded experience)

  • Missing empty states
  • No feedback on actions
  • Input validation missing

Medium (polish for production)

  • i18n readiness
  • Edge case handling
  • Keyboard accessibility gaps

For each finding: describe the issue, show the fix with Tailwind classes, then ask for approval before applying:

AskUserQuestion:
  question: "Apply this fix?"
  header: "Hardening Fix"
  options:
    - label: "Apply"
      description: "Apply this fix now"
    - label: "Skip"
      description: "Skip this fix and move to the next finding"
    - label: "Apply all"
      description: "Apply this and all remaining fixes without asking"

If the user selects "Apply all", apply all remaining fixes without further prompts.


Phase 4: Verify

After fixes:

  • Test with empty data
  • Test with very long text
  • Test with error responses (if possible)
  • Test loading states
  • Test on mobile