Skip to content

hue DSV preview — Feature Requirements (the data browser)

Status: design · Date: 2026-08-18 · Scope: Delimiter-Separated Values (CSV, TSV, PSV, semicolon-CSV, …) as a hue content kind: the grid preview across all four sinks, the dialect sniffer, the interactive data-browser tier (multi-key sort, filtering, column hide/reorder), the selection/copy deltas, and the sparkles:dsv engine underneath.

NOTE

Phase 1 shipped: the D0 engine (71757564libs/dsv) and the D1 basic preview (3e18af9e — the grid in every sink through the existing md table path, layout pinned by golden glyph-grid fixtures). CHK executed (2026-08-18): the table unification merged, the audit is recorded in the Milestones section, and D2–D5 are re-planned and normative. D2 shipped (47652402): the record-number gutter, per-column caps, decimal alignment, and the pinned header band (a sparkles.ui.components.table viewport extension, 43afbea8, since generalized to freeze panes). D3 shipped (090f05c6): the source copy format, chrome exclusion, and byte-exact whole-grid reproduction. D4's core shipped (546107c68fcf9290): the projection engine, the browser machine, and the TUI's filter bar, header-click sort and reset — residue per row (header menus, palette UI, GUI wiring, fuzzy highlight positions). Everything post-CHK is forward-looking design. Status legend and ID conventions: see the overview.

Design & scope

What it is

A .csv (or .tsv, .psv, sniffed .txt/stdin) target renders as a decorated data grid by default in every sink — the MOD8 doctrine applied to a third document family: GUI, TUI, non-interactive ANSI, and HTML all paint the same grid from the same model, and --raw (CLI9) reverts to highlighted source. Interactively the grid is a data browser: a pinned header, a row-number gutter, multi-key sorting, a filter bar plus header menus, and column hide/reorder — a viewer in the VisiData/csvlens tradition, not an editor.

Almost everything visual already exists. The box-drawn grid is MDP10's table (resolveTracks, bold header + heavy ┝━┿━┥ rule, per-column alignment); the selection regime is TBL1TBL6 unchanged; the wide-content scroll idiom is COD6's border scrollbars; the filter bar follows the picker's query-language doctrine (PKQ). What is genuinely new is the engine (dialect sniffing, an RFC 4180 parser with an identity channel, a record index, projection compute) and the browser state machine over it.

Why the engine is a library (sparkles:dsv)

The same reasoning as sparkles:diff and sparkles:fuzzy: hue's engines are things this repository owns, unit-tests, and benchmarks. sparkles:dsv (libs/dsv) is the allocation-conscious compute core — dialect detection, parsing, the record index, typed columns, and sort/filter execution — @safe pure nothrow @nogc throughout, texts borrowed as spans, storage in caller-owned SmallBuffer arenas, errors as Expected. sparkles:base is its only dependency: the fuzzy full-text remainder (DSF3) is matched by sparkles:fuzzy and combined by the host, so neither engine depends on the other. hue owns the state machine, chrome, keymap, and job scheduling.

Reference points

BorrowedFrom
the viewer-not-editor data-browser shapeVisiData, csvlens
dialect sniffing by field-count consistencyPython csv.Sniffer (design, not code)
quoting/parsing baselineRFC 4180 (+ the real-world deviations)
typed columns driving alignment & comparisonxsv/qsv, VisiData
per-column rainbow tint (raw view, deferred)Rainbow CSV
cell-level CSV diff (deferred)daff

Sequencing constraint: the table-rendering unification

Resolved. The table-rendering unification merged (2026-08-18: the two-view architecture — layout.d content-agnostic core / render.d string view / widgets.d widget view, with the md table case an adapter over buildTableWidgets; see docs/specs/core-cli/table.md §3.0). Phase 1 (D0/D1) shipped before the merge without touching sparkles.ui.components.table, as planned, and rebased over it cleanly — the DSV golden grids are byte-identical under the unified renderer. The CHK re-orientation executed (the audit lives in the Milestones section); the phase-1 no-table-edits invariant is retired: post-CHK work extends the unified component where a row calls for it.

