Appearance
Gallery order (reference)
Issue #1241. Part of the media-curation workbench (#1238). Builds on #378/#400 (project→product graph + media--image ingestion).
Two levels of ordering make the media workbench and the classification-run queue present media "most-visible first" — the order a buyer sees on nanawall.com/projects/foo:
- Project display order — which project comes first.
- Per-image position — where an image sits within its project's gallery.
Where the order lives
| Level | Column | Source |
|---|---|---|
| Project display order | drupal_projects.gallery_order | Ingested from Drupal field_gallery_order (#378/#400). Lower sorts first; NULL last. Already populated for the full catalog. |
| Per-image position | media_tags.gallery_position (on the tag_type='project' row) | Set by recompute / manual reorder (#1241). Lower sorts first; NULL last. |
The (project, image) link is the media_tags row itself (tag_type='project', tag_ref=<project uuid>) — so gallery_position lives on that row, no extra join table. It is indexed by media_tags_project_position (tag_type, tag_ref, gallery_position).
Resolution — the combined order
compareGalleryOrder ($lib/server/admin/project-gallery.ts) is the single definition, mirrored in SQL by galleryOrderBy(assetId):
project gallery_order (nulls last)
→ image gallery_position (nulls last)
→ created_at
→ idEach tiebreak degrades to the next, so the order is always total — an asset with no project, or an un-positioned image, still sorts deterministically (after the ordered ones). galleryOrderBy resolves the two keys with index-backed correlated subqueries, so the sort stays bounded at Drupal scale — no table scan (re-verified by npm run verify:media-bounds, #375/#376 metric).
Consumers
- Media workbench (
/admin/media) defaults to thegallerysort — the min-gallery_orderproject's frames first, in position order. The picker keeps the sharednewestdefault (no regression). - Classification-run queue (
loadAnalyzableAssets) works assets in the same order, so the most-visible images are classified first. - Projects navigator (
projectGalleryNavigator) lists projects ingallery_orderwith their image counts — the bounded query the #1242 autocomplete consumes.
Populating per-image position
The exact nanawall.com /projects/foo per-image order lives in a deferred Drupal case-study→paragraph chain (see drupal-projects-gallery-index) that the current media--image dump does not carry. Until that chain is ingested, the position is populated from a deterministic captured order:
npm run gallery:recompute # recomputeProjectGalleryPositions over every projectrecomputeProjectGalleryPositions numbers each project's images by (created_at, id); it is idempotent. setProjectImageOrder(db, projectUuid, orderedMediaIds) sets an explicit order — the entry point for a manual admin reorder and for the deferred Drupal chain, which will fill the same gallery_position column with the exact order and require no consumer change.
Scale
Verified over the ~18.8k-asset local catalog: the workbench gallery sort returns a bounded page in ~0.3s and the classification queue orders 18.8k assets in ~0.4s. verify:media-bounds asserts the gallery page stays bounded and that paginated pages never overlap (a stable total order).