AhaSlides Design
v0.52.0React · Vue · Lit

Agent feeds · raw

anti-slop.agent.json raw feed

Machine feed: the anti-slop loop + per-surface { rules, criteria, selfCheck }. Compiled from the DS-owned criteria store.

◆ generated — the exact file served to agents at /anti-slop.agent.json · do not edit by hand

anti-slop.agent.json
{
  "generatedFrom": "anti-slop/criteria.json + guidelines/*.json",
  "owner": "ahaslides-design",
  "loop": "You are generating AhaSlides product UI by consuming this design system.\nBefore you write a screen: (1) identify the surface(s) you are building;\n(2) read that surface’s rules below; (3) after building, run the MECHANICAL gate first\n(node screen-lint.mjs --surface=<product|canvas> <files>) to hard-fail the zero-interpretation\ndefects (raw hex, off-scale radius/weight, gradient fills, sub-16px/viewport fonts), then the\nsurface’s BINARY judge — every criterion is PASS or FAIL, no partial credit; (4) fix every FAIL and re-judge;\n(5) ship only when every criterion PASSes.",
  "surfaces": {
    "ux-writing": {
      "surface": "copy",
      "origin": "seeded",
      "skillRef": {
        "build": "aha-design:aha-design-ux-writing",
        "judge": "aha-design:aha-design-ux-writing-judge"
      },
      "rules": [
        {
          "id": "sentence-case-default",
          "rule": "Sentence case is the house default for ALL product UI text — labels, CTAs, menu/tab/nav items, slide-type and interactive-component names, headings, tooltips, helper text, dialog titles, empty states, and messages. Capitalize the first letter only; keep proper nouns, brand names, and acronyms as-is. 'View report' not 'View Report'; 'Multiple choice' / 'Word cloud' / 'Open-ended' / 'Spinner wheel' / 'Q&A' not Title Case.",
          "ref": [
            "UXW-1"
          ]
        },
        {
          "id": "one-canonical-label",
          "rule": "Pick ONE canonical cased form per label and use it in every place it appears — button, menu, tooltip, analytics label. Resolve AI drift (card says 'View Report', menu says 'View report') to the sentence-case 'View report'; never ship both forms.",
          "ref": [
            "UXW-1"
          ]
        },
        {
          "id": "caps-label-exception",
          "rule": "The styled all-caps eyebrow / overline / chip label (the 'capitalized' typography role, owned by aha-design-typography) is intentionally uppercased — it is a deliberate style, not a casing mistake. Don't 'fix' it to sentence case, and don't cite it to justify Title Case elsewhere.",
          "ref": [
            "UXW-1"
          ]
        },
        {
          "id": "message-anatomy",
          "rule": "A message names the OUTCOME (what happened, always), adds the CAUSE only when it changes what the user does, always states the NEXT step for anything recoverable, and exposes the ACTION when a one-tap recovery exists. Title = the OUTCOME; body = the NEXT step — never inverted.",
          "ref": [
            "UXW-2",
            "UXW-3",
            "UXW-7"
          ]
        },
        {
          "id": "be-specific",
          "rule": "Name the specific object and what happened — a concrete count over 'some'. 'Couldn't load some presentations.' not 'Some items could not be loaded.' Never ship a standalone 'Something went wrong', 'An error occurred', 'Oops', 'Unexpected error', 'Some items could not be loaded', or bare 'Failed'.",
          "ref": [
            "UXW-2"
          ]
        },
        {
          "id": "never-dead-end",
          "rule": "Any message about a recoverable problem must tell the user what to do now AND expose the recovery as a real control when a one-tap recovery exists (Retry, Reload, Log in again, Go back, Contact support). A message with an outcome but no next step is a dead-end.",
          "ref": [
            "UXW-3"
          ]
        },
        {
          "id": "human-blameless",
          "rule": "Be blameless, jargon-free, and front-loaded. Don't blame the user ('That file type isn't supported — try a PNG or JPG', not 'You entered an invalid file'). Keep raw codes / stack traces / HTTP status out of the headline — tuck any code into secondary detail. Lead with the user's goal, not the system's failure.",
          "ref": [
            "UXW-4"
          ]
        },
        {
          "id": "match-severity",
          "rule": "Match wording severity to real impact. A transient, retryable hiccup is a calm info/warning with 'Try again', not a blaring red 'Error' — over-alarming trains users to ignore real errors. A destructive or data-loss outcome states the stakes plainly ('This permanently deletes 12 responses.') without crying wolf or burying the lede.",
          "ref": [
            "UXW-5"
          ]
        },
        {
          "id": "empty-is-not-error",
          "rule": "An empty state (success, no data) is encouraging with a primary next action ('No presentations yet. Create your first one.') — never an apology or a red alarm. A load failure follows be-specific + never-dead-end (name it, offer Retry) and is never disguised as emptiness, which would hide the Retry the user needs.",
          "ref": [
            "UXW-6"
          ]
        },
        {
          "id": "copy-survives",
          "rule": "Recovery-required copy must not live only in an auto-dismissing toast — it belongs in an inline Alert or on the page so the words survive. When a message has a title + body, the title carries the outcome and the body the next step (a shapeless single-string message isn't penalised on shape — its vagueness is a be-specific problem).",
          "ref": [
            "UXW-7"
          ]
        }
      ],
      "criteria": [
        {
          "id": "C1",
          "title": "Casing — sentence case across all product UI text",
          "test": "**Rule.** Sentence case is the house default for **all** product UI text — buttons/CTAs, menu/tab/nav items, **slide-type and interactive-component names**, headings, tooltips, dialog titles, empty states, messages. Capitalize the **first letter only**; keep proper nouns / brand names / acronyms as-is."
        },
        {
          "id": "C2",
          "title": "Specific — names the object + what happened",
          "test": "**Rule.** A problem message says *which* thing and *what* about it. Vague, information-free shells are banned as a standalone message: *Something went wrong · An error occurred · Oops / Uh oh · Unexpected error · Some items could not be loaded · bare Failed / Error · Invalid input*."
        },
        {
          "id": "C3",
          "title": "Actionable — states the next step AND exposes the recovery",
          "test": "**Rule.** Any **recoverable** problem tells the user what to do now and exposes the recovery as a real control when a one-tap recovery exists (Retry / Reload / Log in again / Go back / Contact support). An outcome with no next step is a **dead-end** — the \"*so what now?*\" reaction."
        },
        {
          "id": "C4",
          "title": "Human — blameless, jargon-free, no raw codes in the headline",
          "test": "**Rule.** Don't blame the user; keep raw error codes / stack / HTTP status out of the headline (a code belongs in secondary detail); use plain language; lead with the user's goal. ✅ **GOOD:** \"That file type isn't supported — try a PNG or JPG.\" / \"We couldn't open your presentation.\" ❌ **BAD:** \"You entered an invalid file.\" · \"Null reference in render pipeline\" · \"Error 500\" **Common failure mode"
        },
        {
          "id": "C5",
          "title": "Proportional — severity of wording matches real impact",
          "test": "**Rule.** The words (and the type handed to the surface) match actual impact. A transient, retryable hiccup is a calm info/warning, not a blaring red \"Error\"."
        },
        {
          "id": "C6",
          "title": "Empty ≠ error",
          "test": "**Rule.** An empty state (\"nothing here yet\") is a success with no data — encouraging, with a **primary create action**, never an apology or a red alarm. A load **failure** is never disguised as emptiness (\"No presentations\" when the fetch actually errored hides the Retry the user needs)."
        },
        {
          "id": "C7",
          "title": "Right home & shape",
          "test": "**Scope.** If the surface has **no** recovery-required message and **no** title+body message at all (e.g. a labels-only surface), C7 is **`N/A`** in full — not PASS."
        }
      ],
      "selfCheck": [
        {
          "label": "sentence case everywhere; one canonical form per label",
          "verdict": "N/A — per-surface"
        },
        {
          "label": "messages name the object + what happened, no vague placeholders",
          "verdict": "N/A — per-surface"
        },
        {
          "label": "recoverable problems offer a next step + recovery control, no dead-ends",
          "verdict": "N/A — per-surface"
        },
        {
          "label": "empty states distinct from failures; severity matches impact",
          "verdict": "N/A — per-surface"
        }
      ]
    },
    "app-shell": {
      "surface": "shell",
      "origin": "authored",
      "skillRef": {
        "build": "ahaslides-design:app-shell",
        "judge": "ahaslides-design:app-shell",
        "note": "Authored in the DS — there is no aha-design plugin skill for the app shell. The DS OWNS this surface's criteria in anti-slop/criteria.json (surfaces.app-shell, C1..C5). Backfilling a matching aha-design build+judge skill pair is a follow-up, not a dependency."
      },
      "rules": [
        {
          "rule": "Use the real AhaSlides brand mark + wordmark at the top of the nav rail — the shipped logo / an <aha-icon> brand glyph — never a coloured letter-tile or initials square standing in for a logo.",
          "ref": [
            "C1"
          ]
        },
        {
          "rule": "Never ship a placeholder as a final state: a QR/thumbnail/region renders a real asset or an explicit, styled empty state — never a permanent 'Loading QR…' label, spinner, or grey box.",
          "ref": [
            "C2"
          ]
        },
        {
          "rule": "Give each region one primary action and a scannable type hierarchy from the scale; do not render uniform grey-on-white equal-weight cards with evenly-spaced everything (the admin-template tell).",
          "ref": [
            "C3"
          ]
        },
        {
          "rule": "Fill every shell slot with a DS component (aha-button, aha-icon, aha-avatar, aha-badge, DataTable) and take all spacing/radius/colour from --aha-* tokens — never a hand-rolled <button>/<div> with inline hex/px, never a raw <table>.",
          "ref": [
            "C4"
          ]
        },
        {
          "rule": "Animate the active/selected nav state by toggling an attribute/class on a persistent node via the motion tokens (--aha-motion-* + --aha-ease-*); never snap, never a bare duration literal, never rebuild the subtree on selection.",
          "ref": [
            "C5"
          ]
        }
      ],
      "criteria": [
        {
          "id": "C1",
          "title": "Real brand identity, never a placeholder",
          "test": "The brand mark + wordmark are the real AhaSlides assets (aha-icon brand glyph / shipped logo), never a coloured letter tile or initials square standing in for a logo."
        },
        {
          "id": "C2",
          "title": "No dead placeholder shipped as a final state",
          "test": "Every region renders a real asset or a proper empty state — never a permanent 'Loading…' label, spinner, or grey box (e.g. a QR block shows a real code or an explicit 'code unavailable', not 'Loading QR…')."
        },
        {
          "id": "C3",
          "title": "Deliberate hierarchy, one primary action per region",
          "test": "Each region has one clear primary action and a scannable type hierarchy from the scale; not uniform grey-on-white cards of equal weight with evenly-spaced everything."
        },
        {
          "id": "C4",
          "title": "Shell chrome is tokenised and reuses DS components",
          "test": "Nav rail, top bar, and content region take spacing/radius/colour from --aha-* tokens and fill their slots with DS components (aha-button, aha-icon, aha-avatar, aha-badge, DataTable), never hand-rolled buttons/icons/hex/px."
        },
        {
          "id": "C5",
          "title": "Active/selected nav state animates on a persistent node",
          "test": "The active nav item and any selected state toggle an attribute/class on a persistent node via motion tokens (--aha-motion-* / --aha-ease-*), never a bare duration literal and never by rebuilding the subtree."
        }
      ],
      "selfCheck": [
        {
          "label": "real brand mark + wordmark, no placeholder letter-tile",
          "verdict": "consumer self-judges (C1)"
        },
        {
          "label": "no dead 'Loading…' placeholder shipped as final; real asset or explicit empty state",
          "verdict": "consumer self-judges (C2)"
        },
        {
          "label": "one primary action per region; scannable hierarchy, not equal-weight cards",
          "verdict": "consumer self-judges (C3)"
        },
        {
          "label": "every slot is a DS component; all spacing/radius/colour from tokens; no raw table",
          "verdict": "consumer self-judges (C4)"
        },
        {
          "label": "active nav state animates on a persistent node via motion tokens",
          "verdict": "consumer self-judges (C5)"
        }
      ]
    }
  },
  "notWired": [
    "audience",
    "canvas",
    "feedback",
    "overlays",
    "settings",
    "status-badges"
  ]
}