Skip to content

feat(draft): add responsive draft workspace - #7985

Open
traemyn wants to merge 9 commits into
phase-rs:mainfrom
traemyn:feature/draft-workspace
Open

feat(draft): add responsive draft workspace#7985
traemyn wants to merge 9 commits into
phase-rs:mainfrom
traemyn:feature/draft-workspace

Conversation

@traemyn

@traemyn traemyn commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the responsive draft workspace, including persistent deck/sideboard placement, workspace drag interactions, responsive pack and pool UI, and compatible single- and multi-card draft picks. It also aligns multiplayer draft protocol and recovery behavior with the current engine contracts.

Files changed

  • .claude/wf/tablet-deck-header-sideboard-icon-phase-fit.md
  • client/src/adapter/__tests__/draft-adapter.test.ts
  • client/src/adapter/__tests__/draftPodAdapter.cube.test.ts
  • client/src/adapter/__tests__/draftPodAdapter.test.ts
  • client/src/adapter/__tests__/p2pDraftHostBo3.test.ts
  • client/src/adapter/__tests__/p2pDraftWorkspacePersistence.test.ts
  • client/src/adapter/__tests__/server-draft-adapter.test.ts
  • client/src/adapter/draft-adapter.ts
  • client/src/adapter/draftPodGuestAdapter.ts
  • client/src/adapter/draftPodHostAdapter.ts
  • client/src/adapter/p2p-draft-guest.ts
  • client/src/adapter/p2p-draft-host.ts
  • client/src/components/card/HoverCardPreview.tsx
  • client/src/components/card/__tests__/HoverCardPreview.test.tsx
  • client/src/components/chrome/AppShell.tsx
  • client/src/components/chrome/ChromeControls.tsx
  • client/src/components/chrome/ShellContext.tsx
  • client/src/components/chrome/__tests__/AppShell.responsiveDraft.test.tsx
  • client/src/components/chrome/__tests__/ChromeControls.responsiveDraft.test.tsx
  • client/src/components/draft/DeckStatistics.tsx
  • client/src/components/draft/DraftCardFace.tsx
  • client/src/components/draft/DraftProgress.tsx
  • client/src/components/draft/DraftSteps.tsx
  • client/src/components/draft/HostControls.tsx
  • client/src/components/draft/LimitedDeckBuilder.tsx
  • client/src/components/draft/ManaCurve.tsx
  • client/src/components/draft/PackDisplay.tsx
  • client/src/components/draft/PodIcon.tsx
  • client/src/components/draft/PoolPanel.tsx
  • client/src/components/draft/SeatStatusRing.tsx
  • client/src/components/draft/__tests__/CardPoolBoard.test.tsx
  • client/src/components/draft/__tests__/DraftProgress.test.tsx
  • client/src/components/draft/__tests__/DraftSteps.test.tsx
  • client/src/components/draft/__tests__/DraftWorkspace.test.tsx
  • client/src/components/draft/__tests__/LimitedDeckBuilder.test.tsx
  • client/src/components/draft/__tests__/PackDisplay.pod.test.tsx
  • client/src/components/draft/__tests__/PackDisplay.workspace.test.tsx
  • client/src/components/draft/__tests__/PoolPanel.test.tsx
  • client/src/components/draft/__tests__/PoolPanel.workspace.test.tsx
  • client/src/components/draft/__tests__/SealedPackOpening.test.tsx
  • client/src/components/draft/__tests__/SeatStatusRing.test.tsx
  • client/src/components/draft/__tests__/VisualPackDraftSurfaces.test.tsx
  • client/src/components/draft/__tests__/useDraftWorkspaceDrag.test.tsx
  • client/src/components/draft/__tests__/workspaceMigration.test.ts
  • client/src/components/draft/__tests__/workspacePlacement.test.ts
  • client/src/components/draft/__tests__/workspacePreferences.test.ts
  • client/src/components/draft/__tests__/workspaceProjection.test.ts
  • client/src/components/draft/workspace/CardPoolBoard.tsx
  • client/src/components/draft/workspace/CardPoolColumn.tsx
  • client/src/components/draft/workspace/CompactSideboard.tsx
  • client/src/components/draft/workspace/DraftWorkspace.tsx
  • client/src/components/draft/workspace/DraftWorkspaceToolbar.tsx
  • client/src/components/draft/workspace/WorkspaceCard.tsx
  • client/src/components/draft/workspace/types.ts
  • client/src/components/draft/workspace/useDraftWorkspaceDrag.ts
  • client/src/components/draft/workspace/workspaceMigration.ts
  • client/src/components/draft/workspace/workspacePlacement.ts
  • client/src/components/draft/workspace/workspacePreferences.ts
  • client/src/components/draft/workspace/workspaceProjection.ts
  • client/src/components/menu/MenuShell.tsx
  • client/src/components/menu/PopoverMenu.tsx
  • client/src/components/menu/__tests__/MenuShell.test.tsx
  • client/src/components/menu/__tests__/PopoverMenu.test.tsx
  • client/src/components/settings/PreferencesModal.tsx
  • client/src/components/settings/__tests__/PreferencesModal.cardPreview.test.tsx
  • client/src/constants/__tests__/storage.test.ts
  • client/src/constants/storage.ts
  • client/src/game/__tests__/wasmLegalActions.integration.test.ts
  • client/src/hooks/__tests__/useShiftHeld.test.tsx
  • client/src/hooks/useShiftHeld.ts
  • client/src/i18n/__tests__/localeParity.test.ts
  • client/src/i18n/locales/de/draft.json
  • client/src/i18n/locales/de/settings.json
  • client/src/i18n/locales/en/draft.json
  • client/src/i18n/locales/en/settings.json
  • client/src/i18n/locales/es/draft.json
  • client/src/i18n/locales/es/settings.json
  • client/src/i18n/locales/fr/draft.json
  • client/src/i18n/locales/fr/settings.json
  • client/src/i18n/locales/it/draft.json
  • client/src/i18n/locales/it/settings.json
  • client/src/i18n/locales/pl/draft.json
  • client/src/i18n/locales/pl/settings.json
  • client/src/i18n/locales/pt/draft.json
  • client/src/i18n/locales/pt/settings.json
  • client/src/network/__tests__/draftProtocol.test.ts
  • client/src/network/draftProtocol.ts
  • client/src/pages/DraftLandingPage.tsx
  • client/src/pages/DraftPage.tsx
  • client/src/pages/DraftPodPage.tsx
  • client/src/pages/__tests__/DraftPage.pick.integration.test.tsx
  • client/src/pages/__tests__/DraftPage.workspace.test.tsx
  • client/src/pages/__tests__/DraftPodPage.betweenGames.test.tsx
  • client/src/pages/__tests__/DraftPodPage.modeEntry.test.tsx
  • client/src/pages/__tests__/DraftPodPage.podComplete.test.tsx
  • client/src/pages/__tests__/DraftPodPage.podError.test.tsx
  • client/src/pages/__tests__/DraftPodPage.workspace.test.tsx
  • client/src/services/__tests__/backup.test.ts
  • client/src/services/__tests__/draftPersistence.test.ts
  • client/src/services/__tests__/quickDraftPersistence.test.ts
  • client/src/services/__tests__/scryfall.test.ts
  • client/src/services/backup.ts
  • client/src/services/draftPersistence.ts
  • client/src/services/quickDraftPersistence.ts
  • client/src/services/scryfall.ts
  • client/src/stores/__tests__/draftStore.workspace.test.ts
  • client/src/stores/__tests__/multiplayerDraftStore.bo3.test.ts
  • client/src/stores/__tests__/multiplayerDraftStore.test.ts
  • client/src/stores/__tests__/preferencesStore.test.ts
  • client/src/stores/draftStore.ts
  • client/src/stores/multiplayerDraftStore.ts
  • client/src/stores/preferencesStore.ts
  • client/src/wasm/draft_wasm.d.ts
  • client/src/wasm/engine_wasm.d.ts
  • crates/draft-core/src/view.rs
  • crates/server-core/src/protocol.rs

