Skip to content

Dev Tracker ​

Read before changing the tracker, the delivery log or the state record

  1. This page · 2. Tracker — take the next id from max+1, never from the banner (stale three times) · 3. Delivery log — one entry per request, the Avoidable field never softened · 4. Project state — a POINTER record; where it disagrees with a migration, release.json or a contract, those win · 5. Operating-manual corrections — where a replaced sentence's history goes. Gates: check:state (recency, and size after QRS-1288) · check:docs-impact (a Tier A change needs a tracker row and a log entry).

Every confirmed issue / risk / debt / improvement is logged as a permanent QRS-### id with the full field set and a status lifecycle. Log-after-confirmation, never fix-and-forget.

Delivery observations live separately. Per-request notes on how work got delivered — what went well, what cost time, what was avoidable — are in Delivery Log, not here. This page is for defects, debt and decisions; mixing the two would mean a search for open bugs returned retrospectives (QRS-241).

Lifecycle ​

Rules ​

  • Permanent id. QRS-### never gets reused or renumbered.
  • Reference the id in commits that touch the issue.
  • Groom every session/PR — status stays current.
  • Categories: bug · debt · risk · improvement.

Where the tracker lives ​

Known Issues in this portal is the tracker — authoritative, not a staging area. There is no separate system, and waiting for one is what left ~170 confirmed items unnumbered until 2026-07-26.

Every item carries a permanent QRS-###. Ids are allocated from the next free id banner at the top of the tracker page — take it, then bump it in the same edit. Ids are never renumbered, reused or reassigned: they are referenced from commits, ADRs, READMEs and code comments, so a renumber silently invalidates all of those. Numbering is document order as of the bulk assignment, so it is neither chronological nor a priority ranking.

An id is an identity, not a status. Items still awaiting reproduction live in the tracker's Candidate findings — … sections and keep their id when promoted — confirmation is never signalled by withholding a number.

Pre-baseline findings additionally staged in known-issues-pending-tracker.md (historical).

Dev portal and tracker discipline (operating-manual text) ​

Provenance — moved from CLAUDE.md on 2026-09-23 (QRS-1288)

This is the verbatim text of CLAUDE.md § "Dev portal & tracker discipline" as of commit 00c1eca, relocated here under the context-architecture programme. Sentences of the form "this said X until [date]" are corrections recorded at the time they were made; the live rule is the corrected one. Retired vocabulary inside those corrections names what was retired and is not a live claim.

Dev portal & tracker discipline ​

⚠⚠ AFTER ANY CONTEXT RESET, READ documentation/portal/dev-tracker/project-state.md FIRST. It is the compaction-survivable hand-off: current phase, the exact point to continue from, what is implemented and validated, open issues with status, the architectural decisions and their reasoning, constraints, blockers, test counts and open recommendations. It is injected automatically by a SessionStart hook (startup|resume|compact) and again by a PreCompact hook, and its freshness is gated at pre-push by npm run check:state (fails past 10 commits behind).

⚠ It is a POINTER RECORD, not a second source of truth. Where it disagrees with the tracker, release.json, a migration or a parity contract, those win — and fixing it is part of the change that outdated it, exactly like check:docs-impact. ⚠ And like every doc gate here it decides presence and recency, never whether the prose is true (QRS-246).

⚠ START AT documentation/portal/architecture/current-state.md. It is the single index of what is decided, what is built, and what is open — and it exists because on 2026-08-07 a decision taken the previous day was verified to exist in five files and to have reached no ADR and no overview page (QRS-384). Across the portal's page count (measured inventory, top of this file — it was 124 when this sentence was written and is materially higher now) per-page freshness is not achievable by discipline; one authoritative index plus supersession banners is. Trust that page over any other, and if a document contradicts it, report the document as stale rather than following it.

⚠ BUT VERIFY ITS DATE FIRST — the index itself goes stale. It reads "Maintained 2026-08-13" while migrations have landed to 2026-08-22 and commits to 2026-08-25, so it is stale again as of 2026-08-28. Read the date, then diff it against git log — do not trust any snapshot pinned here, including this one. Its header reads "Maintained 2026-08-08" and it was last touched 2026-08-10, while 13 migrations have landed since. Concretely, it states that chat and messages "are all absent" and never lists reminders as built — both wrong as of bb9c375: the chat schema shipped 2026-08-11 (CR-26.0.1-22/23/24) and the reminders backend shipped to Dev 2026-08-12 (CR-26.0.1-27/28). It also predates the orders/payments work it partially describes.

This is not an argument against the page — it is the page's own thesis applied to itself. One index is still far better than 124 pages each drifting privately. The operating rule: read its "Maintained" date, then diff it against git log and the newest release.json change records. Where they disagree, the migrations and the change records win, and updating the page is part of the change that outdated it (check:docs-impact Tier A already demands a change record for any migration, which is what made this gap measurable at all).

The internal dev portal is live at documentation/portal/ (VitePress + mermaid; its own package.json — never part of the app build). Sections: overview/, architecture/ (incl. adr/ — Architecture Decision Records for cross-cutting decisions with real options/tradeoffs, e.g. tenancy model, billing system-of-record, template rendering engine; bigger and more durable than a QRS-### tracker row, which cross-links back to the relevant ADR), backend/ (incl. shared-kit.md and the auto-generated edge-functions/index.md — regenerate via npm run docs:gen; the EF "Type" column is inferred heuristically, verify against source), features/, design-system/, integrations/, guides/, dev-tracker/, releases/ (see "Release management"), and — ⚠ omitted from this list until 2026-08-28 — communications/, payments/ and strategy/, plus consumer/ (added 2026-08-28: consumer-side RESEARCH ONLY, nothing built), plus verticals/. Per-section page counts are in the measured inventory at the top of this file, and they are cross-checked against check:docs's own total on every run — the two agreeing is what makes either trustworthy. (⚠ The first run of check:claims reported 406 pages because it walked the portal's own node_modules; check:docs said 126, and the disagreement is what exposed the bug. A gate whose arithmetic is wrong is worse than no gate, because it launders a fabricated number through a mechanism that looks authoritative.)

verticals/ was missing from this list entirely until 2026-08-12, which is a real gap rather than an omission of a minor folder — it is the home of the G-D discovery gate that check:release enforces (a scope item carrying vertical: "<slug>" needs verticals/<slug>/discovery.md to exist at scoped and be approved at scope_frozen). It holds 28 files across 6 directories — ⚠ including car_sales/ (21 pages), which this list omitted entirely until 2026-08-28 — plus _template/discovery.md, festival_stall/ (discovery.md + journey-map.md — the R1 launch vertical and the first brief completed under the one-industry-at-a-time process, QRS-475), and real_estate/discovery.md, which is explicitly not complete and says so on line one: "THIS IS NOT A COMPLETED DISCOVERY. IT IS THE AGENDA FOR ONE." Read that banner before treating real estate as designed.

Append a delivery-log.md entry for EVERY request [standing instruction from the product owner, QRS-241].documentation/portal/dev-tracker/delivery-log.md is deliberately separate from the tracker: tracker.md records defects, debt and decisions as permanent ids, while the delivery log records how a request was delivered — what went well, what cost time, what was avoidable, what to improve. It exists because the owner declined a process change on the grounds that a proposal built from one session is exactly the isolated incident that should not drive policy; the log is the evidence base that replaces that guess. Its measured columns are machine-collected and its narrative columns are self-reported, and where the two disagree the numbers win — the party being measured writes the notes. Do not soften the Avoidable field: it is the only column that can produce a process change. Review trigger is explicit (10 entries or 2026-08-31), because open-ended observation is indistinguishable from no decision and this repo has a measured history of deferred reconciliation never happening (QRS-180). ⚠ That trigger fired long ago — the log stands at 74 distinct entries as of 2026-08-28 (47 numbered ids + 27 date-headed) and the review has not happened. An un-actioned trigger is the exact failure mode QRS-180 describes, now running against the mechanism built to prevent it. Note also that observations #11 and #33-#37 are missing from the sequence — six gaps, where this line recorded one until 2026-08-28.

Load-bearing ADRs (the R1 architectural spine — read these before designing anything cross-cutting): ADR-0002 (Razorpay billing + app-store compliance), ADR-0006 (RBAC/capabilities), ADR-0007 (feature entitlements — domain×tier matrix + command center), ADR-0008 (ZeptoMail email/OTP), ADR-0009 (vertical-archetype platform — new vertical = config, not code), ADR-0010 (analytics read model), ADR-0011 (surface-matched frontend — web-DOM/shadcn for public+admin, universal Expo/RN for the merchant app, one shared design-token package), ADR-0012 (monorepo structure — npm-workspaces; bounded packages schemas→domain→data + tokens/utils/i18n/analytics/observability; README-everywhere). R1 scope: 6 domains across 5 archetypes (incl. E-commerce), captured in the tracker.

⚠ ADR-0018 (auth architecture) IS CITED BY CODE BUT THE FILE DOES NOT EXIST [found 2026-08-01, re-measured 2026-08-12 and WORSE than recorded: 25 code files, not "ten+", plus 8 more docs — including two SQL files, 20260808210000_v2_production_hardening.sql and supabase/scripts/v2_apply_all.sql, so the phantom ADR is now cited by the schema as well as the client]. They name it as their rationale — supabaseClient.ts, authBootstrap.ts, sessionVault.ts, nonce.ts, socialAuth.{native,web}.ts, sessionStore.ts, useSocialAuth.ts, packages/analytics/src/index.ts, apps/mobile/src/app/_layout.tsx, the auth feature README — and integrations/zeptomail.md says it resolves ADR-0008's open "native OTP vs bespoke EF" question in favour of Supabase native OTP + ZeptoMail as the SMTP relay. But there is no 0018-*.md in documentation/portal/architecture/adr/ (numbering jumps 0012 → 0014 and 0017 → 0019). So the decision that governs the entire auth implementation exists only as scattered code comments. Write it up before extending auth, and do not infer the decision from any single comment. 0013 is a different case and needs no write-up: the ADR index records that it "has no recorded claimant — it was never assigned and no source references it", and the string ADR-0013 appears nowhere in the repo. An unassigned number is a gap in a sequence; 0018 is a dangling reference from 25 files, which is a defect.

The ADR file count is in the measured inventory (top of this file); the NUMBERS that exist are 0001-0012, 0014-0017, 0019-0032 — ⚠ this stopped at 0028 until 2026-08-28; ADR-0029 (WhatsApp/Meta communication), ADR-0030 (tenant communication identity), ADR-0031 (domain schemas for bounded contexts) and ADR-0032 (chat and platform identity, LOCKED 2026-09-05: the phone is a credential and never an address, the slug is the universal auto-assigned address, businesses are discoverable and people are not, and a conversation is between PRINCIPALS) exist. The enumeration is the useful half and the one to trust, because it names the two gaps (0013 unassigned, 0018 dangling) that a bare count hides. Two beyond the list above that this file did not mention and that matter for current work: ADR-0026 (vendor verification & governance) and ADR-0027 (card freshness: purge-on-write, never TTL) — the latter is the ADR behind the campaigns rule below, so cite 0027 rather than re-deriving it. ADR-0028 then fixes the URL namespace: one origin, a closed set of RESERVED first segments for audiences, the universal Expo product mounted ONCE at /app (never twice), /merchant + /consumer as 301 vanity entries, and ⚠ /admin vs /org as separate TRUST boundaries that must never share a route group — read it before adding any top-level route, because the vendor slug namespace is flat and at the root, so a first segment spent is spent permanently. Most are 🟡 Proposed, not Accepted, including the entire ADR-0020..0025 platform-model spine; only 0002, 0003, 0006, 0008, 0011, 0012, 0014, 0015, 0016 and 0019 are 🟢 Accepted. Read "Proposed" as approved-pending and being built to, per the platform-model section above — but do not cite one as settled precedent in an argument.

Auth is NOT complete — email OTP is BROKEN IN A MEASURED WAY [probed 2026-08-01, both facts verified]. Google sign-in works; email OTP does not. Two separate problems, and the second contradicts ADR-0008:

  1. It fails at the SMTP layer right now. A live OTP request to qr-setu-dev returns HTTP 500, and the auth log gives the reason verbatim: 535 "5.7.8 Error: authentication failed: (reason unavailable)". So custom SMTP is enabled (Supabase reaches a relay and attempts auth) but the credential is rejected. Note the useful side effect: Supabase rolls the signup back when the confirmation email fails, so no orphan auth.users row is left behind — a failed OTP looks like nothing happened at all, which is why this went unnoticed.
  2. The relay is pointed at the WRONG PROVIDER. Supabase (Dev and Prod) is configured with smtp.hostinger.com:465, sender no-reply@qrsetu.com — but the domain's verified transactional sender is ZeptoMail. ZeptoMail's own console shows qrsetu.com Verified, and both records resolve in public DNS: DKIM 31152624._domainkey.qrsetu.com (k=rsa; p=MIIBIjANBg…) and CNAME bounce-zem.qrsetu.com → cluster89.zeptomail.in. So the DKIM/bounce half of ADR-0008 is done; only the relay and SPF are not.
  3. SPF still authorizes Hostinger only: v=spf1 include:_spf.mail.hostinger.com ~all — no zeptomail include. So even once the 535 is fixed, mail sent via ZeptoMail would fail SPF. Remember: only ONE SPF TXT record on the root, ever — merge the include, never add a second record. _dmarc is a bare v=DMARC1; p=none with no rua=, so no failure reports are being collected either.

Two doc corrections this exposed, both of which cost real debugging time:integrations/zeptomail.md says the bounce CNAME is named bounce — ZeptoMail actually issued bounce-zem — and it presents the DKIM selector as something you read off the console (true, and it is account-specific: here 31152624). Never conclude "the record is missing" from a guessed selector or a doc-assumed name — that inference was made during this very session and was wrong; read the provider console, then resolve the exact names it gives you. QRS-076 and QRS-167 item 5 remain open. Auth is not done until an OTP lands in an external Gmail and Outlook inbox with dkim=pass spf=pass dmarc=pass.

Every confirmed issue/risk/debt/improvement is logged in the portal tracker (documentation/portal/dev-tracker/tracker.md) as QRS-### (permanent id) with the full field set + status lifecycle; groom every session/PR; reference the id in commits. Log-after-confirmation, never fix-and-forget.

Claude Designs screen reviews (documentation/portal/design-system/screen-reviews/) is the retrospective half of the design-system loop: the PDPR/Screen-Coverage-Mandate/Foundational-Screen-Prompts pages tell Claude Designs how to design a screen correctly before it exists; this section audits screens that already exist in the Claude Designs project against that same standard. Findings split into design-fixable (a ready copy-paste prompt to paste into Claude Designs) and architecture-gated (linked to a QRS-###, resolved during core architecture planning, never sent to Claude Designs as a prompt — that's how the prototype ended up inventing an Ad Manager and an in-house billing engine with no backend decision behind either). After a prompt is applied in Claude Designs, ask for that screen to be revalidated — the live file is re-fetched and re-checked, and the status/history is updated in place rather than overwritten.

The legacy documentation/ tree is archived ​

Provenance — moved from CLAUDE.md on 2026-09-23 (QRS-1288)

This is the verbatim text of CLAUDE.md § "Legacy documentation/ (outside portal/) is ARCHIVED" as of commit 00c1eca, relocated here under the context-architecture programme. Sentences of the form "this said X until [date]" are corrections recorded at the time they were made; the live rule is the corrected one. Retired vocabulary inside those corrections names what was retired and is not a live claim.

Legacy documentation/ (outside portal/) is ARCHIVED — every file is banner-stamped [2026-08-08] ​

documentation/portal/ is the authoritative portal (above). The rest of documentation/ — 129 markdown files across 13 directories — is archived, and each one now carries an ⛔ ARCHIVED banner as its first line. It predates the standards program and the 2026-08-07/08 redesign, and it describes products, schemas and workflows that are retired or deleted (the three "pillars" incl. BioLink, the Vite SPA with src/tiers/, profiles/profile_items, integer business_domain_id, subscription_tiers, digital_menu_*, the /b/ /s/ /w/ routes, plus never-built things like a Zustand authStore+RBAC, KOT modules and cache pipelines). Treat it as intent + domain vocabulary; confirm any file/table/function actually exists before relying on it. Start at documentation/README.md, which lists the specific traps.

Why the banner is per-FILE and not just in the README. Nobody arrives at an archive through its table of contents — they arrive by grepping and landing mid-file. Measured before the sweep: BioLink appeared 394 times across 66 files there, 21 of them in 00-overview/user-types.md alone. A reader who greps BioLink and opens the third hit needs the warning on line 1, not three directories up.

npm run check:docs is deliberately scoped to portal/ and does NOT police the archive. Running it there would report ~400 violations that are all correct for an archive, and a gate that fails on legitimate content becomes a permanent ignore. The banner is the control in documentation/; the gate is the control in documentation/portal/.