@elytracms/studio 0.0.100 → 0.0.102

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 (170) hide show
  1. package/convex/analytics-cost.test.ts +1 -1
  2. package/convex/asset-meta-patch.test.ts +7 -27
  3. package/convex/assets.ts +6 -41
  4. package/convex/cms.ts +2 -29
  5. package/convex/delivery.ts +2 -2
  6. package/convex/record-functions.ts +15 -82
  7. package/convex/redirects.ts +11 -116
  8. package/convex/schema.ts +4 -23
  9. package/convex/shared-capture.convex.test.ts +165 -0
  10. package/convex/triggers.ts +7 -14
  11. package/dist/components/ai/ai-chat-panel-lazy.js +7 -2
  12. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -1
  13. package/dist/components/ai/ai-chat-panel.js +8 -5
  14. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  15. package/dist/components/ai/proposal-card.js +2 -1
  16. package/dist/components/ai/proposal-card.js.map +1 -1
  17. package/dist/components/ai/proposal-unfilled.d.ts +14 -0
  18. package/dist/components/ai/proposal-unfilled.js +33 -0
  19. package/dist/components/ai/proposal-unfilled.js.map +1 -0
  20. package/dist/components/ai/translate-document-action.js +8 -6
  21. package/dist/components/ai/translate-document-action.js.map +1 -1
  22. package/dist/components/ai/translate-skipped.js +1 -0
  23. package/dist/components/ai/translate-skipped.js.map +1 -1
  24. package/dist/components/app-views/studio-config.d.ts +12 -12
  25. package/dist/components/app-views/studio-config.js.map +1 -1
  26. package/dist/components/assets/asset-detail.js +1 -2
  27. package/dist/components/assets/asset-detail.js.map +1 -1
  28. package/dist/components/assets/asset-metadata.d.ts +2 -9
  29. package/dist/components/assets/asset-metadata.js +3 -10
  30. package/dist/components/assets/asset-metadata.js.map +1 -1
  31. package/dist/components/assets/asset-search.js +1 -1
  32. package/dist/components/assets/asset-search.js.map +1 -1
  33. package/dist/components/assets/asset-selection.js +2 -2
  34. package/dist/components/assets/asset-selection.js.map +1 -1
  35. package/dist/components/assets/assets-collection-view-source.d.ts +2 -3
  36. package/dist/components/assets/assets-collection-view-source.js +2 -3
  37. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  38. package/dist/components/builder/__fixtures__/reuse-race.d.ts +30 -0
  39. package/dist/components/builder/__fixtures__/reuse-race.js +77 -0
  40. package/dist/components/builder/__fixtures__/reuse-race.js.map +1 -0
  41. package/dist/components/builder/builder-store.d.ts +37 -1
  42. package/dist/components/builder/builder-store.js +21 -6
  43. package/dist/components/builder/builder-store.js.map +1 -1
  44. package/dist/components/builder/canvas-chrome-css.d.ts +1 -1
  45. package/dist/components/builder/canvas-chrome-css.js +1 -1
  46. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  47. package/dist/components/builder/canvas-chrome.d.ts +1 -1
  48. package/dist/components/builder/canvas-chrome.js +1 -1
  49. package/dist/components/builder/canvas-chrome.js.map +1 -1
  50. package/dist/components/builder/canvas-rebase.d.ts +4 -4
  51. package/dist/components/builder/canvas-rebase.js +3 -2
  52. package/dist/components/builder/canvas-rebase.js.map +1 -1
  53. package/dist/components/builder/canvas-render-context.d.ts +1 -1
  54. package/dist/components/builder/canvas-render-context.js +1 -1
  55. package/dist/components/builder/canvas-render-context.js.map +1 -1
  56. package/dist/components/builder/composition-field-editor-lazy.js +2 -1
  57. package/dist/components/builder/composition-field-editor-lazy.js.map +1 -1
  58. package/dist/components/builder/composition-field-editor.js +3 -3
  59. package/dist/components/builder/composition-field-editor.js.map +1 -1
  60. package/dist/components/builder/reuse-context.d.ts +9 -2
  61. package/dist/components/builder/reuse-context.js +46 -6
  62. package/dist/components/builder/reuse-context.js.map +1 -1
  63. package/dist/components/builder/reuse.d.ts +113 -70
  64. package/dist/components/builder/reuse.js +123 -108
  65. package/dist/components/builder/reuse.js.map +1 -1
  66. package/dist/components/cms/asset-field.js +1 -3
  67. package/dist/components/cms/asset-field.js.map +1 -1
  68. package/dist/components/cms/cell.d.ts +2 -2
  69. package/dist/components/cms/cell.js +8 -18
  70. package/dist/components/cms/cell.js.map +1 -1
  71. package/dist/components/cms/cms-store.d.ts +9 -1
  72. package/dist/components/cms/cms-store.js +1 -0
  73. package/dist/components/cms/cms-store.js.map +1 -1
  74. package/dist/components/cms/detail-editor.js +15 -14
  75. package/dist/components/cms/detail-editor.js.map +1 -1
  76. package/dist/components/cms/field-control.js +13 -1
  77. package/dist/components/cms/field-control.js.map +1 -1
  78. package/dist/components/cms/prop-fields.js +1 -1
  79. package/dist/components/cms/prop-fields.js.map +1 -1
  80. package/dist/components/cms/rich-text/lazy.js +2 -1
  81. package/dist/components/cms/rich-text/lazy.js.map +1 -1
  82. package/dist/components/cms/rich-text/toolbar-state.d.ts +1 -1
  83. package/dist/components/cms/rich-text/toolbar-state.js +1 -1
  84. package/dist/components/cms/rich-text/toolbar-state.js.map +1 -1
  85. package/dist/components/cms/use-field-focus.js +3 -3
  86. package/dist/components/cms/use-field-focus.js.map +1 -1
  87. package/dist/components/cms/version-compare-lazy.js +2 -1
  88. package/dist/components/cms/version-compare-lazy.js.map +1 -1
  89. package/dist/components/components/components-index.js +1 -1
  90. package/dist/components/components/components-index.js.map +1 -1
  91. package/dist/components/deployment-setup-gate.d.ts +1 -1
  92. package/dist/components/deployment-setup-gate.js +1 -1
  93. package/dist/components/deployment-setup-gate.js.map +1 -1
  94. package/dist/components/load-failure.d.ts +62 -0
  95. package/dist/components/load-failure.js +110 -0
  96. package/dist/components/load-failure.js.map +1 -0
  97. package/dist/components/notifications/shell-findings.js +3 -3
  98. package/dist/components/notifications/shell-findings.js.map +1 -1
  99. package/dist/customization.d.ts +3 -3
  100. package/dist/customization.js.map +1 -1
  101. package/dist/lib/ai/apply-content.js +99 -28
  102. package/dist/lib/ai/apply-content.js.map +1 -1
  103. package/dist/lib/ai/chat-context.d.ts +77 -7
  104. package/dist/lib/ai/chat-context.js +139 -14
  105. package/dist/lib/ai/chat-context.js.map +1 -1
  106. package/dist/lib/ai/chat-state.d.ts +7 -1
  107. package/dist/lib/ai/chat-state.js.map +1 -1
  108. package/dist/lib/ai/constraints.d.ts +5 -0
  109. package/dist/lib/ai/constraints.js +21 -6
  110. package/dist/lib/ai/constraints.js.map +1 -1
  111. package/dist/lib/ai/fake-provider.d.ts +76 -26
  112. package/dist/lib/ai/fake-provider.js +210 -66
  113. package/dist/lib/ai/fake-provider.js.map +1 -1
  114. package/dist/lib/ai/index.d.ts +1 -0
  115. package/dist/lib/ai/index.js +1 -0
  116. package/dist/lib/ai/index.js.map +1 -1
  117. package/dist/lib/ai/keyless-assistant.d.ts +37 -0
  118. package/dist/lib/ai/keyless-assistant.js +193 -0
  119. package/dist/lib/ai/keyless-assistant.js.map +1 -0
  120. package/dist/lib/ai/knowledge.d.ts +9 -9
  121. package/dist/lib/ai/knowledge.js +22 -0
  122. package/dist/lib/ai/knowledge.js.map +1 -1
  123. package/dist/lib/ai/shape.d.ts +25 -2
  124. package/dist/lib/ai/shape.js +58 -7
  125. package/dist/lib/ai/shape.js.map +1 -1
  126. package/dist/lib/ai/tools.d.ts +18 -1
  127. package/dist/lib/ai/tools.js +74 -11
  128. package/dist/lib/ai/tools.js.map +1 -1
  129. package/dist/lib/ai/translate-document.d.ts +8 -1
  130. package/dist/lib/ai/translate-document.js +25 -9
  131. package/dist/lib/ai/translate-document.js.map +1 -1
  132. package/dist/lib/ai/types.d.ts +22 -7
  133. package/dist/lib/ai/types.js.map +1 -1
  134. package/dist/lib/ai/update-fields.js +13 -17
  135. package/dist/lib/ai/update-fields.js.map +1 -1
  136. package/dist/lib/ai/writer-form.d.ts +72 -3
  137. package/dist/lib/ai/writer-form.js +320 -42
  138. package/dist/lib/ai/writer-form.js.map +1 -1
  139. package/dist/lib/assets/upload.d.ts +1 -2
  140. package/dist/lib/assets/upload.js +1 -2
  141. package/dist/lib/assets/upload.js.map +1 -1
  142. package/dist/lib/auth-errors.d.ts +1 -1
  143. package/dist/lib/auth-errors.js +1 -1
  144. package/dist/lib/auth-errors.js.map +1 -1
  145. package/dist/lib/engine-version.d.ts +1 -1
  146. package/dist/lib/engine-version.js +1 -1
  147. package/dist/lib/engine-version.js.map +1 -1
  148. package/dist/lib/i18n/messages.de.d.ts +1 -1
  149. package/dist/lib/i18n/messages.de.js +32 -8
  150. package/dist/lib/i18n/messages.de.js.map +1 -1
  151. package/dist/lib/i18n/messages.en.d.ts +25 -3
  152. package/dist/lib/i18n/messages.en.js +40 -10
  153. package/dist/lib/i18n/messages.en.js.map +1 -1
  154. package/dist/lib/operations/cms-persistence.d.ts +28 -0
  155. package/dist/lib/operations/cms-persistence.js +55 -0
  156. package/dist/lib/operations/cms-persistence.js.map +1 -1
  157. package/dist/lib/persistence/convex-adapter.js +7 -2
  158. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  159. package/dist/lib/workspace/index.d.ts +3 -3
  160. package/dist/lib/workspace/index.js +3 -3
  161. package/dist/lib/workspace/index.js.map +1 -1
  162. package/dist/router/navigate-once.d.ts +1 -1
  163. package/dist/router/navigate-once.js.map +1 -1
  164. package/dist/router/root.d.ts +3 -2
  165. package/dist/router/root.js +25 -4
  166. package/dist/router/root.js.map +1 -1
  167. package/package.json +3 -3
  168. package/convex/asset-alt-migration.test.ts +0 -127
  169. package/convex/redirect-migration.test.ts +0 -205
  170. package/convex/version-kind-stamping.test.ts +0 -118
