🎯 Feature Overview (Why?)
PRD #1555: "Either the submodule totals OR the Global budget must be
filled." The backend enforces the XOR and the one-entry-per-category rule at
creation (422 codes, planner_purchase = 81 / planner_purchase_budget =
82), but the UI shows both submodules side by side with no exclusivity
affordance — the user discovers the rule only via a raw error.
Design ↔ implementation divergence. The PRD/design screenshot shows the
submodule totals table and the Global budget field side by side, as if
both can be filled together — but the backend is XOR, so that layout
actively misleads. Mechanically disabling one half whenever the other holds
data makes the screen read as broken, not as a designed either/or. Properly
expressing the XOR needs a reworked design / new UI frame for the
Purchases section, decided with product/design — not bolted onto the
existing side-by-side layout. That design rework is the gating decision for
this feature; the items below are the interaction requirements it must
satisfy plus a candidate approach, not a settled UI.
🧩 Solution
Blocked on a design rework (see Overview): the side-by-side layout and
the XOR backend diverge, so the section needs a new design frame before
this UX is built. Park the design decision for product/design per the
guardrails. The items below are the
requirements that frame must satisfy plus a candidate mechanical approach,
not a settled UI.
- In the planner Purchases module (bespoke
plannerPurchase config,
planner-module-config/module-configs.ts): the two modes (per-category
submodule totals vs global budget) must be a visible, explicit
either/or — the reworked design decides the affordance (e.g. a segmented
control / mode toggle) rather than leaving the user to infer the rule from
greyed-out fields.
- Candidate mechanical fallback if the design keeps the current layout: when
a global-budget entry exists, disable the per-submodule table/form with an
explanatory hint — and vice-versa. Deleting the blocking entry re-enables
the other mode without reload (both submodules' data live in the same
module fetch).
- Map the backend 422 error codes to i18n messages (en+fr) surfaced inline
on the form, not as a raw-JSON toast — covers the race where two tabs
create a budget and a submodule row concurrently (the second gets the
server 422 despite UI gating).
- One-entry-per-category duplicate rule surfaced inline on the category
select (disable already-used categories or map the 422).
- Missing-factor display: entries carry no kg CO₂eq until the average-EF
factor rows are uploaded (data concern, not code) — show an em-dash or
explanatory tooltip instead of a misleading hard 0.
- Guard: reference-kg column and % slider must never appear for Purchases
(manual type — no snapshot rows).
📋 Implementation Plan
⚙️ Clarifications Needed
- Design rework (hard blocker): the side-by-side design and the XOR
backend diverge — a new/reworked Purchases design that expresses the
either/or (mode affordance) is needed before the UX is built. Owned by
product/design; gates the items below.
- Exact 422 error-code list (enumerate from
backend/app/modules_planner/purchase/ in review).
- Missing-factor kg:
— vs 0 + tooltip?
- Switching modes: require explicit deletion (recommended, matches backend),
or offer a "switch to global budget" action that deletes for you (needs
confirm)?
🎯 Feature Overview (Why?)
PRD #1555: "Either the submodule totals OR the Global budget must be
filled." The backend enforces the XOR and the one-entry-per-category rule at
creation (422 codes,
planner_purchase= 81 /planner_purchase_budget=82), but the UI shows both submodules side by side with no exclusivity
affordance — the user discovers the rule only via a raw error.
Design ↔ implementation divergence. The PRD/design screenshot shows the
submodule totals table and the Global budget field side by side, as if
both can be filled together — but the backend is XOR, so that layout
actively misleads. Mechanically disabling one half whenever the other holds
data makes the screen read as broken, not as a designed either/or. Properly
expressing the XOR needs a reworked design / new UI frame for the
Purchases section, decided with product/design — not bolted onto the
existing side-by-side layout. That design rework is the gating decision for
this feature; the items below are the interaction requirements it must
satisfy plus a candidate approach, not a settled UI.
🧩 Solution
plannerPurchaseconfig,planner-module-config/module-configs.ts): the two modes (per-categorysubmodule totals vs global budget) must be a visible, explicit
either/or — the reworked design decides the affordance (e.g. a segmented
control / mode toggle) rather than leaving the user to infer the rule from
greyed-out fields.
a global-budget entry exists, disable the per-submodule table/form with an
explanatory hint — and vice-versa. Deleting the blocking entry re-enables
the other mode without reload (both submodules' data live in the same
module fetch).
on the form, not as a raw-JSON toast — covers the race where two tabs
create a budget and a submodule row concurrently (the second gets the
server 422 despite UI gating).
select (disable already-used categories or map the 422).
factor rows are uploaded (data concern, not code) — show an em-dash or
explanatory tooltip instead of a misleading hard
0.(manual type — no snapshot rows).
📋 Implementation Plan
that expresses the XOR as an explicit either/or (mode affordance);
gates the items below
opposite mode's form/table with hint
modules_planner/purchaseandmap each to an i18n message (en+fr), rendered inline
deleting is the way to switch modes; hint says so
⚙️ Clarifications Needed
backend diverge — a new/reworked Purchases design that expresses the
either/or (mode affordance) is needed before the UX is built. Owned by
product/design; gates the items below.
backend/app/modules_planner/purchase/in review).—vs0+ tooltip?or offer a "switch to global budget" action that deletes for you (needs
confirm)?