Track

Developer

LLM

Model: GPT-5 (Codex; canonical id not exposed)
Tier: Frontier
Thinking: high

Implementation method (required)

Method: /engine-implementer

CR references

None.

Verification

  • Required checks ran clean, or the exact CI-owned alternative is stated below.

  • Gate A output below is for the current committed head.

  • Final review-impl below is clean for the current committed head.

  • Both anchors cite existing analogous code at the same seam.

  • CI=true corepack pnpm@10.20.0 exec vitest run --silent --reporter=dot — 390 files passed, 2 skipped; 4,030 tests passed, 12 todo.

  • CI=true corepack pnpm@10.20.0 run test:integration — 22 tests passed.

  • CI=true corepack pnpm@10.20.0 run test:browser:pack-layout — 39 tests passed.

  • CI=true corepack pnpm@10.20.0 run type-check — passed.

  • CI=true corepack pnpm@10.20.0 run build — passed.

  • CI=true PATH=/tmp/phase-pnpm-10.20:$PATH .githooks/pre-push — passed; runs cargo fmt, clippy, card-data validation, parser/AI tests, lint, and type-check. ESLint completed with 0 errors and 43 existing warnings.

  • git diff --check upstream/main...HEAD — passed.

Gate A

Gate A PASS head=581d5ba7caa39eb4480c4c7edb52bbe3b143ac0f base=6884506bfad8bc5bffa660dea10d0bcf2fd930c3