Non-goals

  • Editing. hue is a viewer; the bounded write surfaces belong to the diff spec (DST5). A projection (sort/filter/hide) never writes back.
  • Multi-sheet workbooks (.xlsx, ODS) — a different acquisition problem.
  • A query engine. The filter bar filters rows; it is not SQL, joins, or aggregation.

Content kind & dispatch (DSK)

IDRequirementStatusTraces to
DSK1DSV must be a content kind like code/markdown/twoslash/diff (the dispatch-collapse doctrine, MOD8/MOD9): detected once by DocumentPipeline.load, dispatched through the same backend pick, rendered by every sink — no new modes.full (3e18af9e)document.d ContentKind.dsv; fromDsvSource
DSK2Detection: the extensions .csv, .tsv, .psv (and .ssv) must select the kind and seed the dialect (DSD1); a .txt/extensionless/stdin source must be content-sniffed (DSD5); --dsv must force the kind (a detection input, like --markdown), and --raw must force highlighted source in every sink (CLI9).full (3e18af9e)detect extension family; CliParams --dsv; fromSource DSD5 gate
DSK3A DSV document must render the grid preview by default in every sink — GUI, TUI, ANSI, HTML — from one shared model and one widget view (viewDsv), the exact shape of MOD8's markdown rule.full (3e18af9e) — golden glyph-grid fixtures pin the shared layout (dsv_view.golden.*); ANSI/HTML verified by smokethe ANSI/HTML case dsv: arms; PreviewModel.present routing; apps/hue/test/fixtures/dsv/
DSK4--raw on a DSV file renders highlighted source like any text file; without a bundled DSV grammar it degrades to plain text per DEG2. Rainbow per-column raw styling is deferred (DSZ2).full (3e18af9e)the existing raw path (DEG2 plain-text degrade verified)
DSK5The status chrome must name the resolved dialect (delimiter · quote · header on/off), the row counts (visible/total when projected), the projection state (sort keys, active filter, hidden-column count), and the copy mode (SEL7 doctrine).full (8fcf9290) — dialect · rows · projection segments in the TUI header (the GUI has no status bar by design; its copy-mode flash covers SEL7)dsvStatusNote + DsvBrowser.chromeNote in docNote

Dialect detection (DSD)

Precedence: flags > sniff > extension seed. All sniffing reads a bounded sample — the first 100 records or 256 KiB, whichever ends first (provisional; one constant, shared with width measurement DSN3).

