Web: drag-and-drop cards and undeployed deck in diagram center - #309
Web: drag-and-drop cards and undeployed deck in diagram center#309tameware wants to merge 13 commits into
Conversation
Makes it clearer which cards are still available while entering a deal. Co-authored-by: Cursor <cursoragent@cursor.com>
Move the remaining-deck strip into the middle cell, stack S/H/D/C top to bottom, and left-align with North/South so available cards read with the deal. Co-authored-by: Cursor <cursoragent@cursor.com>
Reuse hand-card buttons and the seat type size in the center so remaining cards read like holdings already in the diagram. Co-authored-by: Cursor <cursoragent@cursor.com>
Move cards by dragging dealt or undeployed pips; keep holdings sorted high-to-low, reject drops onto full hands, and ignore same-hand drags so order stays rank-based. Co-authored-by: Cursor <cursoragent@cursor.com>
Pin suit glyphs to a shared column, tighten glyph-to-pip gaps, and use the same hand-card spacing in the center so N/S cards line up with undeployed cards without crowding East. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the DDS Web hand diagram UI to treat undeployed cards as a center “deck strip” (four suit rows) and adds HTML5 drag-and-drop so cards can be moved between hands and the center while maintaining high→low ordering and enforcing 13-card hand limits.
Changes:
- Move deck-status into the diagram center and render undeployed cards as four suit rows, omitting cards already entered in hands.
- Add HTML5 drag-and-drop for
.hand-cardelements (hands ↔ center), including sorting and full-hand rejection rules. - Align suit glyph/card columns and add card-count notes for partial hands (with correct singular/plural).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| web/tests/test_web_e2e.py | Adds/updates E2E coverage for center deck layout, drag-and-drop behavior, and alignment/card-count UI assertions. |
| web/tests/dds_web_test.mjs | Extends unit tests for new deck-status HTML structure, sanitization sorting, drag/drop handlers, and CSS/HTML layout constraints. |
| web/dds_web.js | Implements new undeployed card rendering, card movement helpers, drag-and-drop event handlers, sorting sanitization, and updated card-count logic. |
| web/dds_web.html | Moves #deck-status into the center grid cell and adjusts hand row markup for alignment. |
| web/dds_web.css | Updates center-cell layout, suit row alignment, .hand-card spacing, and adds drag/drop affordance styles. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
web/dds_web.js:1742
- sanitizeHandSuitInputs() uses sanitized.indexOf(pipBeforeCaret) to reposition the caret after sorting. If the user has duplicate pips before the caret (e.g. "AA"), indexOf() finds the first occurrence and can jump the caret backward, making editing frustrating (duplicates are already possible since the UI supports >13-card hands). Track the occurrence count of the last pip before the caret and place the caret after that same occurrence in the sorted string.
} else if (pipBeforeCaret) {
const at = sanitized.indexOf(pipBeforeCaret);
newCaret = at >= 0 ? at + 1 : sanitized.length;
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
web/tests/dds_web_test.mjs:2287
- The regex used to locate the center filler div stops at the first
</div>(the inner#deck-statusdiv), so it doesn't actually assert the full.grid-filler-centerblock structure and is brittle if any additional nested divs are introduced. You can make this check both simpler and more precise by matching the.grid-filler-centeropening tag followed immediately by the#deck-statusopening tag.
const centerMatch = html.match(
/<div class="[^"]*grid-filler-center[^"]*"[^>]*>([\s\S]*?)<\/div>/
);
assert.ok(centerMatch, "center filler cell present");
assert.match(centerMatch[1], /id="deck-status"/);
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed the latest Copilot notes on this PR:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
web/dds_web.js:1492
handCardCount()callscollectHands()(allocates Card objects and walks every input) and is used fromdragover-driven paths (canDropCardOnHand). Sincedragoverfires very frequently while dragging, this can cause unnecessary work and UI jank. Compute the target hand’s count directly from its four suit inputs instead of rebuilding all hands.
function handCardCount(direction) {
const hands = collectHands();
return hands[direction] ? hands[direction].length : 0;
}
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed Copilot’s note on |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (1)
web/dds_web.js:416
- Undeployed card buttons only expose their pip text (e.g., “A”) as the accessible name. Even though they’re removed from the tab order via tabindex="-1", they remain focusable programmatically and discoverable to assistive tech, and multiple identical names (“A”, “K”, etc.) across suits are ambiguous. Add an aria-label (and ideally a suit/pip name) consistent with dealt hand-card buttons.
function undeployedCardHtml(card) {
return "<button type=\"button\" class=\"hand-card\" draggable=\"true\"" +
" data-card=\"" + escapeHtml(card.key()) + "\"" +
" tabindex=\"-1\">" +
escapeHtml(card.pip) +
"</button>";
}
Match dealt hand-card naming so identical pips across suits stay distinguishable to assistive tech. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed Copilot’s note on undeployed card accessible names: |
| test("addCardToHand inserts a pip in high-to-low order", () => { | ||
| const document = createMockDocument({ north_spades: "AK" }); | ||
| const ctx = loadDdsWeb(document); | ||
| const card = new ctx.Card("spades", "Q"); | ||
|
|
||
| assert.equal(ctx.addCardToHand("north", card), true); | ||
| assert.equal(document.element("north_spades").value, "AKQ"); | ||
| }); | ||
|
|
||
| test("addCardToHand inserts between existing ranks", () => { | ||
| const document = createMockDocument({ north_spades: "A" }); | ||
| const ctx = loadDdsWeb(document); | ||
|
|
||
| assert.equal(ctx.addCardToHand("north", new ctx.Card("spades", "K")), true); | ||
| assert.equal(document.element("north_spades").value, "AK"); | ||
| }); |
There was a problem hiding this comment.
These two tests are verifying the same thing, despite their descriptions. Both tests would pass if the addCardToHand simply added the card to the end of the suit without caring about order.
The first test should insert several cards out of order, add the 5, 2, Q, T, 7. Verify the result is "AKQT752".
Summary
Deployed to https://tameware.com/adam/bridge/dds/dds_web.html
Test plan
bazel test //web:dds_web_js_testbazel test --test_tag_filters=e2e //web:dds_web_e2e_testMade with Cursor