# 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: 1. 4 KPI tiles — مشتریان جدید (نفر) · طلای فروش‌رفته (گرم) · تعداد سفارشات · نرخ هر گرم طلای ۱۸ عیار 2. 2 KPI tiles — سفارشات تحویل‌شده · مجموع بدهی طلای مشتریان (گرم) 3. Area chart «روند سفارش‌ها» — 2 series (تک / عمده); x-labels rotated **-45°** with `rotateAlways` + `hideOverlappingLabels` (a deliberate overlap fix — do not "tidy" it) 4. 2 donuts — وضعیت سفارشات (8-entry colour array) · تفکیک عمده/تک 5. Horizontal bar + an always-rendered agent table (کارشناس فروش / میزان فروش گرم / تعداد سفارش) 6. Vertical bar «محصولات پرفروش» with an `a-empty` fallback 7. Sales summary «میزان فروش» — 3 equal cells incl. a computed average 8. Table «سفارشات هفته جاری» — `pageSize` 10, `sts_str` as a coloured `a-tag`, `caj` via `jdate` **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--`) 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`/`image` contract on the > **user-app** side. `user-app` consumes it; `user-app-v2` does **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]** Both `apps/user-app` and `apps/user-app-v2` keep their own visual design. 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: 1. Unauthenticated file upload (`file/create` had no middleware in the reference) 2. SMS sent inside `toggleActive` 3. 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 4. The reference's avatar-load crash (`ReagentCustomer.vue:280` dereferenced `data.image.id` unguarded) 5. The branch-mode horizontal layout (`HorizontalNavMenu`, 4 files never ported; `mainLayoutType` narrowed 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 `fullName` collapse — **being reversed** under D0 - `percent` read 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.