Merge branch 'main' of ssh://git.tbs.amn.gg:2222/escrow/nick-doc
# Conflicts: # 09 - Audits/Activity Log.md
This commit is contained in:
@@ -11,6 +11,123 @@ entries on top. Maintained by agents per the rule in `../AGENTS.md`.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-05 — backend@v2.8.84, frontend@v2.8.101 — Notifications wired end-to-end (id-seam normalize + Mini App joins user room)
|
||||
|
||||
**Commits:** backend v2.8.84, frontend v2.8.101
|
||||
**Touched:** backend `services/notification/NotificationService.ts`; frontend `sections/telegram/hooks/use-telegram-notifications.ts`
|
||||
**Why:** No notifications reached the Mini App (in-app, real-time, or Telegram push). Two root causes. (1) **Id seam:** notifications were created with whatever id the event carried — usually a Postgres uuid (`sellerId`/`buyerId`) — but everything that *consumes* a notification keys on the user's session legacy ObjectId: the in-app fetch (`req.user.id`), the `user-<id>` socket room, and `TelegramLink.userId`. So a notification stored under a uuid was invisible to fetch, never reached the room, and `sendTelegramNotificationToUser(uuid)` found `no_link`. Fix: `NotificationService.createNotification`/`createNotificationsBulk` now normalise `userId` via `toCanonicalUserId` (uuid → `users.legacy_object_id`) before persist + real-time emit + Telegram forward, so all three line up. (2) **Mini App never joined its room:** the socket connected but `use-telegram-notifications` only registered listeners — it never emitted `join-user-room`, so the backend's targeted `new-notification` emits had no subscriber. Added a `joinUserRoom(userId)` effect that (re)joins on every connect. `selfId` is the session ObjectId, matching the socket's authed id and the now-normalised emit target.
|
||||
**Verification:** backend `npx tsc --noEmit`; frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint — clean. After deploy: a marketplace event (offer received/accepted, payment, delivery) creates a notification that (a) shows in the Mini App bell list, (b) bumps the unread badge live, and (c) arrives as a Telegram bot message for linked users.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-05 — backend@v2.8.83, frontend@v2.8.100 — Select-offer 403 (id seam) + offer delivery-time `[object Object]`
|
||||
|
||||
**Commits:** backend v2.8.83, frontend v2.8.100
|
||||
**Touched:** backend `services/marketplace/marketplaceController.ts` (`selectOffer`); frontend `sections/telegram/view/telegram-request-detail-view.tsx`
|
||||
**Why:** Testing the new Mini App «انتخاب و پرداخت» surfaced two bugs. (1) **403 on select-offer**: `selectOffer` gated buyer ownership with `toIdString(purchaseRequest.buyerId) !== buyerId` — the session legacy ObjectId vs the PG-uuid buyerId (the recurring seam) → the real buyer got 403. Switched to `!(await sameUser(buyerId, toIdString(purchaseRequest.buyerId)))`. (2) **Delivery time rendered `[object Object]`**: the offer card assumed `offer.deliveryTime` was a number, but it can be an object (`{amount, unit}`). Now extracts `dt.amount ?? dt.value ?? offer.deliveryTimeAmount` when it isn't a plain number.
|
||||
**Verification:** backend `npx tsc --noEmit`; frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint — clean. After deploy: buyer taps «انتخاب و پرداخت» → offer accepted (no 403) → in-shell payment opens; delivery time shows a number of days, not `[object Object]`.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-05 — frontend@v2.8.99 — Mini App buyer: see received offers + select & pay in-shell
|
||||
|
||||
**Commits:** frontend only (v2.8.99)
|
||||
**Touched:** `sections/telegram/view/telegram-request-detail-view.tsx`, `sections/telegram/locales/{fa,en,types}.ts` (`offers_title`, `offer_select_pay`, `offer_delivery_days`, `offer_accepting`)
|
||||
**Why:** When the buyer's stepper reached «انتخاب و پرداخت» (step 3, status `received_offers`) the Mini App had no way to act — the pay CTA only showed for `pending_payment`. Now, for a buyer on an offer-selection status, the detail view fetches and lists the received offers (seller, price, delivery days, description) and each has a «انتخاب و پرداخت» button → `acceptOffer(requestId, offerId)` (sets `selectedOfferId`, moves to `pending_payment`) → `refresh()` → opens the in-shell direct-transfer payment screen via `onPay`. Reused the existing offers SWR (broadened from review-only to also fire on `received_offers`/`offers_received`/`in_negotiation`).
|
||||
**Verification:** frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint clean. After deploy: buyer opens a request with an offer → sees the offer card → «انتخاب و پرداخت» → in-shell payment opens; paying advances the stepper.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-05 — frontend@v2.8.98 — Mini App seller shop: product-type filter + show controls for small shops
|
||||
|
||||
**Commits:** frontend only (v2.8.98)
|
||||
**Touched:** `sections/telegram/view/telegram-seller-shop-view.tsx`
|
||||
**Why:** The seller shop (per-seller template list) already had search + sort but (1) no filter, and (2) the controls bar was gated on `allTemplates.length > 3`, so a typical 3-template shop showed no controls at all. Added a product-type filter (chips built only for the types the seller actually sells — physical/digital/service/consultation, plus «همه») wired through `TelegramListControls`, and lowered the controls threshold to `> 1` so shops with 2–3 templates still get search/filter/sort.
|
||||
**Verification:** frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint clean. After deploy: opening a seller shop with ≥2 templates shows the controls bar; the filter chips reflect the seller's product types and narrow the list.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend@v2.8.82, frontend@v2.8.97 — Stepper advances: offer→step, seller offer detection, real-time on code-verify
|
||||
|
||||
**Commits:** backend v2.8.82, frontend v2.8.97
|
||||
**Touched:** backend `services/marketplace/marketplaceController.ts` (`getOffersForRequest` sellerId filter, `verifyDeliveryCode` socket emit); frontend `sections/request/request-config.tsx` (buyer `received_offers`→3), `actions/marketplace.ts` (`getSellerOfferForRequest` fallback)
|
||||
**Why:** Three related stepper bugs surfaced while testing an offer end-to-end. (1) **Buyer stepper stuck at "awaiting offers" (2)** after an offer arrived: `determineBuyerStep('received_offers')` returned `offersCount>0 ? 3 : 2`, but callers that don't thread `offersCount` (the Mini App) got 2. The status itself means an offer arrived on the buyer's own request, so it now returns 3 unconditionally. (2) **Seller stuck on "send offer" (1)** after submitting: `getSellerOfferForRequest` matched offers by `o.sellerId === user._id`, but `o.sellerId` is a PG uuid and `user._id` is the legacy ObjectId (the seam) → no match → `sellerOfferStatus` undefined → step 1. Backend `getOffersForRequest` now filters to the seller's own offers via `sameUser` when `?sellerId=` is passed, and the client returns `data[0]` as a fallback. (3) **Seller needed a manual refresh** to go from "awaiting buyer" (4) to "receive funds" (5): the seller's code entry (`verifyDeliveryCode`) replaced the buyer's `confirmDelivery` in v2.8.95 but never emitted a socket event, so no live update. Added the `purchase-request-update` / `status-changed` emit on successful verify (matching what `confirmDelivery` did).
|
||||
**Verification:** backend `npx tsc --noEmit`; frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint — all clean. After deploy: buyer sees step 3 once an offer arrives; seller sees step 2 right after sending an offer; seller's stepper advances to "receive funds" without a manual refresh after entering the code.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend@v2.8.96 — Mini App account: don't show "email verified" when the user has no email
|
||||
|
||||
**Commits:** frontend only (v2.8.96)
|
||||
**Touched:** `sections/telegram/view/telegram-account-view.tsx`, `sections/telegram/locales/{fa,en,types}.ts` (`email_not_set`)
|
||||
**Why:** A Telegram sign-up has `isEmailVerified=true` but no email address, so the account header showed a green «ایمیل تأیید شده» badge even though the email field was empty. Gated the verified badge on `user?.email && user?.isEmailVerified`. Added a third state for the no-email case: a «ایمیل ثبت نشده» badge that opens the in-shell settings (`onOpenSettings`) to add an email. The not-verified (has-email-but-unverified) branch is unchanged.
|
||||
**Verification:** frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint clean. After deploy: a Telegram user with no email sees «ایمیل ثبت نشده» (→ settings), not a false «ایمیل تأیید شده».
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend@v2.8.95 — Delivery confirmation: code is entered by the seller, buyer's «تایید دریافت کالا» removed
|
||||
|
||||
**Commits:** frontend only (v2.8.95)
|
||||
**Touched:** `sections/telegram/view/telegram-request-detail-view.tsx` (Mini App buyer: drop confirm button), `sections/request/components/buyer-steps/step-5-receive-goods.tsx` (web buyer: drop confirm button), `sections/request/components/seller-steps/step-4-waiting-for-confirmation.tsx` (web seller: code display → code input)
|
||||
**Why:** The handover flow was wrong/duplicated: the buyer had a «تایید دریافت کالا» button (`confirmDelivery`) AND the seller was shown the expected code. Per the user, the single correct mechanism is: the **buyer gives the 6-digit code, the seller enters it** to confirm — and that 403'd anyway (`confirmDelivery` compared session ObjectId `!==` PG-uuid buyerId — same id seam). Changes: (1) Mini App buyer (`status delivery`) now only displays the code to hand over — the `confirmDelivery` button/handler/import removed. (2) Web buyer step-5: removed the «تایید دریافت کالا» button (kept the code display + an explanatory note). (3) Web seller step-4: replaced the code **display** (`getDeliveryCode`, which leaked the code to the seller and 403s post-v2.8.79) with a 6-digit **input** + «تایید تحویل کالا» calling `verifyDeliveryCode(id, code)`. Both `confirmDelivery` and `verifyDeliveryCode` reach the same `delivered` state, so no escrow-release regression. (Mini App seller already had the input from v2.8.94.)
|
||||
**Verification:** frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint clean. After deploy: buyer sees only the code (no confirm button); seller enters the buyer's code → status → delivered; no 403.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend@v2.8.81 — Public shop settings: resolve uuid→legacy ObjectId (name/avatar were blank publicly)
|
||||
|
||||
**Commits:** backend v2.8.81
|
||||
**Touched:** `services/marketplace/shopSettingsStore.ts` (`getSellerShopSettings` + new `resolveSellerLegacyId`)
|
||||
**Why:** Found while setting shop name/avatar/cover for the three seed sellers. After PUT-ing settings, the seller's OWN `GET /marketplace/shop/settings` returned them correctly, but the PUBLIC `GET /marketplace/shop/settings/:sellerId` (what the shop page shows buyers) returned `data:null` — blank name, no avatar/cover. Root cause: the recurring Mongo↔PG id seam. `shop_settings` rows are keyed by `seller_legacy_object_id` (the seller's legacy Mongo ObjectId, which is what `req.user.id` is on the own-settings path), but the public sellers list returns each seller by their PG **uuid**, so `getSellerShopSettings(uuid)` queried `where seller_legacy_object_id = <uuid>` and matched nothing. Fix: when the direct lookup misses, resolve the incoming id via `select legacy_object_id from users where id=$1` (a 24-hex id is treated as already-legacy) and retry. Mirrors the existing reverse `resolveSellerUuid` helper.
|
||||
**Verification:** backend `npx tsc --noEmit` clean. After deploy: `GET /marketplace/shop/settings/<pg-uuid>` returns the saved name/avatar/cover for all three sellers (was null). Seeded: seller/seller1/seller2 @amn.gg each have a shop name, description, avatar, cover image, social links, and 3 active products.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend@v2.8.80 — Shop sellers list cache: invalidate global `templates:list` on per-seller change
|
||||
|
||||
**Commits:** backend v2.8.80
|
||||
**Touched:** `services/redis/cacheService.ts` (`invalidateTemplatesCache`)
|
||||
**Why:** Found while seeding shops for three sellers via API: after creating templates for `seller1@amn.gg`/`seller2@amn.gg`, the public shop endpoint (`GET /marketplace/request-templates/sellers`) still returned only the first seller. Root cause — the global sellers list is cached under Redis key `templates:list`, but `RequestTemplateService` create/update/delete call `invalidateTemplatesCache(sellerId)` which only deleted `templates:seller:<id>`, never `templates:list`. So a newly-onboarded seller didn't appear in `/shop` until the 5-minute TTL elapsed. Fix: when invalidating with a `sellerId`, also `del('templates:list')` since the aggregated list changes whenever any seller adds/edits/removes a template.
|
||||
**Verification:** backend `npx tsc --noEmit` clean. After deploy: a brand-new seller's first template appears in `GET …/request-templates/sellers` (and dev.amn.gg/shop) immediately, not after TTL. (Seeded data: seller/seller1/seller2 @amn.gg each have 3 active templates — two at 0.01 USDT, one at 0.05 — with images, BSC USDT/USDC payment config.)
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend@v2.8.94 — Telegram Mini App: seller delivery-code entry field
|
||||
|
||||
**Commits:** frontend only (v2.8.94); backend stays v2.8.79
|
||||
**Touched:** `telegram-request-detail-view.tsx` (seller code-entry card), `locales/{fa,en,types}.ts` (`seller_code_*` strings)
|
||||
**Why:** The escrow handover is two-sided: the buyer generates a 6-digit delivery code (shown in v2.8.92) and the **seller enters it** to confirm delivery. The Mini App seller had no field for this — at status `delivery` the seller only saw «منتظر تایید خریدار» with nowhere to type the code. Added a code-entry card for `role === 'seller' && status === 'delivery'`: a numeric 6-digit input (digit-filtered, monospace, centred) + «تایید تحویل کالا» button calling `verifyDeliveryCode(requestId, code)` then `refresh()` so the status advances to `delivered`. Backend `verifyDeliveryCode` already uses the `sameUser` seller gate (v2.8.77), so this composes with the id-seam fix.
|
||||
**Verification:** frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint clean. Admin verifies after deploy: buyer opens `delivery` request → sees code; seller opens same request in Mini App → enters that code → «تایید تحویل کالا» → status → delivered.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend@v2.8.79 — Delivery code: apply `sameUser` to buyer auth gates (Mini App showed "—")
|
||||
|
||||
**Commits:** backend v2.8.79 (frontend stays v2.8.93)
|
||||
**Touched:** `services/marketplace/marketplaceController.ts` (`generateDeliveryCode`, `getDeliveryCode`)
|
||||
**Why:** In the Mini App receive-goods step the delivery code rendered as «—»: both `getDeliveryCode` and `generateDeliveryCode` returned 403. Root cause is the recurring Mongo↔PG id seam — these two handlers still used the strict `request.buyerId !== userId` comparison (session legacy ObjectId vs PG-uuid `buyerId`), unlike `verifyDeliveryCode` which was already moved to the async `sameUser` helper in v2.8.77. Replaced both buyer checks with `!(await sameUser(userId, toIdString(request.buyerId)))`. Added an explicit `!userId` unauthorized guard in `getDeliveryCode` to restore the `string` narrowing the removed `!==` previously provided (so `getDeliveryCode(id, userId)` still type-checks). The repo layer (`getDeliveryCodeForUser`) already resolves the seam via `resolveEntityId`, so once the code exists it loads without regenerating.
|
||||
**Verification:** backend `npx tsc --noEmit` clean. Admin verifies after deploy: buyer opens a `delivery`-status request in the Mini App → a 6-digit delivery code appears (no «—», no 403); seller can verify it.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend@v2.8.78, frontend@v2.8.93 — Gate shop orders on payment: abandon-cancel + 30-min TTL sweep
|
||||
|
||||
**Commits:** backend v2.8.78, frontend v2.8.93
|
||||
**Touched:** backend `services/admin/dataCleanupService.ts` (new `cancelStaleUnpaidRequests`), `services/admin/ttlCleanupJob.ts` (schedule it every 5 min); frontend `telegram-payment-view.tsx` (abandon-cancel on unmount)
|
||||
**Why:** Shop checkout creates the purchase request in `pending_payment` *before* the buyer pays — unavoidable because the direct-balance deposit address is derived per-request, so the request must exist for the AMN scanner to credit it (the payment-intent route requires `purchaseRequestId`/`sellerOfferId`/`sellerId`). That left unpaid orders lingering. Now, per the user's choice (both mechanisms, 30-min window, only cancel orders with **no** deposit): (1) **Frontend abandon-cancel** — when the buyer leaves the in-shell checkout payment screen without completing, the just-created order is set to `cancelled` via `updateRequestStatus`; two refs (`paidRef`, `depositRef`) suppress the cancel if the deposit was confirmed *or* a partial deposit was detected, so funds are never orphaned. (2) **Backend TTL sweep** — `cancelStaleUnpaidRequests` runs every 5 min, cancels `pending_payment` requests older than 30 minutes that have **no** active payment (`findActivePaymentForRequest === null`); any active/slow on-chain payment is left untouched. The sweep is the backstop for app-closed cases the frontend unmount can't catch.
|
||||
**Verification:** backend `npx tsc --noEmit` clean; frontend `npx tsc --noEmit --ignoreDeprecations 6.0` + eslint clean. Admin verifies after deploy: shop checkout → reach payment step → press back without paying → order shows `cancelled` (not lingering pending_payment); a paid/underpaid order is NOT cancelled.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend@v2.8.92 — Telegram Mini App: buyer receive-goods/confirm + checkout stepper line fix
|
||||
|
||||
**Commits:** frontend only (v2.8.92); backend stays at v2.8.77
|
||||
**Touched:** `telegram-request-detail-view.tsx` (buyer receive-goods flow), `components/telegram-request-stepper.tsx` (row width cap/centre), `locales/{fa,en,types}.ts` (delivery-code + confirm-receipt strings)
|
||||
**Why:** (1) The buyer had no way to confirm receipt inside the Mini App — once the seller shipped (status `delivery`, fixed in backend v2.8.77's `sameUser` patch), the buyer was stuck at step 5 «دریافت کالا» with no action. Mirrored the web `step-5-receive-goods`: on `status === 'delivery'` for a buyer, auto-load/generate the delivery code (`getDeliveryCode` → fallback `generateDeliveryCode`) shown dashed/monospace for the buyer to hand the seller, plus a «تایید دریافت کالا» button calling `confirmDelivery(requestId)` then `refresh()` so the stepper advances to «تایید نهایی». (2) The 3-step checkout stepper stretched each cell across the full Telegram width, leaving a long connector stub past the last dot — capped the steps row at `steps.length * 96` and centred it (`margin: 0 auto`), so both the 3-step checkout and 6-step request steppers render tight, even connectors.
|
||||
**Verification:** frontend `npx tsc --noEmit --ignoreDeprecations 6.0` clean. Admin verifies after deploy: buyer opens a `delivery`-status request → sees delivery code + «تایید دریافت کالا» → tap → request advances to تایید نهایی; checkout stepper line is clean (no stub).
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — scanner@ccd96e8, backend@22ae0bd, frontend@10c4292 — direct scanner balance checks and balance watches
|
||||
|
||||
**Commits:** scanner `ccd96e8` (tag `v0.1.8`), backend `22ae0bd` (tag `v2.8.60`), frontend `10c4292` (tag `v2.8.60`)
|
||||
@@ -699,6 +816,19 @@ TON-only, no EVM; backend already has tonProofService).
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend@HEAD — Mini App floating cart FAB (v2.8.60)
|
||||
|
||||
**Why:** User wants the web shop's small edge-docked basket (count badge) in the Mini App too.
|
||||
New `TelegramCartFab` component pinned above the tab bar, shown on shop tab + in-shell seller
|
||||
shop, opens the in-shell cart. Replaced the header cart chip + bottom cart bar.
|
||||
**Pending next (user requests):** (1) Mini App «تنظیمات» — dark/light mode for the Telegram shell
|
||||
(needs a dark palette variant of telegram-shell-css + toggle; should follow webApp.colorScheme);
|
||||
(2) «درخواست جدید» button font — it is Telegram's NATIVE MainButton, font cannot be customized;
|
||||
option is replacing it with an in-shell button using the project font.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-02 — backend@7c4dedf — complete dual-write repos, migrations pipeline, TTL scheduler, address reconciliation
|
||||
|
||||
**Commits:** `7c4dedf` (backend v2.8.44), frontend v2.8.44
|
||||
@@ -709,83 +839,612 @@ TON-only, no EVM; backend already has tonProofService).
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend@41087c7, frontend@ab18334, deployment@8764fdf — CI/CD infrastructure updates and Telegram Mini App delivery flow
|
||||
### 2026-06-03 — frontend@HEAD — Mini App floating support bubble (v2.8.61)
|
||||
|
||||
**Commits:** backend `41087c7`, `ed7b960`, `aaa7a04`, `f380f92`, `64792b1`; frontend `ab18334`, `4bc038e`, `81caab5`, `0f3b7c0`, `482d64a`, `f6b1b0e`, `d37a0c0`, `abc5e73`, `0001f22`, `3d7df1a`, `e90659f`, `88fa4da`, `9f90995`, `e8fff89`, `715e143`, `57f8067`, `57bd04a`; deployment `8764fdf`
|
||||
**Touched:** Backend CI/CD pipeline files, frontend Telegram Mini App components, Docker configurations, payment flow, chat archive, marketplace delivery
|
||||
**Why:** Major CI/CD updates: mount /opt/escrow-dev for compose, build locally with registry push skip, pin pipelines to linux/arm64 agent with native builder. Telegram Mini App: seller delivery-code entry, abandon-cancel unpaid shop orders, buyer receive-goods/confirm, checkout stepper fixes, in-shell direct-transfer payment, search in archived chats, WhatsApp-style archive view with unarchive. Frontend Docker: raise Node.js heap to 4GB for Next.js build.
|
||||
**Verification:** Backend and frontend builds passing, Telegram Mini App features verified in development.
|
||||
**Linked docs updated:** [[Telegram Mini App]], [[CI-CD Pipeline]], [[Payment Flow - DePay & Web3]]
|
||||
**Why:** Parity with the web dashboard's green floating chat widget. New `TelegramSupportFab`
|
||||
(edge-docked, green bubble) on home/shop/requests tabs + in-shell seller shop; opens/creates the
|
||||
support chat. Stacks above the cart FAB in shop contexts.
|
||||
**Verification:** tsc + eslint clean.
|
||||
**In progress / queued:** Points & Referral audit (background agent — bugs observed: values render
|
||||
as "·", Bronze level claims "highest level"); Mini App dark mode (تنظیمات); buyer-parity phase 2
|
||||
(offers view/accept).
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend@9bb73e2, frontend@35ed72d — security hardening and feature enhancements
|
||||
### 2026-06-03 — backend@c5d6490 — Points & Referral audit fixes (v2.8.62)
|
||||
|
||||
**Commits:** backend `9bb73e2`, `0283e73`, `fb85b38`, `94e2dbe`, `118466d`, `fde1c04`, `c2ae239`, `7810d6b`, `e2aad59`, `64c5d5c`, `62f0af8`; frontend `35ed72d`, `0358af5`, `e25b6b8`, `406810d`, `d64e194`, `131700b`, `0bdb5b3`, `7b3e9ca`, `9bafbbb`, `6dc3918`, `a8ae1e3`, `6adb2e0`, `a18e870`, `583d55a`, `7b949bf`
|
||||
**Touched:** Delivery code auth gates, TTL sweep for stale purchase requests, marketplace uuid/ObjectId seam, chat archive/unarchive, participant canonicalization, stepper navigation, floating cart/support buttons, product-style template cards
|
||||
**Why:** Security: use sameUser for buyer auth gates, cancel stale unpaid purchase requests via TTL sweep. Chat: boolean query filter fixes, support unarchive and archived list fetch, participant id mapping. Telegram: seller chat button, notification bell, in-shell shop, account tab parity, role dashboards, live chat, product-style template cards, floating cart, support bubble, checkout CTA.
|
||||
**Verification:** Backend typecheck and tests passing, frontend TypeScript compilation clean.
|
||||
**Linked docs updated:** [[Authentication Flow]], [[Chat Flow]], [[Telegram Mini App]], [[Marketplace API]]
|
||||
**Why (from points audit):** /dashboard/points showed "·" instead of numbers and told Bronze users
|
||||
they are at "the highest level". Root causes: (1) level boundary off-by-one — seeds define
|
||||
inclusive maxPoints (Bronze 0–999) but PG used `>` and Mongo `<`, so exact-boundary users resolved
|
||||
to no level; (2) `level_configs` table in Postgres was created but NEVER seeded → zero levels →
|
||||
null next-level → "highest level" message. Fixes: inclusive comparisons in both repos +
|
||||
self-seeding of the 5 default levels (برنز/نقره/طلا/پلاتین/الماس) in ensurePostgresLevelConfigSchema.
|
||||
**Remaining from audit:** frontend points page swallows API errors (shows zeros instead of an
|
||||
error state); integration ideas: award points on completed escrow, show points in Mini App.
|
||||
**Verification:** tsc clean, 8 tests green. Admin verifies after deploy: /dashboard/points shows
|
||||
real numbers and Bronze shows progress toward نقره.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend@cd79460, frontend@baa3e52 — security remediation and payment features
|
||||
### 2026-06-03 — frontend@131700b — compact floating cart/support FABs (v2.8.63)
|
||||
|
||||
**Commits:** backend `cd79460`, `7c6158d`, `bc5b6aa`, `1c51dd8`; frontend `baa3e52`, `2b99404`, `f9c4b0c`, `450625b`, `59b3a67`, `a133fc0`, `93199d4`
|
||||
**Touched:** Security ownership checks (SEC-001), error message sanitization (SEC-029), PRD tasks remediation (SEC-001..037), security remediation PRD documentation; Telegram: direct-transfer checkout for non-web3, avatar URL fixes, email verify flow, theme/dark mode, settings/addresses in-shell, solar-style icons
|
||||
**Why:** Complete security audit remediation: reconcile SEC-001 ownership checks with main's sameUser helper, stop leaking raw error messages (SEC-029), remediate P1+P3+P4 security tasks, add comprehensive security remediation PRD. Telegram Mini App: direct-transfer payment option, in-shell settings, addresses management, theme configuration, avatar upload, achievements display.
|
||||
**Verification:** Security audit tests passing, error messages no longer expose sensitive data.
|
||||
**Linked docs updated:** [[Security Audit - 2026-05-24]], [[Security Architecture]], [[Telegram Mini App]], [[Payment Flow - DePay & Web3]]
|
||||
**Why:** User feedback — the two edge-docked FABs were too large. Compacted paddings, icons,
|
||||
badge, and stack positions.
|
||||
**Verification:** tsc clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend@c501b2c, frontend@7fe236f — authentication and chat enhancements
|
||||
### 2026-06-03 — frontend@d64e194 — Mini App points view + tap-to-verify email (v2.8.64)
|
||||
|
||||
**Commits:** backend `c501b2c`, `d4e53f4`, `941feee`, `58eb61c`, `4149fcf`, `c5d6490`, `91877ae`, `14d164c`, `8b8c1ae`, `9424395`, `378f8f6`, `14c231e`; frontend `7fe236f`, `7be7e2b`, `4079730`, `5d2113b`, `0888b30`, `8f23cca`, `1cc4212`
|
||||
**Touched:** Pending-email change flow, referral signup bonus, shop settings, chat participant names, seller ratings, points level boundaries, uuid/legacy id tolerance, roles (guard), chat/notification fixes, user management, admin dashboard repair
|
||||
**Why:** Authentication: pending-email change flow with referral signup bonus. Chat: populate participant names on Postgres path, canonicalize participant ids, revert canonicalization that broke membership. Shop: compute seller ratings from real published reviews, seller shop lookup tolerant of uuid/legacy id formats. Points: level boundary fixes, accept legacy 24-hex user ids. Roles: add guard user role. Admin: repair user management dashboard, surface backend error messages.
|
||||
**Verification:** Backend typecheck and marketplace/chat tests passing, frontend TypeScript clean.
|
||||
**Linked docs updated:** [[Authentication Flow]], [[Chat Flow]], [[User API]], [[Marketplace API]], [[Points API]]
|
||||
**Why:** Buyer parity — امتیازات و رفرال now in-shell (TelegramPointsView: points cards, level
|
||||
progress, referral code copy + stats; account-tab row). Email verification from the Mini App:
|
||||
unverified badge is tappable → sends code → code-entry sheet → verifyProfileEmail → session
|
||||
refresh. Reuses existing account.ts actions.
|
||||
**Verification:** tsc + eslint clean. Test after deploy: account tab → امتیازات row; unverified
|
||||
user → tap red badge → enter emailed code → badge turns green.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend@a76b07b, frontend@d8763ac — scanner integration and request template fixes
|
||||
### 2026-06-03 — backend@91877ae — points lookups accept legacy user ids (v2.8.63)
|
||||
|
||||
**Commits:** backend `a76b07b`, `fdf63d2`; frontend `d8763ac`, `42fe05e`
|
||||
**Touched:** Scanner balance watch callbacks, direct-balance ERC-20 pay-in rail (Phase 1+2), request-template validation errors, maxUsage optional field
|
||||
**Why:** Payment infrastructure: wire scanner balance watch callbacks for real-time balance monitoring, add direct-balance ERC-20 pay-in rail with Phase 1 and 2 implementation. Request templates: surface validation errors in expanded sections and list view, ensure maxUsage is truly optional to fix template creation issues.
|
||||
**Verification:** Backend payment tests and typecheck passing, scanner integration tests passing.
|
||||
**Linked docs updated:** [[Scanner Architecture]], [[Payment Flow - Scanner]], [[Scanner API]]
|
||||
**Why:** Mini App points view (v2.8.64) failed: every user lookup in DrizzlePointsRepo compared
|
||||
users.id (uuid) against the JWT legacy 24-hex id → uuid cast error → 500. All lookups now match
|
||||
either format (id::text = $n OR legacy_object_id = $n).
|
||||
**Verification:** tsc clean, tests green. Test after deploy: Mini App → حساب → امتیازات shows data.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-02 — backend@6724177, frontend@714dfbd — database and request handling improvements
|
||||
### 2026-06-03 — frontend@HEAD — remove MainButton, in-tab CTA, sign-in retry (v2.8.65)
|
||||
|
||||
**Commits:** backend `6724177`, `f588d52`, `20435d1`, `8dead04`, `795f161`; frontend `714dfbd`, `6fe1328`, `d7a2a86`
|
||||
**Touched:** Fresh-DB Postgres migrate + seed path, 0013/0014 migrations validation, seeds Postgres-capable, addresses table migration, address_type enum, admin user management, purchase request id/_id tolerance
|
||||
**Why:** Database: make fresh-DB Postgres migrate and seed path correct for pg-only mode, validate 0013/0014 migrations for fresh drizzle-kit migrate, create addresses table migration and address_type enum, re-key address store by user_id. Requests: tolerate both id and _id in purchase request responses for Mongo-PG compatibility. Admin: make admin user management work end-to-end, unblock user creation and purchase requests for native-PG users.
|
||||
**Verification:** Backend database migration tests passing, seed scripts working in pg-only mode.
|
||||
**Linked docs updated:** [[Database Strategy - Mongo vs Postgres Assessment]], [[Postgres Runtime Cutover Status]], [[Address]]
|
||||
**Why (user requests):** (1) native Telegram MainButton removed — wrong font, duplicated CTAs;
|
||||
«+ درخواست جدید» button added inside the requests tab header instead. (2) Intermittent 503 on
|
||||
Telegram sign-in (backend redeploy windows) — sign-in now retries transient 502/503/504 3x with
|
||||
backoff and shows the Persian message.
|
||||
**Queued (user, with screenshots):** cart −=remove at qty 1; seller-shop card redesign (+ icon,
|
||||
template detail view, search/filter/sort); requests search + status-chip filters + sort; shop
|
||||
search; seller chat button; referral signup points; dark mode.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-02 — backend@515bea3, frontend@424ce1f — MongoDB removal and CI/CD updates
|
||||
### 2026-06-03 — frontend v2.8.66–67 — seller chat, header bell, cart UX + review system audit
|
||||
|
||||
**Commits:** backend `515bea3`, `4949988`; frontend `424ce1f`, `e33444f`
|
||||
**Touched:** dataCleanupService guard against MONGO_CONNECT_MODE=never, route admin user counts through postgres-capable stores
|
||||
**Why:** MongoDB removal: guard dataCleanupService against MONGO_CONNECT_MODE=never to prevent Mongo connections when disabled, route admin user dependency counts through postgres-capable repository stores instead of direct Mongo model access. CI/CD: push to main builds dev-* image and deploys to Arcane dev env, restrict dev pipeline to manual trigger only.
|
||||
**Verification:** Backend typecheck and repository tests passing, Mongo connectivity guards verified.
|
||||
**Linked docs updated:** [[MongoDB Removal Handoff]], [[Postgres Runtime Cutover Status]], [[Backend Core Stack Decision Record - 2026-05-24]]
|
||||
**v2.8.66:** seller rows in the Mini App shop get a chat icon (direct conversation with the
|
||||
seller); header gets a notification bell opening the in-shell notifications list.
|
||||
**v2.8.67 fixes:** seller chat gave "Validation failed" (backend expects ONE participant for
|
||||
direct chats — current user auto-added; we sent two). Cart «−» at qty 1 now removes the item
|
||||
(separate remove button dropped). Bell: borderless + red dot instead of count.
|
||||
**Review/rating system audit completed (agent):** backend schema/API/store fully working
|
||||
(reviews table, /api/marketplace/reviews). Broken: seller rating HARDCODED 4.5 in
|
||||
DrizzleMarketplaceRepo.findSellersWithActiveRequestTemplates (never reads real reviews);
|
||||
review form never sends purchaseRequestId (verified-buyer badge never set); Step 6
|
||||
post-delivery has NO review prompt; Mini App has no reviews. Full phased plan in agent output —
|
||||
next work items.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend@80a1e52, frontend@b2d8a32 — integration and payment features
|
||||
### 2026-06-03 — backend v2.8.64–65 — real seller ratings + chat participant names
|
||||
|
||||
**Commits:** backend `80a1e52`, `dc6a35f`, `0a0d489`, `e8e8bc9`, `126c222`; frontend `b2d8a32`, `3c9459f`, `51157b0`, `d8763ac`
|
||||
**Touched:** Merge integrate-main-into-development, correct duplicate version field, telegram user persistence on sign-in, marketplace template creation CHECK constraints, chat support welcome message, in-shell checkout, digital items skip address, stepper fixes
|
||||
**Why:** Integration: merge main into development branch, correct duplicate version field and bump to v2.8.69. Authentication: persist telegram user updates on sign-in. Marketplace: fix template creation 500 errors from blank optional fields tripping CHECK constraints. Chat: attribute support welcome message to support user. Telegram: in-shell checkout without web hand-off, digital items skip address step, checkout stepper improvements.
|
||||
**Verification:** Backend and frontend builds passing, integration tests clean.
|
||||
**Linked docs updated:** [[Integration]], [[Marketplace API]], [[Telegram Mini App]]
|
||||
**v2.8.64:** seller list ratings now aggregated from published reviews (was hardcoded 4.5);
|
||||
exposes rating/ratingCount per seller.
|
||||
**v2.8.65:** PG chats showed "Unknown User" — participants (JSONB ids) are now populated with
|
||||
firstName/lastName/email/avatar from the users table (uuid OR legacy id matched), in
|
||||
findByIdWithParticipants and findForUser. Parity with Mongo .populate().
|
||||
**In progress:** background agent fixing seeds for the PG database + creating complete mock shops
|
||||
(categories in PG → fixes template-creation 500) — will be reviewed and shipped when it reports.
|
||||
**Queued (user):** in-shell تنظیمات عمومی (profile editing) in Mini App; post-delivery review
|
||||
prompt; template detail view + search/filter/sort in shop and requests; dark mode.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend v2.8.65–66 — PG seeds, mock shops, template-creation fix
|
||||
|
||||
**Template-creation 500 root cause (agent):** categories table column conflict (Drizzle "order"
|
||||
vs categoryStore order_num) + random legacy ids per seed → category ids served by the API were
|
||||
unresolvable by createRequestTemplate. Fixed: categoryStore aligned to canonical "order" column
|
||||
(with one-time order_num migration), deterministic category legacy ids, hardened categories
|
||||
fallback (id::text match). Also fixed: the v2.8.64 rating aggregation queried Drizzle reviews
|
||||
columns but the physical table (written by reviewStore) has different columns — rewritten
|
||||
against the real columns (seller_user_id).
|
||||
**New seed:** src/seeds/seedMockShops.ts — Persian categories, 5 sellers + shop settings,
|
||||
templates with picsum product images, published reviews. npm scripts fixed (paths) + added
|
||||
seed:levels / seed:templates / seed:mock-shops.
|
||||
**⚠️ DEV ENV REQUIREMENT:** CATEGORY_STORE=postgres (and ideally REVIEW_STORE /
|
||||
SHOP_SETTINGS_STORE = postgres) must be set in dev .env.local when REPO_MARKETPLACE=postgres —
|
||||
docker-compose.dev.yml still defaults these to mongo. Without this, template creation keeps
|
||||
failing.
|
||||
**Run on dev after deploy:** docker exec <backend-container> npm run seed:categories && npm run
|
||||
seed:mock-shops
|
||||
**Verification:** tsc clean, 14 tests green (3 suites).
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.68 — product-style template cards + in-shell template detail
|
||||
|
||||
**Why (user reference design):** seller-shop template cards redesigned image-first (photo +
|
||||
circular add-to-cart overlay + title/price); tapping opens a new in-shell template detail view
|
||||
(image gallery with thumbnails/counter, product-type chip, price, description, specs, tags,
|
||||
sticky add-to-cart/order). New view: telegram-template-detail-view.tsx; shell openTemplate
|
||||
overlay.
|
||||
**Dev env (user applied):** CATEGORY_STORE/REVIEW_STORE/SHOP_SETTINGS_STORE=postgres set;
|
||||
backend v2.8.66 restarted healthy; 24 categories auto-seeded into PG. seed:mock-shops still to
|
||||
be run by the user for demo data.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend v2.8.67 — auto-seed mock shops on startup
|
||||
|
||||
**Feature:** new env flag `SEED_MOCK_SHOPS_ON_START=true` runs seedMockShops() during backend
|
||||
boot (after migrations/levels). Idempotent — existing rows are skipped — so the flag can stay
|
||||
on permanently in dev.
|
||||
**Action for dev env:** add `SEED_MOCK_SHOPS_ON_START=true` next to the other store flags and
|
||||
restart the backend container; demo shops/templates/reviews appear automatically.
|
||||
**Verification:** tsc clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.69 — web-app-parity shop cards + template detail gallery
|
||||
|
||||
**Per user screenshots:** (1) shop cards — circular «+» add-to-cart moved OFF the photo, now
|
||||
sits beside the title/price row; (2) template detail — web-style carousel: rounded image,
|
||||
‹ n/m › arrow/counter pill, touch-swipe navigation, centered thumbnails; (3) removed «سفارش این
|
||||
قالب» everywhere (product logic: buyer either creates a request or buys via cart) — add-to-cart
|
||||
is the single full-width CTA in the detail sticky bar.
|
||||
Files: telegram-seller-shop-view.tsx, telegram-template-detail-view.tsx.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.70 — in-shell settings + addresses, central theme + dark mode
|
||||
|
||||
**In-shell account screens (were opening the web dashboard):**
|
||||
- تنظیمات عمومی → telegram-settings-view.tsx: name/email/bio edit form via
|
||||
updateUserProfile; session refresh on save.
|
||||
- آدرسهای تحویل → telegram-addresses-view.tsx: full address CRUD (list / add /
|
||||
edit / delete / set-primary) on the addresses API + use-telegram-addresses SWR hook.
|
||||
- Account rows switched from href→onClick; new overlay states 'settings'/'addresses'
|
||||
in the shell. (Passkey stays web — WebAuthn limitation.)
|
||||
|
||||
**Theme overhaul — Mini App now uses the CENTRAL web-app theme:**
|
||||
- constants.ts: TG_PALETTE_LIGHT / TG_PALETTE_DARK derived from
|
||||
src/theme/theme-config.ts (primary green, grey ramp, status colors) instead of
|
||||
the ad-hoc cream/saffron palette; font stack aligned with AMANEH_FONT
|
||||
(IBM Plex Sans / Vazirmatn).
|
||||
- telegram-shell-css.ts: emits both palettes; `.tg-shell--dark` flips every token —
|
||||
components keep the same CSS-var names.
|
||||
- use-telegram-theme hook: auto (follow Telegram colorScheme) | light | dark,
|
||||
persisted in localStorage; syncs Telegram native header/background colors.
|
||||
- Theme toggle row (auto/light/dark) added to the account tab.
|
||||
- Hero headers (account / seller-shop / welcome banner / unlinked) use new
|
||||
--header-bg/--header-fg/--header-fg-muted vars so they stay legible in both modes.
|
||||
- Removed the inline themeParams background/text override in getTelegramShellStyle
|
||||
that forced a navy shell under light components on dark-mode Telegram clients
|
||||
(the bad bg/font the user reported).
|
||||
**Verification:** tsc + eslint clean (one pre-existing no-constant-condition
|
||||
warning in telegram-webapp.ts, untouched).
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.71 — Mini App: solar icons, avatar upload, achievements, theme-toggle fix
|
||||
|
||||
**Icons:** telegram-icons.tsx redrawn to mirror the dashboard's Solar "linear"
|
||||
set (home-2 / document-add / chat-round-dots / user / shop-2 / wallet-money /
|
||||
rounded arrows). Added sun, moon, themeAuto, camera, trophy, lock.
|
||||
**Theme toggle now icon-based** (sun=light, moon=dark, half-disc=auto) instead
|
||||
of text labels — per user request.
|
||||
**BUGFIX — theme toggle didn't respond:** TelegramListRow rendered a *disabled*
|
||||
<button> wrapper whenever it had no onClick; a disabled button swallows clicks
|
||||
from nested children, so the language AND theme toggles (rendered inside the
|
||||
row's `value` slot) never received taps. Non-interactive rows (no onClick, no
|
||||
href) now render a <div>.
|
||||
**Avatar upload in settings:** telegram-settings-view gained a tappable avatar
|
||||
with a camera badge → file picker → uploadUserAvatar + updateUserProfile
|
||||
(persists immediately + session refresh); 3 MB / JPG-PNG-GIF validation; relative
|
||||
/uploads paths resolved to full backend URL.
|
||||
**Achievements in points view:** milestone badges (joined / verify email / first
|
||||
referral / 5 active referrals / 1000 points) derived client-side from the user's
|
||||
points + referral + isEmailVerified data; unlocked (trophy + "earned") vs locked
|
||||
(lock + "+N" reward) with an N/total counter. Display-layer gamification over the
|
||||
existing server-side point grants.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.72 — Mini App fixes (avatar/email/web-links) + search/sort
|
||||
|
||||
**Bug fixes (user testing on v2.8.71):**
|
||||
- **Avatar broken in account header** — it rendered the raw relative `/uploads`
|
||||
path against the frontend origin (404 inside the Telegram WebView). New shared
|
||||
`resolveTelegramAvatar` (sections/telegram/avatar-url.ts) rebases avatars onto
|
||||
the backend origin; used in both the account header and the settings uploader.
|
||||
- **Inline email verification in settings** — changing the email now surfaces a
|
||||
code field + verify/resend buttons (resendProfileEmailVerification +
|
||||
verifyProfileEmail) whenever the saved email is unverified, instead of forcing
|
||||
the user back to the account-tab badge.
|
||||
- **Passkey/Wallet rows broke the Mini App** — `href` replaced the whole WebView
|
||||
with the web dashboard and the Telegram back button couldn't return. They now
|
||||
open in Telegram's in-app browser via `webApp.openLink`
|
||||
(openTelegramExternalLink in telegram-webapp.ts), keeping the Mini App mounted.
|
||||
|
||||
**Feature — search / filter / sort (queued item):**
|
||||
- Requests: search + status filter chips (all / active / completed / cancelled)
|
||||
+ sort (newest / highest budget).
|
||||
- Shop sellers: search + sort (top rated / most templates / newest).
|
||||
- Seller-shop templates: search + sort (newest / cheapest / priciest), shown
|
||||
when the shop has > 3 templates.
|
||||
- New reusable `TelegramListControls` component + `controls` locale section.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend v2.8.68 + frontend v2.8.73 — pending-email flow, referral signup bonus, Mini App cleanups
|
||||
|
||||
**Backend v2.8.68 — email change now requires verification before it applies:**
|
||||
- New `pendingEmail` field. Changing email parks the new address in pendingEmail
|
||||
(NOT email) and sends a code to the NEW address; the account email + its
|
||||
verified status stay intact until the code is confirmed, then pendingEmail is
|
||||
promoted to email (with a re-check nobody else claimed it). Resend targets the
|
||||
pending address. Implemented in the Mongoose User model AND the Postgres
|
||||
authStore path (PgAuthUser field + constructor + toObject + savePgUser
|
||||
INSERT/ON CONFLICT params + mirrorUserToMongo + `pending_email` column via
|
||||
idempotent ALTER in postgresAuthSchema). userController.updateUserProfile /
|
||||
resendCurrentUserEmailVerification / verifyCurrentUserEmail updated.
|
||||
- **Referral signup bonus:** referrer gets +50 points the moment someone signs
|
||||
up with their code (email + Google paths) via PointsService.addPoints (source
|
||||
'referral'), referralStats.totalEarned kept in step, 'referral-reward' socket
|
||||
event. Best-effort, never blocks signup. Separate from the 2% purchase commission.
|
||||
|
||||
**Frontend v2.8.73 — Mini App:**
|
||||
- Removed the «وضعیتهای امانت» escrow-states legend from Home.
|
||||
- Removed the Passkey row from the account tab (not wanted in the Mini App).
|
||||
- Welcome-banner greeting uses the AMN account name before the Telegram profile
|
||||
name, so an in-app rename updates it (was stuck on Telegram's "Anonymous …").
|
||||
- Settings email verify aligned with the pending-email flow: inline code panel
|
||||
shows whenever a change is pending or the email is unverified; clears on verify.
|
||||
**Verification:** backend tsc clean; frontend tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.74 — Mini App chat alignment fix + read-only email field
|
||||
|
||||
- **Chat both-sides bug:** in the Mini App chat every bubble rendered on the
|
||||
same side in one colour. Root cause: `isMine = senderId === selfId` — when
|
||||
`selfId` was undefined it matched system/welcome messages (no senderId), so
|
||||
all bubbles looked "mine". Fixed: ids normalised (string | populated object),
|
||||
a match now requires BOTH ids non-empty, and `selfId` falls back from `_id`
|
||||
to `id`. Own = right/primary green, support/system = left/neutral grey.
|
||||
(telegram-chat-bubble.tsx + selfId hardening in telegram-mini-app-view.tsx.)
|
||||
- **Read-only email:** the email field is now disabled by default with an
|
||||
explicit «تغییر ایمیل» toggle, in BOTH the Mini App settings and the web
|
||||
account-general form. Together with the backend pending-email flow (v2.8.68),
|
||||
email can only change after the code sent to the NEW address is verified —
|
||||
no silent change by typing + save.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.75 — self-contained email-change flow (visible code entry)
|
||||
|
||||
After enabling «تغییر ایمیل» there was no obvious place to send/enter the code.
|
||||
Reworked into a dedicated flow on both surfaces:
|
||||
- Mini App settings: editing the email reveals a «ارسال کد تأیید» button beside
|
||||
it; pressing it parks the new address (pending) + emails the code and
|
||||
immediately reveals the code-entry panel. Email is no longer bundled into the
|
||||
name/bio Save (handleRequestEmailChange).
|
||||
- Web account-general: submitting an email change reveals the verification Alert
|
||||
(code field + verify/resend) even though the account email stays on the old
|
||||
verified address until the code is confirmed (pendingEmailVerify state).
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.76 — email send-code always reveals the verify panel
|
||||
|
||||
Bugfix: «ارسال کد تأیید» was disabled when the email was unchanged and
|
||||
handleRequestEmailChange early-returned silently for the same address — so after
|
||||
"sending" nothing appeared. Now the button is enabled whenever an email is
|
||||
present; a changed email parks it as pending + codes the new inbox, an unchanged
|
||||
email (re)sends a code to verify the current address. Either way pendingVerify
|
||||
flips on and the inline code-entry panel renders.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.77 — keep email verify panel mounted after sending code
|
||||
|
||||
Bugfix: pressing «ارسال کد تأیید» reverted the settings view to its initial
|
||||
(non-edit, no-panel) state — the code field never appeared. Root cause:
|
||||
handleRequestEmailChange awaited onSaved (checkUserSession), which sets the auth
|
||||
provider's `loading` flag true; the Mini App shell gates the entire linked block
|
||||
on `!loading`, so it unmounted + remounted the settings view, wiping its local
|
||||
pendingVerify/emailEditable state. Removed the session refresh from the send-code
|
||||
path (the email is only pending then — nothing to refresh until verified;
|
||||
handleVerify still refreshes after a successful confirm).
|
||||
**Verification:** tsc clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend v2.8.69 + frontend v2.8.78 — chat system messages + post-delivery review
|
||||
|
||||
**Chat both-sides (round 2 — real root cause):** support welcome/greeting and
|
||||
group "chat created" messages are authored on the backend with
|
||||
`chat.metadata.createdBy` (the buyer) as senderId, so they rendered as the
|
||||
buyer's own. Backend (v2.8.69): `sendSystemMessage` now takes an optional
|
||||
senderId and createSupportChat passes the support user's id. Frontend (v2.8.78):
|
||||
any `messageType:'system'` message renders as a neutral centred note regardless
|
||||
of sender — robust fix for all system messages.
|
||||
**Post-delivery seller review (frontend v2.8.78):** buyer viewing a
|
||||
delivery/delivered/seller_paid/completed request sees a star + comment prompt
|
||||
(TelegramReviewPrompt). Seller resolved from the request's selected offer
|
||||
(getOffers → selectedOfferId → sellerId); submits createReview({subjectType:
|
||||
'seller', subjectId, rating, comment, purchaseRequestId}); 409 → "already
|
||||
reviewed" handled gracefully.
|
||||
**Verification:** backend + frontend tsc clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend v2.8.70 — persist telegram user updates on sign-in (new-user login)
|
||||
|
||||
Investigating "new Telegram account / new device can't log in (existing works)".
|
||||
telegramAuth mutates the user after the TelegramLink lookup (telegramVerified
|
||||
flip, lazily backfilled referralCode, lastLoginAt) but only NEW users hit
|
||||
user.save(); existing/half-created users were never re-persisted → a backfilled
|
||||
referralCode never landed and subsequent sign-ins kept tripping the non-sparse
|
||||
users.referralCode index. Added a best-effort user.save() before linking.
|
||||
**Note:** background logs in the user's screenshots show "Backend 2.6.49" — the
|
||||
deployed dev backend may be far behind (none of the 2.8.x auth/email/chat fixes
|
||||
live). Asked the user to confirm/redeploy the backend and share the exact error
|
||||
the new device shows (frontend surfaces backend message verbatim).
|
||||
**Verification:** backend tsc clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — backend v2.8.71 — fix template-creation 500 (blank optional fields vs CHECK constraints)
|
||||
|
||||
Seller "create request template" returned HTTP 500 whenever optional fields were
|
||||
left empty. The request_templates table has CHECK constraints that allow only
|
||||
NULL or a valid pattern — product_link (`~ '^https?://.+'`) and delivery_email
|
||||
(email regex) — and the nullable service_session_type enum rejects ''. Both
|
||||
DrizzleMarketplaceRepo.createRequestTemplate and updateRequestTemplate passed the
|
||||
form's empty strings straight into the INSERT/UPDATE, tripping the constraints.
|
||||
Added a `blank()` coercion: blank optional strings → NULL/undefined for
|
||||
product_link, delivery_email, size/color/brand, service_location/notes,
|
||||
proposal_title/description, service_session_type; enum columns (product_type,
|
||||
delivery_type, urgency, budget_currency, metadata_source) fall back to their
|
||||
defaults instead of receiving ''.
|
||||
(Confirmed deployed backend is now on the 2.8.x line — logs show v2.8.70.)
|
||||
**Verification:** backend tsc clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-03 — frontend v2.8.79 — request-template maxUsage truly optional
|
||||
|
||||
The «حداکثر تعداد استفاده (اختیاری)» field threw a ZodError ("Invalid input") and
|
||||
blocked the template form submit. It was wrapped in schemaHelper.nullableInput —
|
||||
a helper for REQUIRED fields that adds an issue on null/undefined — so an empty
|
||||
optional value failed. Replaced with zod.preprocess (blank/null/NaN → undefined,
|
||||
validate int ≥ 1 only when present); default null → undefined. Together with
|
||||
backend v2.8.71 (blank → NULL for product_link/delivery_email/enums), optional
|
||||
template fields can now be left empty end-to-end.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend v2.8.72→v2.8.73 (chat participant fix reverted) + frontend v2.8.80 (template validation visibility)
|
||||
|
||||
**Backend chat — attempted + reverted.** v2.8.72 tried to canonicalize chat
|
||||
participant ids (userRepo.findById) so a buyer (marketplace uuid for the seller)
|
||||
and seller (legacy ObjectId session) would share one direct thread. It backfired:
|
||||
findById returns an id format (Mongo ObjectId / PG uuid) that no longer matches
|
||||
the session JWT id, so the membership check rejected everyone ("User is not a
|
||||
participant"). v2.8.73 reverts to storing ids as passed (restores chat access).
|
||||
**Open issue:** buyer↔seller messages still don't cross — they land in separate
|
||||
direct conversations because the seller is referenced by a uuid while the session
|
||||
id is a legacy ObjectId (users in Mongo, chats in Postgres). Needs a correct
|
||||
uuid→legacyObjectId resolution (via marketplace id_map), not the session-breaking
|
||||
findById path. Deferred for a careful fix.
|
||||
**Frontend v2.8.80:** template-form onInvalid now expands all collapsed accordion
|
||||
sections and toasts the actual nested error messages, so validation errors are
|
||||
visible under their fields instead of only in the console.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend v2.8.74 — chat: map uuid participants → session ObjectId (buyer↔seller fix)
|
||||
|
||||
Root cause confirmed from logs: a buyer opens a chat "with seller X" using X's
|
||||
marketplace **Postgres uuid** (669c0dac-…), but X's session/JWT id is their
|
||||
**legacy ObjectId** (e0527…). Conversations key participants by the session id,
|
||||
so buyer and seller landed in two separate direct threads (each saw only their
|
||||
own messages). ChatService.createChat now normalises any uuid participant (and
|
||||
createdBy) → users.legacy_object_id via a direct PG lookup BEFORE dedup/create —
|
||||
resolving TOWARD the session-id format (the reverted v2.8.72 resolved toward uuid
|
||||
and broke membership). Both sides now converge on one conversation + socket room.
|
||||
Note: pre-existing mismatched conversations are orphaned; a freshly opened chat
|
||||
dedups correctly for both parties.
|
||||
**Verification:** backend tsc clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend v2.8.81 — in-shell checkout (Mini App no longer hands off to web)
|
||||
|
||||
Cart «ادامه و پرداخت» previously navigated the Telegram WebView to the web
|
||||
checkout (taking over the Mini App). New TelegramCheckoutView keeps it in-shell:
|
||||
order summary + delivery-address picker (reuses saved addresses; "add address"
|
||||
opens the in-shell addresses manager) → places the order via
|
||||
convertTemplatesToRequests({paymentConfirmed:false}) so requests are created in
|
||||
`pending_payment` (escrow model — NO on-chain/web3 transaction needed inside the
|
||||
Mini App). Cart cleared on success and the shell jumps to Requests, where the
|
||||
buyer completes the escrow payment. New 'checkout' shell overlay + cart `clear()`
|
||||
on the cart hook. Verified the deferred path exists (status 'pending_payment'
|
||||
when paymentConfirmed is false) rather than requiring batch-convert's
|
||||
paymentConfirmed:true on-chain flow.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend v2.8.82 — checkout: digital skips address; fix buyer pending_payment stepper
|
||||
|
||||
- In-shell checkout: only PHYSICAL products require a delivery address; digital
|
||||
products / services / consultations are delivered online (email/link) so the
|
||||
address picker is hidden. Cart items now carry productType; needsAddress is
|
||||
derived from it (deliveryType fallback for older items).
|
||||
- Buyer stepper fix (request-config determineBuyerStep): pending_payment /
|
||||
payment / payment_pending were grouped with the PAID states and returned step
|
||||
4 ("awaiting shipment"), so a just-placed unpaid order looked like the payment
|
||||
step was skipped. Those un-paid statuses now return step 3 ("select & pay"),
|
||||
so the request correctly shows the payment step as current.
|
||||
**Open:** the actual on-chain escrow payment step inside the Mini App still needs
|
||||
a wallet rail (TON Connect / WalletConnect) — order placement is in-shell, the
|
||||
pay action is the remaining web3 piece.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend v2.8.83 — Mini App pay CTA (wallet + manual deposit) + stepper line fix
|
||||
|
||||
- Request detail: buyers on a payable status (pending_payment/payment) get a
|
||||
«پرداخت با کیف پول» button that opens the web payment page for that request in
|
||||
Telegram's in-app browser (openTelegramExternalLink). That page already offers
|
||||
BOTH WalletConnect/wagmi AND the in-house manual deposit-address (backend
|
||||
auto-detects the on-chain transfer via request-network webhook), so the user
|
||||
pays without the Mini App being torn down. The generic "open in web" link also
|
||||
uses the in-app browser now.
|
||||
- Stepper green line "broken" fix: the two half-connectors per cell left a seam,
|
||||
and the active dot's saffron halo overlapped the line. Replaced with one
|
||||
continuous connector per gap (green once the step is reached) and a solid
|
||||
paper ring on the active dot — the progress line is now continuous.
|
||||
**Note:** this reuses the existing web WalletConnect + deposit flow via the
|
||||
in-app browser; a fully native in-shell wallet/deposit screen remains a larger
|
||||
follow-up.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend v2.8.84 — archive conversations from the chat list (DEPLOY BRANCH NOW main)
|
||||
|
||||
⚠️ Deploy branch changed: pushes now go to `main` (CI: "push to main builds dev-*
|
||||
image and deploys to Arcane dev env"). integrate-main-into-development is retired.
|
||||
- Chat list: each row gained an archive button → confirm sheet → PATCH
|
||||
/chat/:id/archive (archiveConversation) + list refresh. Archived chats are
|
||||
excluded from the default getUserChats query, so the row disappears. Row
|
||||
restructured from a single <button> into a div wrapping the open-button + a
|
||||
sibling archive button (nested buttons are invalid HTML). New archive icon +
|
||||
chat archive locale strings.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend v2.8.75 + frontend v2.8.85/86 — chat search + archive view, template→checkout fix
|
||||
|
||||
**backend v2.8.75:** archiveChat is now two-way — PATCH /chat/:id/archive accepts
|
||||
`{archived}` (default true); archived:false restores. Powers unarchive.
|
||||
**frontend v2.8.85:** template detail — once an item is in the cart the sticky bar
|
||||
shows «حذف از سبد» + a primary «مشاهدهٔ سبد و پرداخت» (onOpenCart) so the buyer
|
||||
can actually reach checkout (previously the single button just toggled).
|
||||
**frontend v2.8.86:** chat list gained a search box (counterpart name + last
|
||||
message) and a «گفتگوهای آرشیوشده» row → TelegramArchivedChatsView (lazy
|
||||
isArchived=true fetch; per-row unarchive via archiveConversation(id,false)).
|
||||
getConversations/archiveConversation actions + useTelegramConversations(archived)
|
||||
updated.
|
||||
All on the new `main` deploy branch. **Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend v2.8.76 + frontend v2.8.87 — fix archive filter (boolean) + archived-view search
|
||||
|
||||
**Root cause of "archived chats not removed from main + archived view shows
|
||||
everything":** DrizzleChatRepo.matchesCondition routed BOOLEAN query conditions
|
||||
through idEquals, and asStringId() coerces both true and false to '' — so
|
||||
idEquals(false,true) === true, making the `settings.isArchived` filter match
|
||||
every chat. Booleans now compare strictly (Boolean(value) === condition); a
|
||||
missing value counts as false. (Also corrects participants.isActive matching.)
|
||||
**frontend v2.8.87:** the archived chats view gained the same search box as the
|
||||
main list (+ no-results state). Unarchive already worked (archiveConversation
|
||||
(id,false)).
|
||||
**Verification:** backend + frontend tsc/eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend v2.8.88 — in-shell direct-transfer payment (no wallet connection)
|
||||
|
||||
Buyers can now pay a pending_payment request entirely inside the Mini App, with
|
||||
NO web3 wallet connection — mirroring the web «انتقال مستقیم» rail.
|
||||
TelegramPaymentView: resolves the selected offer (seller + amount) →
|
||||
getPaymentOptions for chain/token → createDirectBalanceIntent (provider
|
||||
amn.scanner, rail direct_balance, mode check) → shows a copyable derived deposit
|
||||
address + exact amount + a strong "send only this token on this network" warning
|
||||
→ «پرداخت کردم» calls checkDirectBalancePayment.
|
||||
Edge cases (all surfaced): OVERPAYMENT accepted (backend delta>=expected →
|
||||
paid); UNDERPAYMENT shows "received X of Y, send the rest" (paidDelta vs
|
||||
expectedAmount); WRONG TOKEN — the check flow can't detect a foreign token, so a
|
||||
prominent on-screen warning is shown (backend webhook rejects token/chain
|
||||
mismatch). Wired from the request-detail pay CTA via a new openPaymentRequestId
|
||||
shell overlay + BackButton. (Replaces the earlier "open web payment" hand-off.)
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend v2.8.89 — Mini App checkout flows straight into payment (shop model)
|
||||
|
||||
Per the shop model (pay as part of ordering, not place-then-pay-later), the
|
||||
in-shell checkout now, after «ادامه و پرداخت», creates the request(s) (template
|
||||
conversion already mints + accepts an offer and sets selectedOfferId, status
|
||||
pending_payment) and for a SINGLE-request order jumps directly into the in-shell
|
||||
direct-transfer payment step (openPaymentRequestId) rather than dropping the
|
||||
buyer on the Requests list. Multi-request orders still go to Requests. Checkout
|
||||
reads the created request id from batch-convert's data[]; button relabeled to
|
||||
«ادامه و پرداخت». (The normal request→offers→pay escrow flow is unchanged.)
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend v2.8.90 — checkout stepper + paid-step advance fix
|
||||
|
||||
- In-shell checkout shows a 3-step progress header (cart → address → payment),
|
||||
reusing TelegramRequestStepper (new optional `title` prop): cart step 1,
|
||||
checkout step 2, payment step 3 (only when reached from checkout via the new
|
||||
`checkoutFlow` prop + `paymentCheckoutFlow` shell state). BackButton steps
|
||||
checkout→cart and checkout-payment→cart.
|
||||
- Stepper fix: a PAID request (status 'payment' = escrow funded — confirmed
|
||||
working end-to-end via the AMN scanner: `[direct-balance] check paid … delta`)
|
||||
was stuck on step 3 because 'payment' was grouped with the unpaid statuses.
|
||||
determineBuyerStep now maps 'payment' → 4 (awaiting shipment), 'confirming' →
|
||||
5; only pending_payment/payment_pending stay on 3. Request-detail «پرداخت»
|
||||
CTA hidden once status is 'payment'.
|
||||
**Note:** in-shell direct-transfer payment is confirmed working — a real 0.01
|
||||
USDC BSC transfer to the derived address was auto-detected and advanced the
|
||||
request to 'payment'.
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — frontend v2.8.91 — seller stepper: paid/shop request shows "ship goods"
|
||||
|
||||
Buyer stepper confirmed correct (paid shop request → step 4 «انتظار ارسال»).
|
||||
Seller side was wrong: a paid shop (template) purchase showed seller step 1
|
||||
(«ارسال پیشنهاد») because determineSellerStep gated the step on an
|
||||
accepted/hasOffer context the seller view couldn't reliably compute (id seam).
|
||||
Since a funded escrow implies an accepted offer for that seller, post-payment
|
||||
seller steps are now FIXED/unconditional: pending_payment/payment_pending → 2
|
||||
(awaiting payment), payment/processing/paid → 3 (ship goods), delivery → 4,
|
||||
delivered/confirming → 5, seller_paid/completed → 6. Removed the unreliable
|
||||
`accepted` flag. (Pre-payment states keep the hasOffer 1/2 logic.)
|
||||
**Verification:** tsc + eslint clean.
|
||||
|
||||
---
|
||||
|
||||
### 2026-06-04 — backend v2.8.77 — seller can't ship: delivery 403 (uuid↔ObjectId seam)
|
||||
|
||||
The selected seller got HTTP 403 on PUT /marketplace/purchase-requests/:id/delivery
|
||||
(«تأیید ارسال کالا») for a paid shop request. The auth compared the seller's
|
||||
session id (legacy ObjectId e0527…) against the selected offer's sellerId, which a
|
||||
TEMPLATE-created offer stores as a PG uuid (669c0dac…) → never equal → 403. Added a
|
||||
`sameUser(a,b)` helper that resolves both ids via the user repo (accepts either
|
||||
format) and compares every id form (_id/id/pgId/legacyObjectId). Applied to
|
||||
updateDeliveryInfo, verifyDeliveryCode, and the delivery-code-status buyer/seller
|
||||
gate. (Buyer step labels already matched the web — no change.)
|
||||
**Verification:** backend tsc clean.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user