Skip to content

feat(deck): add companion zone with deckbuilding-restriction validation#77

Merged
jcserv merged 9 commits into
mainfrom
fm/companion-zone-c5
Jun 30, 2026
Merged

feat(deck): add companion zone with deckbuilding-restriction validation#77
jcserv merged 9 commits into
mainfrom
fm/companion-zone-c5

Conversation

@jcserv

@jcserv jcserv commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Intent

Implement issue #37 'Companions': add a COMPANION deck zone alongside mainboard/sideboard/considering/commander, let a user assign a companion card to it, and validate each companion's oracle-text deckbuilding restriction against the deck's mainboard+commander cards with a clear legal/illegal indicator reusing the existing LegalityIssue -> formatLegalityIssue -> DeckLegalityBadge machinery. Companion restrictions are NOT in Scryfall structured data (only the Companion keyword is) and the set of companions is a fixed closed set of ten, so per captain decision they are encoded as a name-keyed predicate registry in lib/deck/legality/companions.ts (option A, hardcoded), a deliberate choice. companionRule runs universally in fullLegality (not per-format), threads cmc/manaCost/oracleText as optional SnapshotCard fields, flags more than one card in the COMPANION zone, and flags unknown cards in the zone. Per captain decisions during review: companion price/tokens DO count toward the deck (like Commander, COMPANION intentionally left out of EXCLUDED_ZONES); companion color identity IS enforced in singleton formats (colorIdentityRule now scans COMPANION). All prior review findings are resolved in the current HEAD: read-only companion visibility, always-render DnD companion drop-target, single-companion check, deck-history zone ordering includes COMPANION, and companion color-identity enforcement. The zirda activated-ability heuristic, null-cmc->MV0 default, and jegantha hybrid-symbol edge are documented and accepted as bounded given the hardcoded approach. UI wiring spans Zone enum + migration, add-intent destinations, import/export adapters, builder move-card menu, hotkeys, sortable rows, owner DnD view, read-only view, deck-mode-bar and history labels, CONTEXT.md domain docs, and tests (1908 passing locally, typecheck clean).

What Changed

  • New COMPANION zone added to the DB schema (Zone enum + migration); a name-keyed predicate registry in lib/deck/legality/companions.ts encodes all ten companion deckbuilding restrictions (hardcoded, not from Scryfall structured data) and runs as companionRule inside fullLegality, flagging unknown companions, multiple companions, and each card's oracle-text restriction against MAINBOARD + COMMANDER cards
  • colorIdentityRule extended to scan the COMPANION zone alongside COMMANDER, so off-color companions surface as legality issues in singleton formats
  • UI wired end-to-end: add-intent destinations, move-card menu, hotkeys, sortable rows, DnD builder, read-only view, deck-history zone ordering, deck-mode-bar, and Arena import/export adapters all surface the new zone

Risk Assessment

✅ Low: Well-bounded additive feature: new enum value, name-keyed predicate registry, and symmetric UI/IO wiring; legality entry point and deck-size counting verified unaffected, with only minor edge-case inaccuracies in two companion predicates.

Testing

Ran 62 unit tests covering companion restriction logic, color-identity enforcement, zone routing (add-intent), and UI menu ordering — all pass. No E2E browser run was performed as the app requires a live DB, but the critical business logic (companion predicate registry, fullLegality wiring, colorIdentityRule scanning COMPANION zone) is fully exercised by the test suite.

Evidence: Full test run output (62 tests, 4 files)

 RUN  v4.1.5 /Users/jarrodservilla/.treehouse/maindeck-7bf776/6/maindeck

(node:95432) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > parseAddCardInput > returns quantity 1 and trimmed term when no count is present 1ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > parseAddCardInput > parses leading 'Nx' / 'N ' counts 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > parseAddCardInput > falls back to quantity 1 when there's no separating space 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > parseAddCardInput > treats huge numbers as part of the name (caps at two digits) 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > buildAddDestinations > includes mainboard, sideboard, considering, companion and create-category by default 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > buildAddDestinations > inserts a mainboard entry per category, in order 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > buildAddDestinations > adds the COMMANDER zone for COMMANDER decks 1ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > buildAddDestinations > disables the COMMANDER entry when one is already set 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > buildAddDestinations > does not include COMMANDER zone for non-Commander formats 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > evaluateAddIntent > treats no-format as legal with zero current copies 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > evaluateAddIntent > counts mainboard + commander toward currentCopies, ignoring sideboard/considering 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > evaluateAddIntent > flags singleton violations in COMMANDER 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > evaluateAddIntent > flags color-identity violations in COMMANDER 0ms
 ✓ |server| lib/deck/__tests__/add-intent.test.ts > evaluateAddIntent > evaluates a non-commander format without typeLine, colorIdentity, or commanderIdentity 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Standard legal deck > returns legal for a minimal valid 60-card standard deck 2ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — unknown legality status > ignores per-card statuses that don't map to a known kind 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Banned card in Standard > flags a banned card with the correct issue code 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Banned card in Standard > flags a restricted card 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Banned card in Standard > flags a not_legal card 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander singleton violation > flags duplicate non-basic cards across mainboard and commander zone 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander with 99 cards (missing 1) > flags deck size not equal to 100 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander with exactly 100 cards and one commander > is legal when the deck has 99 mainboard + 1 commander 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander with zero commanders > flags missing commander card 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Basic lands exempt from singleton > allows multiple copies of basic lands by name 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Basic lands exempt from singleton > allows basic lands detected by typeLine 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Brawl no-size-rules happy path > does not flag size violations for BRAWL format 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck > returns legal for a legal card with 0 copies in deck 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck > flags a banned card 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck > flags singleton violation when card already in deck 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck > does not flag singleton for basic lands 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck > reports the post-add copy count with the cardName prefix 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck > ignores an unknown legality status (treats as legal-ish) 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander color identity > allows an on-color mainboard card 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander color identity > flags a white card under a B/G commander 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander color identity > unions identity across partner commanders 0ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander color identity > flags an off-color companion under a B/G commander 1ms
 ✓ |server| lib/deck/legality.test.ts > validateDeck — Commander color identity > does not flag an on-color companion 1ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck — color identity > flags an off-color card when commanderIdentity is provided 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck — color identity > treats an off-color card as legal when commanderIdentity is not provided 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck — color identity > does not apply color identity rule in non-identity formats 0ms
 ✓ |server| lib/deck/legality.test.ts > getCardLegalityForDeck — color identity > allows a colorless card in a mono-color deck 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > companion zone gating > returns no issues when the companion zone is empty 1ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > companion zone gating > flags a card in the companion zone that is not a known companion 1ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > companion zone gating > flags having more than one companion while still checking each 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > companion zone gating > does not flag too-many when only one companion is present 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Gyruda, Doom of Depths — even mana value > is legal when every card has an even mana value 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Gyruda, Doom of Depths — even mana value > is illegal when a card has an odd mana value 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Obosh, the Preypiercer — odd mana value > ignores lands but flags even-cost nonland cards 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Keruga, the Macrosage — nonland mana value 3+ > allows lands of any cost but requires nonlands at 3+ 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Lurrus of the Dream-Den — permanents mana value 2 or less > ignores instants/sorceries but flags expensive permanents 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Lutri, the Spellchaser — singleton > flags a duplicated nonbasic but allows duplicate basics 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Jegantha, the Wellspring — no repeated mana symbol > allows distinct symbols but flags a repeated colored symbol 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Kaheera, the Orphanguard — creature types > allows the listed types and noncreatures, flags others 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Umori, the Collector — shared card type > is legal when all nonland cards share a type, illegal otherwise 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Yorion, Sky Nomad — oversized deck > requires 20 cards above the format minimum 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > Zirda, the Dawnwaker — permanents with activated abilities > flags a permanent with no activated ability 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > companion restriction only judges mainboard + commander > ignores cards in the sideboard and considering zones 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > companion legality flows through fullLegality + formatting > surfaces a companion_violation from validateDeck-style checks 0ms
 ✓ |server| lib/deck/legality/__tests__/companions.test.ts > registry > covers all ten companions 0ms
 ✓ |client| app/_components/builder/__tests__/move-card-menu.test.tsx > orderZoneOptions > keeps Commander first when no commander is set 1ms
 ✓ |client| app/_components/builder/__tests__/move-card-menu.test.tsx > orderZoneOptions > moves Commander to the bottom when a commander is already set 0ms
 ✓ |client| app/_components/builder/__tests__/move-card-menu.test.tsx > orderZoneOptions > preserves the non-commander zones' relative order 0ms

 Test Files  4 passed (4)
      Tests  62 passed (62)
   Start at  08:40:41
   Duration  2.25s (transform 507ms, setup 279ms, import 1.57s, tests 21ms, environment 782ms)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 3 infos
  • ℹ️ lib/deck/legality/companions.ts:237 - Umori check: if the first nonland card in judged has a null/unrecognised typeLine, cardTypesOf returns [] so shared starts empty and the rule reports "nonland cards do not all share a card type" even when every other card does share one. Same null-typeLine input is harmless for the other predicates (isPermanent/isLand return false), but Umori produces a false violation. Seed shared from the first card with a recognised type, or skip null-type cards.
  • ℹ️ lib/deck/legality/companions.ts:120 - formatMinimum() maps OATHBREAKER to COMMANDER_DECK_SIZE (100), but an Oathbreaker deck is a 60-card singleton, not 100. Only affects Yorion's needed-count math in Oathbreaker (a non-real combination, like Yorion-in-Commander), so impact is nil today, but it's a latent inaccuracy if companion logic is reused.
  • ℹ️ lib/deck/add-intent.ts:47 - COMPANION is offered as an add/move destination in every format (buildAddDestinations, move-card-menu, hotkeys) and companionRule runs universally, matching the captain's documented decision. Noting only: in formats where the companion mechanic doesn't exist the zone is still selectable. Intentional per stated intent.
