From eb1931fa6cfee0f345b423d95cc378835d607f4e Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 21 Sep 2026 17:40:48 -0700 Subject: [PATCH] Measure draft spacing from the final rich-text baseline --- apple/aesel/Tests/NotebookLayoutChecks.swift | 6 +++--- easel/phone/notebook.js | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apple/aesel/Tests/NotebookLayoutChecks.swift b/apple/aesel/Tests/NotebookLayoutChecks.swift index 9fae1ce469..eddf211a4a 100644 --- a/apple/aesel/Tests/NotebookLayoutChecks.swift +++ b/apple/aesel/Tests/NotebookLayoutChecks.swift @@ -69,12 +69,12 @@ final class Files: NSObject, WKURLSchemeHandler { print("Busy donkey fits its notebook; first user text flows beside the preview") for width in [555, 320] { window.setContentSize(NSSize(width:width,height:720)) - for output in ["Done.", "This response wraps across several lines when the notebook is narrow, and the draft must still follow its final line."] { + for output in ["The **orange** circle follows your pointer.\n\nTry a radius of `24` or a blue background.\n\n```js\nconst radius = 24;\nwipe(\"blue\");\n```\n\n[Open Aesthetic Computer](https://aesthetic.computer)", "Done.", "A reply.\n\n```js\nwipe(\"blue\");\n```\n\n[Open Aesthetic Computer](https://aesthetic.computer)", "This response wraps across several lines when the notebook is narrow, and the draft must still follow its final line."] { let encoded = String(data: try JSONSerialization.data(withJSONObject: [output]), encoding: .utf8)! - _ = try await web.evaluateJavaScript("window.updatePhoneNotebook({entries:[{id:'reply',kind:'assistant',text:\(encoded)[0]}],exclusion:{width:100,height:360,top:0}})") + _ = try await web.evaluateJavaScript("window.updatePhoneNotebook({entries:[{id:'question',kind:'user',text:'Make something.'},{id:'reply',kind:'assistant',text:\(encoded)[0]}],exclusion:{width:100,height:360,top:0}})") try await Task.sleep(nanoseconds:100_000_000) let gap = try await web.evaluateJavaScript(""" - (()=>{const p=document.querySelector('article p');const marker=document.createElement('span');marker.style='display:inline-block;width:0;height:0;vertical-align:baseline';p.append(marker);const lastBaseline=marker.getBoundingClientRect().top;marker.remove();return reported-lastBaseline;})() + (()=>{const p=Array.from(document.querySelectorAll('article p')).at(-1);const marker=document.createElement('span');marker.style='display:inline-block;width:0;height:0;vertical-align:baseline';p.append(marker);const lastBaseline=marker.getBoundingClientRect().top;marker.remove();return reported-lastBaseline;})() """) as! Double precondition(abs(gap - 24) < 0.01, "Draft is \(gap)pt after output at width \(width), expected one row") } diff --git a/easel/phone/notebook.js b/easel/phone/notebook.js index d551dfe1e2..1fe4a030e8 100644 --- a/easel/phone/notebook.js +++ b/easel/phone/notebook.js @@ -72,8 +72,18 @@ const reportHeight = () => { // one painting row for shifted baselines/descenders; native overlaps that // row with the top of its editor so typing continues on the next rule. const last = content?.querySelector('article:last-of-type'); - const bottom = last ? last.getBoundingClientRect().bottom - content.getBoundingClientRect().top - (parseFloat(last.style.top) || 0) : 0; - const height = Math.max(24, Math.ceil(bottom / 24) * 24 + 24); + const blocks = last?.querySelectorAll('p, h1, h2, h3, li:not(:has(p)), pre'); + const block = blocks?.length ? blocks[blocks.length - 1] : last; + let baseline = 0; + if (block) { + const marker = document.createElement('span'); + marker.setAttribute('aria-hidden', 'true'); + marker.style.cssText = 'display:inline-block;width:0;height:0;padding:0;margin:0;vertical-align:baseline'; + block.append(marker); + baseline = marker.getBoundingClientRect().top - content.getBoundingClientRect().top; + marker.remove(); + } + const height = Math.max(24, Math.ceil(baseline / 24) * 24 + 24); if (height === reported) return; reported = height; window.webkit?.messageHandlers?.notebook?.postMessage({height}); -- 2.51.2