Files
noteman-slicer/docs/adr/0001-slicer-owns-image-processing-bundle-is-the-only-channel.md
T
Esa Kataja efc920fdc5 Add design docs: glossary, ADR 0001, and project scaffolding
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)
2026-07-28 21:33:14 +03:00

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.