Records the outcome of the design review that reworked the original handoff notes: - CONTEXT.md — ubiquitous language for the slicer (slice, cut, discard, content rectangle, trim, song scale, bundle, project, jump source) - docs/adr/0001 — the slicer owns all image processing and the bundle is the only channel to noteman; noteman's sharp pipeline goes away - pyproject renamed to noteman-slicer - .gitignore keeps scores and bundles out of the repo (copyright, size)
2.8 KiB
The slicer owns all image processing; the bundle is the only channel to noteman
The slicer was originally scoped to share the work with noteman: it would cut the
pages, upload them to POST /api/admin/songs/:id/slices, and noteman's sharp
pipeline would trim, downscale and convert ink to alpha on arrival. We decided
instead that the slicer produces finished, display-ready artifacts and hands them
over as a single zip; noteman does no image manipulation beyond what plain HTML
and CSS do at runtime, and there is no API between the two tools.
Considered options
Split the work (rejected). Slicer cuts, noteman normalises. This was the
existing shape and needed no changes on either side. It failed on inspection:
noteman's normalizeSlice calls sharp's removeAlpha() before its luminance
pass, and removeAlpha() discards the alpha channel rather than compositing it
over white — so any transparency the slicer produced would flatten to black ink.
A polygon-masked slice would have arrived as a solid black rectangle. More
fundamentally, the split put trim, downscale and encoding rules in two repos and
two languages, where they must agree exactly and nothing checks that they do.
Slicer produces final pixels, noteman stores them (chosen).
Direct POST with a bypass flag (rejected). Keeps the API but adds a content-sniff or flag to skip normalisation. Preserves the two-language duplication it was meant to remove, and needs an admin token in a local script.
Consequences
Several rules that read as arbitrary in the slicer's code are only explicable from here:
- The slicer owns trim, staff-height normalisation, the 1920px ceiling, right padding, levels and the ink→alpha conversion. Nothing downstream can rescue a faint scan or rehabilitate a bad crop, so the slicer's output is final.
- Scale is normalised on staff height, not width, and derived once per song. A per-slice factor would change note size line to line down a continuous scroll; a width-based factor breaks the moment a slice comes from a different source (a rescanned page, or a re-engraved replacement system).
- noteman must delete its sharp pipeline and accept a bundle instead. Until that lands, the slicer produces artifacts nothing consumes.
- The bundle is a versioned contract, not an internal format. Two separately
maintained repos now agree on: the marker enum, slice ordering, and the shape
of
song.json. That coupling is deliberately kept as small as possible — which is why every jump source stores its target slice explicitly rather than relying on an unwritten "exactly one Coda per song" resolution rule. - The split is reversible only in one direction. Re-adding server-side processing means re-deriving all of the above; going further the other way (more processing in the slicer) is free.