ScanSlicer/MAINTENANCE.md
dan c8194742d1 docs: add maintenance guide; fix AppImage version selection; bump to 1.1.0
- Add MAINTENANCE.md (dependency updates, EOL watch, build/smoke-test
  workflows, orientation-fix invariants)
- Remove AGENTS.md; fold its unique context into MAINTENANCE.md
- Add .venv/.posit/appimage to .dockerignore to shrink build context
- Fix build-appimage.sh to select the wheel matching pyproject.toml
  version instead of alphabetically-first (bundled stale 1.0.0 code)
- Bump version 1.0.0 -> 1.1.0 in pyproject.toml, uv.lock, AppImage recipe
2026-09-12 15:11:07 +02:00

266 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Maintaining ScanSlicer
This file is a how-to for keeping the fork alive over time. The goals are
narrow and deliberate:
1. **Keep the app functional** for its one real job (detect photos in a flatbed
scan, correct the boxes, export straightened crops).
2. **Avoid security issues and breakage from end-of-life (EOL) components.**
There is **no ambition to develop new features**. Maintenance is manual: when
something drifts out of date, you bump a version, re-verify the app, and commit.
Do not add features, refactor, or "improve" things while doing maintenance —
small, focused changes only.
> Scope guard: this repo is a local fork of `macrodev8/ScanSlicer`, published at
> `https://code.dgerdesmann.de/dan/ScanSlicer.git`. License is GPL v3. Keep the
> upstream credit and the "inspired by PhotoSlicer" note intact. See `README.md`
> and `docs/` for project context.
---
## Where things live
| Component | File(s) |
|---|---|
| Package metadata + dependency constraints | `pyproject.toml` |
| **Pinned** dependency versions (source of truth) | `uv.lock` |
| Runtime container image | `Dockerfile` (ubuntu:24.04 + uv) |
| AppImage recipe + build scripts | `packaging/appimage/` |
| Bundled third-party license notice | `THIRD_PARTY_NOTICES` |
| Dev env (Python 3.12) | `.venv/` (managed by uv) |
Dependency management is done with **uv**. `pyproject.toml` declares the
requirements (mostly loose), and `uv.lock` pins the *exact* versions that get
installed and shipped. `uv.lock` is committed — do not edit it by hand; let uv
rewrite it.
---
## Code invariants (orientation fix + bbox order)
This fork's one real code change is the mirrored-export orientation fix. Keep
these invariants in mind when touching geometry code.
- **`PhotoSlice.bbox` is a 4×2 numpy array in the order
`[top-left, top-right, bottom-right, bottom-left]`.** `Autoslicer.save_slice()`
(`autoslicer.py`) builds the perspective transform assuming that order. Do NOT
change this contract without updating every consumer.
- **`PhotoSlice.swapped`** (default `False`) is the persistent "user explicitly
mirrored left/right" flag. `swap_left_right()` toggles it and reindexes
corners `[TL,TR,BR,BL] -> [TR,TL,BL,BR]` (`bbox[[1,0,3,2]]`) — a pure
left/right mirror that keeps the top and bottom edges in place.
- **Root cause of the original bug:** `normalize_top_left()` (`slices.py`)
re-derives "top" from geometry, which is ambiguous, and `squarize()` /
`reset_rotation()` both re-run it at the end, silently overwriting manual
corrections. The fix makes `normalize_top_left()` apply the same `[1,0,3,2]`
reindex when `swapped` is true, so manual orientation survives. (Historical:
an earlier `[0,3,2,1]` reindex was wrong — it rotated the quad 90°.)
- **UI wiring:** "Swap L/R" button (`ui_builders_preview.py`) →
`CanvasStateMixin.swap_active_slice_left_right()` (`canvas_state.py`) →
`ScanSlicerCanvasActionsMixin.swap_active_box_left_right()`
(`controller_canvas_actions.py`) → `PhotoSlice.swap_left_right()`.
- **Visual color coding** (`canvas_render.py`): corners — left = orange, right
= cyan; edges — red = top (0→1), green = right (1→2), orange = bottom (2→3),
left edge (3→0) follows lock state (blue locked / lightgreen unlocked).
---
## Current pinned versions (snapshot)
Update this table whenever you bump something, so the next session has a
starting point.
| Component | Pinned version | Where |
|---|---|---|
| PySide6 / PySide6-Addons / -Essentials / shiboken6 | 6.10.2 | `uv.lock` |
| opencv-python-headless | 4.13.0.92 | `uv.lock` (constraint `~=4.13.0` in `pyproject.toml`) |
| numpy | 2.2.6 (Py<3.11) / 2.4.2 (Py≥3.11) | `uv.lock` |
| Python (dev env) | 3.12 | `.venv` |
| Python floor | `>=3.10` | `pyproject.toml` |
| uv (both Dockerfiles) | 0.10.2 | `Dockerfile`, `packaging/appimage/Dockerfile` |
| appimage-builder | 1.1.0 | `packaging/appimage/Dockerfile` |
| AppImage base OS | Ubuntu **noble** (24.04) | `packaging/appimage/AppImageBuilder.yml` |
| Runtime image base | Ubuntu 24.04 | `Dockerfile` |
---
## What can go stale, and the priority order
Review these roughly in this order — the first items are the real security/EOL
surface; the last ones are packaging plumbing that only matters at build time.
1. **PySide6 / Qt** — the largest attack surface and the component with the
most aggressive EOL cadence. Qt 6.x minor releases stop receiving updates
quickly; PySide6 tracks Qt. Keep this on a recent 6.x.
2. **opencv-python-headless** — image processing; bump to the latest 4.x.
3. **numpy** — bump to the latest 2.x.
4. **Python** — the *shipped* runtime is what matters. The AppImage bundles its
own Python (from Ubuntu noble, currently 3.12), so the app does not run on
the host's Python. The `requires-python = ">=3.10"` floor only affects
development resolution. Watch Python 3.10's EOL: when it goes EOL, raise the
floor (e.g. to `>=3.11`) so no EOL interpreter is ever considered valid.
5. **Packaging toolchain** — `uv` and `appimage-builder` versions pinned in the
two Dockerfiles. Only bump these when you have a reason (build breaks, or a
notable security fix in the tool itself).
6. **Ubuntu base images** — `noble` is an LTS (supported to ~2029). Revisit
the apt source and GPG key in `AppImageBuilder.yml` and the `FROM` line in
both Dockerfiles when noble approaches EOL.
Suggested cadence: check PyPI/upstream releases every ~3–6 months, or promptly
when a security advisory is published for any of the above.
---
## Updating Python dependencies
All commands run from the repo root. The dev env is `.venv` (Python 3.12).
### 1. Bring the environment up to the committed lock (baseline)
```bash
uv sync --frozen
```
### 2. Bump a single dependency to its latest
```bash
uv lock --upgrade-package PySide6
uv lock --upgrade-package opencv-python-headless
uv lock --upgrade-package numpy
```
`--dry-run` previews the change without touching anything:
```bash
uv lock --upgrade-package PySide6 --dry-run
```
To bump *everything* at once (use sparingly — harder to bisect if something
breaks):
```bash
uv lock --upgrade
```
### 3. Install the updated set and verify
```bash
uv sync
uv run scanslicer /path/to/test/scans
```
Then run the smoke-test checklist below.
### Special case: opencv major/minor bump
`opencv-python-headless` is the **only** dependency with a constraint in
`pyproject.toml` (`~=4.13.0`), which pins it to the 4.13.x line. To move to
4.14+, edit that constraint in `pyproject.toml` first, then run
`uv lock --upgrade-package opencv-python-headless`. PySide6 and numpy are
unpinned in `pyproject.toml`, so uv can move them freely.
### After any version change
- Commit `pyproject.toml` (if edited) **and** `uv.lock` together.
- Update the version table above.
- Update `THIRD_PARTY_NOTICES` — it lists concrete PySide6/Qt, numpy, and
opencv versions, and it ships inside every AppImage. Keep it aligned with
`uv.lock`.
- Rebuild and smoke-test the AppImage if the runtime stack (PySide6/Qt) changed.
---
## Updating the AppImage packaging toolchain
The AppImage build pins its own tool versions in `packaging/appimage/Dockerfile`:
- `uv` (`COPY --from=ghcr.io/astral-sh/uv:0.10.2 ...`)
- `appimage-builder==1.1.0` (an `ARG` near the top)
Bump either by editing that file. If you change `appimage-builder`, the
`AppImageBuilder.yml` recipe may need adjusting for new versions (rare). The
Ubuntu apt source and GPG key live in `AppImageBuilder.yml` — update those when
noble goes EOL or the key rotates.
> **Hardcoded Python paths:** `AppImageBuilder.yml` hardcodes `python3.12` in
> `QT_PLUGIN_PATH` and `LD_LIBRARY_PATH`. If the AppImage base OS ever moves to
> a different Python minor, those two lines must be updated to match.
---
## Updating the runtime Docker image
`Dockerfile` (repo root) pins the base (`ubuntu:24.04`) and `uv 0.10.2`.
Bump these when needed, then rebuild and run the container smoke test:
```bash
docker compose up --build
```
---
## Verifying a change (smoke-test checklist)
There is **no automated test suite**. After any dependency or packaging change,
manually confirm the app still works:
1. **Startup** on both Wayland and X11:
```bash
uv run scanslicer --backend wayland
uv run scanslicer --backend x11
```
2. **Detection** on a representative sample scan (boxes appear on load/Detect).
3. **Save flow** — lock a box, export, confirm the output is straight and not
mirrored (this fork's orientation fix is the thing most likely to regress).
4. **Auto-tune** — run a profile and confirm feedback is written/read without
error.
5. If the runtime stack changed: rebuild the AppImage and confirm it launches
and saves.
6. If the Docker image changed: `docker compose up --build` and confirm the app
opens.
---
## The Docker gotcha (recap — keep this in mind)
The maintainer's Docker points at **Docker Desktop** (`desktop-linux` context).
Its bind-mount filesystem forbids `chown`, but `appimage-builder` needs root
ownership while extracting `.deb` packages, so the Dockerized AppImage build
fails with:
```
dpkg-deb ... Cannot change ownership to uid 0, gid 0: Operation not permitted
```
Workaround: run the build against the **native** daemon instead:
```bash
DOCKER_HOST=unix:///var/run/docker.sock bash packaging/appimage/build-appimage-docker.sh
```
(or `docker context use default` first). Output lands in
`appimage/dist/ScanSlicer-<version>-x86_64.AppImage` (the `appimage/` dir is
git-ignored).
---
## Commit / release hygiene
- Commit `uv.lock` and `pyproject.toml` (and `THIRD_PARTY_NOTICES` when
applicable) together as a single change.
- Write a short, factual commit message, e.g. `Bump PySide6 to 6.10.x`.
- Push to the Forgejo remote: `git push origin`.
- The AppImage build verifies package hashes (`--require-hashes`) from
`uv.lock`; publishing the AppImage alongside the matching committed source is
already handled by committing before building.
---
## One-line summary
Bump PySide6/Qt first and most often; bump opencv and numpy when convenient;
treat uv/appimage-builder/Ubuntu as build plumbing you only touch when
necessary. After every change, run the smoke-test checklist, rebuild the
AppImage if the runtime stack moved, and commit `uv.lock` + `pyproject.toml`
together.