The import review grid rendered every parsed row and re-scanned all rows three
times per render for its counts; RawCsvReviewView recomputes rows + counts once
per change and the grid renders through Virtualize. The export modal embedded the
whole CSV twice (textarea + base64 data: URI); it now shows a 50-line preview
and links to GET /api/raw/tags.csv (ConfigEditor, like /raw), which runs the same
RawTagCsvExport builder and sends it as an attachment.
Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
M1: PageErrorBoundary wraps the body of every InteractiveServer page; the inert layout boundary is removed.
m1: DisposeBridge cancels its CTS when the 5 s wait times out.
m2: a CSV import that commits after the modal closed still raises OnSaved.
m3: document SecurityMode=None no-fallback in Client.CLI.md / Client.UI.md.
m4: browsePageDelayMs written only when loaded or changed; negative flagged.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
UI sweep task 4 (Shared/Raw). Markup-only; no behavior change.
- Wrap the Cancel+primary modal-footer pairs in a Bootstrap btn-group across
RawNameDialog, RawDriverTypeDialog, RawConfirmDialog, RawTagModal,
RawManualTagEntryModal, RawCsvImportModal, RawBrowseModal and
RawCsvExportModal (the last mixes a <button> with the download <a>, which
is valid btn-group membership; the download attribute is unchanged).
- RawBrowseModal: the rebirth confirm pair (Publish rebirth request + Cancel)
becomes a btn-group btn-group-sm in place of the d-flex gap-2 wrapper.
- RawCsvImportModal: drop the py-0 padding hack from the per-row remove
button; .btn-sm sizing covers it.
- RawTree: replace the expander's inline style="width:18px" (and the matching
leaf placeholder) with the shared .tree-twist class.
The CSV import all-or-nothing commit gating and RawTree's tree logic are
untouched.