@elytracms/studio 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/convex/cms.ts +49 -7
  2. package/convex/delivery.ts +14 -16
  3. package/convex/references.ts +38 -2
  4. package/convex/schema.ts +6 -1
  5. package/dist/components/assets/assets-store.js +41 -30
  6. package/dist/components/assets/assets-store.js.map +1 -1
  7. package/dist/components/auth-context.d.ts +4 -2
  8. package/dist/components/auth-context.js +19 -19
  9. package/dist/components/auth-context.js.map +1 -1
  10. package/dist/components/cms/cms-store.js +5 -4
  11. package/dist/components/cms/cms-store.js.map +1 -1
  12. package/dist/components/cms/collection-table.js +10 -3
  13. package/dist/components/cms/collection-table.js.map +1 -1
  14. package/dist/components/cms/detail-editor.js +36 -9
  15. package/dist/components/cms/detail-editor.js.map +1 -1
  16. package/dist/components/cms/field-control.d.ts +2 -1
  17. package/dist/components/cms/field-control.js +161 -73
  18. package/dist/components/cms/field-control.js.map +1 -1
  19. package/dist/components/cms/versions-pane.js +28 -16
  20. package/dist/components/cms/versions-pane.js.map +1 -1
  21. package/dist/components/project-session.d.ts +11 -0
  22. package/dist/components/project-session.js.map +1 -1
  23. package/dist/components/routes/routes-shell.js +22 -22
  24. package/dist/components/routes/routes-shell.js.map +1 -1
  25. package/dist/components/studio-context.js +5 -1
  26. package/dist/components/studio-context.js.map +1 -1
  27. package/dist/lib/cms/published-documents.d.ts +6 -6
  28. package/dist/lib/cms/published-documents.js +21 -58
  29. package/dist/lib/cms/published-documents.js.map +1 -1
  30. package/dist/lib/cms-views/view-model.d.ts +7 -3
  31. package/dist/lib/cms-views/view-model.js +11 -5
  32. package/dist/lib/cms-views/view-model.js.map +1 -1
  33. package/dist/lib/data/collection-content.js +7 -5
  34. package/dist/lib/data/collection-content.js.map +1 -1
  35. package/dist/lib/data/data-store.js +34 -24
  36. package/dist/lib/data/data-store.js.map +1 -1
  37. package/dist/lib/persistence/convex-adapter.js +22 -0
  38. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  39. package/dist/lib/persistence/index.js +6 -2
  40. package/dist/lib/persistence/index.js.map +1 -1
  41. package/dist/lib/query/index.d.ts +6 -0
  42. package/dist/lib/query/index.js +6 -0
  43. package/dist/lib/query/index.js.map +1 -0
  44. package/dist/lib/query/operations-query.d.ts +85 -0
  45. package/dist/lib/query/operations-query.js +124 -0
  46. package/dist/lib/query/operations-query.js.map +1 -0
  47. package/dist/lib/references/reference-model.js +7 -1
  48. package/dist/lib/references/reference-model.js.map +1 -1
  49. package/dist/lib/references/references-store.d.ts +5 -10
  50. package/dist/lib/references/references-store.js +55 -77
  51. package/dist/lib/references/references-store.js.map +1 -1
  52. package/dist/ui/collapsible-section.d.ts +20 -0
  53. package/dist/ui/collapsible-section.js +18 -0
  54. package/dist/ui/collapsible-section.js.map +1 -0
  55. package/dist/ui/index.d.ts +1 -0
  56. package/dist/ui/index.js +1 -0
  57. package/dist/ui/index.js.map +1 -1
  58. package/package.json +7 -2
