NO_COLOR and accessibility
How flagstaff decides colour and redraws — NO_COLOR, FORCE_COLOR, --color, CLI_ACCESSIBLE — and what a screen-reader user gets.
flagstaff decides nothing about the terminal itself. Two questions — may I redraw, and may I
colour — are answered by roundel's output policy, the
one every package in the family asks. Its rules are in
policy.ts
and pinned by policy.test.ts.
Redraws: CLI_ACCESSIBLE
A screen reader reads a redraw as a stream of fragments. Set CLI_ACCESSIBLE=1 and the policy
chooses the accessible mode ahead of the terminal check: every component hoisted with
hoist() prints its static projection once per state and never
redraws — on a terminal as well as off one. A box is its title and text, a table is
header: value lines, a progress bar is 12/30 files · 40%.
… building
… linking
✔ built--json still wins over it: a program asked for structured output gets structured output.
An empty CLI_ACCESSIBLE= counts as unset, the way NO_COLOR does.
The drop-ins are the exception, on purpose: they behave as their incumbents do.
flagstaff/ora imports roundel's chalk and never the policy, because a façade that
reinterpreted its host would fail the host's own suite — so on a terminal CLI_ACCESSIBLE=1
does not stop it animating, where CI=true does, because that is ora's own rule. hoist() is
what honours the switch.
Colour: NO_COLOR wins
flagstaff paints its glyphs through roundel's tokens, and a token is plain text until the
program calls fly() from roundel/theme to decide a colour level. Once it has, the level is,
first match wins:
- none under
--json, whenNO_COLORis set to anything but empty, or in accessible mode unless colour was asked for; - what was asked, when
FORCE_COLOR,--coloror--no-colorasked — the level named, or the terminal's own level and never less than basic colour when none was named; - none in a pipe nobody asked to colour;
- the terminal's own, from
TERMandCOLORTERM, or a known CI runner's.
The full order, edge cases included (TERM=dumb, Azure Pipelines), is colorLevel in the
same file.
So NO_COLOR beats an explicit --color:
import { hoist } from 'flagstaff/loop';
import { spinner } from 'flagstaff/spinner';
import { fly } from 'roundel/theme';
import { json, rt } from './rt.mjs';
fly({}, { ...rt, argv: process.argv }, { json });
hoist(spinner(), rt, { text: 'building' }, { json }).lower({ text: 'built', status: 'ok' });… building
✔ builtColour is never how a state is told apart: ok and fail are ✔ and ✖ before they are
green and red, so the text form carries the whole meaning with colour off.
What a screen-reader user can rely on
- No redraws with
CLI_ACCESSIBLE=1, from anything hoisted on the loop. - No cursor movement, erase or carriage return from
hoist()off a terminal, in any mode (loop.test.ts). - A text form for every component: a plugin without one is refused at registration (Plugins).
- The cursor is shown again after
lower(), and afterSIGINT,SIGTERMorSIGHUP.