Commit Graph

371 Commits

Author SHA1 Message Date
Kinneyzhang
47e8d8c256 perf: retain authoritative publication target state 2026-09-05 07:07:53 +08:00
Kinneyzhang
b2b9462269 perf: rebuild shadow patch output in one pass 2026-09-05 06:53:10 +08:00
Kinneyzhang
c05174ff9d refactor!: publish transactions exclusively through protocol v2 2026-09-05 05:25:29 +08:00
Kinneyzhang
25cb66c040 feat(transaction): expose structured participant registration 2026-09-01 15:52:05 +08:00
Kinneyzhang
469bdff17d feat(transaction): cut over to structured publication authority 2026-09-01 15:37:29 +08:00
Kinneyzhang
2e2d9edc84 fix(package): distinguish transaction-capable TP 2026-09-01 05:57:28 +08:00
Kinneyzhang
9ac8c7a133 feat: expose bounded final-accept markers 2026-09-01 00:50:32 +08:00
Kinneyzhang
76be75f674 feat: add TP v1+v2 transaction contract 2026-08-31 19:35:02 +08:00
Kinneyzhang
7632a05bdf test: lock M0a transaction fault behavior 2026-08-31 17:49:33 +08:00
Kinneyzhang
c2525a137b feat: expose transaction activity boundary 2026-08-27 22:49:21 +08:00
Kinneyzhang
31304c1906 perf: make surface observation opt-in 2026-08-27 22:30:16 +08:00
Kinneyzhang
edc51194b6 chore: migrate conditional bindings for Emacs 31 2026-08-26 00:09:44 +08:00
Kinneyzhang
34b65b338f perf: retain surface state and layered paint slots 2026-08-25 17:16:55 +08:00
Kinneyzhang
479ee8f466 Harden TP transactions and preserve point 2026-08-22 06:18:51 +08:00
Kinneyzhang
db5864141a update 2026-08-13 14:40:44 +08:00
Kinneyzhang
0d35358e05 refactor(tp)!: implement retained reactive runtime
Replace the legacy managed layer renderer with one independent retained/reactive text runtime. TP now owns exact dependencies, stable objects, marker-backed mounts, property contribution composition, atomic publication, rollback, and direct text-property facades without ECSS or Ebox dependencies.\n\nBREAKING CHANGE: remove tp-render, tp-stack, scan-driven managed layers, inline runtime metadata, TP-owned CSS cascade APIs, and dollar-variable declarations.\n\nVerified: 290/290 ERT, shuffled 290/290 (seed 20260806), 8/8 doctests, WERROR compile-all, checkdoc, package-lint, diff-check, and isolated TP-only load.
2026-08-07 00:39:50 +08:00
Kinneyzhang
9d4047daf2 docs(tp): separate property composition from CSS cascade
Define TP as the independent retained text-property runtime and reserve complete stylesheet winner semantics for ECSS.

Verified: make test EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs LOAD_EXTRA="-L ../twidget2/dependencies" (729 passed); git diff --check
2026-08-06 18:03:48 +08:00
Kinneyzhang
1195297011 feat(tp): isolate stylesheet rule domains
Give independent consumers their own rules, cascade layer ordering, and source-order counters so packages such as Ebox cannot pollute TP's default stylesheet or each other. Match generic class and state tokens by value and document caller-owned stylesheet lifecycle.

Verified: make clean; make test (728/728); make compile WERROR=t; checkdoc tp-style.el; git diff --check; Ebox make test against ../tp.
2026-08-06 13:59:08 +08:00
Kinneyzhang
07b84b0685 feat(tp): add scoped retained surface updates
Allow callers to authorize one atomic surface update through generic retained object handles while TP remains the sole owner of mount resolution, buffer publication, and rollback.

Verified: byte compilation with warnings as errors; 726 ERT tests; 92 doctests; targeted scoped-update tests; checkdoc; git diff --check.
2026-08-06 06:32:58 +08:00
Kinneyzhang
fc4a36ba2b refactor(tp): decouple retained core from dash
Replace the two Dash list helpers in tp-core with cl-lib primitives so tp-surface can load through its minimal public module chain. The legacy facade continues to declare Dash for layer and stack APIs.

Verified: standalone tp-surface load without Dash, 718 ERT tests, WERROR compile-all, and git diff --check.
2026-08-06 03:58:39 +08:00
Kinneyzhang
e3d7d1dfc6 feat(tp): support disjoint logical object mounts
Allow retained logical objects to own multiple marker-backed output fragments without leaking handles or positions into pure plans. Expose only defensive numeric mount snapshots through the public object index.

Verified: 718 ERT tests, 92 doctests, WERROR compile-all, checkdoc, and git diff --check.
2026-08-06 03:50:59 +08:00
Kinneyzhang
8d108403ad feat(tp): add rollback-capable transaction participants
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.
2026-08-06 03:40:40 +08:00
Kinneyzhang
905d3523ac feat(tp): add unified convenience APIs
Expose one-shot string and buffer styling plus reactive range watches over the same schema, mutation, binding, and retained surface core.

Verification: 712 ERT tests; 92 doctests; shuffled seed 20260806; compile-all WERROR=t.
2026-08-06 03:31:50 +08:00
Kinneyzhang
84ebf0a548 feat(tp): add retained surfaces and atomic publication
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.
2026-08-06 03:23:59 +08:00
Kinneyzhang
183f49ed3e feat(tp): replace scan reactivity with binding graph
Introduce exact signal and binding dependencies, transactional scheduling, rollback, cycle detection, scoped variable adapters, owner disposal, and scheduler counters. Keep the legacy scanner isolated until retained surfaces complete the cutover.\n\nVerified: 681 ERT tests, 92 doctests, shuffled ERT seed 20260806, and WERROR byte compilation.
2026-08-06 02:36:11 +08:00
Kinneyzhang
ef31a67beb feat(tp): add schema-driven cascade engine
Introduce the pure TP 1.0 property schema, selector, cascade, custom-property, computed-value, provenance, and Emacs projection kernel. Compile static legacy layer definitions into canonical named styles without freezing parameterized or reactive definitions.\n\nVerified with 667 ERT tests, 92 doctests, shuffled seed 20260806, and compile-all with warnings as errors.
2026-08-06 02:12:32 +08:00
Kinneyzhang
ec30126b2d docs(architecture): make TP runtime target standalone
Add self-contained Chinese and English TP 1.0 contracts for objects, bindings, anchors, computed values, transactions, and public API levels without requiring a sibling Ebox checkout.

Verification: make doctest EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs LOAD_EXTRA=-L\\ /Users/geekinney/IPARA/3-RESOURCES/emacs/config/github/test/libs; git diff --check; local Markdown link check.
2026-08-06 01:33:08 +08:00
Kinneyzhang
489a1cff33 docs(architecture): link retained runtime target
Make the TP current-architecture document point to the approved TP/Ebox target and refactor plan while clearly retaining TP 0.3.x as the current implementation.\n\nVerified: relative documentation links resolve\nVerified: git diff --cached --check
2026-08-06 01:06:48 +08:00
Kinneyzhang
6c5c08b0fe Organize tests and document GPLv3 licensing 2026-07-28 23:08:25 +08:00
Kinneyzhang
972b6d4e4c Complete text-property facade and managed lifecycle
Add canonical query semantics, managed metadata and transactions, overlay-aware lookup, reproducible benchmarks, and synchronized API documentation.
2026-07-28 22:42:55 +08:00
Kinneyzhang
a65d79921a Merge dev/0.3.0: CI matrix, feature tracks, architecture review fixes
0.3.0: GitHub Actions CI (Emacs 28.1/29.4/30.1, warnings-as-errors,
shuffled-order rerun, doctests); layer visibility and stack
introspection; reactive layer-buffer registry, minimal-diff tp-text,
anonymous-layer GC; search capture groups, bounds, predicates;
multi-argument parameterized layers; a five-dimension adversarial
architecture/API review whose 29-item fix plan is fully applied
(six confirmed seam bugs fixed, module boundaries tightened to two
hook variables, additive API polish, deprecations); bilingual docs
with 88 executable doctests. Suite 578/578 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 03:44:45 +08:00
Kinneyzhang
fe2c03d2bd Document 0.3.0 across bilingual READMEs, doctests, and ARCHITECTURE.md
Some checks are pending
CI / test (28.1) (push) Waiting to run
CI / test (29.4) (push) Waiting to run
CI / test (30.1) (push) Waiting to run
README.md/README_CN.md gain verified sections for every 0.3.0 feature:
layer visibility (hide/show model, flatten/merge exclusion), lower and
canonical rotate orders, stack introspection (tp-layer-stack-at,
tp-describe-layer), the reactive layer-buffer registry and lifecycle
(track-buffer, anonymous-layer GC, minimal-diff tp-text), search
bounds/SUBEXP/PREDICATE, multi-argument parameterized layers, ABSOLUTE
interval coordinates, palette primaries, prefix-conforming define
aliases, and deprecation boxes for the raw search wrappers. Return
conventions corrected against probed behavior (add-to-layers mutates
in place; tp-clear now explicitly returns nil in code and docs), the
in-place string-mutation warning added for all 16 stack mutators, and
the stale pre-B43 with-range example fixed in both languages.
EN/CN parity is exact (132 headings, 158 fenced blocks each); all 316
blocks executed with 0 failures. tp-doctest.el grows 63 -> 88
assertions, all accessor-based for Emacs-28 print-order safety.
docs/ARCHITECTURE.md rewritten to the post-review truth: two hook
variables, verified per-module dependency table, relocated subsystem
homes, and a new mutable-state inventory.

