{
  "contract": "tessli.public-library-item.v1",
  "canonicalPath": "/library/dashboard-overview",
  "representations": {
    "json": "/library/dashboard-overview/item.json",
    "markdown": "/library/dashboard-overview/item.md"
  },
  "preview": "/preview/dashboard-overview",
  "item": {
    "slug": "dashboard-overview",
    "title": "Dashboard overview",
    "kind": "page",
    "category": "dashboards",
    "summary": "Opens on the one question the dashboard exists to answer, gives the answer in a sentence, backs it with two or three compared metrics and a trend, and lists what needs attention.",
    "status": "community",
    "install": "npx shadcn@latest add online-scope-lab/tessli/dashboard-overview",
    "registry": {
      "type": "registry:block",
      "registryDependencies": [],
      "dependencies": []
    },
    "notes": {
      "id": "dashboard-overview",
      "kind": "page",
      "category": "dashboards",
      "title": "Dashboard overview",
      "summary": "Opens on the one question the dashboard exists to answer, gives the answer in a sentence, backs it with two or three compared metrics and a trend, and lists what needs attention.",
      "use_when": [
        "People open the dashboard with one recurring question, such as whether work is on time, money is on budget or a service is healthy.",
        "The figures have a meaningful baseline (last period, a target) and there are specific items someone can act on."
      ],
      "avoid_when": [
        "People come to explore many unrelated metrics; give them a report builder or a data table with filters instead.",
        "The data is real-time operations monitoring where seconds matter; use a status board with alerts.",
        "There is no baseline or target to compare against yet; show the raw list until there is one."
      ],
      "rules": [
        "H-01",
        "W-01",
        "W-03",
        "W-04",
        "T-03",
        "C-02",
        "ST-01",
        "R-01"
      ],
      "ai_default": "A \"Dashboard\" heading over four identical KPI cards (total users, revenue, sessions, conversion) each with a big number and a green or red \"+12.5%\" badge with an arrow, a large area chart with a gradient fill and no stated baseline, a \"Recent activity\" feed, and no loading, empty or error state.",
      "why_this_is_better": [
        "The heading is the question people open the page to answer, and the first sentence answers it, so most visits end after one line.",
        "Three metrics instead of four interchangeable cards, each with its baseline and target written out (\"Up 8 points from 74% last week\", \"Target 80% or more\"), so a change can be judged without guessing what it is compared with.",
        "Changes are written in words that say which way is better (\"0.5 days faster\"), not a coloured percentage with an arrow, so meaning survives greyscale and screen readers.",
        "The trend is a table with a bar in each row and the value written beside it, so the chart is its own text alternative and needs no chart library.",
        "A short list of the items that need action, longest wait first, with one primary action that clears it, turns the answer into something to do.",
        "Loading keeps metric labels and row positions in place; empty and error each say why and offer one way forward."
      ],
      "failures": [
        "Adding a fourth and fifth metric until the answer is buried in a KPI grid again.",
        "Showing a change without its baseline (\"+8\") or colouring it green when lower would be better.",
        "Writing a heading like \"Overview\" that answers nothing, or an answer sentence that no longer matches the figures.",
        "Turning the attention list into an activity feed of everything that happened.",
        "Shipping the demo figures, or a target nobody agreed, as if they were real."
      ],
      "states": [
        "default",
        "loading",
        "empty",
        "error"
      ],
      "responsive": {
        "desktop": "The question, answer and period run across the top at reading width; the metrics form one bordered strip of aligned columns; below, the attention list takes the wider column beside the trend chart.",
        "mobile": "One column in priority order (question and answer first), then metrics stacked with dividers, the attention list with full-width 44px rows and a full-width action, then the trend as compact horizontal bars that fit 320px."
      },
      "a11y": [
        "The question is the page heading (h1 by default, h2 under a page title); every region is a section labelled by its own heading.",
        "Metrics are a description list; changes and targets are text, never colour or an arrow alone.",
        "The chart is a table with row headers for each period and the value as text; bars, the dashed target line and its legend swatch are hidden from assistive technology, and the target is stated in text.",
        "Loading is announced through a status region and marked aria-busy; empty and error messages appear in a polite live region, and the error names its cause in text as well as colour.",
        "Each item that needs attention is one link at least 44px tall with a visible focus ring."
      ],
      "status": "community",
      "reviewed_by": [],
      "reviewed_at": null,
      "contributors": [],
      "provenance": "original",
      "license": "MIT"
    },
    "rules": [
      {
        "id": "H-01",
        "statement": "Give each section, card or dialog at most one primary-styled action.",
        "path": "/rules/H-01"
      },
      {
        "id": "W-01",
        "statement": "Write headings that make the product's point, not labels like \"Features\" or \"FAQ\".",
        "path": "/rules/W-01"
      },
      {
        "id": "W-03",
        "statement": "Show no ratings, counts, logos, quotes or metrics you can't source.",
        "path": "/rules/W-03"
      },
      {
        "id": "W-04",
        "statement": "Lead answers, errors and notices with the answer or consequence, then the detail.",
        "path": "/rules/W-04"
      },
      {
        "id": "T-03",
        "statement": "Set prices, counts and numeric columns in tabular figures.",
        "path": "/rules/T-03"
      },
      {
        "id": "C-02",
        "statement": "Pair every colour-coded meaning (error, selected, status) with text or shape.",
        "path": "/rules/C-02"
      },
      {
        "id": "ST-01",
        "statement": "Design empty, loading and error states for every region that loads or lists data.",
        "path": "/rules/ST-01"
      },
      {
        "id": "R-01",
        "statement": "On phones, restack content by priority and adapt components; don't shrink the desktop.",
        "path": "/rules/R-01"
      }
    ],
    "explanation": "Most dashboards answer a question nobody asked: four totals, a chart and a feed. People open a dashboard because they want to know one thing, such as whether reviews are happening on time. This item makes that question the heading and puts the answer in the first sentence.\n\nEvery metric carries its comparison in words, with the baseline and the target, so readers can tell whether a change is good without decoding colours. The trend is a plain table with a bar in each row: it reads as a chart, works at 320px and is already its own text alternative. The attention list turns the answer into work, with one primary action.\n\nWhen you adapt it, keep it to two or three metrics, write the answer from the real figures, and replace every demo number before shipping. Design the loading, empty and error states with the real copy; they are part of the item, not extras.",
    "source": {
      "path": "web/registry/items/dashboard-overview/dashboard-overview.tsx",
      "code": "import * as React from \"react\";\n\nimport { cn } from \"@/registry/shadcn/lib/utils\";\n\nexport type DashboardStatus = \"ready\" | \"loading\" | \"empty\" | \"error\";\n\nexport type DashboardMetric = {\n  id: string;\n  /** What is measured, in plain words: \"Reviewed within 2 days\". */\n  label: string;\n  /** The current value, formatted: \"82%\", \"14 notes\", \"1.4 days\". */\n  value: React.ReactNode;\n  /**\n   * The change against a named baseline, in words that say which way is\n   * better: \"Up 8 points from 74% last week\", \"0.5 days faster than last\n   * week\". Never a bare \"+8%\" in green.\n   */\n  comparison?: React.ReactNode;\n  /** A target, definition or caveat: \"Target: 80% or more\". */\n  context?: React.ReactNode;\n};\n\nexport type AttentionItem = {\n  id: string;\n  /** The thing that needs attention, named as people know it. */\n  title: React.ReactNode;\n  /** Where to deal with it. Each row is one link. */\n  href: string;\n  /** Where it lives and who it belongs to: \"Field research · Priya N.\" */\n  detail?: React.ReactNode;\n  /** A short status written as text, e.g. \"Unassigned\". */\n  flag?: string;\n  /** How long, how much or how late, formatted: \"9 days\". */\n  measure: React.ReactNode;\n};\n\nexport type DashboardAttention = {\n  /** Says what the list holds: \"Waiting more than a week\". */\n  title: React.ReactNode;\n  description?: React.ReactNode;\n  /** Longest-waiting or highest-impact first. Show five at most. */\n  items: AttentionItem[];\n  /** Read before each row's measure by screen readers, e.g. \"Waiting\". */\n  measureLabel?: string;\n  /** Shown when items is empty: say that nothing needs attention, and why. */\n  emptyText?: React.ReactNode;\n  /** The one action that clears the list. The page's only primary button. */\n  action?: React.ReactNode;\n  /** The full list, when there are more items than shown. */\n  moreLink?: { label: string; href: string };\n};\n\nexport type TrendPoint = {\n  /** The period: \"14 Sep\", \"This week\". */\n  label: string;\n  value: number;\n  /** The value as written in text; defaults to value + unit. */\n  display?: string;\n  /** Marks the period the metrics describe. Shown in bold text, not colour. */\n  current?: boolean;\n};\n\nexport type DashboardTrend = {\n  /** Make the point: \"Above target for three weeks running\". */\n  title: React.ReactNode;\n  /** One sentence that states what the chart shows, for everyone. */\n  description?: React.ReactNode;\n  /** Column heading for the periods. */\n  periodHeading?: string;\n  /** Column heading for the values: \"Reviewed within 2 days\". */\n  valueHeading: string;\n  /** Oldest first. Eight to twelve periods read well. */\n  points: TrendPoint[];\n  /** The value at the full bar width. Defaults to 100. */\n  max?: number;\n  unit?: string;\n  /** Drawn as a dashed line and named in text below the chart. */\n  target?: { value: number; label: React.ReactNode };\n};\n\nexport type DashboardMessage = {\n  /** Shown before the title for errors, in text: \"Couldn’t load figures\". */\n  label?: string;\n  title: React.ReactNode;\n  /** Empty: why and what next. Error: what failed and what is still safe. */\n  description?: React.ReactNode;\n  /** One primary action, at most one secondary. */\n  actions?: React.ReactNode;\n};\n\nexport type DashboardOverviewProps = Omit<\n  React.ComponentProps<\"div\">,\n  \"title\"\n> & {\n  /** Product or area, shown small above the question. */\n  context?: React.ReactNode;\n  /** The one question this dashboard answers. Rendered as the page heading. */\n  question: React.ReactNode;\n  /** The answer in one or two sentences, answer first. */\n  answer?: React.ReactNode;\n  /** The period, the comparison baseline and when the data was updated. */\n  period?: React.ReactNode;\n  status?: DashboardStatus;\n  /** Two or three metrics. Labels stay visible while values load. */\n  metrics: DashboardMetric[];\n  attention: DashboardAttention;\n  trend: DashboardTrend;\n  /** Replaces the figures when status is \"empty\" or \"error\". */\n  message?: DashboardMessage;\n  /** Read out while loading. */\n  loadingLabel?: string;\n  /** Screen-reader heading for the metrics strip. */\n  metricsHeading?: string;\n  /** 1 when the dashboard is the page; 2 when it sits under a page title. */\n  headingLevel?: 1 | 2;\n  /** Data sources, definitions or a demo-data notice. */\n  footnote?: React.ReactNode;\n};\n\nconst skeleton = \"block rounded-sm bg-muted\";\nconst linkClass =\n  \"inline-flex min-h-11 items-center rounded-sm text-sm font-medium underline underline-offset-4 outline-none hover:decoration-2 focus-visible:ring-[3px] focus-visible:ring-ring/50\";\n\nexport function DashboardOverview({\n  context,\n  question,\n  answer,\n  period,\n  status = \"ready\",\n  metrics,\n  attention,\n  trend,\n  message,\n  loadingLabel = \"Loading figures\",\n  metricsHeading = \"Key figures\",\n  headingLevel = 1,\n  footnote,\n  className,\n  ...props\n}: DashboardOverviewProps) {\n  const id = React.useId();\n  const Title = `h${headingLevel}` as const;\n  const Section = `h${headingLevel + 1}` as \"h2\" | \"h3\";\n  const loading = status === \"loading\";\n  const hasFigures = status === \"ready\" || loading;\n  const ids = {\n    title: `${id}-title`,\n    metrics: `${id}-metrics`,\n    attention: `${id}-attention`,\n    trend: `${id}-trend`,\n    trendDescription: `${id}-trend-description`,\n    message: `${id}-message`,\n  };\n\n  return (\n    <div\n      data-slot=\"dashboard-overview\"\n      data-status={status}\n      className={cn(\"bg-background text-foreground\", className)}\n      {...props}\n    >\n      <div className=\"mx-auto w-full max-w-6xl px-4 py-8 sm:px-6 sm:py-12 lg:px-8\">\n        <header className=\"max-w-3xl\">\n          {context ? (\n            <p className=\"text-sm text-muted-foreground\">{context}</p>\n          ) : null}\n          <Title\n            id={ids.title}\n            className=\"mt-1 text-2xl leading-tight font-semibold tracking-tight text-balance sm:text-3xl\"\n          >\n            {question}\n          </Title>\n          {loading ? (\n            <span aria-hidden=\"true\" className=\"mt-3 block space-y-1.5\">\n              <span className={cn(skeleton, \"h-6 w-full max-w-xl sm:h-7\")} />\n              <span className={cn(skeleton, \"h-6 w-2/3 max-w-sm sm:h-7\")} />\n            </span>\n          ) : answer ? (\n            <div className=\"mt-3 text-base leading-relaxed text-pretty sm:text-lg\">\n              {answer}\n            </div>\n          ) : null}\n          {period ? (\n            <p className=\"mt-3 text-sm text-muted-foreground\">{period}</p>\n          ) : null}\n        </header>\n\n        <p role=\"status\" className=\"sr-only\">\n          {loading ? loadingLabel : \"\"}\n        </p>\n\n        <div aria-live=\"polite\">\n          {!hasFigures && message ? (\n            <section\n              aria-labelledby={ids.message}\n              data-variant={status}\n              className={cn(\n                \"mt-8 max-w-3xl rounded-lg border bg-card px-5 py-6 text-card-foreground sm:px-8 sm:py-8\",\n                status === \"error\"\n                  ? \"border-destructive/50\"\n                  : \"border-dashed border-border\",\n              )}\n            >\n              <Section\n                id={ids.message}\n                className=\"text-lg leading-snug font-semibold tracking-tight text-balance\"\n              >\n                {message.label ? (\n                  <span\n                    className={cn(\n                      \"mb-1 block text-sm font-semibold\",\n                      status === \"error\"\n                        ? \"text-destructive\"\n                        : \"text-muted-foreground\",\n                    )}\n                  >\n                    {message.label}\n                  </span>\n                ) : null}\n                {message.title}\n              </Section>\n              {message.description ? (\n                <div className=\"mt-2 max-w-prose text-sm leading-relaxed text-pretty text-muted-foreground\">\n                  {message.description}\n                </div>\n              ) : null}\n              {message.actions ? (\n                <div className=\"mt-6 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center\">\n                  {message.actions}\n                </div>\n              ) : null}\n            </section>\n          ) : null}\n        </div>\n\n        {hasFigures ? (\n          <div aria-busy={loading || undefined}>\n            <section aria-labelledby={ids.metrics} className=\"mt-8\">\n              <Section id={ids.metrics} className=\"sr-only\">\n                {metricsHeading}\n              </Section>\n              <dl\n                className={cn(\n                  \"grid border-y border-border\",\n                  metrics.length === 2 ? \"sm:grid-cols-2\" : \"sm:grid-cols-3\",\n                )}\n              >\n                {metrics.map((metric) => (\n                  <div\n                    key={metric.id}\n                    className=\"flex flex-col gap-1 border-b border-border py-5 last:border-b-0 sm:row-span-4 sm:grid sm:grid-rows-subgrid sm:gap-y-1 sm:border-b-0 sm:border-l sm:px-6 sm:first:border-l-0 sm:first:pl-0\"\n                  >\n                    <dt className=\"text-sm text-muted-foreground\">\n                      {metric.label}\n                    </dt>\n                    <dd className=\"text-2xl leading-8 font-semibold tracking-tight tabular-nums sm:text-3xl sm:leading-9\">\n                      {loading ? (\n                        <span\n                          aria-hidden=\"true\"\n                          className={cn(skeleton, \"h-8 w-24 sm:h-9\")}\n                        />\n                      ) : (\n                        metric.value\n                      )}\n                    </dd>\n                    {metric.comparison ? (\n                      <dd className=\"text-sm leading-5\">\n                        {loading ? (\n                          <span\n                            aria-hidden=\"true\"\n                            className={cn(skeleton, \"h-5 w-44 max-w-full\")}\n                          />\n                        ) : (\n                          metric.comparison\n                        )}\n                      </dd>\n                    ) : null}\n                    {metric.context ? (\n                      <dd className=\"text-sm leading-5 text-muted-foreground\">\n                        {metric.context}\n                      </dd>\n                    ) : null}\n                  </div>\n                ))}\n              </dl>\n            </section>\n\n            <div className=\"mt-10 grid gap-10 lg:grid-cols-[minmax(0,3fr)_minmax(0,2fr)] lg:gap-12\">\n              <section aria-labelledby={ids.attention}>\n                <Section\n                  id={ids.attention}\n                  className=\"text-lg leading-snug font-semibold tracking-tight text-balance\"\n                >\n                  {attention.title}\n                </Section>\n                {attention.description ? (\n                  <p className=\"mt-1 max-w-prose text-sm leading-relaxed text-muted-foreground\">\n                    {attention.description}\n                  </p>\n                ) : null}\n\n                {loading ? (\n                  <ul\n                    aria-hidden=\"true\"\n                    className=\"mt-4 border-t border-border\"\n                  >\n                    {[0, 1, 2].map((row) => (\n                      <li\n                        key={row}\n                        className=\"flex items-start justify-between gap-4 border-b border-border py-3\"\n                      >\n                        <span className=\"w-full space-y-1.5\">\n                          <span className={cn(skeleton, \"h-5 w-3/4\")} />\n                          <span className={cn(skeleton, \"h-5 w-1/2\")} />\n                        </span>\n                        <span className={cn(skeleton, \"h-5 w-14 shrink-0\")} />\n                      </li>\n                    ))}\n                  </ul>\n                ) : attention.items.length ? (\n                  <ul className=\"mt-4 border-t border-border\">\n                    {attention.items.map((item) => (\n                      <li key={item.id} className=\"border-b border-border\">\n                        <a\n                          href={item.href}\n                          className=\"group/row -mx-2 flex min-h-11 items-start justify-between gap-4 rounded-sm px-2 py-3 outline-none hover:bg-muted focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n                        >\n                          <span className=\"min-w-0\">\n                            <span className=\"block font-medium break-words underline-offset-4 group-hover/row:underline\">\n                              {item.title}\n                            </span>\n                            {item.detail || item.flag ? (\n                              <span className=\"mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted-foreground\">\n                                {item.detail ? (\n                                  <span>{item.detail}</span>\n                                ) : null}\n                                {item.flag ? (\n                                  <span className=\"rounded-sm border border-border px-1.5 text-xs leading-5 font-medium text-foreground\">\n                                    {item.flag}\n                                  </span>\n                                ) : null}\n                              </span>\n                            ) : null}\n                          </span>\n                          <span className=\"shrink-0 text-right text-sm leading-6 tabular-nums\">\n                            {attention.measureLabel ? (\n                              <span className=\"sr-only\">\n                                {attention.measureLabel}{\" \"}\n                              </span>\n                            ) : null}\n                            {item.measure}\n                          </span>\n                        </a>\n                      </li>\n                    ))}\n                  </ul>\n                ) : (\n                  <p className=\"mt-4 border-y border-border py-4 text-sm leading-relaxed text-muted-foreground\">\n                    {attention.emptyText ?? \"Nothing needs attention.\"}\n                  </p>\n                )}\n\n                {!loading && (attention.action || attention.moreLink) ? (\n                  <div className=\"mt-5 flex flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:gap-6\">\n                    {attention.action}\n                    {attention.moreLink ? (\n                      <a href={attention.moreLink.href} className={linkClass}>\n                        {attention.moreLink.label}\n                      </a>\n                    ) : null}\n                  </div>\n                ) : null}\n              </section>\n\n              <TrendChart\n                trend={trend}\n                loading={loading}\n                headingId={ids.trend}\n                descriptionId={ids.trendDescription}\n                Heading={Section}\n              />\n            </div>\n          </div>\n        ) : null}\n\n        {footnote ? (\n          <div className=\"mt-10 max-w-prose border-t border-border pt-4 text-sm leading-relaxed text-muted-foreground\">\n            {footnote}\n          </div>\n        ) : null}\n      </div>\n    </div>\n  );\n}\n\n/**\n * A horizontal bar chart that is also its own text alternative: a table with\n * one row per period, the value written out beside each bar.\n */\nfunction TrendChart({\n  trend,\n  loading,\n  headingId,\n  descriptionId,\n  Heading,\n}: {\n  trend: DashboardTrend;\n  loading: boolean;\n  headingId: string;\n  descriptionId: string;\n  Heading: \"h2\" | \"h3\";\n}) {\n  const {\n    title,\n    description,\n    periodHeading = \"Week\",\n    valueHeading,\n    points,\n    unit = \"%\",\n    target,\n  } = trend;\n  const max = trend.max ?? 100;\n  const percent = (value: number) =>\n    `${Math.min(100, Math.max(0, (value / max) * 100))}%`;\n\n  return (\n    <section aria-labelledby={headingId}>\n      <Heading\n        id={headingId}\n        className=\"text-lg leading-snug font-semibold tracking-tight text-balance\"\n      >\n        {title}\n      </Heading>\n      {loading ? (\n        <span aria-hidden=\"true\" className={cn(skeleton, \"mt-2 h-5 w-3/4\")} />\n      ) : description ? (\n        <p\n          id={descriptionId}\n          className=\"mt-1 max-w-prose text-sm leading-relaxed text-muted-foreground\"\n        >\n          {description}\n        </p>\n      ) : null}\n\n      <table\n        aria-labelledby={headingId}\n        aria-describedby={!loading && description ? descriptionId : undefined}\n        className=\"mt-4 w-full border-collapse text-sm\"\n      >\n        <thead>\n          <tr className=\"border-b border-border text-left text-muted-foreground\">\n            <th scope=\"col\" className=\"py-2 pr-3 font-medium\">\n              {periodHeading}\n            </th>\n            <th scope=\"col\" className=\"py-2 text-right font-medium\">\n              {valueHeading}\n            </th>\n          </tr>\n        </thead>\n        <tbody>\n          {points.map((point) => (\n            <tr key={point.label}>\n              <th\n                scope=\"row\"\n                className={cn(\n                  \"w-px py-1.5 pr-3 text-left whitespace-nowrap\",\n                  point.current ? \"font-semibold\" : \"font-normal\",\n                )}\n              >\n                {point.label}\n              </th>\n              <td className=\"py-1.5\">\n                <span className=\"flex items-center gap-3\">\n                  <span\n                    aria-hidden=\"true\"\n                    className=\"relative block h-4 min-w-0 flex-1 rounded-sm bg-muted\"\n                  >\n                    {loading ? null : (\n                      <span\n                        className={cn(\n                          \"absolute inset-y-0 left-0 block rounded-sm\",\n                          point.current\n                            ? \"bg-foreground\"\n                            : \"bg-muted-foreground\",\n                        )}\n                        style={{ width: percent(point.value) }}\n                      />\n                    )}\n                    {target ? (\n                      <span\n                        className=\"absolute -inset-y-1.5 block border-l-2 border-dashed border-foreground\"\n                        style={{ left: percent(target.value) }}\n                      />\n                    ) : null}\n                  </span>\n                  <span\n                    className={cn(\n                      \"w-12 shrink-0 text-right tabular-nums\",\n                      point.current && \"font-semibold\",\n                    )}\n                  >\n                    {loading ? (\n                      <span\n                        aria-hidden=\"true\"\n                        className={cn(skeleton, \"ml-auto h-5 w-9\")}\n                      />\n                    ) : (\n                      (point.display ?? `${point.value}${unit}`)\n                    )}\n                  </span>\n                </span>\n              </td>\n            </tr>\n          ))}\n        </tbody>\n      </table>\n\n      {target ? (\n        <p className=\"mt-3 flex items-center gap-2 text-sm text-muted-foreground\">\n          <svg\n            aria-hidden=\"true\"\n            viewBox=\"0 0 4 20\"\n            className=\"h-5 w-1 shrink-0 text-foreground\"\n          >\n            <line\n              x1=\"2\"\n              y1=\"0\"\n              x2=\"2\"\n              y2=\"20\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeDasharray=\"4 3\"\n            />\n          </svg>\n          <span>{target.label}</span>\n        </p>\n      ) : null}\n    </section>\n  );\n}\n"
    }
  },
  "boundaries": [
    "Community items are merged and pass automated checks (schema, build, accessibility, overflow and console errors in three themes at five widths); only items with status reviewed have been checked by a maintainer.",
    "Each rule id links to its page at /rules/<id>; every rule is a draft until a maintainer signs it.",
    "Tessli does not claim that using an item improves AI-generated UI; that claim waits for a published evaluation."
  ]
}
