Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5785405495 | ||
|
|
711b0b1abe | ||
|
|
a3d20aa87e | ||
|
|
97cb6a6e35 | ||
|
|
d216c6b761 | ||
|
|
3d3dda6688 | ||
|
|
cfa7c18beb | ||
|
|
5f78cae0b1 | ||
|
|
01001b9008 | ||
|
|
5efd4d113b | ||
|
|
db6e6b98ee | ||
|
|
19b8c9050e | ||
|
|
60b299b3f4 | ||
|
|
c47abf7529 | ||
|
|
dbb3809d7c | ||
|
|
c93786570b | ||
|
|
6a8c7e04f5 | ||
|
|
183f256020 | ||
|
|
888a4019f4 | ||
|
|
e823d89a4a | ||
|
|
665164911f | ||
|
|
83352c4571 | ||
|
|
fd1210e557 |
88
.github/workflows/ci.yml
vendored
88
.github/workflows/ci.yml
vendored
@ -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
2
.gitignore
vendored
@ -4,5 +4,5 @@ archive
|
||||
*.dll
|
||||
*.o
|
||||
*.elc
|
||||
.phrase/
|
||||
AGENTS.md
|
||||
!AGENTS.md
|
||||
|
||||
122
.phrase/docs/CHANGE.md
Normal file
122
.phrase/docs/CHANGE.md
Normal 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
31
.phrase/docs/ISSUES.md
Normal 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 60–70 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)
|
||||
150
.phrase/modules/agent-browser.md
Normal file
150
.phrase/modules/agent-browser.md
Normal 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
|
||||
```
|
||||
51
.phrase/modules/code-simplier.md
Normal file
51
.phrase/modules/code-simplier.md
Normal 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.
|
||||
59
.phrase/modules/copywriting.md
Normal file
59
.phrase/modules/copywriting.md
Normal 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?
|
||||
70
.phrase/modules/linus_coding.md
Normal file
70
.phrase/modules/linus_coding.md
Normal 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
64
.phrase/modules/pr_faq.md
Normal 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.*
|
||||
@ -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).
|
||||
@ -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).
|
||||
@ -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`
|
||||
27
.phrase/phases/phase-doc-improvement-20260125/change_log.md
Normal file
27
.phrase/phases/phase-doc-improvement-20260125/change_log.md
Normal 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`.
|
||||
@ -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).
|
||||
@ -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.
|
||||
@ -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)
|
||||
190
.phrase/phases/phase-kp-overhaul-20260726/HANDOFF.md
Normal file
190
.phrase/phases/phase-kp-overhaul-20260726/HANDOFF.md
Normal 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/双空格/
|
||||
超长词 × 随机宽度 1–300px,断言 ① 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 | 全角字母/数字(ABC123)被当标点附着到前字 | `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 / k≤n 界内)、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`。
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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 60–70 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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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
|
||||
33–85 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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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 1k–8k 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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.158–1.326 ms for C and
|
||||
1.429–1.438 ms for pure Elisp; hard-boundary p99 is 1.251–1.363 ms and
|
||||
1.457–1.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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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 33–85 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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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 1–64px 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 1–64px 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 1–64px 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.
|
||||
@ -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.
|
||||
@ -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`.
|
||||
@ -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`.
|
||||
@ -0,0 +1,56 @@
|
||||
# Issue: C Resize Latency 2026-07-29
|
||||
|
||||
## issue020 [x] C-backed resize reflow still takes roughly 60–70 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 60–70 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).
|
||||
@ -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)
|
||||
@ -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`.
|
||||
@ -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`.
|
||||
@ -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`.
|
||||
@ -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`
|
||||
@ -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`.
|
||||
@ -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 1–64px 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`.
|
||||
@ -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.158–1.326 ms for C and 1.429–1.438 ms for pure
|
||||
Elisp. Hard-boundary p99 is 1.251–1.363 ms and 1.457–1.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).
|
||||
@ -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).
|
||||
@ -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).
|
||||
@ -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`).
|
||||
@ -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).
|
||||
@ -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; 1k–8k 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`.
|
||||
@ -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 33–85 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).
|
||||
@ -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`.
|
||||
@ -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).
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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 1–64px 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.
|
||||
@ -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
|
||||
task037–task042. 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.
|
||||
@ -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 1k–8k 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 1–64px 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 1–64px 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.158–1.326 ms for C
|
||||
and 1.429–1.438 ms for pure Elisp; hard-boundary p99 is
|
||||
1.251–1.363 ms and 1.457–1.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 60–70 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.
|
||||
@ -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.
|
||||
82
.phrase/phases/phase-p1p2-20260726/NOTES.md
Normal file
82
.phrase/phases/phase-p1p2-20260726/NOTES.md
Normal 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
144
AGENTS.md
Normal 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. 协作表达提示
|
||||
- 解释方案时优先描述用户操作(快捷键/鼠标/命令)、可见反馈、撤销/失败路径、边界情况。
|
||||
- 引用文档时用“文件名 + 小节”口语化说明,不逐字背诵。
|
||||
- 提供可选方案时说明它们属于当前还是后续里程碑,帮助用户决策。
|
||||
191
CHANGELOG.md
191
CHANGELOG.md
@ -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.158–1.326 ms for C and 1.429–1.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,000–8,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:
|
||||
|
||||
@ -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
|
||||
|
||||
308
DEVELOPER.md
308
DEVELOPER.md
@ -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 k−1 |
|
||||
| `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 4–7.
|
||||
`ekp-c-set-penalties' takes 4–8. 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` (4–7 args): called by `ekp--c-sync-params`
|
||||
- `ekp-c-set-penalties` (4–8 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
|
||||
|
||||
247
DEVELOPER_ZH.md
247
DEVELOPER_ZH.md
@ -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]` = 到盒 k−1 结束的连续空格盒总宽 |
|
||||
| `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 (若盒 k−1 处断词,再加连字符宽)
|
||||
理想/最小/最大三个值均 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` 4–7 参。
|
||||
`ekp-c-set-penalties` 4–8 参。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`(4–7 参数):`ekp--c-sync-params` 在**每次**
|
||||
- `ekp-c-set-penalties`(4–8 参数):`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
56
Docs/RELEASING.md
Normal 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.
|
||||
570
Docs/REPOSITORY_AUDIT_20260728.md
Normal file
570
Docs/REPOSITORY_AUDIT_20260728.md
Normal file
@ -0,0 +1,570 @@
|
||||
# emacs-kp 仓库系统审计
|
||||
|
||||
> 审计日期:2026-07-28
|
||||
> 审计基线:`main@29cef97`(本地 tag `v1.0.0`)
|
||||
> 范围:Elisp 核心、C 动态模块、buffer/region 集成、测试、性能、兼容性、文档、发布与扩展边界
|
||||
> 性质:问题盘点与方向判断,不包含运行代码修复
|
||||
|
||||
## 后续状态
|
||||
|
||||
- `task002` 已在审计后的工作树中解决 P1-01:DP key 现在包含行宽、
|
||||
looseness 与全部六个运行时代价参数,并按结构相等比较。
|
||||
- `task003` 已继续解决 P2-01:auto-CWS 默认值现在同时进入段落哈希键
|
||||
与最近段落快路径;`issue001` 的两个缓存缺陷均已闭环。
|
||||
- `task004` 已解决 P1-02:保存从逻辑副本序列化,文件系统错误、编码
|
||||
错误与用户中断均不再改变显示 buffer;`issue002` 已闭环。
|
||||
- `task005` 已解决 P1-03/P2-06:copy filter 可组合并恢复,integration
|
||||
生命周期由 auto mode 与实际 justified span 共同决定;`issue003`
|
||||
已闭环。
|
||||
- `task006` 已解决 P1-04:测试 fixture 完整隔离配置,parshape 用例
|
||||
经过公开分派路径;CI、可复现乱序和逐例全新进程入口共同阻断顺序
|
||||
假绿,`issue004` 已闭环。
|
||||
- `task007` 已解决 P2-05:GUI 矩阵共享结构化结果与报告断言边界,
|
||||
任一失败行在 batch 中退出 1;强制失败负控 2/2,真实 GUI 七场景
|
||||
7/7,`issue007` 已闭环。
|
||||
- `task008` 已解决 P2-02:C 1.6 在提取前验证单段/批量完整 schema,
|
||||
非法调用统一 signal `ekp-c-invalid-input`,有效 int32 输入使用
|
||||
int64 中间量;模块 signal 不再被 Elisp 静默吞掉。
|
||||
- `task009` 已解决 P2-03:默认构建改为 portable,native/debug/
|
||||
sanitize 需显式选择;交互命令以 argv 和 `default-directory` 启动
|
||||
make,含空格路径实测通过。`issue005` 已闭环。
|
||||
- `task010` 已解决 P2-07 的仓库内边界:CI action 与 package-lint
|
||||
固定到完整提交,Windows 运行 Elisp 基线,`.phrase` 改为可追踪事实
|
||||
来源,release gate 与远端/产物 checklist 分责。未执行 push、tag
|
||||
变更或发布。
|
||||
- `task011` 已解决 P2-04:49 份词典固定来源路径与 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 入口 | PASS,94/94 | Emacs 30.2,加载 C 1.5 |
|
||||
| C/Elisp fuzz | PASS,300/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 像素矩阵 | PASS,7/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-hyphen:Liang pattern 编译与断词
|
||||
├─ ekp.el:ekp-para、前缀数组、缓存
|
||||
├─ DP
|
||||
│ ├─ Elisp 1D / 2D
|
||||
│ └─ C 1D(15 字段边界,批量按段落并行)
|
||||
└─ renderer:glue、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 使用动态绑定恢复全部
|
||||
> tunable;parshape 用例绑定真实 `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 范围做整体验证,非法输入使用专用 condition;DP 中间
|
||||
> 行宽/前缀差/剩余空间改为 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 arithmetic,API 边缘再收窄。
|
||||
- 增加 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 gate:commit/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
|
||||
> sentinel;tokenizer 与密集插入改为片段单次拼接。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/94;fuzz 300/300;GUI 数值矩阵 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
37
dictionaries/LICENSES.md
Normal 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
54
dictionaries/MANIFEST.tsv
Normal 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
Loading…
Reference in New Issue
Block a user