Drop the duplicate customer app from the monorepo and keep user-app-v2 as the only app; include order pricing, admin cart, OTP/SMS, and dashboard cache updates. Co-authored-by: Cursor <cursoragent@cursor.com>
14 KiB
PROTECTED — intentional monorepo additions (additive-only)
CONFIRMED by the user at the Phase 0 gate (2026-09-21).
Rulings are recorded in DECISIONS.md (D0–D12).
Items here are intentional changes in gold-banking-monorepo. Per CLAUDE.md §4
they must not be removed, restyled, restructured, or have their behaviour
changed. You may only ADD what the reference has and they lack, in a way that
doesn't break them. Any fix that touches a protected item needs explicit approval
with a diff plan first.
Legend: [brief] = named as protected in the kickoff brief · [found] = discovered in Phase 0, proposed for protection · [?] = needs your ruling.
A. Admin panel — Dashboard · [brief]
Location: apps/admin-panel/src/views/Dashboard.vue — one file serving both
/dashboard/ecommerce and /dashboard/analytics. KPI chrome lives in
components/statistics-cards/StatisticsCardLine.vue.
Gate: rid dashboard.view; lacking it redirects to my-data (not a 403 page).
Data: a single GET admin/dashboard/overview on mount, then client-side sorts
(reagent desc by grams_sell; lastOrder desc by w).
Contents, in render order:
- 4 KPI tiles — مشتریان جدید (نفر) · طلای فروشرفته (گرم) · تعداد سفارشات · نرخ هر گرم طلای ۱۸ عیار
- 2 KPI tiles — سفارشات تحویلشده · مجموع بدهی طلای مشتریان (گرم)
- Area chart «روند سفارشها» — 2 series (تک / عمده); x-labels rotated -45° with
rotateAlways+hideOverlappingLabels(a deliberate overlap fix — do not "tidy" it) - 2 donuts — وضعیت سفارشات (8-entry colour array) · تفکیک عمده/تک
- Horizontal bar + an always-rendered agent table (کارشناس فروش / میزان فروش گرم / تعداد سفارش)
- Vertical bar «محصولات پرفروش» with an
a-emptyfallback - Sales summary «میزان فروش» — 3 equal cells incl. a computed average
- Table «سفارشات هفته جاری» —
pageSize10,sts_stras a coloureda-tag,cajviajdate
Chrome (owner-approved 2026-10-14): panel-unified look — plain layout background,
simple page header (no brand badge), bordered ant cards, muted period labels (not gold
pills), flat KPI tiles (soft navy icon well, no lift hover / side accent bar), equal
sales-summary cells. Chart series palette stays #1c2d56 / #28c76f / #ff9f43 / #ea5455.
No filters, no date range, no export, no refresh — that is the intended design, not a gap.
Spec: docs/superpowers/specs/2026-10-14-admin-dashboard-visual-unify-design.md.
Why this matters for parity: the reference's two dashboards (
DashboardECommerce.vue,DashboardAnalytics.vue) were found in the earlier port to be mostly commented-out Vuexy demo markup, with ApexCharts imported but never used in live markup (docs/CHANGELOG-PORT-HISTORY.md). So there is essentially nothing to port into this dashboard. Visual chrome may change only with explicit owner approval (this section).
B. Admin panel — the extra option in product Variants · [brief]
Location: apps/admin-panel/src/views/apps/product/AddProduct.vue:151-270, 315-361, 519-568 (Create/Edit Product).
Gate: the card is gated product.list.variants; add/edit/remove on
.add / .edit / .remove.
Variant table columns: تصویر | عنوان | وزن | اجرت درصدی | تعداد | فعال (tag) | « پیش سفارش» (tag) | عملیات.
The protected extra field is تصویر تنوع (variant image). It is a picker,
not an uploader: a 64×64 flex grid over the product's already-saved gallery
(data.gallery, each {id, url, role, sortOrder}), whose first tile is a
بدون تصویر option and whose selected tile gets a blue .is-on border.
pickVariantImage() sets image_id (posted) plus image_url (thumbnail only,
stripped before POST).
It is populated only in edit mode — on create the gallery is empty and an
a-alert shows a context-dependent message (ابتدا محصول را ذخیره کنید… vs
برای این محصول تصویری ثبت نشده است.).
Variants are edited purely in memory (row key __key = v-<ts>-<rand>) and
saved with the product via PUT product/update/{id} / POST product/create under
a variants[] array — there is no per-variant endpoint. Only validation is
عنوان تنوع الزامی است; delete has no confirm dialog.
Related, and NOT protected by itself: the same
imageId/imagecontract on the user-app side.user-appconsumes it;user-app-v2does not (gap V2-03). Closing that gap in v2 is additive and does not touch this admin code.
C. User apps — new designs and new features · [brief]
apps/user-app (design v1) was removed from this monorepo; the live customer
app is apps/user-app-v2. Historical notes below still mention v1 where a
parity decision depended on it. The following were identified in Phase 0 as
design/feature additions with no henza_pwa equivalent.
C.1 Shared by both apps
| Item | Location |
|---|---|
/notifications route + screen (self-documented "no legacy equivalent") |
views/user/NotificationsView.vue |
ui/PageBar.vue replacing the legacy AppHeader |
components/ui/ |
| Redesigned CSS bottom nav (5 tabs; legacy was a 4-tab GSAP notch) | layout/ |
GallerySlider.vue rewrite + teleported full-screen image preview |
components/product/ |
| Variant chips with per-variant images, stock badges, «نمایش همه» | components/product/SingleProduct.vue |
The whole stock / pre-order rule (qty, isPreOrder, capQty, maxQty) |
stores/cartStore.ts, composables/useProduct.ts — v1 only; porting to v2 is gap MON-01 |
| vant pull-to-refresh on 5 screens | various views |
| IntersectionObserver incremental rendering (20-item window) | list views |
| Ledger-based «اسناد» screen | views/user/TransactionsView.vue |
Order-detail drawer + 4-step tracker + useOrderItemProduct join |
views/user/OrdersView.vue |
9-chip OrderStatus enum |
types/ |
| Dotlottie update dialog | app shell |
New API contracts: GET /home/slider, GoldPriceQuote, productCode, collections[], gold-bank:* session keys |
api/client.ts |
C.2 user-app (design v1) only
Home family: components/home/RateStrip.vue, ShowcaseSlider.vue,
ShowcaseCard.vue, CategoryRail.vue, SectionHead.vue, AccountStrip.vue;
admin banner via GET /home/slider + AdminSlide; the «برگزیده منتزر» and
«تازهها» home sections; trust-badge rows.
C.3 user-app-v2 (design v2) only
components/home/HeroCarousel.vue, ProductCarousel.vue, CategoryGrid.vue,
ProductPanel.vue; the IRANSans FaNum font family (12 files,
src/assets/styles/fonts/iransans/); redesigned HomeView structure; redesigned
DashboardView (hero card + 4 settings rows, replacing card + tile grid); the
staggered first-paint reveal system (module-scope hasRevealed) in
HomeView/CartView/DashboardView; the compact RateStrip pill variant;
SingleProduct's scroll-driven solid header + category deep-link pill; the
pill-shaped SearchInput.
Important boundary. C.2 and C.3 are design differences and stay separate. The five items in
ISSUES.md§V2-01..05 are logic differences and are not protected —CLAUDE.md§2 requires logic to be identical across both apps.
D. Backend — stricter auth than the reference · CONFIRMED (D1)
Protected. The user ruled the target's stricter auth stands as an accepted deviation. Additional obligation from D1: build a full "reference admin operation ↔ target rid" matrix so no operation lacks a permission, and explain the 172-vs-174 key difference.
⚠️ Not yet satisfied: 5 ledger-write routes are still @Public()
(DECISIONS.md §"@Public audit" group A). They contradict D1 and are awaiting
the user's go-ahead to be protected.
The reference leaves every admin route unauthenticated (henza_server/routes/admin.php:12
and :272 — the auth:api group wrapper is commented out at both ends, verified).
apps/api instead applies a global JwtAuthGuard + PermissionsGuard with
144 server-enforced rids, and only 15 routes opt out via @Public().
Strict parity would mean removing authentication from ~215 admin endpoints.
CLAUDE.md §3 already carves out exactly this case ("if matching the reference
would cause … a clear security hole in money flows → STOP and ask").
Proposed: protect the target's stricter auth as an accepted deviation; log the
reference behaviour as SEC-01 and the residual @Public() routes as SEC-02.
E. Backend — Decimal money discipline · CONFIRMED
The reference uses PHP floats throughout. apps/api has zero Float columns
in prisma/schema.prisma; ledger and order math run on Prisma.Decimal.
Reverting to floats for "parity" would introduce rounding error into money and
gram values. Proposed: protect the Decimal discipline. The one leak
(GRAMS_PER_MESGHAL as a JS float, ledger.service.ts:189) is logged as MON-03,
to be fixed in Phase 3 — not now.
Note this does not exempt the target from matching the reference's arithmetic,
including the floor(price/1000)*1000 rounding in ProductVariant.php:183-190.
Same result, better representation.
F. Prior deliberate non-ports · CONFIRMED (D4)
Per D4: broken/destructive reference behaviour is not replicated, only logged — but business behaviour, calculations, rounding and messages must match the reference exactly, and every deliberate non-replication carries a matrix note «عمداً تکرار نشد — ISSUE #n».
Recorded in docs/CHANGELOG-PORT-HISTORY.md as intentionally not ported:
- Unauthenticated file upload (
file/createhad no middleware in the reference) - SMS sent inside
toggleActive - Six destructive data behaviours — deleting a confirmed document, editing a confirmed amount, deleting a category that has dependents, creating a category cycle, orphaning the audit trail — each replaced with a Persian 400 message
- The reference's avatar-load crash (
ReagentCustomer.vue:280dereferenceddata.image.idunguarded) - The branch-mode horizontal layout (
HorizontalNavMenu, 4 files never ported;mainLayoutTypenarrowed to'vertical')
Items 1–4 are security/data-loss carve-outs consistent with CLAUDE.md §3 and are
proposed for protection. Item 5 is different — it is a missing feature, not
a safety carve-out, and Phase 1 should treat it as a real gap unless you say
otherwise.
G. Target-only capabilities · CONFIRMED (D2) — status ➕, do not touch
Per D2, features that exist only in the target are listed with status ➕ and left alone. Ask before touching any of them.
| Capability | Where | Note |
|---|---|---|
Dual-currency ledger (LedgerEntry, gold grams + rial) |
apps/api/src/modules/accounting/ |
replaces the reference's single mixed-unit balance column |
Gram / rial debt split (goldDebtGrams, fiatDebtRial) |
prisma/schema.prisma |
reference mixed both in one field |
Prisma.Decimal money types throughout |
schema + services | see §E |
| Server-enforced rid permissions | auth/acl.constants.ts |
see §D |
UploadedFile model + authenticated upload |
admin-uploads/ |
reference upload was anonymous |
| Status-label / transition helpers | admin-orders.service.ts |
reference had two divergent enums |
This list grows as Phase 1 and Phase 2 identify more. It is not a to-do list — it is a keep-out list.
Summary — confirmed status
| Section | Item | Status |
|---|---|---|
| A | Admin Dashboard | ✅ protected (brief) |
| B | Product Variants image picker | ✅ protected (brief) |
| C | User-app designs + new features | ✅ protected (brief), excluding the 5 v1↔v2 logic gaps (D3) |
| D | Stricter admin auth in apps/api |
✅ protected (D1) — 5 ledger routes still open, pending approval |
| E | Decimal money types | ✅ protected |
| F.1–4 | Prior security / data-loss non-ports | ✅ protected (D4) |
| F.5 | Branch horizontal layout | ❌ a real gap, not protected |
| G | Target-only capabilities | ✅ ➕ leave untouched (D2) |
Explicitly NOT protected (parity work, per DECISIONS.md)
- The
fullNamecollapse — being reversed under D0 percentread from the referring agent — wrong, being corrected under D9- The 5 v1↔v2 logic gaps — D3
- Unverified OTP — D6
- The public customer list — D8
singleCustomer.vue's full-form send — D11
H — Retail storefront (D13) — preserved, out of current scope
Owner's ruling of 2026-09-22. The reference also ran a retail storefront
(B2C single-customer shop). The wallet, cashback and
ConfigStatic['percent'] belong to it.
Keep-out rule — stronger than "protected": these are neither fixed nor touched. Nothing is deleted or disabled on either side; no parity work is done on them; they appear in the matrices only with status ⏸️ حفظشده — خارج از کار فعلی, with no gap row and no work row.
| Surface | Reference | Endpoints |
|---|---|---|
Customer API (prefix cu) |
routes/customer.php |
48 |
Public site API (prefix site) |
routes/site.php |
30 |
| Wallet / cashback admin | admin.php:186-191, Adm\WalletController |
6 |
| Retail-customer admin | admin.php:35,59,60,194-199 + single resource |
15 |
| Retail analytics | admin.php:38 get-analytics-tak → SingleCustomerOrders |
1 |
| Product comments admin | admin.php:150-152 + comment resource |
9 |
| Total | 109 |
Whatever of this already exists in gold-banking-monorepo stays exactly as it
is — it is not restyled, not refactored, not removed.
Shared-surface warning. Product, variant, price, order and customer are shared between the B2B business and the storefront. Before changing any of them, check whether the change reaches the storefront and ask the owner first (D13 rule 4). When in doubt whether something is storefront-only or shared, list it and ask — never guess (D13 rule 5).
Full reasoning and the endpoint classification: DECISIONS.md § D13.