Slide scanning editor
- Svelte 42.5%
- Go 35.6%
- TypeScript 13.4%
- NSIS 2.7%
- Python 2.1%
- Other 3.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
## Structured logging (internal/applog + slog migration) Add `internal/applog` — a new package that initialises the global `log/slog` logger with a rotating JSON file handler (lumberjack: 10 MB / 5 backups / 30 days). Toggled to Debug level via `PHOTOLOG_DEBUG=1`. Includes an `E()` helper that annotates errors with their call-site file:line so origin is visible after wrapping. Migrate all `runtime.LogError` / `runtime.LogWarningf` calls in `app.go`, `internal/camera/librebel/service.go`, and throughout `photolog/service.go` to structured `slog.Error` / `slog.Warn` / `slog.Info` calls. Logger is initialised in `startup()` once the log-directory path is known; failures are non-fatal (slog continues writing to stderr only). ## Markdown formatting in slide notes (goldmark + TextRun) Add `github.com/yuin/goldmark` v1.8.0 as a new dependency. Add `internal/photolog/markdown.go` with `TextRun` (text + bold/italic/ strikethrough flags) and `parseNoteMarkdown()` — parses the inline markdown in a note string, walking the goldmark AST to collect `[][]TextRun` (one slice of runs per line, capped at 4 lines). Block-level structure is flattened; only `**bold**`, `*italic*`, and `~~strike~~` are preserved as run attributes. `ToSlideDTO()` now populates `TopNoteRuns` / `BottomNoteRuns` fields on `SlideDTO`, so the frontend receives server-rendered formatting without needing a markdown parser on the client side. `SlideFrameSvg.svelte` now accepts optional `topNoteRuns` / `bottomNoteRuns` props (`TextRun[][]`). When present they take precedence over plain-text splitting; the fallback `parseNotePlain()` is kept for browser-preview mode. Note text is rendered per-run with `font-weight` / `text-decoration` tspan attributes so bold and strikethrough are visible in the SVG frame. `SlideDTO.TopNoteRuns` / `BottomNoteRuns` propagate through the Wails bindings (`models.ts`, `App.d.ts`, `App.js`) and the frontend `types.ts` / `Slide` type. ## RecropSlide — regenerate JPEG from existing master CR2 Add `App.RecropSlide(slideID string) (CaptureResultDTO, error)` as a new Wails-callable method. The underlying `Service.RecropSlide()` looks up the catalogued `master-cr2` asset, reads the file from disk, calls `generateDerivatives()` to regenerate the JPEG preview and thumbnail, then returns a `CaptureResultDTO` with fresh auto-detected crop bounds — the same shape as a first capture, so the crop modal can be reused. The "Recrop from CR2" button in `MagazineWorkspace.svelte` is disabled when the CR2 asset is absent. `runRecropSlide()` in the store opens the crop modal and leaves `TransformingAsset = true` until the modal is dismissed (matching the existing post-capture flow). `buildCaptureResult()` now unconditionally syncs catalog JPEG dimensions from the actual file (previously only when `Width == 0`), so a regenerated JPEG after RecropSlide doesn't carry stale dimensions from a prior crop. `TransformJPEG()` now returns `([]byte, int, int, error)` — the encoded bytes plus the post-transform width and height — so `TransformJPEGAsset()` can update the catalog dimensions without an additional decode. ## Crop modal UX fixes (SlideCropModal.svelte) - **Drag handler attachment**: `onmousemove` / `onmouseup` are now on the dialog container element instead of `window`, preventing events from being lost when the pointer exits the browser chrome. The manual `addEventListener` / `removeEventListener` calls on window are removed. - **Per-corner aspect-ratio resize**: each of the eight handles (nw / ne / sw / se / n / s / e / w) now has its own resize formula. Corner handles (nw, ne, sw, se) collapse the delta into a single diagonal scalar and preserve the 3:2 ratio from that single degree of freedom. Side handles (e, w) centre the height adjustment around the crop's current midpoint. The previous shared branch that guessed direction from drag mode name caused asymmetric behaviour when pulling corners. - **Crop initialisation guard**: a `cropInitialized` flag prevents re-renders that produce a new `suggestedCrop` object reference from resetting the user's edits mid-interaction. The flag resets when the modal closes. - **`cancelLabel` prop**: the cancel button label is now configurable. Callers from a post-capture flow pass `'Skip crop'`; the standalone recrop flow passes `'Cancel'`. ## SlideFrameSvg improvements - **Portrait clip-path fix**: the `clip-path` attribute is now on a `<g>` wrapper element rather than directly on the `<image>` element. SVG clip-paths on `<image>` clip in pre-transform coordinates, leaving black bars after a 90° rotation; placing it on the wrapping `<g>` clips in post-transform space. - **Strip-clamped `blockStartY()`**: the helper now accepts `stripMin` / `stripMax` bounds and clamps the text block so neither the first baseline minus ascenders nor the last baseline plus descenders overflows the mount's border strip (top: 3–64, bottom: 176–237). - **Used in MagazineWorkspace preview**: the workspace preview pane replaces its plain `<img>` with `SlideFrameSvg` so notes, formatting, and frame decorations appear in the workspace the same way they do in the catalog card. Portrait images are counter-rotated to appear upright (using stored asset dimensions when available, falling back to a browser Image load). ## Sticky formatting preferences When navigating to a pending slide (one with no saved note-formatting fields), the store now preserves the current alignment / margin / inverted button states instead of resetting them to defaults. This means the operator can set their preferred style once and it carries across the session. ## Debug instrumentation (temporary — to be removed) - `App.DebugLog(msg)` / `debugLog()` in `wails.ts` — routes frontend log lines to the application log file via slog.Debug. - Debug overlay in `SlideCropModal` showing live crop coordinates, display scale, and drag event payloads. - `imageutil.JPEGDimensions()` — decodes a JPEG to report its pixel dimensions (used by the `CropJPEGAsset` debug log). - SVG outlines (cyan border on the overlay SVG, hotpink on the image boundary). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
| build | ||
| cmd/liveview-smoke | ||
| docs | ||
| frontend | ||
| internal | ||
| plans | ||
| scripts | ||
| tools/python | ||
| .gitignore | ||
| AGENTS.md | ||
| app.go | ||
| CLAUDE.md | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| PLAN.md | ||
| README.md | ||
| wails.json | ||
Photolog
Photolog is a Wails desktop application for digitizing 35mm slides from Airequipt Model-P magazines with a Canon EOS Rebel SL1.
Current foundations in this repo:
librebel-oriented camera integration scaffold- local catalog/bootstrap persistence
- magazine creation UI
- slide-analysis import and execution flow
- in-repo Python geolocation worker
Requirements
- Go 1.25+
- Node.js 20.19+ or 22.12+
- Wails CLI v2.11.0+
- Python 3 for the geolocation worker
Development
Frontend:
cd frontend
pnpm install
cd ..
Run the desktop app:
wails dev
Run Go tests:
GOCACHE=/tmp/photolog-gocache go test ./...
Run frontend checks:
cd frontend
pnpm run check
pnpm run build
Build a desktop binary:
wails build
Analysis Worker
The geolocation worker lives in:
Setup and runtime notes are documented in:
Offline place lookup is expected at: