Skip to content

PT-4008: Outline the active verse instead of its whole paragraph - #52

Draft
timothy-mccormack wants to merge 7 commits into
mainfrom
pt-4008-verse-outline-geometry
Draft

timothy-mccormack wants to merge 7 commits into
mainfrom
pt-4008-verse-outline-geometry

Conversation

@timothy-mccormack

@timothy-mccormack timothy-mccormack commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

What this is

PT-4008: in the paragraph-structure view, the active-text box now outlines just the verse the caret is in, instead of its whole paragraph.

What changes for users

  • The outline follows the verse. It starts where the verse's text starts and ends where it ends, with straight sides in between, so a verse that starts mid-line makes a Z. This is the "new C" shape from UX review.
  • A verse is outlined across every line it spans: a \q1 with its \q2 lines, or a \p break mid-verse. It stops at headings, tables and chapters.
  • An empty verse's outline is full text height, so it doesn't grow when you start typing.
  • Headings, text before a chapter's first verse, and tables keep the whole-paragraph box, as before.
  • The outline is hidden while the editor is blurred, and is measured again after every update and on resize.

How it works

  1. $getActiveVerseSections (ActiveTextPlugin.tsx) finds the caret's verse, one section per paragraph it runs through. A verse nested in a char span still bounds its section. $getActiveVerseSection, which drives the empty-verse ellipsis, is unchanged.
  2. measureVerseSections reads the sections' line boxes with Range.getClientRects() and drops the hanging space at line wraps.
  3. computeVerseOutlineSegments (verseOutlineGeometry.ts, pure) turns the line boxes into the boxes of one outline.
  4. outlinePathData (verseOutlinePath.ts, pure) traces those boxes into an SVG path, rounding corners by a chosen style. The path is drawn in a .psc-verse-outline overlay placed just after the editor's root element.

Change from the ticket's plan: the ticket proposed three boxes positioned from the verse markers' own boxes. A verse marker is shorter than its line and doesn't show where a section ends mid-line, so that approach breaks with fonts, indents and mid-line endings. Measuring the text's actual line boxes handles every shape with one code path.

Open UX questions

The drawing settings are constants at the top of ActiveTextPlugin.tsx, so each answer is a one-line change:

  • Corners: Sebastian chose between rounding every outward corner and rounding every corner. Outward corners for now (OUTLINE_CORNERS).
  • Shape: Ian is choosing between straight sides ("new C", used now) and hugging every line (OUTLINE_HUGS_LINES).
  • Gutter marker and selection behaviour: Sebastian's answer to Q3 (the \p glyph depending on selection state, plus triple-click, Ctrl+A and Ctrl+Shift+Up/Down behaviours) needs selection and keyboard work beyond this PR. I'd suggest a separate ticket.

For reviewers

  • Anchor vs. focus: CLAUDE.md says to read the selection's focus for "the node the caret is in". This code reads the anchor, to match the toolbar's verse reference and paragraph style; reading focus here alone would make the outline and the toolbar disagree during a range selection.
  • Known limit: in tall scripts (Myanmar, Nastaliq) glyph boxes exceed the line box, so the top and bottom of the outline can cross into neighbouring lines.
  • Unrelated flake: a known-failure test in Editor.test.tsx (PT-4925) sometimes throws an unhandled Lexical error, which fails the run. It happens without this branch too (3 of 5 runs).

Testing

  • nx run-many -t test,lint,typecheck -p platform-editor: 5415 passed. The only lint warning (verse-block.utils.ts, no-loop-func) is already on main.
  • Each fix and behaviour has a test, and reintroducing each bug makes a test fail.
  • Checked in the editor demo in Chromium:
    • a prose Z;
    • a poetry verse across \q1/\q2;
    • the heading fallback;
    • an empty verse before and after typing (the same 22px height);
    • rewrapping when the editor narrows;
    • hiding on blur.
  • The committed packages/platform/dist/ is rebuilt, and verify-committed-dist passes.

🤖 Generated with Claude Code


This change is Reviewable

timothy-mccormack and others added 6 commits November 4, 2026 13:32
Groundwork for outlining just the verse the caret is in, instead of the
whole paragraph. Nothing draws the outline yet, so the editor looks the
same; the drawing waits on UX review.

- $getActiveVerseSection finds the caret's verse section (its verse, the
  next verse, and where it starts), walking the paragraph in document
  order so a verse nested in a char span (`\wj "Come. \v 2 …"\wj*`)
  still bounds its section. Tables and paragraphs without verses get no
  section, so the whole-paragraph outline stays there.
- measureVerseSection reads the section's line boxes from the DOM
  (Range.getClientRects), with the paragraph's content box, direction,
  alignment and CSS zoom.
- computeVerseOutlineSegments (verseOutlineGeometry.ts, pure) turns
  those line boxes into the boxes and step edges of one outline: a
  rectangle, a Z that starts or ends mid-line, hanging indents, RTL, and
  centered lines. This replaces the ticket's three-box plan, which
  guessed lines from verse-marker boxes.
- $getActiveVerseKey now reports a verse nested in a char span (see
  CHANGELOG), and the update listener reads it only when an empty verse
  is present and the editor is focused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty verse's only visible rect is its small verse number, so its outline
was shorter than a verse with text and grew as soon as you typed. The caret
host in an empty verse already has the text's height but no width, so it was
dropped. Let zero-width rects make the line they sit on taller (never wider,
and never start a line), so the outline keeps its size when typing begins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Paragraph Structure view a verse number can sit right against the text
before it, so padding the section's last line past its text ran the outline
into the next verse's number. Stop that line's padding at the number's edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UX review asked for the outline to start where the verse's text starts and
end where it ends, so it takes in no blank space past the text, with its
sides running straight down in between instead of stepping at each line.
Lines no longer run out to the paragraph's edges; the inline-start side runs
at the section's leftmost text (so a hanging first line is taken in and a
first-line indent steps in), and the inline-end side at its longest line.
Hugging each line stays available for centered and end-aligned text.

A wrapped line's hanging space is now dropped in every alignment, since the
outline no longer ends at the content box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UX review asked for poetry and prose to outline the whole verse, so a verse
whose `\q1` line carries on through `\q2` lines (or that a `\p` break splits)
is outlined across all of them, whichever line holds the caret.

$getActiveVerseSections returns one section per paragraph the caret's verse
runs through: back from a carried-on line to the paragraph the verse opens in,
and forward through the running-text paragraphs (Paragraphs, Poetry, Lists
markers) up to the next verse marker, stopping at a heading, a table or a
chapter. measureVerseSections measures them as one outline.
$getActiveVerseSection, which drives the empty-verse ellipsis, is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the paragraph-structure view the active-text box now outlines the verse
the caret is in rather than its whole paragraph, across every line and
paragraph the verse spans. The outline is an SVG path in an overlay placed
just after the editor's root element, measured after each update and on
resize, and hidden while the editor is blurred. Where there is no verse to
outline (a heading, text before the first verse, a table) the paragraph is
boxed as before.

The path traces each stack of touching boxes as one shape and rounds its
corners by a style: only the ends, every outward corner, or every corner.
UX review left the choice between the last two open, so the drawing settings
(padding, corner style and radius, and hugging every line) are constants in
one place; the outline uses outward corners and straight sides for now. Its
line stops half its width short of a next verse number that sits right
against the text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@timothy-mccormack timothy-mccormack changed the title PT-4008: Measure the active verse's section for a per-verse outline PT-4008: Outline the active verse instead of its whole paragraph Nov 6, 2026
Picks up the scribe package's removal. The committed dist's source map
conflicted; it is rebuilt from the merged source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant