sluglist

Checklist mode: structured acceptance

Feedback normally fills a session from the bottom — the client freely creates issues. A checklist fills it from the top: the developer pre-seeds a list of "what shipped and what to verify", and the client walks it with one natural motion — click a row to check it off; click the slug button on a row to flag a problem (that opens the normal issue flow, linked back to the item).

The panel is an accordion of sections that self-navigates: finish a section and it collapses, opening the next one. A summary line (5 of 12 checked · 2 issues · 7 left) replaces a bare counter, and the circle's badge counts what's left, turning to ✓ when everything is checked. The result is a coverage map in session.yaml: what's confirmed, what was flagged (with links to the issues), and what was never checked.

It's entirely opt-in: a second circle appears above the feedback button only when a checklist is configured. Without one, the widget looks and works exactly as before.

const widget = createFeedbackWidget({
  project: "acme",
  connectors: [/* ... */],
  checklist: {
    id: "export-release-2026-07",
    title: "Export + notifications release",
    description: "Walk each item and check it off. Flag anything that looks wrong.",
    sections: [
      {
        title: "Export",
        items: [
          { id: "export-button", title: "On Reports, the Export button downloads a CSV", url: "/reports" },
          { id: "csv-columns", title: "The CSV has all the expected columns", hint: "Open it in a spreadsheet" },
          // Dynamic route: no fabricated id — a human hint + a wildcard match.
          { id: "assessment-header", title: "Opening any assessment shows the new header",
            hint: "Open the dashboard and pick any assessment", url: "/dashboard", url_match: "/assessments/*" },
        ],
      },
      { title: "Notifications", items: [{ id: "email-sent", title: "An email arrives after an export" }] },
    ],
  },
});

Smart links

url must be a static route — it renders as an "Open ↗" chip that navigates there. For a dynamic route (an id/uuid in the path) don't guess an id: give a human hint and a wildcard url_match ("/assessments/*"). It never navigates — it just lights the item up with a "You're here" tag when the tester is on a matching page. The two can coexist (a list url + a detail url_match).

Pass a URL string instead of an object to fetch the checklist at init (GET → JSON of the same shape) — handy when a skill generates it: checklist: "/checklist.json". An unreachable or invalid checklist warns and is skipped; capture still works.

Note

A checklist is a session input and verdicts are its output — nothing carries over. Every session runs the list from scratch, which is why a run is evidence of one moment rather than a status board.

Verdicts: the coverage map

Verdicts land in session.yaml (put-per-verdict, upserted on every click):

checklist:
  id: export-release-2026-07
  title: "Export + notifications release"
  items:
    - id: export-button
      section: "Export"
      title: "On Reports, the Export button downloads a CSV"
      verdict: pass
      issue: null
      ts: 2026-07-24T14:05:10Z
    - id: csv-columns
      section: "Export"
      title: "The CSV has all the expected columns"
      verdict: fail
      issue: "03"          # the issue that documents the failure
      ts: 2026-07-24T14:06:00Z
    - id: email-sent
      section: "Notifications"
      title: "An email arrives after an export"
      verdict: null        # not checked
      issue: null
      ts: null

Generate a checklist from a branch

The package ships a sluglist-checklist skill: point Claude Code at a branch and it builds a client-facing checklist from the diff (user-visible pages/components/text only — refactors, tests and config are excluded), grouped by feature and phrased for a non-developer, written to .sluglist/checklists/<name>.json. Ask it to "generate a checklist from this branch".

Five intents, one per source of truth: branch (the diff), re-test (a fixed session's fixes.yaml), smoke (routes + docs), regression (the project's standing baseline), and scenario (a written brief). The base branch a branch diff runs against comes from .sluglist/PROJECT.md.

The regression checklist has a lifecycle

Four of the five intents produce a list you use once. regression is different: it is a committed baseline at .sluglist/checklists/regression.json, seeded once with the smoke algorithm and then updated after each merge — ask for "update the regression checklist from this branch".

An update is a diff of the list, not a regeneration:

  • Additions for user-visible surface the branch added — one or two loud checks per feature, folded into an existing section when one fits.
  • Removals for items whose surface the branch deleted — always proposed for you to confirm, never applied silently.
  • The ~30-item cap holds. If additions would push past it, the skill names which existing items to cut instead of growing the file.
  • Item ids stay stable for unchanged items, so verdicts recorded in past sessions still map to them.

Scope — the checklist is a session input, verdicts are its output

The checklist enters a session and the verdicts leave with it. There is no lifecycle beyond the session: items are never reopened, verdicts never sync between sessions, nothing is stored as a "done on the server", and issues are never blocked on completing the checklist. Every session runs the checklist from scratch. This is deliberate — it keeps sluglist a capture tool, not a workflow tracker.

See also: the client-acceptance workflow end to end.