Releases: vyncint/mossaic
Release list
v0.6.3
Added
-
Three more templates, so the catalogue is a catalogue:
wave(one clean
sine across the year),pulse(a heartbeat trace, a spike every few weeks)
andinvader(a space invader, centred). Each is 51 columns, which keeps
every lit day clear of the two partial weeks at the ends of a year, and each
is drawn in{0, 2, 4}— the only three shades no reader has to squint at. -
Two gates on template art, which matter now that
#57 invites contributions.
every_template_reads_clearlymeasures the closest pair of shades in every
template and fails anything that is not clear: a picture using2and3
looks two-toned in the.artfile, because the digits differ, and reads as
one flat colour on github.com.no_template_overhangs_the_yearfails a
template that loses a lit day off either end of any year. Both were checked
against art planted to break them.
Changed
-
mossaic-art --helpis grouped rather than a list of thirty flags. The
options now sit under what to draw, where it goes, tracking, making
the commits and output, with the usage and seven examples first. The
colour-theory paragraph that lived inside--backgroundmoved to
docs/ART.md, where there is room to say it properly. Shorter
overall (93 lines against 113) and no flag was dropped. -
Counts read as English. Every user-facing count said
1 template(s),
6 cell(s),59 day(s)— twenty-nine of them, on the first lines a new
user reads and in the markdown the GitHub Action posts. They now agree with
their number, verb included: 1 day is short, 2 days are short. -
The description says what mossaic is. It never mentioned the terminal.
Fixed
-
The one shipped template no longer warns on every single year. Drawing
--template dragonprintednote: 6 cell(s) fell outside 2027 and were droppedfor 2025 through 2029 — and all six were blank margin, so nothing
was lost.Canvas::placecounted every cell that fell outside the year,
lit or not. It now counts only the cells that would have been drawn, so the
note appears when a shade is genuinely lost and stays quiet otherwise.Found by running the flagship path and reading what it printed.
-
A missing
--filein a script names the file, not the terminal. The
chart claims the terminal before it reads the calendar, so
mossaic --file typo.jsonin CI reportedneeds an interactive terminal—
true, and no help at all in finding the typo. With a terminal the behaviour
is unchanged: the error is shown in the chart withrto retry, which is
better than exiting. -
--fontno longer tells its readers to editsrc/art.rs. It printed a
note meant for contributors to everyone who ran it. -
The two guidance messages line their columns up.
could not detect a GitHub userput one of its three suggestions two columns out, and the
--demoline — the one that needs no account at all — came last. The
missing-file message tried to align against an interpolated path, which
cannot be done, and repeated back a path the reader had just typed. -
brew install vyncint/tap/mossaic. The formula is generated by the
release itself from the checksums the archives produced, so it cannot
describe an archive that was never built, and a hand-edit in the tap is
overwritten by the next release rather than quietly kept.Verified by installing it:
brew audit --strictis clean,brew test
passes, and the three binaries plus a copy of the art templates land where
the formula says.
Fixed
-
A release's archives are never replaced once published. The upload
clobbered, so re-running the artifact build for an existing tag rebuilt and
overwrote them — and Rust builds are not bit-for-bit reproducible, so the
replacements had different checksums. Every checksum anyone had recorded
became wrong, including the Homebrew formula's and including one somebody
had written down to verify a download with.Found by re-running the workflow: the formula job reported "tap updated"
where nothing should have changed, and the diff was four checksums.A re-run now fills in what is missing and leaves what is there alone, which
is the case the workflow was built for. Replacing a genuinely corrupt asset
means deleting it first, and that friction is deliberate. -
The Intel-Mac binary is cross-compiled from Apple silicon rather than
built on amacos-13runner. Xcode's toolchain and SDK are universal, so an
arm64 Mac produces an x86-64 binary with nothing butrustup target add—
no sysroot, no linker configuration.The Intel runners are being wound down and it shows: on the v0.6.2 release
this leg sat queued for forty-five minutes while every other target finished
inside ninety seconds. A target nobody can schedule is a target that
silently stops shipping.The job now asks
filewhat it actually built, and fails if the answer is
not the architecture the target names — a cross-build that quietly produced
the host's architecture would pass every other check here and fail on the
first machine that downloaded it. -
The README briefly promised Homebrew before the tap existed, and said
so for an hour or two until it did. An install command that does not work
is worse than one that is not offered; every route the README lists is now
one that has been run. -
The Windows binary is built. The 0.6.2 release shipped Linux and macOS
archives and no.zip: the build step had noshell: bash, and Windows
runners default to PowerShell, where"$TARGET"expands to the empty string
andcargo build --targetis handed nothing. Every step isbashnow. -
Building the artifacts is re-runnable, in
binaries.ymlrather than
insiderelease.yml. Publishing to crates.io happens once and cannot be
repeated; building an archive can fail for reasons that have nothing to do
with the release, and when it does the fix must not be "cut another
version". It fires on a published release, and by hand for any tag whose
artifacts need rebuilding.
v0.6.2
Added
-
Prebuilt binaries on every release, for Linux, macOS and Windows on both
x86-64 and arm64 — so using mossaic no longer needs a Rust toolchain.The reason is not convenience. The pitch for contributing a template is that
it needs no Rust, because a template is a text file — and that pitch is false
if the only way to see what you drew iscargo install. Somebody who does
not write Rust should not have to install a compiler to draw a picture.Linux builds are musl, so they are statically linked and run on any
distribution whatever its glibc: a binary downloaded today still starts next
year. Every target builds on its own architecture rather than
cross-compiling.Each archive carries the three binaries, both licences, the changelog, and a
templates/directory to start from, with a.sha256beside it. Assets are
uploaded under two names: the versioned one, whichcargo binstalland the
Homebrew formula look for, and an unversioned alias, because
releases/latest/download/<asset>resolves by exact file name and a URL that
is stable across releases therefore cannot contain the version.
Changed
-
The
termlensdev-dependency moves to 0.6.1, which carries an
openptyretry that had been written, merged and left unreleased. Without
it this suite fails on macOS at sixteen threads withDevice not configured— a PTY device the kernel has not finished recycling — at
roughly one shard in ten. It is a test-harness fix and touches nothing that
ships. -
Refreshed the lockfile.
cc1.4.3 → 1.4.4,darling0.24.0 → 0.24.1
(with_coreand_macro),either1.17.0 → 1.18.0,log0.4.33 → 0.4.34,
uuid1.24.1 → 1.25.0. Four of those —cc,darling,either,log—
are genuinely compiled;uuidis in the resolution but not the build.This matters only to the binaries.
cargo install mossaic --lockedbuilds
what this file names, so refreshing it is what puts those versions in an
installedmossaic. A library consumer resolves its own dependencies and is
unaffected either way.cargo deny checkpasses on the new lockfile: advisories, bans, licences and
sources all clean. -
generic-arraystays at 0.14.7, and cannot move.cargo updatereports
0.14.9 as available, butcrypto-common 0.1.7requires it exactly:generic-array = "=0.14.7" required by crypto-common 0.1.7 ... digest 0.10.7 -> sha2 0.10.9 -> termwiz 0.23.3 ... -> ratatui-termwiz 0.1.2 -> ratatui 0.30.2 -> mossaic0.1.7 is the newest of the 0.1 line, and the relaxed requirement only arrives
incrypto-common0.2, which needsdigest0.11, which needssha20.11,
which needs atermwizthat depends on it. None of that is this crate's to
move.It is also never compiled here.
ratatui-termwizis an optional backend and
mossaic uses crossterm, so nothing in that subtree —generic-array,sha2,
digest,termwiz— reaches a build:cargo treedoes not list it, and a
full build produces zero artefacts from it against ratatui's ninety. The
entry is in the lockfile because a lockfile records the whole resolution,
optional branches included.
v0.6.1
Fixed
-
A day the picture does not cover is reported as free, not as one that must
stay dark. Both a day the picture wants dark and a day outside it have a
needof zero, and the tracker read the number rather than the intent — so
every day after a picture ends was told to stay dark, when it is nobody's
business. The advice for the two is opposite: one must be left alone or the
drawing is damaged, the other is free.Only the human
--trackreport was affected. The JSON and Markdown formats
carrytoday.kind, which named the two apart correctly all along.The test written for this passed against the bug first time round: a day
outside the picture with no contributions is not recorded at all, so it
takes a different path and never reaches the branch. It needs a day that is
both outside the picture and already contributed to, which is exactly the
case that produced the report.
v0.6.0
Added
Pixel art across the whole year. The font draws five rows of two shades on
Mon–Fri; a canvas is the general case — seven rows, up to 53 columns, any
of GitHub's five shades on any day, the weekend included.
-
--template NAMEand--list-templates. A catalogue of whole-year
pictures, shipped with the crate.--list-templatesprints each one with a
thumbnail, because a name and a sentence are not a picture and the point of
choosing one is what it looks like. Contributing a template is dropping a
.artfile intoart/templates/—build.rsembeds every file it finds, so
there is no list to remember to edit. Local templates in./templates/and
~/.config/mossaic/templates/shadow the built-in ones, so a template can be
tested under the name it will eventually ship with. -
--matrix FILE. Draw a.artfile: seven rows of0–4, or of the
blocks░▒▓█, with#comments and a# name:/# author:/
# description:header. A short row is padded with dark days, so an editor
that strips trailing whitespace cannot corrupt a picture whose last column is
dark. -
--draw, an editor for the year itself. Arrows orhjklto move,0–4
to paint, space to cycle a cell, the mouse to paint directly,cclear,i
invert,uorctrl-zundo,ssave. The HUD shows the date under the
cursor, the days at each shade, how well those shades will separate for a
reader on any palette GitHub ships, and what the picture would cost in
commits — the same arithmetic--writeuses rather than an approximation
of it. Days in the partial weeks at either end of the year are drawn as·
and cost nothing, because they are not days the year has. -
--image FILE, with--invertand--dither. A PNG becomes a year:
shrunk to the calendar keeping its aspect ratio, and quantised to five
shades. A dark pixel becomes a busy day, the way ink reads on paper. Every
source pixel that lands in a cell is averaged into it — the shrink is
enormous, and sampling one pixel in twenty throws the picture away.
--ditheris Floyd–Steinberg, so a gradient reads as one rather than as four
bands. The decoder is written here rather than pulled in: zlib is already
present for the kitty protocol, so this costs no new dependency. PNG only,
and it says so — a JPEG is named as a JPEG, with the one command that
converts it. -
art::Canvas,art::band, andplan::Plan::from_levels— the
library half. A canvas tracks, saves, backfills and writes exactly as text
does, and the GitHub Action reads the same JSON, becauseDaywas already a
band rather than a target and a level-2 day is that band with different
numbers. -
art::Canvas::closest_pair, and the palette rule it exists to enforce.
GitHub's five greens are not evenly spaced: every adjacent pair is 9–20 ΔE
apart in the worst palette it ships.{0, 2, 4}is the only three-shade set
with no faint pair in it, and there is no clear set of four — proved by
enumeration in the suite rather than asserted in prose. -
The GitHub Action takes
template,matrix,image,invertand
dither. Exactly one oftext,template,matrixorimagesays what
is being drawn, and giving two is refused rather than guessed at. Every
existing input and all twenty outputs are unchanged and work for a picture:
a picture is the same plan underneath.matrixandimageare paths in your
repository, so a workflow withoutactions/checkoutis told that rather than
told the file is missing.
Changed
-
Contrast is reported for the shades that look most alike, not the
darkest and brightest. The widest pair always flatters: a picture spanning
levels 0 to 4 reads as ΔE 70 and can still hold two greens nobody can
separate, because the ones sitting next to each other are 9 apart. This
changes what--template,--matrix,--imageand--drawprint; a text
plan is unaffected, since two shades have only one pair. -
The reference template is drawn in three shades, not five.
dragon.art
used all five to demonstrate the format, which demonstrated the wrong thing:
the rendered chart is what showed it up. It reads clearly now. -
--topand--backgroundare refused with a picture rather than
silently ignored. They place letters on rows and shade the field behind
them; a picture uses the whole week and names its own shade for every day.
Only when typed, so the defaults nobody chose still work. -
plan::Plan::holescounts any day past its ceiling, not only the ones
inside the text's own block. A canvas has middle shades, and a level-2 day
that has crept to level 4 is damage nothing takes back. This cannot change
what a text plan reports: text gives a letter day no ceiling — brighter than
brightest is still brightest — so there is nothing for one to pass. -
plan::Speccarries an optionalartfield, the picture stored inline.
A name would be a pointer to something that can change underneath a plan, and
every other field here is resolved for exactly that reason. Plans written
before this load unchanged, and a text plan is written byte-for-byte as it
was — the field is skipped when absent.One direction does not work, and cannot be made to. A plan saved from a
picture and then read by 0.5.0 or earlier draws the picture's name as
text, because that version has no idea theartfield exists and serde
ignores what it does not know. Nothing added here can change what an already
released binary does with a file. If you keep a canvas plan, keep a mossaic
that understands it.
v0.5.0
Added
- Shapes in the font —
mossaic-art "I :heart: RUST". Nineteen of them:
:star::heart::smile::sad::check::circle::square:
:triangle::diamond::note::sun::moon::bolt::up::down:
:left::right::skull::flower:, several with aliases (:cry:is
:sad:,:zap:is:bolt:). A shape is written between colons so it can be
typed on any keyboard; the symbol works where you can type one, and so does a
pasted emoji — ⭐ draws:star:, 😢 draws:cry:, and the variation selector
an emoji keyboard adds is dropped rather than refused. All three spellings
become the same character before anything else sees them, which is what lets
a shape survive being written to a plan, uppercased, and read back by
--trackevery morning — including into the commit message--writeleaves
behind. - Punctuation:
!?,'"+=<>()/\*_
@&#%. The font is 77 glyphs where it was 39. - A picture of everything the font can draw,
in the README and indocs/ART.md— every glyph rendered as contribution
cells by the same rasteriser that draws the chart, in real Primer greens on a
level-1 field. An image rather than a table of characters for the same reason
the sextant chart is gone: half of these symbols are ones a browser may have
no font for, and a picture of the cells has nothing to be missing. mossaic-art --font --png PATH, which writes that sheet. The terminal
view is still--fonton its own.graphics::sheet, the rasteriser generalised from a calendar's seven
rows to any rectangle — so the sheet is drawn by the code that draws the
chart rather than by a second one that would drift from it.graphics::grid
is now a call to it and draws identical pixels.- A list of everything the font can draw in the README as text as well, and
tests that check both against the font: every symbol and emoji in the table,
and the image itself, regenerated and compared byte for byte. A table of
glyphs is exactly the documentation that rots, and it is the only place most
people will look.
Changed
:is the shape delimiter and has no glyph of its own, which is what
keeps the grammar to one reading. A name nobody has drawn, or a colon that
closes nothing, is refused with the list of shapes rather than guessed at.mossaic-art --fontnames its shapes (:star:) instead of printing the
symbol. Several of them are ambiguous-width, so a terminal may draw one in
two columns and tear the aligned output apart.- The README no longer prints the sextant chart inline. It was 740
block-sextant characters (U+1FB2B,U+1FB1B, Unicode 13) that too few
systems have a font for: where one is missing the browser substitutes a font
of a different width and shears every row of a 176-column chart apart, which
is a worse advertisement for the fallback than no picture at all. The promise
it was making stays, with the one command that shows it in a terminal, where
the characters are guaranteed to work.
Tests
- The pixel path is asserted as a picture, not as a byte count. termlens
0.6 hands back the payloads an application transmitted and decodes them, so
tests/pixels.rsnow checks what the chart actually drew: that the year is
pinned to the 106x7 character cells the layout reserved and placed on the
grid's own origin, that it goes out as one image however many of kitty's
4096-byte chunks it takes, that one moved cursor sends a single day-sized
image rather than a cheap-looking year, and that the chart deletes its
images before it exits instead of leaving them over whatever comes next.
None of that changes a cell on screen, which is why none of it was
assertable before. - The year drawn is checked against the year described. Every day's centre
pixel is read out of the decoded image and counted: the number drawn in
Primer's brightest green has to equal the number the footer calls active.
The two halves of the chart — the picture and the text under it — had never
been compared to each other, and nothing on screen can compare them. - The in-process sixel decoder rounds the way a terminal does. It
converted a percentage back withp * 255 / 100, the exact inverse of the
encoder's own rounding, so a systematic disagreement with real terminals
would have round-tripped perfectly through a test whose stated purpose was
to catch exactly that.
Changed
Escno longer quits the chart. It cancels the username prompt and
dismisses the help overlay, which is all the UI ever implied it did, andq
is the only quit key. 0.4.0 took the other half of #34's suggestion and
documentedEscinstead, which left the complaint the issue opens with
untouched: pressingEscto dismiss a tooltip exited the program. Documenting
a surprise is not the same as removing it. (#34, #44)
v0.4.0
Added
--today DATEon the chart, not just the tracker. 0.3.0 made time an input
formossaic-artand leftmossaicreading the clock, so the two disagreed
about whether time is an input at all — and the still-to-come half of the
rendering was reachable only from a live fetch, which is why the cursor could
vanish there unnoticed. With--fileit also makes a saved year show its future
days, which is what a plan snapshot for next year actually is. (#38)--palette autocan pick winter.Season::onmatched only Halloween, so a
scale that is fully implemented in all three appearances, measured among the nine
palettesShades::worstreads, and askable with--palette wintercould never
appear by itself. GitHub does not publish the windows; December 19–25 is what it
has been observed using, and the code says so rather than claiming a
specification. (#22)- The chart says when a protocol was asked for and could not be used.
--graphics kittywithout a cell size drew characters silently;--capabilities
explained it and the chart did not. (#37)
Changed
- Cells are the size github.com draws them. The 5% border was drawn first and
the fill inset by it, so the coloured square measured 14px in a 20px pitch —
0.700, where github.com's is 11 on 14, or 0.786. Every cell was about a ninth too
small with a dark rim where a hairline belonged,--pngincluded, which is where
the README's own screenshots come from. The square is now drawn at full size with
the hairline over its edge, and the offsets are rounded to whole pixels: at a 9x19
cell the opaque extent was 12x11 where 12x12 was drawn, soft on one axis and crisp
on the other. (#20) - A current streak is the run ending on a given day, so
Calendar::statstakes
one. Both loops compared positions in a list rather than dates, so any gap read as
an unbroken run — ten non-adjacent Mondays reported a ten-day streak — and the
"quiet today" tolerance was applied to the last elapsed day of any year, so a
year that ended a decade ago reported a current streak of forty. (#18) mossaic-glyphsuses the shared argument parser, so all three binaries agree
about what a user notices:--color=neveris honoured rather than ignored, an
unknown option or a stray argument exits 2 with a message naming the binary, and a
missing value says so instead of quietly defaulting. (#25)Rgb::luminanceis nowRgb::brightness. It applies the luminance
coefficients to gamma-encoded sRGB with no linearisation, so it was not the
quantity its name promised — mid grey came out 0.502 where relative luminance is
0.216. The threshold it produces is the more useful one for choosing a terminal
theme; only the name was wrong. (#35)- A flag that would do nothing is refused rather than ignored.
--trackwith
--snapshot,--writeor--repowrote nothing and said nothing;--demowith
--filesilently picked the demo. Distinct from--commits, which tracking has no
use for but which is a tuning parameter rather than a side effect somebody asked
for. (#26) - The legend says
auto:while the chart is choosing. Cycling withdpast the
narrowest style lands back onAuto, which resolves to whatever fits — so the
press looked like it had done nothing. (#34)
Fixed
- Pixel images are bounded by columns as well as rows. Sixel clears the
character cells it is about to cover by writing spaces, and did so with no idea
how wide the terminal was: a 53-week grid wrote 106 of them from column 6, so on
an 80-column terminal seven rows wrapped over the weekday gutter, the right border
and the rows below — and stayed wrapped, because ratatui believed it had written
those cells. The legend image was never bounded at all, and was clamped onto the
frame's last row. (#17) - A shade GitHub names and this version does not is derived, not dropped.
level_ofmapped anything unrecognised to 0, and nothing downstream re-derives
one — so a day with forty contributions was painted exactly like an empty one
while the header reported a full year. The fallback isart::level, GitHub's own
rule, which the art costing already used. (#19) - The keyboard cursor is visible on a day that has not happened.
patchdrew
nothing at all without a level, the ring included, andfilled_rowsblanked a
future day before consulting the mark — so it vanished in pixel, rounded, snug and
squares cells while the five bordered styles showed it, and the detail line went on
naming the day it was on. (#21) - The capability probe reads a reply that shares its buffer. Only the first
CSI ?was tried, so aCSI ?2026;2$y— the DECRQM answer for synchronized
update, the mode this program itself uses — left in the tty queue by whatever ran
before us hid sixel and the sentinel, making every start-up pay the whole 250 ms
deadline.OSC 11now also accepts the#rrggbbform of an X colour spec, which
was read as no answer at all, so a light terminal replying that way got the dark
palette. (#23) [and]stay inside the years--yearaccepts. The fallback for a year
outside the contribution set stepped without a bound, oneghsubprocess per
keypress, and the header named years the CLI refuses to be given. (#24)- At 80x24 the chart still says how to get out. The footer was one 111-character
line, hard-truncated, so neitherq quitnor? helpwas on screen; the note row
was not counted in the chrome budget, so at one particular height it evicted the
footer; and the help overlay truncated from the bottom, dropping what the terminal
can draw andany key closes thiswith it. The footer now drops the guessable
hints first and the overlay drops its cheapest lines. (#27) - A click that misses a day puts the tooltip away, instead of leaving it naming
an unrelated date — which on a terminal that reports clicks but not motion nothing
later corrected. And the mouse no longer acts behind the help overlay, where the
wheel changed year and started a fetch for a year the reader could not see. (#28) --pngrefuses an empty year rather than writing a zero-width PNG, which the
format forbids and no viewer opens, and reporting success. (#29)- The help overlay is opaque on kitty. An image at
z=-2draws over a cell
background rather than under it, and the panel sets one, so the chart showed
through its text. The painter now stands down while it is up. (#30) - The advice line never points at a smaller style that does not exist. From
Autoat its narrowest it said "press d for a smaller style", wheredgoes to
pixels — three times wider. It now names the columns a year needs. (#31) --capabilitieshonours--cell, so the one command whose job is explaining
the pixel decision no longer reports the opposite of what the run would do. (#34)- A calendar naming a day twice is refused rather than keeping whichever record
an unstable sort put last, and a GraphQL error without amessagesurfaces as an
error rather than as "unexpected response from gh: missing fieldmessage".
calendar::demono longer panics on a year no calendar can hold. (#35) - The character cell is measured again on a resize, which is also when a font
size changes —docs/DESIGN.mdpromises the geometry is scaled to whatever a cell
measures, and it was measured once at startup. (#37) - Eight measured claims in the design notes, and five in the source. The
tightest adjacent ΔE is 9.1 (light + halloween, levels 1 and 2), not the 10.8 that
five places quoted for a different pair (src/art.rsamong them); the sixel coarsening never triggers for a
chart, which needs 20–27 of 256 registers; the kitty layering is two layers at
z=-2andz=-1, not one atz=-1; the probe asks five questions, not four, and
has a window-size fallback; the two-level gap is warned about rather than enforced;
the byte figures are ~5 KB and 44 KB at a 9x19 cell; the xterm-256 collision is
between levels 0 and 1 rather than 1 and 2, and the dimmed ramp ties rather than
inverts; and the layers block was missing four modules. The load-bearing claim —
two levels apart never below ΔE 35.4 — holds, and is now pinned by a test. (#33) - Release hygiene.
.claude/no longer ships inside the crate,
extract-changelog.shruns from anywhere rather than only the repository root, and
docs/RELEASING.mdgained the two steps whose absence let threeaction@v0.2.0
references go stale: bumping them, and repointing the changelog's own link
definitions. (#32)
Documented
- The advice line named a window size that would not have worked. It quoted the
drawable area inside mossaic's own border, so a reader with a 17-row window was
told it "has 15" — resizing to the 17 it asked for still did not fit. Both numbers
are now the ones a terminal reports. The README's figures were wrong in the other
direction ("about 115×26" for the bordered grid, against 166×27), and are now
measured in a pty: 112×19 for pixel or square cells, 165 columns for rounded
corners. (#31, #33) - The README is a quarter shorter and both its pictures are 2027. The headline
claim is "2027 should read VYNCINT" and the screenshot was a 2026 calendar, so
the first thing a reader saw disagreed with the first thing they read. Both
images are now the same planned 2027 year — once in pixels, once in sextants —
and the screenshot draws the letters on a level-1 field rather than on an
empty graph, which is the option the guide recommends and the old picture never
showed. Its caption is the two commands that reproduce it byte for byte.
It was also rendered with the pre-#20 geometry, so the one image whose entire
job is showing that mossaic draws github.com's cell size was showing cells a
ninth too small; and t...
v0.3.1
Added
-
tests/pixels.rs: the pixel path, end to end. Eight tests over the half of
the program that was not merely unasserted but unreachable. mossaic decides
whether to draw pixels by asking the terminal, so a harness that answered no to
every question could only be driven down the text path — the suite worked around
that with--graphics sixel --cell 10x20, which forces the protocol and hands
over the cell size, skipping the probe, the fallbacks and the auto choice.
termlens 0.5 states which terminal is being simulated, so the real decision runs.What that buys, beyond the probe itself: the byte budgets in
docs/DESIGN.md§4
are now checked against the wire (a year is ~5 KB over kitty against ~44 KB of
sixel, and the test holds the order of magnitude the design decision rests on);
the diffing in §5 is checked the same way, since one cursor move costs under a
tenth of a year rather than re-sending it; the cell contract in §3 is asserted
through a terminal for the first time, with all seven grid rows left to the
painter while the month labels stay text;--graphics textis checked to send no
image at all on a terminal that could draw one;Autois checked never to place
an image too wide to fit; and--capabilities, which had no test at all because
no harness could answer the questions it asks, is checked to agree with what the
chart then does. -
Two tests for behaviour no content assertion can see. An idle chart repaints
on an 80 ms timer so the loading spinner can turn, and every one of those frames
shows exactly the right content —printable_charsis what shows that a settled
chart writes nothing at all. And mossaic ignores keys it does not bind silently,
which used to be the same screen as refusing them with a bell;bells()tells
the two apart.
Changed
- termlens 0.4.2 → 0.5.0 (dev-dependency).
send,send_strandpaste
returnResultrather than panicking, so every input call in the suite now
propagates with?— 24 call sites, which is the whole migration.
v0.3.0
Added
--today DATEmakes "today" an input. The tracker read the clock, so its
answer to "what does today owe" was a fact about when it ran rather than about
a calendar and a date. Now it can be told, which is what lets a report be
reproduced, lets a day that has not arrived be asked what it will owe, and
lets the tests assert on a report at all. (#2, #3)--backfillcommits what the days already past are short of, and nothing
else. A plain--writeputs the same flat count on every lit day, including
the ones already bright — and adding to the busiest of those raises the year's
peak, which is the very thing every letter day is measured against. A shortfall
cannot do that:needis at most the peak already, so what every other day
owes is unchanged afterwards and one pass of arithmetic is enough. Days still to
come are deliberately left for an ordinary commit on the day; back-dating is
only how you reach the ones that have gone.--todayis what "past" means.
Needs--repo, and--writeto commit. (#7)keep-dark, a day the report now warns about. A day inside the text block
that is not part of a letter has to stay empty, and a contribution on it is
the one loss nothing takes back. The Action gainstoday-kindand
tomorrow-kindoutputs, so do not commit tomorrow can arrive a day early.
(#4)- A Windows job in CI. The
cfg(not(unix))probe was never compiled by any
gate, and had drifted out of date in two ways that-D warningswould have
caught. (#11)
Changed
fail-on: behindcounts letter days only. It testedtoday.short, which
is also non-zero for a short background day — so withbackgroundset, each
of the year's ~290 field days failed the job, blaming a letter day for it.
(#9)- A report says nothing about a day its own year does not hold. Tracking
2024 while it is 2026 reported on a day in 2026 and called itoutside, which
is true of the wrong calendar;todayandtomorroware nownullthere.
(#4) holenames damage rather than position, in the JSON report and in the
markdown one. A clean day inside the letters iskeep-dark;holeis what it
becomes once it is too bright. The old value was a position, and the markdown
report read it as damage — so with a background drawn, a day inside the letters
holding no contributions at all was reported as "inside the letters and
already lit — a permanent hole". It now reads as the background day it is.
(#4)- A failed fetch names whose year it was reading. Whose contributions to
track can come from a saved plan, and "gh was not found" gave no hint which
login it had resolved. The login is stripped of control characters where it is
resolved rather than at each of the places that print it, which is the rule the
calendar already followed — a plan file is a file someone may have sent you.
Fixed
- A calendar has to sit somewhere a calendar can sit.
github::parsebounded
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 was within three of the
earliest date aNaiveDatecan express panicked in release as well as debug —
and in the chart that was the worst of it, because the fetch runs on its own
thread: the thread died and the screen sat on "loading" for ever, with no error
and no way to retry.Calendar::builddegrades to an empty grid rather than
panicking, for the same reasonart::Grid::newreturnsNone. - The cursor cannot be walked off the end of the calendar.
move_cursor
added to the date and clamped afterwards, so the clamp could not save it: with
a calendar ending within a week of the last representable date, one arrow key
ended the process. - A saved plan is validated when it is read.
--plan PATHnames a file, and
a file need not have come from your own--save— so it was the way around
every bound the command line enforces. Atopofusize::MAXwrapped past
place's guard (usize::MAX + 5is 4, which is comfortably "inside" a
seven-row week) and drew the letters on scrambled rows; acommitsof
u32::MAXquoted four billion commits a day; ayearof -262143 panicked
building the calendar, and one of 180000 drew a calendarcli::YEARSexists to
refuse.Spec::validateapplies the flags' own ranges, so a saved plan can
never mean something a typed command could not. art::Grid::newkeeps the promise in its own doc comment. It said it
returnedNonerather than panicking "because this is a library: a year
arriving from a command line, a file or a caller is input" — and then stepped
back to a Sunday with unchecked arithmetic, which panics in the first week a
calendar can express. Two guards for one hole, because the second is what the
library owes a caller that is not this CLI.- Three counts from a
--mergecalendar no longer wrap. The year's peak — the
number the entire costing model rests on — was accumulated with+=. A
calendar whose busiest day held four billion reported a peak of 8 and quoted a
price to match it, in release builds; in debug it panicked. Everything around
them already saturated or widened. - Numeric options are bounded where they are read.
--yearwas
range-checked because a binary once passed 999999 through to a panic; every
other number took the unguarded path, andasis not a check.--commits -1
came out as 4,294,967,295 — which--writewould then try to build a
fast-import stream for —--commits 0drew bright letters for no commits at
all,--top -1wrapped past the guard meant to catch it, and
--start-week -1reachedusize::MAX, where building a date from it panicked.
A start column too far right is now refused with the last one that would have
fitted, rather than silently drawing a plan of no days. (#5) - A saved plan's
useris no longer discarded. It was read out of the file
and then overwritten with the nothing a bare--trackcarries, so
mossaic-art --trackignored the login the plan named — and withgh
authenticated it tracked the wrong person rather than saying so. (#6) - A
--mergecalendar from another year says so. Every day of a 2026
calendar falls outside a 2027 grid, so it filtered down to nothing and
everything downstream was correct about an empty year: 9,527 contributions
read as none, and a plan that cannot be drawn read as reachable at one commit
a day. (#8) --todayoutside the plan's year says so. The flag carries no year of its
own, so--today 2027-06-01against a plan whose year defaulted to this one
asked about a calendar the plan does not cover — and the entire "what to do
next" half of the report silently vanished. Still allowed, because every letter
day of an ended year is overdue and that is a retrospective worth asking for.- Test scratch files no longer share a fixed path. Fourteen of them named a
constant under the temp directory, so two test processes in one checkout — a
stress loop beside an ordinarycargo test— raced and failed in ways that
looked like the code.CONTRIBUTING.md§3 asks for hermetic tests, and a fixed
global path is not one. - Two tests no longer assert on the wall clock. One claimed whatever day it
ran on was a lit day inside the letters — true when it was written, false the
morning after — and another that 2027 had not started. Both now pin--today,
and CI runs the CLI suite a second time from a timezone that is already
tomorrow. (#3) - The command
--tracksuggests for back-dating now does what the sentence
above it promises. It printed a plain--writewith the text typed out,
which ignored the saved plan, defaulted to 4 commits a day where the plan
needed 110, and wrote to every lit day rather than the short ones. (#7) - Sixel uses all 256 colour registers. The capacity check bailed at a full
palette rather than a full-plus-one, and it ran after an index had been handed
out — one step later andseen.len() as u8would have wrapped to 0 and
aliased a colour. Restructured so no such index is ever constructed. (#14) - Rustdoc:
term::probecarried doc comments on both sides of its
#[cfg]and rendered them concatenated;calendar::demo's summary line
belonged toStats; the non-unixprobehad no documentation at all. (#10) calendar::demono longer re-implements GitHub's shading. It called
art::level's formula a second time, inu32where the original widens to
u64— a second place to change when GitHub restyles the graph, in a crate
whose whole point is agreeing with github.com. (#14)
Removed
yandYno longer change year. They were undocumented, they duplicated
[,],PageUpandPageDown, and in a chart that bindsh j k lay
that silently moves the year is the one binding with a real chance of
surprising a vim-handed user, to whom it means yank.PageUpandPageDown
are documented now, in the README and in the?overlay. (#12)
v0.2.0
Changed
- The GitHub Action installs the tracker from crates.io instead of building
the checked-out repository, and gains aversioninput to say which release:
the default,latest, follows crates.io, and a pinned number changes only
when you change it. Nothing is cached — a fresh--lockedinstall per run
costs a minute or two and can never be stale, poisoned, or the wrong
version. The ref you pin (@v0.2.0) now chooses only the action's own
steps.
Fixed
- Tracking is no longer refused because of a flag it does not use. Over a
busy year,--track --merge --background 1failed with "the letters would
not show" — a check meant for runs that draw, where--commitsis what
each lit day gets. Tracking never writes a commit; it works out what a
letter day needs from the year's real peak. The GitHub Action passes no
--mergeand so never hit it, which meant the Action and the CLI disagreed
about the same plan. Found by running the Action against a real year and
comparing. - A hole is called a hole again. With no background drawn, a lit day inside
the letters reported as "background — 113 contributions, 113 too many for
level 0", naming a shade the plan never asked for. The markdown report always
got this right, so the text report was the one disagreeing with it.
v0.1.1
No functional changes — the crate is byte-for-byte the same tool as 0.1.0.
This release exists to prove the publishing pipeline works the way it is
supposed to.
Changed
- Releases publish through crates.io Trusted Publishing. The workflow
exchanges the job's OIDC token for a credential that lives for minutes, so
no registry token is stored on the repository at all. 0.1.0 could not do
this — Trusted Publishing is configured on a crate, and the crate did not
exist until 0.1.0 published — so it went out with a stored token, which has
since been deleted along with the zizmor exception that covered it.