Skip to content

fix: time, bounds, and the day that must stay dark - #15

Merged
vyncint merged 4 commits into
mainfrom
fix/tracker-time
Aug 19, 2026
Merged

fix: time, bounds, and the day that must stay dark#15
vyncint merged 4 commits into
mainfrom
fix/tracker-time

Conversation

@vyncint

@vyncint vyncint commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Thirteen findings from a full read of 0.2.0, filed as #2#14 and fixed here. The
thread through most of them is that "today" was ambient rather than an input,
which is why a test could rot, a report could call a dangerous day free, and
docs/ART.md could not reproduce its own example output.

The one that matters most

A day inside the text block that is not part of a letter has to stay empty,
and a contribution on it is the only loss nothing takes back. Those days were
dropped from the plan for having nothing to report (src/plan.rs, "a dark day
that is dark is not news"
), so Plan::on answered None and every surface
described them as days outside the text:

  tomorrow    Thu Aug 20  ·  not part of the text — anything you commit today shows

2026-08-20 is inside VYNCINT's block, week 33 row 4. Committing on it punches a
permanent hole. It now reads:

  today       Wed Aug 19  ·  inside the letters and already lit (113) — a permanent hole
  tomorrow    Thu Aug 20  ·  inside the letters — keep it dark, or it becomes a permanent hole

  the next seven days
    Wed Aug 19   hole
    Thu Aug 20   keep dark
    Fri Aug 21   letter  110 to go

Standing::kind gains keep-dark, and hole now names damage rather than
position — a clean day inside the letters is keep-dark, and becomes a hole
once it is too bright. The Action gains today-kind and tomorrow-kind, so
do not commit tomorrow can arrive while it can still be acted on.

Two new flags

--today DATE makes the tracker's answer a fact about a date rather than
about when it ran. That is what defuses #3, what lets docs/ART.md keep its
"reproduce this exactly" promise, and what answers "what will 1 June owe?".

--backfill commits each day's shortfall instead of a flat count. A plain
--write puts the same number on every lit day including the ones already
bright, and adding to the busiest of those raises the year's peak — which raises
what every other letter day needs, so the bar moves as you walk towards it. A
shortfall provably cannot: the brightest shade is three quarters of the peak, so
topping a day up to need never exceeds the busiest day. One pass.
a_backfill_finishes_the_plan_without_moving_the_target adds the shortfalls to a
real year and checks the price did not move.

Bounds

--year was range-checked because a binary once "passed 999999 through to a
panic"
. Every other number took the unguarded path, and as is not a check:

before now
--commits -1 → 4,294,967,295 commits refused
--commits 0 → bright letters, no commits refused
--top -1 → wrapped past the guard meant to catch it refused
--start-week -1panic in NaiveDate + Days refused
--start-week 100 → silently drew a plan of no days refused, naming the last column that fits

One Args::number_in primitive, which year() now rides on so the two cannot
drift apart.

Everything else

Also, unasked but adjacent: a login resolved from a plan file is now stripped of
control characters where it is resolved rather than at each place that prints it,
which is the rule the calendar already followed.

Testing

  • 115 tests, up from 96. New: the backfill fixed-point property, the retained
    keep-dark days, place's refusal (including usize::MAX), --today
    reproducibility, the flag bounds, the plan's user, the year-mismatch note, and
    --backfill --write verified through git log.
  • Every report assertion now pins --today; CONTRIBUTING.md §3 says so.
  • The test job re-runs the CLI suite under TZ=Pacific/Kiritimati, a timezone
    that is already tomorrow — a backstop for the next assertion of that shape.
  • Verified locally: fmt, clippy -D warnings, cargo doc with
    RUSTDOCFLAGS=-D warnings, MSRV 1.88 cargo check --locked, the same clippy
    and doc gates cross-compiled to x86_64-pc-windows-msvc, and 25 sequential
    release runs of the whole suite.

Checklist

  • cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, cargo test
  • CHANGELOG.md updated under [Unreleased]
  • Tests for every behavioural change
  • Docs updated: README.md, docs/ART.md, docs/DESIGN.md §11, CONTRIBUTING.md §3, action/README.md

Thirteen findings from a full read of 0.2.0. The thread through most of them is
that "today" was ambient rather than an input, which is why a test could rot, a
report could call a dangerous day free, and docs/ART.md could not reproduce its
own example output.

The one that matters most: a day inside the text block that is not part of a
letter has to stay empty, and a contribution on it is the only loss nothing
takes back. Those days were dropped from the plan for having nothing to report,
so every surface described them as days outside the text — free to commit on.
They are kept now, named `keep-dark`, and surfaced a day early through the
Action's `tomorrow-kind`.

