{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "prose",
  "title": "Prose",
  "description": "A branded Markdown renderer for author-written text. Thin, safe wrapper over react-markdown + remark-gfm — no raw HTML (no rehype-raw, skipHtml set) and dangerous URL protocols dropped by default. GFM tables, strikethrough and task lists render. Headings in the brand display face; links are ink text with an orange underline mark, never orange words; code/pre on the sunken surface. headingScale picks the heading ramp: \"page\" (default) for a reading page, \"compact\" for a bounded surface like a card or popover, where a page-sized h1 would swamp its container.",
  "dependencies": [
    "react-markdown@10.1.0",
    "remark-gfm@4.0.1"
  ],
  "registryDependencies": [
    "@waimakers/utils",
    "@waimakers/theme"
  ],
  "files": [
    {
      "path": "src/components/ui/prose.tsx",
      "content": "import type { ComponentProps } from \"react\";\nimport Markdown, { type Components, type ExtraProps } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\n\nimport { cn, focusRing } from \"../../lib/utils\";\n\ntype ProseHeadingScale = \"page\" | \"compact\";\n\ninterface ProseProps {\n  children: string;\n  className?: string;\n  headingScale?: ProseHeadingScale;\n  untrusted?: boolean;\n  headingOffset?: 1 | 2;\n  literalEvidence?: boolean;\n}\n\nconst linkClass =\n  \"font-medium text-foreground underline decoration-primary decoration-2 underline-offset-[3px] transition-[text-decoration-thickness] hover:decoration-[3px] focus-visible:rounded-xs\";\n\nconst headingClasses = {\n  page: {\n    h1: \"mt-8 mb-3 font-display text-2xl font-bold tracking-tight text-foreground first:mt-0\",\n    h2: \"mt-7 mb-3 font-display text-xl font-bold tracking-tight text-foreground first:mt-0\",\n    h3: \"mt-6 mb-2 font-display text-lg font-semibold text-foreground first:mt-0\",\n    h4: \"mt-5 mb-2 font-display text-base font-semibold text-foreground first:mt-0\",\n  },\n  compact: {\n    h1: \"mt-5 mb-2 font-display text-lg font-bold tracking-tight text-foreground first:mt-0\",\n    h2: \"mt-4 mb-2 font-display text-base font-bold tracking-tight text-foreground first:mt-0\",\n    h3: \"mt-4 mb-1 font-display text-sm font-semibold text-foreground first:mt-0\",\n    h4: \"mt-3 mb-1 font-display text-sm font-medium text-foreground first:mt-0\",\n  },\n} satisfies Record<ProseHeadingScale, Record<\"h1\" | \"h2\" | \"h3\" | \"h4\", string>>;\n\nfunction ProseLink({ node: _node, className, href, ...props }: ComponentProps<\"a\"> & ExtraProps) {\n  return (\n    <a\n      href={href}\n      target={href?.startsWith(\"http\") ? \"_blank\" : undefined}\n      rel=\"noreferrer noopener\"\n      className={cn(linkClass, focusRing, className)}\n      {...props}\n    />\n  );\n}\n\nconst bodyComponents: Components = {\n  p: ({ node: _node, className, ...props }) => (\n    <p className={cn(\"my-3 text-sm leading-relaxed text-foreground\", className)} {...props} />\n  ),\n  a: ProseLink,\n  strong: ({ node: _node, className, ...props }) => (\n    <strong className={cn(\"font-semibold text-foreground\", className)} {...props} />\n  ),\n  em: ({ node: _node, className, ...props }) => (\n    <em className={cn(\"italic\", className)} {...props} />\n  ),\n  ul: ({ node: _node, className, ...props }) => (\n    <ul\n      className={cn(\n        \"my-3 pl-5 list-disc space-y-1 text-sm text-foreground marker:text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  ol: ({ node: _node, className, ...props }) => (\n    <ol\n      className={cn(\n        \"my-3 pl-5 list-decimal space-y-1 text-sm text-foreground marker:text-muted-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  li: ({ node: _node, className, ...props }) => (\n    <li className={cn(\"leading-relaxed\", className)} {...props} />\n  ),\n  blockquote: ({ node: _node, className, ...props }) => (\n    <blockquote\n      className={cn(\n        \"my-4 border-l-2 border-border pl-4 text-sm text-muted-foreground italic\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  code: ({ node: _node, className, ...props }) => (\n    <code\n      className={cn(\n        \"rounded-sm bg-muted px-1.5 py-0.5 font-mono text-[0.85em] text-foreground\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  pre: ({ node: _node, className, ...props }) => (\n    <pre\n      className={cn(\n        \"my-4 overflow-x-auto rounded-md border border-border bg-muted p-3 font-mono text-xs leading-relaxed text-foreground\",\n        \"[&>code]:bg-transparent [&>code]:p-0 [&>code]:text-inherit\",\n        className,\n      )}\n      {...props}\n    />\n  ),\n  hr: ({ node: _node, className, ...props }) => (\n    <hr className={cn(\"my-6 border-t border-border\", className)} {...props} />\n  ),\n  img: ({ node: _node, className, src, alt, ...props }) =>\n    src ? (\n      <img\n        src={src}\n        alt={alt ?? \"\"}\n        className={cn(\"my-4 max-w-full rounded-md\", className)}\n        {...props}\n      />\n    ) : null,\n  table: ({ node: _node, className, ...props }) => (\n    <div className=\"my-4 w-full overflow-x-auto\">\n      <table className={cn(\"w-full border-collapse text-left text-sm\", className)} {...props} />\n    </div>\n  ),\n  th: ({ node: _node, className, ...props }) => (\n    <th\n      className={cn(\"border-b border-border px-3 py-2 font-semibold text-foreground\", className)}\n      {...props}\n    />\n  ),\n  td: ({ node: _node, className, ...props }) => (\n    <td className={cn(\"border-b border-border px-3 py-2 text-foreground\", className)} {...props} />\n  ),\n  del: ({ node: _node, className, ...props }) => (\n    <del className={cn(\"text-muted-foreground line-through\", className)} {...props} />\n  ),\n  input: ({ node: _node, className, ...props }) =>\n    props.type === \"checkbox\" ? (\n      <input\n        {...props}\n        disabled\n        className={cn(\"mr-2 size-3.5 translate-y-px accent-primary\", className)}\n      />\n    ) : (\n      <input className={className} {...props} />\n    ),\n};\n\nconst refusedImage: Components[\"img\"] = ({ alt, src }) => (\n  <span\n    data-slot=\"prose-refused-image\"\n    className=\"rounded-sm bg-muted px-1.5 py-0.5 font-mono text-[0.85em] break-all text-muted-foreground\"\n  >\n    {`![${alt ?? \"\"}](${src ?? \"\"})`}\n  </span>\n);\n\nfunction componentsFor(\n  scale: ProseHeadingScale,\n  untrusted: boolean,\n  offset: 1 | 2,\n  literalEvidence = false,\n): Components {\n  const H1 = offset === 2 ? \"h3\" : \"h2\";\n  const H2 = offset === 2 ? \"h4\" : \"h3\";\n  const H3 = offset === 2 ? \"h5\" : \"h4\";\n  const H4 = offset === 2 ? \"h6\" : \"h5\";\n  const heading = headingClasses[scale];\n  return {\n    h1: ({ node: _node, className, ...props }) => (\n      <H1 className={cn(heading.h1, className)} {...props} />\n    ),\n    h2: ({ node: _node, className, ...props }) => (\n      <H2 className={cn(heading.h2, className)} {...props} />\n    ),\n    h3: ({ node: _node, className, ...props }) => (\n      <H3 className={cn(heading.h3, className)} {...props} />\n    ),\n    h4: ({ node: _node, className, ...props }) => (\n      <H4 className={cn(heading.h4, className)} {...props} />\n    ),\n    h5: ({ node: _node, ...props }) => <h6 {...props} />,\n    ...bodyComponents,\n    ...(untrusted\n      ? {\n          img: refusedImage,\n          ...(literalEvidence\n            ? {\n                a: ({ href, children, ...props }) =>\n                  safeProseHref(href) ? (\n                    <ProseLink {...props} href={href}>\n                      {children}\n                    </ProseLink>\n                  ) : (\n                    <span>\n                      <span>{children}</span>\n                      {href && (\n                        <>\n                          {\" \"}\n                          (<code>{href}</code>)\n                        </>\n                      )}\n                    </span>\n                  ),\n              }\n            : {}),\n        }\n      : {}),\n  };\n}\n\nfunction safeProseHref(raw: string | undefined) {\n  const value = raw?.trim() ?? \"\";\n  if (value.startsWith(\"/\") && !value.startsWith(\"//\")) {\n    return true;\n  }\n  const normalized = Array.from(value)\n    .filter((character) => {\n      const point = character.codePointAt(0) ?? 0;\n      return point > 32 && point !== 127;\n    })\n    .join(\"\");\n  return /^(?:https?:|mailto:)/iu.test(normalized);\n}\nconst keepUrlEvidence = (value: string) => value;\nconst componentsByScale = [1, 2].map((offset) => ({\n  trusted: {\n    page: componentsFor(\"page\", false, offset as 1 | 2),\n    compact: componentsFor(\"compact\", false, offset as 1 | 2),\n  },\n  untrusted: {\n    page: componentsFor(\"page\", true, offset as 1 | 2),\n    compact: componentsFor(\"compact\", true, offset as 1 | 2),\n  },\n  evidence: {\n    page: componentsFor(\"page\", true, offset as 1 | 2, true),\n    compact: componentsFor(\"compact\", true, offset as 1 | 2, true),\n  },\n}));\n\nfunction Prose({\n  children,\n  className,\n  headingScale = \"page\",\n  untrusted = false,\n  headingOffset = 1,\n  literalEvidence = false,\n}: ProseProps) {\n  const evidence = untrusted && literalEvidence;\n  const policy = evidence ? \"evidence\" : untrusted ? \"untrusted\" : \"trusted\";\n  return (\n    <div\n      data-slot=\"prose\"\n      className={cn(\n        \"max-w-[68ch] text-foreground [&>:first-child]:mt-0 [&>:last-child]:mb-0\",\n        className,\n      )}\n    >\n      <Markdown\n        skipHtml={!evidence}\n        urlTransform={evidence ? keepUrlEvidence : undefined}\n        remarkPlugins={[remarkGfm]}\n        components={componentsByScale[headingOffset - 1]?.[policy][headingScale]}\n      >\n        {children}\n      </Markdown>\n    </div>\n  );\n}\n\nexport { Prose };\nexport type { ProseHeadingScale, ProseProps };\n",
      "type": "registry:ui",
      "target": "components/ui/prose.tsx"
    }
  ],
  "type": "registry:ui"
}