diff --git a/docs/plans/dry-refactor.md b/docs/plans/dry-refactor.md index e738fc9..13097e0 100644 --- a/docs/plans/dry-refactor.md +++ b/docs/plans/dry-refactor.md @@ -4,6 +4,37 @@ Status: Planned Codebase analysis identifying repeated patterns and consolidation opportunities. Ordered by impact. +## Codebase snapshot + +| Category | Files | Lines | +|----------|------:|------:| +| Elixir source (.ex) | 96 | 18,153 | +| HEEx templates (.heex) | 15 | 2,384 | +| Tests (.exs) | 45 | 6,735 | +| JavaScript (.js) | 1 | 351 | +| CSS (.css) | 5 | 1,218 | +| Config + migrations | 17 | 653 | +| **Total** | **179** | **29,494** | + +Code density: 81.1% (22,108 code / 4,355 blank / 809 comment). + +### Concentration + +- `shop_components.ex` alone is 4,424 lines — 24% of all Elixir source +- Web layer (9,576) slightly larger than contexts (7,950) +- Top 3 files (`shop_components.ex`, `index.html.heex`, `preview_data.ex`) account for 6,650 lines — 36% of source + +### What matters + +Every LOC is maintenance cost, attack surface, and cognitive load. The goal isn't minimising LOC for its own sake — a 50-line module that does one thing clearly beats a 20-line clever abstraction. But 224 lines of copy-pasted template wrapper is the kind of LOC worth eliminating. + +Contexts are well-sized (200-550 lines each, good boundaries). LiveViews average ~157 lines — thin wrappers over contexts, which is right. The problems are in the shared component layer and the repeated boilerplate. + +### Worth questioning + +- `preview_data.ex` (1,049 lines) and `mockups/generator.ex` (522 lines) total 1,570 lines of hard-coded demo content. Should this be seed data or fixtures rather than compiled code? Could be moved behind `Mix.env()` guards or into a mix task to reduce the production runtime footprint by ~8%. +- Test-to-source ratio is 0.37:1 (lines). Reasonable for a template-heavy app, but new features should aim to keep this steady or improve it. + ## High priority ### 1. Extract `<.shop_layout>` wrapper component @@ -42,6 +73,8 @@ Codebase analysis identifying repeated patterns and consolidation opportunities. ``` +**Net saving:** ~195 lines removed (224 duplicated minus ~29 for the new component definition). + **Files:** `shop_components.ex` (new component), all 8 page templates, `collection.ex` inline render. **Complexity:** Medium. The error template has slight variations (no `phx-hook`, different class). Handle with an optional attr or keep error as a special case. @@ -86,6 +119,8 @@ def mount(_params, _session, socket) do end ``` +**Net saving:** ~55 lines removed (75 duplicated minus ~20 for the new hook module). Also removes 7 sets of aliases (Settings, Media, CSSCache, CSSGenerator) from LiveViews. + **Files:** New `theme_hook.ex`, `router.ex` (add to on_mount), all 7 shop LiveViews (remove boilerplate). **Complexity:** Low. Follows established `CartHook` pattern exactly. @@ -96,6 +131,8 @@ end **Fix:** Extract a private `common_preview_assigns/1` function. Each clause merges page-specific attrs on top. +**Net saving:** ~65 lines removed (80 duplicated minus ~15 for the helper function). + **Files:** `lib/simpleshop_theme_web/live/theme_live/index.ex` **Complexity:** Low. Pure refactor within one file. @@ -115,6 +152,8 @@ end Keep `ShopComponents` as a facade that imports all sub-modules for backward compatibility. +**Net saving:** ~0 lines (redistribution, not reduction). Value is in navigability, compile speed, and cognitive load — no single file over ~1,000 lines. + **Files:** `shop_components.ex` split into 5 new files. **Complexity:** Medium. Mechanical but touches many imports. Need to update `page_templates.ex` and any direct references. @@ -125,6 +164,8 @@ Keep `ShopComponents` as a facade that imports all sub-modules for backward comp **Fix:** Extract a `Cart.build_state/1` function that returns `%{items: hydrated, count: n, subtotal: formatted}`. Both CartHook and CheckoutController use it. +**Net saving:** ~15 lines removed. Main value is correctness — single source of truth for cart state calculation. + **Files:** `cart.ex`, `cart_hook.ex`, `checkout_controller.ex` **Complexity:** Low. @@ -135,6 +176,8 @@ Keep `ShopComponents` as a facade that imports all sub-modules for backward comp **Fix:** Add `Vault.encrypt!/1` that raises on failure (for changeset use) and keep `Vault.encrypt/1` for tuple returns. Or create an `Ecto.Type` for encrypted fields. +**Net saving:** ~10 lines removed. Main value is consistent error handling across all encryption callsites. + **Files:** `vault.ex`, `settings.ex`, `provider_connection.ex` **Complexity:** Low-medium. @@ -170,6 +213,22 @@ Keep `ShopComponents` as a facade that imports all sub-modules for backward comp - **Context boundaries** — Products, Orders, Settings, Media are well-separated. - **Page LiveView renders** — now all use `{assigns}` spread (just refactored). +## Impact summary + +| # | Item | Lines saved | Risk | Primary value | +|---|------|------------:|------|---------------| +| 2 | ThemeHook | ~55 | Low | Eliminates mount duplication + alias clutter | +| 1 | shop_layout | ~195 | Medium | Biggest raw line reduction | +| 3 | Preview assigns | ~65 | Low | Single-file cleanup | +| 5 | Cart state builder | ~15 | Low | Correctness (single source of truth) | +| 4 | Split shop_components | ~0 | Medium | Navigability, compile speed | +| 6 | Encryption | ~10 | Low | Consistent error handling | +| 7 | Settings lookups | ~10 | Trivial | Minor cleanup | +| 8 | Secrets loading | ~5 | Trivial | Future-proofing | +| | **Total** | **~355** | | **~1.7% of total source** | + +Combined with the structural improvement of splitting `shop_components.ex` (4,424 lines into 5 modules of ~900 lines each), the largest file drops from 24% of all source to under 5%. + ## Suggested order 1. ThemeHook (item 2) — lowest risk, biggest clarity win