# Refactor Plan: Duplication Reduction Date: 2026-02-15 Scope: Reduce high-impact code duplication in production and test-adjacent areas while preserving behavior. ## Objectives - Eliminate high-risk duplication that can cause drift or inconsistent behavior. - Keep changes incremental and reviewable via phase-based PRs. - Follow strict TDD and repository requirements (`npm test` and `npm run build` green at each phase). ## Working Rules (applies to every phase) 1. Write failing tests first (Red). 2. Implement minimal code to pass (Green). 3. Refactor while staying green. 4. Run full test suite: `npm test`. 5. Run full build: `npm run build`. 6. Do not change behavior unless explicitly planned in acceptance criteria. --- ## Phase 1 — Consolidate Electron API Contract (Highest ROI) ### Problem `electronAPI` shape is duplicated across multiple files and can drift: - `src/main/preload.ts` - `src/renderer/types/electron.d.ts` - Overlapping app/store type surfaces in `src/renderer/store/appStore.ts` ### Goal Create one canonical API contract and consume it from both preload and renderer type declarations. ### Tasks - Add shared contract module (suggested: `src/shared/electronApi.ts`). - Move reusable API interfaces/types there. - Update `src/main/preload.ts` to use the shared type(s) where applicable. - Update `src/renderer/types/electron.d.ts` to reference the shared contract. - Remove duplicate preload-local `ElectronAPI` declarations if no longer needed. ### Acceptance Criteria - Single source of truth for `electronAPI` contract shape. - No contract mismatch between preload and renderer types. - Typecheck/build pass with no API regressions. ### Risks / Notes - Ambient type import patterns in `.d.ts` files can be tricky; keep changes minimal and explicit. --- ## Phase 2 — Extract Common Tag Mutation Workflow ### Problem `TagEngine` repeats similar logic in: - `deleteTag` - `mergeTags` - `renameTag` Repeated steps include finding posts, parsing/updating tags JSON, syncing published files, and progress reporting. ### Goal Refactor shared behavior into private helpers while preserving all task/event semantics. ### Tasks - In `src/main/engine/TagEngine.ts`, add private helpers for: - query posts containing a tag, - transform tag arrays, - persist post tag updates + file sync, - shared progress calculation/reporting. - Update `deleteTag`, `mergeTags`, and `renameTag` to compose helpers. - Expand/adjust tests in `tests/engine/TagEngine.test.ts` to protect behavior. ### Acceptance Criteria - Public method behavior and events unchanged. - Reduced duplicate blocks in `TagEngine`. - Existing and new tests pass. ### Risks / Notes - Must preserve task progress percentages/messages expected by UI. --- ## Phase 3 — Unify Single/Batch Post-Media Operations ### Problem `PostMediaEngine` duplicates linking/unlinking logic across single and batch methods: - `linkMediaToPost` vs `linkManyToPost` - `unlinkMediaFromPost` vs `unlinkManyFromPost` ### Goal Create common primitives and make single/batch methods compose them. ### Tasks - In `src/main/engine/PostMediaEngine.ts`, extract private primitives: - create/delete relation rows, - update media sidecar `linkedPostIds` (add/remove), - shared sort-order handling where applicable. - Refactor single and batch methods to reuse primitives. - Add parity tests in `tests/engine/PostMediaEngine.test.ts`. ### Acceptance Criteria - No behavior regressions in link/unlink semantics. - Event behavior preserved (`mediaLinked`/`mediaUnlinked` vs batch events). - Duplicate logic materially reduced. ### Risks / Notes - Preserve ordering and skip logic for already-linked media. --- ## Phase 4 — Consolidate Project Mapping and Guard Clauses ### Problem `ProjectEngine` repeats: - default-project delete protection checks, - DB row → `ProjectData` mapping in several methods. ### Goal Centralize repetitive mapping and guard logic. ### Tasks - In `src/main/engine/ProjectEngine.ts`, add: - `assertDeletableProject(id)` (or equivalent guard helper), - `mapDbProjectToProjectData(row)` mapper. - Replace repeated mapping/guard code in: - `deleteProject`, `deleteProjectWithData`, - `getProject`, `getAllProjects`, `getActiveProject`. - Validate with `tests/engine/ProjectEngine.test.ts`. ### Acceptance Criteria - Same return shapes and delete behavior as before. - Less repeated mapping/guard logic. ### Risks / Notes - Keep null/undefined normalization unchanged for optional fields. --- ## Phase 5 — Extract Metadata Sync Loop Scaffolding ### Problem `MetadataDiffEngine` has near-identical loop scaffolding in: - `syncDbToFile` - `syncFileToDb` Includes progress reporting, success/failure accounting, and event-loop yielding. ### Goal Abstract common iteration mechanics into one helper with operation-specific callbacks. ### Tasks - In `src/main/engine/MetadataDiffEngine.ts`, add a generic internal iterator helper that handles: - per-item execution, - success/fail accounting, - periodic progress updates, - periodic `setImmediate` yielding. - Refactor both sync methods to use it. - Verify with `tests/engine/MetadataDiffEngine.test.ts`. ### Acceptance Criteria - Same visible progress and final result semantics. - Reduced duplicated loop/control-flow code. ### Risks / Notes - Progress message cadence should remain compatible with UI expectations. --- ## Phase 6 — Deduplicate Shared Color Utility in Renderer ### Problem `getContrastColor` is duplicated in: - `src/renderer/components/TagInput/TagInput.tsx` - `src/renderer/components/TagsView/TagsView.tsx` ### Goal Move color contrast logic into a shared renderer utility. ### Tasks - Add utility module (suggested: `src/renderer/utils/color.ts`). - Replace local implementations in both components. - Add focused tests (suggested: `tests/renderer/utils/color.test.ts`). ### Acceptance Criteria - No visual behavior change. - One implementation for contrast-color calculation. ### Risks / Notes - Verify support for both 3-char and 6-char hex inputs. --- ## Suggested Delivery Sequence 1. Phase 1 (API contract) 2. Phase 2 (TagEngine) 3. Phase 3 (PostMediaEngine) 4. Phase 4 (ProjectEngine) 5. Phase 5 (MetadataDiffEngine) 6. Phase 6 (Renderer utility) Rationale: start with highest blast-radius drift risk, then engine-level duplication, then UI utility cleanup. --- ## Definition of Done (overall) - All phases merged with passing tests/build at each step. - No unresolved failing tests. - Clone report shows meaningful reduction in production hotspots. - Public behavior remains stable unless a change is explicitly approved.