···11+# Back Up Rendered Edits Alongside Originals
22+33+**Date:** 2026-03-13
44+**Status:** Ready for planning
55+66+## What We're Building
77+88+Extend the backup pipeline to detect edited photos/videos and upload the rendered (fullsize JPEG) version alongside the original. Also detect edits made to already-backed-up assets and upload their rendered versions retroactively.
99+1010+## Why This Approach
1111+1212+The backup should be self-contained and viewable without Apple Photos. Currently only originals are backed up. Apple Photos edits are non-destructive (the original is always preserved), but the "finished" version a user actually wants to see requires either Apple Photos or the adjustment plist to re-render. Backing up the rendered version makes the backup independently useful.
1313+1414+## Current State
1515+1616+| Metric | Value |
1717+|--------|-------|
1818+| Total assets | 37,289 |
1919+| Edited assets | 1,312 (3.5%) |
2020+| Rendered versions (fullsize JPEG, resource type 1) | 1,672 resources |
2121+| Locally available renders | 1,480 |
2222+| Original size (edited subset) | ~6.2 GB |
2323+| Rendered size (edited subset) | ~13.7 GB |
2424+2525+Edit sources: Apple Photos (1,181), slo-mo (47), Google Photos (42), Markup (11), Adobe Lens (9), Snapseed (3).
2626+2727+## Key Decisions
2828+2929+1. **Back up rendered versions** (not just adjustment plists). The fullsize JPEG is what users actually see. Plists are Apple-internal and not portable.
3030+3131+2. **Sibling key with `_edited` suffix** in S3:
3232+ ```
3333+ originals/2024/01/{uuid}.heic # original
3434+ originals/2024/01/{uuid}_edited.jpg # rendered edit
3535+ metadata/assets/{uuid}.json # includes edit metadata
3636+ ```
3737+3838+3. **Same pass as originals**. When processing a batch, detect edits and upload both files together. No separate command needed.
3939+4040+4. **Re-scan already-backed-up assets for new edits**. Compare adjustment timestamps against the manifest's `backedUpAt` to detect photos edited after their initial backup.
4141+4242+## Data Sources in Photos.sqlite
4343+4444+### Edit detection
4545+- `ZUNMANAGEDADJUSTMENT` joined via `ZADDITIONALASSETATTRIBUTES` tells us an asset has been edited
4646+- `ZADJUSTMENTTIMESTAMP` tells us when the edit happened
4747+- `ZADJUSTMENTFORMATIDENTIFIER` tells us which editor (com.apple.photo, com.adobe.lens, etc.)
4848+4949+### Rendered file location
5050+- `ZINTERNALRESOURCE` with `ZRESOURCETYPE = 1` (fullsize JPEG) points to the rendered version
5151+- `ZLOCALAVAILABILITY = 1` means the file is on disk
5252+- `ZDATALENGTH` gives the file size
5353+- The actual file lives in the Photos Library package, path derivable from `ZDATASTORECLASSID` + fingerprint
5454+5555+### Export via ladder
5656+- The current exporter uses PhotoKit ID `{uuid}/L0/001` for originals
5757+- Rendered versions may need a different resource variant or direct file copy from the library package
5858+5959+## Scope
6060+6161+### In scope
6262+- Detect which assets have edits (via ZUNMANAGEDADJUSTMENT)
6363+- Add edit metadata to PhotoAsset and the S3 metadata JSON (hasEdit, editedAt, editor)
6464+- Export and upload rendered fullsize JPEG alongside original
6565+- Re-scan manifest for assets edited after backup
6666+- Track edit backup state in manifest (so renders are not re-uploaded)
6767+6868+### Out of scope
6969+- Backing up adjustment plists (edit recipes)
7070+- Handling slo-mo video rendering (complex, different pipeline)
7171+- Re-rendering from adjustment data outside Apple Photos
7272+- Backing up thumbnails or other resource types
7373+7474+## Resolved Questions
7575+7676+1. **Manifest schema**: Extend the existing manifest entry with optional `editS3Key`, `editChecksum`, `editBackedUpAt` fields. No separate entries, no schema break.
7777+7878+2. **Re-edit handling**: Always upload the latest render. Compare adjustment timestamp against `editBackedUpAt` to detect re-edits. The backup should reflect the current state of the edit.
7979+8080+## Open Questions
8181+8282+1. **How does ladder/PhotoKit export the rendered version?** The current `/L0/001` suffix gets the original. Need to investigate what identifier or API call retrieves the fullsize rendered JPEG. May need a ladder change.
8383+8484+2. **What about iCloud-only rendered versions?** 1,480 of 1,672 renders are local. The remaining ~200 may need to be downloaded first, same as iCloud-only originals. Is there an existing mechanism for this?
···11+# UX and Open-Source Readiness
22+33+**Date:** 2026-03-13
44+**Status:** Ready for planning
55+66+## What We're Building
77+88+Make attic friendly to use for technical Mac users and ready to open source. Replace hardcoded Scaleway configuration with a generic S3-compatible config layer, add an interactive `attic init` command, adopt Cliffy for polished CLI output, and improve error messages throughout.
99+1010+## Why This Approach
1111+1212+Attic currently works well as a personal tool but has Scaleway details baked into the code (endpoint, region, keychain service names, type names). To open source it, the tool needs to work with any S3-compatible provider out of the box. The UX should feel polished — good help text, colored output, and clear error messages that tell you what to do next.
1313+1414+## Current State
1515+1616+| Area | Current | Target |
1717+|------|---------|--------|
1818+| S3 endpoint/region | Hardcoded Scaleway constants | Config file, any S3-compatible provider |
1919+| Bucket name | Hardcoded default, `--bucket` flag | Config file, CLI override |
2020+| Credentials | Keychain with hardcoded service names | Keychain with configurable service names |
2121+| Config file | None | `~/.attic/config.json` |
2222+| First-run setup | Manual (read README, set keychain, run) | `attic init` interactive prompts |
2323+| CLI framework | Hand-rolled arg parsing | Cliffy (subcommands, typed flags, help, color) |
2424+| Error messages | Raw exceptions in some paths | Friendly messages with suggested fixes |
2525+| path style | Hardcoded `true` | Config option, default `true` |
2626+| Provider docs | Scaleway-specific | Provider-neutral with EU-focused examples |
2727+2828+## Key Decisions
2929+3030+1. **Config file at `~/.attic/config.json`** — primary configuration source. CLI flags override. No env var fallback (keep it simple, macOS-only tool).
3131+3232+2. **Interactive `attic init`** — asks for S3 endpoint, region, bucket, and keychain service names step by step. Writes config.json. Can offer provider suggestions (Scaleway, Hetzner, OVH as EU options).
3333+3434+3. **Keychain with configurable service names** — stay macOS Keychain-only (security principle from CLAUDE.md), but let config.json specify the service names instead of hardcoding `attic-s3-access-key` / `attic-s3-secret-key`.
3535+3636+4. **Cliffy for CLI** — replace hand-rolled arg parsing with Cliffy. Gets us subcommands, typed flags, auto-generated help, colored output, and shell completions.
3737+3838+5. **`forcePathStyle` as config option** — default `true` (works with most S3-compatible providers). AWS users can set to `false`.
3939+4040+6. **EU-focused provider examples** — highlight Scaleway, Hetzner, OVH as EU data sovereignty options in docs and init prompts. Mention AWS/Backblaze as alternatives. Position attic as a good choice for keeping your photos in the EU.
4141+4242+7. **Top-level error boundary** — catch unhandled errors in mod.ts, present friendly messages instead of stack traces. Pattern: detect known error types (keychain missing, network timeout, S3 access denied) and print actionable guidance.
4343+4444+## Config File Schema
4545+4646+```json
4747+{
4848+ "endpoint": "https://s3.fr-par.scw.cloud",
4949+ "region": "fr-par",
5050+ "bucket": "my-photo-backup",
5151+ "pathStyle": true,
5252+ "keychain": {
5353+ "accessKeyService": "attic-s3-access-key",
5454+ "secretKeyService": "attic-s3-secret-key"
5555+ }
5656+}
5757+```
5858+5959+## Scope
6060+6161+### In scope
6262+- Config file (`~/.attic/config.json`) with validation
6363+- `attic init` interactive setup command
6464+- Cliffy migration for all commands (scan, status, backup, verify)
6565+- Rename `ScalewayCredentials` to `S3Credentials`, remove `SCALEWAY_*` constants
6666+- `createS3Provider()` accepts endpoint, region, pathStyle as parameters
6767+- Top-level error boundary with friendly messages for known failure modes
6868+- Updated README, CLAUDE.md, and architecture docs
6969+- EU-focused provider examples in docs and init
7070+7171+### Out of scope
7272+- Env var credential fallback (keep Keychain-only)
7373+- Non-macOS support
7474+- Provider presets in init (just ask for endpoint/region directly, with examples)
7575+- Web UI or GUI
7676+- Auto-detection of Photos.sqlite path across macOS versions
7777+7878+## Resolved Questions
7979+8080+1. **Audience**: Technical Mac users comfortable with terminal and S3 setup.
8181+2. **Config approach**: Config file at `~/.attic/config.json`, CLI flags override.
8282+3. **Init style**: Interactive prompts, writes config at the end.
8383+4. **Credentials**: Keychain-only with configurable service names in config.
8484+5. **Provider presentation**: EU-focused examples (Scaleway, Hetzner, OVH), others mentioned as alternatives.
8585+6. **Path style**: Config option `pathStyle`, default `true`.
8686+7. **CLI framework**: Cliffy.
8787+8. **Init stores credentials directly**: `attic init` prompts for access key and secret key and runs `security add-generic-password` automatically.
8888+9. **Validate config when S3 is needed**: scan/status only need Photos.sqlite — they work without config. backup/verify validate config and fail fast with a clear message if missing or incomplete.