@elytracms/studio 0.0.87 → 0.0.88

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 (67) hide show
  1. package/dist/components/ai/ai-chat-panel.js +8 -1
  2. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  3. package/dist/components/assets/assets-store.d.ts +11 -1
  4. package/dist/components/assets/assets-store.js +10 -0
  5. package/dist/components/assets/assets-store.js.map +1 -1
  6. package/dist/components/assets/focal-point.d.ts +0 -7
  7. package/dist/components/assets/focal-point.js +0 -10
  8. package/dist/components/assets/focal-point.js.map +1 -1
  9. package/dist/components/builder/canvas-render-context.d.ts +12 -1
  10. package/dist/components/builder/canvas-render-context.js +20 -16
  11. package/dist/components/builder/canvas-render-context.js.map +1 -1
  12. package/dist/components/cms/asset-field.d.ts +2 -1
  13. package/dist/components/cms/asset-field.js +27 -59
  14. package/dist/components/cms/asset-field.js.map +1 -1
  15. package/dist/components/cms/asset-picker-core.d.ts +77 -40
  16. package/dist/components/cms/asset-picker-core.js +73 -46
  17. package/dist/components/cms/asset-picker-core.js.map +1 -1
  18. package/dist/components/cms/asset-picker.d.ts +48 -19
  19. package/dist/components/cms/asset-picker.js +69 -30
  20. package/dist/components/cms/asset-picker.js.map +1 -1
  21. package/dist/components/cms/cell-edit.d.ts +3 -1
  22. package/dist/components/cms/cell-edit.js +2 -2
  23. package/dist/components/cms/cell-edit.js.map +1 -1
  24. package/dist/components/cms/cms-store.js +15 -23
  25. package/dist/components/cms/cms-store.js.map +1 -1
  26. package/dist/components/cms/editable-cell.js +3 -6
  27. package/dist/components/cms/editable-cell.js.map +1 -1
  28. package/dist/components/cms/gate-validation.d.ts +27 -2
  29. package/dist/components/cms/gate-validation.js +19 -1
  30. package/dist/components/cms/gate-validation.js.map +1 -1
  31. package/dist/components/cms/rich-text/editor.js +2 -2
  32. package/dist/components/cms/rich-text/editor.js.map +1 -1
  33. package/dist/components/cms/rich-text/insert.d.ts +7 -0
  34. package/dist/components/cms/rich-text/insert.js +7 -0
  35. package/dist/components/cms/rich-text/insert.js.map +1 -1
  36. package/dist/components/nav-issues.d.ts +23 -3
  37. package/dist/components/nav-issues.js +38 -2
  38. package/dist/components/nav-issues.js.map +1 -1
  39. package/dist/components/section-nav.js +26 -2
  40. package/dist/components/section-nav.js.map +1 -1
  41. package/dist/lib/ai/tools.d.ts +10 -4
  42. package/dist/lib/ai/tools.js +12 -4
  43. package/dist/lib/ai/tools.js.map +1 -1
  44. package/dist/lib/data/collection-content.d.ts +22 -1
  45. package/dist/lib/data/collection-content.js +15 -0
  46. package/dist/lib/data/collection-content.js.map +1 -1
  47. package/dist/lib/engine-version.d.ts +3 -3
  48. package/dist/lib/engine-version.js +3 -3
  49. package/dist/lib/engine-version.js.map +1 -1
  50. package/dist/lib/i18n/messages.de.js +5 -0
  51. package/dist/lib/i18n/messages.de.js.map +1 -1
  52. package/dist/lib/i18n/messages.en.d.ts +4 -0
  53. package/dist/lib/i18n/messages.en.js +6 -0
  54. package/dist/lib/i18n/messages.en.js.map +1 -1
  55. package/dist/lib/operations/session-hydration.d.ts +24 -26
  56. package/dist/lib/operations/session-hydration.js +15 -53
  57. package/dist/lib/operations/session-hydration.js.map +1 -1
  58. package/dist/lib/workspace/config-issues.d.ts +6 -0
  59. package/dist/lib/workspace/config-issues.js +8 -4
  60. package/dist/lib/workspace/config-issues.js.map +1 -1
  61. package/dist/lib/workspace/index.d.ts +1 -1
  62. package/dist/lib/workspace/index.js +1 -1
  63. package/dist/lib/workspace/index.js.map +1 -1
  64. package/dist/router/navigate-once.d.ts +19 -4
  65. package/dist/router/navigate-once.js +22 -5
  66. package/dist/router/navigate-once.js.map +1 -1
  67. package/package.json +3 -3
@@ -94,8 +94,15 @@ export function AiChatPanel() {
94
94
  const result = await operations.assets.list({ projectId });
95
95
  if (!result.ok)
96
96
  return [];
97
+ // EC-588: the type facts ride along, so each asset field's candidates are
98
+ // filtered by its `accept` (and the model sees what each asset is).
97
99
  return result.value
98
- .map((asset) => ({ id: asset.id, title: asset.title ?? asset.filename }))
100
+ .map((asset) => ({
101
+ id: asset.id,
102
+ title: asset.title ?? asset.filename,
103
+ contentType: asset.contentType,
104
+ filename: asset.filename,
105
+ }))
99
106
  .filter((r) => matches(query, r.title, r.id));
100
107
  },
101
108
  // Full document (draft values + localized) — the getDocument/translate tools.
