governance / governance (pull_request) Successful in 15s
Why: RDM now anchors annotations on nodeId + textStart + textEnd + textHash (spec 06 §2) and acceptance 10 §5 requires accurate comment ranges. The workspace only sent whole-node annotations and ignored quote/needsReanchor. - Selecting text inside one node's title in the reader view records a code-point range (Array.from indices; surrogate pairs and ZWJ emoji are counted as runes, half-pair endpoints widen) and sends textStart/textEnd plus quote, so RDM rejects rather than misplaces a drifted range. - No selection keeps whole-node behavior; replies never carry range params; textHash/range are never sent; a range is dropped if it belongs to another node. - Selections spanning several nodes or containing no node text record no range and show a notice that the whole node will be annotated; the composer always shows the scope with a "whole node" escape. - Annotation items show the server quote and a visible needs-reanchor state; review_anchor_invalid: errors map to readable Chinese text. - Reader titles become role=button spans because browsers do not allow text selection inside <button>. Evidence: npm test 40 files / 486 tests pass (30 new in test/review-annotation-anchor.test.ts); npm run build exit 0; node scripts/check-knip.mjs "50 <= 51"; design-guard 7/7; playwright --list compiles 13 tests; headless Chromium check of the selection reader (single, multi-node, triple-click clamp, emoji offsets). Impact: document review UI only; base-gate decision source anchoring is unchanged. Offsets are computed on the displayed node title (no frozen-text read endpoint exists), guarded by quote equality on the server. Refs: unified renderer plan Task 13; phase1 F2; spec 06 §2 Co-authored-by: Cursor <cursoragent@cursor.com>