Closes #2  — `--today DATE`, so a report is a fact about a date
Closes #3  — two tests asserted on the wall clock; CI turned red on Aug 20
Closes #4  — the tracker called a must-stay-dark day free to commit on
Closes #5  — `--commits -1` wrapped to 4 billion, `--start-week -1` panicked
Closes #6  — a saved plan's user was loaded and then overwritten with nothing
Closes #7  — `--backfill`, and the back-dating advice now matches its own prose
Closes #8  — a `--merge` calendar from another year read as an empty year
Closes #9  — `fail-on: behind` fired on background days, blaming a letter day
Closes #10 — probe's rustdoc rendered twice, demo's summary belonged to Stats
Closes #11 — a Windows job, which the cfg(not(unix)) path had never had
Closes #12 — `y`/`Y` removed, `PageUp`/`PageDown` documented
Closes #13 — action.yml still described the pre-0.2.0 build-from-checkout
Closes #14 — sixel gave up a register; demo re-implemented art::level
Two rough edges found while testing the above.

`--today` carries no year of its own, so a date outside the plan's year made the
whole "what to do next" half of the report vanish with no explanation — and the
help text offers exactly that command as an example. It now says so, and says
which `--year` was probably meant. Still allowed: every letter day of an ended
year is overdue, which is a retrospective worth asking for.

Fourteen test scratch paths named a constant under the temp directory, so two
test processes in one checkout raced over the same file and failed in ways that
looked like the code. Observed while running a stress loop beside an ordinary
`cargo test`. CONTRIBUTING.md §3 asks for hermetic tests, and a fixed global
path is not one.
@vyncint vyncint added this to the 0.3.0 milestone Aug 19, 2026
Found by an adversarial review of the previous two commits.

Two ways past every bound the command line enforces, both through the plan
file, which `--plan PATH` names and which need not have come from your own
`--save`. A `top` of `usize::MAX` wrapped `place`'s guard — `usize::MAX + 5` is
4, comfortably "inside" a seven-row week — and drew the letters on scrambled
rows; a `commits` of `u32::MAX` quoted four billion commits a day; a `year` of
-262143 panicked building the calendar, and 180000 drew a calendar `cli::YEARS`
exists to refuse. `Spec::validate` now applies the flags' own ranges, and
`Grid::new` keeps the promise its own doc comment makes about returning `None`.

Three counts summed from a `--merge` calendar could wrap. The year's peak is the
number the whole costing model rests on; a calendar whose busiest day held four
billion reported a peak of 8 and quoted a price to match, in release builds.

`--backfill` wrote the whole year while every doc surface called it the flag for
days already past — including the command `--track` prints under "only
back-dated commits reach those". It now stops at `--today`, and its 34 days and
3,464 commits are exactly what `--track` reports as overdue.

The `--today` note fired on the first example in the help, the README and
docs/ART.md: tracking a year that has not started, where the report already
answers properly. It now fires only when the plan's year has ended, which is the
case that was silent.

Also: `tests/smoke.rs` still asserted the cursor was on Dec 31 2027 "since today
is not in it", which goes red for all of calendar 2027; `Shades::ceiling`
documented a `None` it can never return; `mossaic --help` was the last place
still listing only `[ ]` for the year keys; and the CHANGELOG claimed the
markdown report already agreed about `hole`, when in fact it had been calling a
day with no contributions at all a permanent hole.
A second review pass confirmed the previous commit closed all ten of its
findings, and turned up the sibling of the one it fixed most carefully.

`github::parse` bounded how wide a saved calendar was — 400 days — but not where
it was, and the grid reaches outside the days it is handed: back to the Sunday
before the first, and a week forward from the cursor. A file whose first day sat
within three of the earliest date a NaiveDate can express panicked in release as
well as debug. In the chart that was the worst of it: the fetch runs on its own
thread, so the thread died, the channel never disconnected, and the screen sat on
"loading" for ever with no error and no way to retry — worse than a missing file,
which does report itself. `Calendar::build` now degrades to an empty grid, for
the same reason `art::Grid::new` returns `None`, and `move_cursor` no longer adds
to a date before clamping it, which is a crash one arrow key away.

Also from that pass:

- docs/ART.md claimed the backfill total is "exactly" what --track reports as
  already past, "by construction". True with no background, false with one —
  `overdue` counts letter days and a backfill also lays down the past field.
- "one pass finishes the plan" survived from before backfill became past-only.
  It finishes the past; the rest is deliberately left to happen.
- The test mirroring that property was named for finishing the plan and its
  comment claimed to mirror what backfill writes. It mirrors the arithmetic; the
  date gate is covered end to end in tests/art_cli.rs.
- Refusing a plan file now names the way out. 0.2.0 accepted `--commits -1`,
  which `--save` wrote down as four billion, so a plan in the wild can be one
  this release will not load.
- write_commits streams into git instead of building the whole fast-import body
  in a String first. Worth about 2 MB at 150,000 commits rather than the 40 the
  buffer suggested — git fast-import's own mark table is the bulk of it — but the
  allocation grew with the commit count and now does not.
@vyncint
vyncint merged commit e52271c into main Aug 19, 2026
13 checks passed
@vyncint
vyncint deleted the fix/tracker-time branch August 19, 2026 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant