fix: preserve property defaults and Emacs 31 compatibility

This commit is contained in:
Kinneyzhang 2026-09-08 21:33:14 +08:00
parent 97cb6a6e35
commit 711b0b1abe
15 changed files with 302 additions and 24 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ archive
*.o
*.elc
AGENTS.md
!AGENTS.md

View File

@ -2,6 +2,13 @@
## 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`

View File

@ -28,3 +28,4 @@
- issue026 [ ] [Showcase loses automatic inline policy and test runners can false-green.](../phases/phase-kp-overhaul-20260726/issue_showcase_test_freshness_20260820.md)
- issue027 [x] [Manual integrations remain installed with no projection owner.](../phases/phase-kp-overhaul-20260726/issue_integration_no_projection_20260820.md)
- issue028 [x] [Locked live evaluator can measure stale candidate bytecode.](../phases/phase-kp-overhaul-20260726/issue_live_evaluator_source_freshness_20260820.md)
- issue029 [x] [Emacs 31.1 WERROR rejects obsolete single-binding let macros.](../phases/phase-kp-overhaul-20260726/issue_emacs31_strict_compile_20260901.md)

View File

@ -1,5 +1,38 @@
# 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

View File

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

View File

@ -80,3 +80,15 @@ Stop only when all tasks in this plan have current issue/task evidence,
closed issues have user-visible or contract-level verification, `issue018`
is either genuinely closed by its locked gate or explicitly remains open with
fresh measurements, and the complete diff is committed and reviewed.
## 2026-09-01 Emacs 31 strict-compile follow-up
- `task044` / `issue029` replace the six remaining single-binding
`if-let`/`when-let` forms in production and the three forms in the live
evaluator with their semantically identical starred variants.
- No layout, hyphenation, font-selection, module-path, or evaluator behavior
may change. Existing ERT coverage locks those paths; Emacs 31.1 WERROR byte
compilation is the regression gate for the compatibility defect itself.
- Closure requires root integration compilation, strict production and changed
test-tool compilation, the complete normal and seeded-random ERT suites, C
build/parity fuzz, release/dictionary checks, checkdoc, and diff-check.

View File

@ -1,5 +1,23 @@
# 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.

144
AGENTS.md Normal file
View File

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

View File

@ -253,7 +253,7 @@ caps the narrowest live window at N pixels."
"Unicode whitespace that cannot use the ASCII-space projection path.")
(dolist (property ekp-buffer--owned-properties)
(setf (alist-get property text-property-default-nonsticky) t))
(ekp--make-text-property-default-nonsticky property))
;;; Setup

View File

@ -169,7 +169,7 @@ LANGUAGE identifies the dictionary in that condition."
(lambda (m) (string (string-to-number
(match-string 1 m) 16)))
line))
(when-let ((parsed (ekp-hyphen--parse-pattern line)))
(when-let* ((parsed (ekp-hyphen--parse-pattern line)))
(puthash (car parsed) (cdr parsed) patterns)
(setq maxlen (max maxlen (length (car parsed))))))))
(forward-line 1)))
@ -227,7 +227,7 @@ by default the dictionary's own LEFTHYPHENMIN/RIGHTHYPHENMIN apply
(let ((path (or (and lang (ekp-hyphen--resolve-lang lang)) file)))
(unless path
(signal 'ekp-hyphen-dictionary-not-found (list lang)))
(when-let ((count (gethash path ekp-hyphen--unsupported-cache)))
(when-let* ((count (gethash path ekp-hyphen--unsupported-cache)))
(signal 'ekp-hyphen-unsupported-pattern
(list (or lang path) path count)))
(let ((h (or (gethash path ekp-hyphen--cache)

View File

@ -122,7 +122,7 @@ Use the blank glyph of STRING's Latin font; for a monospace font
that width is the space's own advance."
;; font is monospace, use the pixel of blank
;; as word spacing pixel
(if-let ((font-family (ekp-monospace-p string)))
(if-let* ((font-family (ekp-monospace-p string)))
(ekp--measured-width
(propertize " " 'face `(:family ,font-family)))
(let* ((letter (ekp-get-latin-letter string))
@ -134,14 +134,14 @@ that width is the space's own advance."
(defun ekp-latin-font (string)
"Return the font family used for STRING's Latin letters.
Fall back to the default face family when STRING has no Latin letter."
(if-let ((letter (ekp-get-latin-letter string)))
(if-let* ((letter (ekp-get-latin-letter string)))
(ekp-font-family letter)
(face-attribute 'default :family)))
(defun ekp-cjk-font (string)
"Return the font family used for STRING's CJK characters.
Fall back to the family of a sample CJK glyph when STRING has none."
(if-let ((letter (ekp-get-cjk-letter string)))
(if-let* ((letter (ekp-get-cjk-letter string)))
(ekp-font-family letter)
(ekp-font-family "")))
@ -344,7 +344,7 @@ by merging boxes."
(defun ekp-c-module-dir ()
"Return the C module directory."
(when-let ((root-dir (ekp-root-dir)))
(when-let* ((root-dir (ekp-root-dir)))
(expand-file-name "ekp_c" root-dir)))
(defun ekp-c-module-file ()

14
ekp.el
View File

@ -1790,8 +1790,8 @@ POLICY-ANALYSIS is a precomputed result from `ekp--analyze-policies'."
(while (and (>= tail 0) (= (aref old tail) ?\s))
(setq tail (1- tail)))
(when (>= tail 0)
(when-let ((space
(cl-position ?\s old :from-end t :end (1+ tail))))
(when-let* ((space
(cl-position ?\s old :from-end t :end (1+ tail))))
(1+ space))))))
(defun ekp--para-has-box-type-p (para type)
@ -3606,11 +3606,19 @@ Only property-free, context-stable 1D layouts take this fast path."
'(ekp-glue ekp-soft-break ekp-soft-hyphen ekp-hidden ekp-justified)
"Text properties owned by the lossless render/inversion protocol.")
(defun ekp--make-text-property-default-nonsticky (property)
"Make PROPERTY nonsticky by default in every buffer."
(let ((defaults (copy-tree
(default-toplevel-value
'text-property-default-nonsticky))))
(setf (alist-get property defaults) t)
(set-default-toplevel-value 'text-property-default-nonsticky defaults)))
;; Text typed next to a marker character must never inherit the
;; marker: a self-inserted char inheriting `ekp-glue' would be treated
;; as a synthesized space by the next unjustification and deleted.
(dolist (prop ekp--layout-marker-properties)
(setf (alist-get prop text-property-default-nonsticky) t))
(ekp--make-text-property-default-nonsticky prop))
(defun ekp--hide-string (string)
"Return STRING marked `ekp-hidden' and displayed as nothing."

View File

@ -3481,8 +3481,8 @@ a zero budget still makes progress (exactly one chunk)."
(ekp-buffer-test--with-text ""
(text-mode)
(let* ((ekp-use-c-module nil)
(suffix (copy-sequence "uX"))
(line-start-extra (copy-sequence ""))
(suffix (copy-sequence (string-as-multibyte "uX")))
(line-start-extra (copy-sequence "X"))
(ekp-number-unit-suffixes (list suffix))
(ekp-token-break-policies '((number-unit . no-break)))
(ekp-kinsoku-profile 'custom)
@ -3499,18 +3499,20 @@ a zero budget still makes progress (exactly one chunk)."
(apply original arguments))))
(setq first (cdr (ekp-buffer--live-plan-entry text width)))
(setq key (caar ekp-buffer--live-plan-cache))
(should (multibyte-string-p suffix))
(should (multibyte-string-p line-start-extra))
(should (seq-some (lambda (string) (equal string "uX"))
(ekp-buffer-test--strings-in-tree key)))
(should (seq-some (lambda (string) (equal string ""))
(should (seq-some (lambda (string) (equal string "X"))
(ekp-buffer-test--strings-in-tree key)))
(store-substring suffix 1 "Y")
(store-substring line-start-extra 0 "")
(store-substring line-start-extra 1 "Y")
(let ((changed (cdr (ekp-buffer--live-plan-entry text width))))
(should-not (eq changed first))
(should (= calls 2)))
(should (seq-some (lambda (string) (equal string "uX"))
(ekp-buffer-test--strings-in-tree key)))
(should (seq-some (lambda (string) (equal string ""))
(should (seq-some (lambda (string) (equal string "X"))
(ekp-buffer-test--strings-in-tree key)))))))
(provide 'ekp-buffer-tests)

View File

@ -71,19 +71,19 @@
(defun ekp-live-commit-evaluator--env-number (name fallback)
"Return numeric environment variable NAME, or FALLBACK."
(if-let ((value (getenv name)))
(if-let* ((value (getenv name)))
(string-to-number value)
fallback))
(defun ekp-live-commit-evaluator--env-numbers (name fallback)
"Return comma-separated numeric environment variable NAME, or FALLBACK."
(if-let ((value (getenv name)))
(if-let* ((value (getenv name)))
(mapcar #'string-to-number (split-string value "," t "[ \t]+"))
fallback))
(defun ekp-live-commit-evaluator--env-strings (name fallback)
"Return comma-separated string environment variable NAME, or FALLBACK."
(if-let ((value (getenv name)))
(if-let* ((value (getenv name)))
(split-string value "," t "[ \t]+")
fallback))

View File

@ -2052,7 +2052,13 @@ module is bypassed automatically (it has no looseness support)."
'(ekp-glue ekp-soft-break ekp-soft-hyphen ekp-hidden
ekp-justified)))
(dolist (property ekp--layout-marker-properties)
(should (eq (alist-get property text-property-default-nonsticky) t))))
(should
(eq (alist-get property
(default-value 'text-property-default-nonsticky))
t))
(with-temp-buffer
(should
(eq (alist-get property text-property-default-nonsticky) t)))))
(ert-deftest ekp-test-layout-plan-maps-source-gaps-and-breaks ()
"The semantic plan must retain source offsets for every visual decision."
@ -2813,8 +2819,8 @@ returned the stale looseness-0 layout for the same (string, width)."
"Policy-analysis cache keys must own mutable public policy strings."
(ekp-tests--with-clean-state
(let* ((ekp-use-c-module nil)
(suffix (copy-sequence "uX"))
(line-start-extra (copy-sequence ""))
(suffix (copy-sequence (string-as-multibyte "uX")))
(line-start-extra (copy-sequence "X"))
(ekp-number-unit-suffixes (list suffix))
(ekp-token-break-policies '((number-unit . no-break)))
(ekp-kinsoku-profile 'custom)
@ -2823,6 +2829,8 @@ returned the stale looseness-0 layout for the same (string, width)."
(width 16)
cache-key first-plan)
(setq first-plan (ekp-layout-plan text width))
(should (multibyte-string-p suffix))
(should (multibyte-string-p line-start-extra))
(should
(seq-some
(lambda (interval)
@ -2835,13 +2843,13 @@ returned the stale looseness-0 layout for the same (string, width)."
(should cache-key)
(should (seq-some (lambda (string) (equal string "uX"))
(ekp-tests--strings-in-tree cache-key)))
(should (seq-some (lambda (string) (equal string ""))
(should (seq-some (lambda (string) (equal string "X"))
(ekp-tests--strings-in-tree cache-key)))
(store-substring suffix 1 "Y")
(store-substring line-start-extra 0 "")
(store-substring line-start-extra 1 "Y")
(should (seq-some (lambda (string) (equal string "uX"))
(ekp-tests--strings-in-tree cache-key)))
(should (seq-some (lambda (string) (equal string ""))
(should (seq-some (lambda (string) (equal string "X"))
(ekp-tests--strings-in-tree cache-key)))
(let ((changed-plan (ekp-layout-plan text width)))
(should-not