Super-AI-Components

Source Panel

Ingested sources with pipeline status

Sources

  • Q3-report.pdfPDF · 2.4 MB
    Ready
    Chunks
    1,284
    Q3-report.pdf: Ready, 1,284 chunks
  • pricing-policy.docxWord · 310 KB
    Ready
    Chunks
    96
    Indexed
    2 days ago
    pricing-policy.docx: Ready, 96 chunks
  • kickoff-call.vttTranscript · 48 min
    Chunking
    x
    Step 2 of 3
    kickoff-call.vtt: Chunking, step 2 of 3
  • competitor-teardown.mdMarkdown · 61 KB
    Embedding
    x
    Step 3 of 3
    competitor-teardown.md: Embedding, step 3 of 3
  • annual-review-2024.pdfParse failed — the file is a scan with no text layer
    Failed
    annual-review-2024.pdf: Failed, Parse failed — the file is a scan with no text layer

What it is

The list of everything a retrieval-backed surface knows about — files, pages, transcripts — with each source showing exactly where it is in the ingestion pipeline: parsing, chunking, embedding, ready, or failed. Ready sources carry their chunk count; failed sources carry their error and their own Retry.

Why it matters

Ingestion is slow, multi-stage and fails for boring reasons — a scanned PDF with no text layer, a transcript that times out mid-embed. Every product on the reference board (the NotebookLM sources pane, Lovable's knowledge panel, Claude projects) learned that a single spinner is not enough: when a source never becomes citable, the first question is which stage it died at, and a generic progress indicator has already thrown that away. Naming the stage turns an opaque wait into a diagnosable one, and it is the difference between a user retrying the right file and a user re-uploading everything.

Observed in: NotebookLM · Lovable · Claude projects

Anatomy

  1. 1source-panelRoot wrapper around the header and the list of sources.
  2. 2source-panel-headerHeading and the caller-supplied panel action, shown when either is present.
  3. 3source-panel-headingThe panel label, e.g. Sources.
  4. 4source-panel-itemsThe list of sources, in the order they were added.
  5. 5source-panel-itemOne source. Carries data-stage, so the pipeline position is queryable, not just visible.
  6. 6source-panel-stageThe stage name as visible text — present in all five states, in the same place.
  7. 7source-panel-item-progressIndeterminate progressbar plus a step counter, only while a source is in flight.
  8. 8source-panel-item-statsChunk count and any extra metadata on a ready source, rendered through stat-readout.
  9. 9source-panel-retryPer-row Retry, named after that source. Only rendered on a failed row.
  10. 10source-panel-item-statusVisually hidden role=status text announcing that row's stage transitions.
  11. 11source-panel-emptyWraps the empty-state shown when there are no sources at all.

How to use it

Create a row the moment a source is accepted, in `parsing`, and move it forward by updating that source's `stage` — the component is fully controlled and knows nothing about polling or timing. Set `chunkCount` when a source reaches `ready`; set `errorMessage` and `stage: "failed"` when it dies, and pass `onRetrySource` so the failed row can re-enter the pipeline in place. Reach for the empty state's `emptyAction` to repeat whatever verb this surface uses to add a source, rather than inventing a generic one.

Do

  • Name the current stage in visible text on every row — parsing, chunking and embedding are different waits, and only the name tells them apart.

    • Q3-report.pdfPDF · 2.4 MB
      Parsing
      x
      Step 1 of 3
      Q3-report.pdf: Parsing, step 1 of 3
    • kickoff-call.vttTranscript · 48 min
      Chunking
      x
      Step 2 of 3
      kickoff-call.vtt: Chunking, step 2 of 3
    • pricing-policy.docxWord · 310 KB
      Embedding
      x
      Step 3 of 3
      pricing-policy.docx: Embedding, step 3 of 3
  • Show the chunk count once a source is ready; it is the only visible signal of how well that source will retrieve.

    • Q3-report.pdfPDF · 2.4 MB
      Ready
      Chunks
      1,284
      Q3-report.pdf: Ready, 1,284 chunks
    • pricing-policy.docxWord · 310 KB
      Ready
      Chunks
      96
      Indexed
      2 days ago
      pricing-policy.docx: Ready, 96 chunks

Don't

  • Don't collapse the pipeline into one spinner and a word like Processing — when a source fails, nobody can tell which stage it died at.

    • Q3-report.pdfProcessing…
    • kickoff-call.vttProcessing…
    • pricing-policy.docxProcessing…
  • Don't put Retry at the panel level; one source failing to embed says nothing about the four that succeeded, and a panel-wide retry re-ingests all of them.

    • Q3-report.pdfPDF · 2.4 MB
      Ready
      Chunks
      1,284
      Q3-report.pdf: Ready, 1,284 chunks
    • annual-review-2024.pdfParse failed — the file is a scan with no text layer
      Failed
      annual-review-2024.pdf: Failed, Parse failed — the file is a scan with no text layer

Accessibility

Keyboard
  • Most rows are not tab stops at all. `entity-row` is rendered without `onSelect`, so it stays a `<div>` — the only focusable thing this panel creates is the Retry button, and only on a `failed` row that was given `onRetrySource`. A panel of five parsing sources has zero tab stops of its own.
  • That makes the tab count swing with the data: nothing while a source is ingesting, one stop the moment it fails, back to nothing when the retry succeeds. Anything you pass as `action` or as the empty state's `emptyAction` adds its own stops on top.
  • Retry is a real button, so Space and Enter activate it. There is no Delete or Escape binding, no arrow-key movement between rows, and no keyboard route to remove a source — removal is not in this component's API.
  • The progress bar is an indicator, not a control. It takes no focus and no keys, and there is nothing to cancel an in-flight source from the keyboard.
Screen reader
  • The stage is announced from visible text in every one of the five states, because `source-panel-stage` renders the same badge for `ready` and `failed` as it does for the three in-flight stages. Colour and icon shape are redundant with it, never a substitute — which is what makes the panel legible when the bar is switched off.
  • Each row carries its own visually hidden `role="status"` region reading, for example, "Q3-report.pdf: Chunking, step 2 of 3" — so a parsing → chunking → ready transition is announced without the user going looking. On a `failed` row the `errorMessage` is folded into the same string, and on a `ready` row the chunk count is.
  • That is one live region per row. Moving four sources through the pipeline at once produces four independent announcements, and re-pushing the same stage from a polling loop re-announces it, because the region fires on text change rather than on a real transition.
  • The in-flight bar is a real `role="progressbar"` with no `aria-valuenow` — genuinely indeterminate, not a fake percentage — and it is named per source and per stage ("Q3-report.pdf: Chunking"). Four in-flight rows are four distinguishable bars rather than four announcements of "Loading".
  • Retry's accessible name comes from `retryLabel`, which defaults to `Retry ${source.name}`. Overriding it with a constant is the one change that turns a usable panel into a list of identically-named buttons, so keep the source name in whatever you pass.
  • A ready row's chunk count arrives through `stat-readout` as a `<dl>`, so it reads as "Chunks, 128" rather than as two loose numbers. Every stage icon is `aria-hidden`, so none of them contributes to a name.
  • The panel heading is a `<p>`, not a heading element. It is not in the document outline and cannot be jumped to by heading navigation — if this panel needs to be reachable that way, wrap it in your own heading.
Focus
  • Retrying moves nothing on its own, but the button disappears: a successful retry takes the row out of `failed`, which unmounts the Retry that was just pressed and drops focus to `<body>`, so the next Tab restarts from the top of the page. If retries in your product resolve quickly, move focus to something stable before the stage changes.
  • Retry is the vendored `Button` and ships `focus-visible:ring-3`. It is the panel's only focusable element, so there is nothing else here whose focus style you have to supply.

Watch out for

  • The in-flight bar is deliberately indeterminate — parsing, chunking and embedding report completion, not percentages. If you have a real per-stage percentage, add it as visible text beside the stage name rather than reaching for a value on the bar; a fabricated percentage that sticks at 90% is worse than an honest indeterminate one.
  • Rows are not clickable. Retry lives inside the row, so making the row itself a button would nest one interactive element inside another. If you need jump-to-source (the other half of the citation-ref mechanism), put an explicit control in the row rather than making the whole row activate.
  • A row's status region re-announces whenever its text changes. Move a source through the pipeline on real transitions only — pushing the same stage repeatedly from a polling loop will read the stage out loud on every tick.
  • chunkCount is ignored on anything other than a ready source. A count that survives into failed implies the source is partially retrievable, which is usually not true — clear it when a source drops out of the pipeline.

Installation

npx shadcn@latest add https://super-ai-components.vercel.app/r/source-panel.json

Requires a shadcn app on Base UI (style base-nova). Radix-based styles such as new-york fail to typecheck: these components use Base UI's render prop and callbacks like onOpenChangeComplete, which Radix primitives do not have.