IDRequirementStatusTraces to
DSD1The extension must seed the delimiter (.csv, · .tsv → tab · .psv| · .ssv;), and a consistency sniff over the sample may override it within the candidate set {, ; \t |}: the winning delimiter maximizes field-count consistency across sample records (quote-aware), ties broken by the seed, then by set order. This is what catches the semicolon CSVs European Excel writes into .csv.full (71757564) — hue wiring 3e18af9eseedForExtension; sparkles.dsv.dialect.sniff; adaptDsv
DSD2The quote character must be sniffed from {" '}" unless ' pairs consistently and " does not; RFC 4180 doubled-quote escaping ("") applies to whichever wins. --dsv-quote forces it.full (71757564) — --dsv-quote wiring 3e18af9esniff quote evidence; DsvFlags
DSD3Header presence must be decided by heuristic under --dsv-header=auto (default): the first record is a header when its type profile differs from the body (body columns typed numeric/date while the first row is not) or when it is all-unique, non-numeric names; yes/no force it. A headerless file gets synthetic A B C… column names (display-only, never serialized into a copy).full (71757564) — flag + synthetic names 3e18af9edetectHeader; columnName (A B C…)
DSD4--dsv-delimiter=<char>, --dsv-quote=<char>, --dsv-header=auto|yes|no must force any sniffed decision; a forced delimiter outside the candidate set is accepted verbatim (sniffing then only decides quote/header). All three are runtime-visible in the status chrome (DSK5).full (3e18af9e)CliParams.dsv*DocumentPipelineadaptDsv overrides; re-run header heuristic
DSD5Content detection for .txt/extensionless/stdin: the source is DSV when the sniff finds ≥ 2 columns at ≥ 90% field-count consistency over ≥ 3 sample records (provisional thresholds); otherwise it stays plain text. This is a bounded first step toward DEF6 content-based detection, and the seam it should later flow through.full (71757564) — stdin/.txt wiring 3e18af9eSniffResult.looksDsv; contentLooksDsv; the fromSource gate
DSD6Encoding: UTF-8 with an optional BOM (stripped before parsing, preserved by whole-document copy DSC4); records end at CRLF or LF (mixed accepted, each record remembers its own terminator for DSC4); malformed UTF-8 bytes display as replacement glyphs but round-trip exactly through copy (the raw span is authoritative).partial (71757564) — the parse side ships; the copy consumers are DSC4parseDsv (BOM, per-record Terminator, lone-CR literal)

Model & parser — sparkles:dsv (DSM)

IDRequirementStatusTraces to
DSM1Parsing must implement RFC 4180 quoting — quoted fields, embedded delimiters, embedded newlines, doubled-quote escapes — plus the tolerated deviations: unquoted quotes mid-field are literal, a quote after field content re-enters quoted mode (Excel behavior), a final record without a terminator is a record.full (71757564)sparkles.dsv.parse.parseDsv
DSM2Every cell must carry an identity channel: its half-open raw byte span in the borrowed source (quotes and escapes included) alongside its decoded text — the SEL1 discipline — so sub-cell selection, char-precise crossing (DSC3), and whole-document reproduction (DSC4) stay honest. Decoding is lazy/windowed; the model never copies the file.full (71757564)DsvCell.raw; decodeCell (simple cells borrow)
DSM3Ragged rows must degrade, never error: a short record renders padded missing cells (distinct missing styling), a long record grows the grid to the maximum field count (overflow columns named …+1), and the row is marked ragged; the status chrome counts ragged rows. An empty file or header-only file renders an empty grid plus a note (DEG doctrine).partial (71757564) — accounting, padding, …+N names and the ragged readout ship (3e18af9e); distinct missing styling and the empty-grid note pend (empty degrades to the plain view)DsvDoc.raggedCount; buildTable padding/overflow
DSM4Each column must carry an inferred type from the sample: int · float · date (ISO 8601) · bool · string, with empty cells excluded from inference; a column types as the most specific type ≥ 95% of sampled non-empty cells satisfy (provisional). Types drive alignment (DSG3), sort comparison (DSS2), and filter operators (DSF2) — never rendering of the value itself.full (71757564)classifyValue; inferColumnTypes (95% most-specific-first)
DSM5The library surface is @safe pure nothrow @nogc: borrowed input spans, SmallBuffer-arena storage of plain-data offsets (no string fields), Expected errors, and chunk-bounded entry points (DSN5) that read no clock and touch no I/O — the sparkles:diff/sparkles:fuzzy doctrine. Benchmarked from the first commit.partial (71757564) — attributes/borrowing/Expected/bench ship; chunked entry points await DSN5libs/dsv package contract; bench.d
DSM6Projection compute lives in the library: stable multi-key sort over typed comparators (DSS), typed constraint evaluation (DSF2), and the composed projection (a row-index permutation + column visibility/order) as a pure function of (model, spec) — deterministic, independent of chunking and enumeration order.full (546107c6)sparkles.dsv.projectapplyProjection/compareTyped; determinism by index-tiebreak total order

The grid view (DSG)

DSG1/DSG3/DSG6 are phase-1 (they render through the existing md table path); DSG2, DSG4, and DSG5 need capabilities that path does not have (pinned bands, per-column caps + document-scale horizontal scroll, a row gutter) and are post-CHK — designed here, built against the unified table after the checkpoint.

IDRequirementStatusTraces to
DSG1The grid must render as the MDP10 box-drawn table: rounded outer corners, separators, bold header + heavy ┝━┿━┥ rule, light inner row rules by default, one padding cell per separator — the same widget vocabulary, so GUI/TUI/HTML parity is by construction (MDP-T2 native glyphs in cells).full (3e18af9e)adaptDsv → the md table path (viewMarkdown)
DSG2The header row and the column-rule must stay pinned while the document scrolls vertically (GUI/TUI: a non-scrolling band above the viewport; HTML: position: sticky), and the row-number gutter stays pinned horizontally — the COD6 pinned-gutter idiom at document scale.full (43afbea8) — the component's pinned band (TableViewportSpec.pinHeader); wired for DSV in 47652402 (interactively on by default via the auto table clamp); HTML: position: sticky ships with the semantic table (DSG6)buildTableWidgets freeze-pane emission (pinHeader = top-rows sugar); MdTableExtras.pinHeader; golden tall.txt
DSG3Per-column alignment from type: numeric and date columns right-aligned, booleans centered, strings left — plus missing cells dimmed and ragged overflow tinted; the slots come from the theme like every widget (THM).partial (3e18af9e) — typed alignment (+ decimal, 47652402) ships; the missing/ragged styling slots need a per-cell decoration channel the md funnel lacks — moved into D3buildTable aligns; the pending cell-decoration seam
DSG4A grid wider than the pane must scroll horizontally behind the frame with the border scrollbar (the COD6 bottom-border bar, ScrollView machine, wheel-sideways/Shift+wheel), the row-number gutter pinned; per-column width is capped (provisional: 64 cells) with overlong cell text ellipsized — the full value remains reachable by copy and (deferred) the cell peek DSZ3.full (b6913019) — TBL7/TBL8 inherited through the shared table path; the 64-cell per-column cap wired in 47652402TableViewportSpec; MdTableExtras.columnMaxWidthTableProps.columnMaxWidths; golden wide.txt
DSG5The row-number gutter numbers data records 1-based in source order (the header row unnumbered), and keeps showing source record numbers under any projection — a sorted/filtered view reveals provenance instead of renumbering (the VisiData reading). Gutter content is excluded from selection (SEL2).full (47652402) — the stub column ships; its copy exclusion is DSC (D3)adaptDsv gutter column; TableProps.headerCols heavy stub rule; frozen against h-scroll (freezeLeftColumns, golden wide-scrolled.txt); copy-excluded + selection-inert (090f05c6)
DSG6Sink parity: non-interactive ANSI emits the whole grid once — the pager case: projection/viewport controls do not apply and a --table-max-lines clamp is ignored (a pipe can never scroll; the clamp stays honored for tables embedded in markdown documents). A grid wider than the pane clips behind the frame at offset 0 by default; --table-overflow wrap wraps cells to fit instead. HTML emits the semantic <table> (DSG6's other half) — static, so browser-side sort/filter is out of scope for the v1 emit.full (3e18af9e) — the semantic <table> (+ sticky thead over the theme bg) ships via the shared MdDoc → HTML emitter (the --html arm was rerouted off the widget-HTML interpreter, which cannot lay out the table view's positioned stack — see open-issues); scope attributes pend in the shared emitter; the pager clamp-ignore landed with the UAT fixes on the D4 branchthe ANSI sink arm; the HTML dsv arm → renderMarkdownHtml
DSG7Numeric and date columns should decimal-align (values aligned on the decimal point / a common tail), using the table core's decimal-pad solver — a capability the unification brought that a data grid should not leave unused.full (47652402)MdTableExtras.columnAligns Align.decimaldecimalPadsFor

Browser state (DSB)

The interactive tier is one projection value — sort keys + filter + column visibility/order — owned by a presentation-free state machine, applied by DSM6, painted by DSG.

IDRequirementStatusTraces to
DSB1The projection must be a single regular value (ProjectionSpec): ordered sort keys with per-key direction, the filter AST, and the column order/visibility list. Every mutation route — header click, keymap, filter bar, header menu, columns palette — edits this one value; the machine is presentation-free and unit-tested (the TBL5 doctrine).full (b2530077) — DsvBrowser + DsvProjection, applied by 8fcf9290dsv_browser.d (pure machine); workspace.applyDsvBrowser
DSB2Pristine is a state: a one-key reset returns to source order, no filter, all columns, and the chrome must make non-pristine visually unmissable (DSK5) — because copy semantics differ there (DSC4/DSC5).full (8fcf9290) — Shift-R; the chrome carries the projection segmentsDsvBrowser.reset; Command.dsvReset
DSB3Column hide/reorder must be reachable from the keymap and from a columns palette — a picker-style list of columns with visibility toggles and move-up/down (DEF23 look, TRV list machinery); pointer drag-reorder of header cells is deferred (DSZ4).partial (b2530077) — the state (toggle + last-visible guard) ships; the palette UI and its keymap route pendDsvBrowser.toggleColumn/hiddenCols
DSB4Projection changes on large documents run as background jobs (DSN5) with progress in the status chrome and cancellation on any newer projection edit; the visible grid stays interactive on the previous projection until the new one lands (no half-applied views).not started — reprojection is synchronous by design until DSN5's chunked jobs (D5); fine at sample scalesthe D5 job runner
DSB5All browser keys route through hue's one binding table (KEY) so lantern lists them and CFG6 can rebind them.partial (8fcf9290) — / and Shift-R live in the one binding table (lantern lists them); sort is pointer-only and the palette has no key yetkeymap.d dsvFilter/dsvReset + CtxFlag.hasDsvGrid

Sorting (DSS)

IDRequirementStatusTraces to
DSS1Multi-key sort with explicit UI: header click (or the sort key on the focused column) cycles the column asc → desc → removed as the primary key, demoting existing keys; Shift+click (and a keymap chord) appends the column as the next key instead. The header cell shows each key's rank and direction (1▲, 2▼).full (8fcf9290) — click-cycling, Shift-append, and the rank chrome (/ 1▲ 2▼ badges, chrome-clean: no source identity, never copied) ship in the TUI (c9d1a8e8); the GUI route pends with the GUI browser wiringDsvBrowser.cycleSort; tui.dsvHeaderSortAt (+mods); MdTableExtras.headerBadges
DSS2Comparison is typed per DSM4: numeric/date/bool keys compare by value, strings by Unicode-aware folded comparison; cells that fail the column's type (and missing cells) group after all typed values, comparing as strings among themselves — so a dirty column still sorts usefully.full (546107c6)compareTyped (typed + non-conforming grouping)
DSS3The sort must be stable with the source record index as the final tiebreak — a total, deterministic order (the sparkles:fuzzy invariant-6 doctrine), independent of chunking and identical across sinks and platforms.full (546107c6) — stability by construction: the data-index tiebreak makes the order totalapplyProjection

Filtering (DSF)

Two surfaces, one truth: header menus compile into the same query AST the filter bar edits, so the bar always displays the whole active filter.

IDRequirementStatusTraces to
DSF1The filter bar follows the picker's query doctrine (PKQ; the sparkles:fuzzy SPEC's lexing — whitespace tokens, quoting, \-escapes, ! negation): a query splits into column constraints plus a fuzzy remainder, parsing borrows the input and allocates nothing.partial (8fcf9290) — the bar ships in the TUI (apply-on-Enter, not incremental); the GUI pendsthe / prompt; parseFilterQuery on the PKQ lexing rules
DSF2Column constraints are name:value forms with typed operators: name:text (case-folded contains), name:=text (exact), name:>n name:>=n name:<n name:<=n (typed numeric/date comparison), name: (empty cell), each negatable (!region:EU). name resolves case-insensitively against headers (quoted when it contains spaces); #3: addresses a column by 1-based index. Constraints AND; evaluation is sparkles:dsv's (DSM6).full (b2530077)parseFilterQuery (quoted names, #N, all operators, negation) → sparkles.dsv.project eval
DSF3The fuzzy remainder must match rows by cell through sparkles:fuzzy (generalLanguage profile; a row admits when any visible cell admits the part), with matched positions highlighted in the grid (PKQ6 analog). The host ANDs constraint results with remainder admission — neither engine depends on the other.partial (b2530077) — typo-tolerant per-part any-cell admission through sparkles:fuzzy ships; match-position highlighting pends on the cell-decoration seamfuzzyRowMask; DsvProjection.rowMask
DSF4Header menus offer the per-column quick filters — contains / equals / range / empty — as a popup on the header cell (popup machinery); applying one splices the corresponding constraint into the AST (visible in the bar, removable from either surface). Value-set enumeration (a distinct-values checklist) is deferred (DSZ5).not startedheader popup → AST splice
DSF5Filtering must report: matched/total row counts in the status chrome, live-updating as background evaluation progresses (DSB4); an invalid query keeps the previous projection and surfaces the parse error inline in the bar — it never blanks the grid.full (8fcf9290) — an invalid query keeps the previous projection and surfaces as a notice; the chrome counts visible/total rowsDsvBrowser.setFilter; dsvStatusNote rows segment

