Skip to content

fix(spec-builder): keep the phase controls reachable at phone widths - #3859

Merged
iamwhatever merged 1 commit into
mainfrom
fix/spec-phase-controls-narrow
Aug 16, 2026
Merged

fix(spec-builder): keep the phase controls reachable at phone widths#3859
iamwhatever merged 1 commit into
mainfrom
fix/spec-phase-controls-narrow

Conversation

@buluoray

@buluoray buluoray commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #3844, which merged before this landed. That PR hid the document column
at phone widths — and took two things with it that have no other host.

Capability removed, not just layout

The phase controls. docTabsHeader (SpecDetail.tsx:277) is the sole host of
Approve → Design, Approve → Tasks, Start building and Pause. Its only call site is
inside the sb-doc element that #3844 marked isMobile ? 'hidden'. The fullscreen
review overlay builds its own header and never calls docTabsHeader, and those
actions are additionally gated on !fullscreen. So there was no second route: at
767px and below a spec could not be advanced, could not start building, and could
not be paused at all.

The decision panel. SpecStatePanel was hidden the same way. It is the only
surface that shows a BLOCKING decision and the only one that can answer it, and the
overlay does not render it either — so a blocked spec looked identical to an idle
one
, with no way to find out why it had stopped.

That is a different class from the width defects this series has been fixing. A
squeezed column is hard to read; this removed the ability to act.

The fix

Only the document body steps aside now:

  • the header stays, so the phase controls stay with it
  • SpecStatePanel is visible at every width
  • the column always renders while narrow, instead of being gated on
    comments.length > 0 — that gate removed the phase controls whenever a spec had
    no pending comments, which is the normal case
  • the body is hidden, not unmounted, because DocView holds an in-progress
    comment draft

Tests

The existing narrow-viewport suite gains assertions for each half, and one of my own
was too weak until a mutation showed it:

mutation caught
hide the doc header again yes
hide SpecStatePanel again yes
gate the column on the comment count again first attempt: NO

The third assertion checked only that the full-width class string was present — and
it survives inside a nested ternary, so the mutation passed. It now also asserts the
absence of any comment-count gate on the column, and the mutation is caught. Worth
recording because a test that cannot fail is worse than no test.

tsc clean; 17 Spec Builder suites, 142 tests.

Credit and scope

Design and Opus both pointed at the phase controls; UX, Design and Opus all pointed
at the state panel. Two related findings I am not folding in:

  • The overlay header may clip at 390px — its name span has no truncate /
    min-w-0. Untouched by these lines, but fix(spec-builder): make the workspace usable at phone widths #3844 made the overlay the only document
    route on a phone, so it deserves its own change.
  • GPT's "the overlay loses comment drafts" is a false positive for committed
    comments — those live in SpecDetail state, which the overlay does not unmount.
    Only DocView's uncommitted draft is lost, which is equally true on a desktop and
    is pre-existing. Its suggested remedy (drop the overlay route) would remove the
    only document entry point on a phone.

Round 2 — GPT was right: exposing the header was not enough

Measured in the real build at 390px (isolated pod, seeded spec, DSF=2):

header control before after
Requirements 13–126 13–126
Design 128–199 128–199
Tasks 201–265 201–265
Expand document 280–315 345–380
Approve → Tasks 321–414, clipped by sb-doc … overflow-hidden 10–155 on its own line, clipped by nothing
verdict 1 of 5 unreachable all five reachable

The row measures 414px against a 390px viewport, and overflow-hidden gives no
way to scroll to the overhang — so the control was present in the tree and still
unreachable. My first version of this PR would have shipped inert for its own
stated purpose.
The header now wraps while narrow, which drops that action onto
its own line.

after, 390px

The frame is 780×1688 device px = 390×844 CSS at DSF=2.

The BLOCKING finding, with a correction to its suggested fix

The unbounded-section concern is real and now bounded — but max-h-full would
have been inert. No ancestor in this chain has a definite height, so a
percentage max-height does not resolve; I measured this on a sibling PR where
10% left a 289px list unchanged while 10vh cut it to 84px. The cap is
therefore max-h-[60vh] overflow-y-auto, measured resolving to 506.4px at an
844px viewport, with overflow-y: auto confirmed on the live element.

