cursors: anchor to content when container has a CSS transform#116
Conversation
Reads the cursor container's live CSS transform matrix and stores cursor coordinates in container-local space, so collaborators with different pan/zoom agree on which word/element a cursor is hovering. Default (document.body, no transform) is unchanged. Used by the fridge to fix cursors not following words across users with different pinch-zoom and pan states. Also: require a changeset for any change under packages/.
|
@claude review |
|
Claude finished @spencerc99's task —— View job Code Review: cursors anchor to content when container has CSS transformTodo List:
Overall AssessmentThis is an excellent PR that elegantly solves cursor positioning for transformed containers. The implementation is mathematically sound, well-tested, and maintains backward compatibility. ✅ Strengths1. Sound Mathematical ImplementationThe coordinate transformation logic in // Inverse transform: undo only the linear (a,b,c,d) part
const det = a * d - b * c;
if (det === 0) return { x: 0, y: 0 };
const dx = clientX - m.rect.left;
const dy = clientY - m.rect.top;
return {
x: (d * dx - c * dy) / det,
y: (a * dy - b * dx) / det,
};The key insight is that 2. Excellent Test CoverageThe test file
3. Backward Compatibility
4. Real-World IntegrationThe cursors: {
enabled: true,
coordinateMode: "absolute",
container: ".content", // ← Opts into the new behavior
}5. Proper Documentation
🔍 Technical Deep DiveCoordinate System DesignThe implementation creates a three-layer coordinate system:
This separation allows each user to have different pan/zoom while agreeing on content-relative cursor positions. Transform Matrix Handlingconst matrix = !t || t === "none"
? new DOMMatrixReadOnly()
: new DOMMatrixReadOnly(t);Proper handling of edge cases:
Performance Considerations
🎯 Minor Suggestions1. Error Handling EnhancementConsider adding validation for malformed transform strings: try {
const matrix = new DOMMatrixReadOnly(t);
} catch (e) {
console.warn('[playhtml] Invalid transform matrix, falling back to identity:', t);
return null;
}2. Documentation EnhancementThe comment at // The container has `transform-origin: 0 0` (the only origin we
// support; `getBoundingClientRect()` already reflects the post-
// transform top-left). To recover container-local pre-transform
// coords, subtract the rect origin and undo the matrix's scale/rotate.📋 Files Analysis
|
commit: |
Deploying playhtml with
|
| Latest commit: |
c5d5731
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://a7a01a8c.playhtml.pages.dev |
| Branch Preview URL: | https://cursor-transformed-container.playhtml.pages.dev |
Summary
Cursors now anchor to content when the cursor
containerhas its own CSS transform (pannable / zoomable canvas). The library reads the live transform matrix fromgetComputedStyle()and stores cursor coordinates in the container's local coordinate space, so two clients with different pan/zoom agree on a cursor's content position; each viewer's CSS transform then maps that position to their own viewport pixels.cursors.containeroption — no new API surface.container = document.body, no transform) is unchanged.website/fridge.tsxopts in by settingcontainer: ".content". Cursors now follow the word a user is hovering, regardless of either user's pinch-zoom or pan.CLAUDE.mdto require a changeset for any change underpackages/.The math undoes only the matrix's linear (a, b, c, d) part —
getBoundingClientRect()already absorbs the translate component, so applying the full inverse would subtract translate twice. This was the bug fixed after initial Playwright testing.Test plan
bun run -C packages/playhtml test— 156 tests pass, including 2 new tests for the transformed-container path (DOMMatrixReadOnly is stubbed because jsdom doesn't ship one).bunx tsc --noEmit -p packages/playhtml— clean.containerundefined → matrix branch skipped → original behavior preserved.