Slide scanning editor
  • Svelte 42.5%
  • Go 35.6%
  • TypeScript 13.4%
  • NSIS 2.7%
  • Python 2.1%
  • Other 3.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Kyle Mueller cae73a178d Add RecropSlide, markdown note formatting, structured logging, and crop modal UX fixes
## 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>
2026-04-07 08:36:30 -04:00
build back port to wails2 is complete. Local Paths dialog is working. 2026-03-20 11:21:33 -04:00
cmd/liveview-smoke Application runs on wails3 and offers basic functionality 2026-03-20 08:52:22 -04:00
docs Application runs on wails3 and offers basic functionality 2026-03-20 08:52:22 -04:00
frontend Add RecropSlide, markdown note formatting, structured logging, and crop modal UX fixes 2026-04-07 08:36:30 -04:00
internal Add RecropSlide, markdown note formatting, structured logging, and crop modal UX fixes 2026-04-07 08:36:30 -04:00
plans GPS metadata, EXIF re-application, live view UX, image transforms, and magazine map improvements 2026-03-20 18:22:55 -04:00
scripts back port to wails2 is complete. Local Paths dialog is working. 2026-03-20 11:21:33 -04:00
tools/python Application runs on wails3 and offers basic functionality 2026-03-20 08:52:22 -04:00
.gitignore Application is running, continued work on UI 2026-03-20 08:52:45 -04:00
AGENTS.md back port to wails2 is complete. Local Paths dialog is working. 2026-03-20 11:21:33 -04:00
app.go Add RecropSlide, markdown note formatting, structured logging, and crop modal UX fixes 2026-04-07 08:36:30 -04:00
CLAUDE.md back port to wails2 is complete. Local Paths dialog is working. 2026-03-20 11:21:33 -04:00
go.mod Add RecropSlide, markdown note formatting, structured logging, and crop modal UX fixes 2026-04-07 08:36:30 -04:00
go.sum Add RecropSlide, markdown note formatting, structured logging, and crop modal UX fixes 2026-04-07 08:36:30 -04:00
main.go back port to wails2 is complete. Local Paths dialog is working. 2026-03-20 11:21:33 -04:00
PLAN.md back port to wails2 is complete. Local Paths dialog is working. 2026-03-20 11:21:33 -04:00
README.md back port to wails2 is complete. Local Paths dialog is working. 2026-03-20 11:21:33 -04:00
wails.json Enable Vite HMR in wails dev 2026-03-20 12:29:38 -04:00

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: