6.5 KiB
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 testandnpm run buildgreen at each phase).
Working Rules (applies to every phase)
- Write failing tests first (Red).
- Implement minimal code to pass (Green).
- Refactor while staying green.
- Run full test suite:
npm test. - Run full build:
npm run build. - 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.tssrc/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.tsto use the shared type(s) where applicable. - Update
src/renderer/types/electron.d.tsto reference the shared contract. - Remove duplicate preload-local
ElectronAPIdeclarations if no longer needed.
Acceptance Criteria
- Single source of truth for
electronAPIcontract shape. - No contract mismatch between preload and renderer types.
- Typecheck/build pass with no API regressions.
Risks / Notes
- Ambient type import patterns in
.d.tsfiles can be tricky; keep changes minimal and explicit.
Phase 2 — Extract Common Tag Mutation Workflow
Problem
TagEngine repeats similar logic in:
deleteTagmergeTagsrenameTag
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, andrenameTagto compose helpers. - Expand/adjust tests in
tests/engine/TagEngine.test.tsto 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:
linkMediaToPostvslinkManyToPostunlinkMediaFromPostvsunlinkManyFromPost
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/mediaUnlinkedvs 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 →
ProjectDatamapping 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:
syncDbToFilesyncFileToDb
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
setImmediateyielding.
- 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.tsxsrc/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
- Phase 1 (API contract)
- Phase 2 (TagEngine)
- Phase 3 (PostMediaEngine)
- Phase 4 (ProjectEngine)
- Phase 5 (MetadataDiffEngine)
- 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.