Skip to main content

max / quasi

Place a residual's spans against each other, not one at a time A guard's markup is located by diffing the full render against one with that guard off. When what is deleted shares a boundary with what follows it, several placements rebuild the render exactly, so the check that a span puts the string back cannot tell them apart. `between` took the maximal-prefix placement, which is the latest one, and that is right for some spans and wrong for others. Two adjacent guarded regions is the site, and it is not an edge case: every described region opens `<section id="` and closes `</section>`, so a pair of guarded siblings always shares a prefix. MNW's /fan-plus shares 22 bytes of one, its first region was placed 22 bytes late, and the two spans overlapped. `build`'s assertion caught it, which is the derivation refusing rather than serving one branch's markup inside another's. So a span now carries its whole valid range, earliest to latest, and `placed` picks one point in each by resolving them against one another in document order: as early as its own range allows, never before the floor, where the floor is the start of the span enclosing it or the end of the sibling before it. A span that will not fit inside the one above it closes that one and is retried as its sibling. Neither end of the range is right on its own. Of two adjacent guarded regions the first wants its earliest placement and the second wants its latest, which is why this is a property of the set rather than of any span, and why it is resolved where the set is known. The pass only ever narrows the choices, so a genuine overlap still reaches the assertion. Nothing here can turn one into a tree.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
Author: Max Johnson <me@maxj.phd> · 2026-09-07 22:53 UTC
Signed with PGP, not checked
Commit: 4ac0c0e64216af872b126c2657ab4ac00636aaee
Parent: ce82938
2 files changed, +248 insertions, -3 deletions
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-webview"
3 - version = "0.103.0"
3 + version = "0.103.1"
4 4 description = "The webview renderer for quasi: a screen description in, an htmx document out"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -96,11 +96,18 @@
96 96 );
97 97
98 98 /// A span of the base render, and what decides it.
99 + ///
100 + /// `at` is where the span is finally placed; `first` is the earliest placement
101 + /// that is equally consistent with the two renders it was read from. See
102 + /// [`between`] for why a span has a range at all, and [`placed`] for what picks
103 + /// one point in it.
99 104 #[derive(Clone, Copy, PartialEq, Eq, Debug)]
100 105 struct Span {
101 106 at: usize,
102 107 to: usize,
103 108 repeats: bool,
109 + /// The earliest `at` that rebuilds the same string. `at` is the latest.
110 + first: usize,
104 111 }
105 112
106 113 /// The residual of one staged shape.
@@ -185,10 +192,14 @@
185 192
186 193 let at = found.at.checked_sub(length)?;
187 194 let to = found.at;
188 - (narrow.is_char_boundary(at) && narrow.is_char_boundary(to)).then_some(Span {
195 + // The window shifts with the placement it came from: the body in `narrow`
196 + // sits one copy earlier than the extra copy found in `wide`.
197 + let first = found.first.checked_sub(length)?;
198 + (first <= at && narrow.is_char_boundary(at) && narrow.is_char_boundary(to)).then_some(Span {
189 199 at,
190 200 to,
191 201 repeats: true,
202 + first,
192 203 })
193 204 }
194 205
@@ -215,6 +226,25 @@
215 226 /// alone cannot tell a body from the text that follows it when the two begin
216 227 /// the same way, which is the ambiguity that makes a search necessary and a
217 228 /// named site unnecessary.
229 + ///
230 + /// # The deletion is a range, not a point
231 + ///
232 + /// When what is deleted shares a boundary with what follows it, several
233 + /// placements rebuild `short` from `long` **exactly**, so no check on one
234 + /// placement can tell them apart. Two adjacent described regions are the site,
235 + /// and they are not an edge case: every region opens `<section id="` and closes
236 + /// `</section>`, so a pair of guarded siblings always shares a prefix, and MNW's
237 + /// `/fan-plus` shares 22 bytes of it.
238 + ///
239 + /// So this answers with the whole range. `first` is the earliest placement, from
240 + /// the maximal common suffix, and `at` the latest, from the maximal common
241 + /// prefix. Both rebuild `short`, and [`placed`] picks between them using the one
242 + /// fact a single span does not carry: where the other spans went.
243 + ///
244 + /// Reading only the latest is what put a branch on top of its neighbour. It is
245 + /// tempting to read only the earliest instead, and that is just as wrong the
246 + /// other way: of two adjacent guarded regions, the first wants its earliest
247 + /// placement and the second wants its latest.
218 248 fn between(short: &str, long: &str, length: usize) -> Option<Span> {
219 249 let (s, l) = (short.as_bytes(), long.as_bytes());
220 250
@@ -229,20 +259,90 @@
229 259 if head + tail + length != long.len() {
230 260 return None;
231 261 }
262 +
263 + // The maximal common suffix, measured on its own rather than as whatever is
264 + // left after the prefix has taken what it can. That is what makes `first`
265 + // the earliest placement instead of the same point twice.
266 + let mut back = 0;
267 + while back < s.len() && s[s.len() - 1 - back] == l[l.len() - 1 - back] {
268 + back += 1;
269 + }
270 + let first = long.len().checked_sub(length + back)?;
271 +
232 272 let (at, to) = (head, head + length);
233 - (long.is_char_boundary(at) && long.is_char_boundary(to)).then_some(Span {
273 + (first <= at
274 + && long.is_char_boundary(at)
275 + && long.is_char_boundary(to)
276 + && long.is_char_boundary(first))
277 + .then_some(Span {
234 278 at,
235 279 to,
236 280 repeats: false,
281 + first,
237 282 })
238 283 }
239 284
285 + /// One point in each span's range, chosen so the spans nest.
286 + ///
287 + /// [`between`] answers with a range because a deletion that shares a boundary
288 + /// with its neighbour has several placements that rebuild the render exactly.
289 + /// Picking one is not a property of that span: it is a property of the set, and
290 + /// this is where the set is known.
291 + ///
292 + /// The spans arrive in the order the trace recorded them, which is the order the
293 + /// renderer reached the sites: a container before the members inside it, and
294 + /// siblings left to right. So a span is placed as early as its own range allows
295 + /// but never before the floor, and the floor is the start of the span that
296 + /// encloses it or the end of the sibling before it. A span that will not fit
297 + /// inside the one above it closes that one and is retried against its end.
298 + ///
299 + /// A span whose range cannot satisfy the floor is a genuine overlap rather than
300 + /// an ambiguity, and [`build`] is where that is refused: this narrows the
301 + /// choices and never widens them, so nothing it does can turn a real overlap
302 + /// into a tree.
303 + fn placed(spans: &mut [Span]) {
304 + // Document order, and a container before what is inside it. `derive`
305 + // collects every loop and then every guard, so the order they arrive in is
306 + // the order of two lists rather than the order of the screen; sorting by the
307 + // earliest each could sit at, longest first, is that order recovered from
308 + // the spans themselves.
309 + spans.sort_by_key(|span| (span.first, std::cmp::Reverse(span.to - span.at)));
310 +
311 + // The `to` of every span still open, outermost last.
312 + let mut open: Vec<usize> = Vec::new();
313 + let mut floor = 0;
314 +
315 + for span in spans.iter_mut() {
316 + let length = span.to - span.at;
317 + loop {
318 + let at = span.first.max(floor).min(span.at);
319 + let to = at + length;
320 + match open.last() {
321 + // It does not fit inside the span above it, so that one ends
322 + // here and this is a sibling of it rather than a member.
323 + Some(&bound) if to > bound => {
324 + floor = bound;
325 + open.pop();
326 + }
327 + _ => {
328 + span.at = at;
329 + span.to = to;
330 + floor = at;
331 + open.push(to);
332 + break;
333 + }
334 + }
335 + }
336 + }
337 + }
338 +
240 339 /// The spans, as a tree over the base render.
241 340 ///
242 341 /// Outermost first and then by position, so a span is placed inside the last
243 342 /// one still open. Anything that leaves an open span without closing it inside
244 343 /// is an overlap rather than a nesting, and there is no tree for that.
245 344 fn tree(base: &str, spans: &mut Vec<Span>) -> Vec<Op> {
345 + placed(spans);
246 346 spans.sort_by_key(|span| (span.at, std::cmp::Reverse(span.to)));
247 347 spans.dedup();
248 348 build(base, 0, base.len(), spans, &mut 0)
@@ -304,3 +404,148 @@
304 404 }
305 405 ops
306 406 }
407 +
408 + #[cfg(test)]
409 + mod tests {
410 + use super::{Span, between, placed, shrunk};
411 +
412 + /// Two described regions, which is what a pair of guarded siblings renders
413 + /// as. They share `<section id="` and `</section>`, and that shared boundary
414 + /// is the whole of the ambiguity.
415 + const FIRST: &str =
416 + "<section id=\"fan-plus-membership\" class=\"region group\">MEMBER</section>";
417 + const SECOND: &str = "<section id=\"fan-plus-pitch\" class=\"region group\">PITCH</section>";
418 +
419 + fn both() -> String {
420 + format!("{FIRST}{SECOND}")
421 + }
422 +
423 + /// The wrong placement rebuilds the render exactly, so no check on one span
424 + /// can reject it.
425 + ///
426 + /// This is why the span carries a range rather than a point. Removing the
427 + /// bytes at the *latest* placement gives back the same string as removing
428 + /// the ones the screen actually has, because the two regions begin the same
429 + /// way; a residual built on it would put a branch across two siblings.
430 + #[test]
431 + fn a_deletion_sharing_a_boundary_has_more_than_one_placement() {
432 + let full = both();
433 + let span = shrunk(&full, SECOND).expect("the first region is a deletion");
434 +
435 + assert_eq!(span.first, 0, "the region does start at the front");
436 + assert!(
437 + span.at > span.first,
438 + "the latest placement should differ, or there is no ambiguity to fix: {span:?}"
439 + );
440 +
441 + // Both ends of the range put the same string back, which is the reason
442 + // the choice cannot be made here.
443 + for at in [span.first, span.at] {
444 + let length = span.to - span.at;
445 + let rebuilt = format!("{}{}", &full[..at], &full[at + length..]);
446 + assert_eq!(rebuilt, SECOND, "placement {at} is genuinely valid");
447 + }
448 + }
449 +
450 + /// Placed against each other, two siblings land where they belong.
451 + ///
452 + /// The case that panicked while `/fan-plus` was being staged: MNW
453 + /// `b4a83103`, quasicoherent `14d42beb`. The first region wants the earliest
454 + /// placement in its range and the second wants the latest, so neither
455 + /// "always earliest" nor "always latest" is the rule, and reading only the
456 + /// latest is what overlapped them.
457 + #[test]
458 + fn two_guarded_siblings_are_placed_where_they_are() {
459 + let full = both();
460 + let mut spans = vec![
461 + shrunk(&full, SECOND).expect("the first region"),
462 + shrunk(&full, FIRST).expect("the second region"),
463 + ];
464 +
465 + placed(&mut spans);
466 +
467 + assert_eq!((spans[0].at, spans[0].to), (0, FIRST.len()), "{spans:?}");
468 + assert_eq!(
469 + (spans[1].at, spans[1].to),
470 + (FIRST.len(), full.len()),
471 + "{spans:?}"
472 + );
473 + assert!(
474 + spans[0].to <= spans[1].at,
475 + "they must not overlap: {spans:?}"
476 + );
477 + }
478 +
479 + /// A span inside another stays inside it.
480 + ///
481 + /// The other half of what `placed` has to get right: the floor pushes a
482 + /// span rightwards, and a member pushed past the end of its container would
483 + /// be a worse answer than the overlap. A span that still fits is a member,
484 + /// and only one that cannot fit closes the container and becomes a sibling.
485 + #[test]
486 + fn a_span_that_fits_inside_another_stays_inside_it() {
487 + let mut spans = vec![
488 + Span {
489 + at: 0,
490 + to: 100,
491 + repeats: false,
492 + first: 0,
493 + },
494 + Span {
495 + at: 30,
496 + to: 60,
497 + repeats: false,
498 + first: 10,
499 + },
500 + ];
501 +
502 + placed(&mut spans);
503 +
504 + assert!(
505 + spans[1].at >= spans[0].at && spans[1].to <= spans[0].to,
506 + "the inner span left its container: {spans:?}"
507 + );
508 + }
509 +
510 + /// One shared byte is enough to make a range, and markup always shares one.
511 + ///
512 + /// Two elements with nothing in common but the `<` they open with still
513 + /// admit two placements. That is the argument for resolving the range rather
514 + /// than for detecting a special case: there is no markup where a deletion
515 + /// followed by a sibling is unambiguous, so the narrow reading was never
516 + /// right, and the pair that panicked was only the first to overlap by enough
517 + /// to notice.
518 + #[test]
519 + fn even_one_shared_byte_makes_a_range() {
520 + let full = "<p>alpha</p><div>beta</div>";
521 + let span = shrunk(full, "<div>beta</div>").expect("a deletion");
522 +
523 + assert_eq!(span.first, 0, "the paragraph does start at the front");
524 + assert_eq!(
525 + span.at, 1,
526 + "and the `<` is shared, so it may also start later"
527 + );
528 +
529 + let mut spans = vec![span];
530 + placed(&mut spans);
531 + assert_eq!(
532 + (spans[0].at, spans[0].to),
533 + (0, "<p>alpha</p>".len()),
534 + "resolved against nothing else, a span takes the earliest it may"
535 + );
536 + }
537 +
538 + /// A deletion with genuinely nothing in common is a point.
539 + #[test]
540 + fn a_deletion_with_no_shared_boundary_has_one_placement() {
541 + let span = shrunk("ALPHAbeta", "beta").expect("a deletion");
542 +
543 + assert_eq!((span.first, span.at, span.to), (0, 0, "ALPHA".len()));
544 + }
545 +
546 + /// `between` refuses when the lengths do not add up, rather than guessing.
547 + #[test]
548 + fn a_pair_that_is_not_a_deletion_is_refused() {
549 + assert!(between("abc", "axbxc", 2).is_none());
550 + }
551 + }