v0.1.0
The first release. Feature-complete against glow, plus the contents pane,
search, and a configuration file with rebindable keys.
Added
Rendering engine (src/render/)
-
Document: markdown parsed once and laid out at any number of widths, which
is what a reader that resizes needs. Opaque on purpose — it is the part of
the pipeline a consumer needs, without freezing the shape of what is behind
it. -
Markdown → fixed-width styled line buffer, built on
pulldown-cmarkwith
GFM tables, footnotes, strikethrough, task lists, alerts, and smart
punctuation enabled. -
Claude-artifact visual language: centered reading column with the page
background painted edge to edge, headings styled by weight/color/rhythm
instead of hash marks, rounded code containers with the language in the top
border, accent-bar blockquotes, box-drawn tables, real list markers with
hanging indent. -
GFM alert callouts (
[!NOTE],[!TIP],[!IMPORTANT],[!WARNING],
[!CAUTION]) as icon-and-title callouts, each in its own hue. -
Syntax highlighting via
syntect+two-face, mapped directly to ratatui
styles with the surface background forced so a syntax theme cannot leak its
own page color into a code card. -
Narrow-width fallback: tables become stacked
label: valuecards when a
framed layout would shred prose into unreadable columns. -
RenderedDoccarries the outline, interned links with their exact column
ranges, source byte ranges per line, and a plain-text mirror for search.
Sources and CLI
marquee-markdown [FILE|DIR|-]renders to standard output.- Source classification for all six argument forms (stdin,
-, forge
shorthands, URLs, directories, plain paths), kept pure behind anFsProbe
trait. - Directory arguments resolve a README case-insensitively in declared priority
order. - YAML frontmatter is stripped before rendering.
- Non-markdown files render as a syntax-highlighted fenced block with line
numbers forced on, matching glow. - Full glow flag surface parsed:
-a -l -m -n -p -s -t -w --config, with
-p+-trejected as a hard error. themes,man, andcompletion <shell>subcommands.- ANSI output with real OSC 8 hyperlinks; color and gutters degrade
automatically when the destination is not a terminal.
Full-screen reader (-t)
- Alternate-screen reader with a scroll-tracking status bar showing the
document, the section being read, and progress. - Every glow pager key, kept pointing at what it points at in glow:
j/k,
d/u,f/b,g/G,space, the arrow and page keys,q. Tswitches between the light and dark palette;?opens a key reference;
esccloses what is open rather than quitting.- Horizontal scrolling with
h/l, which is what makes-w 0usable. - Resizing or switching theme re-lays out and keeps the reading position,
carried by source byte offset rather than by line number. - Mouse wheel scrolling under
-m. -tinto a pipe or a file renders once instead of writing cursor movements
into it.
Contents pane and search
- A scroll-tracking table-of-contents pane beside the document, with folding
(h/l), movement on the same keys the document uses, andenterto go to
an entry. - Nesting comes from the order headings appear in, so a document that jumps
from#to###nests sensibly instead of producing an orphan. - The pane distinguishes the active entry, which follows the scroll
position, from the cursor, which is where the reader left it. Scrolling
never drags the cursor away and moving the cursor never pretends the reader
scrolled. - The pane hides itself on a terminal under 60 columns and on a document with
fewer than two headings, where it would cost a quarter of the screen to say
nothing. - In-document search on
/, withnandNto step through hits and a count
in the status bar. Lowercase queries ignore case; a capital makes the query
case-sensitive. - Hits are highlighted through a draw-time overlay, so searching never re-lays
out the document, and a background-only highlight keeps the syntax colors
underneath it. - Hits are re-found whenever the document is laid out again, so a resize or a
theme switch cannot leave the highlight pointing at a stale line. - A prompt captures every printable key: typing
qinto a search box types a
qrather than quitting the reader. escunwinds one layer at a time — overlay, then prompt, then focus, then
the search highlight — and hints rather than quitting at the bottom.
File browser
marquee-markdownwith no argument, or with a directory, lists the markdown
under it — most recently edited first, with the age of each file.- The walk streams from a background thread, so the first screenful appears
immediately and a large tree fills in behind it rather than blocking the
first frame. - Hidden and git-ignored files are left out unless
-aasks for them. Ignore
files are honored outside a git repository too. - Fuzzy filtering on
/, applied as you type. Query and file names are both
normalized first, so a name written with combining marks still matches one
typed with precomposed characters. enterreads a file andescgoes back to the list, so the browser is where
a reading session lives rather than somewhere passed through once. A file
named on the command line has no browser behind it, andescthere still
hints rather than opening one.- glow's paging inconsistency is reproduced deliberately:
f/dmove a whole
screen in the browser and half a screen in the pager, andh/lpage in the
browser but scroll sideways in a document. Anyone with the muscle memory
would find a silent correction more surprising than the quirk.
Remote sources
marquee-markdown github.com/owner/repo,github://owner/repo,
gitlab://owner/repo, and anyhttp(s)://URL.- Repository shorthands resolve through the forge's API to the raw README,
rather than fetching the page around it. GitLab'sreadme_urlpoints at the
page showing the file, so it is rewritten to the raw path; GitHub answers 403
without aUser-Agent, so one is always sent. - The extension in a URL is trusted ahead of the
Content-Typeheader, since
servers routinely hand out markdown astext/plainortext/html. A URL
with no extension served as HTML is shown as highlighted markup rather than
run through the markdown renderer. - Relative links resolve against where the body actually came from, after
redirects, rather than against what was asked for. - Bodies are capped at 8 MiB and requests time out after 20 seconds.
- Fetching is behind a
Fetchertrait with aFakeFetcherbeside it, so every
remote path is unit-tested with no network. Live checks against the real
forges exist as#[ignore]d tests intests/network.rs.
Live reload, links, and the rest of glow's keys
- The open document is watched and re-renders when it changes on disk. The
reader keeps the section they were in rather than the line number, because
an edit above them moves every line below it.rreloads by hand. - The watch is on the containing directory, not the file: most editors save by
writing a temporary file and renaming it over the original, and a watch on
the file itself survives exactly one save. ]and[step through the document's links,enteropens the selected
one, andycopies its address. Relative links resolve against wherever the
document came from.ccopies the markdown as written rather than as rendered — the source is
what a reader wants to paste elsewhere.- Copying goes through the terminal (OSC 52) before the system clipboard, so it
works over SSH: the text lands on the machine the reader is at rather than on
the server. In tmux this needsset -g set-clipboard on. eopens the document in$VISUAL,$EDITOR, orvi, at the line on
screen, and reloads when the editor exits. Editor settings carrying arguments
(EDITOR="emacsclient -nw") are split properly, and line arguments are
spelled the way each editor wants them; an unrecognized editor gets the path
alone rather than a flag it would take for a second filename.ctrl+zsuspends to the shell, on unix. The action does not exist on other
platforms rather than existing and doing nothing, so the key reference stays
truthful.-prenders through$PAGER, defaulting toless -R— without-Rit
would print the escape sequences as text.- Bursts of events are coalesced: dragging a window edge costs one re-layout
and one frame per batch rather than per event.
Configuration
~/.config/marquee-markdown/config.toml, with--configand
MARQUEE_CONFIGnaming a different file.- Precedence is defined in exactly one function: command line, then
environment, then file, then defaults. A switch that was not given
contributes nothing rather thanfalse, so a setting turned on in a file is
not undone by every invocation that omits the flag. [keys.<mode>]rebinds any key in any mode, and an action ofnonetakes a
key away.- Unknown settings, keys, and actions are reported and ignored rather than
refused: a file written for a newer version has to keep working with an older
binary, and one typo should cost one key rather than the whole keymap. A file
that is not valid TOML is still an error, because that is a mistake rather
than version skew. marquee-markdown configprints the settings in force as a file that would
produce them — the only practical way to answer "why is this setting what it
is?" — and the output round-trips.marquee-markdown keysprints every binding as markdown;
docs/KEYBINDINGS.mdis generated from it and a test fails if it drifts.-n(--preserve-new-lines) is now honored, closing the last flag that was
parsed but ignored. A document written one sentence per line keeps its shape
instead of being re-flowed.
Theming
- Two Claude palettes,
paper(light) andslate(dark), compiled in as
constants. - Themes are also a data format:
Palettedeserializes from TOML, so a theme
can be dropped into~/.config/marquee-markdown/themes/or passed to
--styleas a path, with no recompile. Built-ins and user themes are built
through the same constructor. --style auto(default), plusnottyfor unstyled output.
Two names
mmdis installed alongsidemarquee-markdownand is the same program.
Both binaries are stubs over one implementation, so they cannot drift.- The generated man page and completions are named after whichever name was
invoked: completions registered for a name the reader does not type would
simply never fire.
Release engineering
- CI across Linux, macOS and Windows: format, clippy with warnings denied, the
test suite,cargo docwith warnings denied, a build at the minimum
supported Rust version read from the manifest,cargo deny, and a check that
the packaged crate's own tests compile. - A smoke-test job that runs the binary, because several bugs here were
invisible to the whole suite and obvious on first use — piped stdin, closed
pipes,-n, and every generated surface. - A release workflow producing archives for four targets, Debian and RPM
packages, checksums, and release notes taken from this file. Man pages and
completions are generated by the binary into every archive rather than
checked in, so they cannot drift from the flags it accepts. docs/ARCHITECTURE.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,
SECURITY.md, and issue templates.- Homebrew and Scoop packaging templates.
Fixed
- The minimum supported Rust version was wrong: the manifest claimed 1.85 while
the code uses let-chains, which need 1.88. Anyone on 1.85 to 1.87 would have
hit a compile error rather than a clear message. CI now checks it, reading
the version from the manifest so the promise and the test cannot drift. marquee-markdown completion <shell> | headaborted with a panic instead of
stopping quietly:clap_completepanics internally on a write error, and
print!panics on a closed pipe. Every subcommand now writes through one
fallible path, so closing a pipe means what it is supposed to mean.docs/was excluded from the published crate while a test read a file from
it, socargo teston the published package would not have compiled.- Two advisories against
quick-xml, reached throughplistthrough
syntect. Neither is reachable from this program — syntax and theme data
come from binary dumps, not XML — but rather than argue that,syntectis
now built with exactly the features used, which takesplistand
quick-xmlout of the dependency tree altogether. - A named source is no longer ignored in favour of standard input. With
input redirected — a cron job, a CI step, an editor shelling out —
marquee-markdown notes.mdrendered whatever was on stdin instead of
notes.md, or nothing at all when stdin was empty, with nothing on screen to
say so. Standard input is now read when it is the only thing on offer, or
when-asks for it. This is a deliberate divergence from glow, whose rule
is fine when a person types the pipe and quietly wrong everywhere else. - The file browser's paths are spelled with the platform's own separator; the
tests asserting them assumed/and failed on Windows.
Behaviors that differ from glow, verified against glow 3.0.0:
- Long lines inside a fenced block stay inside the container. In glow they wrap
out of it. - Lines containing links keep their width. Glow counts OSC 8 escape bytes as
display columns, which leaves link-bearing lines ragged. Here escape
sequences are structurally unrepresentable in the layout path, so no code
path can count one as a column. - Headings, blockquote bars, tables, and thematic breaks render as typography
rather than as#,|, and--------.
Known gaps
-
Callout and image icons are Nerd Font glyphs; a terminal without one shows a
missing-glyph box. They should be part of the theme format. -
On macOS, file system events are reported per directory, so saving a
different file beside the open one can trigger a redundant reload. The
document is re-read rather than shown wrongly. -
A fetch failure at startup exits with a message rather than opening the
reader with an explanation in the status bar. -
Opening a link hands off to the system handler; a link that is relative to a
fetched document is resolved by joining, without normalizing... -
ctrl+zneeds a shell with job control, as any suspend does. -
A fetched document cannot be reloaded or opened in an editor, because it has
no path on this machine. -
The browser scans once at startup: files created while it is open do not
appear until it is restarted. -
-ais read at startup and cannot be toggled from inside the browser. -
Search matches the rendered text, so a phrase broken across a soft wrap does
not match. Searching is deliberately not incremental: the query runs when
enteris pressed. -
The key reference does not scroll, so on a terminal shorter than about 22
rows the last few bindings are cut off. -
Resizing re-lays out on every event; a large document dragged by a window
edge will work harder than it needs to until a debounce lands.