Skip to content

Web: drag-and-drop cards and undeployed deck in diagram center - #309

Open
tameware wants to merge 13 commits into
dds-bridge:developfrom
tameware:web-drag-n-drop
Open

Web: drag-and-drop cards and undeployed deck in diagram center#309
tameware wants to merge 13 commits into
dds-bridge:developfrom
tameware:web-drag-n-drop

Conversation

@tameware

@tameware tameware commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Move undeployed cards into the hand-diagram center (four suit rows) and omit dealt cards from that strip instead of graying them out.
  • Add HTML5 drag-and-drop between hands and the center: cards insert sorted high→low; full (13-card) hands reject drops; same-hand drags are a no-op.
  • Align suit glyph columns / pip spacing across seats and center
  • show hand card-count notes (singular “1 card”) whenever a hand is not empty and not exactly 13, left-aligned with the pips.
  • Disallow entry of more than 13 cards per hand

Deployed to https://tameware.com/adam/bridge/dds/dds_web.html

Test plan

  • bazel test //web:dds_web_js_test
  • bazel test --test_tag_filters=e2e //web:dds_web_e2e_test
  • Manually drag cards among N/E/S/W and the center; confirm sort order and rejection when a hand already has 13
  • Confirm card-count shows for partial hands (“1 card” / “3 cards”), hides at 0 and 13, and lines up with suit pips

Made with Cursor

tameware and others added 8 commits August 10, 2026 23:27
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-card elements (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.

Comment thread web/dds_web.js
Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-status div), so it doesn't actually assert the full .grid-filler-center block 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-center opening tag followed immediately by the #deck-status opening 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>
@tameware

Copy link
Copy Markdown
Collaborator Author

Addressed the latest Copilot notes on this PR:

  • Center filler regex (dds_web_test.mjs): updated to assert .grid-filler-center is immediately followed by #deck-status via opening tags, so nested </div> no longer truncates the match.
  • Caret / duplicate pips in sanitizeHandSuitInputs: no change — within-suit duplicates are already stripped on input (sanitizeSuitHolding + beep), so indexOf(pipBeforeCaret) cannot hit a second occurrence in the sanitized holding.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() calls collectHands() (allocates Card objects and walks every input) and is used from dragover-driven paths (canDropCardOnHand). Since dragover fires 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>
@tameware

Copy link
Copy Markdown
Collaborator Author

Addressed Copilot’s note on handCardCount(): it now sums the four suit input lengths for the target seat instead of calling collectHands() on every dragover.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@tameware

Copy link
Copy Markdown
Collaborator Author

Addressed Copilot’s note on undeployed card accessible names: undeployedCardHtml now sets aria-label via handCardAriaLabel(\"undeployed\", card) (e.g. "Undeployed heart ace"), consistent with dealt hand-card buttons.

@tameware
tameware requested a lite review from Copilot August 11, 2026 12:35
@tameware
tameware requested a lite review from Copilot and removed request for Copilot August 11, 2026 12:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@tameware tameware self-assigned this Aug 11, 2026
Comment on lines +2357 to +2372
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");
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

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.

3 participants