hue — Feature Requirements (all rendering modes)
Status: living inventory · Date: 2026-07-23 · Scope: apps/hue features common to every rendering mode — invocation, source acquisition, language detection, the highlight engine, themes, color depth, output-mode dispatch, the ANSI / HTML / terminal-previewer sinks, degradation, and non-functional requirements. The raylib GUI is specified separately in gui.md.
See the overview for the status scheme (not started / researched / partial / full (<sha>)), the ID scheme, and the rendering-mode map. Requirement statements use lowercase must / should with the repo's usual force. "Traces to" names the implementing source and, for full rows, the primary commit is in the Status cell.
Invocation & CLI (CLI)
Parsed by sparkles.core_cli.args.parseCliArgs!CliParams in app.d.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| CLI1 | hue [options] [target] must render target — a file path, an HTTP(S) URL (SRC3), or a directory (SRC4, a multi-document set); with no target it highlights hue's own bundled source. | partial (74d8f6a3) | app.main; sourcePath/source; directory via SRC4 (URL pending) |
| CLI2 | --html must select HTML output instead of ANSI. | full (74d8f6a3) | CliParams.html |
| CLI3 | --theme <name> must select a built-in theme (default catppuccin-mocha). | full (74d8f6a3) | CliParams.theme |
| CLI4 | --gui must force the raylib window and --no-gui (alias --tui) must force terminal output; with neither, hue must autodetect (see MOD6). On a no-gui build, --gui must error out cleanly. | full (cdc813f6) | CliParams.gui/noGui/tui; wantGui |
| CLI5 | --font, --font-size, --window-width, --window-height must configure the GUI window (see gui.md). | full (c2b49e99) | CliParams.font*/window* |
| CLI6 | --line-numbers / --code-line-numbers (both default on, disableable with =false) must configure the GUI gutters. | full (5b862346) | CliParams.lineNumbers/codeLineNumbers |
| CLI7 | --help must print a usage/description header for the program and every option. | full (d87397b3) | HelpInfo in app.main |
| CLI8 | --background <no-background|spans|full> must select the terminal background mode (BGM); the default is full. | full (d404dc8c) | CliParams.background; parseBackgroundMode |
| CLI9 | --raw must force the highlighted-source view instead of the markdown preview, in every sink (GUI, TUI, ANSI, HTML). Without it a markdown file renders the decorated preview by default (MOD8); non-markdown files are always source. | full (af6ebe95) | CliParams.raw; preview-vs-raw branch in app.main |
| CLI10 | --ansi-copy=raw|strip (default raw) must set how a GUI text-regime selection over a ```ansi block copies — verbatim source (escapes) or stripSgr-cleaned (gui.md SEL7); runtime-toggleable. | full (debfd92d) | CliParams.ansiCopy |
| CLI11 | --table-copy=tsv|markdown (default tsv) must set how a GUI table grid selection copies — tab-separated cells or re-emitted | … | markdown (gui.md TBL2); runtime-toggleable. | full (debfd92d) | CliParams.tableCopy |
| CLI12 | --out <dir> must select the output directory for the static HTML gallery (HTM6) a directory target renders into; with --out omitted it must default to <target>/html. | full (b61f4701) | CliParams.outDir; gallery.md GAL2 |
| CLI13 | The --diff family must select and configure the diff content kind: --diff [<a> <b> | <rev>[..<rev>]], --patch (force stdin-patch), --diff-layout unified|split, --diff-structural=on|off, --diff-preview, the whitespace/noise toggles, and --diff-copy=text|patch — registered here, specified in diff-view.md. | partial (a2b634e8) | --diff/--patch/--staged ship (DVS1–DVS3); the layout, noise and copy flags await their milestones |
| CLI14 | --pr <number|url> must open a pull/merge-request session through the forge seam. | not started | diff-view.md DPR1/DPR7 |
Source acquisition (SRC)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| SRC1 | With a path argument the whole file must be read into memory as the highlight input. | full (74d8f6a3) | readText(sourcePath) |
| SRC2 | With no path, hue must highlight its own app.d, embedded at compile time via import() so it works from any install. | full (1c7398a0) | import("app.d"); stringImportPaths |
| SRC3 | An http(s):// URL target must be fetched over the network and rendered like a file; the language is derived from the URL path extension, falling back to the response Content-Type. A fetch failure must report an error, not crash. (This is the user's explicit request — unlike the opt-in auto-loaded remote media of media.md IMG3.) | not started | proposed URL fetch in app.main |
| SRC4 | A directory target must open a multi-document session (SRC5) over the files it contains: interactively, the gallery index (gallery.md GAL5) — superseded by the richer file-tree explorer (tree-view.md TVU1) when that lands; with --html, the static gallery (HTM6). A directory in a piped non---html mode must degrade to a static listing or a reported error, not a crash. | full (6d86054e) | gallery.md GAL1; tree-view.md TVU1; dir detection in app.main |
| SRC5 | A directory target must resolve to an ordered, filtered file list — sorted by path, restricted to renderable files: *.twoslash.json under --twoslash, else any file with an extension that is not obviously binary. The filter must be a deny-list, since hue renders unknown text as plain text (DEG2) and canonicalLanguage normalizes a label without deciding membership — an allow-list would drop the .toml/.txt files a directory legitimately holds. Extensionless files, recursive descent and .gitignore awareness are deferred to tree-view.md TVU1. | full (77a476f3) | source_set.collectSources/isRenderable |
| SRC6 | That file list must be the one multi-document session every rendering mode consumes — the HTML gallery iterates it, and the interactive backends navigate it (GAL3) — so a document set is acquired once and rendered many ways. It is the substrate the tab view (TBU1) and navigation (LNK3) later build on, not a parallel mechanism. | full (6d86054e) | source_set.SourceSet — the gallery iterates it, the GUI/TUI navigate it |
| SRC7 | Diff-pair acquisition: a diff target acquires pairs of sources through this same layer — two files (DVS1), a stdin patch plus worktree re-reads (DVS2), git show <rev>:<path> old sides (DVS3) — so source acquisition stays one subsystem. A rename pairs sources whose paths (and so LNG1 languages) differ; the language is resolved per side. | full (a2b634e8) | all three acquisition shapes ship (DVS1–DVS3); DiffSides carries the per-side language |
| SRC8 | Forge acquisition: a PR/MR is an acquirable session source — a network fetch through the forge seam (DPR7) sharing SRC3's URL-fetch machinery once that lands, producing a SRC6-style multi-document session (DVS4). | not started | diff-view.md DPR1/DPR7; SRC3 |
Language detection (LNG)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| LNG1 | The grammar language must be derived from the file extension, canonicalized through sparkles:syntax aliases. | full (74d8f6a3) | canonicalLanguage(extension) |
Highlight engine (ENG)
hue drives the sparkles:syntax precise pipeline; the engine internals are specified in docs/specs/syntax.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| ENG1 | Highlighting must use the injection-aware path so markdown (and other languages with injections.scm) get fenced/inline content highlighted by nested grammars. | full (9b0a4b50) | highlightInjected(cache, lang, source, events) |
| ENG2 | Grammars must be loaded from the nix bundle via $SPARKLES_TS_GRAMMAR_PATH; a GrammarRegistry/TsConfigCache is built once. | full (74d8f6a3) | GrammarRegistry.fromEnvironment; TsConfigCache.create |
| ENG3 | Highlighting must produce a HighlightEvent stream over the source, consumed identically by every rendering mode. | full (74d8f6a3) | SmallBuffer!HighlightEvent events |
| ENG4 | On any engine failure (no grammar, parse error) hue must fall back to a single plain-text span covering the whole source. | full (74d8f6a3) | res.hasError → HighlightEvent.sourceSpan |
Themes (THM)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| THM1 | The named theme must be resolved from builtinThemes; an unknown name must warn and fall back to builtinDark. | full (74d8f6a3) | builtinThemes.get(themeName, …) |
| THM2 | The full sorted built-in theme set must be materialized once (names + parallel Theme values) for the live previewer and GUI. | full (74d8f6a3) | names/themes in app.main |
| THM3 | A theme must be resolved against the standard LabelSet before rendering (ResolvedTheme). | full (74d8f6a3) | resolveTheme(theme, labels) |
Color depth (CLR)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| CLR1 | ANSI output (non-interactive and previewer) must adapt to the terminal's detected color depth. | full (74d8f6a3) | detectColorDepth() |
Output-mode dispatch (MOD)
Exactly one mode runs per invocation; see the mode map.
NOTE
These requirements are scheduled to be reshaped, not merely re-implemented. hue is moving to a single behavior with three backend flavors: the target (file · directory · URL) selects a content kind, the flags select a backend, and content kinds compose — a markdown document may embed a twoslash block, whose documentation popups render through the same markdown view. --markdown, --raw and --twoslash therefore stop selecting code paths and become content kinds and overlays (OVL4), and a directory target opens the file explorer (TVU1) rather than a bespoke index view. The dispatch collapse shipped (a75f1fc9): document.d owns one Document value with a content-detected kind (code / markdown / twoslash — a *.twoslash.json target needs no flag), one DocumentPipeline.load replaces the four load-pipeline copies, and app.main picks a backend once (pickBackend) then dispatches to one of four sinks, each a final switch over the kind. --twoslash/--markdown/--raw are now detection inputs (--overlay twoslash=… is the OVL4 spelling). Content-kind composition (a markdown document embedding a twoslash block) is the remaining piece — see ui-architecture.mdUIA6 and pipeline.md XFM3.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| MOD1 | --gui (explicit or autodetected) on a GUI-enabled build must dispatch to the raylib window and return its exit code. | full (e6063309) | if (cli.gui) version(HueGui) |
| MOD2 | --gui on a no-gui build must print a rebuild hint to stderr and exit non-zero. | full (e6063309) | else stderr.writeln(…); return 1 |
| MOD3 | Non-interactive (stdout not a tty) must emit the whole file once (HTML if --html, else ANSI) and exit. | full (74d8f6a3) | !interactive branch |
| MOD4 | An interactive tty (no --html) must open the live terminal previewer. | full (74d8f6a3) | interactive branch → runLoop |
| MOD5 | If a raw-key session can't be acquired in an otherwise-interactive tty, hue must degrade to emitting the whole file as ANSI. | full (74d8f6a3) | sessFactory is null → emitAnsiWholeFile |
| MOD6 | With no explicit mode flag (--gui/--no-gui/--html) on a GUI-enabled build, hue must default to the GUI window when a graphical display is available (Linux/BSD: $DISPLAY or $WAYLAND_DISPLAY set; macOS/Windows: a local, non-SSH session) and stdout is a tty, and otherwise fall through to the terminal dispatch (MOD3–MOD5). On a no-gui build it always falls through. | full (cdc813f6) | displayAvailable; wantGui in app.main |
| MOD7 | The mode flags must override autodetection: --no-gui/--tui forces the terminal path even with a display present; --gui forces the window even with no display detected (raylib surfaces any failure). | full (cdc813f6) | wantGui flag precedence in app.main |
| MOD8 | A markdown file must render the render-markdown decorated preview by default in every sink — GUI (MDP), interactive TUI (MDP-T), non-interactive ANSI (ANS3), and HTML (HTM5) — reusing the shared MdDoc model and the one viewMarkdown widget view. --raw (CLI9) forces the highlighted-source view. | full (241e8052) | the ContentKind dispatch across sinks; buildPreviewModel + viewMarkdown |
| MOD9 | A diff or PR session is a content kind like code/markdown/twoslash (the dispatch-collapse doctrine): produced once by the pipeline, dispatched through the same backend pick, and rendered by every sink (DVL4) — no new modes. | full (8b4f9797) | ContentKind.diff dispatched by every sink (ANSI/HTML arms; TUI/GUI via the Document transport + ViewerModel) |
ANSI terminal output (ANS)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| ANS1 | Non-interactive/piped output must render the whole file to ANSI with italics and background emission enabled. | full (74d8f6a3) | emitAnsiWholeFile; renderAnsi |
| ANS2 | ```ansi fenced blocks embedded in a doc are passed through as literal SGR (the tty renders them) in ANSI mode. | full (74d8f6a3) | (renderer pass-through) |
| ANS3 | For a markdown file the non-interactive ANSI emit must render the decorated preview by default — the shared viewMarkdown widget view painted to SGR cells via CellGrid.writeAnsi — not highlighted source; box-drawing / quote / rule glyphs emit natively and ```ansi fences pass through (ANS2). --raw (CLI9) reverts to ANS1. | full (c3a5b2af) | the ANSI sink's widget path in app.d |
| ANS4 | A diff session in non-interactive ANSI must emit the unified layout whole — the pager use-case (git diff | hue) — composing with the background modes (BGM*) like any theme background. | full (31fdab59) | runAnsiSink case diff:; verified via git diff | hue |
Background mode (BGM)
How the theme background is applied in terminal rendering — the whole-file ANSI emit and the interactive previewer — selected by --background (CLI8). The three modes; full is the default (the prior fixed behaviour was spans).
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| BGM1 | no-background — emit foreground colors only; the terminal's own background shows through (the theme background is ignored). Useful for piped output over a themed terminal. | full (d404dc8c) | AnsiOptions.emitBackground: false; previewer chrome bg + backdrop gated |
| BGM2 | spans — emit a background only where the theme sets a span background (today's fixed whole-file behaviour, AnsiOptions(emitBackground: true)); selectable via the flag. | full (d404dc8c) | emitAnsiWholeFile; backgroundOptions |
| BGM3 | full (the new default) — fill every line with the theme's default background edge-to-edge, matching the previewer's back-color-erase look (PRV7). Fills each line via sparkles:syntax renderAnsi's fillLine. | full (d404dc8c) | renderAnsi fillLine (3e3d9cb4); backgroundOptions; previewer PRV7 |
HTML output (HTM)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| HTM1 | --html must emit a self-contained <style> + <pre class="syn-root"><code> document with CSS-class highlighting. | full (74d8f6a3) | HTML branch; renderHtml(cssClasses) |
| HTM2 | The emitted stylesheet must carry the theme's default fg/bg on .syn-root (no duplicate pre{} color rule). | full (74d8f6a3) | writeThemeStylesheet |
The GUI's interactive features apply best-effort to HTML output too — HTML selection is the browser's, so parity means emitting markup that copies cleanly and (optionally) a preview/gutter:
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| HTM3 | Best-effort SEL parity: the markup must be selectable so a browser copy yields the original source; any gutter / line-number / decoration content must be non-selectable (user-select: none) so it is excluded. | partial | plain <pre><code> copies to source today; the twoslash overlay realizes the decoration-excluded half on branch (TWH6–TWH8); a plain---html gutter awaits HTM4 |
| HTM4 | Best-effort NUM parity: HTML output must render an optional line-number gutter numbering physical (source) lines — each line wrapped in a .ln span whose number is a CSS ::before counter, so it is never selected or copied (HTM3); below-line overlay annotations must carry no number and must not advance the counter, and blank lines must keep their height and survive a copied selection. | full (b61f4701) | gallery.relayoutGutter; gallery.md GAL4 |
| HTM5 | MDP parity: for a markdown file --html must render a rich HTML preview by default via the shared sparkles:syntax MdDoc → HTML emitter (renderMarkdownHtml) — with theme-derived CSS (writeThemeStylesheet), syntax-highlighted fences (a fenceRenderer hook reusing renderHtml), GitHub callouts, and column-aligned tables — not highlighted source. --raw (CLI9) emits highlighted source (HTM1). | full (af6ebe95) | renderMarkdownHtml (exists; enrichment + default wiring pending); app.main HTML markdown branch |
| HTM6 | A directory target with --html must render a static gallery into --out (CLI12): one standalone page per file in the set (SRC5) plus an index.html linking every page with its name and summary. Single-file --html (stdout fragment) must be unchanged. | full (b61f4701) | gallery.writeGallery; gallery.md GAL1/GAL2 |
| HTM7 | Each gallery page must wrap hue's content fragment in a page shell: a header (name · summary · prev/next/index nav), a full-height single scroll container (exactly one scrollbar, no nested body+pre scrollbars), and a page background matched to the theme's .syn-root background. | full (b61f4701) | gallery.pageShell; gallery.md GAL2/GAL3/GAL6 |
| HTM8 | Gallery pages must implement selection domains: a drag must be confined to the domain it starts in — the code, or one overlay annotation — so a copy never mixes the two. This layers on the pure-CSS code-only default (TWH6) and is hue's own output, not a consumer's responsibility. | full (b61f4701) | gallery.pageShell (selection CSS/JS); gallery.md GAL7; supersedes the JS harness (TWD3) |
| HTM9 | A diff/PR session with --html must render the static review site — the gallery analog: an index of changed files with per-file ± stats, one diff page per file with prev/next navigation, the dual old/new gutter (HTM4 generalized), and split-pane selection domains (HTM8). | not started | diff-view.md DVL4; gallery.md GAL10 |
Interactive terminal previewer (PRV)
IMPORTANT
Retired (D6, M9): previewer.d is deleted. Everything it did is subsumed by the full TUI (tui.md) — live theme cycling is ←/→ there — and the non-Posix tty path degrades to the whole-file ANSI emit (sparkles:tui's input reader is Posix-only). The rows below are kept as the historical record of what the TUI absorbed; none traces to live code.
previewer.d — the live theme browser (the baseline the full TUI (tui.md) extends). Its render/output core was @nogc nothrow.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| PRV1 | The previewer must repaint a full frame, flush, read one key, and repeat until quit/select. | full (74d8f6a3) | runLoop |
| PRV2 | ↑/↓ must cycle the selected theme (wrapping) and repaint live. | full (74d8f6a3) | runLoop Key.up/Key.down |
| PRV3 | Enter must select the theme: leave the alt screen and print the whole file highlighted in that theme onto the primary screen. | full (d1c4e159) | LoopResult.selected; renderFull |
| PRV4 | Any other key / cancel must quit and print nothing. | full (74d8f6a3) | Key.cancel/Key.other |
| PRV5 | A frame must show a header (title · theme name · index), a hint line, separators, the highlighted viewport slice, and a scrolling theme-list window around the selection. | full (844680a3) | Previewer.buildFrame |
| PRV6 | The viewport must show only the top maxCode lines that fit (height-derived), not the whole file, keeping the fold O(visible). | full (fd22c112) | firstLines; buildFrame maxCode |
| PRV7 | The theme backdrop must fill the viewport via back-color-erase (open theme bg, then erase), with begin/end synchronized-output markers. | full (844680a3) | CtlSeq.syncBegin/eraseDisplay/syncEnd |
| PRV8 | The alt screen must be entered on start and restored (show cursor, exit alt screen) on exit; its contents are discarded. | full (74d8f6a3) | enterAltScreen/exitAltScreen |
Degradation & diagnostics (DEG)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| DEG1 | Only degradation warnings are logged (logger level = warning); normal operation is silent. | full (d87397b3) | initLogger(LogLevel.warning) |
| DEG2 | A missing grammar must warn (no grammar for '<lang>') and render plain text, not fail. | full (74d8f6a3) | warning(i"no grammar …") |
| DEG3 | An unknown --theme must warn and use the default dark theme. | full (74d8f6a3) | builtinThemes.get fallback |
| DEG4 | Without $SPARKLES_TS_GRAMMAR_PATH, hue must still run — degrading to plain text for grammar-requiring languages. | full (74d8f6a3) | GrammarRegistry.fromEnvironment |
Build and packaging (BLD)
How hue is compiled and shipped. The GUI backend is included in the default build; a raylib-/ghostty-free variant stays available for terminal-only use and minimal dependency closures.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| BLD1 | The default dub configuration (application) must compile the GUI backend in (pulling sparkles:raylib-text + sparkles:ghostty), so a stock dub build :hue and the installed binary have --gui and the markdown preview with no special build. | full (29bf1a65) | apps/hue/dub.sdl configuration "application" |
| BLD2 | A no-gui dub configuration must build the raylib-/ghostty-free variant (the pre-flip application behaviour) for terminal-only installs and minimal closures. | full (29bf1a65) | apps/hue/dub.sdl configuration "no-gui" |
| BLD3 | The nix packages.hue must build the GUI-enabled default; a packages.hue-no-gui must build the headless variant with no raylib/GL in its closure. (GUI-enabled packages.hue done; the separate hue-no-gui package pending.) | partial (29bf1a65) | nix/packages/hue.nix |
| BLD4 | dub test :hue must stay headless — linking no window/GL — regardless of the default flip; gui.d stays excluded from the unittest config. | full (e6063309) | apps/hue/dub.sdl configuration "unittest" |
Non-functional (NFR)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| NFR1 | Startup (file read, parse, theme-list build) may allocate on the GC; the previewer's per-frame render/output core must be @nogc nothrow (a theme switch triggers no GC pause). | full (0657c94a) | Previewer @nogc methods; TermOut |
| NFR2 | Each previewer repaint must assemble into one reused SmallBuffer and flush with a single write; the resolved theme table is reused and only rebuilt on change. | full (0657c94a) | Previewer.frame/styleBuf; themeView |
| NFR3 | A raylib-/ghostty-free build of hue must remain available (the no-gui config, BLD2); the GUI + preview modules compile into the default application build and the unittest build (minus gui.d), and are excluded by no-gui. | full (29bf1a65) | apps/hue/dub.sdl no-gui excludedSourceFiles |
| NFR4 | GUI/TUI startup (process launch → first paint) should be fast: a provisional target of ≤ 100 ms (optimal ≤ 50 ms). TODO: calibrate against the window-system-integration research and empirical measurement. | researched/not-started | provisional target; no benchmark gate yet |
| NFR5 | GUI window resize must stay within the frame budget — the reflow (WIN3/WRP4 relayout on a width change) must not cause noticeable stutter. | not started | relayout; gui.md WIN3/WRP4 |
| NFR6 | Large files must scroll instantly — a provisional target of up to 1 MB / 20k lines with no per-frame cost growth. Viewport culling (RND1) already makes scroll O(visible); load/relayout cost is the open question. TODO: calibrate against third-party editors + benchmarks. | partial | gui.md RND1 (culled scroll); load/relayout unbenchmarked |
| NFR7 | All logging must route through sparkles.base.logger; raylib's TraceLog output must be encapsulated (a SetTraceLogCallback bridge) and emitted at trace level, so it is silent under hue's default warning level (DEG1). | full (c497d99d) | gui.raylibTraceLog installed via SetTraceLogCallback before InitWindow; hue logging via sparkles.base.logger (DEG1/DEG2) |
| NFR8 | Diff interactivity targets: live re-diff under inline editing (DST5) must fit the debounce budget (provisional ≤ 50 ms for typical files); large-PR sessions (hundreds of files) must stay viewport-culled (DVG5) with lazy per-file diff computation — a file's diff/refinement is computed when it first scrolls into view or is opened, not up front. | not started | diff-view.md DVG5/DST5/DVM6 |
Deferred, researched & branch-only (DEF)
Roadmap items — planned/researched features, and modes that exist on another branch. Library-engine roadmap (TextMate second engine, locals, injection combined, incremental editor loop, UTF-16 sources) lives in the sparkles:syntax spec; the rows below are the ones that surface as hue capabilities.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| DEF1 | --twoslash <nodes.json> mode (ANSI default / --html / --gui) — D-native Twoslash rendering. | planned/branch-only | twoslash.md TWM1–TWM3 |
| DEF2 | --markdown <file.md> mode — treat the input as Markdown and render the preview. Shipped as a standalone HTML emit on main (runMarkdownMode); being folded into the unified preview-by-default HTML path (HTM5) with theme CSS + highlighted fences. | full (af6ebe95) | app.runMarkdownMode; HTM5 |
| DEF3 | The MdDoc → HTML emitter (sparkles:syntax md/render_html.d) that the --markdown/twoslash-docs paths need. | full | libs/syntax md/render_html.d; syntax spec J1 |
| DEF4 | Runtime theme-file parsing (load user themes: native JSON, TextMate/VSCode JSON, Helix TOML) so --theme can name a file, not just a built-in. | researched/not-started | syntax spec D6 |
| DEF5 | CSS-variable multi-theme HTML output mode (one document, theme switched via :root[data-theme] / prefers-color-scheme). | researched/not-started | syntax spec F6; render/html.d |
| DEF6 | Content-based language detection (a Linguist-style cascade) — today the language is only the file extension / fence label. | not started | syntax spec §deferred (canonicalLanguage only) |
| DEF7 | A grapheme/east-asian width table so wide/CJK/combining/tab characters occupy their true cell count (replacing the v1 one-column-per-codepoint metric). | researched/not-started | gui.md FNT6 |
| DEF8 | Color-emoji rendering in the GUI (a separate rasterizer for CBDT/COLR) — raylib/stb_truetype cannot. | not started | gui.md FNT7 |
| DEF9 | Pluggable overlays — a backend-agnostic overlay seam generalized from twoslash, plus new overlay kinds (source map, coverage, tracing, tree-sitter inspector, code size). | researched/not-started | overlays.md OVL* |
| DEF10 | Notifier / interactive popups — a cross-backend (GUI/TUI/HTML) snacks.nvim-style popup component (collapse-to-icon, expand, buttons, expandable items) with startup-info and file-info popups. | researched/not-started | notifier.md NTF*/NSI*/NFI* |
| DEF11 | Full-screen TUI — grow the minimal theme-selection previewer into a terminal port of the GUI viewer: scrolling, a cell scrollbar, SGR mouse, selection → source offsets + OSC 52 copy, wrapping, line numbers, and the markdown preview (reusing the raylib-free widget views). | full (8b1d2aac) | tui.md TIN*/TSF*/TSB*/TSL*/MDP-T*/TCP* |
| DEF12 | UI component library — sparkles:ui ships: canvas-first, no native widgets, three levels, and immediate/retained/SSG interpreters. Its requirements live in docs/specs/ui; hue's port onto it is UIA4. | partial | sparkles:ui; ui-architecture.md UIA* |
| DEF13 | @sparkles/hue web integration — a JS/TS npm package that swaps Shiki for sparkles:syntax in web frameworks (VitePress/Next/Solid Start) across SSG/SSR (process shell-out first) and a future wasm client-side backend. | not started | web-integration.md PKG*/SHL*/WSM*/FWK* |
| DEF14 | Content folding — expand/collapse of code structures, markdown sections/lists, and any tree-sitter CST node (a cross-backend fold-range model + presentation-free fold-state machine, elided from the wrapped-line render). | not started | folding.md FLD*/FSR* |
| DEF15 | Tree / DAG view — an interactive tree + DAG component (snacks.nvim-explorer-style) across GUI/TUI/HTML: file explorer, tree-sitter inspector, file outline, git graph, dependency graph; a sparkles:ui widget. | not started | tree-view.md TRV*/DAG*/TVU* |
| DEF16 | Navigation — link following & go-to: markdown anchors + local-file links, module/import & relative paths, doc-comment ($(REF …)/@see) references, and LSP go-to-definition; intra- and inter-document, cross-backend. | not started | navigation.md LNK*/REF*/LNB* |
| DEF17 | Images & diagrams — render raster images (), diagram fences (mermaid/graphviz), and LaTeX math via one media-block mechanism; GUI texture · terminal graphics protocol · HTML <img>/<svg>. | not started | media.md MDB*/IMG*/DGM*/MTH* |
| DEF18 | Tab view — a tab-bar + active-tab-state-machine component across GUI/TUI/HTML; initial use cases: open files as tabs, and VitePress-style code groups. | not started | tab-view.md TAB*/TBU*/TBB* |
| DEF19 | Transformer pipeline (architecture) — a pluggable parse→transform→compile pipeline (à la unified.js/markdown-it/babel) where highlighting/overlays/folding/navigation/media are transform plugins and the renderers are compilers. | researched/not-started | pipeline.md PIP*/PRS*/XFM*/CMP* |
| DEF20 | Gallery & multi-document navigation — a directory of files rendered as a static HTML gallery (index + per-file pages with a prev/next header, a physical-line gutter, and selection domains) and navigated interactively in the GUI/TUI. Replaces the branch-only JS preview harness. | full (6d86054e) | gallery.md GAL* |
| DEF21 | Diff, PR & review — render diffs (two files, piped unified patch, git revisions) and pull requests (behind a DbI forge seam: GitHub first; GitLab/Gitea/Forgejo/Codeberg as adapters) across all four sinks: unified + side-by-side layouts over a new sparkles:diff engine, layered formatting-noise handling (word-level refinement, formatting-only hunk classification, structural tree-sitter diff incl. commutative-container equivalence, rendered markdown-preview diff), then a write surface — hunk/line staging, inline editing, content-anchored comments with two-target suggestions, and 3-way conflict viewing/resolution. | not started | diff-view.md DVM*/DVS*/DVL*/DVN*/DVG*/DPR*/DST*/DCM*/CFV* |
| DEF22 | Lantern — a which-key-inspired key guide: press a prefix, wait a beat, and a panel lists every key that can follow it. Built on hue's one binding table, which replaced three divergent copies of the keyboard policy and is what makes the keymap enumerable at all (and so is CFG6's prerequisite). Includes the <space> leader map. | partial (c19bb926) | lantern.md KEY*/LTN*/LMP* |
| DEF23 | Picker — a fuzzy finder behind <leader>f / <leader>s / <leader>g / <leader>/: a query constraint language (git:modified src/**/*.rs !mod.rs name), frecency-aware composite ranking, budgeted cancellable searches over the sparkles:event-horizon work-stealing pool, and sources for files, grep, recent, open documents, git, themes, lines and the keymap itself. | not started | picker.md PIK*/PKQ*/PKR*/PKS*/PKL* |
| DEF24 | sparkles:fuzzy — the matcher the picker drives, as its own library: a typo-resistant scorer with match positions, the query/constraint parser, the composite ranking formula, exponential-decay frecency, and a glob matcher. 100% @safe pure nothrow @nogc, benchmarked from the first commit. | not started | picker.md PKM* |
Module coverage (general spec)
Every non-GUI source file maps to the requirements above:
| Source | Key symbols | Requirements |
|---|---|---|
apps/hue/src/app.d | CliParams, main, emitAnsiWholeFile, mode dispatch | CLI*, SRC*, LNG1, ENG*, THM*, CLR1, MOD*, ANS*, HTM*, DEG* |
apps/hue/src/source_set.d | SourceEntry, SourceSet, collectSources, twoslashTally, plainTally | SRC4–SRC6, gallery.md GAL1 |
apps/hue/src/gallery.d | relayoutGutter, pageShell, galleryIndex, writeGallery | HTM4, HTM6–HTM8, gallery.md GAL2/GAL4/GAL6/GAL7 |
apps/hue/src/previewer.d | deleted (D6, M9) — theme cycling lives in the TUI; BackgroundMode moved to ansi_model.d | PRV* (retired) |
apps/hue/dub.sdl | build configurations (application [GUI] / no-gui / unittest) | CLI4, MOD1/2, BLD1/2/4, NFR3 |
nix/packages/hue.nix | packages.hue, packages.hue-no-gui | BLD3 |