Publish client-owned side state after retained surfaces and roll it back in reverse order on any transaction failure.\n\nVerified: 715 ERT tests, 92 doctests, WERROR compile-all, checkdoc, git diff --check.
427 lines
22 KiB
Markdown
427 lines
22 KiB
Markdown
# Changelog
|
|
|
|
All notable changes to the tp library are documented here.
|
|
|
|
## Unreleased
|
|
|
|
### Added
|
|
|
|
- `tp-transaction-participate` lets a client promote rollback-capable opaque side state after all affected surfaces publish but before source values commit. Participant keys are unique per outer transaction, failure rolls participants back in reverse publication order, and observers still run only after the transaction exits.
|
|
- `tp-propertize`, `tp-apply`, and `tp-watch` now provide simple one-shot string, one-shot buffer-range, and reactive existing-text entry points over the same schema/cascade/projector and retained properties-surface core.
|
|
- 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).
|