@@ -1 +1 @@
1
- {"version":3,"file":"use-field-focus.js","sourceRoot":"","sources":["../../../src/components/cms/use-field-focus.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport { SIDEBAR_FIELD_GROUP, tabOrder } from '@elytracms/core/cms-core'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { setStudioLocale } from '../../lib/locale'\nimport { parseDocumentFieldPath } from '../../lib/references'\nimport {\n anchorCandidates,\n deepestExistingAnchor,\n fieldAnchorSelector,\n} from './field-anchors'\nimport type { FieldFocusRequest } from './field-anchors'\n\n/**\n * EC-283 / EC-285 — where a jump-to-field lands, as a pure decision. A field in\n * the reserved `sidebar` group renders in the Details inspector pane (open that\n * pane, EC-285); any other field lives on a content tab (switch to it). Returns\n * `null` for an unknown field name (no-op). Directly testable, no DOM.\n */\nexport function focusTarget(\n collection: CollectionDef,\n fieldName: string,\n): { field: FieldDef; paneId: 'details' } | { field: FieldDef; tab: string } | null {\n const field = collection.fields.find((f) => f.name === fieldName)\n if (!field) return null\n if (field.form?.group === SIDEBAR_FIELD_GROUP) return { field, paneId: 'details' }\n const fieldTabs = tabOrder(collection.form)\n return { field, tab: field.form?.tab ?? fieldTabs[0] ?? 'main' }\n}\n\n/**\n * EC-443 — nested-anchor resolution retries. Expanding a collapsed array item is\n * React state (context-driven), so each nested array level needs a render cycle\n * before its deeper anchors exist; a short retry ladder lets a multi-level\n * expansion cascade settle before the plan degrades to a shallower anchor.\n */\nconst RESOLVE_ATTEMPT_DELAYS_MS = [60, 200, 450] as const\n/** How long the ring highlight (top-level or nested) stays on. */\nconst HIGHLIGHT_CLEAR_MS = 3200\n\nexport interface FieldFocus {\n /** The top-level field ring-highlighted right now (or null). */\n highlightedField: string | null\n /**\n * The active nested focus request (EC-443): drives collapsed-array expansion\n * and the ring on the exact nested control, via `FieldFocusRequestContext`.\n */\n focusRequest: FieldFocusRequest | null\n /** The inspector sidebar's active pane, controlled so a jump can open Details. */\n inspectorPaneId: string | undefined\n setInspectorPaneId: (id: string | undefined) => void\n /**\n * Focus a field by anchor path (`links` or `links/3/badge/text` — the EC-443\n * convention on `FIELD_PATH_ATTR`): switch to its tab/pane (+ locale), expand\n * collapsed array items en route, scroll to the deepest available anchor, and\n * ring-highlight it. Deepest existing anchor wins; the top-level field is the\n * degradation floor.\n */\n focusField: (fieldPath: string, localeHint?: string) => void\n}\n\n/**\n * EC-172 / EC-283 / EC-443 — the field-focus/highlight controller. Focusing a\n * field switches to its tab (or opens the Details pane for a sidebar field) and\n * the given locale, then lands on the DEEPEST available anchor along the path\n * (EC-443): nested paths publish a {@link FieldFocusRequest} (auto-expanding\n * collapsed array items via context), retry-resolve the deepest stamped anchor,\n * scroll it into view, and ring-highlight it — degrading prefix by prefix to the\n * top-level field control, never a silent no-op. Shared by the reference\n * deep-link (`focusPath`, `?field=`) and the Issues pane's jump-to-field. Owns\n * the highlight + inspector-pane state and every scroll/clear timer (cleaned up\n * on unmount, and cleared between back-to-back jumps).\n */\nexport function useFieldFocus(input: {\n collection: CollectionDef\n source: CollectionViewSource\n focusPath: string | undefined\n setActiveTab: (tab: string) => void\n}): FieldFocus {\n const { collection, source, focusPath, setActiveTab } = input\n const [highlightedField, setHighlightedField] = useState<string | null>(null)\n const [focusRequest, setFocusRequest] = useState<FieldFocusRequest | null>(null)\n const [inspectorPaneId, setInspectorPaneId] = useState<string>()\n const focusTimersRef = useRef<number[]>([])\n const nonceRef = useRef(0)\n\n const focusField = useCallback(\n (fieldPath: string, localeHint?: string) => {\n const tokens = fieldPath.split('/')\n const fieldName = tokens[0] ?? fieldPath\n const rest = tokens.slice(1)\n const target = focusTarget(collection, fieldName)\n if (!target) return\n // A sidebar-group field renders in the Details pane; any other on its tab.\n if ('paneId' in target) setInspectorPaneId(target.paneId)\n else setActiveTab(target.tab)\n if (localeHint !== undefined && source.localeConfig.locales.includes(localeHint)) {\n setStudioLocale(localeHint)\n }\n // Clear any timers from a prior jump so back-to-back jumps don't fight.\n focusTimersRef.current.forEach((t) => window.clearTimeout(t))\n\n const scrollTopLevelControl = () => {\n // Some controls (relation/asset pickers) don't render the input id —\n // the field label (`htmlFor`) is always present.\n const control =\n document.getElementById(`f-${target.field.name}`) ??\n document.querySelector(`label[for=\"${CSS.escape(`f-${target.field.name}`)}\"]`)\n control?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }\n\n if (rest.length === 0) {\n // Top-level jump: exactly the EC-283 behavior — ring the field row, scroll.\n setFocusRequest(null)\n setHighlightedField(target.field.name)\n const scrollTimer = window.setTimeout(scrollTopLevelControl, 60)\n const clearTimer = window.setTimeout(() => setHighlightedField(null), HIGHLIGHT_CLEAR_MS)\n focusTimersRef.current = [scrollTimer, clearTimer]\n return\n }\n\n // EC-443 nested jump. Publishing the request expands collapsed array items\n // along the path (context-driven, `object-array-field`); each retry then\n // probes for the deepest stamped anchor. Settling: full path found → ring +\n // scroll it (early); last attempt → deepest existing prefix (ring follows\n // the REAL element, not the wished-for one); nothing stamped → the\n // top-level field row is the floor.\n const nonce = ++nonceRef.current\n setHighlightedField(null)\n setFocusRequest({ path: fieldPath, nonce })\n const candidates = anchorCandidates(fieldName, rest)\n const settle = (resolved: string | null) => {\n if (resolved !== null && resolved !== fieldName) {\n setFocusRequest({ path: resolved, nonce })\n document\n .querySelector(fieldAnchorSelector(resolved))\n ?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n } else {\n setFocusRequest(null)\n setHighlightedField(target.field.name)\n scrollTopLevelControl()\n }\n }\n const settled = { done: false }\n const attemptTimers = RESOLVE_ATTEMPT_DELAYS_MS.map((delay, i) =>\n window.setTimeout(() => {\n if (settled.done) return\n const resolved = deepestExistingAnchor(\n candidates,\n (path) => document.querySelector(fieldAnchorSelector(path)) !== null,\n )\n const isLastAttempt = i === RESOLVE_ATTEMPT_DELAYS_MS.length - 1\n if (resolved === fieldPath || isLastAttempt) {\n settled.done = true\n settle(resolved)\n }\n }, delay),\n )\n const clearTimer = window.setTimeout(() => {\n setHighlightedField(null)\n setFocusRequest(null)\n }, HIGHLIGHT_CLEAR_MS)\n focusTimersRef.current = [...attemptTimers, clearTimer]\n },\n [collection, source.localeConfig, setActiveTab],\n )\n useEffect(() => () => focusTimersRef.current.forEach((t) => window.clearTimeout(t)), [])\n\n // Reference deep links (EC-172): `?field=<index path>` pinpoints the exact\n // field — nested tokens ride along (EC-443), so the SAME path-aware focus runs.\n useEffect(() => {\n if (!focusPath) return\n const parsed = parseDocumentFieldPath(focusPath)\n if (!parsed) return\n focusField([parsed.field, ...parsed.rest].join('/'), parsed.locale)\n }, [focusPath, focusField])\n\n return { highlightedField, focusRequest, inspectorPaneId, setInspectorPaneId, focusField }\n}\n"]}
1
+ {"version":3,"file":"use-field-focus.js","sourceRoot":"","sources":["../../../src/components/cms/use-field-focus.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport { SIDEBAR_FIELD_GROUP, fieldTabOf } from '@elytracms/core/cms-core'\nimport type { CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport type { CollectionViewSource } from '../../lib/collection-view/collection-view-source'\nimport { setStudioLocale } from '../../lib/locale'\nimport { parseDocumentFieldPath } from '../../lib/references'\nimport {\n anchorCandidates,\n deepestExistingAnchor,\n fieldAnchorSelector,\n} from './field-anchors'\nimport type { FieldFocusRequest } from './field-anchors'\n\n/**\n * EC-283 / EC-285 — where a jump-to-field lands, as a pure decision. A field in\n * the reserved `sidebar` group renders in the Details inspector pane (open that\n * pane, EC-285); any other field lives on a content tab (switch to it). Returns\n * `null` for an unknown field name (no-op). Directly testable, no DOM.\n */\nexport function focusTarget(\n collection: CollectionDef,\n fieldName: string,\n): { field: FieldDef; paneId: 'details' } | { field: FieldDef; tab: string } | null {\n const field = collection.fields.find((f) => f.name === fieldName)\n if (!field) return null\n if (field.form?.group === SIDEBAR_FIELD_GROUP) return { field, paneId: 'details' }\n // EC-718: the tab the form renders it on — THE rule (cms-core `fieldTabOf`), never a copy.\n return { field, tab: fieldTabOf(field, collection) }\n}\n\n/**\n * EC-443 — nested-anchor resolution retries. Expanding a collapsed array item is\n * React state (context-driven), so each nested array level needs a render cycle\n * before its deeper anchors exist; a short retry ladder lets a multi-level\n * expansion cascade settle before the plan degrades to a shallower anchor.\n */\nconst RESOLVE_ATTEMPT_DELAYS_MS = [60, 200, 450] as const\n/** How long the ring highlight (top-level or nested) stays on. */\nconst HIGHLIGHT_CLEAR_MS = 3200\n\nexport interface FieldFocus {\n /** The top-level field ring-highlighted right now (or null). */\n highlightedField: string | null\n /**\n * The active nested focus request (EC-443): drives collapsed-array expansion\n * and the ring on the exact nested control, via `FieldFocusRequestContext`.\n */\n focusRequest: FieldFocusRequest | null\n /** The inspector sidebar's active pane, controlled so a jump can open Details. */\n inspectorPaneId: string | undefined\n setInspectorPaneId: (id: string | undefined) => void\n /**\n * Focus a field by anchor path (`links` or `links/3/badge/text` — the EC-443\n * convention on `FIELD_PATH_ATTR`): switch to its tab/pane (+ locale), expand\n * collapsed array items en route, scroll to the deepest available anchor, and\n * ring-highlight it. Deepest existing anchor wins; the top-level field is the\n * degradation floor.\n */\n focusField: (fieldPath: string, localeHint?: string) => void\n}\n\n/**\n * EC-172 / EC-283 / EC-443 — the field-focus/highlight controller. Focusing a\n * field switches to its tab (or opens the Details pane for a sidebar field) and\n * the given locale, then lands on the DEEPEST available anchor along the path\n * (EC-443): nested paths publish a {@link FieldFocusRequest} (auto-expanding\n * collapsed array items via context), retry-resolve the deepest stamped anchor,\n * scroll it into view, and ring-highlight it — degrading prefix by prefix to the\n * top-level field control, never a silent no-op. Shared by the reference\n * deep-link (`focusPath`, `?field=`) and the Issues pane's jump-to-field. Owns\n * the highlight + inspector-pane state and every scroll/clear timer (cleaned up\n * on unmount, and cleared between back-to-back jumps).\n */\nexport function useFieldFocus(input: {\n collection: CollectionDef\n source: CollectionViewSource\n focusPath: string | undefined\n setActiveTab: (tab: string) => void\n}): FieldFocus {\n const { collection, source, focusPath, setActiveTab } = input\n const [highlightedField, setHighlightedField] = useState<string | null>(null)\n const [focusRequest, setFocusRequest] = useState<FieldFocusRequest | null>(null)\n const [inspectorPaneId, setInspectorPaneId] = useState<string>()\n const focusTimersRef = useRef<number[]>([])\n const nonceRef = useRef(0)\n\n const focusField = useCallback(\n (fieldPath: string, localeHint?: string) => {\n const tokens = fieldPath.split('/')\n const fieldName = tokens[0] ?? fieldPath\n const rest = tokens.slice(1)\n const target = focusTarget(collection, fieldName)\n if (!target) return\n // A sidebar-group field renders in the Details pane; any other on its tab.\n if ('paneId' in target) setInspectorPaneId(target.paneId)\n else setActiveTab(target.tab)\n if (localeHint !== undefined && source.localeConfig.locales.includes(localeHint)) {\n setStudioLocale(localeHint)\n }\n // Clear any timers from a prior jump so back-to-back jumps don't fight.\n focusTimersRef.current.forEach((t) => window.clearTimeout(t))\n\n const scrollTopLevelControl = () => {\n // Some controls (relation/asset pickers) don't render the input id —\n // the field label (`htmlFor`) is always present.\n const control =\n document.getElementById(`f-${target.field.name}`) ??\n document.querySelector(`label[for=\"${CSS.escape(`f-${target.field.name}`)}\"]`)\n control?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }\n\n if (rest.length === 0) {\n // Top-level jump: exactly the EC-283 behavior — ring the field row, scroll.\n setFocusRequest(null)\n setHighlightedField(target.field.name)\n const scrollTimer = window.setTimeout(scrollTopLevelControl, 60)\n const clearTimer = window.setTimeout(() => setHighlightedField(null), HIGHLIGHT_CLEAR_MS)\n focusTimersRef.current = [scrollTimer, clearTimer]\n return\n }\n\n // EC-443 nested jump. Publishing the request expands collapsed array items\n // along the path (context-driven, `object-array-field`); each retry then\n // probes for the deepest stamped anchor. Settling: full path found → ring +\n // scroll it (early); last attempt → deepest existing prefix (ring follows\n // the REAL element, not the wished-for one); nothing stamped → the\n // top-level field row is the floor.\n const nonce = ++nonceRef.current\n setHighlightedField(null)\n setFocusRequest({ path: fieldPath, nonce })\n const candidates = anchorCandidates(fieldName, rest)\n const settle = (resolved: string | null) => {\n if (resolved !== null && resolved !== fieldName) {\n setFocusRequest({ path: resolved, nonce })\n document\n .querySelector(fieldAnchorSelector(resolved))\n ?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n } else {\n setFocusRequest(null)\n setHighlightedField(target.field.name)\n scrollTopLevelControl()\n }\n }\n const settled = { done: false }\n const attemptTimers = RESOLVE_ATTEMPT_DELAYS_MS.map((delay, i) =>\n window.setTimeout(() => {\n if (settled.done) return\n const resolved = deepestExistingAnchor(\n candidates,\n (path) => document.querySelector(fieldAnchorSelector(path)) !== null,\n )\n const isLastAttempt = i === RESOLVE_ATTEMPT_DELAYS_MS.length - 1\n if (resolved === fieldPath || isLastAttempt) {\n settled.done = true\n settle(resolved)\n }\n }, delay),\n )\n const clearTimer = window.setTimeout(() => {\n setHighlightedField(null)\n setFocusRequest(null)\n }, HIGHLIGHT_CLEAR_MS)\n focusTimersRef.current = [...attemptTimers, clearTimer]\n },\n [collection, source.localeConfig, setActiveTab],\n )\n useEffect(() => () => focusTimersRef.current.forEach((t) => window.clearTimeout(t)), [])\n\n // Reference deep links (EC-172): `?field=<index path>` pinpoints the exact\n // field — nested tokens ride along (EC-443), so the SAME path-aware focus runs.\n useEffect(() => {\n if (!focusPath) return\n const parsed = parseDocumentFieldPath(focusPath)\n if (!parsed) return\n focusField([parsed.field, ...parsed.rest].join('/'), parsed.locale)\n }, [focusPath, focusField])\n\n return { highlightedField, focusRequest, inspectorPaneId, setInspectorPaneId, focusField }\n}\n"]}
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Suspense, lazy } from 'react';
3
3
  import { Text } from '@elytracms/studio/ui';
4
4
  import { translate } from '../../lib/i18n/index.js';
