The civic top of the funnel: the office page citizens see, the claim that turns a seat into an answering desk, and the desk a staffer actually works. Implements S13-decision-sheet.md verbatim (✅ designed; 🚫/⏳ absent; 🔨/⚠️ in captions/foot) on the DECISIONS #14 three-layer model: ① office page = wiki entity in the place graph (survives elections, occupant = a field) ② person = a normal account ③ claim = verified, term-bound link unlocking badge + inbox + broadcast + edit-protection flip. Authority page and desk are PUSHED pages (app bar + back chevron), never place tabs. 🔴 P0 #1 gates everything: claim approve/reject/link is guarded only by JwtAuthGuard today — AdminGuard/StewardGuard land BEFORE any claim UI ships. H: the app has ZERO authority screens today (API-ready) — this set is the full build reference.
Implements DECISIONS #14: the page is the SEAT ("MLA · Marhaura"), occupant = a field, party chip elected-only (prior lock; render-level guard). E16 disclaimer tiers — claimed = NO banner (the verified badge IS the signal); the dataOnly delta strip below carries "Community record · not confirmed by {name}" + the "This is your seat? Claim it" bar; 🚫 "Government-verified"/"Officially endorsed" copy. E14 responsiveness honesty: open count + resolved count + avg days to first official response ONLY — 🚫 response-rate %, 🚫 resolution rate, 🚫 stars/grades/rankings (weaponizable); unclaimed = counts only, "no responses yet" is not a zero. G19 Follow THE SEAT — the follow survives elections. E15 past holders = ONE collapsed row (🚫 per-holder issue counts). VCM scope line "Covers 47 villages · covers your village Bheldi" = zero-cost, already built. 🔨 E14 avgDays from statusHistory first transition where byUserId==linkedUserId → cron → OfficialResponsivenessCache (S) · E15 new findFormerOfficialsByJurisdiction + {jurisdictionId,roleKey,status} index · C8 unique sparse index on linkedUserId (today NOT unique — two officials could share a linked user) · H app authority page = ZERO screens today, full build.
Implements F18 pitch: "12 issues await your response · 8,400 constituents" (counts gate ≥50, else scarcity "be the first official in Saran"), broadcast named as THE carrot, 🚫 responsiveness/resolution stats on the pitch (don't punish day-1 backlog inheritance) · A2 India-concrete evidence ladder — gov-domain email (@nic.in/@sansad…) primary, ECI winner-list + letterhead PDF secondary, "a photo of your official ID card is enough" framing; 🚫 Aadhaar, 🚫 party attestation alone, 🚫 social screenshots; post-submit additionalEvidence[] WITHOUT restarting the clock · B5 ⚠️ staffer toggle = the linked tier (inbox READ + IN REVIEW + contact edit; 🚫 RESOLVED/broadcast/badge) · A4 tier-specific HONEST SLA "3–7 working days" (🚫 "24h"; panchayat up to 14), read-only status page during review, rejection = mandatory reason enum + 7-day re-apply cooldown (⚠️), 3rd-in-30d → admin unlock, dispute = second admin. 🔴 P0 #1: approve/reject/link guarded ONLY by JwtAuthGuard — any authed user can approve their own claim; 🔨 AdminGuard on approve/reject + StewardGuard on link BEFORE this UI ships. 🔨 evidence string → evidenceUrls[] via uploads.service presign (S) · NEW sparse fields claimMode (term_bound/transfer_bound) + roleType (elected/appointed) · {status,createdAt} index on official_claims (none today) · C7 already-claimed ConflictException → citizen copy "already verified · report an error".
Implements D9 posts AS THE SEAT: composer header "Broadcasting as: MLA · Marhaura", feed display name = "{Role} · {Constituency}" + verified badge, personal name only in badge tap-through; carrier authorId = the linked account (moderation chain intact). 🔨 IMPLEMENTATION = Option 1 labeled post, source OFFICIAL_BROADCAST, regionIds = constituency VCM villages fan-out into place feeds (reuses the issue pattern) + Option 3 rider SmartNotif push to constituency residents; 🚫 the S8 channel mechanism (L–XL, wrong shape). D10 reach "approx 8,400 residents" = VCM village POPULATION, never user count (privacy + primaryLocation sparsity); appointed/district = opt-in subscribe only (⚠️). D11 ⚠️ caps as honest framing — "2 of 3 left this week" via cooldown service (OFFICIAL_BROADCAST_{tier}); emergency label bypass 1/rolling-7d, abuse → claim review. D12 ⚠️ MCC: MUTE elected (statewide on the S7 MCC flag, "resume after results") / LABEL appointed "operational, not campaign". D13 ⚠️ comments OPEN (closed = press release, not civic) — official can Report→steward + pin ONE Clarification; 🚫 delete/close, 🚫 auto-classification. E16 footer "Sent by the verified office of {Name}, {Role}, {Constituency}". C8 hacked-account defense: 3 resident flags/1h → auto-HOLD ("Under review", not deleted); 🔨 Official.broadcastFrozen sparse admin-only freeze; 🚫 auto blast reversal. CMS broadcast audit log rides the admin queue build.
Implements F17 the inbox (the S7 I29 boundary, built here): 🔨 GET /officials/:id/desk/issues — verifiedCivic OR delegate; default OPEN → highest Samarthan → oldest within tier; filters status/category; cursor 20. Status actions IN-ROW (3-tap workflow: open desk → tap status → done); S7-B5 proof gate on RESOLVED — note + photo, individual only, no bulk bypass; "Awaiting proof" + "Awaiting citizen confirmation" (S7-B6) surface as first-class buckets. B5 linked tier (staffer before the official verifies): READ + IN REVIEW only — the RESOLVED action and broadcast stay locked to claimed. F18 empty desk = share-the-page CTA (the acquisition loop continues from zero); weekly digest EMAIL — staffers check email, not the app; realtime in-app pings only for new-issue + dispute. 🔨 indexes: {officialId,likeCount,createdAt} for the Samarthan sort + S7's still-missing {placeId,status,createdAt} & {placeId,reporterId} · B6 canUpdateIssueStatus checks delegates[] · desk reuses S11 skeleton/lazy-avatar 2G kit. Events: desk_issue_actioned{action,hasProof} · broadcast_first_send. METRIC (both lenses): claimed officials with ≥1 status action within 30d of claim ≥40% — <20% means trophy-only.
Marhaura hasn't filed issues on Nyburs yet. Share your office page — issues arrive at this desk, not your WhatsApp.
Share the office pageImplements G19 FOLLOW THE SEAT, not the person — the follower base survives elections, and the rollover push to a seat's whole following is the highest-value re-engagement notification in the module. G20 rollover mechanics: old claim → status=archived + archivedReason/archivedAt (🔨 NEW enum/fields on OfficialClaim); old Official resets claim fields (claimStatus=dataOnly, verifiedCivic=false, unset linkedUserId) — atomic, new current BEFORE old former (kills the 404 window the data lens flagged); the new official starts dataOnly with a fresh claim (NO inheritance, claimMode=term_bound forces re-verify); former official: 30-day read-only desk grace (⚠️) then removed, S12 banner removed, profile chip "Former MLA (2019–2024)" stays. 🔨 G20 frozen authorityRef.label re-hydrate via officialId at READ time — S7-A4 is STILL UNBUILT, old issues show the stale name until it ships. G21 appointed transfer: self-report or community flag → admin → archive (reason=transfer); the seat stays dataOnly (a transferred DM is not "former" — the seat persists); 🚫 inactivity auto-archive. B6 ⚠️ delegation within CLAIMED: principal designates ≤2 delegates (PA/secretary); every delegate action labeled "via {office} team" in statusHistory + broadcasts; caps = RESOLVED + IN REVIEW + broadcast; 🚫 approve-other/unclaim. 🔨 Official.delegates[] (max 2) + IssueStatusEntry.actorType (official|staffer|steward) sparse fields. Events: election_rollover_notif_tapped · seat_followed{source} · delegate_added.
DECISIONS #7/#8: ONE language per screen via i18n — this frame renders S13·A's claimed state end-to-end in hi. Seat name विधायक · मरहौरा, badge सत्यापित, party chip राजद, jurisdiction "47 गाँवों को कवर करता है · आपके गाँव भेल्दी को कवर करता है", CTA सीट को फ़ॉलो करें, responsiveness trio "खुली समस्याएँ / हल हुईं / पहली प्रतिक्रिया औसत", broadcast footer "रमेश सिंह, विधायक, मरहौरा के सत्यापित कार्यालय द्वारा भेजा गया", past holders "पिछले पदधारी". S8-E19's hi bans hold here: "broadcast" renders as घोषणा, never transliterated. i18n ruling: ALL claim/broadcast/responsiveness strings are i18n keys from day one — this is the module's highest-stakes legal-adjacent copy (12 locales ship per DECISIONS #13 build amendment). Numerals, dates, the handle and the brand "Nyburs" stay Latin. Devanagari line metrics run taller than Latin — the dense modifier absorbs it; layout, components and ordering identical to S13·A.
Journey position: S13 = the civic apex of the place graph — citizens reach the authority page from S4 directory rows and S7 issue authorityRefs; officials reach the desk from the claim. DECISIONS #14 is the spine: office page = wiki entity (community-edited publish-then-patrol, survives elections, occupant = a field) · person = normal account · claim = verified term-bound link unlocking badge + S12 banner + S7 inbox/status power + jurisdiction broadcast + the edit-protection flip (claimed page facts → steward-reviewed PENDING, including the official's own corrections — the page stays the community's record). Verification is admin-reviewed, never automatic. App-only / build-phase (cited in captions, not designed elements): 🔴 P0 #1 AdminGuard on claim approve/reject + StewardGuard on link — claim.controller.ts is JwtAuthGuard-only today, LIVE + exploitable on dev; ships BEFORE any claim UI · H the app has ZERO authority-page screens today (API-ready) — S13 is a full app build, reusing the S11 pushed-page skeleton + 2G kit · schema deltas (all sparse/additive): OfficialClaim.claimMode (term_bound/transfer_bound) + roleType (elected/appointed, backfill 283 seed = elected) + status=archived + archivedReason/archivedAt + evidenceUrls[] (via uploads.service presign) + additionalEvidence[]; Official.delegates[] (max 2) + broadcastFrozen; IssueStatusEntry.actorType (official|staffer|steward) · indexes: official_claims {status,createdAt} (none today) · UNIQUE sparse linkedUserId (today indexed but NOT unique — two officials could share a linked user, C8) · desk {officialId,likeCount,createdAt} for the Samarthan sort + S7's missing {placeId,status,createdAt} & {placeId,reporterId} · past-holders {jurisdictionId,roleKey,status} + findFormerOfficialsByJurisdiction · broadcast = Option 1 + 3: labeled post source OFFICIAL_BROADCAST, regionIds = constituency VCM fan-out (reuses the issue pattern) + SmartNotif rider push; caps via cooldown service OFFICIAL_BROADCAST_{tier}; 🚫 the S8 channel mechanism · E14 responsiveness cache: avgDays from statusHistory first transition where byUserId==linkedUserId, cron → OfficialResponsivenessCache (S) · G20 frozen authorityRef.label re-hydrate via officialId at READ time (S7-A4 still unbuilt — old issues show the stale name until it ships) + atomic rollover (new current BEFORE old former) · C7 lookalike-username fuzzy match vs officials seed >0.8 → human review; civic-title tokens in usernames → 7-day seasoning · 12-month "still serving?" one-tap ping (A3) · admin review queue = net-new CMS page (M) with tier SLAs + broadcast audit log. ⚠️ Owner-veto window (design-frozen until confirmed): MCC mute-elected (D12) · comments can't-close (D13) · broadcast cap numbers + emergency override (D11) · delegate max-2 (B6) · appointed district-broadcast opt-in (D10) · former 30-day grace (G20) · re-apply 7-day cooldown (A4) · staffer-as-claimant path (B5). i18n: every claim/broadcast/responsiveness string is a key from day one (highest-stakes legal-adjacent copy; hi = the reference, 12 locales ship per the build amendment). Events: claim_flow_started · claim_submitted{tier,claimerType} · claim_approved/rejected{reason} · broadcast_sent{roleType,reach} · broadcast_first_send · broadcast_flagged · desk_issue_actioned{action,hasProof} · authority_page_viewed{claimStatus} · seat_followed{source} · delegate_added · election_rollover_notif_tapped · invite_mla_cta_tapped. METRIC: claimed officials with ≥1 status action within 30d of claim ≥40% by Q1/M6 (<20% = trophy-only); secondary: broadcast within 14d of claim ≥60%.