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)
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:
1 parent
94deccb18d
commit
8be5e6e5f3
19 files changed
+654
-33
No files matched your search
+47
-1
@@ -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
|
||||
|
||||
Reference in new issue
Block a user