Planning bug: a card's FIX DIRECTION carries the authority of its root-cause evidence without the method — MOTIR-2763 mis-counted its referrer set (2 of 4) and named a test lane that cannot mount the element under test
Filed by motir run MOTIR-2763. The correction is already applied in that card's PR; this is the planner-telemetry record.
What was wrong
MOTIR-2763's Root cause section is exemplary and every one of its claims re-verified exactly on origin/main: three shipped facts at file:line, a measured table of control/orb rects at 1280×800, document.elementFromPoint resolving to the orb at both centres.
Its Fix direction section, written in the same register, made two forward-looking claims that nothing had checked. Both are false.
1. "⚠️ Two surfaces hard-code a viewport-relative height tuned against today's 24px" — there are four
| # | file | constant | in the card? |
|---|---|---|---|
| 1 | app/(authed)/boards/_components/BoardColumn.tsx:142 | h-[calc(100dvh-12rem)] | ✅ named |
| 2 | app/(authed)/plans/[id]/page.tsx:95 | h-[calc(100dvh-10rem)] | ✅ named |
| 3 | components/planning/RoadmapView.tsx:169 | h-[calc(100dvh-13rem)] | ❌ missed |
| 4 | packages/design-system/theme.css:1242 | height: calc(100dvh - 14.5rem) | ❌ missed |
The card's own root-cause section records that its clearance grep covered "app/, components/, packages/design-system/ and app/globals.css" — but that grep was for orb/FAB clearance, and the height enumeration was evidently made over app/ alone. #4 is the sharpest miss: its neighbouring comment literally quotes the board column's 12rem, making it the single loudest referrer in the repo.
Shipping the enumerated two would have pushed the roadmap canvas and every 3d-immersive board past the fold — a regression introduced by the fix for a layout bug.
2. "Extend the shipped tests/e2e/home.spec.ts and issue-list-flow.spec.ts" — that lane cannot mount the orb
AC 4 requires the guard to "fail against the pre-fix layout". In those two files it cannot, ever:
showPlanWithAi=isMotirAiConfigured() && Boolean(activeProject), andisMotirAiConfigured()(lib/ai/availability.ts:15) is a server-side, process-wide read ofMOTIR_AI_URL+MOTIR_AI_SERVICE_TOKEN. No per-test override, no client seampage.routecan reach.playwright.config.ts(the main lane, where both files run) sets neither. That is a standing decision, not an oversight, and it has its own regression guard:tests/e2e/ai-callout-gate.spec.tsasserts the orb's absence there and its header records why — adding the pair once mounted the AI affordances across every authed spec and broke the mobile settings drawer at 375px, where the orb intercepted the hamburger.
So a click({ trial: true }) guard placed as instructed would have passed on the unfixed code — there is no orb there to intercept anything — and stayed green forever. A criterion that reads as the card's strongest was unmeetable at the named location.
Disposition (already done in the MOTIR-2763 PR, not deferred)
- All four height sites converted, and the clearance made a single inherited custom property so it stays conditional on the orb at all four —
packages/design-system/theme.cssis a stylesheet and cannot read a React prop, so a variable was the only mechanism that satisfies AC 3's "no dead space when AI is unconfigured". - The guard ships as
tests/e2e/cloud-orb-clearance.spec.tson the cloud lane (playwright.cloud.config.tssets both AI vars), asserting the orb is mounted first so the hit test cannot pass vacuously. - MOTIR-2763's AC 4, fix-direction ⚠️ clause and context refs amended on the record with this evidence — as a COMMENT. Its DESCRIPTION still carried every one of the false claims until this card's run wrote them into it; see Close-out below.
- Lesson written as
notes.html#289→ #292. (Corrected 2026-08-17. #289 is the unrelated MOTIR-2757 lesson; the entry was located by CONTENT, not by number — citation collision is the norm here, not the exception.)
The rule this asks for
plan-rules/kind-bug.md (or type-bars.md's bug bar) should carry: a bug card's fix direction is a hypothesis, not a checklist. Two authoring bars follow —
- A counted enumeration ("the two files that…") must record the SEARCH that produced it, scoped to the whole repo (
app/,components/,packages/,*.css), not just the directory the defect was found in. A count without its search is a guess wearing a number. - A named test home must name the LANE and state that the lane can reach the asserted state. Where the element under test is gated by a server-side env read, the card must name the config that sets it — the failure mode is not a red test but a permanently green one.
Close-out (motir run MOTIR-2907, 2026-08-17)
The open question above — RULE or lesson? — was this card's real deliverable, and it is settled: SHARPEN. Not on the count axis but on the ORDER axis, which is the stronger one. plan-rules/type-test.md already carries "a type: test card must VERIFY every named SCAFFOLD it leans on … EXISTS in the repo the test RUNS IN and is REACHABLE from its runner" (MOTIR-849 → MOTIR-1445). It shipped, it is always-injected, and the miss happened anyway — because the card that named the test home was type: code, so the pack never loaded. A check already written as a rule that fails again is a TRIGGER gap, not a missing rule, and a widening is cheaper and more defensible than a new gate.
Placement deviates from the section above, deliberately. kind-bug.md / type-bars.md's bug bar would not have fired: the failure is not bug-specific — any card can carry a remedy section, and the general case is a code card with an "approach" heading. The limb landed in phase-deepen.md (phase = deepen — any leaf, any kind, any type) as a PROSPECTIVE LIMB on the precondition-verification rule, whose steps 1–5 enumerate only what a card CONSUMES. A remedy's counted set and named test home are readable in the repo today, which makes each a step-2 discharge rather than a prediction. Plus tell (d) on type-test.md's scaffold rule, cross-referenced both ways.
- motir-meta PR #212 —
phase-deepen.md(+34),type-test.md(+11). Corpus baselines pinned BEFORE the edit:measure.pyworst cell2069/1991 → 2103/2024— the overage grows by 1 line, not 34, because the cap is derived from the landing and both rise together.conserve.pygoes2 → 3failing packs, which is MOTIR-2773 (it has no verdict for added), not a defect in the diff.core.mduntouched — MOTIR-2878 and MOTIR-2702 both coordinate around that file. - Mirror: MOTIR-2924 (
motir-ai),blocked_bythis card. Target is the named constantVERIFY_EVERY_PRECONDITIONinsrc/llm/planningRulePacks.ts, taggedphase-deepeninCORPUS_ORDER— nottreeGeneration.ts, which only re-exports the composition now. The gap was measured, not asserted:prospect/retrospect/fix direction/counted/test homeall 0 against a same-file baseline ofprecondition24 ·enumerat19 ·reachab16.
Re-verified rather than accepted (rung 2)
| the claim above | verdict |
|---|---|
| all four height sites converted | ✅ origin/subtask/MOTIR-2763-orb-clearance touches all four |
tests/e2e/cloud-orb-clearance.spec.ts ships on the cloud lane | ✅ present, 269 lines |
| lesson written | ✅ authored and merged — but as #292 |
| MOTIR-2763 "amended on the record" | ⚠️ comment only. Its description still read "⚠️ Two surfaces", named home.spec.ts / issue-list-flow.spec.ts in fix direction (4), scoped AC 4 to /items and /home, and listed two height files in its context refs. A description is the spec; a comment is not. Written into the description during this close-out — which is this card's own rule, applied to itself. |