Switch the post-directory field separator from -- to ~ (AI)

A double-hyphen separator collides with an easy authoring typo: an
accidental extra "-" in a kebab-case slug (e.g. "hello--world")
silently misparses as slug "hello" + an invalid date, dropping the
article from the index with no visible error. "~" isn't something
anyone would type by accident while writing a normal slug, and
(like "-") is in the URI "unreserved" set, so it never needs
percent-encoding either.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
This commit is contained in:
biosiasandClaude Sonnet 5 committed 2026-09-12 12:32:13 +02:00
1 parent 122593da24
commit 885178d1e7
2 files changed
+19 -13

No files matched your search

+10 -6
View File
@@ -6,7 +6,7 @@ A minimal CMS where articles are Markdown files rendered in the browser. No buil
1. The browser traverses `cmjs/articles/` via a server directory index — discovering authors and their post directories; nginx JSON autoindex is preferred, with HTML autoindexes supported as a fallback.
2. Sorting, filtering, and publish status all come from the post directory's own *name*
(`slug--DDMMYYYY--tags`) — knowable straight from the listing that discovers it, with no
(`slug~DDMMYYYY~tags`) — knowable straight from the listing that discovers it, with no
further request per article. A `.md` file's actual content (title, description, image) is
only fetched once that article is about to be shown — the current page's cards, a single
article, or a search — not for the whole site up front. See "Adding an article" and "Article
@@ -77,7 +77,7 @@ cmjs/
└── author/ # One directory per author
├── author.md # Author profile (optional)
├── avatar.jpg # Optional — referenced by author.md's `avatar:` field
└── slug--DDMMYYYY--tag1+tag2/ # One directory per post — see "Adding an article"
└── slug~DDMMYYYY~tag1+tag2/ # One directory per post — see "Adding an article"
├── slug.md # Article content + frontmatter
└── cover.jpg # Optional — referenced by slug.md's `image:` field
```
@@ -88,7 +88,7 @@ An article's directory *name* is where its publish date, tags, category, and dra
live — not in separate files, and not in frontmatter. The format is:
```
<slug>--<DDMMYYYY>--<tag1>+<tag2>+…
<slug>~<DDMMYYYY>~<tag1>+<tag2>+…
```
- `<slug>` — the stable identifier used in the article's URL (`#post/author/slug`). Treat this
@@ -98,9 +98,13 @@ live — not in separate files, and not in frontmatter. The format is:
- `<tag1>+<tag2>+…` — zero or more tag ids, joined with `+`. A tag id matching `expa-YYYY` is
treated as the article's "year"/category, shown as a badge linking to `#category/expa-YYYY`;
every other tag id is a normal topic tag, shown as a pill linking to `#tag/tag-id`. Omit this
whole segment (and its leading `--`) if the article has no tags.
whole segment (and its leading `~`) if the article has no tags.
A leading underscore on the whole directory name — `_<slug>--...` — marks the article as a
The separator is `~` (tilde), not a hyphen — deliberately, since a slug is expected to be plain
kebab-case (single hyphens only) and `~` isn't something you'd type by accident while writing
one, unlike an extra `-`.
A leading underscore on the whole directory name — `_<slug>~...` — marks the article as a
**draft**: it's excluded from the list, search, and pagination, but still reachable directly by
URL. Remove the underscore to publish it.
@@ -108,7 +112,7 @@ Steps:
1. Create the author directory if it doesn't exist: `cmjs/articles/author/`
2. Create the post directory using the naming convention above, e.g.
`cmjs/articles/author/my-article--16062026--kometa+expa-2026/`
`cmjs/articles/author/my-article~16062026~kometa+expa-2026/`
3. Create the Markdown file inside, named after the slug: `my-article.md`
4. Optionally add a cover image beside it too, e.g. `photo.jpg`, and reference it from
frontmatter with `image: photo.jpg`. Images live next to the article that uses them, not in a
+9 -7
View File
@@ -151,16 +151,18 @@ function excerpt(content) {
// A post directory's own name is the sole authoritative source of its
// slug, publish date, tags/category, and draft status — see the README's
// "Adding an article" section. Format: `<slug>--<DDMMYYYY>[--tag1+tag2+…]`,
// optionally prefixed with `_` for draft. Splitting on the whole name is
// deliberate: a slug is expected not to contain "--" itself, the same way
// it's already expected not to contain "/". Returns null for any directory
// name that doesn't match — callers treat that as a silent skip, e.g. a
// stray directory that isn't a post at all.
// "Adding an article" section. Format: `<slug>~<DDMMYYYY>[~tag1+tag2+…]`,
// optionally prefixed with `_` for draft. `~` (rather than e.g. "--") is
// deliberate: a slug is expected to be plain kebab-case, and "~" isn't a
// character anyone would type by accident while writing one — unlike a
// double-hyphen separator, which collides with an easy slug typo (an extra
// "-") and silently drops the whole article instead of erroring. Returns
// null for any directory name that doesn't match — callers treat that as a
// silent skip, e.g. a stray directory that isn't a post at all.
function parseSlugDirName(name) {
const draft = name.startsWith('_');
const core = draft ? name.slice(1) : name;
const [slug, dateSegment, tagsSegment] = core.split('--');
const [slug, dateSegment, tagsSegment] = core.split('~');
if (!slug || !dateSegment || !/^\d{8}$/.test(dateSegment)) return null;
const day = dateSegment.slice(0, 2);
const month = dateSegment.slice(2, 4);