Anchored on

  • client/src/components/draft/PackDisplay.tsx:377 — existing pack-selection and draft-effect state boundary extended by the workspace presentation.
  • client/src/stores/draftStore.ts:1 — existing draft-state and persistence seam extended for workspace placement/recovery.

Final review-impl

Final review-impl PASS head=581d5ba7caa39eb4480c4c7edb52bbe3b143ac0f

Claimed parse impact

None.

Scope Expansion

None.

Validation Failures

The isolated pre-push hook reached card-data validation but could not locate its untracked data/mtgjson/AtomicCards.json input. Candidate-scoped frontend, integration, type, lint, parser, AI, and review checks passed.

CI Failures

None.

PR handoff

Pipeline-reviewed head: 581d5ba
Current branch head: 581d5ba
Pipeline status: current
Current-head review: clean at 581d5ba

@traemyn
traemyn requested a review from matthewevans as a code owner August 27, 2026 17:43
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 114 files, which is 14 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f4e31e6b-0b6b-4404-a2ec-0d08fde78522

📥 Commits

Reviewing files that changed from the base of the PR and between 40d5019 and c0e7153.

⛔ Files ignored due to path filters (2)
  • client/src/wasm/draft_wasm.d.ts is excluded by !client/src/wasm/**, !**/*.d.ts
  • client/src/wasm/engine_wasm.d.ts is excluded by !client/src/wasm/**, !**/*.d.ts
