{
  "schemaVersion": 1,
  "name": "updates-feed",
  "useFor": "Use for changelogs or activity updates. Provide dates, change groups and optional media. Place in a Prose column or another readable content width.",
  "constraints": [
    "Tags in caps",
    "Colors other than blue ink and red for tags",
    "Cards around each day",
    "Long paragraphs instead of bullets"
  ],
  "documentation": "`UpdatesFeed` props: `days` (`{ date, dateTime?, entries }[]`); each entry is `{ tag, tone?, title?, body?, items?,\nmedia? }`. `tag` is any word; Added, New, Improved, Changed, Fixed and Removed pick their color, `tone` (`blue` | `ink`\n| `danger`) overrides it. `media` is framed with a hairline; an iframe keeps 16:9.\n\n`UpdateTag` is exported for a tag on its own.",
  "source": "Extension in the opencommerce site's voice (mono tags, hairlines; opencommerce-agentcard-web src/components/pricing.tsx chip); the structure from agentcard.sh/updates",
  "exports": [
    "UpdatesFeed",
    "UpdateTag",
    "UpdateDay",
    "UpdateEntry",
    "UpdatesFeedProps"
  ],
  "client": false,
  "docsUrl": "https://opencommerce-ds.vercel.app/components/updates-feed",
  "markdownUrl": "https://opencommerce-ds.vercel.app/r/updates-feed.md",
  "examples": [
    {
      "path": "examples/updates-feed.tsx",
      "content": "import { UpdatesFeed } from \"@/components/site/updates-feed\";\n\nconst DAYS = [\n  {\n    date: \"Wed Oct 14\",\n    dateTime: \"2026-10-14\",\n    entries: [\n      {\n        tag: \"Added\",\n        items: [\n          \"A company can now sell through its own agent: a new purchase package gives the agent one buy tool for each shopper, and every approval goes straight to the shopper.\",\n          \"A company can name its shopper agent, and the name shows on every message the agent sends.\",\n          \"Purchase API answers now carry the merchant's order number once the order is placed.\",\n        ],\n      },\n      {\n        tag: \"Improved\",\n        items: [\"A shopper with no login merges into their account the first time they sign in.\", \"A company now opens up to 10,000 cards at once.\"],\n      },\n      {\n        tag: \"Fixed\",\n        items: [\"An iPhone with its passkey in a second keychain approves on the first try.\", \"A card payment no longer fails when the merchant retries it.\"],\n      },\n    ],\n  },\n  {\n    date: \"Tue Oct 13\",\n    dateTime: \"2026-10-13\",\n    entries: [{ tag: \"Fixed\", items: [\"The Purchase API now answers out-of-stock items with the closest match.\", \"On a vague shopping request the agent asks one question before it searches.\"] }],\n  },\n  {\n    date: \"Fri Feb 27\",\n    dateTime: \"2026-02-27\",\n    entries: [\n      {\n        tag: \"Added\",\n        title: \"Set up from your editor\",\n        body: \"One command adds the card tools to your coding agent and walks you through the first purchase.\",\n        media: (\n          <div role=\"img\" aria-label=\"A screen recording of the setup\" className=\"flex aspect-video items-center justify-center bg-brand\">\n            <span className=\"flex size-14 items-center justify-center rounded-pill bg-white text-fg-brand shadow-key\">▶</span>\n          </div>\n        ),\n      },\n    ],\n  },\n];\n\nexport function Site() {\n  return (\n    <div className=\"mx-auto max-w-(--ds-layout-text) px-5 py-16 sm:px-0\">\n      <UpdatesFeed days={DAYS} />\n    </div>\n  );\n}\n",
      "exports": [
        "Site"
      ],
      "requires": [
        "updates-feed"
      ],
      "packages": [],
      "previews": [
        {
          "name": "Site",
          "url": "https://opencommerce-ds.vercel.app/preview/updates-feed/Site"
        }
      ]
    }
  ],
  "type": "site",
  "title": "Updates feed",
  "description": "Dated release notes grouped by change type.",
  "surface": "site",
  "kinds": [
    "table",
    "chip",
    "body-text",
    "caption",
    "media-frame"
  ],
  "requires": [
    "theme",
    "utils"
  ],
  "packages": [],
  "files": [
    {
      "path": "components/site/updates-feed.tsx",
      "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n// A changelog in the home page's voice: one row per day on hairlines, the date in the small platform mono at the left\n// and the day's changes at the right, grouped under small mono tags in sentence case (Added on the pale blue, Improved\n// on mist, Fixed in the danger red on mist). Each group is a short bulleted list in 14px muted text; a group may also\n// carry a title, a body and media (a video or a screenshot in a window-like frame). On phones the date sits above.\n\ntype Tone = \"blue\" | \"ink\" | \"danger\";\n\ntype UpdateEntry = {\n  /** The tag: Added, Improved, Fixed (any word works; those three pick their color). */\n  tag: string;\n  /** Overrides the tag's color. */\n  tone?: Tone;\n  /** An optional headline for the group, in the display face. */\n  title?: React.ReactNode;\n  /** An optional paragraph under the title. */\n  body?: React.ReactNode;\n  /** The changes, one bullet each. */\n  items?: React.ReactNode[];\n  /** A video (iframe, <video>) or image, framed with a hairline. Video frames keep 16:9. */\n  media?: React.ReactNode;\n};\n\ntype UpdateDay = {\n  /** The date as shown (Wed Oct 14). */\n  date: React.ReactNode;\n  /** A machine date for the <time> element (2026-10-14). */\n  dateTime?: string;\n  entries: UpdateEntry[];\n};\n\nconst TAG_TONE: Record<string, Tone> = { added: \"blue\", new: \"blue\", improved: \"ink\", changed: \"ink\", fixed: \"danger\", removed: \"danger\" };\n\nconst TONES: Record<Tone, string> = {\n  blue: \"bg-tint text-fg-brand\",\n  ink: \"bg-subtle text-fg\",\n  danger: \"bg-subtle text-danger\",\n};\n\nfunction UpdateTag({ tag, tone, className }: { tag: string; tone?: Tone; className?: string }) {\n  const t = tone ?? TAG_TONE[tag.toLowerCase()] ?? \"ink\";\n  return <span className={cn(\"w-fit rounded-chip px-2 py-1 font-mono text-caption leading-none\", TONES[t], className)}>{tag}</span>;\n}\n\nfunction Entry({ entry }: { entry: UpdateEntry }) {\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <UpdateTag tag={entry.tag} tone={entry.tone} />\n      {entry.title ? <h3 className=\"mt-1 font-display text-h5 leading-snug font-medium tracking-tight\">{entry.title}</h3> : null}\n      {entry.body ? <p className=\"text-body-sm leading-relaxed text-fg-secondary\">{entry.body}</p> : null}\n      {entry.items?.length ? (\n        <ul className=\"m-0 flex list-disc flex-col gap-2.5 pl-4.5\">\n          {entry.items.map((item, i) => (\n            <li key={i} className=\"text-body-sm leading-relaxed text-fg-muted\">\n              {item}\n            </li>\n          ))}\n        </ul>\n      ) : null}\n      {entry.media ? (\n        <div className=\"mt-2 overflow-hidden rounded-panel border border-line-strong shadow-card [&_iframe]:block [&_iframe]:aspect-video [&_iframe]:h-auto [&_iframe]:w-full [&_img]:block [&_img]:h-auto [&_img]:w-full [&_video]:block [&_video]:w-full\">\n          {entry.media}\n        </div>\n      ) : null}\n    </div>\n  );\n}\n\ntype UpdatesFeedProps = Omit<React.ComponentProps<\"div\">, \"children\"> & { days: UpdateDay[] };\n\nfunction UpdatesFeed({ days, className, ...props }: UpdatesFeedProps) {\n  return (\n    <div data-slot=\"updates-feed\" className={cn(\"flex flex-col\", className)} {...props}>\n      {days.map((day, i) => (\n        <section key={i} className=\"grid grid-cols-1 gap-2.5 border-b border-line py-6.5 sm:grid-cols-[9.375rem_minmax(0,1fr)] sm:gap-5\">\n          <h2 className=\"m-0 pt-0.5 font-mono text-caption leading-body font-normal text-fg-muted\">\n            {day.dateTime ? <time dateTime={day.dateTime}>{day.date}</time> : day.date}\n          </h2>\n          <div className=\"flex min-w-0 flex-col gap-4.5\">\n            {day.entries.map((entry, j) => (\n              <Entry key={j} entry={entry} />\n            ))}\n          </div>\n        </section>\n      ))}\n    </div>\n  );\n}\n\nexport { UpdatesFeed, UpdateTag };\nexport type { UpdateDay, UpdateEntry, UpdatesFeedProps };\n"
    }
  ]
}
