Skip to content

Decision card: presence faces and tooltips - #232

Open
MaggieAppleton wants to merge 7 commits into
design/decision-card-restylefrom
design/decision-card-presence
Open

MaggieAppleton wants to merge 7 commits into
design/decision-card-restylefrom
design/decision-card-presence

Conversation

@MaggieAppleton

@MaggieAppleton MaggieAppleton commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

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

MaggieAppleton and others added 7 commits September 30, 2026 21:28
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lled by scroll

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@MaggieAppleton

Copy link
Copy Markdown
Collaborator 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.

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