@@ -1 +1 @@
1
- {"version":3,"file":"view-model.js","sourceRoot":"","sources":["../../../src/lib/cms-views/view-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\nimport { documentDefaultLocale, resolveField, titleFieldOf } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, FieldDef, LocaleConfig } from '@elytracms/core/cms-core'\nimport {\n FILTER_OPERATORS,\n compareListEntries,\n filterScalarOf,\n filterableFields,\n matchesCondition,\n validateWhere,\n} from '@elytracms/core/content'\nimport type {\n FilterCondition,\n FilterOperator,\n FilterScalar,\n NormalizedCondition,\n WhereInput,\n} from '@elytracms/core/content'\n\n/**\n * The pure table-view model for the CMS collection table (EC-169).\n *\n * A view bundles three things: filters in the closed AD-3 vocabulary\n * (`eq`/`in`/`lt`/`lte`/`gt`/`gte`, AND-only — exactly `@elytracms/core/content`'s\n * query model, evaluated with the same matching/ordering primitives), a sort\n * (field + direction), and the visible/ordered columns. Everything here is\n * plain data + functions so filtering, dirtiness, and column edits are\n * testable without a DOM.\n *\n * Filters are offered ONLY on fields the schema declares `filterable: true`\n * (EC-143), plus one built-in option: the document's publication state. The\n * state is document metadata, not a schema field — it powers the\n * \"show me drafts → bulk publish\" working set and still speaks only the\n * closed vocabulary (`eq`/`in`).\n */\n\n// --- Schemas (single source of truth — persisted view records reuse these) ----\n\nexport const filterScalarSchema = z.union([z.string(), z.number(), z.boolean()])\n\nexport const filterClauseSchema = z.object({\n /** A schema field name, or {@link STATE_FIELD} for the publication state. */\n field: z.string().min(1),\n op: z.enum(FILTER_OPERATORS),\n /** `in` carries an array of scalars; every other operator one scalar. */\n value: z.union([filterScalarSchema, z.array(filterScalarSchema)]),\n})\nexport type FilterClause = z.infer<typeof filterClauseSchema>\n\nexport const viewSortSchema = z.object({\n field: z.string().min(1),\n dir: z.enum(['asc', 'desc']),\n})\nexport type ViewSort = z.infer<typeof viewSortSchema>\n\nexport const tableViewStateSchema = z.object({\n /** AND-only list of conditions (no OR, no nesting — vision AD-3). */\n filters: z.array(filterClauseSchema),\n sort: viewSortSchema.nullable(),\n /**\n * The canonical column order (field names) — EVERY column, visible or not\n * (EC-213). Order is independent of visibility: hiding then re-showing a\n * column keeps its place. Schema fields absent here resolve in at the end\n * (see {@link resolveColumns}).\n */\n columns: z.array(z.string().min(1)),\n /**\n * The subset of {@link TableViewState.columns} that is hidden. Defaults to\n * `[]` so views persisted before EC-213 (no `hidden` key) load unchanged.\n */\n hidden: z.array(z.string().min(1)).default([]),\n})\nexport type TableViewState = z.infer<typeof tableViewStateSchema>\n\n// --- The built-in publication-state filter column ------------------------------\n\n/**\n * Sentinel field key for the publication state. `$`-prefixed so it can never\n * collide with a schema field name (`fieldDefSchema` names are identifiers).\n */\nexport const STATE_FIELD = '$state'\n\nconst STATE_VALUES = ['draft', 'published'] as const\n\n/** One field the filter builder may offer. */\nexport interface FilterableFieldOption {\n /** Clause field key: a schema field name, or {@link STATE_FIELD}. */\n field: string\n label: string\n /** The schema field def, or `null` for the built-in publication state. */\n def: FieldDef | null\n}\n\nexport const STATE_OPTION: FilterableFieldOption = {\n field: STATE_FIELD,\n label: 'State',\n def: null,\n}\n\n/**\n * The closed list of fields the filter builder offers: exactly the schema's\n * `filterable: true` fields (EC-143) plus the publication state. Nothing else\n * is ever offered — the UI builds queries, it does not invent a language.\n */\nexport function filterableFieldOptions(collection: CollectionDef): FilterableFieldOption[] {\n const schemaOptions = filterableFields(collection).map((field) => ({\n field: field.name,\n label: field.form?.label ?? field.name,\n def: field,\n }))\n return [...schemaOptions, STATE_OPTION]\n}\n\nexport function filterOptionFor(\n collection: CollectionDef,\n field: string,\n): FilterableFieldOption | undefined {\n return filterableFieldOptions(collection).find((option) => option.field === field)\n}\n\n// --- Operators per field type ---------------------------------------------------\n\n/** Display order + labels for the closed vocabulary. */\nexport const OPERATOR_LABELS: Record<FilterOperator, string> = {\n eq: 'equals',\n in: 'is one of',\n lt: 'less than',\n lte: 'at most',\n gt: 'greater than',\n gte: 'at least',\n}\n\nconst ORDERED_OPERATORS: readonly FilterOperator[] = ['eq', 'in', 'lt', 'lte', 'gt', 'gte']\n\n/**\n * The vocabulary subset that makes sense for a field's type. Always a subset\n * of the closed `FILTER_OPERATORS` — never an extension.\n */\nexport function operatorsFor(option: FilterableFieldOption): FilterOperator[] {\n if (option.def === null) return ['eq', 'in'] // publication state\n switch (option.def.type) {\n case 'boolean':\n return ['eq']\n case 'select':\n case 'relation':\n case 'asset':\n return ['eq', 'in']\n default:\n return [...ORDERED_OPERATORS]\n }\n}\n\n/** A sensible starting clause when a field is added to the builder. */\nexport function defaultClause(option: FilterableFieldOption): FilterClause {\n if (option.def === null) return { field: STATE_FIELD, op: 'eq', value: 'published' }\n switch (option.def.type) {\n case 'boolean':\n return { field: option.field, op: 'eq', value: true }\n case 'number':\n return { field: option.field, op: 'eq', value: 0 }\n case 'select':\n return { field: option.field, op: 'eq', value: option.def.options[0]?.value ?? '' }\n default:\n return { field: option.field, op: 'eq', value: '' }\n }\n}\n\n// --- Text ⇄ scalar coercion (the value inputs of the builder) -------------------\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/\n\n/** Coerce one text input into the scalar a clause compares against. */\nexport function scalarFromText(\n option: FilterableFieldOption | undefined,\n raw: string,\n): FilterScalar {\n const type = option?.def?.type\n if (type === 'number') {\n const trimmed = raw.trim()\n if (trimmed === '') return raw // an emptied input must not become eq 0\n const parsed = Number(trimmed)\n return Number.isNaN(parsed) ? raw : parsed\n }\n if (type === 'boolean') return raw === 'true'\n // Stored dates are full ISO strings; a date-input value compares correctly\n // once expanded to midnight UTC (ISO strings order by code units).\n if (type === 'date' && DATE_ONLY.test(raw)) return `${raw}T00:00:00.000Z`\n return raw\n}\n\n/** Parse a comma-separated list for the `in` operator. */\nexport function scalarListFromText(\n option: FilterableFieldOption | undefined,\n raw: string,\n): FilterScalar[] {\n return raw\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '')\n .map((part) => scalarFromText(option, part))\n}\n\nexport function scalarListToText(value: FilterScalar | FilterScalar[]): string {\n const list = Array.isArray(value) ? value : [value]\n return list.map(String).join(', ')\n}\n\n// --- Clause validation -----------------------------------------------------------\n\n/** A structured problem with one clause (by index in the filter list). */\nexport interface ClauseIssue {\n index: number\n message: string\n}\n\n/**\n * Validate clauses against the collection schema. Schema-field clauses run\n * through the canonical `validateWhere` (EC-143: `filterable` enforcement +\n * operator/value shape); state clauses check the `eq`/`in` subset and the\n * draft/published value set. Invalid clauses (e.g. a persisted view whose\n * field is no longer filterable) are reported — never silently applied.\n */\nexport function validateClauses(\n collection: CollectionDef,\n clauses: readonly FilterClause[],\n): ClauseIssue[] {\n const issues: ClauseIssue[] = []\n clauses.forEach((clause, index) => {\n if (clause.field === STATE_FIELD) {\n if (clause.op !== 'eq' && clause.op !== 'in') {\n issues.push({ index, message: 'State filters support only \"eq\" and \"in\".' })\n return\n }\n const values = Array.isArray(clause.value) ? clause.value : [clause.value]\n if (!values.every((value) => (STATE_VALUES as readonly unknown[]).includes(value))) {\n issues.push({ index, message: 'State must be \"draft\" or \"published\".' })\n }\n return\n }\n const where: WhereInput = {\n [clause.field]: { [clause.op]: clause.value } as FilterCondition,\n }\n const errors = validateWhere(collection, where)\n if (errors.length > 0) issues.push({ index, message: errors[0]!.message })\n })\n return issues\n}\n\n// --- Matching & sorting (delegates to @elytracms/core/content's primitives) -------------\n\n/**\n * Published-ness source for the `$state` pseudo-field (EC-224): the version\n * POINTER, supplied by the caller as `cms.isPublished`. Defaults to \"nothing\n * published\" — a bare row carries no publish signal now that the flat `state`\n * field is gone, so the table (which knows the pointer) passes the real\n * predicate; fixtures/tests that don't surface `$state` get the safe default.\n */\nexport type IsPublished = (doc: CmsDocument) => boolean\nconst defaultIsPublished: IsPublished = () => false\n\n/** The scalar a document exposes for a clause/sort field, locale-resolved. */\nfunction scalarOf(\n collection: CollectionDef,\n doc: CmsDocument,\n field: string,\n localeConfig: LocaleConfig,\n isPublished: IsPublished,\n): FilterScalar | null {\n if (field === STATE_FIELD) return isPublished(doc) ? 'published' : 'draft'\n if (!collection.fields.some((f) => f.name === field)) return null\n const locale = documentDefaultLocale(doc, localeConfig)\n return filterScalarOf(resolveField(collection, doc, field, locale, localeConfig).value)\n}\n\n/** Does a document satisfy one clause? Same semantics as the content accessors. */\nexport function clauseMatches(\n collection: CollectionDef,\n doc: CmsDocument,\n localeConfig: LocaleConfig,\n clause: FilterClause,\n isPublished: IsPublished = defaultIsPublished,\n): boolean {\n const value = scalarOf(collection, doc, clause.field, localeConfig, isPublished)\n const condition = { [clause.op]: clause.value } as NormalizedCondition\n return matchesCondition(value, condition)\n}\n\n/**\n * Sort documents by one column in the canonical deterministic order\n * (`compareListEntries`: typed value order, id-ascending tiebreak). Unlike the\n * content accessors, the *table* may sort any rendered column — sorting is a\n * presentation concern here; the `filterable` flag governs filters.\n */\nexport function sortViewDocuments(\n collection: CollectionDef,\n docs: readonly CmsDocument[],\n localeConfig: LocaleConfig,\n sort: ViewSort | null,\n isPublished: IsPublished = defaultIsPublished,\n): CmsDocument[] {\n if (sort === null) return [...docs]\n const positions = new Map(\n docs.map((doc) => [doc.id, scalarOf(collection, doc, sort.field, localeConfig, isPublished)]),\n )\n return [...docs].sort((a, b) =>\n compareListEntries(\n { id: a.id, sortValue: positions.get(a.id) ?? null },\n { id: b.id, sortValue: positions.get(b.id) ?? null },\n sort.dir,\n ),\n )\n}\n\n/** The filtered + sorted documents of a view, plus any clause issues. */\nexport interface AppliedViewState {\n docs: CmsDocument[]\n /** Clauses that failed validation — reported AND excluded from matching. */\n issues: ClauseIssue[]\n}\n\n/**\n * Apply a view state to a document list: validate clauses, AND-match the valid\n * ones, sort. Invalid clauses never filter silently — they come back as\n * issues for the UI to surface.\n */\nexport function applyViewState(\n collection: CollectionDef,\n docs: readonly CmsDocument[],\n localeConfig: LocaleConfig,\n state: TableViewState,\n isPublished: IsPublished = defaultIsPublished,\n): AppliedViewState {\n const issues = validateClauses(collection, state.filters)\n const invalid = new Set(issues.map((issue) => issue.index))\n const active = state.filters.filter((_, index) => !invalid.has(index))\n const matched = docs.filter((doc) =>\n active.every((clause) => clauseMatches(collection, doc, localeConfig, clause, isPublished)),\n )\n return {\n docs: sortViewDocuments(collection, matched, localeConfig, state.sort, isPublished),\n issues,\n }\n}\n\n// --- View state construction, equality, columns ----------------------------------\n\n/** The id of the unnamed built-in \"All documents\" view. */\nexport const ALL_VIEW_ID = 'all'\n\n/** The default (unnamed) view: no filters, no sort, every field visible. */\nexport function defaultViewState(collection: CollectionDef): TableViewState {\n return {\n filters: [],\n sort: null,\n columns: orderedColumnNames(collection),\n hidden: [],\n }\n}\n\n/**\n * The default column order: the schema's title field first (it becomes the\n * default title/link column — EC-213 drops the separate primary-column\n * selector, treating the first visible column as the title), then the\n * remaining fields in schema order.\n */\nfunction orderedColumnNames(collection: CollectionDef): string[] {\n const names = collection.fields.map((field) => field.name)\n const title = titleFieldOf(collection)?.name\n if (title === undefined || !names.includes(title)) return names\n return [title, ...names.filter((name) => name !== title)]\n}\n\n/** Deep-cloned canonical form (stable key order via the Zod shape). */\nexport function normalizeViewState(state: TableViewState): TableViewState {\n return tableViewStateSchema.parse(state)\n}\n\n/** Structural equality on normalized states — drives the \"unsaved changes\" affordance. */\nexport function viewStatesEqual(a: TableViewState, b: TableViewState): boolean {\n return JSON.stringify(normalizeViewState(a)) === JSON.stringify(normalizeViewState(b))\n}\n\n/** One column of a view resolved against the current schema (EC-213). */\nexport interface ResolvedColumn {\n /** The schema field name. */\n field: string\n /** Whether the column renders (`false` ⇒ in {@link TableViewState.hidden}). */\n visible: boolean\n}\n\n/**\n * Resolve a view's columns against the live schema (EC-213): every schema\n * field exactly once, in the view's saved order, with any fields the view\n * predates appended (visible) and any fields no longer in the schema dropped.\n * This is the single source of truth for \"which columns, in what order, shown\n * or hidden\" — order is preserved across hide/show because visibility lives in\n * `hidden`, never in the order.\n */\nexport function resolveColumns(\n collection: CollectionDef,\n state: TableViewState,\n): ResolvedColumn[] {\n const schemaNames = collection.fields.map((field) => field.name)\n const schema = new Set(schemaNames)\n const hidden = new Set(state.hidden)\n // Keep the saved order, restricted to live fields and de-duplicated (a\n // corrupted persisted state must never render one field as two columns).\n const ordered: string[] = []\n const seen = new Set<string>()\n for (const name of state.columns) {\n if (schema.has(name) && !seen.has(name)) {\n ordered.push(name)\n seen.add(name)\n }\n }\n const appended = schemaNames.filter((name) => !seen.has(name))\n return [...ordered, ...appended].map((field) => ({ field, visible: !hidden.has(field) }))\n}\n\n/** The visible columns of a view, in render order (EC-213). */\nexport function visibleColumns(collection: CollectionDef, state: TableViewState): string[] {\n return resolveColumns(collection, state)\n .filter((column) => column.visible)\n .map((column) => column.field)\n}\n\n/**\n * The title/link column: the first visible column (EC-213 — there is no\n * separate primary-column selector). Falls back to the schema title field, then\n * the first field, then `id`, for the degenerate \"everything hidden\" case.\n */\nexport function primaryColumn(collection: CollectionDef, state: TableViewState): string {\n const visible = visibleColumns(collection, state)\n return (\n visible[0] ?? titleFieldOf(collection)?.name ?? collection.fields[0]?.name ?? 'id'\n )\n}\n\n/**\n * Reconcile `columns` to the full resolved order so column edits operate on a\n * complete, stable order (drops removed fields, pins appended ones), and prune\n * `hidden` to fields the live schema still has (a column removed from the schema\n * must not leave dead `hidden` cruft that trips dirty-detection / persists).\n */\nfunction withResolvedOrder(collection: CollectionDef, state: TableViewState): TableViewState {\n const columns = resolveColumns(collection, state).map((column) => column.field)\n const live = new Set(columns)\n const hidden = state.hidden.filter((name) => live.has(name))\n return { ...state, columns, hidden }\n}\n\n/**\n * The canonical, schema-resolved form of a view state — full column order with\n * `hidden` pruned to live fields (EC-213). Two states that render identically\n * share one canonical form, so dirty-detection never trips on a mere column\n * reconciliation or a stale `hidden` entry (only a real filter/sort/visibility/\n * order change counts as \"unsaved\").\n */\nexport function canonicalViewState(\n collection: CollectionDef,\n state: TableViewState,\n): TableViewState {\n return normalizeViewState(withResolvedOrder(collection, state))\n}\n\n/**\n * Toggle a column's visibility — position is preserved (EC-213): visibility\n * lives in `hidden`, the order in `columns` is untouched. Columns the saved\n * order predates are pinned into `columns` first so their place is stable.\n */\nexport function toggleColumn(\n collection: CollectionDef,\n state: TableViewState,\n field: string,\n): TableViewState {\n const base = withResolvedOrder(collection, state)\n const hidden = base.hidden.includes(field)\n ? base.hidden.filter((name) => name !== field)\n : [...base.hidden, field]\n return { ...base, hidden }\n}\n\n/** Move a column one position left (-1) or right (+1) in the full column order. */\nexport function moveColumn(\n collection: CollectionDef,\n state: TableViewState,\n field: string,\n delta: -1 | 1,\n): TableViewState {\n const base = withResolvedOrder(collection, state)\n const from = base.columns.indexOf(field)\n const to = from + delta\n if (from < 0 || to < 0 || to >= base.columns.length) return state\n const columns = [...base.columns]\n columns.splice(from, 1)\n columns.splice(to, 0, field)\n return { ...base, columns }\n}\n\n/** A fresh id for a saved view (uniqueness, not determinism, is the point). */\nexport function newViewId(): string {\n const random = Math.floor(Math.random() * 36 ** 6)\n .toString(36)\n .padStart(6, '0')\n return `view-${Date.now().toString(36)}-${random}`\n}\n"]}
1
+ {"version":3,"file":"view-model.js","sourceRoot":"","sources":["../../../src/lib/cms-views/view-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\nimport { documentDefaultLocale, resolveField, titleFieldOf } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, FieldDef, LocaleConfig } from '@elytracms/core/cms-core'\nimport {\n FILTER_OPERATORS,\n compareListEntries,\n filterScalarOf,\n filterableFields,\n matchesCondition,\n validateWhere,\n} from '@elytracms/core/content'\nimport type {\n FilterCondition,\n FilterOperator,\n FilterScalar,\n NormalizedCondition,\n WhereInput,\n} from '@elytracms/core/content'\n\n/**\n * The pure table-view model for the CMS collection table (EC-169).\n *\n * A view bundles three things: filters in the closed AD-3 vocabulary\n * (`eq`/`in`/`lt`/`lte`/`gt`/`gte`, AND-only — exactly `@elytracms/core/content`'s\n * query model, evaluated with the same matching/ordering primitives), a sort\n * (field + direction), and the visible/ordered columns. Everything here is\n * plain data + functions so filtering, dirtiness, and column edits are\n * testable without a DOM.\n *\n * Filters are offered ONLY on fields the schema declares `filterable: true`\n * (EC-143), plus one built-in option: the document's publication state. The\n * state is document metadata, not a schema field — it powers the\n * \"show me drafts → bulk publish\" working set and still speaks only the\n * closed vocabulary (`eq`/`in`).\n */\n\n// --- Schemas (single source of truth — persisted view records reuse these) ----\n\nexport const filterScalarSchema = z.union([z.string(), z.number(), z.boolean()])\n\nexport const filterClauseSchema = z.object({\n /** A schema field name, or {@link STATE_FIELD} for the publication state. */\n field: z.string().min(1),\n op: z.enum(FILTER_OPERATORS),\n /** `in` carries an array of scalars; every other operator one scalar. */\n value: z.union([filterScalarSchema, z.array(filterScalarSchema)]),\n})\nexport type FilterClause = z.infer<typeof filterClauseSchema>\n\nexport const viewSortSchema = z.object({\n field: z.string().min(1),\n dir: z.enum(['asc', 'desc']),\n})\nexport type ViewSort = z.infer<typeof viewSortSchema>\n\nexport const tableViewStateSchema = z.object({\n /** AND-only list of conditions (no OR, no nesting — vision AD-3). */\n filters: z.array(filterClauseSchema),\n sort: viewSortSchema.nullable(),\n /**\n * The canonical column order (field names) — EVERY column, visible or not\n * (EC-213). Order is independent of visibility: hiding then re-showing a\n * column keeps its place. Schema fields absent here resolve in at the end\n * (see {@link resolveColumns}).\n */\n columns: z.array(z.string().min(1)),\n /**\n * The subset of {@link TableViewState.columns} that is hidden. Defaults to\n * `[]` so views persisted before EC-213 (no `hidden` key) load unchanged.\n */\n hidden: z.array(z.string().min(1)).default([]),\n})\nexport type TableViewState = z.infer<typeof tableViewStateSchema>\n\n// --- The built-in publication-state filter column ------------------------------\n\n/**\n * Sentinel field key for the publication state. `$`-prefixed so it can never\n * collide with a schema field name (`fieldDefSchema` names are identifiers).\n */\nexport const STATE_FIELD = '$state'\n\nconst STATE_VALUES = ['draft', 'published'] as const\n\n/** One field the filter builder may offer. */\nexport interface FilterableFieldOption {\n /** Clause field key: a schema field name, or {@link STATE_FIELD}. */\n field: string\n label: string\n /** The schema field def, or `null` for the built-in publication state. */\n def: FieldDef | null\n}\n\nexport const STATE_OPTION: FilterableFieldOption = {\n field: STATE_FIELD,\n label: 'State',\n def: null,\n}\n\n/**\n * The closed list of fields the filter builder offers: exactly the schema's\n * `filterable: true` fields (EC-143) plus the publication state. Nothing else\n * is ever offered — the UI builds queries, it does not invent a language.\n */\nexport function filterableFieldOptions(collection: CollectionDef): FilterableFieldOption[] {\n const schemaOptions = filterableFields(collection).map((field) => ({\n field: field.name,\n label: field.form?.label ?? field.name,\n def: field,\n }))\n return [...schemaOptions, STATE_OPTION]\n}\n\nexport function filterOptionFor(\n collection: CollectionDef,\n field: string,\n): FilterableFieldOption | undefined {\n return filterableFieldOptions(collection).find((option) => option.field === field)\n}\n\n// --- Operators per field type ---------------------------------------------------\n\n/** Display order + labels for the closed vocabulary. */\nexport const OPERATOR_LABELS: Record<FilterOperator, string> = {\n eq: 'equals',\n in: 'is one of',\n lt: 'less than',\n lte: 'at most',\n gt: 'greater than',\n gte: 'at least',\n}\n\nconst ORDERED_OPERATORS: readonly FilterOperator[] = ['eq', 'in', 'lt', 'lte', 'gt', 'gte']\n\n/**\n * The vocabulary subset that makes sense for a field's type. Always a subset\n * of the closed `FILTER_OPERATORS` — never an extension.\n */\nexport function operatorsFor(option: FilterableFieldOption): FilterOperator[] {\n if (option.def === null) return ['eq', 'in'] // publication state\n switch (option.def.type) {\n case 'boolean':\n return ['eq']\n case 'select':\n case 'relation':\n case 'asset':\n return ['eq', 'in']\n default:\n return [...ORDERED_OPERATORS]\n }\n}\n\n/** A sensible starting clause when a field is added to the builder. */\nexport function defaultClause(option: FilterableFieldOption): FilterClause {\n if (option.def === null) return { field: STATE_FIELD, op: 'eq', value: 'published' }\n switch (option.def.type) {\n case 'boolean':\n return { field: option.field, op: 'eq', value: true }\n case 'number':\n return { field: option.field, op: 'eq', value: 0 }\n case 'select':\n return { field: option.field, op: 'eq', value: option.def.options[0]?.value ?? '' }\n default:\n return { field: option.field, op: 'eq', value: '' }\n }\n}\n\n// --- Text ⇄ scalar coercion (the value inputs of the builder) -------------------\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/\n\n/** Coerce one text input into the scalar a clause compares against. */\nexport function scalarFromText(\n option: FilterableFieldOption | undefined,\n raw: string,\n): FilterScalar {\n const type = option?.def?.type\n if (type === 'number') {\n const trimmed = raw.trim()\n if (trimmed === '') return raw // an emptied input must not become eq 0\n const parsed = Number(trimmed)\n return Number.isNaN(parsed) ? raw : parsed\n }\n if (type === 'boolean') return raw === 'true'\n // Stored dates are full ISO strings; a date-input value compares correctly\n // once expanded to midnight UTC (ISO strings order by code units).\n if (type === 'date' && DATE_ONLY.test(raw)) return `${raw}T00:00:00.000Z`\n return raw\n}\n\n/** Parse a comma-separated list for the `in` operator. */\nexport function scalarListFromText(\n option: FilterableFieldOption | undefined,\n raw: string,\n): FilterScalar[] {\n return raw\n .split(',')\n .map((part) => part.trim())\n .filter((part) => part !== '')\n .map((part) => scalarFromText(option, part))\n}\n\nexport function scalarListToText(value: FilterScalar | FilterScalar[]): string {\n const list = Array.isArray(value) ? value : [value]\n return list.map(String).join(', ')\n}\n\n// --- Clause validation -----------------------------------------------------------\n\n/** A structured problem with one clause (by index in the filter list). */\nexport interface ClauseIssue {\n index: number\n message: string\n}\n\n/**\n * Validate clauses against the collection schema. Schema-field clauses run\n * through the canonical `validateWhere` (EC-143: `filterable` enforcement +\n * operator/value shape); state clauses check the `eq`/`in` subset and the\n * draft/published value set. Invalid clauses (e.g. a persisted view whose\n * field is no longer filterable) are reported — never silently applied.\n */\nexport function validateClauses(\n collection: CollectionDef,\n clauses: readonly FilterClause[],\n): ClauseIssue[] {\n const issues: ClauseIssue[] = []\n clauses.forEach((clause, index) => {\n if (clause.field === STATE_FIELD) {\n if (clause.op !== 'eq' && clause.op !== 'in') {\n issues.push({ index, message: 'State filters support only \"eq\" and \"in\".' })\n return\n }\n const values = Array.isArray(clause.value) ? clause.value : [clause.value]\n if (!values.every((value) => (STATE_VALUES as readonly unknown[]).includes(value))) {\n issues.push({ index, message: 'State must be \"draft\" or \"published\".' })\n }\n return\n }\n const where: WhereInput = {\n [clause.field]: { [clause.op]: clause.value } as FilterCondition,\n }\n const errors = validateWhere(collection, where)\n if (errors.length > 0) issues.push({ index, message: errors[0]!.message })\n })\n return issues\n}\n\n// --- Matching & sorting (delegates to @elytracms/core/content's primitives) -------------\n\n/**\n * Published-ness source for the `$state` pseudo-field (EC-224): the version\n * POINTER, supplied by the caller as `cms.isPublished`. Defaults to \"nothing\n * published\" — a bare row carries no publish signal now that the flat `state`\n * field is gone, so the table (which knows the pointer) passes the real\n * predicate; fixtures/tests that don't surface `$state` get the safe default.\n */\nexport type IsPublished = (doc: CmsDocument) => boolean\nconst defaultIsPublished: IsPublished = () => false\n\n/** The scalar a document exposes for a clause/sort field, locale-resolved. */\nfunction scalarOf(\n collection: CollectionDef,\n doc: CmsDocument,\n field: string,\n localeConfig: LocaleConfig,\n isPublished: IsPublished,\n): FilterScalar | null {\n if (field === STATE_FIELD) return isPublished(doc) ? 'published' : 'draft'\n if (!collection.fields.some((f) => f.name === field)) return null\n const locale = documentDefaultLocale(doc, localeConfig)\n return filterScalarOf(resolveField(collection, doc, field, locale, localeConfig).value)\n}\n\n/** Does a document satisfy one clause? Same semantics as the content accessors. */\nexport function clauseMatches(\n collection: CollectionDef,\n doc: CmsDocument,\n localeConfig: LocaleConfig,\n clause: FilterClause,\n isPublished: IsPublished = defaultIsPublished,\n): boolean {\n const value = scalarOf(collection, doc, clause.field, localeConfig, isPublished)\n const condition = { [clause.op]: clause.value } as NormalizedCondition\n return matchesCondition(value, condition)\n}\n\n/**\n * Sort documents by one column in the canonical deterministic order\n * (`compareListEntries`: typed value order, id-ascending tiebreak). Unlike the\n * content accessors, the *table* may sort any rendered column — sorting is a\n * presentation concern here; the `filterable` flag governs filters.\n */\nexport function sortViewDocuments(\n collection: CollectionDef,\n docs: readonly CmsDocument[],\n localeConfig: LocaleConfig,\n sort: ViewSort | null,\n isPublished: IsPublished = defaultIsPublished,\n): CmsDocument[] {\n if (sort === null) return [...docs]\n const positions = new Map(\n docs.map((doc) => [doc.id, scalarOf(collection, doc, sort.field, localeConfig, isPublished)]),\n )\n return [...docs].sort((a, b) =>\n compareListEntries(\n { id: a.id, sortValue: positions.get(a.id) ?? null },\n { id: b.id, sortValue: positions.get(b.id) ?? null },\n sort.dir,\n ),\n )\n}\n\n/** The filtered + sorted documents of a view, plus any clause issues. */\nexport interface AppliedViewState {\n docs: CmsDocument[]\n /** Clauses that failed validation — reported AND excluded from matching. */\n issues: ClauseIssue[]\n}\n\n/**\n * Apply a view state to a document list: validate clauses, AND-match the valid\n * ones, sort. Invalid clauses never filter silently — they come back as\n * issues for the UI to surface.\n */\nexport function applyViewState(\n collection: CollectionDef,\n docs: readonly CmsDocument[],\n localeConfig: LocaleConfig,\n state: TableViewState,\n isPublished: IsPublished = defaultIsPublished,\n): AppliedViewState {\n const issues = validateClauses(collection, state.filters)\n const invalid = new Set(issues.map((issue) => issue.index))\n const active = state.filters.filter((_, index) => !invalid.has(index))\n const matched = docs.filter((doc) =>\n active.every((clause) => clauseMatches(collection, doc, localeConfig, clause, isPublished)),\n )\n return {\n docs: sortViewDocuments(collection, matched, localeConfig, state.sort, isPublished),\n issues,\n }\n}\n\n// --- View state construction, equality, columns ----------------------------------\n\n/** The id of the unnamed built-in \"All documents\" view. */\nexport const ALL_VIEW_ID = 'all'\n\n/** The default (unnamed) view: no filters, no sort, every field visible. */\nexport function defaultViewState(collection: CollectionDef): TableViewState {\n return {\n filters: [],\n sort: null,\n columns: orderedColumnNames(collection),\n hidden: [],\n }\n}\n\n/**\n * The default column order: the schema's title field first (it becomes the\n * default title/link column — EC-213 drops the separate primary-column\n * selector, treating the first visible column as the title), then the\n * remaining fields in schema order.\n */\nfunction orderedColumnNames(collection: CollectionDef): string[] {\n const names = collection.fields.map((field) => field.name)\n const title = titleFieldOf(collection)?.name\n if (title === undefined || !names.includes(title)) return names\n return [title, ...names.filter((name) => name !== title)]\n}\n\n/** Deep-cloned canonical form (stable key order via the Zod shape). */\nexport function normalizeViewState(state: TableViewState): TableViewState {\n return tableViewStateSchema.parse(state)\n}\n\n/** Structural equality on normalized states — drives the \"unsaved changes\" affordance. */\nexport function viewStatesEqual(a: TableViewState, b: TableViewState): boolean {\n return JSON.stringify(normalizeViewState(a)) === JSON.stringify(normalizeViewState(b))\n}\n\n/** One column of a view resolved against the current schema (EC-213). */\nexport interface ResolvedColumn {\n /** The schema field name. */\n field: string\n /** Whether the column renders (`false` ⇒ in {@link TableViewState.hidden}). */\n visible: boolean\n}\n\n/**\n * Resolve a view's columns against the live schema (EC-213): every schema\n * field exactly once, in the view's saved order, with any fields the view\n * predates appended (visible) and any fields no longer in the schema dropped.\n * This is the single source of truth for \"which columns, in what order, shown\n * or hidden\" — order is preserved across hide/show because visibility lives in\n * `hidden`, never in the order.\n */\nexport function resolveColumns(\n collection: CollectionDef,\n state: TableViewState,\n): ResolvedColumn[] {\n const schemaNames = collection.fields.map((field) => field.name)\n const schema = new Set(schemaNames)\n const hidden = new Set(state.hidden)\n // Keep the saved order, restricted to live fields and de-duplicated (a\n // corrupted persisted state must never render one field as two columns).\n const ordered: string[] = []\n const seen = new Set<string>()\n for (const name of state.columns) {\n if (schema.has(name) && !seen.has(name)) {\n ordered.push(name)\n seen.add(name)\n }\n }\n const appended = schemaNames.filter((name) => !seen.has(name))\n return [...ordered, ...appended].map((field) => ({ field, visible: !hidden.has(field) }))\n}\n\n/** The visible columns of a view, in render order (EC-213). */\nexport function visibleColumns(collection: CollectionDef, state: TableViewState): string[] {\n return resolveColumns(collection, state)\n .filter((column) => column.visible)\n .map((column) => column.field)\n}\n\n/**\n * The title/link column — the document IDENTITY column (EC-242): the schema title\n * field, shown as the leftmost linked cell whose content is `resolveDocumentTitle`\n * (a string `titleField` or a function pattern). Always present regardless of\n * column visibility — it is the document's identity, not a toggleable data column —\n * and excluded from the data columns so the title never shows twice. Falls back to\n * the first visible column, then the first field, then `id`, when no title field\n * is resolvable (e.g. a function title with no backing `title` field).\n */\nexport function primaryColumn(collection: CollectionDef, state: TableViewState): string {\n return (\n titleFieldOf(collection)?.name ??\n visibleColumns(collection, state)[0] ??\n collection.fields[0]?.name ??\n 'id'\n )\n}\n\n/**\n * Reconcile `columns` to the full resolved order so column edits operate on a\n * complete, stable order (drops removed fields, pins appended ones), and prune\n * `hidden` to fields the live schema still has (a column removed from the schema\n * must not leave dead `hidden` cruft that trips dirty-detection / persists).\n */\nfunction withResolvedOrder(collection: CollectionDef, state: TableViewState): TableViewState {\n const columns = resolveColumns(collection, state).map((column) => column.field)\n const live = new Set(columns)\n const hidden = state.hidden.filter((name) => live.has(name))\n return { ...state, columns, hidden }\n}\n\n/**\n * The canonical, schema-resolved form of a view state — full column order with\n * `hidden` pruned to live fields (EC-213). Two states that render identically\n * share one canonical form, so dirty-detection never trips on a mere column\n * reconciliation or a stale `hidden` entry (only a real filter/sort/visibility/\n * order change counts as \"unsaved\").\n */\nexport function canonicalViewState(\n collection: CollectionDef,\n state: TableViewState,\n): TableViewState {\n return normalizeViewState(withResolvedOrder(collection, state))\n}\n\n/**\n * Toggle a column's visibility — position is preserved (EC-213): visibility\n * lives in `hidden`, the order in `columns` is untouched. Columns the saved\n * order predates are pinned into `columns` first so their place is stable.\n */\nexport function toggleColumn(\n collection: CollectionDef,\n state: TableViewState,\n field: string,\n): TableViewState {\n const base = withResolvedOrder(collection, state)\n const hidden = base.hidden.includes(field)\n ? base.hidden.filter((name) => name !== field)\n : [...base.hidden, field]\n return { ...base, hidden }\n}\n\n/** Move a column one position left (-1) or right (+1) in the full column order. */\nexport function moveColumn(\n collection: CollectionDef,\n state: TableViewState,\n field: string,\n delta: -1 | 1,\n): TableViewState {\n const base = withResolvedOrder(collection, state)\n const from = base.columns.indexOf(field)\n const to = from + delta\n if (from < 0 || to < 0 || to >= base.columns.length) return state\n const columns = [...base.columns]\n columns.splice(from, 1)\n columns.splice(to, 0, field)\n return { ...base, columns }\n}\n\n/** A fresh id for a saved view (uniqueness, not determinism, is the point). */\nexport function newViewId(): string {\n const random = Math.floor(Math.random() * 36 ** 6)\n .toString(36)\n .padStart(6, '0')\n return `view-${Date.now().toString(36)}-${random}`\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { createContentClient, createContentContext, } from '@elytracms/core/content';
2
- import { documentKey, sampleLocaleConfig } from '@elytracms/core/cms-core';
2
+ import { documentKey, resolveDocumentTitle, sampleLocaleConfig } from '@elytracms/core/cms-core';
3
3
  import { getStudioLocale } from '../locale/locale-store';
4
4
  /**
5
5
  * Default (no-session) collection schema: EMPTY. The studio ships no demo content
@@ -207,9 +207,11 @@ export function listCollectionEntries(collectionId) {
207
207
  const result = runCollectionListQuery(collectionId);
208
208
  if (!result.ok)
209
209
  return [];
210
- return result.documents.map((doc) => {
211
- const title = collection?.titleField ? doc[collection.titleField] : undefined;
212
- return { id: doc.id, label: typeof title === 'string' && title.length > 0 ? title : doc.id };
213
- });
210
+ // EC-242: label via the shared identity resolver (string `titleField` or a
211
+ // function pattern), over the delivery-shape values; falls back to the id.
212
+ return result.documents.map((doc) => ({
213
+ id: doc.id,
214
+ label: collection ? resolveDocumentTitle(collection, doc, doc.id) : doc.id,
215
+ }));
214
216
  }
215
217
  //# sourceMappingURL=collection-content.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collection-content.js","sourceRoot":"","sources":["../../../src/lib/data/collection-content.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createContentClient,\n createContentContext,\n} from '@elytracms/core/content'\nimport type {\n ContentClient,\n ContentDocumentSource,\n ContentLookup,\n GetDocumentResult,\n ListDocumentsQuery,\n ListDocumentsResult,\n ResolvedDocument,\n} from '@elytracms/core/content'\nimport { documentKey, sampleLocaleConfig } from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentRef,\n Locale,\n LocaleConfig,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport type { ListingQuery } from '@elytracms/core/runtime-renderer'\nimport { getStudioLocale } from '../locale/locale-store'\n\n/**\n * The studio's content delivery engine for canvas previews (EC-166).\n *\n * Repeating sections and template pages preview *delivery-shaped* documents —\n * the exact shape `@elytracms/core/content` serves in production — so the canvas\n * shows what publishing will show. This module wires the canonical content\n * client (closed AD-3 query vocabulary, published perspective, EC-018 locale\n * fallback, depth-1 population, resolved assets) over a **content session**:\n *\n * - Playground mode (and tests): the deterministic EC-145 sample fixtures —\n * byte-identical to the pre-hydration behavior, no backend.\n * - Convex mode: the project-session gate (`components/studio/project-session`)\n * configures the session with the project's HYDRATED schema + documents, so\n * \"Preview with\" lists and section previews show the live content — never\n * fixture titles (EC-177 gap 1).\n *\n * Nothing here invents query semantics; both shapes go through the same\n * canonical client.\n */\n\n/** The schema + documents the preview engine serves (one project session). */\nexport interface ContentSessionState {\n collections: readonly CollectionDef[]\n documents: readonly CmsDocument[]\n /**\n * Published document keys via the version POINTER (EC-224). When present, the\n * preview's published perspective resolves by the pin — never the flat\n * `state` enum — so it matches what delivery serves. Absent (playground\n * fixtures / tests) → the rows' own `state` gates the perspective, unchanged.\n */\n publishedKeys?: readonly string[]\n /** Locale config of the session content; defaults to the sample config. */\n localeConfig?: LocaleConfig\n /**\n * The project's config-sourced routes (EC-207). Wired into the content client so\n * the canvas preview's `resolveLink` (EC-291) derives a rich-text link's URL from\n * the SAME routes delivery serves — the studio-live half of the host link bake.\n * Absent (playground/tests) → no routes, so `resolveLink` returns null and a\n * reference link degrades to the visible `unresolved-ref` issue.\n */\n routes?: readonly RouteRecord[]\n /**\n * The project's asset records (assets table). Wired into the lookup so the\n * content client resolves `asset`-typed FIELDS on listing/relation results\n * (e.g. a ProductGrid product's `gallery`) to delivery shape — the same assets\n * the renderer's `resolveAsset` reads for static props (EC-262). Absent\n * (playground/tests) → assets come only from `asset`-collection fixture docs.\n */\n assets?: readonly AssetRecord[]\n}\n\n/**\n * Default (no-session) collection schema: EMPTY. The studio ships no demo content\n * (Oli, 2026-06-19) — with no backend/content session configured, there is nothing\n * to preview, and the studio says so rather than serving fixtures. A real project's\n * collections + documents arrive via {@link configureContentSession} (Convex mode).\n */\nexport const playgroundCollections: readonly CollectionDef[] = []\n\n/**\n * The perspective a canvas preview resolves references against (EC-300/AD-16). The\n * canvas is draft-coherent: the editable Draft view resolves references to their\n * `'draft'` targets (so a just-made reusable shows immediately), the read-only\n * Published view resolves `'published'` (an honest preview of what delivery serves;\n * an unpublished hard ref shows the in-canvas placeholder). Delivery stays\n * published-only — this does not weaken `live-is-published-only`.\n */\nexport type CanvasPerspective = 'draft' | 'published'\n\nlet contentSession: ContentSessionState | null = null\nlet lookup: ContentLookup = createLookup(playgroundCollections, [], [])\n// Keyed by `${locale}:${perspective}` (EC-300) — one deterministic client per pair.\nconst clients = new Map<string, ContentClient>()\n\n/**\n * Point the preview engine at a project's hydrated content (Convex mode), or\n * back at the deterministic playground fixtures (`null` — the default, and\n * the only state playground mode ever sees).\n */\nexport function configureContentSession(next: ContentSessionState | null): void {\n contentSession = next\n lookup = next\n ? createLookup(next.collections, next.documents, next.publishedKeys, next.assets)\n : createLookup(playgroundCollections, [], [])\n clients.clear()\n}\n\n/** Whether a hydrated content session is configured (Convex mode). */\nexport function hasContentSession(): boolean {\n return contentSession !== null\n}\n\n/** The collections the preview engine currently serves (session or fixtures). */\nfunction activeCollections(): readonly CollectionDef[] {\n return contentSession?.collections ?? playgroundCollections\n}\n\nfunction activeLocaleConfig(): LocaleConfig {\n return contentSession?.localeConfig ?? sampleLocaleConfig\n}\n\n/** Resolve a collection in the active content session (fixtures in playground). */\nexport function playgroundCollection(id: string): CollectionDef | undefined {\n return activeCollections().find((collection) => collection.id === id)\n}\n\n/**\n * Overlay the active session's canonical `filterable` declarations onto a\n * schema that doesn't carry any of its own. The inspector hands the Content\n * tab fixture collection defs; the filter/sort pickers must offer exactly the\n * EC-143 declaration set the delivery engine enforces, so both ends agree. A\n * collection that declares its own filterable fields is left untouched.\n */\nexport function withPlaygroundFilterable(\n collections: readonly CollectionDef[],\n): readonly CollectionDef[] {\n return collections.map((collection) => {\n if (collection.fields.some((field) => field.filterable === true)) return collection\n const counterpart = playgroundCollection(collection.id)\n if (counterpart === undefined) return collection\n const flagged = new Set(\n counterpart.fields.filter((field) => field.filterable === true).map((field) => field.name),\n )\n if (flagged.size === 0) return collection\n return {\n ...collection,\n fields: collection.fields.map((field) =>\n flagged.has(field.name) ? { ...field, filterable: true } : field,\n ),\n }\n })\n}\n\n/** Synthesize the persistence-shaped asset record the asset resolver reads. */\nfunction assetRecordOf(doc: CmsDocument): AssetRecord {\n const values = doc.values\n const alt = doc.localized?.en?.alt\n return {\n id: doc.id,\n projectId: 'playground',\n filename: typeof values.filename === 'string' ? values.filename : doc.id,\n contentType: 'image/png',\n byteSize: 0,\n alt: typeof alt === 'string' ? alt : undefined,\n storageKey: typeof values.url === 'string' ? values.url : doc.id,\n createdAt: '2026-01-01T00:00:00.000Z',\n updatedAt: '2026-01-01T00:00:00.000Z',\n }\n}\n\nfunction createLookup(\n collectionDefs: readonly CollectionDef[],\n documents: readonly CmsDocument[],\n publishedKeys?: readonly string[],\n assetRecords?: readonly AssetRecord[],\n): ContentLookup {\n const collections = new Map(collectionDefs.map((c) => [c.id, c]))\n // EC-224: published-ness is the version POINTER, not the flat `state` enum.\n // A hydrated session hands `publishedKeys` (the pinned set) → wrap each row as\n // a `DocumentHistory` so the published perspective resolves by the pin (the\n // live row is always a draft now) with ZERO dependence on `state`. Without a\n // pointer set (playground fixtures / tests) we keep bare rows, whose own\n // `state` gates the published perspective — byte-identical to before.\n const published = publishedKeys ? new Set(publishedKeys) : null\n const toSource = (doc: CmsDocument): ContentDocumentSource => {\n if (!published) return doc\n return published.has(documentKey({ collection: doc.collection, id: doc.id }))\n ? { draft: doc, published: doc, versions: [] }\n : { draft: doc, published: undefined, versions: [] }\n }\n const byKey = new Map(\n documents.map((doc) => [`${doc.collection}:${doc.id}`, toSource(doc)] as const),\n )\n // Asset resolution source for `asset`-typed FIELDS on resolved documents\n // (listing/relation results, e.g. a product's `gallery`). Two sources, real\n // records last so they win: (1) `asset`-collection fixture documents — how\n // playground/tests model assets; (2) the project's real asset-table records\n // (EC-262) — a Convex session has its assets here, NOT as `asset` documents,\n // so without these the content client resolves no nested image.\n const assets = new Map<string, AssetRecord>()\n for (const doc of documents) {\n if (doc.collection === 'asset') assets.set(doc.id, assetRecordOf(doc))\n }\n for (const record of assetRecords ?? []) assets.set(record.id, record)\n return {\n collection: (id) => collections.get(id),\n document: (ref) => byKey.get(`${ref.collection}:${ref.id}`),\n documents: (collectionId) =>\n documents.filter((doc) => doc.collection === collectionId).map(toSource),\n asset: (id) => assets.get(id),\n // Prefer the record's resolved servable `url` — the one the renderer's\n // resolveAsset reads (EC-262). Real asset-table records carry a Convex blob\n // locator in `storageKey` and the servable URL in `url`; fixture/`asset`-document\n // records (assetRecordOf) leave `url` unset and store the servable url in\n // `storageKey`. Falling back keeps both correct.\n assetUrl: (asset) => asset.url ?? asset.storageKey,\n }\n}\n\n/**\n * One deterministic client per (locale, perspective): EC-018 locale fallback,\n * draft-or-published perspective (EC-300/AD-16, default `'published'`). The studio's\n * ambient active locale (EC-176, `lib/locale`) picks the locale at query time, so the\n * canvas previews follow the locale switcher exactly like delivery would; the canvas's\n * Draft/Published view picks the perspective. The cache resets whenever the content\n * session changes.\n */\nfunction clientFor(locale: Locale, perspective: CanvasPerspective = 'published'): ContentClient {\n const key = `${locale}:${perspective}`\n const existing = clients.get(key)\n if (existing) return existing\n const client = createContentClient({\n lookup,\n context: createContentContext({\n locale,\n perspective,\n locales: activeLocaleConfig(),\n }),\n // EC-291: the routes a rich-text link resolves against (config-sourced) — the\n // studio-live half of the host link bake.\n routes: contentSession?.routes ?? [],\n })\n clients.set(key, client)\n return client\n}\n\n/**\n * Run a closed-vocabulary list query (AD-3: eq/in/lt/lte/gt/gte, AND-only,\n * filterable fields only) and return the delivery-shaped result. Invalid\n * queries come back as structured errors — never thrown, never silently\n * widened. Resolves in the studio's active locale unless one is passed.\n */\nexport function runCollectionListQuery(\n collectionId: string,\n query: ListDocumentsQuery = {},\n locale: Locale = getStudioLocale(),\n): ListDocumentsResult {\n return clientFor(locale).listDocuments(collectionId, query)\n}\n\n/** Resolve one document (by id or slug) into delivery shape. */\nexport function getCollectionDocument(\n collectionId: string,\n idOrSlug: string,\n locale: Locale = getStudioLocale(),\n): GetDocumentResult {\n return clientFor(locale).getDocument(collectionId, idOrSlug)\n}\n\n/**\n * Populate a composition-node `relation` prop to delivery shape for the canvas\n * (EC-254) — the studio analog of the host's baked relations map. Resolves the\n * ref through the active session's client (published perspective, active locale),\n * so the canvas previews a block's picked documents (a ProductGrid's products, a\n * Reusable's element) exactly as delivery renders them. `null` when the target is\n * missing or not visible → the block degrades to its own empty state.\n */\nexport function resolveCanvasRelation(\n ref: DocumentRef,\n locale: Locale = getStudioLocale(),\n perspective: CanvasPerspective = 'published',\n): ResolvedDocument | null {\n return clientFor(locale, perspective).resolveRelation(ref)\n}\n\n/**\n * Resolve a listing block's query to its documents for the canvas (EC-255) — the\n * studio analog of the host's baked listings map. Runs the query (collection +\n * AND-only filter, optional sort/limit) through the active session's client\n * (published perspective, active locale), so the canvas previews a dynamic grid (a\n * ProductGrid keyed to a category) exactly as delivery renders it. An invalid query\n * (structured, never thrown) or a non-list lookup yields `[]` → the block's empty\n * state. Reuses the same `listDocuments` path as `runCollectionListQuery`.\n */\nexport function resolveCanvasListing(\n query: ListingQuery,\n locale: Locale = getStudioLocale(),\n perspective: CanvasPerspective = 'published',\n): ResolvedDocument[] {\n const result = clientFor(locale, perspective).listDocuments(query.collection, {\n where: query.where,\n ...(query.sort ? { sort: query.sort } : {}),\n ...(query.limit !== undefined ? { limit: query.limit } : {}),\n })\n return result.ok ? result.documents : []\n}\n\n/**\n * Resolve a rich-text internal link's target document to its URL path for the canvas\n * (EC-291) — the studio analog of the host's baked links map. Derives the URL through\n * the active session's client (published perspective, active locale, config-sourced\n * routes), so the canvas previews a `{type:'reference'}` link's `href` exactly as\n * delivery serves it. `null` when the target is unpublished, missing, or addressed by\n * no route → the rich-text renderer shows the EC-290 `unresolved-ref` issue. Reuses\n * the same `resolveLink` path as the host bake, so both agree on the derived URL.\n */\nexport function resolveCanvasLink(\n ref: DocumentRef,\n locale: Locale = getStudioLocale(),\n perspective: CanvasPerspective = 'published',\n): string | null {\n return clientFor(locale, perspective).resolveLink(ref)\n}\n\n/** A pickable entry of a collection (the template-page preview switcher). */\nexport interface CollectionEntrySummary {\n id: string\n label: string\n}\n\n/** Published entries of a collection, labeled by the schema's title field. */\nexport function listCollectionEntries(collectionId: string): CollectionEntrySummary[] {\n const collection = playgroundCollection(collectionId)\n const result = runCollectionListQuery(collectionId)\n if (!result.ok) return []\n return result.documents.map((doc) => {\n const title = collection?.titleField ? doc[collection.titleField] : undefined\n return { id: doc.id, label: typeof title === 'string' && title.length > 0 ? title : doc.id }\n })\n}\n"]}
1
+ {"version":3,"file":"collection-content.js","sourceRoot":"","sources":["../../../src/lib/data/collection-content.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createContentClient,\n createContentContext,\n} from '@elytracms/core/content'\nimport type {\n ContentClient,\n ContentDocumentSource,\n ContentLookup,\n GetDocumentResult,\n ListDocumentsQuery,\n ListDocumentsResult,\n ResolvedDocument,\n} from '@elytracms/core/content'\nimport { documentKey, resolveDocumentTitle, sampleLocaleConfig } from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n DocumentRef,\n Locale,\n LocaleConfig,\n RouteRecord,\n} from '@elytracms/core/cms-core'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport type { ListingQuery } from '@elytracms/core/runtime-renderer'\nimport { getStudioLocale } from '../locale/locale-store'\n\n/**\n * The studio's content delivery engine for canvas previews (EC-166).\n *\n * Repeating sections and template pages preview *delivery-shaped* documents —\n * the exact shape `@elytracms/core/content` serves in production — so the canvas\n * shows what publishing will show. This module wires the canonical content\n * client (closed AD-3 query vocabulary, published perspective, EC-018 locale\n * fallback, depth-1 population, resolved assets) over a **content session**:\n *\n * - Playground mode (and tests): the deterministic EC-145 sample fixtures —\n * byte-identical to the pre-hydration behavior, no backend.\n * - Convex mode: the project-session gate (`components/studio/project-session`)\n * configures the session with the project's HYDRATED schema + documents, so\n * \"Preview with\" lists and section previews show the live content — never\n * fixture titles (EC-177 gap 1).\n *\n * Nothing here invents query semantics; both shapes go through the same\n * canonical client.\n */\n\n/** The schema + documents the preview engine serves (one project session). */\nexport interface ContentSessionState {\n collections: readonly CollectionDef[]\n documents: readonly CmsDocument[]\n /**\n * Published document keys via the version POINTER (EC-224). When present, the\n * preview's published perspective resolves by the pin — never the flat\n * `state` enum — so it matches what delivery serves. Absent (playground\n * fixtures / tests) → the rows' own `state` gates the perspective, unchanged.\n */\n publishedKeys?: readonly string[]\n /** Locale config of the session content; defaults to the sample config. */\n localeConfig?: LocaleConfig\n /**\n * The project's config-sourced routes (EC-207). Wired into the content client so\n * the canvas preview's `resolveLink` (EC-291) derives a rich-text link's URL from\n * the SAME routes delivery serves — the studio-live half of the host link bake.\n * Absent (playground/tests) → no routes, so `resolveLink` returns null and a\n * reference link degrades to the visible `unresolved-ref` issue.\n */\n routes?: readonly RouteRecord[]\n /**\n * The project's asset records (assets table). Wired into the lookup so the\n * content client resolves `asset`-typed FIELDS on listing/relation results\n * (e.g. a ProductGrid product's `gallery`) to delivery shape — the same assets\n * the renderer's `resolveAsset` reads for static props (EC-262). Absent\n * (playground/tests) → assets come only from `asset`-collection fixture docs.\n */\n assets?: readonly AssetRecord[]\n}\n\n/**\n * Default (no-session) collection schema: EMPTY. The studio ships no demo content\n * (Oli, 2026-06-19) — with no backend/content session configured, there is nothing\n * to preview, and the studio says so rather than serving fixtures. A real project's\n * collections + documents arrive via {@link configureContentSession} (Convex mode).\n */\nexport const playgroundCollections: readonly CollectionDef[] = []\n\n/**\n * The perspective a canvas preview resolves references against (EC-300/AD-16). The\n * canvas is draft-coherent: the editable Draft view resolves references to their\n * `'draft'` targets (so a just-made reusable shows immediately), the read-only\n * Published view resolves `'published'` (an honest preview of what delivery serves;\n * an unpublished hard ref shows the in-canvas placeholder). Delivery stays\n * published-only — this does not weaken `live-is-published-only`.\n */\nexport type CanvasPerspective = 'draft' | 'published'\n\nlet contentSession: ContentSessionState | null = null\nlet lookup: ContentLookup = createLookup(playgroundCollections, [], [])\n// Keyed by `${locale}:${perspective}` (EC-300) — one deterministic client per pair.\nconst clients = new Map<string, ContentClient>()\n\n/**\n * Point the preview engine at a project's hydrated content (Convex mode), or\n * back at the deterministic playground fixtures (`null` — the default, and\n * the only state playground mode ever sees).\n */\nexport function configureContentSession(next: ContentSessionState | null): void {\n contentSession = next\n lookup = next\n ? createLookup(next.collections, next.documents, next.publishedKeys, next.assets)\n : createLookup(playgroundCollections, [], [])\n clients.clear()\n}\n\n/** Whether a hydrated content session is configured (Convex mode). */\nexport function hasContentSession(): boolean {\n return contentSession !== null\n}\n\n/** The collections the preview engine currently serves (session or fixtures). */\nfunction activeCollections(): readonly CollectionDef[] {\n return contentSession?.collections ?? playgroundCollections\n}\n\nfunction activeLocaleConfig(): LocaleConfig {\n return contentSession?.localeConfig ?? sampleLocaleConfig\n}\n\n/** Resolve a collection in the active content session (fixtures in playground). */\nexport function playgroundCollection(id: string): CollectionDef | undefined {\n return activeCollections().find((collection) => collection.id === id)\n}\n\n/**\n * Overlay the active session's canonical `filterable` declarations onto a\n * schema that doesn't carry any of its own. The inspector hands the Content\n * tab fixture collection defs; the filter/sort pickers must offer exactly the\n * EC-143 declaration set the delivery engine enforces, so both ends agree. A\n * collection that declares its own filterable fields is left untouched.\n */\nexport function withPlaygroundFilterable(\n collections: readonly CollectionDef[],\n): readonly CollectionDef[] {\n return collections.map((collection) => {\n if (collection.fields.some((field) => field.filterable === true)) return collection\n const counterpart = playgroundCollection(collection.id)\n if (counterpart === undefined) return collection\n const flagged = new Set(\n counterpart.fields.filter((field) => field.filterable === true).map((field) => field.name),\n )\n if (flagged.size === 0) return collection\n return {\n ...collection,\n fields: collection.fields.map((field) =>\n flagged.has(field.name) ? { ...field, filterable: true } : field,\n ),\n }\n })\n}\n\n/** Synthesize the persistence-shaped asset record the asset resolver reads. */\nfunction assetRecordOf(doc: CmsDocument): AssetRecord {\n const values = doc.values\n const alt = doc.localized?.en?.alt\n return {\n id: doc.id,\n projectId: 'playground',\n filename: typeof values.filename === 'string' ? values.filename : doc.id,\n contentType: 'image/png',\n byteSize: 0,\n alt: typeof alt === 'string' ? alt : undefined,\n storageKey: typeof values.url === 'string' ? values.url : doc.id,\n createdAt: '2026-01-01T00:00:00.000Z',\n updatedAt: '2026-01-01T00:00:00.000Z',\n }\n}\n\nfunction createLookup(\n collectionDefs: readonly CollectionDef[],\n documents: readonly CmsDocument[],\n publishedKeys?: readonly string[],\n assetRecords?: readonly AssetRecord[],\n): ContentLookup {\n const collections = new Map(collectionDefs.map((c) => [c.id, c]))\n // EC-224: published-ness is the version POINTER, not the flat `state` enum.\n // A hydrated session hands `publishedKeys` (the pinned set) → wrap each row as\n // a `DocumentHistory` so the published perspective resolves by the pin (the\n // live row is always a draft now) with ZERO dependence on `state`. Without a\n // pointer set (playground fixtures / tests) we keep bare rows, whose own\n // `state` gates the published perspective — byte-identical to before.\n const published = publishedKeys ? new Set(publishedKeys) : null\n const toSource = (doc: CmsDocument): ContentDocumentSource => {\n if (!published) return doc\n return published.has(documentKey({ collection: doc.collection, id: doc.id }))\n ? { draft: doc, published: doc, versions: [] }\n : { draft: doc, published: undefined, versions: [] }\n }\n const byKey = new Map(\n documents.map((doc) => [`${doc.collection}:${doc.id}`, toSource(doc)] as const),\n )\n // Asset resolution source for `asset`-typed FIELDS on resolved documents\n // (listing/relation results, e.g. a product's `gallery`). Two sources, real\n // records last so they win: (1) `asset`-collection fixture documents — how\n // playground/tests model assets; (2) the project's real asset-table records\n // (EC-262) — a Convex session has its assets here, NOT as `asset` documents,\n // so without these the content client resolves no nested image.\n const assets = new Map<string, AssetRecord>()\n for (const doc of documents) {\n if (doc.collection === 'asset') assets.set(doc.id, assetRecordOf(doc))\n }\n for (const record of assetRecords ?? []) assets.set(record.id, record)\n return {\n collection: (id) => collections.get(id),\n document: (ref) => byKey.get(`${ref.collection}:${ref.id}`),\n documents: (collectionId) =>\n documents.filter((doc) => doc.collection === collectionId).map(toSource),\n asset: (id) => assets.get(id),\n // Prefer the record's resolved servable `url` — the one the renderer's\n // resolveAsset reads (EC-262). Real asset-table records carry a Convex blob\n // locator in `storageKey` and the servable URL in `url`; fixture/`asset`-document\n // records (assetRecordOf) leave `url` unset and store the servable url in\n // `storageKey`. Falling back keeps both correct.\n assetUrl: (asset) => asset.url ?? asset.storageKey,\n }\n}\n\n/**\n * One deterministic client per (locale, perspective): EC-018 locale fallback,\n * draft-or-published perspective (EC-300/AD-16, default `'published'`). The studio's\n * ambient active locale (EC-176, `lib/locale`) picks the locale at query time, so the\n * canvas previews follow the locale switcher exactly like delivery would; the canvas's\n * Draft/Published view picks the perspective. The cache resets whenever the content\n * session changes.\n */\nfunction clientFor(locale: Locale, perspective: CanvasPerspective = 'published'): ContentClient {\n const key = `${locale}:${perspective}`\n const existing = clients.get(key)\n if (existing) return existing\n const client = createContentClient({\n lookup,\n context: createContentContext({\n locale,\n perspective,\n locales: activeLocaleConfig(),\n }),\n // EC-291: the routes a rich-text link resolves against (config-sourced) — the\n // studio-live half of the host link bake.\n routes: contentSession?.routes ?? [],\n })\n clients.set(key, client)\n return client\n}\n\n/**\n * Run a closed-vocabulary list query (AD-3: eq/in/lt/lte/gt/gte, AND-only,\n * filterable fields only) and return the delivery-shaped result. Invalid\n * queries come back as structured errors — never thrown, never silently\n * widened. Resolves in the studio's active locale unless one is passed.\n */\nexport function runCollectionListQuery(\n collectionId: string,\n query: ListDocumentsQuery = {},\n locale: Locale = getStudioLocale(),\n): ListDocumentsResult {\n return clientFor(locale).listDocuments(collectionId, query)\n}\n\n/** Resolve one document (by id or slug) into delivery shape. */\nexport function getCollectionDocument(\n collectionId: string,\n idOrSlug: string,\n locale: Locale = getStudioLocale(),\n): GetDocumentResult {\n return clientFor(locale).getDocument(collectionId, idOrSlug)\n}\n\n/**\n * Populate a composition-node `relation` prop to delivery shape for the canvas\n * (EC-254) — the studio analog of the host's baked relations map. Resolves the\n * ref through the active session's client (published perspective, active locale),\n * so the canvas previews a block's picked documents (a ProductGrid's products, a\n * Reusable's element) exactly as delivery renders them. `null` when the target is\n * missing or not visible → the block degrades to its own empty state.\n */\nexport function resolveCanvasRelation(\n ref: DocumentRef,\n locale: Locale = getStudioLocale(),\n perspective: CanvasPerspective = 'published',\n): ResolvedDocument | null {\n return clientFor(locale, perspective).resolveRelation(ref)\n}\n\n/**\n * Resolve a listing block's query to its documents for the canvas (EC-255) — the\n * studio analog of the host's baked listings map. Runs the query (collection +\n * AND-only filter, optional sort/limit) through the active session's client\n * (published perspective, active locale), so the canvas previews a dynamic grid (a\n * ProductGrid keyed to a category) exactly as delivery renders it. An invalid query\n * (structured, never thrown) or a non-list lookup yields `[]` → the block's empty\n * state. Reuses the same `listDocuments` path as `runCollectionListQuery`.\n */\nexport function resolveCanvasListing(\n query: ListingQuery,\n locale: Locale = getStudioLocale(),\n perspective: CanvasPerspective = 'published',\n): ResolvedDocument[] {\n const result = clientFor(locale, perspective).listDocuments(query.collection, {\n where: query.where,\n ...(query.sort ? { sort: query.sort } : {}),\n ...(query.limit !== undefined ? { limit: query.limit } : {}),\n })\n return result.ok ? result.documents : []\n}\n\n/**\n * Resolve a rich-text internal link's target document to its URL path for the canvas\n * (EC-291) — the studio analog of the host's baked links map. Derives the URL through\n * the active session's client (published perspective, active locale, config-sourced\n * routes), so the canvas previews a `{type:'reference'}` link's `href` exactly as\n * delivery serves it. `null` when the target is unpublished, missing, or addressed by\n * no route → the rich-text renderer shows the EC-290 `unresolved-ref` issue. Reuses\n * the same `resolveLink` path as the host bake, so both agree on the derived URL.\n */\nexport function resolveCanvasLink(\n ref: DocumentRef,\n locale: Locale = getStudioLocale(),\n perspective: CanvasPerspective = 'published',\n): string | null {\n return clientFor(locale, perspective).resolveLink(ref)\n}\n\n/** A pickable entry of a collection (the template-page preview switcher). */\nexport interface CollectionEntrySummary {\n id: string\n label: string\n}\n\n/** Published entries of a collection, labeled by the schema's title field. */\nexport function listCollectionEntries(collectionId: string): CollectionEntrySummary[] {\n const collection = playgroundCollection(collectionId)\n const result = runCollectionListQuery(collectionId)\n if (!result.ok) return []\n // EC-242: label via the shared identity resolver (string `titleField` or a\n // function pattern), over the delivery-shape values; falls back to the id.\n return result.documents.map((doc) => ({\n id: doc.id,\n label: collection ? resolveDocumentTitle(collection, doc, doc.id) : doc.id,\n }))\n}\n"]}
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useState } from 'react';
3
3
  import { SourceRegistry, captureSample, inferSchema, resolveBinding, resolveCondition, tokensForSchema, } from '@elytracms/core/data-binding';
4
4
  import { getStudioOperations } from '../operations';
5
+ import { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query';
5
6
  import { STUDIO_SYSTEM_COLLECTION_IDS } from '../operations/session-hydration';
6
7
  import { resolveDeploymentCollections } from '../workspace';
7
8
  import { hasContentSession } from './collection-content';
@@ -92,43 +93,52 @@ export function DataProvider({ children, projectId }) {
92
93
  return;
93
94
  dispatch({ type: 'hydrate', sources: collections.map(cmsSourceForCollection) });
94
95
  }, [projectId]);
95
- // Hydrate the project's persisted sources once (operations query). Sources
96
- // provisioned by other sessions on the same project show up here; the
97
- // playground seeds stay for everything not persisted.
96
+ // Hydrate the project's persisted sources through the one operations-backed
97
+ // query cache (EC-308/AD-17). Sources provisioned by other sessions on the
98
+ // same project show up here; the playground seeds stay for everything not
99
+ // persisted. The reducer's `hydrate` keeps session precedence — a source
100
+ // provisioned/removed in THIS session always wins over the hydrated list.
101
+ const cache = useOperationCache();
102
+ const sourcesQuery = useOperationQuery({
103
+ queryKey: ['dataSources', projectId ?? '∅'],
104
+ loader: async (operations) => {
105
+ const result = await loadDataSources(operations, projectId);
106
+ if (!result.ok)
107
+ throw new Error(result.error.message);
108
+ return result.value;
109
+ },
110
+ enabled: projectId !== undefined,
111
+ });
112
+ const hydratedSources = sourcesQuery.data;
98
113
  useEffect(() => {
99
- if (projectId === undefined)
100
- return;
101
- let cancelled = false;
102
- void getStudioOperations()
103
- .then(({ operations }) => loadDataSources(operations, projectId))
104
- .then((result) => {
105
- if (cancelled)
106
- return;
107
- if (!result.ok) {
108
- setPersistError(result.error.message);
109
- return;
110
- }
111
- dispatch({ type: 'hydrate', sources: result.value });
112
- });
113
- return () => {
114
- cancelled = true;
115
- };
116
- }, [projectId]);
114
+ if (hydratedSources)
115
+ dispatch({ type: 'hydrate', sources: hydratedSources });
116
+ }, [hydratedSources]);
117
+ const sourcesError = sourcesQuery.isError ? sourcesQuery.error : null;
118
+ useEffect(() => {
119
+ if (sourcesError)
120
+ setPersistError(sourcesError instanceof Error ? sourcesError.message : 'Loading data sources failed.');
121
+ }, [sourcesError]);
117
122
  // Best-effort persistence on top of the live session: the canvas keeps
