Skip to content

feat: PersonDetail slackHandle + email (self/staff) — screen-gaps phase 2 (refs #83)#103

Merged
themightychris merged 5 commits into
mainfrom
feat/screen-gaps-phase2
May 30, 2026
Merged

feat: PersonDetail slackHandle + email (self/staff) — screen-gaps phase 2 (refs #83)#103
themightychris merged 5 commits into
mainfrom
feat/screen-gaps-phase2

Conversation

@themightychris
Copy link
Copy Markdown
Member

Summary

Second phase of #83 — backend serializer + screen work to surface the two PersonDetail fields the audit flagged.

  • `slackHandle` (public Person field, already in schema) — exposed to all callers, rendered as a "DM on Slack" link in the new Contact sidebar.
  • `email` (lives in PrivateProfile) — gated to self + staff per specs/screens/person-detail.md authorization table, rendered as a mailto link.

`PersonService.get` becomes `async` so it can do the conditional private-store read. Anonymous reads stay free of any private-store touch.

Test plan

  • 3 new API tests: `slackHandle` visible to anonymous (public field), `email` NOT visible to anonymous (PII gate), 404 unknown slug (regression check)
  • 3 new screen tests: Contact section hidden when neither field is set, Slack link href correct when handle set, mailto href correct when email set
  • 36 read-api tests pass, all SPA + shared tests green
  • `npm run type-check && npm run lint` clean

🤖 Generated with Claude Code

themightychris and others added 5 commits May 30, 2026 11:27
Backend serializer + screen work to surface slackHandle (already in
Person, public field) and email (in PrivateProfile, gated to self +
staff) on /members/:slug. PersonService.get becomes async to
incorporate the private-store read.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per specs/screens/person-detail.md authorization table: slackHandle is
a public Person field, exposed to all callers; email lives in
PrivateProfile and is gated to self + staff.

  serializer  — adds slackHandle + email to the PersonDetail shape.
                slackHandle is read from the Person record directly;
                email is supplied by the service via the new
                opts.visibleEmail field.
  service     — PersonService.get becomes async. After the existing
                in-memory work it conditionally awaits
                privateStore.getProfile(personId) when the caller is
                self or staff. Anonymous reads stay free of any
                private-store touch.
  plugin      — PersonService gets the private store in its
                constructor.
  routes      — GET /api/people/:slug and the PATCH-then-refetch site
                pick up the awaits.

Seed fixture's Jane Doe now carries `slackHandle: 'jane-doe'` so
existing tests have a public-handle value to assert against. Three
new read-api cases: slackHandle visible to anonymous, email NOT
visible to anonymous, plus the original detail-shape test still passing.

Refs #83.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renders a Contact section in the sidebar when either slackHandle or
email is present:

  - "DM on Slack" link to https://<SLACK_TEAM_HOST>/team/<slackHandle>
  - Email rendered as a mailto: link

Section is hidden entirely when neither field is set, so the sidebar
stays clean for anonymous reads of contact-less profiles. The API
client's PersonDetail type picks up the two new fields with
load-bearing JSDoc explaining the email gating.

Three new tests: section hidden when neither field set, Slack link
href correct when slackHandle set, mailto href correct when email set.

Refs #83.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 6 validation checkboxes ticked. Notes covers the seed-fixture
augmentation (slackHandle on Jane Doe), the small PersonService.get
caller surface (only 2 call sites), and the Contact-section-hides-
when-empty behavior.

Follow-ups: phase 3 (/pages/:slug content) and phase 4 (/buzz/new
form) named for later plans.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI lint caught what my local sweep missed — beforeEach was imported
but never used (afterEach handles cleanup per-test; setup is inside
each it() block via makeFetchMock).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@themightychris themightychris merged commit 4424fc0 into main May 30, 2026
1 check passed
@themightychris themightychris deleted the feat/screen-gaps-phase2 branch May 30, 2026 15:42
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.

1 participant