✅ **Test** - passed

✅ No issues found.

  • node_modules/.bin/vitest run lib/deck/legality/__tests__/companions.test.ts — 18 tests: zone gating (empty zone, unknown card, multiple companions, single companion), all 10 companion restrictions (Gyruda/Obosh/Keruga/Lurrus/Lutri/Jegantha/Kaheera/Umori/Yorion/Zirda), mainboard+commander scope isolation, fullLegality integration, and registry completeness
  • node_modules/.bin/vitest run lib/deck/legality.test.ts — 27 tests including 2 new companion color-identity cases: flags off-color companion under B/G commander, allows on-color companion
  • node_modules/.bin/vitest run lib/deck/__tests__/add-intent.test.ts — 14 tests, confirming COMPANION appears as add destination alongside mainboard/sideboard/considering
  • node_modules/.bin/vitest run app/_components/builder/__tests__/move-card-menu.test.tsx — 3 tests confirming zone ordering logic with companion in menu
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Summary by CodeRabbit

  • New Features
    • Added support for the Companion deck zone across deck building (drag-and-drop, zone ordering), deck history, and import/export (including Arena format).
    • Added UI controls for moving cards to the Companion zone, including menu selection and keyboard shortcuts.
  • Bug Fixes
    • Expanded deck legality to validate companion restrictions, including color-identity checks that now consider Companion cards, with user-facing companion-violation messages.
  • Documentation
    • Updated the glossary and README to reflect Companion zone support.
  • Tests
    • Updated and added coverage for Companion-zone behavior, deck legality, and serialization formatting.

jcserv added 5 commits June 28, 2026 09:30
Add a COMPANION deck zone so a deck can pin a companion card, and validate
the companion's deckbuilding restriction against the deck's mainboard +
commander cards.

Companion restrictions are not present in Scryfall's structured data (only the
`Companion` keyword is), so the ten companions' conditions are encoded as a
name-keyed predicate registry in lib/deck/legality/companions.ts. companionRule
runs as part of fullLegality for every format, surfacing a companion_violation
LegalityIssue that flows through the existing DeckLegalityBadge.

- prisma: add COMPANION to the Zone enum (+ migration)
- snapshot: thread cmc/manaCost/oracleText into SnapshotCard for restrictions
- UI: companion zone in the move menu, add destinations, row hotkeys, the
  sideboard/considering panel, and every Record<Zone> map; import/export round-trip
- tests: companion legality coverage for all ten companions

Closes #37

Claude-Session: https://claude.ai/code/session_01XF6NhHy3smZTYCSa9swp1B
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@jcserv, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a5ffef2-b47d-41aa-a0d2-b0055101d59f

📥 Commits

Reviewing files that changed from the base of the PR and between 5057144 and 674c305.

📒 Files selected for processing (2)
  • lib/deck/legality/__tests__/companions.test.ts
  • lib/deck/legality/companions.ts
📝 Walkthrough

Walkthrough

Adds COMPANION zone support across schema, legality, deck I/O, builder UI, hotkeys, tests, and docs. It also adds companion-specific legality predicates and formats companion violations as part of full deck legality.

Changes

Companion Zone Feature

Layer / File(s) Summary
DB schema and shared deck types
prisma/migrations/.../migration.sql, prisma/schema.prisma, lib/deck/mutation/types.ts, lib/deck/mutation/snapshot-pure.ts
Adds COMPANION to the Prisma zone enum, extends snapshot card fields for companion checks, and adds the companion_violation legality issue variant.
Companion legality engine
lib/deck/legality/companions.ts, lib/deck/legality/index.ts, lib/deck/legality/shared.ts
Implements companion restriction predicates, companion-zone rule evaluation, legality formatting, color-identity handling, and full legality integration.
Companion legality tests
lib/deck/legality/__tests__/companions.test.ts, lib/deck/legality.test.ts
Adds companion rule coverage, integration checks, registry-size assertions, and companion color-identity tests.
Deck I/O parsing, serialization, and add destinations
lib/deck/io/adapters/_shared.ts, lib/deck/io/adapters/arena.ts, lib/deck/io/serialize.ts, lib/deck/add-intent.ts, lib/deck/__tests__/add-intent.test.ts, lib/deck/io/__tests__/serialize.test.ts
Recognizes companion sections in deck text, orders and labels the zone consistently, serializes Arena companion output, and adds companion as an add destination.
Builder UI, deck history, and hotkeys
app/_components/builder/..., app/_components/deck/deck-history-list.tsx, app/_components/header-search/deck-mode-bar.tsx, app/_components/hotkeys/registry.ts
Adds companion rendering to builder zone panels, updates move-card menu ordering and shortcut handling, and extends deck history and mode-bar labels.
Docs updates
CONTEXT.md, README.md, docs/adr/0002-deck-illegality-is-a-ui-state.md
Updates the glossary, legality description, README feature list, and ADR wording for the companion zone and legality model.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • [Feature]: Companions #37: The changes implement companion-zone support and companion legality checks, matching the issue’s companion-focused objective.

Possibly related PRs

  • jcserv/maindeck#46: Both PRs touch app/_components/builder/move-card-menu.tsx and its ordering tests around zone selection.
  • jcserv/maindeck#56: Both PRs modify app/_components/builder/card-row-sortable.tsx keyboard handling for zone-moving shortcuts.

Poem

🐇 I hop where companions may nest,
With zones neatly sorted, all set to rest.
Ten rules in the moonlight, crisp and true,
The deck knows what each card should do.
Ooh, companion magic—bright and spry! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a companion deck zone with legality validation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fm/companion-zone-c5

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
lib/deck/io/serialize.ts (1)

6-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Deduplicate the zone sort contract.

lib/deck/io/adapters/_shared.ts:68-85 already defines ZONE_ORDER, and this copy now has to stay in lockstep with it. Centralizing the ordering in one shared module would avoid JSON serialization drifting from the adapter order the next time a zone is added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/deck/io/serialize.ts` around lines 6 - 12, The zone ordering contract is
duplicated in serialize.ts and _shared.ts, so JSON serialization can drift from
adapter ordering. Remove the local ZONE_ORDER constant in serialize.ts and reuse
the shared ZONE_ORDER from the _shared module instead, keeping the ordering
definition centralized and consistent across the serializer and adapter code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/deck/io/adapters/arena.ts`:
- Around line 43-48: The Companion section in arena.ts is inserting its own
blank separator, which causes duplicate empty lines before Sideboard and a
trailing newline for companion-only exports. Update the section assembly around
the companion block so the Companion header and its lines are appended without
an internal empty string, and let the existing export flow handle separators
between sections consistently. Use the byZone, lineFor, and lines.push logic in
the arena adapter to locate and normalize this behavior.

