tp--buffer-has-layer-region-p tested liveness with a direct tp-name
property search, which only sees the rendered top layer of each run.
A layer buried below a pushed top, or hidden via tp-hide-layer, keeps
its tp-name inside the tp-layers storage property, so:
- tp-gc-anonymous-layers undefined layers a live buffer still held,
destroying their definitions and reactive deps; pop/show later
resurfaced text whose reactivity was permanently dead;
- tp-reactive-track-buffer (the documented remedy for the
string-insertion registration gap) never registered buried or
hidden layers either, so the gap-closer itself had the gap.
Fix:
- tp-reactive.el gains the shared stack-aware scan
tp-reactive--buffer-layer-names: a tp--map-intervals walk that
collects the direct tp-name of each run plus the tp-name of every
plist inside the run's tp-layers value (read as a plain list of
plists, so tp-reactive needs nothing from the stack module).
tp-reactive-track-buffer is rewritten on it.
- tp--buffer-has-layer-region-p is rewritten on the same scan and
moves - together with tp-gc-anonymous-layers (autoload kept) - from
tp-render.el into tp-layer.el beside tp--anonymous-layer-registry,
so the whole anonymous-layer lifecycle (mint, intern, undefine,
collect) lives in one module and tp-render sheds its only
non-rendering responsibility. tp-render's tp--map-layer-buffers
keeps calling the predicate downward.
Regression tests port the review's gc1/xm02 probes: buried-under-push
kept alive with reactivity surviving a later pop; hidden layer kept
alive with show+setq re-rendering; track-buffer registering buried
and all-hidden layers from inserted strings. Existing tests cover
the visible-kept and killed-buffer-collected control cases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reactive engine located regions solely via the direct tp-name
text property - the render cache of the topmost visible layer - and
wrote only direct properties. The resolved-value snapshots stored
inside the tp-layers property (hidden layers, layers buried below the
top, and in full-stack mode even the visible top's own snapshot) were
never refreshed, so:
- an update received while a layer was hidden rendered stale values
after tp-show-layer, permanently (re-setting the same value is a
watcher no-op and could not repair it);
- with any layer hidden, the very next stack operation - even a no-op
tp-move-layer - rebuilt from the stale snapshot and silently
reverted the reactive update;
- a below-top reactive layer revealed by tp-pop-layer rendered its
stale snapshot;
- one buffer could render two different values of one variable at
once (mixed visible/hidden regions);
- reactive tp-text never reached hidden regions at all.
Fix in tp-render.el:
- tp--write-layer-through-stack-storage: for every run whose
tp-layers holds an entry of the updated layer, replace the layer's
own keys in that entry (preserving its tp-hidden flag) and rebuild
the run via the tp--stack-props-to-list / tp--stack-build-props
codec, which also refreshes the topmost-visible render cache in
full-stack mode. Unchanged runs are left untouched.
- tp--update-layer-regions calls it after the direct render pass.
- tp--replace-reactive-text-in-buffer gains a second pass replacing
text in regions where the layer sits only inside storage (text is
physical - hide/show toggles properties, never text), carrying all
existing properties over the edit, then writes the refreshed props
including tp-text through to storage. The minimal-diff edit is
factored into tp--edit-region-minimal-diff, shared by both passes.
Acceptance tests port the review's XM-01 probe scenarios: update
while hidden renders after show (A3); no-op stack ops never revert
and same-value re-set is never needed (B1-B4); pop reveals current
values (C2); reactive tp-text reaches hidden text (T1); mixed
visible/hidden regions stay in sync (X1); plus the hid1-probe
show+hide round-trip of an unrelated hidden top.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reactive performance (tp-reactive/tp-render/tp-ops): layer->buffer
registry replaces full buffer-list scans on reactive updates, with a
conservative unknown->learning-scan fallback, kill-buffer pruning,
and tp-reactive-track-buffer to close the propertized-string-insert
gap; minimal-diff tp-text replacement preserves point and markers and
makes identical-text updates true no-ops; tp-gc-anonymous-layers
collects interned anonymous layers no registered buffer still shows.
Stack capabilities (tp-stack): tp-hide-layer/tp-show-layer visibility
(hidden layers stay in the stack but do not render; hiding the top
reveals the next visible layer), tp-lower-layer, tp-rotate-layer
DIRECTION/COUNT, tp-layer-stack-at, and modified-run-count returns
with NOERROR options.
Search and layer APIs (tp-search/tp-layer): SUBEXP capture groups and
START/END bounds for tp-regexp-*/tp-match-*, PREDICATE/NOT-CURRENT
exposure on tp-forward/tp-backward/-do (defaults keep 0.2.0 semantics),
multi-argument parameterized layers (define-tp/define-tps arglists of
any arity, tp-layer-props-with-args, tp-layer-arglist), and the
interactive tp-describe-layer.
79 new regression tests; combined suite 522/522 green, shuffled run
green, doctests 63/63, byte-compile clean with warnings-as-errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Byte-compile warnings swept 57 -> 0 across all modules, tests, and
doctest (docstring rewraps and quoting, defvar declarations for the
reactive test variables, prefixed doctest counters, dead-binding
removal, one impossible eq -> equal in a face-merge assertion) with
behavior preserved. GitHub Actions workflow runs an Emacs 28.1/29.4/
30.1 matrix: compile-all with warnings-as-errors, the 443-test suite,
a genuinely shuffled-order rerun (tp-run-shuffled.el runs each test
individually; ERT's member selector cannot reorder), and the 63
README doctests. Makefile gains WERROR, compile-all, and
test-shuffled. Autoload cookies added for the four interactive
commands and the define-tp/define-tps macros. package-lint: 0
findings (main file tp.el); draft MELPA recipe in docs/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
35 fixes across tp-layer.el, tp-stack.el, tp-search.el, tp-reactive.el
and tp-render.el, each empirically reproduced before and after:
layer-definition resolution/cycles/copying/cleanup, clipped region-local
stack mutators, symmetric backward search matching, length-changing
replacements, and reactive re-render correctness (replace-not-accumulate,
buffer-local isolation, batching union, per-interval props).
Adds four per-module regression suites (100 new tests); combined suite
is 438/438 green. tp-test-backward updated to the now-symmetric backward
matching contract it previously codified inverted. The string-vs-buffer
shortfall divergence in tp-forward-do/tp-backward-do is documented in
CHANGELOG as a known divergence rather than changed. Adds CHANGELOG.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp.el (4866 lines) is now an umbrella over nine modules with an
enforceable dependency order: tp-core -> tp-reactive -> tp-layer ->
tp-ops -> tp-search -> tp-render -> tp-stack -> tp-palette ->
tp-builtins. Upward dependencies are inverted through four hook
variables installed by tp-render.el.
Also: require text-property-search (fixes tp-backward void-function),
clip tp-intervals to the requested range, add the shared clipping
interval walker tp--map-intervals and tp-face-properties, remove
synced-conflict junk files, byte-compilation now succeeds (define-tp
macroexpansion previously failed at compile time).
All 280 legacy tests pass plus 8 new tp-core tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>