Keep the research composer visible as one surface #355
- MA
## Why The `/research` composer could open above the visible document pane and hide its focused field. Nested borders also made it look like a card inside a card.
## What changed - Place the draft beside its anchor, flipping or pinning it within the visible scroller when space is tight. Keep its width aligned with prose. - Reveal the anchor and draft after opening or a focused viewport resize, while yielding immediately to reader input and reduced-motion settings. - Make the composer one surface with inline status and errors, and restore focus for retry after a failed start. - Restore a saved scroll offset only when the scroller is created, so normal smooth scrolling is not cancelled. - Apply the reviewed design-contract hashes for the placement and scroll changes.
## Known follow-up Pressing Enter can still jump the document scroller because of Lexical caret scrolling. This PR keeps the research composer visible when that happens; it does not change the underlying jump.
## Verification The 36 focused placement and research tests, `bun run types`, and `bun run ci` passed locally on the rebased commits. Required GitHub checks will run on the final published head.
## Visuals Before: 
After: 
Near bottom: 
Phone: 
MaggieAppletonhead-ref-force-pushed 8e2c02cbad11last month on GitHub
MaggieAppletonreferenced this in Guard editor scroll position across Enter #368last month on GitHub
- MA
**Review:** Changes needed (one small visual bug; the rest is good)
Reviewed in a real browser (headless Playwright against a fake-GitHub server on this branch), desktop 1440x900 and phone 390x844, plus a 30-80ms frame sequence. CI: only the design-contract review hashes fail (`research.tsx`, `plan-editor.tsx`), which is expected and safe; e2e and container pass.
**Findings**
1. **Draft rides over the tab bar when its anchor is partly scrolled out (medium).** `placement.ts` `placeDraft`: when `anchor.bottom < topEdge` it returns `side: "away"` at `top: below`, with no clip. The draft is `position: fixed; z-50`, so while the anchor's bottom is between the scroller top (96px) and the viewport top, the whole card draws over the Document/Decisions tab bar (measured: anchor bottom 28px, draft 28-147px; screenshot D-partial.png shows the "Document" tab covered). The PR text says the draft "goes with" the anchor, but it escapes the pane. Suggested fix: clip the draft to the scroller rect (e.g. `clip-path: inset(...)` computed in `place()`), or hide it (`visibility: hidden`) once its top is above the scroller top and the anchor's last line is out of view. 2. **Opening a draft whooshes the document (low, inherited from the known Enter-jump).** Enter in the slash menu jumps the scroller to the bottom, then the reveal smooth-scrolls back thousands of px (e.g. 3901 -> 1181 in about 850ms). The draft's enter fade (150ms) finishes off-screen, so the user never sees the scale/fade. Whatever scroll position the reader started at, the final state is the anchor line flush at the top edge (draft y=128), not where they were. Suggest: record `scroller.scrollTop` at open and use it as the reveal target if the draft fits there (or scroll instantly when the distance is over about one viewport). Fixing the jump itself makes this moot. 3. **Table cell (nit).** In a cell the draft anchors to the block before the table (a list, in my test) and sits above the table, covering the column rail. Same anchor logic as main, so not a regression; just noting.
**Verified OK** - Placement near the top, middle and bottom of a long document, in a list, and in a table cell: always fully inside the pane; below the anchor when it fits, content space is reserved so prose is not covered. - Scrolling while open: the draft tracks its anchor exactly (draft top = anchor bottom at every offset) except finding 1. - Window resize 1440x900 -> 1000x600 -> back: the draft stays inside the pane and re-places. - Phone 390x844, then shrunk to 470px (simulated keyboard): the draft stays visible under its anchor at full column width; recovers when the viewport grows back. - Long brief (1230 chars): the textarea scrolls internally and the card does not grow, so it can never exceed the pane. - Failure (forced 500, and a real server refusal): inline red message in the footer, send stays enabled, focus returns to the textarea. - Escape and the x button both close the draft and return focus to the editor. The x is top-right and send bottom-right, consistent with the composer. - Reduced motion: no transition, and the reveal scroll is instant. - Single surface: hairline edge, `--radius-lg`, focus ring on the card, quiet footer. It reads consistently with the research card and composer. - `PlanEditor` scroll-restore: switching Document -> Decisions -> Document restores the offset (900 -> 0 -> 900), and switching documents resets to 0 exactly as on main (checked against the 8795 reference). No regression. - Not verified live: the successful submit (card appears and stays), because the fake server runs with the Planner off (port 8797 busy) and the request is refused. The new e2e covers placement but I could not confirm the success card here.
MaggieAppletonreferenced this in Open the document research composer from /research in Chat #369last month on GitHub
- MA
Thanks. Addressed in 0f995d2e.
1. **Draft over the tab bar.** Fixed. A new pure helper, `draftOverflow()` (unit-tested), measures how far the fixed draft runs past the visible scroller. `place()` clips that part with `clip-path: inset(...)`. A partly scrolled-out draft now ends cleanly at the pane edge and reappears as you scroll back. The new e2e test checks this: it wheels the anchor 40px past the top, then asserts the draft is clipped and nothing shows above the scroller. 2. **Whoosh.** - Checked without End: place the caret by clicking, wait about 300ms, then type `/research` and press Enter. The scroller doesn't move (431 → 431), and the draft opens in place at y=345. - The jump reproduces only when End is pressed, or when Enter comes a few ms after the click (scripted timing). That matches #368. - Defence anyway: `revealTarget()` (unit-tested). The opening reveal returns to the scroll offset the reader had at `OPEN_RESEARCH_COMMAND` if the draft fits there. Otherwise it scrolls the minimum, and it jumps instantly instead of gliding when the trip is longer than the pane. - The new e2e now places its caret by clicking the end of the line instead of pressing End. 3. **Table-cell anchor.** Noted. The anchor logic is unchanged from main, so I left it out of scope.
Also from the earlier round: any key, pointer, touch or wheel input now ends the reveal, so it never fights the reader. CI: e2e passed, 388 tests with 1 unrelated flaky, `responsive-workspace.e2e.ts:324`. The only CI failures are the expected design-contract hashes. `research.tsx` is now `9efb66508a77c1cce3bf057c61f62f50db047ab318b9928238aefc922a5afa4c`; `plan-editor.tsx` is unchanged at `48d50ab1…`.
MaggieAppletonhead-ref-force-pushed 243ec28799c2last month on GitHub
MaggieAppletonauto-rebase-enabledlast month on GitHub
MaggieAppletonmerged 03595a82dcdclast month on GitHub
MaggieAppletonhead-ref-deletedlast month on GitHub