📒 Files selected for processing (114)
  • .claude/wf/tablet-deck-header-sideboard-icon-phase-fit.md
  • client/src/adapter/__tests__/draft-adapter.test.ts
  • client/src/adapter/__tests__/draftPodAdapter.cube.test.ts
  • client/src/adapter/__tests__/draftPodAdapter.test.ts
  • client/src/adapter/__tests__/p2pDraftHostBo3.test.ts
  • client/src/adapter/__tests__/p2pDraftWorkspacePersistence.test.ts
  • client/src/adapter/__tests__/server-draft-adapter.test.ts
  • client/src/adapter/draft-adapter.ts
  • client/src/adapter/draftPodGuestAdapter.ts
  • client/src/adapter/draftPodHostAdapter.ts
  • client/src/adapter/p2p-draft-guest.ts
  • client/src/adapter/p2p-draft-host.ts
  • client/src/components/card/HoverCardPreview.tsx
  • client/src/components/card/__tests__/HoverCardPreview.test.tsx
  • client/src/components/chrome/AppShell.tsx
  • client/src/components/chrome/ChromeControls.tsx
  • client/src/components/chrome/ShellContext.tsx
  • client/src/components/chrome/__tests__/AppShell.responsiveDraft.test.tsx
  • client/src/components/chrome/__tests__/ChromeControls.responsiveDraft.test.tsx
  • client/src/components/draft/DeckStatistics.tsx
  • client/src/components/draft/DraftCardFace.tsx
  • client/src/components/draft/DraftProgress.tsx
  • client/src/components/draft/DraftSteps.tsx
  • client/src/components/draft/HostControls.tsx
  • client/src/components/draft/LimitedDeckBuilder.tsx
  • client/src/components/draft/ManaCurve.tsx
  • client/src/components/draft/PackDisplay.tsx
  • client/src/components/draft/PodIcon.tsx
  • client/src/components/draft/PoolPanel.tsx
  • client/src/components/draft/SeatStatusRing.tsx
  • client/src/components/draft/__tests__/CardPoolBoard.test.tsx
  • client/src/components/draft/__tests__/DraftProgress.test.tsx
  • client/src/components/draft/__tests__/DraftSteps.test.tsx
  • client/src/components/draft/__tests__/DraftWorkspace.test.tsx
  • client/src/components/draft/__tests__/LimitedDeckBuilder.test.tsx
  • client/src/components/draft/__tests__/PackDisplay.pod.test.tsx
  • client/src/components/draft/__tests__/PackDisplay.workspace.test.tsx
  • client/src/components/draft/__tests__/PoolPanel.test.tsx
  • client/src/components/draft/__tests__/PoolPanel.workspace.test.tsx
  • client/src/components/draft/__tests__/SealedPackOpening.test.tsx
  • client/src/components/draft/__tests__/SeatStatusRing.test.tsx
  • client/src/components/draft/__tests__/VisualPackDraftSurfaces.test.tsx
  • client/src/components/draft/__tests__/useDraftWorkspaceDrag.test.tsx
  • client/src/components/draft/__tests__/workspaceMigration.test.ts
  • client/src/components/draft/__tests__/workspacePlacement.test.ts
  • client/src/components/draft/__tests__/workspacePreferences.test.ts
  • client/src/components/draft/__tests__/workspaceProjection.test.ts
  • client/src/components/draft/workspace/CardPoolBoard.tsx
  • client/src/components/draft/workspace/CardPoolColumn.tsx
  • client/src/components/draft/workspace/CompactSideboard.tsx
  • client/src/components/draft/workspace/DraftWorkspace.tsx
  • client/src/components/draft/workspace/DraftWorkspaceToolbar.tsx
  • client/src/components/draft/workspace/WorkspaceCard.tsx
  • client/src/components/draft/workspace/types.ts
  • client/src/components/draft/workspace/useDraftWorkspaceDrag.ts
  • client/src/components/draft/workspace/workspaceMigration.ts
  • client/src/components/draft/workspace/workspacePlacement.ts
  • client/src/components/draft/workspace/workspacePreferences.ts
  • client/src/components/draft/workspace/workspaceProjection.ts
  • client/src/components/menu/MenuShell.tsx
  • client/src/components/menu/PopoverMenu.tsx
  • client/src/components/menu/__tests__/MenuShell.test.tsx
  • client/src/components/menu/__tests__/PopoverMenu.test.tsx
  • client/src/components/settings/PreferencesModal.tsx
  • client/src/components/settings/__tests__/PreferencesModal.cardPreview.test.tsx
  • client/src/constants/__tests__/storage.test.ts
  • client/src/constants/storage.ts
  • client/src/game/__tests__/wasmLegalActions.integration.test.ts
  • client/src/hooks/__tests__/useShiftHeld.test.tsx
  • client/src/hooks/useShiftHeld.ts
  • client/src/i18n/__tests__/localeParity.test.ts
  • client/src/i18n/locales/de/draft.json
  • client/src/i18n/locales/de/settings.json
  • client/src/i18n/locales/en/draft.json
  • client/src/i18n/locales/en/settings.json
  • client/src/i18n/locales/es/draft.json
  • client/src/i18n/locales/es/settings.json
  • client/src/i18n/locales/fr/draft.json
  • client/src/i18n/locales/fr/settings.json
  • client/src/i18n/locales/it/draft.json
  • client/src/i18n/locales/it/settings.json
  • client/src/i18n/locales/pl/draft.json
  • client/src/i18n/locales/pl/settings.json
  • client/src/i18n/locales/pt/draft.json
  • client/src/i18n/locales/pt/settings.json
  • client/src/network/__tests__/draftProtocol.test.ts
  • client/src/network/draftProtocol.ts
  • client/src/pages/DraftLandingPage.tsx
  • client/src/pages/DraftPage.tsx
  • client/src/pages/DraftPodPage.tsx
  • client/src/pages/__tests__/DraftPage.pick.integration.test.tsx
  • client/src/pages/__tests__/DraftPage.workspace.test.tsx
  • client/src/pages/__tests__/DraftPodPage.betweenGames.test.tsx
  • client/src/pages/__tests__/DraftPodPage.modeEntry.test.tsx
  • client/src/pages/__tests__/DraftPodPage.podComplete.test.tsx
  • client/src/pages/__tests__/DraftPodPage.podError.test.tsx
  • client/src/pages/__tests__/DraftPodPage.workspace.test.tsx
  • client/src/services/__tests__/backup.test.ts
  • client/src/services/__tests__/draftPersistence.test.ts
  • client/src/services/__tests__/quickDraftPersistence.test.ts
  • client/src/services/__tests__/scryfall.test.ts
  • client/src/services/backup.ts
  • client/src/services/draftPersistence.ts
  • client/src/services/quickDraftPersistence.ts
  • client/src/services/scryfall.ts
  • client/src/stores/__tests__/draftStore.workspace.test.ts
  • client/src/stores/__tests__/multiplayerDraftStore.bo3.test.ts
  • client/src/stores/__tests__/multiplayerDraftStore.test.ts
  • client/src/stores/__tests__/preferencesStore.test.ts
  • client/src/stores/draftStore.ts
  • client/src/stores/multiplayerDraftStore.ts
  • client/src/stores/preferencesStore.ts
  • crates/draft-core/src/view.rs
  • crates/server-core/src/protocol.rs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changes requested — protected build/dependency surface.