Tests

Two more assertions, both falsified by mutation:

mutation caught
stop the header wrapping while narrow yes
change the vh bound back to a percentage yes

Round 3 — took the shrink-0 finding, though its premise does not hold

The mechanism is real but not reachable on a phone. Measured in isolation, a column
whose chat is flex-1 min-h-0 (basis 0, so it absorbs only the leftover) against a
section capped at 60vh:

geometry pinned (shrink-0) shrinkable
844px viewport sec 421px, overflow 0 identical
landscape, 390px viewport (shell 300, cap 234) sec 234px, overflow 0, scrolls identical
contrived: shell 110 shorter than a 120px cap overflow 10px overflow 0

The cap binds before shrinking is ever needed, so on every reachable geometry the two
forms measure the same and the reported clipping does not occur. The edge that does
bite needs a shell shorter than the cap: vh is viewport-relative while this row sits
in the viewport minus the app header, so 0.6v > v − 90 — a viewport under ~225px
tall, which no device has.

Taking it anyway: it is free where it is reachable, strictly safer at that edge, and
cheaper than arguing. Re-verified in the real build afterwards — Approve → Tasks
still lands at 10–155 on its own line, clipped by nothing, all five header controls
reachable, no page overflow.

@buluoray
buluoray requested a review from a team August 16, 2026 01:08
@buluoray
buluoray requested a review from a team as a code owner August 16, 2026 01:08
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 16, 2026
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Advisory design-level review of 928eed0c4ffbe1028b7ff50369e70bbaf94db679 — updated in place on each push; does not block merge.

Design-Verdict: PASS

Restores the only route to phase actions and blocking decisions at phone widths; alternatives were measured and rejected with evidence, and the fix stays minimal.

[DESIGN-REVIEWED] 928eed0

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 928eed0c4ffbe1028b7ff50369e70bbaf94db679 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 928eed0

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 928eed0c4ffbe1028b7ff50369e70bbaf94db679: <one-sentence reason>

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 16, 2026
@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 928eed0c4ffbe1028b7ff50369e70bbaf94db679 — this comment is updated in place on each push.

Review details

I've verified the candidate. Let me confirm the button-count analysis by checking how phase relates to hasTasks.

The advance button renders only when ADVANCE[detail.phase] exists — keys are requirements and design only. The Start button renders only when hasTasks (tasks.md exists on disk), and Pause only when executing. Since detail.phase is derived server-side from which docs exist, tasks.md existing means the phase is no longer requirements/design, so advance and start/pause are mutually exclusive. The header therefore carries the SegmentedControl (explicitly not counted) plus expand plus at most one action button = 2. The max-two-buttons-per-row "never wrap" clause governs how to remedy an over-cap row; this row is within the cap, so flex-wrap here does not violate it. Candidate correctly falsified.

No grounded behavioral defect exists in this layout-only diff.

No findings.

[OPUS-REVIEWED] 928eed0

Verdict parsed from the review's SHA-scoped output markers for commit 928eed0c4ffbe1028b7ff50369e70bbaf94db679.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 928eed0c4ffbe1028b7ff50369e70bbaf94db679: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Advisory premise-level review of 928eed0c4ffbe1028b7ff50369e70bbaf94db679 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push; does not block merge.

Reading the contract, the intent file, the patch, and the surrounding source is done; I verified the load-bearing claims directly in the repo before judging.

