Skip to content

feat(core): segment-fit line-breaking seam + pretext engine#482

Open
arthrod wants to merge 2 commits into
stella:mainfrom
arthrod:feat/segment-fit-pretext
Open

feat(core): segment-fit line-breaking seam + pretext engine#482
arthrod wants to merge 2 commits into
stella:mainfrom
arthrod:feat/segment-fit-pretext

Conversation

@arthrod

@arthrod arthrod commented Jul 24, 2026

Copy link
Copy Markdown

Summary

Adds a swappable segment-fit line-breaking seam to the layout-engine
measurement path, plus a @chenglou/pretext-backed engine that implements it.

Plain-text line breaking today runs a word-walk that re-measures words per call
and, for overlong tokens, binary-searches with findMaxFittingLength slice
probes — each probe a fresh canvas measureText. The seam lets a registered
engine fit lines from segment widths prepared once per (font, text), turning
line fitting into pure arithmetic and collapsing the redundant canvas calls.

Two commits:

  1. feat(core) — the seam in @stll/folio-core: a swappable
    SegmentFitEngine registry + walk driver + a conservative style allowlist,
    gated behind a new segmentFitLineBreaking measurement feature flag. Core
    imports no concrete engine. With the flag off or no engine installed (the
    default), the block is skipped and the legacy walk runs byte-identically.
  2. feat(premirror-bridge) — a new private (unpublished) plugin package
    providing a @chenglou/pretext-backed engine, a frozen parity suite, and the
    benchmark harnesses. It depends only on published @stll/folio-core plus its
    own third-party deps.

Correctness

Line-break and width parity vs the legacy walk is exact on spaced text,
trailing-space edges, overlong tokens, and space-less CJK, and is frozen in
pretextParity.test.ts. The engine declines offset-unsafe text (CR/FF) and
measures legacy-identically there.

On a 500-document corpus of real .docx (2 KB – 884 KB), 490 / 492 documents
paginate to an identical page count
with the engine on vs off. 2 large
documents shifted by one page
— the engine's documented break-rule divergence
(trailing whitespace hangs past the line edge; CJK/Thai breaks between
characters), both OOXML/CSS-defensible but not byte-identical to the legacy walk
on those two documents. This is the one behavior change to weigh.

Benchmarks

Full methodology and numbers in packages/premirror-bridge/bench/RESULTS.md.
Metric that matters: canvas measureText calls (real-canvas measureText /
font shaping is the expensive op the seam avoids).

Deterministic (fake canvas, frozen in the parity suite):

case legacy segment-fit
first-pass 100-word paragraph 199 127
overlong 400-char token 82 3
repeat measure (both paths) 0 0

Real browser, real fonts, cold cache, 492 real .docx:

metric result
aggregate canvas measureText calls 92,129 → 84,517 (8.3% fewer)
documents with fewer calls 166 / 492 (34%)
documents unchanged 280 (57%)
per-doc reduction among the 166 it engages median ~87% (up to 97%)
< 5 KB docs 98% engage, 82.6% fewer calls

