Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions scripts/record-demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,16 +273,20 @@ def shot(duration: int) -> None:
shot(1600)

select_text(page, "#p2", "Backfill history")
page.locator(".lf-fab").click()
page.locator(".lf-composer textarea").fill("Can the backfill stay online?")
# The selection raises the response bar with its field already open and focused,
# so the demo types into it directly and sends with Enter. The Comment button the
# bar used to show is now one Tab away, and the composer around the field draws
# nothing of its own.
field = page.locator(".lf-fab-input")
field.fill("Can the backfill stay online?")
page.wait_for_function(
"""() => document.querySelector('.lf-composer').style.display === 'block'
"""() => document.querySelector('.lf-composer').style.display === 'contents'
&& (CSS.highlights.get('lf-pending')?.size ?? 0) > 0
&& document.getElementById('lf-composer-quote').classList.contains('lf-unseen')"""
)
shot(2300)

page.locator(".lf-composer").get_by_role("button", name="Comment").click()
field.press("Enter")
page.wait_for_selector(".lf-margin-thread")
shot(1500)
page.locator(".lf-threads-toggle").click()
Expand Down
1 change: 0 additions & 1 deletion skills/leaf/assets/runtime/conversation/acknowledgments.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ export function createAcknowledgments(dependencies) {
}
for (const line of pageQueryAll(".lf-receipt"))
if (!wanted.has(line)) line.remove();
document.dispatchEvent(new CustomEvent("lf-acknowledgments"));
}

return { paintAcknowledgments };
Expand Down
12 changes: 10 additions & 2 deletions skills/leaf/assets/runtime/living-margin.js
Original file line number Diff line number Diff line change
Expand Up @@ -1738,12 +1738,20 @@ export function createLivingMargin(dependencies) {
syncThreadRelation(reading, reading.lfChoice?.kind === "comment");
paintKeys();
});
document.addEventListener("lf-actions", render);
// TODO(2026-08-31): Reconcile this provisional acknowledgment-to-Button adapter
// with the in-flight Target Button implementation before their combined changes
// land. The canonical acknowledgment projection and page-edge placement remain the
// contract; only this presentation seam should follow the final Button API.
document.addEventListener("lf-acknowledgments", render);
//
// The row's acknowledgment face is read out of the state projection, so it follows the
// applied log on `lf-actions` rather than the receipt paint: every state application
// dispatches that once it has reconciled, and both of the paths that paint receipts sit
// inside one. A repaint driven from the paint instead ran inside the panel render the
Comment thread
leaf-agent marked this conversation as resolved.
Resolved
// application performs *before* reconciliation, which is early enough to read a
// candidate the same read is about to reject — and it ran inside a dispatch, where the
// fault that candidate throws is reported as an uncaught page error rather than
// rejecting the read.
document.addEventListener("lf-actions", render);
document.addEventListener("lf-answered", render);
document.addEventListener("lf-comparison", render);
offerListeners.add(render);
Expand Down
10 changes: 8 additions & 2 deletions tests/render_cases_navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,15 @@ def wait_hovered(page, text):

def card_body(page, says):
"""A point low on a comment's card, below the quote — where a reader's hand rests
while they read the comment, and where nothing presses."""
while they read the comment, and where nothing presses.

Low on the card *as the reader sees it*: the list scrolls, so the last card's own
bottom can sit below the scroller and behind the panel's foot. A point read off the
card's rect alone lands on the general box there, which hovers no card at all."""
box = page.locator(".lf-thread").filter(has_text=says).first.bounding_box()
return box["x"] + box["width"] / 2, box["y"] + box["height"] - 8
seen = page.locator(".lf-threads").bounding_box()
bottom = min(box["y"] + box["height"], seen["y"] + seen["height"])
return box["x"] + box["width"] / 2, bottom - 8


# Addressable links that start within one digit's width of each other: a run of footnote
Expand Down
14 changes: 10 additions & 4 deletions tests/test_render_conversations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1750,12 +1750,18 @@ def test_a_coined_class_cannot_reach_the_chromes_rules(browser, serve):
# Every one of these is worn by something the runtime puts inside the page rather than
# inside its own container — or, for lf-address, on both sides of that line at once,
# which is the same reason: a scoped rule cannot reach the copy in the page. Except the
# last, which is worn by nothing and is here for the other half of the sentence. lf-copy is the medium `version export` marks on the root, and the runtime
# names it under a negation to withhold the live page's scroller from a file that has
# no panel to scroll beside; a rule that dresses no element can leak onto none, and
# what the pin is for is the day one of these stops being either kind.
# first two, which document level names only to hold a rule off them and which are here
# for the other half of the sentence. lf-copy is the medium `version export` marks on
# the root, and the runtime names it under a negation to withhold the live page's
# scroller from a file that has no panel to scroll beside; a rule that dresses no
# element can leak onto none, and what the pin is for is the day one of these stops
# being either kind.
assert {c for c in surface["global"] if c.startswith("lf-")} == {
"lf-copy",
# The compact response field, named the same way: the general text box's rule
# excludes it at document level because the field takes its whole geometry from
# the response controls it shares a baseline with, inside the chrome's own scope.
"lf-fab-input",
"lf-ui",
# A native label can pass through an intermediate focus target. These project
# the held control's focus until activation settles.
Expand Down
11 changes: 5 additions & 6 deletions tests/test_render_margin.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,13 +706,12 @@ def test_reaction_choices_and_their_receipt_share_an_unided_selected_block(
bar = page.locator(".lf-fab-bar")
expect(bar).to_be_visible()
bar.locator(".lf-react-trigger").click()
item = page.locator(".lf-margin-item").filter(
has=page.locator('.lf-margin-more[aria-expanded="true"]')
)
expect(item).to_have_count(1)
assert abs(item.bounding_box()["y"] - paragraph.bounding_box()["y"]) <= 6
# The choices are the bar's own, raised on the selection where the reader is
# pointing: an anchored response opens in place rather than docking a row of
# options into the margin. What the margin holds for this block is the receipt.
expect(bar).to_have_class(re.compile(r"\blf-react-open\b"))

item.locator('.lf-margin-options .lf-react[data-token="ok"]').click()
bar.locator('.lf-react[data-token="ok"]').click()
round_trip(page)
sent = events_model.read_events(serve.page_dir)[-1]
assert sent["anchor"]["section"] == "s-how" and sent["anchor"]["quote"]
Expand Down
Loading