Files
orion/docs/proposals/persona-template-consolidation-audit.md
Samir Boulahtit 58a9e3f740
All checks were successful
CI / ruff (push) Successful in 18s
CI / pytest (push) Successful in 2h49m10s
CI / validate (push) Successful in 34s
CI / dependency-scanning (push) Successful in 35s
CI / docs (push) Successful in 56s
CI / deploy (push) Successful in 1m11s
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) <noreply@anthropic.com>
2026-05-24 14:09:39 +02:00

210 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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,1003,500 LOC of duplication** can be removed by migrating the top-10 candidates. Roughly 810 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 | 470503 | — | 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` | 261494 | Admin-only merchant CRUD. No merchant/store persona equivalents. |
| `tenancy/admin/admin-users.html` + detail/edit | 287357 | 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 12 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 |
**~89 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.