@@ -1 +1 @@
1
- {"version":3,"file":"ai-chat-panel.js","sourceRoot":"","sources":["../../../src/components/ai/ai-chat-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useReducer, useRef, useState } from 'react'\nimport { MessageSquarePlus, X } from 'lucide-react'\nimport { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai'\nimport type { UIMessage } from 'ai'\nimport { useChat } from '@ai-sdk/react'\nimport {\n createCollectionRegistry,\n resolveDocumentTitle,\n} from '@elytracms/core/cms-core'\nimport type { CmsDocument, FieldValues } from '@elytracms/core/cms-core'\nimport { Button, Heading, Row, Spacer, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport { resolveDeploymentAi } from '@elytracms/studio/lib/workspace'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport {\n DEFAULT_MAX_STEPS,\n buildChatContext,\n chatReviewReducer,\n clearConversation,\n createChatCandidateStore,\n createChatToolExecutor,\n guardAiRouteFetch,\n initialChatReviewState,\n introducedErrors,\n isAiRouteNotMountedError,\n renderCurrentDocumentSection,\n resolveAiConfig,\n resolveChatTarget,\n resolveKnowledge,\n withStepCap,\n} from '@elytracms/studio/lib/ai'\nimport { getAiCurrentDocument } from '@elytracms/studio/lib/ai-current-document'\nimport type {\n ChatCandidateStore,\n ChatToolReaders,\n KnowledgeCollectionReader,\n ProposalReview,\n ResolvedKnowledge,\n RouteHeaders,\n} from '@elytracms/studio/lib/ai'\nimport { useCms } from '../cms/cms-store'\nimport {\n AssistantTurn,\n ChatErrorNote,\n Conversation,\n ConversationEmpty,\n PromptInput,\n ToolStatus,\n UserTurn,\n WorkingIndicator,\n describeToolPart,\n messageText,\n segmentMessageParts,\n} from './chat-ui'\nimport type { MessagePartView } from './chat-ui'\nimport { ProposalCard } from './proposal-card'\nimport { setAiPanelOpen } from '@elytracms/studio/lib/ai-panel'\n\n/**\n * The AI assistant chat panel (EC-199, moved onto the SDK's native protocol by\n * EC-334) — the right-hand collapsible surface in the CMS area. `useChat` +\n * `DefaultChatTransport` own the conversation against the route's native chat\n * branch (visible token streaming); `onToolCall` dispatches into the client\n * executor map (operations reads + terminal engines, all with the user's own\n * session); `sendAutomaticallyWhen` is the SDK predicate wrapped with the\n * per-turn step cap. What the SDK does not own lives in the pure review reducer:\n * full proposals keyed by `toolCallId` (reviewed as a diff, applied as a DRAFT\n * via operations upsert + a provenance-tagged snapshot — the EC-199 trust\n * boundary, unmoved) and blocking errors. EC-380: the \"new conversation\"\n * affordance clears busy-safely (stop → new chat id + review reset; pending\n * proposals are discarded with reject semantics).\n *\n * NEVER a modal (house rule). The whole module (including `ai` + `@ai-sdk/react`)\n * sits behind the lazy boundary (`ai-chat-panel-lazy`) — pinned in bundle tests.\n */\n\n/** Adapt a `RouteHeaders` (static or thunk) to the transport's headers resolvable. */\nfunction toTransportHeaders(headers: RouteHeaders | undefined): () => Promise<Record<string, string>> {\n return async () => {\n const resolved = typeof headers === 'function' ? await headers() : headers\n const record: Record<string, string> = {}\n new Headers(resolved ?? {}).forEach((value, key) => {\n record[key] = value\n })\n return record\n }\n}\n\nexport function AiChatPanel() {\n const cms = useCms()\n const navigate = useNavigate()\n const [review, dispatch] = useReducer(chatReviewReducer, initialChatReviewState)\n const [input, setInput] = useState('')\n // EC-380: the conversation counter — bumping it mints a fresh `useChat` id\n // (a brand-new conversation) and a fresh relation-candidate store.\n const [conversation, setConversation] = useState(1)\n\n const projectId = cms.projectId\n const activeLocale = cms.activeLocale\n\n // Resolve the AI platform config once (presence + capability gate). The panel only\n // mounts when configured, but a misconfigured provider still fails loud here.\n const resolved = useMemo(() => resolveAiConfig(resolveDeploymentAi()), [])\n const provider = resolved.resolved?.provider ?? null\n const knowledgeSources = useMemo(() => resolved.resolved?.knowledge ?? [], [resolved])\n const maxRepairAttempts = resolved.resolved?.limits?.maxRepairAttempts\n const ready = provider !== null && resolved.ok && projectId !== undefined\n\n const registry = useMemo(() => createCollectionRegistry(cms.collections), [cms.collections])\n\n /** Merge a document's non-localized + active-locale values for title/prose reads. */\n const localeValues = useCallback(\n (doc: CmsDocument): FieldValues => ({\n ...doc.values,\n ...(doc.localized?.[activeLocale] ?? {}),\n }),\n [activeLocale],\n )\n\n /** Operations-backed tool readers — client-side, with the user's own session. */\n const readers = useMemo<ChatToolReaders>(() => {\n const matches = (query: string | undefined, ...fields: (string | undefined)[]) =>\n !query || fields.some((f) => f?.toLowerCase().includes(query.toLowerCase()))\n return {\n async searchDocuments({ collection, query }) {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.documents.list({ projectId, collection })\n if (!result.ok) return []\n const target = cms.getCollection(collection)\n return result.value\n .map((rec) => ({\n id: rec.document.id,\n title: target\n ? resolveDocumentTitle(target, localeValues(rec.document), rec.document.id)\n : rec.document.id,\n }))\n .filter((r) => matches(query, r.title, r.id))\n },\n async listAssets({ query }) {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.assets.list({ projectId })\n if (!result.ok) return []\n return result.value\n .map((asset) => ({ id: asset.id, title: asset.title ?? asset.filename }))\n .filter((r) => matches(query, r.title, r.id))\n },\n // Full document (draft values + localized) — the getDocument/translate tools.\n async getDocument({ collection, id }) {\n if (projectId === undefined) return null\n const { operations } = await getStudioOperations()\n const result = await operations.documents.get({ projectId, ref: { collection, id } })\n return result.ok ? result.value?.document ?? null : null\n },\n }\n }, [projectId, cms, localeValues])\n\n /** A fresh, collision-free draft id for the target collection. */\n const mintDocumentId = useCallback(\n (collectionId: string): string => {\n const existing = new Set(cms.documentsOf(collectionId).map((d) => d.id))\n let n = 1\n while (existing.has(`new-${collectionId}-${n}`)) n += 1\n return `new-${collectionId}-${n}`\n },\n [cms],\n )\n\n /** Read `fromCollection(...)` knowledge prose through the operations layer. */\n const readCollection = useCallback<KnowledgeCollectionReader>(\n async (source) => {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.documents.list({ projectId, collection: source.collectionId })\n if (!result.ok) return []\n const target = cms.getCollection(source.collectionId)\n const field =\n source.field ?? target?.fields.find((f) => f.type === 'text' || f.type === 'richText')?.name\n return result.value\n .map((rec) => {\n const values = localeValues(rec.document)\n const content = field ? String(values[field] ?? '') : ''\n return { content }\n })\n .filter((item) => item.content.trim().length > 0)\n },\n [projectId, cms, localeValues],\n )\n\n // The knowledge snapshot the executors read — refreshed by the body thunk on\n // every request, so the terminal engines see what the model saw this turn.\n const knowledgeRef = useRef<ResolvedKnowledge>({ items: [] })\n\n // The chat transport target: the configured route provider's endpoint +\n // session-header thunk (EC-371), re-evaluated per request.\n const chatTarget = useMemo(() => resolveChatTarget(provider), [provider])\n\n const transport = useMemo(\n () =>\n new DefaultChatTransport({\n api: chatTarget.endpoint,\n headers: toTransportHeaders(chatTarget.headers),\n // EC-514: the same response-shape guard the structured proxy uses. Without\n // it a 200 HTML shell (route not mounted — the EC-503 dev degrade) parses\n // as an EMPTY event stream and the turn ends silently with no error at all.\n fetch: guardAiRouteFetch(),\n // The `body` THUNK — re-evaluated per request (fresh context every turn,\n // continuations included): collections-catalog summary + client-resolved\n // knowledge + EC-378's current-document block (read from the seam store\n // at REQUEST time, so every turn sees what is open NOW). The route folds\n // these sections into the system prompt; it resolves no data itself\n // (zero-data-access proxy). The block is advisory — the deterministic\n // omitted-target fallback lives in the executor.\n body: async () => {\n const knowledge = await resolveKnowledge(knowledgeSources, { readCollection })\n knowledgeRef.current = knowledge\n const current = getAiCurrentDocument()\n return {\n context: buildChatContext({\n collections: cms.collections,\n knowledge,\n ...(current\n ? {\n extraSections: [\n renderCurrentDocumentSection({\n collection: current.collection,\n id: current.id,\n title: current.title,\n locale: activeLocale,\n }),\n ],\n }\n : {}),\n }),\n }\n },\n }),\n [chatTarget, knowledgeSources, readCollection, cms.collections, activeLocale],\n )\n\n // Per-conversation relation-candidate store (EC-380: replaced on clear, so a\n // fresh conversation cannot reference the previous one's searched ids).\n const candidatesRef = useRef<{ conversation: number; store: ChatCandidateStore }>({\n conversation,\n store: createChatCandidateStore(),\n })\n if (candidatesRef.current.conversation !== conversation) {\n candidatesRef.current = { conversation, store: createChatCandidateStore() }\n }\n const candidateStore = candidatesRef.current.store\n\n const executor = useMemo(() => {\n if (!provider) return null\n return createChatToolExecutor({\n provider,\n registry,\n collections: cms.collections,\n readers,\n mintDocumentId,\n candidates: candidateStore,\n onProposal: (toolCallId, proposal) => dispatch({ type: 'propose', toolCallId, proposal }),\n knowledge: () => knowledgeRef.current,\n // EC-378: the current-document seam, read at EXECUTION time — the\n // deterministic fallback when the model omits a tool's target.\n currentDocument: () => getAiCurrentDocument(),\n locale: activeLocale,\n ...(cms.localeConfig ? { localeConfig: cms.localeConfig } : {}),\n // EC-473 established that the assistant must validate with the EDITOR's\n // rules, not a subset. EC-497 hands over THE bag itself — one value, built\n // once by `gateValidateOptions` — so the assistant's gate and the editor's\n // gate cannot drift by someone forgetting a key at a call site.\n validateOptions: cms.validateOptions,\n ...(maxRepairAttempts !== undefined ? { maxRepairAttempts } : {}),\n })\n }, [\n provider,\n registry,\n cms.collections,\n cms.localeConfig,\n cms.validateOptions,\n readers,\n mintDocumentId,\n activeLocale,\n maxRepairAttempts,\n candidateStore,\n ])\n\n const chat = useChat({\n // A new conversation is a new chat id — `useChat` recreates its state.\n id: `elytra-assistant-${conversation}`,\n transport,\n onToolCall: async ({ toolCall }) => {\n if (!executor) return\n const outcome = await executor.execute({\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n input: toolCall.input,\n })\n // Deliberately NOT awaited: `onToolCall` runs inside the chat's serial\n // stream job, and `addToolOutput` enqueues on the same executor —\n // awaiting it here deadlocks the stream (verified against ai@7.0.22).\n if (outcome.ok) {\n void chat.addToolOutput({\n tool: toolCall.toolName,\n toolCallId: toolCall.toolCallId,\n output: outcome.output,\n })\n } else {\n void chat.addToolOutput({\n state: 'output-error',\n tool: toolCall.toolName,\n toolCallId: toolCall.toolCallId,\n errorText: outcome.errorText,\n })\n }\n },\n // The SDK predicate wrapped with the per-user-turn step cap: a capped turn\n // stops cleanly with a visible note — never a silent stall (EC-334).\n sendAutomaticallyWhen: withStepCap(lastAssistantMessageIsCompleteWithToolCalls, {\n maxSteps: DEFAULT_MAX_STEPS,\n onCap: () =>\n dispatch({ type: 'fail', message: translate('ai.error.stepCap', { max: DEFAULT_MAX_STEPS }) }),\n }),\n // EC-514: \"no route mounted at <path>\" is named as itself; every other\n // transport/provider failure keeps the generic route note.\n onError: (error) =>\n dispatch({\n type: 'fail',\n message: isAiRouteNotMountedError(error)\n ? translate('ai.error.routeNotMounted', { endpoint: error.endpoint })\n : translate('ai.error.route'),\n }),\n })\n\n const busy = chat.status === 'submitted' || chat.status === 'streaming'\n\n const submit = useCallback(() => {\n const text = input.trim()\n if (text.length === 0 || busy || !ready) return\n setInput('')\n dispatch({ type: 'clear-error' })\n void chat.sendMessage({ text })\n }, [input, busy, ready, chat])\n\n // EC-380: clear / new topic — busy-safe (stop first), pending proposals\n // discarded with reject semantics (the review state resets; nothing was applied).\n const startNewConversation = useCallback(() => {\n void clearConversation({ status: chat.status, stop: chat.stop }, () => {\n dispatch({ type: 'clear' })\n setInput('')\n setConversation((current) => current + 1)\n })\n }, [chat.status, chat.stop])\n\n const applyProposal = useCallback(\n async (proposal: ProposalReview) => {\n if (projectId === undefined) return\n // For an update/translate of an EXISTING document, spread the stored doc\n // first so document-level extras (alias, defaultLocale) survive the apply —\n // the patch replaces only values/localized.\n const existing = cms.getDocument({ collection: proposal.collection, id: proposal.documentId })\n const doc: CmsDocument = {\n ...(existing ?? {}),\n collection: proposal.collection,\n id: proposal.documentId,\n values: proposal.patch.values,\n ...(proposal.patch.localized ? { localized: proposal.patch.localized } : {}),\n }\n // EC-473 — re-validate BEFORE the write. The proposal was validated when it was\n // built, but the editor may have changed the document since (or applied an\n // earlier proposal on top of it), so the patch is re-judged against what is in\n // the session NOW. Same gate as every other AI write path: only errors the\n // apply would INTRODUCE block it — pre-existing brokenness on the document is\n // not the assistant's doing and must not make an unrelated change unappliable.\n const introduced = introducedErrors(\n existing ? cms.validate(existing) : [],\n cms.validate(doc),\n )\n if (introduced.length > 0) {\n dispatch({\n type: 'fail',\n message: translate('ai.error.applyInvalid', {\n issues: introduced\n .slice(0, 3)\n .map((issue) => issue.message)\n .join('; '),\n }),\n })\n return\n }\n const { operations } = await getStudioOperations()\n const upserted = await operations.documents.upsert({ projectId, document: doc })\n if (!upserted.ok) {\n dispatch({ type: 'fail', message: translate('ai.error.applyFailed') })\n return\n }\n // Provenance: record an AI-tagged version snapshot (a restore point + the\n // 'assistant' origin surfaced in the versions pane). Best-effort — the draft\n // already landed above; a snapshot failure must not lose the applied content.\n try {\n await operations.documents.snapshot({\n projectId,\n ref: { collection: proposal.collection, id: proposal.documentId },\n label: translate('cms.version.aiAuthoredTitle'),\n origin: 'assistant',\n })\n } catch {\n // ignore — provenance is additive\n }\n // Reflect in the session so the new draft shows in the list + is navigable.\n cms.saveDocument(doc)\n // EC-378: when the applied proposal targets the document OPEN in the editor,\n // adopt it into the editor's draft buffer — otherwise the editor's autosave\n // would write its stale buffer back and silently revert the applied change.\n const current = getAiCurrentDocument()\n if (\n current &&\n current.collection === proposal.collection &&\n current.id === proposal.documentId\n ) {\n current.adoptDraft(doc)\n }\n dispatch({ type: 'apply', toolCallId: proposal.id })\n },\n [projectId, cms],\n )\n\n const openProposal = useCallback(\n (proposal: ProposalReview) => {\n if (projectId === undefined) return\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: studioPath.cmsDocument(proposal.collection, proposal.documentId) },\n })\n },\n [navigate, projectId],\n )\n\n const hasProposal = useCallback(\n (toolCallId: string) => review.proposals[toolCallId] !== undefined,\n [review.proposals],\n )\n\n const renderAssistantMessage = (message: UIMessage) => {\n const segments = segmentMessageParts(message.parts as MessagePartView[], hasProposal)\n return segments.map((segment, index) => {\n if (segment.kind === 'text') {\n return (\n <AssistantTurn key={`${message.id}-${index}`}>\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {segment.text}\n </Text>\n </AssistantTurn>\n )\n }\n if (segment.kind === 'tools') {\n return (\n <AssistantTurn key={`${message.id}-${index}`}>\n <ToolStatus steps={segment.parts.map(describeToolPart)} />\n </AssistantTurn>\n )\n }\n const proposal = review.proposals[segment.toolCallId]\n if (!proposal) return null\n return (\n <ProposalCard\n key={segment.toolCallId}\n proposal={proposal}\n onApply={() => void applyProposal(proposal)}\n onDiscard={() => dispatch({ type: 'reject', toolCallId: proposal.id })}\n onOpen={() => openProposal(proposal)}\n />\n )\n })\n }\n\n return (\n <aside className=\"flex h-full min-h-0 w-[360px] shrink-0 flex-col border-l border-border bg-card\">\n <Row gap=\"2\" align=\"center\" className=\"h-12 shrink-0 border-b border-border px-3\">\n <Heading level={2} size=\"h4\">\n {translate('ai.panel.title')}\n </Heading>\n <Spacer />\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={startNewConversation}\n disabled={chat.messages.length === 0 && review.error === null && !busy}\n title={translate('ai.panel.newConversation')}\n aria-label={translate('ai.panel.newConversation')}\n >\n <MessageSquarePlus />\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={() => setAiPanelOpen(false)}\n title={translate('ai.panel.close')}\n aria-label={translate('ai.panel.close')}\n >\n <X />\n </Button>\n </Row>\n\n <Conversation atBottomKey={chat.messages}>\n {chat.messages.length === 0 ? <ConversationEmpty /> : null}\n {chat.messages.map((message) =>\n message.role === 'user' ? (\n <UserTurn key={message.id} text={messageText(message)} />\n ) : (\n <div key={message.id} className=\"contents\">\n {renderAssistantMessage(message)}\n </div>\n ),\n )}\n {chat.status === 'submitted' ? (\n <AssistantTurn>\n <WorkingIndicator />\n </AssistantTurn>\n ) : null}\n {review.error !== null ? (\n <AssistantTurn>\n <ChatErrorNote message={review.error} />\n </AssistantTurn>\n ) : null}\n </Conversation>\n\n {ready ? (\n <PromptInput value={input} onChange={setInput} onSubmit={submit} disabled={busy} />\n ) : (\n <div className=\"border-t border-border p-3\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.panel.unavailable')}\n </Text>\n </div>\n )}\n </aside>\n )\n}\n"]}
1
+ {"version":3,"file":"ai-chat-panel.js","sourceRoot":"","sources":["../../../src/components/ai/ai-chat-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useReducer, useRef, useState } from 'react'\nimport { MessageSquarePlus, X } from 'lucide-react'\nimport { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai'\nimport type { UIMessage } from 'ai'\nimport { useChat } from '@ai-sdk/react'\nimport {\n createCollectionRegistry,\n resolveDocumentTitle,\n} from '@elytracms/core/cms-core'\nimport type { CmsDocument, FieldValues } from '@elytracms/core/cms-core'\nimport { Button, Heading, Row, Spacer, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { STUDIO_PROJECT_ROUTE, studioPath, useNavigate } from '@elytracms/studio/router'\nimport { resolveDeploymentAi } from '@elytracms/studio/lib/workspace'\nimport { getStudioOperations } from '@elytracms/studio/lib/operations'\nimport {\n DEFAULT_MAX_STEPS,\n buildChatContext,\n chatReviewReducer,\n clearConversation,\n createChatCandidateStore,\n createChatToolExecutor,\n guardAiRouteFetch,\n initialChatReviewState,\n introducedErrors,\n isAiRouteNotMountedError,\n renderCurrentDocumentSection,\n resolveAiConfig,\n resolveChatTarget,\n resolveKnowledge,\n withStepCap,\n} from '@elytracms/studio/lib/ai'\nimport { getAiCurrentDocument } from '@elytracms/studio/lib/ai-current-document'\nimport type {\n ChatCandidateStore,\n ChatToolReaders,\n KnowledgeCollectionReader,\n ProposalReview,\n ResolvedKnowledge,\n RouteHeaders,\n} from '@elytracms/studio/lib/ai'\nimport { useCms } from '../cms/cms-store'\nimport {\n AssistantTurn,\n ChatErrorNote,\n Conversation,\n ConversationEmpty,\n PromptInput,\n ToolStatus,\n UserTurn,\n WorkingIndicator,\n describeToolPart,\n messageText,\n segmentMessageParts,\n} from './chat-ui'\nimport type { MessagePartView } from './chat-ui'\nimport { ProposalCard } from './proposal-card'\nimport { setAiPanelOpen } from '@elytracms/studio/lib/ai-panel'\n\n/**\n * The AI assistant chat panel (EC-199, moved onto the SDK's native protocol by\n * EC-334) — the right-hand collapsible surface in the CMS area. `useChat` +\n * `DefaultChatTransport` own the conversation against the route's native chat\n * branch (visible token streaming); `onToolCall` dispatches into the client\n * executor map (operations reads + terminal engines, all with the user's own\n * session); `sendAutomaticallyWhen` is the SDK predicate wrapped with the\n * per-turn step cap. What the SDK does not own lives in the pure review reducer:\n * full proposals keyed by `toolCallId` (reviewed as a diff, applied as a DRAFT\n * via operations upsert + a provenance-tagged snapshot — the EC-199 trust\n * boundary, unmoved) and blocking errors. EC-380: the \"new conversation\"\n * affordance clears busy-safely (stop → new chat id + review reset; pending\n * proposals are discarded with reject semantics).\n *\n * NEVER a modal (house rule). The whole module (including `ai` + `@ai-sdk/react`)\n * sits behind the lazy boundary (`ai-chat-panel-lazy`) — pinned in bundle tests.\n */\n\n/** Adapt a `RouteHeaders` (static or thunk) to the transport's headers resolvable. */\nfunction toTransportHeaders(headers: RouteHeaders | undefined): () => Promise<Record<string, string>> {\n return async () => {\n const resolved = typeof headers === 'function' ? await headers() : headers\n const record: Record<string, string> = {}\n new Headers(resolved ?? {}).forEach((value, key) => {\n record[key] = value\n })\n return record\n }\n}\n\nexport function AiChatPanel() {\n const cms = useCms()\n const navigate = useNavigate()\n const [review, dispatch] = useReducer(chatReviewReducer, initialChatReviewState)\n const [input, setInput] = useState('')\n // EC-380: the conversation counter — bumping it mints a fresh `useChat` id\n // (a brand-new conversation) and a fresh relation-candidate store.\n const [conversation, setConversation] = useState(1)\n\n const projectId = cms.projectId\n const activeLocale = cms.activeLocale\n\n // Resolve the AI platform config once (presence + capability gate). The panel only\n // mounts when configured, but a misconfigured provider still fails loud here.\n const resolved = useMemo(() => resolveAiConfig(resolveDeploymentAi()), [])\n const provider = resolved.resolved?.provider ?? null\n const knowledgeSources = useMemo(() => resolved.resolved?.knowledge ?? [], [resolved])\n const maxRepairAttempts = resolved.resolved?.limits?.maxRepairAttempts\n const ready = provider !== null && resolved.ok && projectId !== undefined\n\n const registry = useMemo(() => createCollectionRegistry(cms.collections), [cms.collections])\n\n /** Merge a document's non-localized + active-locale values for title/prose reads. */\n const localeValues = useCallback(\n (doc: CmsDocument): FieldValues => ({\n ...doc.values,\n ...(doc.localized?.[activeLocale] ?? {}),\n }),\n [activeLocale],\n )\n\n /** Operations-backed tool readers — client-side, with the user's own session. */\n const readers = useMemo<ChatToolReaders>(() => {\n const matches = (query: string | undefined, ...fields: (string | undefined)[]) =>\n !query || fields.some((f) => f?.toLowerCase().includes(query.toLowerCase()))\n return {\n async searchDocuments({ collection, query }) {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.documents.list({ projectId, collection })\n if (!result.ok) return []\n const target = cms.getCollection(collection)\n return result.value\n .map((rec) => ({\n id: rec.document.id,\n title: target\n ? resolveDocumentTitle(target, localeValues(rec.document), rec.document.id)\n : rec.document.id,\n }))\n .filter((r) => matches(query, r.title, r.id))\n },\n async listAssets({ query }) {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.assets.list({ projectId })\n if (!result.ok) return []\n // EC-588: the type facts ride along, so each asset field's candidates are\n // filtered by its `accept` (and the model sees what each asset is).\n return result.value\n .map((asset) => ({\n id: asset.id,\n title: asset.title ?? asset.filename,\n contentType: asset.contentType,\n filename: asset.filename,\n }))\n .filter((r) => matches(query, r.title, r.id))\n },\n // Full document (draft values + localized) — the getDocument/translate tools.\n async getDocument({ collection, id }) {\n if (projectId === undefined) return null\n const { operations } = await getStudioOperations()\n const result = await operations.documents.get({ projectId, ref: { collection, id } })\n return result.ok ? result.value?.document ?? null : null\n },\n }\n }, [projectId, cms, localeValues])\n\n /** A fresh, collision-free draft id for the target collection. */\n const mintDocumentId = useCallback(\n (collectionId: string): string => {\n const existing = new Set(cms.documentsOf(collectionId).map((d) => d.id))\n let n = 1\n while (existing.has(`new-${collectionId}-${n}`)) n += 1\n return `new-${collectionId}-${n}`\n },\n [cms],\n )\n\n /** Read `fromCollection(...)` knowledge prose through the operations layer. */\n const readCollection = useCallback<KnowledgeCollectionReader>(\n async (source) => {\n if (projectId === undefined) return []\n const { operations } = await getStudioOperations()\n const result = await operations.documents.list({ projectId, collection: source.collectionId })\n if (!result.ok) return []\n const target = cms.getCollection(source.collectionId)\n const field =\n source.field ?? target?.fields.find((f) => f.type === 'text' || f.type === 'richText')?.name\n return result.value\n .map((rec) => {\n const values = localeValues(rec.document)\n const content = field ? String(values[field] ?? '') : ''\n return { content }\n })\n .filter((item) => item.content.trim().length > 0)\n },\n [projectId, cms, localeValues],\n )\n\n // The knowledge snapshot the executors read — refreshed by the body thunk on\n // every request, so the terminal engines see what the model saw this turn.\n const knowledgeRef = useRef<ResolvedKnowledge>({ items: [] })\n\n // The chat transport target: the configured route provider's endpoint +\n // session-header thunk (EC-371), re-evaluated per request.\n const chatTarget = useMemo(() => resolveChatTarget(provider), [provider])\n\n const transport = useMemo(\n () =>\n new DefaultChatTransport({\n api: chatTarget.endpoint,\n headers: toTransportHeaders(chatTarget.headers),\n // EC-514: the same response-shape guard the structured proxy uses. Without\n // it a 200 HTML shell (route not mounted — the EC-503 dev degrade) parses\n // as an EMPTY event stream and the turn ends silently with no error at all.\n fetch: guardAiRouteFetch(),\n // The `body` THUNK — re-evaluated per request (fresh context every turn,\n // continuations included): collections-catalog summary + client-resolved\n // knowledge + EC-378's current-document block (read from the seam store\n // at REQUEST time, so every turn sees what is open NOW). The route folds\n // these sections into the system prompt; it resolves no data itself\n // (zero-data-access proxy). The block is advisory — the deterministic\n // omitted-target fallback lives in the executor.\n body: async () => {\n const knowledge = await resolveKnowledge(knowledgeSources, { readCollection })\n knowledgeRef.current = knowledge\n const current = getAiCurrentDocument()\n return {\n context: buildChatContext({\n collections: cms.collections,\n knowledge,\n ...(current\n ? {\n extraSections: [\n renderCurrentDocumentSection({\n collection: current.collection,\n id: current.id,\n title: current.title,\n locale: activeLocale,\n }),\n ],\n }\n : {}),\n }),\n }\n },\n }),\n [chatTarget, knowledgeSources, readCollection, cms.collections, activeLocale],\n )\n\n // Per-conversation relation-candidate store (EC-380: replaced on clear, so a\n // fresh conversation cannot reference the previous one's searched ids).\n const candidatesRef = useRef<{ conversation: number; store: ChatCandidateStore }>({\n conversation,\n store: createChatCandidateStore(),\n })\n if (candidatesRef.current.conversation !== conversation) {\n candidatesRef.current = { conversation, store: createChatCandidateStore() }\n }\n const candidateStore = candidatesRef.current.store\n\n const executor = useMemo(() => {\n if (!provider) return null\n return createChatToolExecutor({\n provider,\n registry,\n collections: cms.collections,\n readers,\n mintDocumentId,\n candidates: candidateStore,\n onProposal: (toolCallId, proposal) => dispatch({ type: 'propose', toolCallId, proposal }),\n knowledge: () => knowledgeRef.current,\n // EC-378: the current-document seam, read at EXECUTION time — the\n // deterministic fallback when the model omits a tool's target.\n currentDocument: () => getAiCurrentDocument(),\n locale: activeLocale,\n ...(cms.localeConfig ? { localeConfig: cms.localeConfig } : {}),\n // EC-473 established that the assistant must validate with the EDITOR's\n // rules, not a subset. EC-497 hands over THE bag itself — one value, built\n // once by `gateValidateOptions` — so the assistant's gate and the editor's\n // gate cannot drift by someone forgetting a key at a call site.\n validateOptions: cms.validateOptions,\n ...(maxRepairAttempts !== undefined ? { maxRepairAttempts } : {}),\n })\n }, [\n provider,\n registry,\n cms.collections,\n cms.localeConfig,\n cms.validateOptions,\n readers,\n mintDocumentId,\n activeLocale,\n maxRepairAttempts,\n candidateStore,\n ])\n\n const chat = useChat({\n // A new conversation is a new chat id — `useChat` recreates its state.\n id: `elytra-assistant-${conversation}`,\n transport,\n onToolCall: async ({ toolCall }) => {\n if (!executor) return\n const outcome = await executor.execute({\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n input: toolCall.input,\n })\n // Deliberately NOT awaited: `onToolCall` runs inside the chat's serial\n // stream job, and `addToolOutput` enqueues on the same executor —\n // awaiting it here deadlocks the stream (verified against ai@7.0.22).\n if (outcome.ok) {\n void chat.addToolOutput({\n tool: toolCall.toolName,\n toolCallId: toolCall.toolCallId,\n output: outcome.output,\n })\n } else {\n void chat.addToolOutput({\n state: 'output-error',\n tool: toolCall.toolName,\n toolCallId: toolCall.toolCallId,\n errorText: outcome.errorText,\n })\n }\n },\n // The SDK predicate wrapped with the per-user-turn step cap: a capped turn\n // stops cleanly with a visible note — never a silent stall (EC-334).\n sendAutomaticallyWhen: withStepCap(lastAssistantMessageIsCompleteWithToolCalls, {\n maxSteps: DEFAULT_MAX_STEPS,\n onCap: () =>\n dispatch({ type: 'fail', message: translate('ai.error.stepCap', { max: DEFAULT_MAX_STEPS }) }),\n }),\n // EC-514: \"no route mounted at <path>\" is named as itself; every other\n // transport/provider failure keeps the generic route note.\n onError: (error) =>\n dispatch({\n type: 'fail',\n message: isAiRouteNotMountedError(error)\n ? translate('ai.error.routeNotMounted', { endpoint: error.endpoint })\n : translate('ai.error.route'),\n }),\n })\n\n const busy = chat.status === 'submitted' || chat.status === 'streaming'\n\n const submit = useCallback(() => {\n const text = input.trim()\n if (text.length === 0 || busy || !ready) return\n setInput('')\n dispatch({ type: 'clear-error' })\n void chat.sendMessage({ text })\n }, [input, busy, ready, chat])\n\n // EC-380: clear / new topic — busy-safe (stop first), pending proposals\n // discarded with reject semantics (the review state resets; nothing was applied).\n const startNewConversation = useCallback(() => {\n void clearConversation({ status: chat.status, stop: chat.stop }, () => {\n dispatch({ type: 'clear' })\n setInput('')\n setConversation((current) => current + 1)\n })\n }, [chat.status, chat.stop])\n\n const applyProposal = useCallback(\n async (proposal: ProposalReview) => {\n if (projectId === undefined) return\n // For an update/translate of an EXISTING document, spread the stored doc\n // first so document-level extras (alias, defaultLocale) survive the apply —\n // the patch replaces only values/localized.\n const existing = cms.getDocument({ collection: proposal.collection, id: proposal.documentId })\n const doc: CmsDocument = {\n ...(existing ?? {}),\n collection: proposal.collection,\n id: proposal.documentId,\n values: proposal.patch.values,\n ...(proposal.patch.localized ? { localized: proposal.patch.localized } : {}),\n }\n // EC-473 — re-validate BEFORE the write. The proposal was validated when it was\n // built, but the editor may have changed the document since (or applied an\n // earlier proposal on top of it), so the patch is re-judged against what is in\n // the session NOW. Same gate as every other AI write path: only errors the\n // apply would INTRODUCE block it — pre-existing brokenness on the document is\n // not the assistant's doing and must not make an unrelated change unappliable.\n const introduced = introducedErrors(\n existing ? cms.validate(existing) : [],\n cms.validate(doc),\n )\n if (introduced.length > 0) {\n dispatch({\n type: 'fail',\n message: translate('ai.error.applyInvalid', {\n issues: introduced\n .slice(0, 3)\n .map((issue) => issue.message)\n .join('; '),\n }),\n })\n return\n }\n const { operations } = await getStudioOperations()\n const upserted = await operations.documents.upsert({ projectId, document: doc })\n if (!upserted.ok) {\n dispatch({ type: 'fail', message: translate('ai.error.applyFailed') })\n return\n }\n // Provenance: record an AI-tagged version snapshot (a restore point + the\n // 'assistant' origin surfaced in the versions pane). Best-effort — the draft\n // already landed above; a snapshot failure must not lose the applied content.\n try {\n await operations.documents.snapshot({\n projectId,\n ref: { collection: proposal.collection, id: proposal.documentId },\n label: translate('cms.version.aiAuthoredTitle'),\n origin: 'assistant',\n })\n } catch {\n // ignore — provenance is additive\n }\n // Reflect in the session so the new draft shows in the list + is navigable.\n cms.saveDocument(doc)\n // EC-378: when the applied proposal targets the document OPEN in the editor,\n // adopt it into the editor's draft buffer — otherwise the editor's autosave\n // would write its stale buffer back and silently revert the applied change.\n const current = getAiCurrentDocument()\n if (\n current &&\n current.collection === proposal.collection &&\n current.id === proposal.documentId\n ) {\n current.adoptDraft(doc)\n }\n dispatch({ type: 'apply', toolCallId: proposal.id })\n },\n [projectId, cms],\n )\n\n const openProposal = useCallback(\n (proposal: ProposalReview) => {\n if (projectId === undefined) return\n void navigate({\n to: STUDIO_PROJECT_ROUTE,\n params: { projectId, _splat: studioPath.cmsDocument(proposal.collection, proposal.documentId) },\n })\n },\n [navigate, projectId],\n )\n\n const hasProposal = useCallback(\n (toolCallId: string) => review.proposals[toolCallId] !== undefined,\n [review.proposals],\n )\n\n const renderAssistantMessage = (message: UIMessage) => {\n const segments = segmentMessageParts(message.parts as MessagePartView[], hasProposal)\n return segments.map((segment, index) => {\n if (segment.kind === 'text') {\n return (\n <AssistantTurn key={`${message.id}-${index}`}>\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {segment.text}\n </Text>\n </AssistantTurn>\n )\n }\n if (segment.kind === 'tools') {\n return (\n <AssistantTurn key={`${message.id}-${index}`}>\n <ToolStatus steps={segment.parts.map(describeToolPart)} />\n </AssistantTurn>\n )\n }\n const proposal = review.proposals[segment.toolCallId]\n if (!proposal) return null\n return (\n <ProposalCard\n key={segment.toolCallId}\n proposal={proposal}\n onApply={() => void applyProposal(proposal)}\n onDiscard={() => dispatch({ type: 'reject', toolCallId: proposal.id })}\n onOpen={() => openProposal(proposal)}\n />\n )\n })\n }\n\n return (\n <aside className=\"flex h-full min-h-0 w-[360px] shrink-0 flex-col border-l border-border bg-card\">\n <Row gap=\"2\" align=\"center\" className=\"h-12 shrink-0 border-b border-border px-3\">\n <Heading level={2} size=\"h4\">\n {translate('ai.panel.title')}\n </Heading>\n <Spacer />\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={startNewConversation}\n disabled={chat.messages.length === 0 && review.error === null && !busy}\n title={translate('ai.panel.newConversation')}\n aria-label={translate('ai.panel.newConversation')}\n >\n <MessageSquarePlus />\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n onClick={() => setAiPanelOpen(false)}\n title={translate('ai.panel.close')}\n aria-label={translate('ai.panel.close')}\n >\n <X />\n </Button>\n </Row>\n\n <Conversation atBottomKey={chat.messages}>\n {chat.messages.length === 0 ? <ConversationEmpty /> : null}\n {chat.messages.map((message) =>\n message.role === 'user' ? (\n <UserTurn key={message.id} text={messageText(message)} />\n ) : (\n <div key={message.id} className=\"contents\">\n {renderAssistantMessage(message)}\n </div>\n ),\n )}\n {chat.status === 'submitted' ? (\n <AssistantTurn>\n <WorkingIndicator />\n </AssistantTurn>\n ) : null}\n {review.error !== null ? (\n <AssistantTurn>\n <ChatErrorNote message={review.error} />\n </AssistantTurn>\n ) : null}\n </Conversation>\n\n {ready ? (\n <PromptInput value={input} onChange={setInput} onSubmit={submit} disabled={busy} />\n ) : (\n <div className=\"border-t border-border p-3\">\n <Text size=\"xs\" tone=\"muted\">\n {translate('ai.panel.unavailable')}\n </Text>\n </div>\n )}\n </aside>\n )\n}\n"]}
@@ -22,7 +22,17 @@ export interface AssetsStore {
22
22
  errorMessage?: string;
23
23
  assets: readonly AssetRecord[];
24
24
  uploads: readonly UploadEntry[];
25
- upload(files: Iterable<UploadableFile>): void;
25
+ /**
26
+ * Queue files for upload. Returns the upload-queue id of each file, in order, so a
27
+ * caller can follow exactly ITS uploads (EC-588 A7) — see {@link landedAssetId}.
28
+ */
29
+ upload(files: Iterable<UploadableFile>): readonly string[];
30
+ /**
31
+ * The asset id an upload landed as, once its record is confirmed (EC-588 A7);
32
+ * `undefined` while it runs, after it failed, or for an id this store never issued.
33
+ * A completed upload leaves the queue, so this is where its outcome stays readable.
34
+ */
35
+ landedAssetId(uploadId: string): string | undefined;
26
36
  retryUpload(uploadId: string): void;
27
37
  dismissUpload(uploadId: string): void;
28
38
  /**
@@ -22,6 +22,9 @@ async function loadAssets(operations, projectId) {
22
22
  }
23
23
  export function AssetsProvider({ projectId, children, }) {
24
24
  const uploadFilesRef = useRef(new Map());
25
+ // EC-588 A7: upload id → the asset id it landed as. Written BEFORE the entry leaves
26
+ // the queue, so a reader never sees an upload that is neither running nor landed.
27
+ const landedRef = useRef(new Map());
25
28
  const uploadSeq = useRef(0);
26
29
  const [uploads, setUploads] = useState([]);
27
30
  /** The shared studio operations bundle + the metadata-write facade over it. */
@@ -63,15 +66,18 @@ export function AssetsProvider({ projectId, children, }) {
63
66
  }, previous);
