Background
Two feature components hand-roll the same WebLLM model-download progress panel — same chrome, same WebGPU progress bar, same "what's happening" disclosure copy. Only the headline label string differs:
Both render: an outer <div> with border-border-light / bg-surface-subtle chrome, a header row with the label + pct%, a role="progressbar" track + fill, an optional progress-text line in monospace, and (in RewriteButton's case) the "What's happening?" <details> disclosure that explains the model is downloading locally.
This violates CLAUDE.md's reuse rule:
Never hand-roll a parallel copy or an inline one-off. If a shared piece is missing a variant, add the variant to the shared piece.
Pre-existing duplication — shipped in #123 (PR #123) and untouched by PR #129. Flagged in #129's review pass but explicitly deferred as out of scope for #64 PR A. This issue is that follow-up.
Proposed surface
New shared component at src/design-system/shared/ModelLoadProgress.tsx:
interface ModelLoadProgressProps {
/** 0..1 fraction reported by WebLLM. */
progress: number;
/** Human-readable status from WebLLM (e.g. the weight file being fetched). */
text?: string;
/** Headline label. Defaults to "Loading the rewrite model (~1.2GB, one-time download)". */
label?: string;
/** Show the "What's happening?" disclosure (`RewriteButton` does; `SectionRewrite` currently doesn't). */
showExplainer?: boolean;
}
Re-export from the @design-system barrel. RewriteButton.LoadingPanel and SectionRewrite.LoadingPanel both delete their local copy and use this.
Pros: one canonical model-download panel; the "What's happening?" disclosure can be enabled in SectionRewrite too without a second hand-roll; future model-load surfaces (PR B's ModelSelector will need this too — same progress bar, switching to a different model) reuse it.
Cons: minor — design-system needs a small new export; consumers thread two extra optional props.
Relationship to #124
#124 tracks the OTHER duplication between these two files — the success/warning result strip (RewriteResult in RewriteButton, ProposedSection in SectionRewrite). Different chrome, different purpose. Both should land before the Dialog primitive in PR B of #64 so the new surfaces don't add a third copy of either pattern.
Acceptance criteria
Out of scope
Follow-up to PR #123 + PR #129.
Background
Two feature components hand-roll the same WebLLM model-download progress panel — same chrome, same WebGPU progress bar, same "what's happening" disclosure copy. Only the headline label string differs:
src/components/features/RewriteButton.tsx—LoadingPanelat line 193. Label:"Loading the bullet-rewrite model (~1.2GB, one-time download)".src/components/features/SectionRewrite.tsx—LoadingPanelat line 244. Label:"Loading the rewrite model (~1.2GB, one-time download)".Both render: an outer
<div>withborder-border-light/bg-surface-subtlechrome, a header row with the label +pct%, arole="progressbar"track + fill, an optional progress-text line in monospace, and (inRewriteButton's case) the "What's happening?"<details>disclosure that explains the model is downloading locally.This violates CLAUDE.md's reuse rule:
Pre-existing duplication — shipped in #123 (PR #123) and untouched by PR #129. Flagged in #129's review pass but explicitly deferred as out of scope for #64 PR A. This issue is that follow-up.
Proposed surface
New shared component at
src/design-system/shared/ModelLoadProgress.tsx:Re-export from the
@design-systembarrel.RewriteButton.LoadingPanelandSectionRewrite.LoadingPanelboth delete their local copy and use this.Pros: one canonical model-download panel; the
"What's happening?"disclosure can be enabled inSectionRewritetoo without a second hand-roll; future model-load surfaces (PR B'sModelSelectorwill need this too — same progress bar, switching to a different model) reuse it.Cons: minor — design-system needs a small new export; consumers thread two extra optional props.
Relationship to #124
#124 tracks the OTHER duplication between these two files — the success/warning result strip (
RewriteResultinRewriteButton,ProposedSectioninSectionRewrite). Different chrome, different purpose. Both should land before theDialogprimitive in PR B of #64 so the new surfaces don't add a third copy of either pattern.Acceptance criteria
src/design-system/shared/ModelLoadProgress.tsx.@design-systembarrel (src/design-system/index.ts).RewriteButton.LoadingPanelremoved; consumer imports the shared component.SectionRewrite.LoadingPanelremoved; consumer imports the shared component.npm run dev, both rewrite paths look identical to current.npm run test,npm run typecheck,npm run buildgreen.Out of scope
~1.2GBliteral in the label string (size depends on which model is loading; will be parameterized when PR B ships the model-dimensioned label).Follow-up to PR #123 + PR #129.