Count only rendered steps, and keep a data page's paginator in frame - #127
Conversation
A conditionally rendered panel still reaches the stepper as a child, so counting children counted the ones React had already thrown away. With a hidden last step the count ran one past the end: Submit never appeared, a dead Next took its place, and clicking it landed on a step with nothing in it. Two hidden steps cost two clicks. The per-step validation gate went along with it, reading past the end of its own array and finding nothing to block on. Derive the panels once and let the count, the per-step field names and the rendered children all come from that one list, so they cannot drift apart again. The validation array was already built this way; only the count was not, which is why the error indicators were right while the navigation was wrong. Submit stays gated on whole-form validity throughout, so no invalid command could be submitted - this was navigation, not integrity. Fragments are unchanged: a fragment still counts as one step and still pools its panels' fields. Supporting them properly is a different change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWugkmN6NmoemKeTvSKNDg
The page asked its table to fill the height, but nothing above the table had a height to fill: the split-view pane it sits in gets its size from a stylesheet the library never imports, so the pane was an ordinary block that grew to fit its content. The table grew with it and the page clipped whatever ran past the bottom - which is where the paginator lives. A short page fit and looked correct; a full one lost its paging controls, and with them any way to reach the next page. The pane now allocates what it was given: the action bar keeps its own height, the table takes the rest and scrolls inside it, and the paginator sits outside that scrolling region so it cannot leave the frame. When there is no details pane to split, the split view is not used at all, which removes the dependency on that unimported stylesheet for the common case. A data page still needs an ancestor with a real height - it cannot invent one - so that is now written down with an example of each, and a minimum height keeps a page that violates it usable rather than empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWugkmN6NmoemKeTvSKNDg
|
Reviewer context, kept out of the description because it is not release notes. Verification.
Environment note that matters for anyone reproducing this. One deliberate deviation. The single-pane branch keys on Deliberately out of scope.
Sequencing. #121 (footer Cancel) touches the same footer block as #120 and should be rebased onto this rather than developed in parallel. Open PR #107 (PrimeReact 11) rewrites the stepper wrappers; if it lands first this needs a rebase onto it. |
Counting only rendered steps made the step count change while a wizard is open, and the index walking it was never clamped. A step hidden after the user had already passed it left the index above the last one that still exists: the comparison for the final step is an equality, so it stopped matching, submit never appeared, and every further click pushed the index further out. Hiding every step did the same thing from the other end. The index is now clamped to the steps that exist and compared with inequalities, so an index stranded above the end lands on the last real step rather than past it. Previous moves the wizard instead of absorbing one click per step that vanished. Both siblings get the same treatment. They share the body that renders the steps and each owns the same index state, so fixing one and not the other would leave the second asking for a step index that no longer exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWugkmN6NmoemKeTvSKNDg
Keeping the paginator in frame worked only on the path that stopped using a split view. A page given a details component still mounts one, and the split view positions its panes from a stylesheet this package never imported - so the pane was an ordinary block that grew to content, the height it was given resolved to auto, and the paginator was clipped exactly as before. The two panes stacked instead of siding, too. The component imports the stylesheet itself, the way every other component here imports the one it needs. Allotment is already a dependency and exports the file, so a consumer has nothing to add. The specs could not have caught any of this. They asserted style declarations, and jsdom applies no stylesheets - it cannot tell a height that resolves from one that is inert, which is why putting the common path back inside a split view left every test passing. The runner now processes that one stylesheet, so the specs read computed values and a pane that is not positioned reads as static and auto. The containment claims also assert the paginator exists first: a claim that it sits outside the scrolling region was satisfied by it not being there at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWugkmN6NmoemKeTvSKNDg
|
Reviewer context for Where these came from. Independent code and security review. The security review found no concerns in the diff — and noted the stepper change actually closes a small disclosure, since The specs could not fail on the DataPage defect, and that is now fixed at the root. Reverting the primary production change of The containment claims also assert the paginator exists before claiming where it sits — Route taken for the details path. The stylesheet is imported by the component, which is this package's actual convention — every stylesheet here is imported by the component that needs it, Six mutations, each apply → red → capture → restore → green in one One mutation came back green and was worth more than the five that went red. The first attempt at the dialog's clamp passed, because the clamp added to the shared body module masked it — an assertion guarding an invariant that a different module establishes. The behaviour the dialog's own clamp uniquely owns is that Previous moves the wizard rather than absorbing one click per vanished step; a spec for that is what kills it. Browser spec: assessed and not built. Issue #126 asked for one. There is no browser-mode capability in this repo — no Gate. Found, not fixed — both outside this PR.
Deliberately left. Label corrected from |
The stepper body compares against the last step with an inequality, and that only differs from equality when nothing renders at all - so putting the equality back left every spec passing while reinstating the defect this branch exists to fix. The dialog's own spec cannot see it: the dialog hides the body's footer, so the buttons only exist when the body is used on its own, which is a published entry point. The clamp against a negative index was unmeasured for the same reason, and is now pinned where it is load-bearing. The runner processes one stylesheet, so a computed value governed by any other one reads as a browser default and asserts nothing. Said so where the list is declared, since the next spec to read a computed value is where that bites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWugkmN6NmoemKeTvSKNDg
Summary
Two layout defects that only appear once there is enough content to expose them.
Fixed
Changed