docs(proposals): cross-module persona-template consolidation audit
All checks were successful
All checks were successful
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) <noreply@anthropic.com>
This commit is contained in:
209
docs/proposals/persona-template-consolidation-audit.md
Normal file
209
docs/proposals/persona-template-consolidation-audit.md
Normal file
@@ -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 `<module>.shared.<feature>.*` keys; persona-specific wrapper text (page titles, error messages) stays under `<module>.<persona>.<feature>.*`. 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/<module>-<feature>-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.
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user