castle: a table that fits a phone - #316
Merged
Merged
Conversation
Four things from mobile play tests. A hand too big to fan is a strip: every card whole, scrolled by finger or dragged by mouse, the ends faded where there is more. A drag that went anywhere is not a tap on the card it started on. The middle says less. The counts are on the piles — the draw pile is a thing now, not a number in prose — the price is one line and only the mover's, and the last play shows for a moment then goes. A failed flip shows the card that did not play and stays longer, while the pile it brought slides into the hand one card after another. Another seat's turn is the felt's to show, not a toast's. Golf too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
muchq | 6a8aeea | Commit Preview URL Branch Preview URL |
Sep 07 2026, 04:47 PM |
Review panel findings. A draw-back lands at the same index every turn, and a node React keeps does not run its animation again — so the slide ran once per game. An arriving card's key carries a generation now, and each arrival is a fresh node. A drag on any button but the first ends in a context menu and never a click, and would have left the next tap eaten. A move without the primary button down ends the drag. The last play is one live region for the table's life, with the moment as a keyed child, so a screen reader hears a change rather than a region appearing; the finished fade hides it from the tree as well as the eye. enteredSince lives in rules.ts with the other card helpers and has its multiset test; the castle hook's turn toast has the test golf's had; the flip's animation respects reduced motion, which the phone card size's tie-break had defeated. The screenshots are out of the tree: the PR body reaches them at the commit that carried them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
A card's rank and suit sit top-left and, turned round, bottom-right, the way a real card carries them — so a card mostly under its neighbour still says what it is. The suit is the pip in the middle. The marks scale with the card's font size, so the phone's cards and the flipped one in the last-play line have the same face. That is what lets the strip overlap: each card shows a thumb's width and its index, so a hand of fourteen fits a phone with less scrolling. Every card has an edge now, since two white cards side by side read as one white shape without it, and one lying on another casts onto it. A pointed-at or tabbed-to card comes to the front; a selected one does not, so its right-hand neighbour keeps the index the next pick needs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
The PR body reaches them at the commit that carried them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
The strip is gone: the fan is the hand at every size. Its overlap is capped where a card would lose its corner index or a thumb's width, and a hand wider than the chair scrolls — by finger, or dragged by mouse — with the last card in view cut off, which is how a hand says there is more. A drag that moved nothing, on a hand that fits, is a tap. The arc is bounded at thirty degrees however many cards, so the ends of a big hand still face the player and stay inside the hand's own edges; the scroller keeps room at its sides for the lean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
The PR body reaches them at the commit that carried them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
ReviewStrong PR overall — the fan-everywhere model, corner indexes, and the pick-up explain-itself work are coherent, and the drag/tap tests are careful about the cases they can reach under jsdom. CI is green. One ship-blocker on desktop mouse, then a few real follow-ups. Verified the click bug under headless Chrome with React 18 (count stays Blocker
Should fix
Worth fixing / clarifying
Happy to re-review after the capture fix. |
Opponents' seat frames render at 0.72 zoom, layout and all, so the viewer's own hand has the phone. Their hand count, castle stacks and face-up cards still read at that size; nothing at their chairs is tapped. Screenshots for the PR ride along and come back out next. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
Pointer capture is taken once a drag has moved, not on the press: a captured press retargets its click to the hand and the card never hears the tap. Checked in Chromium; jsdom asserts the press does not capture and the drag does. Also: the pile is a group, so the run inside it stays readable; a faded last play unmounts on animation end instead of holding its line; an arriving card is scrolled into view; the slide-in fills backwards so a landed card still lifts when selected. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four things from mobile play tests, in one PR, plus the notes from the first look at it. Screenshots are from a fixture harness at 390×844 (iPhone 14-ish), 2×. Neither the harness nor the images are in the tree; the images live at the commit that carried them, which the links below pin.
Cards carry their index in the corners
Rank and suit top-left and, turned round, bottom-right, the way a real card does; the suit is the pip in the middle. The marks scale with the card's font size, so the phone's cards and the small flipped one in the last-play line have the same face. Every card has an edge now — two white cards side by side read as one white shape without it — and one lying on another casts onto it.
One fan for every hand, scrolling when it is wide
The fan is the hand at every size. Its overlap grows with the hand but is capped where a card would lose its corner index or a thumb's width to tap (1.4rem, 1rem on a phone), and a hand wider than the chair scrolls — by finger on touch, dragged by mouse on desktop — with the last card in view cut off, which is how a hand says there is more. The arc is bounded at thirty degrees however many cards, so the ends of a big hand still face the player and stay inside the hand's own edges. A mouse drag that moved the hand is not a tap on the card it started on; one that moved nothing, on a hand that fits, is. A drag on any button but the primary, or one whose button came up where the hand could not see, ends without eating the next tap. The pointer is captured once the drag has moved, never on the press: a captured press retargets its click to the hand and the card under it never hears the tap (checked in Chromium, where a real mouse tap toggled nothing before and does now). A pointed-at or tabbed-to card comes to the front; a selected one does not, so the neighbour it would cover keeps the index the next pick needs. The hand is rank-sorted, so the cards you play together are neighbours, and the Play button lists what is selected even when part of it is scrolled away.
Desktop, same 14 cards, all of them in the chair:
The middle says less
Three lines of prose became: the draw pile as a thing (a back with its count), the pile's count on the run, one short price line only on your turn (
Play 8 or higher,Play two or more, 8 or higher,Anything goes), and the last play shown for a moment and then gone.(The last-play line under the price is mid-fade in both; it lingers 3s.) It is one live region for the table's life with the moment as a keyed child, so a screen reader hears a change rather than a region appearing, and the finished fade takes it out of the tree and the layout, so the pile holds no empty line, or card-high hole after a pick-up, until the next play. The pile is a group named by its count, not an image, so the run inside stays readable.
A failed flip explains itself
The pile shows the card that did not play, and stays six seconds instead of three; the pile it brought slides into the hand one card after another. The slide-in is keyed on "faces not in the hand last time", so a normal draw-back gets it too — every turn, since an arriving card's key carries a generation and is a fresh node each time. A new table's deal is not an arrival. A wide hand is scrolled to its first new card, so the arrival is seen.
prefers-reduced-motionkeeps the fades and drops the movement.The other chairs are further away
Opponents' seats are drawn at 0.72 scale, layout and all, so the viewer's own hand has the phone. What their chairs need to say — how many in hand, which castle stacks still stand, what is face up — still reads at that size, and nothing at their chairs is tapped.
zoomrather thantransform, so the shrunk chair takes up less room instead of leaving a scaled-down picture in a full-size box; Firefox before 126 draws them full size.No toast for someone else's turn
The felt already lights the seat on turn. "Your turn" stays. Golf had the same line and gets the same change;
turnMessagehad no other caller and is gone.Tests
The fan's spread bounded and its overlap growing with the hand; a wide hand with every card a button; a tap toggling, a mouse drag scrolling and not toggling, a drag on a hand that fits still a tap, a touch not dragging and its tap toggling, a press that did not move still a tap, a drag on the wrong button not eating the next tap, movement without the button down not scrolling, and the pointer captured by the drag and not by the press. Price line on turn and absent off it; counts as the piles' labels; the flipped card inside the pick-up line; the last play as one region across plays, gone from it when its fade ends and back with the next play, the flipped card's own animation ending not taking it; an arrival scrolled into view and a deal not. Cards that arrived carrying the slide-in class in arrival order, the ones already there not, a second arrival at the same index a fresh node, and a new table's deal not an arrival.
enteredSinceas a multiset diff;describePile's wording; neither hook toasting another seat's turn. The card face, the overlap cap and the arc's geometry are CSS and are what the screenshots are for.501 tests, lint, typecheck and build clean. The perspective is CSS and is what the four-seat screenshot is for.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NvyU4MCzgsmRNa33yW1YNM