In `@lib/deck/legality/companions.ts`:
- Around line 156-160: The Kaheera legality check in the companions predicate
currently rejects creatures based only on printed subtypes from
cardTypesOf/creatureSubtypesOf, so Changeling creatures are missed. Update the
check function to also inspect the card’s oracle text (or equivalent card data)
before rejecting a creature, and treat any card with Changeling as having all
creature types so it passes the KAHEERA_TYPES requirement. Use the existing
companions check logic and the cards iteration in companions.ts to keep the fix
localized.
- Around line 102-107: The hasActivatedAbility helper only checks for colon-form
abilities, so keyword activated abilities like Equip, Crew, and Reconfigure are
missed. Update hasActivatedAbility(card: JudgedCard) in companions.ts to
recognize these keyword-based activated abilities in addition to the existing
isBasicLandCard and /:\s/ checks, using the card.oracleText text so Zirda
legality correctly accepts permanents whose only activated ability is expressed
as a keyword line.

---

Nitpick comments:
In `@lib/deck/io/serialize.ts`:
- Around line 6-12: The zone ordering contract is duplicated in serialize.ts and
_shared.ts, so JSON serialization can drift from adapter ordering. Remove the
local ZONE_ORDER constant in serialize.ts and reuse the shared ZONE_ORDER from
the _shared module instead, keeping the ordering definition centralized and
consistent across the serializer and adapter code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0bbc07e6-b66c-4ed9-80e9-8e356adc9566

📥 Commits

Reviewing files that changed from the base of the PR and between 6d6d123 and 3eee237.

📒 Files selected for processing (25)
  • CONTEXT.md
  • README.md
  • app/_components/builder/__tests__/move-card-menu.test.tsx
  • app/_components/builder/card-row-sortable.tsx
  • app/_components/builder/move-card-menu.tsx
  • app/_components/builder/sideboard-considering-dnd.tsx
  • app/_components/builder/sideboard-considering.tsx
  • app/_components/deck/deck-history-list.tsx
  • app/_components/header-search/deck-mode-bar.tsx
  • app/_components/hotkeys/registry.ts
  • docs/adr/0002-deck-illegality-is-a-ui-state.md
  • lib/deck/__tests__/add-intent.test.ts
  • lib/deck/add-intent.ts
  • lib/deck/io/adapters/_shared.ts
  • lib/deck/io/adapters/arena.ts
  • lib/deck/io/serialize.ts
  • lib/deck/legality.test.ts
  • lib/deck/legality/__tests__/companions.test.ts
  • lib/deck/legality/companions.ts
  • lib/deck/legality/index.ts
  • lib/deck/legality/shared.ts
  • lib/deck/mutation/snapshot-pure.ts
  • lib/deck/mutation/types.ts
  • prisma/migrations/20260628000000_zone_companion/migration.sql
  • prisma/schema.prisma

Comment thread lib/deck/io/adapters/arena.ts
Comment thread lib/deck/legality/companions.ts
Comment thread lib/deck/legality/companions.ts Outdated
jcserv added 4 commits June 29, 2026 08:56
- arena.ts: fix separator logic so companion-only export has no
  trailing blank line and companion+sideboard gets exactly one blank
  between sections
- companions.ts: Kaheera now accepts Changeling creatures (all
  creature types by oracle rule, not printed subtypes)
- companions.ts: hasActivatedAbility covers keyword-shorthand
  activated abilities (Equip, Crew, Reconfigure) that lack a colon
- serialize.ts: remove duplicate ZONE_ORDER; import from _shared
The DnD owner view always rendered the companion ZoneBlockDnd even
when no companion was set. Guard it with companion.length > 0,
matching the existing conditional in the read-only sibling view.
Merge-commit CI ran coverage over companions.ts (new in this PR) and
dropped total branch coverage to 98.85%, below the 99% threshold.

Five previously uncovered branches:
- Gyruda: null cmc falls back to 0 (even — legal)
- Kaheera: creature with no subtype ("Creature", no "—") flags illegal
- Umori: all-land deck short-circuits before type-sharing check
- Umori: null typeLine treated as no shared type
- Zirda: null oracleText treated as empty string (no activated ability)

Two unreachable ?? fallbacks on split()[0] and split()[1] — guaranteed
non-undefined after preceding null/includes checks — marked
/* c8 ignore next */ to exclude from branch denominator.

companions.ts now 100% branch coverage; global branches: 99.85%.
@jcserv jcserv merged commit 8559708 into main Jun 30, 2026
5 checks passed
@jcserv jcserv deleted the fm/companion-zone-c5 branch June 30, 2026 13:09
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.

1 participant