5
+ import { LazySurfaceBoundary } from '../load-failure.js';
5
6
  /**
6
7
  * Lazy boundary for the version-compare surface (EC-322 load-time pass).
7
8
  *
@@ -18,6 +19,6 @@ function CompareFallback() {
18
19
  return (_jsx("div", { className: "grid min-h-0 flex-1 place-items-center p-8", children: _jsx(Text, { size: "sm", tone: "muted", children: translate('cms.version.loadingCompare') }) }));
19
20
  }
20
21
  export function VersionCompare() {
21
- return (_jsx(Suspense, { fallback: _jsx(CompareFallback, {}), children: _jsx(VersionCompareImpl, {}) }));
22
+ return (_jsx(LazySurfaceBoundary, { children: _jsx(Suspense, { fallback: _jsx(CompareFallback, {}), children: _jsx(VersionCompareImpl, {}) }) }));
22
23
  }
23
24
  //# sourceMappingURL=version-compare-lazy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-compare-lazy.js","sourceRoot":"","sources":["../../../src/components/cms/version-compare-lazy.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE1C;;;;;;;;;;GAUG;AACH,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CACnC,MAAM,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CACnF,CAAA;AAED,SAAS,eAAe;IACtB,OAAO,CACL,cAAK,SAAS,EAAC,4CAA4C,YACzD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,4BAA4B,CAAC,GACnC,GACH,CACP,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,CACL,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,eAAe,KAAG,YACrC,KAAC,kBAAkB,KAAG,GACb,CACZ,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy } from 'react'\nimport { Text } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\n\n/**\n * Lazy boundary for the version-compare surface (EC-322 load-time pass).\n *\n * The compare view is a large, genuinely occasional surface — it only renders on\n * the detail editor's Compare tab. Importing it statically kept its whole diff UI\n * on the CMS detail-view critical path for every document, most of which are never\n * compared. Splitting it behind {@link lazy} defers it to the moment the tab opens.\n *\n * The shared `@elytracms/core/content` diff engine it uses stays eager (many other\n * critical-path modules import it), so what defers here is the compare UI itself.\n */\nconst VersionCompareImpl = lazy(() =>\n import('./version-compare').then((module) => ({ default: module.VersionCompare })),\n)\n\nfunction CompareFallback() {\n return (\n <div className=\"grid min-h-0 flex-1 place-items-center p-8\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.version.loadingCompare')}\n </Text>\n </div>\n )\n}\n\nexport function VersionCompare() {\n return (\n <Suspense fallback={<CompareFallback />}>\n <VersionCompareImpl />\n </Suspense>\n )\n}\n"]}
