API-NAME-01: tp-search-forward / tp-search-backward are marked
obsolete since 0.3.0 with tp-forward / tp-backward as the recorded
replacements, and their docstrings now document all four arguments,
the primitive's nil-PREDICATE not-`equal' default, and the raw-use
advice (call the Emacs primitives directly - the wrappers add
nothing). The wrappers keep their exact primitive-delegating bodies
so every existing call stays bit-identical; a defalias onto
tp-forward/tp-backward would have flipped both the argument order
(OBJECT/N vs PREDICATE/NOT-CURRENT) and the default matching
semantics, and would have recursed through tp-forward's own calls.
Internal callers (tp-forward, tp--forward-do) now call the
primitives, leaving the deprecated names with zero in-tree callers.
The tp-forward-do / tp-backward-do summaries now read "search TIMES
times; apply FUNCTION at the Nth match" and explicitly name
tp-search-map as the for-each, so the -do suffix stops reading as
one. tp-forward's docstring states the string-path contract
precisely (list of the FIRST N matches from position 0, point never
involved) next to the buffer path's point motion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SRC-1: tp--pattern-apply-single now swaps START > END bounds before
branching on the object type, so the string path stops signaling a
raw substring args-out-of-range while the buffer path keeps its
historical narrow-to-region swap; the behavior is now uniform and
documented in all six tp-match-*/tp-regexp-* docstrings.
SRC-2: a SUBEXP larger than the pattern's capture-group count (per
regexp-opt-depth) signals "Regexp X has no group N" instead of
silently matching nothing, while legal non-participating and
zero-width groups keep working quietly.
Also corrects the return wording of tp-match-set/tp-regexp-set (a
NEW string for string objects, not "the modified string") and adds
the missing return sections to tp-match-add/tp-regexp-add, matching
the verified tp-match-reset behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp--anonymous-layer-counter and tp--generate-anonymous-layer-name
lived in tp-core, two modules below their only caller
tp--anonymous-layer-name-for - and the counter was tp-core's only
piece of mutable non-defcustom state. Anonymous-layer naming is layer
machinery, so move both into tp-layer.el right beside the registry and
the interning function. tp-core is now pure utilities plus defcustoms.
Also document a deliberate asymmetry that was easy to mistake for an
oversight: tp-layer-reset clears the anonymous-layer registry but the
counter INTENTIONALLY survives it. Detached strings can outlive a
reset while still carrying tp-anon-N property values, so the counter
must keep increasing monotonically - a post-reset anonymous layer must
never be minted under a name a stale string still holds. The comment
protects the future registry refactor from "fixing" this wrongly.
Internal symbols only; no API or behavior change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp--expand-layer-to-props-list and tp--expand-props-to-remove were an
orphaned early draft of "expand a layer name into the property keys it
contributes": nothing calls them - the only reference in the whole
tree (sources, tests, doctest, README) is the pair calling each other.
The maintained equivalent of this logic is the inline expansion in
tp-ops's tp--remove-property, which is what tp-remove actually runs;
if deduplication is ever wanted, that copy is the one to extract.
Keeping a second, uncalled implementation of layer expansion around
invites the two to drift apart silently, so remove the 67 dead lines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp-stack.el required tp-ops but used nothing from it: a whole-word
sweep of every symbol tp-ops defines finds exactly one hit in
tp-stack, and it is a docstring cross-reference to tp-set, not code.
The require only made the module graph look denser than it is and
suggested a stack->ops edge that never existed.
The stack module's real dependencies are tp-core (interval walking),
tp-reactive (the buffer registry it feeds via
tp-reactive--register-layer-buffer) and tp-layer (the stack-storage
codec). All three requires are already present, and tp-stack.el
byte-compiles independently with warnings-as-errors after the
deletion. Load order through tp.el is unaffected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp--flush-batch-updates existed to re-render buffers, which is
tp-render's whole job - but it lived down in tp-reactive and reached
the renderer through the tp--reactive-flush-function inversion,
complete with a guard that silently DROPPED queued re-renders when no
hook was installed. A partial load could thus discard updates without
a trace.
Move tp--flush-batch-updates and the public tp-with-batch-updates
macro (same name, same behavior - only the home file changes) into
tp-render.el, next to tp--reactive-flush-entry, which the flush now
calls directly. Delete the tp--reactive-flush-function defvar, its
silent-drop guard, and the install line. The queue state
(tp--batch-update-pending, tp--batch-update-active,
tp--reactive-updating) and tp--queue-batch-update stay in tp-reactive;
the relocated macro let-binds them downward, which is legal.
Together with the tp-text handler move this takes the sanctioned
upward hooks from four to two - only the genuine lower-layer event
sources remain (tp--reactive-update-function in tp-reactive,
tp--layer-refresh-function in tp-layer) - and a degraded partial load
now fails honestly with a void-function error instead of silently
discarding queued re-renders. The ARCH-4 unwind-protect around the
flush tail in tp--reactive-apply-update is untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reactive tp-text property is consumed by tp-set/tp-reset/tp-add,
which live in tp-ops - yet the code implementing it (the transform,
embedded-props merge, minimal put, replace and handler entry) sat two
layers up in tp-render, wired back down through the
tp--tp-text-handler-function inversion. That made the handler an
upward hook for no reason: none of the seven functions touches
anything above tp-layer, so they belong at the layer of their callers.
Move tp--find-tp-text-reactive-var, tp--tp-text-transform,
tp--merge-embedded-props, tp--put-text-property-unless-equal,
tp--apply-reactive-text-props, tp--tp-text-replace and
tp--handle-tp-text-property into tp-ops.el unchanged; delete the
tp--tp-text-handler-function defvar, the tp--handle-tp-text
dispatcher and the install line, and let tp-set/tp-reset/tp-add call
tp--handle-tp-text-property directly.
tp-render keeps (require 'tp-ops): its updaters
(tp--update-reactive-text, tp--replace-reactive-text-in-buffer) still
call tp--tp-text-transform and tp--apply-reactive-text-props, so the
require flips from phantom (hook-only) to a genuine downward
dependency. tp--replace-reactive-text-in-buffer itself stays in
tp-render - it is re-render machinery, not a property primitive.
Behavior under (require 'tp) is bit-identical. A partial load of
tp-core..tp-ops now handles tp-text for real instead of silently
treating it as an inert property:
(tp-set "hello" 'tp-text "world") => "world" with tp-render absent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp-reactive-reset cleared the four reactive registries and the buffer
registry but not the global tp--batch-update-pending queue. Entries
queued there by a nested variable write during a non-batched update
were flushed by a tail that sat OUTSIDE any unwind-protect, so an
error escaping the re-render (for example from a modification hook)
stranded them; a later tp-layer-reset left the ghost entries naming
layers that no longer existed, and the next completed update's flush
replayed them against the fresh registry.
Fix:
- tp-reactive-reset now also clears tp--batch-update-pending (and so
does tp-layer-reset, which delegates to it).
- tp--reactive-apply-update wraps the non-nested update body in
unwind-protect with the existing flush tail as the cleanup form, so
nested queue entries flush even when the re-render signals; the
reentrancy guard is unbound by then, so the cleanup flush
re-renders exactly like the success path.
Tests port the review's probes: a queued ghost entry disappears on
tp-reactive-reset, and a before-change-functions hook that writes a
second reactive variable and then signals leaves the queue drained
with the nested variable's re-render applied.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The minimal-diff reactive text edit deleted the differing span first
and inserted the replacement after. Deleting collapsed an
insertion-type-nil marker sitting on the FIRST character of the
preserved suffix onto the edit start, so it ended up stranded before
the inserted text instead of on its (unchanged) character -
contradicting the documented "markers sitting in unchanged text keep
their positions". The drift happened even for same-length
replacements ("0" -> "9").
tp--edit-region-minimal-diff now inserts the replacement at
edit-start first and then deletes the shifted old span
((delete-region (point) (+ (point) (- edit-end edit-start)))): the
insertion shifts the suffix-boundary marker right with its character
and the deletion pulls it back into place. Markers whose characters
were deleted now end at the END of the edit rather than the start
(documented side effect, still inside the replacement span).
Point clamping semantics are preserved: the documented "point inside
the edited span ends up at the start of the edit" was previously an
artifact of save-excursion's own marker collapsing under
delete-then-insert, which the new edit order breaks.
tp--replace-reactive-text-in-buffer therefore owns point restoration
now - it remembers point in a marker, clamps it to the edit start
when an edit swallowed it, and restores it at the end - and
tp--update-reactive-text's save-excursion wrapper is removed (it
would have overridden the clamp with its drifted marker). All four
shipped point/marker behavior tests pass unchanged.
New regressions port verify-txt1: suffix-start marker tracking for
grow, same-length and shrink edits (direct calls), the deleted-char
marker landing at the edit end, and a real setq-driven suffix-marker
case.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Multi-argument parameterized layers silently mis-handled every
wrong-arity call:
- Missing args: cl-progv (since its Emacs 27 rewrite) binds
parameters beyond the supplied values to nil, never leaving them
unbound, so (tp-set "s" '(two-arg-layer "red")) quietly produced
(:foreground "red" :background nil) - while the docstring of
tp-layer-props-with-args falsely promised an unbound-variable
error.
- Excess flat-form args fell into extra-props and were applied as
garbage text-property KEYS with value nil ("green" as a property
name).
- The stack path (tp-push-layer with one arg for a two-parameter
layer) fell through tp--normalize-layer-spec's named-inline branch,
building an odd-length plist and dying with the cryptic "Odd length
text property list".
Fixes, per site:
- tp-layer-props-with-args checks (length args) against the arglist
and signals "tp layer NAME takes N argument(s), got M"; its
docstring now documents the explicit error instead of the
impossible unbound-parameter story. Extra values remain ignored
in this direct call, per the documented contract.
- tp--resolve-props signals the same error after the arity split when
fewer values were available (covering parameterized groups too),
and rejects flat-form excess positional args whose first extra
element is not a symbol (i.e. can never be a property key).
- tp--normalize-layer-spec signals "tp layer NAME expects N args,
got M" for any parameterized-layer arity mismatch instead of
falling through to the named-inline branch.
Correct-arity flat, wrapped, extra-props and group behavior is
unchanged (covered by controls in the new tests, which port the
review's verify-arg1 probe).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp-hide-layer's contract says a hidden layer "no longer renders", but
tp-flatten-layers merged the whole stack unfiltered and
tp-merge-layers merged every listed layer unfiltered (only the
tp-hidden bookkeeping flag was stripped), so flattening or merging a
stack with a hidden layer silently un-hid it: the hidden layer's
props became the visible rendering (green -> red in the review
probe). The shipped flatten test asserted only flag absence, missing
the rendering flip.
New semantics, stated in both docstrings:
- tp-flatten-layers discards hidden layers (image-editor flatten):
only visible layers' props merge; a run whose every layer is hidden
flattens to bare text, consistent with all-hidden rendering.
- tp-merge-layers merges hidden matched layers away but excludes
their props, so a merge can never render what was hidden. When ALL
matched layers are hidden the merged layer keeps their merged props
but carries tp-hidden itself - data preserved, nothing un-hidden,
tp-show-layer reveals it.
API-RET-01 slice: both functions now return the number of modified
runs, counting exactly like tp-delete-layer (their previous hardcoded
nil return was undocumented, so no documented behavior changes).
Tests: the flatten-drops-tp-hidden-flag test now asserts the rendered
face; new tests port the hid2-probe scenarios (flatten with hidden
top, all-hidden flatten to bare text, merge excluding hidden props,
all-hidden merge staying hidden and revealable) plus count-return
coverage for both functions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Behavior-preserving relocation of tp--stack-hidden-p,
tp--stack-props-to-list and tp--stack-build-props into tp-layer.el,
beside their existing dependencies tp--layer-stack-to-list and
tp--build-layer-props. The codec's only inputs are dash/seq/plist
utilities, so it sits naturally at the layer-definition level, and
the move lets the reactive re-render engine (tp-render.el, which may
not require tp-stack) read and write stack storage without
duplicating format knowledge - the prerequisite for making reactive
updates write through to hidden/buried layer storage. tp-stack keeps
calling the codec downward; no callers change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 0.3.0 buffer registry made reactive updates walk only registered
buffers, but only tp-set/tp-reset/tp-add registered. Layer stack
mutators (tp-push-layer, tp-put-layer, hide/show, move/raise/rotate,
merge/flatten, add-to-layers) and the pattern-apply paths
(tp-match-add/-reset, tp-regexp-add/-reset) stamped tp-name via
direct set-text-properties without registering, so the moment a layer
was known from one tp-set anywhere, every buffer reached only through
those paths was silently and permanently skipped by reactive updates
- including the README's own flagship tp-push-layer example. This
regressed 0.2.0, whose full buffer-list scan reached every buffer.
Fix: every buffer write that stamps tp-name now registers the buffer
via tp-reactive--register-layer-buffer (called directly, so tp-stack
and tp-search gain no tp-ops edge):
- tp-stack.el: new tp--stack-register-layers registers every named
plist of the freshly written stack - buried and hidden layers
included, which the stack-aware GC liveness fix builds on - beside
all eleven set-text-properties sites; requires tp-reactive.
- tp-search.el: tp--deep-merge-apply and tp--reset-apply register the
target buffer for the applied props' tp-name (and any group
tp-layers entries); requires tp-reactive.
Regression tests: reactive updates reach a second buffer whose layer
arrived via tp-push-layer / tp-regexp-add / tp-match-reset after a
tp-set registered the layer elsewhere, and stack writes register
buried and hidden layers that arrived via string insertion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp-put-layer now dispatches multi-argument layer and group specs —
flat (NAME A1 .. AN) and wrapped (NAME (A1 .. AN)) — checked before
the list-of-specs branch so argument values that are themselves layer
names are not misread as a stack of layers. tp-remove's layer-key
extraction binds every parameter with dummy args via
tp-layer-props-with-args instead of passing a single dummy to the
first parameter only. Five regression tests; suite 527/527, doctests
63/63, compile clean.
Known pre-existing gap surfaced while testing (deliberately not fixed
here, queued for the design review): parameterized layers applied via
the tp-set plist forms never stamp tp-name, so tp-remove by layer
name is a silent no-op for them regardless of arity.
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>
Emacs 28 stores/prints text-property plists in a different order than
29+; the S-mystyle and L-paramgroup assertions compared whole prin1
strings and failed on 28.1 only. Compare per property instead (face,
tp-name, tp-layers — the stack order inside tp-layers is stable), and
note the version-dependent print order beside both README examples.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Job logs are not anonymously readable; emit FAIL/expected/got lines
(or the log tail on a load error) as ::error:: annotations so the
failing assertion is visible through the public Checks API.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dash install succeeded; the step failed writing GITHUB_ENV because
ls -d .elpa/dash-* matched both the package directory and the adjacent
dash-N.N.N.signed marker, producing a second non-KEY=value line
("Invalid format"). Restrict the glob to directories. Also bump
actions/checkout to v6 to clear the Node 20 deprecation warning.
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>
Splits tp.el into nine layered modules behind the tp.el umbrella,
fixes ~43 confirmed bugs across every subsystem, adds tp-member,
grows the ERT suite from 280 to 443 tests plus 63 executable README
doctests, and aligns CHANGELOG, bilingual READMEs, and docs/ with the
actual behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 assertions reproduce README code blocks and compare against the
exact documented outputs; make doctest runs them in batch and exits
non-zero on any mismatch, so docs and code cannot silently drift
again. Wired into the Makefile and mentioned in both READMEs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp-forward-do/tp-backward-do target the TIMES-th match specifically;
when fewer matches exist the string paths applied FUNCTION to the last
available match -- the wrong target -- while buffer paths applied
nothing. Both now apply nothing on shortfall and return the available
count. Updates the two legacy with-range tests that codified the
string behavior, adds four shortfall/exact-count regression tests, and
documents the contract in both READMEs and the docstrings. Suite
443/443 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README.md/README_CN.md: new verified Quick Start (fixes the dead
#quick-start nav link); Installation rewritten for the tp-*.el module
family; every broken or drifted example fixed and executed in batch
Emacs (mandatory () ARGLIST and quoted reactive keywords in all
define-tp/define-tps calls, corrected tp-search-map argument order,
interval-list returns, stacked duplicate-face results, gap intervals,
last-wins tp-plist, non-destructive string forms, per-pattern match
ordering, case-fold regexp outputs, rewired end-to-end theme example);
documents the symmetric tp-backward contract, the length-changing
replacement rules, all four tp-put-layer layer specs, and the
previously-missing tp-member, buffer/display macros, and palette
system; state resets now use tp-layer-reset; license corrected to
GPLv3+. CN mirrors EN exactly (119 headings / 258 fences each; code
blocks identical, comments translated).
docs/ARCHITECTURE.md rewritten around the real nine-module layering
and hook-variable inversions; nonexistent helper names removed.
docs/CODE-ANALYSIS.md marked as pre-split historical analysis with
locations/counts corrected. Reactive docs aligned with the fixed
engine semantics (replace-not-accumulate, buffer-local isolation,
nil computed values, batching union, first-render transform).
287 fenced blocks from both READMEs executed: 0 failures; 56-example
assertion suite passes; combined ERT suite 439/439 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tp--replace-match-text's string branch silently truncated longer
replacements and left residue on shorter ones ("<foo>" into a 3-char
match yielded "<fo"). Strings cannot change length in place, so the
honest contract is: same-length replacements mutate in place as before;
a different-length replacement signals a clear error directing the
caller to a buffer OBJECT. Buffer paths (delete+insert) are unchanged.
Updates the five tests that codified truncation/residue and adds a
same-length regression test; suite 439/439 green.
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>
Like tp-at but distinguishes a property present with value nil from an
absent property, returning (PROPERTY VALUE) or nil. Covered by four
new tests in tp-ops-tests.el; full suite 338/338 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ops/core (B1-B8): tp-remove no longer drops 3rd+ properties; string
removal is per-interval via tp--map-intervals instead of smearing
position-0 props; tp-clear defaults bounds from OBJECT; (tp-get STR N N)
works like the buffer region form; no bogus (:key nil) from trailing
bare keywords; region form signals immediately on flat PROP/VAL args;
face-family prepend semantics extended to font-lock-face/mouse-face.
builtins/palette (B45-B51): Emacs 28.1 compat for plistp/subr-x;
display-buffer macros use a minor-mode keymap instead of mutating the
major-mode map; tp-link resolves palette colors lazily (theme-correct);
tp-palette-alist is the single source of truth (stale defvars dropped);
tp-headline handles integer heights; tp-space matches its documented
pixel spec; tp-parse-color accepts one-sided cons colors.
Test infra: fixture gains unwind-protect teardown via tp-layer-reset
(incl. transforms); file header/provide renamed to tp-tests; suite is
order-independent (verified with shuffled runs). Adds Makefile with
test/compile/clean targets.
334 tests green (280 legacy + 54 new regression tests).
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>
When (frame-parameter nil 'background-mode) returns nil (e.g., in
batch mode or certain configurations), both tp-theme-light-p and
tp-theme-dark-p return nil, causing color parsing to fail for
cons cell ("light" . "dark") and plist (:light "l" :dark "d") formats.
Added default fallback to light color when background-mode is unknown.
This is the root cause of tp-palette settings not taking effect.
Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>