Skip to content

slug

const EPISODIC_PREFIX_LENGTH: 9 = 9;

The YYYYMMDD- prefix an episodic filename carries. Time is part of an episodic entry’s identity and it never receives a correction in place, so the date belongs in the name; every other type is timeless and correctable, so it gets a bare slug.


const SLUG_FALLBACK: "untitled" = "untitled";

The stem used when a title reduces to nothing sluggable, such as an all-punctuation or non-Latin title. A placeholder beats an empty filename, and memhtml doctor can find these by name.


const SLUG_MAX_LENGTH: 80 = 80;

Maximum slug length in characters, before any collision suffix.

function datePrefix(at): string;

Format an instant as the YYYYMMDD stamp of an episodic filename, in UTC.

Date

string


function filenameFor(input): string;

The filename for a memory: 20260802-slug.html for episodic, slug.html otherwise. The date prefix sits outside the slug’s length budget, because it is identity, not title.

Date

boolean

string

string


function hasDatePrefix(filename): boolean;

True when a filename carries the YYYYMMDD- episodic prefix.

string

boolean


function isSlug(value): boolean;

True when a string is already a valid slug, the fixed point of slugify.

string

boolean


function slugify(title): string;

Kebab-case a title into [a-z0-9-], at most SLUG_MAX_LENGTH characters.

Diacritics are folded to their base letters (déployé ⇒ deploye) rather than dropped, so a title stays recognizable. Runs of separators collapse to one hyphen and the result carries no leading or trailing hyphen, which makes the function idempotent: a slug fed back in comes out unchanged.

Truncation cuts at SLUG_MAX_LENGTH and then trims any hyphen the cut exposed, so a truncated slug is still a valid slug rather than one ending mid-separator.

string

string


function withCollisionOrdinal(slug, ordinal): string;

Append a collision suffix. ordinal is a 1-based ordinal for display in the filename; ordinal 1 is the unsuffixed slug, 2 becomes -2, and so on, matching the -2/-3 convention. The suffix is added inside the length budget, so a maximum-length slug is shortened rather than overflowed.

The result never equals the input, at any slug length. That is what makes the store’s collision loop (packages/store/src/store.ts, pathFor) terminate rather than re-propose the name that collided. It is not free near the length cap: for a slug whose own tail IS the suffix, cutting to make room and appending the suffix can rebuild the slug — and because the cut also trims any hyphen it exposes, the rebuild can recur at MORE than one cut width. The stem is therefore re-cut from its own post-trim length until appending the suffix no longer reproduces the input; each re-cut strictly shortens the stem, so the loop terminates and the result stays inside the budget.

string

number

string