Selection & copy (DSC)

The grid is the TBL regime; these rows bind the DSV deltas. DSC1 is phase-1 by construction (inherited through the existing table path); DSC2DSC5 are post-CHK — the serializer and crossing seams are exactly what the table unification may reshape.

IDRequirementStatusTraces to
DSC1Smart drag, sub-cell/rect/row/column selection, Shift/Alt snapping, the whole-table copy button, and the TUI's OSC 52 route apply unchanged (TBL1TBL6, TSL); the serializers stay presentation-free and testable (TBL5).full (3e18af9e) — by construction through the md table pathtable_select.d reuse (tsv/markdown formats)
DSC2--table-copy grows a source mode: the selection re-emitted in the document's own dialect — the resolved delimiter/quote, minimal RFC 4180 quoting (quote only cells containing delimiter/quote/newline), records joined by the document's dominant terminator. source is the default for DSV documents (tsv stays the default for markdown tables); runtime-toggleable with the SEL7 indicator.full (090f05c6)TableCopyFormat.source; resolveTableCopy (--table-copy=auto); the GUI 3-way toggle; the TUI TSL5 wiring
DSC3A text-regime drag that crosses the grid maps char-precise to raw source bytes via the identity channel (DSM2) — the TBL4 crossing rule with quoted cells resolving to their raw spans.partial (090f05c6) — grid-regime copies resolve raw cell bytes via the re-parsed model, and the gutter is selection-inert (SEL2); the char-precise text-regime crossing still slices the decoded bufferDsvCopy.rawCell; unkeyed/srcStart-stripped stub cells
DSC4Whole-document reproduction (SEL8): in the pristine projection, selecting the whole document reproduces the input file byte-for-byte — BOM, quoting, mixed terminators, ragged rows and all.full (090f05c6) — through the grid regime: a whole-grid source copy IS the input file (BOM/CRLF/ragged/unterminated-quote/no-trailing-newline pinned by tests); the text-regime SEL8 reproduction rides DSC3's remaining halfserializeGridCopy whole-grid shortcut; dsv_view.copy.byteExactHardCases
DSC5Under a non-pristine projection, copying is WYSIWYG over the projection: grid-regime copies serialize the visible rows/columns in view order (all formats); DSC4's byte-reproduction guarantee is explicitly scoped to pristine, and the chrome's projection indicator (DSB2) is what tells the user which contract is live.not startedserializers over the projection permutation

