Commit Graph
210 Commits
Author SHA1 Message Date
s.esmaeiliandClaude Opus 5.5 ee43d664d5 fix(experts): an expert edited in the panel now shows up in the app
The app shows a sales expert their own account through the linked trading
Customer row (/customer/me). That row was seeded from the expert once, at first
sign-in, and never updated again, so a name, mobile, credit-limit or wage-percent
change made in the panel never reached the app. In the reference the agent and
that account were one row, so an edit changed both.

Editing an expert now mirrors exactly the changed fields onto the trading row in
the same transaction: name parts, credit limit (gram_level), wage percent, and the
mobile when no other customer holds it. Existing drift heals on the next save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 08:00:01 +03:30
s.esmaeiliandClaude Sonnet 5.5 600ce06d15 fix: Persian duplicate-value errors; admin panel favicon uses the project logo
- PrismaExceptionFilter returned "Unique constraint failed on: mobile" in
  English for a duplicate. It now answers in Persian, naming the field
  (mobile, national code, member code, order number), with a safe generic
  fallback. Covered by a unit test.
- The admin panel still shipped the old "NZ" favicon. favicon.png/.ico are now
  generated from the project logo (the same one the user apps use), in both
  public/ and dist/, and the links carry ?v=2 so browsers drop the cached icon.
- Rebuilt apps/api/release/app.js.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 06:54:28 +03:30
s.esmaeiliandClaude Sonnet 5.5 0c52adcd26 fix(customers): show every non-archived wholesale customer in the main list
The reference (henza_server AdminController::getCustomer) lists type=2 AND
is_archive=0 with no verification or active filter. The develop-branch change
that hid inactive customers with a pending verification step from «عمده»
emptied the list for customers migrated in that state, while their numbers
stayed taken ("already exists"). Restore parity; the sales-expert trading
account exclusion is kept.

Includes the rebuilt release/app.js and a regression test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 06:50:05 +03:30
s.esmaeiliandClaude Sonnet 5.5 e2ec30f241 build: rebuild API release bundle for the admin/expert table split
app.js, seed.js, the Prisma client in release/vendor and the new migration
20261026120000_split_admin_table, built with `pnpm build:release` (API only;
the admin panel and user apps are unchanged by this work).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 23:55:07 +03:30
s.esmaeiliandClaude Sonnet 5.5 06c8a5f434 feat(auth): split panel admins out of AdminUser into their own Admin table
AdminUser held both panel staff and sales experts, so one mobile could not be
both. Staff now live in "Admin" (panel only); sales experts stay in "AdminUser"
untouched. Uniqueness is per table.

- migration 20261026120000_split_admin_table: additive only. Creates "Admin",
  copies non-expert rows with their ids, re-points the "who did this" foreign
  keys (ledger, order status, sms, notification, upload, allocation, workshop,
  admin session) to "Admin" without moving data. Never writes to "AdminUser".
- login, JWT, SSO, branch/workshop supervisors and the users grid use "Admin";
  SALES_EXPERT create/edit still goes through admin/users into "AdminUser".
- an admin cannot be turned into an expert or the reverse (different tables).
- manual/: optional phase-2 script to drop the dormant admin copies from
  "AdminUser", and a read-only expert-login diagnostic.
- docs/ADMIN-TABLE-SPLIT.md: what changed, deploy checklist, rollback.

