Repository navigation
feat(schedule): change the font on a presence-gated timer - #25
Conversation
lib.sh and storage.sh each defined detect_platform, and every entry point sources storage.sh after lib.sh, so lib.sh's copy never ran. The two had already drifted: one read /etc/os-release and the other answered "linux". terminal.sh branches on the platform, so it now sources storage.sh itself, behind a source guard like lib.sh's.
…lizer get_history normalizes every row's machine, stripping the legacy macos-, archlinux-, arch-, wsl-, linux- and unknown- prefixes, while get_machine_id returned the raw hostname. Sync compares the two by equality: a host named linux-box reads its rows back as "box", so a push sent none of them and a pull dropped them from the local history. The id now goes through the same normalizer, so writes, comparisons and the gist filename agree. It moves into storage.sh beside that normalizer.
Usage time ended an apply at the next apply in the merged history, so a font on one machine stopped counting the moment another machine changed its own font. On the real history Monaspice Ne reads 147 days rather than 40, and Roboto Mono 134 rather than 56. The four readers - calculate_usage_time, get_font_usage_stats, get_font_monthly_usage_map and get_stats_machine_favorites - each carried their own copy of that timeline. They now share one apply_spans definition that groups applies by machine, so they agree by construction. Only this machine's latest apply runs to now. Every other machine's latest is left open, because nothing here knows when it ended. Sessions, which collapse consecutive applies of one font, are counted per machine. parse_ts lived in six programs. It lives in the same definitions.
…hedule log_action takes an optional trigger and writes it only when given. apply_font_cmd writes "manual", and a row without the key reads as manual, since every apply before it was typed by hand. A scheduled apply also skips the backgrounded push, because its unit would end it. A scheduled apply runs with no terminal, so its row carries the machine and platform and none of the terminal fields, which would otherwise read "unknown" and outvote the terminals the font was really used in. Storage now writes machine and platform on every row itself, and the terminal context carries only what a terminal reports. The per-font page's last session skips applies with no terminal. font log names the schedule where a terminal would be, and the per-font history says "applied by the schedule".
datapointchris
left a comment
There was a problem hiding this comment.
PR review
Used: read every test the diff adds or edits, and every existing test of code the diff changes, beside that code. Ran bats tests/ on the branch in the suite's own sandbox: 57 of 57 pass.
Standards: testing.md § "Test behavior the user cares about, not what this commit happened to change"; testing.md § "Never assert on rendered output — assert the value it was built from"; pull-requests.md § "Tests the diff adds are checked for whether they test the program or the change".
Findings: 5 design
Kept: all three tests in tests/schedule.bats pin behavior a user reaches. They cover the hold each kind of apply earns, the presence gate, and the turn counts font rank prints. Both edited or added tests in tests/stats.bats stay too. One pins per-machine sessions. The other pins that a timer apply never outvotes a terminal. stats.bats "font rank sorts by likes and by hours apart, over curated unrejected fonts" reads rendered rows. It stays, because show_rankings applies the registry and rejection filter, and nothing else pins that filter.
Design and architecture
-
tests/help.bats— five tests assert that literal help rows and headings fromcommands_tableandusageappear in the output. Delete each one:- "the help screen lists every verb the table carries"
- "a flat verb gets its own synopsis, not the whole screen"
- "a verb with several rows prints all of them"
- "an unknown verb falls back to the full screen"
- "font help still renders the commands and the examples"
Every string they match is a heredoc literal in
bin/font. Reach: this diff adds fourschedulerows and rewrites therankrow, and the next rewording of therejectorsyncrows fails three of these tests while nothing is broken. The one piece of logic on that path isverb_usageselecting a verb's rows. Replace all five with one tree-walk invariant. For each verbhelp_screen_verbsyields, every linefont <verb> --helpprints that starts withfontnames that verb. The walk fails whenhelp_screen_verbsyields nothing. That guard is the one thing the first and last tests do for the walks, which otherwise pass on zero iterations. -
tests/flag-arguments.bats— "a font-name check separates the pattern from grep's own flags" runsgrep -Fxq -- "--help"in a barebash -c. Delete it. It tests GNU grep's documented--handling and never runsbin/font. Reach: the diff rewrites theapplyvalidation block it claims to cover, and the test would still pass with the--removed frombin/font. Replace it with this behavior test: against a fixture registry,font apply -qexits 2 and writes no history row. That runs the program's own check. -
tests/help.batsandtests/selection.bats— two tests duplicate one another test each. Delete both:help.bats"font apply --help does not reach the font-name check". "every verb on the help screen answers --help without acting" already runsapply --help. It checks exit 0 and that no history was written, and that covers both ways the old bug showed.selection.bats"compute_font_weights carries a name containing spaces through intact". "compute_font_weights gives a never-applied font the capped weight" already asserts the whole lineUnseen Mono<TAB><weight>, which pins a spaced name through the tab.
Reach: the diff moves
compute_font_weightsonto the shared_JQ_TIMELINE_DEFSand addsscheduleto the verbs the walk covers. Each future change there pays to read two tests that cannot fail apart. -
tests/list-status.bats— "rejected is not a verb" pins the removal of a verb that no longer exists. Delete it. It only makes sense to someone who knowsfont rejectedonce existed. Reach: the diff edits it toassert_failure 2. That edit is the only place the suite pins the new exit 2 for an unknown command. The test is named for the old verb, so the next sweep of transition tests takes the exit-code check down with it. Replace it with this behavior test:font nonsenseexits 2, and its error goes to stderr with stdout empty. Userun --separate-stderr. -
tests/sync.bats— "the filename carries this machine's id" assertshistory-$(get_machine_id).jsonl. That is the body of_sync_history_filenamecopied into the test. Delete it. Reach: the diff movesget_machine_idintostorage.shand routes it through_JQ_NORMALIZE_MACHINE. This test passes whateverget_machine_idreturns, so it cannot catch the bug that rewrite fixes. Replace it with this behavior test: withunamestubbed to printLinux-Box, a rowlog_actionwrites is a row_sync_own_records "$(get_machine_id)"returns. No other test covers a host name the normalizer changes.schedule.batsstubstestbox, which the normalizer leaves unchanged.
This review does not gate merge.
datapointchris
left a comment
There was a problem hiding this comment.
PR review
Used: I built a copy of the branch in a scratch directory and seeded .dev-data there with scripts/seed-dev-data.sh. I ran font --help, schedule --help, schedule status, rank, stats, log, install --check, and the usage-error paths (schedule bogus, apply, apply Nonexistent_Mono, apply hack, note, sync bogus, bogus). I ran schedule_run twice with no terminal in the environment, with terminal_apply_font and the sync stubbed. bats tests/ passed 57 of 57. I did not run schedule on or off, because both write the real user unit directory.
Standards: help.md § "A failure with two causes is attributed before either one is blamed". pull-requests.md § "Sentences the diff adds are checked for what goes stale, and the finding carries the rewrite". Searches for a rule on code copied between two repos found none.
Findings: 2 correctness · 1 design · 1 prose
Correctness
-
bin/font:814(cmd_font_last),lib/storage.sh(get_recently_used):font lastcan bring back another machine's font. A user runs it to undo a font the schedule chose, and gets a font that was never on this screen.get_recently_usedreads the merged history from every machine. This branch moves usage hours, turns and the schedule's own decision onto the per-machine timeline, on the stated grounds that "another machine's apply says nothing about this screen".font laststays on the merged timeline. Reach: two machines with sync on, either running the schedule. Each check pulls first and pushes after, so every scheduled apply on one box lands in the other's history within minutes. I ran this against the seeded history. This machine's last two applies were Hack (by hand), then Comic Mono (scheduled). I added one scheduled desktop apply a minute later.get_recently_used 2returned[["desktop","Fira Code Nerd Font"],["archlinux","Comic Mono Nerd Font"]]. With Comic Mono on screen,cmd_font_lasttargets.[0], which is Fira Code. The README promises "back to the font applied before this one", which here is Hack. Fix: select.machine == $machinefromget_machine_idinget_recently_used, asget_machine_last_applyalready does. Then rewriteREADME.md:128to say "lasttoggles between the two fonts most recently applied on this machine." -
lib/schedule.sh:19-20,:250,:454-455: dev mode and the installed copy share one timer, andstatusthen names the wrong cause.font schedule onin a checkout writesfont-schedule.servicewithExecStartpointing at the checkout andFONT_ENV=development. That overwrites the installed copy's unit, because the unit name and the launchd label have no mode in them.font schedule offin the checkout deletes the installed copy's timer. The checkout's timer writes.dev-data/schedule.json, so the installed copy'sstatusprintsStatus: On ✓ (timer active), followed by "No check for …, so the timer is not running." Once the worktree is removed,ExecStartpoints at nothing and every run fails before_schedule_record.statuskeeps blaming the timer while it reports the timer as active. Reach: the next change to the schedule, checked the way this PR was (font schedule on,statusandoffin dev mode).CLAUDE.mddocuments that as a supported flow. Not run, becauseonwrites the real unit directory. Read from_schedule_write_systemdand the quiet check inschedule_status.help.md§ "A failure with two causes is attributed before either one is blamed" covers the status line. Fix: put the mode inFONT_SCHEDULE_UNITandFONT_SCHEDULE_LABELwhen_is_dev_mode, for examplefont-schedule-dev. Before blaming the timer,statusshould also read the service's last result (systemctl --user show font-schedule.service -p Result -p ExecMainStatus, or the last exit status fromlaunchctl print). When the timer is active and the service failed, say the run failed and point atjournalctl --user -u font-schedule.service.
Design and architecture
-
lib/schedule.sh,lib/storage.sh,lib/sync.sh: about 460 lines of schedule code are copied from theme, and the coupling is enforced only by a sentence. Reach: the newCLAUDE.mdsays "A fix to one is due in the other", so every later fix to these files has to be made twice. This branch is already the second copy of three theme fixes:- the machine id read through the normalizer
- the per-machine timeline
- the waybar restart through
start_session_daemon
Correctness finding 2 is due in theme as well, since theme's unit is a fixed
theme-schedule. The copies have started to differ. Theme restarts dunst throughstart_session_daemon, and font relies on D-Bus activation. The two-minute offset is a contract between the two tools, but only font records it, inFONT_SCHEDULE_CHECK_OFFSET_MINUTES. Theme's five-minute marks are not named anywhere font can read them. The fleet already ships a shared bash library this way,bashselfupdate, which is loaded from~/.local/liband optional at source time. A schedule library on that pattern could own presence detection, unit and agent writing, outcome recording, the cgroup escape and each tool's check offset. Each tool would supply its draw and its history reader. Without it, each fix is a pair of PRs, and the next difference between the copies is found only when it breaks.
Prose
- Three sentences carry a count or a derived number that stands without it. Each goes stale on the next change to what it counts.
bin/font:76: "sosyncprints its six and a plain verb prints its one."schedulenow prints four, and this sentence has to be edited each time a subcommand is added. Rewrite: "Every row whose first word is the verb, so a verb with subcommands prints one row for each and a plain verb prints its own."CLAUDE.md:39: "A timer run has no terminal, which shapes three things." The list follows directly, and a fourth item makes the count false. Rewrite: "A timer run has no terminal."README.md:258: "statuswarns when no check has run for fifteen minutes." The number isFONT_SCHEDULE_CHECK_MINUTES * 3, which nothing names. Rewrite: "statuswarns when no check has run for three check intervals."
This review does not gate merge.
datapointchris
left a comment
There was a problem hiding this comment.
PR review
Used: Ran font schedule status, on and run in a sandbox HOME, with systemctl, loginctl, hyprctl, pkill, pgrep and gh stubbed. The runs covered the applied, waiting, away and blocked outcomes. Also ran font rank, font current, font log, the font info preview script, and bats tests/ (57 pass).
Standards: documentation.md § "A repo is a product for a stranger; the fleet is one deployment of it" and help.md § "Never write a sentence a later release will make false" are cited below. cli-design.md § "Exit codes are 0 success, 2 usage error, other non-zero for failure" and infrastructure.md § "Scheduled work reports its own outcome" were checked, and the branch meets both.
Findings: 2 correctness · 1 design · 1 prose
Correctness
-
lib/terminals/ghostty.sh:87— A scheduled change never reaches an open Ghostty window.ghostty_applywritesfonts/current.confand signals nothing. A hand apply prints "Restart terminal to see all changes", but the timer has nobody to tell. So the old font stays on screen until a new window opens. Meanwhilefont currentin that window reads the config file and names the new font.font rankcounts hours and a kept or switched turn for a font nobody saw. Those are the numbers this PR adds to judge the schedule. The body rules a reload out with "Ghostty 1.3.1 reloads its config only by keybind or restart". On Linux that premise is false. The installed Ghostty 1.3.1 binary carries the GTK application's log linereceived SIGUSR2, reloading configuration. Reach: every scheduled apply on a Linux machine with a Ghostty window open. Ghostty is the terminal on 272 of the 335 apply rows in this machine's history (read only, nothing written). On macOS reach isunproven — whether the macOS app also reloads on SIGUSR2. Fix: endghostty_applywithpkill -USR2 -x ghostty 2>/dev/null || trueon Linux, the waykitty_applysends USR1. Then rewrite the two sentences that describe the gap. They areREADME.md:260("A scheduled change reaches Ghostty at its next config reload or in a new window, the same as a font applied by hand") andbin/font:110("Ghostty shows a new font once its config reloads or in a new window"). -
bin/font:706—font inforeports the font on screen as unused. The preview scripts atbin/font:706(font info) andbin/font:787(font change) sourcelib.shandstorage.shbut notterminal.sh. Soget_current_fontis undefined inside the preview,$currentis empty, andapply_spanssets the open apply's seconds to null. Reach: picking the font on screen infont infoorfont change. I seeded a five-hour-old apply of JetBrains Mono with Ghostty's config naming it.font currentprintedUsage time: 5h. Thefont infopreview script printedUsage time: not usedandSessions: 1 (avg 0s). The branch did not cause this, butlib/storage.sh:81now claimsfont rank,font statsand the per-font page "agree to the second". Fix: sourceterminal.shin both preview scripts. It pulls inlib.shandstorage.shthrough their guards.
Design and architecture
lib/schedule.sh,CLAUDE.md:41— The schedule is a near-copy of a sibling tool's, and the repo now names that tool to keep the copies aligned.CLAUDE.md:41saysschedule.sh,storage.shandsync.sh"are near-copies of theme's. A fix to one is due in the other".lib/schedule.sh:12builds a constant on "theme's timer checks on the five-minute marks". That breaks documentation.md § "A repo is a product for a stranger; the fleet is one deployment of it", whose own text says nothing committed may name "a sibling tool". Reach: the rule is its own reach, and the repo's sentence names the next change. Every later fix to presence detection, unit writing or status is due twice. The copies already drift within this branch. Diffing the twoschedule.shfiles turns up two fixes that exist only here. One is_schedule_mtimefor BSDstat, where theme still callsdate -r. The other is the dev-modeFONT_ENVwritten into the unit. Fix: move the shared half (presence, the unit and plist writers, the state file, status) into one library both tools load, the waybin/fontalready loads bashselfupdate fromXDG_LIB_HOME. If the copy stays, keep the lesson and drop the name. For example: "Another tool restarting waybar on the five-minute marks would race this one, so checks run two minutes later."
Prose
README.md:82,lib/arch-apps.sh:83,README.md:258,bin/font:76— Four sentences carry a version or a number that the next change makes false. Under help.md § "Never write a sentence a later release will make false":README.md:82: "because Waybar 0.15 can crash on its reload signal". Rewrite: "because Waybar's mpris module can crash on its reload signal."lib/arch-apps.sh:83: "When playerctld starts after waybar 0.15, its mpris module leaks a player". Rewrite: "When playerctld starts after waybar, its mpris module leaks a player".README.md:258: "statuswarns when no check has run for fifteen minutes". This repeats three timesFONT_SCHEDULE_CHECK_MINUTES, andfont schedule --helpalready interpolates the constants. Rewrite: "statuswarns when three checks in a row are missing."bin/font:76: "sosyncprints its six and a plain verb prints its one". The branch edited this sentence. Rewrite: "so a verb with subcommands prints every row it owns, and a plain verb prints its one."
This review does not gate merge.
PR review — mergedThree reviewers ran: two general and one test reviewer. This comment unions reviews 5561399951, 5561400385 and 5561383426. The author's session launched the round. Each reviewer ran blind to the others. Used: every reviewer ran Correctness
Design and architecture
The test reviewer kept these tests, because each pins behavior a user reaches:
Prose
This review does not gate merge. 3 rule(s) proposed, filed into the fleet inbox as proposals. |
…ling it A SIGUSR2 reload segfaults waybar 0.15 when playerctld started after it: the mpris module leaks a player whose handlers outlive the module the reload destroys. A restarted waybar holds no leaked player, so the font apply restarts it. The restart goes through start_session_daemon, Hyprland's exec dispatcher first and a transient systemd-run scope otherwise. An apply run from a systemd service would otherwise leave the new bar in the service's cgroup, which systemd kills when the run ends, setsid or not. dunst needs no such help: D-Bus starts it again through dunst.service.
font schedule on installs a systemd user timer or a launchd agent that checks every five minutes, at two minutes past each five-minute mark. Another tool restarting Waybar on the marks would otherwise race it. A change is due six hours after a scheduled apply and twelve after a hand apply, counted from this machine's own last apply row. It waits while the session is idle or locked, and draws with random_font. Each check records its outcome in schedule.json, read back off the history row the apply wrote. font schedule status prints it beside the timer's state. A scheduled apply pushes its row in the foreground, so the oneshot's cgroup does not take the push down with it. random_font falls back to this machine's last apply row when no terminal names a current font, which is the case under a timer with no terminal attached.
font rank lists, under its two rankings, how often each ranked font the schedule applied kept the screen until the next scheduled change, and how long it lasted before a hand apply replaced it otherwise. A turn ends at the next apply of another font on the same machine. A re-apply of the same font is part of the session it continues, and another machine's apply says nothing about this screen. A turn still running is not counted. The dev fixture carries two days of scheduled applies so the section renders in dev mode.
An unknown command, an unknown sync subcommand, apply with no name or with a name the registry does not hold, and note or reject with no message now exit 2, so a caller can tell a mistyped command from one that ran and failed. Each message goes to stderr. note and reject check their message before reading the terminal, so a missing message is reported as one even where no terminal is detected. An unknown font with no near match exited 1 and printed nothing: the suggestion lookup's grep found no line, and pipefail ended the script. It now prints the error and the pointer to font list. A bare font sync prints its subcommands and exits 0, as font schedule does.
An apply wrote Ghostty's font file and signaled nothing, so an open window kept the old font until it reloaded by hand or a new one opened. A scheduled change then counted hours and a turn for a font nobody saw. On Linux, ghostty_apply and ghostty_set_size now run the reload-config action Ghostty's GTK app exports on the session bus, and an open window redraws in the new font. A bus call fails harmlessly where the action is missing, which SIGUSR2 does not: it ends any Ghostty built without a handler. The call is skipped when no Ghostty runs, since calling its bus name would start one. macOS has no session bus, so there apply still tells the user to reload Ghostty, and nowhere else.
The preview scripts of font info and font change sourced lib.sh and storage.sh but not terminal.sh, so get_current_font was undefined there. The open apply of the font on screen then had no end, and the preview reported it as not used while font current gave its hours. Both now source terminal.sh, which loads the other two through their guards.
font last read the merged history from every machine. With sync on, a scheduled apply on another machine reaches this one within minutes, so undoing a font the schedule chose here could bring back a font that was never on this screen. get_recently_used keeps only this machine's applies, as usage hours, turns and the schedule already do.
The comments and CLAUDE.md explained the two-minute check offset and the turn rule by naming another tool's timer. A reader of this repo has no such tool, so each now states the race itself: a second waybar restart at the same moment can leave two bars. The waybar comment drops a version pin the crash is not limited to, and verb_usage's comment no longer counts sync's subcommands.
…stop A dev-mode `font schedule on` wrote the same unit as the installed copy. It replaced the real timer with one pointed at the checkout, and a later `schedule off` from either side removed the other's. In dev mode the unit is font-schedule-dev and the launchd label ends in -dev, so the two never touch. `schedule status` said a quiet timer was not running even when it fired every five minutes and each run died before it could record a check, such as when the program the unit names was deleted. It now reads the timer's last trigger from systemd, or the job's last exit code from launchd. A run inside the window means the runs are failing, and the message points at the journal or the log that holds their errors.
help.bats named five verbs and their help literally, and asserted -h separately. One test now reads the verbs off the help screen and checks each answers --help and -h with its own rows, without acting. A verb added later is covered without a new test. A mistyped command is pinned to exit 2 with its error on stderr and nothing on stdout. The grep-pattern unit test becomes `font apply --version`, which a user can type. It exits 2 and logs nothing. The sync filename test becomes a host named Linux-Box finding its own rows when it pushes. Deleted: the duplicate weights test for a name with spaces, and the test that `rejected` is not a verb, which the walk covers.
main() matches -h and --help in one case arm, so running -h for every verb on the help screen repeated one check per verb. The walk keeps --help per verb and compares -h against it for reject alone. With the -h arm removed, the test still fails: reject takes "-h" as its reason.
debcde4 to
0448a9b
Compare
font changes the font only when asked, so the fonts
font randomweighs toward get time on screen only when someone remembers to run it. This addsfont schedule, a timer that changes the font while you are at the machine. It also adds what the history needs to judge the result: atriggeron every apply row, usage hours counted per machine, and each font's scheduled turns infont rank.What to look at
lib/schedule.sh(0ccfd50).schedule_decideandschedule_rundecide from this machine's latest apply row, and the outcome is read back off the history row the apply wrote. Check that a check which wrote no row can never recordapplied._JQ_TIMELINE_DEFSinlib/storage.sh(245a145). Every reading of usage hours goes throughapply_spans. Check that another machine's latest apply never runs to now.log_action(11da426). A scheduled row carries machine, platform, ts, font, action and trigger, and no terminal fields. Check every reader of.terminalagainst a row without one.ghostty_reloadinlib/terminals/ghostty.sh(f30b28c). Check it can never start a Ghostty that was not running.start_session_daemoninlib/arch-apps.sh(f1a202d). Waybar has to restart outside the unit's cgroup, or systemd kills it when the oneshot exits._schedule_explain_quietinlib/schedule.sh(316d709). Check that a stopped timer and a run that fails before recording each get their own message.get_schedule_turnsinlib/storage.sh(9983162). A turn ends at the next apply of another font on the same machine.How it was verified
bats tests/: 49 tests pass. Summed per-test time was 6.1 s and 6.3 s on two runs. Main's 53 tests summed 6.4 s and 6.2 s, run alternately with the branch. The branch's wall time was 11.1 s and 12.5 s on two runs, so it moves with load on the box.font schedule runran three times as a real systemd user oneshot in dev mode.With
WAYLAND_DISPLAYandHYPRLAND_INSTANCE_SIGNATUREunset, it recordedblockedand the unit exited non-zero.With the display session's logind IdleHint set, it recorded
away.With a change due, it applied JetBrains Mono Nerd Font to Ghostty, kitty, Waybar, hyprlock and dunst. Waybar was still running in the session scope afterward. The row it wrote, with the machine and timestamp elided:
{"machine":"…","platform":"archlinux","ts":"…","font":"JetBrains Mono Nerd Font","action":"apply","trigger":"schedule"}font schedule on,statusandoffin dev mode, from a copy of the checkout.onwrote onlyfont-schedule-dev.serviceandfont-schedule-dev.timer, withFONT_ENV=developmentin the service.bin/fontdeleted, the timer fired and the service failed withstatus=203/EXEC. Given a twenty-minute quiet spell holding that trigger,_schedule_explain_quietprintedNo check recorded for 20m, though the timer runs: each run fails first.and namedjournalctl --user -u font-schedule-dev.service.No check for 20m, so the timer is not running.offleft no unit files.Ghostty on Linux, with a window open:
ghostty_applyredrew the window in the new font, seen in a screenshot. The same D-Bus call by hand printed()and redrew it too.The
font infopreview, on a five-hour-old apply of the font Ghostty's config names, printedUsage time: 5handSessions: 1 (avg 5h). Before 82e1175 it printedUsage time: not usedandSessions: 1 (avg 0s).Usage hours on a real multi-machine history, before and after the per-machine timeline:
font apply,font apply "Nonexistent Mono",font note,font reject,font sync bogusandfont boguseach exit 2 with nothing on stdout. A barefont syncprints its subcommands and exits 0.Four tests were proved able to fail by undoing the fix each pins, then restoring it:
--removed from the name check:font apply --versionvalidated.get_recently_used: another machine's font came back.get_machine_id: a host namedLinux-Boxlost its own rows.-hremoved from main()'s help arm:font reject -hrejected the font with-has the reason.The previous release's footer, rendering a font whose latest apply was scheduled, shows
Terminal: unknownunder Last Session. This branch shows the latest apply typed in a terminal:Terminal: kitty, its size and its font size.Not established: whether a running kitty re-renders a changed
font_familywhen its config reloads. kitty was sent the reload and its config held the new font. kitty's documentation lists the options a reload cannot change, andfont_familyis not among them.What changes
font schedule on|off|status|run. A systemd user timer on Linux or a launchd agent on macOS checks every five minutes, at two minutes past each five-minute mark. A change is due six hours after a scheduled apply and twelve after a hand apply. It waits while logind marks the display session idle or locked, or on macOS until there has been input in the last ten minutes. WSL is refused.font-schedule-dev, beside the installed copy's. Turning either on or off leaves the other alone.font schedule statustells a stopped timer from runs that fail before they record. The second case names the journal on Linux, orschedule.logon macOS."trigger":"manual"or"trigger":"schedule". A row without one reads as manual.font rank,font stats,font currentandfont infoend each apply at the next apply on the same machine. On a history spread across machines they move by weeks, as the table above shows.font lasttoggles between the two fonts most recently applied on this machine.font infoandfont changepreviews count the time of the font on screen.font ranklists each ranked font's scheduled turns under its two rankings: kept out of turns, switched, and the median time before a switch.reload-configaction on the session bus. On macOS Ghostty shows the font after a config reload or in a new window, and a hand apply prints that.font applywith an unknown name and no near match exited 1 and printed nothing. It now prints the error and points atfont list.Decisions, and what they rejected
get_last_terminal_contextand the per-font terminal mode skip rows without a terminal.ghosttyprocess exists, because calling its bus name would start one.Risk and rollback
Merging cuts a release, and each machine takes it on its next update. A revert leaves a timer on every machine that ran
font schedule on, so runfont schedule offthere first. Rows already written keep theirtrigger, which the previous release does not read. That release showsTerminal: unknownunder a font's Last Session when the font's latest apply was scheduled.What this does not do
The review
#25 (comment) — 4 correctness, 6 design, 1 prose.
Correctness
reload-configaction rather thanpkill -USR2, for the reason under Decisions.Design and architecture
font apply --version, another of grep's flags, where the finding proposed-q.Prose
README.md:82in f30b28c,README.md:258in 316d709, and the other three in 4d2953c.