ekp/postmortem/20260729-text-property-live-layout.md
Kinneyzhang 83352c4571 feat!: add source-clean live buffer layout
Replace the physical ekp-region renderer with ekp-buffer text-property projection backed by shared semantic layout plans and stable live-edit transactions.

Optimize the C-backed resize hot path, and add deterministic performance, ERT, GUI, fuzz, release, and documentation coverage for task017 through task032.

BREAKING CHANGE: require ekp-buffer instead of ekp-region and rename module-owned ekp-region-* settings to ekp-buffer-*.
2026-07-30 01:07:25 +08:00

6.9 KiB

Keep KP Decisions Semantic and Buffer Layout Presentational

Historical note: the text-property projection decision remains current. The partial-KP live state machine described below was replaced by the native progressive model in 20260729-native-progressive-live-editing.md.

Context

The original editor integration inserted a reversible rendered string into the real buffer. Its marker properties made the transform recoverable, and save/copy/search adapters hid much of the representation, but arbitrary Elisp character APIs still observed synthesized spaces, newlines, and discretionary hyphens.

The required outcome was stronger than reversible formatting: the buffer's character stream itself had to remain the logical document. The user also excluded overlays and required existing-character text properties, specifically absolute-pixel min-width, real-space space-width, and line-prefix.

This exposed a second mistake in the old live mode. A function that produces an optimal completed paragraph is not automatically an editing algorithm. Running it after an idle delay caused unfinished lines to snap, performed whole-paragraph work for local edits, and had no explicit overflow, pullback, composition, or convergence policy.

The first deletion implementation also recalculated its look-behind from each backspace. Once two visual lines merged, the next deletion began at a new line boundary and moved the anchor left again. The source round trip was correct, but the projection accumulated an extra break because the recomputation boundary crept into the formerly stable prefix.

Decision

Separate layout decisions from representation.

ekp-layout-plan owns boxes/source offsets, line ranges, exact glue targets, indentation, chosen breaks, and discretionary-hyphen decisions. The public string renderer retains its compatible lossless physical returned string. The buffer renderer consumes the same plan but has no right to insert a character.

The buffer projection is:

  • source ASCII space: ((space-width FACTOR) (min-width ((TARGET-PIXELS))));
  • zero-source CJK/mixed gap: min-width on the preceding complete grapheme, targeting natural advance plus glue;
  • indentation: line-prefix;
  • explicit break/hyphen: a replacing display string on an existing complete grapheme that reproduces the grapheme, appends the optional hyphen, then a visual newline.

Every public property value is mirrored by an EKP owner property. Cleanup removes the public value only while it is still identical to the owned value. Updates use with-silent-modifications; owned properties are nonsticky. No buffer path creates an overlay.

Live editing uses a separate bounded state machine:

  1. keep one stable source-marker anchor for a continuous editing flow, then snapshot and clear only its affected projected suffix;
  2. keep the point-containing unfinished line naturally spaced;
  3. commit the longest fitting prefix on overflow;
  4. pull following material back after deletion;
  5. stop when source position and a position-independent line signature match the old plan;
  6. defer composition and reject stale generation work;
  7. allow a complete quality pass only after leaving the paragraph or an explicit refill command.

The active anchor is released when the flow ends: leaving the paragraph, explicit refill, a hard-boundary reflow, conflict abandonment, or mode teardown. A new deletion starts one line earlier only when it first begins exactly at a visual-line boundary; subsequent backspaces reuse the same anchor. This makes overflow followed by deletion projection-identical.

Automatic work has a hard single-paragraph limit. An oversized paragraph stays naturally editable and diagnostic rather than entering an unbounded DP during mode enable, paste, or typing. ekp-refill-paragraph is the explicit request to pay that cost.

Text properties are buffer-wide, so the narrowest live window supplies one authoritative width. Wider windows may show unused space. Simultaneous different-width KP plans are not claimed.

Alternatives Rejected

Keep physical buffer text and add more adapters

No finite adapter list can change what arbitrary buffer-string, char-after, syntax, or third-party Elisp reads. This compensates at the wrong layer.

Use overlays

The user explicitly excluded them. Keeping an overlay backend would also create two projection ownership models and make lifecycle behavior harder to reason about.

Use min-width alone

It can only add width. Existing ASCII spaces sometimes need shrinking; space-width supplies that shrink and min-width supplies the exact pixel floor after rounding.

Use space-width alone

The factor is rounded by redisplay and is not an exact absolute-pixel guarantee. It also has no effect on tabs.

Reuse completed-paragraph formatting after every edit

Changing its trigger from idle to synchronous would remove the delay but still perform the wrong unit of work and would justify an unfinished line. The editing path needs its own active-line and convergence semantics.

Promise independent widths in multiple windows

One set of buffer text properties cannot encode two different break plans. Pretending otherwise would publish an overflowing plan in at least one window.

Consequences

  • Direct character APIs, search, syntax, save, point, and markers operate on source characters.
  • APIs that preserve or inspect text properties can still observe EKP projection metadata; copy/kill removes that owned metadata.
  • Foreign replacing display ownership makes only the affected hard paragraph natural and diagnostic.
  • Tabs/non-ASCII whitespace remain natural if an exact plan would require shrinking them.
  • Layout property changes do not create layout-only undo or modified-state changes.
  • Ordinary edits perform bounded local flow with no delayed whole-paragraph snap.
  • Very long single paragraphs trade automatic KP quality for bounded input latency until the user explicitly requests refill.

Verification

The architecture requires three evidence layers:

  • ERT for core plan/string parity, source and lifecycle invariants, exact property forms, live overflow/pullback/convergence, composition, undo, ownership, resize, multiwindow policy, and overload behavior;
  • clean GUI probes for exact pixel glue, break/hyphen/indent rendering, point, region, mouse, and source invariants;
  • dynamic before/immediate/settled recording for overflow and pullback, including source/projection hashes, zero overlays, pixel-fit assertions, and temporal review.

Final evidence and counts are recorded in the phase change/task documents, not retroactively copied into this decision record.

Rollback

Revert the text-property buffer renderer and live state machine together. Do not retain a mixed physical/text-property backend. The semantic plan and compatible string renderer can remain independently because their boundary does not depend on buffer projection.