githubnext/chopinPublic

Let's compose rich plans together

Decision card: presence faces and tooltips #232

MergedMaggieAppletonmerged 6 commits intomainfromrefs/pull/232/head
  1. 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 | ![](https://github.com/githubnext/chopin/blob/design-pr-assets/pr-232/before-one.png?raw=true) | ![](https://github.com/githubnext/chopin/blob/design-pr-assets/pr-232/after-one.png?raw=true) | | 5 people | ![](https://github.com/githubnext/chopin/blob/design-pr-assets/pr-232/before-five.png?raw=true) | ![](https://github.com/githubnext/chopin/blob/design-pr-assets/pr-232/after-five.png?raw=true) | | `+N` tooltip | | ![](https://github.com/githubnext/chopin/blob/design-pr-assets/pr-232/after-tooltip.png?raw=true) |

    ## 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)

  2. MA
    Author

    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.

  3. MaggieAppletonreferenced this in Resolved decisions in prose: margin marker, hover preview, pinned popover #234last month on GitHub

  4. MaggieAppletonadded-to-stacklast month on GitHub

  5. MaggieAppletonautomatic-base-change-succeededlast month on GitHub

  6. MaggieAppletonhead-ref-force-pushed 5a867da285b3last month on GitHub

  7. MaggieAppletonmerged 0bb5d634f9felast month on GitHub

  8. MaggieAppletonclosedlast month on GitHub

  9. MaggieAppletonhead-ref-deletedlast month on GitHub