Where the engine engages it is a large win (80–98% fewer calls on the paragraphs
the walk re-measures word-by-word or slice-probes); at steady state it is a
no-op (folio's width cache already covers repeats); when it does not help its
cost is +1 call. The aggregate real-document win is modest because most real
paragraphs are short or otherwise ineligible, but it is real, concentrated, and
never negative beyond noise.

Recommendation

Enable it by default. Steady-state parity, large first-pass/overlong wins where
it engages, +1-call worst case elsewhere, and exact break/width parity on the
common cases. The two one-page pagination shifts are the known trade-off; if
byte-identical pagination is a hard requirement it can ship flag-off and be
opted into per host.

Test plan

  • bun run typecheck — all packages clean
  • bun run build — clean
  • bun run lint — clean
  • bun test on the seam + parity suites — 21 pass
  • bun run changeset:check — OK (folio-core minor)
  • bun run api:check — updated (segmentFitLineBreaking? added to the public
    FolioMeasurementFeatureFlags)
  • bun bench/measure-engine.mjs reproduces the deterministic table

Attribution

The segment-fit design (prepare-once segmentation + measurement, pure-math line
fitting) is @chenglou/pretext's (MIT); the bridge belongs to the premirror
line (samwillis/premirror, MIT © Sam Willis). See
packages/premirror-bridge/NOTICE.

CC on behalf of arthrod

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added optional segment-fit line breaking to improve plain-text measurement efficiency.
    • Added a Pretext-backed bridge package for segment-fit measurement.
    • Added a feature flag to enable or disable the new behaviour.
  • Bug Fixes

    • Preserved legacy line-breaking behaviour for unsupported text and formatting scenarios.
  • Documentation

    • Added setup, usage, licensing, and performance documentation.
  • Tests

    • Added parity, cache, compatibility, and performance coverage.

opencode-agent Bot and others added 2 commits July 23, 2026 22:13
Add a swappable SegmentFitEngine seam to the layout-engine measurement path,
gated behind a new `segmentFitLineBreaking` measurement feature flag. A
registered engine fits plain-text runs from prepared segment widths instead of
the word-walk's per-call word re-measurement and `findMaxFittingLength`
slice-probe binary search. With the flag off or no engine installed (the
default), the block is skipped and the legacy walk runs byte-identically.

- segmentFit.ts: swappable engine registry + walk driver + style allowlist;
  core imports no concrete engine.
- featureFlags.ts: add segmentFitLineBreaking flag (default off) + accessor,
  preserving the existing workerFontMetrics flag.
- measureParagraph.ts: conservative call-site admission gate; declines
  automatic-hyphenation and protected-cross-run-glue runs.
- cache.ts: clearAllCaches drops engine-prepared state on font-env change.
- segmentFit.test.ts: engine-consulted parity + glue-run guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…enchmarks

New private (unpublished) folio plugin providing a pretext-backed engine for the
folio-core segment-fit seam, with a frozen parity suite and benchmark harnesses.

- pretextEngine.ts: prepare-once segmentation + measurement, pure-arithmetic
  line fitting; declines offset-unsafe (CR/FF) text.
- pretextParity.test.ts: exact line-break/width parity vs the legacy walk on
  spaced/CJK/overlong/trailing-space text; frozen canvas-call profile.
- bench/: deterministic per-paragraph micro-benchmark + real-browser corpus
  harness, with RESULTS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your pull request! Before we can merge it, we need you to sign the Contributor License Agreement.

To sign, please post the following comment on this PR (exactly as written):

I have read the CLA Document and I hereby sign the CLA


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a feature-gated, pluggable segment-fit path to paragraph measurement, implements a pretext-backed bridge engine with caching, and adds parity tests, benchmarks, package metadata, documentation, and release notes.

Changes

Segment-fit measurement

Layer / File(s) Summary
Core seam and activation
api-reports/core/..., packages/core/src/layout-engine/measure/featureFlags.ts, packages/core/src/layout-engine/measure/segmentFit.ts
Defines the SegmentFitEngine contract, registry, feature flag, style allowlist, fitting walk, and exported API shape.
Paragraph wrapping integration
packages/core/src/layout-engine/measure/measureParagraph.ts, packages/core/src/layout-engine/measure/cache.ts, packages/core/src/layout-engine/measure/segmentFit.test.ts
Uses segment fitting for eligible text runs, preserves legacy fallback paths, clears prepared caches with measurement caches, and tests gating and bypass behaviour.
Pretext bridge engine
packages/premirror-bridge/src/*, packages/premirror-bridge/package.json, packages/premirror-bridge/tsconfig.json
Adds the package entry point and a pretext-backed engine with prepared-handle caching, grapheme-aware offsets, CR/FF rejection, and cache controls.
Parity validation and benchmarks
packages/premirror-bridge/src/pretextParity.test.ts, packages/premirror-bridge/bench/*
Adds legacy parity tests and deterministic, micro-benchmark, and browser-corpus measurement harnesses with aggregated results.
Package documentation and release metadata
packages/premirror-bridge/README.md, packages/premirror-bridge/NOTICE, .changeset/segment-fit-seam.md
Documents installation and configuration, records attribution, and adds the release note for the segment-fit seam.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ParagraphMeasurement
  participant SegmentFitWalk
  participant PretextEngine
  participant PreparedCache
  ParagraphMeasurement->>SegmentFitWalk: provide text and available line width
  SegmentFitWalk->>PretextEngine: prepare text and CSS font
  PretextEngine->>PreparedCache: read or store prepared handle
  SegmentFitWalk->>PretextEngine: fit next line
  PretextEngine-->>SegmentFitWalk: return UTF-16 end offset and width
  SegmentFitWalk-->>ParagraphMeasurement: commit fitted segment or fall back
Loading

Possibly related PRs

  • stella/folio#287: Both modify the paragraph line-breaking path and its policy decisions.
  • stella/folio#315: Both affect automatic-hyphenation and inline-run line-breaking decisions.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: a segment-fit line-breaking seam in core plus a pretext-backed engine.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/premirror-bridge/package.json (1)

15-18: 🔒 Security & Privacy | 🔵 Trivial

Confirm @chenglou/pretext@0.0.3 is intentionally pinned rather than stale.

The library has since added/changed CJK wordBreak: 'keep-all' handling, punctuation attachment, and soft-hyphen/overlong hyphen behaviour through newer releases. If the frozen parity coverage is meant to use the latest stable layout behaviour, record or upgrade the pinned version accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/premirror-bridge/package.json` around lines 15 - 18, Review the
`@chenglou/pretext` dependency in packages/premirror-bridge/package.json and
confirm whether version 0.0.3 is intentionally pinned. If frozen parity coverage
should use the latest stable layout behavior, upgrade the dependency to the
appropriate newer release; otherwise document the pin’s rationale near the
dependency or in the project’s established dependency documentation.
packages/premirror-bridge/bench/.gitignore (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Gitignore doesn't cover measure-browser.mjs's default outputs.

measure-browser.mjs defaults to writing ./measure-browser.jsonl and ./measure-browser.summary.json into this same bench/ directory, but only measure-engine-*.json is ignored here. If no broader .gitignore elsewhere in the package already covers these, add patterns for them too.

📝 Proposed fix
 measure-engine-*.json
+measure-browser.jsonl
+measure-browser.summary.json
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/premirror-bridge/bench/.gitignore` at line 1, Update the bench
.gitignore alongside measure-engine-*.json to ignore measure-browser.mjs’s
default output files: measure-browser.jsonl and measure-browser.summary.json,
without changing the existing pattern.
packages/premirror-bridge/src/index.ts (1)

19-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

index.ts is a barrel-style re-export.

This file only forwards three named exports from ./pretextEngine, so it functions as the package's public entrypoint rather than an internal barrel — but the filename still collides with the repo rule against index.ts barrels. If package.json's main/exports can point at a differently-named file (e.g. mod.ts), consider renaming to keep the convention consistent repo-wide.

As per coding guidelines, "Do not create barrel files such as index.ts; import from explicit paths."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/premirror-bridge/src/index.ts` at line 19, Rename the public
entrypoint currently defined in index.ts to a non-index filename such as mod.ts,
preserve the pretextSegmentFitEngine, clearPreparedCache, and preparedCacheSize
re-exports, and update package.json main/exports references and any imports that
target index.ts to use the renamed entrypoint.

Source: Coding guidelines

packages/premirror-bridge/src/pretextEngine.ts (1)

32-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unnecessary as casts and a duplicated type instead of consuming @chenglou/pretext's own exported types.

@chenglou/pretext ships full type declarations and exports both LayoutCursor ({ segmentIndex, graphemeIndex }, identical to the local type at line 32) and PreparedTextWithSegments (the "richer manual-layout handle" that retains segment strings). This file instead:

  • Redefines LayoutCursor locally (line 32) rather than importing it.
  • Casts through unknown to reach .segments (line 64) rather than typing prepared as PreparedTextWithSegments and accessing the field directly.
  • Casts prepared/cursor back out of unknown in fitLine (lines 155-156) with no // SAFETY: comment explaining why the cast is sound.

Importing the library's own types would remove two of these casts entirely and trace the remaining one back to its actual source (the SegmentFitEngine interface's deliberate unknown boundary), where a // SAFETY: comment is genuinely warranted.

As per coding guidelines, "Avoid unnecessary as casts; narrow with type guards or in checks, and add a // SAFETY: comment when an unavoidable cast is sound," "Trace type mismatches to their source instead of casting at the consumer," and "Reuse utility types from installed libraries instead of defining equivalent custom types."

Also applies to: 62-77, 154-166

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/premirror-bridge/src/pretextEngine.ts` at line 32, Replace the local
LayoutCursor definition with the exported LayoutCursor and
PreparedTextWithSegments types from `@chenglou/pretext`. Type the prepared-text
value as PreparedTextWithSegments so segments can be accessed directly, and
update fitLine to preserve only the cast required by the SegmentFitEngine
unknown boundary, documenting that cast with a // SAFETY: comment; remove the
other unknown-based casts.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/premirror-bridge/src/pretextEngine.ts`:
- Around line 26-30: Upgrade the fixed `@chenglou/pretext` dependency used by
pretextEngine.ts from 0.0.3 to at least 0.0.5, preferably the current safe
release, and update the lockfile accordingly. In the bridge path around
prepareWithSegments, add an application-level length cap or rejection for
pathological repeated-character runs while preserving existing CR/FF
normalization behavior.

---

Nitpick comments:
In `@packages/premirror-bridge/bench/.gitignore`:
- Line 1: Update the bench .gitignore alongside measure-engine-*.json to ignore
measure-browser.mjs’s default output files: measure-browser.jsonl and
measure-browser.summary.json, without changing the existing pattern.

In `@packages/premirror-bridge/package.json`:
- Around line 15-18: Review the `@chenglou/pretext` dependency in
packages/premirror-bridge/package.json and confirm whether version 0.0.3 is
intentionally pinned. If frozen parity coverage should use the latest stable
layout behavior, upgrade the dependency to the appropriate newer release;
otherwise document the pin’s rationale near the dependency or in the project’s
established dependency documentation.

In `@packages/premirror-bridge/src/index.ts`:
- Line 19: Rename the public entrypoint currently defined in index.ts to a
non-index filename such as mod.ts, preserve the pretextSegmentFitEngine,
clearPreparedCache, and preparedCacheSize re-exports, and update package.json
main/exports references and any imports that target index.ts to use the renamed
entrypoint.

In `@packages/premirror-bridge/src/pretextEngine.ts`:
- Line 32: Replace the local LayoutCursor definition with the exported
LayoutCursor and PreparedTextWithSegments types from `@chenglou/pretext`. Type the
prepared-text value as PreparedTextWithSegments so segments can be accessed
directly, and update fitLine to preserve only the cast required by the
SegmentFitEngine unknown boundary, documenting that cast with a // SAFETY:
comment; remove the other unknown-based casts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 190bd404-d42d-4caf-b530-4be8ffd43ebf

📥 Commits

Reviewing files that changed from the base of the PR and between dafe214 and 47002c5.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • .changeset/segment-fit-seam.md
  • api-reports/core/layout-bridge-engine-measuring.api.md
  • packages/core/src/layout-engine/measure/cache.ts
  • packages/core/src/layout-engine/measure/featureFlags.ts
  • packages/core/src/layout-engine/measure/measureParagraph.ts
  • packages/core/src/layout-engine/measure/segmentFit.test.ts
  • packages/core/src/layout-engine/measure/segmentFit.ts
  • packages/premirror-bridge/.gitignore
  • packages/premirror-bridge/NOTICE
  • packages/premirror-bridge/README.md
  • packages/premirror-bridge/bench/.gitignore
  • packages/premirror-bridge/bench/RESULTS.md
  • packages/premirror-bridge/bench/measure-browser.mjs
  • packages/premirror-bridge/bench/measure-engine.mjs
  • packages/premirror-bridge/package.json
  • packages/premirror-bridge/src/index.ts
  • packages/premirror-bridge/src/pretextEngine.ts
  • packages/premirror-bridge/src/pretextParity.test.ts
  • packages/premirror-bridge/tsconfig.json
📜 Review details
⚠️ CI failures not shown inline (2)

GitHub Actions: CLA Assistant / 0_cla _ cla.txt: feat(core): segment-fit line-breaking seam + pretext engine

Conclusion: failure

View job details

##[group]Run contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08
 with:
   path-to-signatures: signatures/cla.json
   path-to-document: https://github.com/stella/cla/blob/main/CLA.md
   branch: cla-signatures
   remote-organization-name: stella
   remote-repository-name: cla
   allowlist: dependabot[bot],renovate[bot],github-actions[bot],google-labs-jules[bot],cursoragent
   custom-notsigned-prcomment: Thank you for your pull request! Before we can merge it, we need you to sign the [Contributor License Agreement](https://github.com/stella/cla/blob/main/CLA.md).
To sign, please post the following comment on this PR (exactly as written):
> I have read the CLA Document and I hereby sign the CLA
   custom-allsigned-prcomment: All contributors have signed the CLA.
   custom-pr-sign-comment: I have read the CLA Document and I hereby sign the CLA
   use-dco-flag: false
   lock-pullrequest-aftermerge: true
   suggest-recheck: true
 env:
   GITHUB_***REDACTED***
   PERSONAL_ACCESS_***REDACTED***
 ##[endgroup]
 CLA Assistant GitHub Action bot has started the process
 (node:1929) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
 (Use `node --trace-deprecation ...` to show where the warning was created)
 (node:1929) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
 ##[error]Committers of Pull Request number 482 have to sign the CLA 📝

GitHub Actions: CLA Assistant / cla _ cla: feat(core): segment-fit line-breaking seam + pretext engine

Conclusion: failure

View job details

##[group]Run contributor-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08
 with:
   path-to-signatures: signatures/cla.json
   path-to-document: https://github.com/stella/cla/blob/main/CLA.md
   branch: cla-signatures
   remote-organization-name: stella
   remote-repository-name: cla
   allowlist: dependabot[bot],renovate[bot],github-actions[bot],google-labs-jules[bot],cursoragent
   custom-notsigned-prcomment: Thank you for your pull request! Before we can merge it, we need you to sign the [Contributor License Agreement](https://github.com/stella/cla/blob/main/CLA.md).
To sign, please post the following comment on this PR (exactly as written):
> I have read the CLA Document and I hereby sign the CLA
   custom-allsigned-prcomment: All contributors have signed the CLA.
   custom-pr-sign-comment: I have read the CLA Document and I hereby sign the CLA
   use-dco-flag: false
   lock-pullrequest-aftermerge: true
   suggest-recheck: true
 env:
   GITHUB_***REDACTED***
   PERSONAL_ACCESS_***REDACTED***
 ##[endgroup]
 CLA Assistant GitHub Action bot has started the process
 (node:1929) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
 (Use `node --trace-deprecation ...` to show where the warning was created)
 (node:1929) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
 ##[error]Committers of Pull Request number 482 have to sign the CLA 📝
🧰 Additional context used
📓 Path-based instructions (5)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Preserve NOTICE.md, LICENSE, and Eigenpal/docx-editor attribution comments verbatim.

Files:

  • packages/premirror-bridge/tsconfig.json
  • packages/premirror-bridge/package.json
  • api-reports/core/layout-bridge-engine-measuring.api.md
  • packages/premirror-bridge/README.md
  • packages/premirror-bridge/src/index.ts
  • packages/premirror-bridge/NOTICE
  • packages/core/src/layout-engine/measure/cache.ts
  • packages/premirror-bridge/src/pretextParity.test.ts
  • packages/premirror-bridge/bench/measure-browser.mjs
  • packages/core/src/layout-engine/measure/featureFlags.ts
  • packages/core/src/layout-engine/measure/segmentFit.test.ts
  • packages/premirror-bridge/bench/RESULTS.md
  • packages/core/src/layout-engine/measure/measureParagraph.ts
  • packages/core/src/layout-engine/measure/segmentFit.ts
  • packages/premirror-bridge/bench/measure-engine.mjs
  • packages/premirror-bridge/src/pretextEngine.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Keep packages/core framework-neutral and React-free; never import React, React DOM, or React-package types into core.
Use discriminated unions for mutually exclusive states; avoid boolean flag sets and invalid optional-field combinations.
Prefer branded types, discriminated unions, exhaustive checks, and other TypeScript techniques that make bugs structurally impossible.
Do not use TypeScript enums; use as const objects or union types.
Avoid unnecessary as casts; narrow with type guards or in checks, and add a // SAFETY: comment when an unavoidable cast is sound.
Trace type mismatches to their source instead of casting at the consumer.
Do not annotate values the compiler already infers or pass explicit type arguments to inference-driven hooks and API calls.
Use as const satisfies T to validate large object literals without widening them.
Use .at(0) when an element may be absent; use [0] only when existence is established or documented with // SAFETY:.
Prefer arrow functions over function expressions.
Destructure function parameters when the intermediate object is not reused.
Prefer discriminated-union checks such as obj.type === "x"; use in only when no discriminator exists.
Use an options/args/params object for three or more function arguments or interchangeable positional arguments; reserve Props for React props.
Reuse utility types from installed libraries instead of defining equivalent custom types.
Keep helper-local type aliases immediately above the helper they describe.
Hoist noisy return types into nearby aliases, but keep simple return types inline.
Avoid expensive type instantiation in generic hot paths; prefer narrowing and keep unused large types out of inferred returns.
Shared modules must not combine reusable utilities with module-level side-effecting singletons; split them into separate files.
Never import test-only types into production code; use structural constraints instead.
Defer eager module initializatio...

Files:

  • packages/premirror-bridge/src/index.ts
  • packages/core/src/layout-engine/measure/cache.ts
  • packages/premirror-bridge/src/pretextParity.test.ts
  • packages/core/src/layout-engine/measure/featureFlags.ts
  • packages/core/src/layout-engine/measure/segmentFit.test.ts
  • packages/core/src/layout-engine/measure/measureParagraph.ts
  • packages/core/src/layout-engine/measure/segmentFit.ts
  • packages/premirror-bridge/src/pretextEngine.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{tsx,ts}: Do not create single-use mutation hooks; inline the API call and use Result.tryPromise for retries.
Prefer useRouteContext for data supplied by parent loaders over issuing a separate query.
Return void from route loaders that only prime a TanStack Query cache.
Use useSuspenseQuery only for preloaded route/page content or content inside an explicit local Suspense boundary; use useQuery in shared chrome.
Use select with useParams, useSearch, and useRouteContext to subscribe only to needed fields.
Pass from to route hooks and links, or strict: false for shared chrome spanning routes.
Use useDebouncedCallback from use-debounce instead of manually implementing debounce with refs and timeout cleanup.

Files:

  • packages/premirror-bridge/src/index.ts
  • packages/core/src/layout-engine/measure/cache.ts
  • packages/premirror-bridge/src/pretextParity.test.ts
  • packages/core/src/layout-engine/measure/featureFlags.ts
  • packages/core/src/layout-engine/measure/segmentFit.test.ts
  • packages/core/src/layout-engine/measure/measureParagraph.ts
  • packages/core/src/layout-engine/measure/segmentFit.ts
  • packages/premirror-bridge/src/pretextEngine.ts
packages/core/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

packages/core/**/*.ts: Keep shared editor behavior and UI logic in framework-neutral core managers; React hooks must remain thin bindings.
Express fidelity fixes as reusable OOXML or layout invariants, keep normalization/layout inputs immutable and idempotent, and separate parser, model, measurement, pagination, and painting responsibilities.
Use explicit coordinate-space types and discriminated state machines for layout; avoid related booleans, optional combinations, and mutable local flags.

Files:

  • packages/core/src/layout-engine/measure/cache.ts
  • packages/core/src/layout-engine/measure/featureFlags.ts
  • packages/core/src/layout-engine/measure/segmentFit.test.ts
  • packages/core/src/layout-engine/measure/measureParagraph.ts
  • packages/core/src/layout-engine/measure/segmentFit.ts
packages/core/**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Add minimal synthetic regression tests for fidelity invariants; do not encode fixture identity, source metadata, document text, or corpus details in tests.

Files:

  • packages/core/src/layout-engine/measure/segmentFit.test.ts
🪛 LanguageTool
packages/premirror-bridge/README.md

[uncategorized] ~3-~3: Although a hyphen is possible, it is not necessary in a compound modifier in which the first word is an adverb that ends in ‘ly’.
Context: # @stll/premirror-bridge A separately-versioned folio plugin: a [@chenglou/pretext]...

(HYPHENATED_LY_ADVERB_ADJECTIVE)


[uncategorized] ~31-~31: Possible missing comma found.
Context: ...in (workspace vs. release) Inside this monorepo the package resolves @stll/folio-core...

(AI_HYDRA_LEO_MISSING_COMMA)

🔇 Additional comments (18)
packages/core/src/layout-engine/measure/segmentFit.ts (3)

163-204: 🎯 Functional Correctness

Walk driver logic is sound in isolation.

Traced the loop for infinite-loop risk (wrap not resetting lineHasContent(), engine returning a non-advancing endChar, etc.) — all paths correctly fall through to the legacy walk or terminate. The one real correctness gap involving this function is in the caller (see measureParagraph.ts's admission gate comment).


101-124: LGTM!


137-144: LGTM!

api-reports/core/layout-bridge-engine-measuring.api.md (1)

83-83: LGTM!

packages/core/src/layout-engine/measure/featureFlags.ts (1)

27-33: LGTM!

Also applies to: 50-57

packages/core/src/layout-engine/measure/cache.ts (1)

12-12: LGTM!

Also applies to: 450-458

packages/core/src/layout-engine/measure/segmentFit.test.ts (1)

1-254: LGTM!

packages/premirror-bridge/tsconfig.json (1)

1-21: LGTM!

packages/premirror-bridge/.gitignore (1)

1-2: LGTM!

packages/core/src/layout-engine/measure/measureParagraph.ts (1)

1826-1864: 🎯 Functional Correctness

No change needed for cross-run hyphenation resumes.

In the current flow, automatic hyphenation and suppression are resolved from the same paragraph attributes for every run in the paragraph, so an enabled cross-run hyphenation word cannot land inside a later run whose own effective hyphenation state is disabled.

			> Likely an incorrect or invalid review comment.
packages/premirror-bridge/src/pretextEngine.ts (1)

43-52: LGTM! Cache-key separator, LRU eviction, and lazy grapheme-segmenter singleton are correctly implemented and covered by the parity test's cache-collision check.

Also applies to: 127-152

packages/premirror-bridge/src/pretextParity.test.ts (1)

1-182: LGTM! Thorough parity and cache-key coverage matching the documented divergences and benchmark claims.

packages/premirror-bridge/bench/measure-engine.mjs (1)

1-330: LGTM!

packages/premirror-bridge/bench/measure-browser.mjs (1)

1-242: LGTM! Solid concurrency handling, settle-polling, and paired-metric aggregation for a benchmark harness. (Noted separately: its default output filenames aren't covered by bench/.gitignore.)

packages/premirror-bridge/bench/RESULTS.md (1)

1-98: LGTM! Numbers are internally consistent and match the PR objectives.

.changeset/segment-fit-seam.md (1)

1-14: LGTM!

packages/premirror-bridge/README.md (1)

1-48: LGTM!

packages/premirror-bridge/NOTICE (1)

1-22: LGTM!

Comment on lines +26 to +30
import { layoutNextLine, prepareWithSegments } from "@chenglou/pretext";
import type {
SegmentFitEngine,
SegmentFitLine,
} from "@stll/folio-core/layout-engine/measure/segmentFit";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate files =="
git ls-files | rg '(^|/)pretextEngine\.ts$|(^|/)package\.json$|(^|/)pnpm-lock\.yaml$|(^|/)yarn\.lock$|(^|/)package-lock\.json$' || true

echo
echo "== pretextEngine outline =="
ast-grep outline packages/premirror-bridge/src/pretextEngine.ts || true

echo
echo "== Relevant pretextEngine sections =="
sed -n '1,220p' packages/premirror-bridge/src/pretextEngine.ts

echo
echo "== Dependency pins mentioning pretext/pretextEngine core =="
rg -n '`@chenglou/pretext`|pretextEngine|layoutNextLine|prepareWithSegments|LayoutCursor|PreparedTextWithSegments|supportsText' packages -S

echo
echo "== Package manifests =="
for f in $(git ls-files '**/package.json'); do
  if rg -q '"`@chenglou/pretext`"|"`@stll/folio-core`"|pretextEngine' "$f"; then
    echo "--- $f"
    rg -n '"name"|"version"|"`@chenglou/pretext`"|"`@stll/folio-core`"|"`@chenglou/pretext`"' "$f" -A2 -B2 || true
  fi
done

echo
echo "== Lockfile entries =="
if [ -f pnpm-lock.yaml ]; then
  rg -n '`@chenglou/pretext`|/pretext@|/pretext:' pnpm-lock.yaml -A5 -B2 || true
fi
if [ -f package-lock.json ]; then
  node - <<'JS'
const fs = require('fs');
const lock = JSON.parse(fs.readFileSync('package-lock.json','utf8'));
const find = (obj, k, depth=0) => {
  if(typeof obj !== 'object' || !obj || depth>4)return;
  for(const key in obj){
    if(key === k || key.endsWith('/'+k)||key.includes('/'+k)){
      console.log(key, obj[key].version ?? '');
    }
    find(obj[key],k,depth+1);
  }
}
find(lock, '`@chenglou/pretext`');
JS
fi
if [ -f yarn.lock ]; then
  rg -n '`@chenglou/pretext`|pretext@' yarn.lock -A4 -B2 || true
fi

Repository: stella/folio

Length of output: 13645


🌐 Web query:

GHSA-5478-66c3-rhxr @chenglou/pretext isRepeatedSingleCharRun 0.0.3 0.0.5

💡 Result:

The vulnerability GHSA-5478-66c3-rhxr refers to an algorithmic complexity (Denial of Service) flaw in the @chenglou/pretext library [1][2]. The issue stems from the isRepeatedSingleCharRun function in src/analysis.ts, which performed an O(n) full-string scan during text analysis [1][3]. When the library's buildMergedSegmentation function processed long sequences of identical punctuation characters, it called this function repeatedly as it merged segments, leading to O(n^2) total time complexity [1][4]. An attacker could exploit this by providing a large input of repeated punctuation (e.g., 80KB of identical characters), which could block the main thread for an extended period [1][5]. This vulnerability affects versions 0.0.4 and earlier of @chenglou/pretext [6]. It was addressed and patched in version 0.0.5 by replacing the O(n) scan with an O(1) constant-time check [7][3]. Users of the package are advised to upgrade to version 0.0.5 or higher to mitigate this risk [8][6].

Citations:


🌐 Web query:

NPM @chenglou/pretext versions 0.0.3 0.0.5 0.0.8

💡 Result:

The npm package @chenglou/pretext is a JavaScript/TypeScript library for multiline text measurement and layout [1][2]. It has released multiple versions since its initial publication in March 2026 [3][4]. The versions you requested were released on the following dates: - version 0.0.3 was released on March 29, 2026 [4][5]. - version 0.0.5 was released on April 9, 2026 [4][5][6]. - version 0.0.8 was released on June 12, 2026 [3][7]. As of July 24, 2026, there are 9 versions in total (0.0.0 through 0.0.8) [3][2].

Citations:


🌐 Web query:

ghsa npm @chenglou/pretext GHSA-5478-66c3-rhxr fixed versions

💡 Result:

The vulnerability GHSA-5478-66c3-rhxr, which involves an algorithmic complexity issue (Denial of Service) in the text analysis phase of the @chenglou/pretext npm package, was addressed in version 0.0.5 [1][2][3]. Users of the package are advised to upgrade to version 0.0.5 or higher to mitigate the vulnerability, which affected all versions up to and including 0.0.4 [1][4][3]. The issue stemmed from an O(n²) performance degradation in the isRepeatedSingleCharRun function when processing large inputs of repeated identical punctuation characters [2][5][3].

Citations:


Upgrade the fixed @chenglou/pretext dependency

@chenglou/pretext@0.0.3 is affected by GHSA-5478-66c3-rhxr: long runs of repeated punctuation can trigger O(n²) analysis during prepareWithSegments(), blocking the main thread. This bridge accepts arbitrary run text, so length-normalisation needs to cover this input shape as well as CR/FF. Update the pin to ≥0.0.5, ideally current, and add an application-level rejection/cap for pathological repeated-character input if appropriate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/premirror-bridge/src/pretextEngine.ts` around lines 26 - 30, Upgrade
the fixed `@chenglou/pretext` dependency used by pretextEngine.ts from 0.0.3 to at
least 0.0.5, preferably the current safe release, and update the lockfile
accordingly. In the bridge path around prepareWithSegments, add an
application-level length cap or rejection for pathological repeated-character
runs while preserving existing CR/FF normalization behavior.

@jan-kubica

Copy link
Copy Markdown
Contributor

Thanks again for putting this together. I think the general "prepare once logic" is promising. However I’m not yet able to reproduce the headline real-document result:

  • After adding temporary wiring, my 20-document DOCX sample was effectively flat on calls: 64,854 legacy vs 64,827 pretext. Time inside measureText and total wall time were slightly worse.

  • The cold microbenchmark clears the bridge’s prepared cache, but not pretext’s internal measurement cache. That means the warmup populates widths that remain cached during the measured “cold” repetitions. When I also cleared pretext’s cache, the results became mixed: long prose and overlong tokens still improved, while short prose, CJK, and mixed runs regressed.

  • I may be missing part of the intended setup, looking into it.

There seems to also be a few small correctness reproductions around partially occupied lines, kerning, and font-cache. I will write this separately once \i understand the (micro)benchmark results

@jan-kubica

jan-kubica commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

oh and could you please @arthrod sign the CLA #482 (comment)

@jan-kubica

Copy link
Copy Markdown
Contributor

Thanks again for pointing out this direction. I implemented the core idea in #483 using a smaller Folio-native change. It reduced Canvas measurements by about 41% in focused prose and 32.5% across a 20-document DOCX sample, with identical pagination.

Once it lands, please rebase and test whether the remaining segment-fit work can push this further. Thanks again!

@arthrod

arthrod commented Jul 25, 2026

Copy link
Copy Markdown
Author

Thanks! Lemme look into this!

@arthrod

arthrod commented Jul 25, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

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.

2 participants