{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "peer-discussion-forum",
  "title": "Peer Discussion Forum",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/peer-discussion-forum/peer-discussion-forum.component.ts",
      "content": "// StackOverflow / EdStem style student community Q&A thread: breadcrumb header with share\n// actions, solved badges and tags, a question card with voting + bookmark + subscribe, an\n// accepted instructor answer with solution snippets, a peer answer, a markdown composer that\n// appends answers, and a sidebar with discussion info, related threads and etiquette.\nimport { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core'\nimport {\n  ArrowUpRight,\n  Award,\n  Bell,\n  BellOff,\n  Bold,\n  BookOpen,\n  Bookmark,\n  Check,\n  CheckCircle2,\n  ChevronDown,\n  ChevronUp,\n  Code,\n  Copy,\n  Eye,\n  Flag,\n  Italic,\n  Link2,\n  List,\n  LucideAngularModule,\n  MessageSquare,\n  Plus,\n  Quote,\n  Share2,\n  Tag,\n  ThumbsUp,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiTextareaComponent } from '@/ui/textarea/textarea.component'\n\nexport type ForumVote = 'up' | 'down' | null\nexport type ComposerFormat = 'bold' | 'italic' | 'code' | 'link' | 'list' | 'quote'\n\nexport interface CommunityAnswer {\n  id: string\n  authorName: string\n  authorRole: string\n  avatarText: string\n  postedTime: string\n  score: number\n  content: string\n  userVote?: ForumVote\n}\n\ninterface RelatedDiscussion {\n  title: string\n  status: 'Solved' | 'Open'\n  upvotes: string\n  answers: string\n}\n\nconst relatedDiscussions: RelatedDiscussion[] = [\n  {\n    title: 'How to type polymorphic asChild props with Reka UI in Vue 3.5?',\n    status: 'Solved',\n    upvotes: '38 upvotes',\n    answers: '4 answers',\n  },\n  {\n    title: 'Configuring OKLCH Tailwind v4 themes with Nuxt 4',\n    status: 'Solved',\n    upvotes: '24 upvotes',\n    answers: '2 answers',\n  },\n  {\n    title: 'Best practices for CVA compoundVariants in TypeScript',\n    status: 'Open',\n    upvotes: '19 upvotes',\n    answers: '1 answer',\n  },\n  {\n    title: 'Hydration mismatches with client-side theme switchers in Astro SSG',\n    status: 'Solved',\n    upvotes: '52 upvotes',\n    answers: '6 answers',\n  },\n]\n\nconst etiquetteItems = [\n  'Isolate reproducible snippets using [name].variants.ts patterns.',\n  'Search existing questions before posting duplicate topics.',\n  'Mark the accepted solution once your issue is verified.',\n]\n\nfunction applyVote(\n  score: number,\n  current: ForumVote,\n  type: Exclude<ForumVote, null>,\n): { score: number; vote: ForumVote } {\n  if (current === type) return { score: score + (type === 'up' ? -1 : 1), vote: null }\n  let delta = type === 'up' ? 1 : -1\n  if (current === 'up') delta -= 1\n  if (current === 'down') delta += 1\n  return { score: score + delta, vote: type }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-peer-discussion-forum, [ui-peer-discussion-forum]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiAvatarComponent,\n    UiAvatarFallbackComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiSeparatorComponent,\n    UiTextareaComponent,\n  ],\n  template: `\n    <div data-slot=\"peer-discussion-forum\" [class]=\"rootClass\">\n      <header class=\"bg-card rounded-xl border p-5 shadow-xs sm:p-6\">\n        <div class=\"flex flex-col gap-4\">\n          <div class=\"flex flex-wrap items-center justify-between gap-3\">\n            <div class=\"text-muted-foreground flex flex-wrap items-center gap-1.5 text-xs\">\n              <span>CS-314 Advanced Frontend Architecture</span>\n              <span class=\"text-border\">/</span>\n              <span>Discussions</span>\n              <span class=\"text-border\">/</span>\n              <span class=\"text-foreground font-mono font-medium\">#DISC-2048</span>\n            </div>\n            <div class=\"flex items-center gap-2\">\n              <button ui-button variant=\"outline\" size=\"sm\" class=\"gap-1.5 text-xs font-medium\" (click)=\"shareThread()\">\n                <lucide-icon [img]=\"Share2\" class=\"size-3.5\" />\n                <span>{{ copiedLink() ? 'Link Copied!' : 'Share Thread' }}</span>\n              </button>\n              <button ui-button size=\"sm\" class=\"gap-1.5 text-xs font-medium\">\n                <lucide-icon [img]=\"Plus\" class=\"size-3.5\" />\n                Ask Question\n              </button>\n            </div>\n          </div>\n\n          <div class=\"space-y-2.5\">\n            <div class=\"flex flex-wrap items-center gap-2\">\n              <span\n                ui-badge\n                variant=\"outline\"\n                class=\"border-success/30 bg-success/10 text-success gap-1 text-xs font-semibold\"\n              >\n                <lucide-icon [img]=\"CheckCircle2\" class=\"text-success size-3.5\" />\n                Solved · 1 Accepted Answer\n              </span>\n              <span ui-badge variant=\"secondary\" class=\"text-xs font-medium\">Vue 3.5 & Vite</span>\n            </div>\n            <h1 class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl lg:text-3xl\">\n              How to properly avoid circular dependencies in Vue 3.5 SFC variants with CVA?\n            </h1>\n            <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n              @for (tag of tags; track tag) {\n                <span\n                  class=\"bg-muted/70 text-muted-foreground hover:text-foreground inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium transition-colors\"\n                >\n                  <lucide-icon [img]=\"Tag\" class=\"text-muted-foreground size-3\" />\n                  {{ tag }}\n                </span>\n              }\n            </div>\n          </div>\n\n          <ui-separator />\n\n          <div class=\"text-muted-foreground flex flex-wrap items-center gap-x-6 gap-y-2 text-xs\">\n            <div><span>Asked </span><strong class=\"text-foreground font-medium\">2 days ago</strong></div>\n            <div><span>Modified </span><strong class=\"text-foreground font-medium\">18 hours ago</strong></div>\n            <div><span>Viewed </span><strong class=\"text-foreground font-medium tabular-nums\">1,420 times</strong></div>\n            <div>\n              <span>Module </span><strong class=\"text-foreground font-medium\">Component Registry Architecture</strong>\n            </div>\n          </div>\n        </div>\n      </header>\n\n      <div class=\"grid grid-cols-1 gap-6 lg:grid-cols-12\">\n        <main class=\"space-y-6 lg:col-span-8\">\n          <article class=\"bg-card rounded-xl border p-5 shadow-xs sm:p-6\">\n            <div class=\"flex items-start gap-4 sm:gap-6\">\n              <div class=\"flex flex-col items-center\">\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"icon\"\n                  [class]=\"questionUpClass\"\n                  aria-label=\"Upvote question\"\n                  (click)=\"voteQuestion('up')\"\n                >\n                  <lucide-icon [img]=\"ChevronUp\" class=\"size-5\" />\n                </button>\n                <span class=\"text-foreground my-1.5 font-mono text-base font-bold tabular-nums\">{{\n                  questionScore()\n                }}</span>\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"icon\"\n                  [class]=\"questionDownClass\"\n                  aria-label=\"Downvote question\"\n                  (click)=\"voteQuestion('down')\"\n                >\n                  <lucide-icon [img]=\"ChevronDown\" class=\"size-5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  [class]=\"questionBookmarkClass\"\n                  aria-label=\"Bookmark question\"\n                  (click)=\"isQuestionBookmarked.set(!isQuestionBookmarked())\"\n                >\n                  <lucide-icon [img]=\"Bookmark\" [class]=\"questionBookmarkIconClass\" />\n                </button>\n              </div>\n\n              <div class=\"min-w-0 flex-1 space-y-4\">\n                <div class=\"flex flex-wrap items-center justify-between gap-2 border-b pb-3.5\">\n                  <div class=\"flex items-center gap-3\">\n                    <ui-avatar class=\"size-10 border\">\n                      <ui-avatar-fallback class=\"bg-primary/10 text-primary text-xs font-semibold\"\n                        >DC</ui-avatar-fallback\n                      >\n                    </ui-avatar>\n                    <div>\n                      <div class=\"flex items-center gap-2\">\n                        <span class=\"text-foreground text-sm font-semibold\">David Chen</span>\n                        <span ui-badge variant=\"secondary\" class=\"text-xs font-normal\">Student</span>\n                      </div>\n                      <p class=\"text-muted-foreground text-xs\">Posted 2 days ago · Oct 22, 2026 at 14:32</p>\n                    </div>\n                  </div>\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    [class]=\"subscribeClass\"\n                    (click)=\"isSubscribed.set(!isSubscribed())\"\n                  >\n                    @if (isSubscribed()) {\n                      <lucide-icon [img]=\"Bell\" class=\"size-3.5\" />\n                    } @else {\n                      <lucide-icon [img]=\"BellOff\" class=\"size-3.5\" />\n                    }\n                    <span>{{ isSubscribed() ? 'Subscribed' : 'Subscribe' }}</span>\n                  </button>\n                </div>\n\n                <div class=\"text-foreground/90 space-y-3.5 text-sm leading-relaxed\">\n                  <p>\n                    While implementing unbundled UI primitives for our course project using Vue 3.5, TypeScript, and\n                    <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      class-variance-authority\n                    </code>\n                    , we ran into a perplexing runtime error during dev SSR and fast refresh:\n                  </p>\n                  <div\n                    class=\"border-destructive/30 bg-destructive/10 text-destructive rounded-md border px-3.5 py-2.5 font-mono text-xs\"\n                  >\n                    TypeError: $setup.buttonVariants is not a function at Button.vue:24\n                  </div>\n                  <p>\n                    Our initial project structure placed both the component export and the CVA variant definition in the\n                    same barrel file:\n                  </p>\n                  <div class=\"overflow-hidden rounded-lg border bg-zinc-950 text-zinc-100\">\n                    <div\n                      class=\"flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3.5 py-2 text-xs text-zinc-400\"\n                    >\n                      <div class=\"flex items-center gap-2\">\n                        <lucide-icon [img]=\"Code\" class=\"text-warning size-3.5\" />\n                        <span class=\"font-mono text-xs\">components/ui/button/index.ts (Problematic Barrel)</span>\n                      </div>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"h-6 gap-1 px-2 text-xs text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100\"\n                        (click)=\"copyQuestionCode()\"\n                      >\n                        @if (copiedQuestionSnippet()) {\n                          <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                        } @else {\n                          <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                        }\n                        {{ copiedQuestionSnippet() ? 'Copied' : 'Copy code' }}\n                      </button>\n                    </div>\n                    <pre\n                      class=\"overflow-x-auto p-4 font-mono text-xs leading-relaxed text-zinc-300\"\n                    ><code>import {{ '{' }} cva {{ '}' }} from 'class-variance-authority'\n\nexport {{ '{' }} default as Button {{ '}' }} from './Button.vue'</code></pre>\n                  </div>\n                  <p>\n                    Because\n                    <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\">Button.vue</code>\n                    imports\n                    <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\">buttonVariants</code>\n                    from\n                    <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\">./index.ts</code>,\n                    Vite evaluates the module in a cycle where the variant function binding is uninitialized during the\n                    Vue SFC component setup.\n                  </p>\n                  <p>\n                    What is the canonical architecture pattern to cleanly break this circular import while maintaining\n                    convenient barrel exports for consumers?\n                  </p>\n                </div>\n\n                <div class=\"flex flex-wrap items-center justify-between gap-3 pt-2\">\n                  <div class=\"flex items-center gap-1\">\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground h-8 gap-1.5 text-xs\"\n                      (click)=\"shareThread()\"\n                    >\n                      <lucide-icon [img]=\"Share2\" class=\"size-3.5\" />\n                      <span>Share</span>\n                    </button>\n                    <button ui-button variant=\"ghost\" size=\"sm\" class=\"text-muted-foreground h-8 gap-1.5 text-xs\">\n                      <lucide-icon [img]=\"Flag\" class=\"size-3.5\" />\n                      <span>Report</span>\n                    </button>\n                  </div>\n                  <div class=\"text-muted-foreground flex items-center gap-1.5 text-xs\">\n                    <lucide-icon [img]=\"Eye\" class=\"size-3.5\" />\n                    <span class=\"tabular-nums\">1.4k views</span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </article>\n\n          <div class=\"flex items-center justify-between pt-2\">\n            <h2 class=\"text-foreground text-lg font-bold tracking-tight sm:text-xl\">\n              {{ totalAnswersCount() }} Answers\n            </h2>\n            <span class=\"text-muted-foreground text-xs\">\n              Sorted by: <strong class=\"text-foreground font-medium\">Highest score</strong>\n            </span>\n          </div>\n\n          <article\n            class=\"bg-card border-success/40 ring-success/20 relative overflow-hidden rounded-xl border shadow-xs ring-1\"\n          >\n            <div\n              class=\"border-success/20 bg-success/10 flex flex-wrap items-center justify-between gap-2 border-b px-5 py-2.5\"\n            >\n              <div class=\"text-success flex items-center gap-2 text-xs font-semibold\">\n                <lucide-icon [img]=\"CheckCircle2\" class=\"text-success size-4\" />\n                <span>Accepted by Author · Verified Solution</span>\n              </div>\n              <span ui-badge class=\"bg-success hover:bg-success text-xs font-medium text-white\"\n                >Instructor Solution</span\n              >\n            </div>\n            <div class=\"p-5 sm:p-6\">\n              <div class=\"flex items-start gap-4 sm:gap-6\">\n                <div class=\"flex flex-col items-center\">\n                  <button\n                    ui-button\n                    variant=\"outline\"\n                    size=\"icon\"\n                    [class]=\"answer1UpClass\"\n                    aria-label=\"Upvote answer\"\n                    (click)=\"voteAnswer1('up')\"\n                  >\n                    <lucide-icon [img]=\"ChevronUp\" class=\"size-5\" />\n                  </button>\n                  <span class=\"text-foreground my-1.5 font-mono text-base font-bold tabular-nums\">{{\n                    answer1Score()\n                  }}</span>\n                  <button\n                    ui-button\n                    variant=\"outline\"\n                    size=\"icon\"\n                    [class]=\"answer1DownClass\"\n                    aria-label=\"Downvote answer\"\n                    (click)=\"voteAnswer1('down')\"\n                  >\n                    <lucide-icon [img]=\"ChevronDown\" class=\"size-5\" />\n                  </button>\n                  <div\n                    class=\"bg-success/15 text-success mt-3 flex size-8 items-center justify-center rounded-full\"\n                    title=\"Accepted solution\"\n                  >\n                    <lucide-icon [img]=\"Check\" class=\"size-4 stroke-[2.5]\" />\n                  </div>\n                </div>\n\n                <div class=\"min-w-0 flex-1 space-y-4\">\n                  <div class=\"flex flex-wrap items-center justify-between gap-2 border-b pb-3.5\">\n                    <div class=\"flex items-center gap-3\">\n                      <ui-avatar class=\"border-success/30 size-10 border\">\n                        <ui-avatar-fallback class=\"bg-success/20 text-success text-xs font-semibold\"\n                          >MV</ui-avatar-fallback\n                        >\n                      </ui-avatar>\n                      <div>\n                        <div class=\"flex items-center gap-2\">\n                          <span class=\"text-foreground text-sm font-semibold\">Marcus Vance</span>\n                          <span\n                            ui-badge\n                            variant=\"outline\"\n                            class=\"border-success/30 bg-success/10 text-success text-xs font-medium\"\n                          >\n                            Course Instructor\n                          </span>\n                          <span ui-badge variant=\"secondary\" class=\"text-xs\">Staff</span>\n                        </div>\n                        <p class=\"text-muted-foreground text-xs\">Answered 1 day ago · Edited 18h ago</p>\n                      </div>\n                    </div>\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground h-7 gap-1 text-xs\"\n                      (click)=\"isAnswer1Bookmarked.set(!isAnswer1Bookmarked())\"\n                    >\n                      <lucide-icon [img]=\"Bookmark\" [class]=\"answer1BookmarkIconClass\" />\n                      <span>{{ isAnswer1Bookmarked() ? 'Saved' : 'Save' }}</span>\n                    </button>\n                  </div>\n\n                  <div class=\"text-foreground/90 space-y-3.5 text-sm leading-relaxed\">\n                    <p>\n                      Great question, David. This is one of the most frequent traps when building unbundled component\n                      registries with Vue 3.5 and Vite.\n                    </p>\n                    <h3 class=\"text-foreground text-sm font-semibold tracking-tight\">\n                      The Solution: Three-File Sidecar Architecture\n                    </h3>\n                    <p>\n                      To completely eliminate cyclic dependencies and ensure 100% reliable SSR and Vite HMR, extract all\n                      CVA definitions into a dedicated\n                      <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\"\n                        >&lt;name&gt;.variants.ts</code\n                      >\n                      file:\n                    </p>\n                    <div class=\"overflow-hidden rounded-lg border bg-zinc-950 text-zinc-100\">\n                      <div\n                        class=\"flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3.5 py-2 text-xs text-zinc-400\"\n                      >\n                        <div class=\"flex items-center gap-2\">\n                          <lucide-icon [img]=\"Code\" class=\"text-success size-3.5\" />\n                          <span class=\"font-mono text-xs\">components/ui/button/button.variants.ts</span>\n                        </div>\n                        <button\n                          ui-button\n                          variant=\"ghost\"\n                          size=\"sm\"\n                          class=\"h-6 gap-1 px-2 text-xs text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100\"\n                          (click)=\"copyAnswer1Code()\"\n                        >\n                          @if (copiedAnswer1Snippet()) {\n                            <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                          } @else {\n                            <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                          }\n                          {{ copiedAnswer1Snippet() ? 'Copied' : 'Copy code' }}\n                        </button>\n                      </div>\n                      <pre\n                        class=\"overflow-x-auto p-4 font-mono text-xs leading-relaxed text-zinc-300\"\n                      ><code>export const buttonVariants = cva('inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', {{ '{' }} variants: {{ '{' }} variant: {{ '{' }} default: 'bg-primary text-primary-foreground shadow-xs' {{ '}' }} {{ '}' }} {{ '}' }})</code></pre>\n                    </div>\n                    <ul class=\"list-inside list-disc space-y-1.5 pl-1 text-xs sm:text-sm\">\n                      <li>\n                        <strong>Acyclic Dependency Graph:</strong> Button.vue imports strictly from ./button.variants.\n                      </li>\n                      <li>\n                        <strong>Tree-Shakable:</strong> Consumers can import just the variant generator without mounting\n                        the Vue SFC component instance.\n                      </li>\n                      <li>\n                        <strong>Cross-Framework Ready:</strong> The variant file is pure TypeScript, making it 100%\n                        shareable across Vue and React registry packages.\n                      </li>\n                    </ul>\n                  </div>\n\n                  <div class=\"flex items-center justify-between border-t pt-3\">\n                    <div class=\"flex items-center gap-2\">\n                      <button\n                        ui-button\n                        variant=\"outline\"\n                        size=\"sm\"\n                        class=\"h-7 gap-1.5 text-xs\"\n                        (click)=\"voteAnswer1('up')\"\n                      >\n                        <lucide-icon [img]=\"ThumbsUp\" class=\"size-3\" />\n                        <span>Helpful ({{ answer1Score() }})</span>\n                      </button>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"text-muted-foreground h-7 gap-1.5 text-xs\"\n                        (click)=\"shareThread()\"\n                      >\n                        <lucide-icon [img]=\"Share2\" class=\"size-3\" />\n                        <span>Share</span>\n                      </button>\n                    </div>\n                    <span class=\"text-muted-foreground text-xs\">89 students found this helpful</span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </article>\n\n          <article class=\"bg-card rounded-xl border p-5 shadow-xs sm:p-6\">\n            <div class=\"flex items-start gap-4 sm:gap-6\">\n              <div class=\"flex flex-col items-center\">\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"icon\"\n                  [class]=\"answer2UpClass\"\n                  aria-label=\"Upvote answer\"\n                  (click)=\"voteAnswer2('up')\"\n                >\n                  <lucide-icon [img]=\"ChevronUp\" class=\"size-5\" />\n                </button>\n                <span class=\"text-foreground my-1.5 font-mono text-base font-bold tabular-nums\">{{\n                  answer2Score()\n                }}</span>\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"icon\"\n                  [class]=\"answer2DownClass\"\n                  aria-label=\"Downvote answer\"\n                  (click)=\"voteAnswer2('down')\"\n                >\n                  <lucide-icon [img]=\"ChevronDown\" class=\"size-5\" />\n                </button>\n              </div>\n\n              <div class=\"min-w-0 flex-1 space-y-4\">\n                <div class=\"flex flex-wrap items-center justify-between gap-2 border-b pb-3.5\">\n                  <div class=\"flex items-center gap-3\">\n                    <ui-avatar class=\"size-10 border\">\n                      <ui-avatar-fallback class=\"bg-info/20 text-info text-xs font-semibold\">SL</ui-avatar-fallback>\n                    </ui-avatar>\n                    <div>\n                      <div class=\"flex items-center gap-2\">\n                        <span class=\"text-foreground text-sm font-semibold\">Sophia Lin</span>\n                        <span ui-badge variant=\"secondary\" class=\"text-xs font-normal\">Teaching Assistant</span>\n                      </div>\n                      <p class=\"text-muted-foreground text-xs\">Answered 2 days ago · Oct 22, 2026 at 18:40</p>\n                    </div>\n                  </div>\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    class=\"text-muted-foreground h-7 gap-1 text-xs\"\n                    (click)=\"isAnswer2Bookmarked.set(!isAnswer2Bookmarked())\"\n                  >\n                    <lucide-icon [img]=\"Bookmark\" [class]=\"answer2BookmarkIconClass\" />\n                    <span>{{ isAnswer2Bookmarked() ? 'Saved' : 'Save' }}</span>\n                  </button>\n                </div>\n\n                <div class=\"text-foreground/90 space-y-3 text-sm leading-relaxed\">\n                  <p>\n                    Adding to Marcus's answer: another huge benefit of isolating\n                    <code class=\"bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs\"\n                      >&lt;name&gt;.variants.ts</code\n                    >\n                    is for monorepos or dual-framework setups.\n                  </p>\n                  <p>\n                    When you have both Vue and React registry components (like in UIPKGE), the variant definitions can\n                    be shared verbatim in a shared token package without bringing in any Vue SFC or JSX compiler\n                    dependencies. This keeps design tokens consistent across both stacks.\n                  </p>\n                </div>\n\n                <div class=\"flex items-center justify-between border-t pt-3\">\n                  <div class=\"flex items-center gap-2\">\n                    <button\n                      ui-button\n                      variant=\"outline\"\n                      size=\"sm\"\n                      class=\"h-7 gap-1.5 text-xs\"\n                      (click)=\"voteAnswer2('up')\"\n                    >\n                      <lucide-icon [img]=\"ThumbsUp\" class=\"size-3\" />\n                      <span>Helpful ({{ answer2Score() }})</span>\n                    </button>\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground h-7 gap-1.5 text-xs\"\n                      (click)=\"shareThread()\"\n                    >\n                      <lucide-icon [img]=\"Share2\" class=\"size-3\" />\n                      <span>Share</span>\n                    </button>\n                  </div>\n                  <span class=\"text-muted-foreground text-xs\">14 students found this helpful</span>\n                </div>\n              </div>\n            </div>\n          </article>\n\n          @for (ans of customAnswers(); track ans.id) {\n            <article class=\"bg-card rounded-xl border p-5 shadow-xs sm:p-6\">\n              <div class=\"flex items-start gap-4 sm:gap-6\">\n                <div class=\"flex flex-col items-center\">\n                  <button\n                    ui-button\n                    variant=\"outline\"\n                    size=\"icon\"\n                    [class]=\"customUpClass(ans)\"\n                    aria-label=\"Upvote answer\"\n                    (click)=\"voteCustomAnswer(ans.id, 'up')\"\n                  >\n                    <lucide-icon [img]=\"ChevronUp\" class=\"size-5\" />\n                  </button>\n                  <span class=\"text-foreground my-1.5 font-mono text-base font-bold tabular-nums\">{{ ans.score }}</span>\n                  <button\n                    ui-button\n                    variant=\"outline\"\n                    size=\"icon\"\n                    [class]=\"customDownClass(ans)\"\n                    aria-label=\"Downvote answer\"\n                    (click)=\"voteCustomAnswer(ans.id, 'down')\"\n                  >\n                    <lucide-icon [img]=\"ChevronDown\" class=\"size-5\" />\n                  </button>\n                </div>\n                <div class=\"min-w-0 flex-1 space-y-4\">\n                  <div class=\"flex items-center justify-between border-b pb-3.5\">\n                    <div class=\"flex items-center gap-3\">\n                      <ui-avatar class=\"size-10 border\">\n                        <ui-avatar-fallback class=\"bg-primary/10 text-primary text-xs font-semibold\">{{\n                          ans.avatarText\n                        }}</ui-avatar-fallback>\n                      </ui-avatar>\n                      <div>\n                        <div class=\"flex items-center gap-2\">\n                          <span class=\"text-foreground text-sm font-semibold\">{{ ans.authorName }}</span>\n                          <span ui-badge variant=\"secondary\" class=\"text-xs\">{{ ans.authorRole }}</span>\n                        </div>\n                        <p class=\"text-muted-foreground text-xs\">{{ ans.postedTime }}</p>\n                      </div>\n                    </div>\n                  </div>\n                  <div class=\"text-foreground/90 text-sm leading-relaxed whitespace-pre-wrap\">{{ ans.content }}</div>\n                </div>\n              </div>\n            </article>\n          }\n\n          <section class=\"bg-card overflow-hidden rounded-xl border shadow-xs\">\n            <div class=\"bg-muted/40 border-b px-5 py-3\">\n              <h3 class=\"text-foreground text-base font-bold tracking-tight\">Post Your Answer</h3>\n              <p class=\"text-muted-foreground text-xs\">\n                Provide thorough explanations, actionable code examples, and reference architectural best practices.\n              </p>\n            </div>\n            <div class=\"space-y-3.5 p-5\">\n              <div class=\"text-muted-foreground flex flex-wrap items-center gap-1 border-b pb-2.5\">\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  class=\"size-7\"\n                  aria-label=\"Format bold\"\n                  (click)=\"applyFormat('bold')\"\n                >\n                  <lucide-icon [img]=\"Bold\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  class=\"size-7\"\n                  aria-label=\"Format italic\"\n                  (click)=\"applyFormat('italic')\"\n                >\n                  <lucide-icon [img]=\"Italic\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  class=\"size-7\"\n                  aria-label=\"Insert code block\"\n                  (click)=\"applyFormat('code')\"\n                >\n                  <lucide-icon [img]=\"Code\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  class=\"size-7\"\n                  aria-label=\"Insert link\"\n                  (click)=\"applyFormat('link')\"\n                >\n                  <lucide-icon [img]=\"Link2\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  class=\"size-7\"\n                  aria-label=\"Insert list\"\n                  (click)=\"applyFormat('list')\"\n                >\n                  <lucide-icon [img]=\"List\" class=\"size-3.5\" />\n                </button>\n                <button\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon\"\n                  class=\"size-7\"\n                  aria-label=\"Insert blockquote\"\n                  (click)=\"applyFormat('quote')\"\n                >\n                  <lucide-icon [img]=\"Quote\" class=\"size-3.5\" />\n                </button>\n                <span class=\"text-muted-foreground ml-auto text-xs\">Markdown syntax supported</span>\n              </div>\n              <ui-textarea\n                [value]=\"replyDraft()\"\n                (valueChange)=\"replyDraft.set($event)\"\n                placeholder=\"Write your detailed answer with code blocks (e.g. \\`\\`\\`ts ... \\`\\`\\`)...\"\n                [rows]=\"6\"\n                class=\"resize-y font-sans text-sm\"\n              />\n              <div class=\"flex flex-wrap items-center justify-between gap-3 pt-1\">\n                <span class=\"text-muted-foreground text-xs\"\n                  >Draft saved automatically · Be constructive & cite official docs</span\n                >\n                <button\n                  ui-button\n                  class=\"gap-1.5 text-xs font-semibold\"\n                  [disabled]=\"!replyDraft().trim()\"\n                  (click)=\"handlePostAnswer()\"\n                >\n                  <lucide-icon [img]=\"MessageSquare\" class=\"size-3.5\" />\n                  Post Your Answer\n                </button>\n              </div>\n            </div>\n          </section>\n        </main>\n\n        <aside class=\"space-y-6 lg:col-span-4\">\n          <div ui-card>\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <h3 ui-card-title class=\"text-sm font-semibold\">Discussion Info</h3>\n                <span ui-badge variant=\"outline\" class=\"border-success/30 bg-success/10 text-success text-xs\"\n                  >Resolved</span\n                >\n              </div>\n            </div>\n            <div ui-card-content class=\"space-y-3 pt-0\">\n              <ui-separator />\n              <dl class=\"space-y-2.5 text-xs\">\n                <div class=\"flex items-center justify-between gap-2\">\n                  <dt class=\"text-muted-foreground\">Course</dt>\n                  <dd class=\"text-foreground font-medium\">CS-314 Frontend Arch</dd>\n                </div>\n                <div class=\"flex items-center justify-between gap-2\">\n                  <dt class=\"text-muted-foreground\">Category</dt>\n                  <dd class=\"text-foreground font-medium\">Vue 3.5 & Vite</dd>\n                </div>\n                <div class=\"flex items-center justify-between gap-2\">\n                  <dt class=\"text-muted-foreground\">Total Upvotes</dt>\n                  <dd class=\"text-foreground font-medium tabular-nums\">+145 upvotes</dd>\n                </div>\n                <div class=\"flex items-center justify-between gap-2\">\n                  <dt class=\"text-muted-foreground\">Total Views</dt>\n                  <dd class=\"text-foreground font-medium tabular-nums\">1,420</dd>\n                </div>\n                <div class=\"flex items-center justify-between gap-2\">\n                  <dt class=\"text-muted-foreground\">Participants</dt>\n                  <dd class=\"text-foreground font-medium\">4 contributors</dd>\n                </div>\n                <div class=\"flex items-center justify-between gap-2\">\n                  <dt class=\"text-muted-foreground\">Accepted By</dt>\n                  <dd class=\"text-foreground font-medium\">David Chen (Author)</dd>\n                </div>\n              </dl>\n            </div>\n          </div>\n\n          <div ui-card>\n            <div ui-card-header class=\"pb-3\">\n              <h3 ui-card-title class=\"text-sm font-semibold\">Related Discussions</h3>\n              <p ui-card-description class=\"text-xs\">Similar questions from this course cohort</p>\n            </div>\n            <div ui-card-content class=\"space-y-3 pt-0\">\n              <ui-separator />\n              <ul class=\"space-y-3 text-xs\">\n                @for (related of relatedDiscussions; track related.title; let last = $last) {\n                  <li class=\"space-y-1\">\n                    <a\n                      href=\"#\"\n                      class=\"text-foreground hover:text-primary flex items-start justify-between gap-2 font-medium transition-colors\"\n                    >\n                      <span>{{ related.title }}</span>\n                      <lucide-icon [img]=\"ArrowUpRight\" class=\"text-muted-foreground size-3.5 shrink-0\" />\n                    </a>\n                    <div class=\"text-muted-foreground flex items-center gap-2\">\n                      @if (related.status === 'Solved') {\n                        <span class=\"bg-success/10 text-success rounded px-1.5 py-0.5 text-xs font-medium\">Solved</span>\n                      } @else {\n                        <span class=\"bg-muted text-foreground rounded px-1.5 py-0.5 text-xs font-medium\">Open</span>\n                      }\n                      <span class=\"tabular-nums\">{{ related.upvotes }}</span>\n                      <span>· {{ related.answers }}</span>\n                    </div>\n                  </li>\n                  @if (!last) {\n                    <ui-separator />\n                  }\n                }\n              </ul>\n            </div>\n          </div>\n\n          <div ui-card>\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex items-center gap-2\">\n                <lucide-icon [img]=\"BookOpen\" class=\"text-primary size-4\" />\n                <h3 ui-card-title class=\"text-sm font-semibold\">Forum Etiquette</h3>\n              </div>\n            </div>\n            <div ui-card-content class=\"space-y-2.5 pt-0 text-xs\">\n              <ui-separator />\n              <div class=\"text-muted-foreground space-y-2\">\n                @for (rule of etiquetteItems; track rule) {\n                  <div class=\"flex items-start gap-2\">\n                    <lucide-icon [img]=\"Check\" class=\"text-primary mt-0.5 size-3.5 shrink-0\" />\n                    <span>{{ rule }}</span>\n                  </div>\n                }\n              </div>\n            </div>\n          </div>\n\n          <div class=\"hidden items-center gap-2\">\n            <lucide-icon [img]=\"Award\" />\n          </div>\n        </aside>\n      </div>\n    </div>\n  `,\n})\nexport class UiPeerDiscussionForumComponent {\n  protected readonly ArrowUpRight = ArrowUpRight\n  protected readonly Award = Award\n  protected readonly Bell = Bell\n  protected readonly BellOff = BellOff\n  protected readonly Bold = Bold\n  protected readonly BookOpen = BookOpen\n  protected readonly Bookmark = Bookmark\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly ChevronDown = ChevronDown\n  protected readonly ChevronUp = ChevronUp\n  protected readonly Code = Code\n  protected readonly Copy = Copy\n  protected readonly Eye = Eye\n  protected readonly Flag = Flag\n  protected readonly Italic = Italic\n  protected readonly Link2 = Link2\n  protected readonly List = List\n  protected readonly MessageSquare = MessageSquare\n  protected readonly Plus = Plus\n  protected readonly Quote = Quote\n  protected readonly Share2 = Share2\n  protected readonly Tag = Tag\n  protected readonly ThumbsUp = ThumbsUp\n\n  @Input('class') className?: string\n\n  protected readonly tags = ['#architecture', '#reka-ui', '#typescript', '#cva']\n  protected readonly relatedDiscussions = relatedDiscussions\n  protected readonly etiquetteItems = etiquetteItems\n\n  readonly questionScore = signal(42)\n  readonly questionVote = signal<ForumVote>(null)\n  readonly isQuestionBookmarked = signal(false)\n  readonly isSubscribed = signal(true)\n  readonly copiedQuestionSnippet = signal(false)\n  readonly copiedLink = signal(false)\n\n  readonly answer1Score = signal(89)\n  readonly answer1Vote = signal<ForumVote>(null)\n  readonly isAnswer1Bookmarked = signal(false)\n  readonly copiedAnswer1Snippet = signal(false)\n\n  readonly answer2Score = signal(14)\n  readonly answer2Vote = signal<ForumVote>(null)\n  readonly isAnswer2Bookmarked = signal(false)\n\n  readonly replyDraft = signal('')\n  readonly customAnswers = signal<CommunityAnswer[]>([])\n\n  readonly totalAnswersCount = computed(() => 2 + this.customAnswers().length)\n\n  private timers: ReturnType<typeof setTimeout>[] = []\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => this.timers.forEach((t) => clearTimeout(t)))\n  }\n\n  get rootClass(): string {\n    return cn('bg-background text-foreground w-full space-y-6', this.className)\n  }\n\n  private voteBtn(base: string, vote: ForumVote, type: 'up' | 'down', activeUp: string, activeDown: string): string {\n    if (vote === 'up') return cn(base, activeUp)\n    if (vote === 'down') return cn(base, activeDown)\n    return cn(base, 'text-muted-foreground hover:text-foreground')\n  }\n\n  get questionUpClass(): string {\n    return this.voteBtn(\n      'size-9 rounded-lg border',\n      this.questionVote(),\n      'up',\n      'border-primary bg-primary/10 text-primary hover:bg-primary/20',\n      'text-muted-foreground hover:text-foreground',\n    ).replace(\n      'text-muted-foreground hover:text-foreground',\n      this.questionVote() === 'up' ? '' : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  get questionDownClass(): string {\n    return cn(\n      'size-9 rounded-lg border',\n      this.questionVote() === 'down'\n        ? 'border-destructive bg-destructive/10 text-destructive hover:bg-destructive/20'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  get questionBookmarkClass(): string {\n    return cn(\n      'mt-3 size-8 rounded-md',\n      this.isQuestionBookmarked() ? 'text-primary' : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  get questionBookmarkIconClass(): string {\n    return cn('size-4', this.isQuestionBookmarked() && 'fill-current')\n  }\n\n  get subscribeClass(): string {\n    return cn('h-7 gap-1.5 text-xs', this.isSubscribed() ? 'text-primary' : 'text-muted-foreground')\n  }\n\n  get answer1UpClass(): string {\n    return cn(\n      'size-9 rounded-lg border',\n      this.answer1Vote() === 'up'\n        ? 'border-success bg-success/10 text-success hover:bg-success/20'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  get answer1DownClass(): string {\n    return cn(\n      'size-9 rounded-lg border',\n      this.answer1Vote() === 'down'\n        ? 'border-destructive bg-destructive/10 text-destructive hover:bg-destructive/20'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  get answer1BookmarkIconClass(): string {\n    return cn('size-3.5', this.isAnswer1Bookmarked() && 'fill-primary text-primary')\n  }\n\n  get answer2UpClass(): string {\n    return cn(\n      'size-9 rounded-lg border',\n      this.answer2Vote() === 'up'\n        ? 'border-primary bg-primary/10 text-primary hover:bg-primary/20'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  get answer2DownClass(): string {\n    return cn(\n      'size-9 rounded-lg border',\n      this.answer2Vote() === 'down'\n        ? 'border-destructive bg-destructive/10 text-destructive hover:bg-destructive/20'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  get answer2BookmarkIconClass(): string {\n    return cn('size-3.5', this.isAnswer2Bookmarked() && 'fill-primary text-primary')\n  }\n\n  customUpClass(ans: CommunityAnswer): string {\n    return cn(\n      'size-9 rounded-lg border',\n      ans.userVote === 'up'\n        ? 'border-primary bg-primary/10 text-primary'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  customDownClass(ans: CommunityAnswer): string {\n    return cn(\n      'size-9 rounded-lg border',\n      ans.userVote === 'down'\n        ? 'border-destructive bg-destructive/10 text-destructive'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  voteQuestion(type: 'up' | 'down'): void {\n    const next = applyVote(this.questionScore(), this.questionVote(), type)\n    this.questionScore.set(next.score)\n    this.questionVote.set(next.vote)\n  }\n\n  voteAnswer1(type: 'up' | 'down'): void {\n    const next = applyVote(this.answer1Score(), this.answer1Vote(), type)\n    this.answer1Score.set(next.score)\n    this.answer1Vote.set(next.vote)\n  }\n\n  voteAnswer2(type: 'up' | 'down'): void {\n    const next = applyVote(this.answer2Score(), this.answer2Vote(), type)\n    this.answer2Score.set(next.score)\n    this.answer2Vote.set(next.vote)\n  }\n\n  applyFormat(type: ComposerFormat): void {\n    const prev = this.replyDraft()\n    if (type === 'bold') this.replyDraft.set(prev ? `${prev} **bold text**` : '**bold text**')\n    else if (type === 'italic') this.replyDraft.set(prev ? `${prev} *italic text*` : '*italic text*')\n    else if (type === 'code')\n      this.replyDraft.set(\n        prev ? `${prev}\\n\\`\\`\\`ts\\n// your code snippet here\\n\\`\\`\\`\\n` : '```ts\\n// your code snippet here\\n```\\n',\n      )\n    else if (type === 'link')\n      this.replyDraft.set(prev ? `${prev} [link title](https://example.com)` : '[link title](https://example.com)')\n    else if (type === 'list')\n      this.replyDraft.set(prev ? `${prev}\\n- Key insight 1\\n- Key insight 2` : '- Key insight 1\\n- Key insight 2')\n    else this.replyDraft.set(prev ? `${prev}\\n> Quote from documentation` : '> Quote from documentation')\n  }\n\n  handlePostAnswer(): void {\n    const text = this.replyDraft().trim()\n    if (!text) return\n    const newAnswer: CommunityAnswer = {\n      id: `ans-${Date.now()}`,\n      authorName: 'Alex Rivera',\n      authorRole: 'Peer Student',\n      avatarText: 'AR',\n      postedTime: 'Just now',\n      score: 1,\n      content: text,\n      userVote: 'up',\n    }\n    this.customAnswers.update((prev) => [...prev, newAnswer])\n    this.replyDraft.set('')\n  }\n\n  voteCustomAnswer(id: string, type: 'up' | 'down'): void {\n    this.customAnswers.update((prev) =>\n      prev.map((ans) => {\n        if (ans.id !== id) return ans\n        const next = applyVote(ans.score, ans.userVote ?? null, type)\n        return { ...ans, score: next.score, userVote: next.vote }\n      }),\n    )\n  }\n\n  private flash(setter: (v: boolean) => void): void {\n    setter(true)\n    this.timers.push(setTimeout(() => setter(false), 2000))\n  }\n\n  copyQuestionCode(): void {\n    this.flash((v) => this.copiedQuestionSnippet.set(v))\n  }\n\n  copyAnswer1Code(): void {\n    this.flash((v) => this.copiedAnswer1Snippet.set(v))\n  }\n\n  shareThread(): void {\n    this.flash((v) => this.copiedLink.set(v))\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/peer-discussion-forum/peer-discussion-forum.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/peer-discussion-forum/index.ts",
      "content": "export {\n  UiPeerDiscussionForumComponent,\n  type ForumVote,\n  type ComposerFormat,\n  type CommunityAnswer,\n} from './peer-discussion-forum.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/peer-discussion-forum/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/avatar.json",
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/textarea.json"
  ],
  "description": "StackOverflow and EdStem style student community Q&A thread with upvoting, accepted instructor answers, markdown code formatting, rich answer composer, and related discussions sidebar.",
  "categories": [
    "education",
    "app",
    "community"
  ]
}