A 5e storytelling engine with an LLM DM

Give the viewport's rows back below it, not above it master

A viewport that shrank moved its top down to keep its bottom row on the screen's last row, which left the rows it gave back blank above it, in the region the transcript owns. Every insert above an inline viewport scrolls that whole region, blank rows and all, so each shrink carried a band up through the transcript and into the terminal's scrollback for good: a table that streamed to six rows left six blank rows behind it. A shrink now leaves the viewport's top where it is and ends the viewport short of the last row. ratatui's insert_before draws into the rows below a viewport that no longer reaches the bottom and pushes it back down, so the transcript's own rows are what close the gap and no band is ever made. For that to hold within a pass, the transcript gathers a pass's rows and places them after the resize, and the viewport gives back no more rows than are waiting to fill them. A shrink with nothing to write waits, so the prompt is back on the last row before the pass draws. The test fake for the resize seam only recorded the heights storied asked for, which is why 1038 tests missed this. PinnedViewport does to a TestBackend what CrosstermViewport does to a terminal, sharing viewport.rs's arithmetic, and screen_pinned_tests reads the scrollback and the screen above the viewport together. Task 9 of plan 0008. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zSZW6bFzUQTWG37wErsH6