Suite 578/578, doctests 88/88, shuffled green, compile clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 03:44:45 +08:00
Kinneyzhang
aa214456df Complete the 0.3.0 changelog: features, review fixes, boundary moves
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 02:41:23 +08:00
Kinneyzhang
363045ac7c Enumerate the five real tp-set calling conventions
DOC-STR-01: tp-set's docstring claimed "four calling conventions"
and listed three; tp--parse-args actually implements five.  The
block now enumerates all of them - (START END PROPS), (START END
PROPS OBJECT), (STRING PROP VAL ...), (STRING LAYER-NAME [ARG]) and
(STRING LAYER-NAME ARG PROP VAL ...) - and the same enumeration is
repeated in tp-reset and tp-add, which previously inherited the
conventions only implicitly via "Like tp-set".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 02:37:16 +08:00
Kinneyzhang
698dc54b1c Expose the palette primaries and finish the builtin docstrings
API-CONC-01: tp-palette-color (SYMBOL KEY) is now the public generic
accessor - a documented wrapper over the existing private getter -
and tp-palette-has-p (SYMBOL &optional KIND) the consolidated
predicate: nil KIND tests registration, :fg/:bg/:border test that
the palette's definition contains the key.  All ten existing query
functions stay as compatible conveniences, now each carrying a
docstring that says which question it answers (the suffix predicates
test NAME-fg style variant names, not palette contents) and
cross-referencing the primaries.

DOC-STR-02: tp-palette-show (interactive, autoloaded) finally has a
docstring describing the gallery it renders.  tp-suffix-symbol - a
generic two-line intern helper with no tp semantics - is renamed to
tp--suffix-symbol; the old name survives as a compatibility
define-obsolete-function-alias (0.3.0) and tp-palette-show now
calls the private name, leaving the alias without in-tree callers.
tp-theme-dark-p / tp-theme-light-p get their missing docstrings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 02:36:19 +08:00
Kinneyzhang
8c28361640 Add prefix-conforming define aliases and export the group props plural
API-NAME-02: tp-define-layer, tp-define-group and tp-define-palette
are new permanent aliases of define-tp, define-tps and
define-tp-palette, each with a real docstring and the defun indent
spec, so C-h f tp-TAB finds the library's definition macros and
package-prefix review passes.  The historical names stay forever;
zero callers change.

