From 58a9e3f7402cfec513dd2f8dc939aaafdf6697d8 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sun, 24 May 2026 14:09:39 +0200 Subject: [PATCH] docs(proposals): cross-module persona-template consolidation audit Walks every multi-persona module's templates/{admin,merchant,store}/ and classifies each feature cluster as YES / PARTIAL / NO (legit exception) / N/A for consolidation. Produces a prioritized 10-item backlog across 3 waves (~8-9 days of focused work, ~3,100-3,500 LOC removable). Headline findings: - 141 persona templates across 9 modules; loyalty already migrated with 8 shared partials. - Wave 1 (low risk, ~1,190 LOC): messaging.messages, messaging.notifications, billing.billing-history. - Wave 2 (3-persona my-account is the marquee item, ~1,430 LOC): tenancy.my-account, tenancy.profile, messaging.email-templates. - Wave 3 (higher complexity, ~1,820 LOC): tenancy.team, catalog.store-products lists, customers.customers, tenancy.login (security-gated). - Anti-candidates documented inline so contributors don't try to force-fit them (catalog product forms, marketplace admin vs store, cms content-page-edit, etc.). Backend services are uniformly scope-agnostic for every top-10 candidate -- no service/route work required. Added to mkdocs nav under Proposals. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../persona-template-consolidation-audit.md | 209 ++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 210 insertions(+) create mode 100644 docs/proposals/persona-template-consolidation-audit.md diff --git a/docs/proposals/persona-template-consolidation-audit.md b/docs/proposals/persona-template-consolidation-audit.md new file mode 100644 index 00000000..8be3e5f5 --- /dev/null +++ b/docs/proposals/persona-template-consolidation-audit.md @@ -0,0 +1,209 @@ +# Persona Template Consolidation — Cross-Module Audit + +**Date:** 2026-05-24 +**Status:** Proposed (audit + prioritized backlog) +**Motivation:** Loyalty has the persona-template pattern working (`docs/architecture/persona-template-consolidation.md`, enforced by `TPL-016`), and the rule surfaced ~110 warnings across the rest of the codebase. This audit walks every module's persona templates, classifies each cluster as a real consolidation candidate or a legit exception, and produces a prioritized migration backlog. + +The goal is **reducing maintenance overhead and preventing scope/permission drift between personas**, not consolidation for its own sake. Several "duplicates" turn out on inspection to be genuinely different features — those stay separate with `{# noqa: TPL-016 #}`. + +--- + +## Headline numbers + +- **141 persona-scoped templates** across 9 multi-persona modules. +- **110 TPL-016 warnings** (templates >75 LOC without a shared partial). 0 in loyalty (already migrated), 47 in tenancy alone. +- **~27 candidate feature clusters** where the same feature appears in 2+ personas. +- **~3,100–3,500 LOC of duplication** can be removed by migrating the top-10 candidates. Roughly 8–10 working days of focused work, spread across 3 waves. +- Backend services are **uniformly scope-agnostic** for every top-10 candidate — no service/route refactors needed before template work. + +--- + +## Per-module summary + +| Module | admin | merchant | store | Already shares via `shared/` | TPL-016 hits | +| --- | --- | --- | --- | --- | --- | +| loyalty | 11 | 9 | 8 | **8 shared partials** | **0** | +| tenancy | 27 | 6 | 7 | 0 | 47 | +| messaging | 4 | — | 3 | 0 | 7 | +| cms | 4 | — | 3 | 0 | 7 | +| billing | 4 | 3 | 1 | 0 | 5 | +| catalog | 4 | — | 2 | 0 | 4 | +| orders | 1 | — | 3 | 0 | 4 | +| inventory | 1 | — | 1 | 0 | 2 | +| customers | 1 | — | 2 | 0 | 0 | + +Loyalty is the reference for the pattern; the other 8 modules are the migration surface. + +--- + +## Cluster matrix + +A **cluster** = the same logical feature appearing in two or more personas. Counted only clusters where the personas render the *same shape* of data, not just files that share a filename. + +### Genuine consolidation candidates (YES + PARTIAL) + +| Module | Feature | admin LOC | merchant LOC | store LOC | Verdict | Notes | +| --- | --- | --- | --- | --- | --- | --- | +| messaging | messages (conversation list) | 339 | — | 282 | **YES** | Same chat table; only conversation_type filter enum differs. ~80% body shared. | +| messaging | notifications | 365 | — | 233 | **YES** | Same table + status badge; only scope filter differs. ~75% shared. | +| messaging | email-templates | 368 | — | 333 | **YES** | Same Tiptap editor + template list; admin has extra usage-stats cards (flag-gate). ~70% shared. | +| tenancy | my-account | 294 | 253 | 243 | **YES** | Identical layout: personal info form + password change + account metadata; only API prefix differs. ~80% shared, **three-persona win**. | +| tenancy | profile | — | 190 | 206 | **YES** | Two-persona near-duplicate. Same edit form, different scope. ~85% shared. | +| tenancy | team (members + invite) | — | 538 | 303 | **YES** | Same member table + invite modal; merchant has an extra audit-log tab (flag-gate). ~70% shared. | +| catalog | store-products / products (list) | 340 | — | 368 | **YES** | Same table + filters + stat cards. Admin has store selector + marketplace-source link (flags). ~70% shared. | +| billing | billing-history | 207 | 144 | — | **YES** | Same invoice table; admin shows settlement_id column. ~75% shared. | +| customers | customers (list) | 221 | — | 214 | **YES** | Same RFM breakdown + table; admin has cross-merchant scope + segment filter (flags). ~70% shared. | +| billing | subscriptions | 320 | 131 | — | **PARTIAL** | Admin shows tiers + multi-merchant aggregation, merchant shows own. Core list reusable; admin tier-management UI stays separate. ~65% shared. | +| catalog | store-product-detail | 358 | — | 174 | **PARTIAL** | Admin has cross-store override indicators + metadata; store has copy-buttons (inventory sync). Core ~60% reusable. | +| catalog | store-product-create / edit | 470–503 | — | 174 | **PARTIAL** | Admin form has 4-language translation tabs + supplier cost + media picker; store form is single-language. Only ~50% reusable; recommend separate forms. | +| cms | content-pages | 205 | — | 339 | **PARTIAL** | Store version embeds media library tabs in the same template. Core table ~55% shared; media UI stays inline. | +| cms | content-page-edit | 705 | — | 337 | **PARTIAL** | Admin has versioning + audit trail; store is simpler with local drafts. Same rich-text editor (~40% shared). | +| orders | orders (list) | 584 | — | 334 | **PARTIAL** | Admin has settlement metadata + merchant column; store has fulfillment workflow (packing slips, bulk status). Core table ~50% shared. | +| tenancy | login | 215 | 216 | 252 | **PARTIAL** | All three share OAuth flow + form. Store has extra "select tenant" step. ~60% shared, **but security-sensitive — gate behind security review**. | +| inventory | inventory | 603 | — | 374 | **PARTIAL** | Both are heavy dashboards. Admin: cross-store stock + variance reports. Store: per-location stock + transfer queue. ~50% shared core. | +| tenancy | store-detail | 665 | 315 | — | **PARTIAL** | Admin has 4× more sections (domain mgmt, role matrix, audit log). Only ~35% shared. | + +### Legit exceptions (NO) + +These trip TPL-016 by line count but consolidation would force `{% if scope == admin %}` branches everywhere. Each should carry `{# noqa: TPL-016 #}` with a one-line reason. + +| File | LOC | Why standalone | +| --- | --- | --- | +| `loyalty/admin/programs.html` | 347 | Multi-merchant aggregator; merchant/store show one program each. Different shape. *(already suppressed)* | +| `loyalty/admin/merchant-detail.html` | 432 | Admin-only tabbed view of an entire merchant's loyalty footprint. *(already suppressed)* | +| `loyalty/admin/merchant-settings.html` | 182 | Admin-only config aggregator. *(already suppressed)* | +| `loyalty/admin/wallet-debug.html` | 905 | Apple/Google wallet diagnostic tool. *(already suppressed)* | +| `loyalty/store/terminal.html` | 411 | POS hardware UI. *(already suppressed)* | +| `loyalty/store/enroll.html` | 175 | Counter-staff enrollment flow. *(already suppressed)* | +| `cms/admin/store-theme.html` | 463 | Admin theme editor — different feature from `cms/store/media.html` (asset library), same LOC is coincidence. | +| `tenancy/admin/merchants.html` + `merchant-create.html` + `merchant-detail.html` + `merchant-edit.html` | 261–494 | Admin-only merchant CRUD. No merchant/store persona equivalents. | +| `tenancy/admin/admin-users.html` + detail/edit | 287–357 | Platform-staff management — fundamentally an admin-only feature. | +| `marketplace/store/marketplace.html` | ~150 | CSV import workflow — different shape from `marketplace/admin/marketplace-products.html` (catalog browser). Not a duplicate, different feature. | +| `orders/store/order-detail.html` | 455 | Store-only fulfillment workflow view. No admin/merchant pair. | +| `orders/store/invoices.html` | 505 | Store-only invoice list + PDF generation. | + +### Single-persona (N/A) + +Counted for completeness; not consolidation work. + +- `customers/store/customer-detail.html` (178) — store-only purchase history view. +- `billing/merchant/subscription-detail.html` (234) — merchant-only. +- `inventory` files where only admin or only store has a version. +- All `tenancy/admin/admin-*` files (admin user management). +- All `tenancy/admin/merchant-*` files (admin merchant CRUD). + +--- + +## Prioritized backlog (top 10) + +Ranked by `(LOC × duplication % × edit frequency) / effort × risk`. Each item gives a concrete recipe; backend is ready for all of them. + +### Wave 1 — establish the pattern with quick wins (~2 days) + +**1. messaging.messages** — `S effort, L risk` → 480 LOC removed +- `messaging/templates/messaging/admin/messages.html` (339) + `store/messages.html` (282) → `shared/messages-list.html` + 2 thin wrappers. +- Only the conversation_type filter enum differs. Currently TPL-016 flagged. +- JS: factory `messagesList(config)` in `static/shared/js/`, 2 thin wrappers in `admin/js/` + `store/js/`. + +**2. messaging.notifications** — `S effort, L risk` → 450 LOC removed +- Same pattern as #1: `notifications-list.html` shared + 2 wrappers. Same factory shape. + +**3. billing.billing-history** — `S effort, L risk` → 260 LOC removed +- `billing/admin/billing-history.html` (207) + `merchant/billing-history.html` (144) → `invoices-list.html` shared + 2 wrappers. +- Admin's `settlement_id` column gates behind `show_settlement_column` flag. + +**Total Wave 1: ~1,190 LOC removed, low risk, proves the pattern outside loyalty.** + +### Wave 2 — three-persona wins and form sharing (~3 days) + +**4. tenancy.my-account** — `M effort, M risk` → 630 LOC removed +- `tenancy/admin/my-account.html` (294) + `merchant/my-account.html` (253) + `store/my-account.html` (243) → `account-profile.html` shared + 3 wrappers. +- **First three-persona shared template outside loyalty**, highest single-file payoff. +- Watch: token-claim handling differs slightly per persona; verify the password-change endpoint works on each. + +**5. tenancy.profile** — `S effort, L risk` → ~310 LOC removed +- `merchant/profile.html` (190) + `store/profile.html` (206) → `profile-form.html` shared + 2 wrappers. +- Cluster with #4 — they're sibling features in the same module; finish together so the `shared/` dir is one consistent set. + +**6. messaging.email-templates** — `M effort, M risk` → 490 LOC removed +- `messaging/admin/email-templates.html` (368) + `store/email-templates.html` (333) → `email-template-editor.html` shared + 2 wrappers. +- Both use Tiptap. Admin has extra usage-stats cards → `show_usage_stats=true` flag. + +**Total Wave 2: ~1,430 LOC removed, includes the first 3-persona win.** + +### Wave 3 — higher complexity (~3 days) + +**7. tenancy.team** — `M effort, M risk` → 590 LOC removed +- `merchant/team.html` (538) + `store/team.html` (303) → `team-list.html` + `invite-modal.html` shared. +- Merchant audit-log tab extracts to a separate panel with `show_audit_tab=true` flag. + +**8. catalog.store-products (lists only)** — `M effort, M risk` → ~495 LOC removed +- `catalog/admin/store-products.html` (340) + `store/products.html` (368) → `products-list.html` shared. +- Admin's store selector + marketplace-source link gate behind flags. +- **Forms stay separate** (different translation tab system, different field sets — see "Anti-candidates" below). + +**9. customers.customers** — `S effort, L risk` → 305 LOC removed +- `customers/admin/customers.html` (221) + `store/customers.html` (214) → `customer-list.html` shared. +- Admin: cross-merchant + segment filter (flag). Store: per-store scope. + +**10. tenancy.login** — `M effort, H risk` → ~410 LOC removed (**security review gate**) +- `tenancy/admin/login.html` (215) + `merchant/login.html` (216) + `store/login.html` (252) → `login-form.html` shared. +- Store has an extra "select tenant" step → `show_tenant_selector=true` flag. +- **Do this last** and only after a security review of the shared template; login templates handle session boundaries and CSRF, and a regression here is a auth incident, not a UX bug. + +**Total Wave 3: ~1,800 LOC removed (or ~1,390 if login is deferred).** + +--- + +## Anti-candidates (don't consolidate these) + +These look like duplicates but should stay separate. The cost of forcing them into a shared partial is higher than the duplication cost. + +| Cluster | Reason | +| --- | --- | +| **catalog product forms** (admin 470 LOC vs store 174 LOC) | Admin form has 4-language translation tabs + supplier-cost field + marketplace-source picker. Store form is single-language, simpler. Only ~30% structural overlap; consolidation would mean 7+ flags. Keep separate; `{# noqa: TPL-016 #}` on the admin file. | +| **marketplace admin vs store** | Admin = catalog browser (`marketplace-products.html`). Store = CSV import workflow (`marketplace.html`). Different mental models, not the same feature. | +| **orders detail** (store-only) | Store fulfillment workflow has no admin equivalent. Single-persona feature. | +| **tenancy.store-detail** (admin 665 vs merchant 315) | Admin has 4× more sections (domain mgmt, role matrix, audit log). Only ~35% shared — too much divergence for shared partial. | +| **cms.content-page-edit** | Admin has versioning + audit; store is simpler with local drafts. ~40% shared. Possible later; not a top-10 candidate. | +| **inventory** | Admin (cross-store dashboard) vs store (per-location stock + transfers) are different aggregation levels of the same data. Only ~50% shared. Possible later. | +| **`tenancy/admin/admin-users.html` vs `tenancy/admin/merchants.html`** | These are **two different entities in the same persona**, not cross-persona duplication. Their similarity is "list + filters + table" — that's what shared admin macros (`tables.html`, `pagination.html`) already cover. Consolidating into a feature-specific shared partial would be over-engineering. | + +--- + +## Implementation notes + +- **No backend work required** for any Wave 1–2 item. All checked services follow the scope-agnostic + auth-injected-scope pattern already. +- **i18n keys**: each migration follows loyalty's pattern — shared partials use `.shared..*` keys; persona-specific wrapper text (page titles, error messages) stays under `...*`. Add missing keys to all 4 locales (en/fr/de/lb) when migrating. +- **JS factories**: every shared partial gets a paired Alpine factory in `static/shared/js/--list.js`. Persona JS files become 10-20 LOC wrappers calling the factory with config. +- **TPL-016 escalation**: once any non-loyalty module fully migrates, escalate the rule from `warning` → `error` for new persona templates over the threshold. This prevents the pattern from being re-broken. + +## Timeline estimate + +| Wave | Items | Effort | Risk | Cumulative LOC removed | +| --- | --- | --- | --- | --- | +| 1 | messages, notifications, billing-history | ~2 days | Low | ~1,190 | +| 2 | my-account, profile, email-templates | ~3 days | Medium | ~2,620 | +| 3 (no login) | team, catalog products list, customers | ~3 days | Medium | ~4,010 | +| 3 (with login) | + login | +1 day | High (security) | ~4,420 | + +**~8–9 days of focused work** to clear the headline backlog. Doesn't include the anti-candidates or the inventory/cms remaining items (those can stay flagged with reasons). + +--- + +## Open questions before starting + +1. **Wave order**: ship in order (1 → 2 → 3) or pick highest-individual-ROI first regardless of risk? +2. **Login**: do the security review now (and include in Wave 3), or defer indefinitely? +3. **TPL-016 escalation timing**: after Wave 1, Wave 2, or only after the whole backlog clears? +4. **Per-migration commit cadence**: one PR per cluster (10 PRs), one PR per wave (3 PRs), or one big PR (1)? Loyalty's was a single PR which worked; for cross-module this is riskier. + +--- + +## Out of scope + +- Backend (services / routes / schemas) — already scope-agnostic; no work needed. +- Per-frontend base templates (`admin/base.html` / `merchant/base.html` / `store/base.html`) — these correctly stay separate. +- Shared macros under `app/templates/shared/macros/` — those are already shared infrastructure. +- The 5 already-suppressed loyalty exceptions — already documented inline. +- Storefront / customer-facing templates — different audience, not in scope. diff --git a/mkdocs.yml b/mkdocs.yml index 08ec70fd..793d79d7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -341,6 +341,7 @@ nav: - Hosting Site Creation Fix: proposals/hosting-site-creation-fix.md - Loyalty Go-Live Readiness: proposals/loyalty-go-live-readiness.md - Persona Template Consolidation: proposals/persona-template-consolidation.md + - Persona Template Consolidation Audit: proposals/persona-template-consolidation-audit.md - Loyalty Phase 2 Interfaces: proposals/loyalty-phase2-interfaces-plan.md - Loyalty Program Analysis: proposals/loyalty-program-analysis.md - Merchant Intake Checklist: proposals/merchant-intake-checklist.md