Skip to content

feat(people): People area (persons, members, admins, teams, census, linking, Capo Admin, My area) - #97

Draft
viganogabriele wants to merge 26 commits into
mainfrom
claude/eager-mayer-bsom7v
Draft

viganogabriele wants to merge 26 commits into
mainfrom
claude/eager-mayer-bsom7v

Conversation

@viganogabriele

@viganogabriele viganogabriele commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Builds the People area designed in the prototype. Each person has one record with independent Member, Admin and Team roles. The area also covers the census, account linking, Capo Admin and My area.

Scope

  • Dashboard only. @polinetwork/backend has no People procedures yet.
    • The dashboard codes against the typed contract in src/features/people/people.contract.ts (documented in docs/people/contract.md) and parses every response with it.
    • Agent mode serves seeded sample data from people.fake.server.ts.
    • Outside agent mode the area stays hidden until the IdP grants the new people:* permissions.
  • Restricted access. people:self lets volunteers without admin:access into My area. It also opens Capo Admin or their own team when their record gives them that role. Every other People procedure requires admin:access.

What's in it

People service

  • Overview: role, team, campus, account and census filters.
  • Person record:
    • all cards and the activity log;
    • account actions: invite, revoke, link, unlink;
    • HR notes and census answers;
    • member, admin and team actions.
  • Member register: add member, change status, change number, CSV export with a confirmation first.
  • Admins:
    • priority columns and four quick filters;
    • end admin role and set the Capo Admin of reference;
    • appoint, change or end a Capo Admin (requires people:appoint).
  • Teams: index and team pages. Add to team, edit team fields, end membership. A Team Lead sees only their own team.
  • Census: campaign list and campaign page, reminders, close campaign. A 5-step import wizard skips steps with nothing to settle.
  • Linking: Needs review queue with resolve and dismiss, plus pending invites. The review count shows in the side panel.

Telegram › Capo Admin

  • My groups, requests to join (approve / decline), and the admins of those groups.

Account › My area

  • Census card pre-filled from last year.
  • Roles, contacts, profile with "Request a correction", consents, team roster.
  • First sign-in "Is this you?" match and "Ask to be added".

Shared changes

  • FormDialog gains submitTone="danger".
  • The shell takes a second streamed count (needsReview).
  • Sign-in & security now uses its section title.
  • New dev env vars: AGENT_PERMISSIONS and AGENT_PERSON, to preview other roles.
  • Narrow-screen fixes, which also apply to the Telegram user detail:
    • RecordHeader and InlineAlert: below 760px their actions move to their own line.
    • KeyValueList: in containers narrower than 352px the key sits above its value.
    • SectionCard: the header action wraps under the title instead of squeezing it.

Security review (Diubii)

All eight threads are addressed. Each rule is written in docs/people/contract.md → "Backend rules", and tests/people-access.test.mjs covers the access rules.

  • Capo Admin requests: approve and decline reach only requests in the caller's own Capo Admin scope.
  • Unconfirmed match: until the account owner confirms the match, the account acts as a person with no record.
  • CSV export: cells that spreadsheets would run as formulas are neutralized.
  • Team Leads: ending a lead's membership requires people:appoint.
  • admin:access: People procedures outside self-service require it.
  • Input limits: input schemas bound every record, list and string. Telegram usernames follow Telegram's rules. The census import has content checks for the backend.
  • Member details: number, status, fee and Microsoft 365 address require people:members:read, except on the person's own record. Everyone else sees only isMember.

Contract additions beyond the original brief

  • people.admins.setCapoOfReference
  • people.teams.candidates: names only, so Team Leads can add people without people:read.
  • isMember on every person row.

Visual audit corrections

Tables and controls

  • Table chips in Admins, Census and Overview stay within their cells. Short labels stay readable next to long ones, and the extra items open in the custom popover.
  • People date fields use a custom calendar with custom month and year selectors, on desktop and mobile. Grant date/time controls use the existing calendar and time wheels on touch devices too.
  • The shared calendar responds to arrow keys again. Selecting a day or pressing Escape returns focus to the field.
  • The calendar grid fits 320px screens, including six-row months, with touch targets above 36px.
  • Every bottom-navigation label stays readable on narrow screens: WhatsApp shows as WA below 360px. The bottom safe area sits below the bar instead of compressing it.

Phone layouts

  • Person and team headers and the record alerts no longer squeeze titles at 320px.
  • The import stepper stays on one line.
  • Member register statuses become a Status menu.
  • Capo Admin request buttons and consent badges wrap under their text.
  • Census buttons stack with Confirm on top.

Copy and consistency

  • Team cards put missing census answers on their own line.
  • Admin cards say "No groups".
  • Team tables show the course under every name.
  • The My area roster shows "(you)" without breaking the avatar initials.

