Files
orion/docs/proposals/persona-template-consolidation.md
Samir Boulahtit f82dce30ca docs(architecture): persona template consolidation pattern + proposal
Document how admin/merchant/store templates share a single shared/ body
partial while keeping their three separate base templates. Covers:

- The wrapper/partial split and why the three base templates must stay
  separate (nav + permissions isolation).
- The scope contract: pass strings + booleans only, no macro objects,
  no `persona` enum.
- The backend mirror: services scope-agnostic, routes inject scope via
  auth deps, same Pydantic shape across personas.
- Legit exceptions and the heuristic for when to keep a template
  standalone (multi-tenant aggregators, persona-unique features).
- Forward reference to the TPL-016 architecture rule.

Adds both docs to mkdocs nav under Architecture and Proposals
sections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 23:10:29 +02:00

4.9 KiB

Persona Template Consolidation

Date: 2026-05-23 Implemented: 2026-05-23 Status: Done Motivation: Maintainers were editing the "same" admin / merchant / store templates three times for every feature change, with predictable drift between personas. The user wanted to know whether the codebase could move to a model where the shared CRUD body lives once and per-persona wrappers handle only the parts that genuinely differ.

Discovery

Loyalty turned out to be a partial success story already: app/modules/loyalty/templates/loyalty/shared/ held 7 reusable partials (program-form, program-view, cards-list, transactions-list, pins-list, devices-list, analytics-stats), and merchant/admin wrappers were already thin includes. The pattern was working but undocumented, unenforced, and not consistently applied to store/ — store had inlined two big features (cards.html 171 LOC, card-detail.html 205 LOC) instead of using the shared partials.

So this turned into a finish + codify + guard job, not a rewrite.

Implementation Summary

  • Phase A (loyalty cleanup). Migrated store/cards.html to use shared/cards-list.html (171 → 56 LOC) and store/card-detail.html to use shared/card-detail-view.html (205 → 55 LOC). JS factories collapsed similarly (166 → 18 and 152 → 20). The shared card-detail-view.html partial gained three boolean flags (show_copy_buttons, show_category_column, show_pagination) and the shared loyaltyCardDetailView factory gained optional pagination + txLabels/txNotes config so store could preserve its enhancements. Added loyalty.shared.card_detail.col_category to en/fr/de/lb locale files. Fixed a latent bug in the shared factory's formatDateTime (was calling toLocaleDateString with hour/minute opts that get silently ignored).
  • Phase B (codify). Wrote docs/architecture/persona-template-consolidation.md describing the pattern, the scope contract, the backend mirror, and the legit-exception heuristic. This doc is now the reference for any contributor adding a new CRUD feature.
  • Phase C (guard). Added architecture rule TPL-016 (warning) that flags any persona template > 75 LOC that doesn't include a */shared/* partial. Wired both check sites in scripts/validate/validate_architecture.py. Suppressible with {# noqa: TPL-016 #} for the legit exceptions (admin programs aggregator, merchant-detail, store terminal, etc.).

Decisions Made

# Decision Rationale
1 Loyalty cleanup + codify pattern + arch rule Scope-limited; the pattern is the real deliverable.
2 Leave admin/programs.html standalone Multi-merchant aggregator + create-with-search modal is fundamentally a different shape from the merchant/store single-program views. Forcing it into shared would mean if scope == admin in every row.
3 JS/CSS variables only (no macro objects, no persona enum) Existing loyalty pattern proven to work; macro objects bloat call sites and persona branching defeats the purpose of the partial.
4 TPL-016 severity = warning Lets the rule ship without breaking CI on day one. Escalate to error after at least one other module is migrated.

Files Touched

  • Templates: loyalty/templates/loyalty/store/cards.html, loyalty/templates/loyalty/store/card-detail.html, loyalty/templates/loyalty/shared/card-detail-view.html (added flags).
  • JS: loyalty/static/store/js/loyalty-cards.js, loyalty/static/store/js/loyalty-card-detail.js, loyalty/static/shared/js/loyalty-card-detail-view.js (added config options + pagination).
  • i18n: loyalty/locales/{en,fr,de,lb}.json (added shared.card_detail.col_category).
  • Docs: new docs/architecture/persona-template-consolidation.md, this proposal, mkdocs.yml nav.
  • Arch rule: .architecture-rules/frontend.yaml (TPL-016), scripts/validate/validate_architecture.py (check function + 2 wire sites).

Verification

  • python scripts/validate/validate_architecture.py — 16 baseline warnings, no new findings.
  • mkdocs build --strict — clean.
  • Smoke test: store/cards and store/card-detail render identically to the pre-migration version (filters, search, pagination, copy buttons, category column, translated transaction labels all preserved).
  • Pre-commit hooks (architecture, security, performance, audit, ruff) all green.

Out of Scope (Deferred)

  • Applying the pattern to other modules (catalog, billing, etc.). The doc + rule make this a follow-up any contributor can pick up.
  • Escalating TPL-016 from warning to error — wait until ≥1 other module migrates.
  • Consolidating the three analytics.html wrappers further — they're already minimal given each persona has materially different content (admin wallet status, store advanced charts).
  • Settings consolidation — merchant/settings.html and store/settings.html are different features (read-only PIN/permissions display vs editable program form), not duplicates.