- 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
10 KiB
Maintaining ScanSlicer
This file is a how-to for keeping the fork alive over time. The goals are narrow and deliberate:
- Keep the app functional for its one real job (detect photos in a flatbed scan, correct the boxes, export straightened crops).
- 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 athttps://code.dgerdesmann.de/dan/ScanSlicer.git. License is GPL v3. Keep the upstream credit and the "inspired by PhotoSlicer" note intact. SeeREADME.mdanddocs/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.bboxis 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(defaultFalse) 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, andsquarize()/reset_rotation()both re-run it at the end, silently overwriting manual corrections. The fix makesnormalize_top_left()apply the same[1,0,3,2]reindex whenswappedis 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.
- 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.
- opencv-python-headless — image processing; bump to the latest 4.x.
- numpy — bump to the latest 2.x.
- 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. - Packaging toolchain —
uvandappimage-builderversions pinned in the two Dockerfiles. Only bump these when you have a reason (build breaks, or a notable security fix in the tool itself). - Ubuntu base images —
nobleis an LTS (supported to ~2029). Revisit the apt source and GPG key inAppImageBuilder.ymland theFROMline 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)
uv sync --frozen
2. Bump a single dependency to its latest
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:
uv lock --upgrade-package PySide6 --dry-run
To bump everything at once (use sparingly — harder to bisect if something breaks):
uv lock --upgrade
3. Install the updated set and verify
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) anduv.locktogether. - 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 withuv.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(anARGnear 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.ymlhardcodespython3.12inQT_PLUGIN_PATHandLD_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:
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:
- Startup on both Wayland and X11:
uv run scanslicer --backend wayland uv run scanslicer --backend x11 - Detection on a representative sample scan (boxes appear on load/Detect).
- 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).
- Auto-tune — run a profile and confirm feedback is written/read without error.
- If the runtime stack changed: rebuild the AppImage and confirm it launches and saves.
- If the Docker image changed:
docker compose up --buildand 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:
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.lockandpyproject.toml(andTHIRD_PARTY_NOTICESwhen 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) fromuv.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.