renderreport 0.3.2

Data-driven report generation with Typst as embedded render engine — no CLI dependency
Documentation
// ChecklistPanel Component
// Card with label–diagnosis rows and optional status indicators

#let checklist-panel(data) = {
  // Long checklists (e.g. an "all violations" list with dozens of rows) can
  // exceed a single page. theme-card() is breakable: false (right for small
  // cards), which would force the whole card onto one page and overflow past
  // the page boundary instead of paginating — the same breakable body/
  // non-breakable header split used by phase-block() for the same reason.
  block(
    width: 100%,
    fill: color-surface,
    stroke: (paint: color-border, thickness: component-card-border-width),
    radius: 10pt,
    inset: (x: spacing-4, y: spacing-4),
    breakable: true,
  )[
    #if data.title != none [
      #component-title(text(weight: "bold", size: font-size-sm, fill: color-text)[#data.title])
    ]

    #for (i, row) in data.rows.enumerate() [
      #grid(
        columns: (1fr, 2fr),
        column-gutter: spacing-4,
        align: (top, top),

        // Label + optional status dot
        stack(
          dir: ltr,
          spacing: 4pt,
          if row.status != none {
            let dot-color = if row.status == "good" { color-ok }
              else if row.status == "warn" { color-warn }
              else if row.status == "bad"  { color-bad }
              else { color-text-muted }
            box(
              width: 7pt,
              height: 7pt,
              radius: 999pt,
              fill: dot-color,
            )
          },
          text(size: font-size-sm, weight: "bold", fill: color-text)[#row.label],
        ),

        text(size: font-size-sm, fill: color-text)[#row.diagnosis],
      )

      #if i < data.rows.len() - 1 [
        #v(spacing-2)
        #line(length: 100%, stroke: 0.5pt + color-border)
        #v(spacing-2)
      ]
    ]
  ]
}