Tested on a copy with 12,000 experts: expert rows byte-identical (md5) before
and after; full migration chain applies and matches schema.prisma.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 23:52:33 +03:30
s.esmaeiliandCursor 4679e2498c Exclude sales-expert trading accounts from wholesale customer lists.
Expert trading rows are APP_SHOPKEEPER for orders/ledger and were showing next to real omde customers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-25 23:16:13 +03:30
s.esmaeiliandCursor 066bfd2818 Build release artifacts for deploy.
Refresh API release bundle and admin/user app dist outputs for Dokploy copy-only deploys.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-25 22:46:21 +03:30
s.esmaeiliandCursor 6592e88a2c Fix admin IAM redirect and separate pending wholesale lists.
Keep admin browser bootstrap from falling back to the customer app URL, and exclude activated customers from the pending-verification list so it no longer mirrors the main omde grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-25 22:44:48 +03:30
mahmood 55d27151c9 feat(products): port missing variant fields from henza and redesign variant modal 2026-10-25 16:06:17 +03:30
mahmood 143d9aefbb fix(cities): remove 'استان' prefix, keep clean city/province names 2026-10-25 13:31:35 +03:30
mahmood 41fc2496ef fix(cities): keep only 31 provinces, remove bulky sub-cities seed 2026-10-25 13:23:57 +03:30
mahmood bb281cc5d6 feat(cities): seed all Iran provinces and cities with tree hierarchy 2026-10-25 13:04:07 +03:30
mahmood af5095a0e3 build: rebuild release and app bundles 2026-10-25 12:08:58 +03:30
mahmood 88cc8935de build: rebuild assets and update bundles 2026-10-25 11:55:55 +03:30
mahmood 5bd77c33d3 update admin panel, api, and shared-types 2026-10-25 11:43:45 +03:30
mahmood 2134de99e2 Fix some for iam docs 2026-10-18 22:45:47 +03:30
s.esmaeiliandCursor 3223c90983 build: rebuild release bundles for orders pagination and dashboard hero
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-17 13:21:55 +03:30
s.esmaeiliandCursor afb068c643 feat: paginate customer orders and polish the dashboard hero
Add paged orders API and infinite scroll, short order numbers, and align the guest invite card with the logged-in hero shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-17 13:11:42 +03:30
s.esmaeiliandClaude Opus 5.5 f550172567 build: rebuild release bundles for the product detail action bar fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:54:27 +03:30
s.esmaeiliandClaude Opus 5.5 037280cc09 fix: size the v2 product detail to the visible screen so its action bar stays on screen
اپ۲: دکمه‌ی «افزودن به سفارش» در صفحه‌ی جزئیات محصول دیگر از پایین صفحه بیرون نمی‌افتد و همیشه دیده می‌شود.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:49:35 +03:30
s.esmaeiliandClaude Haiku 5.5 1cfc19f483 build: rebuild release bundles for wine colour change
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 15:30:33 +03:30
s.esmaeiliandClaude Haiku 5.5 c018fe6dae style: use the brand wine instead of navy for fills and borders
اپ۲: رنگ دکمه‌ها، نوار نرخ طلا و خط‌های جداکننده‌ی بالای صفحه از سرمه‌ای به قرمز-شرابی برند تغییر کرد.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 15:27:45 +03:30
s.esmaeiliandClaude Haiku 5.5 b4f260a1c5 build: rebuild release bundles for navy and person-form fixes
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 15:21:24 +03:30
s.esmaeiliandClaude Haiku 5.5 1c4571a68d fix: remove navy from the customer app chrome and keep person forms fresh
اپ۲: رنگ سرمه‌ای از پس‌زمینه‌ی بالای برنامه و رنگ نوار مرورگر حذف شد؛ همه‌جا از رنگ صفحه‌ی اصلی استفاده می‌شود.
پنل: فرم افزودن شخص با هر بار جابه‌جایی نوع شخص خالی باز می‌شود و اطلاعات نیمه‌کارهٔ قبلی دیگر نمایش داده نمی‌شود.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 15:18:26 +03:30
s.esmaeiliandClaude Haiku 5.5 890f5a9725 build: rebuild release bundles with retail dashboard change
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 14:54:52 +03:30
s.esmaeiliandClaude Haiku 5.5 640a42da05 feat: show retail dashboard figures to the super admin only
پنل: بخش‌های «تک فروشی» داشبورد (کارت، نمودار روند و تفکیک عمده/تک) فقط برای مدیر اصلی نمایش داده می‌شود.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 14:51:25 +03:30
s.esmaeiliandClaude Haiku 5.5 0520aaf1b1 build: rebuild release bundles with new logo
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 14:37:52 +03:30
s.esmaeiliandClaude Haiku 5.5 37902e8bff feat: replace brand logo across apps and icons
اپ: لوگوی تازه‌ی برند در صفحه‌ی اصلی، لودر و صفحه‌ی ورود نمایش داده می‌شود.
پنل: لوگوی تازه‌ی برند در ورود، منو و کامنت‌ها نمایش داده می‌شود.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 14:34:19 +03:30
s.esmaeiliandClaude Haiku 5.5 c845f6db19 build: rebuild release bundles for build 181
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 13:35:35 +03:30
s.esmaeiliandClaude Haiku 5.5 532ceee189 feat: move brand name to one config and redesign the accounting card
اپ: نام برند در همه‌ی صفحه‌ها و پیام‌ها یکسان شده و از یک تنظیم مرکزی خوانده می‌شود.
اپ: کارت حسابداری در صفحه‌ی مشتری بازطراحی شد؛ مانده‌ها و وضعیت لینک واضح‌تر نمایش داده می‌شوند.
پنل: کارت حسابداری در پرونده‌ی مشتری بازطراحی شد؛ مانده‌ها، آخرین ارسال و فرم لینک دستی واضح‌تر شده‌اند.
پنل: ستون «کد» در جدول‌ها به‌طور پیش‌فرض پنهان است و از انتخاب ستون‌ها قابل نمایش است.
پنل: تغییر نمایش ستون‌ها در جدول‌ها بلافاصله اعمال می‌شود.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 13:31:20 +03:30
s.esmaeiliandClaude Haiku 5.5 5d2a1efb0c build(api): rebuild release bundle with baseline-first migration fix
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 10:46:15 +03:30
s.esmaeiliandClaude Haiku 5.5 e7c9b7238f fix(api): run baseline migration first so an empty database boots
Migrations were applied in plain name order, so 0002_admin_schema.sql ran
before 20260910113720_first and failed with relation "City" does not exist.
sortMigrations now always puts the baseline first; existing databases are
unaffected because their applied names are unchanged.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 10:45:30 +03:30
M.Lotfi 2a33c0f290 Merge remote-tracking branch 'origin/design/login-restore' 2026-10-14 10:19:27 +03:30
M.Lotfi 3f033071f7 Fix some for docker 2026-10-14 08:48:15 +03:30
s.esmaeiliandClaude Haiku 5.5 96416490bc build: rebuild admin-panel, user-app, user-app-v2 and shared-types dist
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 04:34:01 +03:30
s.esmaeiliandClaude Haiku 5.5 cf014e1f6c chore: commit pending admin-panel, api auth and parity doc changes
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 04:33:24 +03:30
s.esmaeiliandClaude Haiku 5.5 55856cac89 ui(admin-panel): add form-page styles from the design line (styles only)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 03:57:46 +03:30
s.esmaeiliandClaude Haiku 5.5 e26dddebd9 ui(admin-panel): restore login page design from the local design line
Only the template, styles and decorative icon imports change. The script
logic (login flow, validation, redirects) is identical to dev.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 03:55:51 +03:30
s.esmaeiliandClaude Haiku 5.5 ea987b708f fix(accounting): zarrin copy and sign notes match verified install
Zarrin v228 was tested on the real install (test account 953): negative
balance = owes, and temporary documents move the balance at once. Update
the admin copy and provider comments to match what was verified.

