# Plan: add `--dry-run` to `fuj sync` ## Context `fuj infer` already supports `--dry-run` (it builds the planned `BatchUpdateValues` operations, prints them, and skips the actual write — see `go/internal/services/banksync/infer.go:136-156` and the `Dry run: would update N row(s).` line in `go/cmd/fuj/main.go:209-213`). `fuj sync` had no equivalent. It always committed three potential writes to the payments sheet: `WriteHeader` (if the header row is missing/wrong), `AppendValues` (for each new Fio transaction), and `SortByDateColumn` (if `--sort`, default true). For inspecting what a sync *would* do — useful when debugging dedupe, sanity-checking a date window, or wiring up the command for the first time on a new account — the only options were pointing at a throwaway spreadsheet or reading the diff after the fact. This change mirrors `infer`'s read-only mode for `sync`: same flag name, same output style, same "build the data structures, print instead of writing" shape. ## Files modified 1. `go/internal/services/banksync/sync.go` — `DryRun bool` field added to `SyncOpts`; three write points gated on `opts.DryRun` 2. `go/cmd/fuj/main.go` — `--dry-run` flag added to `syncCmd`; final println split on `*dryRun` 3. `go/internal/services/banksync/sync_test.go` — `TestSyncToSheets_DryRun` added 4. `CHANGELOG.md` — entry added ## Behaviour When `--dry-run` is set: - If the sheet header is missing/wrong → prints `Dry run: would write header row`; skips `WriteHeader` - For each non-deduped Fio transaction → prints `Dry run: would append date=… amount=… sender=… vs=… message=…`; skips `AppendValues` - If `--sort` is true → prints `Dry run: would sort by date`; skips `SortByDateColumn` - Returns `len(newRows)` so the caller can print `Dry run: would sync N new transaction(s).` The existing ID-dedup logic runs in full even during dry-run (reads the sheet, builds `existingIDs`), so the output reflects exactly what the next real sync would do.