@elytracms/studio 0.0.24 → 0.0.26

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 (62) 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/asset-library.js +5 -2
  6. package/dist/components/assets/asset-library.js.map +1 -1
  7. package/dist/components/assets/assets-store.js +45 -30
  8. package/dist/components/assets/assets-store.js.map +1 -1
  9. package/dist/components/auth-context.d.ts +4 -2
  10. package/dist/components/auth-context.js +19 -19
  11. package/dist/components/auth-context.js.map +1 -1
  12. package/dist/components/cms/cms-store.js +5 -4
  13. package/dist/components/cms/cms-store.js.map +1 -1
  14. package/dist/components/cms/collection-table.js +10 -3
  15. package/dist/components/cms/collection-table.js.map +1 -1
  16. package/dist/components/cms/detail-editor.js +36 -9
  17. package/dist/components/cms/detail-editor.js.map +1 -1
  18. package/dist/components/cms/field-control.d.ts +2 -1
  19. package/dist/components/cms/field-control.js +166 -74
  20. package/dist/components/cms/field-control.js.map +1 -1
  21. package/dist/components/cms/versions-pane.js +28 -16
  22. package/dist/components/cms/versions-pane.js.map +1 -1
  23. package/dist/components/project-session.d.ts +11 -0
  24. package/dist/components/project-session.js.map +1 -1
  25. package/dist/components/routes/routes-shell.js +22 -22
  26. package/dist/components/routes/routes-shell.js.map +1 -1
  27. package/dist/components/studio-context.js +5 -1
  28. package/dist/components/studio-context.js.map +1 -1
  29. package/dist/lib/assets/delete-asset.js +7 -3
  30. package/dist/lib/assets/delete-asset.js.map +1 -1
  31. package/dist/lib/cms/published-documents.d.ts +6 -6
  32. package/dist/lib/cms/published-documents.js +21 -58
  33. package/dist/lib/cms/published-documents.js.map +1 -1
  34. package/dist/lib/cms-views/view-model.d.ts +7 -3
  35. package/dist/lib/cms-views/view-model.js +11 -5
  36. package/dist/lib/cms-views/view-model.js.map +1 -1
  37. package/dist/lib/data/collection-content.js +7 -5
  38. package/dist/lib/data/collection-content.js.map +1 -1
  39. package/dist/lib/data/data-store.js +34 -24
  40. package/dist/lib/data/data-store.js.map +1 -1
  41. package/dist/lib/persistence/convex-adapter.js +22 -0
  42. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  43. package/dist/lib/persistence/index.js +6 -2
  44. package/dist/lib/persistence/index.js.map +1 -1
  45. package/dist/lib/query/index.d.ts +6 -0
  46. package/dist/lib/query/index.js +6 -0
  47. package/dist/lib/query/index.js.map +1 -0
  48. package/dist/lib/query/operations-query.d.ts +85 -0
  49. package/dist/lib/query/operations-query.js +124 -0
  50. package/dist/lib/query/operations-query.js.map +1 -0
  51. package/dist/lib/references/reference-model.js +7 -1
  52. package/dist/lib/references/reference-model.js.map +1 -1
  53. package/dist/lib/references/references-store.d.ts +5 -10
  54. package/dist/lib/references/references-store.js +61 -77
  55. package/dist/lib/references/references-store.js.map +1 -1
  56. package/dist/ui/collapsible-section.d.ts +20 -0
  57. package/dist/ui/collapsible-section.js +18 -0
  58. package/dist/ui/collapsible-section.js.map +1 -0
  59. package/dist/ui/index.d.ts +1 -0
  60. package/dist/ui/index.js +1 -0
  61. package/dist/ui/index.js.map +1 -1
  62. package/package.json +3 -2
package/convex/cms.ts CHANGED
@@ -98,11 +98,15 @@ export const saveDocumentVersion = mutation({
98
98
  },
99
99
  handler: async (ctx, args) => {
100
100
  await requireRole(ctx, 'editor') // EC-150
101
- const existing = await ctx.db
101
+ // EC-302: versions are appended monotonically, so the latest-created row holds
102
+ // the highest version. Read just that one (index-ordered, newest first) instead
103
+ // of collecting the whole — unbounded — history to compute `max+1`.
104
+ const latest = await ctx.db
102
105
  .query('documentVersions')
103
106
  .withIndex('by_doc', (q) => q.eq('collection', args.collection).eq('docId', args.docId))
104
- .collect()
105
- const version = existing.reduce((max, r) => Math.max(max, r.version), 0) + 1
107
+ .order('desc')
108
+ .first()
109
+ const version = (latest?.version ?? 0) + 1
106
110
  const id = await ctx.db.insert('documentVersions', { ...args, version })
107
111
  return ctx.db.get(id)
108
112
  },
@@ -125,11 +129,49 @@ export const getDocumentVersion = query({
125
129
  args: { collection: v.string(), docId: v.string(), version: v.number() },
126
130
  handler: async (ctx, args) => {
127
131
  await requireRole(ctx, 'viewer') // EC-150
128
- const rows = await ctx.db
132
+ // EC-307: address the row directly via `by_doc_version` instead of collecting
133
+ // the whole — unbounded — per-document history and finding in JS. `version` is
134
+ // unique per (collection, docId), so `.unique()` both fetches and asserts that.
135
+ return ctx.db
129
136
  .query('documentVersions')
130
- .withIndex('by_doc', (q) => q.eq('collection', args.collection).eq('docId', args.docId))
131
- .collect()
132
- return rows.find((r) => r.version === args.version) ?? null
137
+ .withIndex('by_doc_version', (q) =>
138
+ q.eq('collection', args.collection).eq('docId', args.docId).eq('version', args.version),
139
+ )
140
+ .unique()
141
+ },
142
+ })
143
+
144
+ /**
145
+ * The pinned published snapshot of every currently-published document (EC-307) —
146
+ * one bulk read collapsing the per-document `listVersions` fan-out the studio's
147
+ * published pool used to issue (`published-documents.ts`). Reads each published
148
+ * document's pinned version row directly via `by_doc_version`. Returns the
149
+ * `CmsDocument` snapshots (the live perspective); unpublished documents are absent.
150
+ */
151
+ export const listPublishedDocuments = query({
152
+ args: { collection: v.optional(v.string()) },
153
+ handler: async (ctx, args) => {
154
+ await requireRole(ctx, 'viewer') // EC-150
155
+ const collection = args.collection
156
+ const docs = collection
157
+ ? await ctx.db
158
+ .query('cmsDocuments')
159
+ .withIndex('by_collection', (q) => q.eq('collection', collection))
160
+ .collect()
161
+ : await ctx.db.query('cmsDocuments').collect()
162
+ const snapshots: unknown[] = []
163
+ for (const doc of docs) {
164
+ const version = doc.publishedVersion
165
+ if (version === undefined) continue
166
+ const pinned = await ctx.db
167
+ .query('documentVersions')
168
+ .withIndex('by_doc_version', (q) =>
169
+ q.eq('collection', doc.collection).eq('docId', doc.docId).eq('version', version),
170
+ )
171
+ .unique()
172
+ if (pinned) snapshots.push(pinned.snapshot)
173
+ }
174
+ return snapshots
133
175
  },
134
176
  })
135
177
 
@@ -136,25 +136,23 @@ export async function loadSnapshot(
136
136
  if (perspective === 'draft') {
137
137
  documents = documentRows.map((row) => row.document as unknown)
138
138
  } else {
139
- const versionsByKey = new Map<string, Map<number, unknown>>()
140
- for (const row of await ctx.db.query('documentVersions').collect()) {
141
- const key = `${row.collection}:${row.docId}`
142
- let byVersion = versionsByKey.get(key)
143
- if (!byVersion) {
144
- byVersion = new Map()
145
- versionsByKey.set(key, byVersion)
146
- }
147
- byVersion.set(row.version, row.snapshot as unknown)
148
- }
149
139
  documents = []
150
140
  for (const row of documentRows) {
151
141
  if (row.publishedVersion === undefined) continue
152
- // Serve the pinned recorded version, NOT the live draft. A dangling pin
153
- // (version missing) is dropped — it never falls back to the draft.
154
- const snapshot = versionsByKey
155
- .get(`${row.collection}:${row.docId}`)
156
- ?.get(row.publishedVersion)
157
- if (snapshot !== undefined) documents.push(snapshot)
142
+ // EC-308: read THIS document's pinned version directly via `by_doc_version`
143
+ // (EC-307) instead of collecting the entire append-only `documentVersions`
144
+ // table and joining in memory — the published snapshot now scales with the
145
+ // number of published docs, not with total edit history. Serve the pinned
146
+ // recorded version, NOT the live draft; a dangling pin (`.unique()` → null)
147
+ // is dropped, never falling back to the draft.
148
+ const version = row.publishedVersion
149
+ const pinned = await ctx.db
150
+ .query('documentVersions')
151
+ .withIndex('by_doc_version', (q) =>
152
+ q.eq('collection', row.collection).eq('docId', row.docId).eq('version', version),
153
+ )
154
+ .unique()
155
+ if (pinned) documents.push(pinned.snapshot as unknown)
158
156
  }
159
157
  }
160
158
 
@@ -187,11 +187,47 @@ export const inbound = query({
187
187
  },
188
188
  })
189
189
 
190
+ export const inboundMany = query({
191
+ args: {
192
+ targets: v.array(v.object({ targetType: targetTypeValidator, targetId: v.string() })),
193
+ variant: v.optional(variantValidator),
194
+ },
195
+ handler: async (ctx, args) => {
196
+ await requireRole(ctx, 'viewer') // EC-150
197
+ // EC-308: inbound entries for many targets in ONE round-trip — loop the
198
+ // `by_target` index server-side instead of the client issuing one `inbound`
199
+ // query per target (the bulk delete/unpublish pre-flight fan-out). One result
200
+ // list per target, in `targets` order.
201
+ const groups = []
202
+ for (const target of args.targets) {
203
+ const rows = await ctx.db
204
+ .query('referenceEntries')
205
+ .withIndex('by_target', (q) => {
206
+ const scoped = q.eq('targetType', target.targetType).eq('targetId', target.targetId)
207
+ return args.variant ? scoped.eq('variant', args.variant) : scoped
208
+ })
209
+ .collect()
210
+ groups.push(rows)
211
+ }
212
+ return groups
213
+ },
214
+ })
215
+
190
216
  export const list = query({
191
217
  args: { variant: v.optional(variantValidator) },
192
218
  handler: async (ctx, args) => {
193
219
  await requireRole(ctx, 'viewer') // EC-150
194
- const docs = await ctx.db.query('referenceEntries').collect()
195
- return args.variant ? docs.filter((d) => d.variant === args.variant) : docs
220
+ // EC-302: `by_source` leads with `variant`, so a variant filter is an index
221
+ // prefix — push it into the query instead of collecting the whole table and
222
+ // filtering in JS. Callers treat the result as an unordered set, so the shift
223
+ // from insertion order to index order is immaterial.
224
+ const variant = args.variant
225
+ if (variant !== undefined) {
226
+ return ctx.db
227
+ .query('referenceEntries')
228
+ .withIndex('by_source', (q) => q.eq('variant', variant))
229
+ .collect()
230
+ }
231
+ return ctx.db.query('referenceEntries').collect()
196
232
  },
197
233
  })
package/convex/schema.ts CHANGED
@@ -108,7 +108,12 @@ export default defineSchema({
108
108
  snapshot: v.any(),
109
109
  label: v.optional(v.string()),
110
110
  createdAt: v.string(),
111
- }).index('by_doc', ['collection', 'docId']),
111
+ })
112
+ .index('by_doc', ['collection', 'docId'])
113
+ // EC-307: address one recorded version directly (the hot restore/publish-pool
114
+ // path) instead of collecting a document's whole — unbounded — history and
115
+ // finding in JS. `version` is unique per (collection, docId), so a lookup is `.unique()`.
116
+ .index('by_doc_version', ['collection', 'docId', 'version']),
112
117
 