Scale (DSN)

The normative target is ~100 MB / 1M rows interactive — a data browser that dies at 1 MB isn't one. Correctness ships first; the machinery is staged post-CHK (provisional D5). DSN1 alone is phase-1: it is a property of the D0 model, not of the renderer.

IDRequirementStatusTraces to
DSN1The model must hold offsets, not copies: the source stays one borrowed buffer; the record index and cell spans are arena-stored integers, so resident overhead is proportional to record count, not content size.full (71757564)DsvDoc span arenas over the borrowed source
DSN2The record index (quote-aware record boundaries — the one inherently sequential pass) must build lazily/in the background; the sample-parsed head renders immediately and the scrollbar range grows as indexing progresses (the status chrome shows progress).not startedbackground index job; DSB4 runner
DSN3Column widths, dialect, types, and the header heuristic come from the bounded sample (DSD preamble) — never a whole-file scan; widths are stable thereafter (no reflow as later rows appear).not startedsniff/width measurement over sample
DSN4Rendering must be viewport-culled (RND1): only visible rows are decoded and laid out per frame; scroll cost must not grow with row count.not startedviewDsv windowed materialization
DSN5Sort/filter/index execution must be chunk-bounded pure calls (the sparkles:fuzzy searchChunk shape): the library exposes resumable cursors, hue schedules chunks on the event-horizon pool with cancellation between chunks; results are deterministic regardless of chunking (DSS3).not startedsparkles.dsv.project cursors; DSB4
DSN6Provisional budgets, to be calibrated with benchmarks: first grid paint of a 100 MB file ≤ 300 ms (sample-parsed head); full index of 1M rows in the background ≤ 2 s; sort of 1M indexed rows ≤ 1 s; filter keystroke-to-first-results ≤ 100 ms. A libs/dsv/bench/ harness pins them.not startedlibs/dsv/bench/