Verification notes (evidence for the review below):

  • docTabsHeader really is the sole host of the phase controls: grep for docTabsHeader finds one call site, SpecDetail.tsx:471, and the overlay (lines 537–553) builds its own header with the actions gated on !fullscreen. The description's central claim holds.
  • The old code (visible in the patch's minus lines) did gate the whole narrow column on comments.length > 0 and hid SpecStatePanel behind isMobile ? 'hidden'.
  • isMobile ? 'hidden' in spec-builder now has exactly 2 remaining occurrences: the pointer-only splitter and the doc body — both deliberate; no unfixed siblings of the "hidden sole host" root cause.
  • max-h-[NNvh] is an established idiom (22 existing uses across website/src), and the committed screenshot follows the documented temp-screenshots/README.md convention.
  • The one soft spot: the Round-3 min-h-0-instead-of-shrink-0 change, which the author's own measurements show is unreachable on any real device, and which the test now pins.

First-Principles-Verdict: CONCERNS

Every item is a measured fix except Round 3's min-h-0, taken by the author's own admission for an edge no device can reach.

What this change ships

Intent: restore the ability to advance, build, pause, and unblock a spec on a phone, which #3844 removed. This is a FIX.

  1. Phase controls (Approve → Design/Tasks, Start building, Pause) reachable again at phone widths — justified
  2. The blocking-decision panel visible at every width — justified
  3. Doc column always renders while narrow, no longer only when comments are pending — justified
  4. Header wraps while narrow so the last action lands on its own line — justified, measured
  5. Stacked column capped at 60vh and scrolls internally — justified, measured (% shown inert)
  6. Column made shrinkable (min-h-0 replacing shrink-0) — rides along; inherited requirement
  7. Document body still hidden-not-unmounted, draft preserved — justified
  8. Narrow-viewport tests rewritten, mutation-checked — rides along with the fix
  9. One screenshot under temp-screenshots/ — justified by that directory's documented convention

Watch

  • Item 6 is inherited, not derived: the description concedes "on every reachable geometry the two forms measure the same" and the binding edge needs "a viewport under ~225px tall, which no device has" — taken "cheaper than arguing." The test now enshrines it with the message "the stacked column must be able to shrink," which asserts a necessity the author measured as nonexistent; a future reader will trust that message over the code comment.

Subtractions

  • Delete the assertion expect(s, 'the stacked column must be able to shrink').not.toMatch(/'w-full shrink-0 border-t/) in specBuilderNarrowViewport.test.ts:152 — its only unique coverage is the unreachable ~225px edge; a revert to the old broken form is already caught by the comments.length > 0 \? 'w-full assertion.
  • Shrink the min-h-0 rationale in the section comment (SpecDetail.tsx:449–455) to one line; six lines defending an edge no device reaches is permanent reader cost with no matching harm.

[FIRST-PRINCIPLES-REVIEWED] 928eed0

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

Advisory UX-level review of 928eed0c4ffbe1028b7ff50369e70bbaf94db679 — updated in place on each push; does not block merge.

UX-Verdict: CONCERNS

Phase controls are reachable again, but the newly exposed doc tabs switch nothing visible on a phone — a control that reads as dead.

Watch

  • Tabs without a panel. At narrow widths docTabsHeader now renders Requirements/Design/Tasks with onChange={setTab} while the body below is hidden — tapping "Design" moves the highlight and nothing else changes on screen; the document only appears via the separate expand icon. Every phone visit, friction on the primary read-the-doc task, persistent. Smallest fix: on mobile make a tab tap also open the overlay (setTab + setExpanded(true)), turning the tabs into navigation instead of a dead selector.
  • Two identical icon-only expand buttons on one screen. The chat header keeps its mobile-only Maximize2 (isMobile && <Btn …expand_document_for_review>) and the stacked doc header now shows the same icon doing the same thing a few hundred pixels lower. A newcomer can't tell them apart or guess why there are two. Keep one — the doc-header instance, next to the tabs it expands.

Suggestions

  • The stacked column is overflow-y-auto; make docTabsHeader sticky top-0 inside it so a long state panel + comment tray can't scroll Approve/Pause out of view.

[UX-REVIEWED] 928eed0

@buluoray
buluoray force-pushed the fix/spec-phase-controls-narrow branch from 54410f8 to 77a63b4 Compare August 16, 2026 01:28
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 16, 2026
Hiding the document column at phone widths took more than the document with
it. `docTabsHeader` is the SOLE host of the phase controls -- Approve → Design,
Approve → Tasks, Start building, Pause -- and it lives inside the `sb-doc`
element that was hidden. The fullscreen review overlay builds its own header
and never calls `docTabsHeader`, and those actions are additionally gated on
`!fullscreen`, so there was no second route: at 767px and below a spec could
not be advanced, could not start building, and could not be paused.

`SpecStatePanel` was hidden the same way. It is the only surface that shows a
BLOCKING decision and the only one that can answer it, and the overlay does
not render it either -- so a blocked spec was indistinguishable from an idle
one.

Now only the document BODY steps aside. The header stays and keeps the phase
controls, the state panel is visible at every width, and the column itself
always renders while narrow rather than being gated on the comment count --
that gate took the phase controls away whenever a spec had no pending
comments, which is the normal case.

Exposing the header was not sufficient on its own. Measured in the real build
at 390px, the header row is 414px wide and the pane's `overflow-hidden` clipped
`Approve → Tasks` at left 321 / right 414 with no way to scroll to it, so the
control was visible in the tree and still unreachable. The header wraps while
narrow, which puts that action on its own line: left 10 / right 155, clipped by
nothing, and all five header controls reachable.

The stacked column carries a `60vh` cap with internal scrolling, so an
accumulating state panel plus a staged comment cannot push the comment tray
past the page shell. The bound is in `vh` rather than a percentage because no
ancestor in this chain has a definite height, which makes a percentage
max-height inert -- measured resolving to 506.4px at an 844px viewport.

The body is hidden rather than unmounted because DocView holds an in-progress
comment draft.
@buluoray
buluoray force-pushed the fix/spec-phase-controls-narrow branch from 77a63b4 to 928eed0 Compare August 16, 2026 01:42
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 16, 2026
@iamwhatever
iamwhatever enabled auto-merge (squash) August 16, 2026 02:04

@iamwhatever iamwhatever left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Tier 1 auto-approve: cosmetic-UI (3 files). Criteria: no conflict, no requested changes, no security surface, AI reviewers green. Category: responsive layout fix — keeps spec-builder phase controls reachable at phone widths.

@iamwhatever
iamwhatever merged commit f58aef2 into main Aug 16, 2026
65 of 67 checks passed
@iamwhatever
iamwhatever deleted the fix/spec-phase-controls-narrow branch August 16, 2026 02:05
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Aug 16, 2026
encomjp pushed a commit to encomjp/kirocrew-customapi that referenced this pull request Aug 22, 2026
…irodotdev#3859)

Hiding the document column at phone widths took more than the document with
it. `docTabsHeader` is the SOLE host of the phase controls -- Approve → Design,
Approve → Tasks, Start building, Pause -- and it lives inside the `sb-doc`
element that was hidden. The fullscreen review overlay builds its own header
and never calls `docTabsHeader`, and those actions are additionally gated on
`!fullscreen`, so there was no second route: at 767px and below a spec could
not be advanced, could not start building, and could not be paused.

`SpecStatePanel` was hidden the same way. It is the only surface that shows a
BLOCKING decision and the only one that can answer it, and the overlay does
not render it either -- so a blocked spec was indistinguishable from an idle
one.

Now only the document BODY steps aside. The header stays and keeps the phase
controls, the state panel is visible at every width, and the column itself
always renders while narrow rather than being gated on the comment count --
that gate took the phase controls away whenever a spec had no pending
comments, which is the normal case.

Exposing the header was not sufficient on its own. Measured in the real build
at 390px, the header row is 414px wide and the pane's `overflow-hidden` clipped
`Approve → Tasks` at left 321 / right 414 with no way to scroll to it, so the
control was visible in the tree and still unreachable. The header wraps while
narrow, which puts that action on its own line: left 10 / right 155, clipped by
nothing, and all five header controls reachable.

The stacked column carries a `60vh` cap with internal scrolling, so an
accumulating state panel plus a staged comment cannot push the comment tray
past the page shell. The bound is in `vh` rather than a percentage because no
ancestor in this chain has a definite height, which makes a percentage
max-height inert -- measured resolving to 506.4px at an 844px viewport.

The body is hidden rather than unmounted because DocView holds an in-progress
comment draft.
@bolichen97

Copy link
Copy Markdown
Collaborator

Open PR relationship audit

This is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion.

Relationship findings

  • PR #5111 is PARTIALLY_COVERED relative to this PR. Coverage is explicitly incomplete; this finding is not a completion or closure claim. Recommended action for PR #5111: KEEP. The merged PR mitigated the symptom at one viewport and recorded the remaining gap; this PR fixes the cause at every viewport. Files: website/src/apps/spec-builder/components/SpecDetail.tsx.

No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants