67 KiB
Backlog — every open item, triaged
Last verified: 2026-08-02 against commit
b876490.
Reconciled from five ledgers — hardening/issues.md (18 items), for-backend.md (67 REQs), 53 report "Follow-ups" sections, 22 backend handoff files, two manual-testing iterations — plus product/notes/open-questions.md, root CLAUDE.md §6, phase 2's drift list, and phase 3's 283 flow gaps (the primary, freshest, code-verified input; see docs/flows/index.md). ~700 raw candidate rows were harvested; this file is the deduped, triaged result. Old ids stay greppable in their source files — nothing was rewritten.
Area: client · server · contract · ops · product · docs. Sev: blocker (product is wrong
or unusable) · major · minor · deferred. Status: open · in-progress · blocked (on what) ·
deferred (trigger). Blocks: the docs/flows/ journey(s) it degrades.
Closed items (with what closed them) are in backlog-closed.md. The distilled decision log is in decisions.md. The business-area overlay is in implemented.md.
Blockers (18)
| ID | Area | Item | Origin | Status | Blocks |
|---|---|---|---|---|---|
| BL-001 | server | Admin RBAC is structurally dead: DynamicPermissionService.CanAccess grants only the literal role admin; every seeded admin holds super_admin/finance and 403s on all DynamicPermission-gated controllers. No code path ever writes a DynamicPermission claim. |
H-04, H-05, admin-backoffice gap-1/3/12, bnpl gap-7, cancellation-and-refunds gap-9, messaging-tickets gap-1/3, nurse-verification gap-3, nurse-earnings-and-payouts gap-7, partner-center gap-8, reviews gap-1/2, booking-request gap-3, booking-lifecycle-evv gap-3/5, nurse-catalog-and-pricing gap-2 | open | admin-backoffice, bnpl-installments, cancellation-and-refunds, messaging-tickets, nurse-verification, nurse-earnings-and-payouts, partner-center, reviews, booking-request, booking-lifecycle-evv, nurse-catalog-and-pricing |
| BL-002 | ops | No seeded account holds the literal admin role (Seed:AdminUsername/Password unset, SeedDataBase.cs returns early) — even a fixed BL-001 is untestable out of the box. |
admin-backoffice gap-2 | open | admin-backoffice |
| BL-003 | ops | Committed live credentials (DB sa, JWE/field-encryption key halves, Kavenegar, Neshan, Finnotech, Telegram bot token) must be rotated and moved out of git before onboarding real users. |
CLAUDE.md §6, refinement-phase-0 follow-up 3 | open | — (pre-launch) |
| BL-004 | ops | GET /api/v1/dev/last_otp/{phone} is anonymous and Development-only by design, but the deployment runs as Development — live on api.balinyaar.ir, lets anyone who knows a phone read its login code. |
auth-login-otp gap-9, public-front-door gap-10 | open | auth-login-otp, public-front-door |
| BL-005 | server | Card payment is a dead end everywhere: MockPaymentProvider redirects to a non-existent host, and nothing fires the PSP webhook locally, so no new card payment can ever reach ConfirmPaymentAndPostLedger. |
checkout-and-payment gap-1/2 | open | checkout-and-payment |
| BL-006 | contract | Booking deadline timestamps ship without a timezone (DateTime not DateTimeOffset); the client reads the offset-less value as local time — in Tehran the 30-minute payment window renders ~4h and expires while the timer still shows time left. |
booking-request gap-1 | open | booking-request, checkout-and-payment |
| BL-007 | ops | No bnpl payment-gateway row is ever seeded — every BNPL initiate/eligibility call 400s with "No active BNPL gateway is configured" on the live server. |
bnpl gap-1 | open | bnpl-installments |
| BL-008 | client | The BNPL wizard is a dead end for every real booking-request id: its mock cross-imports the bookings-mock store (seeded ids 1-2 only), bypassing the real-data flip entirely. | H-07, bnpl gap-2/3 | open | bnpl-installments |
| BL-009 | client | Refunds mock reads the retired bookings-mock store (ids 5001-5005 only), so any real booking id 404s inside the cancel-policy preview; flipping the flag today would also render a 10000% refund (percent-scale bug on an already-0-100 field). | H-06, cancellation-and-refunds gap-1/2/4 | open | cancellation-and-refunds |
| BL-010 | client | Verification is 100% client-mocked under one flag covering nurse + public + admin; the mock resets to empty on every reload, so even a server-verified nurse renders unverified everywhere the app reads it, hiding the real search-visibility gate and "go live" CTA. submitCredentialDetails is also a no-op though the real endpoint matches field-for-field. |
H-08, nurse-verification gap-1/2/4, onboarding-nurse gap-1/2/3 | open | nurse-verification, onboarding-nurse, search-and-discovery, public-front-door |
| BL-011 | client | Patient/care records are 100% mocked on both the family record and nurse visit-note panel, and the real DTO shapes would break a naive flip: medication/routine fields mismatch server field-for-field, and care-plan ids are string client-side vs long server-side. |
care-circle-patients gap-1..4, patient-care-records gap-1..3, H-17 | open | care-circle-patients, patient-care-records, booking-lifecycle-evv |
| BL-012 | server | Nurse earnings/payouts: mock hides four working endpoints; no process operation exists anywhere in the client (the irreversible payout step has no UI); DeriveEarningsState marks a booking "paid" whenever it merely has a payout link regardless of that payout's actual status; the four earnings buckets don't reconcile against the ledger. |
H-09, nurse-earnings-and-payouts gap-1/4/5/9/10/11 | open | nurse-earnings-and-payouts |
| BL-013 | client | Partner center is 100% mocked with zero tenancy (any signed-in caller resolves to center id 1), and 5 core routes (centers/me, /me/nurses, /me/bookings, /me/bookings/{id}, /me/settlement) don't exist server-side; a center owner has no legal read of its own issued invoices. |
partner-center gap-1/2/3/9/10, REQ-032, REQ-033, REQ-064 | open | partner-center |
| BL-014 | server | Search results are index rows, not nurses — one nurse with 3 variants × 3 areas shows as "9 پرستار" with no de-dup; the trust dossier on results is mocked, and the by-id profile page asserts "verified" unconditionally, even for an in_review nurse. |
search-and-discovery gap-1/3/4 | open | search-and-discovery, public-front-door |
| BL-015 | client | Editing an address through the UI silently destroys data: the form never collects postalCode/recipientName/recipientPhone, and the update handler nulls them unconditionally on every save. |
addresses-and-map gap-4 | open | addresses-and-map |
| BL-016 | server | A booking whose remaining sessions get swept to missed never reaches a payable state — the allSettled → Completed re-check only lives inside the checkout path, so already-checked-out sessions can never enter a payout batch. The "today" session feed also applies no date filter and lists a nurse's whole history. |
booking-lifecycle-evv gap-1/2 | open | booking-lifecycle-evv, nurse-earnings-and-payouts |
| BL-017 | server | Reviews can never leave moderation on the live stack: the queue and status-PATCH sit behind BL-001, and AutoApproveClean is absent from config — a real review never reaches a nurse's profile except via the seeder. |
reviews gap-1/2/3 | open | reviews |
| BL-018 | server | A production database has zero option groups outside Development — the one seeded «نوع شیفت» group only exists because the demo seeder is Dev-only, so every builder collapses to two steps and every variant in a category duplicates every other, deployed. | nurse-catalog-and-pricing gap-3 | open | nurse-catalog-and-pricing |
Major (86)
| ID | Area | Item | Origin | Status | Blocks |
|---|---|---|---|---|---|
| BL-019 | client | Customer without an emergency contact cannot save name or language — the profile validator requires it non-empty but the UI has no field-level pre-check, and the resulting 400 is completely silent (no onError). |
account-and-settings gap-1/2 | open | account-and-settings |
| BL-020 | client | Partner settings hub shows fabricated identity — USE_PARTNER_MOCK resolves the same center for any caller, zero real tenancy on the screen. |
account-and-settings gap-6 | open | account-and-settings, partner-center |
| BL-021 | client | Sign-out confirmation is inconsistent — customer hub gates it behind a confirm dialog; nurse/admin/partner fire on the first tap and end every session on the account. | account-and-settings gap-7 | open | account-and-settings |
| BL-022 | client | A customer cannot set an avatar — the real endpoint exists but the client's uploadAvatar only targets the nurse route. |
account-and-settings gap-8 | open | account-and-settings |
| BL-023 | product | preferredLanguage is stored and never consumed anywhere — the UI locale comes only from the URL prefix, so the زبان sheet has two controls that do different things. |
account-and-settings gap-3 | open | account-and-settings |
| BL-024 | server | Nurse avatars never load anywhere in the app — LocalDiskObjectStorage.GetUrl returns a file:// URI a browser cannot fetch. |
account-and-settings gap-4, onboarding-nurse gap-4, search-and-discovery gap-6, public-front-door gap-2 | open | account-and-settings, onboarding-nurse, search-and-discovery, public-front-door, booking-request |
| BL-025 | ops | NEXT_PUBLIC_NESHAN_KEY is unset in every environment, so the real map (tiles, address search, locate-me) never renders anywhere — every environment gets the keyless grid stand-in, and the response shapes are unverified against the live Neshan API. |
addresses-and-map gap-1/2 | open | addresses-and-map |
| BL-026 | client | CITY_CENTROIDS is keyed on mock-seed city ids; only Tehran matches the real seeded cities, so the map picker opens ~400km away for every other city. |
addresses-and-map gap-3 | open | addresses-and-map |
| BL-027 | client | Recipient name/phone/postal-code are never displayed anywhere on an address card either, even where the (broken, BL-015) form would collect them. | addresses-and-map gap-5 | open | addresses-and-map |
| BL-028 | contract | Admin list pagination silently breaks on flip: admin/apis/clientApi.ts sends page_size; controllers declare PageSize. Binding is case- but not separator-insensitive, so every admin list falls back to the server default page size. |
admin-backoffice gap-6, search-and-discovery gap-13 | open | admin-backoffice, search-and-discovery |
| BL-029 | server | /fa/admin/roles and /fa/admin/users have no server — admin_roles/* and admin_users/* are phantom; REQ-061 (admin user directory) was never given a ledger header despite ten live client files citing it. |
admin-backoffice gap-7/8, REQ-031, REQ-061 | open | admin-backoffice |
| BL-030 | client | `admin_cancellation_policies/list | upsert` are unwired — no screen edits the cancellation tiers; they are effectively read-only in production. | admin-backoffice gap-11 | open |
| BL-031 | client | Reuse-detection cannot tell theft from two tabs: the single-flight refresh guard is per-tab module state, so two tabs 401ing at once race and the loser's classified-as-reuse kills every session with no explanation. useSelectRole's own rotation bypasses the guard the same way. |
auth-login-otp gap-2/3 | open | auth-login-otp |
| BL-032 | server | Session revocation does not kill access tokens — RefreshTokenCommandHandler revokes sessions but never rotates the security stamp, so "logged out everywhere" is up to 60 minutes late. |
auth-login-otp gap-4 | open | auth-login-otp |
| BL-033 | client | The access cookie expires 45 minutes before the token does (900s vs 60min), so 15 idle minutes bounces any full page load to /login even though the refresh cookie could have recovered the session. |
auth-login-otp gap-5 | open | auth-login-otp |
| BL-034 | client | A ?next= pointing at /partner/... never survives login — appRoleForPath has no partner branch, and /me carries no signal that the caller administers a partner center. |
auth-login-otp gap-11, partner-center gap-4, REQ-038 | open | auth-login-otp, partner-center |
| BL-035 | client | useLogout always signs out every device — an absent refreshToken means "everywhere"; no UI offers a single-device choice. |
auth-login-otp gap-12 | open | auth-login-otp |
| BL-036 | client | resolveRoleDestination checks admin before customer/nurse, so a user holding both an admin sub-role and customer can never reach the family app from login. |
auth-login-otp gap-13 | open | auth-login-otp |
| BL-037 | ops | POST /auth/request_otp 500s on a fresh clone — the committed SMS provider is telegram with nothing listening; login is unreachable until booted with the mock provider. |
auth-login-otp gap-1 | open | auth-login-otp |
| BL-038 | client | Query cache is never cleared on logout/login — only authKeys are invalidated, so a device that signs out of one account and into another can render stale, cross-account data from the previous session's cache. |
H-11 | open | auth-login-otp, account-and-settings |
| BL-039 | server | BookingRoles.Admin over-grants: Support/Moderation roles get clinical care-instruction reads and financial nurse-payable-balance reads, wider than the "admin/finance" comment claims. |
H-05 | open | booking-lifecycle-evv, nurse-earnings-and-payouts |
| BL-040 | client | The «پاسخداده» nurse-inbox tab is unpaged — the API filters one status at a time with no group filter, so nurse/requests fires three page-1 queries and concatenates; a nurse with >20 answered requests silently loses rows. |
booking-request gap-4, REQ-050 (residue) | open | booking-request |
| BL-041 | ops | Nothing seeded is actionable in the booking-request flow — no pending/accepted request survives the demo seeder's epoch-anchored aging, so the nurse inbox, accept/reject and payment-window handoff are all unwalkable without creating a fresh request. |
booking-request gap-2 | open | booking-request |
| BL-042 | server | nurseAvatarUrl on a booking request is served as a raw local-disk path — same root cause as BL-024, called out because checkout renders it directly. |
booking-request gap-11 | open | checkout-and-payment |
| BL-043 | client | NEXT_PUBLIC_EVV_MOCK_GPS now defaults to off (derived from the now-real bookings flag), so a tester away from the seeded Tehran address fires a real GPS mismatch — and a support alert — on every check-in; the doc-comment still describes the old default. |
booking-lifecycle-evv gap-6 | open | booking-lifecycle-evv |
| BL-044 | server | The nurse never sees the address on a confirmed booking — BookingMapper.ToDetailDto nulls the address snapshot for the nurse role even though the server itself reads it to compute the EVV match. |
booking-lifecycle-evv gap-7, REQ-051 | open | booking-lifecycle-evv |
| BL-045 | client | POST bookings/submit_care_instructions/{id} is unwired — no customer form exists anywhere; on a booking a tester creates, the nurse's care-instructions card is empty. |
booking-lifecycle-evv gap-10 | open | booking-lifecycle-evv |
| BL-046 | client | The nurse booking detail is half real: the bookings/EVV half is server truth, but the mounted visit-notes panel (task checklist, continuity history, note-save) is entirely patientRecords-mocked — same root cause as BL-011. |
booking-lifecycle-evv gap-12 | open | booking-lifecycle-evv |
| BL-047 | client | H-06's exact mocked-cross-import defect also shows up as: CHANNEL_BY_BOOKING pins the BNPL-revert refund channel to one fixture id, so every real booking silently demos as a card refund. |
cancellation-and-refunds gap-3 (folded severity note) | open | cancellation-and-refunds |
| BL-048 | contract | The client discards six fields the server now serves on refund status (platformFeeRefundedIrr, nursePayoutRefundedIrr, refundPercentageApplied, cancellationPolicyCode, createdAt, completedAt) — the fee-split transparency section can never render even once BL-009 is fixed. |
cancellation-and-refunds gap-5 | open | cancellation-and-refunds |
| BL-049 | contract | Cancellation enum drift on three fields (CancellationPolicyCode, CancellationLeadTime, appliesTo) between client and the seeded/wire values — every one drives an i18n lookup that will miss on flip. |
cancellation-and-refunds gap-6 | open | cancellation-and-refunds |
| BL-050 | server | POST admin_refunds creates and executes in one call — no preview/approve/reject route exists, so the admin RefundPanel's three-step console has no real backend. |
cancellation-and-refunds gap-10, REQ-035 | deferred (trigger: admin refund console prioritized) | cancellation-and-refunds |
| BL-051 | client | No admin console exists for refund/clawback settlement — confirm_settlement, mark_failed, write_off all have server handlers but zero client screens. |
cancellation-and-refunds gap-12 | open | cancellation-and-refunds |
| BL-052 | server | expectedCustomerRefundEta is hardcoded null in the cancellation preview — the BNPL 10-business-day window is invisible before the customer confirms, exactly where it matters most. |
cancellation-and-refunds gap-14 | open | cancellation-and-refunds |
| BL-053 | ops | customer_no_show has no seeded cancellation-policy row — the product doc's "up to 100% charge" tier does not exist; a customer no-show falls through to the 50%-back tier. |
cancellation-and-refunds gap-15 | open | cancellation-and-refunds |
| BL-054 | server | No credit note / invoice reversal on refund — a refunded booking's VAT-bearing commission invoice stays as originally issued. | cancellation-and-refunds gap-16 | open | cancellation-and-refunds |
| BL-055 | server | Seeded visit-note taskResults labels are lost — the handler deserializes with case-sensitive options against camelCase seeded JSON; any externally-written JSON in that column silently degrades to blank labels. |
care-circle-patients gap-5, patient-care-records gap-7 | open | care-circle-patients, patient-care-records |
| BL-056 | client | Editing a patient destroys their birth date — the form always submits ageToBirthDate(age) = YYYY-01-01, overwriting the real recorded date on every save even with no change. |
care-circle-patients gap-10 | open | care-circle-patients |
| BL-057 | server | Tenancy leak on patient records: a foreign patient returns 403, not 404, across all three handlers — confirms the patient exists to an unauthorized caller, violating the repo's 404-not-403 invariant. | patient-care-records gap-8 | open | care-circle-patients, patient-care-records |
| BL-058 | contract | A verified nurse is shown as unverified on the checkout screen — the checkout summary DTO declares nurseVerified but the server never constructs it. |
checkout-and-payment gap-3, REQ-046 | open | checkout-and-payment |
| BL-059 | client | Payment confirmation cannot deep-link to the booking — the server does serve bookingId but the client types it away and hardcodes null; the receipt also always hides the tracking code and paid-at. |
checkout-and-payment gap-5, H-10, REQ-046 | open | checkout-and-payment |
| BL-060 | client | The invoice screen derives a money row client-side (gross − commission − vat) that is wrong by exactly the VAT amount, and never shows the server's real totalIrr — violates the "client never computes money" rule. |
checkout-and-payment gap-7 | open | checkout-and-payment |
| BL-061 | server | Customer payment history is a live 404 — bookings/payment_history doesn't exist; the wallet «پرداختها» tab is permanently empty for a card-paying customer. |
checkout-and-payment gap-8, REQ-047 | open | checkout-and-payment |
| BL-062 | server | The escrow ledger has no read surface at all — a customer, admin or tester has no way to see the balanced capture group the flow's core money invariant depends on. | checkout-and-payment gap-10 | open | checkout-and-payment |
| BL-063 | client | getUnreadTotal is a hardcoded null on the real path — the chrome support badge can never light up. |
messaging-tickets gap-4, account-and-settings gap-10, REQ-059 | open | messaging-tickets, account-and-settings |
| BL-064 | client | mapSummary hardcodes lastMessagePreview/lastAuthorRole to null on the real path — the ticket inbox card shows no message preview. |
messaging-tickets gap-5, REQ-059 | open | messaging-tickets |
| BL-065 | client | POST /tickets/emergency has zero client callers — the nurse's emergency banner only dials tel:; the "then opens a ticket" half of the business playbook is manual. |
messaging-tickets gap-6 | open | messaging-tickets |
| BL-066 | contract | 11 of 14 server notification types are unknown to the client's parser — only 3 deep-link; the nurse's most important notification (booking_confirmed_nurse) and the entire booking_request_* lifecycle are non-navigable. |
notifications gap-1/2/3 | open | notifications, booking-request |
| BL-067 | client | The client handles 10 notification types the server never emits, while the server's real payout-paid/payout-failed events have no client branch at all — a nurse is never told a payout paid or failed. | notifications gap-6 | open | notifications, nurse-earnings-and-payouts |
| BL-068 | server | Every notification title/body is an English literal rendered verbatim into the Persian RTL feed — no i18n path for notification content, only for chrome. | notifications gap-7 | open | notifications |
| BL-069 | client | No catalogue-authoring UI exists at all — all seven admin_catalog/* routes have zero client callers; an admin cannot add a pricing dimension without SQL, compounding BL-018. |
nurse-catalog-and-pricing gap-1 | open | nurse-catalog-and-pricing |
| BL-070 | server | ZWNJ is stripped from every stored Persian string via a global FixPersianChars normalizer, contradicting the repo's "with a ZWNJ, always" naming rule. |
nurse-catalog-and-pricing gap-4 | open | nurse-catalog-and-pricing |
| BL-071 | server | A nurse's price edit is not shielded from an in-flight booking request — the conversion reads the live variant price, not a request-time snapshot, so editing price inside the 30-minute payment window changes what the customer pays. | nurse-catalog-and-pricing gap-5 | open | nurse-catalog-and-pricing, booking-request |
| BL-072 | client | The «همراهی و مراقبت روزمره» category is data-only per its own seed comment, but the builder offers it like any real pricing path. | nurse-catalog-and-pricing gap-8 | open | nurse-catalog-and-pricing |
| BL-073 | client | toHistoryItem hardcodes failureReason: null though the live wire carries it — a failed payout loses its reason in the nurse's history on flip. |
nurse-earnings-and-payouts gap-3 | open | nurse-earnings-and-payouts |
| BL-074 | client | previewPayoutBatch sums net amounts and fabricates a processing date client-side — the client computing money and a payout date, violating the "client never computes money" rule. |
nurse-earnings-and-payouts gap-6 | open | nurse-earnings-and-payouts |
| BL-075 | contract | recordTransferReference targets a route that doesn't exist — the batch-detail reconcile field 404s on flip. |
nurse-earnings-and-payouts gap-12, REQ-036 | deferred (trigger: payout reconciliation console prioritized) | nurse-earnings-and-payouts |
| BL-076 | client | POST admin_payouts/{id}/mark_failed exists server-side but has no client op — a reconciled bank rejection cannot be recorded from the console. |
nurse-earnings-and-payouts gap-14 | open | nurse-earnings-and-payouts |
| BL-077 | server | Whole-city + a specific district in the same city are both accepted for one nurse, and search then returns her twice with no de-dup on (nurseId, variantId). |
nurse-service-areas gap-1 | open | nurse-service-areas, search-and-discovery |
| BL-078 | client | The coverage screen has no error state — isError is dropped, so a failed list load renders the "no area registered" empty state, telling a nurse with real coverage she is invisible in search. |
nurse-service-areas gap-3 | open | nurse-service-areas |
| BL-079 | contract | No edit and no deactivate for a service area — changing a district means remove-then-add, which silently drops the nurse from search between the two calls. | nurse-service-areas gap-5 | open | nurse-service-areas |
| BL-080 | client | nurse/verification/page.tsx unconditionally imports the verification mock module — the one production seam breach in the client; the mock ships in every build regardless of the render gate. |
nurse-verification gap-4 | open | nurse-verification |
| BL-081 | client | foldQueueRows folds the per-step admin verification queue to per-nurse items but leaves the step-progress counts at zero and the pager count wrong — a nurse's steps can straddle a page boundary. |
nurse-verification gap-6, REQ-062 | open | nurse-verification |
| BL-082 | contract | approveVerification/rejectVerification/getDocumentSignedUrl target routes that don't exist — the admin case page's visible approve/reject CTAs would 404 the moment BL-001 is fixed. |
nurse-verification gap-7, REQ-034 | deferred (trigger: verification-admin console prioritized) | nurse-verification |
| BL-083 | client | No admin UI exists for suspend/scan_expiring or the verification step-type catalog — the "data-driven catalog" design rule has no admin surface. |
nurse-verification gap-8 | open | nurse-verification |
| BL-084 | client | Admin verification case URLs are mistyped — the route folder is [nurseId] but the value passed is a nurseVerificationId; typing a nurse id into the URL opens the wrong case. |
nurse-verification gap-11 | open | nurse-verification |
| BL-085 | product | Onboarding never creates a customer_profile — it creates a patient and stops; nothing blocks a customer from booking without ever setting an emergency contact. |
onboarding-customer gap-1 | open | onboarding-customer |
| BL-086 | contract | A customer's own gender is unsettable through the app — no client call and no command field carries it; a fresh account's /me.gender stays null forever. |
onboarding-customer gap-3 | open | onboarding-customer |
| BL-087 | client | The customer profile upsert has no PATCH semantics — every sheet save rewrites the whole profile including name, so a stale form on one device can silently overwrite a name changed on another. | onboarding-customer gap-5 | open | onboarding-customer, account-and-settings |
| BL-088 | client | /fa/select-role is reachable by any authenticated user with no guard, letting a customer permanently self-grant the nurse role by typing a URL, with no confirmation step for an irreversible grant. |
onboarding-customer gap-7 | open | onboarding-customer |
| BL-089 | client | useSelectRole swallows a failed post-select token rotation — the role persists server-side but the client keeps a stale token until the next silent refresh happens to fire, so a role-gated call in between 403s. |
onboarding-customer gap-9 | open | onboarding-customer |
| BL-090 | client | The duplicate-IBAN error message never reaches the nurse — the bank page discards the server's field error and shows a generic "registration failed" toast. | onboarding-nurse gap-7 | open | onboarding-nurse |
| BL-091 | server | Bank-ownership inquiry ignores the national id entirely — a nurse who never did identity KYC (national_id = NULL) still gets matchedNationalId = true, so the payout gate opens on a claim nothing actually checked. |
onboarding-nurse gap-8 | open | onboarding-nurse, nurse-earnings-and-payouts |
| BL-092 | client | verifyOwnership is a dead seam op — no hook calls it, so a mismatch bank account has no re-inquiry affordance; the UI only offers "re-enter the IBAN". |
onboarding-nurse gap-9 | open | onboarding-nurse |
| BL-093 | contract | Shape mismatch blocks the partner portal even once mocked data is off: the server serves one capped/inline dashboard aggregate; the portal needs paginated /me-scoped splits it cannot get from that shape. |
partner-center gap-5 | open | partner-center |
| BL-094 | client | GET /centers/{id}/dashboard — the one portal endpoint that is real — needs a center id the portal has no way to discover, so it goes unused. |
partner-center gap-6 | open | partner-center |
| BL-095 | server | Structured taskResults are discarded in both directions on the real patient-records path — accepted on write and returned on read by the server, but the client hardcodes them away; the "wire has no structured field" client comments are simply wrong. |
patient-care-records gap-6 | open | patient-care-records |
| BL-096 | product | Tier (c) of the public front door (guest search + public nurse profiles) is unbuilt — REQ-066/067 need a backend phase plus an explicit privacy sign-off before any guest-facing search/profile screen is built. | public-front-door gap-6, ui-phase-13 follow-up | deferred (trigger: privacy sign-off + REQ-066/067 delivered) | public-front-door, search-and-discovery |
| BL-097 | product | /terms and /privacy ship placeholder legal copy behind a draft banner, flagged for human/legal review since ui-phase-3 and still unreviewed — a real pre-launch item. |
public-front-door gap-5, ui-phase-3 follow-up 1 | open | public-front-door |
| BL-098 | server | GET /api/v1/nurses/{id}/profile returns 200 anonymously for an unverified (in_review) nurse — the persona that "must never appear in search" is reachable and enumerable by id, even though search itself correctly hides her. |
public-front-door gap-3 | open | public-front-door, search-and-discovery |
| BL-099 | client | The nurse cannot see their own reviews anywhere in the app — the only signal is a notification the (BL-001-blocked) moderation handler dispatches. | reviews gap-8 | open | reviews |
| BL-100 | client | The client hardcodes the five review tag codes instead of reading the server's tag master — a newly seeded tag is invisible to the UI and an unseeded code fails submit outright. | reviews gap-7 | open | reviews |
| BL-101 | server | ReviewModerationStatus.Rejected is unreachable end to end — the submit-time banned-word path maps to Hidden, not Rejected, and the only other producer is the BL-001-blocked admin PATCH; the client still renders a rejected chip nobody can ever trigger. |
reviews gap-4 | open | reviews |
| BL-102 | server | Review eligibility distinguishes "booking not found" from "not your booking" with a 200, letting any authenticated caller probe whether an arbitrary booking id exists — violates the repo's 404-not-403 tenancy invariant. | reviews gap-9 | open | reviews |
| BL-103 | product | No free-text search exists — a customer who knows a nurse's name by name cannot find her; category/city/gender/price filters only. | search-and-discovery gap-10, REQ-041 | open | search-and-discovery |
| BL-104 | client | Guests cannot reach search at all — every search route sits behind a customer-only route guard and /search is not in the public-path list, even though the underlying endpoints are already anonymous. |
search-and-discovery gap-11 | open | search-and-discovery, public-front-door |
Minor (115)
| ID | Area | Item | Origin | Status | Blocks |
|---|---|---|---|---|---|
| BL-105 | client | Admin hub renders a raw i18n key as the role label — the translation keys were never added. | account-and-settings gap-5 | open | account-and-settings |
| BL-106 | client | Customer hub navigates with a hand-built locale prefix instead of the shared navigation helper — works today, breaks silently if localePrefix ever changes. |
account-and-settings gap-11 | open | account-and-settings |
| BL-107 | client | UNVERIFIED: a system-theme user may see one light/dark flip after hydration (SSR cookie vs. post-hydration MUI resolution can disagree). | account-and-settings gap-12 | open | account-and-settings |
| BL-108 | product | No notification-preference surface exists anywhere — the settings row only deep-links to the notification centre. | account-and-settings gap-9 | open | account-and-settings, notifications |
| BL-109 | client | The pin is a required address-form field, so the API's "no pin" state and its UI badge are unreachable except via direct API calls or seed data. | addresses-and-map gap-6 | open | addresses-and-map |
| BL-110 | ops | Geography is seeded one city per province; districts exist only for Tehran, so the region cascade is a two-step formality everywhere else. | addresses-and-map gap-7 | open | addresses-and-map |
| BL-111 | client | GET geo/tree is live but unused — the region cascade makes three round trips instead of one. |
addresses-and-map gap-8 | open | addresses-and-map |
| BL-112 | client | Map-pin RTL geometry is dodged via inline style, untested for RTL drift beyond the keyless fallback path. | addresses-and-map gap-9 | open | addresses-and-map |
| BL-113 | docs | No product/ doc describes address entry, the map-pin picker, or the Neshan integration. |
addresses-and-map gap-10 | open | addresses-and-map |
| BL-114 | client | /fa/admin/notifications is a placeholder stub and a true orphan — no link reaches it and no bell is mounted in the admin shell. |
admin-backoffice gap-9, notifications gap-8 | open | admin-backoffice, notifications |
| BL-115 | client | POST holidays/delete_holiday is unwired — the admin console offers no delete affordance for a holiday row. |
admin-backoffice gap-10 | open | admin-backoffice |
| BL-116 | ops | admin_search/rebuild_index and admin_booking_requests/expire are reachable only by curl, and both 403 for a seeded admin anyway. |
admin-backoffice gap-12 | open | admin-backoffice, booking-request |
| BL-117 | client | useAdminCapabilities hides tabs but never blocks the route itself — every admin console stays URL-reachable regardless of capability; server enforcement is currently just the blanket BL-001 403. |
admin-backoffice gap-13, H-14 | open | admin-backoffice |
| BL-118 | client | A 429 on OTP verify renders as "wrong code" — the empty error body leaves nothing to branch on, unlike the phone-step's correct 429 handling. | auth-login-otp gap-6 | open | auth-login-otp |
| BL-119 | contract | The client ignores the server's OTP metadata (codeLength, expiresInSeconds) and hardcodes its own — the 60-second code expiry is never shown, only the 120-second resend cooldown. |
auth-login-otp gap-7 | open | auth-login-otp |
| BL-120 | docs | Stale client comments claim the server exposes no OTP code length or machine-readable failure code — it exposes both. | auth-login-otp gap-8 | open | auth-login-otp |
| BL-121 | client | A signed-in user visiting /login sees the phone form again instead of being redirected away, unlike /welcome. |
auth-login-otp gap-10 | open | auth-login-otp |
| BL-122 | product | The OTP SMS template is not WebOTP-conformant, so the client's WebOTP autofill ships but never fires; landing this is a Kavenegar-dashboard template edit (ops action), not a code change. | auth-login-otp gap-14, REQ-039 | open | auth-login-otp |
| BL-123 | ops | No seeded account is role-less, so /fa/select-role has no natural path in the demo world. |
auth-login-otp gap-15, onboarding-nurse gap-10 | open | onboarding-customer, onboarding-nurse |
| BL-124 | client | Client hardcodes English toasts for 401/403/5xx/network errors — no i18n dictionary. | H-13 | open | (all authenticated flows) |
| BL-125 | client | Admin read-only consoles are inconsistently client-guarded — some pages check capabilities only to gate mutation buttons, admin/audit has zero check, and no shared CapabilityGuard component exists. |
H-14 | open | admin-backoffice |
| BL-126 | docs | bnpl/apis/clientApi.ts and its header comment both claim the by-request BNPL read 404s — it exists and returns 200, a stale comment blocking a partial de-mock. |
bnpl gap-5 | open | bnpl-installments |
| BL-127 | client | acceptBnplSchedule derives request status client-side from the order status, mislabelling a failed/reverted/cancelled order as awaiting-payment — the expired-window branch can never fire on the real path. |
bnpl gap-6 | open | bnpl-installments |
| BL-128 | product | providerCommissionReversedAmount is left null on a reverted BNPL order — nothing in the UI or admin surfaces the resulting commission shortfall. |
bnpl gap-8 | open | bnpl-installments |
| BL-129 | ops | Seeded BNPL eligibility status ("approved") is not in the closed status vocabulary on either side of the wire. |
bnpl gap-9 | open | bnpl-installments |
| BL-130 | server | D3 KYC inputs are half-wired — the client sends nationalId/mobile/consent, but eligibility only reads the mobile; legal consent is collected and silently discarded. |
bnpl gap-10, REQ-023 (residue note) | open | bnpl-installments |
| BL-131 | ops | Nothing fires the BNPL provider webhook in dev, so even a successfully initiated real order would never reach settled — settlement is webhook-driven by design. |
bnpl gap-11 | open | bnpl-installments |
| BL-132 | client | /fa/bookings/checkout/bnpl/gateway is an orphan route on the real path, reachable only from the mock and gated outside development. |
bnpl gap-12 | open | bnpl-installments |
| BL-133 | client | No client path produces a disputed booking status — it renders in the status map but nothing ever raises one. |
booking-lifecycle-evv gap-4 | open | booking-lifecycle-evv |
| BL-134 | contract | The nurse's today-feed session row carries no service/variant label — every row says patient name + visit index only. | booking-lifecycle-evv gap-8, REQ-052 | open | booking-lifecycle-evv |
| BL-135 | contract | A booking list row has no patientId — a booking card can never deep-link to the care record. |
booking-lifecycle-evv gap-9, care-circle-patients gap-13, REQ-057 | open | booking-lifecycle-evv, care-circle-patients |
| BL-136 | client | POST booking_sessions/cancel/{id} is unwired — a nurse cannot cancel a single visit from the UI. |
booking-lifecycle-evv gap-11 | open | booking-lifecycle-evv |
| BL-137 | docs | services/bookings/apis/serverApi.ts is dead code with no importer, and its own doc-block still claims the domain is mock-primary. |
booking-lifecycle-evv gap-13 | open | booking-lifecycle-evv |
| BL-138 | contract | No structured rejection-reason code exists — the client runs a seven-keyword heuristic on free text to decide whether to offer "retry with the same nurse," and it silently misses any other wording. | booking-request gap-5, REQ-044 | open | booking-request |
| BL-139 | contract | variantLabel is typed as client-augmented/optional though the server always serves it, and the list row still lacks variantPrice/variantPriceUnit, so the nurse inbox card can't show the money without opening the detail. |
booking-request gap-6/7, REQ-050 (residue) | open | booking-request |
| BL-140 | contract | patientAge is served on every nurse-inbox row but never modelled client-side — a pure client-side type-widening fix. |
booking-request gap-8 | open | booking-request |
| BL-141 | client | The customer's pending-request list doesn't poll while the detail view does, so countdowns on /bookings go stale until a manual refresh. |
booking-request gap-9 | open | booking-request |
| BL-142 | client | useBookingRequest has no auth gate (unlike both list hooks) — a hard reload before hydration can flash an error card. |
booking-request gap-10 | open | booking-request |
| BL-143 | product | The nurse sees the patient's display name pre-accept, wider than the integration doc's "notes + coarse address only" — confirm this is the intended stage-1 boundary. | booking-request gap-12 | open | booking-request |
| BL-144 | client | The client's 22 in-memory mocks are module-scoped and reset on reload/HMR — a mocked cancel demo doesn't survive navigating to the refund-status screen. | cancellation-and-refunds gap-17 | open | cancellation-and-refunds |
| BL-145 | contract | preview.refundableSessionIds doesn't exist on the wire (only sessions[]) — harmless today, but the client type lies. |
cancellation-and-refunds gap-7 | open | cancellation-and-refunds |
| BL-146 | docs | The refunds clientApi.ts doc-block is stale — claims REQ-019/020/021 are contract gaps; all three routes exist and return 200. |
cancellation-and-refunds gap-8 | open | cancellation-and-refunds |
| BL-147 | server | No GET refunds/my — the wallet «استردادها» tab renders empty on the real path. |
cancellation-and-refunds gap-11, REQ-048 | open | cancellation-and-refunds |
| BL-148 | client | /fa/admin/finance is a hub with a single payouts tile — no refunds, clawbacks, or invoice-issue surface. |
cancellation-and-refunds gap-13 | open | cancellation-and-refunds |
| BL-149 | client | Client discards a field the server does serve on the visit-note read — the "N of M tasks" summary chip can never render on the real path. | care-circle-patients gap-6 | open | care-circle-patients |
| BL-150 | contract | deniedReason enum mismatch on patient-record access denial — server sends not_authorized, client/docs declare no_access/not_found. |
care-circle-patients gap-7, patient-care-records gap-5 | open | care-circle-patients, patient-care-records |
| BL-151 | docs | Stale client comments assert patient-record endpoints "have no backend" — all exist, are authorized, and were probed 200. | care-circle-patients gap-8/9, patient-care-records gap-12 | open | care-circle-patients, patient-care-records |
| BL-152 | ops | Seeded care plans are empty and seeded patients have no relation/conditions — three of four record tabs would be blank the moment the mock flag flips. | care-circle-patients gap-11/12, patient-care-records gap-9 | open | care-circle-patients, patient-care-records |
| BL-153 | docs | No product/business/ file covers patient care records at all — no documented rule for append-only, encryption, or the clinical-access gate. |
care-circle-patients gap-14, patient-care-records gap-10 | open | care-circle-patients, patient-care-records |
| BL-154 | client | Archiving a patient is optimistic with no undo — a stale/cross-tenant id 404s and the card silently reappears with a generic toast. | care-circle-patients gap-15 | open | care-circle-patients |
| BL-155 | client | The only way to demo patient-record access-denial uses a hardcoded sentinel patient id — untested against how the real server denies. | patient-care-records gap-11 | open | patient-care-records |
| BL-156 | contract | sessionCount is served nullable but typed non-null client-side; UNVERIFIED how the i18n label renders a null count. |
checkout-and-payment gap-4 | open | checkout-and-payment |
| BL-157 | docs | Payment client constants/comments are stale — claim a live endpoint 404s, claim the mock is still primary, and hardcode a platform-fee rate that no longer matches the server's seeded value. | checkout-and-payment gap-6 | open | checkout-and-payment |
| BL-158 | server | Invoice carries no payment method, transaction reference, or seller fiscal identity — those rows never render on the real path. | checkout-and-payment gap-9, REQ-049 | open | checkout-and-payment |
| BL-159 | server | GET invoices/{bookingId} tenancy is enforced only inside the query handler; UNVERIFIED whether a foreign customer actually gets 404. |
checkout-and-payment gap-11 | open | checkout-and-payment |
| BL-160 | server | Staff authorization is inconsistent by layer for tickets — the handler-level role set includes Support/Finance, but the controller attribute blocks them; two different definitions of "staff" in one request path. | messaging-tickets gap-3 | open | messaging-tickets |
| BL-161 | client | POST/DELETE tickets/{id}/participants are unwired — no UI can attach a third party to a thread. |
messaging-tickets gap-7 | open | messaging-tickets |
| BL-162 | client | close/reopen are live and working server-side but unreachable client-side — one shared flag gates them alongside the genuinely-missing assign, so two already-shippable features stay dark. |
messaging-tickets gap-8, REQ-063 (residue) | open | messaging-tickets |
| BL-163 | server | POST tickets/{id}/assign is a phantom endpoint the client already targets. |
messaging-tickets gap-9, REQ-063 | deferred (trigger: ticket assignment prioritized) | messaging-tickets |
| BL-164 | docs | Stale client comment claims the server has no field for clientMessageId echo — it is sent and echoed today. |
messaging-tickets gap-10 | open | messaging-tickets |
| BL-165 | contract | Client TicketAuthorRole declares system; the server's role vocabulary doesn't — one side must move. |
messaging-tickets gap-11 | open | messaging-tickets |
| BL-166 | product | Auto-created coordination tickets open with no first message — both actors see an empty thread with no explanation of what it's for. | messaging-tickets gap-12 | open | messaging-tickets |
| BL-167 | contract | Ticket message history is unpaginated by contract — a long thread has no incremental read. | messaging-tickets gap-13 | open | messaging-tickets |
| BL-168 | client | Ticket attachments are fully designed but gated off behind a flag. | messaging-tickets gap-14, REQ-060 | deferred (trigger: attachment upload prioritized) | messaging-tickets |
| BL-169 | contract | refund_issued/refund_processed and review_moderated/review_published are near-miss names on either side of the same contract. |
notifications gap-4 | open | notifications |
| BL-170 | server | verification_expiry_prompt is dispatched with a null data payload — even a matching client branch couldn't route it. |
notifications gap-5 | open | notifications, nurse-verification |
| BL-171 | client | NotificationBellPopover (full mark-read + deep-link logic) is exported but mounted nowhere — dead UI. |
notifications gap-9 | open | notifications |
| BL-172 | client | Notification "load more" isn't real pagination — it grows the page size and refetches the whole feed every time. | notifications gap-10 | open | notifications |
| BL-173 | server | Notification retention (90d/24h) is a hardcoded constant rather than a platform_configs row, against the repo's config-is-rows convention. |
notifications gap-11 | open | notifications |
| BL-174 | client | Both notification pages are client components with no generateMetadata, departing from the thin-RSC-page convention. |
notifications gap-12 | open | notifications |
| BL-175 | product | No unread-notification cap or archive — the feed grows until the 90-day sweep; unread rows are never separately swept. | notifications gap-13 | open | notifications |
| BL-176 | docs | IVariantSnapshotSerializer's doc-comment names the wrong table for the frozen JSON snapshot. |
nurse-catalog-and-pricing gap-6 | open | nurse-catalog-and-pricing |
| BL-177 | server | Category iconKey and description fields are null for all seeded categories — the catalog grid falls back to a generic icon with no explainer copy. |
nurse-catalog-and-pricing gap-7 | open | nurse-catalog-and-pricing |
| BL-178 | server | sessionCount is unvalidated against priceUnit — nothing stops an invalid combination from being saved. |
nurse-catalog-and-pricing gap-9 | open | nurse-catalog-and-pricing |
| BL-179 | client | The mock payout store's fixture booking ids deep-link into the (now real) bookings screens and 404. | nurse-earnings-and-payouts gap-2 | open | nurse-earnings-and-payouts |
| BL-180 | contract | The client sends Idempotency-Key on payout generate/retry, which the controller never reads — decorative and misleading to a reader. |
nurse-earnings-and-payouts gap-13 | open | nurse-earnings-and-payouts |
| BL-181 | server | holidayShifted is always false — PayoutBatchDto carries no such flag, so a bank-holiday date shift can never be shown. |
nurse-earnings-and-payouts gap-15 | open | nurse-earnings-and-payouts |
| BL-182 | product | No payout forecast — the nurse dashboard's "next batch" line renders nothing on the real path. | nurse-earnings-and-payouts gap-16, REQ-053 | open | nurse-earnings-and-payouts |
| BL-183 | client | Only one bank-rail failure reason is mapped to a label; every other reason falls back to a generic message. | nurse-earnings-and-payouts gap-17 | open | nurse-earnings-and-payouts |
| BL-184 | docs | Stale payouts doc-blocks assert three live endpoints don't exist server-side — all three return 200. | nurse-earnings-and-payouts gap-18 | open | nurse-earnings-and-payouts |
| BL-185 | client | Duplicate React keys on the nurse-service-areas search results whenever the BL-077 duplicate-listing bug fires. | nurse-service-areas gap-2, search-and-discovery gap-2 | open | nurse-service-areas, search-and-discovery |
| BL-186 | docs | The service-areas integration doc states a conflict rule that the live server doesn't enforce (probed 200 where it claims a conflict). | nurse-service-areas gap-4 | open | nurse-service-areas |
| BL-187 | client | No warning that removing a nurse's last service area de-lists her from search — the confirm-dialog copy doesn't distinguish this case. | nurse-service-areas gap-6 | open | nurse-service-areas |
| BL-188 | ops | Remove-service-area was left end-to-end unverified this pass (token expired mid-probe); code-traced only. | nurse-service-areas gap-7 | open | nurse-service-areas |
| BL-189 | ops | The shared demo DB has drifted from the seeder's own definitions (nurse 1 now has 5 service areas, not the seeded 3) and the idempotent seeder will never correct it. | nurse-service-areas gap-8 | open | nurse-service-areas |
| BL-190 | client | Verification step model omits isRequired though the wire serves it, so the "X of Y" progress meter would be wrong the moment an optional step type is added. |
nurse-verification gap-9 | open | nurse-verification |
| BL-191 | contract | No submittedAt on the nurse verification status, and no per-step detail on the public trust-badge read. |
nurse-verification gap-10, REQ-043, REQ-055 | open | nurse-verification, search-and-discovery |
| BL-192 | ops | Seeded verification data leaves two of six catalog step types unexercisable in the demo world. | nurse-verification gap-12 | open | nurse-verification |
| BL-193 | client | POST customer_profiles/avatar is live and implemented but no client code calls it — a customer can never set a photo. |
onboarding-customer gap-2 | open | onboarding-customer, account-and-settings |
| BL-194 | client | preferredLanguage write silently commits a default 'fa' the customer never chose, the first time they open and save the language sheet. |
onboarding-customer gap-4/6 | open | onboarding-customer |
| BL-195 | client | avatarUrl on the nurse-profile upsert input is dead on the real path — populated by the form but never sent (harmless today, since the multipart route already persisted it separately). |
onboarding-nurse gap-5 | open | onboarding-nurse |
| BL-196 | docs | The onboarding integration doc states duplicate-IBAN returns 409; the handler actually returns 400 with a field error. | onboarding-nurse gap-6 | open | onboarding-nurse |
| BL-197 | client | The bank-account page polls a stuck ownership inquiry forever, with no ceiling and no timeout copy. | onboarding-nurse gap-11 | open | onboarding-nurse |
| BL-198 | server | The HTTP merchant-of-record resolver (internal/bookings/{id}/center) is DynamicPermission-gated and 403s for both seeded admins — reachable only in-process today. |
partner-center gap-7 | open | partner-center |
| BL-199 | server | Two merchant-of-record resolvers can disagree — BNPL reads a platform config value while invoicing resolves the actual center, so a MoR center's BNPL order and invoice could name different sellers. | partner-center gap-10 (folded severity, see BL-013) | open | partner-center, bnpl-installments |
| BL-200 | ops | Seeded partner-center invoices carry null Moadian reference/PDF fields — the settlement view's document columns have nothing real to render. | partner-center gap-11 | open | partner-center |
| BL-201 | product | Center self-onboarding (write-then-masked IBAN) is deferred and has never been exercised on a real route. | partner-center gap-12 | deferred (trigger: center self-service onboarding prioritized) | partner-center |
| BL-202 | client | Welcome page's Open-Graph image is Latin-only — no Persian glyph rendering for the fa share card. |
public-front-door gap-7 | open | public-front-door |
| BL-203 | client | /fa/welcome has no in-app link at all — reachable only as the / rewrite body or by typing the URL. |
public-front-door gap-8 | open | public-front-door |
| BL-204 | ops | NEXT_PUBLIC_SITE_URL is unset in dev, so robots.txt/sitemap.xml/OG tags fall back to localhost (dev-only caveat, .env.production is correct). |
public-front-door gap-9 | open | public-front-door |
| BL-205 | ops | next dev misrepresents this flow entirely — stale prerenders serve ahead of the middleware; only a production build reproduces real behavior. |
public-front-door gap-1 | open | public-front-door |
| BL-206 | server | No anonymous rate limit on the public search/profile reads — falls back to the shared global per-IP limiter. | public-front-door gap-4, search-and-discovery gap-12, REQ-066 (residue) | open | public-front-door, search-and-discovery |
| BL-207 | client | GET nurses/{id}/review_tags (the "% of reviewers said X" rollup) and POST reviews/{id}/tags are both unwired — no chip-aggregate view and no post-submit tag amendment. |
reviews gap-5/6 | open | reviews |
| BL-208 | server | The low-rating support alert is raised on submit but has no reachable triage surface — reads sit behind BL-001. | reviews gap-10 | open | reviews |
| BL-209 | docs | Stale review doc comments claim the mock is still primary; the real client is live and the 14.6KB mock module is dead behind the flag. | reviews gap-11 | open | reviews |
| BL-210 | server | distanceKm is always null on search results — the covering index carries no coordinate, so the distance chip can never render. |
search-and-discovery gap-7 | open | search-and-discovery |
| BL-211 | contract | topReviewTag and variantDisplayName are never served on search results — dead code on the client's tag chip and no multi-variant collapse. |
search-and-discovery gap-8, REQ-040 | open | search-and-discovery |
| BL-212 | client | nurseGender is hardcoded 'female' in the public-profile mapping because the DTO carries no gender field — unused today, but a live lie in the typed model. |
search-and-discovery gap-9, REQ-042 | open | search-and-discovery |
| BL-213 | server | attributeChips is empty for every seeded nurse, and a masked reviewer name maps to an empty string — the profile snippet renders a bare separator with nothing around it. |
search-and-discovery gap-14 | open | search-and-discovery |
| BL-214 | contract | No nurse-facing read-back of submitted credential details (INO-on-file, specialties) — the returning-nurse form can't hydrate from the server. | REQ-056 | open | nurse-verification |
| BL-215 | contract | Policy numbers (dispute-window hours, cancellation lead-time, refund ETA) have no public/authenticated read — the client keeps them single-sourced in a local constants file. | REQ-065, ui-phase-12 follow-up 3 | open | cancellation-and-refunds |
| BL-216 | contract | No typed address/variant snapshot objects — BookingDetailDto still carries opaque JSON strings, forcing the client's defensive multi-key parse in three separate places. |
REQ-045, ui-phase-6 follow-up 2, ui-phase-7 follow-up 2 | open | booking-lifecycle-evv, checkout-and-payment |
| BL-217 | client | Two admin-only multi-field dialogs (GrantRoleDialog in admin/roles, PreviewBatchDialog in admin/payouts) still hold raw useState instead of react-hook-form — the only genuine residue of the app-wide form migration. |
iteration-2 #7 | open | admin-backoffice |
| BL-218 | client | H-01 residue: the auth-gate root-cause fix landed, but client/middleware.ts was never migrated to Next 16's src/proxy.ts as the original fix prescribed, and outputFileTracingRoot was never added alongside turbopack.root — a production build may still warn from the stray root-level lockfile. |
H-01 | open | auth-login-otp |
| BL-219 | docs | Rename the SET_VIA_USER_SECRETS_OR_ENV placeholder sentinel now that user-secrets is confirmedly removed — filed by phase 2 with an exact 7-file worklist (appsettings.*.json, StartupSecretsGuard.PlaceholderMarkers, .githooks/pre-commit, .githooks/README.md, StartupSecretsGuardTests.cs, docs/rules/shared/git-and-gates.md, docs/rules/server/structure.md). Optional cleanup, not urgent — the name is a load-bearing sentinel and the mechanism is already documented correctly elsewhere. |
C-2 (open-contradictions.md) | deferred (trigger: none — cleanup-of-convenience) | — |
Deferred (43)
These are recorded decisions with pull-triggers, not bugs — carried here (per this phase's Status vocabulary)
for phase 5's roadmap/deferred.md to pick up. Grouped by theme; each origin id is greppable in the source
handoff/report files for the full context.
| ID | Area | Item | Origin | Trigger | Blocks |
|---|---|---|---|---|---|
| BL-220 | ops | Redis (shared cache, cross-instance scheduler/money lock) is not deployed — every current seam is correct only for a single instance. | handoff-after-refinement-phase-7/8, refinement-phase-7/8/9 follow-ups | running more than one instance | — |
| BL-221 | server | Elasticsearch INurseSearch backend + outbox feeder not built — SQL search is the real MVP implementation. |
handoff-after-backend-phase-7, handoff-after-refinement-phase-7/8/9, backend-phase-7 follow-up | SQL search shows real strain | search-and-discovery |
| BL-222 | product | IAnalyticsSink / an analytics warehouse is not built — events aren't routed anywhere durable yet. |
handoff-after-backend-phase-1, handoff-after-refinement-phase-9 | product pulls it | — |
| BL-223 | server | A real holiday-calendar feed (vs. the seeded/admin-CRUD calendar) is not built. | handoff-after-backend-phase-1, handoff-after-refinement-phase-9 | product pulls it | — |
| BL-224 | server | organizations/organization_nurses (employer model) tables are not built. |
handoff-after-refinement-phase-9, frontend-phase-15 follow-up 2 | product pulls it | — |
| BL-225 | product | fraud_flags / ML fraud scoring is not built — manual suspension + support alerts cover this for now. |
handoff-after-backend-phase-6/14, handoff-after-refinement-phase-9 | product pulls it | — |
| BL-226 | server | recurring_booking_schedules is not built. |
handoff-after-refinement-phase-9, frontend-phase-15 follow-up 2 | product pulls it | — |
| BL-227 | server | bnpl_settlement_entries (tranched settlement) is modelled but not built. |
handoff-after-backend-phase-12, handoff-after-refinement-phase-9 | product pulls it | bnpl-installments |
| BL-228 | product | Nurse availability slots/exceptions (soft scheduling guidance) are not built. | handoff-after-backend-phase-5/7, handoff-after-refinement-phase-9 | product pulls it | booking-request |
| BL-229 | product | Customer national-ID KYC collection is deliberately not built — the product decision is to never gate browsing/booking on it. | handoff-after-backend-phase-3, handoff-after-refinement-phase-9 | never, by design (informational) | — |
| BL-230 | server | A geography bulk-import feed (IGeoDataImporter) is deferred — the idempotent seed + admin CRUD is sufficient for MVP. |
handoff-after-backend-phase-4, backend-phase-4 follow-up | product pulls it | addresses-and-map |
| BL-231 | product | Holiday/surge pricing, a distinct Companionship pricing tier, and tiered per-category commission are all deferred. | handoff-after-backend-phase-5, backend-phase-5 follow-up | product pulls it | nurse-catalog-and-pricing |
| BL-232 | product | GPS-radius "nurses near me" map discovery is explicitly not planned — coverage stays named-district-only. | handoff-after-backend-phase-4/7 | none — permanent product decision | search-and-discovery |
| BL-233 | server | Automated MoH/INO license lookup and a professional-liability-insurance verification step type are both deferred. | handoff-after-backend-phase-6 | a lookup portal exists / product pulls it | nurse-verification |
| BL-234 | ops | The credential-expiry scan and the EVV no-show sweep both run only via manual admin endpoints — no cron calls either yet. | handoff-after-backend-phase-6/9, backend-phase-6 follow-up | scheduled-ops phase | nurse-verification, booking-lifecycle-evv |
| BL-235 | server | SMS.ir/Ghasedak SMS adapters are not built — only Kavenegar's real-SMS path is wired (attempting either throws at startup by design, never silently mocks). | handoff-after-refinement-phase-8, refinement-phase-8 follow-up | a second SMS vendor is needed | auth-login-otp |
| BL-236 | ops | Finnotech/Moadian token-exchange refresh and the Moadian signing certificate are not wired — both are deploy-time actions once real credentials exist. | handoff-after-refinement-phase-8 | going to a real Moadian integration | partner-center |
| BL-237 | ops | The Moadian reconciliation poll and the refund-settlement (BNPL processing→succeeded) poll are both thin/manual — no scheduled cron for either. | handoff-after-backend-phase-11, handoff-after-refinement-phase-7/8, refinement-phase-6/7/8 follow-ups | scheduled-ops phase | cancellation-and-refunds, bnpl-installments |
| BL-238 | server | Per-provider-code BNPL revert is incomplete — the refund path only drives the SnappPay default regardless of the transaction's actual provider. | handoff-after-refinement-phase-8, refinement-phase-8 follow-up | a second BNPL provider goes live | bnpl-installments |
| BL-239 | product | A dedicated merchant-of-record center-settlement payout path (تسهیم split leg) is deferred per decision 6.6. | handoff-after-refinement-phase-8, refinement-phase-6/8 follow-ups | product pulls it | partner-center |
| BL-240 | ops | The weekly payout-batch cron generates automatically, but processing a batch stays a deliberate, explicit admin action by design (not a bug). | handoff-after-refinement-phase-7, backend-phase-13 follow-up | none — permanent product decision | nurse-earnings-and-payouts |
| BL-241 | product | On-demand/instant nurse payout withdrawal and per-nurse payout-frequency configuration are both deferred (MVP is one fixed weekly cadence for everyone). | handoff-after-backend-phase-13, frontend-phase-12 follow-up 4 | product pulls it | nurse-earnings-and-payouts |
| BL-242 | server | Automated clawback recovery beyond simple next-batch netting is not built. | handoff-after-backend-phase-13 | product pulls it | nurse-earnings-and-payouts |
| BL-243 | server | Two-way (nurse-reviews-customer) double-blind reviews with timed reveal are not built. | handoff-after-backend-phase-14 | product pulls it | reviews |
| BL-244 | server | A first-class incidents entity is not built — manual suspension + support alerts stand in for now. |
handoff-after-backend-phase-14 | product pulls it | admin-backoffice |
| BL-245 | server | Verified 2026-08-02 (phase 5), resolving this row's own trigger: ResolveSupportAlert, AssignSupportAlert (SupportAlertsController.cs), and nurse suspension (AdminSuspendVerificationCommand, feature folder SuspendVerification) are all real, built, and code-traced. Only FlagConcern — a softer flag-without-suspending action — is genuinely absent from the codebase; not separately filed as it has no UI or caller either. |
backend-phase-14 follow-up, phase-5 roadmap verification | none — resolved; the residual FlagConcern gap is small enough to pick up opportunistically, not worth its own BL-### |
admin-backoffice |
| BL-246 | server | Partner-center license verification is mocked to manual-approve at MVP rather than an automated eNamad/MoH check. | handoff-after-backend-phase-15 | product pulls it | partner-center |
| BL-247 | product | There is no telephony seam for emergencies — the emergency contact is an out-of-platform tel: link by deliberate design. |
handoff-after-backend-phase-15 | none — permanent product decision | messaging-tickets |
| BL-248 | client | SMS/push notification channels are not built — only in-app notifications are real. | handoff-after-refinement-phase-9 | notification UX demands out-of-app reach | notifications |
| BL-249 | server | Legacy UserRefreshTokens (gRPC auth path) still exists alongside the real session-based REST auth. |
backend-phase-2 follow-up | gRPC moves to sessions, or is dropped | auth-login-otp |
| BL-250 | client | The ESLint unused-vars gate is a repo-wide no-op — the config patches an export path that doesn't carry the rule, so @typescript-eslint/no-unused-vars never actually runs despite client/CLAUDE.md's golden rule 11 claiming it does. |
frontend-phase-13 follow-up 1 | dedicated infra task | — |
| BL-251 | client | PWA/offline caching (Workbox) is unbuilt — marked "maybe" in the original product backlog, i.e. optional. | product/notes open-questions.md | product pulls it | — |
| BL-252 | server | Optional short-TTL cache over hot search result pages was shipped no-cache at MVP. | backend-phase-7 follow-up | search read latency becomes a real problem | search-and-discovery |
| BL-253 | server | A single explicit DB transaction for the payment-webhook confirm path (currently two commits, kept safe via idempotency + a forward-only guard) is flagged as future hardening. | backend-phase-10 follow-up | IUnitOfWork grows a transaction scope |
checkout-and-payment |
| BL-254 | server | The partner-center dashboard's sponsored-nurse list is capped at 50 with an exact count — needs pagination if a center's roster grows past that. | backend-phase-15 follow-up | a center exceeds 50 sponsored nurses | partner-center |
| BL-255 | server | Bookings/Invoices.partner_center_id columns exist with no DB-level FK — only nurse_profiles.partner_center_id got one, per the shipping phase's own Definition of Done. |
backend-phase-15 follow-up, backend-phase-11 follow-up | a data-integrity pass on partner-center columns | partner-center |
| BL-256 | client | Skeleton→content crossfade exists as a one-line-per-screen pattern but was never retrofitted onto every list/detail page. | ui-phase-12 follow-up 1 | a dedicated visual-polish pass | — |
| BL-257 | client | No true desktop search layout — a full responsive pass beyond the phone-width frame was explicitly deferred post-chain. | ui-phase-12 follow-up 2 | product decides to support desktop | search-and-discovery |
| BL-258 | client | Persian OG image variant is Latin-only by deliberate scope cut on /welcome and other share cards. |
ui-phase-13 follow-up, public-front-door gap-7 | Persian social sharing becomes a priority | public-front-door |
| BL-259 | server | List-row EVV presence indicator (a lightweight "currently checked in" flag on the booking list DTO) was never built — needs a product/API decision to avoid an N+1 read. | ui-phase-5 follow-up 1 | product prioritizes a list-level EVV signal | booking-lifecycle-evv |
| BL-260 | client | Web-push for new nurse booking requests remains deferred — the 15-second poll is the only freshness mechanism. | ui-phase-7 follow-up 5, REQ-054 | push infra (service worker + VAPID + dispatch rail) is built | booking-request |
| BL-261 | client | Ticket attachments' composer button is fully built but gated off pending the upload/signed-URL backend. | REQ-060 (see also BL-168) | attachment backend delivered | messaging-tickets |
| BL-262 | product | Real payment-gateway/Shaparak logos near the pay CTA are deferred — no licensed assets yet; a generic lock-icon trust notice stands in. | ui-phase-6 follow-up 5 | licensed gateway assets obtained | checkout-and-payment |