From 355a16e4468bb6e2e00392173d7c30032c117337 Mon Sep 17 00:00:00 2001 From: James Thiessen <25james1234@gmail.com> Date: Mon, 27 Apr 2026 17:40:36 -0500 Subject: [PATCH 1/4] Add waypoints feature with polished UI - Add runChain.ts to orchestrate chained conversions through waypoints - Update main.ts with waypoint state management and rendering - Polish via/waypoints section: clearer labels, hover states, animations, self-explanatory round-trip button, entry animation on chips - Add chainOrchestrator tests and update README/CLAUDE.md Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 78 ++++++++++ README.md | 18 +++ index.html | 23 +++ src/main.ts | 196 ++++++++++++++++++++++-- src/runChain.ts | 85 ++++++++++ style.css | 175 ++++++++++++++++++++- test/handlers/chainOrchestrator.test.ts | 136 ++++++++++++++++ 7 files changed, 693 insertions(+), 18 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/runChain.ts create mode 100644 test/handlers/chainOrchestrator.test.ts diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..fb761481 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +# Install dependencies (requires Bun) +bun install + +# Development server +bunx vite + +# Production build +bun run build + +# Run tests +bun test + +# Run a single test file +bun test test/TraversionGraph.test.ts + +# Generate format cache (run after build; uses puppeteer/Chromium — slow first run) +bun run cache:build + +# Type-check only (no emit) +bunx tsc --noEmit + +# Docker (prebuilt image) +docker compose -f docker/docker-compose.yml up -d + +# Docker (local build) +docker compose -f docker/docker-compose.yml -f docker/docker-compose.override.yml up --build -d +``` + +## Architecture + +The app is a **browser-based, privacy-first file converter** that runs all conversions client-side. It supports cross-medium conversion (e.g. AVI → PDF) by chaining multiple handlers together. + +### Core abstractions (`src/`) + +- **`FormatHandler.ts`** — Defines the key interfaces: + - `FileFormat` — a format with MIME, extension, `from`/`to` booleans, `internal` ref, `category`, and `lossless` flag. + - `FileData` — `{ name: string, bytes: Uint8Array }`. Handlers own the buffer's lifetime; clone with `new Uint8Array()` if mutation is possible. + - `FormatHandler` — interface all handlers implement: `name`, `supportedFormats`, `ready`, `init()`, `doConvert()`. Optional `supportAnyInput` flag marks generic "catch-all" handlers (e.g. archive wrappers). + - `FormatDefinition` / `builder()` — fluent API for defining formats in handlers (`.allowFrom()`, `.allowTo()`, `.markLossless()`, etc.). + +- **`TraversionGraph.ts`** — Dijkstra's algorithm over the format graph. Nodes are unique formats (identified by `mime(format)`); edges are handler-provided conversions. Cost factors: depth, category-change penalties, lossy multiplier, handler/format priority index. `searchPath()` is an async generator yielding candidate paths. Handlers registered with `supportAnyInput` get edges from every known node to their output formats (with an `ANY_INPUT_COST` penalty to prefer specific paths). + +- **`CommonFormats.ts`** — Pre-built `FormatDefinition` instances (PNG, JPEG, MP4, MP3, PDF, etc.) and the `Category` enum (`image`, `video`, `audio`, `text`, `document`, `archive`, `data`, `vector`, `font`, `code`, `spreadsheet`, `presentation`). Reuse these in handlers to avoid duplicate format definitions. + +- **`PriorityQueue.ts`** — Min-heap used by `TraversionGraph`. + +### Handlers (`src/handlers/`) + +Each handler wraps one conversion tool. Naming convention: file `foo.ts`, class `fooHandler`, registered in `src/handlers/index.ts`. Handler order in `index.ts` matters — it affects the `handlerIndex` cost term in pathfinding (earlier = slightly preferred). + +Key handlers: `FFmpeg.ts` (video/audio), `ImageMagick.ts` (images), `pandoc.ts` (documents), `font.ts`, `threejs.ts` (3D), `sevenZip.ts`, `sqlite.ts`, and ~60 others. + +**WASM dependencies** must be declared in `vite.config.js` under `viteStaticCopy` targets, served from `/convert/wasm/`. Never link directly to `node_modules` for WASM. External git-repo dependencies are added as submodules under `src/handlers/`. + +### Format cache + +On first load, the app builds the supported-format map by calling `init()` on every handler. This is slow (~seconds). The result can be serialized to `cache.json` via `printSupportedFormatCache()` in the browser console, then rebuilt offline with `bun run cache:build`. Disable the cache when testing handler changes. + +### Build targets + +- **Web** — Vite SPA served under `/convert/` base path. +- **Electron** — `src/electron.cjs` is the main process; built with `IS_DESKTOP=true`. +- **Docker** — nginx serves the built output; Dockerfile installs Chromium for `buildCache.js`. + +### Testing + +- `test/TraversionGraph.test.ts` — graph traversal unit tests. +- `test/commonFormats.test.ts` — format definition tests. +- `test/handlers/.test.ts` — optional per-handler unit tests (add for handlers with non-trivial parsing/serialization logic). +- `test/MockedHandler.ts` — test utility for creating stub handlers. +- Test resources live in `test/resources/`. diff --git a/README.md b/README.md index 16705757..7ab9b17d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,24 @@ For a semi-technical overview of this tool, check out the video: https://youtu.b 5. Click **Convert**! 6. Hopefully, after a bit (or a lot) of thinking, the program will spit out the file you wanted. If not, see the "Issues" section below. +### Chained / waypoint conversion + +Between the "Convert from" and "Convert to" columns is a **Via (optional)** panel. It lets you force the conversion through one or more intermediate formats in a single click — no re-uploading required. + +**Example — PNG → WAV → PNG (round-trip / reinterpretation):** + +1. Drop a PNG and let the tool auto-select it as the input format. +2. Click **Round trip ↺** to automatically mirror the input as the output (PNG → PNG). +3. Click **+ Add intermediate format** and pick **WAV** from the picker. +4. Click **Convert**. One download arrives: the final PNG. + +**Tips:** + +- Add as many waypoints as you like by clicking **+ Add intermediate format** multiple times. +- Remove a waypoint by clicking **×** on its chip. +- Tick **Also download intermediate files** to also receive each leg's output (e.g. the WAV above). +- Switching between Simple / Advanced mode clears the waypoint list. + ## Issues Ever since the YouTube video released, we've been getting spammed with issues suggesting the addition of all kinds of niche file formats. To keep things organized, I've decided to specify what counts as a valid issue and what doesn't. diff --git a/index.html b/index.html index f85e0f68..dc0f15bd 100644 --- a/index.html +++ b/index.html @@ -41,6 +41,20 @@

Convert from:

+
+

Waypoints (optional)

+

Force conversion through a specific intermediate format

+
+
+ + + +
+
+

Convert to:

@@ -53,6 +67,15 @@

Convert to:

+ +
+

Add a waypoint

+ +
+ +
+
+