The roster of a place — implemented verbatim from S9-decision-sheet.md (✅ rulings only; 🚫/⏳ absent, 🔨 cited in captions). THE ruling: A1 universal safe default — a user appears ONLY when residency is VERIFIED (HOME_CONFIRMED), placeListVisible ≠ false and the profile is PUBLIC; consent disclosed at S1 (A2), self-managed from the surface (A3). B8 residents FIRST for everyone — the migrant's 5-second "is anyone from home here?" moment never opens on an MLA. DECISIONS #8: ONE language per screen — en frames + one hi-locale reference. DECISIONS #10: Seva, never karma.
Owner 2026-06-14 (UX walk): two value adds on top of the locked privacy design — a search field at the top ("Find someone from Bheldi…", reuses existing user search) so a migrant can find ONE specific person at scale, not just load-more; and a SAFE, non-PII follow signal on rows — "Active here" / "New" badge + a mutual-follow line ("Followed by Priya + 2 you know") — to lift the tab's follow KPI without breaching A6 minimalism (no gender/DOB/city/DM). · Implements B8 RESIDENTS FIRST, universal order: people → maintainers → ONE "See representatives →" link-row — officials detail rows CUT from this tab (full S4 duplication; 🚫 context-aware home/away reordering) · A3 pinned self-view row "You appear here · ⋯" ships WITH the tab — menu open in situ: Remove from this list (one tap → placeListVisible=false) + Manage followers; discoverable from the surface by a low-literacy user · A1 only VERIFIED (HOME_CONFIRMED) + placeListVisible + PUBLIC profiles render here · A6 stub anatomy LOCKED: name + @handle + avatar + follow state, nothing more (🚫 gender/DOB/current-city/DM quick-action — the roster must never become a DM-discovery pipeline) · C13 maintainer rows gain Seva + role label (🔨 karma/role missing from PlacePersonStub DTO, S) · C14 keep: load-more button (never infinite scroll on 2G), optimistic Follow + rollback (annotated row), self-row follow suppression · D15 standard person-follow only — "their posts in your timeline" is TRUE today; 🚫 place-scoped dual-follow; block-relation filter applies both ways. G metric: ≥8 follows per 100 tab opens by day 30. Tall frame = the full tab scroll in one device frame.
THE ruling of the screen in three excerpts. A2 the S1 consent line in situ — ONE quiet line under the home-check: "You'll appear on Bheldi's People tab. You can hide this anytime." (i18n set_place_people_disclosure; proportionate SPDI notice, never alarming; amends S1-spec §3) · A3 the self-view ⋯ menu expanded — Remove from this list = ONE tap → placeListVisible=false · C14 🔨 residentsRequireAuth is UNHANDLED in Flutter today — logged-out viewers get the login-prompt row; the auth-gate on resident stubs stays. Invariants (🔨 app/API-only): A1 today's residentPrivacyFilter has NO residency filter — PENDING/BACKFILL users would be falsely labeled "resident of Bheldi"; the gate = VERIFIED (HOME_CONFIRMED) + NEW settings.placeListVisible opt-out (default on post-confirm) + profileType PUBLIC · A5 SEQUENCING INVARIANT (launch-blocker order): VERIFIED filter + @Throttle + block-relation filter + S1 disclosure land BEFORE the Cohort-R backfill / worker sync ever runs — the list is empty today only because the data is dark; that's the window · A4 minor exclusion server-side when DOB present (🚫 minors ever listed) · A6 field minimalism LOCKED — block any future PR adding stub fields without opt-in gating · A7 anti-scrape @Throttle ≈30/60s/user (none today); offset pagination acceptable at launch WITH throttle, keyset cursor = post-launch hardening; onAppCount stays public (social proof, no PII).
The same tab opened by a Bheldi-born viewer working away. B8 residents lead UNIVERSALLY — no context-aware home/away reordering, the structure itself serves the migrant's 5-second moment · B9 the first ~5 resident stubs RIDE the landing/getPlace payload so faces render without the tab-mount cold call (2G reality: today's separate call = 4–12s spinner = abandoned tab; 🔨 S/M, payload-only change) · B10 the "From Bheldi" treatment IS the section — a row NEVER shows a current city: 🚫 "Lives in Delhi" chip in v1, architecturally impossible (no current-city field exists; HOME_CONFIRMED overwrites the worker-detected value) AND unsafe as a default (migration disclosure = dossier); ⏳ S12: profile-scope OPT-IN chip, state/region granularity max — "lives away" on the roster at most, never a city without explicit consent · D15 the Follow promise is TRUE for the migrant — posts from home land in his timeline; this tab exists to seed the local social graph (E events: place_people_tab_open · place_people_follow_tapped{fromSection, isFirstFollow}).
Implements C12 the data-dark HONEST empty state: "No one from Bheldi is here yet" + "As people join, they'll appear here" — 🚫 "Be the first" false invitation while the primaryLocation backfill is OFF: the list is empty because the data is dark, not because neighbours don't exist, and an invitation the tap can't honour burns trust (A5: by the time Cohort-R turns the data on, the privacy gates are already live) · C14 pull-to-refresh error path gets explicit copy + RETRY (+ i18n — the tab has ZERO keys today, C11 🔨) · 2G kit: skeleton ROWS mirror the real stub geometry (circular avatar + two lines + follow pill) — never a lone spinner. 🔨 avatar imgproxy width ≤120px verify · onAppCount 60–300s cache at scale · E place_people_login_prompt_shown fires on the S9·B auth row, not here.
As people join, they'll appear here.
DECISIONS #8: ONE language per screen via i18n — this frame renders S9·A end-to-end in hi. C11 🔨 the tab has ZERO i18n keys today — every string here is a NEW key: residents = "गाँव के लोग", reps link = "प्रतिनिधि देखें →" · maintainer copy is SEVA-framed — the word "steward" stays banned in contributor-facing copy (S5-B13) and "karma" never renders (DECISIONS #10): section "पेज की सेवा में", role labels "अग्रणी · ★ 1,240 सेवा" / "योगदानकर्ता · ★ 410 सेवा" (role display names = separate per-locale task) · follow vocabulary absorbed (D15): फ़ॉलो करें / फ़ॉलोइंग · self-row "आप यहाँ दिखते हैं", menu "इस सूची से हटाएँ" + "फ़ॉलोअर्स प्रबंधित करें" · load-more "और लोग देखें". Numerals, @handles and the brand "Nyburs" stay Latin. Layout, components and ordering identical to S9·A.
S9 People tab — app/API-only rulings NOT depictable in a static mockup (spec ledger, cited in captions): A1 🔨 residentPrivacyFilter has NO residency filter today (PENDING/BACKFILL users would be falsely listed) — add VERIFIED/HOME_CONFIRMED gate + NEW settings.placeListVisible field (default on at S1 confirm) + profileType PUBLIC check · A4 🔨 server-side minor exclusion when DOB present (additional gate, DOB optional) · A5 🔨 SEQUENCING INVARIANT: VERIFIED filter + @Throttle + block-relation filter + S1 disclosure ship BEFORE the Cohort-R primaryLocation backfill / worker sync ever runs · A7 🔨 @Throttle ≈30/60s/user on the endpoint (none today); keyset cursor = post-launch hardening; onAppCount stays public · B9 🔨 first ~5 resident stubs ride the landing/getPlace payload (S/M) · B10 no current-city field exists — HOME_CONFIRMED overwrites worker-detected; ⏳ S12 profile-scope opt-in chip, state granularity max · C13 🔨 karma/role added to PlacePersonStub DTO (S) — UI renders Seva, never "karma" (DECISIONS #10) · C14 🔨 residentsRequireAuth unhandled in Flutter (render the login-prompt row); avatar imgproxy width ≤120px verify; onAppCount 60–300s cache at scale; optimistic Follow + rollback toast · D15 🔨 block-relation filter applies to the roster BOTH ways · C11 🔨 i18n debt: the tab has ZERO keys today — full key set ships with the tab, hi = reference locale, 12 locales via the normal pipeline · E events: place_people_tab_open · place_people_follow_tapped{fromSection, isFirstFollow} · place_people_self_row_managed · place_people_login_prompt_shown. METRIC: ≥8 follows per 100 tab opens by day 30 — the tab exists to seed the local social graph; near-zero = structure failed.