64
67
  if (result.ok) {
65
68
  // Completed uploads leave the queue; the asset appears in the library.
69
+ landedRef.current.set(uploadId, result.asset.id);
66
70
  uploadFilesRef.current.delete(uploadId);
67
71
  setUploads((prev) => prev.filter((u) => u.id !== uploadId));
68
72
  await refresh();
69
73
  }
70
74
  }, [studio, projectId, refresh, setUploadState]);
71
75
  const upload = useCallback((files) => {
76
+ const ids = [];
72
77
  for (const file of files) {
73
78
  uploadSeq.current += 1;
74
79
  const uploadId = `upload-${uploadSeq.current}`;
80
+ ids.push(uploadId);
75
81
  uploadFilesRef.current.set(uploadId, file);
76
82
  setUploads((prev) => [
77
83
  ...prev,
@@ -79,7 +85,9 @@ export function AssetsProvider({ projectId, children, }) {
79
85
  ]);
80
86
  void runEntry(uploadId, file);
81
87
  }
88
+ return ids;
82
89
  }, [runEntry]);
90
+ const landedAssetId = useCallback((uploadId) => landedRef.current.get(uploadId), []);
83
91
  const retryUpload = useCallback((uploadId) => {
84
92
  const file = uploadFilesRef.current.get(uploadId);
85
93
  const entry = uploads.find((u) => u.id === uploadId);
@@ -146,6 +154,7 @@ export function AssetsProvider({ projectId, children, }) {
146
154
  assets,
147
155
  uploads,
148
156
  upload,
157
+ landedAssetId,
149
158
  retryUpload,
150
159
  dismissUpload,
151
160
  usagesOf,
@@ -160,6 +169,7 @@ export function AssetsProvider({ projectId, children, }) {
160
169
  assets,
161
170
  uploads,
162
171
  upload,
172
+ landedAssetId,
163
173
  retryUpload,
164
174
  dismissUpload,
165
175
  usagesOf,
@@ -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 { useCallback, useContext, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { AssetRecord, AssetRepository } from '@elytracms/core/persistence'\nimport { deleteAsset, runVariantBackfill, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets'\nimport type {\n BackfillProgress,\n BackfillSignal,\n DeleteAssetResult,\n UploadState,\n UploadableFile,\n} 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 { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\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 /**\n * Backfill downscaled previews onto existing images (EC-400 AD-1). Sequential,\n * idempotent, cancellable; each patch flows through the operations facade and\n * the shared query cache (AD-5/AD-17). Reports progress via `handlers.onProgress`\n * and stops when `handlers.signal.cancelled` flips.\n */\n generatePreviews(handlers?: {\n onProgress?: (progress: BackfillProgress) => void\n signal?: BackfillSignal\n }): Promise<BackfillProgress>\n refresh(): Promise<void>\n}\n\nconst { Context: AssetsContext, useRequired: useAssets } = createRequiredContext<AssetsStore>(\n 'Assets',\n { article: 'an' },\n)\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, errorMessage } = listStoreState(query)\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 generatePreviews = useCallback(\n async (handlers?: {\n onProgress?: (progress: BackfillProgress) => void\n signal?: BackfillSignal\n }) => {\n const { adapter, assetRepo } = await studio()\n // Reads (list) + the variant patch (upsert) go through the operations facade;\n // bytes ride on `adapter.blobs`. The backfill is per-variant (EC-420 AD-4):\n // one fetch of each original fills whichever of thumb/lqip is missing. Each\n // generated record is patched into the shared cache in place (EC-301 perf,\n // AD-17) so the grid updates live without a full re-list.\n return runVariantBackfill(\n {\n assets: assetRepo,\n blobs: adapter.blobs,\n onGenerated: (updated) =>\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === updated.id ? updated : a)),\n ),\n ...(handlers?.onProgress ? { onProgress: handlers.onProgress } : {}),\n ...(handlers?.signal ? { signal: handlers.signal } : {}),\n },\n projectId,\n )\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 generatePreviews,\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 generatePreviews,\n refresh,\n ],\n )\n\n return <AssetsContext.Provider value={store}>{children}</AssetsContext.Provider>\n}\n\nexport { useAssets }\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 { useCallback, useContext, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { AssetRecord, AssetRepository } from '@elytracms/core/persistence'\nimport { deleteAsset, runVariantBackfill, runUpload, xhrUploadTransport } from '@elytracms/studio/lib/assets'\nimport type {\n BackfillProgress,\n BackfillSignal,\n DeleteAssetResult,\n UploadState,\n UploadableFile,\n} 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 { listStoreState, useOperationCache, useOperationQuery } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\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 /**\n * Queue files for upload. Returns the upload-queue id of each file, in order, so a\n * caller can follow exactly ITS uploads (EC-588 A7) — see {@link landedAssetId}.\n */\n upload(files: Iterable<UploadableFile>): readonly string[]\n /**\n * The asset id an upload landed as, once its record is confirmed (EC-588 A7);\n * `undefined` while it runs, after it failed, or for an id this store never issued.\n * A completed upload leaves the queue, so this is where its outcome stays readable.\n */\n landedAssetId(uploadId: string): string | undefined\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 /**\n * Backfill downscaled previews onto existing images (EC-400 AD-1). Sequential,\n * idempotent, cancellable; each patch flows through the operations facade and\n * the shared query cache (AD-5/AD-17). Reports progress via `handlers.onProgress`\n * and stops when `handlers.signal.cancelled` flips.\n */\n generatePreviews(handlers?: {\n onProgress?: (progress: BackfillProgress) => void\n signal?: BackfillSignal\n }): Promise<BackfillProgress>\n refresh(): Promise<void>\n}\n\nconst { Context: AssetsContext, useRequired: useAssets } = createRequiredContext<AssetsStore>(\n 'Assets',\n { article: 'an' },\n)\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 // EC-588 A7: upload id → the asset id it landed as. Written BEFORE the entry leaves\n // the queue, so a reader never sees an upload that is neither running nor landed.\n const landedRef = useRef(new Map<string, string>())\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, errorMessage } = listStoreState(query)\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 landedRef.current.set(uploadId, result.asset.id)\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>): readonly string[] => {\n const ids: string[] = []\n for (const file of files) {\n uploadSeq.current += 1\n const uploadId = `upload-${uploadSeq.current}`\n ids.push(uploadId)\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 return ids\n },\n [runEntry],\n )\n\n const landedAssetId = useCallback((uploadId: string) => landedRef.current.get(uploadId), [])\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 generatePreviews = useCallback(\n async (handlers?: {\n onProgress?: (progress: BackfillProgress) => void\n signal?: BackfillSignal\n }) => {\n const { adapter, assetRepo } = await studio()\n // Reads (list) + the variant patch (upsert) go through the operations facade;\n // bytes ride on `adapter.blobs`. The backfill is per-variant (EC-420 AD-4):\n // one fetch of each original fills whichever of thumb/lqip is missing. Each\n // generated record is patched into the shared cache in place (EC-301 perf,\n // AD-17) so the grid updates live without a full re-list.\n return runVariantBackfill(\n {\n assets: assetRepo,\n blobs: adapter.blobs,\n onGenerated: (updated) =>\n cache.patch<AssetRecord[]>(assetsQueryKey(projectId), (prev) =>\n (prev ?? []).map((a) => (a.id === updated.id ? updated : a)),\n ),\n ...(handlers?.onProgress ? { onProgress: handlers.onProgress } : {}),\n ...(handlers?.signal ? { signal: handlers.signal } : {}),\n },\n projectId,\n )\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 landedAssetId,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n generatePreviews,\n refresh,\n }),\n [\n projectId,\n status,\n errorMessage,\n assets,\n uploads,\n upload,\n landedAssetId,\n retryUpload,\n dismissUpload,\n usagesOf,\n remove,\n updateMetadata,\n generatePreviews,\n refresh,\n ],\n )\n\n return <AssetsContext.Provider value={store}>{children}</AssetsContext.Provider>\n}\n\nexport { useAssets }\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"]}
@@ -29,10 +29,3 @@ export declare function focalPointFromPoint(rect: {
29
29
  }, clientX: number, clientY: number): FocalPoint;
30
30
  /** CSS `object-position` for a focal point (percent), matching delivery (EC-230). */
31
31
  export declare function focalPointObjectPosition(point: FocalPoint | undefined): string | undefined;
32
- /**
33
- * The asset ids present in `after` but not in `before` — the records that an
34
- * upload added to the library. The field uses this to auto-select an asset it
35
- * just uploaded (the `upload()` API is fire-and-forget, so the new id is learned
36
- * by diffing the library, not returned). Order follows `after`.
37
- */
38
- export declare function newlyAddedAssetIds(before: Iterable<string>, after: readonly string[]): string[];
@@ -34,14 +34,4 @@ export function focalPointObjectPosition(point) {
34
34
  return undefined;
35
35
  return `${clampUnit(point.x) * 100}% ${clampUnit(point.y) * 100}%`;
36
36
  }
37
- /**
38
- * The asset ids present in `after` but not in `before` — the records that an
39
- * upload added to the library. The field uses this to auto-select an asset it
40
- * just uploaded (the `upload()` API is fire-and-forget, so the new id is learned
41
- * by diffing the library, not returned). Order follows `after`.
42
- */
43
- export function newlyAddedAssetIds(before, after) {
44
- const known = new Set(before);
45
- return after.filter((id) => !known.has(id));
46
- }
47
37
  //# sourceMappingURL=focal-point.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"focal-point.js","sourceRoot":"","sources":["../../../src/components/assets/focal-point.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,sDAAsD;AACtD,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,GAAG,CAAA;IAC/B,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAClC,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,MAAM,kBAAkB,GAAe,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAA;AAEhE;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAkE,EAClE,OAAe,EACf,OAAe;IAEf,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,EAAE,GAAG,kBAAkB,EAAE,CAAA;IACzE,OAAO;QACL,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QAChD,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;KACjD,CAAA;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,wBAAwB,CAAC,KAA6B;IACpE,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAA;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAwB,EACxB,KAAwB;IAExB,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;IAC7B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;AAC7C,CAAC","sourcesContent":["/**\n * Pure focal-point + upload-selection helpers for the image-field authoring UI\n * (EC-248). Kept side-effect-free and DOM-free so the coordinate math and the\n * \"which upload just landed\" reconciliation are unit-testable without a browser.\n *\n * A focal point is the EC-230 normalized 0–1 hotspot stored on the asset record\n * and threaded to delivery's `object-position`. The picker lets an editor place\n * it directly on the image preview.\n */\n\n/** A normalized focal point — both axes in `[0, 1]`. */\nexport interface FocalPoint {\n x: number\n y: number\n}\n\n/** Clamp a number into the unit interval `[0, 1]`. */\nexport function clampUnit(n: number): number {\n if (Number.isNaN(n)) return 0.5\n return n < 0 ? 0 : n > 1 ? 1 : n\n}\n\n/** The neutral default focal point (image centre) — what an unset hotspot renders as. */\nexport const CENTER_FOCAL_POINT: FocalPoint = { x: 0.5, y: 0.5 }\n\n/**\n * Convert a pointer position over the preview's bounding box into a normalized\n * focal point. A degenerate (zero-area) box falls back to centre rather than\n * dividing by zero, so a not-yet-laid-out element never yields `NaN`.\n */\nexport function focalPointFromPoint(\n rect: { left: number; top: number; width: number; height: number },\n clientX: number,\n clientY: number,\n): FocalPoint {\n if (rect.width <= 0 || rect.height <= 0) return { ...CENTER_FOCAL_POINT }\n return {\n x: clampUnit((clientX - rect.left) / rect.width),\n y: clampUnit((clientY - rect.top) / rect.height),\n }\n}\n\n/** CSS `object-position` for a focal point (percent), matching delivery (EC-230). */\nexport function focalPointObjectPosition(point: FocalPoint | undefined): string | undefined {\n if (!point) return undefined\n return `${clampUnit(point.x) * 100}% ${clampUnit(point.y) * 100}%`\n}\n\n/**\n * The asset ids present in `after` but not in `before` — the records that an\n * upload added to the library. The field uses this to auto-select an asset it\n * just uploaded (the `upload()` API is fire-and-forget, so the new id is learned\n * by diffing the library, not returned). Order follows `after`.\n */\nexport function newlyAddedAssetIds(\n before: Iterable<string>,\n after: readonly string[],\n): string[] {\n const known = new Set(before)\n return after.filter((id) => !known.has(id))\n}\n"]}
1
+ {"version":3,"file":"focal-point.js","sourceRoot":"","sources":["../../../src/components/assets/focal-point.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,sDAAsD;AACtD,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,GAAG,CAAA;IAC/B,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAClC,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,MAAM,kBAAkB,GAAe,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAA;AAEhE;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAkE,EAClE,OAAe,EACf,OAAe;IAEf,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,EAAE,GAAG,kBAAkB,EAAE,CAAA;IACzE,OAAO;QACL,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QAChD,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;KACjD,CAAA;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,wBAAwB,CAAC,KAA6B;IACpE,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAA;IAC5B,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAA;AACpE,CAAC","sourcesContent":["/**\n * Pure focal-point + upload-selection helpers for the image-field authoring UI\n * (EC-248). Kept side-effect-free and DOM-free so the coordinate math and the\n * \"which upload just landed\" reconciliation are unit-testable without a browser.\n *\n * A focal point is the EC-230 normalized 0–1 hotspot stored on the asset record\n * and threaded to delivery's `object-position`. The picker lets an editor place\n * it directly on the image preview.\n */\n\n/** A normalized focal point — both axes in `[0, 1]`. */\nexport interface FocalPoint {\n x: number\n y: number\n}\n\n/** Clamp a number into the unit interval `[0, 1]`. */\nexport function clampUnit(n: number): number {\n if (Number.isNaN(n)) return 0.5\n return n < 0 ? 0 : n > 1 ? 1 : n\n}\n\n/** The neutral default focal point (image centre) — what an unset hotspot renders as. */\nexport const CENTER_FOCAL_POINT: FocalPoint = { x: 0.5, y: 0.5 }\n\n/**\n * Convert a pointer position over the preview's bounding box into a normalized\n * focal point. A degenerate (zero-area) box falls back to centre rather than\n * dividing by zero, so a not-yet-laid-out element never yields `NaN`.\n */\nexport function focalPointFromPoint(\n rect: { left: number; top: number; width: number; height: number },\n clientX: number,\n clientY: number,\n): FocalPoint {\n if (rect.width <= 0 || rect.height <= 0) return { ...CENTER_FOCAL_POINT }\n return {\n x: clampUnit((clientX - rect.left) / rect.width),\n y: clampUnit((clientY - rect.top) / rect.height),\n }\n}\n\n/** CSS `object-position` for a focal point (percent), matching delivery (EC-230). */\nexport function focalPointObjectPosition(point: FocalPoint | undefined): string | undefined {\n if (!point) return undefined\n return `${clampUnit(point.x) * 100}% ${clampUnit(point.y) * 100}%`\n}\n"]}
@@ -1,4 +1,5 @@
1
- import type { RenderContext, ResolveAsset, ResolveLink, ResolveListing, ResolveRelation } from '@elytracms/core/runtime-renderer';
1
+ import type { AssetRecord } from '@elytracms/core/persistence';
2
+ import type { RenderAsset, RenderContext, ResolveAsset, ResolveLink, ResolveListing, ResolveRelation } from '@elytracms/core/runtime-renderer';
2
3
  import type { ComponentNode } from '@elytracms/core/project-graph';
3
4
  import type { StudioRegistry } from '@elytracms/studio/lib/registry';
4
5
  import type { CanvasPerspective } from '@elytracms/studio/lib/data';
@@ -65,6 +66,16 @@ export declare function createCanvasRenderContext(deps: CanvasRenderContextDeps)
65
66
  * the given perspective. Returns the context plus the page-node id set the
66
67
  * canvas also needs for spacing-handle gating.
67
68
  */
69
+ /**
70
+ * The canvas's asset reader IS delivery's (NK-049 finding, 2026-10-23). This used to
71
+ * hand-pick `url/width/height/alt/focalPoint`, read `alt` from the legacy column
72
+ * only and drop `meta` — so a component's AI badge (`meta.aiGenerated`) and an
73
+ * editor-set `meta.alt` rendered on the site but not on the canvas. One reader
74
+ * (`resolvedAsset`, `content/assets.ts`) means the canvas and the host cannot
75
+ * disagree about an asset's own facts; `RenderAsset` is a subset of
76
+ * `ResolvedAsset`, `lqip` included. Pure, exported for the node test.
77
+ */
78
+ export declare function canvasRenderAsset(record: AssetRecord | undefined): RenderAsset | null;
68
79
  export declare function useCanvasRenderContext(args: {
69
80
  /** The edited composition root (`builder.present`). */
70
81
  present: ComponentNode;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react';
3
+ import { resolvedAsset } from '@elytracms/core/content';
3
4
  import { listingQueryOf } from '@elytracms/core/runtime-renderer';
4
5
  import { translate } from '@elytracms/studio/lib/i18n';
5
6
  import { contentQueryErrorMessage } from '@elytracms/studio/lib/persona/labels';
@@ -80,6 +81,20 @@ export function createCanvasRenderContext(deps) {
80
81
  * the given perspective. Returns the context plus the page-node id set the
81
82
  * canvas also needs for spacing-handle gating.
82
83
  */
84
+ /**
85
+ * The canvas's asset reader IS delivery's (NK-049 finding, 2026-10-23). This used to
86
+ * hand-pick `url/width/height/alt/focalPoint`, read `alt` from the legacy column
87
+ * only and drop `meta` — so a component's AI badge (`meta.aiGenerated`) and an
88
+ * editor-set `meta.alt` rendered on the site but not on the canvas. One reader
89
+ * (`resolvedAsset`, `content/assets.ts`) means the canvas and the host cannot
90
+ * disagree about an asset's own facts; `RenderAsset` is a subset of
91
+ * `ResolvedAsset`, `lqip` included. Pure, exported for the node test.
92
+ */
93
+ export function canvasRenderAsset(record) {
94
+ if (!record?.url)
95
+ return null;
96
+ return resolvedAsset(record, record.url);
97
+ }
83
98
  export function useCanvasRenderContext(args) {
84
99
  const { present, selectedId, hoveredId, studio, perspective, richEditEpochs } = args;
85
100
  // EC-460: the canvas follows the preview content session reactively. The
@@ -95,23 +110,12 @@ export function useCanvasRenderContext(args) {
95
110
  // Only page nodes are editable; layout chrome (rendered around the page) is
96
111
  // read-only context and must not be selectable (it isn't in the page tree).
97
112
  const pageNodeIds = useMemo(() => collectIds(present), [present]);
98
- // EC-195: resolve an `asset`-typed Image prop (an asset id) to its url +
99
- // intrinsic dimensions from the live asset library, so the canvas previews
100
- // exactly what the host renders. A record without a resolved url (e.g. a
101
- // never-served seed) → null → the implementation's visible empty state.
113
+ // EC-195: resolve an `asset`-typed Image prop (an asset id) from the live asset
114
+ // library, so the canvas previews exactly what the host renders. A record
115
+ // without a resolved url (e.g. a never-served seed) → null → the
116
+ // implementation's visible empty state.
102
117
  const assetsStore = useOptionalAssets();
103
- const resolveAsset = useCallback((assetId) => {
104
- const record = assetsStore?.assets.find((a) => a.id === assetId);
105
- if (!record?.url)
106
- return null;
107
- return {
108
- url: record.url,
109
- width: record.width ?? null,
110
- height: record.height ?? null,
111
- alt: record.alt ?? null,
112
- focalPoint: record.focalPoint ?? null,
113
- };
114
- }, [assetsStore]);
118
+ const resolveAsset = useCallback((assetId) => canvasRenderAsset(assetsStore?.assets.find((a) => a.id === assetId)), [assetsStore]);
115
119
  // The canvas's active locale for relation/listing/link resolution below.
116
120
  // EC-479: the ambient locale is `null` until a project's config is installed (it
117
121
  // no longer opens on the engine's `en/de` fixture). `undefined` means "you pick" to