113
118
  /** Asset metadata; bytes live in Convex/file storage behind the blob adapter (EC-044). */
114
119
  assets: defineTable({
@@ -170,8 +170,11 @@ function DeleteAssetButton({ asset }) {
170
170
  const confirm = async () => {
171
171
  setBusy(true);
172
172
  setError(null);
173
- const inUse = (usages?.length ?? 0) > 0;
174
- const result = await store.remove(asset.id, { confirmInUse: inUse });
173
+ // The dialog has already enumerated usages (or shown there are none) and the user
174
+ // is confirming deletion either way — so confirm unconditionally, letting
175
+ // deleteAsset skip its redundant inbound re-check (EC-301 perf). Weak-by-default:
176
+ // a still-referenced asset deletes anyway, dangling into a visible issue.
177
+ const result = await store.remove(asset.id, { confirmInUse: true });
175
178
  setBusy(false);
176
179
  if (result.outcome === 'deleted' || result.outcome === 'not-found') {
177
180
  setOpen(false);
@@ -1 +1 @@
1
- {"version":3,"file":"asset-library.js","sourceRoot":"","sources":["../../../src/components/assets/asset-library.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n AlertTriangle,\n ImageIcon,\n Link2,\n Pencil,\n RotateCcw,\n Search,\n Trash2,\n Upload,\n X,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { ChangeEvent, DragEvent } from 'react'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport type { UploadState } from '@elytracms/studio/lib/assets'\nimport { buildAssetReferenceImpact } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport {\n Badge,\n Button,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Grid,\n Heading,\n Input,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { filterAssets } from './asset-search'\nimport { useAssets } from './assets-store'\nimport type { UploadEntry } from './assets-store'\n\n/**\n * Asset library surface (EC-151, EC-152): file picker + drag-and-drop uploads\n * with visible progress/failure/retry, a searchable grid of stored assets\n * (filename/alt/title), metadata editing (alt, title — persisted through the\n * operations asset facade), and reference-respecting deletion (usage count +\n * block-or-confirm). All storage access goes through the persistence adapter\n * via the assets store. Each card answers \"where is this asset used?\" via the\n * shared references panel (EC-172); `focusAssetId` (the `?asset=` deep-link\n * param) scrolls that card into view and opens its references dialog.\n */\nexport function AssetLibrary({\n projectId,\n focusAssetId,\n}: {\n projectId: string\n focusAssetId?: string\n}) {\n const store = useAssets()\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n const [dragging, setDragging] = useState(false)\n const [query, setQuery] = useState('')\n\n const onPick = (event: ChangeEvent<HTMLInputElement>) => {\n if (event.target.files) store.upload([...event.target.files])\n event.target.value = ''\n }\n\n const onDrop = (event: DragEvent) => {\n event.preventDefault()\n setDragging(false)\n if (event.dataTransfer.files.length > 0) store.upload([...event.dataTransfer.files])\n }\n\n if (store.status === 'error') {\n return (\n <Stack gap=\"2\" className=\"p-6\" data-testid=\"assets-error\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text weight=\"semibold\">Assets failed to load</Text>\n </Row>\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? 'The persistence backend is unreachable.'}\n </Text>\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void store.refresh()}>\n <RotateCcw /> Retry\n </Button>\n </Row>\n </Stack>\n )\n }\n\n return (\n <Stack\n gap=\"4\"\n className={cn(\n 'flex-1 p-4 sm:p-6',\n dragging && 'outline-2 outline-dashed outline-primary/60 -outline-offset-4',\n )}\n onDragOver={(event) => {\n event.preventDefault()\n setDragging(true)\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n data-testid=\"asset-library\"\n >\n <Row gap=\"2\">\n <Heading level={2} size=\"h2\">\n Assets\n </Heading>\n <Badge variant=\"outline\">{store.assets.length}</Badge>\n <Spacer />\n <AssetSearchInput value={query} onChange={setQuery} />\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={onPick}\n data-testid=\"asset-file-input\"\n />\n <Button size=\"sm\" onClick={() => fileInputRef.current?.click()}>\n <Upload /> Upload\n </Button>\n </Row>\n\n <DropHint visible={dragging || store.assets.length === 0} />\n\n {store.uploads.length > 0 ? (\n <Panel data-testid=\"upload-queue\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n Uploads\n </Text>\n </PanelHeader>\n <PanelBody className=\"divide-y divide-border p-0\">\n {store.uploads.map((entry) => (\n <UploadRow\n key={entry.id}\n entry={entry}\n onRetry={() => store.retryUpload(entry.id)}\n onDismiss={() => store.dismissUpload(entry.id)}\n />\n ))}\n </PanelBody>\n </Panel>\n ) : null}\n\n {store.status === 'loading' ? (\n <Text size=\"sm\" tone=\"muted\">\n Loading assets…\n </Text>\n ) : (\n <AssetGrid\n assets={filterAssets(store.assets, query)}\n filtered={query.trim().length > 0}\n projectId={projectId}\n focusAssetId={focusAssetId}\n />\n )}\n </Stack>\n )\n}\n\n/** Named search primitive over filename / alt / title (EC-152). */\nfunction AssetSearchInput({\n value,\n onChange,\n}: {\n value: string\n onChange: (value: string) => void\n}) {\n return (\n <span className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder=\"Search assets…\"\n aria-label=\"Search assets\"\n className=\"w-56 pl-8\"\n data-testid=\"asset-search\"\n />\n </span>\n )\n}\n\n/** Persistent drop affordance; doubles as the empty state. */\nfunction DropHint({ visible }: { visible: boolean }) {\n return (\n <Stack\n gap=\"1\"\n align=\"center\"\n className={cn(\n 'rounded-lg border border-dashed border-border p-6 text-center',\n !visible && 'hidden sm:flex',\n )}\n >\n <ImageIcon className=\"size-5 text-muted-foreground\" />\n <Text size=\"sm\" tone=\"muted\">\n Drop files anywhere to upload, or use the Upload button.\n </Text>\n </Stack>\n )\n}\n\nconst PHASE_LABEL: Record<UploadState['phase'], string> = {\n idle: 'queued',\n preparing: 'preparing',\n uploading: 'uploading',\n confirming: 'saving metadata',\n done: 'done',\n failed: 'failed',\n}\n\n/** One in-flight upload: progress while transferring, explicit failure + retry. */\nfunction UploadRow({\n entry,\n onRetry,\n onDismiss,\n}: {\n entry: UploadEntry\n onRetry: () => void\n onDismiss: () => void\n}) {\n const { state } = entry\n const failed = state.phase === 'failed'\n return (\n <Stack gap=\"1\" className=\"px-3 py-2\" data-testid=\"upload-row\">\n <Row gap=\"2\">\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 flex-1 truncate\">\n {entry.filename}\n </Text>\n <Badge variant={failed ? 'error' : state.phase === 'done' ? 'success' : 'info'}>\n {PHASE_LABEL[state.phase]}\n {state.attempt > 1 ? ` · attempt ${state.attempt}` : ''}\n </Badge>\n {failed ? (\n <>\n {state.failure?.retryable !== false ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} data-testid=\"upload-retry\">\n <RotateCcw /> Retry\n </Button>\n ) : null}\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={onDismiss} aria-label=\"Dismiss upload\">\n <X />\n </Button>\n </>\n ) : null}\n </Row>\n {failed ? (\n <Text size=\"xs\" tone=\"muted\" data-testid=\"upload-error\">\n {state.failure?.stage}: {state.failure?.message}\n </Text>\n ) : (\n <ProgressBar value={state.progress} />\n )}\n </Stack>\n )\n}\n\n/** Named progress primitive for byte transfer. */\nfunction ProgressBar({ value }: { value: number }) {\n return (\n <span className=\"block h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <span\n className=\"block h-full rounded-full bg-primary transition-[width]\"\n style={{ width: `${Math.round(value * 100)}%` }}\n />\n </span>\n )\n}\n\nfunction AssetGrid({\n assets,\n filtered,\n projectId,\n focusAssetId,\n}: {\n assets: readonly AssetRecord[]\n filtered: boolean\n projectId: string\n focusAssetId?: string\n}) {\n if (assets.length === 0) {\n // The unfiltered empty state is the DropHint above; the *filtered* empty\n // state must say so explicitly (deterministic, reproducible).\n return filtered ? (\n <Text size=\"sm\" tone=\"muted\" data-testid=\"asset-search-empty\">\n No assets match your search.\n </Text>\n ) : null\n }\n return (\n <Grid minItemWidth=\"220px\" gap=\"3\" data-testid=\"asset-grid\">\n {assets.map((asset) => (\n <AssetCard\n key={asset.id}\n asset={asset}\n projectId={projectId}\n focused={asset.id === focusAssetId}\n />\n ))}\n </Grid>\n )\n}\n\nfunction formatBytes(byteSize: number): string {\n if (byteSize < 1024) return `${byteSize} B`\n if (byteSize < 1024 * 1024) return `${(byteSize / 1024).toFixed(1)} KB`\n return `${(byteSize / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction AssetCard({\n asset,\n projectId,\n focused,\n}: {\n asset: AssetRecord\n projectId: string\n /** Deep-link target (`?asset=`): scroll into view + open the references dialog. */\n focused?: boolean\n}) {\n const cardRef = useRef<HTMLDivElement | null>(null)\n useEffect(() => {\n if (focused) cardRef.current?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }, [focused])\n return (\n <div ref={cardRef}>\n <Panel\n className={cn('overflow-hidden', focused && 'ring-2 ring-primary/70')}\n data-testid=\"asset-card\"\n >\n <AssetThumbnail asset={asset} />\n <Stack gap=\"1\" className=\"p-3\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate\" title={asset.filename}>\n {asset.title ?? asset.filename}\n </Text>\n {asset.title ? (\n <Text size=\"xs\" tone=\"muted\" className=\"truncate\">\n {asset.filename}\n </Text>\n ) : null}\n <Text\n size=\"xs\"\n tone={asset.alt ? 'muted' : 'subtle'}\n className=\"truncate\"\n data-testid=\"asset-alt\"\n >\n {asset.alt ? `Alt: ${asset.alt}` : 'No alt text'}\n </Text>\n <Row gap=\"1\" wrap>\n <Code tone=\"muted\">{asset.contentType}</Code>\n {asset.width && asset.height ? (\n <Code tone=\"muted\">\n {asset.width}×{asset.height}\n </Code>\n ) : null}\n <Code tone=\"muted\">{formatBytes(asset.byteSize)}</Code>\n </Row>\n <Row gap=\"1\">\n <Code tone=\"muted\" className=\"min-w-0 flex-1 truncate\">\n {asset.id}\n </Code>\n <AssetReferencesButton asset={asset} projectId={projectId} autoOpen={focused} />\n <EditAssetButton asset={asset} />\n <DeleteAssetButton asset={asset} />\n </Row>\n </Stack>\n </Panel>\n </div>\n )\n}\n\n/**\n * \"Where is this asset used?\" (EC-172): the shared references panel, inbound\n * over the EC-155 index, hosted in a dialog per asset card.\n */\nfunction AssetReferencesButton({\n asset,\n projectId,\n autoOpen,\n}: {\n asset: AssetRecord\n projectId: string\n autoOpen?: boolean\n}) {\n const [open, setOpen] = useState(false)\n useEffect(() => {\n if (autoOpen) setOpen(true)\n }, [autoOpen])\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`References of ${asset.filename}`}\n onClick={() => setOpen(true)}\n data-testid=\"asset-references\"\n >\n <Link2 />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>Used by — {asset.title ?? asset.filename}</DialogTitle>\n <DialogDescription>\n Everything referencing this asset, with deep links to the exact field or node.\n </DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: asset.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Metadata editing (EC-152): alt + title, persisted through the operations\n * asset facade (`assets.upsert`). Failures render in the dialog — never a\n * silent loss.\n */\nfunction EditAssetButton({ asset }: { asset: AssetRecord }) {\n const store = useAssets()\n const [open, setOpen] = useState(false)\n const [alt, setAlt] = useState('')\n const [title, setTitle] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = () => {\n setAlt(asset.alt ?? '')\n setTitle(asset.title ?? '')\n setError(null)\n setOpen(true)\n }\n\n const save = async () => {\n setBusy(true)\n setError(null)\n const result = await store.updateMetadata(asset.id, { alt, title })\n setBusy(false)\n if (result.ok) setOpen(false)\n else setError(result.message)\n }\n\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Edit ${asset.filename}`}\n onClick={openDialog}\n data-testid=\"asset-edit\"\n >\n <Pencil />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Edit {asset.filename}</DialogTitle>\n <DialogDescription>\n Alt text flows through the delivery shape into rendered markup; the title is the\n library display name.\n </DialogDescription>\n </DialogHeader>\n <Field label=\"Alt text\" htmlFor={`asset-alt-${asset.id}`}>\n <Input\n id={`asset-alt-${asset.id}`}\n value={alt}\n onChange={(e) => setAlt(e.target.value)}\n placeholder=\"Describe the image…\"\n data-testid=\"asset-edit-alt\"\n />\n </Field>\n <Field label=\"Title\" htmlFor={`asset-title-${asset.id}`}>\n <Input\n id={`asset-title-${asset.id}`}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder={asset.filename}\n data-testid=\"asset-edit-title\"\n />\n </Field>\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-edit-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={busy}\n data-testid=\"asset-edit-save\"\n >\n Save\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/** Image preview from the stored serve URL; icon fallback for other types. */\nfunction AssetThumbnail({ asset }: { asset: AssetRecord }) {\n const isImage = asset.contentType.startsWith('image/')\n // In-memory/opaque locators (memory://, convex://) are not fetchable by <img>.\n const resolvable = asset.url?.startsWith('http') ?? false\n if (isImage && asset.url && resolvable) {\n return (\n <img\n src={asset.url}\n alt={asset.alt ?? asset.filename}\n className=\"aspect-video w-full bg-muted object-cover\"\n />\n )\n }\n return (\n <span className=\"grid aspect-video w-full place-items-center bg-muted text-muted-foreground\">\n <ImageIcon className=\"size-6\" />\n </span>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): the dialog enumerates the exact\n * inbound references — index-accurate (EC-155), with deep links to the field or\n * node — using the shared EC-173 impact rows. Deleting a referenced asset is\n * weak-by-default (allowed, with an explicit \"delete anyway\"): every reference\n * dangles into a visible validation issue rather than silently breaking.\n */\nfunction DeleteAssetButton({ asset }: { asset: AssetRecord }) {\n const store = useAssets()\n const [open, setOpen] = useState(false)\n const [usages, setUsages] = useState<ReferenceEntryView[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = async () => {\n setOpen(true)\n setError(null)\n setUsages(null)\n try {\n setUsages(await store.usagesOf(asset.id))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const confirm = async () => {\n setBusy(true)\n setError(null)\n const inUse = (usages?.length ?? 0) > 0\n const result = await store.remove(asset.id, { confirmInUse: inUse })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n } else if (result.outcome === 'blocked') {\n // A reference appeared between the pre-flight read and confirm (rare race).\n setError(`Asset is now referenced in ${result.usageCount} place(s) — reopen to review.`)\n } else {\n setError(result.message)\n }\n }\n\n const impact = usages ? buildAssetReferenceImpact(usages) : null\n const usageCount = impact?.broken.length ?? 0\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Delete ${asset.filename}`}\n onClick={() => void openDialog()}\n data-testid=\"asset-delete\"\n >\n <Trash2 />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>Delete {asset.filename}?</DialogTitle>\n <DialogDescription>\n Removes the stored bytes and the metadata record. This cannot be undone.\n </DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n Checking references…\n </Text>\n ) : usageCount > 0 ? (\n <Stack gap=\"2\" data-testid=\"asset-usage-warning\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" weight=\"semibold\">\n In use in {usageCount} place{usageCount === 1 ? '' : 's'}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={store.projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n Deleting will leave these references broken — they will surface as validation\n issues.\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n No references found. Safe to delete.\n </Text>\n )}\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-delete-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n Cancel\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => void confirm()}\n disabled={busy || usages === null}\n data-testid=\"asset-delete-confirm\"\n >\n {usageCount > 0 ? 'Delete anyway' : 'Delete'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
1
+ {"version":3,"file":"asset-library.js","sourceRoot":"","sources":["../../../src/components/assets/asset-library.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n AlertTriangle,\n ImageIcon,\n Link2,\n Pencil,\n RotateCcw,\n Search,\n Trash2,\n Upload,\n X,\n} from 'lucide-react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { ChangeEvent, DragEvent } from 'react'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport type { UploadState } from '@elytracms/studio/lib/assets'\nimport { buildAssetReferenceImpact } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { ReferenceImpactRows } from '../cms/reference-confirm'\nimport { ReferencesPanel } from '../references/references-panel'\nimport {\n Badge,\n Button,\n Code,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Field,\n Grid,\n Heading,\n Input,\n Panel,\n PanelBody,\n PanelHeader,\n Row,\n Spacer,\n Stack,\n Text,\n} from '@elytracms/studio/ui'\nimport { filterAssets } from './asset-search'\nimport { useAssets } from './assets-store'\nimport type { UploadEntry } from './assets-store'\n\n/**\n * Asset library surface (EC-151, EC-152): file picker + drag-and-drop uploads\n * with visible progress/failure/retry, a searchable grid of stored assets\n * (filename/alt/title), metadata editing (alt, title — persisted through the\n * operations asset facade), and reference-respecting deletion (usage count +\n * block-or-confirm). All storage access goes through the persistence adapter\n * via the assets store. Each card answers \"where is this asset used?\" via the\n * shared references panel (EC-172); `focusAssetId` (the `?asset=` deep-link\n * param) scrolls that card into view and opens its references dialog.\n */\nexport function AssetLibrary({\n projectId,\n focusAssetId,\n}: {\n projectId: string\n focusAssetId?: string\n}) {\n const store = useAssets()\n const fileInputRef = useRef<HTMLInputElement | null>(null)\n const [dragging, setDragging] = useState(false)\n const [query, setQuery] = useState('')\n\n const onPick = (event: ChangeEvent<HTMLInputElement>) => {\n if (event.target.files) store.upload([...event.target.files])\n event.target.value = ''\n }\n\n const onDrop = (event: DragEvent) => {\n event.preventDefault()\n setDragging(false)\n if (event.dataTransfer.files.length > 0) store.upload([...event.dataTransfer.files])\n }\n\n if (store.status === 'error') {\n return (\n <Stack gap=\"2\" className=\"p-6\" data-testid=\"assets-error\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-destructive\" />\n <Text weight=\"semibold\">Assets failed to load</Text>\n </Row>\n <Text size=\"sm\" tone=\"muted\">\n {store.errorMessage ?? 'The persistence backend is unreachable.'}\n </Text>\n <Row>\n <Button variant=\"outline\" size=\"sm\" onClick={() => void store.refresh()}>\n <RotateCcw /> Retry\n </Button>\n </Row>\n </Stack>\n )\n }\n\n return (\n <Stack\n gap=\"4\"\n className={cn(\n 'flex-1 p-4 sm:p-6',\n dragging && 'outline-2 outline-dashed outline-primary/60 -outline-offset-4',\n )}\n onDragOver={(event) => {\n event.preventDefault()\n setDragging(true)\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n data-testid=\"asset-library\"\n >\n <Row gap=\"2\">\n <Heading level={2} size=\"h2\">\n Assets\n </Heading>\n <Badge variant=\"outline\">{store.assets.length}</Badge>\n <Spacer />\n <AssetSearchInput value={query} onChange={setQuery} />\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n onChange={onPick}\n data-testid=\"asset-file-input\"\n />\n <Button size=\"sm\" onClick={() => fileInputRef.current?.click()}>\n <Upload /> Upload\n </Button>\n </Row>\n\n <DropHint visible={dragging || store.assets.length === 0} />\n\n {store.uploads.length > 0 ? (\n <Panel data-testid=\"upload-queue\">\n <PanelHeader>\n <Text size=\"xs\" weight=\"semibold\" tone=\"muted\">\n Uploads\n </Text>\n </PanelHeader>\n <PanelBody className=\"divide-y divide-border p-0\">\n {store.uploads.map((entry) => (\n <UploadRow\n key={entry.id}\n entry={entry}\n onRetry={() => store.retryUpload(entry.id)}\n onDismiss={() => store.dismissUpload(entry.id)}\n />\n ))}\n </PanelBody>\n </Panel>\n ) : null}\n\n {store.status === 'loading' ? (\n <Text size=\"sm\" tone=\"muted\">\n Loading assets…\n </Text>\n ) : (\n <AssetGrid\n assets={filterAssets(store.assets, query)}\n filtered={query.trim().length > 0}\n projectId={projectId}\n focusAssetId={focusAssetId}\n />\n )}\n </Stack>\n )\n}\n\n/** Named search primitive over filename / alt / title (EC-152). */\nfunction AssetSearchInput({\n value,\n onChange,\n}: {\n value: string\n onChange: (value: string) => void\n}) {\n return (\n <span className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground\" />\n <Input\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder=\"Search assets…\"\n aria-label=\"Search assets\"\n className=\"w-56 pl-8\"\n data-testid=\"asset-search\"\n />\n </span>\n )\n}\n\n/** Persistent drop affordance; doubles as the empty state. */\nfunction DropHint({ visible }: { visible: boolean }) {\n return (\n <Stack\n gap=\"1\"\n align=\"center\"\n className={cn(\n 'rounded-lg border border-dashed border-border p-6 text-center',\n !visible && 'hidden sm:flex',\n )}\n >\n <ImageIcon className=\"size-5 text-muted-foreground\" />\n <Text size=\"sm\" tone=\"muted\">\n Drop files anywhere to upload, or use the Upload button.\n </Text>\n </Stack>\n )\n}\n\nconst PHASE_LABEL: Record<UploadState['phase'], string> = {\n idle: 'queued',\n preparing: 'preparing',\n uploading: 'uploading',\n confirming: 'saving metadata',\n done: 'done',\n failed: 'failed',\n}\n\n/** One in-flight upload: progress while transferring, explicit failure + retry. */\nfunction UploadRow({\n entry,\n onRetry,\n onDismiss,\n}: {\n entry: UploadEntry\n onRetry: () => void\n onDismiss: () => void\n}) {\n const { state } = entry\n const failed = state.phase === 'failed'\n return (\n <Stack gap=\"1\" className=\"px-3 py-2\" data-testid=\"upload-row\">\n <Row gap=\"2\">\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 flex-1 truncate\">\n {entry.filename}\n </Text>\n <Badge variant={failed ? 'error' : state.phase === 'done' ? 'success' : 'info'}>\n {PHASE_LABEL[state.phase]}\n {state.attempt > 1 ? ` · attempt ${state.attempt}` : ''}\n </Badge>\n {failed ? (\n <>\n {state.failure?.retryable !== false ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} data-testid=\"upload-retry\">\n <RotateCcw /> Retry\n </Button>\n ) : null}\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={onDismiss} aria-label=\"Dismiss upload\">\n <X />\n </Button>\n </>\n ) : null}\n </Row>\n {failed ? (\n <Text size=\"xs\" tone=\"muted\" data-testid=\"upload-error\">\n {state.failure?.stage}: {state.failure?.message}\n </Text>\n ) : (\n <ProgressBar value={state.progress} />\n )}\n </Stack>\n )\n}\n\n/** Named progress primitive for byte transfer. */\nfunction ProgressBar({ value }: { value: number }) {\n return (\n <span className=\"block h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <span\n className=\"block h-full rounded-full bg-primary transition-[width]\"\n style={{ width: `${Math.round(value * 100)}%` }}\n />\n </span>\n )\n}\n\nfunction AssetGrid({\n assets,\n filtered,\n projectId,\n focusAssetId,\n}: {\n assets: readonly AssetRecord[]\n filtered: boolean\n projectId: string\n focusAssetId?: string\n}) {\n if (assets.length === 0) {\n // The unfiltered empty state is the DropHint above; the *filtered* empty\n // state must say so explicitly (deterministic, reproducible).\n return filtered ? (\n <Text size=\"sm\" tone=\"muted\" data-testid=\"asset-search-empty\">\n No assets match your search.\n </Text>\n ) : null\n }\n return (\n <Grid minItemWidth=\"220px\" gap=\"3\" data-testid=\"asset-grid\">\n {assets.map((asset) => (\n <AssetCard\n key={asset.id}\n asset={asset}\n projectId={projectId}\n focused={asset.id === focusAssetId}\n />\n ))}\n </Grid>\n )\n}\n\nfunction formatBytes(byteSize: number): string {\n if (byteSize < 1024) return `${byteSize} B`\n if (byteSize < 1024 * 1024) return `${(byteSize / 1024).toFixed(1)} KB`\n return `${(byteSize / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction AssetCard({\n asset,\n projectId,\n focused,\n}: {\n asset: AssetRecord\n projectId: string\n /** Deep-link target (`?asset=`): scroll into view + open the references dialog. */\n focused?: boolean\n}) {\n const cardRef = useRef<HTMLDivElement | null>(null)\n useEffect(() => {\n if (focused) cardRef.current?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }, [focused])\n return (\n <div ref={cardRef}>\n <Panel\n className={cn('overflow-hidden', focused && 'ring-2 ring-primary/70')}\n data-testid=\"asset-card\"\n >\n <AssetThumbnail asset={asset} />\n <Stack gap=\"1\" className=\"p-3\">\n <Text size=\"sm\" weight=\"medium\" className=\"truncate\" title={asset.filename}>\n {asset.title ?? asset.filename}\n </Text>\n {asset.title ? (\n <Text size=\"xs\" tone=\"muted\" className=\"truncate\">\n {asset.filename}\n </Text>\n ) : null}\n <Text\n size=\"xs\"\n tone={asset.alt ? 'muted' : 'subtle'}\n className=\"truncate\"\n data-testid=\"asset-alt\"\n >\n {asset.alt ? `Alt: ${asset.alt}` : 'No alt text'}\n </Text>\n <Row gap=\"1\" wrap>\n <Code tone=\"muted\">{asset.contentType}</Code>\n {asset.width && asset.height ? (\n <Code tone=\"muted\">\n {asset.width}×{asset.height}\n </Code>\n ) : null}\n <Code tone=\"muted\">{formatBytes(asset.byteSize)}</Code>\n </Row>\n <Row gap=\"1\">\n <Code tone=\"muted\" className=\"min-w-0 flex-1 truncate\">\n {asset.id}\n </Code>\n <AssetReferencesButton asset={asset} projectId={projectId} autoOpen={focused} />\n <EditAssetButton asset={asset} />\n <DeleteAssetButton asset={asset} />\n </Row>\n </Stack>\n </Panel>\n </div>\n )\n}\n\n/**\n * \"Where is this asset used?\" (EC-172): the shared references panel, inbound\n * over the EC-155 index, hosted in a dialog per asset card.\n */\nfunction AssetReferencesButton({\n asset,\n projectId,\n autoOpen,\n}: {\n asset: AssetRecord\n projectId: string\n autoOpen?: boolean\n}) {\n const [open, setOpen] = useState(false)\n useEffect(() => {\n if (autoOpen) setOpen(true)\n }, [autoOpen])\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`References of ${asset.filename}`}\n onClick={() => setOpen(true)}\n data-testid=\"asset-references\"\n >\n <Link2 />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>Used by — {asset.title ?? asset.filename}</DialogTitle>\n <DialogDescription>\n Everything referencing this asset, with deep links to the exact field or node.\n </DialogDescription>\n </DialogHeader>\n {open ? (\n <ReferencesPanel projectId={projectId} target={{ type: 'asset', id: asset.id }} />\n ) : null}\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/**\n * Metadata editing (EC-152): alt + title, persisted through the operations\n * asset facade (`assets.upsert`). Failures render in the dialog — never a\n * silent loss.\n */\nfunction EditAssetButton({ asset }: { asset: AssetRecord }) {\n const store = useAssets()\n const [open, setOpen] = useState(false)\n const [alt, setAlt] = useState('')\n const [title, setTitle] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = () => {\n setAlt(asset.alt ?? '')\n setTitle(asset.title ?? '')\n setError(null)\n setOpen(true)\n }\n\n const save = async () => {\n setBusy(true)\n setError(null)\n const result = await store.updateMetadata(asset.id, { alt, title })\n setBusy(false)\n if (result.ok) setOpen(false)\n else setError(result.message)\n }\n\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Edit ${asset.filename}`}\n onClick={openDialog}\n data-testid=\"asset-edit\"\n >\n <Pencil />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-w-md\">\n <DialogHeader>\n <DialogTitle>Edit {asset.filename}</DialogTitle>\n <DialogDescription>\n Alt text flows through the delivery shape into rendered markup; the title is the\n library display name.\n </DialogDescription>\n </DialogHeader>\n <Field label=\"Alt text\" htmlFor={`asset-alt-${asset.id}`}>\n <Input\n id={`asset-alt-${asset.id}`}\n value={alt}\n onChange={(e) => setAlt(e.target.value)}\n placeholder=\"Describe the image…\"\n data-testid=\"asset-edit-alt\"\n />\n </Field>\n <Field label=\"Title\" htmlFor={`asset-title-${asset.id}`}>\n <Input\n id={`asset-title-${asset.id}`}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder={asset.filename}\n data-testid=\"asset-edit-title\"\n />\n </Field>\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-edit-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n onClick={() => void save()}\n disabled={busy}\n data-testid=\"asset-edit-save\"\n >\n Save\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\n/** Image preview from the stored serve URL; icon fallback for other types. */\nfunction AssetThumbnail({ asset }: { asset: AssetRecord }) {\n const isImage = asset.contentType.startsWith('image/')\n // In-memory/opaque locators (memory://, convex://) are not fetchable by <img>.\n const resolvable = asset.url?.startsWith('http') ?? false\n if (isImage && asset.url && resolvable) {\n return (\n <img\n src={asset.url}\n alt={asset.alt ?? asset.filename}\n className=\"aspect-video w-full bg-muted object-cover\"\n />\n )\n }\n return (\n <span className=\"grid aspect-video w-full place-items-center bg-muted text-muted-foreground\">\n <ImageIcon className=\"size-6\" />\n </span>\n )\n}\n\n/**\n * Reference-respecting delete (EC-151, EC-202): the dialog enumerates the exact\n * inbound references — index-accurate (EC-155), with deep links to the field or\n * node — using the shared EC-173 impact rows. Deleting a referenced asset is\n * weak-by-default (allowed, with an explicit \"delete anyway\"): every reference\n * dangles into a visible validation issue rather than silently breaking.\n */\nfunction DeleteAssetButton({ asset }: { asset: AssetRecord }) {\n const store = useAssets()\n const [open, setOpen] = useState(false)\n const [usages, setUsages] = useState<ReferenceEntryView[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n\n const openDialog = async () => {\n setOpen(true)\n setError(null)\n setUsages(null)\n try {\n setUsages(await store.usagesOf(asset.id))\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n }\n }\n\n const confirm = async () => {\n setBusy(true)\n setError(null)\n // The dialog has already enumerated usages (or shown there are none) and the user\n // is confirming deletion either way — so confirm unconditionally, letting\n // deleteAsset skip its redundant inbound re-check (EC-301 perf). Weak-by-default:\n // a still-referenced asset deletes anyway, dangling into a visible issue.\n const result = await store.remove(asset.id, { confirmInUse: true })\n setBusy(false)\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n setOpen(false)\n } else if (result.outcome === 'blocked') {\n // A reference appeared between the pre-flight read and confirm (rare race).\n setError(`Asset is now referenced in ${result.usageCount} place(s) — reopen to review.`)\n } else {\n setError(result.message)\n }\n }\n\n const impact = usages ? buildAssetReferenceImpact(usages) : null\n const usageCount = impact?.broken.length ?? 0\n return (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Delete ${asset.filename}`}\n onClick={() => void openDialog()}\n data-testid=\"asset-delete\"\n >\n <Trash2 />\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"max-h-[80vh] max-w-md overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>Delete {asset.filename}?</DialogTitle>\n <DialogDescription>\n Removes the stored bytes and the metadata record. This cannot be undone.\n </DialogDescription>\n </DialogHeader>\n {usages === null && error === null ? (\n <Text size=\"sm\" tone=\"muted\">\n Checking references…\n </Text>\n ) : usageCount > 0 ? (\n <Stack gap=\"2\" data-testid=\"asset-usage-warning\">\n <Row gap=\"2\">\n <AlertTriangle className=\"size-4 text-warning\" />\n <Text size=\"sm\" weight=\"semibold\">\n In use in {usageCount} place{usageCount === 1 ? '' : 's'}\n </Text>\n </Row>\n <ReferenceImpactRows\n projectId={store.projectId}\n broken={impact!.broken}\n onNavigate={() => setOpen(false)}\n />\n <Text size=\"xs\" tone=\"muted\">\n Deleting will leave these references broken — they will surface as validation\n issues.\n </Text>\n </Stack>\n ) : (\n <Text size=\"sm\" tone=\"muted\">\n No references found. Safe to delete.\n </Text>\n )}\n {error ? (\n <Text size=\"sm\" className=\"text-destructive\" data-testid=\"asset-delete-error\">\n {error}\n </Text>\n ) : null}\n <DialogFooter>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setOpen(false)} disabled={busy}>\n Cancel\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n onClick={() => void confirm()}\n disabled={busy || usages === null}\n data-testid=\"asset-delete-confirm\"\n >\n {usageCount > 0 ? 'Delete anyway' : 'Delete'}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n"]}
@@ -1,43 +1,54 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
3
3
  import { deleteAsset, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets';
4
4
  import { loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references';
5
5
  import { getStudioOperations, operationsAssetRepository } from '@elytracms/studio/lib/operations';
6
+ import { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query';
6
7
  import { updateAssetMetadata } from './asset-metadata';
7
8
  const AssetsContext = createContext(null);
9
+ const EMPTY_ASSETS = [];
10
+ /** Cache key for a project's asset library (shared by the query and its patches). */
11
+ function assetsQueryKey(projectId) {
12
+ return ['assets', projectId];
13
+ }
14
+ /** Load a project's asset library (pure over the operations client). A list
15
+ * failure throws so the query surfaces it as an error status. */
16
+ async function loadAssets(operations, projectId) {
17
+ const result = await operations.assets.list({ projectId });
18
+ if (!result.ok)
19
+ throw new Error(result.error.message);
20
+ return [...result.value];
21
+ }
8
22
  export function AssetsProvider({ projectId, children, }) {
9
23
  const uploadFilesRef = useRef(new Map());
10
24
  const uploadSeq = useRef(0);
11
- const [status, setStatus] = useState('loading');
12
- const [errorMessage, setErrorMessage] = useState(undefined);
13
- const [assets, setAssets] = useState([]);
14
25
  const [uploads, setUploads] = useState([]);
15
26
  /** The shared studio operations bundle + the metadata-write facade over it. */
16
27
  const studio = useCallback(async () => {
17
28
  const bundle = await getStudioOperations();
18
29
  return { ...bundle, assetRepo: operationsAssetRepository(bundle.operations) };
19
30
  }, []);
31
+ // The library list, read through the one operations-backed query cache
32
+ // (EC-308/AD-17). No `invalidateOn`: this provider is the sole writer of its
33
+ // project's assets, so its own mutations patch the cache in place (the EC-301
34
+ // rule — a metadata or focal-point edit must never trigger a full re-list).
35
+ const query = useOperationQuery({
36
+ queryKey: assetsQueryKey(projectId),
37
+ loader: (operations) => loadAssets(operations, projectId),
38
+ });
39
+ const cache = useOperationCache();
40
+ const refetch = query.refetch;
41
+ const assets = query.data ?? EMPTY_ASSETS;
42
+ const status = query.isPending
43
+ ? 'loading'
44
+ : query.isError
45
+ ? 'error'
46
+ : 'ready';
47
+ const errorMessage = query.isError && query.error instanceof Error ? query.error.message : undefined;
48
+ /** Re-list from the backend (the public escape hatch + post-upload reconcile). */
20
49
  const refresh = useCallback(async () => {
21
- try {
22
- const { operations } = await studio();
23
- const result = await operations.assets.list({ projectId });
24
- if (!result.ok) {
25
- setStatus('error');
26
- setErrorMessage(result.error.message);
27
- return;
28
- }
29
- setAssets(result.value);
30
- setStatus('ready');
31
- setErrorMessage(undefined);
32
- }
33
- catch (error) {
34
- setStatus('error');
35
- setErrorMessage(error instanceof Error ? error.message : String(error));
36
- }
37
- }, [studio, projectId]);
38
- useEffect(() => {
39
- void refresh();
40
- }, [refresh]);
50
+ await refetch();
51
+ }, [refetch]);
41
52
  const setUploadState = useCallback((uploadId, state) => {
42
53
  setUploads((prev) => prev.map((u) => (u.id === uploadId ? { ...u, state } : u)));
43
54
  }, []);
@@ -94,25 +105,29 @@ export function AssetsProvider({ projectId, children, }) {
94
105
  // facade); blob byte deletion stays on the adapter; the usage gate reads
95
106
  // the EC-155 reference index (`adapter.references`, EC-202).
96
107
  const result = await deleteAsset({ assets: assetRepo, blobs: adapter.blobs, references: adapter.references }, projectId, assetId, options);
97
- if (result.outcome === 'deleted' || result.outcome === 'not-found')
98
- await refresh();
108
+ // Drop the one removed record from the cache rather than re-listing the whole
109
+ // library (EC-301 perf, mirrors updateMetadata) — a delete must not trigger a full
110
+ // `assets.list` round-trip on top of the deletion itself.
111
+ if (result.outcome === 'deleted' || result.outcome === 'not-found') {
112
+ cache.patch(assetsQueryKey(projectId), (prev) => (prev ?? []).filter((a) => a.id !== assetId));
113
+ }
99
114
  return result;
100
- }, [studio, projectId, refresh]);
115
+ }, [studio, projectId, cache]);
101
116
  const updateMetadata = useCallback(async (assetId, patch) => {
102
117
  // Metadata writes go through the `assets.upsert` command via the
103
118
  // operations facade (EC-141) — never straight at a repository.
104
119
  const { assetRepo } = await studio();
105
120
  const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch);
106
121
  if (result.ok) {
107
- // Patch the one updated record into local state rather than re-listing the
122
+ // Patch the one updated record into the cache rather than re-listing the
108
123
  // whole library (EC-301). A focal-point or alt edit must not trigger a full
109
124
  // `assets.list` round-trip — that re-list (plus the marker only moving once
110
125
  // it returned) was the focal-point picker's lag and query storm.
111
126
  const updated = result.asset;
112
- setAssets((prev) => prev.map((a) => (a.id === assetId ? updated : a)));
127
+ cache.patch(assetsQueryKey(projectId), (prev) => (prev ?? []).map((a) => (a.id === assetId ? updated : a)));
113
128
  }
114
129
  return result;
115
- }, [studio, projectId]);
130
+ }, [studio, projectId, cache]);
116
131
  const store = useMemo(() => ({
117
132
  projectId,
118
133
  status,
@@ -1 +1 @@
1
- {"version":3,"file":"assets-store.js","sourceRoot":"","sources":["../../../src/components/assets/assets-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { AssetRecord, AssetRepository } from '@elytracms/core/persistence'\nimport { deleteAsset, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets'\nimport type { DeleteAssetResult, UploadState, UploadableFile } from '@elytracms/studio/lib/assets'\nimport { loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { getStudioOperations, operationsAssetRepository } from '@elytracms/studio/lib/operations'\nimport type { StudioOperations } from '@elytracms/studio/lib/operations'\nimport { updateAssetMetadata } from './asset-metadata'\nimport type { AssetMetadataPatch, UpdateAssetMetadataResult } from './asset-metadata'\n\n/**\n * Asset library store (EC-151, EC-141). Asset *metadata* mutations go through\n * the shared operations client (`assets.*` commands — validate → authorize →\n * mutate → event); blob *bytes* ride on `adapter.blobs`, which is transport,\n * not domain. The UI works identically against the in-memory seed and a live\n * Convex deployment.\n */\n\nexport interface UploadEntry {\n id: string\n filename: string\n state: UploadState\n}\n\nexport interface AssetsStore {\n /** The active project (e.g. for reference deep links, EC-202). */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n assets: readonly AssetRecord[]\n uploads: readonly UploadEntry[]\n upload(files: Iterable<UploadableFile>): void\n retryUpload(uploadId: string): void\n dismissUpload(uploadId: string): void\n /**\n * Index-accurate inbound references of an asset (EC-202): the \"used by\"\n * pre-flight of the block-or-confirm delete dialog, read from the EC-155\n * reference index (not the retired string scan).\n */\n usagesOf(assetId: string): Promise<ReferenceEntryView[]>\n remove(assetId: string, options?: { confirmInUse?: boolean }): Promise<DeleteAssetResult>\n /** Edit alt/title (EC-152); persists through the operations asset facade. */\n updateMetadata(assetId: string, patch: AssetMetadataPatch): Promise<UpdateAssetMetadataResult>\n refresh(): Promise<void>\n}\n\nconst AssetsContext = createContext<AssetsStore | null>(null)\n\nexport function AssetsProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const uploadFilesRef = useRef(new Map<string, UploadableFile>())\n const uploadSeq = useRef(0)\n const [status, setStatus] = useState<AssetsStore['status']>('loading')\n const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)\n const [assets, setAssets] = useState<readonly AssetRecord[]>([])\n const [uploads, setUploads] = useState<readonly UploadEntry[]>([])\n\n /** The shared studio operations bundle + the metadata-write facade over it. */\n const studio = useCallback(async (): Promise<\n StudioOperations & { assetRepo: AssetRepository }\n > => {\n const bundle = await getStudioOperations()\n return { ...bundle, assetRepo: operationsAssetRepository(bundle.operations) }\n }, [])\n\n const refresh = useCallback(async () => {\n try {\n const { operations } = await studio()\n const result = await operations.assets.list({ projectId })\n if (!result.ok) {\n setStatus('error')\n setErrorMessage(result.error.message)\n return\n }\n setAssets(result.value)\n setStatus('ready')\n setErrorMessage(undefined)\n } catch (error) {\n setStatus('error')\n setErrorMessage(error instanceof Error ? error.message : String(error))\n }\n }, [studio, projectId])\n\n useEffect(() => {\n void refresh()\n }, [refresh])\n\n const setUploadState = useCallback((uploadId: string, state: UploadState) => {\n setUploads((prev) => prev.map((u) => (u.id === uploadId ? { ...u, state } : u)))\n }, [])\n\n const runEntry = useCallback(\n async (uploadId: string, file: UploadableFile, previous?: UploadState) => {\n const { adapter, assetRepo } = await studio()\n const result = await runUpload(\n {\n // Bytes ride on blob storage (transport); the metadata record is\n // confirmed through the `assets.upsert` operation command.\n blobs: adapter.blobs,\n assets: assetRepo,\n ...(typeof XMLHttpRequest !== 'undefined' ? { transport: xhrUploadTransport } : {}),\n },\n {\n projectId,\n file,\n onState: (state) => setUploadState(uploadId, state),\n },\n previous,\n )\n if (result.ok) {\n // Completed uploads leave the queue; the asset appears in the library.\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n await refresh()\n }\n },\n [studio, projectId, refresh, setUploadState],\n )\n\n const upload = useCallback(\n (files: Iterable<UploadableFile>) => {\n for (const file of files) {\n uploadSeq.current += 1\n const uploadId = `upload-${uploadSeq.current}`\n uploadFilesRef.current.set(uploadId, file)\n setUploads((prev) => [\n ...prev,\n { id: uploadId, filename: file.name, state: { phase: 'idle', progress: 0, attempt: 0 } },\n ])\n void runEntry(uploadId, file)\n }\n },\n [runEntry],\n )\n\n const retryUpload = useCallback(\n (uploadId: string) => {\n const file = uploadFilesRef.current.get(uploadId)\n const entry = uploads.find((u) => u.id === uploadId)\n if (!file || !entry || entry.state.phase !== 'failed') return\n void runEntry(uploadId, file, entry.state)\n },\n [runEntry, uploads],\n )\n\n const dismissUpload = useCallback((uploadId: string) => {\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n }, [])\n\n const usagesOf = useCallback(\n // Index-accurate (EC-202): inbound references from the EC-155 index via the\n // operations queries — the same machinery the References panel uses.\n async (assetId: string) => loadInboundAssetReferenceViews(projectId, assetId),\n [projectId],\n )\n\n const remove = useCallback(\n async (assetId: string, options?: { confirmInUse?: boolean }) => {\n const { adapter, assetRepo } = await studio()\n // Metadata removal goes through the `assets.remove` command (via the\n // facade); blob byte deletion stays on the adapter; the usage gate reads\n // the EC-155 reference index (`adapter.references`, EC-202).\n const result = await deleteAsset(\n { assets: assetRepo, blobs: adapter.blobs, references: adapter.references },\n projectId,\n assetId,\n options,\n )\n if (result.outcome === 'deleted' || result.outcome === 'not-found') await refresh()\n return result\n },\n [studio, projectId, refresh],\n )\n\n const updateMetadata = useCallback(\n async (assetId: string, patch: AssetMetadataPatch) => {\n // Metadata writes go through the `assets.upsert` command via the\n // operations facade (EC-141) — never straight at a repository.\n const { assetRepo } = await studio()\n const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch)\n if (result.ok) {\n // Patch the one updated record into local state rather than re-listing the\n // whole library (EC-301). A focal-point or alt edit must not trigger a full\n // `assets.list` round-trip — that re-list (plus the marker only moving once\n // it returned) was the focal-point picker's lag and query storm.\n const updated = result.asset\n setAssets((prev) => prev.map((a) => (a.id === assetId ? updated : a)))\n }\n return result\n },\n [studio, projectId],\n )\n\n const store = useMemo<AssetsStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n assets,\n uploads,\n upload,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n refresh,\n }),\n [\n projectId,\n status,\n errorMessage,\n assets,\n uploads,\n upload,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n refresh,\n ],\n )\n\n return <AssetsContext.Provider value={store}>{children}</AssetsContext.Provider>\n}\n\nexport function useAssets(): AssetsStore {\n const store = useContext(AssetsContext)\n if (!store) throw new Error('useAssets must be used within an <AssetsProvider>.')\n return store\n}\n\n/**\n * The assets store when an `<AssetsProvider>` is mounted above, else `null`.\n * Lets surfaces outside the Assets section (the CMS asset picker, EC-152)\n * reuse an ambient provider or mount their own.\n */\nexport function useOptionalAssets(): AssetsStore | null {\n return useContext(AssetsContext)\n}\n"]}
1
+ {"version":3,"file":"assets-store.js","sourceRoot":"","sources":["../../../src/components/assets/assets-store.tsx"],"names":[],"mappings":"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","sourcesContent":["import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { AssetRecord, AssetRepository } from '@elytracms/core/persistence'\nimport { deleteAsset, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets'\nimport type { DeleteAssetResult, UploadState, UploadableFile } from '@elytracms/studio/lib/assets'\nimport { loadInboundAssetReferenceViews } from '@elytracms/studio/lib/references'\nimport type { ReferenceEntryView } from '@elytracms/studio/lib/references'\nimport { getStudioOperations, operationsAssetRepository } from '@elytracms/studio/lib/operations'\nimport type { OperationsClient, StudioOperations } from '@elytracms/studio/lib/operations'\nimport { useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { updateAssetMetadata } from './asset-metadata'\nimport type { AssetMetadataPatch, UpdateAssetMetadataResult } from './asset-metadata'\n\n/**\n * Asset library store (EC-151, EC-141). Asset *metadata* mutations go through\n * the shared operations client (`assets.*` commands — validate → authorize →\n * mutate → event); blob *bytes* ride on `adapter.blobs`, which is transport,\n * not domain. The UI works identically against the in-memory seed and a live\n * Convex deployment.\n */\n\nexport interface UploadEntry {\n id: string\n filename: string\n state: UploadState\n}\n\nexport interface AssetsStore {\n /** The active project (e.g. for reference deep links, EC-202). */\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n assets: readonly AssetRecord[]\n uploads: readonly UploadEntry[]\n upload(files: Iterable<UploadableFile>): void\n retryUpload(uploadId: string): void\n dismissUpload(uploadId: string): void\n /**\n * Index-accurate inbound references of an asset (EC-202): the \"used by\"\n * pre-flight of the block-or-confirm delete dialog, read from the EC-155\n * reference index (not the retired string scan).\n */\n usagesOf(assetId: string): Promise<ReferenceEntryView[]>\n remove(assetId: string, options?: { confirmInUse?: boolean }): Promise<DeleteAssetResult>\n /** Edit alt/title (EC-152); persists through the operations asset facade. */\n updateMetadata(assetId: string, patch: AssetMetadataPatch): Promise<UpdateAssetMetadataResult>\n refresh(): Promise<void>\n}\n\nconst AssetsContext = createContext<AssetsStore | null>(null)\n\nconst EMPTY_ASSETS: readonly AssetRecord[] = []\n\n/** Cache key for a project's asset library (shared by the query and its patches). */\nfunction assetsQueryKey(projectId: string): readonly [string, string] {\n return ['assets', projectId]\n}\n\n/** Load a project's asset library (pure over the operations client). A list\n * failure throws so the query surfaces it as an error status. */\nasync function loadAssets(operations: OperationsClient, projectId: string): Promise<AssetRecord[]> {\n const result = await operations.assets.list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n}\n\nexport function AssetsProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const uploadFilesRef = useRef(new Map<string, UploadableFile>())\n const uploadSeq = useRef(0)\n const [uploads, setUploads] = useState<readonly UploadEntry[]>([])\n\n /** The shared studio operations bundle + the metadata-write facade over it. */\n const studio = useCallback(async (): Promise<\n StudioOperations & { assetRepo: AssetRepository }\n > => {\n const bundle = await getStudioOperations()\n return { ...bundle, assetRepo: operationsAssetRepository(bundle.operations) }\n }, [])\n\n // The library list, read through the one operations-backed query cache\n // (EC-308/AD-17). No `invalidateOn`: this provider is the sole writer of its\n // project's assets, so its own mutations patch the cache in place (the EC-301\n // rule — a metadata or focal-point edit must never trigger a full re-list).\n const query = useOperationQuery<AssetRecord[]>({\n queryKey: assetsQueryKey(projectId),\n loader: (operations) => loadAssets(operations, projectId),\n })\n const cache = useOperationCache()\n const refetch = query.refetch\n const assets = query.data ?? EMPTY_ASSETS\n const status: AssetsStore['status'] = query.isPending\n ? 'loading'\n : query.isError\n ? 'error'\n : 'ready'\n const errorMessage =\n query.isError && query.error instanceof Error ? query.error.message : undefined\n\n /** Re-list from the backend (the public escape hatch + post-upload reconcile). */\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const setUploadState = useCallback((uploadId: string, state: UploadState) => {\n setUploads((prev) => prev.map((u) => (u.id === uploadId ? { ...u, state } : u)))\n }, [])\n\n const runEntry = useCallback(\n async (uploadId: string, file: UploadableFile, previous?: UploadState) => {\n const { adapter, assetRepo } = await studio()\n const result = await runUpload(\n {\n // Bytes ride on blob storage (transport); the metadata record is\n // confirmed through the `assets.upsert` operation command.\n blobs: adapter.blobs,\n assets: assetRepo,\n ...(typeof XMLHttpRequest !== 'undefined' ? { transport: xhrUploadTransport } : {}),\n },\n {\n projectId,\n file,\n onState: (state) => setUploadState(uploadId, state),\n },\n previous,\n )\n if (result.ok) {\n // Completed uploads leave the queue; the asset appears in the library.\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n await refresh()\n }\n },\n [studio, projectId, refresh, setUploadState],\n )\n\n const upload = useCallback(\n (files: Iterable<UploadableFile>) => {\n for (const file of files) {\n uploadSeq.current += 1\n const uploadId = `upload-${uploadSeq.current}`\n uploadFilesRef.current.set(uploadId, file)\n setUploads((prev) => [\n ...prev,\n { id: uploadId, filename: file.name, state: { phase: 'idle', progress: 0, attempt: 0 } },\n ])\n void runEntry(uploadId, file)\n }\n },\n [runEntry],\n )\n\n const retryUpload = useCallback(\n (uploadId: string) => {\n const file = uploadFilesRef.current.get(uploadId)\n const entry = uploads.find((u) => u.id === uploadId)\n if (!file || !entry || entry.state.phase !== 'failed') return\n void runEntry(uploadId, file, entry.state)\n },\n [runEntry, uploads],\n )\n\n const dismissUpload = useCallback((uploadId: string) => {\n uploadFilesRef.current.delete(uploadId)\n setUploads((prev) => prev.filter((u) => u.id !== uploadId))\n }, [])\n\n const usagesOf = useCallback(\n // Index-accurate (EC-202): inbound references from the EC-155 index via the\n // operations queries — the same machinery the References panel uses.\n async (assetId: string) => loadInboundAssetReferenceViews(projectId, assetId),\n [projectId],\n )\n\n const remove = useCallback(\n async (assetId: string, options?: { confirmInUse?: boolean }) => {\n const { adapter, assetRepo } = await studio()\n // Metadata removal goes through the `assets.remove` command (via the\n // facade); blob byte deletion stays on the adapter; the usage gate reads\n // the EC-155 reference index (`adapter.references`, EC-202).\n const result = await deleteAsset(\n { assets: assetRepo, blobs: adapter.blobs, references: adapter.references },\n projectId,\n assetId,\n options,\n )\n // Drop the one removed record from the cache rather than re-listing the whole\n // library (EC-301 perf, mirrors updateMetadata) — a delete must not trigger a full\n // `assets.list` round-trip on top of the deletion itself.\n if (result.outcome === 'deleted' || result.outcome === 'not-found') {\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).filter((a) => a.id !== assetId),\n )\n }\n return result\n },\n [studio, projectId, cache],\n )\n\n const updateMetadata = useCallback(\n async (assetId: string, patch: AssetMetadataPatch) => {\n // Metadata writes go through the `assets.upsert` command via the\n // operations facade (EC-141) — never straight at a repository.\n const { assetRepo } = await studio()\n const result = await updateAssetMetadata({ assets: assetRepo }, projectId, assetId, patch)\n if (result.ok) {\n // Patch the one updated record into the cache rather than re-listing the\n // whole library (EC-301). A focal-point or alt edit must not trigger a full\n // `assets.list` round-trip — that re-list (plus the marker only moving once\n // it returned) was the focal-point picker's lag and query storm.\n const updated = result.asset\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === assetId ? updated : a)),\n )\n }\n return result\n },\n [studio, projectId, cache],\n )\n\n const store = useMemo<AssetsStore>(\n () => ({\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n assets,\n uploads,\n upload,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n refresh,\n }),\n [\n projectId,\n status,\n errorMessage,\n assets,\n uploads,\n upload,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n refresh,\n ],\n )\n\n return <AssetsContext.Provider value={store}>{children}</AssetsContext.Provider>\n}\n\nexport function useAssets(): AssetsStore {\n const store = useContext(AssetsContext)\n if (!store) throw new Error('useAssets must be used within an <AssetsProvider>.')\n return store\n}\n\n/**\n * The assets store when an `<AssetsProvider>` is mounted above, else `null`.\n * Lets surfaces outside the Assets section (the CMS asset picker, EC-152)\n * reuse an ambient provider or mount their own.\n */\nexport function useOptionalAssets(): AssetsStore | null {\n return useContext(AssetsContext)\n}\n"]}
@@ -32,7 +32,9 @@ export declare function useAuth(): AuthStore;
32
32
  export declare function useOptionalAuth(): AuthStore | null;
33
33
  /**
34
34
  * The signed-in user's role on a project (for the shell's role badge).
35
- * Resolves via the members read store; `null` while loading or when the user
36
- * is not a member.
35
+ * Read through the one operations-backed query cache (EC-308/AD-17) via the
36
+ * `members.find` query — no longer reaching past the spine into
37
+ * `adapter.members` — and re-read when membership changes. `null` while loading
38
+ * or when the user is not a member.
37
39
  */
38
40
  export declare function useProjectRole(projectId: string | null): ProjectRole | null;