Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,49 @@ listed under a **Changed** or **Removed** heading.

## [Unreleased]

### Fixed

- **A picture was never offered a better placement, even when one drew it
cleanly** (#97). `--track` on a picture passed `None` where the text path
passed a real sweep, so the tool said *"cannot be drawn cleanly"* and stopped
— for the one verdict a reader cannot answer by contributing more. The
reasoning at the call site was that a picture "is usually the full width of
the year, so there is no column to move it to", which is true of the four
shipped templates and wrong about every picture narrower than the year — the
shape `docs/ART.md` and #57 ask contributors to draw. The overhang it was
guarding against is now *measured*, so a full-width template still gets no
suggestion, for the reason rather than by refusing to look.

Found on a live plan: `vyncint/contribution-art` draws an eleven-column heart
and posts the report to a public issue. Five days inside the picture picked up
ordinary contributions after the plan was made, so every day it published
"this year is lost" when what it meant was "move it two columns right".

### Added

- **`suggested-start-week` and `suggested-holes` Action outputs**, so a
workflow can act on the answer rather than read it. `suggested-holes` of `0`
means that column draws the plan cleanly.

### Changed

- **A suggestion prefers a column that has not begun**, when several cost the
same number of holes. A clean column in March is arithmetic, not advice: the
only way to draw there is `--backfill` into days months gone. This is not a
corner case — an eleven-column picture in a fifty-three column year can have
nine placements costing zero holes, and ranked by column alone the answer is
always the one in January. Holes still win outright; the preference only
breaks ties, because back-dating is a thing this tool does and unlighting a
day is not. Applies to text as well as to pictures.
- **A clean column is described as one.** `--start-week 37 draws it cleanly.`
rather than "would leave 0 holes instead of 5" — arithmetic the reader had to
finish before knowing it was the answer to their problem. The `holed` headline
carries it too, which is where it was most missing: that line is the Action's
`headline` output and the subject of the issue the shipped consumer opens.
- **`plan::best_start_week` takes a `today`**, and `plan::best_start_week_of`
is its canvas twin. Breaking for anything calling the library directly; the
binaries and the Action are unaffected.

## [0.7.0] - 2026-09-06

Twenty findings and one security advisory, all reported against 0.6.3 with a
Expand Down Expand Up @@ -1066,7 +1109,7 @@ there was none.

[termlens]: https://github.com/vyncint/termlens

[Unreleased]: https://github.com/vyncint/mossaic/compare/v0.6.3...HEAD
[Unreleased]: https://github.com/vyncint/mossaic/compare/v0.7.0...HEAD
[0.7.0]: https://github.com/vyncint/mossaic/compare/v0.6.3...v0.7.0
[0.6.3]: https://github.com/vyncint/mossaic/compare/v0.6.2...v0.6.3
[0.6.2]: https://github.com/vyncint/mossaic/compare/v0.6.1...v0.6.2
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ placement that would salvage the most:
--start-week 1 would leave 23 instead of 61.
```

When a column draws it *cleanly* it says so instead — `--start-week 37 draws it
cleanly.` — and it says it for pictures as well as for text, preferring a
column that has not begun when several cost the same. A clean column in March
is arithmetic; one starting next Sunday is advice.

**A field beats an empty graph.** Letters on nothing means not contributing on
the other 290 days of the year. `--background 1` draws the background as a shade
instead, so the art is one green against another and the year stays alive — the
Expand Down
18 changes: 17 additions & 1 deletion action/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,29 @@ least two levels between the two — the `legibility` output says `clear`,
notification title), `markdown` (the whole report, fit for a message body),
`json` (everything), and the scalars: `bright`, `letters`, `owing-days`,
`owing-commits`, `holes`, `today-short`, `tomorrow-need`, `today-kind`,
`tomorrow-kind`.
`tomorrow-kind`, `start-week`, `columns`, `suggested-start-week`,
`suggested-holes`.

With a `background` set, also: `field-level`, `field-days`, `field-bright`,
`field-owing-days`, `field-owing-commits`, plus `legibility` (`clear` /
`readable` / `faint`) and `separation` (the CIE76 ΔE between the two shades in
the worst palette a reader might have).

`suggested-start-week` and `suggested-holes` say where the plan would be
better placed. They matter for one verdict: `holed` is the only one you cannot
answer by contributing more, so the column to move to is the only thing left to
do about it — and until 0.8.0 a picture was never offered one. Act on them when
`suggested-holes` is below `holes`; `0` there means that column draws the plan
cleanly.

```yaml
- name: Say where it can still be drawn
if: >-
steps.art.outputs.verdict == 'holed' &&
steps.art.outputs.suggested-holes == '0'
run: echo "move it to week ${{ steps.art.outputs.suggested-start-week }}"
```

`today-short` counts a background day too, so a daily "what do I owe today"
notification keeps working unchanged when you add one. `today-kind` is what
tells the two apart — `letter`, `background`, `keep-dark`, `hole` or `outside`.
Expand Down
19 changes: 19 additions & 0 deletions action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,23 @@ outputs:
columns:
description: How many calendar columns the plan spans.
value: ${{ steps.track.outputs.columns }}
suggested-start-week:
description: >-
The best column the year has for this plan — often the one it already
sits in, so it is worth acting on only when `suggested-holes` is below
`holes`. A `holed` plan is the case this exists for: it is the one
verdict that cannot be answered by contributing more, so the column to
move to is the only thing left to do about it. Empty when no column
fits: a placement that pushes part of the drawing out of the year is
never suggested, and the first and last calendar columns are partial
weeks, so a picture with ink right to its edges has nowhere to go.
value: ${{ steps.track.outputs.suggested-start-week }}
suggested-holes:
description: >-
The holes `suggested-start-week` would leave. `0` means that column
draws the plan cleanly; any other number below `holes` means less bad
rather than fixed.
value: ${{ steps.track.outputs.suggested-holes }}
legibility:
description: >-
`clear`, `readable` or `faint` — how well the letters stand out from the
Expand Down Expand Up @@ -369,6 +386,8 @@ runs:
"field-level=" + (.field_level | tostring),
"start-week=" + (.start_week | tostring),
"columns=" + (.columns | tostring),
"suggested-start-week=" + ((.suggested_start_week // "") | tostring),
"suggested-holes=" + ((.suggested_holes // "") | tostring),
"legibility=" + .legibility,
"separation=" + (.separation | floor | tostring),
"field-days=" + (.field_days | tostring),
Expand Down
47 changes: 46 additions & 1 deletion docs/ART.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,48 @@ plan wants dark, and contributing on it punches a hole in the drawing exactly
as contributing inside a letter does. That is why the table above reports 219
of them: they are part of the picture.

### When a picture cannot be drawn where it sits

`holed` is the one verdict you cannot answer by contributing more: days inside
the picture are already brighter than the shade they are drawn at, and nothing
takes a contribution away. So it is the verdict that most owes you a next move,
and `--track` sweeps every column of the year to find one:

```
Cannot be drawn cleanly — 5 days are brighter than the picture wants,
and nothing takes a contribution away.
--start-week 37 draws it cleanly.
```

It is offered in every format — `suggested_start_week` and `suggested_holes` in
json, a line of its own in markdown, and the `suggested-start-week` output on
the Action.

Two things decide which column it picks.

**Fewest holes wins.** If nothing draws the picture cleanly you are told the
least bad column instead — `--start-week 12 would leave 3 holes instead of 25`
— and if every column is equally bad you are told that, because an emptier year
is then the only way out.

**Among columns that tie, one that has not begun yet.** A clean column in March
is arithmetic, not advice: the only way to draw there is `--backfill` into days
five months gone. This matters more than it sounds. An eleven-column picture in
a fifty-three column year can easily have nine placements costing zero holes,
and ranked by column alone the answer is always the one in January.

The preference only breaks ties. A past column that draws the picture cleanly
still beats a future one that does not, because back-dating is a thing this tool
does and unlighting a day is not.

A column that would push part of the picture off the end of the year is never
suggested: a truncated picture is not a cleaner drawing of the same picture, it
is a smaller one. The first and last calendar columns are partial weeks, so a
picture carrying ink right to its edges overhangs them wherever it is put, and
is offered nothing at all. Blank margins are not counted — losing an empty cell
costs the picture nothing — so `dragon`, fifty-three columns wide with quiet
edges, still places.

## Tracking it, day by day

Drawing the art is one command. Getting there while also living a normal year is
Expand Down Expand Up @@ -411,7 +453,10 @@ Four kinds of answer, and only two of them are work:
Nothing takes contributions away, so it is a hole in the text for good. This is
the honest answer to "why can't I write VYNCINT in 2026": not that it is
expensive, but that the year has already been written on. `--track` counts the
holes, and sweeps `--start-week` to find the placement that runs into fewest.
holes, and sweeps `--start-week` to find the placement that runs into fewest —
for a picture as well as for text, preferring a column that has not begun when
several cost the same. See
[When a picture cannot be drawn where it sits](#when-a-picture-cannot-be-drawn-where-it-sits).
- **A day outside the text with contributions.** Noise around the letters rather
than damage to them; reported, not warned about.

Expand Down
55 changes: 44 additions & 11 deletions src/bin/mossaic-art.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,10 +829,17 @@ fn track_canvas(
let year_total = actual
.values()
.fold(0u32, |sum, count| sum.saturating_add(*count));
// No suggestion: a picture is usually the full width of the year, so
// there is no column to move it to, and offering one that does not fit
// is worse than offering none.
let report = plan::Report::of(&plan, &who, year_total, today, None);
// The sweep, on the path that publishes. A picture used to pass
// `None` here on the reasoning that it "is usually the full width of
// the year, so there is no column to move it to" — true of the four
// shipped templates and wrong about every picture narrower than the
// year, which is the shape docs/ART.md asks contributors to draw.
// The overhang it was guarding against is now measured by
// `best_start_week_of` rather than assumed here, so a full-width
// template still gets exactly `None` — for the reason, not by
// refusing to look.
let suggestion = plan::best_start_week_of(canvas, grid, &actual, today);
let report = plan::Report::of(&plan, &who, year_total, today, suggestion);
match options.format {
Format::Json => println!(
"{}",
Expand Down Expand Up @@ -869,11 +876,29 @@ fn track_canvas(
{owing_days} to go.",
plural(wanted, "day is", "days are")
),
plan::Verdict::Holed { holes } => println!(
" Cannot be drawn cleanly — {holes} {} brighter than the \
picture wants,\n and nothing takes a contribution away.",
plural(holes, "day is", "days are")
),
plan::Verdict::Holed { holes } => {
println!(
" Cannot be drawn cleanly — {holes} {} brighter than the \
picture wants,\n and nothing takes a contribution away.",
plural(holes, "day is", "days are")
);
// `holed` is the one verdict a reader cannot act on by
// contributing more, so it is the one that most owes them a next
// move. The letters path has said this since it had a sweep; the
// picture path printed the diagnosis and stopped.
match plan::best_start_week_of(canvas, grid, &actual, today) {
Some((week, 0)) => println!(" --start-week {week} draws it cleanly."),
Some((week, left)) if left < holes => println!(
" --start-week {week} would leave {left} {} instead of {holes}.",
plural(left, "hole", "holes")
),
_ => println!(
" Every placement in {} runs into the same problem; an emptier\n \
year is the way out.",
grid.year
),
}
}
}

println!("\n level days done owing each");
Expand Down Expand Up @@ -1277,8 +1302,15 @@ fn track_progress(
// number the text report prints, so a notification never has to be parsed
// out of a screen.
if options.format != Format::Text {
let suggestion =
plan::best_start_week(grid, columns.len(), options.top, columns, &actual, hideable);
let suggestion = plan::best_start_week(
grid,
columns.len(),
options.top,
columns,
&actual,
hideable,
today,
);
let year_total = actual
.values()
.fold(0u32, |sum, count| sum.saturating_add(*count));
Expand Down Expand Up @@ -1454,6 +1486,7 @@ fn track_progress(
columns,
&actual,
hideable,
today,
) {
Some((week, left)) if left < holes => {
println!(" --start-week {week} would leave {left} instead of {holes}.")
Expand Down
Loading