118
- // working on session state; a failed write surfaces via `persistError`.
123
+ // working on session state; a failed write surfaces via `persistError`. On a
124
+ // confirmed write, invalidate the hydration query so a later remount
125
+ // re-hydrates the provisioned/removed source rather than a stale list.
119
126
  const persist = useCallback((write) => {
120
127
  if (projectId === undefined)
121
128
  return;
122
129
  void getStudioOperations()
123
130
  .then(({ operations }) => write(operations, projectId))
124
131
  .then((result) => {
125
- if (!result.ok)
132
+ if (!result.ok) {
126
133
  setPersistError(result.error.message);
134
+ return;
135
+ }
136
+ void cache.invalidate(['dataSources', projectId]);
127
137
  })
128
138
  .catch((error) => {
129
139
  setPersistError(error instanceof Error ? error.message : 'Saving the data source failed.');
130
140
  });
131
- }, [projectId]);
141
+ }, [projectId, cache]);
132
142
  const store = useMemo(() => {
133
143
  const registry = SourceRegistry.from(state.sources);
134
144
  const payloads = {};
@@ -1 +1 @@
1
- {"version":3,"file":"data-store.js","sourceRoot":"","sources":["../../../src/lib/data/data-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n SourceRegistry,\n captureSample,\n inferSchema,\n resolveBinding,\n resolveCondition,\n tokensForSchema,\n} from '@elytracms/core/data-binding'\nimport type {\n BindingResolution,\n ConditionResolution,\n DataSource,\n InferredSchema,\n PayloadStore,\n RepeaterItemContext,\n} from '@elytracms/core/data-binding'\nimport type { BindingReference, Condition, JsonValue } from '@elytracms/core/project-graph'\nimport { getStudioOperations } from '../operations'\nimport type { OperationResult, OperationsClient } from '../operations'\nimport { STUDIO_SYSTEM_COLLECTION_IDS } from '../operations/session-hydration'\nimport { resolveDeploymentCollections } from '../workspace'\nimport { hasContentSession } from './collection-content'\nimport { captureSourceSample, cmsSourceForCollection } from './sample-capture'\nimport { deleteDataSource, loadDataSources, saveDataSource } from './sources-store'\n\n/**\n * The project's live data layer (brief §4.6, §7.5, EC-167). Holds the source\n * registry the Builder's Content tab binds through, plus the captured payloads\n * that drive binding resolution. One uniform model across CMS queries, data\n * providers, and the built-in REST/Convex kinds — resolution, sampling,\n * inference and tokenization treat them all identically.\n *\n * There is no source-authoring UI (EC-167): sources exist because code declared\n * them (registered data providers), the schema implied them (per-collection CMS\n * queries), or an editor flow provisioned them (repeating sections, template\n * pages).\n *\n * Persistence: when mounted with a `projectId`, the store hydrates the\n * project's persisted source records (the `cms-data-sources` system collection,\n * see `sources-store.ts`) over the deterministic playground seeds — persisted\n * wins by id, but never over a source provisioned/removed in THIS session —\n * and every provision/remove writes through `@elytracms/core/operations`. Without a\n * `projectId` (tests, fixtures) the store is pure session state, exactly as\n * before.\n */\nexport interface DataStore {\n sources: DataSource[]\n registry: SourceRegistry\n payloads: PayloadStore\n knownSourceIds: ReadonlySet<string>\n\n getSource: (id: string) => DataSource | undefined\n removeSource: (id: string) => void\n /**\n * Create or replace a source with a known id + config and capture its sample\n * immediately (EC-166/EC-167): repeating sections and template pages\n * provision their per-scope CMS query / provider sources through this.\n */\n provisionSource: (source: DataSource) => void\n\n /**\n * The latest persistence failure in plain language, or `null`. Persistence\n * is best-effort on top of the live session (the canvas keeps working on\n * session state), but a failure is recorded here — visible, never silent.\n */\n persistError: string | null\n\n /** The stable path tokens a source exposes from its inferred schema (EC-085). */\n tokensOf: (id: string) => string[]\n schemaOf: (id: string) => InferredSchema | undefined\n sampleOf: (id: string) => JsonValue | undefined\n\n /** Resolve a binding to a renderer-ready resolution (value/props/condition/issue). */\n resolve: (ref: BindingReference, item?: RepeaterItemContext) => BindingResolution\n /** Resolve a binding to the concrete value the renderer injects (EC-086–089). */\n resolveValue: (ref: BindingReference, item?: RepeaterItemContext) => JsonValue | undefined\n /** Evaluate a condition against the captured payloads (EC-088). */\n resolveCondition: (condition: Condition, item?: RepeaterItemContext) => ConditionResolution\n}\n\nexport interface DataState {\n sources: DataSource[]\n /**\n * Ids provisioned or removed in THIS session. Hydration (which resolves\n * asynchronously) never overrides them — the editor's latest action wins\n * over whatever was persisted before it.\n */\n sessionIds: ReadonlySet<string>\n}\n\nexport type DataAction =\n | { type: 'remove'; id: string }\n | { type: 'provision'; source: DataSource }\n /** Merge the project's persisted source records in (persisted wins over seeds). */\n | { type: 'hydrate'; sources: DataSource[] }\n\nfunction upsert(sources: DataSource[], source: DataSource): DataSource[] {\n const exists = sources.some((s) => s.id === source.id)\n return exists ? sources.map((s) => (s.id === source.id ? source : s)) : [...sources, source]\n}\n\n/**\n * A persisted record hydrates as-is when it carries its captured sample;\n * otherwise recapture. Exception (EC-177 gap 1): with a hydrated content\n * session configured (Convex mode), CMS sources ALWAYS recapture by replaying\n * their stored query over the live documents — a sample captured by an earlier\n * session may carry stale (or fixture-derived) entries.\n */\nfunction withSample(source: DataSource): DataSource {\n if (source.kind === 'cms' && hasContentSession()) {\n const sample = captureSourceSample(source)\n return sample !== undefined ? captureSample(source, sample) : source\n }\n if (source.sample !== undefined) return source\n // No persisted sample: try an honest capture (a provider runs; anything the\n // studio can't fetch returns undefined) — never fabricate.\n const sample = captureSourceSample(source)\n return sample !== undefined ? captureSample(source, sample) : source\n}\n\n/**\n * The store's reducer, exported because hydration/session-precedence semantics\n * live here and the app's test harness has no DOM to drive effects through.\n */\nexport function dataStateReducer(state: DataState, action: DataAction): DataState {\n switch (action.type) {\n case 'remove':\n return {\n ...state,\n sources: state.sources.filter((s) => s.id !== action.id),\n sessionIds: new Set([...state.sessionIds, action.id]),\n }\n case 'provision': {\n // Upsert by id; attach a sample only if one can be captured honestly (a\n // provider run, or live CMS content) — otherwise the source carries none.\n const sample = captureSourceSample(action.source)\n const provisioned = sample !== undefined ? captureSample(action.source, sample) : action.source\n return {\n ...state,\n sources: upsert(state.sources, provisioned),\n sessionIds: new Set([...state.sessionIds, provisioned.id]),\n }\n }\n case 'hydrate': {\n let sources = state.sources\n for (const source of action.sources) {\n if (state.sessionIds.has(source.id)) continue // this session already decided\n sources = upsert(sources, withSample(source))\n }\n return sources === state.sources ? state : { ...state, sources }\n }\n default:\n return state\n }\n}\n\nconst DataContext = createContext<DataStore | null>(null)\n\n/**\n * The session's starting state: EMPTY. The studio seeds no sample sources (Oli,\n * 2026-06-19) — real sources arrive from the project's config collections (the\n * hydrate effect, Convex mode) or are added by the author. Nothing is faked.\n */\nexport function initialDataState(): DataState {\n return { sources: [], sessionIds: new Set() }\n}\n\nexport interface DataProviderProps {\n children: ReactNode\n /**\n * When set, the store hydrates from and persists to this project's\n * `cms-data-sources` system collection (through `@elytracms/core/operations`).\n * Without it the store is pure session state (tests, fixtures).\n */\n projectId?: string\n}\n\nexport function DataProvider({ children, projectId }: DataProviderProps) {\n const [state, dispatch] = useReducer(dataStateReducer, undefined, initialDataState)\n const [persistError, setPersistError] = useState<string | null>(null)\n\n // EC-192: surface every config-driven content collection as a pickable CMS\n // source, so the binding/condition pickers list what actually exists — the\n // same collection set the CMS uses (`resolveDeploymentCollections`) — rather\n // than only the demo fixture sources. Merged additively (the `hydrate` upsert\n // skips session-decided ids and recaptures live samples in Convex mode); studio\n // SYSTEM collections (routes/redirects/saved views/data sources) are excluded.\n useEffect(() => {\n if (projectId === undefined) return\n const collections = resolveDeploymentCollections().filter(\n (collection) => !STUDIO_SYSTEM_COLLECTION_IDS.has(collection.id),\n )\n if (collections.length === 0) return\n dispatch({ type: 'hydrate', sources: collections.map(cmsSourceForCollection) })\n }, [projectId])\n\n // Hydrate the project's persisted sources once (operations query). Sources\n // provisioned by other sessions on the same project show up here; the\n // playground seeds stay for everything not persisted.\n useEffect(() => {\n if (projectId === undefined) return\n let cancelled = false\n void getStudioOperations()\n .then(({ operations }) => loadDataSources(operations, projectId))\n .then((result) => {\n if (cancelled) return\n if (!result.ok) {\n setPersistError(result.error.message)\n return\n }\n dispatch({ type: 'hydrate', sources: result.value })\n })\n return () => {\n cancelled = true\n }\n }, [projectId])\n\n // Best-effort persistence on top of the live session: the canvas keeps\n // working on session state; a failed write surfaces via `persistError`.\n const persist = useCallback(\n (write: (operations: OperationsClient, projectId: string) => Promise<OperationResult<void>>) => {\n if (projectId === undefined) return\n void getStudioOperations()\n .then(({ operations }) => write(operations, projectId))\n .then((result) => {\n if (!result.ok) setPersistError(result.error.message)\n })\n .catch((error: unknown) => {\n setPersistError(error instanceof Error ? error.message : 'Saving the data source failed.')\n })\n },\n [projectId],\n )\n\n const store = useMemo<DataStore>(() => {\n const registry = SourceRegistry.from(state.sources)\n const payloads: Record<string, JsonValue> = {}\n for (const source of state.sources) {\n if (source.sample !== undefined) payloads[source.id] = source.sample\n }\n const knownSourceIds = registry.knownSourceIds()\n const ctx = { payloads, registry }\n\n const getSource = (id: string) => registry.get(id)\n const resolve = (ref: BindingReference, item?: RepeaterItemContext) =>\n resolveBinding(ref, ctx, item)\n\n return {\n sources: state.sources,\n registry,\n payloads,\n knownSourceIds,\n getSource,\n removeSource: (id) => {\n dispatch({ type: 'remove', id })\n persist((operations, pid) => deleteDataSource(operations, pid, id))\n },\n provisionSource: (source) => {\n dispatch({ type: 'provision', source })\n // Persist exactly what the session resolves: the source plus any sample\n // that can be captured honestly (a provider run / live CMS content) —\n // never a fabricated one. Matches the `provision` reducer.\n const sample = captureSourceSample(source)\n const provisioned = sample !== undefined ? captureSample(source, sample) : source\n persist((operations, pid) => saveDataSource(operations, pid, provisioned))\n },\n persistError,\n tokensOf: (id) => {\n const schema = getSource(id)?.schema\n return schema ? tokensForSchema(schema) : []\n },\n schemaOf: (id) => {\n const source = getSource(id)\n if (source?.schema) return source.schema\n if (source?.sample !== undefined) return inferSchema(source.sample)\n return undefined\n },\n sampleOf: (id) => getSource(id)?.sample,\n resolve,\n resolveValue: (ref, item) => {\n const r = resolve(ref, item)\n switch (r.kind) {\n case 'value':\n return r.value\n case 'props':\n return r.props\n case 'condition':\n return r.pass\n case 'issue':\n return undefined\n default: {\n const never: never = r\n return never\n }\n }\n },\n resolveCondition: (condition, item) => resolveCondition(condition, ctx, item),\n }\n }, [state, persist, persistError])\n\n return <DataContext.Provider value={store}>{children}</DataContext.Provider>\n}\n\nexport function useData(): DataStore {\n const store = useContext(DataContext)\n if (!store) throw new Error('useData must be used within a <DataProvider>.')\n return store\n}\n"]}
1
+ {"version":3,"file":"data-store.js","sourceRoot":"","sources":["../../../src/lib/data/data-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n SourceRegistry,\n captureSample,\n inferSchema,\n resolveBinding,\n resolveCondition,\n tokensForSchema,\n} from '@elytracms/core/data-binding'\nimport type {\n BindingResolution,\n ConditionResolution,\n DataSource,\n InferredSchema,\n PayloadStore,\n RepeaterItemContext,\n} from '@elytracms/core/data-binding'\nimport type { BindingReference, Condition, JsonValue } from '@elytracms/core/project-graph'\nimport { getStudioOperations } from '../operations'\nimport type { OperationResult, OperationsClient } from '../operations'\nimport { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { STUDIO_SYSTEM_COLLECTION_IDS } from '../operations/session-hydration'\nimport { resolveDeploymentCollections } from '../workspace'\nimport { hasContentSession } from './collection-content'\nimport { captureSourceSample, cmsSourceForCollection } from './sample-capture'\nimport { deleteDataSource, loadDataSources, saveDataSource } from './sources-store'\n\n/**\n * The project's live data layer (brief §4.6, §7.5, EC-167). Holds the source\n * registry the Builder's Content tab binds through, plus the captured payloads\n * that drive binding resolution. One uniform model across CMS queries, data\n * providers, and the built-in REST/Convex kinds — resolution, sampling,\n * inference and tokenization treat them all identically.\n *\n * There is no source-authoring UI (EC-167): sources exist because code declared\n * them (registered data providers), the schema implied them (per-collection CMS\n * queries), or an editor flow provisioned them (repeating sections, template\n * pages).\n *\n * Persistence: when mounted with a `projectId`, the store hydrates the\n * project's persisted source records (the `cms-data-sources` system collection,\n * see `sources-store.ts`) over the deterministic playground seeds — persisted\n * wins by id, but never over a source provisioned/removed in THIS session —\n * and every provision/remove writes through `@elytracms/core/operations`. Without a\n * `projectId` (tests, fixtures) the store is pure session state, exactly as\n * before.\n */\nexport interface DataStore {\n sources: DataSource[]\n registry: SourceRegistry\n payloads: PayloadStore\n knownSourceIds: ReadonlySet<string>\n\n getSource: (id: string) => DataSource | undefined\n removeSource: (id: string) => void\n /**\n * Create or replace a source with a known id + config and capture its sample\n * immediately (EC-166/EC-167): repeating sections and template pages\n * provision their per-scope CMS query / provider sources through this.\n */\n provisionSource: (source: DataSource) => void\n\n /**\n * The latest persistence failure in plain language, or `null`. Persistence\n * is best-effort on top of the live session (the canvas keeps working on\n * session state), but a failure is recorded here — visible, never silent.\n */\n persistError: string | null\n\n /** The stable path tokens a source exposes from its inferred schema (EC-085). */\n tokensOf: (id: string) => string[]\n schemaOf: (id: string) => InferredSchema | undefined\n sampleOf: (id: string) => JsonValue | undefined\n\n /** Resolve a binding to a renderer-ready resolution (value/props/condition/issue). */\n resolve: (ref: BindingReference, item?: RepeaterItemContext) => BindingResolution\n /** Resolve a binding to the concrete value the renderer injects (EC-086–089). */\n resolveValue: (ref: BindingReference, item?: RepeaterItemContext) => JsonValue | undefined\n /** Evaluate a condition against the captured payloads (EC-088). */\n resolveCondition: (condition: Condition, item?: RepeaterItemContext) => ConditionResolution\n}\n\nexport interface DataState {\n sources: DataSource[]\n /**\n * Ids provisioned or removed in THIS session. Hydration (which resolves\n * asynchronously) never overrides them — the editor's latest action wins\n * over whatever was persisted before it.\n */\n sessionIds: ReadonlySet<string>\n}\n\nexport type DataAction =\n | { type: 'remove'; id: string }\n | { type: 'provision'; source: DataSource }\n /** Merge the project's persisted source records in (persisted wins over seeds). */\n | { type: 'hydrate'; sources: DataSource[] }\n\nfunction upsert(sources: DataSource[], source: DataSource): DataSource[] {\n const exists = sources.some((s) => s.id === source.id)\n return exists ? sources.map((s) => (s.id === source.id ? source : s)) : [...sources, source]\n}\n\n/**\n * A persisted record hydrates as-is when it carries its captured sample;\n * otherwise recapture. Exception (EC-177 gap 1): with a hydrated content\n * session configured (Convex mode), CMS sources ALWAYS recapture by replaying\n * their stored query over the live documents — a sample captured by an earlier\n * session may carry stale (or fixture-derived) entries.\n */\nfunction withSample(source: DataSource): DataSource {\n if (source.kind === 'cms' && hasContentSession()) {\n const sample = captureSourceSample(source)\n return sample !== undefined ? captureSample(source, sample) : source\n }\n if (source.sample !== undefined) return source\n // No persisted sample: try an honest capture (a provider runs; anything the\n // studio can't fetch returns undefined) — never fabricate.\n const sample = captureSourceSample(source)\n return sample !== undefined ? captureSample(source, sample) : source\n}\n\n/**\n * The store's reducer, exported because hydration/session-precedence semantics\n * live here and the app's test harness has no DOM to drive effects through.\n */\nexport function dataStateReducer(state: DataState, action: DataAction): DataState {\n switch (action.type) {\n case 'remove':\n return {\n ...state,\n sources: state.sources.filter((s) => s.id !== action.id),\n sessionIds: new Set([...state.sessionIds, action.id]),\n }\n case 'provision': {\n // Upsert by id; attach a sample only if one can be captured honestly (a\n // provider run, or live CMS content) — otherwise the source carries none.\n const sample = captureSourceSample(action.source)\n const provisioned = sample !== undefined ? captureSample(action.source, sample) : action.source\n return {\n ...state,\n sources: upsert(state.sources, provisioned),\n sessionIds: new Set([...state.sessionIds, provisioned.id]),\n }\n }\n case 'hydrate': {\n let sources = state.sources\n for (const source of action.sources) {\n if (state.sessionIds.has(source.id)) continue // this session already decided\n sources = upsert(sources, withSample(source))\n }\n return sources === state.sources ? state : { ...state, sources }\n }\n default:\n return state\n }\n}\n\nconst DataContext = createContext<DataStore | null>(null)\n\n/**\n * The session's starting state: EMPTY. The studio seeds no sample sources (Oli,\n * 2026-06-19) — real sources arrive from the project's config collections (the\n * hydrate effect, Convex mode) or are added by the author. Nothing is faked.\n */\nexport function initialDataState(): DataState {\n return { sources: [], sessionIds: new Set() }\n}\n\nexport interface DataProviderProps {\n children: ReactNode\n /**\n * When set, the store hydrates from and persists to this project's\n * `cms-data-sources` system collection (through `@elytracms/core/operations`).\n * Without it the store is pure session state (tests, fixtures).\n */\n projectId?: string\n}\n\nexport function DataProvider({ children, projectId }: DataProviderProps) {\n const [state, dispatch] = useReducer(dataStateReducer, undefined, initialDataState)\n const [persistError, setPersistError] = useState<string | null>(null)\n\n // EC-192: surface every config-driven content collection as a pickable CMS\n // source, so the binding/condition pickers list what actually exists — the\n // same collection set the CMS uses (`resolveDeploymentCollections`) — rather\n // than only the demo fixture sources. Merged additively (the `hydrate` upsert\n // skips session-decided ids and recaptures live samples in Convex mode); studio\n // SYSTEM collections (routes/redirects/saved views/data sources) are excluded.\n useEffect(() => {\n if (projectId === undefined) return\n const collections = resolveDeploymentCollections().filter(\n (collection) => !STUDIO_SYSTEM_COLLECTION_IDS.has(collection.id),\n )\n if (collections.length === 0) return\n dispatch({ type: 'hydrate', sources: collections.map(cmsSourceForCollection) })\n }, [projectId])\n\n // Hydrate the project's persisted sources through the one operations-backed\n // query cache (EC-308/AD-17). Sources provisioned by other sessions on the\n // same project show up here; the playground seeds stay for everything not\n // persisted. The reducer's `hydrate` keeps session precedence — a source\n // provisioned/removed in THIS session always wins over the hydrated list.\n const cache = useOperationCache()\n const sourcesQuery = useOperationQuery<DataSource[]>({\n queryKey: ['dataSources', projectId ?? '∅'],\n loader: async (operations) => {\n const result = await loadDataSources(operations, projectId as string)\n if (!result.ok) throw new Error(result.error.message)\n return result.value\n },\n enabled: projectId !== undefined,\n })\n const hydratedSources = sourcesQuery.data\n useEffect(() => {\n if (hydratedSources) dispatch({ type: 'hydrate', sources: hydratedSources })\n }, [hydratedSources])\n const sourcesError = sourcesQuery.isError ? sourcesQuery.error : null\n useEffect(() => {\n if (sourcesError)\n setPersistError(sourcesError instanceof Error ? sourcesError.message : 'Loading data sources failed.')\n }, [sourcesError])\n\n // Best-effort persistence on top of the live session: the canvas keeps\n // working on session state; a failed write surfaces via `persistError`. On a\n // confirmed write, invalidate the hydration query so a later remount\n // re-hydrates the provisioned/removed source rather than a stale list.\n const persist = useCallback(\n (write: (operations: OperationsClient, projectId: string) => Promise<OperationResult<void>>) => {\n if (projectId === undefined) return\n void getStudioOperations()\n .then(({ operations }) => write(operations, projectId))\n .then((result) => {\n if (!result.ok) {\n setPersistError(result.error.message)\n return\n }\n void cache.invalidate(['dataSources', projectId])\n })\n .catch((error: unknown) => {\n setPersistError(error instanceof Error ? error.message : 'Saving the data source failed.')\n })\n },\n [projectId, cache],\n )\n\n const store = useMemo<DataStore>(() => {\n const registry = SourceRegistry.from(state.sources)\n const payloads: Record<string, JsonValue> = {}\n for (const source of state.sources) {\n if (source.sample !== undefined) payloads[source.id] = source.sample\n }\n const knownSourceIds = registry.knownSourceIds()\n const ctx = { payloads, registry }\n\n const getSource = (id: string) => registry.get(id)\n const resolve = (ref: BindingReference, item?: RepeaterItemContext) =>\n resolveBinding(ref, ctx, item)\n\n return {\n sources: state.sources,\n registry,\n payloads,\n knownSourceIds,\n getSource,\n removeSource: (id) => {\n dispatch({ type: 'remove', id })\n persist((operations, pid) => deleteDataSource(operations, pid, id))\n },\n provisionSource: (source) => {\n dispatch({ type: 'provision', source })\n // Persist exactly what the session resolves: the source plus any sample\n // that can be captured honestly (a provider run / live CMS content) —\n // never a fabricated one. Matches the `provision` reducer.\n const sample = captureSourceSample(source)\n const provisioned = sample !== undefined ? captureSample(source, sample) : source\n persist((operations, pid) => saveDataSource(operations, pid, provisioned))\n },\n persistError,\n tokensOf: (id) => {\n const schema = getSource(id)?.schema\n return schema ? tokensForSchema(schema) : []\n },\n schemaOf: (id) => {\n const source = getSource(id)\n if (source?.schema) return source.schema\n if (source?.sample !== undefined) return inferSchema(source.sample)\n return undefined\n },\n sampleOf: (id) => getSource(id)?.sample,\n resolve,\n resolveValue: (ref, item) => {\n const r = resolve(ref, item)\n switch (r.kind) {\n case 'value':\n return r.value\n case 'props':\n return r.props\n case 'condition':\n return r.pass\n case 'issue':\n return undefined\n default: {\n const never: never = r\n return never\n }\n }\n },\n resolveCondition: (condition, item) => resolveCondition(condition, ctx, item),\n }\n }, [state, persist, persistError])\n\n return <DataContext.Provider value={store}>{children}</DataContext.Provider>\n}\n\nexport function useData(): DataStore {\n const store = useContext(DataContext)\n if (!store) throw new Error('useData must be used within a <DataProvider>.')\n return store\n}\n"]}
@@ -119,6 +119,17 @@ class ConvexCmsRepository {
119
119
  .map(docRecordFromDoc)
120
120
  .sort((a, b) => (documentKey(a.document) < documentKey(b.document) ? -1 : 1));
121
121
  }
122
+ async listPublishedDocuments(projectId, collection) {
123
+ // EC-307: one bulk read for the published pool (collapses the per-document
124
+ // `listVersions` fan-out). The handler resolves each pin to its snapshot.
125
+ const snapshots = (await this.client.query(queryRef('cms:listPublishedDocuments'), {
126
+ ...(collection ? { collection } : {}),
127
+ }));
128
+ // Deterministic document-key order (parity with the in-memory adapter).
129
+ return snapshots
130
+ .map((doc) => documentSchema.parse(doc))
131
+ .sort((a, b) => (documentKey(a) < documentKey(b) ? -1 : 1));
132
+ }
122
133
  async deleteDocument(projectId, ref) {
123
134
  // EC-153 parity: deleting a missing document is not-found, never a no-op.
124
135
  await this.getDocument(projectId, ref);
@@ -344,6 +355,17 @@ class ConvexReferenceIndexRepository {
344
355
  }));
345
356
  return sortedReferenceRecords(docs);
346
357
  }
358
+ async inboundMany(projectId, targets, options = {}) {
359
+ // EC-308: one round-trip for all targets (the handler loops the by_target
360
+ // index server-side), replacing the client's per-target inbound fan-out.
361
+ if (targets.length === 0)
362
+ return [];
363
+ const groups = (await this.client.query(queryRef('references:inboundMany'), {
364
+ targets: targets.map((target) => ({ targetType: target.type, targetId: target.id })),
365
+ ...(options.variant ? { variant: options.variant } : {}),
366
+ }));
367
+ return groups.map((docs) => sortedReferenceRecords(docs));
368
+ }
347
369
  async listEntries(projectId, variant) {
348
370
  const docs = (await this.client.query(queryRef('references:list'), {
349
371
  ...(variant ? { variant } : {}),