Deferred (DSZ)

IDRequirementStatusTraces to
DSZ1DSV diffhue diff a.csv b.csv as a cell-level diff (daff-style): row alignment via sparkles:diff, changed cells tinted in the grid through the preview-diff decoration channel (MDP21, DVN6).not startedsparkles:diff + grid decorations
DSZ2Rainbow raw view — per-column color in --raw (Rainbow CSV): either a bundled tree-sitter CSV grammar or a sparkles:dsv-driven highlight-event stream into the existing ANSI/HTML renderers.not startedDSK4; highlight-event synthesis
DSZ3Cell peek — a popup showing a truncated cell's full value (popup), with copy.not startedDSG4 ellipsis sites
DSZ4Pointer drag-reorder of header cells (the keymap/palette route DSB3 ships first).not startedheader drag machine
DSZ5Distinct-values menu — a header-menu checklist of a column's value set with counts (needs a full-column scan; rides the DSN5 job runner).not startedDSF4
DSZ6Frozen data columns — pinning the first N data columns against horizontal scroll (the gutter idiom generalized).not startedDSG4
DSZ7Number/date display formatting (thousands separators, locale decimal comma detection for semicolon dialects) — display-only, never mutating copy fidelity.not startedDSM4 inference notes

Milestones

Phase 1 runs in parallel with the table-rendering unification (separate worktree) and must not conflict with it: D0 is pure library work, and D1 consumes the existing md table rendering path as-is.

