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

15 KiB
Raw Blame History

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.messagesS 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.notificationsS effort, L risk → 450 LOC removed

  • Same pattern as #1: notifications-list.html shared + 2 wrappers. Same factory shape.

3. billing.billing-historyS 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-accountM 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.profileS 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-templatesM 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.teamM 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.customersS 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.loginM 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 warningerror 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.