Introduce pure retained plans, stable objects, marker-backed range ownership, side indexes, content/property diffs, and multi-buffer rollback as the generic publication runtime. Verification: 707 ERT tests; 92 doctests; shuffled seed 20260806; compile-all WERROR=t.
425 lines
21 KiB
Markdown
425 lines
21 KiB
Markdown
# Changelog
|
|
|
|
All notable changes to the tp library are documented here.
|
|
|
|
## Unreleased
|
|
|
|
### Added
|
|
|
|
- TP 1.0 retained surfaces now provide defensive pure plans, prepare-scoped object identity, keyed/positional reconciliation, `content` and `properties` capabilities, marker-backed range anchors, same-surface overlapping property contributions, compare-before-write conflicts and explicit rebase, common-prefix/suffix text edits, property-run diffs, side indexes, opaque client state, generic reports, lifecycle cleanup, and atomic multi-buffer publication with exact rollback. Pure materialization uses the same plan semantics without leaving live handles or subscriptions.
|
|
- TP 1.0 signals and bindings now form an exact source→binding and binding→binding dependency graph with conditional rewiring, memoized equality cutoffs, transaction-local candidate signal values, deduplicated topological flushing, nested-write stabilization, rollback, cycle paths, owner disposal, buffer-scoped sources, variable adapters, and public scheduler counters. The legacy layer scanner remains isolated only until the retained-surface cutover.
|
|
- The first TP 1.0 runtime slice: `tp-style.el` provides atomic namespaced property schemas, structured subject selectors and combinators, deterministic origin/importance/layer/specificity/scope/source-order cascade, property-specific inheritance, tagged CSS-wide values, custom-property fallback/cycle handling, explicit `tp-computed` value sources, named declarations, provenance, and final Emacs-property projection. Ordinary function values remain literal.
|
|
- Internal Stage 2 canonical façade records and dataflow:
|
|
`tp--native-range`, `tp--presence`, `tp--request`, `tp--match`,
|
|
and `tp--result`. Public entry points and historical return shapes
|
|
remain compatible.
|
|
- Stage 3 text-only native façade in `tp-query.el`:
|
|
`tp-lookup-result`, `tp-lookup`, `tp-property-change`,
|
|
`tp-property-any`, `tp-property-not-all`, and
|
|
`tp-with-mutation-policy`.
|
|
- Stage 4 managed lifecycle APIs: `tp-attach-managed-layers`,
|
|
`tp-detach-managed-layers`, `tp-managed-layer-diagnostics`,
|
|
`tp-managed-buffer-diagnostics`, `tp-managed-diagnostics`, and
|
|
`tp-layer-transaction`.
|
|
- Stage 5 overlay-aware lookup modes for `tp-lookup`: `:char` and
|
|
`:char-source` report Emacs character-property values and the
|
|
winning overlay identity when an overlay wins.
|
|
- `docs/API-SEMANTICS.md` centralizes the current object/coordinate,
|
|
mutation, presence/nil, search-result, layer-ownership, `tp-text`,
|
|
error, and hidden-stack conflict contracts.
|
|
- `tp-any-value`, a unique public sentinel for property-search wildcard
|
|
matching when later positional arguments also need to be supplied.
|
|
- `tp-unresolved-layer` and `tp-layer-conflict` error types.
|
|
- `tp-reactive-observer-errors`, a newest-first structured record of
|
|
isolated watcher callback failures.
|
|
|
|
### Fixed
|
|
|
|
- Initial `tp-text` rendering now preserves every embedded property
|
|
interval for strings and buffers instead of spreading position-zero
|
|
properties across the replacement.
|
|
- `tp-text` application preserves the caller's `tp-set`, `tp-reset`,
|
|
or `tp-add` write semantics even when replacement text is unchanged.
|
|
- Explicit nil properties override embedded `tp-text` values.
|
|
- Non-parameterized layer redefinition refreshes managed regions with
|
|
old/new ownership reconciliation: removed keys disappear, new keys
|
|
replace them, and unrelated or externally changed values survive.
|
|
- Search APIs distinguish omitted VALUE (any directly present value)
|
|
from explicit nil (a present nil value), with one presence-aware run
|
|
scanner shared by strings and buffers.
|
|
- Removing the last nested sub-property removes the empty parent key
|
|
consistently for strings and buffers.
|
|
- Stack NOERROR catches only unresolved layer specifications; errors
|
|
from layer bodies and internal operations propagate.
|
|
- When hidden-layer full-stack storage detects an external direct
|
|
property edit, stack decoding now signals `tp-layer-conflict` before
|
|
any managed write instead of silently discarding the external value.
|
|
- Buffer transaction rollback tracks its live range with markers, so
|
|
insertions and deletions inside the range are removed or restored
|
|
together with the original text-property snapshot.
|
|
|
|
### Changed
|
|
|
|
- Transform and compute failures now propagate as business-output
|
|
errors. A transform returning a non-string also signals. Watcher
|
|
failures remain isolated observers, but are recorded structurally
|
|
while the managed update continues.
|
|
- Managed layer entries now store `tp-meta` in authoritative
|
|
`tp-layers` storage, including parameterized args and definition
|
|
versions. Direct rendered properties and public stack queries strip
|
|
`tp-meta`; historical returns remain unchanged.
|
|
- Parameterized mounted layer entries refresh from stored args after
|
|
redefinition.
|
|
- Insert/copy/yank/stickiness/narrowing/indirect-buffer behavior is
|
|
now documented as direct Emacs delegation with no tp wrapper.
|
|
- Overlay creation, movement, deletion, priority management, and
|
|
lifecycle remain native Emacs responsibilities; tp only reports
|
|
overlay-aware lookup results.
|
|
- `tp-with-mutation-policy` accepts only ordinary+respect,
|
|
ordinary+inhibit, and silent+inhibit; silent+respect is rejected.
|
|
- Theme enable/disable events now increment theme generation and expose
|
|
conservative refresh diagnostics. Reproducible benchmark evidence is
|
|
recorded in `docs/BENCHMARKS.md`; timings are advisory baseline data,
|
|
not release thresholds.
|
|
|
|
## 0.3.0 (2026-07-27)
|
|
|
|
### Added
|
|
|
|
Layer stack:
|
|
|
|
- **Layer visibility**: `tp-hide-layer` / `tp-show-layer` — a hidden
|
|
layer stays in the stack (and keeps receiving reactive updates) but
|
|
does not render; hiding the visible top reveals the next visible
|
|
layer, and with every layer hidden the text renders bare.
|
|
`tp-flatten-layers` merges only visible layers; `tp-merge-layers`
|
|
excludes hidden matched layers' props.
|
|
- `tp-lower-layer` (mirror of `tp-raise-layer`) and a
|
|
family-consistent `tp-rotate-layer` calling order
|
|
`(START END DIRECTION [COUNT] [OBJECT])`, selected unambiguously by
|
|
the symbols `up` / `down`; the legacy order keeps working.
|
|
- `tp-layer-stack-at` — the full ordered stack at one position as
|
|
`(NAME . PROPS)` conses, hidden layers marked by a `tp-hidden`
|
|
entry.
|
|
- Stack mutators return the number of property runs they modified
|
|
(including `tp-merge-layers` / `tp-flatten-layers`), and layer-name
|
|
lookups gained optional NOERROR arguments where they previously
|
|
signaled.
|
|
- `tp-describe-layer` — interactive help-buffer description of a
|
|
layer: storage format, arglist, stored body, expanded props,
|
|
reactive deps, transform, owning group.
|
|
|
|
Reactive engine:
|
|
|
|
- **Layer→buffer registry**: reactive updates now visit only the
|
|
buffers registered as showing the affected layer instead of scanning
|
|
the whole `(buffer-list)`; every buffer-mutating write path
|
|
registers (tp-set family, stack mutators, match/regexp appliers),
|
|
killed buffers are pruned, and an unknown layer falls back to one
|
|
learning full scan. `tp-reactive-layer-buffers` exposes the
|
|
registry; `tp-reactive-track-buffer` closes the
|
|
insert-a-propertized-string gap.
|
|
- **Minimal-diff `tp-text` re-render**: only the differing span is
|
|
edited (insert-before-delete), so point and markers in unchanged
|
|
text stay put and identical-text updates no longer touch the buffer
|
|
at all (buffer-modified flag preserved).
|
|
- `tp-gc-anonymous-layers` — collects interned anonymous layers that
|
|
no registered live buffer still shows (stack-aware: buried and
|
|
hidden layers count as alive; string-only layers are conservatively
|
|
kept).
|
|
|
|
Search and matching:
|
|
|
|
- `tp-regexp-set/reset/add` accept SUBEXP: properties apply to that
|
|
capture group per match (non-participating groups contribute
|
|
nothing); SUBEXP beyond the pattern's group count signals a clear
|
|
error.
|
|
- `tp-match-*` / `tp-regexp-*` accept START/END bounds with
|
|
as-if-only-that-portion semantics; reversed bounds are swapped.
|
|
- `tp-forward` / `tp-backward` / `tp-forward-do` / `tp-backward-do`
|
|
accept PREDICATE and NOT-CURRENT, passed through to the
|
|
text-property-search machinery; defaults keep the 0.2.0 symmetric
|
|
equal-matching contract exactly.
|
|
|
|
Layer definitions:
|
|
|
|
- **Multi-argument parameterized layers**: `define-tp` / `define-tps`
|
|
arglists may declare any number of parameters;
|
|
`(LAYER ARG1 ... ARGN)` and wrapped `(LAYER (ARG1 ... ARGN))` specs
|
|
work in `tp-set` and `tp-put-layer`; new `tp-layer-props-with-args`
|
|
/ `tp-group-props-with-args` / `tp-layer-arglist`. Wrong-arity
|
|
calls signal clear errors naming the layer and both counts.
|
|
- Prefix-conforming aliases `tp-define-layer` / `tp-define-group` /
|
|
`tp-define-palette` for discoverability (`C-h f tp-…`).
|
|
|
|
Core and palette:
|
|
|
|
- `tp-intervals` / `tp-intervals-map` accept an optional ABSOLUTE
|
|
argument returning native buffer coordinates (feedable straight
|
|
back into `tp-set`); the range-relative default is unchanged.
|
|
- `tp-palette-color` (generic theme-resolved accessor) and
|
|
`tp-palette-has-p` consolidate the palette query surface; all
|
|
existing query functions remain.
|
|
|
|
### Fixed
|
|
|
|
All six were found by an adversarial architecture/API review of the
|
|
new 0.3.0 code and confirmed with minimal reproductions before fixing:
|
|
|
|
- The reactive buffer registry only registered `tp-set`-family writes;
|
|
layers applied via `tp-push-layer`, `tp-match-set`, etc. never
|
|
re-rendered on variable updates.
|
|
- Reactive updates wrote only the rendered top layer; hidden or
|
|
buried layers kept stale props (visible again on `tp-show-layer`).
|
|
- `tp-gc-anonymous-layers` and `tp-reactive-track-buffer` scanned only
|
|
direct `tp-name` properties, so a layer buried in a stack (or
|
|
hidden) could be wrongly collected / missed.
|
|
- `tp-flatten-layers` / `tp-merge-layers` rendered hidden layers'
|
|
properties despite `tp-hide-layer`'s documented contract.
|
|
- Minimal-diff `tp-text` edits deleted before inserting, so markers at
|
|
the suffix boundary drifted to the wrong character.
|
|
- An error escaping a reactive update could strand queued batch
|
|
entries (now drained under `unwind-protect`; `tp-reactive-reset`
|
|
clears the queue).
|
|
|
|
### Changed
|
|
|
|
- **Module boundaries tightened** (behavior identical under
|
|
`(require 'tp)`): the `tp-text` handler chain moved from tp-render
|
|
into tp-ops — partial loads now get working `tp-text` replacement —
|
|
and `tp-with-batch-updates` moved up into tp-render; two of the four
|
|
upward hook variables are gone
|
|
(`tp--tp-text-handler-function`, `tp--reactive-flush-function`).
|
|
The layer-stack storage codec and the anonymous-layer machinery now
|
|
live in tp-layer; tp-stack's phantom dependency on tp-ops is gone;
|
|
67 lines of dead code deleted. tp-core holds no mutable state.
|
|
- String forms of all 16 stack mutators document that they modify the
|
|
string in place (unlike `tp-set`'s copy semantics); unifying this is
|
|
on the 0.4 ledger.
|
|
|
|
### Deprecated
|
|
|
|
- `tp-search-forward` / `tp-search-backward` (0.3.0) — thin wrappers
|
|
whose nil-PREDICATE default contradicts the rest of the library's
|
|
equal-matching; use `tp-forward` / `tp-backward`, or the Emacs
|
|
primitives for raw access.
|
|
- `tp-suffix-symbol` (0.3.0) — internal helper now private as
|
|
`tp--suffix-symbol`; a compatibility alias remains.
|
|
|
|
### Infrastructure
|
|
|
|
- GitHub Actions CI: Emacs 28.1 / 29.4 / 30.1 matrix running
|
|
byte-compilation with warnings-as-errors, the full ERT suite, a
|
|
shuffled-order rerun of every test (`make test-shuffled`,
|
|
`tp-run-shuffled.el`; `SHUFFLE_SEED=N` reproduces an order), and the
|
|
README doctests.
|
|
- The whole tree byte-compiles with zero warnings (57 fixed:
|
|
docstring rewraps and quoting, `defvar` declarations for reactive
|
|
test variables, prefixed doctest counters, one impossible `eq`
|
|
comparison corrected to `equal`).
|
|
- Autoload cookies for the interactive commands (`tp-debug-show`,
|
|
`tp-debug-clear`, `tp-reactive-reset`, `tp-layer-reset`,
|
|
`tp-palette-show`, `tp-clear`) and the `define-tp` / `define-tps`
|
|
macros.
|
|
- Two doctest assertions made property-order-insensitive (Emacs 28
|
|
prints text-property plists in a different order than 29+).
|
|
|
|
## 0.2.0 (2026-07-26)
|
|
|
|
### Architecture
|
|
|
|
- **tp.el was split into layered modules.** `(require 'tp)` still loads
|
|
everything; nothing changes for users. Each module depends only on
|
|
the ones before it, and the byte compiler enforces the order:
|
|
|
|
| Module | Responsibility |
|
|
|---|---|
|
|
| `tp-core.el` | Intervals, plist/face merge engine, debug logging, `$var` utilities |
|
|
| `tp-reactive.el` | Reactive dependency registry, variable watchers, batching queue |
|
|
| `tp-layer.el` | `define-tp` / `define-tps`, layer registry and resolution |
|
|
| `tp-ops.el` | `tp-set` / `tp-reset` / `tp-add` / `tp-get` / `tp-at` / `tp-remove` / `tp-clear` |
|
|
| `tp-search.el` | `tp-match-*`, `tp-regexp-*`, `tp-search`, navigation |
|
|
| `tp-render.el` | Reactive re-rendering engine (installs itself into lower modules) |
|
|
| `tp-stack.el` | Layer stack operations (push/pop/move/merge/flatten/...) |
|
|
| `tp-palette.el` | Light/dark color palette data |
|
|
| `tp-builtins.el` | Built-in layers, palette gallery, display-buffer helpers |
|
|
|
|
- The library now byte-compiles cleanly (previously `define-tp`
|
|
macro-expansion failed at compile time).
|
|
- New shared engine `tp--map-intervals`: a clipping interval walker that
|
|
underlies region operations; property edits can no longer bleed
|
|
outside the requested region.
|
|
- New public constant `tp-face-properties` (`'(face font-lock-face
|
|
mouse-face)`): the property family that gets face-aware merging.
|
|
|
|
### Fixed
|
|
|
|
Core operations:
|
|
|
|
- `(require 'text-property-search)` was missing; `tp-backward` signaled
|
|
`void-function` in batch/fresh sessions.
|
|
- `tp-remove` string form silently dropped the 3rd and later properties.
|
|
- String-form removal helpers sampled properties at position 0 and
|
|
smeared them across the range, destroying neighboring intervals; they
|
|
now work per-interval.
|
|
- `tp-clear` computed default bounds from the current buffer even when
|
|
clearing a string (silent no-op or range error).
|
|
- `(tp-get STRING START END ...)` returned nil silently; it now behaves
|
|
like the buffer region form.
|
|
- `tp-intervals` returned unclipped intervals (including negative
|
|
offsets); it now clips to `[START, END)`.
|
|
- Region-form calls with flat prop/val arguments — `(tp-set 1 4 'face
|
|
'bold)` — silently discarded the value and failed later; they now
|
|
signal a clear error immediately.
|
|
- Face-family prepend semantics in `tp-add` covered only `face`; they
|
|
now cover `font-lock-face` and `mouse-face` too.
|
|
- `tp--parse-face-list` no longer invents a `(:key nil)` pair for a
|
|
trailing bare keyword.
|
|
|
|
Built-ins and palette:
|
|
|
|
- Emacs 28.1 compatibility restored (`plistp` is Emacs 29+; a compat
|
|
shim is used, and `subr-x` is required where needed).
|
|
- `tp-pop-to-buffer` / `tp-switch-to-buffer` no longer bind `q` in the
|
|
shared major-mode keymap (a buffer-local minor-mode keymap is used)
|
|
and no longer capture a `buffer` variable from the caller.
|
|
- `tp-link` resolved its palette color once at load time; the color is
|
|
now resolved at application time, so theme switches are honored.
|
|
- `define-tp-palette` no longer generates per-palette defvars;
|
|
`tp-palette-alist` is the single source of truth and palette
|
|
redefinition takes effect immediately.
|
|
- `tp-headline` emitted invalid `(:height nil)` for integer heights.
|
|
- `tp-space` now produces the documented pixel `(space :width (N))`
|
|
spec.
|
|
- `tp-parse-color` accepts one-sided cons colors like `("white" . nil)`.
|
|
|
|
Layer definitions (tp-layer):
|
|
|
|
- Parameterized `define-tps` groups: the documented format returned nil
|
|
props via `tp-group-props-with-arg`; all documented element shapes now
|
|
resolve correctly.
|
|
- Cyclic layer references signal a clear error naming the cycle
|
|
(previously crashed with `excessive-lisp-nesting`); diamond-shaped
|
|
reuse is not a false positive.
|
|
- `define-tp` errors at macro-expansion time when extra body forms are
|
|
present (previously silently discarded all but the first).
|
|
- `$`-symbols in parameterized layer bodies resolve to their variables'
|
|
current values at evaluation time (previously leaked literally into
|
|
the output props); parameterized layers remain non-reactive, and the
|
|
choice is documented.
|
|
- `tp-layer-props` / `tp-group-props` and their `-with-arg` variants
|
|
return copies; mutating a returned plist can no longer corrupt the
|
|
registry.
|
|
- `:transform` in `define-tps` group elements is honored (was silently
|
|
dropped).
|
|
- Group redefinition and `tp-undefine-group` clean up the layers the
|
|
group generated, including their reactive deps and transforms
|
|
(previously orphaned).
|
|
- The group-element parser errors on unknown keywords instead of
|
|
advancing by one and re-reading a value as a key.
|
|
- Anonymous reactive layers are interned: an `equal` `$var` props spec
|
|
reuses the existing registry entry instead of minting a new one on
|
|
every `tp-set` (unbounded leak fixed).
|
|
|
|
Layer stacks (tp-stack):
|
|
|
|
- All stack mutators were rewritten onto a shared clipped region walker;
|
|
region ops no longer alter text outside `[START, END)`, and
|
|
`tp-put-layer` is region-local instead of switching behavior on
|
|
whole-object emptiness.
|
|
- The documented inline-plist spec (`(face bold ...)`) and
|
|
list-of-layer-names spec (`'(layer-a layer-b)`) for `tp-put-layer`
|
|
work (previously errored), handled at the call site.
|
|
- `tp-region-layer-props` no longer double-offsets string positions.
|
|
- `tp-merge-layers` / `tp-flatten-layers` no longer drop explicitly-nil
|
|
values (presence is checked with `plist-member`).
|
|
- Single-layer stacks no longer carry a garbage `(tp-layers nil)`
|
|
property, and its absence is tolerated everywhere.
|
|
- `tp-layer-top` respects the requested region instead of reading only
|
|
the first interval.
|
|
|
|
Search and navigation (tp-search):
|
|
|
|
- `tp-backward` buffer paths passed no predicate to
|
|
`text-property-search-backward`, so matching was inverted relative to
|
|
`tp-forward`; backward now mirrors forward's equal-matching
|
|
semantics. The legacy test that codified the inverted behavior
|
|
(`tp-test-backward`) was updated to the symmetric contract.
|
|
- Empty and zero-width patterns no longer loop forever in the
|
|
match/regexp apply engines.
|
|
- Length-changing replacements work in buffers in `tp-forward-do` /
|
|
`tp-backward-do` / `tp-search-map` (previously signaled
|
|
`args-out-of-range` via `store-substring`). On strings — which cannot
|
|
change length in place — a length-changing replacement signals a
|
|
clear error instead of silently truncating or leaving residue;
|
|
same-length string replacements are unchanged.
|
|
- `tp-search-map` with a non-current buffer OBJECT operates on that
|
|
buffer (previously read and mutated the current buffer) and no longer
|
|
corrupts buffers on length-changing replacements.
|
|
- `tp-match-add` buffer path uses face-family-aware merging like the
|
|
string path, so existing faces are preserved.
|
|
- `tp-search-map` can remove properties on strings (nil-props ranges
|
|
were previously skipped).
|
|
- The triplicated ~38-line replacement lambda was extracted into one
|
|
shared helper.
|
|
- `tp-forward-do` / `tp-backward-do` shortfall is now all-or-nothing on
|
|
both paths: TIMES targets the TIMES-th match specifically, so when
|
|
fewer matches exist nothing is applied and the available count is
|
|
returned. String paths previously acted on the last available match —
|
|
the wrong target; the two legacy tests codifying that
|
|
(`tp-test-forward-do-on-string-with-range` and its backward twin)
|
|
were updated.
|
|
|
|
Reactive rendering (tp-reactive / tp-render):
|
|
|
|
- Sub-region `tp-text` on a string no longer discards the rest of the
|
|
string.
|
|
- Computed-variable updates deep-merge resolved props with the layer
|
|
definition, preserving sibling static attributes.
|
|
- Reactive refresh replaces the re-rendered layer's own property keys
|
|
instead of accumulating (bold→italic no longer yields
|
|
`(italic bold)`), while preserving other layers' properties.
|
|
- `setq-local` re-renders the buffer without leaking buffer-local
|
|
values into the global layer definition.
|
|
- Reactive `tp-text` replacement preserves unrelated existing
|
|
properties.
|
|
- Computed values of nil propagate (nil was conflated with the error
|
|
sentinel).
|
|
- Variable-watcher reentrancy: nested `set` calls inside the update
|
|
path queue their re-render through the batch queue instead of
|
|
recursing.
|
|
- Batched updates union their WHERE and tp-text flags at flush time
|
|
instead of freezing the first change's.
|
|
- Reactive strings keep per-interval props on re-render (previously
|
|
only position-0 props survived and were smeared).
|
|
- `:transform` applies on the first render too, not only on updates.
|
|
|
|
Test infrastructure:
|
|
|
|
- The test fixture now tears down with `unwind-protect` and resets all
|
|
registries including `tp-layer-transforms` (previously leaked across
|
|
tests); the suite passes in randomized order.
|
|
- `tp-tests.el` header and `provide` renamed to match its file name.
|
|
|
|
### Added
|
|
|
|
- `tp-member`: like `tp-at`, but distinguishes "property present with
|
|
value nil" from "property absent" (plist-member-style result).
|
|
- `Makefile` with `test` / `doctest` / `compile` / `clean` targets.
|
|
- `tp-doctest.el`: executable documentation tests — 63 assertions
|
|
reproducing README examples and comparing against their exact
|
|
documented outputs (`make doctest`).
|
|
- Per-module regression test suites: `tp-core-tests.el`,
|
|
`tp-ops-tests.el`, `tp-builtins-tests.el`, `tp-layer-tests.el`,
|
|
`tp-stack-tests.el`, `tp-search-tests.el`, `tp-render-tests.el` —
|
|
the combined suite grew from 280 to 439 tests.
|
|
|
|
### Changed
|
|
|
|
- License clarified to GPLv3+ in file headers, matching the shipped
|
|
LICENSE file (headers previously said v2+).
|
|
|
|
## 0.1.0
|
|
|
|
Initial release (monolithic tp.el).
|