From da1f0848822f4992769df09161b60fd189190ca0 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 8 Nov 2026 16:17:45 +0100 Subject: [PATCH] docs(specs): add functional specs for ten roadmap initiatives The roadmap says what each initiative must achieve. Nothing said what it does in detail, so an agent picking up an initiative had to invent the product decisions. These ten specs close that gap, each in the house format already used by commander-power-meter.md. Every spec carries a dated market sweep of the whole category, not only the named incumbents, and a verified account of what the code does today. Three findings from the sweep shaped the specs: - Generation is commoditised, verification is not. Six products sell conversational Commander building; one documents that its own results vary between runs. None publishes which claims were checked against card data and which are model reasoning. - Collection-to-deck is crowded. SuperBrew, DeckFinder, ManaBuilder, MTG Agents and ManaForge all rank by coverage. None excludes copies already committed to another of the player's decks, and none separates Oracle identity for legality from printing identity for price. - Reproducibility is absent category-wide. Five hand simulators report numbers without a seed, an interval or a sample size. Two corrections to earlier assumptions, both from reading the sources: - I-14's "show the query before it runs" is already shipped by OffMeta. What is unoccupied is deck context surviving an edited query, and French. The spec scopes to those. - I-20's reference, Archidekt collaborators, grants direct real-time write access, not review. Propose-and-accept exists nowhere in the category, so the risk is overbuilding, not falling behind. I-13's spec records the UI audit: no card art anywhere, a design system document describing tokens the code does not have, a dead 3D stack with zero imports, and borders at 1.51:1 and 1.33:1 against a 3:1 requirement. Each spec ends with delivery slices and their dependencies, so the three scheduled agents can take work without colliding. Verified: tsc clean, lint clean, 3509 tests passing, every relative link in the new directory resolves. Co-Authored-By: Claude Opus 5 (1M context) --- docs/product/roadmap.md | 22 ++++ docs/product/specs/README.md | 42 +++++++ .../product/specs/i02-checkable-ai-copilot.md | 96 ++++++++++++++++ docs/product/specs/i13-visual-direction.md | 102 +++++++++++++++++ .../i14-verifiable-natural-language-search.md | 101 +++++++++++++++++ .../i15-rule-zero-sheets-and-pod-profiles.md | 105 ++++++++++++++++++ .../i16-decks-buildable-from-a-collection.md | 99 +++++++++++++++++ .../i17-precon-path-and-tiered-upgrades.md | 105 ++++++++++++++++++ .../specs/i18-per-deck-radar-of-new-cards.md | 96 ++++++++++++++++ .../specs/i19-offline-game-night-viewing.md | 97 ++++++++++++++++ .../product/specs/i20-collaborative-review.md | 99 +++++++++++++++++ .../i21-reproducible-probability-bench.md | 102 +++++++++++++++++ 12 files changed, 1066 insertions(+) create mode 100644 docs/product/specs/README.md create mode 100644 docs/product/specs/i02-checkable-ai-copilot.md create mode 100644 docs/product/specs/i13-visual-direction.md create mode 100644 docs/product/specs/i14-verifiable-natural-language-search.md create mode 100644 docs/product/specs/i15-rule-zero-sheets-and-pod-profiles.md create mode 100644 docs/product/specs/i16-decks-buildable-from-a-collection.md create mode 100644 docs/product/specs/i17-precon-path-and-tiered-upgrades.md create mode 100644 docs/product/specs/i18-per-deck-radar-of-new-cards.md create mode 100644 docs/product/specs/i19-offline-game-night-viewing.md create mode 100644 docs/product/specs/i20-collaborative-review.md create mode 100644 docs/product/specs/i21-reproducible-probability-bench.md diff --git a/docs/product/roadmap.md b/docs/product/roadmap.md index 343435c7..161ab15f 100644 --- a/docs/product/roadmap.md +++ b/docs/product/roadmap.md @@ -209,6 +209,8 @@ The November competitive pass did not open a new initiative for work the roadmap # Initiatives +Detailed functional specs live in [`docs/product/specs/`](specs/README.md): one per initiative, each with a dated market sweep, the current state of the code, and delivery slices with their dependencies. + ## I-01: Deck Editor Workflow **Outcome:** a player can build, reorganize and refine a deck without losing cards, context or intent. @@ -312,6 +314,8 @@ A user can move a card between every legal zone, reload the page, change its pri ## I-02: AI Deck Copilot +**Spec:** [`specs/i02-checkable-ai-copilot.md`](specs/i02-checkable-ai-copilot.md) + **Outcome:** a player can describe what they want and receive a legal, budget-aware and explainable plan. **Status:** In progress @@ -796,6 +800,8 @@ A local model is only promoted if it matches the required quality for a clearly ## I-13: Product Visual Redesign +**Spec:** [`specs/i13-visual-direction.md`](specs/i13-visual-direction.md) + **Outcome:** a player can find, build, understand and share a Commander deck in an interface that feels coherent, distinctive and calm even when card data is dense. **Status:** In progress @@ -837,6 +843,8 @@ The deck-library-to-editor-to-share journey is visually coherent and remains at ## I-14: Verifiable Natural-Language Search +**Spec:** [`specs/i14-verifiable-natural-language-search.md`](specs/i14-verifiable-natural-language-search.md) + **Outcome:** a player can describe a card function in English or French, read the Scryfall query that search will run, edit it, and get the cards that query returns. **Status:** Next @@ -862,6 +870,8 @@ For a fixed description and deck context, the player sees one Scryfall query, ca ## I-15: Rule 0 Sheets And Pod Profiles +**Spec:** [`specs/i15-rule-zero-sheets-and-pod-profiles.md`](specs/i15-rule-zero-sheets-and-pod-profiles.md) + **Outcome:** a playgroup can share intentions and house rules before a game, and can tell those preferences apart from official rules. **Status:** Next @@ -886,6 +896,8 @@ Four players can compare consented profiles and see house preferences distinguis ## I-16: Decks Buildable From A Collection +**Spec:** [`specs/i16-decks-buildable-from-a-collection.md`](specs/i16-decks-buildable-from-a-collection.md) + **Outcome:** a player can start from owned cards and see which commanders or lists those cards can build, including free copies and the cost of what is missing. **Status:** Next @@ -911,6 +923,8 @@ A collection produces a ranked, sourced view of buildable lists that shows free ## I-17: Precon Path And Tiered Upgrades +**Spec:** [`specs/i17-precon-path-and-tiered-upgrades.md`](specs/i17-precon-path-and-tiered-upgrades.md) + **Outcome:** a player can open a dated official precon, see the diff from that list, and apply a reversible budget bundle on a separate working copy. **Status:** Next @@ -935,6 +949,8 @@ A player can open a dated precon, preview a budget bundle, apply it to a working ## I-18: Per-Deck Radar Of New Cards +**Spec:** [`specs/i18-per-deck-radar-of-new-cards.md`](specs/i18-per-deck-radar-of-new-cards.md) + **Outcome:** a player can ask which newly released, legal cards matter for one deck, read a sourced reason, and adopt a card only from a visible diff. **Status:** Later @@ -959,6 +975,8 @@ A player can request a sourced, legal shortlist for one deck and accept or ignor ## I-19: Offline Game-Night Viewing +**Spec:** [`specs/i19-offline-game-night-viewing.md`](specs/i19-offline-game-night-viewing.md) + **Outcome:** a player can choose one deck and read that deck, its primer and its tokens without a network. **Status:** Next @@ -983,6 +1001,8 @@ After an explicit download, the chosen deck, primer and tokens remain readable o ## I-20: Collaborative Review +**Spec:** [`specs/i20-collaborative-review.md`](specs/i20-collaborative-review.md) + **Outcome:** an owner can invite someone to propose a deck patch, then accept or reject that patch without handing over the deck. **Status:** Later @@ -1007,6 +1027,8 @@ A collaborator can propose a change, the owner can accept or reject it, and the ## I-21: Reproducible Probability Bench +**Spec:** [`specs/i21-reproducible-probability-bench.md`](specs/i21-reproducible-probability-bench.md) + **Outcome:** a player can compare thousands of simulated hands between two deck snapshots under an explicit mulligan policy, and can rerun the same comparison. **Status:** Later diff --git a/docs/product/specs/README.md b/docs/product/specs/README.md new file mode 100644 index 00000000..11509be6 --- /dev/null +++ b/docs/product/specs/README.md @@ -0,0 +1,42 @@ +# Functional specs + +One spec per roadmap initiative. The [roadmap](../roadmap.md) says _what_ an initiative must achieve and _why_; a spec here says what it does in detail, what the market already does, what the code looks like today, and how the work splits into slices an agent or a developer can pick up without making a product decision. + +Written on 2026-11-08 from a market sweep of the whole category, not only the named incumbents, and from the code as it stood on `dev` that day. + +## How to read one + +Every spec follows the same shape, matching the house format already used by [`commander-power-meter.md`](../commander-power-meter.md): + +**Overview** · **What the market does** (sourced, dated) · **Current state in this codebase** (verified paths) · **Functional requirements** (numbered, testable) · **Data and API** · **Non-goals** · **Testing strategy** · **Delivery slices** (with dependencies) · **Open questions** + +The **Delivery slices** table is the part that matters for automation: each slice is independently reviewable, and the dependency column says which ones cannot start yet. Three scheduled agents pick work from this roadmap every day — the dependency edges are what stop them colliding. + +## Index + +| Spec | Initiative | Status | The one-line reason it exists | +| ------------------------------------------------------------------------------- | ---------- | ------ | ----------------------------------------------------------------------------------------------------- | +| [Checkable AI copilot](i02-checkable-ai-copilot.md) | I-02 | Now | Competitors validate **or** explain; doing both visibly is the cheapest differentiator on the roadmap | +| [Visual direction](i13-visual-direction.md) | I-13 | Next | The audit, the measured failures, and the register chosen over the fantasy one | +| [Verifiable natural-language search](i14-verifiable-natural-language-search.md) | I-14 | Next | Showing the query is already table stakes; deck context and French are not | +| [Rule 0 sheets and pod profiles](i15-rule-zero-sheets-and-pod-profiles.md) | I-15 | Next | Everyone classifies a deck; nobody records what four people agreed | +| [Decks buildable from a collection](i16-decks-buildable-from-a-collection.md) | I-16 | Next | Four products rank by coverage; none excludes copies already committed elsewhere | +| [Precon path and tiered upgrades](i17-precon-path-and-tiered-upgrades.md) | I-17 | Next | The market publishes upgrade articles; none is a reversible operation on your list | +| [Per-deck radar of new cards](i18-per-deck-radar-of-new-cards.md) | I-18 | Later | Spoiler coverage is universal and entirely set-centric; the per-deck join is open | +| [Offline game-night viewing](i19-offline-game-night-viewing.md) | I-19 | Next | Competitors ship offline card databases; the one deck you are about to play is the honest slice | +| [Collaborative review](i20-collaborative-review.md) | I-20 | Later | Archidekt gives collaborators direct write access; propose-and-accept exists nowhere | +| [Reproducible probability bench](i21-reproducible-probability-bench.md) | I-21 | Later | Five tools simulate thousands of hands; none publishes a seed or an interval | + +## What the sweep concluded + +Three findings shaped every spec above. + +**Generation is commoditised; verification is not.** Six products sell conversational Commander building, several free. One of them documents that its own results vary between runs. What no competitor publishes is which claims were checked against card data and which are model reasoning. + +**The collection-to-deck direction is crowded; the ownership detail is not.** SuperBrew, DeckFinder, ManaBuilder, MTG Agents and ManaForge all rank decks by collection coverage. None states that a copy already committed to another of your decks is excluded, and none separates Oracle identity (for legality) from printing identity (for price). + +**Reproducibility is absent across the whole category.** Hand simulators, bracket calculators and AI builders all return a number without a seed, an interval or a sample size. For a product positioning itself on checkable output, that is the widest opening found in the sweep. + +## Discipline + +A spec is a plan, not a record. It does not mark anything shipped — the roadmap's `Status` and its scope checkboxes remain the only source of truth for what exists. Competitor rows are claims read from public pages on a stated date, not evidence; revalidate before a row justifies implementation. diff --git a/docs/product/specs/i02-checkable-ai-copilot.md b/docs/product/specs/i02-checkable-ai-copilot.md new file mode 100644 index 00000000..6bbf9373 --- /dev/null +++ b/docs/product/specs/i02-checkable-ai-copilot.md @@ -0,0 +1,96 @@ +# Spec: Checkable AI Copilot (I-02) + +> **Initiative:** [I-02 in the roadmap](../roadmap.md) · **Status:** In progress · **Priority:** Now · **Researched:** 2026-11-08 +> **Owns:** making the deterministic verdict visible. **Does not own:** natural-language search (I-14) or meta evidence (I-09). + +## Overview + +The copilot already validates its own output deterministically and never lets the model decide legality. The player cannot see that. This spec makes the check visible: every claim in a suggestion is marked as **verified against card data** or as **model reasoning**, and the validator's verdict is shown rather than silently applied. + +## What the market does + +Conversational Commander building is no longer a gap. Six products sell it, several free. Read on 2026-11-08 from each product's own pages: + +| Product | Validation | Explanation | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | +| **ManaTap** | States deterministic legality and colour-identity validation; a Deck Checker scores legality, curve, ramp, draw, removal, threats, synergy | Chat and an iterative workshop | +| **Farseek** | **States no deterministic legality validation**; performs live Scryfall lookups before each recommendation | Argues why a card fits, reading the whole list | +| **DeckCheck** | DTI, PowerTune, ManaTune, DeckTrim; its support page documents that **AI results vary between runs** | Deckhand chat | +| **MTG Master** | States it enforces legal commander, colour identity and singleton | Bracket and collection-aware generation | +| **ManaForge** | Re-validates saved decks against live ban lists | Build-from-stock, commander swaps | +| **CommanderSalt** | — | A May 2026 community thread calls its report detailed but prone to overrating decks | + +**The pattern.** Products either validate **or** explain. ManaTap validates and scores but does not publish which claims were checked. Farseek explains persuasively and does not claim validation at all. DeckCheck openly documents that its output is non-deterministic. + +**The opening.** Do both, and say which is which. A suggestion where "this is legal in your colour identity" is a checked fact and "this smooths your turn-three" is model reasoning is a different product from one where both arrive in the same sentence at the same confidence. The validator already exists; it is simply invisible. + +This is also the cheapest differentiator on the roadmap: it is a presentation change over machinery that already passes its tests. + +## Current state in this codebase + +Already shipped, per the roadmap: + +- Claude and OpenAI providers, server-side keys, rate limiting, prompt-injection protections. +- A **deterministic post-generation validator**; the LLM never decides legality. +- Structured plan before generation; per-card reasoning; alternatives by budget, power and play pattern. +- Diff before applying; "why is this card here?" and "what is the weakest card?". +- A golden evaluation set for structure and evidence contracts. +- `ExternalRecommendationFreshness` and AI-suggested labelling (#1052) — the labelling vocabulary already exists for external sources. + +Open in the roadmap: _"Keep the deterministic validator's verdict visible in the copilot's output instead of only applying it silently."_ This spec details that item. + +## Functional requirements + +### 1. Two kinds of claim, never mixed + +1.1 Every statement attached to a suggestion is typed as **checked** or **reasoned**. +1.2 A checked claim names what verified it — Scryfall card data, the format banlist, the colour-identity rule, the Game Changers list, the singleton rule — and carries the data's observation time. +1.3 A reasoned claim is labelled as model reasoning, with no source, and is never styled to look checked. +1.4 Checked and reasoned claims are visually distinguishable without relying on colour alone. + +### 2. The verdict is shown + +2.1 After generation, the validator's result is rendered: what passed, what was corrected, what was rejected. +2.2 A suggestion the validator rejected is shown as rejected with the rule that rejected it, not silently dropped. A player learning that the model proposed an illegal card, and that the product caught it, is the proof the feature exists to give. +2.3 The count of corrections is visible at the top of the result, not buried per card. + +### 3. Reproducibility is stated + +3.1 The result states whether the same brief would reproduce the same plan, and names the provider and model used. +3.2 Where output is non-deterministic, say so — DeckCheck documents this and the product should not pretend otherwise. + +### 4. No regression in the existing contract + +4.1 The LLM still never decides legality, colour identity, quantities or bracket rules. +4.2 Making the verdict visible changes presentation only; the validator's behaviour is unchanged and its tests are untouched. + +## Non-goals + +- Increasing generation volume or speed. Explicitly a non-goal in the roadmap: a wrong deck produced instantly is the failure this product exists to avoid. +- Qualitative provider-backed evaluation of usefulness, until a stable review rubric exists — that remains the open golden-set item. +- Replacing I-09's source-and-freshness labelling with a second vocabulary. Reuse it. + +## Testing strategy + +- **Contract:** every claim in a copilot response carries a type; an untyped claim fails the test. +- **Contract:** a checked claim carries a source and an observation time; a reasoned claim carries neither. +- **Sociable:** a brief that provokes an illegal suggestion renders it as rejected with its rule, and the deck is unchanged. +- **Accessibility:** the checked/reasoned distinction survives greyscale and a screen reader. +- **Regression:** the existing golden set passes unchanged — this spec must not alter validator behaviour. + +## Delivery slices + +| # | Slice | Depends on | +| --- | -------------------------------------------------------------------------- | ---------- | +| 1 | Type every claim at the API boundary: `checked` with source, or `reasoned` | — | +| 2 | Render the distinction, non-colour-dependent, in the suggestion panel | 1 | +| 3 | Validator verdict panel: passed, corrected, rejected with rules | 1 | +| 4 | Rejected suggestions shown rather than dropped | 3 | +| 5 | Provider, model and reproducibility statement on the result | 1 | + +Slice 1 is a contract change and should land with its tests before any UI work; slices 2 and 3 can then proceed in parallel. + +## Open questions + +- Does showing rejected suggestions undermine confidence, or build it? The argument for showing them is that the product's whole claim is that the check is real — and an invisible check is indistinguishable from no check. Worth watching once it ships. +- Should the verdict be exportable with the deck, so a player can show their pod that the list was validated? That edges into I-15's Rule 0 sheet and may belong there instead. diff --git a/docs/product/specs/i13-visual-direction.md b/docs/product/specs/i13-visual-direction.md new file mode 100644 index 00000000..9815ca61 --- /dev/null +++ b/docs/product/specs/i13-visual-direction.md @@ -0,0 +1,102 @@ +# Spec: Visual Direction (I-13) + +> **Initiative:** [I-13 in the roadmap](../roadmap.md) · **Status:** Next · **Audited:** 2026-11-08 +> **Owns:** the visual direction, its tokens and its acceptance thresholds. **Does not own:** the functional surfaces being restyled. + +## Overview + +The roadmap's first I-13 slice asks for an audit and an agreed direction before implementation. This spec is that deliverable: what the audit measured, what it found, the direction chosen, and the thresholds each redesigned slice must meet. + +## What the audit found + +Measured on 2026-11-08 from a local dev server at 1440×900 and 390×844, with contrast computed from the real values in `src/styles/globals.css`. + +### Blocking + +1. **No card art anywhere.** The hero's product preview is four empty rounded rectangles and an unlabelled bar chart. On mobile the preview is hidden entirely, so a phone visitor sees no product visual at all. Magic's own art is the one visual asset the category gives away free, and every incumbent leads with it. + +2. **The documented identity does not exist in the code.** `docs/design/design-system.md` describes Cinzel Decorative, a `--gold` family, a noise overlay and floating mana. `grep --gold src/` returns **0 results**. The doc claims `--background: #0a0a0f` and `--accent: #6366f1`; the code says `#1b1628` and `#7c3aed`, plus a "lumen" token layer the doc never mentions. + +### To fix + +3. **A dead 3D stack is still declared.** `three`, `gsap`, `@react-three/fiber`, `@react-three/drei`, `@react-three/postprocessing` and `@types/three` have **0 imports** in `src/`. `public/textures/` ships leather, paper and stone maps nothing references. The README still advertises a "3D landing experience". + +4. **Borders fail WCAG 1.4.11**, which requires 3:1 for component boundaries: + +| Pair | Ratio | Required | +| --------------------------------------- | ------ | -------- | +| `--border #443a5e` / surface (dark) | 1.51:1 | 3:1 | +| `--border #e4dcf2` / white (light) | 1.33:1 | 3:1 | +| `--accent #7c3aed` as text / background | 3.09:1 | 4.5:1 | + +`src/components/deck/MoxfieldProfileResults.tsx:102` applies `text-[var(--accent)]` to a `text-xs` link — the failing pair, on the smallest text in the interface. + +5. **Brackets are encoded by hue alone.** Green, blue, amber, red each pass 3:1 individually, but a deuteranopic player cannot separate bracket 1 from bracket 4 — exhibition from optimized. + +### To tighten + +6. The first screen carries a headline, a paragraph and a button, then roughly 150 px of nothing. 7. The same action appears four times under two labels ("Start a guest deck" ×3, "Guest deck" in the nav). 8. "Commander field guide" appears three times in one viewport. 9. Three always-visible theme buttons compete with the only call to action on a 390 px screen. + +### What works, and must survive + +Text contrast is **excellent** — 16.09:1 primary, 8.06:1 secondary. The light theme is better balanced than the dark one. The hero headline states the product in one sentence. The capability copy describes real functions without inflation. + +## The direction + +**Two registers, one token set.** + +- **The instrument** for the application — editor, analysis, acquisition. Dense, tabular, calm. Monospace tabular figures wherever digits compare in a column. One signal hue. A provenance chip on every piece of external data. This register _is_ the "AI you can check" positioning made visible, and it is what no competitor's UI expresses. +- **Paper and ink** for everything public — landing, public decks, discovery. The interface recedes and card art carries the colour. The five mana colours, already in the token set, are the only semantic palette. + +They are not two designs. They share neutrals, type scale and spacing, and differ in density and in how much imagery is allowed. One system, two settings. + +**Rejected:** a fantasy-serif "grimoire" register. It is what the current documentation describes, it photographs well on one mockup, and it degrades fastest under the density of a 100-card editor. Choosing it would require a discipline no one sustains, and it dates quickly. Keep Cinzel out of the product. + +## Token requirements + +1. **Every token is defined in the base `:root`.** A colour whose only definition sits inside a media query or a `[data-theme]` block never applies in the unstamped default state. +2. **Borders meet 3:1** against the surface behind them, in both themes. This is a correction, not a preference. +3. **`--accent` is never a text colour.** Dark themes use `--accent-text`, which already measures 9.54:1. The failing call site above is the first fix. +4. **No status is encoded by hue alone.** Bracket, severity and legality each carry a shape, a glyph or a number in addition to colour. +5. **One accent.** Semantic colours — good, warning, critical — are separate from the accent and do not become a second brand hue. +6. **Tabular numerals** wherever figures align in columns. + +## Non-goals + +- A native mobile application. Narrow web widths are in scope; an app is not. +- Rewriting working functionality for visual reasons. +- Copying Moxfield's or EDHREC's layout, artwork or interaction details. +- Reviving the 3D landing. The direction above replaces it; the dependencies should be removed rather than reconnected. + +## Acceptance thresholds per slice + +A redesigned slice is not done until all of these hold: + +- Every text pair meets 4.5:1; every component boundary meets 3:1; measured, not estimated. +- No information is conveyed by colour alone. +- Keyboard focus is visible on every interactive element; the slice is operable by screen reader. +- The slice renders correctly in all **three** theme states: explicit light, explicit dark, and unstamped system default. +- No horizontal body scroll at 390 px; wide content scrolls inside its own container. +- Visual regression coverage exists for the slice. +- Lighthouse performance does not regress against the pre-slice measurement. + +## Delivery slices + +| # | Slice | Depends on | +| --- | ---------------------------------------------------------------------------------------------------------- | ---------- | +| 1 | **Token corrections** — borders to 3:1, accent-as-text removed, non-colour status encoding | — | +| 2 | **Remove the dead 3D stack** and align the README with reality | — | +| 3 | **Rewrite `design-system.md` from the code**, including the lumen layer | 1 | +| 4 | **Card art in the hero** — a real commander, its colour identity, three cards, with a designed empty state | 1 | +| 5 | Deck library and navigation in the instrument register | 1, 3 | +| 6 | Deck editor: card views, warnings, analytics, at density | 5 | +| 7 | Public decks and discovery in the paper-and-ink register | 4 | +| 8 | Responsive, accessibility, visual-regression and performance validation across the redesigned journey | 5, 6, 7 | + +Slices 1, 2 and 3 are corrections with no aesthetic debate attached, and should land before any restyling begins. Slice 4 is the highest visible return for the least effort in the whole initiative. + +## Open questions + +- Which card art appears in the hero for a visitor with no deck? A fixed curated commander is predictable and legally safe; a rotating one is livelier and harder to keep looking deliberate. +- Does the instrument register survive the landing page, or does the split confuse the brand? The shared token set is the mitigation, but it needs one side-by-side review before slice 7. +- `public/textures/` is 32 KB of unused leather, paper and stone. Delete with slice 2, or keep for the paper-and-ink register? Keeping unused assets is how the 3D stack survived this long. diff --git a/docs/product/specs/i14-verifiable-natural-language-search.md b/docs/product/specs/i14-verifiable-natural-language-search.md new file mode 100644 index 00000000..5d1f5401 --- /dev/null +++ b/docs/product/specs/i14-verifiable-natural-language-search.md @@ -0,0 +1,101 @@ +# Spec: Verifiable Natural-Language Search (I-14) + +> **Initiative:** [I-14 in the roadmap](../roadmap.md) · **Status:** Next · **Researched:** 2026-11-08 +> **Owns:** turning a description into a readable Scryfall query. **Does not own:** building or changing a deck — that stays in I-02. + +## Overview + +A player describes a card function in English or French. The product writes **one Scryfall query**, shows it before running it, lets the player edit it, and returns only what that query returns. The query is the contract: what the player reads is what the search executes. + +## What the market does + +Natural-language card search is a crowded space as of 2026-11-08. Every row below was read from the product's own public pages; treat each as a claim to confirm before it justifies scope. + +| Product | What it does | What it does not state | +| ------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| **OffMeta** (offmeta.app) | Plain-English card search, and the player can **inspect and edit the generated search** | No deck context; English only | +| **Farseek** (farseek.ai) | Writes the Scryfall query from a description and returns real cards | Query editing is not advertised; English only | +| **DeckCheck** Smart Search (deckcheck.co) | Plain English to a Scryfall query, inside the deck editor | Does not state that the query is shown before it runs | +| **Imskir** (Chrome extension, updated 2026-03-30) | Translates descriptions into Scryfall syntax in the browser | Not deck-aware; a separate surface from any builder | +| **CardMystic**, **Find That Card** | Describe-and-find engines; Find That Card adds Scryfall Tagger art and function tags | No deck context; English only | + +**The honest conclusion.** "Show the query" is **not** a differentiator — OffMeta already does it. Two things remain genuinely unoccupied: + +1. **Deck context survives the translation.** None of these tools states that the active format, colour identity and legality of the open deck constrain both the generated query and the player's edited version. +2. **French.** Every tool above is English-only. The product already ships a reviewed French catalogue, and Commander is large in France. + +Scope the initiative around those two, not around the translation itself. + +## Current state in this codebase + +- `src/lib/scryfall/client.ts` — `searchCards(query, page)` sends raw Scryfall syntax, caches results for 1 h in the DB, and serialises calls behind a 100 ms mutex. +- `src/components/search/SearchBar.tsx`, `SearchFilters.tsx`, `SetAutocomplete.tsx` — syntax and filter driven search. +- There is **no** natural-language layer anywhere, and no place where a query string is surfaced to the player as an editable artifact. + +## Functional requirements + +### 1. One query, shown before it runs + +1.1 A description in English or French produces exactly **one** Scryfall query string. +1.2 The query is displayed in full, in a monospace field, **before** any request to Scryfall. +1.3 Running the search is an explicit action. Nothing is fetched while the player is still typing the description. +1.4 If the model cannot produce a query, say so and leave the syntax search untouched. Never fall back to a guessed query. + +### 2. The query is editable, and the edit wins + +2.1 The player can edit the query text freely. +2.2 An edited query runs verbatim. The description is not re-translated behind the player's back. +2.3 Editing the description after an edit discards the edited query only after an explicit confirmation. + +### 3. Deck context is preserved on both paths + +3.1 When a deck is open, the generated query carries its format legality and the commander's colour identity. +3.2 The same context is re-applied to an **edited** query. An edit that would widen beyond the deck's colour identity is shown as a warning, not silently corrected. +3.3 A player can disable the context for one search; the field states that results may be illegal in the open deck. + +### 4. Results come only from Scryfall + +4.1 Every returned card comes from the executed query's response. No card name is ever produced by the model. +4.2 A query returning nothing shows the empty state and the query that produced it, so the player can edit rather than retry blindly. + +### 5. Both languages, and a way back + +5.1 French and English descriptions are supported, with regression coverage per language. +5.2 A visible control returns to syntax-only search and keeps the current results. + +## Data and API + +- New route `POST /api/search/translate` → `{ query: string, context: { format, colorIdentity } }`. Server-side only; the provider key never reaches the browser. +- The route returns the query **only**. It never returns cards: card data comes from the existing Scryfall path, so the cache, the rate limiter and the headers stay in one place. +- Rate limit per account, reusing the existing AI rate-limit middleware. + +## Non-goals + +- Generating or modifying a deck. That is I-02. +- Teaching Scryfall syntax. The query is shown to be verified and edited, not as a tutorial. +- Supporting languages beyond English and French before I-11 activates a third locale. +- Caching translations across accounts: a description can carry private deck context. + +## Testing strategy + +- **Solitary:** a fixed description with a fixed provider response produces a stable query string, per language. +- **Contract:** the translate route never returns card names; a response containing them fails the test. +- **Sociable:** with a deck open, the generated query carries its colour identity; an edited query that drops it raises the warning and still runs. +- **Regression:** a French description and its English equivalent produce queries that return the same card set for a fixed corpus. + +## Delivery slices + +| # | Slice | Depends on | +| --- | ----------------------------------------------------------------------- | ---------- | +| 1 | Translate route + query display, English, no deck context | — | +| 2 | Editable query, explicit run, empty-state with the query visible | 1 | +| 3 | Deck context on generated and edited queries, with the widening warning | 2 | +| 4 | French support and per-language regression set | 1 | +| 5 | Return-to-syntax control and the disable-context escape hatch | 3 | + +Slices 1-2-3 are one chain; slice 4 is independent of 2 and 3 and can run in parallel. + +## Open questions + +- Which provider writes the query? Reusing I-02's Claude path keeps one key and one rate limiter, but a smaller model may be enough and cheaper for a one-line translation. +- Should a generated query be stored on the deck as a saved search? That would make it a shareable artifact, which is appealing but adds a persistence surface this initiative does not currently need. diff --git a/docs/product/specs/i15-rule-zero-sheets-and-pod-profiles.md b/docs/product/specs/i15-rule-zero-sheets-and-pod-profiles.md new file mode 100644 index 00000000..75c9d3bf --- /dev/null +++ b/docs/product/specs/i15-rule-zero-sheets-and-pod-profiles.md @@ -0,0 +1,105 @@ +# Spec: Rule 0 Sheets And Pod Profiles (I-15) + +> **Initiative:** [I-15 in the roadmap](../roadmap.md) · **Status:** Next · **Researched:** 2026-11-08 +> **Owns:** stating and comparing playgroup intentions. **Does not own:** official bracket scoring, which stays in I-03. + +## Overview + +Before a game, a pod needs thirty seconds of shared context: how fast each deck wants to end, how it wins, whether it runs combos, extra turns or resource denial, how many proxies are in it, and which house rules apply. The sheet records that, keeps it private until the owner shares it, and never pretends to be the official rules. + +## What the market does + +Wizards shipped **Commander Brackets** in 2025 as the official vocabulary: five tiers — Exhibition, Core, Upgraded, Optimized, cEDH. The informal 1-to-10 scale it replaced meant something different in every playgroup. Brackets anchor the conversation to how quickly a deck expects to end a game. + +| Product | What it does | Limit | +| ------------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| **EDHREC salt** | Community survey scoring how much players dislike facing a card, 0 to 4 | A card-level opinion poll, not a deck agreement | +| **ScrollVault**, **EDHcheck**, **castlog** | Read a list and return one of the five official tiers | They classify a deck; they do not record what a pod agreed | +| **CommanderSalt** | Explains bracket, power, salt and synergies for an imported list | A community thread from May 2026 calls the report entertaining but prone to overrating decks | +| **commandermatchup.online** | Positions itself as a Rule 0 matchmaking hub | Matchmaking is a different product from a pod's own sheet | +| **DeckCheck DTI** | A deck-tendency index inside the editor | Proprietary scale, not the official brackets | + +One number worth keeping: a published bracket calculator tested against **1,229 tournament decklists matched the organiser's bracket exactly 77.1 % of the time, and was within one bracket 99.5 %** of the time. That is the realistic accuracy ceiling for automatic classification, and the reason this initiative records stated intentions rather than inferring them. + +**The gap.** Every tool classifies a deck. None records an agreement between four people, keeps it private by default, and keeps preference apart from rule. + +## Current state in this codebase + +- `src/lib/deck/bracket.ts` — bracket scoring across six dimensions, with the Game Changers list and banlist detection. This is the official-rules side and must not be mixed into the sheet. +- No salt data, no pod concept, no per-deck declaration of intent. +- Sharing today is public-or-private at deck level (I-08). There is no consented, scoped share. + +## Functional requirements + +### 1. The sheet + +1.1 A sheet attaches to a deck, or to a pod of up to four decks. +1.2 Recorded fields: intended speed, how the deck wins, combos present, extra turns, resource denial (land destruction, stax), proxy count, and free-text house rules. +1.3 Every field is **player-stated**. Nothing on the sheet is inferred from the decklist. +1.4 The sheet shows the deck's computed bracket **beside** the stated intent, visibly separated, with the label that one is calculated and the other declared. + +### 2. Privacy and consent + +2.1 A sheet is private by default. Nobody sees it until the owner shares it. +2.2 Sharing is an explicit action scoped to one pod, and is revocable. +2.3 Revoking removes the sheet from the pod view immediately; it does not retroactively delete what other players already read. +2.4 A shared sheet never exposes the deck's collection or ownership data. + +### 3. Comparison + +3.1 Up to four consented sheets compare side by side on one screen. +3.2 The comparison highlights declared mismatches — a deck stating "ends on turn 5" in a pod stating "long games" — as a conversation prompt, never as a verdict. +3.3 Official data (bracket, Game Changers, banlist) renders in a separate column from declared preferences, with distinct styling. + +### 4. Honesty + +4.1 The sheet states, on screen, that it does not guarantee a balanced game. +4.2 Any salt-style score is labelled as community opinion with its source and date, never as a rule. + +## Data model + +``` +PodSheet id, ownerId, deckId?, podId?, createdAt, updatedAt + speed enum (exhibition | core | upgraded | optimized | cedh | custom) + winConditions text[] + hasCombos bool + text + hasExtraTurns bool + text + hasResourceDenial bool + text + proxyCount int + houseRules text +Pod id, name, ownerId, createdAt +PodShare podSheetId, podId, consentedAt, revokedAt? +``` + +`PodShare` carries the consent, so revoking is a row update and never a delete of the player's own sheet. + +## Non-goals + +- Matchmaking, ranking or pairing players. That is a different product. +- Inferring intent from a decklist. Automatic classification stays in I-03, where it is already labelled as calculated. +- A public directory of sheets. +- Replacing the official bracket. The sheet sits beside it. + +## Testing strategy + +- **Solitary:** a sheet with declared fields renders them unchanged; no field is derived. +- **Boundary:** a revoked share disappears from the pod comparison on the next read; the owner's sheet survives. +- **Sociable:** four consented sheets compare, and the official column is present and visually distinct in both themes. +- **Privacy:** a sheet shared to pod A is unreachable from pod B, and from an unauthenticated request. + +## Delivery slices + +| # | Slice | Depends on | +| --- | -------------------------------------------------------------------- | ---------- | +| 1 | Sheet model, per-deck create and edit, private only | — | +| 2 | The computed bracket shown beside declared intent, visibly separated | 1 | +| 3 | Pod creation, explicit consent share, revoke | 1 | +| 4 | Four-way comparison with the mismatch prompts | 2, 3 | +| 5 | Salt-style score as a labelled, dated community signal | 4 | + +Slice 5 depends on I-09's source-and-freshness disclosure already being applied to EDHREC data; do not start it before that is true. + +## Open questions + +- Is the pod a durable object players reuse weekly, or a throwaway for one night? Durable implies membership and invitations, which is noticeably more product. +- Should the proxy count feed the acquisition plan in I-06? Tempting, but it couples a social declaration to a financial calculation; keep them apart until a player asks. diff --git a/docs/product/specs/i16-decks-buildable-from-a-collection.md b/docs/product/specs/i16-decks-buildable-from-a-collection.md new file mode 100644 index 00000000..b016890c --- /dev/null +++ b/docs/product/specs/i16-decks-buildable-from-a-collection.md @@ -0,0 +1,99 @@ +# Spec: Decks Buildable From A Collection (I-16) + +> **Initiative:** [I-16 in the roadmap](../roadmap.md) · **Status:** Next · **Researched:** 2026-11-08 +> **Depends on:** I-06 printing-level ownership. Do not rank lists while cost still depends on a printing the model cannot name. + +## Overview + +Today the product answers "what does this deck cost me?". This initiative answers the reverse: "what can I build with what I own?" — ranked by coverage, honest about what is missing, and without touching ownership or the open deck. + +## What the market does + +This is the most contested space of the eight initiatives. Four products already answer the question. + +| Product | What it does | Price | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| **SuperBrew** (MTGGoldfish) | Finds decks to build from your collection | Premium only, $6/month | +| **DeckFinder** (deck-finder.com) | Collection to deck coverage for Commander, constructed formats and Arena; shows which decks you are closest to finishing and what is missing | — | +| **ManaBuilder** (manabuilder.com) | Upload a collection, AI generates a complete legal list from owned cards | — | +| **MTG Agents** | An Insights tab with "Which decks can I build?" and "Missing for a deck" panels | — | +| **ManaForge** | Build-from-stock generation from paper or Arena collections, including wildcard awareness | from ~EUR 9.99/month | + +**What none of them states.** Three things, each of which the product can already do or nearly do: + +1. **Free copies.** No competitor states that a copy already committed to another of your decks is excluded from a candidate list. A player with one Sol Ring and six decks is told six times that they own it. +2. **Printing-level cost.** Coverage computed on Oracle identity makes a cheap and an expensive printing interchangeable. The product's I-06 model distinguishes them. +3. **Provenance of the candidate.** Where did the suggested list come from — a community deck, a precon, a generated list? Only a sourced candidate can be judged. + +Rank the initiative on those three, not on coverage percentage, which is table stakes. + +## Current state in this codebase + +- `src/lib/collection/acquisition-plan.ts` — aggregates required copies across owned decks and subtracts collection quantities. This is the one-deck direction. +- `src/lib/collection/acquisition-intent.ts`, `acquisition-csv.ts`, `printing-csv.ts` — buy-now/buy-later state and exports. +- `src/lib/deck/bracket.ts`, colour-identity and legality validation — reusable for candidate legality. +- **Missing:** any reverse index from a collection to candidate lists, and any notion of a copy being committed to a deck. + +## Functional requirements + +### 1. Ranking + +1.1 Given a collection, return candidate commanders or lists ranked by the share of the candidate the collection already covers. +1.2 Coverage counts **copies**, not distinct rows — four basics owned once is not four cards. +1.3 Ranking never mutates the collection, a deck, or acquisition state. +1.4 The ranking request is bounded and paginated; a 20 000-card collection must not produce an unbounded scan. + +### 2. Two identities, used for two different jobs + +2.1 **Oracle identity** decides colour identity, legality and singleton — a card is the card, whatever the printing. +2.2 **Printing identity** decides price and ownership — the cost of what is missing depends on which printing is owned or needed. +2.3 A candidate whose missing cards have no known price shows those rows separately, never folded into a subtotal of zero. + +### 3. Free-cards mode + +3.1 A mode treats copies already allocated to the player's other decks as unavailable. +3.2 The mode is an explicit toggle with its effect stated: "excludes copies used in your other decks". +3.3 Switching the mode re-ranks without changing any stored data. + +### 4. Provenance + +4.1 Every candidate shows where it came from: a public deck, a precon catalogue entry, or a generated skeleton. +4.2 A generated candidate is labelled as generated and is never ranked above a sourced list at equal coverage. + +## Data and API + +- `GET /api/collection/buildable?mode=all|free&cursor=` → ranked candidates with `{ source, coverage: { owned, total }, missing: { count, knownPriceSubtotal, unpricedCount } }`. +- Read-only. The route must be incapable of writing: no ownership, deck or intent mutation in its code path. +- A commitment index (printing → decks using it) is required for free-cards mode. Derive it from existing deck rows; do not introduce a second source of truth for ownership. + +## Non-goals + +- Generating a full 100-card list from the collection. That is I-02 with a collection constraint, not this initiative. +- Arena wildcard optimisation. Explicitly out, per the roadmap's non-goals. +- Buying. The acquisition plan in I-06 owns that, and this initiative hands off to it. +- Ranking against Oracle identity alone — blocked until I-06's printing-level ownership lands. + +## Testing strategy + +- **Solitary:** coverage arithmetic over quantities, including the four-basics case and the duplicate-printing case. +- **Boundary:** free-cards mode with one copy shared by three decks returns the copy as available in none of them but leaves ownership untouched. +- **Contract:** the buildable route, called with every mode, produces zero writes — asserted against the Prisma client, not by inspection. +- **Performance:** a synthetic 20 000-card collection returns the first page within the route's deadline. + +## Delivery slices + +| # | Slice | Depends on | +| --- | -------------------------------------------------------------------------- | ---------- | +| 0 | **I-06 printing-level ownership** — blocking, not part of this initiative | — | +| 1 | Coverage computation over a fixed candidate corpus, Oracle legality, no UI | 0 | +| 2 | Read-only ranked route with pagination and unpriced disclosure | 1 | +| 3 | Collection-page surface showing rank, coverage and missing cost | 2 | +| 4 | Commitment index and free-cards mode | 2 | +| 5 | Provenance labels and the generated-candidate rule | 3 | + +Slice 0 is the whole risk of this initiative. Validate demand before starting slice 1: the roadmap already marks this effort as medium-to-large. + +## Open questions + +- Where does the candidate corpus come from? Public community decks are free but uneven; precons are dated and trustworthy but few. Starting with precons would make this initiative share its catalogue with I-17. +- Does coverage weight cards by role? A collection covering 95 % of a list but missing its commander covers nothing useful. A hard floor on the command zone is probably required before any percentage is shown. diff --git a/docs/product/specs/i17-precon-path-and-tiered-upgrades.md b/docs/product/specs/i17-precon-path-and-tiered-upgrades.md new file mode 100644 index 00000000..dda40768 --- /dev/null +++ b/docs/product/specs/i17-precon-path-and-tiered-upgrades.md @@ -0,0 +1,105 @@ +# Spec: Precon Path And Tiered Upgrades (I-17) + +> **Initiative:** [I-17 in the roadmap](../roadmap.md) · **Status:** Next · **Researched:** 2026-11-08 +> **Owns:** dated official precon lists and editorial swap bundles. **Does not own:** generic budget suggestions, which stay in I-02. + +## Overview + +A precon is where most Commander players start. This initiative keeps a **dated catalogue** of official lists, shows the diff between the sealed deck and the player's copy, and offers reversible budget bundles applied to a separate working copy — so a player can try an upgrade path without destroying the list they own. + +## What the market does + +Precon upgrading is a mature editorial category, and the tiers are already standardised across sites. + +| Source | What it offers | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| **EDHREC precon upgrades** | Per-precon upgrade pages driven by their inclusion data | +| **Draftsim** | A precon database with full decklists, commanders and current singles value; guides naming cards to add, five budget picks and what to cut | +| **The Spark MTG** | Upgrade kits placing all decks of a set on one page with **budget, mid and full tiers** and a one-click cart per tier | +| **RateMyDecks** | Upgrade paths at **$30, $60 and $100** | +| **PreconForge** | "The first 10 cards you should replace" framing | + +The market has also converged on a **bracket mapping** worth reusing as editorial guidance, not as a promise: roughly $30 moves a precon from bracket 1 to low bracket 2, $60 reaches mid bracket 2, and $100 can reach bracket 3. + +**The gap.** Every source above is an article. None of them is a _reversible operation on your own list_. The player reads a guide and edits by hand, losing the sealed baseline. Owning the diff and the undo is the product's angle. + +## Current state in this codebase + +- No precon catalogue. The only occurrence of "precon" in `src/` is inside `src/app/api/ai/build/route.ts`, in prompt text. +- `src/lib/deck/` has snapshots and a deterministic comparison engine (I-07) — the diff machinery already exists and should be reused rather than rebuilt. +- Deck duplication and forking exist (I-08) — the working-copy mechanism exists too. + +This initiative is therefore mostly **data plus editorial**, not new engine work. + +## Functional requirements + +### 1. The catalogue + +1.1 Each precon is stored as a dated, versioned list with its source URL and the date that source was read. +1.2 A precon entry names its set, its commander, and its release date. +1.3 The catalogue is append-only: correcting a list creates a new version, so a player's existing diff never changes under them. +1.4 Card identity is stored at printing level, because a precon ships specific printings and the price depends on them. + +### 2. The diff from sealed + +2.1 Opening a precon shows the diff between the sealed list and the player's working copy. +2.2 The commander is preserved until the player changes it explicitly. +2.3 The diff reuses the I-07 comparison engine. No second diff implementation. + +### 3. Bundles + +3.1 A bundle is an editorial set of swaps — cards in, cards out — tagged with a budget band. +3.2 Bands follow the market convention: roughly $30, $60 and $100, labelled as estimates. +3.3 Applying a bundle creates or updates a **distinct working copy**. The sealed list is never modified. +3.4 Applying a bundle is reversible in one action, restoring the working copy to its pre-bundle state. +3.5 A bundle states which cards it could not apply and why — colour identity, legality, or a card already present. + +### 4. Prices are estimates + +4.1 Every price shown is labelled an estimate, never a quote. +4.2 Cards with no known price are listed separately and excluded from the subtotal, matching the rule already applied in I-06. + +## Data model + +``` +Precon id, setCode, name, commanderOracleId, releaseDate, sourceUrl, sourceReadAt, version +PreconCard preconId, scryfallId, quantity, zone +UpgradeBundle id, preconId, band (budget|mid|full), estimatedUsd, curatorNote, sourceUrl, sourceReadAt +BundleSwap bundleId, addScryfallId?, removeScryfallId?, reason +BundleApply deckId, bundleId, appliedAt, revertedAt?, beforeSnapshotId +``` + +`beforeSnapshotId` makes the undo exact: reverting restores a snapshot that already exists, rather than replaying inverse operations. + +## Non-goals + +- Generating bundles with the LLM. Bundles are editorial and sourced; generic AI suggestions stay in I-02. +- A shopping cart or checkout. Handing off to I-06's acquisition plan is the boundary. +- Covering every precon ever printed before the mechanism is proven. Start with one recent set. +- Claiming a bracket outcome. The bracket mapping is guidance, and the computed bracket from I-03 remains the only number shown as calculated. + +## Testing strategy + +- **Solitary:** applying a bundle to a fixture precon produces the expected list; reverting restores the exact pre-apply snapshot. +- **Boundary:** a swap whose card is already in the deck, or breaks colour identity, is reported as not applied and does not partially mutate the list. +- **Contract:** a precon list is immutable — writing to an existing version fails; a correction creates version n+1. +- **Sociable:** opening a precon, applying the mid bundle and reverting leaves the working copy byte-identical to its starting state. + +## Delivery slices + +| # | Slice | Depends on | +| --- | ------------------------------------------------------------- | ---------- | +| 1 | Catalogue model and one dated set imported, read-only display | — | +| 2 | Open-as-working-copy and the sealed diff via the I-07 engine | 1 | +| 3 | Bundle model and application with the not-applied report | 2 | +| 4 | One-action revert via `beforeSnapshotId` | 3 | +| 5 | Budget bands, estimate labelling and unpriced disclosure | 3 | +| 6 | Hand-off to the I-06 acquisition plan for the missing cards | 5 | + +Slices 1 and 2 are useful alone: a dated precon catalogue with a diff is shippable value before any bundle exists. + +## Open questions + +- Who curates the bundles? Editorial content needs an owner and a refresh cadence, or it dates badly — the same failure mode as `design-system.md`. +- Does the catalogue share its storage with I-16's candidate corpus? Precons are the most trustworthy candidate source, so one table serving both is attractive. +- Universes Beyond sets ship four precons at a time (the Star Trek set, 2026-11-13, is the immediate example). Is per-set bulk import a day-one requirement? diff --git a/docs/product/specs/i18-per-deck-radar-of-new-cards.md b/docs/product/specs/i18-per-deck-radar-of-new-cards.md new file mode 100644 index 00000000..9454e552 --- /dev/null +++ b/docs/product/specs/i18-per-deck-radar-of-new-cards.md @@ -0,0 +1,96 @@ +# Spec: Per-Deck Radar Of New Cards (I-18) + +> **Initiative:** [I-18 in the roadmap](../roadmap.md) · **Status:** Later · **Researched:** 2026-11-08 +> **Owns:** on-demand, per-deck shortlists from a dated set. **Does not own:** notifications, which stay in I-08 until this flow is validated. + +## Overview + +A new set drops roughly every six weeks. A player with eight decks wants one answer per deck: _which of these 500 cards matter here, and why?_ The radar answers on request, from a named source, and never changes the deck. + +## What the market does + +The spoiler-season market is large and **entirely set-centric**. Nothing found on 2026-11-08 is deck-centric. + +| Source | What it offers | +| ------------------------------------------------------------------------------- | -------------------------------------------------- | +| **AetherHub**, **Draftsim**, **mtgdatabase**, **Playgroup.gg**, **Untapped.gg** | Visual spoiler galleries of every new card, by set | +| **EDHREC set pages** | Set cards ranked by early Commander inclusion | +| **DeckCheck Deckhand reports** | Deck-aware chat reports, not tied to a set release | +| Nerdleagues, Metagame | Card lists and release schedules | + +**The gap is the join.** Everyone publishes "here is the new set". Nobody publishes "here is what the new set means for _your_ Ur-Dragon list". That join is cheap for the product: the deck is already modelled, legality is already computed, and Scryfall already exposes a set corpus. + +**Immediate context:** the Star Trek set releases **2026-11-13** with 586 cards and four Universes Beyond Commander precons — a dated corpus landing within days of this spec. + +## Current state in this codebase + +- `src/lib/scryfall/client.ts` — can query a set; results cached 1 h, cards 24 h. +- `src/lib/deck/bracket.ts` and colour-identity/legality validation — the filter for "legal in this deck" already exists. +- `src/lib/recommendations/` and the EDHREC integration (I-09) — source-and-freshness labelling already exists and must be reused. +- **Missing:** any set-release awareness, and any per-deck compute budget. + +## Functional requirements + +### 1. On request only + +1.1 The radar runs when the player asks, for one deck at a time. Nothing is computed on a schedule. +1.2 The request names the set and shows the corpus date, so the player knows what was examined. +1.3 One request has a hard compute ceiling. Reaching it returns a partial, labelled result rather than running longer. + +### 2. Legality first, relevance second + +2.1 The set corpus is filtered to cards legal in that deck: format, colour identity, singleton and banlist. +2.2 Only cards surviving the filter are considered for relevance. No illegal card is ever shown as a candidate. + +### 3. A named reason per card + +3.1 Each candidate carries a reason attributed to a named source — EDHREC early inclusion, a Commander Spellbook combo with a card already in the deck, or a role the deck is measurably short of per I-03. +3.2 A reason generated by the model is labelled as AI-suggested, separately from any Scryfall or EDHREC verification, exactly as I-09 already requires. +3.3 A candidate with no attributable reason is not shown. Filling the list is not a goal. + +### 4. The deck never changes by itself + +4.1 Adopting a card opens the existing diff preview; the player confirms. +4.2 No bulk-apply. One card, one decision. +4.3 Dismissing a candidate is remembered for that deck and set, so a second request does not repeat it. + +### 5. Deduplication + +5.1 A card reachable through two reasons appears once, with both reasons listed. +5.2 Reprints already in the deck under another printing are not candidates. + +## Data and API + +- `POST /api/decks/{id}/radar` → `{ setCode }`, returns `{ corpusDate, truncated, candidates: [{ scryfallId, reasons: [{ source, detail, observedAt }] }] }`. +- Dismissals stored per `(deckId, setCode, scryfallId)`. +- The route reuses the Scryfall cache; a set corpus is fetched once and shared across decks and players. + +## Non-goals + +- Notifications, digests or e-mail. Explicitly deferred until the on-demand flow is validated; I-08 owns notifications. +- Spoiler-season coverage of unreleased cards. The corpus is a released, dated set. +- Automatic deck modification, under any setting. +- Ranking decks against each other. + +## Testing strategy + +- **Solitary:** the legality filter over a fixture set returns only cards legal in the fixture deck, including the colour-identity and singleton edges. +- **Contract:** every returned candidate carries at least one reason with a source and an `observedAt`; a reasonless candidate fails the test. +- **Boundary:** a dismissed card does not reappear; the same card reachable by two reasons appears once with two reasons. +- **Budget:** a corpus exceeding the ceiling returns `truncated: true` and a partial list, never a timeout. + +## Delivery slices + +| # | Slice | Depends on | +| --- | ---------------------------------------------------------------------- | ---------- | +| 1 | Dated set corpus fetch and cache, no deck logic | — | +| 2 | Per-deck legality filter over the corpus | 1 | +| 3 | Reasons from EDHREC inclusion and Spellbook combos, with source labels | 2 | +| 4 | Diff-based adoption and per-deck dismissals | 2 | +| 5 | Deduplication, reprint handling and the compute ceiling | 3 | +| 6 | _(Later, separate decision)_ opt-in notifications | 4, 5, I-08 | + +## Open questions + +- Which signal carries the most weight early in a set's life, when EDHREC has almost no data? A combo with a card already in the deck is deterministic and available on day one; inclusion data is not. Starting with combos may be both cheaper and more honest. +- Four Universes Beyond precons shipping with a set (Star Trek, 2026-11-13) blur "new cards" and "precon contents". Does the radar consider precon-only cards, which many players will never own as singles? diff --git a/docs/product/specs/i19-offline-game-night-viewing.md b/docs/product/specs/i19-offline-game-night-viewing.md new file mode 100644 index 00000000..52f418e0 --- /dev/null +++ b/docs/product/specs/i19-offline-game-night-viewing.md @@ -0,0 +1,97 @@ +# Spec: Offline Game-Night Viewing (I-19) + +> **Initiative:** [I-19 in the roadmap](../roadmap.md) · **Status:** Next · **Researched:** 2026-11-08 +> **Owns:** a voluntary, read-only cache of **one** deck. **Does not own:** offline editing, multi-device sync, or local inference (I-12). + +## Overview + +Game night happens in a shop basement with no signal. The player needs one deck, its primer and its token list — readable, dated, and gone from the device when they sign out. Nothing more. + +## What the market does + +Offline is a solved problem on mobile, and the bar is set by full card databases, not by single decks. + +| Product | Offline capability | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------- | +| **ManaBox** | Core card database free and fully offline; the usual recommendation for logging cards in a shop with bad wifi | +| **MTGDB** (iOS, Android) | Over 80 000 cards available offline: search, browse, build, manage collection | +| **MTG Deck Builder AI** (iOS) | Download chosen formats once, then search the pool with no connection | +| **MTG Unofficial** (Android) | Offline card search plus game utilities | + +**Read this honestly.** The product cannot and should not match an 80 000-card offline database — that is a native-app capability, and a native mobile app is explicitly out of scope in I-13. The defensible slice is much narrower and still covers the actual game-night need: **the one deck you are about to play**, with the primer and tokens that competitors' offline modes do not carry. + +Framing matters here. "Offline deck builder" invites a comparison the product loses. "Your deck, readable at the table" is a claim it can keep. + +## Current state in this codebase + +- **Nothing.** No service worker, no web app manifest, no `next-pwa`, no cache strategy. Verified by grep across `src/`, `public/` and `next.config.ts` on 2026-11-08. +- Primers exist and render as safe Markdown (I-08). Token checklists exist and export (I-01). +- This is the only one of the eight initiatives that is entirely greenfield, and the roadmap rates it High impact / **large** effort. That rating is correct. + +## Functional requirements + +### 1. Explicit download + +1.1 Downloading is an action on one deck. Nothing is cached in the background, ever. +1.2 The download includes the decklist, card images at a stated resolution, the primer and the required-token list. +1.3 Progress and final size are shown before and during the download; a player on mobile data must be able to judge the cost. +1.4 A second download of the same deck refreshes it and updates its cached date. + +### 2. Read-only offline + +2.1 Offline, the cached deck opens read-only. Editing controls are absent, not merely disabled. +2.2 The cached copy displays **the date it was cached**, prominently. A stale list at a table is worse than no list. +2.3 Any action requiring the network states that it needs a connection instead of failing silently. + +### 3. Privacy on a shared device + +3.1 Signing out purges the cache for that account before the session ends. +3.2 One account's cached deck is never readable by the next account on the same device. +3.3 The cache is per-origin and per-account; a cached deck is never exposed to another site or another profile. + +### 4. Bounded storage + +4.1 Only one deck is cached at a time by default; caching another replaces it unless the player opts into keeping both. +4.2 The player can delete the cache explicitly, without signing out. +4.3 Exceeding the browser's quota is reported as an actionable message, not an exception. + +## Technical approach + +- A **service worker** with an explicit, versioned cache name. No runtime caching of arbitrary requests — only the deck payload the player asked for. +- Card images cached as a stated-resolution set, not the full-size art, to keep the payload defensible on mobile data. +- A web app manifest is required for installability but **installability is not a goal of this initiative** — do not conflate the two. +- Sign-out purge runs in the auth sign-out path and is covered by a test, because this is the requirement most likely to rot silently. + +## Non-goals + +- Offline editing, in any form. A read-only cache that cannot diverge needs no conflict resolution — that is the entire reason this slice is affordable. +- Multi-device sync. +- An offline card database or offline search. +- Local model inference. That is I-12 and stays parked. +- Push notifications, background sync, or any other service-worker capability beyond the deck cache. + +## Testing strategy + +- **Sociable, offline:** Playwright with the context set offline opens the cached deck, shows the cached date, and exposes no editing control. +- **Privacy:** sign out with a cached deck, sign in as another account, assert the first deck is unreachable — this test is the point of the initiative. +- **Boundary:** quota exceeded produces the actionable message; a second deck replaces the first unless opted in. +- **Freshness:** a deck edited online and re-downloaded shows the new cached date and the new contents. + +## Delivery slices + +| # | Slice | Depends on | +| --- | ----------------------------------------------------------------------- | ---------- | +| 1 | Service worker skeleton with a versioned cache, no deck logic | — | +| 2 | Download one deck: list, images, primer, tokens, with size and progress | 1 | +| 3 | Read-only offline rendering with the cached date | 2 | +| 4 | Sign-out purge, with its test | 2 | +| 5 | Quota handling, explicit delete, single-deck replacement rule | 3 | +| 6 | Airplane-mode E2E coverage in the Docker suite | 3, 4 | + +Slice 4 must not lag slice 2 by more than one PR: shipping a cache without the purge creates a privacy problem on shared devices. + +## Open questions + +- Which image resolution? `normal` is legible at a table and roughly a fifth of `png`; 100 cards of `normal` is a defensible payload, `png` is not. +- Does a service worker interact badly with the existing Sentry and Vercel Speed Insights setup? Worth a spike before slice 1, because a misconfigured worker can cache a broken shell and lock players out of the live site. +- Is "one deck" too strict for a player bringing three decks to a pod night? Perhaps, but shipping one and measuring is cheaper than guessing three. diff --git a/docs/product/specs/i20-collaborative-review.md b/docs/product/specs/i20-collaborative-review.md new file mode 100644 index 00000000..f918cb92 --- /dev/null +++ b/docs/product/specs/i20-collaborative-review.md @@ -0,0 +1,99 @@ +# Spec: Collaborative Review (I-20) + +> **Initiative:** [I-20 in the roadmap](../roadmap.md) · **Status:** Later · **Researched:** 2026-11-08 +> **Owns:** proposing a deck patch that the owner accepts or rejects. **Does not own:** comments, "seeking feedback" and forks, which stay in I-08. + +## Overview + +A friend who knows the format should be able to say "cut these three, add these three" **as a change the owner can apply in one action** — not as a paragraph in a comment thread the owner retypes by hand. The owner stays the owner: the collaborator proposes, never publishes. + +## What the market does + +This is the clearest unoccupied space of the eight initiatives. + +**Archidekt collaborators**, the reference named in the roadmap, works the opposite way. A collaborator can **add, remove and edit cards directly, in real time, exactly as the deck's creator can**. They are blocked only from changing the deck's name, format, game type, description and privacy. The owner gets popup notifications after the fact. + +That is **shared write access**, not review. It suits two people building together on a call. It does not suit "I asked three people and want to evaluate what each suggested" — the case this initiative addresses. + +Nothing found on 2026-11-08 offers a propose-and-accept workflow for Magic decks. Every other product in the space treats feedback as prose: TappedOut's deck-help threads, Moxfield's comments, Reddit's r/EDH deck-help posts. The pattern this initiative borrows is the pull request, and the market has simply not applied it to decklists. + +**Consequence for scope:** there is no competitor behaviour to match. The risk is not falling behind; it is building a heavyweight permission system for a workflow nobody has validated. The roadmap rates this Medium impact / large effort and places it in Later. Both are right. + +## Current state in this codebase + +- `src/lib/deck/` snapshots and the deterministic comparison engine (I-07) — a patch is a diff between two snapshots, and that machinery exists. +- Deck forking with attribution, public/private decks, threaded comments, "seeking feedback" and the moderation queue (I-08). +- Ownership is single-owner throughout: there is no role model, no share-scoped permission, and no second writer anywhere in the deck APIs. + +## Functional requirements + +### 1. Invitation and role + +1.1 An owner invites a specific account as a **reviewer** on one deck. +1.2 The reviewer role can read the deck and submit a patch. It cannot modify the deck, publish it, rename it, change its privacy, or delete it. +1.3 The invitation is revocable at any time; revoking leaves already-submitted patches intact and visible to the owner. +1.4 A reviewer never reaches the owner's collection, acquisition plan or private decks — only the deck they were invited to. + +### 2. The patch + +2.1 A patch is a set of proposed additions and removals with quantities and zones, plus an optional note. +2.2 A patch is computed and stored as a diff against a **named base snapshot**, so it can be evaluated later even if the deck moved on. +2.3 A patch is immutable once submitted. A reviewer who changes their mind submits another. +2.4 A patch whose base snapshot no longer matches the current deck is shown as **stale**, with what changed underneath it, and requires an explicit re-check before it can be applied. + +### 3. Accept or reject + +3.1 The owner sees the patch rendered through the I-07 comparison engine — the same diff view used for snapshots, not a second implementation. +3.2 Accepting applies the patch and records who proposed it. Rejecting records the decision and keeps the patch in history. +3.3 Partial acceptance is **out of scope for the first slice**: a patch is accepted or rejected whole. Cherry-picking rows is a second product and should wait for evidence that owners want it. +3.4 Applying a patch creates a new snapshot, so the pre-patch state is always recoverable. + +### 4. Attribution and history + +4.1 The deck history shows, per patch: who proposed it, when, the base snapshot, the decision, and when it was decided. +4.2 Attribution survives the reviewer's invitation being revoked. +4.3 Accepting a patch never silently overwrites a concurrent owner edit — the stale check in 2.4 is the guard. + +## Data model + +``` +DeckReviewer deckId, userId, invitedAt, revokedAt? +DeckPatch id, deckId, proposerId, baseSnapshotId, note, submittedAt, + decision (pending|accepted|rejected), decidedAt?, resultSnapshotId? +PatchRow patchId, scryfallId, zone, quantityDelta +``` + +Storing `baseSnapshotId` rather than a timestamp is what makes staleness computable rather than guessed. + +## Non-goals + +- Real-time co-editing. Explicitly excluded by the roadmap; Archidekt already serves that need. +- Partial acceptance, in the first slice. +- Organisations, teams or nested permissions. One owner, N reviewers, one deck. +- Turning comments into patches. Comments stay prose in I-08. + +## Testing strategy + +- **Permission:** a reviewer's token is rejected on every deck mutation route — asserted route by route, not by inspection of middleware. +- **Isolation:** a reviewer on deck A cannot read deck B, the owner's collection, or the owner's acquisition plan. +- **Staleness:** a patch whose base snapshot has been superseded reports stale and refuses to apply before re-check. +- **History:** attribution survives revocation; a rejected patch remains readable with its decision. +- **Sociable:** propose, accept, and confirm the pre-patch snapshot still restores the original list. + +## Delivery slices + +| # | Slice | Depends on | +| --- | ------------------------------------------------------------ | ---------- | +| 1 | Reviewer role and invitation, read-only access, revocation | — | +| 2 | Patch submission against a base snapshot, immutable | 1 | +| 3 | Owner's diff view through the I-07 engine, accept and reject | 2 | +| 4 | Staleness detection and the re-check gate | 3 | +| 5 | Attribution history surviving revocation | 3 | + +Slice 1 is the one carrying real risk: it introduces the first non-owner writer in a codebase whose deck APIs assume single ownership throughout. Audit every deck route before slice 2, not after. + +## Open questions + +- Is a patch worth it if the reviewer cannot explain it card by card? A note per row costs little and is probably what makes the patch persuasive rather than merely applicable. +- Should a public deck accept patches from anyone, as a pull request does? That is a moderation surface, and I-08's report-and-review queue exists precisely because the project decided moderation needs deliberate design. Start invitation-only. +- Does this initiative need the owner to be able to counter-propose? That is a conversation, and conversations live in I-08. diff --git a/docs/product/specs/i21-reproducible-probability-bench.md b/docs/product/specs/i21-reproducible-probability-bench.md new file mode 100644 index 00000000..fd917a6b --- /dev/null +++ b/docs/product/specs/i21-reproducible-probability-bench.md @@ -0,0 +1,102 @@ +# Spec: Reproducible Probability Bench (I-21) + +> **Initiative:** [I-21 in the roadmap](../roadmap.md) · **Status:** Later · **Researched:** 2026-11-08 +> **Owns:** seeded, comparable simulation between two snapshots. **Does not own:** recorded goldfish logs and player-authored comparisons, which stay in I-07. + +## Overview + +"Did that change actually make the deck more consistent?" is currently answered by feel. This bench answers it with a seeded sample: simulate thousands of hands for two snapshots under a stated mulligan policy, report colour availability and land drops on turns 2 to 5 with intervals, and **reproduce the same numbers on a rerun**. + +## What the market does + +Hand-probability tools are abundant and technically competent. + +| Product | Method | Reported output | +| ------------------------------ | --------------------------------------------------- | ----------------------------------------------------------------------- | +| **ManaTap** mulligan simulator | Thousands of iterations | Keep rates for 7, 6 and 5 cards | +| **ScrollVault** hand simulator | WebAssembly engine, ~50 000 hands in the background | Running statistics over generated hands | +| **MyDeckStats** | Exact combinatorics **plus** Monte Carlo | Opening-hand and mulligan odds, curve behaviour over thousands of games | +| **KrakenTheMeta** | Hypergeometric calculator | London mulligan opening-hand odds | +| **Deckstats**, **Archidekt** | Multi-turn hand odds | Named as the references in the roadmap | + +**What none of them states.** Reading all five on 2026-11-08, none advertises: + +1. **A published seed.** Rerun any of them and the numbers move. A player cannot quote a result and have a friend reproduce it. +2. **A two-snapshot comparison with a statistical verdict.** They measure one list. Measuring whether version B is _distinguishably_ better than version A is the question a deckbuilder actually has. +3. **Intervals.** A keep rate of 71 % from 1 000 hands and from 50 000 hands are different claims; none of them says which. + +Those three are the initiative, and they align exactly with the product's "AI you can check" positioning: a number you can reproduce and whose uncertainty is stated. + +## Current state in this codebase + +- `src/lib/playtest/` — `analytics.ts`, `comparison.ts`, `ai-context.ts`, `action-log-export.ts`. Snapshot-linked sessions, a deterministic comparison engine, cohort-size disclosure and early-signal guidance already exist (I-07). +- Opening-hand evidence already distinguishes land balance from immediate castability and surfaces missing colours and dead hands. +- **Missing:** any seeded RNG, any Monte Carlo loop, and any statistical comparison between two samples. + +The disclosure habits this initiative needs are therefore already in the codebase; the mathematics is not. + +## Functional requirements + +### 1. Seeded and reproducible + +1.1 Every run records its **seed**, sample size, mulligan policy and the two snapshot ids. +1.2 Rerunning with the same recorded inputs reproduces the reported intervals **exactly**. This is the acceptance criterion of the whole initiative. +1.3 The seed is visible and copyable, so a result can be quoted and checked by someone else. +1.4 The shuffler is a seeded PRNG committed in the repo. `Math.random()` is never used in the bench. + +### 2. What is measured + +2.1 Colour availability and land drops on turns 2, 3, 4 and 5. +2.2 Keep rate per hand size under the stated mulligan policy. +2.3 Each reported figure carries a confidence interval derived from the sample size. +2.4 Assumptions are listed on screen: no opponent interaction, no decisions beyond the mulligan policy, no mana-ability optimisation beyond a stated rule. + +### 3. The mulligan policy is explicit + +3.1 The policy is chosen from a small, named set — for example "keep 2 to 5 lands", "London to 6 then keep" — never implicit. +3.2 The policy is recorded with the run and shown with the result. +3.3 Changing the policy invalidates the comparison between two existing runs; the UI says so rather than silently comparing across policies. + +### 4. Comparison between two snapshots + +4.1 Two snapshots are simulated under the same seed, sample size and policy. +4.2 The result reports the difference per metric **with a statistical comparison**, not two numbers side by side. +4.3 A difference inside the interval is reported as **not distinguishable at this sample size**, with the sample size that would be needed. This is the most important sentence the feature will produce. + +### 5. Bounded compute + +5.1 Sample size is capped per run, and the cap is stated. +5.2 Simulation runs where it does not block the interface; a long run reports progress and can be cancelled. + +## Non-goals + +- An AI opponent, or any opponent at all. +- A full Magic rules engine. No triggers, no stack, no targeting. +- Replacing I-07's action logs or player-authored snapshot comparisons. This bench is evidence beside them, not instead of them. +- Optimising play decisions. The bench measures what the deck draws, not how well it is piloted. + +## Testing strategy + +- **Reproducibility:** the same seed, size and policy produce byte-identical reported intervals across two runs and across processes. This test is the specification. +- **Statistical:** a deck with 40 lands and one with 30 compare as distinguishable; two shuffles of the same list compare as not distinguishable. Testing the summary, not a single hand, is explicitly required by the roadmap. +- **Boundary:** sample size at the cap, and a cancelled run leaving no partial result recorded. +- **Honesty:** a difference inside the interval renders the "not distinguishable" message and the required sample size. + +## Delivery slices + +| # | Slice | Depends on | +| --- | ---------------------------------------------------------------------- | ---------- | +| 1 | Seeded PRNG and shuffler, with the reproducibility test | — | +| 2 | Single-snapshot simulation: keep rate, land drops, colour availability | 1 | +| 3 | Intervals from sample size, and the assumptions panel | 2 | +| 4 | Named mulligan policies, recorded with the run | 2 | +| 5 | Two-snapshot comparison with the statistical verdict | 3, 4 | +| 6 | Compute cap, progress and cancellation | 2 | + +Slice 1 alone is worth shipping: a committed seeded shuffler is reusable by I-07's goldfish engine and makes its existing evidence reproducible too. + +## Open questions + +- Where does the simulation run? The client keeps the server cheap and matches ScrollVault's WebAssembly approach; the server makes a result quotable by URL. A quotable seed argues for the server, which is the differentiator. +- Which statistical test? A two-proportion comparison is enough for keep rates, but land-drop curves across four turns invite multiple-comparison error. Pick one and state it on screen rather than reporting four independent verdicts. +- Is "not distinguishable at this sample size" acceptable product copy, or will it read as the tool failing? It is the honest answer and the reason the feature exists — but it needs wording that reads as rigour rather than as an error.