Verified

  • pnpm check, pnpm typecheck, pnpm test and pnpm build pass. Tests: 48 passed, 3 optional integration tests skipped. The 25 existing test-registration lint warnings are unchanged.
  • Every People page, Capo Admin and My area at 1440, 390 and 320px, light and dark. An automated pass reports no horizontal page scroll and no clipped text.
  • Viewers checked:
    • full administrator;
    • Capo Admin (P-0098) and Team Lead (P-0034) with only people:self;
    • unconfirmed match (match:P-0131);
    • no record (none).
  • No-result empty states, loading skeletons and the main dialogs: Add person, Add member, Add to team, Import, census form, Request a correction, Ask to be added.
  • Custom date picker at 320x568, 320x740, 390x844 and desktop, light and dark. Month and year selection, six-row months, arrow keys, Enter, Escape and focus restoration work. The picker renders no native date or select control.
  • At 320px every bottom-nav label fits; at 390px the full WhatsApp label returns.
  • A regression test checks picker date values across time zones and rejects impossible days.

Not verified

  • Empty states with no data at all. The sample data always has rows; the no-results states were checked instead.
  • The signed-in flow without agent mode needs a development IdP client.
  • The backend still has to implement the contract.

Handoff

docs/people/ has:

  • the brief
  • the decisions
  • the contract
  • plan.md, with the per-phase checklist and progress log

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn

Brief, settled decisions, backend contract and phased plan for the People area,
plus the prototype source as a structure and copy reference. The prototype is
excluded from format and lint checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
@coderabbitai

coderabbitai Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

claude and others added 14 commits October 14, 2026 12:17
- New permissions: people:self, people:read, people:write, people:members:read,
  people:members:write, people:appoint.
- people:self alone admits a user to the dashboard; adminMiddleware now requires
  admin:access explicitly so existing reads stay closed to them.
- People middlewares for each scope, and dev-only AGENT_PERMISSIONS and
  AGENT_PERSON to preview restricted roles in agent mode.
- Typed people.* contract (zod) and the agent-mode sample data ported from the
  prototype.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
- People service (Overview, Member register, Admins, Teams, Census, Linking),
  Telegram › Capo Admin and Account › My area / Sign-in & security.
- Sections open by permission, admin:access, or a role on the person's own
  record (Capo Admin, Team Lead); /dashboard redirects pages a user can't open.
- PeopleApi with an agent-mode implementation over the sample data and a
  backend adapter that parses every response with the contract.
- Placeholder routes for every People page; pages land in the next phases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Overview table with role, team, campus, account and census filters, and the
person record with its cards and activity. Adds Add person / Edit profile,
invite, revoke and unlink confirmations, Link account, HR notes and census
answers dialogs. On mobile the Overview cards show a labelled line of role
chips and the account status instead of bare column values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Status segments, fee filter, search and sort by number, name or admission
date. Add member, Change status and Change member number dialogs, and an
export that confirms before downloading the CSV. The person record gets the
Member card menu, and its "declared member" alert opens Add member in the
register with the person preselected. People dialogs now reload the page
before closing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Admins table with Active / Former scope, campus and year filters and four
quick filters, columns that appear as the table widens and an Open in
Telegram row action. End admin role asks for a reason, and the Direttivo can
appoint, change or end a Capo Admin and set an admin's Capo Admin of
reference, from the list or the record's Admin card. FormDialog gains a
danger submit tone for destructive forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Teams index with one card per team and every active member in one table;
team pages with Active / Former, columns for the team's own fields and
contacts for readers and the team's Lead. Add to team, Edit team fields
and End membership from the lists and the person record, which also gets an
"Add role" menu. A Team Lead without people:read lands on their own team.
Adds the people.teams.candidates query so Leads can pick a person without
reading every record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Census list with a progress meter per campaign, and a campaign page with the
answer breakdown, segments and role filter, per-person and bulk reminders,
View answers and Close campaign. Import responses uploads the Forms export
and walks through Review, Duplicates, Telegram and Confirm, skipping steps
with nothing to settle; nothing is saved before the last step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
People › Linking lists the sign-ins the backend couldn't link (Dismiss, or
Resolve with the resolutions that fit each kind and a required reason) and
the pending invites with Resend and Revoke. The Needs review count streams
into the section panel like the open reports count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Telegram › Capo Admin shows the groups of the signed-in Capo Admin's
course with the ones that have no admins flagged, the requests to join
them with Approve and Decline, and the admins of those groups with their
phone and Telegram and links to message them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Account › My area shows the signed-in person's census (pre-filled from last
year, with Confirm and I'm leaving), roles, people to contact, profile with
Request a correction, consents with Revoke and their team roster. A first
sign-in asks "Is this you?" with the matched record; a sign-in with no record
can ask to be added. The Sign-in & security page now uses its section title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Adds People page notes to docs/design.md, a People paragraph to the README
and the People architecture line to the agent instructions, removes the
page placeholder now that every route is built, and records what is still
unverified in docs/people/plan.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Resolves the section pills conflict in panel.tsx: keeps main's scroll
observer for the current pill and this branch's Needs review count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Constrain table chips, use custom date pickers on every viewport and restore
calendar keyboard focus. Resolve the academic year centrally and read census
deadlines from campaign data.

