docs(direction): standalone HTML + Android APK are the primary targets; drop iOS (#172)
## Summary Bring all direction-flavoured docs into sync with the May 2026 state of the project. **Primary distribution targets are now:** 1. **Desktop offline standalone HTML** (`dist/web-standalone/index.html`) — produced by `yarn build:web:standalone` 2. **Android APK** (Capacitor wrapper) — produced by `.github/workflows/build-android.yml` or `scripts/build-apk-local.sh` **Demoted to secondary:** - The deployed web PWA (`dist/web/`) is still buildable and self-hostable via the included Docker + Cloudflare Pages paths, but is no longer the recommended user-facing distribution. **Out of scope:** - iOS in any form (App Store, PWA via Safari, Add to Home Screen). ## What changed | File | Change | |---|---| | `.claude/rules/project-direction.md` | Main rewrite: "One code path, two distribution targets"; "Mobile = Android APK, not iOS"; "What's NOT in scope" updated; Phase E.1 issue list recast | | `.claude/rules/modernization-roadmap.md` | Phase E.1 table: #50 (iOS Photos picker) and #52 (PWA install prompt UX) marked out-of-scope; #23 recast from Web Share Target PWA to Android Intent filter; "PWA is sole channel" claim brought current; key constraints updated | | `.claude/rules/privacy-invariants.md` | §2 expanded to cover all three distribution paths (standalone inlines everything, APK uses Capacitor's localhost interceptor, self-host PWA caches via service worker) | | `CLAUDE.md` | Top of file + Tech Stack section reflect dual primary distribution | | `README.md` | Features list + Project Direction section reflect dual primary, iOS dropped, standalone-on-Android note updated to point at the APK | | `docs/android-apk.md` | Line 21 note flipped (APK is now primary, not "personal-distribution / not official"); comparison table relabelled; AAR conclusion updated | | `docs/deploying.md` | Reframed as the self-host PWA doc; iOS Safari install instructions removed; intro note clarifies this is secondary distribution | | `docs/architecture.md` | History note brought current — mentions APK #156 + standalone HTML as primaries | | `docs/PRIVACY_GAPS.md` | Android filesystem-isolation note updated to recommend the APK path | | `docs/standalone-html.md` | "No PWA install" trade-off bullet now points at the Android APK | 10 files changed, 81 insertions, 58 deletions. ## Phase E issues to close as out-of-scope This direction change makes two open issues out-of-scope. Suggested follow-up — close (not in this PR, since closing is a separate decision): - **#50** iOS Photos picker UX note — iOS dropped entirely - **#52** PWA install prompt UX — deployed PWA demoted to self-host only #23 (Web Share Target PWA) is recast rather than closed — the underlying "let users share files into MetaScrub from the Android gallery" feature is still wanted, but the implementation switches from the Web Share Target API in the manifest to a Capacitor `@capacitor/share` / native Intent filter. ## What I deliberately did NOT touch - **`CHANGELOG.md`** — historical release notes. The Phase G entry says "PWA is the sole distribution channel" which was accurate at the time; changelogs are snapshots, not living documents. - **`docs/superpowers/plans/2026-05-14-phase-g-rollout.md` + `docs/superpowers/specs/2026-05-14-phase-g-electron-retirement-design.md`** — historical phase plans/specs describing the work as it was at the time of execution. - **Playwright e2e mobile-iOS configs** — these test responsive layouts under an iOS Safari user agent, useful coverage independent of iOS being a shipping target. Removing them is a separate test-strategy decision, not a direction-doc concern. - **Source code** — only two iOS references in `src/`, both in comments describing the mobile-browser landscape generally (not iOS-gated code paths). No code change needed for the direction shift. ## Test plan - [x] All edits are documentation-only; no source code touched - [x] No `*.md` linting in CI; prettier-check only targets `src/**/*.{ts,tsx}` - [ ] Reviewer reads `.claude/rules/project-direction.md` first; satellites mirror its language - [ ] Decide whether to close #50 + #52 as out-of-scope as a follow-up 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Randa <obuvuyoviz26@gmail.com> Reviewed-on: http://forgejo.localhost:3000/forgejo_admin/exifcleaner-web/pulls/172
This commit is contained in:
parent
e8c0940024
commit
6e52fd894f
10 changed files with 81 additions and 58 deletions
|
|
@ -183,14 +183,14 @@ These phases collapse the previously two-engine architecture (bundled ExifTool i
|
|||
|
||||
Phase E was re-scoped on 2026-05-10 (see [`docs/superpowers/specs/2026-05-10-phase-e-f-design.md`](../../docs/superpowers/specs/2026-05-10-phase-e-f-design.md)). Items shipped during the original Phase E run are marked closed; the remaining work plus post-Phase-D mobile/UX issues are organised into three sub-tracks. HEIC moved to Phase F as the lead format-coverage item.
|
||||
|
||||
**E.1 Mobile-critical UX (ship first)**
|
||||
**E.1 Android-critical UX (ship first)**
|
||||
|
||||
| Issue | Item | Status |
|
||||
| --- | --- | --- |
|
||||
| #49 | Mobile touch UX pass | 🟡 Pending |
|
||||
| #50 | iOS Photos picker UX note | 🟡 Pending |
|
||||
| #49 | Mobile touch UX pass (APK) | 🟡 Pending |
|
||||
| #50 | iOS Photos picker UX note | ❌ Out of scope (iOS dropped 2026-05-21; close as out-of-scope) |
|
||||
| #51 | Unsupported-format messaging | 🟡 Pending |
|
||||
| #52 | PWA install prompt UX | 🟡 Pending |
|
||||
| #52 | PWA install prompt UX | ❌ Out of scope (deployed PWA demoted to self-host only; close as out-of-scope) |
|
||||
| #63 | Per-format size caps + messaging | 🟡 Pending |
|
||||
|
||||
**E.2 Features (after E.1 baseline)**
|
||||
|
|
@ -198,8 +198,8 @@ Phase E was re-scoped on 2026-05-10 (see [`docs/superpowers/specs/2026-05-10-pha
|
|||
| Issue | Item | Status |
|
||||
| --- | --- | --- |
|
||||
| #19 | Folder support — zip-output portion | 🟡 Pending (folder recursion shipped earlier) |
|
||||
| #22 | Detailed before/after metadata diff | 🟡 Pending (un-deferred from v6 per project-direction.md) |
|
||||
| #23 | Web Share Target (PWA) | 🟡 Pending |
|
||||
| #22 | Detailed before/after metadata diff | 🟡 Pending (un-deferred from v6 per project-direction.md). Design now anticipates an `ExifToolDiffStrategy` fallback if the WebPerl-ExifTool POC greenlights — see [`docs/poc/webperl-exiftool.md`](../../docs/poc/webperl-exiftool.md). |
|
||||
| #23 | Android share-from-gallery intent filter (recast from Web Share Target) | 🟡 Pending. Original "Web Share Target API in PWA manifest" scope dropped with iOS / deployed-PWA-primary; APK path uses Capacitor `@capacitor/share` or a native `Intent` filter in `AndroidManifest.xml`. |
|
||||
|
||||
**E.3 Trust copy**
|
||||
|
||||
|
|
@ -263,7 +263,7 @@ Issue #80. Single PR, build-green commits.
|
|||
- Docs pass (this rule, CLAUDE.md, README.md, docs/architecture.md, privacy-invariants §1/§4/§6, project-direction, PRIVACY_GAPS xattr entry)
|
||||
- Forgejo CI migration deferred to a follow-up; GitHub Actions stays. CI yaml needed no edit because the deleted `tests/main/` simply stopped being picked up by vitest globbing.
|
||||
|
||||
The PWA is now the sole distribution channel. macOS xattr scrubbing is documented as a gap in `PRIVACY_GAPS.md`.
|
||||
The PWA was the sole distribution channel at Phase G; the Capacitor APK (#156, May 2026) then added the Android distribution path, and the standalone HTML covers desktop offline. Today (2026-05-21) the **primary distributions are standalone HTML + Android APK**; the deployed PWA is a self-host option. macOS xattr scrubbing is documented as a gap in `PRIVACY_GAPS.md`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -275,7 +275,7 @@ The PWA is now the sole distribution channel. macOS xattr scrubbing is documente
|
|||
- BEM CSS naming
|
||||
- System fonts only
|
||||
- Respect motion preferences (`prefers-reduced-motion`)
|
||||
- Support modern browsers (Chrome, Firefox, Safari, Edge); PWA install on mobile
|
||||
- Support modern desktop browsers (Chrome, Firefox, Edge — Safari best-effort, not a target) for the standalone HTML; support the Capacitor WebView (Chromium) for the APK. iOS / deployed-PWA-install-on-mobile are out of scope.
|
||||
- Maintain backward compatibility for translation contributors
|
||||
- **NEVER auto-publish** — deploys run on push to master; do not run release commands locally
|
||||
- **NEVER enable auto-update / telemetry / analytics** — zero network traffic in production
|
||||
|
|
|
|||
|
|
@ -16,13 +16,20 @@ This includes "last resort fallbacks." A recurring pitch is to add server-side p
|
|||
|
||||
If a future feature seems to require server-side processing (collaboration, sharing, sync), it needs to be a separate product with explicit branding (e.g. `exifcleaner-cloud`), not smuggled into the privacy-first app.
|
||||
|
||||
### 2. Web build is fully offline in production
|
||||
### 2. Every distribution target is fully offline in production
|
||||
|
||||
No network calls from the production renderer. CSP is `connect-src 'self'`. The PWA service worker caches the app shell on first visit and serves everything from local storage thereafter. Adding `fetch()` to a remote endpoint requires an explicit privacy review and almost certainly belongs behind a default-off opt-in — or out of scope entirely.
|
||||
No network calls from the production renderer, regardless of which distribution it ships through. CSP is `connect-src 'self'`.
|
||||
|
||||
- **Standalone HTML** (`dist/web-standalone/index.html`) — everything is inlined; opening the file from disk has nothing to fetch. Self-evidently offline.
|
||||
- **Android APK** — Capacitor serves bundled assets via an internal `https://localhost/` interceptor scheme inside the WebView. No network involved.
|
||||
- **Deployed web PWA** (self-host) — the service worker caches the app shell on first visit and serves everything from local storage thereafter.
|
||||
|
||||
Adding `fetch()` to a remote endpoint requires an explicit privacy review and almost certainly belongs behind a default-off opt-in — or out of scope entirely.
|
||||
|
||||
This is enforced by:
|
||||
|
||||
- `public/_headers` (Cloudflare Pages) and `nginx.conf` (Docker) — both set `connect-src 'self'`
|
||||
- `public/_headers` (Cloudflare Pages) and `nginx.conf` (Docker) — both set `connect-src 'self'` for the PWA self-host path
|
||||
- The Capacitor APK does not add any native-level `Content-Security-Policy` header — the meta-tag CSP from `vite.config.web.ts` is the authority (see `docs/android-apk.md`)
|
||||
- The web build has no analytics SDK, no error reporting, no version-check ping, no font CDN
|
||||
|
||||
If a build error or crash report would help diagnose a problem, log it locally and surface it in the UI; do not phone home.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Purpose:** One-page summary of where ExifCleaner is heading. Resolves ambiguity in feature requests and architectural choices when the answer depends on the project's overall trajectory.
|
||||
|
||||
**Last updated:** 2026-05-14 (Phase G shipped — Electron retired)
|
||||
**Last updated:** 2026-05-21 (standalone HTML + Android APK are now the primary distribution targets; iOS is out of scope; the deployed web PWA is demoted to a self-host option)
|
||||
|
||||
For details, see:
|
||||
|
||||
|
|
@ -13,15 +13,23 @@ For details, see:
|
|||
|
||||
---
|
||||
|
||||
## One code path, one distribution channel
|
||||
## One code path, two distribution targets
|
||||
|
||||
ExifCleaner ships as a browser PWA. The WASM/pure-TS strategy registry runs entirely in the user's browser — no server, no native shell. Phase D (2026-05-10) collapsed Electron and web onto a single processing engine; Phase G (2026-05-14, issue #80) then retired the Electron shell itself, leaving the PWA as the only distribution channel.
|
||||
MetaScrub ships **two binaries from one codebase**:
|
||||
|
||||
1. **Desktop offline standalone HTML** (`dist/web-standalone/index.html`) — produced by `yarn build:web:standalone`. A single self-contained file (~830 KB today, larger with future format-coverage additions) that runs in any modern desktop browser, no server, no install. The primary desktop distribution path.
|
||||
2. **Android APK** (Capacitor wrapper) — produced by the workflow at `.github/workflows/build-android.yml` (or `scripts/build-apk-local.sh`). Sideloadable APK that runs the same web bundle inside a system WebView, fully offline from first launch. The primary Android distribution path.
|
||||
|
||||
Both consume the same `src/` codebase, the same WASM/pure-TS `strategy_registry.ts`, the same forensic test battery. No native processing path; the registry is the sole authority for what is supported.
|
||||
|
||||
A **deployed web PWA** (`dist/web/` produced by `yarn build:web`) also still builds and is self-hostable via the included `Dockerfile` + `nginx.conf` or the Cloudflare Pages workflow. It is **not** a primary distribution target — it exists for self-hosters and the occasional drop-in browser visit. Phase G (2026-05-14, issue #80) retired the Electron shell; the APK (shipped #156, 2026-05) replaced "PWA on Android" as the mobile path.
|
||||
|
||||
**Practical implications for new work:**
|
||||
|
||||
- New format strategies go in `src/infrastructure/wasm/strategies/` and run in the browser. There is no native processing path.
|
||||
- New format strategies go in `src/infrastructure/wasm/strategies/` and run in the browser (or Capacitor WebView, same thing). There is no native processing path.
|
||||
- The strategy registry (`src/infrastructure/wasm/strategy_registry.ts`) is the sole authority for what is supported.
|
||||
- One engine, one set of forensic tests, one set of format-strategy gap analyses. No native runtime to maintain.
|
||||
- Bundle changes are scored against **both** primary outputs — the standalone HTML (single-file size) and the APK (asset bundle size). PWA bundle size is a secondary concern.
|
||||
- Filesystem-specific privacy controls that needed a native shell (e.g. macOS xattr scrubbing) are documented as gaps in `PRIVACY_GAPS.md` rather than re-implemented.
|
||||
|
||||
## RAW is unsupported from v5 forward
|
||||
|
|
@ -30,18 +38,24 @@ ExifCleaner ships as a browser PWA. The WASM/pure-TS strategy registry runs enti
|
|||
|
||||
User-facing: the unsupported-format message for RAW directs users to ExifTool standalone or another dedicated RAW tool. Documented in `PRIVACY_GAPS.md` (the inverse of the forensic docs — what we cannot claim to clean).
|
||||
|
||||
## Mobile = PWA, not native apps
|
||||
## Mobile = Android APK, not iOS, not PWA-on-Android
|
||||
|
||||
The mobile story is the deployed web app installed via "Add to Home Screen" / Chrome's install prompt — not separate iOS/Android codebases. This is settled; pitches for native apps don't fit the project's positioning or maintenance model.
|
||||
The mobile distribution is the **Capacitor APK** (#156, shipped 2026-05). Not iOS, not "install the deployed PWA from Chrome on Android."
|
||||
|
||||
The PWA install path is real and works:
|
||||
Why the APK rather than PWA-install-on-Android:
|
||||
|
||||
- Android (Chrome) — full install prompt, Web Share Target, offline-capable
|
||||
- iOS (Safari) — Add to Home Screen via the share sheet; weaker capabilities than Android (no install prompt, no Web Share Target)
|
||||
- **Truly offline from first launch.** The APK ships every asset; there is no "must visit the page once with internet to install" step.
|
||||
- **No HTTPS-origin dependency.** Capacitor serves bundled assets via an internal `https://localhost/` scheme inside the WebView, so service workers, ES modules, and WASM all work without ever touching the network.
|
||||
- **Trust signal.** An installed-from-APK app reads differently to less-technical users than a bookmarked Chrome tab.
|
||||
- **Same code path.** The JS source of truth is identical to the web build; Capacitor is a thin native shell.
|
||||
|
||||
Open mobile gap issues track the work: HEIC strategy (#48 — most-hit format on iPhone), touch UX pass (#49), iOS Photos picker UX note (#50), unsupported-format messaging (#51), in-app install prompt UX (#52).
|
||||
See [`docs/android-apk.md`](../../docs/android-apk.md) for the full APK build + distribution story.
|
||||
|
||||
**HEIC is the highest-priority deferred format.** iPhones default to HEIC; if a user drops their photos into the deployed web app today, the strategy returns "unsupported." Closing that gap is what makes the web app actually usable on mobile.
|
||||
iOS in any form (App Store, PWA via Safari, Add to Home Screen) is **out of scope**. The audience constraints don't match the tradeoffs (no sideloading without dev tools; weak PWA capabilities on Safari; App Store review is hostile to privacy apps that disable update pings; the engineering and UX cost to support iOS Photos picker, share sheet, no-install-prompt UX, file-system limits, etc. is significant and ongoing). Dropping iOS lets us simplify docs, UX copy, and CI surface.
|
||||
|
||||
**HEIC is still the highest-priority deferred format.** Modern Android phones produce HEIC by default (Pixel, Samsung); if a user drops their photos into MetaScrub today, the strategy returns "unsupported." Closing that gap is what makes MetaScrub actually usable on Android.
|
||||
|
||||
Open mobile gap issues track the remaining Android work: HEIC strategy (#48), touch UX pass (#49), unsupported-format messaging (#51), per-format size caps with mobile-friendly messaging (#63). Issues that were iOS-specific (#50 iOS Photos picker, #52 PWA install prompt UX) should be closed as out-of-scope per this direction.
|
||||
|
||||
## Privacy is the product
|
||||
|
||||
|
|
@ -60,12 +74,13 @@ Things that come up periodically and are settled "no":
|
|||
|
||||
- **Cloud sync / collaboration / sharing.** Different product. Would require a server. Not happening.
|
||||
- **Server-side processing for hard formats.** See privacy invariants.
|
||||
- **Native iOS/Android apps.** PWA is the mobile story.
|
||||
- **iOS in any form** — App Store app, PWA via Safari, Add to Home Screen. The Android APK + desktop standalone HTML cover the audience we serve.
|
||||
- **A native iOS app.** Same answer as above.
|
||||
- **A deployed-PWA-first mobile story.** The Capacitor APK is the Android distribution; the deployed PWA is a self-host option, not the recommended mobile path.
|
||||
- **Auto-update.** Users are privacy-conscious; outbound network calls in production are a bug, not a feature.
|
||||
- **Adding analytics or crash reporting.** Same.
|
||||
- **RAW format support, in any form.** Removed in Phase D (shipped 2026-05-10). Years of work for a small audience; users with RAW workflows belong on dedicated tools (ExifTool standalone, etc.).
|
||||
- **A WASM port of ExifTool that covers RAW.** Same answer — the audience doesn't justify the engineering cost.
|
||||
- **A desktop / Electron build.** Retired in Phase G (shipped 2026-05-14, issue #80). The PWA is the sole distribution channel.
|
||||
- **RAW format support via hand-rolled walkers.** Years of work for a small audience. *Note (May 2026):* the WebPerl-ExifTool POC ([`docs/poc/webperl-exiftool.md`](../../docs/poc/webperl-exiftool.md)) opened a viable path to RAW + long-tail coverage via an opt-in `ExifToolFallbackStrategy`. If that lands, RAW would be supported on the APK + standalone targets via that strategy specifically. This entry will be revised when (if) the design phase greenlights.
|
||||
- **A desktop / Electron build.** Retired in Phase G (shipped 2026-05-14, issue #80). The standalone HTML covers the desktop offline case.
|
||||
|
||||
## What's in scope and probably under-prioritized
|
||||
|
||||
|
|
@ -73,8 +88,8 @@ If you're looking for high-leverage work that fits the direction, the post-Phase
|
|||
|
||||
**Phase E — features + UX polish**
|
||||
|
||||
- **E.1 Mobile-critical UX** (#49, #50, #51, #52, #63) — ship first; closes the gap between "the webapp works on mobile" and "the webapp is usable on mobile"
|
||||
- **E.2 Features** — folder zip output (#19), detailed before/after metadata diff (#22), Web Share Target (#23)
|
||||
- **E.1 Android-critical UX** (#49, #51, #63) — ship first; closes the gap between "the APK works" and "the APK is usable." iOS-specific items (#50, #52) are now out of scope per the direction change above.
|
||||
- **E.2 Features** — folder zip output (#19), detailed before/after metadata diff (#22), Android share-from-gallery intent filter (was #23 "Web Share Target PWA" — recast for the APK path: the Capacitor `@capacitor/share` / native `Intent` filter approach replaces the Web Share Target API).
|
||||
- **E.3 Trust copy** — empty-state copy (#68), visible offline indicator (#69)
|
||||
|
||||
**Phase F — depth + coverage hardening**
|
||||
|
|
@ -87,4 +102,5 @@ If you're looking for high-leverage work that fits the direction, the post-Phase
|
|||
**Adjacent (not in E/F)**
|
||||
|
||||
- PNG/WebP/GIF/BMP hand-rolled walkers — small per-format effort, follows the established pattern; not yet filed as issues, will slot into F.1 when filed
|
||||
- Cloudflare Pages deploy activation (#66) — workflow exists, just needs the secrets wired up. See [`docs/deploying.md`](../../docs/deploying.md).
|
||||
- Cloudflare Pages deploy activation (#66) — workflow exists, just needs the secrets wired up. Note: the deployed PWA is now a self-host option, not the primary distribution, so this is a "nice to have" rather than a release-gate. See [`docs/deploying.md`](../../docs/deploying.md).
|
||||
- `ExifToolFallbackStrategy` + `ExifToolDiffStrategy` design phase — the WebPerl-ExifTool POC ([`docs/poc/webperl-exiftool.md`](../../docs/poc/webperl-exiftool.md)) showed a viable opt-in path to RAW/HEIC/AVIF coverage on the standalone + APK targets. If greenlit, both strategies register last in their respective registries and lazy-load the 7 MB WASM only when a fallback-eligible format is dropped.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# MetaScrub
|
||||
|
||||
Privacy-focused metadata stripper, shipped as a browser PWA. No Perl runtime, no server-side calls, no Electron shell. MIT license. Forked from [szTheory/exifcleaner](https://github.com/szTheory/exifcleaner) and rebranded in v5; lineage notes in the README.
|
||||
Privacy-focused metadata stripper. **Primary distributions: desktop offline standalone HTML + Android APK** (Capacitor wrapper). The deployed web PWA still builds and is self-hostable, but it's a secondary target. iOS in any form is out of scope. No Perl runtime, no server-side calls, no Electron shell. MIT license. Forked from [szTheory/exifcleaner](https://github.com/szTheory/exifcleaner) and rebranded in v5; lineage notes in the README.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Runtime**: Modern browsers (Chrome, Firefox, Safari, Edge). Installable as a PWA.
|
||||
- **Runtime**: Modern browsers (Chrome, Firefox, Edge) on desktop; Capacitor WebView (Chromium) inside the Android APK. Safari is supported best-effort but not a target (iOS is out of scope).
|
||||
- **Renderer**: React 19 SPA built by Vite, with BEM CSS (no framework)
|
||||
- **Language**: TypeScript 5.7 with `strict: true` + `verbatimModuleSyntax: true` (type-check only; Vite/esbuild compile)
|
||||
- **Build**: `vite` 7.x — `vite.config.web.ts` produces `dist/web/`; `vite.config.web.standalone.ts` produces the single-file inlined HTML at `dist/web-standalone/index.html`.
|
||||
- **Build**: `vite` 7.x — `vite.config.web.standalone.ts` produces the primary desktop output (`dist/web-standalone/index.html`, single-file inlined). `vite.config.web.ts` produces `dist/web/`, used as the source for the Android APK (via Capacitor `cap sync android`) and for the self-host PWA path. `dist/web/` is not a primary user-facing distribution by itself.
|
||||
- **Processing engine**: hand-rolled WASM/pure-TS `FormatStrategy` implementations registered in `src/infrastructure/wasm/strategy_registry.ts`. The registry is the sole authority for what is supported.
|
||||
- **Production deps (4)**: `jszip` (Office), `pdf-lib` (PDF), `react` + `react-dom` (UI).
|
||||
- **Performance is sacred**: the app should process hundreds of files in seconds. Never add sync I/O in the loop or heavy DOM operations per row.
|
||||
|
|
@ -218,5 +218,5 @@ Root configs: `.prettierrc` (tabs), `.gitattributes` (`* text=auto eol=lf`), `vi
|
|||
- **Phase G shipped 2026-05-14** (#80): Electron shell retired. Deleted `src/main/`, `src/preload/`, `src/infrastructure/electron/`, the xattr command + service, `src/common/{ipc_channels,platform}.ts`, electron-vite + electron-builder dependencies and configs, and ~2000 lines of yarn.lock. macOS xattr scrubbing is documented as a gap in `PRIVACY_GAPS.md`.
|
||||
- **Phase D shipped 2026-05-10**: WASM-by-default routing, single processing engine across Electron and web.
|
||||
- **Phases A, B, C** earlier: Office + fragmented MP4 strategies (A); deployable webapp with Vite + PWA + Docker (B); web e2e coverage + parallel CI (C).
|
||||
- **Active backlog**: HEIC strategy (#48) — highest-priority gap (iPhone default format). PNG/WebP/GIF/BMP/TIFF hand-rolled walkers. Office Phase 2 hardening (#62). Per-format size caps with mobile-friendly messaging (#63).
|
||||
- **Active backlog**: HEIC strategy (#48) — highest-priority gap (default format on modern Android Pixel/Samsung cameras). PNG/WebP/GIF/BMP/TIFF hand-rolled walkers. Office Phase 2 hardening (#62). Per-format size caps with mobile-friendly messaging (#63). `ExifToolFallbackStrategy` + `ExifToolDiffStrategy` design phase ([`docs/poc/webperl-exiftool.md`](docs/poc/webperl-exiftool.md)) — opt-in RAW/HEIC/AVIF/long-tail coverage on standalone + APK targets.
|
||||
- See `.claude/rules/modernization-roadmap.md` for the full phase status and `docs/superpowers/specs/` for the migration design + audit specs.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
## Features
|
||||
|
||||
- Browser-based: install as a PWA or run from a single self-contained HTML file — no server, no native install
|
||||
- Two ways to run, both fully offline: open the single self-contained HTML file in any desktop browser, or install the Android APK (sideload — no Play Store account, no Internet required at install or runtime)
|
||||
- Drag and drop files or folders; recursive folder processing
|
||||
- Fully offline — files never leave your device
|
||||
- Privacy controls: preserve orientation, preserve color profile, timestamps written to epoch
|
||||
|
|
@ -19,7 +19,9 @@ See the [CHANGELOG](CHANGELOG.md) for release history.
|
|||
|
||||
## Project Direction
|
||||
|
||||
MetaScrub is a browser PWA built on a single processing engine — WASM and pure-TypeScript format strategies that run entirely in the user's browser. Phase D (shipped 2026-05-10) collapsed the previous two-engine architecture onto one strategy registry; Phase G (shipped 2026-05-14, issue #80) retired the Electron desktop shell. The PWA is now the sole distribution channel.
|
||||
MetaScrub ships **two binaries from one codebase**: a **standalone offline HTML file** (`dist/web-standalone/index.html`) for desktop use, and an **Android APK** (Capacitor wrapper) for offline Android use. Both consume the same WASM and pure-TypeScript format strategies registered in `src/infrastructure/wasm/strategy_registry.ts` — identical processing path on both targets.
|
||||
|
||||
Phase D (shipped 2026-05-10) collapsed the previous two-engine architecture onto one strategy registry; Phase G (shipped 2026-05-14, issue #80) retired the Electron shell; the Capacitor APK (shipped May 2026, #156) replaced "install the PWA on Android" as the mobile path. The deployed web PWA produced by `yarn build:web` is still self-hostable via Docker / Cloudflare Pages, but is no longer a primary distribution target. **iOS in any form is out of scope.**
|
||||
|
||||
Hand-rolled pure-TypeScript marker and chunk walkers cover documented containers (JPEG, PNG today, WebP/GIF/BMP/TIFF in flight). For ISOBMFF-based formats (HEIC, AVIF, MP4), the existing video-strategy box walker provides a foundation; a targeted Rust→WASM module is the second-line option only if a hand-rolled approach proves insufficient. Library evaluations under [`docs/poc/`](docs/poc/) showed the hand-rolled approach is smaller, more transparent, and more thorough than the maintained alternatives we tried (`little_exif` and `exiv2-wasm` both leave significant metadata behind on JPEG/PNG).
|
||||
|
||||
|
|
@ -68,7 +70,7 @@ yarn build:web:standalone
|
|||
|
||||
Produces `dist/web-standalone/index.html` — one self-contained ~830 KB HTML file with the entire app inlined (JS, CSS, dependencies). Open it directly in any modern desktop browser from disk; no server, no install, no internet. Email it, share it on a USB stick — recipients double-click it and it runs.
|
||||
|
||||
This is a desktop deliverable (Chrome, Brave, Edge, Firefox, Safari). Chrome on Android doesn't support opening local HTML files — for mobile, use the PWA install path against a deployed instance. Trade-offs (no service worker, no lazy-loading, no PWA install) and the full rationale live in [`docs/standalone-html.md`](docs/standalone-html.md).
|
||||
This is a **desktop deliverable** (Chrome, Brave, Edge, Firefox). Chrome on Android doesn't support opening local HTML files — for mobile, use the [Android APK](docs/android-apk.md) instead. Trade-offs (no service worker, no lazy-loading, no PWA install) and the full rationale live in [`docs/standalone-html.md`](docs/standalone-html.md).
|
||||
|
||||
### Option 2: Docker
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ The current shipping state. Expect this table to drift; the README's Format Supp
|
|||
**What you can do:**
|
||||
- Use Android 10 or later if available (scoped storage applies automatically).
|
||||
- Move the cleaned file to your app-private storage immediately after the strip (e.g. into Signal's media folder before sharing).
|
||||
- For users on Android ≤9 who need stronger isolation, the PWA path (Add to Home Screen + the browser's download flow) has the same behaviour — this isn't an APK-specific regression; it's a property of the platform's pre-scoped-storage filesystem model.
|
||||
- This isn't an APK-specific regression; it's a property of the platform's pre-scoped-storage filesystem model. A self-hosted PWA install (`Add to Home Screen` + the browser's download flow) shows the same behaviour on Android ≤9, but the APK is the recommended Android distribution either way (see [`.claude/rules/project-direction.md`](../.claude/rules/project-direction.md)).
|
||||
|
||||
**What MetaScrub could do (deferred):** a Capacitor-aware adapter in `web_api.ts` (detectable via `typeof Capacitor !== 'undefined'`) using `@capacitor/filesystem` to write into the app's private external storage with an explicit location picker for export. This is a one-issue follow-up; not implemented in the v1 APK pass to keep the patch small.
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ This doc covers two questions asked together:
|
|||
| Min API level | 23 (Capacitor 7 default) | Covers 99%+ of devices; WASM secure-context requirement is well within range. |
|
||||
|
||||
> [!NOTE]
|
||||
> The APK path produces a **personal-distribution APK** (sideloaded, unsigned or self-signed). It is not the project's official mobile story — `project-direction.md` documents that as the PWA install path ("Add to Home Screen" from the deployed web app). Use this doc when you want a single file you can hand someone who doesn't trust hosted apps or who isn't online when they need the tool. The PWA path stays the recommended option for most users.
|
||||
> **The Capacitor APK is now MetaScrub's primary Android distribution** (direction change 2026-05-21 — see [`.claude/rules/project-direction.md`](../.claude/rules/project-direction.md)). The deployed web PWA is no longer the recommended mobile path; it remains buildable for self-hosters but the APK is what we tell Android users to install. The signed-release-vs-personal-sideload distinction below is the only remaining axis; both produce the same artifact.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -255,21 +255,23 @@ F-Droid submission is out of scope for the initial APK implementation but is ach
|
|||
|
||||
---
|
||||
|
||||
## What's different from the PWA path
|
||||
## What's different from the deployed PWA path (for self-hosters)
|
||||
|
||||
| Concern | PWA install (Add to Home Screen) | Capacitor APK |
|
||||
The APK is now the primary Android distribution; this table is here for self-hosters comparing the two paths.
|
||||
|
||||
| Concern | Deployed PWA (self-host) | Capacitor APK (primary) |
|
||||
| --- | --- | --- |
|
||||
| Distribution medium | Visit a URL once, install via browser | Sideload `.apk` file |
|
||||
| Requires network for first run | Yes (must load the page once) | No (everything in the APK) |
|
||||
| Updates | Automatic on next visit, after service worker refresh | Manual — rebuild + redistribute |
|
||||
| Sharing intents from gallery | Web Share Target (Android only) | Android Intent filter via Capacitor plugin (works on every Android version) |
|
||||
| Sharing intents from gallery | Web Share Target (Android Chrome only; not supported on iOS, but iOS is out of scope anyway) | Android Intent filter via Capacitor plugin (works on every supported Android version) |
|
||||
| Photos picker | Browser file input | Native Android picker (slicker UX) |
|
||||
| App icon / name | Browser-managed | Real native app metadata |
|
||||
| Trust signals | "It's just a web page" | "It's an installed app" — meaningful to less-technical users |
|
||||
| Size on disk | ~few MB cached | ~5–10 MB APK |
|
||||
| Developer-side cost | None beyond hosting the PWA | One-time Capacitor setup; rebuild on each release |
|
||||
|
||||
The APK path makes sense specifically when "must work fully offline from first launch" or "I'm handing this to someone via Bluetooth in a context with no internet" are the requirements. Otherwise the PWA is lower friction.
|
||||
The APK is the recommended Android distribution. The deployed-PWA path exists for users who'd rather drop in through a browser on a machine they trust, or for self-hosters who maintain their own deploy.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -293,7 +295,7 @@ Before calling the APK path shippable, decide: accept the `Downloads/` behavior
|
|||
## Known issues before shipping
|
||||
|
||||
- **File output path (privacy-relevant):** See the note above. The `<a download>` → `Downloads/` behavior on Android 9 is a real exposure for a privacy tool; resolve before distributing beyond personal use.
|
||||
- **Drag-and-drop:** Not a thing on mobile. The existing file picker path covers this, but the empty-state copy that says "drop files here" needs a mobile-aware variant (tracked in #49 / #51 for the PWA; same fix applies to the APK).
|
||||
- **Drag-and-drop:** Not a thing on mobile. The existing file picker path covers this, but the empty-state copy that says "drop files here" needs a mobile-aware variant — tracked in #49 / #51 (touch UX + unsupported-format messaging; both apply to the APK).
|
||||
- **Back button behaviour:** Android's hardware back button needs to either dismiss the current view or close the app. Capacitor's default is "close app on back from root view"; verify this matches expectations before shipping.
|
||||
- **Splash screen flash:** Capacitor shows a blank screen while the WebView initializes. On a dark-themed app this is a visible white flash. `@capacitor/splash-screen` addresses it; worth adding for any non-personal distribution.
|
||||
- **WebView vintage:** The WebView is Chromium-based and auto-updated on stock Android 7+. Platform-by-platform:
|
||||
|
|
@ -427,7 +429,7 @@ Rewrite strategies in Rust, compile to WASM with `wasm32-wasi`, run in a WASI ru
|
|||
|
||||
**Recommendation: close the AAR question as won't-do.**
|
||||
|
||||
An AAR makes sense when you are building a library for other Android developers to embed (a "MetaScrub SDK") or when you need a headless processing API for a background service. MetaScrub is an end-user app, not a library product. Its mobile story is the PWA for most users and the Capacitor APK for offline/sideload distribution. Neither use case requires an AAR.
|
||||
An AAR makes sense when you are building a library for other Android developers to embed (a "MetaScrub SDK") or when you need a headless processing API for a background service. MetaScrub is an end-user app, not a library product. Its Android distribution is the Capacitor APK (primary) with the deployed PWA available as a self-host option. Neither use case requires an AAR.
|
||||
|
||||
For any path that produces a true headless API (A, E, F), the maintenance cost of a second implementation is high and the audience (third-party Android developers wanting to embed metadata stripping) is small. Users who need ExifTool-equivalent functionality in an Android app are better served by shelling out to ExifTool CLI or running a local process wrapping the WASM build.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ A walkthrough of how MetaScrub is wired together, written for a backend develope
|
|||
|
||||
If you've worked on a microservice backend with a DDD-flavoured domain layer, almost everything in this codebase has a familiar analogue. The places where it diverges (React reconciliation, the strategy-registry pattern) are called out explicitly.
|
||||
|
||||
> **History note.** This project began life as [ExifCleaner](https://github.com/szTheory/exifcleaner), an Electron desktop app wrapping Perl ExifTool. Phase D (2026-05-10) consolidated everything onto one WASM/pure-TS engine that ran in both Electron and web. Phase G (2026-05-14, issue #80) retired the Electron shell entirely. v5 rebranded the fork as **MetaScrub** to reflect the broader format coverage (PDF, Office, MP4 — not just EXIF). The PWA is now the sole distribution channel; this document reflects that state.
|
||||
> **History note.** This project began life as [ExifCleaner](https://github.com/szTheory/exifcleaner), an Electron desktop app wrapping Perl ExifTool. Phase D (2026-05-10) consolidated everything onto one WASM/pure-TS engine that ran in both Electron and web. Phase G (2026-05-14, issue #80) retired the Electron shell entirely. v5 rebranded the fork as **MetaScrub** to reflect the broader format coverage (PDF, Office, MP4 — not just EXIF). The Capacitor APK (#156, May 2026) added the Android distribution. Today (2026-05-21) the **primary distributions are desktop standalone HTML + Android APK**; the deployed web PWA is a self-host option, not the recommended user-facing path; iOS is out of scope. This document reflects that state.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
# Deploying the web app
|
||||
# Deploying the web PWA (self-host)
|
||||
|
||||
> [!NOTE]
|
||||
> **MetaScrub's primary distributions are the desktop standalone HTML and the Android APK** (see [`.claude/rules/project-direction.md`](../.claude/rules/project-direction.md)). This doc covers the **secondary, self-host path**: deploying the `dist/web/` PWA bundle to your own infrastructure for users who'd rather hit a URL than download a file or sideload an APK. The deploy paths below are still supported but are no longer recommended as the main user-facing distribution.
|
||||
|
||||
The web build is plain static assets — HTML, JS, CSS, WASM, a manifest, a service worker. Anything that serves static files over HTTPS will host it. Two reference paths are documented below; pick whichever fits.
|
||||
|
||||
|
|
@ -163,25 +166,18 @@ Free.
|
|||
2. Enter the domain
|
||||
3. Add the DNS records Cloudflare shows. If the domain is on Cloudflare's nameservers, this is automatic.
|
||||
|
||||
## Installing as a PWA on a phone
|
||||
## Installing the self-hosted PWA from a phone
|
||||
|
||||
Once the URL is reachable over HTTPS, the install flow is the same regardless of how it's hosted.
|
||||
> [!NOTE]
|
||||
> The recommended Android path is the Capacitor APK ([`docs/android-apk.md`](android-apk.md)) — fully offline from first launch, no HTTPS deploy required. The instructions below are only for users hitting a self-hosted PWA deploy. **iOS is out of scope** for MetaScrub overall; if it happens to work via Safari Add-to-Home-Screen on a self-host deploy that's a side effect, not a supported target.
|
||||
|
||||
### Android (Chrome)
|
||||
### Android (Chrome) — self-hosted PWA
|
||||
|
||||
1. Open the URL
|
||||
2. Wait a few seconds for the service worker to register
|
||||
3. ⋮ menu → **Install app** (older Chrome: "Add to Home Screen")
|
||||
4. Confirm. Icon lands on the home screen, launches in standalone mode (no browser chrome). Subsequent launches work offline.
|
||||
|
||||
### iOS (Safari)
|
||||
|
||||
1. Open the URL
|
||||
2. Share sheet → **Add to Home Screen**
|
||||
3. Confirm.
|
||||
|
||||
iOS PWAs have weaker capabilities than Android: no install banner, no Web Share Target API, more file-system limits. Functional, but second-class. See open issue #52 for the planned in-app install prompt UX.
|
||||
|
||||
### Offline behaviour
|
||||
|
||||
The first visit (online) caches the app shell + WASM modules via the service worker. After that, the PWA loads from cache regardless of connectivity. The actual file processing is in-browser anyway — files never leave the device — so offline use is a first-class case.
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ Email, USB stick, Signal/AirDrop/Magic Wormhole — anything that moves a single
|
|||
|
||||
- **No lazy-loading.** The PDF strategy (~430 KB) loads eagerly even if the user never processes a PDF. For the hand-off use case this is fine; for a hosted deploy it would be wasteful.
|
||||
- **No service worker.** The deployed PWA caches itself for offline use after first visit; the standalone HTML is *always* offline because it never had a network connection to lose.
|
||||
- **No PWA install.** "Add to home screen" requires a service worker. Users who want the app icon experience should install from the hosted PWA instead.
|
||||
- **No PWA install.** "Add to home screen" requires a service worker. Android users who want a real app icon should install the [Android APK](android-apk.md); users with a self-hosted PWA deploy can install from there.
|
||||
- **No favicon.** Stripped because `<link rel="icon">` would trigger a CORS error in some browsers' console under `file://`. Cosmetic, no functional impact.
|
||||
- **No manifest.** Same reason.
|
||||
- **Bigger initial load.** 830 KB single file vs ~620 KB across two split chunks. ~30% size penalty for the convenience of a single deliverable.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue