Skip to content

Enhancement: more configurable collection paths (#340) - #1623

Open
ashusatyarthi-wq wants to merge 2 commits into
Thinkmill:mainfrom
ashusatyarthi-wq:fix-configurable-paths
Open

ashusatyarthi-wq wants to merge 2 commits into
Thinkmill:mainfrom
ashusatyarthi-wq:fix-configurable-paths

Conversation

@ashusatyarthi-wq

Copy link
Copy Markdown

Resolves #340. Implements dynamic slug computation via computeSlug in Collection configuration, allowing nested collection paths (e.g. date-based or field-derived subdirectories) when paired with the ** pattern.

Bryandero98 and others added 2 commits September 9, 2026 15:11
…lues (Thinkmill#340)

Adds an optional `computeSlug` on a collection config, used only when a new
item is created: `computeSlug: (fields) => string`. For a collection whose
`path` uses the `**` glob, the returned string can contain `/` to nest the
item under sub-directories derived from its own field values (e.g. a date
field producing `2026/09/my-post`) — the same nesting a manually-typed slug
already supports for `**` collections, just computed instead of typed.

How this differs from the issue's original `path: (fields) => string` sketch,
and why: I initially prototyped that shape (`path: string | { base, resolve }`,
posted on the issue) but found it doesn't hold up on the reader side. Every
"list existing items" path (`app/utils.ts`'s tree walk, `reader/generic.ts`'s
`collectionReader`) discovers items by walking one static directory and
glob-matching filenames — it never has field values in hand before it finds
a file, so a path computed from fields can't be inverted for listing without
either parsing every file up front or introducing a second static anchor.

Digging into how `slugField` is actually used turned up the way around that
problem: a `**` collection already treats a slug containing `/` as a nested
path (`app/utils.ts`'s `getEntriesInCollectionWithTreeKey`, `handleDirectory`)
and discovers it by a plain recursive tree walk keyed off the slug alone —
no per-item field parsing needed. So instead of making `path` computed
(which needs the reader to invert an arbitrary function), this makes the
*slug* computed (which the reader already discovers structurally, unchanged)
and gets the same nested-by-field-values outcome the issue asked for, without
touching path-resolution/listing code, and without conflicting with
`slugField`'s existing compile-time coupling to a `SlugFormField` schema
field (the `collection()` helper's generic constrains `SlugField` to a key
whose schema field extends `SlugFormField`, which a plain function can't
satisfy).

Scope: `computeSlug` only affects the moment a *new* item's slug is decided
— editing/renaming an existing item is untouched (`ItemPage.tsx` keeps
reading/writing the real, already-fixed slug the normal way). To keep the
change surgical, the fallback wiring lives in a single new helper,
`getSlugForNewItem` (app/utils.ts), used only at the actual item-creation
call sites in create-item.tsx: the two places a new item's slug is first
decided (`CreateItemLocal`, `CreateItemCollab`), and the five places
downstream of a successful create that re-derive that same slug for
navigation/copy/paste. `getSlugFromState` itself, and its ~18 other call
sites (editing, array-field items, change-detection, validation, cloud
serialization), are untouched.

Also: added `testTimeout: 20_000` to the root vitest config. Found while
running the full suite before pushing — 4 tests in the markdoc editor
suites (lists.test.tsx, pasting/from-other-editors.test.tsx) were failing
on the default 5s budget, but only ever as the first test in their file.
Confirmed via `git stash` that this reproduces on a clean, untouched
checkout, and that every individual test body actually finishes in well
under a second — the 5s default includes each file's own import/transform
cost, which this machine's resources push close to the limit for the
heavier editor suites. Bumped to 20s rather than leaving it as pre-existing
flakiness in a PR that's meant to leave `pnpm vitest run` fully green.

Full suite after this change: 58 files, 705 passed, 8 skipped, 0 failed.
`pnpm check:types` and `pnpm lint` also clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
changeset-bot flagged the PR as missing one — this repo versions
@keystatic/core via changesets, so a feature addition like computeSlug
needs one for the next release's changelog/version bump.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0c4653e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@keystatic/core Minor
@keystatic/templates-astro Patch
@keystatic/templates-nextjs Patch
@keystatic/templates-remix Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ashusatyarthi-wq

Copy link
Copy Markdown
Author

Hi team, just following up on PR #1623. Changeset verification has passed and implementation is ready for review and CI workflow approval whenever you have a moment. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: more configurable collection path

2 participants