Compare commits

...

23 Commits
v1.0.0 ... main

Author SHA1 Message Date
Kinneyzhang
5785405495 merge: reconcile the preserved C1b baseline with current main
Some checks are pending
CI / elisp-only (Emacs ${{ matrix.emacs-version }}) (29.1) (push) Waiting to run
CI / elisp-only (Emacs ${{ matrix.emacs-version }}) (30.1) (push) Waiting to run
CI / elisp-only (Emacs ${{ matrix.emacs-version }}) (snapshot) (push) Waiting to run
CI / package-lint + checkdoc (push) Waiting to run
CI / full suite + C parity (apt Emacs) (push) Waiting to run
CI / C module under ASan/UBSan (push) Waiting to run
CI / macOS (dylib build + full suite) (push) Waiting to run
CI / Windows Elisp baseline (Emacs 30.1) (push) Waiting to run
2026-09-08 21:38:16 +08:00
Kinneyzhang
711b0b1abe fix: preserve property defaults and Emacs 31 compatibility 2026-09-08 21:33:14 +08:00
Kinneyzhang
a3d20aa87e chore: freeze verified ekp baseline before C1b 2026-09-05 05:07:38 +08:00
Kinneyzhang
97cb6a6e35 docs: close source live append performance gate 2026-08-20 09:44:18 +08:00
Kinneyzhang
d216c6b761 docs: record native live append gate 2026-08-20 09:35:52 +08:00
Kinneyzhang
3d3dda6688 perf: add native automatic live append backend 2026-08-20 09:35:35 +08:00
Kinneyzhang
cfa7c18beb docs: record rejected strict DP experiment 2026-08-20 08:09:22 +08:00
Kinneyzhang
5f78cae0b1 docs: close append attribution record 2026-08-20 08:00:23 +08:00
Kinneyzhang
01001b9008 perf: split append attribution 2026-08-20 07:58:44 +08:00
Kinneyzhang
5efd4d113b docs: close performance attribution record 2026-08-20 07:55:44 +08:00
Kinneyzhang
db6e6b98ee perf: attribute source live append costs 2026-08-20 07:53:28 +08:00
Kinneyzhang
19b8c9050e docs: add source-fresh performance postmortem 2026-08-20 07:44:36 +08:00
Kinneyzhang
60b299b3f4 docs: record source-fresh performance boundary 2026-08-20 07:44:25 +08:00
Kinneyzhang
c47abf7529 docs: refresh hardening verification counts 2026-08-20 07:41:39 +08:00
Kinneyzhang
dbb3809d7c test: name backward live regression accurately 2026-08-20 07:37:24 +08:00
Kinneyzhang
c93786570b docs: record post-audit verification commit 2026-08-20 07:34:30 +08:00
Kinneyzhang
6a8c7e04f5 fix: harden post-audit contracts and test gates 2026-08-20 07:34:03 +08:00
Kinneyzhang
183f256020 docs: close confirmed repository issues 2026-08-19 20:17:54 +08:00
Kinneyzhang
888a4019f4 update ekp-showcase demo 2026-08-08 09:31:08 +08:00
Kinneyzhang
e823d89a4a fix: restore TeX final-pass reachability 2026-08-02 15:30:12 +08:00
Kinneyzhang
665164911f perf: make live structural commits incremental
Reuse prepared paragraph data, safe Elisp DP states, common layout lines, and dirty-island source reconstruction for exact append commits. Simplify C int32 validation and add the frozen task030 evaluator, regressions, and decision records.
2026-07-30 08:24:54 +08:00
Kinneyzhang
83352c4571 feat!: add source-clean live buffer layout
Replace the physical ekp-region renderer with ekp-buffer text-property projection backed by shared semantic layout plans and stable live-edit transactions.

Optimize the C-backed resize hot path, and add deterministic performance, ERT, GUI, fuzz, release, and documentation coverage for task017 through task032.

BREAKING CHANGE: require ekp-buffer instead of ekp-region and rename module-owned ekp-region-* settings to ekp-buffer-*.
2026-07-30 01:07:25 +08:00
Kinneyzhang
fd1210e557 fix: close repository audit findings
Complete task002 through task016 across cache correctness, editor lifecycle, C boundaries, build and release governance, dictionary provenance, performance, interactive workflows, tests, documentation, and final cleanup.
2026-07-28 22:43:10 +08:00
165 changed files with 26484 additions and 2745 deletions

View File

@ -20,57 +20,71 @@ jobs:
# stable; snapshot is advisory (allowed to fail)
emacs-version: ['29.1', '30.1', 'snapshot']
steps:
- uses: actions/checkout@v4
- uses: purcell/setup-emacs@master
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- uses: purcell/setup-emacs@bdc64dc730ae1fcba200bfd52cb1b4cf6159cbe5 # v8.0
with:
version: ${{ matrix.emacs-version }}
- name: Byte-compile (warnings are errors)
run: |
emacs -Q --batch -L . \
--eval '(setq byte-compile-error-on-warn t)' \
-f batch-byte-compile ekp.el ekp-utils.el ekp-hyphen.el ekp-region.el
-f batch-byte-compile ekp.el ekp-utils.el ekp-hyphen.el ekp-buffer.el
- name: Run ERT suite (C-module tests auto-skip)
run: tests/run-tests.sh emacs
- name: Run ERT suite in permuted order
if: matrix.emacs-version == '30.1'
env:
EKP_TEST_SEED: '20260728'
run: tests/run-tests.sh emacs --random-order
lint:
name: package-lint + checkdoc
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: purcell/setup-emacs@master
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- uses: purcell/setup-emacs@bdc64dc730ae1fcba200bfd52cb1b4cf6159cbe5 # v8.0
with:
version: '30.1'
- name: Install package-lint
run: |
emacs -Q --batch \
--eval "(require 'package)" \
--eval "(add-to-list 'package-archives '(\"melpa\" . \"https://melpa.org/packages/\") t)" \
--eval "(package-initialize)" \
--eval "(package-refresh-contents)" \
--eval "(package-install 'package-lint)"
- name: Check out pinned package-lint
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
repository: purcell/package-lint
ref: 35996f478d81e51dae4fa30d051f741895d07399
path: .ci/package-lint
persist-credentials: false
- name: package-lint
run: |
emacs -Q --batch \
--eval "(require 'package)" \
--eval "(package-initialize)" \
emacs -Q --batch -L .ci/package-lint \
--eval "(require 'package-lint)" \
--eval "(setq package-lint-main-file \"ekp.el\")" \
-f package-lint-batch-and-exit \
ekp.el ekp-utils.el ekp-hyphen.el ekp-region.el
ekp.el ekp-utils.el ekp-hyphen.el ekp-buffer.el
- name: checkdoc
run: |
emacs -Q --batch \
--eval "(setq sentence-end-double-space t)" \
--eval "(dolist (f '(\"ekp.el\" \"ekp-utils.el\" \"ekp-hyphen.el\" \"ekp-region.el\"))
--eval "(dolist (f '(\"ekp.el\" \"ekp-utils.el\" \"ekp-hyphen.el\" \"ekp-buffer.el\"))
(checkdoc-file f))" \
2>&1 | tee checkdoc.log
test ! -s checkdoc.log
- name: Release invariants
run: tests/check-release.sh
- name: Dictionary manifest and pinned source
run: |
tests/check-dictionaries.sh
dictionaries/update.sh check
test-c-module:
name: full suite + C parity (apt Emacs)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- name: Install Emacs
run: sudo apt-get update && sudo apt-get install -y emacs-nox
- name: Ensure emacs-module.h is findable
@ -92,7 +106,9 @@ jobs:
name: C module under ASan/UBSan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- name: Install Emacs
run: sudo apt-get update && sudo apt-get install -y emacs-nox gcc
- name: Ensure emacs-module.h is findable
@ -104,7 +120,7 @@ jobs:
sudo install -m 644 "$HDR" /usr/local/include/
fi
- name: Build C module with sanitizers
run: make -C ekp_c DEBUG=1
run: make -C ekp_c PROFILE=sanitize
- name: Property fuzz under ASan/UBSan
# Emacs itself is not ASan-instrumented: preload the runtime and
# skip leak checking (the Emacs process "leaks" by design).
@ -117,8 +133,10 @@ jobs:
name: macOS (dylib build + full suite)
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: purcell/setup-emacs@master
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- uses: purcell/setup-emacs@bdc64dc730ae1fcba200bfd52cb1b4cf6159cbe5 # v8.0
with:
version: '30.1'
- name: Build C module
@ -127,3 +145,27 @@ jobs:
run: tests/run-tests.sh emacs
- name: Property fuzz (300 cases, C vs elisp parity)
run: emacs -Q --batch -L . -l tests/ekp-fuzz.el
test-windows:
name: Windows Elisp baseline (Emacs 30.1)
runs-on: windows-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- name: Install Emacs 30.1
shell: pwsh
run: choco install emacs --version=30.1.0 --yes --no-progress
- name: Byte-compile (warnings are errors)
shell: pwsh
run: >
emacs -Q --batch -L .
--eval "(setq byte-compile-error-on-warn t)"
-f batch-byte-compile
ekp.el ekp-utils.el ekp-hyphen.el ekp-buffer.el
- name: Run ERT suite (C-module tests auto-skip)
shell: pwsh
run: >
emacs -Q --batch -L . -L tests
--eval "(load-file \"tests/load-project-source.el\")"
-f ert-run-tests-batch-and-exit

2
.gitignore vendored
View File

@ -4,5 +4,5 @@ archive
*.dll
*.o
*.elc
.phrase/
AGENTS.md
!AGENTS.md

122
.phrase/docs/CHANGE.md Normal file
View File

@ -0,0 +1,122 @@
# Change Log Index
## phase-kp-overhaul-20260726
- 2026-09-01 restore Emacs 31.1 strict byte compilation without behavior
changes (`task044`, `issue029`):
`.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md`
- 2026-08-31 restore the Emacs 31 fresh-source baseline while preserving
multibyte policy ownership and real-buffer marker noninheritance
(`task043`):
`.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md`
- 2026-08-20 plan and implement post-audit hardening (`task037``task042`,
`task030` source-fresh evaluator continuation; `issue022``issue028`):
`.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md`
- 2026-08-20 select and implement native automatic live-append backend
(`task030`; `ekp-auto-justify-native-append`):
`.phrase/phases/phase-kp-overhaul-20260726/adr_native_live_append_backend_20260820.md`
- 2026-08-20 close the complete source-fresh live-append performance gate
(`task030`, `issue018`):
`.phrase/phases/phase-kp-overhaul-20260726/change_post_audit_hardening_20260820.md`
- 2026-08-13 close issues awaiting user-visible confirmation (`issue011`
`issue017`, `issue019`, `issue020`; `issue018`/`task030` remain open):
`.phrase/phases/phase-kp-overhaul-20260726/change_close_user_confirmation_20260813.md`
- 2026-08-02 complete fixed final-pass emergency stretch and TeX-style
active-path preservation (`task036`; `issue021` closed after user visual
confirmation):
`.phrase/phases/phase-kp-overhaul-20260726/change_final_pass_emergency_stretch_20260802.md`
- 2026-07-28 repository system audit:
`.phrase/phases/phase-kp-overhaul-20260726/change_repository_audit_20260728.md`
- 2026-07-28 complete DP cache signature (`task002`):
`.phrase/phases/phase-kp-overhaul-20260726/change_cache_signature_20260728.md`
- 2026-07-28 complete automatic spacing cache identity (`task003`):
`.phrase/phases/phase-kp-overhaul-20260726/change_auto_spacing_signature_20260728.md`
- 2026-07-28 make saving non-mutating and failure-safe (`task004`):
`.phrase/phases/phase-kp-overhaul-20260726/change_save_transaction_20260728.md`
- 2026-07-28 compose and lifecycle-own buffer integrations (`task005`):
`.phrase/phases/phase-kp-overhaul-20260726/change_integration_lifecycle_20260728.md`
- 2026-07-28 eliminate ERT order-dependent false greens (`task006`):
`.phrase/phases/phase-kp-overhaul-20260726/change_test_isolation_20260728.md`
- 2026-07-28 make GUI verification fail closed (`task007`):
`.phrase/phases/phase-kp-overhaul-20260726/change_gui_verification_20260728.md`
- 2026-07-28 close the C API and arithmetic contract (`task008`):
`.phrase/phases/phase-kp-overhaul-20260726/change_c_api_contract_20260728.md`
- 2026-07-28 make C builds shell-free and profiled (`task009`):
`.phrase/phases/phase-kp-overhaul-20260726/change_c_build_boundary_20260728.md`
- 2026-07-28 close release and CI governance (`task010`):
`.phrase/phases/phase-kp-overhaul-20260726/change_release_governance_20260728.md`
- 2026-07-28 pin dictionary bytes and fail closed on unsupported syntax
(`task011`):
`.phrase/phases/phase-kp-overhaul-20260726/change_dictionary_governance_20260728.md`
- 2026-07-28 make measured text builders linear and cache nil (`task012`):
`.phrase/phases/phase-kp-overhaul-20260726/change_hot_loop_performance_20260728.md`
- 2026-07-28 clarify interactive protection workflows (`task013`):
`.phrase/phases/phase-kp-overhaul-20260726/change_interactive_protection_20260728.md`
- 2026-07-28 centralize duplicated core layout rules (`task014`):
`.phrase/phases/phase-kp-overhaul-20260726/change_core_rule_ownership_20260728.md`
- 2026-07-28 remove residual slop and pass the independent final gate
(`task015`):
`.phrase/phases/phase-kp-overhaul-20260726/change_final_cleanup_review_20260728.md`
- 2026-07-28 make the same-string paragraph fast path property-sensitive
(`task016`):
`.phrase/phases/phase-kp-overhaul-20260726/change_property_sensitive_fast_path_20260728.md`
- 2026-07-28 rename editor integration around its buffer owner (`task017`):
`.phrase/phases/phase-kp-overhaul-20260726/change_buffer_module_naming_20260728.md`
- 2026-07-28 record the logical-text API boundary (`issue011`, no
implementation):
`.phrase/phases/phase-kp-overhaul-20260726/change_logical_text_api_issue_20260728.md`
- 2026-07-29 record the text-property-only layout and non-polluting hyphen
feasibility (`task018`, `issue011` remains open):
`.phrase/phases/phase-kp-overhaul-20260726/change_text_property_layout_research_20260729.md`
- 2026-07-29 implement and close the shared KP layout plan, source-clean
static projection, seamless live flow, and repository quality gate
(`task019``task022`; `issue011` awaits user-visible acceptance):
`.phrase/phases/phase-kp-overhaul-20260726/change_text_property_layout_implementation_20260729.md`
- 2026-07-29 fix and verify inactive-mark selection and same-turn
edge-whitespace visibility (`task023``task024`; `issue012` and
`issue013` await user-visible acceptance):
`.phrase/phases/phase-kp-overhaul-20260726/change_text_property_layout_implementation_20260729.md`
- 2026-07-29 replace partial live KP with the approved native progressive
editing model (`task025``task026` complete; `issue014` awaits
user-visible confirmation):
`.phrase/phases/phase-kp-overhaul-20260726/change_text_property_layout_implementation_20260729.md`
- 2026-07-29 implement and verify the native soft-wrap lifecycle for narrow
split windows (`task027` complete; `issue015` awaits user-visible
confirmation):
`.phrase/phases/phase-kp-overhaul-20260726/change_text_property_layout_implementation_20260729.md`
- 2026-07-29 implement and developer-verify semantic hard-line prefix live
editing (`task028`; `issue016` awaits user-visible confirmation):
`.phrase/phases/phase-kp-overhaul-20260726/change_semantic_live_prefix_20260729.md`
- 2026-07-29 make point-only motion projection-stable and move the live
boundary to a source-edit-owned frontier (`task029` complete;
`issue016` and `issue017` await user-visible confirmation; separate
narrow-width append debt is `issue018`/`task030`):
`.phrase/phases/phase-kp-overhaul-20260726/change_editing_frontier_stability_20260729.md`
- 2026-07-29 implement and developer-verify the stable live-layout
transaction that supersedes per-keystroke frontier replanning
(`task031` complete; `issue019` awaits user confirmation; `task030`
is unblocked for structural-commit profiling):
`.phrase/phases/phase-kp-overhaul-20260726/change_stable_live_transaction_20260729.md`
- 2026-07-30 reduce core and complete C-backed resize p95 to
27.687/27.487 ms with exact layout parity (`task032` complete;
`issue020` awaits user confirmation):
`.phrase/phases/phase-kp-overhaul-20260726/change_c_resize_latency_20260729.md`
- 2026-07-30 implement exact incremental live-append preparation, DP, and
projection reuse (`task030` implementation verified; locked
source-instrumented 16 ms gate and `issue018` remain open):
`.phrase/phases/phase-kp-overhaul-20260726/change_narrow_live_append_latency_20260730.md`
- 2026-08-01 implement and developer-verify the rigid-inline-atom
emergency-break correction (`task033` complete; `issue021` awaits user
confirmation):
`.phrase/phases/phase-kp-overhaul-20260726/change_overlong_no_break_atom_20260801.md`
- 2026-08-01 implement configurable break policies, record G002-G008
implementation/GUI/performance/repository/cleanup/review-blocker evidence,
and close developer work for `task034` (`issue021` still awaits user visual
confirmation; final independent code review APPROVE and architecture CLEAR):
`.phrase/phases/phase-kp-overhaul-20260726/change_configurable_break_policies_20260801.md`
- 2026-08-02 record the failed line-width emergency-stretch attempt
(`task035` closed as falsified; superseded by completed `task036` above):
`.phrase/phases/phase-kp-overhaul-20260726/change_final_pass_emergency_stretch_20260802.md`
## phase-doc-improvement-20260125
- See `.phrase/phases/phase-doc-improvement-20260125/change_log.md`

31
.phrase/docs/ISSUES.md Normal file
View File

@ -0,0 +1,31 @@
# Known Issues
- issue001 [x] [Incomplete cache signatures reuse stale layout results.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue002 [x] [Failed saves leave a justified buffer unformatted.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue003 [x] [Buffer integration is not composable or lifecycle-owned.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue004 [x] [Test order dependency produces a false green suite.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue005 [x] [C API and build boundary need explicit validation.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue006 [x] [Dictionary claims exceed parser and provenance coverage.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue007 [x] [GUI verification reports failure without failing automation.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue008 [x] [Hot-loop allocation and nil caching need benchmark gates.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue009 [x] [Release and documentation governance are not closed.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue010 [x] [Same-string property mutation bypasses paragraph cache identity.](../phases/phase-kp-overhaul-20260726/issue_repository_audit_20260728.md)
- issue011 [x] [Direct Elisp buffer APIs observe physical layout text.](../phases/phase-kp-overhaul-20260726/issue_logical_text_api_20260728.md)
- issue012 [x] [Reprojection activates an inactive mark and selects text.](../phases/phase-kp-overhaul-20260726/issue_live_editing_interaction_20260729.md#issue012--reprojection-activates-an-inactive-mark)
- issue013 [x] [Active-line edge whitespace is hidden until another glyph arrives.](../phases/phase-kp-overhaul-20260726/issue_live_editing_interaction_20260729.md#issue013--active-line-edge-whitespace-is-hidden)
- issue014 [x] [Near-edge live editing publishes KP breaks before the paragraph is complete.](../phases/phase-kp-overhaul-20260726/issue_natural_live_editing_20260729.md)
- issue015 [x] [Narrow side-by-side windows truncate instead of soft-wrapping live text.](../phases/phase-kp-overhaul-20260726/issue_native_soft_wrap_20260729.md)
- issue016 [x] [Live editing freezes native rows instead of projecting a semantic hard-line prefix.](../phases/phase-kp-overhaul-20260726/issue_semantic_live_prefix_20260729.md)
- issue017 [x] [Point-only motion changes an already published live projection.](../phases/phase-kp-overhaul-20260726/issue_point_motion_projection_stability_20260729.md)
- issue018 [ ] [Unique live appends miss the frame budget at very narrow widths.](../phases/phase-kp-overhaul-20260726/issue_narrow_live_append_latency_20260729.md)
- issue019 [x] [Per-edit frontier replanning destabilizes live projection and cannot restore reversible edits exactly.](../phases/phase-kp-overhaul-20260726/issue_stable_live_edit_transaction_20260729.md)
- issue020 [x] [C-backed resize reflow still takes roughly 6070 ms.](../phases/phase-kp-overhaul-20260726/issue_c_resize_latency_20260729.md)
- issue021 [x] [Final-pass emergency layout can isolate CJK source lines at narrow widths.](../phases/phase-kp-overhaul-20260726/issue_overlong_no_break_atom_20260801.md)
- issue022 [ ] [Invalid buffer widths mutate or create invalid projection state.](../phases/phase-kp-overhaul-20260726/issue_buffer_width_atomicity_20260820.md)
- issue023 [ ] [Backward deletion can republish a stable live projection.](../phases/phase-kp-overhaul-20260726/issue_live_backward_projection_20260820.md)
- issue024 [ ] [Hyphenation locale spelling can select the wrong dictionary.](../phases/phase-kp-overhaul-20260726/issue_hyphen_locale_20260820.md)
- issue025 [x] [C direct API accepts invalid break positions.](../phases/phase-kp-overhaul-20260726/issue_c_position_validation_20260820.md)
- issue026 [ ] [Showcase loses automatic inline policy and test runners can false-green.](../phases/phase-kp-overhaul-20260726/issue_showcase_test_freshness_20260820.md)
- issue027 [x] [Manual integrations remain installed with no projection owner.](../phases/phase-kp-overhaul-20260726/issue_integration_no_projection_20260820.md)
- issue028 [x] [Locked live evaluator can measure stale candidate bytecode.](../phases/phase-kp-overhaul-20260726/issue_live_evaluator_source_freshness_20260820.md)
- issue029 [x] [Emacs 31.1 WERROR rejects obsolete single-binding let macros.](../phases/phase-kp-overhaul-20260726/issue_emacs31_strict_compile_20260901.md)

View File

@ -0,0 +1,150 @@
---
name: agent-browser
description: Automates browser interactions for web testing, form filling, screenshots, and data extraction using the 'agent-browser' CLI tool.
intent: ["browser", "web-automation", "scrape", "screenshot", "e2e-test"]
dependencies: ["github.com/vercel-labs/agent-browser"]
version: 1.0.0
---
# Browser Automation with agent-browser
> **⚠️ Prerequisite**: This module requires the `agent-browser` CLI tool.
> Ensure it is installed in your environment before using:
> `npm install -g @vercel/agent-browser` (or equivalent)
## Quick start
```bash
agent-browser open <url> # Navigate to page
agent-browser snapshot -i # Get interactive elements with refs
agent-browser click @e1 # Click element by ref
agent-browser fill @e2 "text" # Fill input by ref
agent-browser close # Close browser
```
## Core workflow
1. Navigate: `agent-browser open <url>`
2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`)
3. Interact using refs from the snapshot
4. Re-snapshot after navigation or significant DOM changes
## Commands
### Navigation
```bash
agent-browser open <url> # Navigate to URL
agent-browser back # Go back
agent-browser forward # Go forward
agent-browser reload # Reload page
agent-browser close # Close browser
```
### Snapshot (page analysis)
```bash
agent-browser snapshot # Full accessibility tree
agent-browser snapshot -i # Interactive elements only (recommended)
agent-browser snapshot -c # Compact output
agent-browser snapshot -d 3 # Limit depth to 3
```
### Interactions (use @refs from snapshot)
```bash
agent-browser click @e1 # Click
agent-browser dblclick @e1 # Double-click
agent-browser fill @e2 "text" # Clear and type
agent-browser type @e2 "text" # Type without clearing
agent-browser press Enter # Press key
agent-browser press Control+a # Key combination
agent-browser hover @e1 # Hover
agent-browser check @e1 # Check checkbox
agent-browser uncheck @e1 # Uncheck checkbox
agent-browser select @e1 "value" # Select dropdown
agent-browser scroll down 500 # Scroll page
agent-browser scrollintoview @e1 # Scroll element into view
```
### Get information
```bash
agent-browser get text @e1 # Get element text
agent-browser get value @e1 # Get input value
agent-browser get title # Get page title
agent-browser get url # Get current URL
```
### Screenshots
```bash
agent-browser screenshot # Screenshot to stdout
agent-browser screenshot path.png # Save to file
agent-browser screenshot --full # Full page
```
### Wait
```bash
agent-browser wait @e1 # Wait for element
agent-browser wait 2000 # Wait milliseconds
agent-browser wait --text "Success" # Wait for text
agent-browser wait --load networkidle # Wait for network idle
```
### Semantic locators (alternative to refs)
```bash
agent-browser find role button click --name "Submit"
agent-browser find text "Sign In" click
agent-browser find label "Email" fill "user@test.com"
```
## Example: Form submission
```bash
agent-browser open https://example.com/form
agent-browser snapshot -i
# Output shows: textbox "Email" [ref=e1], textbox "Password" [ref=e2], button "Submit" [ref=e3]
agent-browser fill @e1 "user@example.com"
agent-browser fill @e2 "password123"
agent-browser click @e3
agent-browser wait --load networkidle
agent-browser snapshot -i # Check result
```
## Example: Authentication with saved state
```bash
# Login once
agent-browser open https://app.example.com/login
agent-browser snapshot -i
agent-browser fill @e1 "username"
agent-browser fill @e2 "password"
agent-browser click @e3
agent-browser wait --url "**/dashboard"
agent-browser state save auth.json
# Later sessions: load saved state
agent-browser state load auth.json
agent-browser open https://app.example.com/dashboard
```
## Sessions (parallel browsers)
```bash
agent-browser --session test1 open site-a.com
agent-browser --session test2 open site-b.com
agent-browser session list
```
## JSON output (for parsing)
Add `--json` for machine-readable output:
```bash
agent-browser snapshot -i --json
agent-browser get text @e1 --json
```
## Debugging
```bash
agent-browser open example.com --headed # Show browser window
agent-browser console # View console messages
agent-browser errors # View page errors
```

View File

@ -0,0 +1,51 @@
---
name: code-simplifier
description: Simplifies and refines code for clarity, consistency, and maintainability while preserving all functionality. Focuses on recently modified code unless instructed otherwise.
---
You are an expert code simplification specialist focused on enhancing code clarity, consistency, and maintainability while preserving exact functionality. Your expertise lies in applying project-specific best practices to simplify and improve code without altering its behavior. You prioritize readable, explicit code over overly compact solutions. This is a balance that you have mastered as a result your years as an expert software engineer.
You will analyze recently modified code and apply refinements that:
1. **Preserve Functionality**: Never change what the code does - only how it does it. All original features, outputs, and behaviors must remain intact.
2. **Apply Project Standards**: Follow the established coding standards from CLAUDE.md including:
- Use ES modules with proper import sorting and extensions
- Prefer `function` keyword over arrow functions
- Use explicit return type annotations for top-level functions
- Follow proper React component patterns with explicit Props types
- Use proper error handling patterns (avoid try/catch when possible)
- Maintain consistent naming conventions
3. **Enhance Clarity**: Simplify code structure by:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code
4. **Maintain Balance**: Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
Your refinement process:
1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
3. Apply project-specific best practices and coding standards
4. Ensure all functionality remains unchanged
5. Verify the refined code is simpler and more maintainable
6. Document only significant changes that affect understanding
You operate autonomously and proactively, refining code immediately after it's written or modified without requiring explicit requests. Your goal is to ensure all code meets the highest standards of elegance and maintainability while preserving its complete functionality.

View File

@ -0,0 +1,59 @@
---
name: conversion_copywriting
description: "Expert Copywriter persona for creating high-conversion product copy, READMEs, release notes, and marketing materials. Focuses on user benefits and cost reduction."
intent: ["copywriting", "marketing", "readme", "docs", "release-notes"]
version: 1.0.0
---
# Module: Conversion Copywriting
## Purpose
This module is activated when the user needs to write **product copy, READMEs, release notes, or marketing materials**. Your goal is to act as a **Conversion Copywriter** who prioritizes clarity, tangible benefits, and reader action over fluff.
## Core Principles (The 10 Commandments)
**1. Define the Reader's Task First**
- Before writing, define the explicit action the reader must take after reading: Understand, Try, Buy, Share, Bookmark, or Memorize one sentence.
**2. Conclusion First, Evidence Second**
- BLUF (Bottom Line Up Front). State the verifiable conclusion (What / For Whom / Solving What) immediately. Do not bury the lead behind background info.
**3. Cost-Centric vs. Feature-Centric**
- Readers fear costs: Learning cost, Migration cost, Trial error cost, Maintenance cost, Risk of failure.
- Frame features around **reducing these costs** rather than just listing technical specs.
**4. Tangible Specifics over Abstract Adjectives**
- Replace abstract buzzwords (Efficient, Elegant, Revolutionary) with **perceptible facts**: "Fewer steps," "No interruptions," "Faster search," "Zero config," "Predictable results."
**5. Causal Narrative over "Hard Opinions"**
- Let opinions grow from logic: Trigger → Conflict/Pain → Attempt → Failure → New Approach → Result.
- Avoid direct judgment, bashing competitors, or creating imaginary enemies.
**6. Single Idea per Paragraph**
- One paragraph = One point.
- It should answer: What happened? Why does it matter? How do I fix it? What do I get?
**7. Provide a "Verification Path"**
- Every promise must have a **Minimum Viable Verification (MVV)**.
- Give the reader a specific action, scene, or comparison to prove the claim immediately (e.g., "Try it for 5 minutes and you will see...").
**8. Restrain Jargon and Metrics**
- Use jargon ONLY to save explanation time for experts.
- Use metrics ONLY when they are interpretable and verifiable. Otherwise, describe the outcome in plain language.
**9. Consistency is King**
- Unify terminology, naming, and tone intensity throughout the text. Consistency builds trust better than "fancy sentences."
**10. Structure: Hook → Context → Proof → Action**
- **Hook**: Resonance / Contrast / Problem.
- **One-liner**: Positioning.
- **Proof**: Scenarios / Examples.
- **Inventory**: Features / Specs.
- **Friction**: Limits / Boundaries (Honesty).
- **CTA**: The next step.
## Workflow
1. **Analyze**: Ask the user: "Who is this for? What is the one thing they should do after reading?"
2. **Draft**: Apply the 10 principles. Strip away adjectives. Insert verification paths.
3. **Refine**: Check against the "Cost-Centric" rule. Did we reduce the user's mental load?

View File

@ -0,0 +1,70 @@
---
name: linus_coding
description: "Linus Torvalds persona for code review, implementation, refactoring, and bug fixing. Enforces strict quality, data structure design, and zero regressions."
intent: ["coding", "refactor", "bugfix", "review", "implement"]
version: 1.0.0
---
# Module: Linus Style Coding & Review
## Purpose
This module is activated when the user requests code implementation, refactoring, bug fixing, or code review. You must adopt the persona of **Linus Torvalds**.
## Role Definition
You are Linus Torvalds, the creator and chief architect of the Linux kernel. You have maintained the Linux kernel for over 30 years. You analyze code quality risks to ensure the project is built on a solid technical foundation.
## Core Philosophy
**1. "Good Taste"**
"Sometimes you can look at a problem from a different angle, rewrite it so special cases disappear and become normal cases."
- Eliminating edge cases is always better than adding conditional checks.
**2. "Never break userspace"**
"We don't break userspace!"
- Any change that causes existing programs to crash is a bug. Backward compatibility is sacred.
**3. Pragmatism**
"I'm a damn pragmatist."
- Solve real problems, not hypothetical threats. Reject over-engineering.
**4. Simplicity Obsession**
"If you need more than 3 levels of indentation, you're already dead, fix your program."
- Functions must be small and focused. Complexity is the root of all evil.
## Communication Style
- **Language**: Think in English, express in Chinese.
- **Tone**: Direct, sharp, zero fluff. Focus strictly on technical issues.
## Thinking Process (Mandatory before coding)
**Layer 1: Data Structure Analysis**
"Bad programmers worry about the code. Good programmers worry about data structures."
- What are the core data? Who owns it? Are there unnecessary copies?
**Layer 2: Special Case Identification**
"Good code has no special cases"
- Can the data structure be redesigned to eliminate if/else branches?
**Layer 3: Complexity Review**
- Can the concept count be reduced? If indentation > 3, reject it.
**Layer 4: Breaking Analysis**
"Never break userspace"
- List existing features/dependencies that might be affected.
**Layer 5: Practicality Validation**
- Does this problem really exist in production?
## Code Review Output Format
When reviewing or presenting code, you must include:
**【Taste Score】**
🟢 Good Taste / 🟡 Acceptable / 🔴 Garbage
**【Fatal Issues】**
- [Directly point out the worst part]
**【Improvement Direction】**
- [Specific advice, e.g., "Eliminate this special case", "Simplify data structure"]

64
.phrase/modules/pr_faq.md Normal file
View File

@ -0,0 +1,64 @@
---
name: pr_faq
description: "Amazon-style PR/FAQ workflow for project initiation, vague ideas, or new phases. Use this when the user says 'I have an idea' or 'Start a new project'."
intent: ["init", "start", "idea", "phase"]
version: 1.0.0
---
# Module: Amazon Style PR/FAQ (Project Initiation)
## Purpose
This module is activated when the user wants to start a new project, a new phase, or has a vague idea that needs clarification. Your goal is to act as a **Strict Product Manager** to guide the user in completing an Amazon-style PR/FAQ document *before* any technical planning or coding begins.
## Workflow
1. **Interview Mode**: Do not just ask the user to "fill in the template". Conduct an interview. Ask probing questions about the target customer, the specific problem, and the solution.
2. **Drafting**: Based on the user's answers, draft the PR/FAQ using the template below.
3. **Review**: refined the draft with the user until it is sharp, clear, and inspiring.
4. **Decomposition**: ONLY after the PR/FAQ is finalized, split the content into `spec_*.md` (Requirements) and `plan_*.md` (Milestones/Tasks).
## Template
### Press Release (PR)
**Headline**
> This is the press release headline.
**Subtitle**
> The subtitle reframes the headline solution, adding additional points of information.
**Date**
> The potential date to launch the product or service.
**Intro paragraph**
> Describe the solution and details about the target customer and benefits.
**Problem paragraph**
> Describe the top 2-3 problems for the customers you intend to serve.
**Solution paragraph**
> Describe how the product/service solves the problem.
**Company leader quote**
> Write a quote that talks about why the company decided to tackle this problem and the solution.
**How the product/service works**
> How will a customer start using the solution and how does it work?
**Customer quote**
> Write a quote from an imaginary customer.
**How to get started**
> In one sentence, describe how anyone can get started today, and provide a URL.
### FAQ
> The FAQ frequently asked questions is the second page, and formats all content in a series of questions and answers.
**Internal FAQs**
> Questions stakeholders will likely ask (e.g., risks, dependencies, technical challenges, costs).
**Customer FAQs**
> Questions customers will likely ask (e.g., pricing, compatibility, support).
*Instructions: Predict questions stakeholders or customers will likely ask, and answer them early. Doing this highlights the depth of thinking.*

View File

@ -0,0 +1,7 @@
# Phase: Maintenance (2026-01-26)
## Purpose
Fix build issues and maintain codebase stability.
## Tasks
- [x] task001: Fix Windows build failure in `ekp_c` (Make `cc` not found).

View File

@ -0,0 +1,20 @@
# Task 001: Fix Windows build failure
## Issue
User reports `make` fails on Windows because `cc` is not found.
Current Makefile relies on `uname` and assumes `cc` exists.
## Plan
1. Detect Windows via `OS` environment variable (standard on Windows).
2. On Windows, default CC to `gcc` if not set.
3. Remove reliance on `uname` for Windows detection.
4. Verify `pthread` linking.
## Status
- [x] Completed (2026-01-26)
## Validation
- Run `make` in `ekp_c/`.
- Verify `ekp.dll` is created.
- [x] Confirmed `make` builds `ekp.dll`.
- [x] Confirmed `make test` passes (loads module in Emacs).

View File

@ -0,0 +1,9 @@
# Changes Log - Phase Maintenance 2026-01-26
## 2026-01-26
- **Fix**: Update `ekp_c/Makefile` to support Windows build.
- Detect `Windows_NT` and use `gcc` instead of `cc`.
- Set default `EMACS_ROOT` and `EMACS` path for the current environment.
- Add `EMACS_ROOT` include path to `CFLAGS`.
- Fix `test` target to use configured `$(EMACS)` executable.
- Task: `task001`

View File

@ -0,0 +1,27 @@
# Change Log: Phase Doc Improvement 20260125
## 2026-07-28
- **Modify**: `task_doc_improvement.md`
- Backfilled task001-task006 completion markers from the phase's existing
change log and delivered files; no product or documentation behavior
changed.
## 2026-01-25
- **Add**: `DEVELOPER.md` and `DEVELOPER_ZH.md`
- Extracted technical details from READMEs.
- Added detailed `ekp-para` struct definition and field explanations.
- Added Elisp Core API reference (`ekp-pixel-justify`, `ekp-pixel-range-justify`, `ekp-param-set`).
- Added C Module architecture, memory model, and API reference (`ekp-c-init`, `ekp-c-break-with-prefixes`).
- Added Architecture diagram.
- **Modify**: `readme.md` and `readme_zh.md`
- Refocused on User Guide (Installation, Configuration, Usage).
- Removed internal implementation details.
- Added links to new Developer Guides.
- Cleaned up formatting and structure.
- **Add**: Project Phase Structure
- Initialized `.phrase/` directory.
- Created `spec`, `plan`, `task` for `phase-doc-improvement-20260125`.

View File

@ -0,0 +1,17 @@
# Plan: Documentation Improvement
## Milestones
1. **Structure Setup**: Initialize `.phrase` and new files.
2. **Extraction**: Move technical content from READMEs to DEVELOPER docs.
3. **Enhancement**: Flesh out API details in DEVELOPER docs using source code as reference.
4. **Polish**: Refine User Guide in READMEs.
## Scope
- Files: `readme.md`, `readme_zh.md`, `DEVELOPER.md` (new), `DEVELOPER_ZH.md` (new).
- Languages: English, Chinese.
## Dependencies
- Source code (`ekp.el`, `ekp_c/*`) for accurate API documentation.
## Risks
- Documentation becoming out of sync with code (mitigated by referencing current codebase).

View File

@ -0,0 +1,30 @@
# Spec: Documentation Improvement
## Summary
Restructure and enhance documentation for `emacs-kp`. Separate User Guide from Developer Documentation to improve readability for both audiences. Provide in-depth API reference for developers.
## Goals
1. **Separation of Concerns**: `readme.md` for users, `DEVELOPER.md` for contributors.
2. **Completeness**:
- Users: Clear installation, configuration, and feature overview.
- Developers: Comprehensive API reference for both Elisp and C layers, architecture diagrams, data structure definitions.
3. **Bilingual Support**: Maintain parity between English and Chinese documentation.
## Non-Goals
- Changing the code or functionality of `emacs-kp`.
- Adding new tutorials (beyond basic usage).
## User Flows
- **User**: Lands on repo -> Reads `readme.md` -> Installs & Configures -> Uses package.
- **Contributor**: Lands on repo -> Sees "Developer Guide" link -> Reads `DEVELOPER.md` -> Understands internals -> Submits PR.
## Acceptance Criteria
1. `DEVELOPER.md` and `DEVELOPER_ZH.md` exist and contain:
- Architecture overview.
- Elisp Core API (`ekp-pixel-justify`, parameters, etc.).
- Data Structures (`ekp-para`, `ekp-box`, etc.).
- C Module details (API, build, memory model).
2. `readme.md` and `readme_zh.md` are cleaned up:
- No C implementation details (moved to Dev guide).
- Clearer "Quick Start" and "Configuration".
3. No broken links between documents.

View File

@ -0,0 +1,8 @@
# Tasks: Documentation Improvement
- task001 [x] Create `DEVELOPER.md` with extracted technical content from `readme.md`
- task002 [x] Create `DEVELOPER_ZH.md` with extracted technical content from `readme_zh.md`
- task003 [x] Enhance `DEVELOPER.md` with detailed Elisp API and C Module internals
- task004 [x] Enhance `DEVELOPER_ZH.md` with detailed Elisp API and C Module internals
- task005 [x] Refine `readme.md` to be user-focused (remove internal details, add links to Dev docs)
- task006 [x] Refine `readme_zh.md` to be user-focused (remove internal details, add links to Dev docs)

View File

@ -0,0 +1,190 @@
# KP 算法系统性优化 — 进度交接文档
> **2026-07-28 superseded note:** 本文的“未完成事项”保留为 2026-07-26
> 历史快照;后续审计、整改和验证状态以
> `task_repository_audit_20260728.md``.phrase/docs/ISSUES.md`
> 为准。
>
> 阶段:phase-kp-overhaul-20260726
> 状态:**已完成并提交**——分支 `kp-overhaul`(基于 main@11437cb),
> 合并到 main:`git checkout main && git merge kp-overhaul`
> 审查状态:自查 + 300 例随机性质测试已完成(0 失败);多智能体 workflow 审查仍可选(见"未完成事项")
> 本文档面向下一次会话/贡献者,保证无缝衔接。
## 〇、续做增量(同日第二轮)
- **新发现并修复缺陷 #15**:缓存 key 与 `ekp--last-para` 快路径均未包含
`ekp-latin-lang`——切换断词语言后同一字符串返回**旧语言的断词结果**
(旧代码同样存在此 bug,已实测复现)。修复:key 与快路径都纳入语言;
新增回归测试 `ekp-test-para-cache-tracks-language`。测试总数 35 → **36**,
全部通过;byte-compile 仍零警告。
- **300 例随机性质测试通过(0 失败)**:随机中西混排/CJK 标点/ZWSP/双空格/
超长词 × 随机宽度 1300px,断言 ① C 与 elisp 输出逐字节一致 ② 内容零丢失
③ 代价有限 ④ 不报错。脚本已固化为 tests/ekp-fuzz.el(确定性 LCG 种子 42,可复现;
需 C 模块,单独运行:emacs -Q --batch -L . -l tests/ekp-fuzz.el)。
## 一、本阶段目标(原始指令)
清除未提交文件 → 全面分析仓库 → 找出 kp 算法设计缺陷与未完善功能 →
系统性优化,确保功能全部实现、性能实测 → 重写/完善文档。
## 二、提交内容
以下改动已在 `kp-overhaul` 分支提交(refactor! 单提交,含本文档):
```
M ekp.el # 核心重写:DP、缓存、参数、渲染、C 桥接
M ekp-utils.el # 字体检测 batch 回退、全角/组合字符修复、删 Rust 死代码
M ekp-hyphen.el # 仅 docstring 修正
M ekp_c/ekp_kp.c # 两遍紧急策略、badness 封顶、空格数组、参数化 penalties
M ekp_c/ekp.c # API v1.1:break-with-arrays 11 参、set-penalties 4-6 参
M ekp_c/ekp_module.h # 版本 1.1、结构体新字段
M tests/ekp-tests.el # 全新 ERT 套件(36 个测试)
A tests/ekp-bench.el # 基准脚本
A tests/ekp-demo.el # 交互式 demo(从旧 tests 迁移)
A tests/run-tests.sh # 一键跑测试
A tests/ekp-fuzz.el # 300 例随机性质测试(需 C 模块,单独运行)
M readme.md / readme_zh.md / DEVELOPER.md / DEVELOPER_ZH.md / ekp_c/README.md
```
`ekp_c/ekp.dylib` 已用新源码重新编译(版本 1.1,gitignore 忽略编译产物)。
会话开始时已按指令 `git clean -fd` 清除了全部 Syncthing sync-conflict 垃圾文件。
**建议提交信息**(Conventional Commits,单提交或按 fix/perf/test/docs 拆分):
```
refactor!: overhaul KP core — correctness, C parity, performance, tests, docs
- fix: ekp-param-set silently reset after first justify (now persists; ekp-param-reset added)
- fix: narrow-width CJK returned empty string (data loss); two-pass emergency breaks
- fix: penalties never synced to C module; space-box metrics divergence C vs elisp
- fix: fullwidth letters/digits misclassified as CJK punctuation
- fix: combining chars split from base char in tokenizer
- fix: punctuation-wrapped words (word!/(word)/word;) never hyphenated
- fix: para cache hash-collision aliasing (equal-keyed structured keys + limit)
- fix: renderer double-counted stripped space widths; negative glue clamped
- feat: real looseness support via (position × line-count) DP
- perf: O(1) line metrics/gap counts (was O(n) allocs in O(n²) loop);
box measurement dedupe; eq fast-path para lookup; C module 3-19× faster
- test: 36 batch-safe ERT tests + 300-case property fuzz replacing ad-hoc suite
- docs: all five docs rewritten to match implementation
BREAKING: requires Emacs 29.1+; C module must be rebuilt (v1.1, arity changes);
ekp-threshold-factor / ekp-flagged-penalty / ekp-forced-break-penalty removed;
Rust module stubs removed.
```
## 三、已完成工作(按类别)
### 1. 实测确认并修复的正确性缺陷(elisp)
| # | 缺陷 | 修复 |
|---|------|------|
| 1 | `ekp-param-set` 一次性失效:第二次排版起用户参数被静默重置 | 显式参数持久化(`ekp--params-explicit`),新增 `ekp-param-reset`;auto 模式按字符串派生 |
| 2 | 超窄宽度 CJK 整段返回空串(数据丢失);超长不可断词产生负宽 glue | 两遍 DP:严格遍 + 紧急单盒断行遍(仅在段尾不可达时);glue 钳制 ≥0 |
| 3 | `flagged-positions` 死代码(从未填充)、`ekp-threshold-factor` 剪枝语义可疑 | 连同 `ekp-flagged-penalty`/`ekp-forced-break-penalty` 一并删除 |
| 4 | para 缓存用 sxhash 整数 key,碰撞会串段 | `equal` 结构化 key(内容+属性区间+字体+参数或 `auto`)+ `ekp-para-cache-limit`(256) |
| 5 | 全角字母/数字()被当标点附着到前字 | `ekp-cjk-fw-punct-p` 排除 FF10-19/FF21-3A/FF41-5A |
| 6 | 组合字符(café NFD)被当空格拆成独立 box | 零宽附着类(Mn/Mc/Me、ZWJ/ZWNJ、变体选择符)并入前文;ZWSP 仍作断点 |
| 7 | `word!`、`(word)`、`word;` 等不断词(正则类不全) | 左右标点类补全(`ekp--word-left/right-punct`) |
| 8 | batch/tty 下 `font-at` 崩溃,包完全不可用 | 字体检测全部加 `display-multi-font-p` 回退 → 测试可自动化 |
| 9 | 宽度 ≤0 静默吞文本 | `user-error` 校验;非字符串输入 `wrong-type-argument` |
| 10 | looseness ±1 无效(alt-paths 只延伸最优前缀,状态不闭合) | 真正的 (位置×行数) 2D DP(`ekp--dp-run-loose`) |
| 11 | 断词连字符不带样式;宽度按无属性 "-" 测量 | 渲染继承所断词属性;宽度按字符串首字符属性测量 |
| 12 | 渲染层剥离空格 box 后又把宽度再分配(与 DP 的排除度量双重计算) | 删除再分配;DP 契约:度量已排除,行宽精确 == 目标(有测试锁定) |
| 13 | force-break demerits 不累计前缀(与 C 不一致) | 统一为紧急断行公式 `(lp+10000)²+rest²`,两引擎一致 |
| 14 | shrink 容量计算不含 cws(与 min-prefix 可行域矛盾) | badness/分配均含 cws-shrink |
### 2. C/Elisp 一致性(全部实测验证)
- **参数同步**:`ekp--c-sync-params` 每次进 C 前推送 6 个 penalty(C `ekp-c-set-penalties` 扩为 4-6 参;consec-hyphen/last-line-short 不再硬编码)。
- **badness 封顶**:C 侧超 10000 曾变 `EKP_INFINITY`(断点被丢),现与 elisp 一致封顶 10000。
- **空格 box 度量**:新增 `lead-spaces`/`trail-spaces` 数组(n+1)传给 C;`ekp-c-break-with-arrays` 9→11 参,batch 向量 9→11 元素。
- **两遍紧急策略**:C 与 elisp 完全相同(严格遍 → 不可达时紧急遍)。
- **版本门禁**:模块版本 1.1;`ekp-c-module-load` 拒绝旧模块并回落 elisp(`ekp-c-module-required-version`)。
- **looseness ≠ 0 时自动绕过 C**(`ekp--c-available-p`)。
- **验证结果**:6 个测试文件 × 5 宽度 = 30/30 输出逐字节一致;penalty 极值下同样一致。
### 3. 性能(实测,batch Emacs 30.2,Apple Silicon,3 次冷缓存取最小)
| 场景 | 改造前 elisp(解释) | 改造后 elisp(编译) | 改造后 C |
|------|-----:|-----:|-----:|
| justify 中文 w=200 | 7547 ms | **96 ms** | **57 ms** |
| justify 混排 w=300 | 5540 ms | 53 ms | 23 ms |
| range 中文 340-380 | 29696 ms | 294 ms | 75 ms |
| range 混排 280-320 | 68534 ms | 480 ms | 34 ms |
| 仅 DP(zh, w=400) | 2382 ms | 15 ms | **1.3 ms** |
(旧 C 模块对照:justify-zh-200 197ms / range-zh 430ms / DP 25ms → 新 C 快 3-19×)
关键优化:① 前缀计数数组使行度量/间隙统计 O(1)(旧内层每候选 O(n) 分配,总 O(n³));② 两遍法保持 DP 稀疏;③ 盒宽测量去重(段属性均匀时仅按字符串 key);④ `ekp--last-para` eq 快路径(消除每次 get-para 的 prin1+全串哈希);⑤ para 级 glue 数组跨 C 调用复用;⑥ bool-vector 连字符标志。
基线/复现脚本:`tests/ekp-bench.el`(改造前基线数字已录入 DEVELOPER*.md §9)。
### 4. 测试(tests/ekp-tests.el,36 个 ERT,**全部通过**(含语言切换回归))
覆盖:断词(en/de-ISO8859/边距/语言回退)、分箱(kinsoku 开闭标点/全角/组合字符/空格保留)、行宽不变式、任意宽度不丢内容、窄宽回归、非法参数、参数持久化/reset、参数同步到 C、looseness、缓存(命中/属性区分/上限/dp 复用)、O(1) 度量与暴力交叉验证、属性保留、连字符继承属性、range-justify、C/elisp 一致性(含 batch)。C 模块未编译时相关测试自动 skip。
运行:`tests/run-tests.sh /Applications/Emacs.app/Contents/MacOS/Emacs`
### 5. 文档(全部重写,与实现逐条对齐)
readme.md / readme_zh.md(用户指南 + 真实性能表 + 已知限制)、
DEVELOPER.md / DEVELOPER_ZH.md(五阶段管线、数据结构、demerits 公式与
TeX 差异、两遍策略、C 集成、基准方法学)、ekp_c/README.md(修正了
"wavefront 并行"“zero copy" 等与实现不符的旧说法;明确 `ekp-c-break-lines`
为实验路径)。**Package-Requires 已改为 Emacs 29.1**(string-pixel-width
/ object-intervals 实际要求;旧标注 27.1 不真实)。
### 6. 死代码清理
ekp-utils.el 的 Rust 模块支持(ekp_rust 目录不存在)已删除;
process 回调的 eval 式 lambda 改为词法闭包;byte-compile 零警告。
## 四、未完成事项(下次会话优先处理)
1. **多智能体对抗审查(可选)**:因会话限额(21:50 Asia/Shanghai 重置)
未能以 workflow 形式执行;已用两项替代手段覆盖主要风险:
① 针对脚本 prompt 中列出的重点自查项逐项人工核查——eq 快路径过期
(发现并修复了语言维度的真实 bug,见"续做增量")、C 空格数组索引
(lead/trail 均 n+1 元素,i<n / kn 界内)rest²/penalty² 均以 double
计算无 int32 溢出;② 300 例随机性质测试 0 失败。如仍需 workflow 审查,
脚本已存盘:
`~/.claude/projects/-Users-geekinney-IPARA-3-RESOURCES-emacs-config-github-emacs-kp-ekp-c/ffca1e3d-9bda-4b18-814c-e95d7a8222c5/workflows/scripts/ekp-final-review-wf_03fd954b-f7a.js`
(resumeFromRunId: `wf_03fd954b-f7a`),或直接 `/code-review`
2. **GUI 真实字体视觉验证**:batch 下全部验证通过;真实字体渲染建议用户在
图形 Emacs 里执行 `tests/ekp-demo.el` 中的注释示例
(如 `(ekp-demo-justify "zh" "en_US" "Cascadia Next SC" 666)`)。
3. **合并**:改动已提交到 `kp-overhaul` 分支;确认后合并到 main
(`git checkout main && git merge kp-overhaul`),如需推送再 `git push`
4. (可选后续)`ekp-justify-region` 之类的交互命令、词典编码显式处理
(目前依赖 Emacs 自动检测,de_DE ISO-8859 已实测正确)。
## 五、快速接续命令
```bash
EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
REPO=~/IPARA/3-RESOURCES/emacs/config/github/emacs-kp
# 全量测试(36 个,约 25s,C 模块存在时含一致性测试)
$REPO/tests/run-tests.sh $EMACS
# 重建 C 模块(改 ekp_c/ 后必须;版本门禁 1.1)
cd $REPO/ekp_c && make clean && make
# 基准(elisp / C 两引擎)
$EMACS -Q --batch -L $REPO --eval '(setq ekp-use-c-module nil)' -l $REPO/tests/ekp-bench.el
$EMACS -Q --batch -L $REPO --eval '(progn (require (quote ekp)) (ekp-c-module-load))' -l $REPO/tests/ekp-bench.el
```
## 六、关键设计契约(改动任何一侧都要维护)
1. **两引擎逐字节一致**:改 demerits/度量公式必须同时改 `ekp--dp-run-1d`
`ekp_c/ekp_kp.c``dp_process_position`,并跑一致性测试。
2. **DP 与渲染的空格契约**:DP 度量排除行首(i>0)/行尾空格串
(lead/trail-spaces 数组),渲染剥离同一批 box 且**不再**补偿宽度。
3. **紧急断行只在第二遍**:存在合法排版时结果必须是纯 K-P 最优。
4. **`ekp--last-para` 失效点**:任何影响 para 内容的全局状态变化
(参数 apply/reset、clear-caches)都必须置 nil。
5. C 模块 API 变化必须递增 `EKP_VERSION_MINOR` 并同步
`ekp-c-module-required-version`

View File

@ -0,0 +1,52 @@
# ADR: Native Backend for Automatic Live Append 2026-08-20
## Context
Source-loaded automatic live append exceeded the interaction budget because
the strict Elisp append DP interpreted every structural transition. The
existing C backend already accepts the prepared paragraph arrays and produces
exactly the same break result, but `ekp-use-c-module=nil` previously disabled
it even inside the live append path.
## Decision
Add `ekp-auto-justify-native-append`, defaulting to non-nil. When auto mode is
publishing an already prepared, context-safe 1D live append and the compatible
C module is loaded, `ekp--dp-cache-append` may use the native DP regardless of
the ordinary full-layout `ekp-use-c-module` setting. The string API and full
paragraph layout continue to obey `ekp-use-c-module` directly. Setting the new
option to nil restores pure Elisp live append; an unavailable module always
falls back to Elisp.
The native call receives the same prepared 15-field arrays and is validated by
the existing C/Elisp parity contract. No C ABI field or source projection
representation changes.
## Alternatives
- Duplicate the strict Elisp DP for append: rejected after the parity
experiment required a second 160-line transition kernel and returned nil.
- Reuse final-pass transient state: rejected because artificial candidates and
surviving-path arrays are not part of the persisted state.
- Keep the source stress debt open: insufficient after the user selected the
native live-append architecture.
## Consequences
- A loaded C module accelerates live append even when full layout is explicitly
configured for Elisp; this is documented and user-controllable.
- The remaining source latency belongs to Elisp-owned append preparation and
semantic plan assembly, which remain exact and testable.
- Native-unavailable environments retain the previous pure-Elisp behavior.
## Verification
- A public buffer regression proves native calls occur only when the option is
enabled and are absent when it is disabled.
- Existing append-chain, C/Elisp parity, fuzz, and source-clean tests remain
required; source-fresh evaluator reports native-live usage explicitly.
## Rollback
Set `ekp-auto-justify-native-append` to nil or revert the dispatch change;
the full layout API and valid C contract remain independently usable.

View File

@ -0,0 +1,38 @@
# Change Log: Automatic Spacing Cache Identity 2026-07-28
## task003
- **Modify**`ekp.el`
- Added one spacing signature used by the paragraph hash and
`ekp--last-para`.
- Automatic mode records `ekp-default-cws-stretch-pixel`; explicit mode
records all nine spacing values.
- Kept font-derived automatic inputs owned by the existing font and
display-context identity.
- **Modify**`tests/ekp-tests.el`
- Added separate regressions for paragraph-table reuse and the same-string
fast path.
- Added an identity/count control proving unchanged signatures still hit.
- **Modify** — public, developer, audit, and phase documentation
- Documented the complete spacing identity and closed `issue001`.
- Added the decision record
`postmortem/20260728-paragraph-spacing-signature.md`.
## Validation
- Focused red: 1/3 passed; both stale-value regressions returned 2 instead
of the new default 9.
- Focused green: 3/3.
- Full ERT: 99/99.
- C/Elisp fuzz: 300/300.
- Byte compilation with warnings as errors: clean.
- checkdoc: clean.
## Behavior and Risk
- Changing automatic CJK stretch takes effect on the next call without
clearing caches.
- Returning to an earlier signature may reuse its still-valid paragraph.
- No public API, C ABI, saved-file format, dependency, or watcher changed.

View File

@ -0,0 +1,61 @@
# Change Log: Buffer Module Naming 2026-07-28
## task017
- **Delete/Add**`ekp-region.el``ekp-buffer.el`
- Renamed the editor integration file and provided feature around the
buffer lifecycle owner.
- Renamed module configuration and private symbols from `ekp-region*` to
`ekp-buffer*`.
- Attached the buffer customization subgroup to the top-level `ekp`
group and made the protrusion-reserve helper explicitly private.
- Kept public commands whose names correctly describe region, buffer, or
mode behavior.
- **Delete/Add**`tests/ekp-region-tests.el`
`tests/ekp-buffer-tests.el`
- Renamed the test feature, fixtures, and ERT names.
- Updated default, random-order, isolated, GUI, and showcase loaders.
- **Modify** — CI, contribution, public/developer/audit documentation
- Updated compilation, lint, checkdoc, test, and Windows CI paths.
- Replaced the documented require/configuration surface and file maps.
- Added the breaking migration to `CHANGELOG.md`.
- Recorded the ownership decision in
`postmortem/20260728-buffer-module-naming.md`.
## Cleanup Review
- Naming mismatch: resolved at the owning module/feature/configuration/test
boundary.
- Dead code, duplication, error handling, dependency, and UI: unchanged and
outside this pass.
- Fallback findings: only the existing selected-window width fallback,
classified as a grounded undisplayed-buffer boundary.
- No masking fallback, swallowed error, compatibility shim, new dependency,
new abstraction, or unrelated refactor was introduced.
## Validation
- Pre-change behavior lock: ERT 130/130.
- Focused buffer ERT after rename: 44/44.
- Default and seeded-permuted ERT: 130/130 each.
- Fresh-process isolation: every one of 130 selected ERT tests passed.
- C/Elisp property fuzz: 300/300.
- Warning-as-error production byte compilation: pass.
- Checkdoc and pinned package-lint: pass.
- Release invariants, shell syntax, diff check, and active-surface stale-name
scan: pass.
- Mechanical runtime/test equivalence against the approved rename table:
pass.
## Behavior and Risk
- Layout, serialization, search, copy, undo, and automatic reflow behavior
are unchanged.
- This is an intentional source-level breaking rename: users must require
`ekp-buffer` and rename any `ekp-region-*` settings.
- No saved-file format, dependency, Emacs baseline, C ABI, or release
artifact changed.
- Generated `.elc` verification artifacts were removed after compilation;
they can be regenerated from the source.

View File

@ -0,0 +1,36 @@
# Change Log: C API and Arithmetic Contract 2026-07-28
## task008
- **Modify**`ekp_c/ekp.c`, `ekp_c/ekp_module.h`, `ekp_c/ekp_kp.c`
- Preflighted all single/batch schema fields before extraction.
- Added `ekp-c-invalid-input` and atomic penalty validation.
- Widened line arithmetic and stored rest values to 64-bit.
- Bumped the module to 1.6.
- **Modify**`ekp.el`, `ekp-utils.el`
- Required C 1.6.
- Preserved nil fallback but propagated enabled-backend signals.
- **Add/Modify** — C boundary tests, loaders, and documentation
- Added six direct module cases and a public dispatch error control.
- Updated public/developer/C API contracts and audit records.
## Validation
- Direct C boundary red/green: 0/6 → 6/6.
- Public dispatcher signal red/green: 0/1 → 1/1.
- Release C build: C11, `-Wall -Wextra -Wpedantic`, zero warnings.
- Sanitizer C build: ASan/UBSan, zero compile warnings. Loading the ASan
dylib into signed macOS Emacs was blocked by platform runtime policy, so
runtime sanitizer evidence remains delegated to Linux CI.
- Full ERT: 116/116.
- C/Elisp fuzz: 300/300.
## Behavior and Risk
- Valid C calls retain the same arguments and result shape.
- Invalid direct calls now use a stable, specific error condition.
- Allocation/no-result remains recoverable via Elisp; module signals expose
broken internal contracts instead of hiding them.
- Older 1.5 modules are rejected until rebuilt.

View File

@ -0,0 +1,39 @@
# Change Log: Shell-Free Profiled C Builds 2026-07-28
## Planning
- Added `task009` and locked its process, profile, whitespace-path, and
build/test acceptance criteria before implementation.
## task009
- **Modify**`ekp-utils.el`
- Deleted the one-use generic callback wrapper and shell command.
- Added direct argv `make-process`, explicit profile validation, correct
working directory, and success/failure output lifecycle.
- **Modify**`ekp_c/Makefile`, `.github/workflows/ci.yml`
- Made portable flags the default.
- Split native, debug, and sanitizer flags behind `PROFILE`.
- Updated sanitizer CI to the same vocabulary.
- **Modify** — tests and public/developer/C/audit documentation
- Added process-shape and unknown-profile controls.
- Documented profile ownership and closed `issue005`.
## Validation
- Focused process/profile red/green: 0/2 → 2/2.
- portable/native/debug/sanitize: all compiled with zero warnings.
- Real `ekp-c-module-build 'portable`: exit 0 and loaded C 1.6.
- Portable copy under `/tmp/ekp build.*`: built successfully.
- Full ERT and fuzz evidence from `task008` remains valid; final phase QA
reruns both after all slices.
## Behavior and Risk
- `portable` no longer emits host-specific instructions.
- `native` artifacts are intentionally machine-specific.
- Failed async builds keep and display their output buffer; successful
builds reload and remove it.
- The old `DEBUG=1` spelling is deleted rather than retained as a shim.

View File

@ -0,0 +1,39 @@
# Change: C Resize Latency 2026-07-29
## 2026-07-30 — Complete task032 core and resize optimization
- **Modify** `ekp.el`: resolve a paragraph once per layout plan, reuse its
prepared DP data, memoize width-independent natural gap geometry, and omit
true zero-source/zero-target projection gaps.
- **Modify** `ekp-buffer.el`: publish owned properties in one mutation,
consume prepared gap geometry without projection-time measurement, and
keep the active paragraph out of the static resize pass before installing
its live prefix.
- **Add** focused RED/GREEN regressions in `tests/ekp-tests.el` and
`tests/ekp-buffer-tests.el`, plus the frozen dual-path evaluator in
`tests/ekp-c-resize-evaluator.el` and
`tests/run-c-resize-evaluator.sh`.
- **Performance:** four interleaved rounds reduce core p95 from 42.006 ms to
27.687 ms (34.09%) and complete resize p95 from 46.611 ms to 27.487 ms
(41.03%); both p50 gains also exceed 20%.
- **Correctness:** frozen-C, candidate-C, and Elisp projection hashes match.
Normal, random-order, and isolated ERT gates pass; 300 fuzz cases, static
checks, release checks, and reviewed temporal GUI evidence pass.
- **Risk:** external window-system allocation can trigger an Emacs GC pause
at the resize callback boundary. Total, GC, and EKP mutator time remain
separately observable; no debounce or global GC behavior changed.
## 2026-07-29 — Plan issue020 and task032
- **Add** `issue020` and `task032` for the reported 6070 ms C-backed
resize/reflow latency.
- **Add** the `c-resize-latency` performance-goal contract before
optimization: paired p50/p95 improvement of at least 20%, candidate p95
at or below 50 ms, exact frozen-C and Elisp parity, and complete
regression gates.
- **Decision:** measure complete reflow, plan construction, Emacs/C
marshalling, C DP, and projection publication separately before choosing
the implementation layer.
- **Risk:** a whole-reflow stopwatch can misattribute Elisp measurement or
property publication to the C algorithm. No runtime change is included in
this planning entry.

View File

@ -0,0 +1,48 @@
# Change Log: Complete DP Cache Signature 2026-07-28
## task002
- **Modify**`ekp.el`
- Replaced the width/looseness special-case key with one flat signature
containing every runtime DP input not frozen into `ekp-para`.
- Changed each paragraph's DP cache from `eql` to structural `equal`
comparison.
- Result: parameter changes cannot alias stale results, and structurally
identical non-zero-looseness signatures now hit the cache.
- **Modify**`tests/ekp-tests.el`
- Added six parameter-by-parameter cached-vs-fresh regressions through
`ekp-dp-cache`.
- Added an identity/count assertion proving the fix preserves real cache
hits instead of merely forcing recomputation.
- **Modify** — public and developer documentation
- Removed the temporary manual-cache-clear workaround.
- Documented the complete signature and added an Unreleased changelog
entry.
- Preserved the dated audit evidence while recording the follow-up status.
- **Add**`postmortem/20260728-dp-cache-signature.md`
- Recorded why a complete structural key was chosen over watchers,
collision-prone hashes, or another mutable signature owner.
## Validation
- Focused red: 0/2 expected tests passed on the old key.
- Focused green: 2/2 passed on the complete signature.
- Full source ERT: 96/96.
- Full compiled ERT: 96/96.
- C/Elisp fuzz: 300/300.
- Byte compilation with warnings as errors: clean.
- checkdoc: clean.
- Public rendered-output probe: cached and fresh results match in both
Elisp and C modes after a penalty change.
- 100,000 cache-hit microbenchmark: approximately 0.37 seconds versus
0.17 seconds for the old scalar key (about 2 microseconds per lookup).
## Risk
- Cache keys are small structural lists instead of scalar/cons `eql` keys.
- Deliberately sweeping many parameter combinations retains one result per
signature until the paragraph cache is cleared.
- No public API, C ABI, paragraph format, or saved-file format changed.

View File

@ -0,0 +1,22 @@
# Change: Close User-Confirmation Issues 2026-08-13
## 2026-08-13 — Close issues awaiting user-visible confirmation
- **Modify** `.phrase/docs/ISSUES.md` to mark `issue011``issue017`,
`issue019`, and `issue020` as resolved.
- **Modify** the corresponding issue detail files to record the closure
date, responsible closure path, and that the commit is
documentation-only.
- **Leave open** `issue018`/`task030`: the locked source-instrumented
16 ms stress target is still unmet, so that item remains an active
engineering debt rather than a pending user-confirmation gate.
- **Behavior/Risk:** no runtime change. This pass closes the required
user-visible acceptance gate for work that had already completed
developer, repository, and dynamic GUI verification.
## Verification
- `issue011``issue017`, `issue019`, and `issue020` now use `[x]` in the
global issue index and their detail headers.
- `issue018` remains `[ ]`; `task030` remains `[ ]`.
- No production or test code was changed.

View File

@ -0,0 +1,175 @@
# Change: Configurable Break Policies 2026-08-01
Superseded note: the historical hard-atom adjacency rule recorded below was
removed by `task036`. Explicit atoms forbid only interior breaks; current
final-pass reachability semantics are recorded in
`change_final_pass_emergency_stretch_20260802.md`.
## 2026-08-01 — Plan task034 and record the red baseline
- **Add** `task034` for configurable break policies and the remaining
orphan-glyph quality failure in the 280px showcase paragraph.
- **Modify** the text-property layout spec to make inline code wrapping,
token policies, kinsoku profiles, overlong-token behavior, buffer measure,
and region break-policy precedence explicit.
- **Modify** the text-property layout plan with M13, preserving the approved
architecture: resolve policy intervals before tokenization, compile to the
existing hyphen/break vectors before DP, and keep the C boundary at 15
arguments and 15 batch fields.
- **Red baseline:** the latest screenshot shows the previous atom cascade is
gone, but the crafted inline-code paragraph still permits pathological
single-CJK source lines around the atom, including `行`, `内`, and `永`.
The implementation oracle must reject any such single-CJK source line when
a legal non-emergency alternative exists.
- **Verification contract:** focused RED/GREEN ERT for core, buffer, command,
cache, and diagnostics; C/Elisp parity; 300-case property fuzz;
warning-as-error Elisp compilation; C builds/tests; checkdoc/package/static
release gates; and reviewed fullscreen GUI evidence for inline wrapping,
no orphan glyph, explicit no-break, block verbatim, measure modes, and
overlong-token modes.
- **Behavior/Risk:** Planning records only. Runtime behavior is unchanged.
This entry opened `task034`; later entries record its implementation and
closure.
## 2026-08-01 — Record task034 implementation and G004 GUI evidence
- **Modify** `task034`, M13, and the text-property layout spec with verified
implementation facts from G002/G003/G004. At this point `task034` was still
open pending later gates; G006 closure is recorded below.
- **Implementation evidence recorded:** core policy compilation preserves the
existing C boundary; buffer policy ownership separates block skip faces from
inline faces; public local/profile/region controls and diagnostics exist;
showcase verification distinguishes automatic inline code from explicit
`ekp-no-break` and verbatim block code.
- **GUI evidence recorded:** G004 passed GUI verifier ERT 7/7 and the clean
single-window evidence run at `/tmp/ekp-g004-evidence.Tp77dW` reports 12/12
checkpoints with no failed assertions, 25.75s/206-frame recording, no black
segments, automatic inline wrapping 3→2→3 lines across 280→340→280, every
internal inline split as source whitespace, exact source, zero overlays, C
active, and a settled no-hyphen→normal policy transition with stale
nil-plan spans at zero.
- **Verification status:** G005 repository-wide gates and G006 cleanup are
recorded below.
- **Behavior/Risk:** Documentation synchronization only in this entry. Do not
close `task034` or any user-confirmation issue from this G004-only evidence
at this point in the sequence.
## 2026-08-01 — Record G005 final performance and repository gate evidence
- **Modify** `task034` and the policy-boundary postmortem with the confirmed
G005 root causes and final gate evidence. At this point `task034` was still
open pending G006 cleanup; G006 closure is recorded below.
- **Root causes recorded:** paragraph identity included unconditional policy
measure, causing paragraph rebuilds; volatile property clearing fragmented
equal property runs; policy analysis ran before paragraph-cache hits; and
repeated same-paragraph/same-width requests duplicated semantic plan
assembly.
- **Fix evidence recorded:** policy measure was removed from the semantic
signature; adjacent equal filtered intervals are canonicalized; policy full
analysis now uses a bounded two-tier cache keyed by clean source, canonical
layout and raw face policy intervals, policy signature, and width context,
with a measure tier only for actual width-sensitive no-break/overflow
candidates; and semantic plans use a bounded per-paragraph plan cache. G007
later strengthened the consumer boundary from shallow copies to
consumer-owned copies of plan-owned mutable payloads; see the G008 entry
below.
- **Performance evidence recorded:** formal four-interleaved evaluator reports
layout parity true. Core baseline p50/p95 was 38.6679/51.6782ms and
candidate p50/p95 was 21.7102/33.0040ms, for 43.8549%/36.1354% gains.
Resize baseline p50/p95 was 43.3831/55.6250ms and candidate p50/p95 was
22.0919/32.8202ms, for 49.0773%/40.9973% gains. Both candidate p95 values
are under 50ms and both gain sets exceed 20%.
- **Live evaluator recorded:** the source-instrumented locked goal remains the
known `validation_failed` debt; parity, zero-work, GC, conflict, and
all-width-nonregression checks are true. Current C p95/p99 is
26.449/26.740ms, a 76.65%/77.23% improvement. Current Elisp p95/p99 is
49.940/52.017ms, a 91.64%/91.35% improvement. This is consistent with
historical open `issue018` and is not a regression.
- **Production public-path evidence recorded:** three byte-compiled runs
passed with zero GC. C append p99 was 1.361-1.368ms and hard p99 was
1.876-1.891ms. Elisp append p99 was 1.692-1.775ms and hard p99 was
2.100-2.230ms.
- **Verification recorded:** default ERT passed 262/262; seeded permuted ERT
seed 20260728 passed 255/255; isolated per-test process suite exited 0;
the subsequently added alias guard passed targeted verification; property
fuzz passed 300/300; warning-as-error byte compilation passed; pinned
package-lint at `35996f478d81e51dae4fa30d051f741895d07399` exited 0 with
only an external obsolete warning from the local names dependency; checkdoc
was empty; release, 49-entry dictionary manifest, pinned dictionary update,
shell syntax, CI YAML, and diff-check passed; portable, native, debug, and
sanitize C builds were warning-clean; module 1.6/4-thread smoke passed; and
focused C verification passed 19/19 including the 15-argument/15-field
boundary.
- **Verification status:** G005 gates are complete. G006 cleanup is recorded
below.
## 2026-08-01 — Close task034 with G006 cleanup evidence
- **Modify** `task034`, M13, the global change index, and the policy-boundary
postmortem with G006 cleanup evidence.
- **Cleanup evidence recorded:** G006 edited only `ekp.el` and
`ekp-buffer.el`, removing redundant policy/cache code without changing the
locked behavior.
- **Verification recorded:** targeted cleanup suites passed 6/6 and 3/3; full
core ERT passed 125/125; full buffer ERT passed 120/120; warning-as-error
byte compilation passed; diff-check passed; and the C build gate passed.
- **Behavior/Risk:** `task034` is closed for developer implementation,
repository gates, performance gates, and cleanup gates. `issue021` remains
open pending user visual confirmation. Independent final code/architecture
review has not yet been claimed.
## 2026-08-01 — Resolve G006/G007 review blockers with G008 ownership fixes
- **Modify** `task034`, M13, developer documentation, changelog, and
postmortems with the G007/G008 ownership and backend-contract corrections.
- **Review blockers resolved:** G006 cleanup's independent review found that
shallow semantic-plan copies left nested line/gap/glue payloads mutable
through cache hits, and that malformed non-nil C results could still be
treated too softly. G007 changed the semantic-plan cache boundary to return
consumer-owned copies of every plan-owned mutable payload: source string,
context, boxes, offsets, lines, glues, gaps, and signatures. `para` remains
intentionally shared because paragraph-cache ownership and append identity
rely on that object. G007 final review then found one remaining alias:
`copy-tree` did not copy strings inside the returned context, so mutating a
dynamic policy suffix from the returned plan could poison the cached context.
G008 resolves that final blocker by using the recursive context copier for
cons/vector/string payloads both when the context snapshot/cache key is
created and when a plan is returned.
- **Backend contract recorded:** only a nil whole C result or nil per-item
breaks may fall back to Elisp. Any malformed non-nil single or batch output
signals `ekp-backend-contract-error`. The C entry remains 15 arguments and
the batch payload remains 15 fields.
- **Hard-atom boundary recorded:** the ordinary boundary immediately before a
following explicit `ekp-no-break` atom is forbidden before DP. This direct
pre-DP adjacency rule is separate from the final-pass emergency-stretch
model and is locked by a focused regression.
- **Verification recorded:** formal resize parity is true; core baseline
p50/p95 is 36.595/48.357ms and candidate p50/p95 is 23.266/36.363ms, for
36.42%/24.80% gains. Resize baseline p50/p95 is 41.443/53.541ms and
candidate p50/p95 is 23.761/36.889ms, for 42.67%/31.10% gains. ERT passes
268/268; property fuzz passes 300/300; release gates pass; byte-compiled
current public path records zero GC with C append/hard p99
1.440-1.464/2.022-2.050ms and Elisp append/hard p99
1.648-1.687/2.136-2.335ms, all below 16ms.
- **Behavior/Risk:** G007 resolved the first recorded G006 review blockers,
and G008 resolves the final string-leaf context blocker, but this change
record does not claim final independent APPROVE/CLEAR. `issue021` remains
open pending user visual confirmation, and historical `issue018` remains
open.
## 2026-08-02 — Record final independent review clearance
- **Review remediation:** the first final code-review pass found warning-only
defects in changed test fixtures and a stale C README setter example. The
fixtures now byte-compile with warnings as errors, and the README documents
the optional eighth `EMERGENCY-STRETCH` argument.
- **Verification:** the remediation-focused ERT passes 5/5, full ERT passes
288/288, every changed test file compiles warning-clean, and diff-check
passes.
- **Independent verdicts:** the code-reviewer re-review returns `APPROVE` with
zero findings; the subsequent architect review returns `CLEAR` and confirms
core K-P ownership, content-independent final-pass reachability, Elisp/C
parity, stable ABI, cache ownership, and complete nonempty GUI oracles.
- **Behavior/Risk:** `task034` and its independent developer gate are
complete. `issue021` closed after the user's 2026-08-02 visual
confirmation, and historical `issue018` remains open.

View File

@ -0,0 +1,33 @@
# Change Log: Core Rule Ownership 2026-07-28
## task014
- **Modify**`ekp.el`, `ekp-region.el`
- Centralized the edge-space exclusion used by both Elisp DPs, C-result
reconstruction, and line-glue rendering in one inline pure rule.
- Centralized the five lossless layout marker properties used by renderer
nonstickiness and region logical-string detection.
- **Modify**`tests/ekp-tests.el`
- Added an independent brute-force cross-check for every candidate
line's excluded edge-space width.
- Added a direct marker vocabulary/noninheritance contract.
- **Modify/Add** — developer/audit/changelog records and
`postmortem/20260728-core-rule-ownership.md`
- Recorded why two narrow owners have net value while a file/struct split
does not.
## Validation
- Direct-rule red/green: 0/2 → 2/2; focused related invariants: 4/4.
- Full ERT: 129/129; C/Elisp fuzz: 300/300.
- Warning-as-error production compilation, checkdoc, release, dictionary,
and diff gates: pass.
## Behavior and Risk
- Public behavior and C ABI are unchanged.
- The edge-space helper is `defsubst`; it adds no byte-compiled inner-loop
allocation.
- No wrapper ladder, module, or data object is introduced.

View File

@ -0,0 +1,57 @@
# Change Log: Dictionary Syntax and Provenance 2026-07-28
## Planning
- Added `task011` and a decision gate before parser changes: replacement
syntax could only be implemented if the fixed-width Elisp/C DP and
lossless renderer could represent it correctly.
## task011
- **Modify**`ekp-hyphen.el`, `ekp.el`
- Count and reject slash/replacement dictionaries with a typed,
cached condition.
- Propagate unsupported syntax through the public formatter while still
treating a missing optional dictionary as “hyphenation unavailable.”
- **Add/Modify/Delete**`dictionaries/`
- Added a 49-entry manifest with pinned source paths, SHA-256, syntax
counts, and license evidence.
- Replaced the moving/GNU-specific updater with fail-closed POSIX
`check` and deterministic `export` modes.
- Retained Basque explicitly as a verified legacy byte.
- Removed `hyph_sa_IN.dic`; the pinned upstream snapshot has no
authoritative license statement for that exact hyphenation data.
- **Add** — affected-language and inventory tests
- Locked libhyphen golden expectations for Hungarian, Catalan, and
Albanian, plus the Esperanto slash-pattern boundary.
- Added the offline 49-entry checksum/inventory/license/syntax gate.
- **Modify** — CI, release, public/developer/audit documentation
- Added offline and pinned-upstream dictionary gates.
- Replaced “every bundled dictionary works” with the exact supported
ordinary-pattern contract.
- **Add**`postmortem/20260728-dictionary-contract.md`
- Recorded why parser-only or renderer-only replacement support would be
incorrect and why the future boundary crosses the DP/C ABI.
## Validation
- Affected languages red/green: 0/2 → 2/2.
- Offline manifest: 49/49 pass.
- Pinned upstream check: 49/49 normalized bytes pass.
- Two fresh exports: byte-identical, 49 dictionaries each.
- Full ERT: 121/121.
- Warning-as-error byte compilation: pass.
- Production checkdoc, updater/check shell syntax, workflow YAML, and diff
checks: pass.
## Behavior and Risk
- `eo`, `ca`, `hu_HU`, and `sq_AL` now signal instead of silently using a
linguistically incomplete pattern subset.
- Forty-five ordinary-pattern dictionaries remain directly usable.
- Sanskrit support is deliberately removed until exact license evidence
exists; no compatibility shim or unverified license assumption remains.

View File

@ -0,0 +1,74 @@
# Change: Editing-Frontier Stability 2026-07-29
## 2026-07-29 — Implement and developer-verify task029
- **Modify** `ekp-buffer.el`.
- Adds a source-relative `frontier` marker to live state.
- Derives the active semantic line from the latest real edit, relocates
it in `after-change`, and preserves it through width/font/context
reflow.
- Deletes point-driven live-boundary publication. Point motion inside
the active hard line is display read-only; leaving the hard line keeps
the existing static completion transition.
- Keeps core DP semantics, `ekp.el`, C ABI, schema, plan contract, source
characters, and no-overlay ownership unchanged.
- **Modify** `tests/ekp-buffer-tests.el`,
`tests/ekp-gui-verify.el`, and
`tests/ekp-buffer-live-bench.el`.
- Locks exact property/state/source/undo identity and zero
plan/cache/property writes across backward/forward point motion.
- Proves reflow and deferred IME completion preserve the source-edit
frontier even when point moves elsewhere.
- Updates GUI and benchmark adapters to assert projection, plan,
generation, cache, active-index, and frontier stability.
- **Modify** bilingual user/developer docs, spec, plan, technical
reference, issues, changelog, and superseding postmortem to use the same
source-edit-frontier vocabulary.
- **Verification:** focused regressions RED before implementation and
GREEN afterward; independent focused ERT 10/10; buffer ERT 93/93;
default, seed-`20260729`, and isolated ERT 182/182; C/Elisp fuzz 300/300;
warning-as-error compilation, checkdoc, pinned package-lint, release,
dictionary, pinned-source, no-overlay, no-stale-symbol, and diff gates
pass. Point-motion p99 is 0.033 ms on C and 0.037 ms in the independent
Elisp review, both with zero planner/cache calls.
- **GUI evidence:** reviewed run
`/private/tmp/ekp-frontier-live-v3-66WYRW` contains 39 manifest lines and
a 26.6-second recording. All checkpoints and assertions pass, no black
segment is detected, and the final evidence verdict is PASS.
- **Review:** independent architecture review is CLEAR. Independent code
review reports zero blockers; the one residual performance concern is
separately owned by `issue018`/`task030`.
- **Status:** task029 developer gate complete. `issue016` and `issue017`
remain open until the user personally confirms the visible editing
experience.
## 2026-07-29 — Record the independent append-performance debt
- **Add** `issue018` and `task030` after the current checked-in 80-pixel
benchmark failed to reproduce task028's 6.399 ms append p99.
- **Add** `postmortem/20260729-narrow-live-append-replanning.md` to keep
unique-source-state planning cost separate from task029's point-motion
correctness fix.
- **Evidence:** repeated GC-excluded C-backend runs record roughly
3385 ms append p99, 291 plan calls, and zero cache hits. The latest run
recorded 84.526 ms. In the same run, point motion recorded 0.033 ms p99
with zero plan/cache calls.
- **Behavior/Risk:** documentation only. No performance shortcut or core
DP change is included in task029.
## 2026-07-29 — Plan task029
- **Add** `issue017`: point-only motion currently mutates an already
published semantic live prefix.
- **Add** `task029`: move live-boundary ownership from transient point to
the latest real source edit, while preserving whole-hard-line planning.
- **Modify** the current spec, plan, technical reference, and `issue016`
expectation so point is not a plan or projection invalidation input.
- **Add** `postmortem/20260729-editing-frontier-not-point.md` as the
superseding decision for the point-driven portion of `task028`.
- **Validation planned:** focused RED/GREEN public-hook ERT, complete
buffer/default/random/isolated suites, fuzz/static gates, live
benchmark, GUI dynamic verification, full diff review, and independent
code/architecture review.
- **Status:** Planning complete; superseded by the implementation record
above.

View File

@ -0,0 +1,53 @@
# Change Log: Final Cleanup and Independent Gate 2026-07-28
## task015
- **Delete/Modify** — changed runtime and test files
- Deleted nine unused one-line paragraph accessors and routed remaining
callers directly to the owning `ekp-para` fields.
- Replaced the direct dependency on private
`buffer-substring--filter` with public `filter-buffer-substring`
dispatch under the prior filter binding.
- Kept DELETE lifecycle cleanup outside the temporary dispatch binding so
ownership restoration observes the real buffer-local slot.
- **Review** — fallback-like paths
- Classified language, display, optional-resource, build-command, C
allocation/no-result, sequential-pool, and test-harness paths.
- Retained only documented external-boundary compatibility/fail-safe
behavior with tests and visible failure evidence.
- Found no masking fallback, swallowed business-logic error, broad shim,
or escalation candidate.
- **Modify/Add** — phase, audit, postmortem, and final-gate records
- Documented public filter dispatch ownership in
`postmortem/20260728-public-filter-dispatch.md`.
- Recorded the full post-clean verification and independent review
evidence.
## Validation
- Default and seeded-permuted ERT: 130/130 each.
- Fresh-process isolation: every one of 130 ERT tests passed.
- C/Elisp fuzz: 300/300.
- Production byte compilation with warnings as errors and checkdoc: pass.
- Pinned package-lint commit
`35996f478d81e51dae4fa30d051f741895d07399`: pass.
- Portable, native, debug, and sanitize C profiles: warning-free; portable
artifact restored.
- Release gate, offline dictionary 49/49, pinned-upstream dictionary 49/49,
shell/YAML, diff, private-API, dead-accessor, secret, and Markdown-link
checks: pass.
- Live GUI matrix: 7/7 with a clean fullscreen one-window screenshot.
- Independent code review: 98 paths, zero findings, `APPROVE`.
- Independent architecture invariant review: `CLEAR`.
## Behavior and Risk
- Cleanup reduced indirection and removed a private dependency without
changing public behavior, saved-file formats, C ABI, dependencies, or the
Emacs 29.1 baseline.
- No remote push, tag, publication, release artifact, or production action
was performed.
- Optional product expansions in audit section 4.4 remain demand-gated
future directions, not incomplete repository defects.

View File

@ -0,0 +1,91 @@
# Change: Final-Pass Emergency Stretch 2026-08-02
## 2026-08-02 — Close issue021 after user visual confirmation
- **Modify** the global issue index and issue detail to close `issue021`.
- **User confirmation:** the user accepted the visible result with “可以了,
提交吧” and requested the commit.
- **Behavior/Risk:** no runtime change; this closes the required user-visible
acceptance gate after the developer, dynamic GUI, and independent review
gates had already passed.
## 2026-08-02 — Complete task036 with TeX active-path preservation
- **Modify** `ekp.el` and `ekp_c/ekp_kp.c`: keep strict K-P unchanged; use
fixed `ekp-emergency-stretch-pixel` for ordinary final-pass underfull
candidates; and, only when an overfull candidate would otherwise extinguish
the final active path to a breakpoint, install the best provisional path
with tight fitness and zero incremental demerits.
- **Delete** the wrong-layer rule that forbade an otherwise legal boundary
immediately before an explicit hard atom. Hard atoms forbid only their
interior breaks; an overwide atom remains intact but need not stand alone.
- **Modify** the renderer to distribute the chosen line's actual rest using
its actual TeX glue-set proportions. Elisp 1D, looseness/parshape, and C
behavior match; the public 15-field/15-argument C contract is unchanged.
- **Strengthen tests** so isolated-CJK oracles require a nonempty plan that
exactly covers the source, preventing an empty result from false-greening.
- **Review remediation:** make the changed test fixtures warning-clean under
`byte-compile-error-on-warn` and document the optional eighth
`EMERGENCY-STRETCH` argument in the C README API example.
- **Verification:** focused root regressions 8/8, emergency selector 10/10,
core/buffer/GUI oracle 8/8, full ERT 288/288, seeded and isolated suites
279/279, fuzz 300/300, warning-clean portable/native/debug/sanitize C
builds, warning-as-error production and changed-test Elisp compilation,
checkdoc/package/static/release gates, formal performance gates, and
reviewed 42.78-second dynamic GUI evidence at
`/tmp/ekp-g009-evidence-retry.UOpPNp` with `VERDICT=PASS`. Final independent
code review returns `APPROVE` and architecture review returns `CLEAR`.
- **Behavior/Risk:** no CJK-orphan, unit, atom-adjacency, or screenshot
heuristic exists. `task036` is complete; `issue021` closed after the
user's 2026-08-02 visual confirmation.
The entries below are historical steps. Their hard/atomic fixed-cost wording
was superseded by the completed task036 semantics above.
## 2026-08-02 — Open task036 after task035 full-regression failure
- **Modify** `task035` to record the line-width-sized emergency stretch as a
falsified attempt, not accepted current behavior.
- **Add** `task036` for TeX-style fixed-dimension final-pass emergency
stretch. The implementation must expose `ekp-emergency-stretch-pixel`
where nil auto-resolves to roughly three display-font `M` widths and a
non-negative integer fixes the pixel value.
- **Preserve** the constraints: strict pass unchanged, 15-field C paragraph
ABI unchanged, renderer semantics synchronized, fixed artificial emergency
transition only for truly overfull first permitted hard/atomic runs, and no
CJK-orphan/unit/screenshot-specific penalty.
- **Verification planned:** five new RED cases for fixed dimension behavior,
nil auto, integer override, C parity without ABI growth, and renderer
width/glue consistency; the three regressions broken by task035; focused
G009 GUI/core/public-buffer/C parity; final fullscreen dynamic GUI evidence;
full ERT/fuzz/build/static/release gates.
- **Behavior/Risk:** Documentation records the next atom of work only.
Runtime behavior is not claimed fixed by this entry.
## 2026-08-02 — Close task035 as falsified
- **Modify** core K-P documentation to state the attempted final-pass model:
strict pass unchanged; final pass gives ordinary underfull candidates a
line-width-sized finite background emergency stretch; those candidates
still use adjustment ratio, badness, fitness, and demerits.
- **Modify** user-facing hard-atom documentation to avoid claiming a
hard-coded fullest-prefix fallback. A hard atom may occupy one overflow
line, while the ordinary prefix remains a normal K-P decision with finite
emergency stretch.
- **Modify** `issue021`, `task035`, the spec, plan, changelog, developer
docs, C README, and global change/issue indexes to record the corrected
owner and current acceptance boundary.
- **Add** `postmortem/20260802-final-pass-emergency-stretch.md` to explain
why the ordinary-underfull fixed fallback was the wrong layer, why the
TeX-style emergency-stretch model is used, and why the earlier 84px oracle
was invalid.
- **Verification:** Focused core single-CJK regressions pass 2/2. The
implementation lane reports focused G009 core/C/hard-atom/buffer/GUI-oracle
coverage 18/18, warning-as-error byte compilation pass, and C build pass.
Full regression then falsified the line-width stretch detail and forced the
`task036` fixed-dimension follow-up. This documentation pass ran stale-text
search and `git diff --check`.
- **Behavior/Risk:** `task035` is not current truth. No public configuration,
C ABI, or renderer ownership change is accepted by this record. `issue021`
remains open until task036 and the final user-visible visual acceptance gate
pass.

View File

@ -0,0 +1,32 @@
# Change Log: Fail-Closed GUI Verification 2026-07-28
## task007
- **Modify**`tests/ekp-gui-verify.el`
- Returned structured per-case results.
- Centralized table formatting and pass/fail aggregation.
- Exited with status 1 after reporting any batch failure.
- **Add/Modify** — GUI ERT tests and test loaders
- Added forced-failure and success controls.
- Included the batch-safe GUI boundary tests in normal, permuted, and
isolated runners.
- **Modify** — public, developer, audit, phase, and postmortem records
- Clarified the tests-only loading boundary and nonzero contract.
## Validation
- Focused red: forced-failure control 0/1.
- Focused green: failure/success controls 2/2.
- Default full ERT: 110/110.
- Non-default permuted ERT: 110/110.
- Live GUI matrix: 7/7 PASS, every row `over=0`.
- Screenshot `/tmp/emacs-kp-gui-20260728-clean.png` was inspected: one
fullscreen `*ekp-gui-verify*` window, no split, no scratch or client noise.
## Behavior and Risk
- Interactive matrix behavior remains a visible report buffer.
- Batch callers now receive a trustworthy status; this is intentional.
- The matrix remains a developer tool and adds no runtime dependency.

View File

@ -0,0 +1,39 @@
# Change Log: Hot-loop Performance 2026-07-28
## task012
- **Modify**`ekp-utils.el`
- Accumulate tokenizer box fragments in reverse and concatenate once when
a box is emitted.
- Preserve zero-width attachment and text-property behavior.
- **Modify**`ekp-hyphen.el`
- Distinguish cache misses from cached nil with an uninterned sentinel.
- Build dense inserted output from original word slices in one
concatenation.
- **Modify**`tests/ekp-tests.el`, `tests/ekp-bench.el`
- Add nil call-count, long propertized token, and dense insertion
regressions.
- Add reusable 1k8k adversarial builder measurements.
- **Modify/Add** — developer/audit/issue documentation and
`postmortem/20260728-linear-text-builders.md`
- Record the evidence threshold, measurements, ownership decision, and
rollback boundary.
## Validation
- Nil cache red/green: 0/1 → 1/1; focused behavior tests: 3/3.
- Source-mode 8,000-character tokenizer: 3.133 s → 1.100 s.
- Source-mode 8,000-character dense insertion: 0.945 s → 0.013 s.
- Full ERT: 124/124; C/Elisp fuzz: 300/300.
- Warning-as-error production byte compilation and production checkdoc:
pass.
## Behavior and Risk
- Public tokenization and hyphenation output is unchanged.
- The cache now retains negative results until its existing invalidation
boundary.
- The implementation adds no builder abstraction or dependency.

View File

@ -0,0 +1,37 @@
# Change Log: Composable Buffer Integration Lifecycle 2026-07-28
## task005
- **Modify**`ekp-region.el`
- Saved and restored the previous local/inherited substring-filter owner.
- Composed prior filtering with EKP logical inversion while retaining
DELETE behavior.
- Split structural unjustify from the public lifecycle boundary.
- Removed integrations when the final span disappears outside auto mode,
including external delete paths.
- **Modify**`tests/ekp-region-tests.el`
- Added local/inherited filter restoration, copy and DELETE composition,
final-unjustify cleanup, and auto-mode-disable cases.
- **Modify** — public, developer, audit, and phase documentation
- Documented composition order and lifecycle ownership.
- Closed `issue003` and added
`postmortem/20260728-buffer-integration-ownership.md`.
## Validation
- Focused red: 0/2 for prior-filter output and final-span cleanup.
- Focused green: 2/2; expanded integration matrix 5/5.
- Full ERT: 107/107.
- C/Elisp fuzz: 300/300.
- Byte compilation with warnings as errors: clean.
- checkdoc: clean.
## Behavior and Risk
- Existing mode/user substring filters remain active under EKP.
- Manual unjustify, DELETE, and mode shutdown restore exact ownership.
- A prior filter that deliberately strips all text properties also removes
EKP's structural markers; such a filter owns that returned representation.
- No dependency, public API, or saved-file format changed.

View File

@ -0,0 +1,36 @@
# Change Log: Interactive Protection Workflow 2026-07-28
## task013
- **Modify**`ekp-region.el`
- All four protection commands now share one property/feedback owner.
- Interactive calls report the affected character count and
current-buffer-session lifetime.
- Auto-justify mode help and a standard EKP menu expose the existing
format, protection, removal, and diagnostic commands.
- **Modify**`tests/ekp-region-tests.el`
- Drive no-break and verbatim mark/clear commands through
`call-interactively`.
- Verify behavior through the real public formatters, visible feedback,
mode help, and menu discovery.
- **Modify/Add** — bilingual README, changelog, audit and
`postmortem/20260728-session-local-protection.md`
- State that manual text properties do not survive plain-text save/reopen
and route persistent syntax through faces/predicates.
## Validation
- Focused public-path red/green: 0/3 → 3/3.
- Full default and seeded-permuted ERT: 127/127 each.
- Fresh-process isolation: every one of 127 ERT tests passed.
- Warning-as-error production byte compilation, checkdoc, release,
dictionary, and diff gates: pass.
## Behavior and Risk
- No file format, persistent metadata, or global key binding is added.
- Programmatic callers remain quiet unless they request the optional
announcement; interactive callers always receive feedback.
- Existing commands and arguments remain compatible.

View File

@ -0,0 +1,28 @@
# Change Log: Logical Text API Issue 2026-07-28
## issue011
- **Add**
`.phrase/phases/phase-kp-overhaul-20260726/issue_logical_text_api_20260728.md`
- Recorded that direct Elisp buffer APIs observe EKP's physical layout
representation.
- Captured the verified physical-vs-logical extraction evidence, root
cause, required outcome, and unresolved architecture questions.
- Explicitly deferred implementation and avoided selecting a design or
creating an execution task.
- **Modify**`.phrase/docs/ISSUES.md`, `.phrase/docs/CHANGE.md`
- Added the open issue and linked this documentation-only change.
## Behavior and Risk
- Runtime code and behavior are unchanged.
- No implementation plan, compatibility promise, or solution architecture
was approved.
- The issue remains open until the user chooses to resume design work.
## Validation
- Confirmed `issue011` is the next unused global issue ID.
- Confirmed the issue index link resolves to the phase detail.
- `git diff --check` passes.

View File

@ -0,0 +1,35 @@
# Change: Narrow Live-Append Latency 2026-07-30
## 2026-07-30 — Implement exact incremental live commits
- **Modify** `ekp.el`: retain prepared paragraph and 1D-DP state in layout
plans, retokenize only the last incomplete word plus appended suffix,
extend prefix/break/protrusion data from the first dirty box, resume Elisp
DP from the earliest state that can reach the new tail, and reuse unchanged
layout-line records.
- **Modify** `ekp-buffer.el`: reconstruct only the live dirty island, attempt
exact append planning before a full plan, preserve cache identity, and
release the old hard-line transaction before the next paragraph becomes
live.
- **Modify** `ekp_c/ekp.c`: validate signed 32-bit values with one
`extract_integer` call instead of three Lisp predicate/comparison calls.
- **Add** exact append/fallback, multi-engine, unsafe-context,
stable-boundary, dirty-island, and invalid-C-vector regressions, plus a
frozen baseline/candidate evaluator covering five widths, four paragraph
lengths, C/Elisp, and two GC modes.
- **Performance:** the four-round source-instrumented evaluator reduces
80-pixel C p95/p99 from 114.717/119.201 ms to 25.490/25.785 ms and Elisp
from 588.017/597.093 ms to 43.860/47.578 ms. Ordinary keys remain a
zero-work path at 0.595 ms p99 and all widths avoid regression.
- **Production check:** with production files byte-compiled, three repeated
public-command runs measure append p99 at 1.1581.326 ms for C and
1.4291.438 ms for pure Elisp; hard-boundary p99 is 1.2511.363 ms and
1.4571.470 ms respectively.
- **Decision:** retain the C module. Rust would cross the same Emacs module
ABI and cannot remove the Elisp-owned tokenization, measurement,
transaction, or publication work. Rewriting the already sub-millisecond C
layer would add Cargo and cross-platform release surface without a
measured end-to-end gain.
- **Open gate:** the locked source-instrumented evaluator still exceeds its
absolute 16 ms C/Elisp target, so `task030` and `issue018` remain open even
though the byte-compiled production path is within budget.

View File

@ -0,0 +1,44 @@
# Change: Rigid Inline Atom Break Cascade 2026-08-01
Superseded note: this file records the earlier task033 repair. The current
ordinary-underfull final-pass model is recorded in
`change_final_pass_emergency_stretch_20260802.md`; an overwide atom remains
intact but is not guaranteed a standalone line, and atom adjacency has no
special break rule.
## 2026-08-01 — Implement and developer-verify task033
- **Modify** `ekp.el` so the emergency pass preserves its established first
transition but adds a fullest-underfull prefix fallback only when a
forbidden break run jumps directly to an overfull permitted candidate.
- **Modify** `ekp_c/ekp_kp.c` with the same state transition and keep the
looseness/parshape Elisp path in parity.
- **Add** core, public-buffer, and GUI regressions covering diverse CJK
prefixes, exact source boundaries, atom integrity, Elisp/C parity, and
repeated 480→280→340→280 reflow.
- **Modify** user/developer documentation and add
`postmortem/20260801-forbidden-run-emergency-boundary.md` to record why the
broader fullest-prefix replacement was rejected.
- **Verification:** RED reproduced boundary 1 instead of 11. GREEN passes
normal, random-order, and isolated 201-test runs; 300 C/Elisp fuzz cases;
warning-as-error Elisp compilation; package-lint; checkdoc; release and
dictionary gates; warning-clean debug/sanitize/portable C builds; and
reviewed dynamic GUI evidence at
`/private/tmp/ekp-atom-gui-final-Beg8hb` with verdict PASS.
- **Behavior/Risk:** Strict K-P, ordinary emergency layouts, buffer
projection, source text, and public APIs are unchanged. The additional
relaxation is limited to emergency scans that cross forbidden break
positions before an overfull candidate.
## 2026-08-01 — Plan issue021 and task033
- **Add** `issue021` with the 280px showcase reproduction and deterministic
width-40 batch analogue.
- **Add** `task033` and M12 acceptance gates for the core emergency-break
correction, Elisp/C parity, and real GUI verification.
- **Modify** the text-property layout spec to require a full ordinary prefix
line and one intact rigid atom line rather than one-glyph emergency
fragmentation.
- **Behavior/Risk:** Planning records only. Runtime behavior is unchanged.
The change will affect only the second pass used after strict K-P has
already proved the paragraph unreachable.

View File

@ -0,0 +1,161 @@
# Change: Post-Audit Hardening 2026-08-20
## 2026-09-01 — Restore Emacs 31.1 strict byte compilation
- **Modify** `ekp.el`, `ekp-utils.el`, and `ekp-hyphen.el` by replacing six
obsolete single-binding `if-let`/`when-let` forms with the behavior-identical
starred variants.
- **Modify** `tests/ekp-live-commit-evaluator.el` in the same mechanical way so
maintained performance tooling is strict-compile clean too.
- **Add** `issue029` and close `task044` with exact environment and evidence.
- **Behavior/Risk:** no data flow, branch, public API, layout, hyphenation,
font, module-path, or evaluator behavior changes.
- **Evidence:** root `make`; Emacs 31.1 WERROR production and changed-tool
compilation; normal and seed-20260901 random ERT 296/296; C build; 300-case
parity fuzz; release invariants; local 49-entry dictionary manifest/hash;
checkdoc; and diff-check. The user explicitly excluded the network-backed
fixed-upstream dictionary fetch, so it is not claimed.
## 2026-08-31 — Restore the Emacs 31 fresh-source baseline
- **Modify** `ekp.el` and `ekp-buffer.el` so layout and projection marker
properties update the toplevel default of Emacs 31's automatically
buffer-local `text-property-default-nonsticky`; new and existing buffers
that have no explicit override now inherit the nonsticky protocol.
- **Modify** `tests/ekp-tests.el` and `tests/ekp-buffer-tests.el` so policy
ownership fixtures remain mutable multibyte strings while mutating ASCII
code points that Emacs 31 permits in place. The tests still prove that
cache keys own the original policy values.
- **Behavior/Risk:** no layout algorithm or public policy changes. This fixes
marker noninheritance in real buffers and removes an Emacs 31-only fixture
mutation error.
- **Evidence:** focused ERT 3/3; source-first normal and seed-20260831 random
ERT 296/296; source load, release invariants, 49-entry dictionary manifest,
and pinned dictionary update checks pass.
## 2026-08-20 — Lock follow-up goals and task boundaries
- **Add** `plan_post_audit_hardening_20260820.md` with the evidence-backed
scope, non-goals, acceptance gates, and stop condition for the post-audit
defects.
- **Add** `task_post_audit_hardening_20260820.md` with `task037``task042`
and the remaining `task030` performance gate.
- **Add** issue records for each user-visible or public-contract defect before
implementation, including `issue028` for the stale-bytecode evaluator
false-green.
- **Behavior/Risk:** documentation-only planning. Closed historical issues
remain closed; `issue018` remains open until its locked gate is met.
## Verification
- Next task ID is `task037`; no task ID is reused.
- The plan explicitly separates buffer ownership, live semantics, dictionary
lookup, C validation, test gates, integration lifecycle, and performance.
- No production or test source is changed in this planning pass.
## 2026-08-20 — Implement correctness and gate hardening
- **Modify** `ekp-buffer.el` and `tests/ekp-buffer-tests.el` for preflight
width validation, backward-delete live stability, and no-projection
integration cleanup (`task037`, `task038`, `task042`; `issue022`,
`issue023`, `issue027`).
- **Modify** `ekp-hyphen.el` and `tests/ekp-tests.el` for exact normalized
locale lookup (`task039`, `issue024`).
- **Modify** `ekp_c/ekp.c` and `tests/ekp-c-tests.el` for sorted, unique,
in-range break-position validation in direct and batch APIs (`task040`,
`issue025`), with the 15-field ABI unchanged.
- **Modify** the showcase, source loader, test runners, Windows CI entry,
and complete ERT selection to remove stale-bytecode false greens and
restore automatic inline-code verification (`task041`, `issue026`).
- **Modify** the live evaluator script/bootstrap to load source from the
selected baseline/candidate root (`task030`, `issue028`). The corrected
narrowed matrix still exceeds the source 16 ms target, so `issue018` and
`task030` remain open.
- **Commit:** `6a8c7e0`.
## 2026-08-20 — Record source-fresh performance boundary
- **Add** `postmortem/20260820-source-fresh-live-commit-gate.md` to explain
why the stale-bytecode evaluator result was invalid and why the corrected
source gate remains an open `task030` performance target.
- **Evidence:** width-80/two-row/GC-excluded source candidate p95/p99 are
23.294 ms (C) and 78.593 ms (Elisp), with parity, zero-work, GC,
conflict, and non-regression checks true. The high-row full matrix was
stopped after a source DP cell exceeded two minutes of CPU; it is not
claimed as complete evidence.
- **Commit:** `60b299b` and `postmortem` follow-up commit.
## 2026-08-20 — Attribute incremental append cost and preserve raw evidence
- **Modify** `tests/ekp-live-commit-evaluator.el` to time
`ekp-layout-plan-append` and `ekp--dp-cache-append` separately in each raw
sample and report them beside total/publication timings.
- **Modify** `tests/run-live-commit-evaluator.sh` to write each run into a
unique raw directory and atomically replace the report after comparison.
- **Evidence:** source-fresh width-80/two-row/GC-excluded candidate p95 is
17.161 ms C append / 1.187 ms C append-DP, and 73.293 ms Elisp append /
57.063 ms Elisp append-DP; raw JSONL is nonempty and parity/zero-work/GC/
conflict/non-regression remain true. No production optimization is claimed
yet because the measured DP/append owners require an exact redesign.
- **Commit:** `db6e6b9`.
## 2026-08-20 — Split append preparation, assembly, and DP timings
- **Modify** the evaluator to time `ekp--append-para` and
`ekp--layout-plan-from-para` independently, fixing a wrapper-name collision
found by the first red run.
- **Evidence:** latest source-fresh candidate p95 is C append/append-DP
16.953/1.015 ms and Elisp append/append-DP 71.896/55.970 ms; both engines
spend about 9.753 ms in append preparation and 5.663 ms in plan assembly.
The report remains red, raw JSONL is nonempty, and the wrapper itself now
passes the same matrix without argument errors.
- **Commit:** `01001b9`.
## 2026-08-20 — Implement user-selected native live-append backend
- **Modify** `ekp-buffer.el` and `ekp.el` to let prepared automatic live
append use the loaded C 1D DP when `ekp-auto-justify-native-append` is
non-nil, without changing ordinary full-layout dispatch or the 15-field C
ABI. Native-unavailable and option-disabled paths remain Elisp.
- **Add** `adr_native_live_append_backend_20260820.md`, bilingual README
guidance, evaluator backend metadata, and a public buffer regression proving
the setting toggles the bridge.
- **Verification:** native bridge/parity regression 1/1 and source-first ERT
296/296 pass. In the bounded all-width, 2/4/8/16-row, GC-excluded
source-fresh round, the candidate width-80 C p95/p99 was
14.190/16.495 ms and the Elisp-configured live path (native append backend)
was 13.484/14.334 ms. Layout parity, zero-work, GC, conflict, and width
non-regression checks passed; the C p99 remains just above the locked
16 ms target, so `issue018/task030` stays open.
- **Commit:** `3d3dda6`.
## 2026-08-20 — Close the locked source-fresh live-append gate
- **Verify** the complete four-round evaluator after the native backend change:
every baseline/candidate round loads its own source root across 64/80/96/
128/160 px, 2/4/8/16-row fixtures, C/Elisp engines, and default/excluded
GC.
- **Evidence:** width-80 candidate C p95/p99 is 12.010/14.622 ms and the
Elisp-configured live path (native append backend) is 10.980/11.194 ms;
paired improvements are 77.72/74.03% and 94.19/94.57%. Ordinary-key p99 is
0.551 ms. Layout parity, zero-work, GC exclusion, conflict-free, and
all-width non-regression checks are true. The evaluator then passes full
source-first ERT 296/296, fuzz 300/300, and release checks.
- **Result:** Mark `task030` and `issue018` resolved; retain the raw samples
under `.omx/goals/performance/narrow-live-commit/raw/run.bxULD9` and the
JSON report at `.omx/goals/performance/narrow-live-commit/latest-report.json`.
- **Commit:** this closure-record commit.
## Verification
- Source-first normal ERT: 296/296.
- Source-first seeded random ERT: 296/296, including all nine GUI verifier
tests previously omitted by the name filter.
- Per-test isolated ERT: 294/294 process runs logged `0 unexpected`; the
later batch-position test is covered by focused isolated C ERT.
- C focused ERT: 15/15; C portable build warning-clean; source fuzz:
300/300 with zero failures; checkdoc, release, dictionary, and shell gates
pass.
- Complete source evaluator: parity, zero-work, GC, conflicts, and width
non-regression pass; width-80 C/Elisp p99 is 14.622/11.194 ms. The formal
performance issue is closed.

View File

@ -0,0 +1,45 @@
# Change Log: Property-Sensitive Paragraph Fast Path 2026-07-28
## task016
- **Modify**`ekp.el`
- Store the complete structural paragraph key beside the most-recent
string and paragraph.
- Compare the same key used by the paragraph hash cache before reusing the
fast-path result.
- Delete six style-variable watchers that compensated for the former
partial identity.
- **Modify**`tests/ekp-tests.el`
- Warm one string object, mutate its `ekp-no-break` property in place, and
require the next lookup to match a fresh property-aware paragraph for
CJK and Latin-with-space inputs.
- Retain distinct-string property, language, style, and unchanged-key hit
controls.
- **Modify/Add** — README, developer, audit, issue, phase, and postmortem
records
- Document one cache-identity owner and close `issue010`.
- Record the wrong-layer compensation in
`postmortem/20260728-paragraph-fast-path-identity.md`.
## Validation
- Same-object property mutation red/green: 0/1 → 1/1.
- Focused paragraph-cache matrix: 6/6.
- Default and seeded-permuted full ERT: 130/130 each.
- Fresh-process isolation: every one of 130 ERT tests passed.
- C/Elisp fuzz: 300/300.
- Warning-as-error production compilation, checkdoc, pinned package-lint,
four C profiles, release/dictionary/static gates, and GUI matrix 7/7:
pass.
- Independent architecture review: `CLEAR` for the shared-key ownership.
## Behavior and Risk
- Public APIs, saved-file formats, C ABI, and Emacs 29.1 baseline are
unchanged.
- Cache hits now require the complete text/configuration identity already
owned by `ekp--para-key`.
- The change removes invalidation branches instead of adding watchers or a
second identity model.

View File

@ -0,0 +1,43 @@
# Change Log: Release and CI Governance 2026-07-28
## Planning
- Added `task010` and locked the local/remote boundary, immutable input,
Windows, tracked-record, and version-pair acceptance criteria before code.
## task010
- **Add**`tests/check-release.sh`, `Docs/RELEASING.md`
- Added one executable repository-invariant gate and a distinct
credentialed release checklist.
- **Modify**`.github/workflows/ci.yml`
- Pinned checkout and Emacs setup actions to verified full upstream SHAs.
- Replaced live MELPA installation with a pinned package-lint checkout.
- Added an Emacs 30.1 Windows Elisp compile/ERT baseline.
- **Modify**`.gitignore`, contributor/public/audit documentation
- Made `.phrase` records versionable.
- Documented immutable release artifacts, checksums, and remote checks.
- Closed `issue009` without claiming an unperformed remote publish.
- **Add**`postmortem/20260728-release-governance.md`
- Recorded why local invariants and credentialed remote release evidence
remain separate boundaries.
## Validation
- Release gate red: floating refs, missing Windows/public ERT path, and
ignored `.phrase` were all reported.
- Release gate green: repository invariants pass.
- Pinned package-lint commit: pass with an empty package directory.
- Workflow YAML parse and POSIX shell syntax: pass.
- Default ERT: 119/119.
- `git diff --check`: pass.
## Behavior and Risk
- Full-SHA action pins intentionally require explicit maintenance updates.
- Windows CI covers the required pure-Elisp product baseline; the optional C
module remains exercised on Linux and macOS.
- No commit, tag, remote workflow, artifact, push, or publication was made.

View File

@ -0,0 +1,39 @@
# Change Log: Repository System Audit 2026-07-28
## 2026-07-28
- **Add**`Docs/REPOSITORY_AUDIT_20260728.md`
- Task: `task001`.
- Recorded the architecture baseline, verified strengths, four P1 defects,
seven P2 risks, three P3 opportunities, extension routes, non-goals, and
acceptance gates.
- Separated evidence, inference, and unknowns so future fixes can start
from reproducible facts.
- **Modify**`readme.md`, `readme_zh.md`
- Task: `task001`.
- Corrected the bundled dictionary count, C module version, ERT command
description, GUI verification loading instructions, and the cached
parameter-change caveat.
- Linked the dated repository audit.
- **Modify**`DEVELOPER.md`, `DEVELOPER_ZH.md`
- Task: `task001`.
- Linked the audit and removed stale hard-coded test counts.
- **Modify**`ekp_c/README.md`
- Task: `task001`.
- Corrected the documented malformed-argument behavior without changing
the C API.
- **Add/Modify**`.phrase` plan, task, issue, and index records
- Task: `task001`.
- Registered the audit and its unresolved follow-up work in the current
phase.
## Behavior and Risk
- Runtime behavior is unchanged.
- The report records implementation directions, not approved designs.
- Windows, remote CI, package-lint, sanitizer runtime loading, and a clean
GUI screenshot remain explicit validation gaps.

View File

@ -0,0 +1,38 @@
# Change Log: Non-mutating Save Serialization 2026-07-28
## task004
- **Modify**`ekp-region.el`
- Removed the before/after-save unjustify/rejustify transaction.
- Added one logical write-buffer owner using the documented
`write-region-annotate-functions` buffer-switch contract.
- Added bounded cleanup on success, retry, integration removal, and source
buffer teardown.
- **Modify**`tests/ekp-region-tests.el`
- Added real filesystem failure, forced encoding failure with successful
retry, and `quit` regressions through `save-buffer`.
- Preserved the existing successful logical-write assertion.
- **Modify** — public, developer, audit, and phase documentation
- Documented failure-safe visible behavior and closed `issue002`.
- Added `postmortem/20260728-nonmutating-save-serialization.md`.
## Validation
- Focused red: 0/2; failure and interruption both removed the layout.
- Focused green: 2/2.
- Save matrix: 4/4 for success, filesystem failure, encoding retry, and
interruption.
- Full ERT: 102/102.
- C/Elisp fuzz: 300/300.
- Byte compilation with warnings as errors: clean.
- checkdoc: clean.
## Behavior and Risk
- The display buffer is never unformatted during saving.
- Later write annotations and coding conversion operate on logical text.
- A failed write may retain one hidden logical copy until retry or teardown;
it never grows beyond one per source buffer.
- No global advice, saved-file format, public API, or dependency changed.

View File

@ -0,0 +1,76 @@
# Change Log: Semantic Live Prefix 2026-07-29
## 2026-07-29 — Implement and verify task028
- **Modify** `ekp-buffer.el`.
- Replaces native-row commitment with full-hard-line
`ekp-layout-plan` consumption and point-line semantic prefix
projection.
- Keeps the point-containing line and suffix natural; previous semantic
lines own their break whitespace and may reflow together.
- Adds a 16-entry buffer-local plan LRU, same-line point-motion fast
path, common-prefix differential publication, transactional rollback,
theme-enable/theme-disable/frame-font context invalidation, and
fail-closed oversized/conflict handling.
- Leaves `ekp.el`, core DP semantics, C ABI, DP schema, and plan records
unchanged for this task.
- **Modify** `tests/ekp-buffer-tests.el`,
`tests/ekp-gui-verify.el`, and **Add**
`tests/ekp-buffer-live-bench.el`.
- Covers public editing commands, point boundaries, earlier-break
revision, cache/history reuse, zero-write stable plans, whitespace and
hyphen ownership, IME, failure rollback, resize, narrowing, lifecycle,
exact source, and zero-overlay invariants.
- Default and seed-`20260729` ERT pass 181/181; all 181 tests pass in
independent Emacs processes; C/Elisp fuzz passes 300/300.
- Main dynamic GUI evidence is retained at
`/private/tmp/ekp-semantic-live-v4-vFZTkr`; split-window evidence is at
`/private/tmp/ekp-semantic-split-v3-uPwuOi`. Both reviewed temporal
reports return PASS with exact source, zero overlays, and `hscroll=0`.
- C-backend append p99 is 6.399 ms with GC excluded. Default raw p99 is
50.212 ms when samples include approximately 45 ms GC pauses; this
residual risk is recorded instead of changing global GC behavior.
- **Subsequent audit:** the 6.399 ms result is not reproducible with the
current checked-in 80-pixel benchmark. Repeated GC-excluded task029
runs measure roughly 3385 ms p99 with 291 unique plan misses.
`issue018`/`task030` now track that separate performance debt; this
historical entry is retained to show what the task028 run reported.
- **Modify** bilingual user/developer docs, `CHANGELOG.md`, `issue016`,
plan/technical records, and the superseding postmortem to match the
implemented semantic-prefix model.
- **Behavior/Risk:** The active edit suffix is now native while all earlier
semantic lines are one jointly revisable KP prefix. Automated and GUI
developer gates pass. Independent code review returns APPROVE and
independent architecture review returns CLEAR. `task028` is complete;
`issue016` remains open until the user personally confirms the visible
editing experience.
## 2026-07-29 — Plan task028
- **Add** `issue_semantic_live_prefix_20260729.md`.
- Records `issue016`: live editing currently freezes completed native
rows instead of repeatedly consuming the full hard-line KP plan and
projecting only the semantic prefix before point.
- **Modify** `task_repository_audit_20260728.md`.
- Adds `task028` as the next atomic implementation task with red tests,
performance checks, GUI dynamic verification, and user-confirmation
closure rules.
- **Modify** `spec_text_property_layout_engine_20260729.md`.
- Replaces the native-row live contract with the semantic hard-line
prefix contract: full hard-line plan input, point-line boundary,
prefix-only projection, natural point line and suffix, and no core
DP/C ABI changes.
- **Modify** `plan_text_property_layout_engine_20260729.md`.
- Adds milestone M7 for replacing native-row commitment with semantic
prefix projection while preserving the completed-paragraph DP owner.
- **Modify** `tech-refer_text_property_layout_20260729.md`.
- Supersedes the native progressive row model for live editing and
documents the buffer-local plan cache, line-signature diffing, and
fail-closed ownership rules.
- **Add** `postmortem/20260729-whole-hard-line-live-prefix.md`.
- Records why the native-row model was wrong for global KP alignment and
why the correction belongs in `ekp-buffer`, not in core DP.
- **Behavior/Risk:** Documentation and planning only. Runtime behavior is
unchanged. `issue015` remains open exactly as before pending user-visible
confirmation; `issue016` remains open until implementation and user
confirmation.

View File

@ -0,0 +1,56 @@
# Change: Stable Live Edit Transaction 2026-07-29
## 2026-07-29 — Implement and verify task031
- **Modify** `ekp-buffer.el`: replace frontier-owned immediate publication
with committed live state plus one baseline-snapshot edit transaction;
naturalize only the owning dirty span range; restore reversible edits
exactly; and publish only at native-row or structural commit events.
- **Modify** `ekp-buffer.el`: keep active live spans out of static lazy-reflow
chunks and recognize both forward and backward native-row crossings.
- **Modify** `tests/ekp-buffer-tests.el` and
`tests/ekp-gui-verify.el`: add RED/GREEN public-path coverage for zero-plan
same-row edits, local middle-row anchors, object-identical reversal,
forward/backward row crossing, zero-work point motion, lazy ownership,
stable yank/undo, resize, and hard completion.
- **Modify** bilingual user/developer documentation, changelog, spec, plan,
technical reference, task/issue records, and the decision postmortem to
make transaction triggers and signature-diff ownership explicit.
- **Verification:** buffer 99/99; default, seed-`20260729`, and isolated ERT
188/188 each; C/Elisp fuzz 300/300; warnings-as-errors production/test
compile; empty checkdoc; pinned package-lint exit 0; release and 49-entry
dictionary manifest pass. The earlier exact pinned-source run remains
applicable because dictionary bytes did not change; two redundant final
fetches failed with GitHub `early EOF`, not a byte mismatch.
- **Benchmark:** with GC excluded, cache-revisit and point-motion scenarios
perform zero plans; 291 appends contain 15 structural plans. Remaining
C/Elisp structural p99 spikes move to `issue018`/`task030`.
- **GUI evidence:** reviewed PASS at
`/private/tmp/ekp-stable-transaction-final5-2BFryc`: 48.95 seconds,
55 checkpoints, 17 assertions per checkpoint, completed run-end, one
fullscreen target window, no black segment, split, stale buffer, client
instruction, or transient blank frame.
- **Behavior/Risk:** ordinary editing is intentionally stability-first;
the current dirty row may be temporarily non-optimal until a structural
commit. Completed paragraphs still use the unchanged global KP core.
## 2026-07-29 — Plan issue019 and task031
- **Add** `issue019`: per-edit whole-hard-line replanning and suffix
invalidation share one overloaded frontier and cannot express stable local
editing or exact reversible restoration.
- **Add** `task031`: replace that model with committed projection, a dirty
edit transaction, preserved unaffected anchors, and atomic structural
commits.
- **Modify** `task030` to depend on `task031`; performance work must measure
the surviving path instead of optimizing the rejected control flow.
- **Modify** the current spec, plan, technical reference, issue index, and
decision history so trigger ownership is explicit: transactions decide
when layout may change, signature diffing decides what properties change,
and the unchanged core DP decides final layout.
- **Validation planned:** focused public-path RED/GREEN ERT; complete
automated/static gates; re-profiled live benchmark; temporal GUI evidence
for stable input, local middle-line editing, exact reversible restoration,
structural commits, and zero-work point motion.
- **Behavior/Risk:** this planning entry is superseded by the completed
implementation entry above.

View File

@ -0,0 +1,31 @@
# Change Log: ERT State and Order Isolation 2026-07-28
## task006
- **Modify**`tests/ekp-tests.el`
- Dynamically scoped and restored all isolated EKP tunables.
- Replaced the mislabeled internal predicate assertion with a public
parshape dispatch test and added a fixture-restoration control.
- **Add/Modify** — test runners and `.github/workflows/ci.yml`
- Added seeded permutation and fresh-process-per-test runners using public
ERT APIs.
- Added the reproducible permuted-order lane to CI.
- **Modify** — changelog, developer, audit, phase, and postmortem records
- Documented the commands, invariant, evidence, and `issue004` closure.
## Validation
- Focused red: isolated C-loaded parshape test 0/1.
- Focused green: public dispatch, fixture restoration, and C-parameter
cases 3/3; isolated parshape 1/1.
- Reproducibly permuted full ERT: 108/108.
- Every ERT in a fresh Emacs process: 108/108.
## Behavior and Risk
- Production code and public API are unchanged.
- CI gains one full ERT invocation on Emacs 30.1.
- The slower fresh-process runner is an explicit local/release diagnostic,
not part of every CI job.

View File

@ -0,0 +1,286 @@
# Change Log: Text-Property Layout Implementation 2026-07-29
## 2026-07-29 — Complete task027
- **Modify** `ekp-buffer.el`.
- Makes native soft wrapping a mode-owned precondition by temporarily
setting both `truncate-lines` and
`truncate-partial-width-windows` buffer-locally to nil before the
first reflow.
- Restores exact prior values and local/global ownership on normal
disable, major-mode teardown, and failed activation. An activation
error rolls back the full lifecycle and then remains visible.
- Centralizes installation and removal of the auto-mode hook set; no
source character, overlay, synthetic live break, or wrapping algorithm
was added.
- **Modify** buffer/GUI tests and bilingual user/developer documentation.
- Adds public lifecycle regressions for normal disable, forced reflow
failure, and major-mode teardown.
- Adds a deterministic 44-column split-window scenario and records the
native-wrap requirement in the spec, plan, issue, changelog, and
decision history.
- **Validation:** focused lifecycle ERT 3/3; buffer ERT 77/77; default,
seed-`20260729`, and isolated full ERT 166/166; C/Elisp fuzz 300/300;
warning-as-error compilation; checkdoc; pinned package-lint; release,
dictionary, pinned-source, no-overlay, stale-name, conflict, artifact,
and diff gates.
- **Dynamic evidence:** `/private/tmp/ekp-soft-wrap-final-pass-PIUigY`
retains a reviewed per-character Emacs 30.2 recording in an intentional
two-window frame. The 44-column selected window advances from one to two
visual rows with `hscroll=0`, exact source text, zero overlays, zero live
replacing breaks, and no pending edit. Manifest/report/recording/contact
SHA-256:
`53cd23f8e1be1f29545edb6daa80184d438ca87d4d7068a182faf691a8437ad8`,
`a97a74cc5a6255c2eb5992c266c727e3ed5d5556f37672ca319373a65a110e4d`,
`2b3f7c155d1f0e2feeca2bb79a86aafe612cc1489c486121491ec6268bc1de0a`,
`21a78ae849e3a1d5cda1d80ee375cd655e49b221b5192a219ac6ab11d9122d53`.
- **Behavior/Risk:** Narrow split windows now follow normal Emacs soft
wrapping while auto mode is active. A buffer that deliberately requested
truncation gets that exact setting back when the mode ends. `issue015`
stays open pending user-visible confirmation.
## 2026-07-29 — Plan task027
- **Add** `issue_native_soft_wrap_20260729.md`.
- Records `issue015`: Emacs's default narrow partial-window policy
horizontally truncates the active paragraph, so the native progressive
engine never observes a completed visual row.
- **Modify** phase spec, task list, and global issue/change indexes.
- Makes native soft wrapping an explicit auto-mode lifecycle
precondition with exact restoration on teardown.
- **Behavior/Risk:** Planning and red-test preparation only. Runtime
behavior is unchanged at this entry.
## 2026-07-29 — Complete task025 and task026
- **Modify** `ekp-buffer.el`.
- Deletes partial-KP live lookahead, stable anchors, line signatures,
convergence, and push/pull repair.
- Makes Emacs native redisplay own the active tail. EKP projects only
internal gap widths on completed native visual rows and reserves full
KP breaks, prefixes, and discretionary hyphens for completed hard
paragraphs.
- Invalidates a committed prefix before edits cross back into it, defers
IME composition, rejects stale generations, respects narrowing, and
keeps foreign display ownership isolated.
- **Modify** `ekp.el`, buffer tests, and GUI verification.
- Removes the now-unused partial live-flow API and replaces its
implementation-shaped tests with the approved three-state behavioral
contract.
- Adds dynamic actions for native wrap, deletion restoration, and hard
paragraph completion.
- **Modify** bilingual user/developer docs, phase spec/plan/tasks/issue,
changelog, and the live-layout decision history.
- Records the native progressive model as the current workflow without
rewriting the earlier partial-flow decision as if it never existed.
- **Validation:** focused live contract ERT 4/4; buffer ERT 75/75; default,
seed-`20260729`, and isolated ERT 164/164; fuzz 300/300;
warning-as-error compilation; checkdoc; pinned package-lint; release,
dictionary, pinned-source, ownership, no-overlay, stale-name, and diff
gates. Exact 164px ASCII/CJK probes and all seven GUI variants pass.
- **Dynamic evidence:** `/private/tmp/ekp-native-live-2dYu2U` retains a
14.82-second fullscreen single-window Emacs 30.2 recording. It proves
native mixed-text wrap with zero live owned breaks, exact deletion back
to the original three native rows, and full KP only after hard newline.
Every immediate/redisplay assertion passes; the recording contains no
black segment or non-Emacs frame. Manifest/report/recording SHA-256:
`cc3a626c7f8fb68f1ef1ea4ba0dd1e276281afee7a56fb88835f49bd86f239e1`,
`0be9337a95469c6fe710518dbfa27432b4abcd511f623182021776f911887ab0`,
`f87c3adbf16e5d17860471c1a85c89dc688b24c8e08ad82172291fc87483562e`.
- **Behavior/Risk:** Underfilled editing performs no layout planning and
looks native. Completed native rows receive gap-only alignment. A hard
paragraph receives full KP only after completion or exit. Native visual
row discovery still depends on one authoritative graphical window;
alternate-width simultaneous plans remain intentionally unsupported.
`issue014` stays open pending user-visible confirmation.
## 2026-07-29 — Plan task025 and task026
- **Add** `issue_natural_live_editing_20260729.md`.
- Records `issue014`: partial KP breaks and hyphens appear while typing
near the right edge.
- **Modify** the phase spec, plan, task list, and global issue/change
indexes.
- Replaces the live product contract with the approved three-state model:
native active tail, gap-only completed native rows, and full KP after
hard-paragraph completion.
- **Behavior/Risk:** Planning and red-test preparation only. Runtime
behavior is unchanged at this entry.
## 2026-07-29 — Complete task023 and task024
- **Modify** `ekp-buffer.el`.
- Restores the mark marker without activating it, then restores
`mark-active` as independent editor state.
- Keeps static paragraph-edge whitespace hiding off the active live line
and clears from the source span beginning when its first line becomes
active.
- **Modify** buffer tests and GUI verification evidence.
- Adds inactive/active mark, same-turn leading/trailing space and tab,
CJK space, deletion-exposed whitespace, consecutive space/backspace,
yank, newline, and undo regressions through public command paths.
- Retains the existing overflow/pullback, paragraph transition, resize,
IME, foreign-owner, teardown, and bounded-work matrix.
- **Modify** bilingual user/developer docs, phase spec/plan/tasks/issues,
changelog, and `postmortem/20260729-active-line-edge-state.md`.
- Records the editor-state and active-line ownership rules at their
durable documentation boundaries.
- **Validation:** focused red 0/3 → green 3/3; buffer ERT 79/79; full
default and seed-`20260729` ERT 170/170; isolated fresh-process ERT
170/170; C/Elisp fuzz 300/300; warning-as-error compilation; checkdoc;
pinned package-lint; release, dictionary, shell, artifact, stale-name,
no-overlay, and diff gates.
- **Dynamic evidence:**
`/private/tmp/ekp-interaction-clean-rdZhdD` records three actions:
showcase width change, one-space insertion, and deletion exposing that
space. Every immediate and redisplay assertion passes, with no region,
pending transaction, replacing display, overlay, black segment, or
non-Emacs window. Manifest/report/recording SHA-256:
`0eaeebd1697d69304cfd70189d7381a026b761333ab46ab1e2c5923a6e2db0c9`,
`940efdb923a831dcd4b098cafee33b9a67c472f227be1897bd54cab0693f8b68`,
`519a4ba2757b453e54a2518c83784bfed26317eae4c3e246a984f38a2d30b2f5`.
- **Behavior/Risk:** The two regressions are fixed at shared projection
owners rather than showcase keys or self-insert advice. Static committed
lines retain edge cleanup. `issue012` and `issue013` remain open only for
required user-visible acceptance.
## 2026-07-29 — Plan task023 and task024
- **Add** `issue_live_editing_interaction_20260729.md`.
- Records `issue012` for inactive-mark activation during reprojection and
`issue013` for hidden active-line edge whitespace.
- **Modify** the phase spec, plan, task list, and global issue/change
indexes.
- Makes same-turn whitespace visibility and independent `mark-active`
preservation explicit product contracts.
- Adds a bounded adjacent-interaction audit instead of treating the two
reported symptoms as isolated key-handler patches.
- **Behavior/Risk:** Planning and red-test records only; runtime behavior is
unchanged at this entry.
## 2026-07-29 — Complete task022
- **Add** `spec_text_property_layout_engine_20260729.md`.
- Records the current user-visible contract, command and live-edit flows,
display-property mapping, failure/rollback behavior, edge cases, and
acceptance criteria.
- Corrects a completion-audit gap: the objective required a phase spec,
but the implementation phase had only plan/technical records.
- **Modify** implementation, tests, automation, bilingual documentation,
phase records, changelog, and the live-layout postmortem.
- Links the spec from the plan, tasks, issue, and change trace.
- Closes the repository-wide text-property architecture gate after a full
diff and ownership review.
- Removes the dead `ekp-buffer--justified-spans` and
`ekp--para-glue-shrink` functions; adds no wrapper, compatibility shim,
dependency, overlay path, or source-character fallback.
- **Validation:** buffer ERT 71/71; full default and seeded-permuted ERT
162/162; isolated fresh-process ERT 162/162; C/Elisp fuzz 300/300;
warning-as-error byte compilation; checkdoc; pinned package-lint;
release, dictionary, pinned-upstream dictionary, shell, diff, stale-name,
no-overlay, and dead-private-function scans; portable/native/debug/
sanitizer C builds; exact 164px GUI glue; seven-case GUI matrix; and
dynamic overflow/type/delete temporal verification.
- **Retained GUI/dynamic evidence:**
- `/tmp/ekp-live-final-029c-fOG8xp/manifest.jsonl`
(`sha256:727305251edf07e4cee43bc6b8b25610a384e7d4f071656de388ec6e23913fd9`)
records one live Emacs window, source/projection hashes, zero overlays,
exact overflow/pullback assertions, immediate/redisplay equality, and a
completed 31,707ms run.
- The clean first and final screenshots are
`frames/000001-setup-setup.png`
(`sha256:5e324e25f278867b57c31b825c7d56c87d15b18f7fd1e99e276857127c0ddab8`)
and `frames/000008-completion-final.png`
(`sha256:f5e28d98190b8a494d7f010b423c217b104e76f8c79cc792a8359a328ae1797d`).
- `report.md`
(`sha256:fec75f10a851f220a937456fa17900c178f9466874340d1e178ca6e333693904`)
returns `PASS` with no incomplete or black segments; `recording.mov`
(`sha256:2e31a13b70c2387a0e9a08a1b87947180a46310e3f1becb8c445c768998ac347`)
is the 4,875,735-byte temporal source.
- **Independent review:** code reviewer `APPROVE`, zero findings;
architecture reviewer `CLEAR`, no unresolved architectural blocker.
- **Evidence correction:** replaces the stale duration in phase records
with 31.71 seconds, matching the retained dynamic
manifest's 31,707ms `run-end`.
- **Strengthen** `tests/ekp-buffer-tests.el`.
- Replaces presence-only display checks with exact structural assertions
for ASCII `((space-width FACTOR) (min-width ((PIXELS))))`, zero-source
CJK `(min-width ((PIXELS)))`, and the `line-prefix` pixel-space value.
- Focused exact-property ERT passed 3/3; the current full suite passed
162/162. Warning-as-error compilation of the strengthened test file,
production checkdoc, release checks, artifact cleanliness, and
`git diff --check` passed.
- **Behavior/Risk:** The runtime and repository gates are complete.
`issue011` intentionally remains open until the user accepts the visible
editing behavior, as required by the issue lifecycle.
## 2026-07-29 — Complete task021
- **Modify** `ekp-buffer.el`.
- Adds synchronous bounded live flow with a natural unfinished line,
minimal overflow push, deletion pullback, stable-line convergence,
generation-checked IME/resize work, and automatic paragraph limits.
- Keeps one source-marker anchor for a continuous editing flow so
repeated deletion cannot move the recomputation boundary into the
stable prefix.
- Releases the active anchor on paragraph exit, explicit refill,
hard-boundary reflow, conflict abandonment, and mode teardown.
- **Modify** `tests/ekp-buffer-tests.el`, `tests/ekp-gui-verify.el`.
- Adds public-hook live-edit regressions, bounded-work tests, exact
type/delete break restoration, anchor lifecycle checks, and
before/immediate/settled source/projection hash assertions.
- **Modify** bilingual user/developer docs, current plan/test specification,
changelog, and `postmortem/20260729-text-property-live-layout.md`.
- **Behavior/Risk:** Ordinary edits never run a delayed whole-paragraph
formatter. The unfinished point line remains natural; overflow and
pullback publish synchronously. Buffer ERT passed 71/71. The final
31.71s dynamic artifact passed every checkpoint with zero overlays,
no immediate/settled hash drift, and exact deletion restoration.
## 2026-07-29 — Complete task020
- **Delete** `ekp-region.el`, `tests/ekp-region-tests.el`.
- **Add** `ekp-buffer.el`, `tests/ekp-buffer-tests.el`.
- Replaces delete/insert physical layout with owned text properties on
existing source characters only.
- Maps ASCII glue to combined `space-width`/absolute-pixel `min-width`,
zero-source glue to `min-width`, indentation to `line-prefix`, and
breaks/hyphens to replacing display strings.
- Preserves foreign property owners, direct logical text APIs, copy/save/
search semantics, point, undo, modified state, and external hooks.
- **Modify** GUI verification and bilingual user/developer documentation.
- **Behavior/Risk:** Buffer ERT passed 71/71. The clean Emacs 30.2 static
probe passed exact 164px ASCII/CJK targets, display-only break/hyphen,
point/vertical motion/region/mouse, unchanged source, and zero overlays;
the 7-case scale/remap/fringe/width matrix passed.
## 2026-07-29 — Complete task019
- **Modify** `ekp.el`.
- Adds semantic `ekp-layout-plan`, `ekp-layout-line`, and
`ekp-layout-gap` records with source offsets, exact glue targets,
break kinds, and discretionary-hyphen decisions.
- Makes the existing reversible string renderer consume the shared plan.
- **Modify** `tests/ekp-tests.el`.
- Adds source mapping, hyphen decision, and public-dispatch regressions.
- **Modify** `DEVELOPER.md`, `DEVELOPER_ZH.md`.
- Documents the layout/rendering representation boundary.
- **Behavior/Risk:** Public string formatting remains compatible. The new
plan is the single source for the upcoming buffer display projection.
Validation: focused red 0/3 → green 3/3; full ERT 133/133; fuzz 300/300;
warning-as-error compilation, checkdoc, and diff checks clean.
## 2026-07-29 — Plan task019 through task022
- **Add**
`plan_text_property_layout_engine_20260729.md`.
- Locks the shared core-plan architecture, text-property-only buffer
mapping, live active-line state machine, narrowest-window policy,
display ownership, verification gates, and rollback boundary.
- **Modify**
`task_repository_audit_20260728.md`.
- Adds atomic `task019` through `task022` for core extraction, static
projection, live editing, and final closure.
- **Behavior/Risk:** Planning records only. Runtime behavior is unchanged.
The main risk is Emacs display-property interaction; each milestone has a
stop gate before downstream implementation.

View File

@ -0,0 +1,56 @@
# Change Log: Text-Property Layout Research 2026-07-29
## task018 / issue011
- **Add**
`.phrase/phases/phase-kp-overhaul-20260726/tech-refer_text_property_layout_20260729.md`
- Recorded the user-mandated text-property-only direction.
- Mapped KP boxes, Latin/CJK glue, indentation, chosen breaks, and
discretionary hyphens to Emacs display properties.
- Recorded official documentation/source evidence, exact boundaries, and
a minimum GUI verification gate.
- Proposed a non-polluting Latin hyphen representation in which the
preceding source grapheme displays as
`GRAPHEME + HYPHEN + NEWLINE`.
- **Modify**
`.phrase/phases/phase-kp-overhaul-20260726/issue_logical_text_api_20260728.md`
- Marked design research as resumed while keeping the issue open.
- Added the confirmed primitives, Emacs 30.2 GUI probe results, proposed
hyphen owner, and unresolved architecture constraints.
- **Modify**
`.phrase/phases/phase-kp-overhaul-20260726/task_repository_audit_20260728.md`
- Added and completed documentation-only `task018`.
- **Modify**`.omx/plans/prd-ekp-seamless-live-layout.md`,
`.omx/plans/test-spec-ekp-seamless-live-layout.md`
- Added a supersession notice so the prior overlay-oriented prototypes
are not executed after the user's text-property-only decision.
- **Modify**`.phrase/docs/CHANGE.md`
- Indexed this phase change.
## Behavior and Risk
- No runtime source, test implementation, package API, or persisted file
format changed.
- The recorded GUI probes used temporary buffers in clean Emacs 30.2
daemons and left no repository or user-buffer state.
- Static LTR feasibility is evidence, not proof of live editing,
multi-window independence, bidi correctness, or foreign display-property
composition.
- `issue011` remains open.
## Validation
- Official GNU Emacs display/text-property documentation was cross-checked
against the Emacs 30.2 display source.
- GUI glue probe: a natural 7px source space rendered at exact 3px and 20px
targets through combined `space-width` and `min-width`; source characters
were unchanged.
- GUI hyphen probe: `abcdefgh` remained eight source characters while a
property on `d` displayed `d-\n`, produced two screen lines, moved one
visual line to source position 5, and honored a 20px `line-prefix`.
- Documentation links, task/change traceability, Markdown structure, and
`git diff --check` were inspected.

View File

@ -0,0 +1,23 @@
# Issue: Buffer Width Validation and Failure Atomicity 2026-08-20
## issue022 [ ] Invalid buffer widths mutate or create invalid projection state
- **Status:** Runtime fix verified by `task037`; user-visible confirmation
remains pending.
- **Summary:** `ekp-justify-region` accepts zero, negative, and non-integer
widths without the string API's positive-integer validation. It clears the
existing projection before a later type error, so a rejected request can
destroy valid visible state.
- **Repro:** In a justified buffer, call `ekp-justify-region` with `0`, `-1`,
`1.5`, or `"80"`.
- **Expected vs Actual:** Expected a preflight signal with the old projection
unchanged. Actual zero creates a span with width zero; a type error after a
prior projection leaves no spans.
- **Related:** `ekp-buffer.el`, `ekp.el`, `task037`.
- **Fix:** `ekp-justify-region` now validates the width before font-lock or
projection clearing.
- **Verification:** Red/green focused ERT 1/1 and source-first full ERT
295/295.
- **Resolved At:** Pending user-visible confirmation.
- **Resolved By:** Developer implementation and verification.
- **Commit:** `6a8c7e0`.

View File

@ -0,0 +1,19 @@
# Issue: C Position Vector Validation 2026-08-20
## issue025 [ ] C direct API accepts invalid break positions
- **Status:** Closed by direct contract verification on 2026-08-20.
- **Summary:** The C boundary validates vector shape and signed integer type,
but not the exclusive break-index range, monotonic ordering, or duplicate
policy of hyphen/forbidden positions.
- **Expected vs Actual:** Malformed direct arguments should signal
`ekp-c-invalid-input` per the C API contract. Current `[-1]` and `[99]`
vectors return a layout result instead.
- **Related:** `ekp_c/ekp.c`, `ekp_c/ekp_kp.c`, `task040`.
- **Fix:** Shared preflight now rejects out-of-range, duplicate, and
non-increasing vectors for both direct and batch calls.
- **Verification:** Direct/batch C ERT 15/15, portable warning-clean build,
300-case fuzz, and Elisp parity pass.
- **Resolved At:** 2026-08-20.
- **Resolved By:** Developer implementation and contract verification.
- **Commit:** `6a8c7e0`.

View File

@ -0,0 +1,56 @@
# Issue: C Resize Latency 2026-07-29
## issue020 [x] C-backed resize reflow still takes roughly 6070 ms
- **Status:** Closed by user re-audit on 2026-08-13. Developer verification
was complete.
- **Summary:** Resizing with the C dynamic module enabled was observed at
roughly 6070 ms. That number covered more than the C algorithm: the
buffer path also rebuilt layout input, crossed the Emacs-module boundary,
reconstructed layout data, and published text properties.
- **Environment:** Graphical Emacs 30.2 on Apple Silicon; C module 1.6;
portable production profile unless a benchmark explicitly labels another
profile.
- **Repro:**
1. Load the C module and a representative mixed Latin/CJK buffer.
2. Reflow the same source through a deterministic sequence of uncached
widths.
3. Measure complete reflow and its plan, module, DP, and publication
layers with GC pauses reported separately.
- **Expected vs Actual:**
- Expected: candidate p95 is at most 50 ms and both p50 and p95 improve by
at least 20% against the frozen same-machine baseline.
- Actual: the final four-round evaluator records core p50/p95 of
15.318/27.687 ms and complete resize p50/p95 of 15.900/27.487 ms.
Against the frozen baseline this is a 33.25%/34.09% core improvement
and a 43.51%/41.03% complete-resize improvement.
- **Investigation:** The portable C call averaged roughly 1.8 ms and was
not the dominant layer. Repeated paragraph resolution, projection-time
gap measurement, no-op gap records, duplicate property publication, and
publishing the active paragraph once statically and again as the live
prefix owned most avoidable work. A real GUI resize can still include an
Emacs GC pause after `set-window-margins`; that pause is reported
separately instead of being hidden by a global threshold change.
- **Required Outcome:** Reduce the real owning layer while preserving exact
frozen-C and Elisp layout parity, exact cache identity, stable live
transactions, and source-clean projection.
- **Forbidden Shortcuts:** Longer debounce, skipped intermediate widths,
stale or approximate plan reuse, asynchronous stale publication, and
global GC changes.
- **Fix:** Resolve each paragraph once per plan, carry prepared DP/gap
geometry through projection, omit true zero-source/zero-target gaps,
publish owned properties atomically, and exclude the active paragraph
from the static resize pass before installing its live prefix.
- **Verification:** Four interleaved frozen-baseline/candidate rounds pass
the core and complete-resize gates with exact frozen-C/Elisp layout
parity. The normal and permuted 193-test suites pass, 300 fuzz cases pass,
warning-as-error byte compilation, package-lint, checkdoc, release checks,
and the isolated-test run pass. Reviewed GUI evidence at
`/private/tmp/ekp-c-resize-gui-final6-FMFLxx` records four real width
changes, 14 checkpoints, no failed assertion or pixel overflow, and a
maximum EKP mutator time of 22.989 ms; one 74.099 ms total sample contains
58.792 ms of Emacs GC and 15.307 ms of EKP work.
- **User Confirmation:** Provided by user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; developer verification complete.
- **Commit:** — (documentation-only closure).

View File

@ -0,0 +1,44 @@
# issue029 — Emacs 31.1 strict compilation rejects obsolete let macros
Status: resolved
## Environment
- GNU Emacs 31.1 development build `fac653279dcb`
- macOS Apple Silicon
- EKP HEAD `97cb6a6e3506`
## Reproduction
Run the repository CI byte-compilation command with
`byte-compile-error-on-warn` set to `t` against the four production files.
## Expected vs actual
- Expected: production and maintained test tools compile with warnings treated
as errors on the recorded Emacs executable.
- Actual: Emacs 31.1 rejects six single-binding `if-let`/`when-let` forms in
production; strict changed-tool compilation also finds three such forms in
`tests/ekp-live-commit-evaluator.el`.
## Investigation and fix
The macros became obsolete in Emacs 31.1. Every affected form has exactly one
binding, so the starred replacement has identical binding, truth, body, and
fallback behavior and needs no compatibility branch. Replace only those nine
macro names; do not alter layout or evaluator data flow.
## Verification
`task044` passed root integration compilation; WERROR production and changed
evaluator compilation; normal and seed-20260901 random ERT 296/296; warning-clean
C build; 300-case C/Elisp parity fuzz; release invariants; local 49-entry
dictionary manifest/hash; checkdoc; and diff-check. The network-backed
fixed-upstream dictionary fetch was stopped and excluded at the user's explicit
direction; no upstream-provenance claim is made.
Resolved At: 2026-09-01
Resolved By: C1a nine-repository verification
Commit: pending (protected pre-existing dirty worktree)

View File

@ -0,0 +1,20 @@
# Issue: Hyphenation Locale Resolution 2026-08-20
## issue024 [ ] Hyphenation accepts common locale spelling but selects the wrong dictionary
- **Status:** Runtime fix verified by `task039`; user-visible confirmation
remains pending.
- **Summary:** `de-CH` and `de-DE` normalize to underscore forms but are
looked up in the short-code registry, where they can resolve to the first
arbitrary German dictionary rather than the exact locale.
- **Expected vs Actual:** Equivalent `de_CH`/`de-CH` spellings should select
`hyph_de_CH.dic`; current `de-CH` selects `hyph_de_AT.dic`.
- **Related:** `ekp-hyphen.el`, `plan_dictionary_governance_20260728.md`,
`task039`.
- **Fix:** Raw and normalized exact locale keys are checked before the
documented short-code fallback.
- **Verification:** Locale-equivalence ERT 11/11 and full dictionary gates
pass.
- **Resolved At:** Pending user-visible confirmation.
- **Resolved By:** Developer implementation and verification.
- **Commit:** `6a8c7e0`.

View File

@ -0,0 +1,20 @@
# Issue: No-Projection Integration Lifecycle 2026-08-20
## issue027 [ ] Manual integrations remain installed with no projection owner
- **Status:** Closed by lifecycle verification on 2026-08-20.
- **Summary:** A manual justify request that produces no spans, such as an
empty or foreign-display-only buffer, still installs EKP change hooks and
the substring filter even though auto-mode is disabled.
- **Expected vs Actual:** Integrations should have one active owner: spans or
auto-mode. Actual `spans=nil` still leaves the filter and hooks installed
until a later change happens to trigger cleanup.
- **Related:** `ekp-buffer.el`, `plan_integration_lifecycle_20260728.md`,
`task042`.
- **Fix:** Manual justification installs integrations only when a span or
auto-mode owner exists.
- **Verification:** Empty/foreign-only lifecycle ERT 2/2 and full buffer
suite pass.
- **Resolved At:** 2026-08-20.
- **Resolved By:** Developer implementation and lifecycle verification.
- **Commit:** `6a8c7e0`.

View File

@ -0,0 +1,22 @@
# Issue: Backward Live Projection Stability 2026-08-20
## issue023 [ ] Backward deletion can republish a stable projection
- **Status:** Runtime fix verified by `task038`; user-visible confirmation
remains pending.
- **Summary:** Live row crossing treats forward typing and backward deletion
identically. After a forward crossing, deleting a suffix into the previous
native row can republish earlier rows and visibly move already typed text.
- **Expected vs Actual:** The current live-edit contract preserves unaffected
committed projection anchors during local tail shrink. Actual
`ekp-buffer--live-row-crossed-p` triggers a full prefix publication in both
directions.
- **Related:** `postmortem/20260730-incremental-live-append-ownership.md`,
`ekp-buffer.el`, `task038`.
- **Fix:** A live row crossing now commits only when the native row moves
forward; backward tail shrink keeps the transaction local.
- **Verification:** Public edit-path regression and source-first full/random/
isolated ERT all pass.
- **Resolved At:** Pending user-visible confirmation.
- **Resolved By:** Developer implementation and verification.
- **Commit:** `6a8c7e0`.

View File

@ -0,0 +1,119 @@
# Issue: Live Editing Interaction Regressions 2026-07-29
## issue012 [x] Reprojection activates an inactive mark
- **Status:** Closed by user re-audit on 2026-08-13. Runtime fix and
repository verification were complete.
- **Summary:** Changing width in `ekp-showcase` can highlight text even
though the user did not activate a region.
- **Environment:** Text-property renderer after `task022`; Emacs 30.2.
- **Repro:**
1. Open `ekp-showcase`.
2. Move point so the buffer has an existing but inactive mark.
3. Press `-`, `+`, Left, or Right to change the projection width.
4. Observe a highlighted region between point and the old mark.
- **Expected vs Actual:**
- Expected: width changes preserve point, mark position, and whether the
region is active.
- Actual: the old mark becomes active during reprojection.
- **Investigation:** `ekp-justify-region` restores every existing mark with
`set-mark`. That command activates the mark even when it was inactive
before layout. The showcase exposes the central command defect because
every width key calls the same reprojection path.
- **Root Cause:** The projection command restores the mark's position but
does not preserve the independent `mark-active` editor state.
- **Required Outcome:** Restore the mark marker without activating it, and
prove both the central command and showcase width flow leave an inactive
mark inactive.
- **Fix:** `task023` now restores the existing mark marker directly and
restores `mark-active` independently. It never calls `set-mark` during
reprojection.
- **Verification:** The focused regression failed before the fix because
`mark-active` became non-nil, then passed after the owner-layer change.
A clean full-screen GUI width-key run preserved point 120, mark 20,
`mark_active=false`, `region_active=false`, and zero overlays before,
immediately after, and after redisplay.
- **User Confirmation:** Provided by user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; runtime work previously implemented.
- **Commit:** — (documentation-only closure).
## issue013 [x] Active-line edge whitespace is hidden
- **Status:** Closed by user re-audit on 2026-08-13. Runtime fix and
repository verification were complete.
- **Summary:** With `ekp-auto-justify-mode` enabled, a newly typed trailing
space or tab can appear to do nothing. It becomes visible only after a
following non-whitespace glyph is typed. The same defect affects newly
typed leading whitespace and whitespace exposed by deletion.
- **Environment:** Text-property renderer after `task022`; Emacs 30.2.
- **Repro:**
1. Enable `ekp-auto-justify-mode` in a non-empty paragraph.
2. Move to the paragraph end and type one space.
3. Inspect the inserted source character or type one ordinary glyph.
- **Expected vs Actual:**
- Expected: every command-loop insertion is visible immediately on the
natural active line.
- Actual: the source space exists immediately, but EKP owns
`display ""` on it until another glyph makes it an interior character.
- **Investigation:** The live renderer correctly asks `ekp--project-line`
to leave the active line's glue natural, then separately applies static
paragraph-edge cleanup. Because the KP plan strips leading and trailing
whitespace from its content bounds, that cleanup hides active-line edge
whitespace after every edit.
- **Root Cause:** Static completed-paragraph edge policy leaks into the
unfinished active-line projection. The active line is only partly
natural.
- **Required Outcome:** Keep all source characters on the point-containing
active line natural, including leading/trailing spaces and tabs. Preserve
static edge cleanup on committed lines.
- **Fix:** `task023` prevents static paragraph-edge hiding from running on
the active line. When the first planned line is active, live cleanup also
starts at the source span beginning so an old hidden leading edge cannot
survive.
- **Verification:** Focused mark/edge regressions failed 0/3 before the
fix and passed 3/3 after it. The adjacent public-command matrix covers
consecutive whitespace/backspace, yank, newline, undo, active region,
overflow/pullback, paragraph exit, resize, IME, and teardown. In the
clean GUI run, one space advanced point 67→68 and cursor x 462→469 in
the same action; the source space had neither EKP-owned nor public
replacing display immediately or after redisplay. Typing and deleting a
following glyph left that state unchanged.
- **User Confirmation:** Provided by user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; runtime work previously implemented.
- **Commit:** — (documentation-only closure).
## Related Audit Scope
`task024` checks the same ownership boundary across character insertion,
consecutive whitespace, deletion/backspace, newline, yank, undo, point,
mark/region, visual-break boundaries, paragraph entry/exit, resize, and
mode teardown. New concrete defects receive new issue IDs rather than being
silently folded into these two symptoms.
## Verification Evidence
- Buffer ERT: 79/79.
- Full ERT: 170/170 in default order and 170/170 with seed `20260729`;
every test also passed in a fresh Emacs process.
- C/Elisp property fuzz: 300/300.
- Warning-as-error compilation: production files plus
`tests/ekp-buffer-tests.el`.
- checkdoc, pinned package-lint, release, dictionary, shell syntax,
artifact, stale-name, no-overlay, and diff checks pass.
- Dynamic artifact:
`/private/tmp/ekp-interaction-clean-rdZhdD`.
`report.md` returns `PASS`, `manifest.jsonl` records all assertions true,
and the temporal contact sheet contains one clean full-screen Emacs
window with no black segment.
- Artifact hashes:
- manifest:
`0eaeebd1697d69304cfd70189d7381a026b761333ab46ab1e2c5923a6e2db0c9`
- report:
`940efdb923a831dcd4b098cafee33b9a67c472f227be1897bd54cab0693f8b68`
- recording:
`519a4ba2757b453e54a2518c83784bfed26317eae4c3e246a984f38a2d30b2f5`
- first/final screenshots:
`8cd697130fd54b65bde6d1c06410f88c205779abce563b1e83661849dfc163e2`,
`11996136c3924aea9d539f77bd9db13b55310c089967c43091d2f61133a0d4db`

View File

@ -0,0 +1,20 @@
# Issue: Live Evaluator Source Freshness 2026-08-20
## issue028 [ ] Locked live evaluator can measure stale candidate bytecode
- **Status:** Closed by source-fresh evaluator verification on 2026-08-20;
performance gate remains open under the now-correct source measurement.
- **Summary:** `run-live-commit-evaluator.sh` loaded the candidate by name and
allowed an ignored local `.elc` to win. A report could therefore claim the
locked source-instrumented 16 ms gate passed while measuring bytecode.
- **Expected vs Actual:** Every baseline and candidate round must explicitly
`load-file` the four production source files for its code root. Before the
bootstrap fix, a narrowed matrix reported `pass:true`; afterward it reports
source candidate p99 above 16 ms and keeps `issue018` open.
- **Related:** `tests/run-live-commit-evaluator.sh`,
`tests/ekp-live-commit-evaluator.el`, `issue018`, `task030`.
- **Verification:** Source-load bootstrap, narrowed matrix, and source-path
live benchmark are complete; full locked matrix remains the task030 gate.
- **Resolved At:** 2026-08-20.
- **Resolved By:** Developer evaluator verification.
- **Commit:** `6a8c7e0`.

View File

@ -0,0 +1,132 @@
# Issue: Logical Text API Boundary 2026-07-28
## issue011 [x] Direct Elisp buffer APIs observe physical layout text
- **Status:** Closed by user re-audit on 2026-08-13. Runtime fix and
complete repository gate were implemented by `task019``task022`.
- **Summary:** The former buffer renderer wrote synthesized glue spaces,
soft newlines, discretionary hyphens, and hidden payload text into the
real buffer. Boundary adapters made save/copy/search appear logical, but
ordinary Elisp character APIs still observed the physical layout.
- **Environment:** Text-property renderer and synchronous live flow after
`task021`; Emacs 30.2.
- **Historical Repro:**
1. Insert text into a temporary buffer.
2. Run the pre-`task020` `ekp-justify-region` with a narrow width.
3. Compare the original text with `buffer-string` or
`buffer-substring-no-properties`.
4. Compare the same region through `filter-buffer-substring`.
- **Historical Evidence:** The pre-fix batch probe produced a 39-character
physical representation from a 30-character logical string.
`buffer-string` did not equal the original, while the boundary-filtered
substring did.
- **Expected vs Current:**
- **Expected:** Ordinary Elisp buffer-text APIs observe the original
character sequence while EKP's justified presentation remains visible
and editable.
- **Current implementation:** `buffer-string`,
`buffer-substring-no-properties`, direct character access, ordinary
search, syntax, markers, and saving operate on the unchanged source
characters. APIs that preserve or inspect text properties may still
observe EKP's display metadata; copy/kill strips that owned metadata.
- **Investigation:**
- The former `ekp-justify-region` deleted source text and inserted the
physical rendered string.
- Reversible `ekp-glue`, `ekp-soft-break`, `ekp-soft-hyphen`, and
`ekp-hidden` markers made restoration possible but did not keep direct
buffer reads logical.
- Save/copy/isearch adapters compensated at individual boundaries and
could not cover arbitrary Elisp consumers.
- The correct owner is the buffer representation: source characters must
remain authoritative, and layout must be presentational state only.
- **2026-07-29 Design Evidence:**
- The user ruled out overlays and selected text properties on existing
source characters as the only projection mechanism.
- `(min-width ((PIXELS)))` can add an exact absolute-pixel minimum width
without adding buffer characters.
- `(space-width FACTOR)` can shrink or stretch existing ASCII spaces.
Combined with `min-width`, it rendered a natural 7px space at exact 3px
and 20px targets in a clean GUI Emacs 30.2 probe while source text
remained `"a b"`.
- `line-prefix` can provide display-only indentation for
non-continuation lines. Native continuation lines still require
`wrap-prefix`.
- A replacing `display` string on an existing grapheme can show the
grapheme followed by a discretionary hyphen and visual newline. A GUI
probe displayed `d-\n` from the source `d` in `abcdefgh`; the source
stayed eight characters, two screen lines were produced, one
`vertical-motion` landed on source position 5, and a 20px
`line-prefix` applied to both rows.
- The proposed Latin hyphen owner is the complete grapheme immediately
before the selected break. Its display string reproduces that grapheme,
appends the hyphen and newline, and uses the `cursor` property to keep
point on the source glyph.
- Full reasoning, source links, limitations, and verification gates are
recorded in `tech-refer_text_property_layout_20260729.md`.
- **Root Cause:** Logical document state and rendered layout shared one
mutable character sequence. Reversible markers made the physical
representation recoverable, not logical.
- **Required Outcome:** Remove the leak at the representation owner rather
than adding per-command adapters. Ordinary Elisp character consumers must
see logical content without losing justified display, exact round-trip,
or seamless editing.
- **Implemented Design:**
- Logical source characters stay in the real buffer.
- The buffer renderer creates no overlay and inserts no layout character.
- Existing ASCII spaces use combined `space-width` and absolute-pixel
`min-width`; zero-source CJK/mixed glue uses `min-width` on the
preceding complete grapheme; indentation uses `line-prefix`.
- CJK breaks and Latin discretionary hyphens use replacing display
strings on an existing complete grapheme.
- Projection values have exact EKP owner properties, are nonsticky, and
update under `with-silent-modifications`.
- Foreign `display`, `line-prefix`, `wrap-prefix`, `composition`, and
`invisible` owners make only the affected hard paragraph natural and
diagnostic.
- One buffer uses the narrowest live window as its authoritative width;
simultaneous independent per-window plans are not claimed.
- Live editing uses a natural unfinished line, bounded overflow/pullback,
a stable active-flow anchor, line-signature convergence, IME deferral,
stale-generation rejection, and bounded automatic paragraph work.
- **Fix:** `ekp.el` now exposes a reusable semantic layout plan while
retaining the compatible returned-string API. `ekp-buffer.el` consumes
that plan as a source-clean text-property projection and runs a distinct
synchronous live-flow state machine.
- **Verification to Date:**
- Buffer ERT passes 79/79, including unchanged source/tick/undo/modified
state, direct logical reads, save/copy/isearch, foreign ownership,
no-overlay checks, exact property forms, overflow, deletion pullback,
exact type/delete break restoration, immediate active-line edge
whitespace, active/inactive mark preservation, anchor lifecycle, IME,
undo, resize, multiwindow width, and overload behavior.
- A clean Emacs 30.2 static probe passes exact 164px ASCII and
zero-source CJK glue, display-only breaks/hyphens, `line-prefix`, point,
vertical motion, region, mouse, unchanged source, and zero overlays.
The seven-case scale/remap/fringe/width matrix passes.
- A 31.71s dynamic recording passes before/immediate/settled overflow and
deletion checkpoints. Immediate and settled source/projection hashes
match; deletion restores the original source/projection hashes and
break count; every assertion is true and temporal review passes.
- Final-source default and seeded-permuted ERT pass 170/170; every one of
the 170 tests passes in a fresh Emacs process; C/Elisp fuzz passes
300/300.
- Warning-as-error Elisp compilation, checkdoc, pinned package-lint,
release/dictionary/static gates, all four C build profiles, full diff
review, and anti-slop cleanup pass.
- Independent final code review returns `APPROVE` with no findings;
independent architecture review returns `CLEAR`.
- **User Confirmation:** On 2026-07-28, the user explicitly defined the
target as clean text through Elisp APIs and requested recording only,
with implementation deferred. On 2026-07-29, the user resumed design,
excluded overlays, selected text properties, identified `min-width`,
`space-width`, and `line-prefix`, requested a non-polluting hyphen
implementation, then explicitly requested complete implementation under
the persistent goal model. Final visible-behavior acceptance was provided
by the user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; runtime work previously implemented.
- **Commit:** — (documentation-only closure).
- **Related:** `ekp.el`, `ekp-buffer.el`, `task017``task022`,
`spec_text_property_layout_engine_20260729.md`,
`tech-refer_text_property_layout_20260729.md`,
`postmortem/20260729-text-property-live-layout.md`.

View File

@ -0,0 +1,97 @@
# Issue: Narrow Live-Append Latency 2026-07-29
## issue018 [x] Unique live appends miss the frame budget at very narrow widths
- **Status:** Resolved. Native live-append backend and the locked source-fresh
performance gate are verified.
- **Summary:** Stable transactions removed per-key whole-hard-line planning,
and task030 incrementally extends paragraph preparation, DP state, and
layout lines only at structural row crossings. The user-selected
`ekp-auto-justify-native-append` backend now uses the loaded C 1D DP for
prepared live appends even when full layout is configured for Elisp.
Byte-compiled production paths and the source-fresh evaluator are within the
16 ms frame budget.
- **Environment:** Emacs 30.2 on macOS, C backend 1.6, benchmark width
fixed at 80 px, `gc-cons-threshold` bound to
`most-positive-fixnum` so mutator work is measured without GC pauses.
- **Repro:**
1. For the production-shaped check, byte-compile the four production
Elisp files into a temporary package root, put that root first on
`load-path`, and run `tests/ekp-buffer-live-bench.el` once with the C
module loaded and once with `ekp-use-c-module` nil.
2. Confirm `ekp--dp-run-1d` is byte code and inspect the `append` and
`hard-boundary` rows.
3. For the locked source/instrumentation matrix, run
`tests/run-live-commit-evaluator.sh` and inspect
`.omx/goals/performance/narrow-live-commit/latest-report.json`.
- **Expected vs Actual:**
- Expected: live append p99 remains below the 16 ms interaction budget,
including narrow windows.
- Actual production path: three repeated byte-compiled public-command runs
measure append p99 at 1.1581.326 ms for C and 1.4291.438 ms for pure
Elisp. Hard-boundary p99 is 1.2511.363 ms and 1.4571.470 ms.
- Actual locked evaluator (2026-08-20): four interleaved source-fresh rounds
across 64/80/96/128/160 px, 2/4/8/16 rows, both engines, and both GC
modes measure width-80 C at 12.010/14.622 ms p95/p99 and the
Elisp-configured live path at 10.980/11.194 ms. Paired improvements are
77.72/74.03% and 94.19/94.57%, respectively.
- **Investigation:**
- The task029 point-motion change is not the cause. Navigation is now a
zero-work path and does not enter planning or projection publication.
- The old workload produced 291 distinct hard-line strings and 291 plan
calls. `task031` removes that obsolete control flow: only 15 actual
visual-row crossings invoke `ekp-layout-plan`.
- The remaining high percentiles coincide with those allowed structural
commits and must be profiled as commit latency, not average key latency.
- Wider measurements are materially faster; the pathological result is
width-sensitive rather than a general point-motion regression.
- The previously recorded 6.399 ms task028 append p99 is not
reproducible with the current checked-in benchmark and must not remain
the current performance claim.
- Before the native backend, both backends missed p99 at structural
boundaries, so task030 evaluated both rather than optimizing only the C
wrapper.
- The frozen C wrapper measured only 2.615/2.655 ms p95/p99 and the
candidate 0.697/0.701 ms. Paragraph preparation, DP state reconstruction,
plan construction, and publication owned the end-to-end cost.
- Byte compilation removes most of the pure-Elisp interpreter and closure
overhead. The final source-fresh gate is now independently green; the
production result is retained as a separate deployment check.
- Rust is not selected. It would use the same `emacs_env` ABI and cannot
remove Elisp-owned font measurement, transaction, or projection work.
- **Required Outcome:**
- Diagnose and reduce unique-state narrow-width append planning cost
without changing KP output semantics, the core DP contract, C ABI, or
source-clean display model.
- Keep cache identity exact; do not introduce approximate/stale plan
reuse, timers, skipped edits, global GC changes, or point-based
invalidation.
- Preserve the task029 invariant that point-only motion performs zero
plan, cache, and text-property work.
- **Fix:** `task030` retains exact prepared paragraph/context data, extends
property-free append tails from the last complete-word boundary, resumes
pure-Elisp DP from a safe reachable state, reuses common layout lines, and
reconstructs only the live dirty source island. Unsupported contexts take
the unchanged full path. C int32 validation now performs one extraction.
- **Native backend update (2026-08-20):** `ekp-auto-justify-native-append`
now routes prepared 1D live append DP through the loaded C module even when
full layout uses Elisp. The option can be disabled for a pure-Elisp live
path, and unavailable modules fall back exactly. In a bounded source-fresh
all-width, 2/4/8/16-row, GC-excluded round first reduced append-DP and left
a noisy C p99 near the target. The complete four-round locked run then
measured width-80 total p95/p99 at 12.010/14.622 ms for C and
10.980/11.194 ms for the Elisp-configured live path, closing the gate.
- **Verification:** Exact baseline/C/Elisp hashes and append-chain
equivalence pass across 64/80/96/128/160 px, 2/4/8/16-row fixtures,
default/excluded GC, unsafe fallbacks, and randomized chains. Current
source-first normal and random-order ERT pass 296/296; the native bridge
parity regression passes 1/1; 300 fuzz cases, warning-clean production/C
compilation, C contract tests, release/dictionary checks, and reviewed
temporal GUI evidence pass. The complete four-round source-fresh evaluator
also passes every performance condition.
- **User Confirmation:** User selected architecture option 2 (`2`) and the
complete source-fresh gate now passes.
- **Resolved At:** 2026-08-20.
- **Resolved By:** Codex.
- **Commit:** `3d3dda6` (native backend), `d216c6b` (phase record), and this
closure-record commit (complete four-round gate closure).

View File

@ -0,0 +1,50 @@
# Issue: Native Soft Wrap 2026-07-29
## issue015 [x] Narrow split windows truncate live text
- **Status:** Closed by user re-audit on 2026-08-13. Implementation and
independent verification were complete.
- **Summary:** With `ekp-auto-justify-mode` enabled in a narrow
side-by-side window, typing past the right edge horizontally scrolls the
line and displays a `$` truncation indicator instead of naturally
soft-wrapping.
- **Environment:** Graphical Emacs 30.2; side-by-side windows; native
progressive live layout.
- **Repro:**
1. Split a graphical frame side by side until the editing window is
narrower than 50 columns.
2. Enable `ekp-auto-justify-mode`.
3. Type a hard line beyond the right edge.
4. Observe horizontal scrolling and the left `$` indicator.
- **Expected vs Actual:**
- Expected: the active hard line follows native soft wrapping, point
moves onto a new visual row, and EKP may align only the row just left.
- Actual: no visual row is created, so native-row publication cannot
begin.
- **Root Cause:** Emacs 30.2 defaults
`truncate-partial-width-windows` to `50`. In narrower side-by-side
windows that setting overrides `truncate-lines=nil` and enables
truncation. The auto mode declared native soft wrap as an algorithmic
precondition but did not own the corresponding display variables.
- **Required Outcome:** While the mode is enabled, force native soft
wrapping in full-width and partial-width windows. On disable or
major-mode change, restore both the prior values and whether each value
was buffer-local.
- **Fix:** `task027` snapshots both truncation variables and their
buffer-local ownership before enabling native wrapping. Normal disable,
major-mode teardown, and failed activation restore that state exactly;
activation errors remain visible after cleanup instead of leaving a
half-enabled mode.
- **Verification:** The lifecycle regressions pass 3/3; buffer ERT passes
77/77; default, seed-`20260729`, and isolated ERT pass 166/166; C/Elisp
fuzz passes 300/300. Warning-as-error compilation, checkdoc, pinned
package-lint, release, dictionary, pinned-source, and static gates pass.
Retained dynamic evidence in
`/private/tmp/ekp-soft-wrap-final-pass-PIUigY` shows per-character typing
naturally advancing from one to two visual rows in a 44-column split,
with `hscroll=0`, exact source text, zero overlays, and zero live
replacing breaks.
- **User Confirmation:** Provided by user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; implementation complete.
- **Commit:** — (documentation-only closure).

View File

@ -0,0 +1,54 @@
# Issue: Natural Live Editing 2026-07-29
## issue014 [x] Near-edge editing publishes premature KP layout
- **Status:** Closed by user re-audit on 2026-08-13. Implementation and
independent verification were complete.
- **Summary:** With `ekp-auto-justify-mode` enabled, typing near the right
edge can publish several visual KP breaks and a discretionary hyphen
while the user is still composing the current hard paragraph. The result
does not feel like ordinary Emacs editing.
- **Environment:** Text-property live-flow renderer after `task024`;
graphical Emacs 30.2; mixed Latin/CJK prose.
- **Repro:**
1. Enable `ekp-auto-justify-mode` in a graphical window.
2. Type mixed Latin/CJK prose continuously toward the right edge.
3. Observe the transition one character at a time as Emacs naturally
wraps.
- **Expected vs Actual:**
- Expected: the source tail containing point is displayed exactly as
ordinary Emacs would display it. Before natural wrap, EKP publishes no
layout property there. After natural wrap, EKP may align only the row
the user has left. A complete KP pass runs only after the hard
paragraph ends or point leaves it.
- Actual: bounded KP flow selects and publishes display breaks during
typing. `natural-p` suppresses glue projection only; it still permits
prefix and break/hyphen projection.
- **Root Cause:** The live model treats an unfinished paragraph as a
continuously maintained partial KP plan. “Natural active line” is only
a glue exception inside that plan, not native Emacs display ownership.
- **Required Outcome:** Replace partial live KP with three explicit states:
a natural active tail, locally aligned rows already left by natural
wrapping, and a complete KP paragraph after completion. No live row may
add a discretionary hyphen or replacing visual break.
- **Fix:** `task025` deletes the partial-KP
anchor/lookahead/convergence/push-pull state machine. The active hard
paragraph is restored to native display before each source edit. After
the edit, EKP derives completed native visual rows from Emacs redisplay
and projects only their internal gap widths. The point-containing tail
stays native, and only hard-paragraph completion or paragraph exit runs
the complete KP planner. Live projection cannot install replacing
breaks, prefixes, or discretionary hyphens.
- **Verification:** Focused live contract ERT passes 4/4; buffer ERT passes
75/75; default, seeded-permuted, and isolated full ERT pass 164/164; fuzz
passes 300/300. Warning-as-error compilation, checkdoc, pinned
package-lint, release, dictionary, pinned-source, static ownership,
no-overlay, and diff gates pass. The retained clean Emacs 30.2 dynamic
artifact at `/private/tmp/ekp-native-live-2dYu2U` proves native
mixed-text wrap, exact deletion restoration, and the sole full-KP
transition after hard newline; every immediate and redisplay checkpoint
passes, with zero live owned breaks and no black recording segment.
- **User Confirmation:** Provided by user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; implementation complete.
- **Commit:** — (documentation-only closure).

View File

@ -0,0 +1,69 @@
# Issue: Final-Pass CJK Isolation 2026-08-01
## issue021 [x] Final-pass emergency layout can isolate CJK source lines at narrow widths
- **Status:** Resolved. Developer verification and independent review passed;
the user accepted the visible result and requested the commit.
- **Summary:** At narrow measures, the final K-P pass can choose one-glyph CJK
source lines in the mixed showcase paragraph. The visible symptom was first
found around an explicit hard atom, then reproduced after automatic inline
policy was split from hard atoms.
- **Environment:** Graphical Emacs 30.2 on macOS, narrow showcase widths,
justify alignment, C module active for 1D layout. The same core problem is
visible through the public buffer path, so this is not a renderer-only
issue.
- **Repro:** Run `M-x ekp-showcase`, set a narrow width such as 168px or
280px, and inspect the mixed Chinese/Latin inline paragraph. The failure
appears when a CJK glyph becomes a complete source line even though the
paragraph has legal non-emergency alternatives.
- **Expected vs Actual:** Ordinary underfull candidates in the final pass
should remain normal K-P candidates: adjustment ratio, badness, fitness,
and demerits decide the global path. Separately, the final pass must not
lose all reachability when a legal candidate is overfull. The previous
model confused that reachability safeguard with a hard/atomic fixed-cost
scoring shortcut, and an empty-plan oracle could hide the failure.
- **Investigation:** The strict pass is not the bug. The buffer projector
reproduces the semantic plan exactly and owns no line-choice compensation.
The root is in core DP final-pass modeling: ordinary underfull candidates
must receive finite emergency stretch and stay inside normal K-P scoring.
TeX's `artificial_demerits` instead preserves the last active path in the
final pass independent of content class; it is not owned by CJK, units,
hard atoms, or their adjacency.
- **Required Outcome:** Eliminate unintended isolated CJK source lines at the
checked narrow showcase widths without adding CJK-orphan, number-unit, or
screenshot-specific penalties. Preserve source text, zero overlays,
strict-pass behavior, Elisp/C parity, and the public C ABI.
- **Fix Plan:** Keep strict K-P unchanged. In the final pass, add a
TeX-style fixed emergency-stretch dimension to ordinary underfull
candidates and score them through normal badness, fitness, and demerits.
Expose the dimension as `ekp-emergency-stretch-pixel`: nil auto-resolves to
roughly three display-font `M` widths, while a non-negative integer fixes
the pixel value. If an overfull candidate would otherwise remove the final
active path to a breakpoint and no non-overfull candidate survives there,
install the best provisional path with tight fitness and zero incremental
demerits. The Elisp 1D path, looseness/parshape path, renderer semantics,
and C engine share this rule without changing the 15-field C paragraph ABI.
- **Verification:**
- Focused core single-CJK regressions pass 2/2.
- The implementation lane's focused G009 suite covering core, C parity,
hard atoms, public buffer projection, and GUI oracle passes 18/18.
- Warning-as-error Elisp byte compilation and the C build pass.
- Full regression falsified task035's line-width-sized emergency stretch:
the stretch must be a fixed dimension, not derived from each candidate
measure.
- Task036's fixed-dimension RED cases pass and the three regressions broken
by task035 remain green. Direct regression proves artificial lines add
zero demerits and preserve a complete nonempty source-covering plan.
- Focused root regressions pass 8/8, emergency selection passes 10/10,
core/buffer/GUI oracle passes 8/8, full ERT passes 288/288, seeded and
isolated suites pass 279/279, fuzz passes 300/300, and all build/static/
release/performance gates pass.
- Reviewed 42.78-second fullscreen single-window evidence at
`/tmp/ekp-g009-evidence-retry.UOpPNp` covers 480→168→280→168 and
no-hyphen→normal policy change, reports no isolated CJK source lines,
exact source, zero overlays/stale spans, active C, and `VERDICT=PASS`.
- **User Confirmation:** On 2026-08-02, the user replied “可以了,提交吧”,
accepting the visible result and requesting submission.
- **Resolved At:** 2026-08-02.
- **Resolved By:** `task036`; final-pass active-path preservation in core K-P.
- **Commit:** This commit (`fix: restore TeX final-pass reachability`).

View File

@ -0,0 +1,81 @@
# Issue: Point-Motion Projection Stability 2026-07-29
## issue017 [x] Point-only motion changes an already published live projection
- **Status:** Closed by user re-audit on 2026-08-13. The zero-work
point-motion invariant remains current; `issue019`/`task031` supersede
frontier-owned immediate publication and extend it across navigation.
- **Summary:** `task028` correctly made one whole hard line the planning
unit, but it also made transient point position the persistent live
projection boundary. Moving point across semantic plan lines therefore
removes and reinstalls display properties even though source text,
authoritative width, font metrics, and layout options are unchanged.
- **Environment:** Graphical Emacs 30.2; `ekp-auto-justify-mode`; one hard
line with a multi-line semantic KP plan.
- **Repro:**
1. Edit the end of a long hard line so EKP publishes a semantic prefix.
2. Move point backward into an already projected semantic line without
changing source text.
3. Observe the prefix lose projection; move point forward and observe it
return.
- **Expected vs Actual:**
- Expected: point-only motion inside the active hard line is display
read-only. The existing plan, editing frontier, active semantic index,
line signatures, projection properties, source text, and cache state
remain unchanged.
- Actual: `post-command-hook` calls
`ekp-buffer--publish-live-boundary`, derives a new active index from
point, and mutates the projected prefix.
- **Root Cause:** The live state stores a plan-relative `active-index` but
no source-relative editing frontier. `ekp-buffer--project-live-plan`
therefore reads `(point)` both after source edits and during unrelated
reprojection. This gives cursor navigation ownership of layout state.
- **Required Outcome:**
- The latest real source edit owns a source-relative editing frontier.
- `before-change-functions` naturalizes the semantic line actually being
edited and its suffix before source mutation.
- `after-change-functions` moves the frontier to the changed region's
new end and publishes exactly once.
- Point-only motion inside the same hard line performs no plan, cache, or
text-property work.
- Width/font/layout-context changes may rebuild the plan, but they map
the preserved frontier into that plan rather than reading transient
point.
- Point-only motion, including leaving the hard line, performs zero work.
The next real edit elsewhere, hard newline, or explicit refill owns the
completed-paragraph transition.
- Core DP, C ABI, DP schema, source text, and the no-overlay contract do
not change.
- **Fix:** `task029` adds a source-relative frontier marker to live state,
derives the active semantic index from it, moves it only after real
changes, preserves it through reflow, and removes point-driven
publication from `post-command-hook`. The hard-line completion path is
unchanged.
- **Verification:**
- The focused point-motion test failed before implementation because
cursor movement removed the owned projection; the reflow regression
also selected point's line instead of the edit frontier. Both pass
after the fix.
- Independent focused ERT passes 10/10; buffer ERT passes 93/93.
Default, seed-`20260729`, and isolated full suites pass 182/182;
C/Elisp fuzz passes 300/300.
- Warning-as-error compilation, checkdoc, pinned package-lint, release,
dictionary, pinned-source, no-overlay, no-stale-symbol, and diff gates
pass.
- Point motion measures 0.033 ms p99 on the C backend and 0.037 ms in an
independent Elisp-backend review, with zero planner/cache calls.
- The reviewed GUI run
`/private/tmp/ekp-frontier-live-v3-66WYRW` records 39 manifest lines
over a 26.6-second real screen capture. Backward and forward motion
preserve projection hash, plan object, generation, cache size, active
index, and frontier. Delete, yank, real undo, narrow/restore resize,
and hard newline also pass; source is exact, overlays are zero,
`hscroll` is zero, no black segment is detected, and the evidence
report returns PASS.
- Independent architecture review is CLEAR. Independent code review
reports zero blockers; its only residual is the separate narrow-width
append latency tracked by `issue018`/`task030`.
- **User Confirmation:** Provided by user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; runtime work previously implemented.
- **Commit:** — (documentation-only closure).

View File

@ -0,0 +1,273 @@
# Issues: Repository System Audit 2026-07-28
Detailed evidence and prioritization live in
`Docs/REPOSITORY_AUDIT_20260728.md`.
## issue001 [x] Incomplete cache signatures reuse stale layout results
- **Status (2026-07-28):** Resolved by `task002` and `task003`.
- **Summary:** DP and automatic paragraph caches omitted configuration
values that determine the result.
- **Environment:** `main@29cef97`, Emacs 30.2, C module 1.5.
- **Repro:** Compute a paragraph, change `ekp-hyphen-penalty` or
`ekp-default-cws-stretch-pixel`, and compute again without clearing caches.
- **Expected vs Actual:** The second result must match a fresh computation;
it instead reuses the old cached object/result.
- **Investigation:** DP reuse and paragraph preprocessing have separate
owners, so each needs a complete structural signature.
- **Fix:** `task002` keys DP by width, looseness, and six runtime cost
parameters. `task003` shares one spacing signature between the paragraph
hash and same-string fast path, without adding watchers.
- **Verification:** Focused red/green cache tests; full ERT 99/99; C/Elisp
fuzz 300/300; warning-clean byte compilation; checkdoc clean.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P1-01, P2-01; `task002`, `task003`.
## issue002 [x] Failed saves leave a justified buffer unformatted
- **Status (2026-07-28):** Resolved by `task004`.
- **Summary:** The pre-save hook reverses layout, but a failed write does not
run an unconditional restoration path.
- **Environment:** `main@29cef97`, Emacs 30.2.
- **Repro:** Justify a visited buffer, make its target directory unavailable,
then call `save-buffer`.
- **Expected vs Actual:** The save should fail while preserving the visible
justified state; the buffer remains unformatted and retains stale save
markers.
- **Investigation:** Restoration existed only in `after-save-hook`; no hook
could provide finally semantics across every write failure.
- **Fix:** Whole-buffer writes switch to a logical copy from
`write-region-annotate-functions`; the display buffer is never
unformatted.
- **Verification:** Successful write, missing-directory failure, forced
encoding error plus retry, and `quit`; full ERT 102/102; fuzz 300/300;
warning-clean byte compilation; checkdoc clean.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P1-02; `task004`.
## issue003 [x] Buffer integration is not composable or lifecycle-owned
- **Status (2026-07-28):** Resolved by `task005`.
- **Summary:** EKP overwrites an existing copy filter and leaves hooks/filter
installed after manual unjustify removes the final layout span.
- **Environment:** `main@29cef97`, Emacs 30.2.
- **Repro:** Install a buffer-local `filter-buffer-substring-function`,
justify, copy, then unjustify the whole buffer.
- **Expected vs Actual:** Both filters should retain their semantics and
unused integrations should be removed; the prior filter is bypassed and
EKP integrations remain.
- **Investigation:** Installation used direct `setq-local`; public and
internal unjustify shared one body with no lifecycle boundary.
- **Fix:** Record local/inherited filter ownership, run the prior filter
before EKP logical inversion, split internal structural unjustify from the
public cleanup wrapper, and observe external deletion with a guarded
after-change hook.
- **Verification:** Public copy and DELETE filter paths; local/global
restoration; final unjustify; mode disable; full ERT 107/107; fuzz
300/300; warning-clean byte compilation; checkdoc clean.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P1-03, P2-06; `task005`.
## issue004 [x] Test order dependency produces a false green suite
- **Status (2026-07-28):** Resolved by `task006`.
- **Summary:** The named parshape C-bypass test exercises first-line indent
and passes only after another test leaks `ekp-use-c-module=nil`.
- **Environment:** `main@29cef97`, Emacs 30.2, C module 1.5.
- **Repro:** Load the C module and run only
`ekp-test-parshape-bypasses-c`.
- **Expected vs Actual:** The test should independently prove the real
parshape dispatch path; it fails in isolation while the full suite passes.
- **Investigation:** The test binds the wrong variable and fixtures restore
only a subset of mutable global state.
- **Fix:** Bind the real parshape and drive `ekp-pixel-justify`; dynamically
restore every isolated EKP tunable; provide permuted-order and
fresh-process runners, with the former in CI.
- **Verification:** Focused 0/1 red then 3/3 green; reproducibly permuted
ERT 108/108; all 108 ERT tests passed independently in fresh Emacs
processes.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P1-04; `task006`.
## issue005 [x] C API and build boundary need explicit validation
- **Status (2026-07-28):** Resolved by `task008` and `task009`.
- **Summary:** Malformed direct calls can signal despite the former nil-only
contract, extreme int32 arithmetic is not closed, and the interactive
build command uses an unquoted shell path.
- **Environment:** `main@29cef97`; macOS arm64; C11 toolchain.
- **Repro:** Pass a malformed field vector directly; inspect int32
accumulation and `ekp-c-module-build` path construction.
- **Expected vs Actual:** Inputs, error semantics, ranges, and build profiles
should be explicit; the current boundary mixes signaling, clamping, and
shell assumptions.
- **Investigation:** Fifteen-field schema checks happen piecemeal; arithmetic
continues in int32; default flags include native/LTO optimization.
- **Fix:** C 1.6 preflights the complete schema and uses
`ekp-c-invalid-input`; DP intermediates are int64 and backend signals
surface. Builds use direct argv and explicit portable/native/debug/
sanitize profiles, with portable default.
- **Verification:** Direct boundary 0/6 → 6/6; dispatcher 0/1 → 1/1;
build process/profile 0/2 → 2/2; full ERT 116/116; fuzz 300/300; four
warning-clean profiles; real interactive and whitespace-path portable
builds. ASan runtime loading is blocked by macOS policy and remains a
Linux CI gate.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P2-02, P2-03; `task008`, `task009`.
## issue006 [x] Dictionary claims exceed parser and provenance coverage
- **Status (2026-07-28):** Resolved by `task011`.
- **Summary:** Alternative pattern syntax is skipped, several license/source
companions are absent, and the update script is unpinned.
- **Environment:** 50 bundled `hyph_*.dic` files at `main@29cef97`.
- **Repro:** Count non-comment patterns containing `/` and compare dictionary
files with same-name README files.
- **Expected vs Actual:** Supported syntax and provenance should be auditable;
2399 alternative patterns across three languages are ignored and 20
dictionaries lack same-name README files.
- **Investigation:** The parser intentionally skipped `/` patterns; deeper
review found 2399 libhyphen replacement rules across Hungarian, Catalan,
and Albanian plus four Esperanto slash-prefixed rules. The updater pulled
moving HEAD without a manifest/checksums, and Sanskrit had no exact license
evidence at the pinned snapshot.
- **Fix:** Reject those four dictionaries with a typed condition instead of
corrupt partial support; pin 49 files and their checksum/source/license
evidence; remove unverifiable Sanskrit; add portable check/export tooling.
- **Verification:** Golden red 0/2 → 2/2; offline/upstream 49/49; two
identical exports; full ERT 121/121; compile/checkdoc/shell/YAML/diff pass.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P2-04; `task011`.
## issue007 [x] GUI verification reports failure without failing automation
- **Status (2026-07-28):** Resolved by `task007`.
- **Summary:** The full fit command lives only under `tests/`, while its
noninteractive matrix prints failures but exits successfully.
- **Environment:** `main@29cef97`, GUI Emacs 30.2.
- **Repro:** Load `tests/ekp-gui-verify.el`; force a failing row in
noninteractive matrix mode.
- **Expected vs Actual:** Installation/loading instructions should be clear
and any failed row should return nonzero; failure is currently textual
only.
- **Investigation:** The diagnostic and matrix have no shared packaged
entrypoint or assertion boundary.
- **Fix:** Cases return structured results to one report boundary; batch
failure exits 1 after printing the table. Documentation explicitly loads
the developer tool and states it is not part of `ekp-region`.
- **Verification:** Forced-failure/success controls 2/2; default and
permuted full ERT 110/110; real GUI matrix 7/7; clean fullscreen
single-window screenshot inspected at
`/tmp/emacs-kp-gui-20260728-clean.png`.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P2-05; `task007`.
## issue008 [x] Hot-loop allocation and nil caching need benchmark gates
- **Status (2026-07-28):** Resolved by `task012`.
- **Summary:** Tokenization and hyphen insertion repeatedly rebuild strings,
and nil hyphen results are recomputed.
- **Environment:** `main@29cef97`, Emacs 30.2.
- **Repro:** Profile long tokens and query an unhyphenated word twice while
counting `ekp-hyphen--compute`.
- **Expected vs Actual:** Repeated nil results should hit the cache and
hot-loop growth should remain linear; nil results compute twice.
- **Investigation:** Cache lookup uses `or`, which cannot distinguish an
absent key from a cached nil.
- **Fix:** The word-position cache distinguishes absent entries with a
sentinel; tokenizer boxes accumulate reversed fragments and join once;
inserted words concatenate original slices once.
- **Verification:** Nil call-count red 0/1 → 1/1; exact long/propertized and
dense output regressions 3/3; 1k8k benchmark improved the 8k cases from
3.133/0.945 s to 1.100/0.013 s; full ERT 124/124; fuzz 300/300;
warning-as-error production compilation and checkdoc passed.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P3-01; `task012`.
## issue009 [x] Release and documentation governance are not closed
- **Status (2026-07-28):** Resolved locally by `task010`.
- **Summary:** Local version/tag state is ahead of the published remote,
CI dependencies float, Windows is absent, and ignored `.phrase` records are
called the source of truth.
- **Environment:** local `main@29cef97`, `origin/main@11437cb`.
- **Repro:** Compare local/remote commits and tags, CI pins, `.gitignore`, and
the repository workflow contract.
- **Expected vs Actual:** Release metadata and versioned decisions should be
mutually consistent; local `v1.0.0` is not present remotely and the process
source of truth is ignored.
- **Investigation:** No single release gate currently checks commit, tag,
changelog, package/C versions, CI, and artifacts together.
- **Fix:** `.phrase` is tracked truth; actions and package-lint are immutable
inputs; Windows compiles/runs the pure-Elisp baseline; an executable local
invariant gate and separate remote/artifact checklist own the release path.
- **Verification:** Gate red → green; pinned package-lint pass; workflow YAML
and shell syntax pass; default ERT 119/119; diff check pass.
- **User Confirmation:** User requested completion of all audit findings on
2026-07-28; the automated local acceptance criteria are satisfied.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P2-07; `task010`.
## issue010 [x] Same-string property mutation bypasses paragraph identity
- **Status (2026-07-28):** Resolved by `task016`.
- **Summary:** `ekp--para-key` includes layout-relevant text properties, but
the most-recent paragraph fast path compares only string identity plus a
partial configuration tuple.
- **Environment:** current remediation working tree; Emacs 30.2.
- **Repro:** Resolve a string, add `ekp-no-break` to the same string object,
then resolve it again without clearing caches.
- **Expected vs Actual:** The second lookup must equal a fresh property-aware
paragraph; it returns the pre-mutation paragraph object.
- **Investigation:** The fast path duplicates only part of the hash-cache
identity contract, so in-place property changes cannot invalidate it.
- **Fix:** Store `(string, complete ekp--para-key, paragraph)` in the
most-recent slot and compare the same structural key used by the hash
cache. Remove the six style-variable watchers that compensated for the
former partial identity.
- **Verification:** Same-object mutation red 0/1 → 1/1; focused cache matrix
6/6; default and seeded-permuted ERT 130/130; every one of 130 ERT tests
passed in a fresh Emacs process; fuzz 300/300; warning-clean Elisp/C
compilation, checkdoc, package-lint, release/dictionary/static gates, and
GUI matrix 7/7 passed. Independent architecture review returned `CLEAR`
for the shared-key ownership.
- **User Confirmation:** User requested completion of every repository
finding on 2026-07-28.
- **Resolved At:** 2026-07-28.
- **Resolved By:** Codex.
- **Commit:** Pending.
- **Related:** audit P1-05; `task016`.

View File

@ -0,0 +1,97 @@
# Issue: Semantic Live Prefix 2026-07-29
## issue016 [x] Live editing freezes native rows instead of projecting a semantic hard-line prefix
- **Status:** Closed by user re-audit on 2026-08-13. The whole-hard-line
commit result is retained; per-edit replanning was superseded by
`issue019`/`task031`.
- **Summary:** Before `task028`, live editing committed completed native
visual rows independently. User testing showed that earlier soft-wrapped
rows in the same hard line did not become globally KP-aligned while the
user continued typing. The behavior was row-local, not a dynamic
projection of one semantic hard-line plan.
- **Environment:** Graphical Emacs 30.2; `ekp-auto-justify-mode`; mixed
Latin/CJK hard lines that wrap across multiple visual rows.
- **Repro:**
1. Enable `ekp-auto-justify-mode`.
2. Type one long hard line that naturally soft-wraps into multiple rows.
3. Continue editing on the current unfinished row without inserting a
hard newline.
4. Observe the rows before point.
- **Expected vs Actual:**
- Expected: EKP sends the complete current hard-line source text to the
existing `ekp-layout-plan` and projects only the complete semantic
lines before the line containing the latest real source edit. Those
preceding lines may change breaks and glue together whenever text,
width, font, or layout options change. The editing-frontier line and
all following source stay naturally editable. Point-only navigation
does not change the projection.
- Actual before `task028`: the live model derived native visual rows and
committed them one at a time. Once a row was treated as completed,
later edits could not let the whole hard-line KP plan revise earlier
breaks and glue as a single semantic decision.
- **Root Cause:** `ekp-buffer` owned live editing but used native visual-row
boundaries as the persistent layout unit. That was the wrong owner
boundary. Core DP already computed the correct complete-text plan; the
buffer layer needed to consume that plan and decide which prefix was safe
to project, not invent a row-freezing live algorithm.
- **Required Outcome:** Replace native-row commitment with semantic prefix
projection:
- keep the core DP, C ABI, DP schema, and `ekp-layout-plan` contract
unchanged;
- pass the full current hard line to `ekp-layout-plan`;
- find the plan line containing the latest real source edit;
- project only the complete plan lines before that line;
- leave the edit-frontier plan line and all following source natural;
- compare old/new line signatures so unchanged prefixes are not
reinstalled;
- cache recent full hard-line plans in `ekp-buffer` only;
- fail closed to native display for conflicts, oversized hard lines, or
stale generations;
- never use overlays or insert source layout characters.
- **Fix:** `task028` replaces the native-row commit state with one current
whole-hard-line semantic plan. `task029` makes the latest real source
edit, rather than transient point, own the natural-suffix frontier.
`ekp-buffer` projects only prior lines, owns break whitespace on the
preceding semantic line, keeps the editable suffix property-free,
reuses a 16-entry buffer-local plan LRU, and updates changed projection
suffixes from common-prefix line signatures. The core DP, C ABI, schema,
and `ekp-layout-plan` contract are unchanged.
- **Refinement:** `issue017`/`task029` supersede the point-driven boundary
portion of `task028`. The persistent natural-suffix boundary belongs to
the latest source edit; transient point motion is display read-only.
- **Later Refinement:** `issue019`/`task031` supersede immediate
whole-hard-line replanning after every edit. The plan remains authoritative
at structural commits; stable same-row editing is owned by a saved
committed projection and local dirty transaction.
- **Verification:**
- Public-path and edge-case ERT pass in the 181-test default suite, the
seed-`20260729` suite, and 181 isolated Emacs processes. C/Elisp fuzz
passes 300/300.
- Warning-as-error compilation, checkdoc, the CI-pinned package-lint,
release, dictionary, pinned-source, no-overlay, and source-clean gates
pass.
- The main dynamic GUI run
`/private/tmp/ekp-semantic-live-v4-vFZTkr` exercises typing, deletion,
backward/forward point motion, yank, real undo, narrow/restore resize,
and hard newline. Its 38 checkpoints have exact source text, zero
overlays, `hscroll=0`, no pending transaction, no failed assertion,
and a reviewed temporal verdict of PASS.
- The 44-column split-window run
`/private/tmp/ekp-semantic-split-v3-uPwuOi` proves native incremental
soft wrapping, one semantic projected prefix line, a natural active
line, exact source text, zero overlays, `hscroll=0`, clean disable, and
a reviewed temporal verdict of PASS.
- A subsequent task029 audit could not reproduce the original 6.399 ms
append p99 with the current checked-in benchmark. At its synthetic
80-pixel width, repeated GC-excluded runs instead record one plan per
unique append and roughly 3385 ms p99. `issue018`/`task030` track this
separate performance debt. Point motion remains a zero-work path at
about 0.03 ms p99.
- Final independent code review returns APPROVE; independent architecture
review returns CLEAR. Both reviewed the latest font-context
invalidation delta.
- **User Confirmation:** Provided by user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; runtime work previously implemented.
- **Commit:** — (documentation-only closure).

View File

@ -0,0 +1,25 @@
# Issue: Showcase Contract and Test Freshness 2026-08-20
## issue026 [ ] Showcase loses automatic inline policy and runners can false-green
- **Status:** Runtime/test fix verified by `task041`; user-visible
confirmation remains pending.
- **Summary:** The showcase's automatic inline-code face was commented out,
so clean source runs fail the GUI policy contract. Local stale ignored
`tests/*.elc` files can mask the regression, and random/isolated selectors
omit nine GUI verifier tests.
- **Expected vs Actual:** Clean, source-fresh normal/random/isolated runners
should execute the complete ERT inventory and retain the automatic inline
example. Current source has 3 failing showcase tests; stale local bytecode
reports a false green.
- **Related:** `tests/ekp-showcase.el`, `tests/run-tests.sh`,
`tests/run-tests-random-order.el`, `tests/run-tests-isolated.sh`,
`task041`.
- **Fix:** The automatic inline face is restored; one source loader uses
`load-file` for every production/test source and all runners select the
complete ERT registry.
- **Verification:** Source-first showcase 3/3, normal/random/isolated 295-test
inventories, and GUI contract checks pass.
- **Resolved At:** Pending user-visible confirmation.
- **Resolved By:** Developer implementation and verification.
- **Commit:** `6a8c7e0`.

View File

@ -0,0 +1,72 @@
# Issue: Stable Live Edit Transaction 2026-07-29
## issue019 [x] Per-edit frontier replanning destabilizes live projection and cannot restore reversible edits exactly
- **Status:** Closed by user re-audit on 2026-08-13. Implementation was
developer-verified by `task031`.
- **Summary:** The current live path makes one `frontier` simultaneously own
the latest edit, the natural suffix, and projection reuse. Before every
edit it clears the touched projected line and every following line; after
every edit it replans the complete hard line. This makes unrelated rows
move during continuous input and turns narrow append latency into a
symptom of the same ownership error.
- **Environment:** Graphical Emacs 30.2; `ekp-auto-justify-mode`; long mixed
Latin/CJK hard lines with an installed live prefix.
- **Repro:**
1. Type a hard line until multiple visual rows exist.
2. Continue typing within the unfinished row, or edit a previously
projected middle row.
3. Observe earlier/succeeding rows and the owned text properties after each
source change.
4. Delete a projected space and insert the identical space again.
- **Expected vs Actual:**
- Expected: ordinary same-row input leaves the committed projection
untouched. A middle-row edit opens one local dirty island while
unaffected break anchors remain installed. Small changes are absorbed by
existing glue/native wrapping; local words move only when the dirty row
no longer fits. Restoring the exact source restores the exact projection
immediately.
- Actual: `before-change` invalidates the touched line and its complete
projected suffix. `after-change` moves `frontier`, runs or reuses a
whole-hard-line plan, and republishes immediately. The transaction stores
only an old paragraph marker and deleted fragment, so it cannot restore a
committed baseline as a first-class state transition.
- **Root Cause:** `frontier` conflates three independent owners: source-edit
location, unprojected edit boundary, and plan/projection reuse boundary.
Signature diffing reduces writes after a plan is chosen, but cannot prevent
global optimal breaks from changing when a new plan is computed after
every key.
- **Required Outcome:**
- committed projection owns the last published source/plan/signatures/spans;
- a dirty edit transaction snapshots that baseline and owns only the
affected local island;
- ordinary same-row edits do no whole-hard-line DP and do not rewrite
unaffected projection;
- crossing a native visual-row boundary atomically replans the completed
hard-line prefix, leaving the new current row natural;
- the next real edit elsewhere, hard newline/paragraph completion,
explicit refill, or width/font/layout-context change is a commit event;
- point-only motion performs zero planning, cache, property, or layout work;
- a reversible edit restores source and the complete owned projection
`equal-including-properties` without depending on a best-effort replan;
- completed paragraphs still use the existing globally optimal core DP;
- no overlays or source layout characters are introduced.
- **Fix:** `task031` deletes frontier-owned immediate publication. Committed
live state now owns source/key/plan/signatures/prefix/spans; one edit
transaction snapshots that state and marker offsets, naturalizes only its
dirty island, restores exact baselines directly, and permits replanning
only at native-row or structural commit events. Static lazy-reflow chunks
exclude the active paragraph. The core DP and `ekp.el` are unchanged.
- **Verification:** Focused public-path regressions were observed red before
implementation and green afterward. Final buffer/default/random/isolated
ERT runs pass 99/99, 188/188, 188/188, and 188/188; C/Elisp fuzz passes
300/300; compile, checkdoc, package-lint, release, and local dictionary
gates pass. The reviewed dynamic run
`/private/tmp/ekp-stable-transaction-final5-2BFryc` is PASS with 55
checkpoints, no failed assertion, a completed run-end, and no black
segment or visual noise. Same-row and point-motion benchmark scenarios
perform zero plans; 291 appends now contain only 15 structural plans.
- **User Confirmation:** Provided by user re-audit on 2026-08-13.
- **Resolved At:** 2026-08-13.
- **Resolved By:** User re-audit closure; `task031` developer verification.
- **Commit:** — (documentation-only closure).

View File

@ -0,0 +1,28 @@
# Plan: Complete Automatic Spacing Cache Identity
## Scope
Finish `issue001` by fixing the auto-CWS paragraph-cache defect only.
Preserve the completed DP signature work in `task002`.
## Resolution Path
1. Prove the paragraph hash and same-string fast path both reuse stale
automatic spacing after `ekp-default-cws-stretch-pixel` changes.
2. Give paragraph preprocessing one spacing-signature owner shared by both
lookup paths.
3. Keep automatic font-derived values represented by the existing
font/display-context identity; add the remaining CJK default explicitly.
4. Prove an unchanged signature still hits instead of disabling caching.
5. Run focused and full ERT, fuzz, warning-clean compilation, and checkdoc.
## Non-goals
- No variable watcher for the CJK default.
- No DP-key or C ABI change.
- No editor-integration changes.
## Rollback
Revert the spacing-signature helper, the two lookup uses, and the three
regression tests. Caches are process-local, so no data migration is needed.

View File

@ -0,0 +1,87 @@
# Plan: Buffer Module Naming 2026-07-28
## Goal
Rename the editor-facing buffer integration module from `ekp-region` to
`ekp-buffer` so its file, feature, customization group, configuration
variables, private namespace, tests, and documentation match the
responsibility it actually owns.
## Intended User Path
- Load buffer integration with `(require 'ekp-buffer)`.
- Keep public operation names whose suffix describes their real operand:
`ekp-justify-region`, `ekp-unjustify-region`, `ekp-justify-buffer`,
`ekp-unjustify-buffer`, and the region protection commands.
- Keep `ekp-auto-justify-mode`, `ekp-org-setup`, `ekp-markdown-setup`,
`ekp-refill-paragraph`, and `ekp-diagnose`; they already follow the
package-level `ekp-` namespace and describe behavior rather than the old
module filename.
- Rename the module-owned public configuration surface and all private
implementation/test symbols from `ekp-region*` to `ekp-buffer*`.
## Scope and Order
1. Rename `ekp-region.el` to `ekp-buffer.el` and
`tests/ekp-region-tests.el` to `tests/ekp-buffer-tests.el`.
2. Rename the provided feature, customization group, module-owned settings,
internal functions/state, and test namespace.
3. Update test runners, GUI/showcase helpers, CI, contributor commands, and
release checks that name the files or symbols.
4. Update current public/developer/audit documentation and add an
Unreleased breaking migration note.
5. Preserve historical phase/postmortem wording; add a new decision record
instead of rewriting history.
## Compatibility Decision
This is an explicit breaking rename requested for the current Unreleased
line. Do not retain `ekp-region.el`, provide `ekp-region`, define obsolete
aliases, or add variable aliases. The migration is one direct replacement:
`ekp-region` becomes `ekp-buffer`. Public region commands retain their
semantic names.
## Behavior Lock
- Existing full ERT suite: 130 tests before the rename.
- Existing buffer tests cover exact round-trip, save failure, isearch,
kill/yank, undo, mode lifecycle, resize/lazy reflow, protection commands,
and whole-buffer/DWIM paths.
- The rename changes lookup names only; rendered output and persisted bytes
must remain identical.
## Cleanup and Fallback Inventory
- Smell: the `ekp-region` namespace names only one operation scope while
the module owns the entire buffer/editor lifecycle.
- No dead-code, duplication, error-handling, dependency, or UI pass is in
scope.
- The selected-window fallback in width calculation is a grounded
undisplayed-buffer boundary and remains unchanged.
- No masking fallback, swallowed error, broad compatibility shim, or
escalation candidate was found.
## Validation
- Focused load and buffer ERT through `ekp-buffer`.
- Default, seeded-permuted, and fresh-process-isolated full ERT.
- C/Elisp fuzz to prove rendered behavior remains unchanged.
- Warning-as-error byte compilation, checkdoc, package-lint/release gates,
shell syntax, and `git diff --check`.
- Scan active runtime, tests, CI, and current documentation for stale
`ekp-region` / `ekp-region-tests` references; historical records are the
only allowed occurrences.
- Read the complete diff before closing the task.
## Stop Condition
The new feature loads from `ekp-buffer.el`, all renamed tests and automation
pass, current documentation presents only the new API, historical references
are explicitly contextual, and no behavior or unrelated source changed.
## Result
Completed as `task017`. The approved direct rename is mechanically
equivalent to the prior runtime/test implementation apart from the
customization parent and explicitly private protrusion helper. All validation
listed above passed; no compatibility shim or unrelated cleanup was added.

View File

@ -0,0 +1,31 @@
# Plan: C API and Arithmetic Contract
## Scope
Resolve the P2-02 half of `issue005`: validate the complete public schema
before extraction, make caller errors explicit, and eliminate signed
overflow for valid inputs.
## Resolution Path
1. Lock malformed single/batch vectors, bad penalty scalars, out-of-range
integers, and a valid extreme-width calculation with direct C red tests.
2. Validate vector types, n/n+1 lengths, scalar types, positivity, and
signed 32-bit range before allocating or extracting.
3. Register one `ekp-c-invalid-input` condition for caller errors; retain
nil only for allocation/no-result.
4. Widen DP line-metric, adjustment, flexibility, and rest intermediates to
64-bit without changing the 15-field ABI.
5. Propagate module signals through the enabled Elisp dispatcher.
6. Build release/sanitizer profiles and run focused/full ERT plus fuzz.
## Non-goals
- No new algorithm or C tokenization.
- No change to valid 15-field layout or result shape.
- Build command/profile portability is the next atomic task.
## Rollback
Restore permissive extraction, int32 intermediates, C 1.5 requirement, and
the error-swallowing dispatcher. Valid persisted data is unaffected.

View File

@ -0,0 +1,29 @@
# Plan: Shell-Free Profiled C Builds
## Scope
Finish `issue005` P2-03: invoke make as argv in the module directory and
separate portable, native, debug, and sanitizer build intent.
## Resolution Path
1. Lock the interactive command shape with a whitespace-path process test
and an invalid-profile control.
2. Remove the one-use process wrapper and shell interpolation; use
`make-process` with a real argv and bound `default-directory`.
3. Make `portable` the Makefile default; isolate `native`, `debug`, and
`sanitize` flags behind explicit `PROFILE`.
4. Update CI and all build documentation to the same vocabulary.
5. Build every profile warning-clean, load/test the portable result, and
compile a copied source tree whose path contains spaces.
## Non-goals
- No dependency on CMake or another build system.
- No automatic compiler/package installation.
- No publishing or replacing a released artifact.
## Rollback
Restore the shell command and monolithic flags, then remove profile tests and
documentation. No persisted data changes.

View File

@ -0,0 +1,42 @@
# Plan: Complete DP Cache Signature
## Scope
Fix `issue001` P1-01 only: DP results must not alias when any
algorithm/cost parameter changes. The auto-CWS paragraph-key defect remains
open for a separate atomic task.
## Resolution Path
1. Drive the public `ekp-dp-cache` path with cached-then-changed parameters.
2. Prove each scenario differs from its baseline and matches a fresh
computation.
3. Replace the width/looseness special-case key with one complete flat
signature.
4. Use structural equality for DP-cache keys and prove identical signatures
still hit the cache.
5. Run focused tests, the complete suite, C/Elisp fuzz, byte compilation,
checkdoc, and diff/static checks.
## Data Ownership
The `ekp-para` DP cache owns reuse. Its key must therefore contain every
runtime value read by the DP that is not already frozen into that paragraph:
- line width and looseness;
- line, hyphen, adjacent-fitness, and consecutive-hyphen penalties;
- last-line minimum ratio and short-line penalty.
Paragraph-construction inputs remain owned by `ekp--para-key`.
## Non-goals
- No cache watchers or eager cache clearing.
- No C ABI change.
- No save/copy integration changes.
- No fix for the separate auto-CWS paragraph cache.
## Rollback
Revert the key/table change and the associated regression tests. No stored
file format or public API changes.

View File

@ -0,0 +1,42 @@
# Plan: Core Rule Ownership 2026-07-28
## Goal
Close audit P3-02 with the smallest net-value refactor: centralize only rules
that are currently duplicated across the hot DP/render paths, without
splitting files or wrapping workflows.
## Existing Owners
- DP reuse identity is already owned by `ekp--dp-key` and paragraph
construction identity by `ekp--para-key`/`ekp--spacing-signature`.
- Ideal line width is already owned by `ekp--line-ideal-pixel` and covered
by a brute-force test.
## Missing Owners
1. Edge-space exclusion is repeated in 1D DP, loose DP, line-glue rendering,
and ideal-width reconstruction.
2. The five layout-marker property names are repeated between the renderer
and region inversion detection.
## Resolution Path
1. Add red direct-rule tests against independent edge-space recomputation
and the complete marker vocabulary/nonstickiness contract.
2. Add one inline edge-space rule and reuse it at all four call sites.
3. Add one marker property constant and reuse it in the renderer and region
detection.
4. Retain existing exact round-trip, DP/C parity, fuzz, compile, and checkdoc
gates.
## Non-goals
- Do not split `ekp.el` or shrink the `ekp-para` struct cosmetically.
- Do not allocate a line-metrics object in the DP inner loop.
- Do not add accessors, adapters, or change the C ABI.
## Rollback
Inline the two rules again. Keep the direct invariants if a measurable
source-mode regression makes the inline helper unacceptable.

View File

@ -0,0 +1,44 @@
# Plan: Dictionary Syntax and Provenance 2026-07-28
## Goal
Make the 50 bundled dictionaries reproducible and accurately described:
every byte has a pinned source/checksum/license trail, and the parser's
handling of replacement/alternative patterns is explicit and tested.
## Scope
1. Confirm upstream replacement syntax and golden words for Hungarian,
Catalan, and Albanian before changing the parser.
2. Add failing tests for the chosen affected-language contract and updater
manifest validation.
3. Pin the LibreOffice dictionaries commit and replace GNU-specific,
moving-HEAD update commands with a fail-closed POSIX workflow.
4. Generate a deterministic manifest containing source path, SHA-256,
syntax flags, and local/upstream license evidence for every dictionary.
5. Document the supported syntax honestly. Implement replacement semantics
only if they fit the fixed-width box/DP model without corrupting logical
text or creating a second typography engine.
6. Run golden tests, manifest verification, a double-run reproducibility
comparison, full ERT, byte compilation, and checkdoc.
## Non-goals
- Do not add automatic language detection.
- Do not silently treat replacement patterns as ordinary Liang patterns.
- Do not move font measurement, tokenization, or rendering ownership into
the dictionary parser.
- Do not add a parser dependency.
## Decision Gate
Replacement patterns conditionally rewrite glyphs at a selected break. If
correct support requires break-specific box widths across Elisp DP, C
marshalling, and rendering, preserve the safe skip behavior, expose/count it,
add language golden limitations, and document that subset. Do not emit
linguistically incorrect breaks merely to increase pattern coverage.
## Rollback
Restore the previous parser and bundle together. A manifest must never claim
checksums or source paths for bytes that are not present.

View File

@ -0,0 +1,87 @@
# Plan: Final Cleanup and Review Gate 2026-07-28
## Goal
Remove residual low-signal code from the repository-remediation diff, prove
every documented acceptance criterion, and obtain independent code and
architecture approval.
## Behavior Lock
- Full ERT: 129/129.
- Seeded permuted ERT: 129/129.
- Every pre-owner ERT passed in a fresh Emacs process.
- C/Elisp fuzz: 300/300.
- Exact round-trip, public protection, C parity, save failure, and release
boundary regressions are present.
## Cleanup Scope
All changed runtime/test/build files, with edits limited to a proven smell.
Documentation is reviewed for drift, not rewritten cosmetically.
## Smells and Order
1. **Fallback gate:** classify every fallback-like path before editing.
2. **Dead code:** remove the unused paragraph accessor stack and route its
few test/render call sites directly to the owning `ekp-para`.
3. **Boundary scan:** replace direct use of dependency-private dispatch
internals with the public dispatcher while preserving prior-filter and
DELETE lifecycle semantics.
4. **Duplication/naming:** retain direct code where extraction would add
glue; make no speculative refactor.
5. **Tests:** add nothing unless cleanup exposes an unprotected behavior.
## Fallback Inventory
- Short-language dictionary resolution: grounded compatibility behavior;
deterministic and covered by `ekp-test-hyphen-lang-fallback`.
- Batch/tty font fallback: grounded external-display boundary; batch tests
and GUI matrix cover fallback and primary environments.
- Interactive C build/load catch: grounded outer command boundary; retains
build buffer and reports the error.
- Missing optional dictionary: grounded optional-resource fallback; only
not-found is caught, unsupported syntax propagates.
- C nil/allocation/no-result and thread-pool sequential paths: grounded
fail-safe/performance-boundary fallbacks; semantic errors propagate and
C/Elisp parity/fuzz cover equivalence.
- Test/fuzz catches: assertion harness boundaries, not production defaults.
No masking fallback, broad compatibility shim, swallowed business-logic
error, or escalation candidate was found.
## Cleanup Result
- Removed nine unused one-line paragraph accessors and routed the remaining
callers to the owning `ekp-para`.
- Removed the direct call to Emacs's private `buffer-substring--filter`.
EKP now temporarily binds the prior filter and delegates through public
`filter-buffer-substring`.
- The first boundary repair exposed a DELETE lifecycle ordering bug in the
focused test: cleanup ran while the temporary prior-filter binding was
active. The final implementation inhibits cleanup during dispatch, unwinds
the binding, then performs ownership cleanup against the real slot.
- Direct private-API scan is clean. No new abstraction, file, dependency,
compatibility shim, or silent fallback was introduced.
## Quality and Review Gate
Run full/default/permuted/isolated ERT, fuzz, portable/native/debug/sanitize
C builds, warning-as-error Elisp compilation, checkdoc, pinned
package-lint, shell/YAML/release/dictionary/static checks, GUI matrix, full
diff review, private-boundary scan, then independent `code-reviewer`
APPROVE and `architect` CLEAR.
Current post-fix evidence: default and seeded-permuted ERT 130/130; every
one of 130 ERT tests in a fresh Emacs process; C/Elisp fuzz 300/300;
warning-clean Elisp/C builds; checkdoc and pinned package-lint; release,
dictionary, shell/YAML/static checks; and live GUI matrix 7/7 with a clean
single-window screenshot. Independent code review covered 98 current paths
with zero findings and returned `APPROVE`; the independent architecture
invariant review returned `CLEAR`. `task015` is closed.
## Stop Condition
All issue/task records are closed, both independent review lanes are clean,
all gates pass from the final source state, and no remote publish/tag/push
action has been performed.

View File

@ -0,0 +1,27 @@
# Plan: Fail-Closed GUI Verification
## Scope
Resolve `issue007`: retain the explicitly loaded developer GUI matrix while
making any failed row a nonzero automation result.
## Resolution Path
1. Add a batch-safe forced-failure control for the missing assertion
boundary.
2. Preserve each case as structured data and format all rows centrally.
3. Print the full table, then exit 1 in batch mode when any row fails.
4. Document the tests-only loading boundary and automation status.
5. Run focused/full ERT, a non-default permutation, and the live seven-case
GUI matrix; inspect a clean guarded screenshot.
## Non-goals
- No headless approximation of real pixel rendering.
- No promotion of showcase/matrix code into the runtime package.
- No GUI CI provider or new dependency.
## Rollback
Restore string-only case results and table rendering, then remove the two
batch-boundary tests. No persisted data changes.

View File

@ -0,0 +1,44 @@
# Plan: Hot-loop Performance and Nil Cache 2026-07-28
## Goal
Remove only measured allocation pathologies and make cached “no hyphen”
results real cache hits without changing tokenization or hyphenation output.
## Baseline Evidence
Source-mode, three-run adversarial benchmark on Emacs 30.2:
| Length | one Latin token | dense hyphen insertion |
|---:|---:|---:|
| 1,000 | 0.304 s | 0.082 s |
| 2,000 | 0.755 s | 0.061 s |
| 4,000 | 1.202 s | 0.291 s |
| 8,000 | 3.133 s | 0.945 s |
At 8× input, tokenizer time grew about 10.3× and dense insertion about
11.5×. Both justify a linear builder. Separately, two lookups of a word
with no breaks call `ekp-hyphen--compute` twice.
## Scope
1. Add a failing call-count test for cached nil and behavior controls for
long/propertized tokenizer and dense insertion output.
2. Replace per-character accumulator concatenation with fragment lists that
concatenate once per emitted box.
3. Build inserted hyphen output from original word slices in one pass.
4. Use an explicit miss sentinel for the word-position cache.
5. Add reusable adversarial scaling output to `tests/ekp-bench.el`.
6. Re-run before/after scaling, focused/full ERT, warning-as-error byte
compilation, checkdoc, fuzz, and diff review.
## Non-goals
- Do not move tokenization or hyphenation into C.
- Do not add a rope/builder abstraction or dependency.
- Do not optimize ordinary paragraph code whose scaling is not measured.
## Rollback
Restore the string accumulators and insertion loop together. Keep the nil
cache regression test even if the builder change is reverted.

View File

@ -0,0 +1,30 @@
# Plan: Composable Buffer Integration Lifecycle
## Scope
Resolve `issue003`: compose and restore an existing substring filter, and
remove save/search/copy integrations when no justified span or auto mode
needs them.
## Resolution Path
1. Drive the public `filter-buffer-substring` path with a prior local filter
and prove EKP currently overwrites it.
2. Record the prior filter and whether it was local; define prior-filter
then logical-inversion order so DELETE remains owned by the prior path.
3. Split internal structural unjustify from the public lifecycle wrapper.
4. Derive integration removal from actual spans plus auto-mode state,
including external deletion and mode shutdown.
5. Verify copy, delete, local/global restoration, manual unjustify,
isearch, mode disable, full ERT, fuzz, compilation, and checkdoc.
## Non-goals
- No new command/menu surface.
- No general multi-filter framework.
- No save-serialization redesign beyond preserving `task004`.
## Rollback
Restore direct filter assignment and the prior public unjustify body, then
remove the composition/lifecycle tests. No persisted state changes.

View File

@ -0,0 +1,36 @@
# Plan: Interactive Protection Workflow 2026-07-28
## Goal
Make the existing no-break and verbatim workflows discoverable and
unambiguous without inventing persistence or another command layer.
## Resolution Path
1. Exercise all four protection commands through `call-interactively`.
2. Prove no-break affects the real formatter and verbatim affects the real
region formatter.
3. Give every property change explicit feedback that states its
current-buffer-session lifetime.
4. Expose the existing commands in the minor mode's standard menu and mode
help; keep the existing `fill-paragraph` remap.
5. Document removal commands and session-local lifetime in both READMEs.
## Default Behavior
Protection text properties remain local to the live buffer. Saving writes
logical text only; reopening does not restore manually applied protection.
Mode-native faces/predicates remain the persistent-source integration path.
## Non-goals
- Do not serialize text properties or change the file format.
- Do not add a transient UI, dependency, or global key binding.
- Do not infer language/mode syntax beyond the existing Org/Markdown
presets.
## Validation
Focused red/green public-command ERT, full ERT in default/permuted/isolated
orders, warning-as-error byte compilation, checkdoc, and documentation/diff
checks.

View File

@ -0,0 +1,94 @@
# Plan: Post-Audit Hardening 2026-08-20
## Context
The repository audit after `183f256` found runtime and gate defects that are
independent of the already accepted text-property layout architecture. This
plan stays inside `phase-kp-overhaul-20260726`; it does not reopen closed
issues or weaken the source-clean display contract.
## Goals
1. Make public buffer width errors fail before projection mutation and keep
the previous projection intact on every rejected request.
2. Define and test the backward-delete live projection invariant before
changing the row-crossing owner.
3. Resolve locale spellings to the exact bundled dictionary before short-code
fallback, and reject malformed C position vectors at the module boundary.
4. Restore the showcase's automatic inline-code contract and make every test
runner use current source and the complete ERT set.
5. Remove manual buffer integrations when no projection or auto-mode owner
remains.
6. Re-profile `issue018`/`task030` after the correctness fixes with a
source-fresh evaluator and close it only if the locked source-instrumented
16 ms contract is actually met.
## Scope and milestones
- M1 (`task037`, `task042`): buffer width validation, failure atomicity, and
integration lifecycle.
- M2 (`task038`): backward-delete projection semantics and a regression that
drives the public edit path.
- M3 (`task039`, `task040`): exact hyphenation locale lookup and complete C
position-vector validation, with direct boundary tests.
- M4 (`task041`): showcase automatic inline face, source-first test loading,
and complete random/isolated selection.
- M5 (`task030`, `issue028`): make the locked width/engine matrix source-fresh,
then optimize only the remaining structural commit owner.
## Non-goals
- Do not change valid KP output, the 15-field C ABI, or the source-clean
buffer representation.
- Do not hide latency with debounce, stale layout reuse, skipped publication,
global GC changes, or test-only exceptions.
- Do not close `issue018` from byte-compiled production numbers alone.
- Do not rewrite historical postmortems; append current decisions and
verification evidence.
## Acceptance gates
- Invalid or non-positive buffer widths signal before any owned property,
span, filter, hook, point, mark, or modified-state change.
- Backward deletion either preserves the committed projection as specified or
has an explicit revised contract documented and tested through the public
command path.
- `de-CH`/`de_CH` and equivalent exact locale spellings select the same exact
dictionary; unknown locales use the documented short-code fallback.
- Out-of-range, unsorted, or duplicate C hyphen/forbidden positions signal
`ekp-c-invalid-input`; valid arrays retain exact Elisp/C parity.
- Clean source, stale bytecode, normal, random, and isolated test runners
all exercise the same complete current ERT inventory and the showcase's
automatic inline contract.
- The live evaluator explicitly loads source from both baseline and candidate
roots; a bytecode-backed `pass:true` is not valid evidence.
- Full ERT, fuzz, C build/tests, byte compilation, checkdoc, package-lint,
release, dictionary, and focused performance gates pass before closure.
## Risks and dependencies
- Backward-delete behavior is a product invariant, not a local condition;
implementation must follow the existing spec and real public-path evidence.
- C position validation must match the DP's exclusive break-index domain and
preserve sorted-vector assumptions used by binary search.
- The performance gate depends on trustworthy source loading; stale ignored
`.elc` files must not influence any result.
## Stop condition
Stop only when all tasks in this plan have current issue/task evidence,
closed issues have user-visible or contract-level verification, `issue018`
is either genuinely closed by its locked gate or explicitly remains open with
fresh measurements, and the complete diff is committed and reviewed.
## 2026-09-01 Emacs 31 strict-compile follow-up
- `task044` / `issue029` replace the six remaining single-binding
`if-let`/`when-let` forms in production and the three forms in the live
evaluator with their semantically identical starred variants.
- No layout, hyphenation, font-selection, module-path, or evaluator behavior
may change. Existing ERT coverage locks those paths; Emacs 31.1 WERROR byte
compilation is the regression gate for the compatibility defect itself.
- Closure requires root integration compilation, strict production and changed
test-tool compilation, the complete normal and seeded-random ERT suites, C
build/parity fuzz, release/dictionary checks, checkdoc, and diff-check.

View File

@ -0,0 +1,36 @@
# Plan: Property-Sensitive Paragraph Fast Path 2026-07-28
## Scope
Fix `issue010` only: mutating layout-relevant text properties on an already
cached string object must not reuse stale paragraph preprocessing.
## Resolution Path
1. Warm the public paragraph resolver with the exact string object.
2. Mutate `ekp-no-break` on that object and prove the current fast path
returns stale boxes/break permissions.
3. Make the fast path compare the same complete structural paragraph key as
the hash cache instead of maintaining a partial parallel signature.
4. Remove invalidation watchers that become redundant once one key owns both
paths.
5. Verify the mutated object matches a fresh computation across CJK and
Latin-with-space inputs, then rerun every final gate.
## Ownership
`ekp--para-key` is the sole owner of paragraph preprocessing identity. The
most-recent lookup may bypass the hash table, but it may not bypass or
partially reimplement that key.
## Non-goals
- No new cache layer or eager global invalidation.
- No public API, rendering, C ABI, or serialized-data change.
- No attempt to observe arbitrary font/theme mutation; the documented
`ekp-clear-caches` boundary remains unchanged.
## Rollback
Restore the partial fast-path tuple and its variable watchers. No persisted
state needs migration, but the same-object property regression returns.

View File

@ -0,0 +1,40 @@
# Plan: Release and CI Governance 2026-07-28
## Goal
Make repository-local release evidence reproducible: CI inputs are immutable,
the supported Windows platform is exercised, `.phrase` decisions are tracked,
and one local gate checks the version and workflow invariants before a release.
## Scope
1. Add a failing static release-gate test for action pins, platform coverage,
tracked decision records, package/changelog version agreement, and the C
module version pair.
2. Pin third-party actions to full upstream commit SHAs with readable tag
comments.
3. Add a Windows batch-test job using the existing public test runner.
4. Stop ignoring `.phrase` and document the release checklist, including
remote/tag and artifact checks that cannot safely be automated locally.
5. Run the gate, YAML parse/static checks, default tests, and diff checks.
## Non-goals
- Do not push commits or tags, publish artifacts, or rewrite existing tags.
- Do not claim that an unrun remote GitHub Actions workflow is green.
- Do not add a release framework or package dependency.
## Risks and Mitigations
- **Pinned actions age:** keep the human-readable upstream tag beside each SHA
and make updates an explicit reviewed change.
- **Windows shell differences:** call Emacs directly from PowerShell instead
of depending on the POSIX test wrapper.
- **Local versus remote state:** the gate checks repository invariants; the
release checklist separately requires a clean commit, signed/annotated tag,
remote CI, checksums, and remote tag verification.
## Rollback
Revert the gate, workflow, `.gitignore`, and release documentation together.
Do not restore floating action refs independently.

View File

@ -0,0 +1,48 @@
# Plan: Repository System Audit 2026-07-28
## Context
This audit is a follow-up inside the existing `phase-kp-overhaul-20260726`.
It does not open a new phase because the requested repository-wide health
review is part of the same overhaul purpose.
## Milestones
1. Inventory the repository, architecture, public contracts, current phase,
and release state.
2. Audit Elisp core, C module, buffer/region integration, tests, GUI,
dictionaries, documentation, and release maintenance.
3. Reproduce high-risk findings and run the existing verification surfaces
from a clean source snapshot.
4. Publish a prioritized system audit with evidence, inference boundaries,
improvement directions, extension options, and explicit non-goals.
5. Record open issues and correct factual drift in public documentation.
## Scope
- Runtime source is read-only for this task.
- Documentation changes may add the audit and correct facts already proven
by the repository.
- No release, push, dependency addition, or architectural implementation.
## Evidence
- Emacs 30.2 ERT suite and C/Elisp fuzz suite.
- Clean-copy byte compilation, checkdoc, C release build, ERT, and fuzz.
- GUI fit matrix across scaling, remapping, fringe, and narrow-window cases.
- Targeted negative probes for caches, save failure, copy-filter composition,
and test isolation.
- Local/remote Git and tag inspection.
## Risks and Dependencies
- Windows and remote CI were not available locally.
- macOS sanitizer runtime policy prevented loading the debug C module.
- The GUI matrix produced numerical evidence, but the clean screenshot path
was obstructed by a macOS permission prompt.
## Stop Condition
The audit is complete when findings are prioritized and documented, factual
README drift is corrected, phase records are updated, the complete diff is
reviewed, and no runtime source file has changed.

View File

@ -0,0 +1,30 @@
# Plan: Non-mutating Save Serialization
## Scope
Resolve `issue002`: saving justified content must write logical text while
the display buffer remains unchanged on success, filesystem failure,
encoding failure, or interruption.
## Resolution Path
1. Drive `save-buffer` through real failure and `quit` paths and prove the
old before/after-hook transaction leaves the buffer unformatted.
2. Stop mutating the source buffer during save.
3. Use Emacs's `write-region-annotate-functions` buffer-switch contract to
serialize a logical copy at the actual write boundary.
4. Preserve later annotation/coding processing and clean the copy on
success, retry, integration removal, or source-buffer teardown.
5. Verify success, three failure classes, retry, full ERT, fuzz,
warnings-as-errors compilation, and checkdoc.
## Non-goals
- No global advice around `save-buffer` or `write-region`.
- No replacement implementation of Emacs file saving.
- No copy-filter composition or manual-unjustify lifecycle change.
## Rollback
Restore the before/after save hooks and remove the logical write-buffer
tests. No file format or persisted metadata changes.

View File

@ -0,0 +1,29 @@
# Plan: ERT State and Order Isolation
## Scope
Resolve `issue004`: make the parshape dispatch regression truthful, restore
all test-owned EKP configuration, and make order dependence observable.
## Resolution Path
1. Reproduce the named parshape test alone with the C module loaded.
2. Drive the public formatter with a real parshape and an observable C-call
negative control.
3. Dynamically bind every mutable EKP option and spacing state owned by the
shared clean-state fixture.
4. Add public-ERT-API runners for reproducible permutation and one fresh
Emacs process per test.
5. Run the focused matrix, full permutation, full isolation, compilation,
and checkdoc.
## Non-goals
- No test framework dependency.
- No reliance on private ERT functions.
- No production formatter change.
## Rollback
Restore the partial fixture and default runner, then remove the new
permutation/isolation entry points and CI step. No persisted data changes.

View File

@ -0,0 +1,411 @@
# Plan: Text-Property KP Layout Engine 2026-07-29
The authoritative user-visible contract is
`spec_text_property_layout_engine_20260729.md`.
## Goal
Resolve `issue011` at the representation owner. The real buffer keeps only
the user's logical characters. EKP computes Knuth-Plass breaks, glue widths,
indentation, and discretionary hyphens as a reusable layout plan, then a
buffer renderer projects that plan with text properties on existing source
characters.
No buffer path may insert layout spaces, soft newlines, or discretionary
hyphens. No buffer path may use overlays.
## User Contract
- `buffer-string`, `buffer-substring-no-properties`, direct character
access, search, syntax, markers, point, save, and ordinary editing observe
the original character sequence.
- Manual commands produce the same KP break and glue decisions as the
string API.
- `ekp-auto-justify-mode` keeps the current unfinished visual row natural and
preserves unaffected committed break anchors during continuous editing.
- Ordinary same-row edits do not call whole-hard-line planning. A dirty edit
transaction owns the local natural island and a committed projection owns
the saved source, plan, signatures, spans, and anchors.
- Native soft-wrap crossing atomically replans the completed hard-line prefix
once. Signature diffing limits the property update; core DP remains the
owner of the committed result.
- Hard newline/paragraph completion, the next real edit outside the dirty
island, explicit refill, and width/font/layout-context change are commit
events. Point-only motion is never one.
- Reversible source edits restore the saved projection exactly rather than
relying on a recomputation to happen to reproduce it.
- Layout-property updates do not change the modified flag, undo history,
character-modified tick, point, mark, or external modification hooks.
- Turning the mode off removes only EKP-owned projection properties and
restores every pre-existing property exactly.
- Final-pass emergency layout remains owned by core K-P. Ordinary underfull
candidates use finite emergency stretch and normal badness/demerits; the
final active path is preserved with TeX-style zero-increment artificial
demerits only when an overfull candidate would otherwise extinguish it.
The buffer renderer and policy compiler must not add CJK-orphan, unit,
hard-atom-adjacency, or screenshot-specific compensation.
## Architecture
### Core owner: `ekp.el`
Introduce one semantic paragraph plan built from the existing `ekp-para`,
DP breaks, line glues, and source box offsets. A plan contains ordered
lines; each line records its source/box bounds, leading indentation,
interior glue targets, chosen break, and discretionary-hyphen state.
The plan contains no buffer positions, markers, overlays, or display
property forms.
`ekp-pixel-justify` remains the public string formatter. Its renderer
consumes the plan and preserves the existing reversible string output and
tests. The buffer renderer consumes the same plan but has different
representation rights.
### Buffer owner: `ekp-buffer.el`
The buffer renderer maps plan operations to existing source characters:
- ASCII source-space glue:
`((space-width FACTOR) (min-width ((TARGET))))`.
- Zero-source CJK/mixed glue: `min-width` on the preceding complete
grapheme, with a target equal to the grapheme's natural width plus glue.
- Leading indentation/alignment: `line-prefix` over the complete planned
display line.
- Break at source whitespace: the first break-space displays as a newline;
remaining boundary whitespace displays as empty.
- CJK break: the preceding complete grapheme displays as
`GRAPHEME + NEWLINE`.
- Latin discretionary break: the preceding complete grapheme displays as
`GRAPHEME + HYPHEN + NEWLINE`, with cursor anchoring on the reproduced
grapheme.
- Paragraph-edge whitespace stripped by the KP model remains in the buffer
and displays as empty.
All projection properties are installed through `with-silent-modifications`
and are nonsticky. Copy/kill strips only EKP projection metadata so stale
layout cannot be yanked elsewhere.
### Display ownership
EKP never overwrites an unowned replacing `display`, `line-prefix`, or
`wrap-prefix` value. A paragraph with a property that cannot be composed
losslessly is kept verbatim and reported by diagnostics. Removing a layout
restores the exact pre-existing property values.
### Multiple windows
Text properties are buffer-wide. One buffer therefore has one
authoritative plan: the narrowest live window showing that buffer, matching
the current `ekp-buffer--effective-width` behavior. Wider windows may have
unused right-side space; no window may receive an overflowing plan.
Simultaneous different KP plans for one buffer are explicitly not claimed.
## Live Editing State Machine
1. The active hard line owns one committed projection: baseline source,
whole-hard-line plan, line signatures, projected spans, and anchors.
2. The first edit in one visual row snapshots that state and opens a dirty
island. `before-change` removes only the affected projection; it never
clears an unrelated suffix.
3. Same-row edits update source and dirty bounds only. Existing glue and
native soft wrapping absorb local edits without whole-hard-line DP.
4. Restoring the baseline source restores the saved owned properties and
committed state immediately and closes the transaction.
5. Crossing a native soft-wrap boundary commits once: recompute or reuse the
whole-hard-line plan, derive the completed prefix, and apply the
signature-diff update as one silent publication. The new current row is
natural.
6. A real edit outside the dirty island commits the old transaction before
opening a new one. Hard newline/paragraph completion, explicit refill, and
width/font/layout-context changes are also commit boundaries.
7. Point-only motion anywhere is display read-only and cannot commit,
finalize, plan, touch cache identity, or write projection properties.
8. IME composition stays entirely native until commit. Stale generations,
foreign display ownership, unsupported shrink, oversized hard lines, or
projection failures fail closed to native display for that hard line.
9. Hard-paragraph completion uses one existing full KP quality pass and
starts a new natural active hard line.
## Milestones and Gates
### M1 — Core layout plan and string parity
- Add plan structs and `ekp-layout-plan`.
- Make the string renderer consume the plan.
- Gate: every existing core rendered string remains
`equal-including-properties`; C/Elisp parity and fuzz remain green.
### M2 — Static text-property buffer renderer
- Replace delete/insert justification with property projection.
- Remove physical inversion/save/isearch adapters that no longer own a
character transformation.
- Gate: source characters, positions, modified state, undo, hooks, and
foreign properties are invariant; exact GUI glue/break/hyphen/indent
probes pass.
### M3 — Seamless live-editing foundation
- Implement composition deferral, resize generation cancellation,
visible-first large-buffer work, the single-paragraph planning guard, and
exact editor-state preservation.
- Gate: source, point/mark, whitespace, IME, resize, and teardown regressions
are locked before selecting the final live row-boundary model.
### M4 — Product and repository closure
- Update bilingual user/developer documentation, `issue011`, task/change
records, changelog, and a design postmortem.
- Run default/permuted/isolated ERT, fuzz, byte compilation with warnings as
errors, checkdoc, C parity/build gates, GUI matrix, dynamic verification,
anti-slop cleanup, and independent architecture/code review.
### M5 — Interaction regression closure
- Preserve the mark marker and `mark-active` as independent editor state
across every reprojection, including showcase width changes.
- Keep active-line edge whitespace natural while retaining edge cleanup on
committed static lines.
- Gate: focused red/green ERT, a public-command interaction matrix, clean
GUI width-key/single-space evidence, full repository gates, and user
confirmation for `issue012` and `issue013`.
### M6 — Native progressive editing
- Delete the partial-KP live-flow model and its lookahead/convergence state.
- Keep the active source tail under native Emacs redisplay ownership.
- Align only completed native screen rows without live break/hyphen
projection; run complete KP at hard-paragraph completion.
- Own native soft wrapping while auto mode is active, including narrow
partial-width windows, and restore the previous display-variable
ownership on teardown.
- Gate: per-keystroke mixed-text GUI evidence, backward-edit invalidation,
paragraph-completion transition, narrow split-window soft wrapping, full
repository gates, and user confirmation for `issue014` and `issue015`.
Historical note: M6 is superseded for live planning by M7. Its active-tail
and soft-wrap lifecycle decisions remain prerequisites, but native visual
rows are not the durable planning unit.
### M7 — Semantic hard-line prefix editing
- Status: implementation, automated/performance/GUI gates, and independent
code/architecture reviews are complete. `issue016` remains open for the
user's visible editing confirmation.
- Replace native-row commitment with whole-hard-line plan consumption in
`ekp-buffer`.
- Keep `ekp.el`, core DP semantics, C ABI, DP schema, and
`ekp-layout-plan` contracts unchanged.
- Project only complete semantic plan lines before point; keep the
point-containing plan line and all following source natural.
- Add buffer-local history cache entries keyed by hard-line text,
text-property/layout context, authoritative width, font/face/text-scale
context, and EKP layout parameters.
- Use semantic line signatures to avoid rewriting unchanged prefixes and
to prove later edits can revise earlier breaks/glue together.
- Gate: public-path red/green ERT, GUI dynamic recording, latency/cache
benchmark, full repository gates, independent review, and user
confirmation for `issue016`.
### M8 — Source-edit-owned live frontier
- Status: superseded by M10 for edit-trigger ownership. The zero-work
point-motion invariant remains current; `issue017` remains open for the
user's visible confirmation.
- Preserve the latest real source-edit position as the live frontier.
- Make point-only motion within the active hard line perform no DP, cache,
or text-property work.
- Preserve the frontier across width/font/layout-context reflow and map it
into the resulting whole-hard-line plan.
- Gate: focused property-identity RED/GREEN ERT, public earlier-line edit,
reflow-after-motion regression, full repository gates, dynamic GUI
point-motion evidence, independent review, and user confirmation.
### M9 — Narrow unique-append latency
- Status: re-profiled as `issue018`/`task030` after M10; ready for work.
Ordinary same-row planning is gone, so only structural-commit spikes are
in scope.
- Establish a repeatable width/length matrix before optimizing.
- Reduce new-source-state planning cost without stale plan reuse,
debounce, skipped publication, global GC changes, or weaker layout
semantics.
- Keep task029's point-motion zero-work invariant as a permanent gate.
### M10 — Stable live projection transactions
- Status: complete as `issue019`/`task031`; automated, static, benchmark,
and reviewed temporal GUI gates pass. `issue019` remains open only for
user-visible confirmation.
- Replace frontier-owned native suffix invalidation with a committed
projection baseline and one persistent dirty edit transaction.
- Preserve unaffected break anchors during middle-line edits; use native
soft wrapping for local word migration.
- Replan only at soft-wrap or structural commit boundaries and publish the
changed prefix atomically.
- Restore reversible edits exactly from the saved baseline.
- Preserve core DP/C ABI/schema/plan contracts and the point-motion
zero-work invariant.
- Gate: focused RED/GREEN public-path ERT, complete repository gates,
re-profiled live benchmarks, and temporal GUI evidence.
### M11 — C-backed resize latency
- Status: `task032` developer-complete; `issue020` awaits user confirmation.
- Freeze the current portable module and measure an interleaved
baseline/candidate width-and-length matrix with raw p50/p95 evidence.
- Attribute complete resize time across plan construction, Emacs/C
marshalling, C DP, and projection publication before selecting a change.
- Require exact frozen-C and Elisp layout parity; no stale width reuse,
debounce inflation, skipped reflows, approximate planning, or global GC
workaround is acceptable.
- Gate: at least 20% paired p50/p95 improvement, candidate p95 at or below
50 ms, complete automated/static gates, and temporal GUI resize evidence.
- Result: core p50/p95 improved by 33.25%/34.09% to 15.318/27.687 ms;
complete resize improved by 43.51%/41.03% to 15.900/27.487 ms. Exact
frozen-C/Elisp parity and all automated, static, and GUI gates pass.
### M12 — Rigid inline atom emergency breaking
- Status: `task033` implemented and developer-verified as the earlier
rigid-atom repair. `task036` supersedes its ordinary-underfull emergency
model; `issue021` was closed after user-visible confirmation.
- Reproduce the showcase's narrow CJK prefix plus `ekp-no-break` atom through
the public string and semantic-plan paths before changing the algorithm.
- Historical task033 design: keep the strict K-P pass and the established
first-candidate emergency transition unchanged, then add a narrow
forbidden-run prefix fallback. The current task036 model replaces that
fallback with fixed final-pass emergency stretch plus content-independent
active-path preservation.
- Preserve atom integrity, source text, Elisp/C parity, looseness/parshape
semantics, append correctness, and the buffer projection contract.
- Gate: focused RED/GREEN core and buffer regressions, complete ERT in normal
and random order, isolated tests, C/Elisp fuzz, warning-as-error builds,
static/release checks, and clean static plus dynamic 280px GUI evidence.
- Result: the failing boundary moved from 1 to the full prefix boundary 11 in
Elisp, C, and public buffer paths. Normal/random/isolated ERT pass 201/201,
fuzz passes 300/300, static and build gates pass, and reviewed
480→280→340→280 GUI evidence returns PASS.
### M13 — Configurable break policies and orphan-glyph closure
- Status: `task034` implementation, focused automated evidence, and G004 GUI
evidence are recorded. G005 repository-wide gates and G006 cleanup are
complete. `task034` is closed for developer work, `issue021` is closed
after user visual confirmation, and independent final code review returned
APPROVE with architecture status CLEAR.
- Implement the locked A2/B2/C2/D1/E1/F1/G2/H1/I1/J1/K1/M1 contract:
inline code defaults to no-hyphen rather than no-break, known inline faces
are recognized through mode profiles, region policy outranks explicit
local values which outrank mode profiles and globals, block faces remain
verbatim, explicit no-break never downgrades, automatic no-break downgrades
to no-hyphen when overwide, inline and block faces use separate paths,
manual properties stay session-only, hyphenation defaults to auto,
URL/path/identifier default to no-hyphen, compact number-unit defaults to
no-break, kinsoku defaults to common, overlong tokens default to emergency,
and buffer measure defaults to the narrowest live window.
- Keep the chosen architecture: resolve private structural policy intervals
before tokenization, compile them into existing hyphen positions and
forbidden-break vectors before DP, preserve the original source in the
semantic plan, and keep the C boundary at the current 15 arguments and
15-field batch payload unless architecture review is reopened.
- Split block and inline ownership in `ekp-buffer`: `ekp-buffer-skip-faces`
remains paragraph-level verbatim, `ekp-buffer-inline-faces` annotates only
exact inline intervals, and `ekp-buffer-mode-policy-alist` is consulted
without auto-copying profile values into buffer locals.
- Add public region controls for `ekp-break-policy`: normal, enable
hyphenation, disable hyphenation, and clear. Existing no-break/verbatim
commands remain the only hard-atom and paragraph-bypass controls.
- Fix the remaining visible quality bug from the user's latest screenshot:
the 280px showcase path must not isolate any pathological single-CJK source
line around inline code, including `行`, `内`, or `永`, when a legal
non-emergency alternative exists.
- Gate: follow the RALPLAN test specification R1-R8 plus GUI verification.
Required evidence includes focused RED/GREEN ERT, full normal/permuted/
isolated ERT, 300-case property fuzz, C/Elisp parity, warning-as-error
Elisp compilation, C builds/tests, checkdoc/package/static/release gates,
and reviewed fullscreen single-window screenshots for inline wrapping,
no orphan glyph, explicit no-break, block verbatim, measure modes, and
overlong-token modes.
- Current evidence: G002 records core policy compilation, C parity, 300-case
fuzz, byte compilation, checkdoc, C build, and focused policy/cache gates.
G003 records buffer/profile/local/region controls, diagnostics, generation
reflow, source/editor invariants, focused core/buffer suites, byte
compilation, and checkdoc. G004 records GUI verifier ERT 7/7 and a clean
single-window run at `/tmp/ekp-g004-evidence.Tp77dW` with 12/12 checkpoints,
no failed assertions, 25.75s/206-frame recording, no black segments,
automatic inline wrapping 3→2→3 lines across 280→340→280, source-space
internal inline breaks, explicit hard atom, block verbatim, C active, and
no stale policy projection after the no-hyphen→normal transition.
- Final evidence: G005 recorded repository-wide default/seeded/isolated ERT,
property fuzz, warning-clean Elisp/C builds, checkdoc/package/release/
dictionary/static gates, performance checks, and full diff review. G006
cleanup touched only `ekp.el` and `ekp-buffer.el`; targeted cleanup suites
passed 6/6 and 3/3, full core ERT passed 125/125, full buffer ERT passed
120/120, and byte compilation, diff-check, and C build gates passed. G006
was then review-blocked on nested semantic-plan cache aliasing and malformed
non-nil C fallback semantics. G007 resolved those first blockers: cached
semantic plans return consumer-owned copies of plan-owned mutable payloads
while intentionally sharing `para`; malformed non-nil C single/batch output
signals `ekp-backend-contract-error`, with nil-result Elisp fallback and
the 15-argument/15-field ABI unchanged. G007 final review found one
remaining string-leaf context alias; G008 resolves it by recursively
copying cons/vector/string context payloads both for snapshot/cache-key
creation and returned plans. Fresh G008 gates record formal resize parity
true, ERT 268/268, fuzz 300/300, release pass, core p50/p95
23.266/36.363ms, resize p50/p95 23.761/36.889ms, and byte-compiled current
public path p99 below 16ms with zero GC. After review remediation made all
changed tests warning-clean and synchronized the eighth C setter parameter,
final independent code review returned APPROVE and architecture review
returned CLEAR.
### M14 — Final-pass emergency stretch
- Status: `task035` was falsified; `task036` is implemented and its developer
gates pass. `issue021` is closed after user-visible confirmation.
- Correct the core K-P owner, not the renderer or policy compiler. Strict
pass behavior remains unchanged.
- In the final pass, ordinary underfull candidates receive finite background
emergency stretch and still compute adjustment ratio, badness, fitness, and
demerits. Separately, when an overfull candidate would otherwise eliminate
the final active path to a breakpoint and no non-overfull candidate survives
there, install the best provisional path with tight fitness and zero
incremental demerits, matching TeX's `artificial_demerits` purpose.
- Do not add CJK-orphan, unit, or screenshot-specific penalties. Unit suffix
configuration remains only a token-classification input, not a layout
scoring rule.
- Gate: focused core/public-buffer/C parity tests and GUI-oracle checks must
reject any isolated CJK source line in the showcase paragraph at checked
widths. Final fullscreen visual review remains required before closing
`issue021`.
- Result: the Elisp 1D, looseness/parshape, and C paths share the same rule;
explicit atom interiors remain unbreakable but adjacent legal boundaries
remain legal; the public 15-field/15-argument C contract is unchanged.
Focused regressions pass 8/8, the emergency selector passes 10/10, the
core/buffer/GUI oracle passes 8/8, full ERT passes 288/288, seeded and
isolated core suites pass 279/279, fuzz passes 300/300, all build/static/
release gates pass, performance gates pass, and reviewed dynamic GUI
evidence at `/tmp/ekp-g009-evidence-retry.UOpPNp` returns PASS.
## Stop Gates
- Stop the affected paragraph instead of stealing a foreign replacing
display owner.
- Stop exact shrink projection for tabs or non-ASCII whitespace when
`space-width` cannot express it; keep that paragraph verbatim and report
why.
- Stop if the live-edit implementation appears to require a core DP, C
ABI, DP schema, or `ekp-layout-plan` semantic change; that means the
buffer/core boundary has been crossed incorrectly.
- Stop and return to diagnosis after two failed fixes for the same display
or live-edit invariant.
- Do not claim completion while any source-character, undo, modified-state,
property-restoration, GUI, or dynamic-edit invariant is unproved.
## Rollback
The string renderer can be restored to the current direct implementation
because M1 preserves its public result. The buffer renderer can be reverted
to the last released physical representation only as a full rollback; no
compatibility shim or mixed physical/property backend will be retained.

View File

@ -0,0 +1,244 @@
# Spec: Source-Clean Text-Property KP Layout 2026-07-29
## Summary
`ekp-buffer.el` presents Knuth-Plass layout without rewriting the buffer's
character stream. The core computes a reusable semantic layout plan;
manual buffer commands and `ekp-auto-justify-mode` project that plan using
text properties on existing source characters only.
The character sequence remains the document. Visual glue, breaks,
indentation, and discretionary hyphens are display metadata: EKP creates no
overlay and inserts no layout character.
## Goals
1. Keep `buffer-string`, `buffer-substring-no-properties`, direct character
access, search, syntax, markers, saving, and ordinary editing on the
original character sequence.
2. Use the same core glue, break, indentation, and hyphen decisions for the
compatible string renderer and the non-mutating buffer renderer.
3. Make automatic layout stable while editing: committed prefix lines keep
their projection until a structural commit, the current unfinished visual
row stays under Emacs redisplay ownership, and a middle-line edit
naturalizes only its local dirty island while preserving unaffected break
anchors.
4. Preserve undo, modified state, character-modified tick, point, mark,
copy/kill, isearch, IME composition, narrowing, and mode lifecycle.
5. Fail closed around display ownership: unsupported or conflicting hard
paragraphs remain naturally displayed and are diagnosable.
## Non-Goals
- Hiding EKP metadata from Lisp that explicitly inspects text properties.
The clean-text guarantee concerns the character sequence; copy/kill strips
EKP-owned projection metadata.
- Independent simultaneous layouts for different-width windows showing the
same buffer. The narrowest live window is authoritative.
- Shrinking tabs or non-ASCII whitespace with `space-width`.
- Claiming verified bidi, Arabic, Hebrew, Indic shaping, ligatures, or
arbitrary multi-codepoint grapheme behavior beyond the tested LTR
Latin/CJK boundary.
- Retaining `(require 'ekp-region)` or old private/configuration names.
## User Flows
### Load and use buffer commands
1. The user adds the package to `load-path`.
2. The user loads `(require 'ekp-buffer)`.
3. `M-x ekp-justify-region` projects the active region, or the hard
paragraph at point when no region is active.
4. `M-x ekp-justify-buffer` projects the accessible buffer.
5. `M-x ekp-unjustify-region` or `ekp-unjustify-buffer` removes only
EKP-owned projection properties; source characters do not need
restoration.
### Edit with automatic layout
1. The user enables `M-x ekp-auto-justify-mode`.
2. A committed projection records the complete source baseline, one
whole-hard-line `ekp-layout-plan`, line signatures, projected spans, and
break anchors. Core DP receives no point, marker, window, or edit-state
input.
3. Ordinary input within the current native visual row performs no
whole-hard-line planning. Previously committed rows remain
property-identical and the current row stays natural.
4. Editing a committed middle row starts a dirty transaction. Only the
affected local island becomes natural; following and preceding unaffected
break anchors remain installed. Native wrapping absorbs local push/pull.
5. Crossing into the next native visual row atomically replans the completed
hard-line prefix once. Line-signature diffing limits property writes; the
new current row remains natural.
6. A hard newline/paragraph end, the next real edit outside the dirty island,
explicit refill, or width/font/layout-context change commits the
transaction. Point-only motion anywhere performs zero plan, cache,
property, or layout work.
7. Restoring the baseline source restores the saved projection immediately
and exactly, including text properties, plan signatures, and spans.
8. Completed paragraphs use the existing full Knuth-Plass projection.
9. Disabling the mode cancels pending work, detaches markers, restores any
prior copy filter, and removes every EKP-owned projection.
### Use logical text during projection
1. Search and isearch operate directly on source characters, including
across display-only breaks and discretionary hyphens.
2. Save and `write-region` serialize source characters; visual layout never
reaches disk.
3. Copy/kill composes with an existing
`filter-buffer-substring-function`, removes only EKP-owned projection
metadata from the copied string, and preserves delete semantics.
4. Lisp that needs a property-free string uses
`buffer-substring-no-properties`; property-aware APIs may inspect the
EKP display metadata without seeing synthesized layout characters.
### Diagnose an unsupported paragraph
1. EKP detects foreign `display`, `line-prefix`, `wrap-prefix`,
`composition`, or `invisible` ownership, or an exact shrink request for
unsupported whitespace.
2. EKP leaves only that hard paragraph natural instead of stealing or
approximating the foreign representation.
3. `M-x ekp-diagnose` reports the authoritative width and recorded
conflicts. The user may remove the conflict or explicitly refill after
changing the content/configuration.
## Display Contract
- ASCII source spaces use:
```elisp
((space-width FACTOR)
(min-width ((TARGET-PIXELS))))
```
- A zero-source CJK/mixed gap applies `min-width` to the preceding complete
grapheme, with a target equal to its natural advance plus the planned
glue.
- Indentation uses `line-prefix`.
- A source-whitespace break displays the first boundary character as a
newline and any remaining boundary whitespace as empty.
- A CJK or Latin discretionary break uses a replacing display string on an
existing complete grapheme. It reproduces that grapheme, appends the
optional hyphen, then a visual newline.
- Every public projection value has an EKP owner property. Cleanup removes
a public value only while it is still identical to the owned value.
- All projection updates use `with-silent-modifications`; owned properties
are nonsticky.
## Edge Cases
- Active IME composition defers projection and retries only for the current
generation.
- Stale resize/background generations cannot publish.
- A hard paragraph over `ekp-auto-justify-paragraph-limit` remains natural
during automatic work; explicit `ekp-refill-paragraph` is unbounded.
- Large buffers reflow visible-first in time-bounded hard-paragraph chunks.
- Foreign property conflicts are isolated to their hard paragraph.
- Narrowing does not cause projection cleanup or installation outside the
intended accessible operation; mode teardown widens to remove all owned
state.
- Major-mode changes and mode disable leave the logical text and prior
integrations intact.
- A rigid inline atom that jumps a candidate line from underfull to overfull
stays intact without forcing the preceding prose through one-box emergency
lines. In the final pass, ordinary underfull candidates receive finite
emergency stretch and remain normal K-P cost candidates. An atom wider than
the measure stays intact but may share an overflow line with preceding
ordinary content when TeX-style artificial demerits must preserve the last
active path. Atom adjacency has no special break or scoring rule.
- Inline code is not rigid by default. Known Org/Markdown inline faces use
the inline policy path, not the paragraph verbatim path. The default policy
permits legal wrapping at existing boundaries, suppresses discretionary
dictionary hyphenation, and preserves source whitespace literally. Known
code-block faces remain paragraph-level verbatim.
- Manual `ekp-no-break` is the only explicit hard-atom owner and is never
downgraded. Automatic no-break spans, such as compact number-unit tokens,
downgrade to no-hyphen when wider than the effective measure.
- The previous narrow showcase orphan-glyph failure is now a permanent
acceptance invariant: the inline-code paragraph must not isolate CJK glyphs
such as `行`, `内`, or `永` as one-character source lines around automatic
inline code when a non-emergency legal alternative exists. Dynamic evidence
must distinguish automatic inline code from explicit hard atoms and block
verbatim spans.
- The GUI oracle rejects any isolated CJK source line in the showcase
paragraph at the checked widths. This is a core K-P invariant, not a
unit-suffix, screenshot-specific, or renderer compensation rule.
- Effective policy precedence is deterministic: region properties, then
explicit buffer/file/dir-local values, then major-mode profiles, then
global defaults. Manual text properties are session-local; persistence
comes from mode syntax/profiles and file/dir locals.
- URL, path, and identifier tokens default to no-hyphen. Compact number-unit
tokens default to no-break. Hyphenation defaults to `auto`, with `on` and
`off` overrides. Kinsoku defaults to `common`, with `zh`, `ja`, `off`, and
custom additions available. Ordinary overlong tokens default to the current
emergency output, with `overflow` and `natural` alternatives.
- The buffer measure defaults to the narrowest live window. A positive
integer fixed measure and `(max . PIXELS)` cap are configurable and must be
reported by diagnostics when they create overflow risk.
- Reprojection preserves point, the mark marker, and `mark-active`
independently. An inactive historical mark must never become a visible
selection merely because width or layout options changed.
- Semantic live-prefix editing still requires real Emacs soft wrapping for
the active natural suffix.
`ekp-auto-justify-mode` temporarily disables both `truncate-lines` and
narrow partial-window truncation, then restores their prior values and
local/global binding state when the mode ends.
## Acceptance Criteria
1. `ekp.el` exposes a semantic layout plan containing source offsets,
per-line glue targets, break kinds, indentation, and discretionary
hyphen decisions; the public string API remains
`equal-including-properties` compatible.
2. No EKP buffer path creates an overlay or inserts glue spaces, soft
newlines, or discretionary hyphens into source text.
3. ASCII gaps use the exact combined `space-width`/absolute-pixel
`min-width` form; zero-source gaps, indentation, breaks, and hyphens use
the display contract above.
4. Source characters, positions, point/mark, modified state, undo history,
character-modified tick, save output, search, and syntax remain logical.
5. Automatic editing proves committed-projection and dirty-transaction
ownership: ordinary same-row edits perform no whole-hard-line planning or
unaffected property writes; middle-row edits preserve unrelated anchors;
reversible edits restore the complete projection
`equal-including-properties`; soft-wrap crossing publishes one atomic
whole-hard-line prefix update; structural commit events use the existing
`ekp-layout-plan`; and point-only motion performs zero layout work. IME
deferral, stale resize rejection, and exactly one complete KP pass after
hard-paragraph completion remain required. Narrow side-by-side windows
soft-wrap rather than horizontally scroll. A single leading/trailing
space or tab is visible in the same command-loop turn, including
whitespace exposed by deletion.
6. Copy/kill, isearch, foreign ownership, multiwindow narrowest-width
policy, narrowing, resize, major-mode change, and teardown have
deterministic tests.
7. Exact GUI probes cover 164px ASCII and zero-source CJK glue,
display-only break/hyphen/indentation, point, vertical motion, region,
mouse, scaling/remapping/fringes, unchanged source, and zero overlays.
8. Dynamic per-keystroke evidence proves mixed Latin/CJK same-row typing
preserves the committed projection, a real visual-row crossing atomically
publishes all completed rows from one hard-line plan, exact source
reversal restores the baseline projection, point-only motion preserves
every state object and property exactly, and paragraph completion performs
the full completed-paragraph KP transition.
9. Default, permuted, and isolated ERT; C/Elisp fuzz; warning-as-error
Elisp/C builds; checkdoc; package/static/release gates; full diff review;
independent code review; and independent architecture review pass.
10. Elisp, C, semantic-plan, string-renderer, and real GUI paths keep an
`ekp-no-break` atom intact while proving that its preceding CJK prefix is
not fragmented into one-glyph lines at narrow measures.
11. Inline and token break policies are configurable at global, mode profile,
explicit local, and region scopes. The accepted defaults are: inline code
`no-hyphen`; block code verbatim; URL/path/identifier `no-hyphen`;
compact number-unit `no-break`; hyphenation `auto`; kinsoku `common`;
overlong token `emergency`; buffer measure `narrowest-window`.
12. Region `ekp-break-policy` supports `normal`, `hyphenate`, and
`no-hyphen`, and never creates a second hard-atom representation.
Overlapping explicit `ekp-no-break` wins over every new policy.
13. The showcase paragraph containing inline code, CJK prose, NBSP-backed
numbers, and units must have no pathological single-CJK source line in
the inspected 280px GUI path while retaining exact source text, zero
overlays, block-code verbatim display, internal source-space inline
breaks, and C/Elisp plan parity.

View File

@ -0,0 +1,92 @@
# Tasks: Post-Audit Hardening 2026-08-20
- task044 [x] Restore Emacs 31.1 WERROR compilation by replacing obsolete
single-binding `if-let`/`when-let` forms with behavior-identical
`if-let*`/`when-let*` forms in production and the live evaluator.
- Source: `issue029` and the C1a nine-repository strict-compile gate.
- Validation: root `make`; Emacs 31.1 WERROR production and changed-tool
compilation; normal/random ERT 296/296; C build and 300-case fuzz;
release, local 49-entry dictionary manifest/hash, checkdoc, and diff gates.
The network-backed fixed-upstream dictionary fetch was explicitly excluded
by the user and is not claimed as evidence.
- task043 [x] Restore the Emacs 31 fresh-source baseline without weakening
cache-ownership or marker-noninheritance contracts. Keep policy fixtures
mutable and multibyte, and install EKP marker properties in the global
`text-property-default-nonsticky` default used by real buffers.
- Source: Emacs 31 fresh-source failures in the M0a baseline.
- Validation: focused ERT 3/3, normal and seeded-random ERT 296/296,
source-load, release, and 49-entry dictionary gates.
- task037 [x] Validate public buffer widths before mutation and make rejected
`ekp-justify-region` requests failure-atomic. Add red tests for zero,
negative, non-integer, and projection-preservation cases.
- Source: post-audit buffer probe and `plan_post_audit_hardening_20260820.md`.
- Validation: focused buffer ERT 4/4 plus source-preferred full ERT 296/296.
- task038 [x] Resolve backward-delete live projection semantics. Add a public
edit-path regression at the narrowest reproducible width and update the
live-layout spec/postmortem with the accepted invariant.
- Source: `postmortem/20260730-incremental-live-append-ownership.md` and
`plan_post_audit_hardening_20260820.md`.
- Validation: focused live-edit ERT 4/4, randomized ERT 296/296, and
source-first isolated ERT 294/294 process runs plus focused isolated
coverage of the later batch-position test.
- task039 [x] Make hyphenation locale resolution try the exact normalized
locale registry before short-code fallback. Add equivalent BCP-47/underscore
tests and preserve fail-closed unsupported-pattern behavior.
- Source: `ekp-hyphen.el`, `plan_dictionary_governance_20260728.md`, and
the post-audit locale probe.
- Validation: focused dictionary ERT 11/11, manifest check, and full ERT
296/296.
- task040 [x] Validate C hyphen and forbidden position vectors for the exact
exclusive break-index domain, monotonic ordering, and duplicate policy
before DP access. Add direct single/batch ABI regressions without changing
the 15-field schema.
- Source: `plan_c_api_contract_20260728.md` and the post-audit C probe.
- Validation: C warning-clean build, direct/batch C ERT 15/15, fuzz
300/300, and Elisp parity.
- task041 [x] Restore the showcase automatic inline-code face and make normal,
random, and isolated runners source-fresh and complete over the ERT
inventory. Add a regression proving stale ignored bytecode cannot mask the
showcase contract.
- Source: `888a401`, `plan_test_isolation_20260728.md`, and the post-audit
clean-source failure.
- Validation: source loader symbol checks, clean-source showcase 3/3,
normal/random/isolated 296-test inventories, and GUI contract checks.
- task042 [x] Remove manual buffer integrations when no spans remain and
auto-mode does not own the lifecycle. Preserve composition with foreign
filters and add empty/foreign-only teardown regressions.
- Source: `plan_integration_lifecycle_20260728.md` and the post-audit
no-projection probe.
- Validation: focused integration ERT 4/4 and full source-first buffer
suite.
- task030 [x] Re-profile and, if still necessary, optimize the remaining
source-instrumented narrow live-append structural commit path after
task037task042. Keep exact parity, source-clean projection, and zero-work
point motion as hard constraints; close only on the locked 16 ms gate.
- `issue028` is part of this task: each baseline/candidate evaluator round
must explicitly load source files from its own code root, never rely on
local `.elc` precedence.
- The evaluator now records `append_ms` and `append_dp_ms` and preserves
nonempty raw JSONL per run. Current source-fresh p95 is 17.161/1.187 ms
for C append/append-DP and 73.293/57.063 ms for Elisp; choose a
production optimization only after this attribution remains stable.
- It now also records append preparation/assembly separately: latest p95
is 9.753/5.663 ms for both engines, while Elisp append-DP is 55.970 ms
and C append-DP is 1.015 ms. The evaluator wrapper collision was fixed and
the same source matrix passes the harness without argument errors.
- User-selected architecture option 2 is implemented as
`ekp-auto-justify-native-append`: loaded C DP is used only for prepared
auto live append, while full/string `ekp-use-c-module=nil` remains pure
Elisp and unavailable native modules fall back exactly. The source
append-DP p95 drops from roughly 56 ms to roughly 2.2 ms in the complete
all-width/row run; preparation and assembly remain exact. The formal
four-round source-fresh evaluator passes with width-80 C p95/p99
12.010/14.622 ms and Elisp-configured live p95/p99 10.980/11.194 ms,
closing the locked 16 ms gate.

View File

@ -0,0 +1,926 @@
# Tasks: Repository System Audit 2026-07-28
- task001 [x] Produce a repository-wide audit covering architecture, Elisp,
C, buffer integration, tests, GUI, dictionaries, performance,
compatibility, documentation, release health, and extension directions.
- Source: user request; existing `phase-kp-overhaul-20260726`.
- Output: `Docs/REPOSITORY_AUDIT_20260728.md`.
- Validation: 94/94 ERT, 300/300 C/Elisp fuzz, clean-copy byte compilation,
checkdoc, warning-clean C release build, clean-copy ERT/fuzz, GUI fit
matrix 7/7, targeted negative probes, link/stale-fact/diff checks.
- Impact: documentation and planning records only; no runtime behavior
changed.
- task002 [x] Complete the DP cache signature so algorithm-parameter changes
never reuse stale results.
- Source: `issue001`, audit P1-01, and
`plan_cache_signature_20260728.md`.
- Red test: parameter-by-parameter cached-vs-fresh comparison through
`ekp-dp-cache`, plus a same-signature cache-hit assertion.
- Validation: focused red/green ERT, complete ERT, C/Elisp fuzz,
warning-clean byte compilation, checkdoc, and full diff review.
- Impact: `ekp.el`, `tests/ekp-tests.el`, user documentation, and phase
records.
- Completed: 2026-07-28; focused 0/2 → 2/2, full ERT 96/96, fuzz
300/300, byte compilation/checkdoc clean, Elisp/C rendered-output
probes matched fresh computation.
- task003 [x] Complete automatic spacing identity across both paragraph
cache lookup paths.
- Source: `issue001`, audit P2-01, and
`plan_auto_spacing_signature_20260728.md`.
- Red test: change `ekp-default-cws-stretch-pixel` without clearing
caches and exercise the paragraph hash and `ekp--last-para` separately;
retain an unchanged-signature hit control.
- Validation: focused 1/3 → 3/3, complete ERT 99/99, C/Elisp fuzz
300/300, warning-clean byte compilation, and checkdoc.
- Impact: `ekp.el`, `tests/ekp-tests.el`, public/developer documentation,
audit status, phase records, and a design postmortem.
- Completed: 2026-07-28; `issue001` is resolved.
- task004 [x] Make justified-buffer saving non-mutating and failure-safe.
- Source: `issue002`, audit P1-02, and
`plan_save_transaction_20260728.md`.
- Red test: real missing-directory failure and forced `quit` both left the
old buffer unformatted.
- Validation: focused 0/2 → 2/2, then success/failure/encoding/retry 4/4;
complete ERT 102/102; C/Elisp fuzz 300/300; warning-clean byte
compilation; checkdoc.
- Impact: `ekp-region.el`, `tests/ekp-region-tests.el`, user/developer
documentation, audit status, phase records, and a design postmortem.
- Completed: 2026-07-28; `issue002` is resolved.
- task005 [x] Make buffer integrations composable and span/mode-owned.
- Source: `issue003`, audit P1-03/P2-06, and
`plan_integration_lifecycle_20260728.md`.
- Red test: existing filter output was lost and final unjustify left the
EKP filter/hooks installed (0/2).
- Validation: focused 2/2 then five public composition/lifecycle cases;
complete ERT 107/107; C/Elisp fuzz 300/300; warning-clean byte
compilation; checkdoc.
- Impact: `ekp-region.el`, `tests/ekp-region-tests.el`, public/developer
documentation, audit status, phase records, and a design postmortem.
- Completed: 2026-07-28; `issue003` is resolved.
- task006 [x] Eliminate ERT order-dependent false greens.
- Source: `issue004`, audit P1-04, and
`plan_test_isolation_20260728.md`.
- Red test: loading C and running only
`ekp-test-parshape-bypasses-c` failed 0/1.
- Validation: focused public-dispatch/config-restoration/C-parameter
matrix 3/3; reproducibly permuted ERT 108/108; every ERT independently
passed in a fresh Emacs process, 108/108.
- Impact: core fixture and dispatch regression, reusable test runners,
CI, developer documentation, audit status, phase records, and a testing
postmortem.
- Completed: 2026-07-28; `issue004` is resolved.
- task007 [x] Make GUI verification fail closed in automation.
- Source: `issue007`, audit P2-05, and
`plan_gui_verification_20260728.md`.
- Red test: the forced noninteractive failure control had no report
assertion boundary, 0/1.
- Validation: focused forced-failure/success ERT 2/2; default and
permuted full ERT 110/110; real GUI matrix 7/7; inspected clean
fullscreen single-window screenshot.
- Impact: GUI verification result/report contract, batch-safe negative
tests, test loaders, public/developer documentation, audit status,
phase records, and a testing postmortem.
- Completed: 2026-07-28; `issue007` is resolved.
- task008 [x] Close the C API input, error, and arithmetic contract.
- Source: `issue005`, audit P2-02, and
`plan_c_api_contract_20260728.md`.
- Red tests: malformed/short/mismatched/out-of-range/penalty inputs and
extreme valid int32 arithmetic failed 0/6; public dispatcher swallowed
a forced module error, 0/1.
- Validation: focused C boundary 6/6 and public error propagation 1/1;
warning-clean release and sanitizer compilation; full ERT 116/116;
C/Elisp fuzz 300/300.
- Impact: C API validation, 64-bit DP intermediates, module version 1.6,
Elisp backend error propagation, tests, public/developer documentation,
audit status, phase records, and an architecture postmortem.
- Completed: 2026-07-28; P2-02 is resolved. `issue005` remains open until
the build invocation/profile slice is complete.
- task009 [x] Make C builds shell-free, portable by default, and profiled.
- Source: `issue005`, audit P2-03, and
`plan_c_build_boundary_20260728.md`.
- Red tests: capture the interactive build process shape for a module path
containing spaces and reject an unknown profile.
- Validation: focused ERT, real portable/native/debug/sanitize builds,
a copied build from a whitespace path, full ERT/fuzz, byte compilation,
checkdoc, and diff review.
- Impact: `ekp-utils.el`, `ekp_c/Makefile`, CI build arguments, tests,
public/developer/C documentation, audit status, and phase records.
- Completed: 2026-07-28; process/profile red 0/2 → 2/2; all four profiles
compiled warning-free; portable succeeded through the real interactive
command and from a whitespace path. `issue005` is resolved.
- task010 [x] Close repository-local release and CI governance.
- Source: `issue009`, audit P2-07, and
`plan_release_governance_20260728.md`.
- Red test: a static release gate must reject floating action refs, absent
Windows coverage, ignored `.phrase` truth, or inconsistent package/C
version declarations.
- Validation: red/green gate, workflow syntax/static inspection, default
ERT, and `git diff --check`.
- Impact: CI, `.gitignore`, release documentation, audit/phase records, and
a release-governance postmortem.
- Completed: 2026-07-28; invariant gate red → green, pinned package-lint
passed, workflow YAML and shell syntax passed, and default ERT was
119/119. `issue009` is resolved locally; no remote release action was
performed.
- task011 [x] Make dictionary syntax support and provenance auditable.
- Source: `issue006`, audit P2-04, and
`plan_dictionary_governance_20260728.md`.
- Red tests: affected-language golden behavior and a manifest/updater
verifier must fail against the unpinned moving-HEAD bundle.
- Validation: golden red/green, 49/49 checksum/source/license manifest,
two identical pinned update outputs, full ERT, byte compilation,
checkdoc, and diff review.
- Impact: hyphen parser/tests, dictionary updater and metadata, public/
developer/audit documentation, phase records, and an architecture
postmortem.
- Completed: 2026-07-28; affected-language red 0/2 → 2/2; offline and
upstream 49/49; two exports identical; full ERT 121/121; compile,
checkdoc, shell/YAML, and diff checks passed. `issue006` is resolved.
- task012 [x] Make measured tokenizer/insertion growth linear and cache nil.
- Source: `issue008`, audit P3-01, and
`plan_hot_loop_performance_20260728.md`.
- Red tests: two no-break lookups must compute once; long/propertized
tokenization and dense insertion must retain exact output.
- Validation: before/after 1k8k scaling, focused/full ERT, warning-as-error
byte compilation, checkdoc, fuzz, and diff review.
- Impact: tokenizer/hyphen cache/insertion, benchmark/tests, developer/
audit documentation, phase records, and a performance postmortem.
- Completed: 2026-07-28; nil-cache red 0/1 → 1/1; focused output
regressions 3/3; 8,000-character tokenizer/insertion improved from
3.133/0.945 s to 1.100/0.013 s; full ERT 124/124, fuzz 300/300,
warning-as-error production compilation and checkdoc passed.
- task013 [x] Clarify and test interactive protection workflows.
- Source: audit P3-03, ultragoal G009, and
`plan_interactive_protection_20260728.md`.
- Red tests: drive all four no-break/verbatim commands interactively,
assert real formatter behavior and session-lifetime feedback, and
require a standard mode menu exposing the workflows.
- Validation: focused red/green ERT, default/permuted/isolated full ERT,
warning-as-error byte compilation, checkdoc, and diff review.
- Impact: public commands/mode map, region tests, bilingual user
documentation, audit/phase records, and a workflow postmortem.
- Completed: 2026-07-28; focused public-path red 0/3 → 3/3; default
and seeded-permuted ERT 127/127; every ERT passed in a fresh Emacs
process; warning-as-error production compilation, checkdoc, release,
dictionary, and diff gates passed.
- task014 [x] Centralize the remaining duplicated core layout rules.
- Source: audit P3-02 and `plan_core_rule_ownership_20260728.md`.
- Red tests: require a directly testable edge-space exclusion rule and
one complete, nonsticky render-marker vocabulary.
- Validation: focused red/green ERT, full ERT, C/Elisp fuzz,
warning-as-error byte compilation, checkdoc, and diff review.
- Impact: `ekp.el`, `ekp-region.el`, core tests, developer/audit records,
and an architecture postmortem; no public behavior or ABI change.
- Completed: 2026-07-28; direct rules red 0/2 → 2/2; related focused
invariants 4/4; full ERT 129/129; C/Elisp fuzz 300/300;
warning-as-error compilation, checkdoc, release, dictionary, and diff
gates passed.
- task015 [x] Run bounded anti-slop cleanup and the final independent gate.
- Source: ultragoal G010, `ai-slop-cleaner`, `code-review`, and
`plan_final_cleanup_review_20260728.md`.
- Behavior lock: 129/129 ERT and 300/300 fuzz before cleanup.
- Cleanup: delete only proven dead/pass-through accessors; classify every
fallback-like path and retain grounded boundary behavior.
- Validation: all repository quality gates plus independent
`code-reviewer` APPROVE and `architect` CLEAR.
- Impact: internal cleanup, final records, and review artifacts; no
public behavior, baseline, ABI, or remote release action.
- Completed: 2026-07-28; removed nine dead pass-through accessors and the
private substring-filter dependency; classified all fallback-like paths
with no masking fallback retained; default/permuted/isolated ERT
130/130, fuzz 300/300, warning-clean Elisp/C builds, checkdoc,
package-lint, release/dictionary/static gates, and GUI matrix 7/7
passed. Independent review covered 98 paths with zero findings and
returned `APPROVE`; architecture review returned `CLEAR`.
- task016 [x] Make the same-string paragraph fast path property-sensitive.
- Source: `issue010`, independent final code review, and
`plan_property_sensitive_fast_path_20260728.md`.
- Red test: warm one string object, mutate its `ekp-no-break` property,
and require the next lookup to match a fresh paragraph for both CJK and
Latin-with-space inputs.
- Validation: focused red/green, default/permuted/isolated full ERT, fuzz,
static/build/GUI gates, and independent review.
- Impact: paragraph cache identity, core regressions, developer/audit/
phase records, and a cache-ownership postmortem; no public API or C ABI
change.
- Completed: 2026-07-28; same-object property mutation red 0/1 → 1/1;
focused cache matrix 6/6; default and seeded-permuted ERT 130/130;
every one of 130 ERT tests passed in a fresh Emacs process; C/Elisp fuzz
300/300; warning-as-error compilation, checkdoc, package-lint, four C
profiles, release/dictionary/static gates, and GUI matrix 7/7 passed.
Independent review confirmed the implementation fix and architecture
ownership; `issue010` is resolved.
- task017 [x] Rename the editor integration module around its buffer owner.
- Source: user request and `plan_buffer_module_naming_20260728.md`.
- Behavior lock: run the existing 130-test ERT suite before editing; the
rename must preserve rendered output, logical serialization, and every
public region/buffer workflow.
- Change: rename the file/feature/customization/configuration/private/test
namespaces from `ekp-region` to `ekp-buffer`, while keeping public
operation names that correctly describe region or buffer operands.
- Validation: focused/default/permuted/isolated ERT, C/Elisp fuzz,
warning-as-error byte compilation, checkdoc, release/package/static
gates, stale-name scan, and full diff review.
- Impact: editor integration source/tests, automation, bilingual public
and developer documentation, current audit records, phase records,
changelog, and a naming decision postmortem.
- Completed: 2026-07-28; pre-change ERT 130/130; focused buffer ERT 44/44;
default and seeded-permuted ERT 130/130; every one of 130 ERT tests
passed in a fresh Emacs process; C/Elisp fuzz 300/300; warning-as-error
byte compilation, checkdoc, pinned package-lint, release/shell/diff
gates, active stale-name scan, and mechanical equivalence check passed.
- task018 [x] Record the text-property-only layout and hyphen feasibility.
- Source: `issue011`, the user's 2026-07-29 display-property constraints,
and `tech-refer_text_property_layout_20260729.md`.
- Scope: documentation and clean GUI probes only; no runtime source or
test implementation changes.
- Evidence: confirm absolute-pixel `min-width`, real-space
shrink/stretch through `space-width`, `line-prefix` indentation, and a
replacing display string that renders a source grapheme plus
discretionary hyphen and visual newline without changing source
characters.
- Validation: official GNU Emacs documentation/source review; clean
Emacs 30.2 GUI glue, hyphen/newline, vertical-motion, and line-prefix
probes; document link/traceability review; `git diff --check`.
- Impact: `issue011`, one new technical reference, current OMX plan/test
supersession notices, phase/global change records; no runtime behavior.
- Completed: 2026-07-29. Static LTR feasibility is recorded; `issue011`
remains open for the full GUI/editor-semantic gate, live editing, and
the one-buffer/multiple-window decision.
- task019 [x] Extract a semantic KP layout plan and preserve string output.
- Source: `issue011`,
`spec_text_property_layout_engine_20260729.md`, and
`plan_text_property_layout_engine_20260729.md` M1.
- Red tests: require line/source/gap/break/hyphen plan data and require the
existing string renderer to consume that plan without changing its
`equal-including-properties` output.
- Validation: focused red/green ERT, full core ERT, C/Elisp parity, fuzz,
warning-as-error byte compilation, and checkdoc.
- Impact: `ekp.el`, core tests, developer reference, and phase records.
- Completed: 2026-07-29; focused plan tests 0/3 → 3/3; the public string
renderer consumes `ekp-layout-plan`; full ERT passed 133/133, C/Elisp
fuzz passed 300/300, and warning-as-error compilation, checkdoc, and
diff checks were clean.
- task020 [x] Replace physical buffer formatting with text-property projection.
- Source: `issue011`,
`spec_text_property_layout_engine_20260729.md`, and
`plan_text_property_layout_engine_20260729.md` M2.
- Red tests: unchanged source characters/positions/ticks/undo/modified
state; exact cleanup and foreign-property restoration; no overlays or
synthesized layout characters; copy/save/isearch see logical text.
- Validation: focused ERT plus clean Emacs 30.2 GUI glue, CJK break,
Latin discretionary hyphen, indentation, point, region, and mouse probes.
- Impact: `ekp-buffer.el`, buffer/GUI tests, commands, integrations,
bilingual docs, and phase records.
- Completed: 2026-07-29. `ekp-buffer.el` now projects only onto existing
source characters with owned, nonsticky text properties and creates no
overlay. Buffer ERT passed 71/71. A clean Emacs 30.2 GUI probe rendered
exact 164px ASCII and zero-source CJK glue, display-only Latin hyphens
and breaks, `line-prefix`, point/vertical motion/region/mouse behavior,
unchanged source text, and zero overlays. The 7-case scale/remap/fringe/
width matrix passed.
- task021 [x] Implement active-line and bounded-suffix live layout.
- Source: the user's seamless-editing requirement,
`spec_text_property_layout_engine_20260729.md`, and
`plan_text_property_layout_engine_20260729.md` M3.
- Red tests: unfinished line stays natural; overflow pushes a minimal
suffix; deletion pulls back; stable prefix does not move; stale
generations cancel; composition defers; no delayed whole-paragraph snap.
- Validation: deterministic ERT, performance budgets, and dynamic GUI
recordings with temporal review.
- Impact: `ekp-buffer.el`, live-edit state/tests/benchmarks, mode help,
bilingual docs, and phase records.
- Completed: 2026-07-29. Live edits publish synchronously from a stable
active-flow anchor, preserve a natural unfinished line, push the
minimal overflow suffix, pull text back on deletion, converge on an
unchanged line signature, defer IME composition, reject stale
generations, and bound both lookahead and automatic paragraph work.
The type/delete round-trip regression failed with an extra break before
the stable-anchor fix and now restores exact prior break positions.
Buffer ERT passed 71/71. The 31.71s dynamic GUI artifact recorded
before/immediate/settled overflow and deletion checkpoints; source and
projection hashes were stable after redisplay, deletion restored the
original hashes and two breaks, all assertions passed, zero overlays
were present, and temporal review returned PASS.
- task022 [x] Close the text-property layout architecture and quality gates.
- Source: `issue011`,
`spec_text_property_layout_engine_20260729.md`, and
`plan_text_property_layout_engine_20260729.md` M4.
- Work: resolve documentation drift, record the one-width-per-buffer and
display-conflict decisions, remove dead physical-layout adapters, run
anti-slop cleanup, and obtain independent reviews.
- Validation: default/permuted/isolated ERT, fuzz, warning-clean Elisp/C
builds, checkdoc/static/release gates, GUI matrix, dynamic evidence,
full diff review, code-reviewer APPROVE, and architect CLEAR.
- Impact: runtime/tests/docs/postmortem and `issue011` closure.
- Completed: 2026-07-29. Buffer ERT passed 71/71; full default and
seeded-permuted ERT passed 162/162; every one of 162 tests passed in a
fresh Emacs process; C/Elisp fuzz passed 300/300. Warning-as-error
byte compilation, checkdoc, pinned package-lint, release, bundled and
pinned-upstream dictionary, shell, diff, stale-name, no-overlay, and
dead-private-function gates passed. Portable, native, debug, and
sanitizer C profiles built cleanly. Static GUI verification passed
exact 164px ASCII/CJK glue and the seven-case display matrix; dynamic
verification passed the overflow/type/delete round trip with zero
overlays and stable source/projection hashes. Anti-slop cleanup removed
the dead `ekp-buffer--justified-spans` and `ekp--para-glue-shrink`
functions. Independent code review returned `APPROVE` with no findings;
independent architecture review returned `CLEAR`. `issue011` remains
open only for the required user-visible behavior confirmation.
- Completion-audit correction: the final objective named a phase spec,
but the phase initially had none. Added
`spec_text_property_layout_engine_20260729.md` and linked it from the
plan, tasks, issue, and change record before claiming goal completion.
The same audit corrected the dynamic duration to the manifest's
31.707-second `run-end` value and strengthened the existing display
tests to assert the exact nested `space-width`, `min-width`, and
`line-prefix` values. The strengthened focused tests passed 3/3; the
current full suite passed 162/162; warning-as-error test compilation,
checkdoc, release, artifact-cleanliness, and diff gates remained clean.
- task023 [x] Preserve editor selection state and natural active-line edges.
- Source: `issue012`, `issue013`, and
`spec_text_property_layout_engine_20260729.md`.
- Red tests: reprojection must preserve an inactive mark; one
command-loop space/tab at either active-line edge and whitespace
exposed by deletion must carry no EKP replacing display.
- Work: restore the mark marker without changing `mark-active`; prevent
completed-paragraph edge hiding from running on the active live line.
- Validation: focused red/green ERT plus clean GUI width-key and
single-space checkpoints.
- Impact: `ekp-buffer.el`, buffer/GUI tests, user docs, issue/change
records, and the live-layout decision record.
- Completed: `ekp-justify-region` now restores the mark marker and
`mark-active` independently. Live projection never applies static edge
hiding to the active line and clears from the source span beginning
when its first line is active. Focused red 0/3 → green 3/3; clean GUI
width and single-space checkpoints pass.
- task024 [x] Audit the live interaction matrix for adjacent regressions.
- Source: the user's request for detailed seamless-editing review and
`issue_live_editing_interaction_20260729.md`.
- Work: exercise insertion, consecutive whitespace, deletion/backspace,
newline, yank, undo, point, inactive/active mark, visual-break
boundaries, paragraph transitions, resize, and teardown through public
command paths.
- Validation: deterministic matrix ERT, full repository gates, and a
temporally reviewed GUI recording with explicit invariants.
- Impact: tests/verification artifacts and only root-cause runtime fixes
for newly reproduced defects.
- Completed: added public-command coverage for active/inactive regions,
edge whitespace, consecutive spaces/backspace, yank, newline, undo,
and deletion-exposed whitespace; existing tests cover the remaining
flow, resize, composition, transition, and teardown boundaries. Buffer
ERT passes 79/79; default/random/isolated full ERT passes 170/170;
fuzz passes 300/300; compiler, lint, release, dictionary, static, diff,
and clean dynamic GUI gates pass.
- task025 [x] Replace partial live KP with natural progressive editing.
- Source: `issue014`, the user's approved three-state model, and
`spec_text_property_layout_engine_20260729.md`.
- Red tests: an underfull active tail owns no EKP layout property; natural
wrap aligns only completed rows without a replacing break or live
hyphen; editing/backspacing into a committed row restores native
display; hard-paragraph completion permits one complete KP pass.
- Work: delete the stable-anchor/lookahead/convergence/push-pull live
state machine. Keep a natural active tail, project only gaps on rows
already left by native screen wrapping, and run full KP only at hard
paragraph completion or paragraph exit.
- Validation: focused red/green ERT, full repository gates, and
per-keystroke mixed Latin/CJK GUI recording across wrap and deletion.
- Impact: `ekp-buffer.el`, core live-flow API if it becomes unused,
buffer/GUI tests, bilingual docs, spec/plan, issue/change records, and
the live-layout postmortem.
- Completed: removed stable-anchor/lookahead/convergence/push-pull flow
and its unused core signatures. The live path now clears the active
hard paragraph to native display, commits only internal gap widths on
native rows already left by point, keeps the active tail untouched,
and runs complete KP only on hard-paragraph completion or exit. Live
rows cannot publish prefixes, replacing breaks, or discretionary
hyphens.
- task026 [x] Close natural-live-edit verification and documentation.
- Source: `task025`, `issue014`, and the repository completion contract.
- Work: remove obsolete tests/docs/private APIs, update the durable
design record, inspect the full diff, and retain deterministic temporal
evidence.
- Validation: default/random/isolated ERT, fuzz, warning-as-error
compilation, checkdoc, pinned package-lint, release/dictionary/static
gates, clean fullscreen GUI screenshots, and reviewed recording.
- Impact: tests, documentation, phase closure records, and no additional
runtime abstraction.
- Completed: buffer ERT 75/75; default, seeded-permuted, and isolated full
ERT 164/164; fuzz 300/300; compiler, checkdoc, pinned package-lint,
release, dictionary, pinned-source, ownership, no-overlay, stale-name,
and diff gates pass. Seven static GUI variants pass. The retained
14.82-second fullscreen recording
`/private/tmp/ekp-native-live-2dYu2U/recording.mov` proves native wrap,
deletion restoration, and hard-newline completion without flicker or
non-Emacs frames. `issue014` remains open only for required
user-visible confirmation.
- task027 [x] Make native soft wrapping an owned auto-mode precondition.
- Source: `issue015`, the user's narrow split-window screenshot, and
`spec_text_property_layout_engine_20260729.md`.
- Red tests: enabling `ekp-auto-justify-mode` must locally disable both
explicit line truncation and Emacs's default narrow partial-window
truncation; normal disable, activation failure, and major-mode teardown
must restore the exact prior values and local-binding ownership.
- Work: give the minor-mode lifecycle temporary ownership of
`truncate-lines` and `truncate-partial-width-windows`. Do not synthesize
a break, alter source text, or permanently overwrite user settings.
- Validation: focused red/green ERT, split-window GUI typing across the
native wrap boundary, full repository gates, and temporal review.
- Impact: `ekp-buffer.el`, buffer/GUI tests, bilingual docs, spec,
issue/change records, and the native-live postmortem.
- Completed: the mode owns native soft wrapping before its first reflow,
shares one hook lifecycle for activation and cleanup, and uses
`unwind-protect` so a failed activation restores state before
propagating the original error. Focused lifecycle ERT passes 3/3;
buffer ERT 77/77; default, seed-`20260729`, and isolated full ERT
166/166; fuzz 300/300; compiler, checkdoc, pinned package-lint,
release, dictionary, pinned-source, no-overlay, stale-name, conflict,
artifact, and diff gates pass. Retained 44-column split-window
evidence proves native wrap with `hscroll=0`, exact source text, zero
overlays, and zero live replacing breaks. `issue015` remains open only
for required user-visible confirmation.
- task028 [x] Replace native-row live commitment with semantic hard-line
prefix projection.
- Source: `issue016`, the user's multi-row live-editing screenshot, and
`postmortem/20260729-whole-hard-line-live-prefix.md`.
- Red tests: through public editing paths, require the complete current
hard line to be planned by the existing `ekp-layout-plan`, require only
plan lines before the point-containing line to be projected, require
earlier breaks/glue to change together after later edits, and require
plan cache hits/zero writes when the semantic plan is unchanged.
- Work: delete the native-row freezing model from `ekp-buffer` live
editing. Keep the core DP, C ABI, DP schema, and completed-paragraph
plan semantics unchanged. Add buffer-local live plan history,
line-signature diffing, point-movement boundary updates, fail-closed
conflict handling, and exact cleanup without overlays or source layout
characters.
- Validation: focused red/green ERT for self-insert, yank, delete, real
undo/redo, point movement, hard newline, resize, major-mode change, and
projection failure; buffer/full/random/isolated ERT; C/Elisp fuzz;
warning-as-error compilation; checkdoc; package/release/dictionary/
no-overlay/static gates; performance benchmark with latency and cache
counters; clean GUI dynamic recording proving aligned semantic prefix,
natural point line/suffix, exact source text, zero overlays,
`hscroll=0`, and no delayed snap.
- Impact: `ekp-buffer.el`, live-edit tests/benchmarks, GUI verification,
bilingual user/developer docs, spec/plan/tech reference, issue/change
records, and the superseding live-prefix postmortem.
- Completed: 2026-07-29. The implementation consumes one complete
hard-line `ekp-layout-plan`, projects only semantic lines before the
point-containing line, keeps the active line/suffix natural, assigns
break whitespace to the preceding semantic owner, uses a bounded
16-entry live plan LRU, and publishes only the changed signature
suffix. No task028 change was made to core DP semantics, the C ABI,
DP schema, or plan contract.
- Verification: focused font-context invalidation RED 0/1 → GREEN 1/1;
buffer ERT 92/92; default and seed-`20260729` full ERT 181/181;
181/181 isolated-process ERT with the added regression independently
green; C/Elisp fuzz 300/300; warning-as-error compilation, checkdoc,
pinned package-lint, release, dictionary, pinned-source, static
ownership, and diff gates pass. Reviewed temporal GUI evidence at
`/private/tmp/ekp-semantic-live-v4-vFZTkr` and
`/private/tmp/ekp-semantic-split-v3-uPwuOi` returns PASS. The
GC-excluded C-backend append p99 is below one frame; default-GC raw p99
retains a documented collection-pause risk. Independent code review is
APPROVE and architecture review is CLEAR.
- Closure: task028's implementation gate is complete. `issue016` remains
open until the user personally confirms the visible editing
experience.
- task029 [x] Make the semantic live prefix independent of point-only
motion.
- Source: `issue017`, the user's clarification that an already published
layout is not cursor-owned, and
`postmortem/20260729-editing-frontier-not-point.md`.
- Red tests: after a real source edit publishes a multi-line prefix,
moving point backward and forward inside the same hard line must
preserve the exact owned-property projection, editing frontier,
active index, signatures, plan/cache counters, modified tick, undo
state, and source characters. A real edit in an earlier projected line
must still naturalize that line before mutation and relocate the
frontier afterward. Reflow after point motion must map the preserved
frontier into the new plan.
- Work: store the latest real source-edit position in the live state,
derive the active semantic line from it, preserve it across
width/font/layout reflow, and delete point-driven boundary publication
from `post-command-hook`. Keep leaving-hard-line completion unchanged.
- Constraints: do not modify core DP semantics, `ekp.el`, C ABI, DP
schema, or the layout-plan contract; do not add overlays or source
layout characters.
- Validation: focused RED/GREEN ERT; complete buffer/default/random/
isolated ERT; C/Elisp fuzz; compiler/checkdoc/package/release/
dictionary/static gates; benchmark; clean dynamic GUI motion evidence;
full diff review; independent code and architecture review.
- Impact: `ekp-buffer.el`, buffer and GUI tests, benchmark assertions,
bilingual user/developer docs, spec/plan/technical reference,
issue/change records, and the superseding frontier postmortem.
- Completed: 2026-07-29. Live state now owns a source-relative frontier
marker at the latest real edit. Projection derives its active semantic
line from that marker; `after-change` relocates it, reflow preserves
it, and `post-command-hook` no longer republishes inside the active
hard line. Leaving the hard line and hard-newline completion retain the
existing static transition. Core DP semantics, `ekp.el`, C ABI, schema,
and the layout-plan contract are unchanged.
- Verification: the two focused regressions failed before the fix and
passed afterward; independent review ran 10/10 focused tests and the
buffer suite passes 93/93. Default, seed-`20260729`, and isolated full
suites pass 182/182; C/Elisp fuzz passes 300/300. Warning-as-error
compilation, checkdoc, pinned package-lint, release, dictionary,
pinned-source, no-overlay, no-stale-symbol, and diff gates pass.
Point-motion benchmark p99 is 0.033 ms on the C backend and 0.037 ms
in the independent Elisp-backend review, with zero planner/cache calls
in both. Reviewed 26.6-second GUI evidence at
`/private/tmp/ekp-frontier-live-v3-66WYRW` contains 39 manifest lines,
all staged assertions green, no black segment, and a PASS report.
Architecture review is CLEAR; code review reports zero blockers and
isolates the unrelated append latency as `issue018`/`task030`.
- Closure: task029's developer gate is complete. `issue016` and
`issue017` remain open until the user personally confirms the visible
editing experience.
- task030 [ ] Diagnose and optimize unique-state live append planning at
very narrow widths.
- Source: `issue018` and
`postmortem/20260729-narrow-live-append-replanning.md`.
- Problem: after task031, the checked-in 80-pixel workload performs zero
planning on ordinary same-row edits but still records 15 structural
boundary plans across 291 appends. A fresh GC-excluded run measured
C median 2.177 ms/p99 51.170 ms and Elisp median 2.176 ms/p99
187.499 ms; the high percentiles now belong to permitted commit spikes,
not per-key frontier replanning.
- Work: profile a documented width/length and commit-event matrix,
identify the surviving structural-planning owner of the cost, and
implement the smallest exact optimization that reduces commit latency.
- Constraints: preserve KP output semantics, exact cache identity,
task031's stable transaction/structural-commit contract, source-clean
text properties, zero-work point-only motion, and the existing core
DP/C ABI/schema contracts unless a separately approved architecture
decision changes them. Do not use debounce, stale reuse, skipped edits,
timers, or global GC changes to hide mutator latency.
- Validation: result-equivalence ERT/fuzz, profiler before/after evidence,
repeatable Elisp/C default-GC and GC-excluded width-matrix benchmarks,
complete repository gates, and dynamic GUI typing evidence.
- Dependency: `task031` is complete. Optimize only the surviving
structural-commit path; the obsolete per-keystroke whole-hard-line path
no longer exists.
- Implementation checkpoint: layout plans now retain prepared paragraph
and context data. Exact plain-text appends retokenize only from the last
complete-word boundary, rebuild derived vectors from the first dirty box,
resume pure-Elisp 1D DP from the earliest state that can reach the new
tail, and reuse unchanged line records. The buffer reconstructs only its
dirty source island and falls back to the complete planner for every
unsupported context. C signed-int validation now uses one module
extraction instead of three Lisp predicate/comparison calls.
- Architecture decision: keep the C module. The frozen baseline places C
DP at only 2.615/2.655 ms p95/p99, and the candidate at
0.697/0.701 ms. Rust would cross the same Emacs module ABI while leaving
Elisp tokenization, measurement, transactions, and property publication
unchanged.
- Performance checkpoint: four interleaved source-instrumented rounds
reduce 80-pixel C p95/p99 from 114.717/119.201 ms to
25.490/25.785 ms (77.78/78.37%) and Elisp from 588.017/597.093 ms to
43.860/47.578 ms (92.54/92.03%). Exact parity, zero-work ordinary keys,
valid GC exclusion, conflict freedom, and all-width non-regression pass.
- Production checkpoint: with production files byte-compiled, three
repeated public-command runs measure append p99 at 1.1581.326 ms for C
and 1.4291.438 ms for pure Elisp; hard-boundary p99 is
1.2511.363 ms and 1.4571.470 ms respectively.
- Verification checkpoint: normal and random-order ERT pass 199/199;
300-case C/Elisp fuzz, portable warning-clean C build and 9/9 C tests,
warning-as-error production compilation, release checks, exact
append-chain review, and the 11-action/33-checkpoint temporal GUI run
pass.
- Remaining gate: the locked source-instrumented evaluator still exceeds
its absolute 16 ms target for C and Elisp. Keep `task030` open rather
than redefining the evaluator after observing the result.
- task031 [x] Replace the overloaded live frontier with committed projection,
a dirty edit transaction, and atomic structural commits.
- Source: `issue019`, the user's stable-editing correction, and
`postmortem/20260729-stable-live-transaction.md`.
- Red tests: drive installed before/after-change and command hooks to prove
that ordinary same-row edits perform no whole-hard-line planning, a
middle-line edit preserves unaffected projection anchors, deleting and
reinserting the same space restores the exact
`equal-including-properties` projection, point-only motion across or
outside the active hard line performs zero planning/property writes, and
the next real edit elsewhere commits the prior dirty transaction.
- Work: delete `frontier = natural suffix` ownership. Store the committed
source, plan, line signatures, spans, and owned projection baseline in
the edit transaction. Naturalize only the dirty edit island, preserve
unaffected break anchors, let native soft wrapping absorb local
push/pull, and replan atomically only when input crosses a visual-row
boundary or another structural commit event occurs.
- Commit events: native soft-wrap crossing, hard newline/paragraph end, the
next real source edit outside the dirty island, explicit paragraph
refill, and width/font/layout-context change. Cursor motion is never a
commit event.
- Constraints: keep core DP, `ekp.el`, C ABI, DP schema, and layout-plan
semantics unchanged; never add overlays or source layout characters;
preserve exact source/editor state and fail closed on conflicts.
- Validation: focused RED/GREEN ERT, complete buffer/default/random/
isolated ERT, C/Elisp fuzz, warning-as-error compile/checkdoc/package/
release/static gates, a re-profiled live benchmark, and temporal GUI
evidence for stable normal input, middle-line edits, reversible edits,
local word migration, hard completion, and point-only motion.
- Impact: `ekp-buffer.el`, buffer/live benchmark/GUI tests, bilingual
user/developer docs, spec/plan/technical reference, issue/change records,
changelog, and the superseding postmortem.
- Implementation: `ekp-buffer--live-state` owns the committed
source/key/plan/signatures/prefix/spans; `ekp-buffer--live-edit` snapshots
that state plus marker offsets and one dirty island.
`ekp-buffer--start-live-edit` naturalizes only the affected span range,
`ekp-buffer--ordinary-live-edit-finished` restores an exact baseline or
publishes only on a real native-row crossing, and hard/elsewhere/
refill/context events commit through their existing boundaries. Lazy
reflow excludes the active paragraph so static chunks cannot detach
live-owned spans. No task031 change was made to `ekp.el` or the core DP.
- Verification: focused RED tests first failed for same-row planning,
middle-anchor invalidation, exact reversal, point-motion work, backward
row crossing, and lazy-reflow ownership, then passed after the owning
fixes. Final runs pass buffer 99/99 and default, seed-`20260729`, and
isolated suites 188/188 each; C/Elisp fuzz passes 300/300. Production
and task test files compile with warnings as errors; checkdoc is empty;
pinned package-lint exits 0; release and 49-entry dictionary-manifest
gates pass. The exact pinned-source gate passed earlier in this task
with unchanged dictionary bytes; two redundant final downloads ended in
GitHub transport `early EOF`, not a content mismatch.
- Performance: at 80 px with GC excluded, same-row cache-revisit work
performs zero plans (C p99 1.627 ms; Elisp p99 1.502 ms), point motion
performs zero plan/cache calls (C p99 0.017 ms; Elisp p99 0.015 ms), and
291 appends contain only 15 permitted structural plans. The remaining
commit spikes are the re-profiled `task030` surface.
- Dynamic evidence: the reviewed 48.95-second fullscreen run at
`/private/tmp/ekp-stable-transaction-final5-2BFryc` contains 55 ordered
checkpoints (11 actions × 5 phases), 17 green assertions at every
checkpoint, one 1434×900 target window, a completed run-end, no black
segment, and a PASS report. It covers middle-row locality, exact
reversal, forward structural crossing, backward deletion stability,
point motion, public yank/undo, resize/context commits, and hard
paragraph completion.
- Closure: developer verification is complete. `issue019` remains open
only for the required user-visible confirmation; `task030` is unblocked.
- task032 [x] Reduce C-backed resize/reflow latency with exact layout parity.
- Source: `issue020` and the user's observed 6070 ms C resize latency.
- Baseline first: freeze the current portable C module and build a
same-machine evaluator that separates complete reflow, plan construction,
Emacs/C marshalling, C DP, and projection publication. Exclude startup,
resize debounce, and GC pauses from the mutator measurement.
- Performance gate: an interleaved baseline/candidate width-and-length
matrix must improve both p50 and p95 by at least 20%, keep candidate p95
at or below 50 ms, and report raw samples rather than a best-of run.
- Correctness gate: every candidate C result must match the frozen C
baseline and Elisp layout exactly; complete ERT, C/Elisp fuzz,
warning-as-error builds, static/release checks, and temporal GUI resize
evidence remain mandatory.
- Constraints: do not weaken cache identity, KP output semantics, the
stable transaction model, or source-clean projection. Do not hide work
with longer debounce, skipped widths, stale results, approximate reuse,
background publication, or global GC changes.
- Stop condition: optimize only the layer proven dominant by the baseline
profile. If C DP is not the owner of most resize time, fix the actual
Elisp/C or projection boundary instead of micro-optimizing the DP.
- Impact: evaluator/benchmark, the proven runtime owner, C module tests,
performance documentation, issue/change records, and a decision
postmortem when the selected optimization is non-obvious.
- Implementation: resolve one paragraph/DP payload per plan, cache natural
gap geometry, omit true no-op gaps, consolidate owned property writes,
and avoid static-plus-live double publication of the active paragraph.
- Result: four interleaved rounds reduce core p50/p95 from
22.949/42.006 ms to 15.318/27.687 ms and complete resize p50/p95 from
28.149/46.611 ms to 15.900/27.487 ms. Exact frozen-C/Elisp parity holds.
- Verification: normal and permuted 193-test suites, the per-test isolated
runner, 300-case fuzzing, warning-as-error compilation, package-lint,
checkdoc, release checks, and reviewed dynamic GUI evidence pass.
- Closure: developer verification is complete. `issue020` remains open
only for the required user-visible confirmation.
- task033 [x] Prevent rigid inline atoms from fragmenting preceding CJK.
- Source: `issue021`, the user's 2026-08-01 showcase screenshot, and
`plan_text_property_layout_engine_20260729.md` M12.
- Red tests: reproduce the deterministic batch underfull-to-overfull jump
and the real 280px showcase path; require the fullest permitted prefix
line, one intact atom line, and matching Elisp/C plans.
- Work: correct the emergency transition owner in core DP and its C/loose
equivalents without changing strict K-P results or buffer projection.
- Validation: focused RED/GREEN core and buffer ERT, normal/random/
isolated full ERT, C/Elisp fuzz, warning-as-error compilation, checkdoc,
release/static gates, and inspected static/dynamic GUI evidence.
- Impact: core line breaking, C parity, regression tests, user/developer
documentation, issue/change records, and no new public API.
- Result: retain the original emergency transition and add a fallback only
for a forbidden-run underfull-to-overfull jump. This was later superseded
by the accepted `task036` final-pass model; strict K-P and buffer
projection remain unchanged.
- Verification: the RED boundary was 1 instead of 11. Normal,
seeded-random, and isolated ERT pass 201/201; property fuzz passes
300/300; Elisp/static/release/dictionary and three C profile builds pass.
Reviewed 62.7-second GUI evidence at
`/private/tmp/ekp-atom-gui-final-Beg8hb` passes all nine checkpoints with
an intact atom, one-line full prefix, exact source, zero overlays, active
C engine, and no black or transient vertical frame.
- Closure: developer verification is complete. `issue021` remains open
only for the required user-visible confirmation.
- task034 [x] Make break policy configurable and eliminate inline-code
induced orphan glyph lines.
- Source: the approved RALPLAN PRD
`.omx/plans/prd-ekp-configurable-break-policies.md`, test spec
`.omx/plans/test-spec-ekp-configurable-break-policies.md`, and the
user's 2026-08-01 follow-up screenshot showing the showcase no longer
collapses into the previous one-glyph cascade but still permits
pathological single-CJK source lines around the inline atom, including
`行`, `内`, and `永`.
- Work: implement the locked A2/B2/C2/D1/E1/F1/G2/H1/I1/J1/K1/M1 policy
contract. Inline code defaults to legal wrapping with no dictionary
hyphenation; block code remains verbatim; explicit `ekp-no-break` stays
rigid forever; automatic no-break downgrades to no-hyphen when wider than
the effective measure; URL/path/identifier default to no-hyphen; compact
number-unit tokens default to no-break; kinsoku defaults to `common`;
ordinary overlong tokens default to current emergency output; and buffer
measure defaults to the narrowest live window with fixed and max-cap
alternatives.
- Public configuration: add global, major-mode profile, buffer/file/dir
local, and region controls for inline code, hyphenation, token policies,
kinsoku profiles, overlong-token behavior, and buffer measure. Region
`ekp-break-policy` provides `normal`, `hyphenate`, and `no-hyphen`; the
existing `ekp-no-break` remains the only manual hard-atom owner.
- Red tests: prove inline faces no longer cause paragraph skipping; prove
region > explicit local > mode profile > global precedence; prove token
classification/downgrade behavior; prove `common`/`zh`/`ja`/`off`/custom
kinsoku; prove `emergency`/`overflow`/`natural` overlong modes; prove
`narrowest-window`, fixed, and `(max . PIXELS)` measure resolution; and
reproduce the current showcase orphan-glyph screenshot as a failing
public path before the fix, with an oracle that rejects any pathological
single-CJK source line in the crafted paragraph when a legal
non-emergency alternative exists.
- Verification: focused RED/GREEN ERT for core, buffer, command, cache, and
diagnostics; C/Elisp parity without changing the 15-argument C entry or
15-field batch payload; 300-case property fuzz; warning-as-error Elisp
compilation; C builds/tests; checkdoc/package/static/release gates; and
clean fullscreen single-window GUI evidence for inline wrapping,
no-orphan showcase text, explicit no-break, block verbatim, measure
modes, and overlong-token modes.
- Impact: `ekp.el`, `ekp-buffer.el`, `ekp_c/ekp_kp.c` only if parity logic
requires a mirrored DP adjustment, tests, bilingual user/developer
documentation, spec/plan/tech reference, issue/change records, changelog,
and a postmortem explaining the policy boundary.
- Implementation evidence recorded: G002 completed core policy compilation
without changing the 15-argument/15-field C boundary; G003 completed
buffer ownership, consult-only mode profiles, local/region controls,
diagnostics, and generation reflow; G004 completed the showcase split
between automatic inline code, explicit no-break, and verbatim block;
G005 performance work removed unconditional policy measure from paragraph
semantic identity, canonicalized adjacent equal filtered property
intervals, moved repeated policy full analysis behind a bounded two-tier
cache, and added a bounded per-paragraph semantic plan cache; G006
cleanup touched only `ekp.el` and `ekp-buffer.el`, removing redundant
policy/cache code while preserving behavior; G007 resolved the first G006
review blockers by returning consumer-owned copies of all plan-owned
mutable payloads from semantic-plan cache hits, keeping `para`
intentionally shared for append identity, and by making malformed non-nil
C single/batch results signal `ekp-backend-contract-error` while
preserving nil-result Elisp fallback and the 15-argument/15-field C
boundary; G008 resolved the final string-leaf context alias by using the
recursive context copier for cons/vector/string payloads both when the
context snapshot/cache key is created and when a plan is returned.
- GUI evidence recorded: `/tmp/ekp-g004-evidence.Tp77dW` has 12/12
checkpoints, a 25.75s/206-frame recording, no black segments, no failed
assertions, automatic inline wrapping 3→2→3 lines across 280→340→280,
every internal inline split as source whitespace, C active, exact source,
zero overlays, and policy no-hyphen→normal settling with generation_delta
1, live nil-plan span 1, stale nil-plan span 0, and current projection.
- Verified so far: G002 focused core policy ERT 22/22, no-break 5/5, cache
15/15, C parity 5/5, 300-case fuzz, warning-clean C build, byte
compilation, checkdoc, and diff-check; G003 core ERT 118/118, buffer ERT
119/119, watcher invariants 2/2, byte compilation, checkdoc, and
diff-check; G004 GUI verifier ERT 7/7 and clean GUI evidence; G005
formal four-interleaved evaluator layout parity true, core baseline
p50/p95 38.6679/51.6782ms versus candidate 21.7102/33.0040ms for
43.8549%/36.1354% gains, resize baseline p50/p95 43.3831/55.6250ms
versus candidate 22.0919/32.8202ms for 49.0773%/40.9973% gains, both
candidate p95 values under 50ms and both gain sets at least 20%; live
evaluator source-instrumented locked goal remains the known
`validation_failed` debt, with parity, zero-work, GC, conflict, and
all-width-nonregression true, current C p95/p99 26.449/26.740ms
(76.65%/77.23% improvement), current Elisp p95/p99 49.940/52.017ms
(91.64%/91.35% improvement), consistent with historical open `issue018`
and not a regression; byte-compiled production public path passed three
runs with zero GC, C append p99 1.361-1.368ms, C hard p99
1.876-1.891ms, Elisp append p99 1.692-1.775ms, and Elisp hard p99
2.100-2.230ms; default ERT 262/262, seeded permuted ERT seed 20260728
255/255, isolated per-test process suite exit 0, targeted alias guard
passed, property fuzz 300/300, warning-as-error byte compilation,
package-lint pinned at `35996f478d81e51dae4fa30d051f741895d07399` exit
0 with only an external obsolete warning from the local names dependency,
empty checkdoc, release, 49-entry dictionary manifest, pinned dictionary
update, shell syntax, CI YAML, diff-check, portable/native/debug/sanitize
warning-clean C builds, module 1.6/4-thread smoke, and focused C 19/19
including the 15-argument/15-field boundary passed; G006 targeted
cleanup verification passed 6/6 and 3/3, full core ERT 125/125 and
buffer ERT 120/120 passed, and byte compilation, diff-check, and C build
gates passed; G008 formal resize parity true, core baseline/candidate
p50/p95 36.595/48.357ms versus 23.266/36.363ms for 36.42%/24.80%
gains, resize baseline/candidate p50/p95 41.443/53.541ms versus
23.761/36.889ms for 42.67%/31.10% gains, ERT 268/268, fuzz 300/300,
release gates pass, and byte-compiled current public path records zero
GC with C append/hard p99 1.440-1.464/2.022-2.050ms and Elisp
append/hard p99 1.648-1.687/2.136-2.335ms, all below 16ms.
- Closure: task034 developer implementation, repository gates, performance
gates, cleanup gates, G007 first-blocker fixes, and the G008 final
string-leaf context fix are complete.
`issue021` is closed after user visual confirmation, historical `issue018`
remains open, and final independent review returned APPROVE with
architecture status CLEAR.
- task035 [x] Falsify line-width-scaled final-pass emergency stretch.
- Source: `issue021`, the user's 2026-08-02 narrow-showcase screenshot,
and the G009 upstream K-P review.
- Red tests: reproduce the mixed showcase paragraph at narrow widths and
reject any isolated CJK source line through the core, public buffer path,
and GUI oracle.
- Work attempted: keep the strict pass unchanged; in the final pass add a
line-width-sized finite background emergency stretch to ordinary
underfull candidates and score them through adjustment ratio, badness,
fitness, and demerits. Keep the fixed artificial emergency transition
only for truly overfull first permitted hard/atomic runs. Do not add
CJK-orphan, unit, or screenshot-specific penalties.
- Validation: focused core single-CJK ERT passes 2/2; focused core, C,
hard-atom, buffer, and GUI-oracle G009 coverage passes 18/18 in the
implementation lane; warning-as-error byte compilation and the C build
pass. Full regression then falsified the line-width stretch detail: it
made emergency stretch scale with the candidate measure instead of a TeX
fixed dimension and regressed existing emergency behavior.
- Impact: `ekp.el`, `ekp_c/ekp_kp.c`, tests, bilingual documentation,
phase records, changelog, and postmortem. Public configuration and the C
ABI are unchanged.
- Closure: task035 is closed as a disproved implementation attempt, not as
the accepted current behavior. Continue with `task036`.
- task036 [x] Implement fixed-dimension final-pass emergency stretch and
preserve the final active path.
- Source: task035 full-regression failure, `issue021`, and the G009
original K-P correction.
- Work: replace the candidate-width/line-width emergency stretch detail
with a TeX-style fixed dimension exposed as
`ekp-emergency-stretch-pixel`. A nil value auto-resolves to roughly three
display-font `M` widths; a non-negative integer fixes the stretch in
pixels. Keep the strict pass unchanged and the 15-field C paragraph ABI
stable. In the final pass, preserve the best provisional overfull path
with tight fitness and zero incremental demerits only when no normal
active path survives to that breakpoint. Do not add CJK-orphan, unit,
atom-adjacency, or screenshot-specific penalties.
- Red tests: add five focused RED cases covering fixed dimension versus
measure scaling, nil auto resolution, non-negative integer override,
Elisp/C parity without C ABI growth, and renderer width/glue consistency.
- Regression gate: the original three regressions that task035 broke must
stay green alongside the new RED cases.
- Full gate: focused G009 core/public-buffer/C parity and GUI-oracle
coverage, final fullscreen dynamic GUI evidence, default/permuted/
isolated ERT, 300-case fuzz, warning-as-error byte compilation, C builds,
checkdoc/package/static/release gates, and diff-check.
- Result: production Elisp 1D, looseness/parshape, and C implementations
match; renderer glue distribution uses the chosen line's actual rest;
explicit hard atoms forbid only interior breaks and may share an overflow
line with preceding ordinary content. The test oracle now requires a
nonempty source-covering plan so an empty result cannot false-green.
- Validation: focused root regressions pass 8/8, emergency selection passes
10/10, core/buffer/GUI oracle passes 8/8, full ERT passes 288/288, seeded
and isolated suites pass 279/279, fuzz passes 300/300, portable/native/
debug/sanitize C builds and warning-as-error production plus changed-test
byte compilation pass, checkdoc/package/static/release gates pass, formal
core/resize performance gates pass, and reviewed 42.78-second
single-window dynamic GUI evidence at
`/tmp/ekp-g009-evidence-retry.UOpPNp` returns PASS.
Final independent code review returns APPROVE and architecture review
returns CLEAR.
- Closure: developer implementation and full quality gate complete;
`issue021` closed after the user's 2026-08-02 visual confirmation.

View File

@ -0,0 +1,408 @@
# Technical Reference: Text-Property-Only KP Layout 2026-07-29
## Status and Reader Task
Implemented by `task019` through `task022` for static/source-clean
projection, then refined by later live-editing tasks. `task028` supersedes
the native-row live planning unit with semantic hard-line prefix
projection. This record is the technical reference for the semantic layout
plan, text-property-only buffer projection, live editing boundary, and
their explicit constraints.
## User Constraints
- Do not use overlays.
- Do not insert glue spaces, soft newlines, or discretionary hyphens into
buffer text.
- Express layout through text properties on existing source characters.
- Keep the source character sequence and all logical buffer positions
unchanged.
- An unfinished active line and later live-edit behavior remain separate
product concerns; a static display proof does not establish seamless
editing.
## Conclusion
Emacs 29.1 and later expose enough display primitives to make a
text-property-only KP renderer plausible:
- `(min-width ((PIXELS)))` gives a source span an absolute minimum display
width and pads only in the display layer.
- `(space-width FACTOR)` scales existing ASCII space glyphs without
replacing their source characters.
- Combining `space-width` with `min-width` expresses both shrink and stretch
while using `min-width` as an exact pixel floor after floating-point
rounding.
- `line-prefix` supplies indentation for non-continuation display lines.
- A replacing `display` string attached to an existing grapheme can show
that grapheme followed by a discretionary hyphen and a display-only
newline. The original grapheme remains in the buffer.
These primitives close the representation gap for the implemented
single-authoritative-width buffer renderer. Foreign ownership, multiple
windows, and seamless incremental editing are governed by the decisions
below rather than inferred from the primitives alone.
The package baseline does not need to move: Emacs `NEWS.29` records
`min-width` as a new `display` property, and the repository's CI exercises
the declared Emacs 29.1 floor.
## Confirmed Emacs Semantics
### Absolute pixel minimum width
The `min-width` display specification has the form
`(min-width (WIDTH))`. The outer one-element list identifies a contiguous
display run by `eq`. A pixel expression of the form `(N)` means exactly
`N` pixels, so the fully nested form for 20 pixels is:
```elisp
(min-width ((20)))
```
Emacs displays the underlying text normally and appends display-only white
space if the rendered span is narrower than the requested minimum.
Each independently padded adjacent span must receive a distinct identity
list. Reusing the same inner list object can make Emacs treat adjacent
spans as one run and add padding only at the end of the combined run.
References:
- [Other Display Specifications](https://www.gnu.org/software/emacs/manual/html_node/elisp/Other-Display-Specs.html)
- [Pixel Specification for Spaces](https://www.gnu.org/software/emacs/manual/html_node/elisp/Pixel-Specification.html)
- [Emacs 30.2 display implementation](https://github.com/emacs-mirror/emacs/blob/emacs-30.2/src/xdisp.c#L5606-L5686)
### Scaling real spaces
`(space-width FACTOR)` multiplies the rendered width of every ASCII space
covered by the property. A factor below 1 shrinks; a factor above 1
stretches.
The Emacs 30.2 implementation adds three boundaries beyond the manual's
summary:
- `FACTOR` must be a number greater than zero.
- The graphical display path checks specifically for ASCII `SPC`.
- The specification is ignored on a non-window-system frame.
It does not affect tabs. NBSP, ideographic spaces, tabs, and other Unicode
space characters therefore need their own policy; they must not be assumed
to behave like an ASCII word space.
References:
- [Other Display Specifications](https://www.gnu.org/software/emacs/manual/html_node/elisp/Other-Display-Specs.html)
- [Emacs 30.2 `space-width` parsing](https://github.com/emacs-mirror/emacs/blob/emacs-30.2/src/xdisp.c#L6039-L6052)
- [Emacs 30.2 ASCII-space scaling](https://github.com/emacs-mirror/emacs/blob/emacs-30.2/src/xdisp.c#L32723-L32729)
### Combining the two specifications
For a real ASCII whitespace run whose measured natural width is `N` and
whose KP target width is `G > 0`, use the conceptual display value:
```elisp
((space-width FACTOR)
(min-width ((G))))
```
where `FACTOR = G / N`.
`space-width` first makes shrink possible. The display engine rounds the
scaled glyph width to pixels; `min-width` then pads any downward rounding
error to the exact target. For a target at least as wide as the natural
run, `min-width` alone is sufficient, but one combined representation can
keep the renderer uniform.
A zero-width source-space run is not represented by `(space-width 0)`,
because zero is rejected. Line-edge spaces must instead be consumed by the
chosen break representation, retained naturally, or handled by a separately
verified display rule.
### Indentation
The `line-prefix` text property adds a display-only prefix to every
non-continuation line. It must cover the complete affected text range so
redisplay can find it at each line start.
If EKP relies on Emacs's native wrapping, continuation lines use
`wrap-prefix`, not `line-prefix`. If EKP realizes each KP break as a newline
inside a replacing display string, the following row is a non-continuation
display line and `line-prefix` is applicable.
Reference:
- [Special Properties: `line-prefix`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Special-Properties.html)
## Mapping KP Output to Existing Source Text
| KP result | Text-property-only projection |
| --- | --- |
| Ordinary box | Display the original source grapheme or word unchanged. |
| Latin word glue over ASCII spaces | Apply `space-width`; add `min-width` as the exact pixel floor. |
| CJK or mixed glue with no source space | Apply `min-width` to the preceding source box with target `natural-box-width + glue-width`. |
| Leading indentation | Apply `line-prefix`; use `wrap-prefix` only if the selected break backend creates continuation lines. |
| Break at an existing single space | Replace that space on display with a newline while leaving the source space in the buffer. |
| CJK break with no boundary character | Make the preceding grapheme display as `GRAPHEME + NEWLINE`. |
| Latin discretionary hyphen break | Make the preceding grapheme display as `GRAPHEME + HYPHEN + NEWLINE`. |
| Final line | Remove forced-break/hyphen properties and leave it ragged unless alignment requires a leading prefix. |
The current algorithm already computes exact per-gap pixel results for
stretch and shrink. The planned change is a renderer substitution, not a
new KP cost or distribution model.
## Discretionary Latin Hyphen Without Source Pollution
### Proposed break owner
For a KP break between source positions `P` and `P+1` inside a Latin word:
1. Identify the complete grapheme immediately before the break. Never split
a combining sequence.
2. Leave every source character untouched.
3. Attach a replacing `display` text property to that grapheme.
4. Build the replacement string from the same visible grapheme, followed by
the configured hyphen glyph and `"\n"`.
5. Copy the grapheme's relevant face/font properties to the replacement
string, but remove recursive `display` state.
6. Put a `cursor` property on the reproduced grapheme so point on the
covered source positions is drawn on the grapheme rather than on the
synthetic hyphen or newline.
7. Include the hyphen width in the KP line measurement exactly as the
current renderer already does.
8. Remove or move the property atomically when a later plan chooses a
different break.
Conceptually, breaking `abcdefgh` after `d` displays:
```text
abcd-
efgh
```
while the source characters remain exactly:
```text
abcdefgh
```
The hyphen and newline exist only inside the `display` property's
replacement string. `char-after`, character positions, regexp search, and
`buffer-substring-no-properties` continue to operate on the original word.
### Why the preceding grapheme owns the replacement
Attaching `"-\nE"` to the next grapheme would make point on that source
grapheme default to the hyphen or newline portion. Attaching
`"D-\n"` to the preceding grapheme keeps the visible source glyph at the
property's logical anchor, and the next source grapheme begins naturally on
the following display line.
### Runtime evidence on Emacs 30.2
A clean GUI Emacs 30.2 probe applied a `display` text property containing
`"d-\n"` with `cursor 1` to the source `d` in `abcdefgh`.
Observed result:
- `substring-no-properties (buffer-string)` remained `"abcdefgh"`.
- `buffer-size` remained 8.
- `count-screen-lines` reported 2.
- `vertical-motion` by one display line moved point from position 1 to
position 5, the source `e`.
- A 20px `line-prefix` placed both visual rows at x=20.
This proves the minimal LTR shape is expressible. It does not yet prove
mouse selection, active-region highlighting, isearch faces, bidi text,
multi-codepoint graphemes, IME composition, or foreign display-property
composition.
References:
- [Display strings](https://www.gnu.org/software/emacs/manual/html_node/elisp/Other-Display-Specs.html)
- [Cursor placement in display strings](https://www.gnu.org/software/emacs/manual/html_node/elisp/Special-Properties.html)
- [Emacs 30.2 cursor handling around display-string newlines](https://github.com/emacs-mirror/emacs/blob/emacs-30.2/src/xdisp.c#L1899-L1956)
## Runtime Evidence for Exact Glue
A clean GUI Emacs 30.2 probe measured the current font's ASCII space at
7px, then applied the combined `space-width` and `min-width` display value
to two real source spaces:
- target 3px rendered as exactly 3px;
- target 20px rendered as exactly 20px;
- the buffer still returned the unchanged source characters `"a b"`.
This proves one-font, one-window shrink and stretch. The production gate
must repeat the probe across text scaling, face remapping, font fallback,
mixed property runs, and window resizing.
## Text API Boundary
This direction removes character pollution but not all property metadata.
- `buffer-substring-no-properties`, direct character access, search,
syntax parsing, markers, point, and character counts see the original
source characters.
- `buffer-string` and `buffer-substring` copy text properties into the
returned string. Lisp that explicitly examines properties can observe
EKP's display metadata.
- Property-only updates must use `with-silent-modifications` so layout
refresh does not alter the modified flag, ordinary undo history, or
external change hooks.
- EKP-owned properties must be nonsticky so new user input does not inherit
a stale break, hyphen, width, or prefix.
- Copy/yank and insertion of propertized strings require an ownership rule
that strips or regenerates EKP layout properties rather than carrying a
stale layout into another location.
References:
- [Text Properties](https://www.gnu.org/software/emacs/manual/html_node/elisp/Text-Properties.html)
- [Examining Buffer Contents](https://www.gnu.org/software/emacs/manual/html_node/elisp/Buffer-Contents.html)
- [Changing Text Properties](https://www.gnu.org/software/emacs/manual/html_node/elisp/Changing-Properties.html)
## Implementation Decisions and Remaining Boundaries
### Chosen break semantics
The replacement-string approach forces the exact KP break and makes
`line-prefix` usable. A native-wrap alternative might avoid replacing the
break-owner grapheme by filling the line exactly with `min-width`, but it
still needs proof that Emacs never wraps at an earlier legal boundary and
that exact-edge stretch glyphs remain on the intended line.
The implementation uses the replacing-string backend because it represents
the chosen KP break explicitly. Native wrapping is not a fallback.
### Foreign `display` ownership
Text has one effective `display` property value at each position. Parallel
non-replacing specifications can coexist in one value, but a foreign
replacing string, image, invisibility rule, composition, or font-lock
manager can conflict with EKP's width or break owner.
EKP rejects the affected hard paragraph with an explicit diagnostic. It
never erases, merges, or silently reorders a foreign replacing
specification.
### Graphemes, bidi, and shaping
A replacing display string is treated as one display object for
bidirectional reordering. The first implementation slice should therefore
target LTR Latin hyphenation and CJK. Arabic, Hebrew, Indic shaping,
ligatures, and multi-codepoint graphemes remain gated until their cursor,
selection, and shaping behavior is proven.
### Multiple windows
Text properties belong to the buffer, not a window. One property set cannot
simultaneously encode two different KP break plans for the same buffer shown
at different widths.
The narrowest live window showing the buffer is authoritative. Wider
windows can retain right-side space but cannot overflow. Independent
simultaneous width plans are explicitly outside this text-property-only
architecture.
### Live editing
The current target policy separates stable editing from global KP
commitment. One committed projection owns the hard-line baseline source,
semantic plan, line signatures, projected spans, and break anchors. One
dirty edit transaction snapshots that baseline and naturalizes only the
affected local island.
Ordinary input inside the same native visual row performs no whole-hard-line
planning. Existing glue and native soft wrapping absorb local changes while
unaffected anchors remain installed. If the row overflows, native wrapping
moves only the required local words. Crossing into a new native visual row
is a structural commit: `ekp-buffer` calls or reuses the existing
whole-hard-line `ekp-layout-plan` once, then old/new semantic signatures
limit one silent prefix publication.
Hard newline/paragraph completion, the next real edit outside the dirty
island, explicit refill, and width/font/layout-context changes are the other
commit events. Point-only motion is not a layout event anywhere. A reversible
edit restores the saved owned projection directly and
`equal-including-properties`.
This remains a buffer-layer policy, not a DP variant. Core DP plans complete
text and receives no point, marker, window, buffer, redisplay, transaction,
or composition state. The C ABI, DP schema, and plan contract do not change.
Static paragraph-edge hiding applies only to committed projected lines. The
dirty edit island retains leading/trailing source whitespace with no
replacing display, so a single space or tab is visible in the same
command-loop turn. Reprojection restores the mark marker and `mark-active`
as separate editor state; `set-mark` is not a valid positional restoration
API because it activates an otherwise inactive mark.
Automatic planning is bounded by
`ekp-auto-justify-paragraph-limit`. An oversized single hard line stays
natural and diagnostic rather than blocking input; the explicit
`ekp-refill-paragraph` command bypasses this limit.
## Rejected Shortcuts
- Insert U+00AD, `-`, whitespace, or newline characters: violates the
logical-text requirement.
- Overlay `before-string` or `after-string`: explicitly excluded by the
user.
- Use `min-width` alone for shrink: it only adds padding.
- Use `space-width` alone as an exact-width guarantee: floating-point
scaling is rounded to pixels.
- Treat tabs or all Unicode whitespace as ASCII spaces: contradicted by the
documented and source-level behavior.
- Claim the GUI probe proves seamless editing: it covers a static LTR
display case only.
## Runtime Verification Gate
1. Exact glue:
- 1px through 64px targets;
- shrink and stretch;
- CJK, ASCII space, mixed gap, and repeated spaces;
- text scaling, face remap, and fallback fonts.
2. Breaks and hyphens:
- Latin word break with hyphen;
- CJK break without whitespace;
- break at one and multiple source spaces;
- final-line removal and breakpoint movement.
3. Editor semantics:
- point at every source position;
- `C-n`/`C-p`, visual beginning/end of line, mouse hit testing;
- active region, isearch, kill/yank, undo/redo, and narrowing.
4. Ownership:
- font-lock refontification;
- a foreign replacing `display` property;
- composition and IME preedit;
- exact restoration of pre-existing properties.
5. State invariants:
- unchanged source characters and `buffer-chars-modified-tick`;
- unchanged modified flag, undo list, and external change-hook count;
- no stale property inheritance after insert, delete, split, or join.
6. Architecture decision:
- explicitly resolve or relax simultaneous different-width windows
before calling the renderer complete.
## Stop Condition
This research task is complete when the confirmed primitives, proposed
hyphen owner, known limitations, and runtime evidence are recorded.
The original static feasibility and one-buffer/multiple-window gates were
satisfied by `task019``task022`.
`task028` proved whole-hard-line plan consumption and semantic signature
publication. `task029` proved that point-only motion must perform zero work.
`issue019`/`task031` supersede their per-edit trigger ownership: a frontier
cannot simultaneously be latest edit, natural suffix, and projection reuse
boundary. Whole-hard-line planning remains valid at structural commits, not
after every key.
The task029 performance audit recorded 291 unique append plans and zero cache
hits at 80 pixels. `task031` now removes ordinary same-row planning: the same
291-edit workload records 15 structural plans, while same-row revisit and
point-motion scenarios record zero plans. The remaining C/Elisp p99 spikes
coincide with permitted commit events; `issue018`/`task030` now own that
unblocked, narrower performance surface.

View File

@ -0,0 +1,82 @@
# P1+P2 功能阶段笔记(2026-07-26 起)
> 用户指令:高质量完成 P1(标点挤压、ragged 模式、no-break API)+ P2(悬挂、
> parshape/首行缩进、连续标点);代码块等特殊文本需正确处理;**通用机制优先,
> 万不得已才做场景特化**。
## 总体架构决策
1. **地基 = 逐间隙断行许可(breaks-allowed)+ 标点独立成盒**
禁则从"吞噬式附着"迁移为 DP 层的断点禁止;所有后续特性(NBSP、
no-break 区间、行内 verbatim 原子、标点类别)都是这套机制的实例。
2. **Emacs 显示引擎约束(已确证)**:无法缩减字形 advance(无负宽
display)→ CLREQ 行中标点挤压不可渲染;行首/行尾挤压视觉上等价于
"悬挂"(protrusion)→ P1-1 + P2-4 + P2-6 统一为**边缘突出机制**,
按字符类配比率(可扩展到拉丁连字符突出 = microtype)。
3. ragged-right/left/center:DP 侧 = 刚性 glue(stretch/shrink 数组置零)
+ 每行额外伸展量 R(badness 以 R 为 flexibility);渲染侧分派剩余量
(右/左/对半)。C 只需 +1 标量。
4. parshape/首行缩进:每行宽依赖行号 → 复用 looseness 的 2D DP,
elisp-only(C 自动旁路,同 looseness 先例)。
5. verbatim:段落级豁免(region 层谓词/属性)+ 行内原子
(ekp-no-break 属性 → 禁断点 + 刚性 glue + 禁断词)。
## 关键实现事实(读码结论)
- tokenizer 附着逻辑在 ekp-utils.el `ekp--handle-cjk-char/latin-char`
(开放标点 hold-and-prepend;闭合标点 append-to-prev)。
**已知老 bug**:连续闭合标点(字。」)第二个独立成盒且断点未禁止 →
」可出现行首;开放标点跨空格 hold 还会导致盒序与原文顺序不一致。
迁移后两者都根治。
- `ekp--str-type` 返回 space/latin/cjk/cjk-punct → 拆成 cjk-open
(opening-punct-p:general-category Ps/Pi)/ cjk-close(fw-punct-p
且非 open)。“” 特例保持 'cjk。
- `ekp--glue-type` 新矩阵(保持旧拓扑等价):space→nws;
before=open→nws(原盒内);after=close→nws(原盒内;close-close
从 cws 改为 nws,属有意修正);latin-latin→lws;cjk-cjk→cws;
cjk/latin 混→mws;其余含标点→cws。
- breaks-allowed 规则:`allowed[k] = !(tail(box[k-1])=open || head(box[k])=close)`,
k∈[1,n-1];k=n(段末)恒可。存 bool-vector(elisp DP 用)+
forbidden-positions int 向量(C 打包用,稀疏,仿 hyphen-positions)。
- DP 改动(elisp `ekp--dp-run-1d` + C `dp_process_position` 镜像):
候选 k 需 allowed;不 allowed 时**不 throw**继续延伸;
紧急兜底从 single-box 推广为 atomic-run(i 到 k 间无允许断点);
多盒紧急行需记录 gaps 计数(渲染 normal 路径 clamp ≥0 自然溢出)。
- C 桥:`ekp-c-break-with-arrays` 11→12 参(forbidden-positions),
batch 向量同步;`ekp-c-set-penalties` 后续 ragged 加 extra-stretch
标量;protrusion 再加两数组(head/tail protrude px)。每次 API 变
动 bump EKP_VERSION_MINOR + `ekp-c-module-required-version`
- 隐性收益:「Hello / Hello」 之前整盒无法匹配断词正则(左右标点类
不含 CJK 引号)→ 拆盒后可正常断词。
- 测试影响:tests/ekp-tests.el 里 split-* 结构测试要改为新盒契约;
新增行为级禁则测试(任意宽度:行首无 close、行尾无 open、
字。」不拆)。fuzz 断言与引擎无关,应保持 0 失败。
## 阶段与提交计划(全部完成 2026-07-26)
- [x] A 地基 64eb2f3:标点成盒 + breaks-allowed + DP/C 1.2;顺带修复
连续闭合标点行首漏洞、open-punct 跨空格盒序错乱、「Hello 断词失效;
半角标点禁则(纯标点盒判定)
- [x] B ea96a6d:ekp-no-break 属性(刚性原子/禁断词)、NBSP/NNBSP/
FIGURE SPACE/WJ/ZWNBSP、命令 ×2;零 C 改动
- [x] C 57a3abe:ekp-alignment 四模式 + ekp-ragged-stretch-pixel;
C 1.3(set-penalties 第 7 参 extra-stretch,缺省归零)
- [x] D f6aa64b:ekp-protrusion 右缘悬挂(cjk-close/latin-close/hyphen
比率);DP/渲染/C 重建三处 lw=width+release 同步;C 1.4
(break-with-arrays 14 参);region 预留 protrusion-reserve;
仅右缘(左缘无法渲染,文档已注明)
- [x] E 720b1cd:ekp-parshape + ekp-first-line-indent(t=2em 按段落
CJK 字体);loose 2D 每行宽;C 旁路
- [x] F 4d9a018:ekp-verbatim 段落豁免 + ekp-region-skip-faces +
buffer-local skip-predicate;行内原子沿用 ekp-no-break;核心零改动
- [x] G:readme×2 排版特性/verbatim 章节、DEVELOPER×2 §5.1;GUI 目检
(悬挂+缩进+verbatim+auto-mode 齐行/ragged 两态截图确认)
最终状态:66 ERT 全绿,fuzz 300/300(每阶段跑),C 模块 1.4 两引擎
逐字节一致。行中挤压不可渲染(Emacs 无负宽 display)= 已知边界。
## 验证清单(每阶段)
byte-compile 零警告(error-on-warn)→ 47+ ERT → C 重建 + parity →
fuzz 300 → 提交。改 ekp_c/ 后必须 make clean && make。
Emacs: /Applications/Emacs.app/Contents/MacOS/Emacs

144
AGENTS.md Normal file
View File

@ -0,0 +1,144 @@
# 核心协议:意图识别(强制执行)
在处理任何请求之前,你必须先识别用户的意图并遵循相应的协议。
## 1. 🌱 启动 / 立项 / 模糊想法
**触发条件**:用户想要开启新项目、新阶段,或者只有一个模糊的想法。
**行动**
1. **扫描**:读取 `.phrase/modules/pr_faq.md` 的 YAML 元数据以确认匹配。
2. **加载**:仅当匹配成功时,完整读取该文件内容。
3. **执行**:扮演“严格的产品经理”角色。进行访谈以起草亚马逊风格的 PR/FAQ。
4. **约束**:在 PR/FAQ 最终确定之前,禁止开始编写代码或拆解任务。
## 2. 🔨 编码 / 重构 / 审查
**触发条件**用户请求代码实现、Bug 修复、重构或代码审查。
**行动**
1. **扫描**:读取 `.phrase/modules/linus_coding.md` 的 YAML 元数据以确认匹配。
2. **加载**:仅当匹配成功时,完整读取该文件内容。
3. **执行**扮演“Linus Torvalds”角色。
4. **约束**在编码前和编码过程中严格执行“5 层思考模型”和“好品味”判断。
## 3. ✍️ 文案 / 营销 / 文档
**触发条件**:用户需要撰写 README、发布说明、产品介绍或营销文案。
**行动**
1. **扫描**:读取 `.phrase/modules/copywriting.md` 的 YAML 元数据以确认匹配。
2. **加载**:仅当匹配成功时,完整读取该文件内容。
3. **执行**:扮演“转化率文案专家”角色。
4. **约束**:遵循“结论先行”、“降低成本”、“可感知的具体”等 10 大原则。
## 4. 🌐 浏览器 / 网页自动化 / 爬虫
**触发条件**:用户需要访问网页、抓取数据、截图、测试 Web UI 或填写表单。
**行动**
1. **扫描**:读取 `.phrase/modules/agent-browser.md` 的 YAML 元数据以确认匹配。
2. **检查**:确保环境中已安装 `agent-browser` 依赖。
3. **加载**:仅当匹配成功且依赖满足时,完整读取该文件内容。
4. **执行**:使用 CLI 工具进行浏览器自动化操作。
## 5. 📋 任务执行(默认)
**触发条件**:用户想要执行一个具体的、已定义的任务。
**行动**:遵循下方的“文档驱动开发”工作流。
---
“文档驱动开发Doc-Driven Development先锁定文档 → 拆 `taskNNN` → 实现与验证 → 回写文档。
---
## 0. 原则(按优先级)
- 仓库既有规范 > 本文;冲突时按 `README`/`STYLEGUIDE` 等执行,并在 `issue_*`/`change_*` 记录取舍。
- 文档为事实来源:需求、交互、接口只能来自 `spec/plan/tech-refer/adr`
- 单次仅处理一个原子任务;所有改动可追溯到 `taskNNN` 与其依据(`spec`/`issue`/`adr`)。
- 每个 `taskNNN` 必须说明验证方式(测试或手动步骤)。
- 实现完成必须回写:`task_*`、`change_*`,必要时更新 `spec_*`/`issue_*`/`adr_*`。
---
## 1. 仓库结构与文档
- 代码根:`App/`, `Core/`, `UI/`, `Shared/`, `Tests/`, `Assets/`, `Samples/`, `Schemas/`, `StackWM-Bridging-Header.h`。保持分层清晰,`Tests/` 镜像核心模块。
- 文档根:`.phrase/`
- 阶段:`.phrase/phases/phase-<purpose>-<YYYYMMDD>/`
- 全局索引:`.phrase/docs/`
- `Docs/` 为外部文档,可继续独立存放。
---
## 2. Phase 工作流
1. **Phase Gate**(仅当用户明确开启新阶段):在新 `phase-*` 目录创建最小集 `spec_*`, `plan_*`, `task_*`, 视需求补 `tech-refer_*`/`adr_*``issue_*` 可后置。
2. **In-Phase Loop**(默认):
- 新需求 → 更新当前 `plan_*` → 拆 `taskNNN`
- 实现 → 在 `task_*` 中新增/更新并执行对应任务。
- Bug → 在 `.phrase/docs/ISSUES.md` 登记 `issueNNN`,在 phase 写详情,再拆 `taskNNN`
- 不可逆决策 → 先写 `adr_*` 或在 `tech-refer_*` 增 “Decision”。
3. **Task 闭环**:完成后需
1) 将 `task_*` 条目标记 `[x]`
2) 在 phase `change_*` 记录条目,并于 `.phrase/docs/CHANGE.md` 加索引
3) 若影响交互,更新对应 `spec_*`
4) 若解决问题,更新 `ISSUES.md` 和 issue 详情(含验证结论)
当目标与当前 phase purpose 明显不同、需要独立里程碑或架构大重构时,可建议开启新 phase但需用户确认。
### Phase 生命周期
- 开启阶段:在 `.phrase/phases/phase-<purpose>-<date>/` 下创建 `spec/plan/task/...`
- 阶段完结:用户确认后,将整个目录重命名为 `DONE-phase-<purpose>-<date>/`,同时把主要文档也按规则改为 `DONE-PLAN-*`、`DONE-TASK-*` 等,确保一眼可见结项状态。
---
## 3. Task / Issue 规范
- `taskNNN` 为三位递增 ID`task001` 起),不可重排或复用;拆分/合并需创建新 ID 并在原任务注明流向。
- 任何对 `task_*` 的增删改/勾选都要在当前 phase `change_*` 记录一次,可批量合并但必须可追溯。
- 原子任务标准:一次工作会话可完成、产出可观察、可独立验证,既不过细也不过粗。
- Issue
- 全局索引:`.phrase/docs/ISSUES.md` 用 `issueNNN [ ]/[x]` 并链接 phase 详情。
- 详情文件 `issue_<purpose>_<YYYYMMDD>.md` 需含环境、复现、调查、根因、修复、验证、关联的 `taskNNN`/提交。
- 用户可感知问题需在标记 `[x]` 前获得确认,并记录 `Resolved At/By/Commit`
---
## 4. Build / Test / Dev
- 首选仓库入口:若提供 Makefile、GitHub Actions、或 scripts/,优先使用。
- windows系统下 emacs 路径: "C:\Users\26289\Apps\emacs-30.2\emacs-30.2\bin"
- 常见 Elisp 验证方式:
- 运行 ERT 测试emacs -Q --batch -L . -l <test-file> -f ert-run-tests-batch-and-exit
- 交互手动验证emacs -Q -L . -l <pkg>.el 后在 UI 中 M-x 执行命令
- 可选lint/格式(按仓库约定),例如 package-lint、checkdoc、byte-compile若项目采用
- 测试用例全部写在根目录的 tests/ 目录下
---
## 5. 编码与验证
- 遵循仓库已有编码规范缩进、命名、lexical-binding 等)。
- 明确支持的 Emacs 版本范围;涉及 API 差异时要写清楚 fallback 或条件分支策略。
- 尽量保持改动最小化:除非任务是“清理”,否则避免批量格式化与无关重排。
- 关键路径加可诊断日志(遵循项目 logging 方案)。
- 测试优先覆盖核心逻辑UI/系统胶水可提供手动验证步骤。测试必须确定性,必要时注入依赖或 mock。
---
## 6. 文档更新与 Changelog
- `change_*`phase 内的真实变更记录;每个完成的 `taskNNN` 至少一条,包含日期、文件/路径、Add|Modify|Delete、受影响函数、行为/风险说明,按时间倒序。
- `.phrase/docs/CHANGE.md`:仅索引与摘要,指向对应 phase `change_*` 条目;可按工作会话批量更新。
- `spec_*`/`plan_*`/`tech-refer_*`/`adr_*`/`issue_*` 均需随变更回写(增量即可),保持单一事实来源。
---
## 7. 提交、PR 与安全
- 默认使用 Conventional Commits`feat:`, `fix:`, `docs:`, `test:`, `chore:` 等),一份提交聚焦单个 `taskNNN`
- PR 描述需列出关联的 `taskNNN`/`issueNNN`、动机、行为变化、验证方式、风险/回滚方案,并在 UI 变化时附截图/GIF。
- 禁止提交密钥、token、证书、真实用户数据涉及权限/配置的任务,需在 `spec_*``tech-refer_*` 清楚描述失败反馈、API 边界与排查方式。
---
## 8. 模板速览
- `spec`: Summary / Goals & Non-goals / User Flows操作→反馈→回退/ Edge Cases / Acceptance Criteria
- `plan`: Milestones / Scope / Priorities / Risks & Dependencies /可选Rollback
- `tech-refer`: Options / Proposed Approach / Interfaces & APIs / Trade-offs / Risks & Mitigations
- `task`: `task001 [ ] 产出 + 验证方式 + 影响范围`
- `issue`: `issueNNN [ ] Summary + Environment + Repro + Expected vs Actual + Investigation + Fix + Verification + User Confirmation + Resolved At/By/Commit`
- `adr`: Context / Decision / Alternatives / Consequences / Rollback
---
## 9. 协作表达提示
- 解释方案时优先描述用户操作(快捷键/鼠标/命令)、可见反馈、撤销/失败路径、边界情况。
- 引用文档时用“文件名 + 小节”口语化说明,不逐字背诵。
- 提供可选方案时说明它们属于当前还是后续里程碑,帮助用户决策。

View File

@ -4,6 +4,197 @@ All notable changes to emacs-kp are documented here. The format
follows [Keep a Changelog](https://keepachangelog.com/), and the
project aims to follow [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Fixed
- Malformed non-nil C backend results no longer fall back to Elisp. Nil whole
results and nil per-item breaks remain soft failures; malformed single or
batch output now signals `ekp-backend-contract-error`, so a broken backend
cannot silently produce a different layout.
- An overwide `ekp-no-break` inline atom no longer collapses its preceding
CJK text into one-glyph emergency lines. The final pass now gives ordinary
underfull candidates finite background emergency stretch and lets normal
badness/fitness/demerits choose the layout. If an overfull candidate would
otherwise extinguish every active path, TeX-style artificial demerits
preserve the best provisional path with zero incremental cost. The rule is
content-independent and identical in the Elisp 1D, looseness/parshape, and
C engines.
- C-backed core layout and complete resize/reflow now reuse prepared
paragraph, DP, and gap geometry and avoid duplicate projection
publication. The four-round same-machine gate records p95 values of
27.687 ms and 27.487 ms respectively, with exact frozen-C/Elisp parity.
- Reprojecting a buffer now preserves the mark marker and `mark-active`
independently. Showcase width keys and resize reflow no longer turn an
old inactive mark into a highlighted region.
- The unfinished live line now keeps its source-edge whitespace natural.
A leading/trailing space or tab appears in the same input turn, and
whitespace exposed by backspace is no longer hidden by `display ""`
until another glyph arrives.
- Enabling or disabling a theme, or changing a frame's default font, now
invalidates live plan history and reflows every active auto-justify
buffer with the new glyph metrics.
- Changing any Knuth-Plass cost parameter now selects a correctly keyed
DP/render result immediately; cached paragraphs no longer require
`ekp-clear-caches`. Structurally equal non-zero-looseness signatures now
reuse their cached result instead of missing an `eql` cons key.
- Changing `ekp-default-cws-stretch-pixel` in automatic spacing mode now
invalidates both paragraph-cache lookup paths immediately. Unchanged
spacing signatures still reuse the cached paragraph.
- Buffer layout no longer rewrites the source character stream. Saving,
region writes, direct Elisp reads, syntax, and search therefore operate
on logical text without a temporary serialization copy.
- Live editing now separates committed projection from a local dirty edit
transaction. Same-row typing performs no whole-hard-line planning,
middle-row edits preserve unaffected break anchors, and exact source
reversal restores the saved projection `equal-including-properties`.
- Point-only motion no longer changes live state or projection anywhere,
including after leaving the active hard paragraph. Structural commits are
owned by visual-row crossing, hard completion, the next real edit
elsewhere, explicit refill, and width/font/layout-context changes.
- Kill/copy integration now composes with and restores an existing
`filter-buffer-substring-function`, including DELETE operations, and
strips only EKP-owned projection properties from copied text. Removing
the final layout span outside auto mode restores the exact prior filter.
- Dictionary syntax no longer degrades silently: files containing
libhyphen replacement/slash patterns fail with an explicit condition
because the fixed-width DP cannot model their conditional rewrites.
- Mutating layout-relevant text properties on an already cached string now
invalidates the same-string paragraph fast path. It reuses the complete
structural cache key instead of a partial parallel signature.
### Changed
- The core now exposes semantic `ekp-layout-plan`, `ekp-layout-line`, and
`ekp-layout-gap` records. The compatible string renderer and the buffer
renderer consume the same break, glue, indentation, and discretionary-
hyphen decisions.
- Buffer/region layout is now a pure text-property projection on existing
source characters: ASCII spaces combine `space-width` and absolute-pixel
`min-width`; zero-source CJK/mixed glue uses `min-width` on a complete
grapheme; indentation uses `line-prefix`; and visual breaks/hyphens use
replacing display strings. EKP creates no overlay and inserts no layout
character into a buffer.
- Auto-justify now uses stable hard-line transactions. A dirty island stays
native between commit events, so ordinary keys neither run DP nor rewrite
unrelated properties. Native visual-row crossing atomically republishes
every completed row from the unchanged whole-hard-line core plan; the new
row remains natural. A buffer-local 16-entry LRU reuses recent
text/context plans, and common-prefix signatures minimize writes at
commits. IME and resize publication remain generation-checked; no
edit-idle or cursor-motion formatter exists.
- Auto-justify now owns its native soft-wrap precondition. It temporarily
disables `truncate-lines` and narrow partial-window truncation, including
Emacs's default sub-50-column behavior, then restores the prior values and
buffer-local ownership on teardown.
- One buffer uses the narrowest displayed window as its authoritative
width. Foreign replacing display owners and unsupported non-ASCII
whitespace shrink keep only the affected hard paragraph natural and are
reported by `ekp-diagnose`.
- Automatic work bounds a single hard paragraph with
`ekp-auto-justify-paragraph-limit` (default 2,048 characters). Longer
paragraphs remain naturally editable; `ekp-refill-paragraph` is the
explicit unbounded quality pass.
- **Breaking:** the editor integration module is now `ekp-buffer.el` and
provides `ekp-buffer`; replace `(require 'ekp-region)` with
`(require 'ekp-buffer)`. Module-owned configuration names now use the
`ekp-buffer-` prefix. Public commands whose names describe an actual
region or buffer operand are unchanged.
- The bundled dictionary inventory is now 49 reproducible entries with a
pinned LibreOffice commit, per-file SHA-256/source/license manifest, and a
portable verifier/exporter. Sanskrit was removed because the pinned
upstream data has no authoritative license statement for that exact
hyphenation file.
- Auto-justify mode now exposes its existing formatting, protection, and
diagnostic commands in an EKP menu. Interactive no-break/verbatim changes
report that their text properties last only for the current buffer
session.
### Tests
- ERT now covers source/tick/undo invariants, exact ASCII and CJK glue
projection, display-only static breaks and hyphens, zero-projection
underfilled input, whole-hard-line semantic-prefix selection, non-frozen
earlier breaks, common-prefix differential writes, plan-cache reuse,
point-motion projection stability, stable dirty transactions, exact
reversible restoration, atomic visual-row commits, hard-paragraph
completion, IME/stale generations, foreign ownership,
narrowest-window policy, inactive/active mark preservation, immediate
edge whitespace, consecutive spaces, newline/yank/real-undo paths, and
the oversized-paragraph guard.
GUI probes verify pixel widths. Reviewed temporal recordings verify
width-key selection and immediate edge-whitespace states, plus stable
middle-row edits, exact reversible restoration, atomic visual-row
commits, zero-work point motion, public yank/undo, resize commits, and
hard-paragraph completion without delayed snap or overlays.
- ERT fixtures now restore every mutable EKP option they isolate. The
parshape C-bypass regression drives the public formatter, and reusable
permuted-order plus per-test fresh-process runners prevent alphabetical
execution from hiding leaked state.
- The GUI fit matrix now exits with status 1 when any row fails. A
batch-safe forced-failure control locks the automation contract while the
real seven-case matrix remains an explicitly loaded developer tool.
- Release invariants now have one local gate. CI action inputs are immutable,
Windows runs the Elisp baseline, and the `.phrase` decision source is
versionable instead of ignored.
### Performance
- Live structural commits now extend prepared paragraph data from the last
complete-word boundary, resume pure-Elisp DP from a safe retained state,
reuse unchanged layout lines, and reconstruct only the buffer's dirty
source island. Three repeated byte-compiled public-command runs at 80
pixels measure append p99 at 1.1581.326 ms for C and 1.4291.438 ms for
pure Elisp; same-row editing and point motion still perform zero planning.
The stricter frozen source-instrumented matrix improves C p99 by 78.37% to
25.785 ms and Elisp p99 by 92.03% to 47.578 ms, but remains openly red
against its locked 16 ms absolute stress target (`issue018`/`task030`).
No stale reuse, debounce, skipped publication, or global GC workaround
hides either result.
- Tokenization now accumulates fragments and joins once per emitted box;
dense hyphen insertion likewise joins original word slices once. On the
1,0008,000-character adversarial benchmark, the 8,000-character cases
improved from 3.133 s to 1.100 s and from 0.945 s to 0.013 s respectively.
- Cached “no hyphen” results now use an explicit miss sentinel, so repeated
lookups do not rerun the dictionary computation.
### Internal
- Cached semantic layout plans now return consumer-owned copies of all
plan-owned mutable payloads: strings, context, boxes, offsets, line/glue/
gap records, and signatures. Layout context snapshots and returned contexts
recursively copy conses, vectors, and strings, so mutable policy inputs
cannot alias the cache key. The paragraph object remains intentionally
shared for stable append identity.
- DP reuse identity, edge-space exclusion, and the lossless marker
vocabulary now each have one directly tested rule owner. The consolidation
removes formula/property-list drift without adding modules or hot-loop
allocations.
- Final cleanup removed nine dead pass-through accessors and replaced direct
use of Emacs's private substring-filter dispatcher with the public
`filter-buffer-substring` path while preserving DELETE lifecycle ownership.
- The paragraph fast path no longer needs style-variable watchers: both the
one-entry path and hash cache have one structural identity owner.
### C module (1.6)
- Signed 32-bit module inputs now use one `extract_integer` validation
instead of calling Lisp `integerp`, `>=`, and `<=` for every value. The
frozen 80-pixel candidate module layer measures 0.697/0.701 ms p95/p99,
down from 2.615/2.655 ms, without changing the ABI or DP output.
- The 15-field single and batch APIs now preflight vector shape, lengths,
scalar types, and signed 32-bit input range before extraction. Caller
errors signal `ekp-c-invalid-input`; allocation/no-result still returns
nil. Enabled-backend signals are no longer swallowed by the Elisp
dispatcher.
- Line metric, flexibility, and remaining-space intermediates are now
64-bit, eliminating signed overflow when valid int32 widths and
protrusions combine.
- The default C build is portable (`PROFILE=portable`). Native CPU/LTO,
debug, and sanitizer flags are explicit profiles, and
`ekp-c-module-build` now passes argv directly to `make` from the module
directory instead of interpolating a shell command.
## [1.0.0] - 2026-07-27
First tagged release. Highlights of the work leading up to it:

View File

@ -11,16 +11,23 @@ EMACS=emacs # or /path/to/Emacs
# Byte-compile with warnings as errors (CI does this)
$EMACS -Q --batch -L . \
--eval '(setq byte-compile-error-on-warn t)' \
-f batch-byte-compile ekp.el ekp-utils.el ekp-hyphen.el ekp-region.el
-f batch-byte-compile ekp.el ekp-utils.el ekp-hyphen.el ekp-buffer.el
# Run the ERT suite (C-module tests auto-skip if not built)
tests/run-tests.sh $EMACS
# Build the C module (required for the parity tests and fuzz)
make -C ekp_c # add DEBUG=1 for ASan/UBSan
make -C ekp_c PROFILE=portable
# Property fuzz: 300 random cases, asserts C and Elisp agree byte-for-byte
$EMACS -Q --batch -L . -l tests/ekp-fuzz.el
# Check release/CI/version invariants
tests/check-release.sh
# Check dictionary inventory and pinned upstream bytes
tests/check-dictionaries.sh
dictionaries/update.sh check
```
## Ground rules
@ -29,21 +36,23 @@ $EMACS -Q --batch -L . -l tests/ekp-fuzz.el
to the demerits or line-metric formulas must touch both
`ekp--dp-run-1d` (Elisp) and `dp_process_position` (`ekp_c/ekp_kp.c`),
and the fuzz suite must stay at 300/300.
- **The layout is lossless.** The renderer's marker properties
(`ekp-glue`, `ekp-soft-break`, `ekp-soft-hyphen`, `ekp-hidden`) must
round-trip exactly through `ekp-unjustify-region`.
- **The two renderers have different rights.** The string renderer's
physical marker vocabulary (`ekp-glue`, `ekp-soft-break`,
`ekp-soft-hyphen`, `ekp-hidden`) stays lossless and compatible. The
buffer renderer must create no overlay or source character and may use
only EKP-owned text properties on existing characters.
- Any C-module API change bumps `EKP_VERSION_MINOR` and the matching
`ekp-c-module-required-version`, and rebuilds the module.
- New behavior needs an ERT test. Buffer-level behavior (save,
isearch, undo, kill/yank, mode interactions) goes in
`tests/ekp-region-tests.el`.
`tests/ekp-buffer-tests.el`.
## Style
- `lexical-binding: t` everywhere; keep byte-compilation warning-free.
- `checkdoc` clean (CI enforces it): imperative docstring first lines,
arguments mentioned in uppercase, two spaces after a sentence.
- `package-lint` clean: the `ekp-` / `ekp-region-` namespaces, proper
- `package-lint` clean: the `ekp-` / `ekp-buffer-` namespaces, proper
autoload cookies on interactive entry points.
- Match the surrounding code; keep comments about *why*, not *what*.
@ -53,6 +62,12 @@ Conventional Commits (`feat:`, `fix:`, `perf:`, `refactor!:`,
`docs:`, `test:`, `chore:`). Explain the reasoning in the body, not
just the change.
## Releases
Follow [Docs/RELEASING.md](Docs/RELEASING.md). In particular, action
dependencies stay pinned to full commit SHAs, released artifacts are
immutable, and every artifact gets a SHA-256 checksum.
## License
By contributing you agree that your contributions are licensed under

View File

@ -4,6 +4,9 @@ This document describes the internal architecture, algorithms and APIs of
`emacs-kp`, as implemented. It is intended for contributors and advanced
users.
Current repository health and prioritized follow-up work are recorded in
the [2026-07-28 system audit](./Docs/REPOSITORY_AUDIT_20260728.md).
## 1. Pipeline Overview
A justification call flows through five stages:
@ -55,17 +58,26 @@ Everything the DP and renderer need, computed once per paragraph:
| `lead-spaces` | `lead-spaces[i]` = width of the space-box run starting at box i; index 0 forced to 0 (first-line indentation is kept) |
| `trail-spaces` | `trail-spaces[k]` = width of the space-box run ending at box k1 |
| `glue-params` | plist snapshot of the nine spacing values at creation time |
| `dp-cache` | hash: line-width → dp-result plist |
| `dp-cache` | equal-keyed hash: complete DP signature → dp-result plist |
The paragraph cache (`ekp--para-cache`) is keyed with `equal` on a
structured key — string content, printed text-property intervals,
detected fonts, the hyphenation language (`ekp-latin-lang`), and
either the nine explicit spacing values or the symbol `auto`.
either the nine explicit spacing values or the automatic CJK stretch
default. The other automatic values are derived from font measurement,
which is already represented by the font and display-context fields.
Structured keys make hash collisions harmless (they were possible with
the previous `sxhash`-integer scheme). The cache is flushed when it
exceeds `ekp-para-cache-limit`. A one-entry fast path
(`ekp--last-para`, checked by string `eq` + language) covers the many
same-string lookups inside one justification call.
(`ekp--last-para`) bypasses only the hash lookup: it compares the same
complete structural key before reuse. In-place changes to layout-relevant
text properties therefore miss both paths and match a fresh paragraph.
The DP signature is separate from the paragraph key. It contains line
width, looseness, and all six runtime cost parameters; therefore a
parameter change selects a new result without flushing width-independent
paragraph data. Structural `equal` comparison also lets non-zero
looseness signatures hit the cache.
### dp-result
@ -88,6 +100,8 @@ computed for ideal, min and max in O(1). Space-box runs at the line
edges are excluded because the renderer strips them; the DP and the
renderer therefore agree exactly, and every justified line renders at
precisely the target width (`ekp-test-justify-line-width-invariant`).
`ekp--line-stripped-space-pixel` owns this exclusion rule for the 1D/loose
DP, C-result reconstruction, and renderer.
## 4. The Knuth-Plass DP
@ -107,10 +121,12 @@ badness = min(10000, 100·|adjustment/flexibility|³)
```
Fitness classes (tight/decent/loose/very-loose) follow the TeX ratio
thresholds. Special cases: single-box lines use flexibility 1 and
fitness decent; the last line pays `(line-penalty + short-badness)²`
where `short-badness = last-line-short-penalty × (1 fill)` when the
fill ratio is below `ekp-last-line-min-ratio`.
thresholds. Special cases: single-box lines use flexibility 1 in the
strict pass; the final pass uses the same finite emergency stretch as
ordinary underfull lines. The last line pays
`(line-penalty + short-badness)²` where
`short-badness = last-line-short-penalty × (1 fill)` when the fill ratio
is below `ekp-last-line-min-ratio`.
Deviations from the 1981 paper, by design: penalties are always added
as `+p²` (no negative/flagged penalties), there is no `q`/looseness in
@ -121,12 +137,22 @@ the main pass (see §6), and adjacent-fitness is a flat constant.
Some inputs admit no valid layout: an unbreakable box wider than the
line, or a rigid (all-`nws`) region that cannot stretch to the target.
A strict pass runs first; if the paragraph end is unreachable, a second
pass additionally allows **emergency breaks** — single-box lines with
demerits `(line-penalty + 10000)² + rest²`, at least as bad as any
regular line. This guarantees, by induction over positions, that every
input produces output (regression: narrow CJK used to return an empty
string), while the common case pays nothing and keeps pure K-P
optimality. Both engines implement the identical strategy.
pass adds a finite background emergency stretch to ordinary underfull
candidates and still scores them through the same adjustment ratio,
badness, fitness, and demerits used by the strict pass. That keeps
underfull final-pass choices inside the global K-P DP instead of forcing
them through a separate fixed-cost path.
Separately, the final pass implements TeX's `artificial_demerits` safeguard.
When an overfull candidate would otherwise remove the last active path to a
breakpoint and no non-overfull candidate survives there, the best provisional
path is installed with tight fitness and zero incremental demerits. This is a
reachability rule, not a hard-atom scoring shortcut: it never competes while a
normal active path survives, and it does not inspect CJK, units, or token
classes. Thus every input still produces a complete plan (regression: narrow
CJK used to return an empty result), while ordinary underfull lines compete
by normal K-P cost. The Elisp 1D, looseness/parshape, and C engines implement
the identical strategy.
## 5. Rendering
@ -139,31 +165,137 @@ optimality. Both engines implement the identical strategy.
- Last lines are ragged-right (ideal glues + trailing filler);
single-box lines get a trailing filler clamped at ≥ 0.
`ekp--pixel-justify` then strips leading space boxes (except on the
first line — indentation) and trailing space boxes, and appends a
hyphen — propertized like the word it breaks — where a line ends at a
hyphenation point. Stripped widths are *not* redistributed: the DP
already excluded them (§3).
`ekp-layout-plan` is the representation boundary between layout and
rendering. It combines the paragraph boxes/source offsets, DP breaks,
per-line glue targets, indentation, stripped edges, and discretionary
hyphen decisions into `ekp-layout-plan`, `ekp-layout-line`, and
`ekp-layout-gap` records. The plan contains no buffer positions or display
mechanism. `ekp-render-layout-string` consumes it for the public string API;
the buffer integration can consume the same decisions without re-running or
reinterpreting the KP algorithm.
Glues become `(space :width (N))` display properties, so justification
is pixel-exact in GUI Emacs and column-exact in batch/tty.
Cached semantic plans are immutable to the cache owner. A cache hit returns
a consumer-owned copy of every plan-owned mutable payload: source string,
context tree, box vector and box strings, source offsets, line records,
line glues, gap records, and line signatures. `ekp-layout-plan-para` is the
intentional exception: paragraph-cache ownership predates the semantic-plan
cache, and append planning depends on stable paragraph identity.
Layout context snapshots and returned plan contexts use the same recursive
copier for conses, vectors, and strings, so mutable policy inputs cannot alias
the cache key or a later consumer plan.
The output is **lossless**: boxes are located in the source string
(`ekp--box-offsets`), and every synthesized or hidden piece records the
original text it stands for —
The two consumers deliberately have different representation rights.
| property | on | value / meaning |
|-------------------|-----------------------|--------------------------------|
| `ekp-glue` | synthesized glue space| original text it replaced |
| `ekp-soft-break` | inserted `\n` | whitespace swallowed at break |
| `ekp-soft-hyphen` | inserted hyphen | marker only |
| `ekp-hidden` | paragraph-edge text | kept verbatim, `display ""` |
#### String renderer
Zero-width glue with a non-empty original renders as the hidden
original itself, so no character is ever dropped. `ekp-region.el`
inverts these four structurally (`ekp-unjustify-region`) — exact even
after the justified text was edited — and builds
`ekp-justify-region` / `ekp-auto-justify-mode` on top.
`ekp-render-layout-string` preserves the public string API. It strips
leading/trailing space boxes, synthesizes display spaces and visual
newlines, and appends a propertized discretionary hyphen where selected.
The returned string remains lossless through four private markers:
| property | on | value / meaning |
|-------------------|------------------------|-------------------------------|
| `ekp-glue` | synthesized glue space | original text it replaced |
| `ekp-soft-break` | synthesized `\n` | swallowed boundary whitespace |
| `ekp-soft-hyphen` | synthesized hyphen | marker only |
| `ekp-hidden` | paragraph-edge text | source retained, display empty|
This physical representation exists only in the returned string. It is
kept for API compatibility and is not installed into a source buffer.
`ekp--layout-marker-properties` owns its marker vocabulary and
non-inheritance contract.
#### Buffer renderer
`ekp-buffer.el` keeps the buffer's character sequence untouched and
projects the same plan with text properties on existing source characters:
- source ASCII spaces:
`((space-width FACTOR) (min-width ((TARGET-PIXELS))))`;
- zero-source CJK/mixed glue: `min-width` on the preceding complete
grapheme, targeting its natural advance plus glue;
- indentation: `line-prefix`;
- a source-whitespace break: the first boundary character displays as
newline and the rest as empty;
- a CJK or discretionary-hyphen break: a replacing display string
reproduces the existing complete grapheme, appends the optional hyphen,
then a visual newline.
`ekp-buffer--display` and `ekp-buffer--line-prefix` record exact ownership.
Removal clears the public property only when its value is still identical
to EKP's owner value, so a later foreign change is not erased. Paragraphs
with foreign `display`, `line-prefix`, `wrap-prefix`, `composition`, or
`invisible` ownership stay verbatim. Exact shrink of tabs/non-ASCII
whitespace is also refused because `space-width` affects ASCII spaces only.
All installation/removal runs inside `with-silent-modifications`, and owned
properties are nonsticky. Buffer characters, point/mark, modified state,
undo, character-modified tick, and external change hooks therefore remain
source-owned. Reprojection restores the mark marker without calling
`set-mark`, then restores `mark-active` independently; an inactive mark
cannot become a region as a layout side effect. No EKP buffer path creates
an overlay.
Saving, ordinary search, syntax, and direct Elisp character APIs need no
logical-text adapter: the real buffer is already logical. Copy filtering
remains necessary because `buffer-substring` intentionally preserves text
properties. EKP composes with the previous
`filter-buffer-substring-function`, then removes only its projection
metadata from the copied string.
#### Live flow
Live editing uses the ordinary whole-text Knuth-Plass plan without giving
editing state to the core planner:
1. Live state owns the last committed hard-line source, normal
`ekp-layout-plan`, semantic signatures, projected spans, and stable break
anchors at the narrowest-window authoritative width.
2. The first real change opens one edit transaction. It snapshots that
committed state, then removes EKP properties only from the smallest
projected span range containing the edit. Span objects and unaffected
anchors remain registered.
3. Further changes inside the dirty island use native soft wrapping and do
no whole-hard-line planning. If the logical source returns exactly to the
snapshot, EKP restores the saved owned-property runs and marker offsets
directly; the state, plan, signatures, and spans retain object identity.
4. Crossing a native visual-row boundary is a commit. EKP computes or reuses
one whole-hard-line plan, derives every completed semantic row, and
publishes their changed suffix silently as one command-loop transition.
The new current row remains natural.
5. The other commits are a hard newline/paragraph completion, the next real
edit outside the dirty island, explicit refill, and width/font/face/theme
or layout-context change. Point motion is never a commit, even across hard
paragraphs; there is no live `post-command-hook`.
6. Stable line signatures minimize property writes at a commit, while the
16-entry buffer-local LRU reuses recent text/context plans. Neither
mechanism decides when layout is allowed to change; the transaction owns
that policy.
7. IME preedit, foreign display ownership, unsupported shrink, oversized
hard lines, stale generations, or publication errors fail closed to
native display. A partial projection is rolled back and the original
error is surfaced.
Native wrapping is a state-machine precondition, not a user preference the
mode can merely hope is enabled. On activation, the mode snapshots the
values and local-binding ownership of `truncate-lines` and
`truncate-partial-width-windows`, then makes both buffer-local and nil.
Teardown restores local values or removes the temporary bindings so global
ownership resumes. This prevents Emacs's default 50-column partial-window
threshold from silently turning a narrow split into horizontal scrolling.
This state model needs no live lookahead, push/pull convergence, per-key
whole-line planner, or idle formatter. A real edit after point motion may
commit the previous active hard line, including when narrowing makes that
line inaccessible; point motion itself remains a strict no-op.
No edit-idle whole-paragraph formatter exists. Resize/background work is
generation-owned. Large buffers are processed visible-first in hard-
paragraph chunks; a single hard paragraph above
`ekp-auto-justify-paragraph-limit` remains natural during automatic work
and requires explicit `ekp-refill-paragraph` for an unbounded quality pass.
Because text properties are buffer-wide, the narrowest live window supplies
the one authoritative width.
### 5.1 Break permissions, alignment, protrusion, shapes
@ -171,9 +303,33 @@ after the justified text was edited — and builds
own box; `ekp-para-breaks-allowed` forbids gaps per kinsoku (full-
and halfwidth), `ekp-no-break' spans and NBSP-family joiners.
Forbidden gaps carry no glue. The DP skips them as candidates while
the line keeps extending; the emergency fallback treats a run with
no permitted inner break as atomic. C receives the sparse
`forbidden-positions` vector.
the line keeps extending. In the final pass, legal underfull candidates
receive finite emergency stretch and are scored by normal
badness/demerits. If an overfull candidate would extinguish the last active
final-pass path, TeX-style artificial demerits preserve that path with zero
incremental cost. C receives the sparse `forbidden-positions` vector.
Explicit hard atoms forbid only breaks inside their interval; an otherwise
legal boundary immediately before or after an atom remains legal. Atom
adjacency therefore receives no special scoring or break prohibition.
- **Configurable policy compilation**: buffer and core policy variables are
resolved before tokenization into private structural intervals. Region
`ekp-break-policy` wins first, explicit buffer/file/dir locals win over
mode profiles, and profiles win over global defaults. Token policies are
the only merged category map; scalar and face-list options replace the
lower scope. The core may use private properties such as
`ekp--face-break-policy`, `ekp--no-hyphen`, and
`ekp--literal-spacing` on an analysis copy, but cache keys, plan strings,
boxes, rendered strings, and `ekp--last-para` must contain only public
source properties. Automatic face no-break is measured over the
contiguous private face-policy span and downgrades to no-hyphen when
overwide; explicit `ekp-no-break` never downgrades.
- **Literal inline spaces**: face-derived `no-hyphen` preserves source
spaces as literal boxes inside a line, forbids a break that would move a
literal source-space box to line start, and permits the complementary
break after a source-space box. When such whitespace is the selected
visual break, source ownership belongs to the break gap metadata, not to a
trailing visible line box. This rule is policy-derived and must not depend
on whether the source also carries a public `face` property.
- **Alignment** (`ekp-alignment`): non-justify modes zero the glue
stretch/shrink arrays and class params; the DP widens `max_w` by an
extra per-line stretch R (`ekp-c-set-penalties` arg 7), so badness =
@ -193,10 +349,12 @@ after the justified text was edited — and builds
(position × line-count) DP and bypass C. Indents render as leading
`ekp-glue' spacers.
C module 1.5: `ekp-c-break-with-arrays' takes 15 args
C module 1.6: `ekp-c-break-with-arrays' takes 15 args
(…, forbidden-positions, tail-protrudes, hyphen-protrude,
first-line-width); batch vectors have 15 elements;
`ekp-c-set-penalties' takes 47.
`ekp-c-set-penalties' takes 48. Policy compilation feeds the existing
hyphen-position and forbidden-break vectors; it must not add a sixteenth C
argument or batch field without a new architecture decision.
Performance after the feature wave (byte-compiled + C, Apple
Silicon, batch): justify zh w=200 ≈ 54 ms, range zh ≈ 117 ms —
@ -208,6 +366,13 @@ reality check (60-paragraph, 26 k-char article, region layer
included): ≈ 73 ms per width change cold, less on revisit;
incremental single-paragraph re-justify after an edit ≈ 17 ms.
Adversarial source-mode builders are measured separately by
`ekp-bench-adversarial-builders`. From 1,000 to 8,000 characters, the
fragment-based tokenizer grew 6.3× and dense insertion 7.7× (near the
expected linear 8× input growth). At 8,000 characters they took 1.100 s and
0.013 s, versus 3.133 s and 0.945 s before the change. The hyphen-position
cache uses an explicit miss sentinel so a legitimate nil result is reusable.
## 6. Looseness
`ekp-looseness` ≠ 0 switches to `ekp--dp-run-loose`, a full
@ -219,7 +384,7 @@ engines never disagree.
## 7. C Module Integration
The C module (`ekp_c/`, version 1.5) runs only stage ④. Elisp remains
The C module (`ekp_c/`, version 1.6) runs only stage ④. Elisp remains
the source of truth for all font-dependent data.
- `ekp-c-break-with-arrays` (15 args): the para's prefix arrays, glue
@ -232,19 +397,24 @@ the source of truth for all font-dependent data.
pool is created lazily on the first multi-paragraph batch and sized
to the machine's cores; a full queue blocks the submitter rather
than dropping the task.
- `ekp-c-set-penalties` (47 args): called by `ekp--c-sync-params`
- `ekp-c-set-penalties` (48 args): called by `ekp--c-sync-params`
before *every* C entry, so `ekp-line-penalty` & friends always take
effect (regression: they were never synced before).
- `ekp-c-module-load` refuses modules older than
`ekp-c-module-required-version` and falls back to Elisp, preventing
arity mismatches after upgrades.
Any C failure — a NULL result, an allocation failure, or a bad
argument — falls back to the Elisp engine; the Elisp bridge also
wraps the calls in `condition-case`. The module never silently
produces a different layout on partial failure. The two engines are
verified byte-identical by `ekp-test-c-parity-simple` /
`ekp-test-c-parity-files` and the 300-case property fuzz.
An unavailable module, an incompatible module version, a nil whole C result,
or a nil per-item/break result falls back to the Elisp engine. Invalid
direct API input signals `ekp-c-invalid-input`. Any non-nil malformed
backend result signals `ekp-backend-contract-error`: malformed cons shape,
non-list breaks, non-integer/out-of-range/non-increasing/partial breaks,
nonnumeric cost, or malformed batch result shape. Any signal from an
enabled backend propagates through the public formatter; the dispatcher does
not catch and hide it. The module never silently produces a different
layout on partial failure. The two engines are verified byte-identical by
`ekp-test-c-parity-simple` / `ekp-test-c-parity-files` and the 300-case
property fuzz.
### Future direction: a paragraph-handle API
@ -267,7 +437,7 @@ become a bottleneck.
## 8. Hyphenation (ekp-hyphen.el)
Liang's pattern algorithm, Pyphen-compatible:
Liang's ordinary pattern algorithm:
- `dictionaries/hyph_*.dic` are compiled to a pattern hash on first
use and cached per path. Files may be UTF-8 or ISO-8859 (Emacs
@ -275,6 +445,16 @@ Liang's pattern algorithm, Pyphen-compatible:
- `ekp-hyphen-create LANG` resolves exact codes, then progressively
shorter prefixes (`"de_CH" → "de"`).
- Margins default to 2 characters on each side of a break.
- A slash in a non-comment pattern is fail-closed. Libhyphen replacement
rules change the visible text and width only when their break wins; EKP's
current fixed-width boxes cannot represent that honestly. The compiler
counts the rules and signals `ekp-hyphen-unsupported-pattern`, and the
public formatter propagates it.
- `dictionaries/MANIFEST.tsv` pins 49 inventory entries to one LibreOffice
commit (plus one explicitly identified legacy Basque byte), their SHA-256,
syntax flag, and license evidence. `tests/check-dictionaries.sh` is the
offline gate; `dictionaries/update.sh check` verifies normalized bytes
against the upstream commit on macOS and Linux.
Word boxes are matched against
`^[left-punct]* (latin-word) [right-punct]*$` so that punctuation-
@ -284,12 +464,34 @@ punctuation stays glued to the first/last syllable box.
## 9. Testing & Benchmarks
```bash
tests/run-tests.sh [emacs] # 36 ERT tests, batch-safe
tests/run-tests.sh [emacs] # batch-safe ERT suite
tests/run-tests.sh [emacs] --random-order
tests/run-tests-isolated.sh [emacs] # each ERT in a fresh process
tests/check-dictionaries.sh # offline inventory/checksum gate
dictionaries/update.sh check # verify pinned upstream bytes
make -C ekp_c PROFILE=portable # release-portable default
make -C ekp_c PROFILE=native # local benchmark only
make -C ekp_c PROFILE=debug # symbols, no optimization
make -C ekp_c PROFILE=sanitize # ASan + UBSan
emacs -Q --batch -L . --eval '(setq ekp-use-c-module nil)' -l tests/ekp-bench.el
emacs -Q --batch -L . --eval '(progn (require (quote ekp)) (ekp-c-module-load))' \
-l tests/ekp-bench.el
```
Set `EKP_TEST_SEED` to reproduce or vary the permuted-order run. Test
fixtures dynamically restore all EKP configuration they isolate; tests that
exercise dispatch must use the public formatter rather than only an internal
eligibility predicate.
The GUI matrix is loaded explicitly from `tests/ekp-gui-verify.el`. It
returns status 1 after printing the table when any row fails; the ERT suite
contains a forced-failure control for this boundary.
`M-x ekp-c-module-build` uses the same four profile names and invokes make
as an argv process in `ekp_c/`; it never constructs a shell `cd` command.
Release/CI artifacts use `portable`. Use `native` only for measurements on
the machine that will run the module.
Key invariants under test: rendered line width == target (pixel-exact
justification), no content loss at any width, brute-force cross-checks
of the O(1) prefix machinery, Elisp/C parity on the bundled texts, and
@ -327,11 +529,11 @@ ekp.el Core: para struct, caching, DP (1D + looseness),
ekp-utils.el Tokenizer (boxes, kinsoku), font detection with
batch/tty fallbacks, C module loading
ekp-hyphen.el Liang hyphenation + dictionary registry
ekp-region.el Buffer/region commands, ekp-auto-justify-mode, and
editor integration (save, isearch, kill-ring, undo)
ekp-buffer.el Text-property-only buffer/region projection, synchronous
live flow, window lifecycle, copy filtering, diagnostics
ekp_c/ C dynamic module (see ekp_c/README.md)
dictionaries/ Hunspell hyphenation patterns (from LibreOffice)
tests/ ekp-tests.el, ekp-region-tests.el (ERT),
tests/ ekp-tests.el, ekp-buffer-tests.el (ERT),
ekp-fuzz.el (parity fuzz), ekp-bench.el,
ekp-demo.el, ekp-showcase.el, sample texts,
run-tests.sh

View File

@ -2,6 +2,9 @@
本文档描述 `emacs-kp` 的实际内部架构、算法与 API,面向贡献者和高级用户。
当前仓库健康度与后续工作的优先级见
[2026-07-28 系统审计](./Docs/REPOSITORY_AUDIT_20260728.md)。
## 1. 处理管线
一次排版调用经过五个阶段:
@ -52,14 +55,20 @@ DP 和渲染需要的一切,每段只算一次:
| `lead-spaces` | `lead-spaces[i]` = 从盒 i 开始的连续空格盒总宽;下标 0 强制为 0(首行缩进保留) |
| `trail-spaces` | `trail-spaces[k]` = 到盒 k1 结束的连续空格盒总宽 |
| `glue-params` | 创建时九个间距值的 plist 快照 |
| `dp-cache` | 哈希:行宽 → dp-result plist |
| `dp-cache` | `equal` 比较的哈希:完整 DP 签名 → dp-result plist |
段落缓存(`ekp--para-cache`)以 `equal` 比较结构化 key——字符串内容、
文本属性区间的打印形式、检测字体、断词语言(`ekp-latin-lang`)、九个
显式间距值(或符号 `auto`)。结构化 key 使哈希碰撞无害(旧的 `sxhash`
整数方案理论上可能串段)。超过 `ekp-para-cache-limit` 时整体清空。
单条快路径(`ekp--last-para`,按字符串 `eq` + 语言校验)覆盖同一次
排版内的大量同字符串查询。
显式间距值或自动 CJK stretch 默认值。其他自动值由字体测量派生,已
由字体与显示上下文字段表达。结构化 key 使哈希碰撞无害(旧的
`sxhash` 整数方案理论上可能串段)。超过 `ekp-para-cache-limit`
整体清空。单条快路径(`ekp--last-para`)只绕过哈希 lookup,复用前仍
比较同一个完整结构 key。因此排版相关 text property 的原地修改会让
两条路径都 miss,并得到与 fresh paragraph 相同的结果。
DP 签名独立于段落 key,包含行宽、looseness 与全部六个运行时代价参数。
参数变化会选择新结果,无需丢弃与宽度无关的段落数据;结构化 `equal`
比较也让非零 looseness 签名能够正常命中缓存。
### dp-result
@ -80,6 +89,8 @@ width = raw space-w (若盒 k1 处断词,再加连字符宽)
理想/最小/最大三个值均 O(1) 得出。行边缘的空格盒串被排除,因为渲染层
会剥离它们;DP 与渲染层因此严格一致,每一行的渲染宽度精确等于目标宽
(测试 `ekp-test-justify-line-width-invariant`)。
`ekp--line-stripped-space-pixel` 统一拥有 1D/loose DP、C 结果重建与
渲染层使用的这一排除规则。
## 4. Knuth-Plass 动态规划
@ -98,7 +109,8 @@ badness = min(10000, 100·|adjustment/flexibility|³)
```
松紧等级(tight/decent/loose/very-loose)沿用 TeX 的比例阈值。特殊情
况:单盒行 flexibility 固定为 1、fitness 为 decent;末行代价为
况:严格遍的单盒行 flexibility 固定为 1;最终遍使用与普通欠宽行相同
的有限 emergency stretch。末行代价为
`(line-penalty + 短行badness)²`,填充率低于 `ekp-last-line-min-ratio`
`短行badness = last-line-short-penalty × (1 填充率)`
@ -110,10 +122,17 @@ penalty/flagged 断点),主流程无 `q`/looseness(见 §6),相邻松紧惩
某些输入不存在合法排版:比行宽更宽的不可断盒子,或无法伸展到目标宽
的刚性(全 `nws`)区段。先跑严格遍;若段尾不可达,第二遍额外允许
**紧急断行**——demerits 为 `(line-penalty + 10000)² + rest²` 的单盒行,
不低于任何常规行的代价。由位置归纳可证:任何输入必有输出(回归:窄
栏 CJK 曾整段返回空串),常规输入不付任何代价、保持纯 K-P 最优。两个
引擎实现完全相同的策略。
为普通欠宽候选加入有限 background emergency stretch,并继续走同一套
adjustment ratio、badness、fitness 和 demerits。这样最终遍的欠宽选择
仍由全局 K-P DP 比较,不会被塞进另一条固定代价路径。
另外,最终遍实现 TeX 的 `artificial_demerits` 可达性保护。如果某个
超宽候选将使一个断点的最后活动路径消失,且该断点没有任何未超宽候选
存活,就以 tight fitness 和零增量 demerits 安装最佳暂存路径。这不是
hard atom 专用的评分捷径:只要正常活动路径还在,它就不参与竞争,也不
检查中文、单位或 token 类别。因此任何输入仍会得到覆盖完整源文本的
plan(回归:窄栏 CJK 曾整段返回空结果),而普通欠宽行仍按正常 K-P 代价
竞争。Elisp 1D、looseness/parshape 与 C 引擎实现相同语义。
## 5. 渲染
@ -125,35 +144,142 @@ penalty/flagged 断点),主流程无 `q`/looseness(见 §6),相邻松紧惩
≥ 0。
- 末行右侧不齐(理想 glue + 尾部填充);单盒行的尾部填充钳制 ≥ 0。
`ekp--pixel-justify` 随后剥离行首空格盒(首行除外——缩进)与行尾空格
盒,在断词处附加连字符(继承所断单词的文本属性)。剥离的宽度**不再**
重新分配:DP 已经排除了它们(§3)。
`ekp-layout-plan` 是布局决策与渲染方式之间的表示边界。它把段落盒子
及其原文 offset、DP 断点、逐行 glue 目标、缩进、边缘剥离和可选断词
决策组合成 `ekp-layout-plan`、`ekp-layout-line` 和 `ekp-layout-gap`
记录。plan 不包含 buffer 位置或具体显示机制。
`ekp-render-layout-string` 用它服务现有字符串 API;buffer 集成可复用
完全相同的决策,无需重新运行或重新解释 KP 算法。
Glue 渲染为 `(space :width (N))` display 属性,GUI 下像素级精确,
batch/tty 下按字符列精确。
缓存的 semantic plan 对缓存 owner 不可变。缓存命中会返回 consumer
自有副本,覆盖所有 plan 自有的可变 payload:源字符串、context 树、盒
向量与盒字符串、源 offset、line 记录、line glue、gap 记录和 line
signature。`ekp-layout-plan-para` 是有意例外:段落缓存所有权早于
semantic-plan cache,append planning 依赖稳定的段落身份。
layout context snapshot 与返回 plan context 使用同一个递归 copier,
覆盖 cons、vector 与 string,因此可变 policy 输入不会别名到 cache key
或后续 consumer plan。
渲染输出是**无损**的:先用 `ekp--box-offsets` 在原串中定位每个盒子,
然后每一处合成/隐藏内容都记录它所对应的原文——
两个消费方拥有不同且明确的表示权限。
| 属性 | 位置 | 值 / 含义 |
|-------------------|-----------------|----------------------------|
| `ekp-glue` | 合成的 glue 空格| 它所替换的原文 |
| `ekp-soft-break` | 插入的 `\n` | 断点处被吞掉的空白 |
| `ekp-soft-hyphen` | 插入的连字符 | 仅作标记 |
| `ekp-hidden` | 段落边缘文本 | 原样保留,`display ""` 隐藏|
#### 字符串渲染器
零宽 glue 若对应非空原文,直接渲染为隐藏的原文本身,因此任何字符都
不会丢失。`ekp-region.el` 对这四类标记做纯结构逆变换
(`ekp-unjustify-region`)——即使排版后又被编辑过也能精确还原——并在
其上实现 `ekp-justify-region` / `ekp-auto-justify-mode`
`ekp-render-layout-string` 保持公开字符串 API 兼容。它剥离首尾空格
盒、合成 display 空格与视觉换行,并在选中断词点附加继承样式的连字符。
返回字符串通过四类私有标记保持无损:
| 属性 | 位置 | 值 / 含义 |
|-------------------|------------------|---------------------------|
| `ekp-glue` | 合成的 glue 空格 | 它所替换的原文 |
| `ekp-soft-break` | 合成的 `\n` | 断点处吞掉的边界空白 |
| `ekp-soft-hyphen` | 合成的连字符 | 仅作标记 |
| `ekp-hidden` | 段落边缘文本 | 保留源文本、显示为空 |
这份物理表示只存在于返回字符串中,用于兼容既有 API,绝不会安装到源
buffer。`ekp--layout-marker-properties` 统一拥有其标记词汇表与不继承
契约。
#### Buffer 渲染器
`ekp-buffer.el` 保持 buffer 字符序列不变,只在现有源字符上用文本属性
投影同一份 plan:
- 源 ASCII 空格:
`((space-width FACTOR) (min-width ((TARGET-PIXELS))))`;
- 无源空格的 CJK/混排 glue:把 `min-width` 加在前一个完整字素上,
目标为其自然 advance 加 glue;
- 缩进:`line-prefix`;
- 源空白断点:第一个边界字符显示成换行,其余显示为空;
- CJK 或拉丁断词断点:replacing display string 重现已有完整字素,
接上可选连字符和视觉换行。
`ekp-buffer--display``ekp-buffer--line-prefix` 记录精确 owner。
移除时只有公开属性的值仍与 EKP owner 值相同时才清除,因此后来的外部
修改不会被误删。带外部 `display`、`line-prefix`、`wrap-prefix`、
`composition``invisible` owner 的段落保持 verbatim。若精确排版
要求缩窄 tab/非 ASCII 空白也会拒绝,因为 `space-width` 只影响 ASCII
空格。
所有安装/移除都在 `with-silent-modifications` 内完成,自有属性设为
nonsticky。因此 buffer 字符、point/mark、modified 状态、undo、字符
修改 tick 与外部 change hook 仍完全由源编辑拥有。任何 EKP buffer
路径都不会创建 overlay。重投影直接恢复 mark marker,不会调用会激活
选区的 `set-mark`,随后独立恢复 `mark-active`;因此布局不能把旧 mark
变成选区。
保存、普通搜索、语法和直接 Elisp 字符 API 不再需要逻辑文本 adapter:
真实 buffer 本来就是逻辑文本。复制过滤仍有必要,因为
`buffer-substring` 按设计保留文本属性。EKP 先组合原有
`filter-buffer-substring-function`,再只移除自己在复制字符串中的投影
元数据。
#### 实时流式排版
实时编辑直接消费正常的整段 Knuth-Plass plan,不会把编辑状态交给
核心规划器:
1. live state 按最窄窗口权威宽度持有最后一次提交的硬行源文本、正常
`ekp-layout-plan`、语义签名、投影 span 与稳定断行锚点。
2. 第一次真实变更打开一个编辑事务。事务先快照提交态,再只移除包含该
编辑的最小投影 span 范围中的 EKP 属性;span 对象与无关锚点仍保持
注册。
3. 脏岛内的后续变更交给原生软折行,不规划整条硬行。若逻辑源文本精确
回到快照,EKP 直接恢复保存的自有属性区间与 marker 偏移;state、plan、
signatures 和 spans 都保持对象身份。
4. 跨过原生视觉行边界才提交。EKP 计算或复用一次整条硬行 plan,得到
全部已完成语义行,并在同一个 command-loop 转换中静默发布真正变化的
后缀;新的当前行保持自然。
5. 其他提交事件只有硬换行/段落完成、下一次真实编辑发生在脏岛之外、
显式 refill,以及宽度/字体/face/主题或布局上下文变化。point 移动永远
不是提交,即使跨越硬段落也一样;live 路径没有 `post-command-hook`
6. 稳定行签名只负责在提交时减少属性写入,buffer-local 16 项 LRU 只负责
复用近期文本/上下文 plan。何时允许布局变化由事务控制,不是二者控制。
7. IME preedit、外部显示所有权、不支持的收缩、超长硬行、过期
generation 或发布错误都会 fail closed 到原生显示。半成品投影会
回滚,原始错误继续向外报告。
原生软折行是状态机的前提,不能只希望用户碰巧开启它。mode 启用时先
保存 `truncate-lines``truncate-partial-width-windows` 的值与
buffer-local 所有权,再把二者设为 buffer-local `nil`;teardown 时恢复
原局部值,或移除临时局部绑定让全局值重新接管。这样 Emacs 默认的
50 列分栏阈值就不会悄悄把窄分栏变成横向滚动。
这个状态模型不需要 live lookahead、push/pull 收敛、逐键整行规划器或
idle formatter。point 移动本身严格零副作用;之后若在别处发生真实编辑,
则允许提交先前活动硬行,即使 narrowing 让该行已不在可访问区内。
不存在编辑空闲后整段 formatter。resize/后台工作都带 generation。
大 buffer 按可见优先的硬段落 chunk 处理;单个硬段落超过
`ekp-auto-justify-paragraph-limit` 时,自动路径保持自然显示,只有显式
`ekp-refill-paragraph` 才执行无界完整质量 pass。文本属性属于 buffer,
所以以最窄活动窗口作为唯一权威宽度。
### 5.1 断行许可、对齐、悬挂、段形
- **断行许可**:每个 CJK 字符(含标点)独立成盒;
`ekp-para-breaks-allowed` 按禁则(全角与半角)、`ekp-no-break`
区间及 NBSP 族连接符禁止相应间隙,被禁间隙不携带 glue。DP 跳过
被禁候选但继续延伸行;紧急兜底把"内部无许可断点的连跑段"视为
原子。C 侧接收稀疏 `forbidden-positions` 向量。
被禁候选但继续延伸行。最终遍中的合法欠宽候选获得有限 emergency
stretch 并按正常 badness/demerits 评分;若超宽候选将消灭最后活动
路径,TeX 风格 artificial demerits 以零增量代价保留该路径。C 侧接收
稀疏 `forbidden-positions` 向量。显式 hard atom 只禁止其区间内部
断行;atom 前后本来合法的边界仍然合法。atom 邻接不获得特殊评分,
也不会额外禁止断点。
- **可配置 policy 编译**:buffer 与 core policy 变量在 tokenization
之前解析为私有结构区间。区域 `ekp-break-policy` 优先,显式
buffer/file/dir local 值优先于 mode profile,profile 优先于全局默认。
只有 token policy 是按类别合并的 map;标量与 face 列表都直接替换
低优先级值。core 可以在 analysis copy 上使用
`ekp--face-break-policy`、`ekp--no-hyphen`、`ekp--literal-spacing`
等私有属性,但 cache key、plan string、box、rendered string 与
`ekp--last-para` 只能保留公开源属性。自动 face no-break 按连续的
私有 face-policy 区间整体测宽,超宽时降级为 no-hyphen;显式
`ekp-no-break` 永不降级。
- **行内字面空格**:face 派生的 `no-hyphen` 在行内保留源空格为字面
box,禁止会把源空格 box 移到行首的断点,并允许互补的"空格后到内容"
断点。当这些空白成为选中的视觉断点时,源归属属于 break gap metadata,
而不是尾随可见行 box。该规则来自 policy,不能依赖源上是否另有公开
`face` 属性。
- **对齐**(`ekp-alignment`):非两端对齐把 glue 伸缩数组与类参数
置零,DP 给 `max_w` 加每行额外伸展 R(`ekp-c-set-penalties` 第 7
参),badness = 100·(欠宽/R)³;渲染层按模式分派剩余(尾部/对半/
@ -169,9 +295,10 @@ batch/tty 下按字符列精确。
`ekp-parshape``ekp-looseness` 才需要(位置×行数)DP 并旁路 C。
缩进渲染为行首 `ekp-glue` 垫片。
C 模块 1.5:`ekp-c-break-with-arrays` 15 参(…、forbidden-positions、
C 模块 1.6:`ekp-c-break-with-arrays` 15 参(…、forbidden-positions、
tail-protrudes、hyphen-protrude、first-line-width);batch 向量 15 元;
`ekp-c-set-penalties` 47 参。
`ekp-c-set-penalties` 48 参。policy 编译只进入现有断词位置与禁断
向量;没有新的架构决策时,不得增加第 16 个 C 参数或 batch 字段。
特性完成后的性能(字节编译 + C,Apple Silicon,batch):justify zh
w=200 ≈ 54 ms、range zh ≈ 117 ms——justify 与特性前持平,range 因盒
@ -180,6 +307,12 @@ w=200 ≈ 54 ms、range zh ≈ 117 ms——justify 与特性前持平,range 因
续变宽实测(60 段 2.6 万字文章,含 region 层全链路):每次变宽约
73 ms,重访宽度更快;编辑后单段增量重排约 17 ms。
`ekp-bench-adversarial-builders` 单独测量源码解释模式下的退化
builder。输入从 1,000 增到 8,000 字符时,基于片段的 tokenizer 增长
6.3×,密集插入增长 7.7×,接近输入 8× 的线性增长。8,000 字符分别从
3.133 s 降到 1.100 s、从 0.945 s 降到 0.013 s。断词位置缓存使用
显式 miss sentinel,因此合法的 nil 结果也能复用。
## 6. Looseness
`ekp-looseness` ≠ 0 时切换到 `ekp--dp-run-loose`:完整的
@ -190,7 +323,7 @@ w=200 ≈ 54 ms、range zh ≈ 117 ms——justify 与特性前持平,range 因
## 7. C 模块集成
C 模块(`ekp_c/`,版本 1.5)只执行阶段 ④。所有字体相关数据以 Elisp
C 模块(`ekp_c/`,版本 1.6)只执行阶段 ④。所有字体相关数据以 Elisp
为唯一事实来源。
- `ekp-c-break-with-arrays`(15 参数):para 的前缀数组、glue 数组、
@ -200,15 +333,20 @@ C 模块(`ekp_c/`,版本 1.5)只执行阶段 ④。所有字体相关数据以 E
——每段一个任务(这是正确的并行粒度;DP 本身天然串行)。线程池在
首次多段落 batch 时惰性创建,按机器核心数定大小;队列满时提交方
阻塞等待而非丢弃任务。
- `ekp-c-set-penalties`(47 参数):`ekp--c-sync-params` 在**每次**
- `ekp-c-set-penalties`(48 参数):`ekp--c-sync-params` 在**每次**
进入 C 之前调用,保证 `ekp-line-penalty` 等变量始终生效(回归:此
前从未同步)。
- `ekp-c-module-load` 拒绝低于 `ekp-c-module-required-version` 的模块
并回落到 Elisp,避免升级后的参数数量不匹配。
C 端任何失败——NULL 结果、分配失败或非法参数——都回落到 Elisp 引擎
(Elisp 桥接层也用 `condition-case` 兜住);模块不会在部分失败时静默
产出不同的排版。两引擎输出逐字节一致,由
模块不可用、ABI 版本不兼容、整批 C 结果为 nil 或单项/断点结果为
nil 时回落到 Elisp。直接 API 的非法输入 signal
`ekp-c-invalid-input`。任何非 nil 的畸形后端结果 signal
`ekp-backend-contract-error`:结果 cons 形状错误、breaks 不是 list、
break 非整数/越界/非递增/未覆盖段尾、cost 非数字,或 batch 结果形状
错误。已启用后端发出的任何 signal 都会穿过公共 formatter,dispatcher
不捕获或隐藏。模块不会在部分失败时静默产出不同的排版。两引擎输出
逐字节一致,由
`ekp-test-c-parity-simple` / `ekp-test-c-parity-files` 及 300 例性质
fuzz 验证。
@ -229,13 +367,21 @@ fuzz 验证。
## 8. 断词(ekp-hyphen.el)
Liang 模式算法,兼容 Pyphen:
普通 Liang 模式算法:
- `dictionaries/hyph_*.dic` 首次使用时编译为模式哈希并按路径缓存。
文件可为 UTF-8 或 ISO-8859(Emacs 自动检测;由
`ekp-test-hyphen-de-iso8859-dict` 验证)。
- `ekp-hyphen-create LANG` 先精确匹配,再逐级缩短(`"de_CH" → "de"`)。
- 断点两侧默认至少保留 2 个字符。
- 非注释 pattern 出现斜杠时失败关闭。libhyphen 替换规则只在断点
胜出时改变可见文字与宽度,当前固定宽度 box 无法诚实表达;编译器
计数后 signal `ekp-hyphen-unsupported-pattern`,公共排版入口保留该
错误。
- `dictionaries/MANIFEST.tsv` 将 49 个条目固定到 LibreOffice 提交
(另明确标记一个 legacy Basque 字节),记录 SHA-256、语法标记和许可
证据。`tests/check-dictionaries.sh` 做离线门禁,
`dictionaries/update.sh check` 在 macOS/Linux 对照固定上游字节。
词盒按 `^[左标点]* (拉丁词) [右标点]*$` 匹配,因此被标点包裹的词
(`(word)`、`word!`、`»word«`)仍可断词;标点粘在首/末音节盒上。
@ -243,12 +389,31 @@ Liang 模式算法,兼容 Pyphen:
## 9. 测试与基准
```bash
tests/run-tests.sh [emacs] # 36 个 ERT 测试,batch 可跑
tests/run-tests.sh [emacs] # batch 可跑的 ERT 测试集
tests/run-tests.sh [emacs] --random-order
tests/run-tests-isolated.sh [emacs] # 每个 ERT 使用全新进程
tests/check-dictionaries.sh # 离线清单/校验值门禁
dictionaries/update.sh check # 核对固定上游字节
make -C ekp_c PROFILE=portable # 默认可移植发布构建
make -C ekp_c PROFILE=native # 仅本机基准
make -C ekp_c PROFILE=debug # 调试符号,不优化
make -C ekp_c PROFILE=sanitize # ASan + UBSan
emacs -Q --batch -L . --eval '(setq ekp-use-c-module nil)' -l tests/ekp-bench.el
emacs -Q --batch -L . --eval '(progn (require (quote ekp)) (ekp-c-module-load))' \
-l tests/ekp-bench.el
```
可用 `EKP_TEST_SEED` 复现或改变乱序。测试 fixture 会动态恢复其隔离的
全部 EKP 配置;分派类测试必须经过公开排版入口,不能只断言内部资格
谓词。
GUI 矩阵需显式加载 `tests/ekp-gui-verify.el`。任一行失败时,它先打印
完整表格,再以状态码 1 退出ERT 套件包含该边界的强制失败负控。
`M-x ekp-c-module-build` 使用同一组四种 profile并在 `ekp_c/` 中以
argv 直接启动 make不再构造 shell `cd` 命令。发布/CI 使用
`portable``native` 仅用于将在同一机器运行的基准。
核心被测不变式:渲染行宽 == 目标宽(像素级对齐)、任意宽度下不丢内
容、O(1) 前缀机制与暴力算法交叉验证、内置文本上的 Elisp/C 一致性、
参数持久化/同步回归。
@ -282,11 +447,11 @@ ekp.el 核心:para 结构、缓存、DP(1D + looseness)、
ekp-utils.el 分词器(盒子、避头尾)、带 batch/tty 回退的字体
检测、C 模块加载
ekp-hyphen.el Liang 断词 + 词典注册
ekp-region.el buffer/region 命令、ekp-auto-justify-mode,以及
编辑器集成(保存、isearch、kill-ring、undo)
ekp-buffer.el 纯文本属性 buffer/region 投影、同步实时流动、窗口
lifecycle、复制过滤与诊断
ekp_c/ C 动态模块(见 ekp_c/README.md)
dictionaries/ Hunspell 断词模式(来自 LibreOffice)
tests/ ekp-tests.el、ekp-region-tests.el(ERT)、
tests/ ekp-tests.el、ekp-buffer-tests.el(ERT)、
ekp-fuzz.el(一致性 fuzz)、ekp-bench.el、
ekp-demo.el、ekp-showcase.el、示例文本、run-tests.sh
```

56
Docs/RELEASING.md Normal file
View File

@ -0,0 +1,56 @@
# Releasing emacs-kp
Releases are immutable snapshots. Changed bytes require a new version,
new checksums, and a new tag; do not replace an existing release asset.
## Repository gate
Run the local invariant check before creating a release commit:
```sh
tests/check-release.sh
```
It verifies:
- every GitHub Action is pinned to a full commit SHA;
- Windows and the public ERT batch entry point are present in CI;
- `.phrase` decision records are not ignored;
- the package header has a matching dated changelog release;
- the Elisp C-module requirement matches the C header ABI version.
This static gate does not prove a remote workflow or published artifact.
## Release checklist
1. Move all shipped entries out of `Unreleased` into a dated
`[X.Y.Z] - YYYY-MM-DD` section and set `Version: X.Y.Z` in `ekp.el`.
2. If the C API changed, bump both `ekp-c-module-required-version` in
`ekp-utils.el` and `EKP_VERSION_MAJOR`/`EKP_VERSION_MINOR` in
`ekp_c/ekp_module.h`. Update the C version in user and developer docs.
3. Run:
```sh
tests/check-release.sh
tests/run-tests.sh emacs
EKP_TEST_SEED=20260728 tests/run-tests.sh emacs --random-order
tests/run-tests-isolated.sh emacs
tests/check-dictionaries.sh
dictionaries/update.sh check
make -C ekp_c PROFILE=portable
emacs -Q --batch -L . -l tests/ekp-fuzz.el
```
4. Byte-compile with warnings as errors and run package-lint/checkdoc using
the same pinned inputs as CI.
5. Verify the worktree is clean and the release commit is the reviewed commit.
6. Create `vX.Y.Z` at that exact commit. Never move an existing release tag.
7. Let the pinned Linux, macOS, Windows, and sanitizer CI jobs finish
successfully on the release commit.
8. Build release artifacts from the tagged commit with `PROFILE=portable`.
Record a SHA-256 checksum for every artifact.
9. Push the commit and tag, publish the immutable assets and checksums, then
verify the remote tag resolves to the reviewed release commit.
If any item fails, fix it in a new commit and restart from the repository
gate. Do not publish a partial release.

View File

@ -0,0 +1,570 @@
# emacs-kp 仓库系统审计
> 审计日期2026-07-28
> 审计基线:`main@29cef97`(本地 tag `v1.0.0`
> 范围Elisp 核心、C 动态模块、buffer/region 集成、测试、性能、兼容性、文档、发布与扩展边界
> 性质:问题盘点与方向判断,不包含运行代码修复
## 后续状态
- `task002` 已在审计后的工作树中解决 P1-01DP key 现在包含行宽、
looseness 与全部六个运行时代价参数,并按结构相等比较。
- `task003` 已继续解决 P2-01auto-CWS 默认值现在同时进入段落哈希键
与最近段落快路径;`issue001` 的两个缓存缺陷均已闭环。
- `task004` 已解决 P1-02保存从逻辑副本序列化文件系统错误、编码
错误与用户中断均不再改变显示 buffer`issue002` 已闭环。
- `task005` 已解决 P1-03/P2-06copy filter 可组合并恢复integration
生命周期由 auto mode 与实际 justified span 共同决定;`issue003`
已闭环。
- `task006` 已解决 P1-04测试 fixture 完整隔离配置parshape 用例
经过公开分派路径CI、可复现乱序和逐例全新进程入口共同阻断顺序
假绿,`issue004` 已闭环。
- `task007` 已解决 P2-05GUI 矩阵共享结构化结果与报告断言边界,
任一失败行在 batch 中退出 1强制失败负控 2/2真实 GUI 七场景
7/7`issue007` 已闭环。
- `task008` 已解决 P2-02C 1.6 在提取前验证单段/批量完整 schema
非法调用统一 signal `ekp-c-invalid-input`,有效 int32 输入使用
int64 中间量;模块 signal 不再被 Elisp 静默吞掉。
- `task009` 已解决 P2-03默认构建改为 portablenative/debug/
sanitize 需显式选择;交互命令以 argv 和 `default-directory` 启动
make含空格路径实测通过。`issue005` 已闭环。
- `task010` 已解决 P2-07 的仓库内边界CI action 与 package-lint
固定到完整提交Windows 运行 Elisp 基线,`.phrase` 改为可追踪事实
来源release gate 与远端/产物 checklist 分责。未执行 push、tag
变更或发布。
- `task011` 已解决 P2-0449 份词典固定来源路径与 SHA-256更新器
可在 macOS/Linux 重建相同输出4 份含斜杠/替换语法的词典明确失败
关闭,不再静默降级。缺少准确许可证据的 Sanskrit 文件已移除。
- `task012` 已按 benchmark 修复 nil 缓存与长文本构造热点;
`task013` 已补齐交互保护命令、反馈与 session-local 文档;
`task014` 已集中行边空格与 render-marker 规则归属。
- 最终独立审查新增的 P1-05 已由 `task016` 解决most-recent fast
path 与 hash cache 共享完整 `ekp--para-key`,同一字符串对象原地
修改排版属性也会失效;`issue010` 已闭环。
- `task015` 已完成终局清理与门禁:删除九个死 accessor 和依赖私有
substring-filter API 的调用;最终独立代码审查 `APPROVE`、架构审查
`CLEAR`,全部仓库内 issue/task 均已闭环。
- `task017` 已将当前编辑器集成模块直接改名为 `ekp-buffer.el`
下文的 `ekp-region.el``ekp-region-*` 仍保留为审计基线的历史
证据,不代表当前加载入口。
- 下文保留 `main@29cef97` 的原始证据与判断,不把历史问题改写成从未发生。
## 结论先行
emacs-kp 的算法地基是可靠的Elisp 掌握文本与字体事实C 只镜像动态规划热路径;严格遍/紧急遍保证困难输入不丢内容buffer 层用可逆布局标记保存逻辑文本测试覆盖了核心算法、C/Elisp parity、编辑器协议与真实 GUI 像素贴合。
审计基线最需要处理的不是重写算法,而是四个已经实证的边界缺陷:
1. 算法 penalty 改变后,既有 DP 缓存仍按旧参数返回结果。
2. 保存失败会让原本已排版的 buffer 留在未排版状态。
3. EKP 直接覆盖已有的 `filter-buffer-substring-function`,可能破坏其他 mode 的复制语义。
4. 一个 C 绕过测试依赖前序测试泄漏的全局状态,整套测试为绿但单独运行失败。
**【Taste Score】** 🟡 Acceptable
设计方向正确,关键算法有真实测试;但缓存键、保存事务和编辑器协议组合这几个边界还没有单一 owner。继续堆 fallback、watcher 或 hook 会放大问题,下一步应先修正状态模型与边界契约。
**【Fatal Issues】**
- `ekp--dp-key` 没有覆盖所有影响 demerits 的参数,配置修改会静默失效。
- save/copy 集成不是可组合、可回滚的事务边界。
- 测试 fixture 没有恢复完整的全局配置,已经产生假绿。
**【Improvement Direction】**
- 先把“影响缓存结果的完整输入”变成显式键。
- 把保存、复制、isearch 视为编辑器协议边界,做可组合和失败恢复。
- 让每个测试独立运行,并让 GUI/本地 C 边界失败能可靠返回非零。
- C 模块继续只做 DP除非 benchmark 证明跨边界复制是主要瓶颈,不要把字体、分词或渲染语义下沉到 C。
## 1. 审计方法与验证基线
### 1.1 读取范围
- 核心:`ekp.el`、`ekp-utils.el`、`ekp-hyphen.el`
- 编辑器集成:`ekp-region.el`
- C 模块:`ekp_c/*.c`、`ekp_c/*.h`、`ekp_c/Makefile`
- 测试与演示:`tests/*.el`、`tests/run-tests.sh`
- 文档与发布README、DEVELOPER、CHANGELOG、CONTRIBUTING、CI、词典更新脚本、`.phrase`
- 历史决策:`.phrase/phases/phase-kp-overhaul-20260726/HANDOFF.md`、`.phrase/phases/phase-p1p2-20260726/NOTES.md`
### 1.2 新鲜验证
| 验证 | 结果 | 说明 |
|---|---|---|
| 默认 ERT 入口 | PASS94/94 | Emacs 30.2,加载 C 1.5 |
| C/Elisp fuzz | PASS300/300 | 字节级一致0 failure |
| 临时干净副本 byte-compile | PASS | `byte-compile-error-on-warn=t` |
| 临时干净副本 checkdoc | PASS | 四个发布 Elisp 文件无输出 |
| 临时干净副本 C release build | PASS | C11、`-Wall -Wextra -Wpedantic` 无警告 |
| 临时干净副本 ERT + fuzz | PASS | 新构建 C 模块94/94 + 300/300 |
| GUI 像素矩阵 | PASS7/7 | base、缩放、face remap、无 fringe、窄窗均无 overflow |
| 单测独立负控 | FAIL符合审计预期 | `ekp-test-parshape-bypasses-c` 单独加载 C 后失败,确认测试顺序依赖 |
GUI 数值矩阵的七种场景均满足 `widest == target``over=0`。干净截图的第二次抓取被 macOS 录屏权限提示覆盖;未批准系统权限,因此矩阵输出可作为数值证据,但本次不把截图当成无噪声视觉证据。
### 1.3 未验证项
- 未运行本地 `package-lint`;仓库 CI 会从 MELPA 动态安装它,但本机没有固定版本。
- 未运行 Windows 构建与 GUI 验证。
- 未验证远端 GitHub Actions 的运行结果;本地分支尚未推到 `origin/main`
- C 极值整数风险未在 macOS sanitizer 下动态复现:`make DEBUG=1` 能构建,但当前系统因 sanitizer runtime code-signing policy 拒绝加载该模块。
## 2. 当前架构
```text
字符串 API
ekp-pixel-justify / ekp-pixel-range-justify
├─ ekp-utils分箱、字体与像素测量
├─ ekp-hyphenLiang pattern 编译与断词
├─ ekp.elekp-para、前缀数组、缓存
├─ DP
│ ├─ Elisp 1D / 2D
│ └─ C 1D15 字段边界,批量按段落并行)
└─ rendererglue、soft break、soft hyphen、hidden payload
buffer API
ekp-region.el
├─ justify / unjustify
├─ auto mode宽度跟随、dirty 段落、lazy chunk
└─ 编辑器协议save、isearch、kill/yank、undo、modified state
```
### 2.1 扎实的部分
1. **Elisp 是语义事实源C 只做纯 DP。**
证据:`ekp.el:1733-1749` 构造 15 字段输入;`ekp_c/ekp.c:590-611` 暴露对应 API`ekp_c/README.md` 明确字体相关数据留在 Elisp。
判断:这是正确边界。不要重新把分词、字体测量或渲染下沉到 C。
2. **严格遍 → 紧急遍的失败模型一致。**
证据:`ekp.el:1173-1202` 与 `ekp_c/ekp_kp.c:501-537`fuzz 300/300。
判断:困难输入不丢内容的核心承诺有代码与性质测试双重保护。
3. **布局是可逆结构,不是破坏性格式化。**
证据:`ekp.el:2180-2191` 定义四类布局标记;`ekp-region.el:402-445` 做结构反转region 测试覆盖 roundtrip、save、isearch、kill、undo。
判断:模型有价值,但所有编辑器协议都必须显式适配,生命周期边界必须更硬。
4. **CI 覆盖面优于一般小型 Elisp 包。**
证据:`.github/workflows/ci.yml` 覆盖 Emacs 29.1/30.1/snapshot、C parity、fuzz、ASan/UBSan、macOS。
判断测试类型正确主要缺口是可复现性、Windows 和失败退出契约。
## 3. 按优先级排序的问题
优先级含义:
- P0数据丢失、安全或默认路径不可用立即阻断发布
- P1已实证的核心行为错误或测试可信度缺陷
- P2重要边界风险、兼容性/维护性缺陷或已知能力落差
- P3优化与体验机会应由基准或真实需求驱动
本次未发现 P0。
### P1-01 算法参数不在 DP 缓存键中,修改后静默复用旧结果
- **Evidence**
- `ekp--dp-key` 只包含 `line-pixel` 和非零 `ekp-looseness``ekp.el:1184-1190`。
- demerits 直接读取 `ekp-line-penalty`、`ekp-hyphen-penalty`、`ekp-adjacent-fitness-penalty`、`ekp-consecutive-hyphen-penalty` 等全局值:`ekp.el:1080-1125`。
- README 声称所有算法参数对两个引擎生效。
- 实测同一段落/宽度先用 hyphen penalty 0再改为 1000000缓存对象仍相同清缓存后断点改变cost 从 `100226389.0` 变为 `601214639.0`
- **Inference**
- 问题 owner 是 DP 缓存键,不是 C 参数同步。C 同步只在真正计算时运行,命中缓存时根本不会进入同步或 DP。
- **Impact**
- 用户通过 `setq` 或 Custom 调整排版质量参数时,界面可能看起来“设置无效”,直到显式 `ekp-clear-caches` 或段落键变化。
- **Direction**
- 把所有影响 DP 结果的参数收敛到一个不可变的 DP key不要再靠零散 watcher 猜测失效点。
- 回归测试必须在不清缓存的情况下修改每一类参数,并断言结果或 cost 与 fresh computation 一致。
- **Confidence**High
### P1-02 保存失败会留下未排版 buffer 和 stale save state
> 后续状态2026-07-28已由 `task004` 修复。保存不再执行
> unjustify/rejustify 事务,而是在 `write-region` annotation 边界写入
> 隐藏逻辑副本;成功、文件系统失败、编码失败和 `quit` 均有回归覆盖。
- **Evidence**
- `before-save-hook` 先把全部 justified spans 反转:`ekp-region.el:461-479`。
- 只有 `after-save-hook` 会重排并清空 `ekp-region--save-state``ekp-region.el:481-492`。
- 负路径实测:让 visited file 的目标目录消失后调用 `save-buffer`,得到 `still-justified=nil`、`save-state=t`。
- **Inference**
- 成功路径测试不足以证明保存事务;底层写入报错或保存被中断时,`after-save-hook` 不会提供 finally 语义。
- **Impact**
- README 的“保存逻辑文本,同时屏幕保持排版态”承诺在失败路径不成立;后续保存还可能携带陈旧 marker。
- **Direction**
- 先定义保存事务的 owner 和失败恢复路径,再实现;恢复必须无条件执行,写盘失败仍要回到原显示态。
- 增加写盘失败、编码失败、用户中断三类负路径测试。
- **Confidence**High
### P1-03 覆盖已有 copy filter破坏其他 mode/user 的复制语义
> 后续状态2026-07-28已由 `task005` 修复。测试覆盖 local/global
> prior filter、复制、DELETE kill、手动 unjustify 与 mode 关闭。
- **Evidence**
- 安装集成时直接 `setq-local filter-buffer-substring-function``ekp-region.el:378-389`。
- 移除时只删除 EKP 自己的当前值,没有保存或恢复前一个 filter`ekp-region.el:391-399`。
- 负路径实测:预设 filter 为复制内容加 `PRE:`justify 后该前缀消失。
- **Inference**
- `filter-buffer-substring-function` 是单槽协议EKP 必须显式组合或恢复前一个 owner直接覆盖不是可组合集成。
- **Impact**
- 在已有复制过滤逻辑的 major mode 中kill/copy 可能丢失 mode 定义的语义。
- **Direction**
- 记录前一个 buffer-local filter明确调用顺序并在最后一个 justified span 消失时恢复。
- 测试已有 filter、EKP filter、删除式 kill 三条真实 public path。
- **Confidence**High
### P1-04 测试顺序依赖制造假绿
> **Resolved by task006 (2026-07-28):** fixture 使用动态绑定恢复全部
> tunableparshape 用例绑定真实 `ekp-parshape` 并驱动公开 formatter。
> 108/108 通过可复现乱序,全部 108 个测试也逐个在全新 Emacs 进程
> 中通过。
- **Evidence**
- `ekp-test-parshape-bypasses-c` 的名称和 docstring 测 parshape代码却绑定 `ekp-first-line-indent``tests/ekp-tests.el:225-228`。
- C 1.5 已支持 first-line indent真正绕过 C 的条件是 `ekp-parshape` 非 nil`ekp.el:1667-1675`。
- `ekp-test-params-affect-c-module` 在结束时把全局 `ekp-use-c-module` 留为 nil`tests/ekp-tests.el:434-448`。
- 全套 94/94单独加载 C 后运行该测试则 1/1 FAIL。
- **Inference**
- 默认字母序恰好隐藏了错误测试;当前 fixture 只恢复部分 penalty没有恢复引擎和全部样式状态。
- **Impact**
- 测试套件可能继续掩盖其他全局状态泄漏,绿灯可信度下降。
- **Direction**
- 让 clean-state fixture 保存/恢复所有全局 tunable该测试绑定真实 `ekp-parshape`
- CI 增加 isolated 或 randomized-order lane至少覆盖依赖全局状态的测试。
- **Confidence**High
### P1-05 同一字符串对象的属性修改绕过完整段落 key
> 后续状态2026-07-28已由 `task016` 修复。most-recent slot
> 保存并比较完整 `ekp--para-key`,删除六个补偿性 style watcher
> CJK 与 Latin-with-space 原地属性修改回归、专项缓存矩阵及完整
> default/permuted/isolated ERT 均通过。
- **Evidence**
- `ekp--para-key` 包含过滤后的 text-property intervals。
- `ekp--get-para` 的 most-recent fast path 只比较对象 identity、语言、
width context 与 spacing signature。
- 独立审查实测warm `"文中"` 后在原对象加入 `ekp-no-break`,下一次
lookup 仍返回原 paragraph清缓存后的 fresh paragraph 不允许相同
break。
- **Inference**
- 这不是 hash collision而是第二套不完整 identity 规则绕开了唯一
完整 key。
- **Direction**
- 让 fast path 与 hash cache 共享 `ekp--para-key`,删除并行维护的
partial signature/watchers。
- 用 CJK 与 Latin-with-space 两类原地 property mutation 锁定 public
paragraph resolver。
- **Confidence**High
### P2-01 自动 CJK stretch 默认值不在段落缓存键中
> 后续状态2026-07-28已由 `task003` 修复。新增回归分别覆盖
> 段落哈希键、`ekp--last-para` 快路径及相同签名真实命中;修复没有
> 新增 watcher。
- **Evidence**
- `ekp-default-cws-stretch-pixel` 定义于 `ekp.el:94-97`,在 auto 参数计算中使用:`ekp.el:329-336`。
- auto 模式的 para key 只写入符号 `auto``ekp.el:740-746`。
- 实测把默认值从 2 改为 9 后,返回同一个 para缓存中的 `:cws-stretch` 仍为 2。
- **Inference**
- 这是 P1-01 同一类模型缺陷:缓存键没有完整表达决定结果的输入。
- **Direction**
- 将该值纳入 auto 参数 snapshot/key测试修改后无需手动清缓存。
- **Confidence**High
### P2-02 C API 的输入契约、错误语义与数值范围不闭合
> **Resolved by task008 (2026-07-28):** C 1.6 对 15 字段 shape、长度、
> 类型和 int32 范围做整体验证,非法输入使用专用 conditionDP 中间
> 行宽/前缀差/剩余空间改为 int64。六项边界红测 0/6 → 6/6公开
> dispatcher error 传播 0/1 → 1/1完整 ERT 116/116、fuzz 300/300。
- **Evidence**
- 文档说 bad argument 返回 nil直接传入长度错误的 vector 实测得到 `args-out-of-range`
- `ekp_c/ekp.c:176-237`、`323-374` 在多次 `vec_get`/`extract_integer` 后才统一检查 pending non-local exit。
- 输入先 clamp 到 `int32_t`,随后在 `ekp_c/ekp_kp.c:287-304` 继续做有符号加减。
- **Inference**
- 正常 `ekp.el` 调用会捕获 signal 并回落 Elisp因此常规文本不受影响但直接 C API 的文档契约不真实。
- `INT32_MAX` 附近的 width/protrusion/prefix 组合可能触发 C signed overflow。真实字体不接近此范围但公开 API 没有限制它。
- **Direction**
- 先校验 15 字段 arity、`n/n+1` 数组长度、排序位置和数值范围,再分配/提取。
- 统一选择“明确 signal”或“返回 nil”不要文档与实现各说一套。
- 内部度量改为 `int64_t` 或 checked/saturating arithmeticAPI 边缘再收窄。
- 增加 malformed vectors、极值、batch >1024 的 C 边界测试。
- **Confidence**Medium-High
### P2-03 C 构建入口对路径、CPU 和调试环境过度假设
> **Resolved by task009 (2026-07-28):** `PROFILE=portable` 成为默认,
> native/debug/sanitize 独立;交互构建不经过 shell。两项红测
> 0/2 → 2/2四 profile 均零警告,真实交互构建与含空格副本构建
> 均成功。
- **Evidence**
- `ekp-c-module-build` 拼接 `cd %s && make` 交给 shell路径没有 quote`ekp-utils.el:405-419`。
- 默认 Makefile 使用 `-march=native -flto``ekp_c/Makefile:33-36`。
- README 把普通 `make` 描述为通用入口。
- 当前 macOS 上 `make DEBUG=1` 构建成功,但加载时因 sanitizer runtime code-signing policy 失败。
- **Inference**
- 安装路径含空格或 shell 元字符时,交互 build 会失败,特殊路径还形成命令注入面。
- `-march=native` 适合本机 benchmark不适合分发预编译产物。
- **Direction**
- 用进程 `default-directory` + 参数向量调用 `make`,不要启动 shell 执行 `cd`
- portable 作为默认 profile`NATIVE=1`、`LTO=1`、`DEBUG=1` 显式选择。
- 文档分别说明 Linux sanitizer 与 macOS 调试模块的 runtime/code-signing 条件。
- **Confidence**High
### P2-04 词典覆盖与 parser 能力被文档高估
> 后续状态2026-07-28已由 `task011` 闭环。进一步核对发现除
> 2399 条三语言 replacement rules 外Esperanto 还有 4 条斜杠模式。
> 当前固定宽度 DP 无法表达“仅断点胜出时替换文字与宽度”,因此四份
> 词典统一 signal 而非产生错误断词49 项 manifest/upstream gate 与
> 双导出一致性均通过。
- **Evidence**
- 仓库实际有 50 个 `hyph_*.dic`,不是 README 所写的 70+。
- `ekp-hyphen--compile` 跳过所有含 `/` 的 alternative patterns`ekp-hyphen.el:114-153`。
- 仓库中 3 个词典含 2399 条非注释 alternative pattern主要来自 Hungarian也涉及 Catalan/Albanian。
- 50 个词典中有 20 个没有同名 `README_hyph_*.txt`README 却要求“见每个词典的 README”。
- `dictionaries/update.sh` 拉取移动中的 LibreOffice HEAD没有 pinned commit、manifest、checksum、错误退出或跨平台处理。
- **Inference**
- “任意 bundled dictionary 都完整工作”不成立。普通 Liang patterns 可用,但 replacement/alternative 语义被有意忽略,相关语言的断词质量会下降。
- 许可证不一定不合规,但仅凭当前仓库无法完整核对每个词典的来源版本与许可文件。
- **Direction**
- 先诚实声明 parser 支持的语法子集。
- 为 Hungarian/Catalan/Albanian 加 golden words再决定是否值得实现 replacement semantics。
- 更新脚本必须 pin upstream commit生成来源/许可/checksum manifest并在 macOS/Linux 都可运行。
- **Confidence**High
### P2-05 GUI 验证工具有结果,但不是可靠的发布门禁
> **Resolved by task007 (2026-07-28):** 单场景返回结构化结果,统一
> report 在 batch 任一失败时退出 1。README 明确工具加载边界;强制
> 失败/成功负控 2/2真实 GUI 矩阵 7/7干净全屏单窗口截图已复核。
- **Evidence**
- README 推荐 `M-x ekp-gui-verify`,命令实际位于 `tests/ekp-gui-verify.el`,普通 `(require 'ekp-region)` 不会定义它。
- `ekp-gui-verify-matrix` 在 noninteractive 下只打印 `FAIL` 文本,不会以非零状态退出:`tests/ekp-gui-verify.el:184-191`。
- 本次真实 GUI 矩阵 7/7 PASS。
- **Inference**
- 工具对人工诊断有价值,但当前无法直接成为 CI gate用户也可能按 README 执行一个尚未加载的命令。
- **Direction**
- 二选一:把单次诊断纳入发布包,或明确给出加载 tests 工具的命令。
- matrix 发现任何 FAIL 时必须 signal/exit 1表格只是报告不是判定。
- **Confidence**High
### P2-06 手动 unjustify 后集成 hook/filter 残留
> 后续状态2026-07-28已由 `task005` 修复。auto mode 外最后
> justified span 消失时,公共 unjustify 与外部删除路径都会卸载
> integrations内部 reflow/isearch 继续使用不改变生命周期的核心反转。
- **Evidence**
- `ekp-unjustify-region` 只反转文本属性:`ekp-region.el:402-445`。
- integrations 只在 minor mode 关闭路径移除:`ekp-region.el:928-938`。
- 实测全 buffer unjustify 后已经没有 justified span但 save/isearch hooks 和 local copy filter 仍存在。
- **Inference**
- 无 justified span 时多数 hook 会空跑,但 copy filter 仍占据协议槽;它与 P1-03 共享生命周期 owner。
- **Direction**
- 由“buffer 是否仍有 justified span / auto mode 是否开启”派生 integration 生命周期,不要让手动命令和 mode 各自维护一套真相。
- **Confidence**High
### P2-07 发布与文档治理没有闭环
> 后续状态2026-07-28仓库内治理已由 `task010` 闭环。静态 gate
> 检查 action SHA、Windows/ERT 路径、`.phrase`、package/changelog
> 与 C ABI 版本;远端 CI、tag 和 content-addressed artifact 由发布
> checklist 在实际发版时验证,本轮没有冒充已发布。
- **Evidence**
- 本地 `main``origin/main` ahead 31本地 `v1.0.0` 指向 HEAD远端没有该 tag。
- `CHANGELOG.md` 与 package header 已声明 1.0.0。
- `.phrase/``.gitignore` 忽略,但仓库协议又把它定义为需求/task/change/issue 的事实源。
- 审计前 README 的 C 版本、词典数量、测试数量、GUI 工具入口均与实现不一致。
- CI 使用 `purcell/setup-emacs@master`、运行时最新 MELPA `package-lint`、`macos-latest`,没有 Windows job。
- **Inference**
- 如果 GitHub `origin` 是正式发布面,则 1.0.0 尚未真正发布;如果不是,仓库缺少明确的发布状态说明。
- 被忽略的 `.phrase` 可以做本地过程数据,但不能同时作为可审计的版本化事实源。
- **Direction**
- 在 push 前增加 release gatecommit/tag/changelog/version/CI/checksum 一致。
- 明确 `.phrase` 是 tracked source of truth 还是 local workflow state只能选一个。
- pin CI action/工具版本,增加 Windows 构建 lane。
- **Confidence**High
### P3-01 tokenizer 与断词缓存还有局部性能债
> **Resolved by task012 (2026-07-28):** nil cache 使用显式 miss
> sentineltokenizer 与密集插入改为片段单次拼接。8,000 字符耗时
> 分别从 3.133 s 降到 1.100 s、从 0.945 s 降到 0.013 s完整 ERT
> 124/124、fuzz 300/300、warning-as-error 编译与 checkdoc 通过。
- **Evidence**
- `ekp-split-to-boxes` 在字符循环中反复 `concat``ekp-utils.el:253-316`。
- `ekp-hyphen-inserted` 每个断点重建字符串:`ekp-hyphen.el:216-224`。
- `ekp-hyphen--positions``(or (gethash ...) (puthash ...))`;缓存值为 nil 时无法区分 miss。对无 pattern 的 `qzxq` 连续查询两次,`ekp-hyphen--compute` 实际调用两次。
- **Inference**
- 极长 URL、长 token、重复 acronym 或大量零宽字符下会出现额外分配;正常段落不是当前最大瓶颈。
- **Direction**
- 先把 benchmark 加入这些退化输入,再做线性 builder 与显式 cache sentinel。
- **Confidence**High
### P3-02 核心函数与 `ekp-para` 承担了过多规则
> **Resolved by task014 (2026-07-28):** 既有 `ekp--dp-key` /
> `ekp--line-ideal-pixel` owner 保留;四处重复的行缘空格公式收敛到
> 一个内联纯规则renderer/region 的五项 marker 词汇表收敛到一个
> 常量。直接规则测试与既有 roundtrip/C parity 共同锁定边界,未拆
> 文件、未新增热循环分配。
- **Evidence**
- `ekp-para` 同时保存分箱、测宽、glue、禁则、悬挂、offset 和 DP cache`ekp.el:215-246`。
- `ekp--make-para`、1D/2D DP、renderer 都是长流程space/protrusion 公式还必须在 DP、C 重建和 renderer 三处保持一致。
- **Inference**
- 继续加 typography 选项时,最可能出现的错误不是算法本身,而是忘记更新 key、15 字段边界或渲染反变换。
- **Direction**
- 不拆新文件。先在 `ekp.el` 内把“cache signature”“line metrics”“render marker protocol”变成三个可直接测试的规则 owner。
- 只抽取能消除重复规则的纯函数;不要建立 helper ladder。
- **Confidence**High
### P3-03 交互入口与临时标记的产品语义不够清楚
> **Resolved by task013 (2026-07-28):** 四个保护命令均通过真实
> interactive/public formatter 路径测试minor mode 增加标准 EKP
> 菜单与 help命令反馈及双语文档明确属性仅在当前 buffer session
> 有效。未引入持久化格式或全局快捷键。
- **Evidence**
- minor mode keymap 只 remap `fill-paragraph``ekp-region.el:897-899`。
- no-break/verbatim 命令只写入普通 text property`ekp-region.el:568-595`;普通文件保存重开不会保留这些属性。
- **Inference**
- 用户难以发现 justify/unjustify/protect 操作,也可能误以为手工保护会持久化。
- **Direction**
- 先补 mode help、状态反馈和“仅当前 buffer session 有效”的文档;只有真实用户需要时再设计持久化语法映射。
- **Confidence**Medium
## 4. 优化与扩展路线
### 4.1 第一优先级:恢复行为可信度
1. 完整 DP cache signature。
2. 保存失败的无条件恢复。
3. copy filter 组合与 integrations 单一生命周期。
4. 测试 fixture 隔离、错误 parshape 测试修正、负路径测试。
这四项完成前,不建议扩大 typography 功能面。
### 4.2 第二优先级:硬化 C 与发布边界
1. 15 字段 schema preflight、`int64_t`/checked arithmetic。
2. portable/native/debug 三种构建 profile。
3. 可诊断的 C fallback用户路径仍回落但 debug/CI 能看到失败原因。
4. GUI matrix 非零退出、Windows job、固定 CI 依赖。
5. 词典来源/许可/checksum manifest。
### 4.3 性能方向:只做有基准支撑的工作
| 方向 | 当前判断 | 启动条件 |
|---|---|---|
| tokenizer 线性 builder | 小而明确 | 长 token benchmark 显示明显非线性 |
| nil-aware hyphen cache | 低成本 | 可直接修复并加计数测试 |
| C paragraph-handle API | 潜在高收益、高复杂度 | range search profile 证明 15 数组重复 marshal 是主要瓶颈 |
| 更多线程内并行 | 不建议 | 单段 DP 有前向依赖,当前按段落并行边界正确 |
| 把测宽/分词移入 C | 不建议 | 会拆散字体事实源并放大 parity 成本 |
### 4.4 可选产品扩展
1. **精确 width search 模式**
当前 ternary + local scan 明确不保证全局最优。可为小范围提供 exhaustive 选项,并保留当前快速默认。
2. **按段落选择断词语言**
当前 `ekp-latin-lang` 是全局单值。若真实文档有多语言段落,可增加 buffer-local resolver不要先做自动语言识别。
3. **可发布的诊断入口**
`ekp-diagnose` 与 GUI fit check 收敛成一个用户入口:快速检查当前 buffer需要完整矩阵时再加载 tests 工具。
4. **mode-native verbatim/no-break**
Org/Markdown 优先从语法/face 派生保护,不把瞬时 text property 当持久存储。其他 mode 通过一个 buffer-local predicate 接入。
5. **发布包与词典可选化**
词典占仓库约 6.1 MiB。若分发场景确有需要可提供 core + language packs没有安装/更新痛点前不要拆包。
### 4.5 明确不做
- 左缘悬挂与行中 glyph advance 压缩:当前 Emacs 显示模型不能可靠表达,已有文档说明。
- 把一个 coherent `ekp.el` 按“utils/common”标签拆成多个文件。
- 为了“以后也许需要”而新增协议层、adapter 或兼容 shim。
- 在没有 profile 数据前重写 C DP 或增加候选级并行。
## 5. 建议的验收门槛
### 核心行为
- 所有影响 DP 的参数修改后,不清缓存也与 fresh computation 一致。
- 保存成功、保存失败、用户中断后buffer 的显示态、逻辑文本、modified state 和 marker state 都正确。
- 已有 copy filter 与 EKP filter 组合后,两方语义都保留。
### 测试
- 每个 ERT 可单独运行。
- randomized order 通过。
- 现有 94 ERT、300 fuzz、C parity 全通过。
- GUI matrix 任一 FAIL 返回非零。
### C 边界
- malformed vector 不越界、不继续带 pending exit 调用 API。
- 数值上限有文档、有检查、有 sanitizer 覆盖。
- portable build 不含 `-march=native`native benchmark profile 单独启用。
### 发布
- package version、C ABI version、README、CHANGELOG、tag、远端 commit 对齐。
- CI action/tool 版本固定。
- 词典 manifest 能回答“来源 commit、文件 checksum、许可文件”。
## 6. Evidence / Inference / Unknown 总结
### Evidence
- 默认与 fresh-build 测试均 94/94fuzz 300/300GUI 数值矩阵 7/7。
- 四个 P1 问题均有代码路径与负控/独立复现。
- README/C ABI/test count/词典数量与仓库事实曾发生漂移。
- 本地 main/tag 与 origin 状态不一致。
### Inference
- C `int32_t` 极值会造成 signed overflow 风险,但常规字体输入不会接近该范围。
- alternative patterns 被忽略会降低相关语言质量;具体单词影响需要 golden corpus 定量。
- 交互可发现性不足与 text property 非持久化会造成用户困惑,尚无用户研究数据。
### Unknown
- Windows C 模块和 region UI 在当前版本是否完整通过。
- GitHub Actions 在尚未推送的 31 个本地提交上是否通过。
- 远端缺少 v1.0.0 是有意暂缓还是发布遗漏。
- 用户是否真正需要精确 width search、多语言 resolver 或 language packs。
## 7. 停止条件
本次任务在以下状态停止:
- 已覆盖代码、C、测试、GUI、文档、发布与扩展面
- 已把问题按证据和优先级写入文档;
- 已给出优化方向与“不做什么”;
- 未修改运行代码,也未推送/发布;
- 具体修复应从 P1-01 开始,每次只闭环一个原子问题。

37
dictionaries/LICENSES.md Normal file
View File

@ -0,0 +1,37 @@
# Dictionary provenance and license evidence
The dictionary bytes in this directory are third-party data, not covered
solely by emacs-kp's `COPYING`. `MANIFEST.tsv` identifies the evidence for
every bundled file:
- `embedded` means the dictionary itself contains its copyright and license
or redistribution notice.
- `README_hyph_*.txt` names the checked-in companion notice.
- `LICENSES.md` refers to one of the pinned upstream companion files below.
The pinned LibreOffice snapshot is
`8fb8e794237cff49ec212023f96bcdb7d3fbf56c`. Immutable source links:
| Dictionaries | Upstream license evidence |
|---|---|
| `hyph_eo.dic` | [`eo/license-en.txt`](https://github.com/LibreOffice/dictionaries/blob/8fb8e794237cff49ec212023f96bcdb7d3fbf56c/eo/license-en.txt) |
| `hyph_id_ID.dic` | [`id/LICENSE-dict`](https://github.com/LibreOffice/dictionaries/blob/8fb8e794237cff49ec212023f96bcdb7d3fbf56c/id/LICENSE-dict) |
| `hyph_mr_IN.dic` | [`mr_IN/COPYING`](https://github.com/LibreOffice/dictionaries/blob/8fb8e794237cff49ec212023f96bcdb7d3fbf56c/mr_IN/COPYING) |
| `hyph_nl_NL.dic` | [`nl_NL/license_en_EN.txt`](https://github.com/LibreOffice/dictionaries/blob/8fb8e794237cff49ec212023f96bcdb7d3fbf56c/nl_NL/license_en_EN.txt) |
| `hyph_ru_RU.dic` | [`ru_RU/README_ru_RU.txt`](https://github.com/LibreOffice/dictionaries/blob/8fb8e794237cff49ec212023f96bcdb7d3fbf56c/ru_RU/README_ru_RU.txt) |
`hyph_eu.dic` is a legacy checked-in source whose byte is not present in the
pinned LibreOffice tree. Its origin, copyright, redistribution terms, and
checksum are retained in `README_hyph_eu.txt` and `MANIFEST.tsv`; the updater
copies that verified local byte instead of pretending it came from the
snapshot.
`hyph_sa_IN.dic` is intentionally not bundled. At the pinned snapshot it has
neither an in-file copyright/license notice nor a package companion that
states terms for the hyphenation data. LibreOffice reverted a generic
`COPYING` addition because spelling and hyphenation files may have different
licenses. Re-add Sanskrit only after an authoritative license statement for
that exact data is available.
License texts and notices remain the authority. This index is provenance
metadata, not a reinterpretation of their terms.

54
dictionaries/MANIFEST.tsv Normal file
View File

@ -0,0 +1,54 @@
# emacs-kp dictionary manifest version 1
# upstream https://github.com/LibreOffice/dictionaries.git
# upstream-commit 8fb8e794237cff49ec212023f96bcdb7d3fbf56c
# normalization CRLF is converted to LF; hyph_sr-Latn.dic is renamed hyph_sr_Latn.dic
# columns file source-path sha256 license-evidence unsupported-slash-patterns
hyph_af_ZA.dic af_ZA/hyph_af_ZA.dic 541a45d73d92c69445613ec558c2c1a6829550d1a1ad8aa9dc6dfb752db87da1 embedded 0
hyph_as_IN.dic as_IN/hyph_as_IN.dic 321ffb692c12792dddb4294ab5857821fc1ab8f2d0a8c0fcdced00200602ead6 embedded 0
hyph_be_BY.dic be_BY/hyph_be_BY.dic e4339b5a53d2f0fb4179c1060173788d5b6ed3f07bbbccb8b74f5cea614a809e embedded 0
hyph_bg_BG.dic bg_BG/hyph_bg_BG.dic 7dad4f2ccfce1909b27f53082cf57c7914809a8ba31ccbcee3dc817dcd7c7994 README_hyph_bg_BG.txt 0
hyph_ca.dic ca/dictionaries/hyph_ca.dic 4a4816e93a92df35819c5f4775106577aa279739a12c687291d747a3ce767b8b embedded 2
hyph_cs_CZ.dic cs_CZ/hyph_cs_CZ.dic a378d024438b446ac0524237dc6d728dc520550db5fe0fb1fdbb47c2ee0ca525 README_hyph_cs_CZ.txt 0
hyph_da_DK.dic da_DK/hyph_da_DK.dic 44d0dd1d85bcdb1e63003c97cd08924f10a8a8127380b1b07ac2e75a950c216b README_hyph_da_DK.txt 0
hyph_de_AT.dic de/hyph_de_AT.dic 3e0b4687479d25099e37ebd946998c08fcccc12a0acf9380d8b19319a50b9f3a embedded 0
hyph_de_CH.dic de/hyph_de_CH.dic 825159436515ab150d3da0ddf5eedb814c0ec6c7eec98fbd53e3ba5b2a4a1443 embedded 0
hyph_de_DE.dic de/hyph_de_DE.dic 2e2f5ffea6bc53f67ffe8c076921c7b1b34a2ec2b9ec57a4608374bb65a1a1e1 embedded 0
hyph_el_GR.dic el_GR/hyph_el_GR.dic f2284153c3f9a809b959dcaeb349e00d90b4c4cf37c3d3a338b371c32f10e89d README_hyph_el_GR.txt 0
hyph_en_GB.dic en/hyph_en_GB.dic 9fdc97f7faabcda30c15a9d53bc7fbf21daec795fb3c9a9588f7bdb30c563f41 README_hyph_en_GB.txt 0
hyph_en_US.dic en/hyph_en_US.dic 486fb6840b1049d5d252ef784be11cd4dc2be0bf296568b74b41e4f4e17cba7d README_hyph_en_US.txt 0
hyph_eo.dic eo/hyph_eo.dic f2feb4cabe54b696affec72960b1670cfd9e4eae007854944bd02c0d348f59d7 LICENSES.md 4
hyph_es.dic es/hyph_es.dic a95be7365a482b930ade0ff54d413cd24357ade97cefd434b704286eda828d71 embedded 0
hyph_et_EE.dic et_EE/hyph_et_EE.dic 2ee13914fcd9e9b110e9e3d56e1d6acc28bf792c15b1b69efc2e71ac96c23c74 README_hyph_et_EE.txt 0
hyph_eu.dic legacy 5f499f513435a8432961750fae7b31bc5d3989b686a744c331bb75471a14b71b README_hyph_eu.txt 0
hyph_fr.dic fr_FR/hyph_fr.dic 476ca60b958400c8b3fbe55764b4900df87acf60394ef62362705d9c701db191 README_hyph_fr.txt 0
hyph_gl.dic gl/hyph_gl.dic 357355b82eb82ed081433503327ac4ba5b4f3606b6a3e50d69091d57eef4d5b8 README_hyph_gl.txt 0
hyph_hr_HR.dic hr_HR/hyph_hr_HR.dic 77613ff6e907d2e9ffd93d45bfb16873aa9182e2215df276dbf850d1b0e28f65 README_hyph_hr_HR.txt 0
hyph_hu_HU.dic hu_HU/hyph_hu_HU.dic 354939e518b838dbfe01ab617c25c94586b1accc2d5bdcf57ba07406e8d1fee9 embedded 2396
hyph_id_ID.dic id/hyph_id_ID.dic ac53214620527f8a145ce4d70f0a3a5d336a145643b52547375aedacef558dc0 LICENSES.md 0
hyph_is.dic is/hyph_is.dic 690ed04b72b60d05f66971ad9fd799656419228bfa8df00190adf02e27c0b1ef README_hyph_is.txt 0
hyph_it_IT.dic it_IT/hyph_it_IT.dic 8c25b6fb62c2fb97586c6f7c6fb48f29f49e90a5c33c09ac9112dba6942b50cd README_hyph_it_IT.txt 0
hyph_kn_IN.dic kn_IN/hyph_kn_IN.dic a15acb05a834979904330afda768accfa7fee10da386cc534d7944144eb1997f embedded 0
hyph_lt.dic lt_LT/hyph_lt.dic 376e9fbdc09db5feb0025181d84242b6e01e02f413301f7d059fd99dc0597316 README_hyph_lt_LT.txt 0
hyph_lv_LV.dic lv_LV/hyph_lv_LV.dic 775d9a539fd8e98fabbfefd87af5887bb24eb4700b47ed5f8e360b0bab75b078 README_hyph_lv_LV.txt 0
hyph_mn_MN.dic mn_MN/hyph_mn_MN.dic c49d5ead63948d84c75d43245d837a9d7c8dc36c2a97dc5e8a7ade031c7a00e9 embedded 0
hyph_mr_IN.dic mr_IN/hyph_mr_IN.dic 740707cd6d149d1339e921e126a25dcea889712f4889fbc72434d753324409ed LICENSES.md 0
hyph_nb_NO.dic no/hyph_nb_NO.dic c6c9b07d9f2c60bb47857fd629d9a453026312c894665d22c72e8e1699420f6c README_hyph_NO.txt 0
hyph_nl_NL.dic nl_NL/hyph_nl_NL.dic 0a97b76b259ed4165d094dedf9b6fb98c57734a437df0b5b263ee13e262d5f8f LICENSES.md 0
hyph_nn_NO.dic no/hyph_nn_NO.dic c6c9b07d9f2c60bb47857fd629d9a453026312c894665d22c72e8e1699420f6c README_hyph_NO.txt 0
hyph_or_IN.dic or_IN/hyph_or_IN.dic 264b3a880fc7dc9f477abac80708172144ec3bb28baa8266539fe7ffff7a8a30 embedded 0
hyph_pa_IN.dic pa_IN/hyph_pa_IN.dic 87b2e3d7dc8cc4e4f0bb1d6f4265e8d8eefa478fbc890a60839868af4f5c2c06 embedded 0
hyph_pl_PL.dic pl_PL/hyph_pl_PL.dic 9fe3917ec1fb09b7ffa560e85fb810e098d45a9a45ad6d5c08311544dd92a100 README_hyph_pl_PL.txt 0
hyph_pt_BR.dic pt_BR/hyph_pt_BR.dic a8eb1a64e4af4649c739211b397fa37b01f9d5774cf558fe3895fc3f26fd33a4 README_hyph_pt_BR.txt 0
hyph_pt_PT.dic pt_PT/hyph_pt_PT.dic 6f18ee89de6db21d9b06120a5527ec41d8132514fc112ccfe7d289ba91649840 README_hyph_pt_PT.txt 0
hyph_ro_RO.dic ro/hyph_ro_RO.dic 24adbfe7cc2267ea5ce4afd34f7d69da5a9163ef3dd57b158bbb196b9d651e94 embedded 0
hyph_ru_RU.dic ru_RU/hyph_ru_RU.dic e9a6ae92765fc7e63d879f2ccf3f50f2b95bc8ed7618a6f31c34ce7d39844c83 LICENSES.md 0
hyph_sk_SK.dic sk_SK/hyph_sk_SK.dic 7359863ec5d5dbbf35c7d4f38faca1af7b43bc4aa643871e63f2dc0dfac4f922 README_hyph_sk_SK.txt 0
hyph_sl_SI.dic sl_SI/hyph_sl_SI.dic 3d9026755aa0936e9331595a08bb65c295ccac84acefe58cb6c9fe2ff11ceef8 README_hyph_sl_SI.txt 0
hyph_sq_AL.dic sq_AL/hyph_sq_AL.dic 99fbce3b0e60cc500e45643dd40903dfa71cfc1605a5b139cfb22129bd665dbe README_hyph_sq_AL.txt 1
hyph_sr.dic sr/hyph_sr.dic ce785026592f525c38e67152699224d47a4cdf555a1a4eb65d50458c5548f69f README_hyph_sr.txt 0
hyph_sr_Latn.dic sr/hyph_sr-Latn.dic b423f910400472979a2b0fe1c95965c940e927f7588dace0e42c17f7b0663376 README_hyph_sr.txt 0
hyph_sv.dic sv_SE/hyph_sv.dic 44c5a59f1ef8a17b9433e4510a1abcac753d34ac875dc27ea95427f1e581f17b README_hyph_sv.txt 0
hyph_te_IN.dic te_IN/hyph_te_IN.dic 2ad470071dfd9bb3ad5101d528e1fb0090d96c841c4f2bb07b325476b3c853fe embedded 0
hyph_th_TH.dic th_TH/hyph_th_TH.dic fbd19d561c19a13d1901a0b9653e24b4c0c2c81177064266e2acb23bac68145b README_hyph_th_TH.txt 0
hyph_uk_UA.dic uk_UA/hyph_uk_UA.dic ae80645b757d6b57e5cdf34ab6cf7441a2414c60bff2352c7e815c1c12baad3d README_hyph_uk_UA.txt 0
hyph_zu_ZA.dic zu_ZA/hyph_zu_ZA.dic e75802e08127877d38891c140e8989af559665bae82f94d33c340cba2aa9de20 embedded 0
Can't render this file because it has a wrong number of fields in line 2.

Some files were not shown because too many files have changed in this diff Show More