feat(ui): vs-AI comms trim, overlay confirm button, recall-to-slot drag
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 51s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m8s
- vs-AI: the comms hub drops the Chat tab (Dictionary only); a finished AI game has no comms at all, so its 💬 history-header entry is hidden too. - Confirm-move ✅: redone as an absolute overlay pinned to the right edge (its right edge sits under the preview caption), so the rack keeps a fixed tile size — this reverts the tile-shrink that resized the letters at 6 tiles. - Recall: dragging a placed tile back to the rack now drops it at the slot the pointer is over (drag-to-position, a gap opens), instead of snapping to its original slot; double-tap still recalls to the origin. New pure recallToSlot. Tests: placement recallToSlot units; e2e recall-to-position; vs-AI comms e2e updated. Docs: UI_DESIGN + FUNCTIONAL (+ru).
This commit is contained in:
@@ -102,6 +102,51 @@ export function recallIndex(p: Placement, rackIndex: number): Placement {
|
||||
return { ...p, pending: p.pending.filter((t) => t.rackIndex !== rackIndex) };
|
||||
}
|
||||
|
||||
/**
|
||||
* recallToSlot recalls the pending tile at (row, col) and reinserts its rack letter at the
|
||||
* given visible slot, reordering the rack the way a drag-to-reorder does (the drop position the
|
||||
* player chose) instead of snapping the tile back to its original slot. It returns the new
|
||||
* placement together with the index permutation applied to the rack, so the caller can permute
|
||||
* its parallel stable ids in lockstep. toSlot counts the slots that stay visible after the
|
||||
* recall (those still on the board are skipped); it is clamped to a valid position. The result
|
||||
* is the unchanged placement and an identity permutation when no pending tile sits at (row, col).
|
||||
*/
|
||||
export function recallToSlot(
|
||||
p: Placement,
|
||||
row: number,
|
||||
col: number,
|
||||
toSlot: number,
|
||||
): { placement: Placement; order: number[] } {
|
||||
const identity = p.rack.map((_, i) => i);
|
||||
const tile = p.pending.find((t) => t.row === row && t.col === col);
|
||||
if (!tile) return { placement: p, order: identity };
|
||||
const ri = tile.rackIndex;
|
||||
const rest = p.pending.filter((t) => t !== tile);
|
||||
const usedByRest = new Set(rest.map((t) => t.rackIndex));
|
||||
// Reinsert ri just before the toSlot-th slot that stays visible after the recall.
|
||||
const others = identity.filter((i) => i !== ri);
|
||||
let visible = 0;
|
||||
let insertPos = others.length;
|
||||
for (let pos = 0; pos < others.length; pos++) {
|
||||
if (usedByRest.has(others[pos])) continue;
|
||||
if (visible === toSlot) {
|
||||
insertPos = pos;
|
||||
break;
|
||||
}
|
||||
visible++;
|
||||
}
|
||||
const order = [...others];
|
||||
order.splice(insertPos, 0, ri);
|
||||
const newIndex = new Map(order.map((oldIdx, idx) => [oldIdx, idx]));
|
||||
return {
|
||||
placement: {
|
||||
rack: order.map((i) => p.rack[i]),
|
||||
pending: rest.map((t) => ({ ...t, rackIndex: newIndex.get(t.rackIndex)! })),
|
||||
},
|
||||
order,
|
||||
};
|
||||
}
|
||||
|
||||
export function reset(p: Placement): Placement {
|
||||
return { ...p, pending: [] };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user