Support all led teams and active rosters with scoped deep links. Gate People
behind explicit backend readiness, validate real calendar dates and stop
closed sample campaigns from accepting answers. Update the backend contract
and rollout documentation, add regression coverage and clear lint warnings.
Restore the original server configuration, authorization, route loaders and
People contract/data behavior, as requested. Keep custom date fields, table
chip constraints and calendar keyboard focus.

Fit the calendar to narrow screens and retain readable mobile navigation
labels. Reserve the device safe area below the bottom bar. Limit added tests
to calendar values used by the UI picker.

@Diubii Diubii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review di sicurezza dell'area People, fatta con Claude Code. Il backend people.* non esiste ancora, quindi oggi nessun dato reale è esposto. Però people.fake.server.ts è il riferimento da cui partirà chi implementa il backend, e docs/people/contract.md non fissa alcune di queste regole. Conviene sistemarle prima che vengano copiate.

Come ho testato: ho eseguito il fake con utenti diversi (volontario con solo people:self, utente senza record, Team Lead, Capo Admin, match non confermato, people:read senza people:members:read). I casi segnati come verificato li ho riprodotti davvero, gli altri vengono dalla lettura del codice. pnpm test passa (43 ok, 3 skipped).

# Gravità Problema
1 🔴 Alta Chiunque abbia people:self approva o rifiuta qualsiasi richiesta di diventare admin di un gruppo Telegram
2 🟠 Media Un match non confermato al primo accesso eredita i poteri e i dati del record
3 🟠 Media Formula injection nell'export CSV dei membri
4 🟡 Bassa Un Team Lead può chiudere un ruolo da lead senza people:appoint
5 🟡 Bassa I dati da socio sono visibili con il solo people:read
6 🟡 Bassa I middleware People non richiedono admin:access, mentre l'interfaccia sì
7 🟡 Bassa Input senza limiti di dimensione, import controllato solo per estensione

Ho controllato e sono a posto: i link t.me/wa.me non possono cambiare dominio, non c'è dangerouslySetInnerHTML, l'agent mode funziona solo in sviluppo, i token non finiscono nel route context, l'export è in POST con no-store, l'upload in FormData passa per httpLink.

Dettagli e proposte di correzione nei commenti sulle righe.

Comment thread src/features/people/people.fake.server.ts
Comment thread src/features/people/people.fake.server.ts
Comment thread src/features/people/people.fake.server.ts Outdated
Comment thread src/features/people/people.fake.server.ts
Comment thread src/features/people/people.fake.server.ts Outdated
Comment thread src/server/auth.middleware.ts
Comment thread src/features/people/people.contract.ts
Comment thread src/features/people/people-census.functions.ts
Diubii and others added 11 commits October 15, 2026 18:09
- Capo Admin approve/decline only reach requests in the caller's own scope.
- An unconfirmed or rejected match acts as a person with no record; confirmMatch
  refuses after a rejection.
- The member export neutralizes cells that spreadsheets would run as formulas.
- Ending a lead's team membership needs people:appoint, and the menus hide it.
- People procedures outside self-service need admin:access in the dashboard too.
- Input schemas bound team fields, import decisions, census lists, correction
  fields and Telegram usernames; the forms validate usernames the same way.
- docs/people/contract.md lists these as backend rules, plus content checks for
  the census import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Runs the agent-mode People API under Node and checks the rules bd21599 added:
only the group's Capo Admin reaches a join request, an unconfirmed match gets
none of the record's powers, ending a Lead role needs people:appoint, and the
member export keeps every cell as text. Each test fails on the code before
bd21599. endTeamMembership's comment now states the people:appoint rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without people:members:read, people rows and records carry member: null and
only the new isMember flag; the viewer's own record keeps its details. The
person page shows a plain Member card in that case, Overview shows "Yes" in
place of the number, and the contract documents the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Below the shell breakpoint RecordHeader actions and InlineAlert actions take
their own line, so a 320px screen no longer squeezes the title, meta or
message into a narrow column. Team cards put the missing census answers on
their own line instead of wrapping mid-phrase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Below the shell breakpoint only the current step shows its label, so the five
steps no longer wrap onto a second line that starts with a connector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
- KeyValueList puts each key above its value in containers narrower than
  352px, so emails and course names get the full card width on phones.
- SectionCard wraps its header action under the title instead of squeezing
  the title onto two lines.
- People rows move their badges and buttons under the text when both don't
  fit, so titles like "Photo and video" no longer break word by word.
- The My area team roster marks the viewer with a muted "(you)" that stays
  out of the avatar initials.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Below the shell breakpoint the five member statuses become a Status menu
beside the fee filter, like the census Answer filter, instead of a segmented
control clipped mid-word. Admin cards say "No groups" rather than "0 groups".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
The name and date keep at least 160px, so on narrow cards Decline and Approve
move to their own line instead of breaking the date over two lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Member emails now need people:members:read, so a Team Lead saw their own
Microsoft 365 address but the course for everyone else. Team tables show the
course for every row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Below the shell breakpoint Confirm, I'm leaving and Cancel stack full width
with Confirm on top, like dialog footers, instead of leaving Confirm alone on
a second line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ti91VKqKpduLAF376TunMn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants