feat: add custom document ordering to collections#1305
Conversation
Adds a `manualSorting` collection option that lets editors manually order docs in a collection (e.g. storefronts) with the order preserved when listing from the API. - Order is stored as a fractional-index string at `sys.sortKey` on both the Drafts and Published copies, so a reorder is a single-doc write and applies to live listings immediately without publishing (and without marking docs as edited). - The CMS gets a "Manual order" sort with drag-and-drop rows (both densities, keyboard accessible) plus "Move to top/bottom" actions, and an "Assign positions" banner that initializes keys for docs created before the option was enabled (or by import scripts). - `listDocs()` defaults to manual order for opted-in collections when no `orderBy`/`query` is passed; `orderBy: 'sys.sortKey'` also works explicitly. New docs are appended to the end of the manual order. - The fractional-indexing helpers (`generateKeyBetween`, `generateNKeysBetween`, `generateKeyAfter`) are vendored from rocicorp/fractional-indexing (MIT) and exported for import scripts. Closes #1303 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Xgdd2x1BnTarkWKftet4q
Renames the collection option (and the derived identifiers, sort value, and "Custom order" UI label) from "manual" to "custom" naming. The stored field remains `sys.sortKey`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Xgdd2x1BnTarkWKftet4q
|
@jeremydw fable kinda nailed this impl pretty well. i just tested and everything looks pretty good to me. for newly added
lmk if any feedback. |
|
Wow, that's great. Two comments:
|
|
i don't think a tooltip is necessary, it's opt-in at the moment so devs would need to know it exists and would probably have a pre-existing defined purpose for it, hopefully it's clear enough from that. re: draft/published, there's a comment about it in the PR desc but basically it currently changes the position in draft mode but doesn't change the modifiedAt time (so the "draft" badge wouldn't appear). i think ai did this to avoid having it show the draft badge, but rethinking this now and it'd probably make sense to show the draft badge as an indication that it requires republishing. |
Reordering docs now updates sys.modifiedAt/modifiedBy on the draft doc (like any other edit), so docs show as having unpublished changes and must be published for the new order to take effect on live listings. The published/scheduled copies are no longer written directly on reorder; the sort key propagates on publish, which copies the draft sys wholesale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Xgdd2x1BnTarkWKftet4q


Summary
Adds support for custom document ordering in collections via a new
customSortingcollection schema option. Editors can now drag documents to reorder them or use "Move to top/bottom" actions, with the order persisted as a fractional-index string insys.sortKey.Key Changes
Fractional indexing library (
packages/root-cms/shared/sort-key.ts): Vendored implementation from rocicorp/fractional-indexing for generating sort keys that maintain order through insertions without rewriting existing keys.CMS UI enhancements:
customSortingis enabled@hello-pangea/dndwith visual drag handlesBackend integration:
cmsSetDocSortKey(): Updates a single doc's sort key across draft/published/scheduled copiescmsAssignSortKeys(): Batch-assigns sort keys with Firestore batch write chunking (500 writes per batch)fetchMaxSortKey(): Queries the current maximum sort key in a collectionsys.modifiedAtto avoid marking docs as editedClient API (
RootCMSClient.listDocs()): Automatically orders results bysys.sortKeywhencustomSortingis enabled and no explicitorderByis provided.Sorting utilities (
doc-sort.ts):sortDocsCustomOrder()function that sorts docs by sort key (keyed docs first) with keyless docs at the end (newest first) for visibility.Schema updates: Added
customSorting?: booleanoption to theCollectiontype and serialized it in the app context.Implementation Details
https://claude.ai/code/session_018Xgdd2x1BnTarkWKftet4q