پنل: متن راهنمای زرین به‌روز شد: علامت مانده روی نصب واقعی بررسی شده است، و هشدار مربوط به ثبت سند موقت در «ثبت سفارش» اضافه شد.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-13 19:19:38 +03:30
s.esmaeiliandClaude Haiku 5.5 5b6207cd4f chore: consolidate all branches into dev
Side branches (fix/*, integrate/*, release/*, ui/admin-responsive, migration/legacy-data)
are fully contained in dev and main. dev and main are the only long-lived branches now.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-13 17:36:57 +03:30
s.esmaeiliandCursor d6d33d31c3 build: admin panel with responsive menus
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 10:31:21 +03:30
s.esmaeiliandCursor 5a30024548 ui(admin-panel): responsive users, offers, reports, finance, sms, config, notify and branch menus
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 10:30:25 +03:30
s.esmaeiliandCursor c1b3e90b9b feat(admin-panel): responsive branches, workshops, products and orders
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 09:14:28 +03:30
s.esmaeiliandCursor 0a2466ca0c ui(admin-panel): responsive persons menu, XTable phone cards, full-screen form modal
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 09:08:38 +03:30
s.esmaeiliandCursor 28b0a4be38 ui(admin-panel): responsive dashboard and mobile drawer layout
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 08:53:25 +03:30
s.esmaeiliandClaude Haiku 5.5 6ccdbc06f0 build: release build of admin, user apps and api
Rebuilt dist/ and api release bundle, with the migration for catalog paging indexes.

Fix Less build error: fade(@token, N%) on theme tokens failed in both user-app
and user-app-v2 CustomerChoiceView; use alpha(), the project's token-aware helper.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-12 15:37:03 +03:30
s.esmaeiliandClaude Sonnet 4.6 8b74c85a2a fix(accounting): verify and fix Zarrin driver on real v228 install
Tested 20/20 on the owner's live Zarrin v228 (870 accounts, 656 items):

Reads fixed:
- listKarSakhte: now pages by 100 via pageNum (was returning only first 100 of 656)
- goroohSakhte: HTTP 400 with a valid body is now accepted (was treated as missing)
- parvande «مانده از قبل» row is not a balance; skip it and anchor the running
  balance on the live listMandeHesabha balance, walked back through movements
- infogardeshsakhte: hand-printed JSON repaired, timeout raised to 90s
- document delete: now searches all ledger pages, not just the first one
- listhesabha names are «first * last»; the «*» is now stripped
- keep-alive ECONNRESET: a socket hang-up on a reused connection is retried once
  on a fresh connection (server closes idle connections)

Writes fixed (test account 953 «تست اپ گلدبانک»):
- temporary flag: set from shomareSanad («موقت»), not noeFactor
- noeFactor «فاکتور موقت» also flags a document left open in Zarrin's own UI

New: test script apps/api/scripts/test-accounting-zarrin.ts (13 read + 7 write checks)
Docs: §13 added to ACCOUNTING-INTEGRATION-PLAN.md; ACC-01 in ISSUES.md updated
New: docs/FRESH-SERVER-DEPLOY-AUDIT.md (fresh-server deploy checklist)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-10-12 15:23:24 +03:30
s.esmaeiliandClaude Opus 5 9fc0886a57 feat(apps): read products by id instead of loading the whole catalogue
Step 3 of docs/CATALOG-PAGINATION-PLAN.md. Step 2 added the targeted
routes; these are the five callers that were fetching all ~16k products
to find a handful of rows:

  product screen    whole catalogue, then find(id)  -> GET /products/:id
  cart re-sync      whole catalogue for 2-3 lines   -> ?ids=
  favourites        whole catalogue, filtered here  -> ?ids=
  home categories   categories derived from it      -> /products/facets
  order row labels  joined on variantId             -> included with the order

Three of these were not merely slow, they were going to be wrong.
Favourites filtered the loaded catalogue, so once the list pages a
favourite outside the loaded slice silently disappears. Home categories
were derived the same way, so a category whose products all sit further
down would vanish from the home screen. Order rows looked their title up
in the catalogue, which ALREADY failed for a product that had since left
the storefront — exactly the old orders history exists for — and
rendered a blank row.

Order labels went to the server rather than to another id lookup: the
item carries productVariantId, not a product id, so ?ids= could not
answer it. One nested include in getOrders returns the variant, its
product and the product's first image. Deliberately NOT filtered by
storefront visibility, so a de-listed product still shows what was
bought; asserted in the new spec. OrderItemCard is synchronous now — no
fetch, no onMounted, the row renders complete on its first frame.

variantLookup is deleted from catalogStore rather than left behind: it
existed only to label order rows, and keeping it would have kept a
reason to load the full catalogue.

getProductsByIds chunks at 100 because the route caps ids there and
drops the rest. A partial answer reads to callers as "these products are
no longer on the storefront" — the cart would silently drop valid lines.
Favourites already run to 874 rows in the imported data (FAV-01).

A failed cart re-sync now leaves the cart alone: it is the person's own
data in localStorage, and dropping lines because the network blinked is
worse than a slightly stale price. The product screen tells a real 404
from a failed request, so only the former renders the not-found page.

One bug introduced and caught while doing this: moving the home
categories to /facets meant memory.init() no longer loaded the catalogue
as a side effect, and the home rails read catalog.products — the home
screen would have rendered empty rails. HomeView now loads it
explicitly. Found by grepping every caller of configStore.init, which is
also how I know HomeView is the only one.

Still on the unpaged list by design: the home rails and the
search/newest lists. Moving those onto paged reads is step 4, kept
separate because it is the risky part — debounce, loading state, and
telling "no results" from "still loading".

api 334/334 across 30 suites; both apps vue-tsc clean. getOrders has a
spec now (3 cases) because its include is a contract both apps depend on
and the method had no test at all. Nothing click-tested — the cart
re-sync and the favourites screen are the two worth a manual pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-12 15:19:50 +03:30
s.esmaeiliandClaude Opus 5 cbb93dec5d feat(catalog): page and filter the storefront server-side (steps 1-2)
The real catalogue reaches ~16k products and GET /catalog/products had no
paging parameters at all: it returned every active product with its
variants, images, category, brand and collections in one response. The
apps' PAGE_SIZE = 20 was only a render window over that full array, so
scrolling made zero requests. The reference paged this server-side; the
port had moved it to the client (STUB-03), so this is a return to
reference behaviour.

Step 1 — indexes, which were not optional. Product carried NO index
whatsoever (not isActive, categoryId, brandId or createdAt) and
ProductVariant had none on productId, so every per-product include
scanned its whole table. Adding take/skip on top of that would have
paged the response without making the query faster, which is the exact
failure this change exists to avoid. Product_isActive_createdAt_idx is
composite so one index serves the filter, the default ordering and the
OFFSET walk.

CREATE INDEX, not CONCURRENTLY: the deploy image's migration runner
wraps each file in a transaction, which CONCURRENTLY cannot join (D15).
Every statement is IF NOT EXISTS because both migration paths run it.

Step 2 — the routes. Paging is opt-in, which is the whole
backward-compatibility story: with no query parameters the route returns
the same plain array from the same cache as before, so the admin panel,
the smoke scripts and installed app builds are untouched. Pass page,
limit or any filter and it becomes { rows, total, page, limit }.

The filters had to move too, not just the paging. The apps filtered
title, category, brand and gram range in the browser over the full
array; paging the response while leaving that in place would have made
search quietly WRONG — searching only the first page and reporting
"محصولی پیدا نشد" for products that exist. So every filter is built into
the Prisma query, and count uses the same where clause, so `total`
describes the set the app is actually scrolling.

Three routes added so the apps can stop fetching everything to find one
thing (wired up in step 3): ?ids=a,b,c for the cart, favourites and
order items; /:id for the product screen, which today downloads the
whole catalogue to find one row; /facets for the filter sheet's options,
which the apps currently derive by scanning the catalogue in the
browser.

Deliberately not cached: CatalogCacheService exists because the unpaged
query is expensive and every screen wanted it. A paged, filtered query
is neither — bounded by limit and index-served — and caching it would
need a key per filter combination, i.e. unbounded memory for cheap
queries plus a second place for stale rows to hide.

D27's two storefront rules (active, has an image) live in one shared
where clause, so the list, its count, the single-product route, by-ids
and the facets can never disagree about what is visible. limit and ids
are both capped at 100; an uncapped limit is just the old "give me
everything" query spelled differently.

17 new cases, 329/329 overall. All 17 passed on the first run, which is
not evidence by itself, so one filter was deliberately broken to confirm
the suite catches it (1 failed/16 passed) and then restored.

Not measured on real data — the database container was not running, so
16k-row behaviour is reasoned from the query plan, not timed. Worth one
EXPLAIN ANALYZE on a full dataset before step 4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-12 15:01:32 +03:30
s.esmaeiliandClaude Opus 5 bfbac92df5 fix(apps): back button follows real history; screens re-read on entry
Reported: from the profile page, going to login and pressing back landed
on home instead of the profile — with the instruction to audit the whole
structure rather than that one case. Seven defects, four reachable in
ordinary use.

NAVIGATION

PageBar's back arrow pushed a FIXED route name (`backTo`) and ignored
history, so it could not know where the person came from. That was true
of 14 of the 15 PageBar uses in each app — only SearchView used
router.back() — so it was never one screen's bug. Inherited from the
reference, which pushed a fixed name in every component's back arrow;
not replicated (D4).

`backTo` is now a fallback, used only when there is nothing to go back
to (a shared link, a refresh). All 14 declarations are kept untouched,
because that is exactly when they are right.

Answering "is there anywhere to go back to?" needs care: history.length
counts entries from before the app was opened, so a link opened in a
fresh tab would look navigable and back would walk the person out of the
app. navStore records the history `position` of the app's first
navigation and compares the current one against it — direction-proof,
unlike a counter, which only grows and would be wrong after one
back-and-forward. Confirmed vue-router writes `position` on every entry
rather than assuming it.

Three pushes became replaces, each of which left behind a page the
guards immediately bounce, making "back" a no-op or a trip home:
  - after sign-in the login screen stayed in history — the worst, since
    the same file already honoured ?redirect=, so signing in and
    pressing back disagreed about where the person belonged;
  - the 401 handler kept the page whose request had just failed;
  - complete-profile kept a form that is unreachable once the documents
    are in.
CartView's post-order push is left alone: back lands on the emptied
cart, which is a real screen, not a dead end.

READ FRESHNESS

Owner's rule: fetch what a screen needs when it is opened, rather than
caching with a lifetime. Better than the TTL I had proposed — a TTL
guesses how long data stays true, while entering the screen is when it
actually matters.

OrdersView was the worst: it is in the keep-alive list but only loaded
in onMounted, so the second and every later visit made no request at
all, and the first read the cache. Order status changes on the admin
side and nothing here invalidates it, so the list could be stale for a
whole session. Ledger entries had the same shape and a stronger reason —
they are written by the accounting side, which this app can never learn
about. The gold price was forced only by pull-to-refresh on the two
screens that show it most prominently.

The catalogue is deliberately NOT re-read, by the owner's own reasoning
that this must not add server load: it is the entire product list in one
response, it changes when an admin edits it, and pull-to-refresh and
placing an order already invalidate it.

The cache layer itself is sound — concurrent callers share one in-flight
promise, and a response arriving after an invalidate is returned but not
stored, so a write cannot lose a race against a read in flight. Its one
real gap was that nothing ever went stale. The policy is now written at
the top of queryCache.ts so a TTL is not re-introduced.

Both apps vue-tsc clean. Not click-tested in a browser; the back-button
behaviour is worth one manual pass.

اپ: دکمه بازگشت به صفحه قبلی واقعی برمی‌گردد و اطلاعات هر صفحه در هر بازدید به‌روز می‌شود.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-12 12:42:42 +03:30