Skip to content

Commit 9daa5a9

Browse files
cscheidclaude
andcommitted
docs(plan): edit-chrome top-crop fix (bd-pvcnea83)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 765f9f1 commit 9daa5a9

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Fix: edit chrome cropped at the top of the viewport
2+
3+
**Strand:** bd-pvcnea83
4+
**Date:** 2026-06-25
5+
**Status:** in progress
6+
7+
## Problem (confirmed in the binary)
8+
9+
Editing the first block of a title-less document (or any block scrolled near the
10+
viewport top) crops the floating edit chrome. The rich-text toolbar is
11+
`position:absolute; bottom:100%; margin-bottom:4px` (floats *above* the edit
12+
box); for a block flush against the top of the scroll area it lands at negative
13+
`top` (above the iframe viewport, `scrollTop` already 0) and is clipped — only a
14+
sliver shows. Measured: editor `top:15`, toolbar `top:-15.4 → bottom:11`
15+
(height 26.4). The inline nesting breadcrumb lives in the toolbar, so it crops
16+
too. The standalone `BreadcrumbChip` (non-rich blocks at the top) has the same
17+
top-crop: its geometry sets `top = surfaceTop − chipH`, negative for a top
18+
surface.
19+
20+
## Approach: collision-aware flip (above → below)
21+
22+
Standard floating-toolbar behavior. When there isn't room above
23+
(`surfaceTop − chromeHeight − gap < 0`, viewport-relative), render the chrome
24+
**below** the block instead of above. While editing a top block the chrome then
25+
sits just under it (briefly over the next block), fully visible; the edited text
26+
is never covered. Parity-neutral (no change to the rendered/preview document
27+
spacing). Generalizes correctly to any near-top block, not just the literal
28+
first one.
29+
30+
Shared pure helper (mirrors the chip's existing `computeChipGeometry` pattern):
31+
32+
```ts
33+
// editChromeGeometry.ts
34+
export function shouldPlaceChromeBelow(surfaceTop: number, chromeHeight: number, gap: number): boolean {
35+
return surfaceTop - chromeHeight - gap < 0;
36+
}
37+
```
38+
39+
- **Toolbar** (`RichTextToolbar`): `useLayoutEffect` measures the
40+
`.q2-richtext-editor` box's viewport top + the toolbar's height; if it would
41+
clip above, set a `q2-rt-toolbar-below` class (`top:100%; bottom:auto;
42+
margin-top:4px`). Guard: skip when `offsetHeight <= 0` (degenerate jsdom rects)
43+
so the default `above` placement is kept there.
44+
- **Chip** (`BreadcrumbChip`): in the geometry effect, when
45+
`shouldPlaceChromeBelow(sRect.top, chipH, GAP)` (and real geometry —
46+
`sRect.height > 0`), set `top = (sRect.bottom − hostRect.top) + GAP` (below)
47+
instead of `surfaceTop − chipH` (above). Horizontal left-spill geometry is
48+
unchanged.
49+
50+
Both compute placement from the *surface's* stable top (not the chrome's flipped
51+
position), so there is no flip/re-measure loop.
52+
53+
## Tests (TDD)
54+
55+
1. **Unit (RED first):** `editChromeGeometry.test.ts``shouldPlaceChromeBelow`
56+
true for `(15, 26, 4)` (the measured case), false for `(100, 26, 4)`, and
57+
boundary cases.
58+
2. **Real-binary e2e:** title-less fixture; edit the first block →
59+
- rich Para: `.q2-rt-toolbar` has `q2-rt-toolbar-below` and its rect top ≥ 0
60+
(not clipped);
61+
- first block is a code block (non-rich): standalone chip rect top ≥ 0
62+
(placed below).
63+
Follow the established floating-chrome testing pattern (pure unit + real e2e;
64+
jsdom rects are degenerate, so vertical placement is not asserted in jsdom).
65+
66+
## Notes / known interactions
67+
68+
- The hub-client `q2-preview-breadcrumb-geometry` e2e asserts "chip above
69+
surface" for a TOP paragraph. That suite is already red (bd-fpys25b0,
70+
rich-text default-on) and will need its top-block expectation updated to the
71+
flipped (below) placement when bd-fpys25b0 is addressed. Out of scope here;
72+
noted on that strand.
73+
74+
## Work items
75+
76+
- [x] `editChromeGeometry.ts` + unit test (`shouldPlaceChromeBelow`, 5 cases)
77+
- [x] Toolbar: `useLayoutEffect` measure + `q2-rt-toolbar-below` class + CSS
78+
- [x] Chip: flip `top` below when clipped (guarded on `sRect.height > 0`)
79+
- [x] preview-renderer unit (505) + integration (515) suites green
80+
- [x] Real-binary e2e on title-less fixtures (toolbar + chip),
81+
`q2-preview-spa/e2e/edit-chrome-placement.spec.ts` (2 tests); full spa e2e
82+
suite green (39). Verified live in Chrome: toolbar flips below (top 48.5,
83+
uncropped) for the first paragraph; chip flips below (top 83.8, uncropped)
84+
for a first code block.
85+
- [x] hub-client build + unit (662) + integration (76) green;
86+
`cargo xtask verify --skip-hub-build` green (one flaky pampa-oracle spike
87+
failure on first run, passed on re-run — unrelated to this change)
88+
- [ ] hub-client/changelog.md (two-commit) — pending commit

0 commit comments

Comments
 (0)