MilestoneDeliversGate
D0 ✅ (71757564)Basic sparkles:dsv — dialect seed + sniff (DSD1DSD4, DSD6), RFC parser + identity channel (DSM1DSM3), typed columns (DSM4), offsets-not-copies storage (DSN1). No hue or sparkles:ui code touched.unit-tested pure lib; adversarial fixtures (quotes-in-quotes, ragged, CRLF/BOM, huge cells); bench baseline
D1 ✅ (3e18af9e)Basic hue preview — the content kind (DSK1DSK4, DSK5's dialect readout) rendered in all four sinks through the existing md table rendering path: DsvDoc adapted onto the same table model viewMarkdown's table case consumes (DSG1/DSG3/DSG6), inheriting that path's current limits — no pinned header, no width caps / document h-scroll, no row gutter (DSG2/DSG4/DSG5 wait for CHK). The TBL selection/copy regime is inherited by construction (DSC1, tsv/markdown formats only).golden frames GUI/TUI/ANSI/HTML; DSD5 stdin/txt sniff; zero edits under sparkles.ui.components.table (the parallel-work invariant)

CHK — re-orientation checkpoint: ✅ executed 2026-08-18. Both entry conditions were met (D0 + D1 shipped; the unification merged) and the branch rebased over the unified pipeline with no adapter changes needed — the D1 MdDoc synthesis feeds the md table case, which is now itself an adapter over buildTableWidgets, and the DSV goldens are byte-identical. The row audit:

RowVerdict at CHK
DSG4Largely satisfied by TBL7/TBL8: the framed viewport (pinned frame, border scrollbars, wheel/bar routing, --table-overflow/--table-max-lines) is inherited through the shared table path. Residual: the DSV per-column cap default and its columnMaxWidths wiring.
DSG2Changed shape: the viewport pins the frame while the whole interior (header included) scrolls as one offset — the pinned header becomes a TableViewportSpec pinned-header-rows extension of the component (now permitted), not a host-painted band.
DSG5Changed shape: the core models row headers (TableProps.headerCols stub columns) — the record-number gutter becomes a stub column with a copy-exclusion delta, not host chrome.
DSG3Unchanged owner; easier now (TableWidgetStyle semantic slots are the styling seam for missing/ragged).
DSG7 (new)The core's decimal-pad solver makes numeric decimal alignment cheap — added to the D2 scope.
DSC2DSC5Unchanged shape: the srcStart/srcEnd identity channel survives wrapping in the widget view, the serializers (table_select.d) and the TBL regime are intact; the decoded-buffer vs raw-bytes reconciliation (Document.dsvText) is still the D3 substance.
DSB/DSS/DSF/DSNUntouched by the unification; sketches carried forward as-is.

The milestones below are normative as re-planned at CHK.

MilestoneDelivers (re-planned at CHK)Gate
D2 ✅ (47652402)The full grid on the unified table: the pinned-header viewport extension (DSG2TableViewportSpec grows pinned header rows), the headerCols record-number stub column (DSG5), the DSV per-column cap default via columnMaxWidths (DSG4 residual), decimal alignment (DSG7), and DSV fixtures for the TBL7/TBL8 viewport goldensgoldens for wide/tall DSV grids incl. the pinned header; component changes land with their own table.md §3.0 updates
D3 ✅ (090f05c6)DSV copy deltas (DSC2DSC5): the source dialect format (default for DSV) + stub-column exclusion, char-precise crossing via the raw-span reconciliation, pristine byte reproduction; the missing/ragged styling slots (DSG3/DSM3 completion — the per-cell decoration channel composes with the selection work)round-trip: whole-doc copy == input bytes; gui.md TBL2/TBL6 + tui.md TSL5 gain source
D4 ◐ core shipped (546107c68fcf9290)The browser (DSB/DSS/DSF): projection machine, multi-key sort, filter bar + header menus, columns palette; carried forward from D3: the char-precise text-regime raw crossing (DSC3) and the missing/ragged styling slots (DSG3/DSM3)machine unit tests; determinism (DSS3); keymap in lantern
D5Scale (DSN2DSN6): background index, chunked jobs, the 100 MB / 1M-row budgets pinned by libs/dsv/bench/DSN6 budgets pinned; a 100 MB corpus scenario

Cross-references (threading status)

Threaded with this spec: feature-requirements.mdCLI26 (the --dsv family), MOD10 (DSV as a content kind), DEF28 (the roadmap row), and CLI11's planned-source note; index.md's Documentation-map row, ID-scheme mnemonics, and mode-map note; the gui.md TBL preamble pointer.

Threaded with D0 (71757564): the AGENTS.md sparkles:dsv sub-package row; the module-coverage table below. Threaded with D3 (090f05c6): gui.md TBL2/TBL6 and tui.md TSL5 gained source.

Landing with later code, not before:

  • The apps/hue module-coverage rows here and in feature-requirements.md — no hue DSV files exist yet (D1).
  • tui.md: a DSV-T parity area (by construction via TSF3, like MDP-T/DIF-T) once rows exist to bind.

Module coverage (sparkles:dsv)

SourceKey symbolsRequirements
libs/dsv/src/sparkles/dsv/model.dSpan, Dialect, DsvCell/DsvRecord/DsvDoc, decodeCell, classifyValue, inferColumnTypesDSM2DSM4, DSN1
libs/dsv/src/sparkles/dsv/parse.dparseDsv (tolerant RFC 4180, BOM/terminators, modal/ragged accounting)DSM1, DSM3, DSD6
libs/dsv/src/sparkles/dsv/dialect.dseedForExtension, sniff, detectHeader, SniffResult.looksDsvDSD1DSD3, DSD5
libs/dsv/src/sparkles/dsv/bench.dparse (1k/10k rows) + sniff --bench baselinesDSM5 (the benchmarked-from-D0 half)
libs/dsv/dub.sdllibrary/unittest configurations (base-only dependency)DSM5

Feature requirements · GUI · TUI · Picker · Overview