feat(tui): add optional Nerd Font icons - #313
frigidplatypus wants to merge 9 commits into
Conversation
|
@frigidplatypus saw that the CI is breaking, can you fix it? |
There was a problem hiding this comment.
🟡 Changes recommended
Three moderate findings and one documentation nit remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds optional Nerd Font icons to the TUI while retaining emoji defaults.
Changes:
- Adds
[tui].iconsconfiguration. - Routes TUI rendering through the selected icon set.
- Adds documentation and configuration/icon-selection tests.
File summaries
| File | Summary |
|---|---|
docs/tui.md |
Documents icon configuration. Nit (2 votes): add an issue link or concrete problem statement. |
docs/config.md |
Adds the icon configuration reference. |
crates/outl-tui/src/view/warnings_banner.rs |
Uses the selected warning icon. |
crates/outl-tui/src/view/toasts.rs |
Uses selected toast icons. |
crates/outl-tui/src/view/sidebar.rs |
Uses selected sidebar icons. |
crates/outl-tui/src/view/overlays.rs |
Uses selected overlay icons. |
crates/outl-tui/src/view/outline.rs |
Routes property and inline rendering through icons. |
crates/outl-tui/src/view/namespace.rs |
Uses the selected file icon. |
crates/outl-tui/src/view/inline.rs |
Adds icon-aware inline rendering. |
crates/outl-tui/src/view/chrome.rs |
Uses selected chrome icons and dynamic widths. |
crates/outl-tui/src/view/backlinks.rs |
Uses the selected fallback file icon. |
crates/outl-tui/src/state.rs |
Stores the icon set in application state. |
crates/outl-tui/src/runtime.rs |
Loads icon configuration. Moderate (1 vote): selection occurs after the initial load, leaving startup warning chips inconsistent. |
crates/outl-tui/src/lib.rs |
Registers the icon module. |
crates/outl-tui/src/icons.rs |
Defines emoji and Nerd Font sets. Moderate (1 vote): auto-run remains hard-coded to ▶. |
crates/outl-tui/src/app.rs |
Updates rendering test calls. |
crates/outl-tui/src/actions/reminders.rs |
Uses the selected reminder icon. |
crates/outl-tui/src/actions/lifecycle/mod.rs |
Initializes default icons. |
crates/outl-tui/src/actions/lifecycle/loading.rs |
Uses the selected warning marker. |
crates/outl-config/src/tui.rs |
Defines icon configuration and tests. Moderate (1 vote): desktop settings saves can reset the persisted icon preference. |
crates/outl-config/src/schema.rs |
Moves TUI configuration definitions. |
crates/outl-config/src/lib.rs |
Exports the new configuration types. |
crates/outl-config/CLAUDE.md |
Documents the configuration setting. |
Review details
Suppressed comments (3)
crates/outl-config/src/tui.rs:11
- This new option is not preserved by the desktop settings writer:
Settings::saveconstructsConfigwithTuiCfg::default()andrestore_unmodeled_sectionscurrently never copieson_disk.tuiback (seecrates/outl-desktop/src-tauri/src/settings.rs:193,249-275). Editing any desktop setting therefore silently rewrites[tui].icons = "nerd-font"toemoji. Restore the wholeTuiCfgin that adapter before shipping the new preference.
pub icons: TuiIconStyle,
crates/outl-tui/src/icons.rs:40
- The
auto-runproperty is still hard-coded to▶, so selectingicons = "nerd-font"does not change this property icon even thoughrender_blocknow routes property rendering throughIconSet::property_glyph. Give it a style-specific play glyph (preserving▶for Emoji) instead of returning a literal.
"auto-run" => Some("▶"),
crates/outl-tui/src/runtime.rs:422
App::new()has already calledload_current()atactions/lifecycle/mod.rs:147, and that first load formats the parse-warning status chip with the default emoji icon. Replacingapp.iconshere leaves a Nerd Font launch with an emoji chip; later reloads no longer recognize or clear it becauseloading.rsmatches the current warning glyph. Initialize theIconSetbefore the first load (for example, pass the style intoApp::new) and cover the startup-warning case.
app.icons = crate::icons::IconSet::new(icon_style);
- Files reviewed: 23/23 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
I'm unable to get the Mac ci to pass, although it seems unrelated to my changes. |
into() hardcodes TuiCfg::default() (icons = emoji), and restore_unmodeled_sections never carried [tui] back from disk, so every settings save silently rewrote a hand-set icons = "nerd-font" or mouse_capture = true. The code comment claimed the restore existed; the pin test makes the claim true.
Two review findings on the icon opt-in:
- property_glyph("auto-run") and command_glyph("run") hardcoded "▶"
outside the IconSet, so a nerd-font terminal still drew the emoji
play glyph. IconSet gains a play field (nf-fa-play for Nerd Font).
- App::new runs the first load_current(), which stamps the
parse-warning status chip with icons.warning; the runtime only
assigned the configured IconSet afterwards, so a nerd-font launch
booted with an emoji chip that no later reload could clear (the
clear path only recognises its own marker). Icon style is now an
App::new parameter, applied before the first load.
|
Pushed dfc861f + c07e79f addressing all three review findings (details in the updated description). On CI: the macOS failure is the pre-existing flaky statistical pin |
Adding App::new's icon_style param pushed every test call site over its ratchet line (+1 across five files, +6 in runtime.rs where the widened call went vertical). Tests now go through App::new_for_tests, which pins the emoji set behind the same five-arg shape they had before; the boot orchestrator calls the real constructor and its arg list fits the 100-column single line by naming the root param root.
There was a problem hiding this comment.
🔵 Needs a closer look
Two unresolved moderate icon-routing and default-rendering issues remain.
Review details
Suppressed comments (2)
crates/outl-tui/src/icons.rs:14
- This catalog only covers the emoji/placeholder subset, but the TUI's own chrome still emits other icon glyphs directly:
view::chromeuses☑,●,⟳,◌, and⇇, whileview::outlineand the help text use▼/▶. With[tui] icons = "nerd-font", users therefore get a mixed icon style instead of the advertised routing of all TUI-owned icons through the selected set. Add roles for these glyphs and route their renderers throughIconSet, or narrow the feature/documentation to the subset it actually controls.
pub(crate) struct IconSet {
pub(crate) calendar: &'static str,
pub(crate) file: &'static str,
pub(crate) image: &'static str,
pub(crate) clock: &'static str,
crates/outl-tui/src/icons.rs:91
- The default emoji mode no longer preserves the existing
iso*command glyph:command_iconpreviously returned🔢, butIconSet::emojinow returns#. This changes the default rendering of/iso-dateand related commands despite the PR promising emoji compatibility; keep the emoji value here and reserve\u{f292}for the Nerd Font set.
hashtag: "#",
- Files reviewed: 39/39 changed files
- Comments generated: 0 new
- Review effort level: Lite
The nerd-font set only covered property/command glyphs, so a Nerd Font
user got ☑ ● ⟳ ◌ ⇇ and ▼/▶ chrome next to PUA chevrons — a mixed style
from the option that promises one coherent set. The chips, the fold
markers (now a fold_span accessor sharing glyph + colour) and the help
legend all read from the selected set; the emoji set keeps the exact
pre-PR glyphs.
Also fixes the emoji default: iso* commands rendered # where they used
to render 🔢. The hashtag role keeps 🔢 for emoji and \u{f292} for
Nerd Font. docs/tui.md now names what stays Unicode in both styles
(task checkboxes, calendar dots, scrollbar symbols).
|
Both suppressed comments are addressed in 02ddb7b. Chrome glyphs under
|
avelino
left a comment
There was a problem hiding this comment.
the shape is right: opt-in, emoji stays the default, docs updated, and right_segments_width stopped being a hardcoded 34 and started measuring with unicode_width. what blocks it is transcription.
the emoji set is not a copy of the literals it deleted. five glyphs change for people who never touched the config, and the test named emoji_preserves_the_pre_iconset_glyphs checks four fields, which happen to be the four that did not regress. it is the same class as the 🔢 you already fixed in the previous round, the fix just did not generalise to the rest of the table.
one more thing missing: a CHANGELOG.md entry under ## [Unreleased] / ### Added. every recent feat: has one, and the closest precedent is [tui] mouse_capture, which got its own.
| star: "⭐", | ||
| history: "🕘", | ||
| bolt: "⚡", | ||
| search: "🔍", |
There was a problem hiding this comment.
this drops 🔎. pre-PR overlays.rs used 🔎 (U+1F50E) for both the Search category and the search / find commands. self.search is 🔍 (U+1F50D), a different codepoint.
two more in the same table, both from routing to an existing field: command_glyph line 106 sends week* to self.calendar (was 📆), line 107 sends stamp to self.clock (was 🕒).
i grepped the workspace: 🔎, 📆 and 🕒 existed nowhere else, so they leave the repo entirely. the PR body says the emoji set keeps the exact pre-PR glyphs, and emoji is the default, so this lands on everyone.
| search: "🔍", | |
| search: "🔎", |
week and stamp need their own fields, or an explicit note saying the collision is intended.
| warning: "⚠", | ||
| save: "💾", | ||
| clipboard: "📋", | ||
| moon: "🌙", |
There was a problem hiding this comment.
💤 (overlays.rs:668, the snoozed-reminder chip) became 🌙. this one is not just a different codepoint, it reads differently: 💤 means snoozed, 🌙 reads as night mode.
| moon: "🌙", | |
| moon: "💤", |
| } | ||
|
|
||
| #[test] | ||
| fn emoji_preserves_the_pre_iconset_glyphs() { |
There was a problem hiding this comment.
the name says it preserves the pre-IconSet glyphs. the body checks four fields, and those four are exactly the ones that did not regress, so it goes green while the contract it names is broken. that is how the five above survived three review rounds.
assert every field of emoji() against the literal table you deleted, not a sample. this is a constant-extraction refactor: exhaustive is cheap here, and it is the only thing that catches a transcription typo.
| // model it. Restore it so a modal save can't wipe a hand-set icon | ||
| // style or mouse-capture toggle (the `into()` conversion leaves | ||
| // `TuiCfg::default()`, and a default here silently means "emoji"). | ||
| cfg.tui = on_disk.tui.clone(); |
There was a problem hiding this comment.
real fix, thanks. [snapshot] and [storage] have the identical bug though, and the comments at lines 194 and 197 already claim save restores them from disk.
Config has 12 sections, this function restores 6, Settings models 5. a hand-set op_threshold or lru_cap gets reset to the default on every modal save, silently, same as icons did.
| cfg.tui = on_disk.tui.clone(); | |
| cfg.tui = on_disk.tui.clone(); | |
| cfg.snapshot = on_disk.snapshot.clone(); | |
| cfg.storage = on_disk.storage.clone(); |
and extend save_restores_the_tui_section_the_desktop_never_models to cover them.
| ToastKind::Success => ("✓", Color::LightGreen), | ||
| ToastKind::Info => ("ℹ", Color::LightCyan), | ||
| ToastKind::Warning => ("⚠", Color::LightYellow), | ||
| ToastKind::Warning => (icons.warning, Color::LightYellow), |
There was a problem hiding this comment.
one of four arms routed. Success (✓), Info (ℹ) and Error (✕) stay literal right above and below, so with icons = "nerd-font" the same widget paints a PUA glyph on a warning toast and unicode on the next one.
either all four go through the IconSet, or warning comes back out and the toast stays unicode. the exception list in the icons.rs module doc names ✕ but not ✓ or ℹ, so as it stands the list and the code disagree whichever way you read it.
The snoozed-reminder chip rendered 🌙; upstream draws 💤 for it, so the Emoji set now matches (field renamed moon -> snooze to match upstream's own `snoozed` vocabulary, nerd variant nf-fa-bell-slash-o). All four toast accents (success/info/warning/error) route through IconSet now — only warning was, so Nerd Font mode still leaked three colour emoji. Each value matches upstream's toasts.rs literal byte for byte. Restores the issue outlmd#142 backlinks rationale comment and the workspace_root param name in runtime.rs (unrelated churn from the icon-style plumbing); the ratchet ceiling rises accordingly.
Saving the settings modal rebuilds `Config` from the flat wire shape and restored only some of the sections it does not model. `[snapshot]` and `[storage]` were missing, so a save silently reset the boot-cache policy and the op-log LRU cap to defaults — the same class of loss the `[tui]` and `[backup]` restores exist to prevent. Restore both from disk and cover them in the renamed save_restores_the_sections_the_desktop_never_models test.
|
@copilot resolve the merge conflicts in this pull request |
|
Merge done, thanks for the contribution! |
Problem
The TUI hardcodes emoji for its chrome icons. Emoji rendering varies by terminal and font, including color, width, and visual style, and some terminal users do not want emoji in a terminal UI.
There is currently no supported way to use a compact monochrome icon set instead.
Why Now
The TUI already has a broad icon surface across the header, sidebar, overlays, warnings, reminders, and placeholders. Adding the preference now prevents more icon usage from becoming hardcoded and inconsistent.
Solution
[tui] icons = "nerd-font"as an opt-in.This is a user-facing configuration improvement, not an exploratory RFC.
Verification
cargo fmt --allcargo test -p outl-config -p outl-tuicargo clippy -p outl-config -p outl-tui --all-targets -- -D warningsRUSTDOCFLAGS="-D warnings" cargo doc -p outl-config -p outl-tui --no-depsscripts/check-file-size.shReview follow-up (dfc861f, c07e79f)
All three Copilot findings are addressed:
[tui](dfc861f):restore_unmodeled_sectionsnow copieson_disk.tuiback, so a settings-modal save can no longer silently rewriteicons = "nerd-font"(ormouse_capture) to defaults. Pinned bysave_restores_the_tui_section_the_desktop_never_models.auto-run/runhardcoded▶(c07e79f):IconSetgains aplayfield (nf-fa-playU+F04B for Nerd Font,▶unchanged for Emoji);property_glyph("auto-run")andcommand_glyph("run")route through it. Pinned byplay_routes_through_the_icon_set.c07e79f): the icon style is now anApp::newparameter, so the parse-warning status chip is stamped with the configured set from the very firstload_current()and stays clearable by later reloads. Pinned byboot_stamps_the_warning_chip_with_the_configured_icon_set.The param addition pushed five test files over the file-size ratchet line, so
63195d2keeps them under it: tests construct throughApp::new_for_tests(same five-arg shape as before, emoji set pinned behind it), and the boot orchestrator calls the real constructor.Review follow-up 2 (02ddb7b) — suppressed comments
icons = "nerd-font": fair —☑ ● ⟳ ◌ ⇇(header/footer chips) and▼/▶(fold markers + help legend) were still literals. They areIconSetroles now; the markers render throughIconSet::fold_span(glyph + colour together). The emoji set keeps the exact pre-PR glyphs, anddocs/tui.mdnow names what deliberately stays Unicode in both styles: task checkboxes (☐/◐/☑mirror document state), calendar day dots, and scrollbar symbols.iso*commands rendered#instead of🔢in emoji mode: regression caught,hashtagis🔢for Emoji and\u{f292}only for Nerd Font. Pinned byemoji_preserves_the_pre_iconset_glyphs.Note on the macOS CI failure
the_cycle_dense_generator_rejects_far_more_moves_than_the_shared_one(outl-core/tests/convergence_property.rs, pre-existing onmain) is a statistical pin on a randomly-seeded proptest — it draws a fresh sample each run and has a narrow floor (≥180/200 rejections), so it flakes on unlucky seeds independent of any PR. This branch touches nooutl-corecode; re-running the job passes.