API-SYM-01: tp-group-props-with-args is now public, delegating to
the private implementation, mirroring tp-layer-props-with-args -
multi-argument parameterized groups are a public feature (reachable
via tp-put-layer specs) and previously had no public introspection
path.  All four -with-arg/-with-args docstrings now cross-reference
their singular/plural sibling (calling out the one-character name
difference) and their layer/group counterpart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 02:34:31 +08:00
Kinneyzhang
f6032a891e Accept the family-consistent rotate order; document stack string mutation
API-ARG-01: tp-rotate-layer's region form now also accepts the
canonical (START END DIRECTION &optional COUNT OBJECT) order, with
OBJECT last like every sibling.  Dispatch is unambiguous: the
symbols up/down can never be a valid OBJECT, so a third argument of
up/down selects the canonical order and anything else (nil, buffer,
string) keeps the legacy (START END OBJECT DIRECTION COUNT) order
bit-identically.  (tp-rotate-layer 1 5 'up) now works without a nil
placeholder.  The docstring lists the canonical order first and the
legacy order as a permanent compatibility form.

API-MUT-01: every string-form stack mutator (all 16: put/push/pop/
delete/move/raise/lower/rotate/pin/switch/hide/show/merge/flatten/
add-to-layers/add-to-all-layers) now carries an explicit warning
block: the string form modifies STRING destructively in place,
unlike tp-set's copy semantics - never pass literals or shared
strings.  Semantics unification is deferred to 0.4; no behavior
changes.

API-NAME-03: tp-pin-layer's summary now says what it does - a
one-shot move to the top of the stack; nothing stays pinned, and
later pushes cover it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 02:31:45 +08:00
Kinneyzhang
b4dc8abc8b Add ABSOLUTE coordinates to tp-intervals and tp-intervals-map
API-COORD-01: both functions take a new trailing optional ABSOLUTE
argument.  When non-nil, buffer results use native 1-based buffer
positions - directly reusable in tp-set/tp-remove calls - instead of
the legacy START-relative 0-based offsets, which stay the default
this cycle (the default flip is deferred to 0.4).  String results
were always absolute and are unchanged.

DOC-STR-02: tp-intervals-map's one-line docstring is rewritten to
its real contract: the tp-layers layer-stack split handed to
FUNCTION (TOP-PROPS vs BELOW-PROPS-LST), the coordinate system
including ABSOLUTE, and the return value (FUNCTION's non-nil
results in interval order).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 02:27:53 +08:00
Kinneyzhang
985b51d2c6 Deprecate the raw search wrappers and state the nav contracts
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>
2026-07-27 02:26:36 +08:00
Kinneyzhang
575c99be88 Normalize reversed bounds and validate SUBEXP in the pattern engine
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>
2026-07-27 02:24:11 +08:00
Kinneyzhang
e870b87941 Move the anonymous-layer counter and name generator into tp-layer
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>
2026-07-27 02:09:58 +08:00
Kinneyzhang
95fb4ae862 Delete the dead layer-expansion pair in tp-layer
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>
2026-07-27 02:08:41 +08:00
Kinneyzhang
99bd6f7c15 Drop tp-stack's phantom dependency on tp-ops
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>
2026-07-27 02:07:43 +08:00
Kinneyzhang
6295d5ccbc Move the batch flush and tp-with-batch-updates up into tp-render
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>
2026-07-27 02:06:28 +08:00
Kinneyzhang
2b000b5f0e Move the tp-text handler chain from tp-render into tp-ops
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>
2026-07-27 02:04:40 +08:00
Kinneyzhang
8f50231695 Clear the pending queue on reset and drain it under unwind-protect
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>
2026-07-27 01:56:59 +08:00
Kinneyzhang
a0d0e71ff8 Insert before deleting in minimal-diff tp-text edits so markers track
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>
2026-07-27 01:54:43 +08:00
Kinneyzhang
117c714b97 Signal clear arity errors for wrong-arity parameterized-layer calls
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>
2026-07-27 01:49:47 +08:00
Kinneyzhang
e15a18d718 Flatten/merge no longer render hidden layers; both return run counts
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>
2026-07-27 01:47:03 +08:00
Kinneyzhang
b49e2740e8 Make GC liveness and track-buffer scans stack-aware; GC moves to tp-layer
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>
2026-07-27 01:44:06 +08:00