Decision card: presence faces and tooltips #232
- MA
**Stacked on #231** (base `design/decision-card-restyle`). Retarget to `main` after #231 merges.
## Summary
The `@handle` pills in the decision card header become a face stack with design-system tooltips.
- `QuestionView` gains `renderPeople(people)`; without it the old pills still render. The editor host supplies `PresenceFaces`: 24px `Face` (`ring="page"`), overlapping by 0.375rem, deduped by handle (case-insensitive), max 3 then `+N` (`--text-xs` 600 tertiary), centred on the 1.5rem mark row. - `IconTooltip` now works on any element with `data-tooltip` (not only buttons); `data-tooltip-verbatim` skips capitalisation so handles keep their casing. Faces tooltip the handle, `+N` lists the hidden handles, comma separated. - `Face`: radius scales with size (18px and under `--radius-sm`, larger `--radius-md`), and a new `titled={false}` prop suppresses the native `title` where a tooltip is used (no DOM stripping). - Room header presence faces get the same tooltip (trivial, same prop).
## Decisions made
- The group is labelled "Editing this question: a, b, c" and lists every person including those behind `+N`; the `+N` itself is not a separate a11y node beyond its tooltip. - Dedupe is case-insensitive and keeps the first spelling. - `+N` counts unique hidden handles, not connections. - Radius threshold: `size <= 18` is small. Implemented as a static class map (`FACE_RADIUS_CLASS`) to avoid a new dynamic design-contract exception; `sourceHash` renewed for `face.tsx`, `question-view.tsx`, `questionnaire.tsx` (no new dynamic data flow). - Tooltip pure logic (`tooltipText`) is unit tested; hover/DOM behaviour is in Playwright since `bun test` has no DOM. - Existing gap, not fixed here: no production client currently sends `question:presence` with a question (only clears it), so faces only appear when such frames arrive. The e2e injects peer presence frames through `routeWebSocket`. Wiring focus-to-presence is a separate change.
## Before / after (2x)
| | Before | After | | --- | --- | --- | | 1 person |  |  | | 5 people |  |  | | `+N` tooltip | |  |
## Checks
- `bun run fix`, `bun test` (1741 pass, 2 skip, 0 fail), `bun run types` (exit 0), `bun run ci` (exit 0). - E2E (`--project=fixtures`): `sidecar.e2e.ts` (38 passed, including the new face tooltip test), plus `responsive-decisions`, `collab`, `toolbar` (40 passed in the combined run). One `responsive-decisions` 390x844 run showed a transient strict-mode duplicate card that did not reproduce on rerun.
馃 Generated with [Claude Code](https://claude.com/claude-code)
- MA
Review and fixes (pushed to this branch).
**Gap closed: faces now appear in real sessions.** `QuestionView` gains `onQuestionFocus(question | undefined)`: fired on focus within the current question (choosing an option or typing both focus a control), cleared on blur out of the question, question change, and unmount. `QuestionnaireController.focusQuestion` feeds a pure `FocusReporter` that sends `question:presence` once per change (no per-keystroke traffic), waits until the draft is open, resends after a reconnect, and clears on submit, cancel and stop. The existing protocol frame and server relay were sufficient; no protocol change.
**Tests.** Unit: `FocusReporter` dedupe/online/reconnect, controller presence frames. E2E face test now uses four real browser contexts (Bo, cy, Di, ed) focusing the question, asserts three faces + `+1`, verbatim tooltips, no native title, clearing on blur and on a peer closing. Flake fix: the tooltip hides on scroll and scroll events arrive a frame late, so the test settles the page (scrollIntoView + two frames) and arrives with stepped mouse movement; passed 4/4 repeats plus full runs.
**Review notes, no change needed.** Tooltip is supplementary: the group's aria-label names everyone (including those behind +N) and faces keep img names; non-button tooltip targets are not focusable, which is acceptable as the information is not tooltip-only. Tokens/static class maps OK. Design-contract hashes renewed (no new dynamic data flow).
Checks: fix, test (1745 pass), types, ci all green; e2e fixtures sidecar + responsive-decisions + collab: 41 passed after merging #231. Unrelated: `responsive-decisions` 320x568 once hit a transient strict-mode duplicate card (two step panels mid-swap), not reproduced on rerun.
MaggieAppletonreferenced this in Resolved decisions in prose: margin marker, hover preview, pinned popover #234last month on GitHub
MaggieAppletonadded-to-stacklast month on GitHub
MaggieAppletonautomatic-base-change-succeededlast month on GitHub
MaggieAppletonhead-ref-force-pushed 5a867da285b3last month on GitHub
MaggieAppletonmerged 0bb5d634f9felast month on GitHub
MaggieAppletonhead-ref-deletedlast month on GitHub