feat: page titles must be valid, unique file names on Windows and macOS - new/rename/move refuse, lint reports Unportable Titles, new never overwrites (#155)
CI / verify (push) Successful in 1m37s
Release / release (push) Successful in 37s

Files changed:
- CHANGES.md
- README.md
- VERSION
- instructions/page-lifecycle.md
- instructions/wiki-lint/SKILL.md
- kb/CONTRACT.md
- kb/CONVENTIONS.md
- kb/CONVENTIONS.md.template
- tools/CONTRACT.md
- tools/chemenu/commands/_util.py
- tools/chemenu/commands/lint.py
- tools/chemenu/commands/new_page.py
- tools/chemenu/commands/page_ops.py
- tools/chemenu/lint_core.py
- tools/chemenu/tests/test_lint.py
- tools/chemenu/tests/test_new_page.py
- tools/chemenu/tests/test_page_ops.py
- tools/chemenu/tests/test_titles.py
- tools/chemenu/titles.py
This commit is contained in:
torben committed 2026-09-30 06:29:28 +02:00
1 parent 94deccb18d
commit 8be5e6e5f3
19 files changed
+654 -33

No files matched your search

+47 -1
View File
@@ -59,14 +59,19 @@ concern - readable here, never shipped as something to parse.
---
## 7.1.0-beta.32 - 2026-09-29 - dist upgrade --latest: one-command update from the release feed
## 8.0.0-beta.1 - 2026-09-30 - Page titles must form valid, unique file names on Windows and macOS
**Author:** Torben Nehmer
**Breaking Change:** Page titles must form valid, unique file names on Windows and macOS: new and rename refuse forbidden characters, reserved names (including INDEX and COLLECTION), a trailing dot or space, and titles that collide with another page by case or Unicode normalization; lint reports existing violations as hard errors - rename each affected page with tools/wikitool rename
**Migration:** none required - No page format changes; the rule only refuses titles, and each affected page is renamed individually with tools/wikitool rename
<!-- wikitool:bumps -->
**High impact**
- wikitool: one data record per command - `-h`, index and CONTRACT.md render from cli_contract (Gitea #121 Phase 1)
- dist upgrade --latest: one-command update from the release feed
- Page titles must form valid, unique file names on Windows and macOS
**Medium impact**
- CalDAV task-tracker provider (Nextcloud Tasks, iOS Reminders); review reports unknown-value findings instead of skipping them
@@ -103,6 +108,47 @@ concern - readable here, never shipped as something to parse.
- new_page/type_resolver comments no longer claim only entities declare a layout:
<!-- /wikitool:bumps -->
### Page titles must form valid, unique file names on Windows and macOS (Gitea #155)
A title is the wiki's only identifier for a page and becomes the file name one to one, but nothing
checked that the name was usable outside Linux. A corpus written on Linux could not be checked out
on Windows (`CON.md`, `A: B.md`, a trailing dot) or collapsed two pages into one on macOS and
Windows (`Foo.md` and `FOO.md`, or the same accented title in NFC and NFD). The rule is now stated
once, in `kb/CONTRACT.md` § "Titles are identifiers", implemented as pure functions in
`chemenu/titles.py`, and enforced on every platform - a corpus written on Linux is read on the
others.
A title is refused when it is empty, contains one of `< > : " / \ | ? *` or a control character,
ends with a dot or a space, or starts - before its first dot, ignoring case and trailing spaces -
with a Windows device name (`CON`, `PRN`, `AUX`, `NUL`, `COM0`-`COM9`, `LPT0`-`LPT9`, including the
superscript digits) or with `INDEX` or `COLLECTION`, the two names the stack owns next to a page.
Two titles collide when their NFC-normalized, case-folded forms are equal. The full title,
including a type's `title_prefix`, is what is checked.
- `new` checks the title for every type and every root, then the collision against the corpus for
`kb/` pages, then that the target file does not exist - all before anything is created, the
tracker project included. This last check also fixes a data-loss bug found on the way: for the
`root: repo` types, `new instruction --name gates` silently overwrote `instructions/gates.md`.
`new` never overwrites an existing file now.
- `rename --to` is checked the same way, also under `--dry-run`, with the page itself excluded so
a case-only rename (`Foo` to `FOO`) still works. `rename --from` is deliberately never checked:
it is how a page that is already invalid gets fixed.
- `move` refuses a target that an existing entry claims under another case or normalization, for
a single move and in `--reconcile` alike.
- `lint` reports existing violations under **Unportable Titles**, with the colliding paths named
and a `wikitool rename` remedy. The finding is a hard error at every `kb_version` and is
deliberately not migration-gated: there is no migration for it, so `kb_version` never advances
on its account, and each affected page is renamed individually.
The bump is `--major` because a corpus that carries such a title stops passing `lint --fail-on-error`
after the upgrade; a demo/testbed corpus and the shipped instructions are clean. Uncertain and
refused conservatively: whether `COM0`, `LPT0` and the superscript forms are device names on every
Windows version differs, so all of them are refused.
`tools/CONTRACT.md` is regenerated, and `kb/CONTRACT.md`, `kb/CONVENTIONS.md` and its template,
`instructions/page-lifecycle.md`, `instructions/wiki-lint/SKILL.md` and `README.md` carry the
rule.
### dist upgrade --latest: one-command update from the release feed (Gitea #161)
Updating a tarball instance took a manual detour: `version notes`, then fetching the `.tar.gz` and