🔴 Blocker

client/package.json:18, client/package.json:78, and client/package.json:90 change the package-script/dependency surface by adding test:browser:pack-layout, @vitest/browser-playwright, and playwright. Repository review policy classifies client/package.json as a hard-stop path, so this PR cannot be implementation-reviewed, approved, or enqueued while those changes are present without explicit maintainer review of that surface.

Please split or remove the package-manifest/script changes from this PR, or obtain explicit maintainer approval for that protected surface before resubmitting the current head. The responsive draft implementation itself was intentionally not reviewed in this disposition.

Recommendation: resolve the protected client/package.json change first, then request a fresh full review.

@matthewevans matthewevans self-assigned this Aug 27, 2026
Resolve the HoverCardPreview conflict by preserving the responsive-workspace effective mode and the merged hover-preview docking fix.

Co-authored-by: traemyn <traemyn@gmail.com>
@matthewevans

Copy link
Copy Markdown
Member

Maintainer port complete — fresh review pending.

I merged current main into this branch at c0e715338edb989821d1496d271964ad7bb82c0e to resolve the HoverCardPreview.tsx conflict caused by merged PR #7974. The resolution preserves both the workspace effective preview mode and the merged hover-preview docking/position behavior.

The earlier requested-changes review was attached to a21f855885839b9ff1b7f4f6db78cca4d158a9fc; its client/package.json blocker is no longer present on this head. This large, cross-surface change now requires a new full implementation review and current-head CI/artifact evidence before any approval or merge-queue action. No approval or enqueue has been performed.

@matthewevans matthewevans added the feature Larger-scoped feature label Aug 27, 2026
@matthewevans matthewevans removed their assignment Aug 27, 2026
@traemyn
traemyn requested a review from matthewevans August 27, 2026 19:34
@matthewevans matthewevans self-assigned this Aug 27, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changes requested — bound the untrusted workspace snapshot.

🔴 Blocker

[MED] The new per-seat workspace transport accepts an unbounded placements map before it knows the authoritative pool. Evidence: client/src/components/draft/workspace/types.ts:67-106 iterates and clones every placement while only virtualBasics is capped at lines 109-114; client/src/adapter/p2p-draft-host.ts:964-977 runs that validation for each guest-owned draft_workspace_update before reconciliation; and client/src/network/draftPeerSession.ts:49-63 / client/src/network/draftProtocol.ts:868-881 impose no decoded-frame bound. The current test intentionally accepts more than the 1,000-entry cap at client/src/services/__tests__/draftPersistence.test.ts:384-397.

Why it matters: an authenticated peer can send an arbitrarily large valid-looking snapshot, forcing the host to allocate/clone it and then serialize a persisted session before reconciliation prunes the unknown instance IDs. This turns a display-only update into a host memory/CPU and persistence denial-of-service path.

Suggested fix: apply a bounded, pool-aware admission check at the host boundary (or a conservative schema cap) before cloning every placement, then add a transport-level regression test that rejects an oversized unknown-placement payload without persisting it. Keep the virtual-basic bound and make the placement bound explicit rather than relying on later reconciliation.

✅ Clean

The previous client/package.json hard-stop is absent from c0e715338edb989821d1496d271964ad7bb82c0e; current-head CI is green. The #7974 hover-preview conflict resolution itself preserves both the workspace effective mode and the dock-position API.

Recommendation: request changes for bounded workspace-update admission, then re-run the full current-head review.

@matthewevans matthewevans removed their assignment Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Larger-scoped feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants