iconmate 1.2.1

CLI to fetch icons and save them into your Vite, NextJS, or similar TypeScript project
Documentation
# `iconmate sync` — Plan

Status: **v1 shipped.** CLI (`iconmate sync [--apply] [--prune] [--rename old=new]`) and a read-only TUI dialog (`Shift+S`) now implement the reconciliation flow. Path-drift detection (§3.3) and inline TUI collision-rename (§4.3) are deferred to a follow-up.

## 1. Why it exists

Two recurring nuisances today, across every preset:

1. **Hand-dropped SVGs.** A developer (or designer, or coding agent) drops `foo.svg` into `assets/icons/` without running iconmate. Nothing references it from the barrel, so the icon is invisible to the rest of the app.
2. **Barrel drift.** Someone deletes an SVG manually, leaves the barrel entry pointing at a missing file. Or pulls a teammate's branch that only edited the filesystem.

Today the only fix is manual editing of `index.ts` / `icons.dart`. `sync` automates the reconciliation while staying out of the way.

## 2. Hard constraints (safety rails)

The user's primary fear: a command called `sync` that wakes up and rewrites their project. These rules exist to make that fear unfounded.

### 2.1 Never touches SVG assets

`sync` **only ever edits the barrel file** (`index.ts` / `icons.dart`). It never:

- Deletes SVG files
- Moves or renames SVG files
- Modifies SVG file contents

Worst case failure mode is a broken build (missing barrel entry), which `git checkout` of the barrel undoes instantly. The actual icon folder is untouched.

### 2.2 Dry-run by default

- `iconmate sync` — **read-only**. Prints the diff, exits. Writes nothing.
- `iconmate sync --apply` — actually writes.

This inverts the usual convention intentionally. `sync` should be annoying in the safe direction.

### 2.3 Double opt-in for destructive edits

Removing a barrel entry is the only operation that can break user code (call sites referencing the removed identifier stop compiling). So:

- `iconmate sync --apply` adds orphan files and fixes drifted paths, but **leaves orphan entries alone**.
- `iconmate sync --apply --prune` is required before sync will remove orphan entries.

Adding entries is always safe — worst case is an unused constant.

### 2.4 Atomic write + one-shot backup

- Write new barrel to a tempfile, `rename()` atomically. No half-written files on crash.
- Keep a `.iconmate.backup` copy of the previous barrel next to the real one for one session, so a bad sync is one `mv` away from undone.

### 2.5 No rename detection heuristics

If `heart.svg` was renamed to `red_heart.svg` on disk, `sync` sees one orphan file + one orphan entry. It will **not** try to match them by content hash, filename similarity, or any other heuristic. Clever-but-wrong-sometimes matching destroys trust. The user runs `iconmate rename` explicitly when they mean rename.

## 3. What it reconciles

Three operation categories, run against every preset (JS presets parse `index.ts`, Flutter parses `icons.dart`, otherwise identical logic):

### 3.1 Orphan files — SVG on disk, no barrel entry

- Infer an identifier from the filename via existing sanitization rules.
- Add an entry to the barrel.
- **Collision handling** (inferred identifier already exists in barrel):
  - **Non-interactive (`iconmate sync --apply`):** hard error, print the collision, exit non-zero. Offer a `--rename foo=fooAlt` flag to resolve, or suggest renaming the file on disk. No silent `foo2` auto-suffixing.
  - **TUI (`iconmate` interactive):** prompt inline with "would collide with existing `foo` — rename to: [___]", pre-filled with `fooAlt` as a suggestion. User accepts or edits.

### 3.2 Orphan entries — barrel entry, no SVG on disk

- `--apply` alone: listed but left alone (double opt-in protects call sites).
- `--apply --prune`: removed from the barrel.

### 3.3 Path drift — barrel entry points to wrong location

- File exists at a different relative path than the barrel claims (e.g. user moved `assets/icons/heart.svg` to `assets/icons/shapes/heart.svg`).
- `--apply`: fix the path in the barrel. Safe — identifier and call sites are unchanged.

## 4. UX

### 4.1 CLI, non-interactive

```
$ iconmate sync
Barrel: assets/icons/icons.dart

Would add (2):
  + heart        → assets/icons/heart.svg           (orphan file)
  + chevronRight → assets/icons/chevron_right.svg   (orphan file)

Would prune (1):
  - oldIcon      → assets/icons/old_icon.svg        (file missing)

Would fix (1):
  ~ settings     assets/icons/settings.svg → assets/icons/ui/settings.svg

Run with --apply to write additions and fixes.
Run with --apply --prune to also remove orphan entries.
```

### 4.2 Collision output

```
$ iconmate sync --apply
Error: orphan file assets/icons/foo.svg would collide with existing barrel entry `foo`.

Resolve by one of:
  • Rename the file:       mv assets/icons/foo.svg assets/icons/foo_alt.svg
  • Override identifier:   iconmate sync --apply --rename foo=fooAlt
```

Exit code non-zero. No partial writes when any collision is present — sync is all-or-nothing within a single run.

### 4.3 TUI

Surface sync as an action in the main TUI (alongside add/delete). Shows the same diff as the CLI, but each row has `a` (apply this item), `s` (skip), `r` (rename — for collisions). Pressing `A` applies all safe operations; prune requires a second confirmation.

## 5. Code touch points

Rough orientation, not a task list. Depends on the Flutter preset landing first (sync relies on preset-aware barrel parse/write).

- New: `src/sync.rs`
  - `SyncPlan { additions, removals, path_fixes, collisions }`
  - `compute_sync_plan(folder, preset, barrel_path) -> SyncPlan`
  - `apply_sync_plan(plan, options: { prune, renames }) -> Result<...>`
- `src/main.rs`
  - New subcommand `sync` with `--apply`, `--prune`, `--rename <id=newId>` flags.
- `src/views/`
  - New `sync_popup.rs` for the TUI flow.
- `src/utils.rs`
  - `get_existing_icons` stays preset-aware (shared with Flutter work).
  - Filesystem scan helper: list `*.svg` in folder, return relative paths.
- `README.md`
  - Document `iconmate sync` with a prominent note that it's dry-run by default and never touches SVG files.

## 6. Prerequisites

- Flutter preset shipped (parse-and-edit for `icons.dart` is reused here).
- Atomic write helper (tempfile + rename) extracted if not already present.
- Agreement on the `.iconmate.backup` convention — or decision to skip backups and rely on git. Open question for when we revisit.

## 7. Explicitly out of scope

- Rename detection.
- Bulk re-sanitization (changing identifier casing rules across a whole barrel).
- Multi-folder sync in one invocation.
- Automatic `pubspec.yaml` reconciliation for Flutter projects. The one-time pubspec hint from the Flutter preset covers this.