1
+ {"version":3,"file":"version-compare-lazy.js","sourceRoot":"","sources":["../../../src/components/cms/version-compare-lazy.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD;;;;;;;;;;GAUG;AACH,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CACnC,MAAM,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CACnF,CAAA;AAED,SAAS,eAAe;IACtB,OAAO,CACL,cAAK,SAAS,EAAC,4CAA4C,YACzD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,4BAA4B,CAAC,GACnC,GACH,CACP,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,CACL,KAAC,mBAAmB,cAClB,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAC,eAAe,KAAG,YACrC,KAAC,kBAAkB,KAAG,GACb,GACS,CACvB,CAAA;AACH,CAAC","sourcesContent":["import { Suspense, lazy } from 'react'\nimport { Text } from '@elytracms/studio/ui'\nimport { translate } from '../../lib/i18n'\nimport { LazySurfaceBoundary } from '../load-failure'\n\n/**\n * Lazy boundary for the version-compare surface (EC-322 load-time pass).\n *\n * The compare view is a large, genuinely occasional surface — it only renders on\n * the detail editor's Compare tab. Importing it statically kept its whole diff UI\n * on the CMS detail-view critical path for every document, most of which are never\n * compared. Splitting it behind {@link lazy} defers it to the moment the tab opens.\n *\n * The shared `@elytracms/core/content` diff engine it uses stays eager (many other\n * critical-path modules import it), so what defers here is the compare UI itself.\n */\nconst VersionCompareImpl = lazy(() =>\n import('./version-compare').then((module) => ({ default: module.VersionCompare })),\n)\n\nfunction CompareFallback() {\n return (\n <div className=\"grid min-h-0 flex-1 place-items-center p-8\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('cms.version.loadingCompare')}\n </Text>\n </div>\n )\n}\n\nexport function VersionCompare() {\n return (\n <LazySurfaceBoundary>\n <Suspense fallback={<CompareFallback />}>\n <VersionCompareImpl />\n </Suspense>\n </LazySurfaceBoundary>\n )\n}\n"]}
@@ -20,7 +20,7 @@ export function ComponentsIndex({ projectId }) {
20
20
  return (_jsxs("div", { className: "mx-auto w-full max-w-4xl p-4 sm:p-6", children: [_jsx("div", { className: "mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden", children: _jsx(RegistryBrowser, { projectId: projectId }) }), _jsxs(Stack, { gap: "6", children: [_jsxs(Stack, { gap: "1", children: [_jsx(Heading, { size: "display", children: translate('components.index.title') }), _jsx(Text, { tone: "muted", children: translate('components.index.subtitle') })] }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsx(StatCard, { label: translate('components.index.stat.total'), value: stats.total }), _jsx(StatCard, { label: translate('components.index.stat.valid'), value: health.valid, tone: "success" }), _jsx(StatCard, { label: translate('components.index.stat.needAttention'), value: health.incompatible + health.invalid, tone: health.incompatible + health.invalid > 0 ? 'warning' : 'muted', hint: translate('components.index.stat.needAttentionHint', {
21
21
  incompatible: health.incompatible,
22
22
  invalid: health.invalid,
23
- }) })] }), stats.categories.length > 0 ? (_jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(Layers, { className: "size-4 text-muted-foreground" }), _jsx(Text, { size: "sm", weight: "semibold", children: translate('components.index.byCategory') })] }), _jsx(Row, { gap: "2", wrap: true, className: "p-4", children: stats.categories.map((category) => (_jsxs(Badge, { variant: "outline", children: [_jsx("span", { className: category.key ? 'capitalize' : undefined, children: category.key || translate('components.taxonomy.uncategorized') }), _jsx("span", { className: "font-mono text-muted-foreground", children: category.count })] }, category.key || '__uncategorized__'))) })] })) : null, _jsxs(Card, { className: "flex items-center gap-3 p-4", children: [_jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground", children: _jsx(MousePointerClick, { className: "size-4" }) }), _jsx(Text, { size: "sm", tone: "muted", children: translate('components.index.pickHint') })] }), _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(BookOpen, { className: "size-4 text-muted-foreground" }), _jsx(Text, { size: "sm", weight: "semibold", children: translate('components.index.guide.title') }), _jsx(Spacer, {}), _jsx(Badge, { variant: "outline", children: translate('components.index.guide.badge') })] }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(Text, { size: "sm", tone: "muted", children: translate('components.index.guide.intro') }), _jsxs(Stack, { gap: "2", children: [_jsx(DocPointer, { path: "docs/component-authoring.md", description: translate('components.index.guide.doc.authoring') }), _jsx(DocPointer, { path: "docs/fixtures.md", description: translate('components.index.guide.doc.fixtures') }), _jsx(DocPointer, { path: "docs/extensibility.md", description: translate('components.index.guide.doc.extensibility') })] }), _jsxs(Text, { size: "xs", tone: "subtle", children: [_jsx(Sparkles, { className: "mr-1 inline size-3", "aria-hidden": true }), translate('components.index.guide.namespaceNote')] })] })] })] })] }));
23
+ }) })] }), stats.categories.length > 0 ? (_jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(Layers, { className: "size-4 text-muted-foreground" }), _jsx(Text, { size: "sm", weight: "semibold", children: translate('components.index.byCategory') })] }), _jsx(Row, { gap: "2", wrap: true, className: "p-4", children: stats.categories.map((category) => (_jsxs(Badge, { variant: "outline", children: [_jsx("span", { className: category.key ? 'capitalize' : undefined, children: category.key || translate('components.taxonomy.uncategorized') }), _jsx("span", { className: "font-mono text-muted-foreground", children: category.count })] }, category.key || '__uncategorized__'))) })] })) : null, _jsxs(Card, { className: "flex items-center gap-3 p-4", children: [_jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground", children: _jsx(MousePointerClick, { className: "size-4" }) }), _jsx(Text, { size: "sm", tone: "muted", children: translate('components.index.pickHint') })] }), _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(BookOpen, { className: "size-4 text-muted-foreground" }), _jsx(Text, { size: "sm", weight: "semibold", children: translate('components.index.guide.title') }), _jsx(Spacer, {}), _jsx(Badge, { variant: "outline", children: translate('components.index.guide.badge') })] }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(Text, { size: "sm", tone: "muted", children: translate('components.index.guide.intro') }), _jsxs(Stack, { gap: "2", children: [_jsx(DocPointer, { path: "docs/component-authoring.md", description: translate('components.index.guide.doc.authoring') }), _jsx(DocPointer, { path: "docs/guide/03-components.md", description: translate('components.index.guide.doc.components') }), _jsx(DocPointer, { path: "docs/guide/00-overview.md", description: translate('components.index.guide.doc.guide') })] }), _jsxs(Text, { size: "xs", tone: "subtle", children: [_jsx(Sparkles, { className: "mr-1 inline size-3", "aria-hidden": true }), translate('components.index.guide.namespaceNote')] })] })] })] })] }));
24
24
  }
25
25
  function DocPointer({ path, description }) {
26
26
  return (_jsxs(Row, { gap: "3", align: "center", className: "rounded-md border border-border p-2.5", children: [_jsx(Code, { tone: "primary", className: "shrink-0", children: path }), _jsx(Text, { size: "xs", tone: "muted", children: description })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"components-index.js","sourceRoot":"","sources":["../../../src/components/components/components-index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { BookOpen, Layers, MousePointerClick, Sparkles } from 'lucide-react'\nimport { getStudioRegistry, registryHealth } from '../../lib/registry'\nimport { translate } from '../../lib/i18n'\nimport { RegistryBrowser } from './registry-browser'\nimport { deriveComponentStats } from './taxonomy'\nimport { Badge, Card, Code, Heading, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\n\n/**\n * The Components section landing view (EC-050, overhauled EC-320). Serves BOTH\n * audiences: editors get a live census of what components exist (totals,\n * per-category breakdown, health) and a \"pick one\" nudge;\n * devs get pointers to the real in-repo docs for registering and extending\n * components — no code sample to drift, no invented docs.\n */\nexport function ComponentsIndex({ projectId }: { projectId: string }) {\n const studio = getStudioRegistry()\n const manifests = studio.registry.list()\n const stats = deriveComponentStats(manifests)\n const health = registryHealth(studio)\n\n return (\n <div className=\"mx-auto w-full max-w-4xl p-4 sm:p-6\">\n {/* Mobile-only browser (the sidebar is hidden under md). */}\n <div className=\"mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden\">\n <RegistryBrowser projectId={projectId} />\n </div>\n\n <Stack gap=\"6\">\n <Stack gap=\"1\">\n <Heading size=\"display\">{translate('components.index.title')}</Heading>\n <Text tone=\"muted\">{translate('components.index.subtitle')}</Text>\n </Stack>\n\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <StatCard label={translate('components.index.stat.total')} value={stats.total} />\n <StatCard\n label={translate('components.index.stat.valid')}\n value={health.valid}\n tone=\"success\"\n />\n <StatCard\n label={translate('components.index.stat.needAttention')}\n value={health.incompatible + health.invalid}\n tone={health.incompatible + health.invalid > 0 ? 'warning' : 'muted'}\n hint={translate('components.index.stat.needAttentionHint', {\n incompatible: health.incompatible,\n invalid: health.invalid,\n })}\n />\n </div>\n\n {/* By category — a cheap census derived from manifest `category`. */}\n {stats.categories.length > 0 ? (\n <Panel>\n <PanelHeader>\n <Layers className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.byCategory')}\n </Text>\n </PanelHeader>\n <Row gap=\"2\" wrap className=\"p-4\">\n {stats.categories.map((category) => (\n <Badge key={category.key || '__uncategorized__'} variant=\"outline\">\n <span className={category.key ? 'capitalize' : undefined}>\n {category.key || translate('components.taxonomy.uncategorized')}\n </span>\n <span className=\"font-mono text-muted-foreground\">{category.count}</span>\n </Badge>\n ))}\n </Row>\n </Panel>\n ) : null}\n\n <Card className=\"flex items-center gap-3 p-4\">\n <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground\">\n <MousePointerClick className=\"size-4\" />\n </span>\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.pickHint')}\n </Text>\n </Card>\n\n {/* Dev guidance — pointers to the REAL in-repo docs (no code sample to drift). */}\n <Panel>\n <PanelHeader>\n <BookOpen className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.guide.title')}\n </Text>\n <Spacer />\n <Badge variant=\"outline\">{translate('components.index.guide.badge')}</Badge>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.guide.intro')}\n </Text>\n <Stack gap=\"2\">\n <DocPointer\n path=\"docs/component-authoring.md\"\n description={translate('components.index.guide.doc.authoring')}\n />\n <DocPointer\n path=\"docs/fixtures.md\"\n description={translate('components.index.guide.doc.fixtures')}\n />\n <DocPointer\n path=\"docs/extensibility.md\"\n description={translate('components.index.guide.doc.extensibility')}\n />\n </Stack>\n <Text size=\"xs\" tone=\"subtle\">\n <Sparkles className=\"mr-1 inline size-3\" aria-hidden />\n {translate('components.index.guide.namespaceNote')}\n </Text>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction DocPointer({ path, description }: { path: string; description: string }) {\n return (\n <Row gap=\"3\" align=\"center\" className=\"rounded-md border border-border p-2.5\">\n <Code tone=\"primary\" className=\"shrink-0\">\n {path}\n </Code>\n <Text size=\"xs\" tone=\"muted\">\n {description}\n </Text>\n </Row>\n )\n}\n\nfunction StatCard({\n label,\n value,\n hint,\n tone = 'default',\n}: {\n label: string\n value: number\n hint?: string\n tone?: 'default' | 'success' | 'warning' | 'muted'\n}) {\n const valueClass =\n tone === 'success'\n ? 'text-success'\n : tone === 'warning'\n ? 'text-warning'\n : tone === 'muted'\n ? 'text-muted-foreground'\n : 'text-foreground'\n return (\n <Card className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\" mono>\n {label.toUpperCase()}\n </Text>\n <div className={`mt-1 text-2xl font-semibold tracking-tight ${valueClass}`}>{value}</div>\n {hint ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-0.5\">\n {hint}\n </Text>\n ) : null}\n </Card>\n )\n}\n"]}
1
+ {"version":3,"file":"components-index.js","sourceRoot":"","sources":["../../../src/components/components/components-index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { BookOpen, Layers, MousePointerClick, Sparkles } from 'lucide-react'\nimport { getStudioRegistry, registryHealth } from '../../lib/registry'\nimport { translate } from '../../lib/i18n'\nimport { RegistryBrowser } from './registry-browser'\nimport { deriveComponentStats } from './taxonomy'\nimport { Badge, Card, Code, Heading, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\n\n/**\n * The Components section landing view (EC-050, overhauled EC-320). Serves BOTH\n * audiences: editors get a live census of what components exist (totals,\n * per-category breakdown, health) and a \"pick one\" nudge;\n * devs get pointers to the real in-repo docs for registering and extending\n * components — no code sample to drift, no invented docs.\n */\nexport function ComponentsIndex({ projectId }: { projectId: string }) {\n const studio = getStudioRegistry()\n const manifests = studio.registry.list()\n const stats = deriveComponentStats(manifests)\n const health = registryHealth(studio)\n\n return (\n <div className=\"mx-auto w-full max-w-4xl p-4 sm:p-6\">\n {/* Mobile-only browser (the sidebar is hidden under md). */}\n <div className=\"mb-6 h-80 overflow-hidden rounded-lg border border-border md:hidden\">\n <RegistryBrowser projectId={projectId} />\n </div>\n\n <Stack gap=\"6\">\n <Stack gap=\"1\">\n <Heading size=\"display\">{translate('components.index.title')}</Heading>\n <Text tone=\"muted\">{translate('components.index.subtitle')}</Text>\n </Stack>\n\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <StatCard label={translate('components.index.stat.total')} value={stats.total} />\n <StatCard\n label={translate('components.index.stat.valid')}\n value={health.valid}\n tone=\"success\"\n />\n <StatCard\n label={translate('components.index.stat.needAttention')}\n value={health.incompatible + health.invalid}\n tone={health.incompatible + health.invalid > 0 ? 'warning' : 'muted'}\n hint={translate('components.index.stat.needAttentionHint', {\n incompatible: health.incompatible,\n invalid: health.invalid,\n })}\n />\n </div>\n\n {/* By category — a cheap census derived from manifest `category`. */}\n {stats.categories.length > 0 ? (\n <Panel>\n <PanelHeader>\n <Layers className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.byCategory')}\n </Text>\n </PanelHeader>\n <Row gap=\"2\" wrap className=\"p-4\">\n {stats.categories.map((category) => (\n <Badge key={category.key || '__uncategorized__'} variant=\"outline\">\n <span className={category.key ? 'capitalize' : undefined}>\n {category.key || translate('components.taxonomy.uncategorized')}\n </span>\n <span className=\"font-mono text-muted-foreground\">{category.count}</span>\n </Badge>\n ))}\n </Row>\n </Panel>\n ) : null}\n\n <Card className=\"flex items-center gap-3 p-4\">\n <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-muted-foreground\">\n <MousePointerClick className=\"size-4\" />\n </span>\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.pickHint')}\n </Text>\n </Card>\n\n {/* Dev guidance — pointers to the REAL docs (no code sample to drift). They live in the\n engine repository's docs/, never in the consumer's repo (EC-723: the rail pointed at a\n HISTORICAL doc and a missing one). */}\n <Panel>\n <PanelHeader>\n <BookOpen className=\"size-4 text-muted-foreground\" />\n <Text size=\"sm\" weight=\"semibold\">\n {translate('components.index.guide.title')}\n </Text>\n <Spacer />\n <Badge variant=\"outline\">{translate('components.index.guide.badge')}</Badge>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('components.index.guide.intro')}\n </Text>\n <Stack gap=\"2\">\n <DocPointer\n path=\"docs/component-authoring.md\"\n description={translate('components.index.guide.doc.authoring')}\n />\n <DocPointer\n path=\"docs/guide/03-components.md\"\n description={translate('components.index.guide.doc.components')}\n />\n <DocPointer\n path=\"docs/guide/00-overview.md\"\n description={translate('components.index.guide.doc.guide')}\n />\n </Stack>\n <Text size=\"xs\" tone=\"subtle\">\n <Sparkles className=\"mr-1 inline size-3\" aria-hidden />\n {translate('components.index.guide.namespaceNote')}\n </Text>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction DocPointer({ path, description }: { path: string; description: string }) {\n return (\n <Row gap=\"3\" align=\"center\" className=\"rounded-md border border-border p-2.5\">\n <Code tone=\"primary\" className=\"shrink-0\">\n {path}\n </Code>\n <Text size=\"xs\" tone=\"muted\">\n {description}\n </Text>\n </Row>\n )\n}\n\nfunction StatCard({\n label,\n value,\n hint,\n tone = 'default',\n}: {\n label: string\n value: number\n hint?: string\n tone?: 'default' | 'success' | 'warning' | 'muted'\n}) {\n const valueClass =\n tone === 'success'\n ? 'text-success'\n : tone === 'warning'\n ? 'text-warning'\n : tone === 'muted'\n ? 'text-muted-foreground'\n : 'text-foreground'\n return (\n <Card className=\"p-4\">\n <Text size=\"xs\" tone=\"muted\" mono>\n {label.toUpperCase()}\n </Text>\n <div className={`mt-1 text-2xl font-semibold tracking-tight ${valueClass}`}>{value}</div>\n {hint ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"mt-0.5\">\n {hint}\n </Text>\n ) : null}\n </Card>\n )\n}\n"]}
@@ -7,7 +7,7 @@ import type { DeploymentReadinessReport } from '@elytracms/core/export-sync/depl
7
7
  * `checkDeploymentReady` contract. It renders ONE deterministic "Deployment setup"
8
8
  * screen listing each failing precondition with its copy-pasteable fix, instead of
9
9
  * letting the app boot into a misleading downstream error (most painfully the
10
- * Konditorei JWT-key footgun). All checks passing → children render untouched.
10
+ * first real-site migration's JWT-key footgun). All checks passing → children render untouched.
11
11
  *
12
12
  * Runs before auth and any adapter is constructed (it is the thing that keeps
13
13
  * `selectAuthAdapter`/`selectPersistenceAdapter` from throwing on a misconfigured
@@ -13,7 +13,7 @@ import { probeDeployedFunctionsVersion } from '@elytracms/core/export-sync/deplo
13
13
  * `checkDeploymentReady` contract. It renders ONE deterministic "Deployment setup"
14
14
  * screen listing each failing precondition with its copy-pasteable fix, instead of
15
15
  * letting the app boot into a misleading downstream error (most painfully the
16
- * Konditorei JWT-key footgun). All checks passing → children render untouched.
16
+ * first real-site migration's JWT-key footgun). All checks passing → children render untouched.
17
17
  *
18
18
  * Runs before auth and any adapter is constructed (it is the thing that keeps
19
19
  * `selectAuthAdapter`/`selectPersistenceAdapter` from throwing on a misconfigured
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-setup-gate.js","sourceRoot":"","sources":["../../src/components/deployment-setup-gate.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Loader2 } from 'lucide-react'\nimport { Button, Card, CardContent, CardHeader, CardTitle, Code, Stack, Text } from '@elytracms/studio/ui'\nimport {\n resolveBackendConfig,\n selectPersistenceAdapter,\n studioDeploymentReadiness,\n} from '../lib/persistence'\nimport {\n getWorkspaceState,\n resolveDeploymentAi,\n resolveDeploymentCollections,\n resolveDeploymentUrl,\n} from '../lib/workspace'\nimport { translate, useHydrated, useT } from '../lib/i18n'\nimport { probeDeployedFunctionsVersion } from '@elytracms/core/export-sync/deployment-ready'\nimport type {\n DeploymentReadinessReport,\n FunctionsVersionProbe,\n ReadinessCheck,\n} from '@elytracms/core/export-sync/deployment-ready'\n\n/**\n * Deployment-setup boot gate (EC-258) — the single readiness surface that\n * supersedes the separate `BackendGate` (missing `VITE_CONVEX_URL` / unreachable)\n * and `WorkspaceGate` (invalid config) screens by composing the shared\n * `checkDeploymentReady` contract. It renders ONE deterministic \"Deployment setup\"\n * screen listing each failing precondition with its copy-pasteable fix, instead of\n * letting the app boot into a misleading downstream error (most painfully the\n * Konditorei JWT-key footgun). All checks passing → children render untouched.\n *\n * Runs before auth and any adapter is constructed (it is the thing that keeps\n * `selectAuthAdapter`/`selectPersistenceAdapter` from throwing on a misconfigured\n * backend). Convex mode probes reachability once on the client (`adapter.status()`)\n * with a retry; playground and unconfigured resolve synchronously. SSR renders the\n * deterministic \"checking\" state. Auth-signing-keys is reported as `unknown` at boot\n * (the JWKS endpoint is CORS-blocked from the browser — EC-257's honest sign-in error\n * is the browser's actionable surface; `elytra doctor` probes it from node), and the\n * per-user bucket stays `unknown` (this gate runs pre-auth), so neither ever blocks boot.\n */\nexport function DeploymentSetupGate({ children }: { children: ReactNode }) {\n const config = useMemo(() => resolveBackendConfig(), [])\n // EC-399: a configured (and not disabled) `defineStudio({ ai })` block means the\n // AI route exists — its provider-key expectation joins the deployment-env checks.\n const aiRouteConfigured = useMemo(() => {\n const ai = resolveDeploymentAi()\n return Boolean(ai && ai.enabled !== false)\n }, [])\n\n const staticReport = useCallback(\n (): DeploymentReadinessReport =>\n studioDeploymentReadiness({\n backend: config,\n workspace: getWorkspaceState(),\n collections: resolveDeploymentCollections(),\n aiRouteConfigured,\n }),\n [config, aiRouteConfigured],\n )\n\n const [state, setState] = useState<GateState>(() =>\n config.kind === 'convex' ? { phase: 'checking' } : { phase: 'resolved', report: staticReport() },\n )\n\n const check = useCallback(() => {\n if (config.kind !== 'convex') {\n setState({ phase: 'resolved', report: staticReport() })\n return\n }\n const effectiveUrl = resolveDeploymentUrl(config.url)\n const resolve = (\n reachable: { available: boolean; detail?: string },\n functionsVersion: FunctionsVersionProbe,\n ) =>\n setState({\n phase: 'resolved',\n report: studioDeploymentReadiness({\n backend: config,\n effectiveUrl,\n workspace: getWorkspaceState(),\n collections: resolveDeploymentCollections(),\n reachable,\n functionsVersion,\n aiRouteConfigured,\n }),\n })\n setState({ phase: 'checking' })\n // EC-552: the version handshake probes in parallel with the reachability check —\n // same deployment URL, one extra public query. The probe never rejects (network\n // failures resolve to an honest `unknown`), so awaiting it in both paths is safe.\n const versionProbe = probeDeployedFunctionsVersion(effectiveUrl)\n void selectPersistenceAdapter()\n .then((adapter) => adapter.status())\n .then(async (status) =>\n resolve(\n { available: status.available, ...(status.detail ? { detail: status.detail } : {}) },\n await versionProbe,\n ),\n )\n .catch(async (error: unknown) =>\n resolve(\n { available: false, detail: error instanceof Error ? error.message : String(error) },\n await versionProbe,\n ),\n )\n }, [config, staticReport, aiRouteConfigured])\n\n useEffect(() => {\n if (config.kind === 'convex') check()\n }, [config.kind, check])\n\n if (state.phase === 'checking') return <DeploymentChecking />\n if (blockingFailures(state.report).length > 0) {\n return (\n <DeploymentSetupScreen\n report={state.report}\n onRetry={config.kind === 'convex' ? check : undefined}\n />\n )\n }\n return <>{children}</>\n}\n\ntype GateState = { phase: 'checking' } | { phase: 'resolved'; report: DeploymentReadinessReport }\n\n/**\n * The checks that BLOCK boot: a failed deployment-, deployment-env-, or app-level\n * precondition. The per-user bucket (\"not a member\") is not a boot blocker — it is\n * surfaced post-auth (\"ask an admin\"), and at boot it is `unknown` (pre-auth) anyway.\n * Deployment-env checks are `unknown` in the browser today (nothing gathered — EC-399),\n * so they never block in practice; `warn` findings never block anywhere.\n */\nfunction blockingFailures(report: DeploymentReadinessReport): ReadinessCheck[] {\n return report.checks.filter(\n (c) =>\n c.status === 'fail' &&\n (c.bucket === 'deployment' || c.bucket === 'deployment-env' || c.bucket === 'app'),\n )\n}\n\n/**\n * Deterministic boot state while the backend probe is in flight — and, in a\n * CONNECTED build, the body of the prerendered shell itself.\n *\n * **EC-525.** EC-523 removed the last localized string from the shell and proved it\n * against the built artefact — but against the *playground* build, which is the demo's\n * default and the one nobody deploys. Every real studio is connected, so\n * `config.kind === 'convex'` seeds `{ phase: 'checking' }` and *this* is what a\n * production cold load actually bakes: `Checking deployment…`, in the build machine's\n * locale, shown to every visitor whatever theirs is. The class was not closed until\n * both builds satisfied it.\n *\n * Same shape as `SessionLoading` (`router/root.tsx`), for the same reasons, which are\n * written out there: a build-time prerender has no request and therefore no locale, so\n * the shell shows a spinner and an EMPTY `role=\"status\"` region, and `useHydrated`\n * fills it in the visitor's real language once hydration lands. No `aria-label` — that\n * bakes the wrong-language string somewhere a screen-reader user cannot see it flip —\n * and no `aria-busy`, which would suppress the announcement this shape exists to make.\n *\n * Exported so the connected shell's pre-hydration markup is pinnable with\n * `renderToStaticMarkup` — same reason `SessionLoading` is. The built artefact is the\n * real proof; the pin is what fails when someone puts a string back.\n */\nexport function DeploymentChecking() {\n const hydrated = useHydrated()\n const t = useT()\n return (\n <BootScreenFrame>\n <div role=\"status\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" aria-hidden=\"true\" />\n {hydrated ? <span className=\"sr-only\">{t('shell.deploymentSetup.checking')}</span> : null}\n </div>\n </BootScreenFrame>\n )\n}\n\n/**\n * The unified \"Deployment setup\" screen (EC-258): every failing check + its exact\n * fix. Pure/presentational so it is fixture-reproducible and static-render testable.\n */\nexport function DeploymentSetupScreen({\n report,\n onRetry,\n}: {\n report: DeploymentReadinessReport\n onRetry?: () => void\n}) {\n const failures = report.checks.filter((c) => c.status === 'fail')\n const envChecks = report.checks.filter((c) => c.bucket === 'deployment-env')\n return (\n <BootScreenFrame>\n <Card className=\"max-w-2xl border-destructive/40\">\n <CardHeader>\n <CardTitle>{translate('shell.deploymentSetup.title')}</CardTitle>\n </CardHeader>\n <CardContent>\n <Stack gap=\"3\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('shell.deploymentSetup.description')}\n </Text>\n <Stack gap=\"2\" role=\"list\" aria-label={translate('shell.deploymentSetup.issuesLabel')}>\n {failures.map((check) => (\n <CheckRow key={check.id} check={check} />\n ))}\n </Stack>\n {envChecks.length > 0 ? <ExpectedEnvSection checks={envChecks} /> : null}\n {onRetry ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} className=\"self-start\">\n {translate('shell.deploymentSetup.retry')}\n </Button>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n </BootScreenFrame>\n )\n}\n\n/**\n * The expected-env facts (EC-399) — the SAME contract `elytra doctor` checks, so the\n * studio and CLI tell one story. Presence only: each row is a var name + set/missing/\n * unknown status text; values never exist in the report. In the browser the presence\n * sources aren't readable pre-auth, so rows typically read `unknown` and point at\n * `elytra doctor` — which is exactly the honest answer.\n */\nfunction ExpectedEnvSection({ checks }: { checks: ReadinessCheck[] }) {\n return (\n <Stack gap=\"2\">\n <Text size=\"sm\" className=\"font-medium\">\n {translate('shell.deploymentSetup.envSection')}\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {translate('shell.deploymentSetup.envSectionHint')}\n </Text>\n <Stack gap=\"1\" role=\"list\" aria-label={translate('shell.deploymentSetup.envSection')}>\n {checks.map((check) => (\n <EnvCheckRow key={check.id} check={check} />\n ))}\n </Stack>\n </Stack>\n )\n}\n\nconst ENV_STATUS_GLYPH: Record<ReadinessCheck['status'], string> = {\n ok: '✓',\n warn: '!',\n fail: '✗',\n unknown: '?',\n}\n\nfunction EnvCheckRow({ check }: { check: ReadinessCheck }) {\n const failing = check.status === 'fail'\n return (\n <div\n role=\"listitem\"\n className={`rounded-md border p-2 ${failing ? 'border-destructive/30 bg-destructive/5' : 'border-border bg-muted/30'}`}\n >\n <Text size=\"xs\" mono className={failing ? 'text-destructive' : undefined}>\n {ENV_STATUS_GLYPH[check.status]} [{check.id}]\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {check.detail}\n </Text>\n {check.fix && (check.status === 'fail' || check.status === 'warn') ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mt-1\">\n {translate('shell.deploymentSetup.fixLabel')}: <Code className=\"break-all\">{check.fix}</Code>\n </Text>\n ) : null}\n </div>\n )\n}\n\nfunction CheckRow({ check }: { check: ReadinessCheck }) {\n return (\n <div role=\"listitem\" className=\"rounded-md border border-destructive/30 bg-destructive/5 p-2\">\n <Text size=\"xs\" mono className=\"text-destructive\">\n [{check.id}]\n </Text>\n <Text size=\"sm\">{check.detail}</Text>\n {check.issues && check.issues.length > 0 ? <CheckIssueList issues={check.issues} /> : null}\n {check.fix ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mt-1\">\n {translate('shell.deploymentSetup.fixLabel')}: <Code className=\"break-all\">{check.fix}</Code>\n </Text>\n ) : null}\n </div>\n )\n}\n\n/**\n * EC-626 — the located issues behind a failing check, one line each, exactly as\n * `elytra doctor` prints them (core's `formatConfigIssue`, applied in the shared\n * contract): `config-valid` lists every config issue instead of a count. English, like\n * the check's `detail` — they name keys and paths in the developer's own config.\n */\nfunction CheckIssueList({ issues }: { issues: readonly string[] }) {\n return (\n <Stack gap=\"1\" role=\"list\" aria-label={translate('shell.workspaceGate.issuesLabel')} className=\"mt-1\">\n {issues.map((issue, index) => (\n <Text key={index} role=\"listitem\" size=\"xs\" mono className=\"break-words\">\n {issue}\n </Text>\n ))}\n </Stack>\n )\n}\n\nfunction BootScreenFrame({ children }: { children: ReactNode }) {\n return <div className=\"grid min-h-dvh place-items-center bg-background p-6\">{children}</div>\n}\n"]}
1
+ {"version":3,"file":"deployment-setup-gate.js","sourceRoot":"","sources":["../../src/components/deployment-setup-gate.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Loader2 } from 'lucide-react'\nimport { Button, Card, CardContent, CardHeader, CardTitle, Code, Stack, Text } from '@elytracms/studio/ui'\nimport {\n resolveBackendConfig,\n selectPersistenceAdapter,\n studioDeploymentReadiness,\n} from '../lib/persistence'\nimport {\n getWorkspaceState,\n resolveDeploymentAi,\n resolveDeploymentCollections,\n resolveDeploymentUrl,\n} from '../lib/workspace'\nimport { translate, useHydrated, useT } from '../lib/i18n'\nimport { probeDeployedFunctionsVersion } from '@elytracms/core/export-sync/deployment-ready'\nimport type {\n DeploymentReadinessReport,\n FunctionsVersionProbe,\n ReadinessCheck,\n} from '@elytracms/core/export-sync/deployment-ready'\n\n/**\n * Deployment-setup boot gate (EC-258) — the single readiness surface that\n * supersedes the separate `BackendGate` (missing `VITE_CONVEX_URL` / unreachable)\n * and `WorkspaceGate` (invalid config) screens by composing the shared\n * `checkDeploymentReady` contract. It renders ONE deterministic \"Deployment setup\"\n * screen listing each failing precondition with its copy-pasteable fix, instead of\n * letting the app boot into a misleading downstream error (most painfully the\n * first real-site migration's JWT-key footgun). All checks passing → children render untouched.\n *\n * Runs before auth and any adapter is constructed (it is the thing that keeps\n * `selectAuthAdapter`/`selectPersistenceAdapter` from throwing on a misconfigured\n * backend). Convex mode probes reachability once on the client (`adapter.status()`)\n * with a retry; playground and unconfigured resolve synchronously. SSR renders the\n * deterministic \"checking\" state. Auth-signing-keys is reported as `unknown` at boot\n * (the JWKS endpoint is CORS-blocked from the browser — EC-257's honest sign-in error\n * is the browser's actionable surface; `elytra doctor` probes it from node), and the\n * per-user bucket stays `unknown` (this gate runs pre-auth), so neither ever blocks boot.\n */\nexport function DeploymentSetupGate({ children }: { children: ReactNode }) {\n const config = useMemo(() => resolveBackendConfig(), [])\n // EC-399: a configured (and not disabled) `defineStudio({ ai })` block means the\n // AI route exists — its provider-key expectation joins the deployment-env checks.\n const aiRouteConfigured = useMemo(() => {\n const ai = resolveDeploymentAi()\n return Boolean(ai && ai.enabled !== false)\n }, [])\n\n const staticReport = useCallback(\n (): DeploymentReadinessReport =>\n studioDeploymentReadiness({\n backend: config,\n workspace: getWorkspaceState(),\n collections: resolveDeploymentCollections(),\n aiRouteConfigured,\n }),\n [config, aiRouteConfigured],\n )\n\n const [state, setState] = useState<GateState>(() =>\n config.kind === 'convex' ? { phase: 'checking' } : { phase: 'resolved', report: staticReport() },\n )\n\n const check = useCallback(() => {\n if (config.kind !== 'convex') {\n setState({ phase: 'resolved', report: staticReport() })\n return\n }\n const effectiveUrl = resolveDeploymentUrl(config.url)\n const resolve = (\n reachable: { available: boolean; detail?: string },\n functionsVersion: FunctionsVersionProbe,\n ) =>\n setState({\n phase: 'resolved',\n report: studioDeploymentReadiness({\n backend: config,\n effectiveUrl,\n workspace: getWorkspaceState(),\n collections: resolveDeploymentCollections(),\n reachable,\n functionsVersion,\n aiRouteConfigured,\n }),\n })\n setState({ phase: 'checking' })\n // EC-552: the version handshake probes in parallel with the reachability check —\n // same deployment URL, one extra public query. The probe never rejects (network\n // failures resolve to an honest `unknown`), so awaiting it in both paths is safe.\n const versionProbe = probeDeployedFunctionsVersion(effectiveUrl)\n void selectPersistenceAdapter()\n .then((adapter) => adapter.status())\n .then(async (status) =>\n resolve(\n { available: status.available, ...(status.detail ? { detail: status.detail } : {}) },\n await versionProbe,\n ),\n )\n .catch(async (error: unknown) =>\n resolve(\n { available: false, detail: error instanceof Error ? error.message : String(error) },\n await versionProbe,\n ),\n )\n }, [config, staticReport, aiRouteConfigured])\n\n useEffect(() => {\n if (config.kind === 'convex') check()\n }, [config.kind, check])\n\n if (state.phase === 'checking') return <DeploymentChecking />\n if (blockingFailures(state.report).length > 0) {\n return (\n <DeploymentSetupScreen\n report={state.report}\n onRetry={config.kind === 'convex' ? check : undefined}\n />\n )\n }\n return <>{children}</>\n}\n\ntype GateState = { phase: 'checking' } | { phase: 'resolved'; report: DeploymentReadinessReport }\n\n/**\n * The checks that BLOCK boot: a failed deployment-, deployment-env-, or app-level\n * precondition. The per-user bucket (\"not a member\") is not a boot blocker — it is\n * surfaced post-auth (\"ask an admin\"), and at boot it is `unknown` (pre-auth) anyway.\n * Deployment-env checks are `unknown` in the browser today (nothing gathered — EC-399),\n * so they never block in practice; `warn` findings never block anywhere.\n */\nfunction blockingFailures(report: DeploymentReadinessReport): ReadinessCheck[] {\n return report.checks.filter(\n (c) =>\n c.status === 'fail' &&\n (c.bucket === 'deployment' || c.bucket === 'deployment-env' || c.bucket === 'app'),\n )\n}\n\n/**\n * Deterministic boot state while the backend probe is in flight — and, in a\n * CONNECTED build, the body of the prerendered shell itself.\n *\n * **EC-525.** EC-523 removed the last localized string from the shell and proved it\n * against the built artefact — but against the *playground* build, which is the demo's\n * default and the one nobody deploys. Every real studio is connected, so\n * `config.kind === 'convex'` seeds `{ phase: 'checking' }` and *this* is what a\n * production cold load actually bakes: `Checking deployment…`, in the build machine's\n * locale, shown to every visitor whatever theirs is. The class was not closed until\n * both builds satisfied it.\n *\n * Same shape as `SessionLoading` (`router/root.tsx`), for the same reasons, which are\n * written out there: a build-time prerender has no request and therefore no locale, so\n * the shell shows a spinner and an EMPTY `role=\"status\"` region, and `useHydrated`\n * fills it in the visitor's real language once hydration lands. No `aria-label` — that\n * bakes the wrong-language string somewhere a screen-reader user cannot see it flip —\n * and no `aria-busy`, which would suppress the announcement this shape exists to make.\n *\n * Exported so the connected shell's pre-hydration markup is pinnable with\n * `renderToStaticMarkup` — same reason `SessionLoading` is. The built artefact is the\n * real proof; the pin is what fails when someone puts a string back.\n */\nexport function DeploymentChecking() {\n const hydrated = useHydrated()\n const t = useT()\n return (\n <BootScreenFrame>\n <div role=\"status\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" aria-hidden=\"true\" />\n {hydrated ? <span className=\"sr-only\">{t('shell.deploymentSetup.checking')}</span> : null}\n </div>\n </BootScreenFrame>\n )\n}\n\n/**\n * The unified \"Deployment setup\" screen (EC-258): every failing check + its exact\n * fix. Pure/presentational so it is fixture-reproducible and static-render testable.\n */\nexport function DeploymentSetupScreen({\n report,\n onRetry,\n}: {\n report: DeploymentReadinessReport\n onRetry?: () => void\n}) {\n const failures = report.checks.filter((c) => c.status === 'fail')\n const envChecks = report.checks.filter((c) => c.bucket === 'deployment-env')\n return (\n <BootScreenFrame>\n <Card className=\"max-w-2xl border-destructive/40\">\n <CardHeader>\n <CardTitle>{translate('shell.deploymentSetup.title')}</CardTitle>\n </CardHeader>\n <CardContent>\n <Stack gap=\"3\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('shell.deploymentSetup.description')}\n </Text>\n <Stack gap=\"2\" role=\"list\" aria-label={translate('shell.deploymentSetup.issuesLabel')}>\n {failures.map((check) => (\n <CheckRow key={check.id} check={check} />\n ))}\n </Stack>\n {envChecks.length > 0 ? <ExpectedEnvSection checks={envChecks} /> : null}\n {onRetry ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry} className=\"self-start\">\n {translate('shell.deploymentSetup.retry')}\n </Button>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n </BootScreenFrame>\n )\n}\n\n/**\n * The expected-env facts (EC-399) — the SAME contract `elytra doctor` checks, so the\n * studio and CLI tell one story. Presence only: each row is a var name + set/missing/\n * unknown status text; values never exist in the report. In the browser the presence\n * sources aren't readable pre-auth, so rows typically read `unknown` and point at\n * `elytra doctor` — which is exactly the honest answer.\n */\nfunction ExpectedEnvSection({ checks }: { checks: ReadinessCheck[] }) {\n return (\n <Stack gap=\"2\">\n <Text size=\"sm\" className=\"font-medium\">\n {translate('shell.deploymentSetup.envSection')}\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {translate('shell.deploymentSetup.envSectionHint')}\n </Text>\n <Stack gap=\"1\" role=\"list\" aria-label={translate('shell.deploymentSetup.envSection')}>\n {checks.map((check) => (\n <EnvCheckRow key={check.id} check={check} />\n ))}\n </Stack>\n </Stack>\n )\n}\n\nconst ENV_STATUS_GLYPH: Record<ReadinessCheck['status'], string> = {\n ok: '✓',\n warn: '!',\n fail: '✗',\n unknown: '?',\n}\n\nfunction EnvCheckRow({ check }: { check: ReadinessCheck }) {\n const failing = check.status === 'fail'\n return (\n <div\n role=\"listitem\"\n className={`rounded-md border p-2 ${failing ? 'border-destructive/30 bg-destructive/5' : 'border-border bg-muted/30'}`}\n >\n <Text size=\"xs\" mono className={failing ? 'text-destructive' : undefined}>\n {ENV_STATUS_GLYPH[check.status]} [{check.id}]\n </Text>\n <Text size=\"xs\" tone=\"muted\">\n {check.detail}\n </Text>\n {check.fix && (check.status === 'fail' || check.status === 'warn') ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mt-1\">\n {translate('shell.deploymentSetup.fixLabel')}: <Code className=\"break-all\">{check.fix}</Code>\n </Text>\n ) : null}\n </div>\n )\n}\n\nfunction CheckRow({ check }: { check: ReadinessCheck }) {\n return (\n <div role=\"listitem\" className=\"rounded-md border border-destructive/30 bg-destructive/5 p-2\">\n <Text size=\"xs\" mono className=\"text-destructive\">\n [{check.id}]\n </Text>\n <Text size=\"sm\">{check.detail}</Text>\n {check.issues && check.issues.length > 0 ? <CheckIssueList issues={check.issues} /> : null}\n {check.fix ? (\n <Text size=\"xs\" tone=\"muted\" className=\"mt-1\">\n {translate('shell.deploymentSetup.fixLabel')}: <Code className=\"break-all\">{check.fix}</Code>\n </Text>\n ) : null}\n </div>\n )\n}\n\n/**\n * EC-626 — the located issues behind a failing check, one line each, exactly as\n * `elytra doctor` prints them (core's `formatConfigIssue`, applied in the shared\n * contract): `config-valid` lists every config issue instead of a count. English, like\n * the check's `detail` — they name keys and paths in the developer's own config.\n */\nfunction CheckIssueList({ issues }: { issues: readonly string[] }) {\n return (\n <Stack gap=\"1\" role=\"list\" aria-label={translate('shell.workspaceGate.issuesLabel')} className=\"mt-1\">\n {issues.map((issue, index) => (\n <Text key={index} role=\"listitem\" size=\"xs\" mono className=\"break-words\">\n {issue}\n </Text>\n ))}\n </Stack>\n )\n}\n\nfunction BootScreenFrame({ children }: { children: ReactNode }) {\n return <div className=\"grid min-h-dvh place-items-center bg-background p-6\">{children}</div>\n}\n"]}
@@ -0,0 +1,62 @@
1
+ import { Component } from 'react';
2
+ import type { ReactNode } from 'react';
3
+ /** Is this error a lazily imported module that failed to download? (pure) */
4
+ export declare function isModuleLoadFailure(error: unknown): boolean;
5
+ /** Where the notice renders: the whole page (the root catch-all) or in place of one surface. */
6
+ export type FailurePlacement = 'page' | 'panel';
7
+ export interface FailureNoticeProps {
8
+ error: unknown;
9
+ placement: FailurePlacement;
10
+ /** Playground mode — Reload then states that it empties the playground. */
11
+ playground: boolean;
12
+ onBack: () => void;
13
+ onReload: () => void;
14
+ }
15
+ /**
16
+ * The notice itself — presentational, every effect injected, so its two shapes are pinned
17
+ * with `renderToStaticMarkup`. {@link StudioFailure} wires it to the router and the page.
18
+ */
19
+ export declare function FailureNotice({ error, placement, playground, onBack, onReload }: FailureNoticeProps): import("react").JSX.Element;
20
+ /**
21
+ * The wired notice. **Back** goes one step back in the router's own history — in-app, no
22
+ * reload — or, when this tab has no earlier studio entry (a deep link), to the studio home;
23
+ * leaving the studio would unload the playground, which is exactly what Back is for avoiding.
24
+ * **Reload** is the full page reload it says it is.
25
+ */
26
+ export declare function StudioFailure({ error, placement }: {
27
+ error: unknown;
28
+ placement: FailurePlacement;
29
+ }): import("react").JSX.Element;
30
+ /**
31
+ * The root route's `errorComponent` — the catch-all for any error no surface boundary took.
32
+ * It renders inside the root `shellComponent` (the `<html>` document with the stylesheet),
33
+ * so it is themed; outside the provider stack, so it needs none of it.
34
+ */
35
+ export declare function StudioRouteError({ error }: {
36
+ error: unknown;
37
+ }): import("react").JSX.Element;
38
+ export interface LazySurfaceBoundaryProps {
39
+ children: ReactNode;
40
+ /**
41
+ * Frame the notice the way the surface frames itself (the AI panel's aside, the translate
42
+ * dialog) so the failure shows exactly where the surface would have. Default: unframed.
43
+ */
44
+ frame?: (notice: ReactNode) => ReactNode;
45
+ }
46
+ interface LazySurfaceBoundaryState {
47
+ /** `[error]`, so a thrown `undefined` still counts as caught. */
48
+ caught: [unknown] | null;
49
+ }
50
+ /**
51
+ * The ONE boundary each lazily loaded surface sits in (EC-717): a failed panel shows the
52
+ * notice IN PLACE and the rest of the studio stays usable. Measured before it existed: one
53
+ * failed translate chunk blanked the whole studio. It never resets on its own — the lazy
54
+ * import's rejection is cached, so re-rendering would only throw again; the surface's own
55
+ * unmount (closing the panel or dialog, leaving the document) is the reset.
56
+ */
57
+ export declare class LazySurfaceBoundary extends Component<LazySurfaceBoundaryProps, LazySurfaceBoundaryState> {
58
+ state: LazySurfaceBoundaryState;
59
+ static getDerivedStateFromError(error: unknown): LazySurfaceBoundaryState;
60
+ render(): ReactNode;
61
+ }
62
+ export {};
@@ -0,0 +1,110 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Component } from 'react';
3
+ import { useRouter } from '@tanstack/react-router';
4
+ import { CircleAlert, PackageX } from 'lucide-react';
5
+ import { Button, Heading, Row, Stack, Text } from '@elytracms/studio/ui';
6
+ import { cn } from '../lib/utils.js';
7
+ import { useT } from '../lib/i18n/index.js';
8
+ import { isPlaygroundMode } from '../lib/persistence/index.js';
9
+ /**
10
+ * The studio's failure notice (EC-717) — what a render error shows instead of the whole
11
+ * studio disappearing. Two places use it: the root route's `errorComponent` (the catch-all,
12
+ * rendered inside the themed document shell) and {@link LazySurfaceBoundary}, which sits
13
+ * inside each of the five lazily loaded surfaces so a failed panel stays a local notice.
14
+ *
15
+ * **The failure it names first** is a module that did not download. Measured (EC-567 V6): a
16
+ * CLI command beside `pnpm dev` swapped the dev server's dep cache, every chunk URL the page
17
+ * already knew answered 504, and the next lazily imported surface threw "Failed to fetch
18
+ * dynamically imported module". A dependency added while the studio is open does the same.
19
+ *
20
+ * **Why there is no retry button.** A retry cannot re-import a failed module without a
21
+ * reload (measured in Chromium for the operator's decision): the browser never re-fetches a
22
+ * failed `import()` URL, a cache-busted parent still fails on its failed static dependency,
23
+ * and `React.lazy` caches the rejection. So the notice offers what does work:
24
+ * - **Back** — in-app navigation, no reload, so the playground's in-memory content survives
25
+ * (its adapter is module-cached); the failed surface stays unavailable until a reload.
26
+ * - **Reload** — saying what it costs in the playground: everything in this tab.
27
+ * - **Restart the dev server** — when a reload does not help, because the dep cache itself was
28
+ * destroyed and the chunks it named are gone until Vite re-optimizes.
29
+ *
30
+ * Any other error gets a plain message and Reload. Its message is always shown — a notice
31
+ * that hid what broke would be the silent failure the studio never ships.
32
+ */
33
+ /**
34
+ * The browsers' messages for a dynamic import whose module (or one of its static
35
+ * dependencies) failed to download — Chromium, Firefox, Safari. The same three
36
+ * TanStack Router recognises (`isModuleNotFoundError`, used by its lazy route components).
37
+ */
38
+ const MODULE_LOAD_FAILURE_PREFIXES = [
39
+ 'Failed to fetch dynamically imported module',
40
+ 'error loading dynamically imported module',
41
+ 'Importing a module script failed',
42
+ ];
43
+ /** Is this error a lazily imported module that failed to download? (pure) */
44
+ export function isModuleLoadFailure(error) {
45
+ const message = errorMessage(error);
46
+ return message !== undefined && MODULE_LOAD_FAILURE_PREFIXES.some((prefix) => message.startsWith(prefix));
47
+ }
48
+ function errorMessage(error) {
49
+ if (error instanceof Error)
50
+ return error.message;
51
+ if (typeof error === 'object' && error !== null && typeof error.message === 'string') {
52
+ return error.message;
53
+ }
54
+ return typeof error === 'string' ? error : undefined;
55
+ }
56
+ /**
57
+ * The notice itself — presentational, every effect injected, so its two shapes are pinned
58
+ * with `renderToStaticMarkup`. {@link StudioFailure} wires it to the router and the page.
59
+ */
60
+ export function FailureNotice({ error, placement, playground, onBack, onReload }) {
61
+ const t = useT();
62
+ const moduleFailure = isModuleLoadFailure(error);
63
+ const Icon = moduleFailure ? PackageX : CircleAlert;
64
+ const detail = errorMessage(error);
65
+ return (_jsx("div", { "data-studio-failure": moduleFailure ? 'module' : 'error', role: "alert", className: cn('grid place-items-center', placement === 'page' ? 'min-h-dvh bg-background p-8' : 'min-h-0 w-full flex-1 p-4'), children: _jsxs(Stack, { gap: "3", className: cn('w-full rounded-xl border border-dashed border-destructive/30 bg-destructive/5', placement === 'page' ? 'max-w-lg px-8 py-8' : 'max-w-md px-5 py-5'), children: [_jsxs(Row, { gap: "2", align: "center", children: [_jsx(Icon, { "aria-hidden": true, className: "size-5 shrink-0 text-destructive" }), _jsx(Heading, { level: placement === 'page' ? 1 : 2, size: "h3", children: t(moduleFailure ? 'shell.failure.module.title' : 'shell.failure.error.title') })] }), _jsx(Text, { size: "sm", tone: "muted", children: t(moduleFailure ? 'shell.failure.module.body' : 'shell.failure.error.body') }), detail ? (_jsx(Text, { as: "div", size: "xs", tone: "muted", mono: true, className: "max-h-24 overflow-auto break-all rounded-md bg-muted/60 px-2 py-1.5", children: detail })) : null, _jsxs(Row, { gap: "2", wrap: true, children: [moduleFailure ? (_jsx(Button, { size: "sm", onClick: onBack, "data-testid": "studio-failure-back", children: t('shell.failure.back') })) : null, _jsx(Button, { size: "sm", variant: moduleFailure ? 'outline' : 'default', onClick: onReload, "data-testid": "studio-failure-reload", children: t('shell.failure.reload') })] }), playground ? (_jsx(Text, { size: "xs", tone: "muted", children: t('shell.failure.reloadCost') })) : null, moduleFailure ? (_jsx(Text, { size: "xs", tone: "muted", children: t('shell.failure.module.restartHint') })) : null] }) }));
66
+ }
67
+ /**
68
+ * The wired notice. **Back** goes one step back in the router's own history — in-app, no
69
+ * reload — or, when this tab has no earlier studio entry (a deep link), to the studio home;
70
+ * leaving the studio would unload the playground, which is exactly what Back is for avoiding.
71
+ * **Reload** is the full page reload it says it is.
72
+ */
73
+ export function StudioFailure({ error, placement }) {
74
+ const router = useRouter();
75
+ return (_jsx(FailureNotice, { error: error, placement: placement, playground: isPlaygroundMode(), onBack: () => {
76
+ if (router.history.canGoBack())
77
+ router.history.back();
78
+ else
79
+ void router.navigate({ to: '/' });
80
+ }, onReload: () => window.location.reload() }));
81
+ }
82
+ /**
83
+ * The root route's `errorComponent` — the catch-all for any error no surface boundary took.
84
+ * It renders inside the root `shellComponent` (the `<html>` document with the stylesheet),
85
+ * so it is themed; outside the provider stack, so it needs none of it.
86
+ */
87
+ export function StudioRouteError({ error }) {
88
+ return _jsx(StudioFailure, { error: error, placement: "page" });
89
+ }
90
+ /**
91
+ * The ONE boundary each lazily loaded surface sits in (EC-717): a failed panel shows the
92
+ * notice IN PLACE and the rest of the studio stays usable. Measured before it existed: one
93
+ * failed translate chunk blanked the whole studio. It never resets on its own — the lazy
94
+ * import's rejection is cached, so re-rendering would only throw again; the surface's own
95
+ * unmount (closing the panel or dialog, leaving the document) is the reset.
96
+ */
97
+ export class LazySurfaceBoundary extends Component {
98
+ state = { caught: null };
99
+ static getDerivedStateFromError(error) {
100
+ return { caught: [error] };
101
+ }
102
+ render() {
103
+ const { caught } = this.state;
104
+ if (!caught)
105
+ return this.props.children;
106
+ const notice = _jsx(StudioFailure, { error: caught[0], placement: "panel" });
107
+ return this.props.frame ? this.props.frame(notice) : notice;
108
+ }
109
+ }
110
+ //# sourceMappingURL=load-failure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-failure.js","sourceRoot":"","sources":["../../src/components/load-failure.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component } from 'react'\nimport type { ReactNode } from 'react'\nimport { useRouter } from '@tanstack/react-router'\nimport { CircleAlert, PackageX } from 'lucide-react'\nimport { Button, Heading, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '../lib/utils'\nimport { useT } from '../lib/i18n'\nimport { isPlaygroundMode } from '../lib/persistence'\n\n/**\n * The studio's failure notice (EC-717) — what a render error shows instead of the whole\n * studio disappearing. Two places use it: the root route's `errorComponent` (the catch-all,\n * rendered inside the themed document shell) and {@link LazySurfaceBoundary}, which sits\n * inside each of the five lazily loaded surfaces so a failed panel stays a local notice.\n *\n * **The failure it names first** is a module that did not download. Measured (EC-567 V6): a\n * CLI command beside `pnpm dev` swapped the dev server's dep cache, every chunk URL the page\n * already knew answered 504, and the next lazily imported surface threw \"Failed to fetch\n * dynamically imported module\". A dependency added while the studio is open does the same.\n *\n * **Why there is no retry button.** A retry cannot re-import a failed module without a\n * reload (measured in Chromium for the operator's decision): the browser never re-fetches a\n * failed `import()` URL, a cache-busted parent still fails on its failed static dependency,\n * and `React.lazy` caches the rejection. So the notice offers what does work:\n * - **Back** — in-app navigation, no reload, so the playground's in-memory content survives\n * (its adapter is module-cached); the failed surface stays unavailable until a reload.\n * - **Reload** — saying what it costs in the playground: everything in this tab.\n * - **Restart the dev server** — when a reload does not help, because the dep cache itself was\n * destroyed and the chunks it named are gone until Vite re-optimizes.\n *\n * Any other error gets a plain message and Reload. Its message is always shown — a notice\n * that hid what broke would be the silent failure the studio never ships.\n */\n\n/**\n * The browsers' messages for a dynamic import whose module (or one of its static\n * dependencies) failed to download — Chromium, Firefox, Safari. The same three\n * TanStack Router recognises (`isModuleNotFoundError`, used by its lazy route components).\n */\nconst MODULE_LOAD_FAILURE_PREFIXES = [\n 'Failed to fetch dynamically imported module',\n 'error loading dynamically imported module',\n 'Importing a module script failed',\n] as const\n\n/** Is this error a lazily imported module that failed to download? (pure) */\nexport function isModuleLoadFailure(error: unknown): boolean {\n const message = errorMessage(error)\n return message !== undefined && MODULE_LOAD_FAILURE_PREFIXES.some((prefix) => message.startsWith(prefix))\n}\n\nfunction errorMessage(error: unknown): string | undefined {\n if (error instanceof Error) return error.message\n if (typeof error === 'object' && error !== null && typeof (error as { message?: unknown }).message === 'string') {\n return (error as { message: string }).message\n }\n return typeof error === 'string' ? error : undefined\n}\n\n/** Where the notice renders: the whole page (the root catch-all) or in place of one surface. */\nexport type FailurePlacement = 'page' | 'panel'\n\nexport interface FailureNoticeProps {\n error: unknown\n placement: FailurePlacement\n /** Playground mode — Reload then states that it empties the playground. */\n playground: boolean\n onBack: () => void\n onReload: () => void\n}\n\n/**\n * The notice itself — presentational, every effect injected, so its two shapes are pinned\n * with `renderToStaticMarkup`. {@link StudioFailure} wires it to the router and the page.\n */\nexport function FailureNotice({ error, placement, playground, onBack, onReload }: FailureNoticeProps) {\n const t = useT()\n const moduleFailure = isModuleLoadFailure(error)\n const Icon = moduleFailure ? PackageX : CircleAlert\n const detail = errorMessage(error)\n return (\n <div\n data-studio-failure={moduleFailure ? 'module' : 'error'}\n role=\"alert\"\n className={cn(\n 'grid place-items-center',\n placement === 'page' ? 'min-h-dvh bg-background p-8' : 'min-h-0 w-full flex-1 p-4',\n )}\n >\n <Stack\n gap=\"3\"\n className={cn(\n 'w-full rounded-xl border border-dashed border-destructive/30 bg-destructive/5',\n placement === 'page' ? 'max-w-lg px-8 py-8' : 'max-w-md px-5 py-5',\n )}\n >\n <Row gap=\"2\" align=\"center\">\n <Icon aria-hidden className=\"size-5 shrink-0 text-destructive\" />\n <Heading level={placement === 'page' ? 1 : 2} size=\"h3\">\n {t(moduleFailure ? 'shell.failure.module.title' : 'shell.failure.error.title')}\n </Heading>\n </Row>\n <Text size=\"sm\" tone=\"muted\">\n {t(moduleFailure ? 'shell.failure.module.body' : 'shell.failure.error.body')}\n </Text>\n {detail ? (\n <Text as=\"div\" size=\"xs\" tone=\"muted\" mono className=\"max-h-24 overflow-auto break-all rounded-md bg-muted/60 px-2 py-1.5\">\n {detail}\n </Text>\n ) : null}\n <Row gap=\"2\" wrap>\n {moduleFailure ? (\n <Button size=\"sm\" onClick={onBack} data-testid=\"studio-failure-back\">\n {t('shell.failure.back')}\n </Button>\n ) : null}\n <Button size=\"sm\" variant={moduleFailure ? 'outline' : 'default'} onClick={onReload} data-testid=\"studio-failure-reload\">\n {t('shell.failure.reload')}\n </Button>\n </Row>\n {playground ? (\n <Text size=\"xs\" tone=\"muted\">\n {t('shell.failure.reloadCost')}\n </Text>\n ) : null}\n {moduleFailure ? (\n <Text size=\"xs\" tone=\"muted\">\n {t('shell.failure.module.restartHint')}\n </Text>\n ) : null}\n </Stack>\n </div>\n )\n}\n\n/**\n * The wired notice. **Back** goes one step back in the router's own history — in-app, no\n * reload — or, when this tab has no earlier studio entry (a deep link), to the studio home;\n * leaving the studio would unload the playground, which is exactly what Back is for avoiding.\n * **Reload** is the full page reload it says it is.\n */\nexport function StudioFailure({ error, placement }: { error: unknown; placement: FailurePlacement }) {\n const router = useRouter()\n return (\n <FailureNotice\n error={error}\n placement={placement}\n playground={isPlaygroundMode()}\n onBack={() => {\n if (router.history.canGoBack()) router.history.back()\n else void router.navigate({ to: '/' })\n }}\n onReload={() => window.location.reload()}\n />\n )\n}\n\n/**\n * The root route's `errorComponent` — the catch-all for any error no surface boundary took.\n * It renders inside the root `shellComponent` (the `<html>` document with the stylesheet),\n * so it is themed; outside the provider stack, so it needs none of it.\n */\nexport function StudioRouteError({ error }: { error: unknown }) {\n return <StudioFailure error={error} placement=\"page\" />\n}\n\nexport interface LazySurfaceBoundaryProps {\n children: ReactNode\n /**\n * Frame the notice the way the surface frames itself (the AI panel's aside, the translate\n * dialog) so the failure shows exactly where the surface would have. Default: unframed.\n */\n frame?: (notice: ReactNode) => ReactNode\n}\n\ninterface LazySurfaceBoundaryState {\n /** `[error]`, so a thrown `undefined` still counts as caught. */\n caught: [unknown] | null\n}\n\n/**\n * The ONE boundary each lazily loaded surface sits in (EC-717): a failed panel shows the\n * notice IN PLACE and the rest of the studio stays usable. Measured before it existed: one\n * failed translate chunk blanked the whole studio. It never resets on its own — the lazy\n * import's rejection is cached, so re-rendering would only throw again; the surface's own\n * unmount (closing the panel or dialog, leaving the document) is the reset.\n */\nexport class LazySurfaceBoundary extends Component<LazySurfaceBoundaryProps, LazySurfaceBoundaryState> {\n override state: LazySurfaceBoundaryState = { caught: null }\n\n static getDerivedStateFromError(error: unknown): LazySurfaceBoundaryState {\n return { caught: [error] }\n }\n\n override render(): ReactNode {\n const { caught } = this.state\n if (!caught) return this.props.children\n const notice = <StudioFailure error={caught[0]} placement=\"panel\" />\n return this.props.frame ? this.props.frame(notice) : notice\n }\n}\n"]}
@@ -108,9 +108,9 @@ function useProjectConfigIssues() {
108
108
  return useMemo(() => projectConfigIssues(resolveDeploymentCollections()), []);
109
109
  }
110
110
  /**
111
- * EC-467 — the project's reusable-elements config issues: a declaration that cannot
112
- * resolve, or an ambiguous inference the studio refuses to guess through. Static per studio
113
- * boot (registry + config collections + studio config). The pure core is `resolveReuse`.
111
+ * EC-467 / EC-728 — the project's reusable-elements config issues: a declaration that cannot
112
+ * resolve (an undeclared project has none — the embed is declared, never inferred). Static per
113
+ * studio boot (registry + config collections + studio config). The pure core is `resolveReuse`.
114
114
  */
115
115
  function useReuseIssues() {
116
116
  return useMemo(() => resolveReuse(getStudioRegistry().registry.list(), resolveDeploymentCollections(), resolveDeploymentReuse())
@@ -1 +1 @@
1
- {"version":3,"file":"shell-findings.js","sourceRoot":"","sources":["../../../src/components/notifications/shell-findings.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAGrD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AACtF,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AACrE,OAAO,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAA;AAC5G,OAAO,EAAE,oBAAoB,EAAE,MAAM,6CAA6C,CAAA;AAClF,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE5F,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAErE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AAErE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAG7D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;IACjC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,iBAAiB,EAAE,CAAA;IACtD,MAAM,YAAY,GAAG,sBAAsB,EAAE,CAAA;IAC7C,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;IACpC,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC9C,MAAM,YAAY,GAAG,2BAA2B,EAAE,CAAA;IAClD,MAAM,cAAc,GAAG,yBAAyB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC5D,MAAM,WAAW,GAAG,uBAAuB,EAAE,CAAA;IAC7C,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAE5C,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,MAAM,GAAc;YACxB,kFAAkF;YAClF,6DAA6D;YAC7D,GAAG,UAAU,CAAC,QAAQ,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;YACzD,GAAG,UAAU,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC;YACtG,GAAG,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC;YACpC,GAAG,UAAU,CAAC,SAAS,EAAE,YAAY,CAAC;YACtC,GAAG,UAAU,CAAC,UAAU,EAAE,cAAc,CAAC;SAC1C,CAAA;QACD,OAAO,aAAa,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;IACzG,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAA;AAC/H,CAAC;AAED,SAAS,UAAU,CAAC,MAA2B,EAAE,KAAwD;IACvG,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAA;AAChF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACvC,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC/C,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QAC7B,OAAO;YACL,OAAO,EAAE,IAAI,KAAK,IAAI,IAAI,oBAAoB,CAAC,IAAI,EAAE,oBAAoB,EAAE,MAAM,CAAC;YAClF,cAAc,EAAE,IAAI,KAAK,IAAI,IAAI,oBAAoB,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC;YACnF,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACvC,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzE,OAAO,EAAE,IAAI,KAAK,IAAI,IAAI,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC;aACtD,CAAC,CAAC;SACJ,CAAA;IACH,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAE,OAAO,EAAwB;IAChE,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;IACjC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAC5F,OAAO,CACL,4BACG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACxB,KAAC,eAAe,IAAe,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,IAArC,IAAI,CAAC,EAAE,CAAkC,CAChE,CAAC,GACD,CACJ,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,EAAE,IAAI,EAAE,OAAO,EAA6C;IACnF,MAAM,MAAM,GAAG,IAAI,CAAC,SAAU,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAA;IAClE,YAAY,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAA;IAC/C,OAAO,IAAI,CAAA;AACb,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,QAAQ,MAAM,EAAE,CAAA;AACzB,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB;IAC7B,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,4BAA4B,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;AAC/E,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc;IACrB,OAAO,OAAO,CACZ,GAAG,EAAE,CACH,YAAY,CAAC,iBAAiB,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,4BAA4B,EAAE,EAAE,sBAAsB,EAAE,CAAC;SACxG,MAAM,EACX,EAAE,CACH,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,SAAiB;IACvC,MAAM,SAAS,GAAG,aAAa,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC,IAAI,CAAA;IACnE,OAAO,OAAO,CACZ,GAAG,EAAE,CACH,eAAe,CACb,qBAAqB,CACnB,EAAE,MAAM,EAAE,uBAAuB,EAAE,EAAE,WAAW,EAAE,4BAA4B,EAAE,EAAE,EAClF,SAAS,IAAI,EAAE,CAChB,CACF,EACH,CAAC,SAAS,CAAC,CACZ,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,2BAA2B;IAClC,MAAM,OAAO,GAAG,oBAAoB,CAAC,uBAAuB,EAAE,qBAAqB,EAAE,2BAA2B,CAAC,CAAA;IACjH,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;AACnE,CAAC","sourcesContent":["import { useMemo, useSyncExternalStore } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { redirectRecordKind, roleGrantsCapability } from '@elytracms/core/persistence'\nimport { getStudioRegistry } from '../../lib/registry'\nimport {\n projectConfigIssues,\n resolveDeploymentCollections,\n resolveDeploymentReuse,\n resolveDeploymentRoutes,\n} from '../../lib/workspace'\nimport { routeValidationIssues } from '../../lib/routes/route-issues'\nimport { contentSessionOrphans, serverContentSessionOrphans, subscribeContentSession } from '../../lib/data'\nimport { projectRolePolicyFor } from '../../lib/operations/project-runtime-config'\nimport { customizationItems, orphanedCollectionItems, routeIssueItems } from '../nav-issues'\nimport type { NavIssueItem } from '../nav-issues'\nimport { useContributedNavIssues, useNavIssues } from '../nav-issues-registry'\nimport { resolveReuse } from '../builder/reuse'\nimport { useRecordList } from '../records/record-rail'\nimport { useFieldRenderers } from '../cms/field-renderer'\nimport { useProjectRole } from '../auth-context'\nimport { isNavViewVisible, useStudioNav } from '../app-views/sidebar'\nimport type { AppViewModule } from '../app-views/module'\nimport { useDeliveryHealthFindings } from '../delivery-health-banner'\nimport type { DeliveryHealthFinding } from '../delivery-health-banner'\nimport { canSeeFinding, orderFindings } from './notice-model'\nimport type { EngineFindingSource, Finding, FindingAudience } from './notice-model'\n\n/**\n * EC-690 — every persistent finding of the studio shell, for the ONE issues tray.\n *\n * The ENGINE's own sources are computed here, in render — config (EC-479), customization\n * (EC-236/467/478), the URL map (EC-469/590/628), orphaned collections (EC-606) and\n * instant-publish health (EC-268) — so they are there on the first paint (no effect has to\n * run before the badge can count them) and no contributor can overwrite them: they are not\n * in the registry's namespace at all. Contributions (`useNavIssues`, a view's `useIssues`)\n * come from the registry. Then the viewer's audience filter (owner ruling (b)) and the ONE\n * order ({@link orderFindings}).\n *\n * The findings hooks moved here from `SectionNav`, which renders none of them any more (the\n * rail foot is only its collapse toggle — owner ruling (a)).\n */\nexport function useShellFindings(project: Project): Finding[] {\n const nav = useStudioNav(project)\n const { issues: rendererIssues } = useFieldRenderers()\n const configIssues = useProjectConfigIssues()\n const reuseIssues = useReuseIssues()\n const routeIssues = useRouteIssues(project.id)\n const orphanIssues = useOrphanedCollectionIssues()\n const deliveryIssues = useDeliveryHealthFindings(project.id)\n const contributed = useContributedNavIssues()\n const audience = useFindingAudience(project)\n\n return useMemo(() => {\n const engine: Finding[] = [\n // Config issues lead: \"this project declares no collections\" explains every other\n // emptiness on the screen, so it is the first thing to read.\n ...asFindings('config', customizationItems(configIssues)),\n ...asFindings('customization', customizationItems([...nav.issues, ...rendererIssues, ...reuseIssues])),\n ...asFindings('routes', routeIssues),\n ...asFindings('orphans', orphanIssues),\n ...asFindings('delivery', deliveryIssues),\n ]\n return orderFindings([...engine, ...contributed]).filter((finding) => canSeeFinding(finding, audience))\n }, [configIssues, nav.issues, rendererIssues, reuseIssues, routeIssues, orphanIssues, deliveryIssues, contributed, audience])\n}\n\nfunction asFindings(source: EngineFindingSource, items: readonly (NavIssueItem | DeliveryHealthFinding)[]): Finding[] {\n return items.map((item) => ({ id: `${source}:${item.key}`, source, ...item }))\n}\n\n/**\n * Who is reading (owner ruling (b)): an admin (`project.administer`) sees config and\n * deployment findings; a located finding shows to whoever can open its location. While the\n * role is still loading nobody is an admin and no view is visible, so a gated finding\n * appears once the role is known, never to the wrong reader first.\n */\nexport function useFindingAudience(project: Project): FindingAudience {\n const nav = useStudioNav(project)\n const role = useProjectRole(project.id)\n return useMemo(() => {\n const policy = projectRolePolicyFor(project.id)\n const ctx = { project, role }\n return {\n isAdmin: role !== null && roleGrantsCapability(role, 'project.administer', policy),\n canReadContent: role !== null && roleGrantsCapability(role, 'content.read', policy),\n views: nav.registry.views.map((view) => ({\n id: view.id,\n ...(view.recordKind !== undefined ? { recordKind: view.recordKind } : {}),\n visible: role !== null && isNavViewVisible(view, ctx),\n })),\n }\n }, [nav, role, project])\n}\n\n/**\n * EC-616 → EC-690: runs every view's always-on findings hook (`useIssues`) — whether or not\n * the view is open — and publishes what it returns. One component per view (each hook keeps\n * its own hook order), each its OWN publisher with the source `view:<id>`: before EC-690 every\n * probe published under the one source `view:`, so two such views overwrote each other and\n * one unmount retracted both (critic point 1).\n */\nexport function ViewIssuesProbes({ project }: { project: Project }) {\n const nav = useStudioNav(project)\n const issueViews = useMemo(() => nav.registry.views.filter((view) => view.useIssues), [nav])\n return (\n <>\n {issueViews.map((view) => (\n <ViewIssuesProbe key={view.id} view={view} project={project} />\n ))}\n </>\n )\n}\n\nfunction ViewIssuesProbe({ view, project }: { view: AppViewModule; project: Project }) {\n const issues = view.useIssues!({ project, projectId: project.id })\n useNavIssues(viewIssuesSource(view.id), issues)\n return null\n}\n\n/** The source a view's always-on findings publish under. */\nexport function viewIssuesSource(viewId: string): string {\n return `view:${viewId}`\n}\n\n/**\n * EC-479 — what the project has not declared but the studio cannot work without. Read from\n * the config holder alone (static per studio boot), so it needs no CMS provider and is\n * memoised once. The pure decision core is `projectConfigIssues`.\n */\nfunction useProjectConfigIssues(): CustomizationIssue[] {\n return useMemo(() => projectConfigIssues(resolveDeploymentCollections()), [])\n}\n\n/**\n * EC-467 — the project's reusable-elements config issues: a declaration that cannot\n * resolve, or an ambiguous inference the studio refuses to guess through. Static per studio\n * boot (registry + config collections + studio config). The pure core is `resolveReuse`.\n */\nfunction useReuseIssues(): CustomizationIssue[] {\n return useMemo(\n () =>\n resolveReuse(getStudioRegistry().registry.list(), resolveDeploymentCollections(), resolveDeploymentReuse())\n .issues,\n [],\n )\n}\n\n/**\n * EC-469 — the project's URL-map issues: two routes that match the same URLs, a redirect\n * chain that loops, a duplicate or unreadable redirect. Routes are CODE (config, static per\n * boot); redirects are editorial DATA (the rail, live) — the read shares the rail's own query\n * key, so a finding disappears the moment it is fixed; while that query is loading or failed\n * only the code-side conflicts are reported (the rail surfaces its own load error).\n */\nfunction useRouteIssues(projectId: string): NavIssueItem[] {\n const redirects = useRecordList(redirectRecordKind, projectId).data\n return useMemo(\n () =>\n routeIssueItems(\n routeValidationIssues(\n { routes: resolveDeploymentRoutes(), collections: resolveDeploymentCollections() },\n redirects ?? [],\n ),\n ),\n [redirects],\n )\n}\n\n/**\n * EC-606 — stored documents whose collection the project does not declare. Read from the\n * content session (subscribable, configured from the same hydration in both modes); the\n * server snapshot is empty — there is no session during SSR. The pure mapping is\n * {@link orphanedCollectionItems}.\n */\nfunction useOrphanedCollectionIssues(): NavIssueItem[] {\n const orphans = useSyncExternalStore(subscribeContentSession, contentSessionOrphans, serverContentSessionOrphans)\n return useMemo(() => orphanedCollectionItems(orphans), [orphans])\n}\n"]}
1
+ {"version":3,"file":"shell-findings.js","sourceRoot":"","sources":["../../../src/components/notifications/shell-findings.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useSyncExternalStore } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { redirectRecordKind, roleGrantsCapability } from '@elytracms/core/persistence'\nimport { getStudioRegistry } from '../../lib/registry'\nimport {\n projectConfigIssues,\n resolveDeploymentCollections,\n resolveDeploymentReuse,\n resolveDeploymentRoutes,\n} from '../../lib/workspace'\nimport { routeValidationIssues } from '../../lib/routes/route-issues'\nimport { contentSessionOrphans, serverContentSessionOrphans, subscribeContentSession } from '../../lib/data'\nimport { projectRolePolicyFor } from '../../lib/operations/project-runtime-config'\nimport { customizationItems, orphanedCollectionItems, routeIssueItems } from '../nav-issues'\nimport type { NavIssueItem } from '../nav-issues'\nimport { useContributedNavIssues, useNavIssues } from '../nav-issues-registry'\nimport { resolveReuse } from '../builder/reuse'\nimport { useRecordList } from '../records/record-rail'\nimport { useFieldRenderers } from '../cms/field-renderer'\nimport { useProjectRole } from '../auth-context'\nimport { isNavViewVisible, useStudioNav } from '../app-views/sidebar'\nimport type { AppViewModule } from '../app-views/module'\nimport { useDeliveryHealthFindings } from '../delivery-health-banner'\nimport type { DeliveryHealthFinding } from '../delivery-health-banner'\nimport { canSeeFinding, orderFindings } from './notice-model'\nimport type { EngineFindingSource, Finding, FindingAudience } from './notice-model'\n\n/**\n * EC-690 — every persistent finding of the studio shell, for the ONE issues tray.\n *\n * The ENGINE's own sources are computed here, in render — config (EC-479), customization\n * (EC-236/467/478), the URL map (EC-469/590/628), orphaned collections (EC-606) and\n * instant-publish health (EC-268) — so they are there on the first paint (no effect has to\n * run before the badge can count them) and no contributor can overwrite them: they are not\n * in the registry's namespace at all. Contributions (`useNavIssues`, a view's `useIssues`)\n * come from the registry. Then the viewer's audience filter (owner ruling (b)) and the ONE\n * order ({@link orderFindings}).\n *\n * The findings hooks moved here from `SectionNav`, which renders none of them any more (the\n * rail foot is only its collapse toggle — owner ruling (a)).\n */\nexport function useShellFindings(project: Project): Finding[] {\n const nav = useStudioNav(project)\n const { issues: rendererIssues } = useFieldRenderers()\n const configIssues = useProjectConfigIssues()\n const reuseIssues = useReuseIssues()\n const routeIssues = useRouteIssues(project.id)\n const orphanIssues = useOrphanedCollectionIssues()\n const deliveryIssues = useDeliveryHealthFindings(project.id)\n const contributed = useContributedNavIssues()\n const audience = useFindingAudience(project)\n\n return useMemo(() => {\n const engine: Finding[] = [\n // Config issues lead: \"this project declares no collections\" explains every other\n // emptiness on the screen, so it is the first thing to read.\n ...asFindings('config', customizationItems(configIssues)),\n ...asFindings('customization', customizationItems([...nav.issues, ...rendererIssues, ...reuseIssues])),\n ...asFindings('routes', routeIssues),\n ...asFindings('orphans', orphanIssues),\n ...asFindings('delivery', deliveryIssues),\n ]\n return orderFindings([...engine, ...contributed]).filter((finding) => canSeeFinding(finding, audience))\n }, [configIssues, nav.issues, rendererIssues, reuseIssues, routeIssues, orphanIssues, deliveryIssues, contributed, audience])\n}\n\nfunction asFindings(source: EngineFindingSource, items: readonly (NavIssueItem | DeliveryHealthFinding)[]): Finding[] {\n return items.map((item) => ({ id: `${source}:${item.key}`, source, ...item }))\n}\n\n/**\n * Who is reading (owner ruling (b)): an admin (`project.administer`) sees config and\n * deployment findings; a located finding shows to whoever can open its location. While the\n * role is still loading nobody is an admin and no view is visible, so a gated finding\n * appears once the role is known, never to the wrong reader first.\n */\nexport function useFindingAudience(project: Project): FindingAudience {\n const nav = useStudioNav(project)\n const role = useProjectRole(project.id)\n return useMemo(() => {\n const policy = projectRolePolicyFor(project.id)\n const ctx = { project, role }\n return {\n isAdmin: role !== null && roleGrantsCapability(role, 'project.administer', policy),\n canReadContent: role !== null && roleGrantsCapability(role, 'content.read', policy),\n views: nav.registry.views.map((view) => ({\n id: view.id,\n ...(view.recordKind !== undefined ? { recordKind: view.recordKind } : {}),\n visible: role !== null && isNavViewVisible(view, ctx),\n })),\n }\n }, [nav, role, project])\n}\n\n/**\n * EC-616 → EC-690: runs every view's always-on findings hook (`useIssues`) — whether or not\n * the view is open — and publishes what it returns. One component per view (each hook keeps\n * its own hook order), each its OWN publisher with the source `view:<id>`: before EC-690 every\n * probe published under the one source `view:`, so two such views overwrote each other and\n * one unmount retracted both (critic point 1).\n */\nexport function ViewIssuesProbes({ project }: { project: Project }) {\n const nav = useStudioNav(project)\n const issueViews = useMemo(() => nav.registry.views.filter((view) => view.useIssues), [nav])\n return (\n <>\n {issueViews.map((view) => (\n <ViewIssuesProbe key={view.id} view={view} project={project} />\n ))}\n </>\n )\n}\n\nfunction ViewIssuesProbe({ view, project }: { view: AppViewModule; project: Project }) {\n const issues = view.useIssues!({ project, projectId: project.id })\n useNavIssues(viewIssuesSource(view.id), issues)\n return null\n}\n\n/** The source a view's always-on findings publish under. */\nexport function viewIssuesSource(viewId: string): string {\n return `view:${viewId}`\n}\n\n/**\n * EC-479 — what the project has not declared but the studio cannot work without. Read from\n * the config holder alone (static per studio boot), so it needs no CMS provider and is\n * memoised once. The pure decision core is `projectConfigIssues`.\n */\nfunction useProjectConfigIssues(): CustomizationIssue[] {\n return useMemo(() => projectConfigIssues(resolveDeploymentCollections()), [])\n}\n\n/**\n * EC-467 / EC-728 — the project's reusable-elements config issues: a declaration that cannot\n * resolve (an undeclared project has none — the embed is declared, never inferred). Static per\n * studio boot (registry + config collections + studio config). The pure core is `resolveReuse`.\n */\nfunction useReuseIssues(): CustomizationIssue[] {\n return useMemo(\n () =>\n resolveReuse(getStudioRegistry().registry.list(), resolveDeploymentCollections(), resolveDeploymentReuse())\n .issues,\n [],\n )\n}\n\n/**\n * EC-469 — the project's URL-map issues: two routes that match the same URLs, a redirect\n * chain that loops, a duplicate or unreadable redirect. Routes are CODE (config, static per\n * boot); redirects are editorial DATA (the rail, live) — the read shares the rail's own query\n * key, so a finding disappears the moment it is fixed; while that query is loading or failed\n * only the code-side conflicts are reported (the rail surfaces its own load error).\n */\nfunction useRouteIssues(projectId: string): NavIssueItem[] {\n const redirects = useRecordList(redirectRecordKind, projectId).data\n return useMemo(\n () =>\n routeIssueItems(\n routeValidationIssues(\n { routes: resolveDeploymentRoutes(), collections: resolveDeploymentCollections() },\n redirects ?? [],\n ),\n ),\n [redirects],\n )\n}\n\n/**\n * EC-606 — stored documents whose collection the project does not declare. Read from the\n * content session (subscribable, configured from the same hydration in both modes); the\n * server snapshot is empty — there is no session during SSR. The pure mapping is\n * {@link orphanedCollectionItems}.\n */\nfunction useOrphanedCollectionIssues(): NavIssueItem[] {\n const orphans = useSyncExternalStore(subscribeContentSession, contentSessionOrphans, serverContentSessionOrphans)\n return useMemo(() => orphanedCollectionItems(orphans), [orphans])\n}\n"]}