@elytracms/studio 0.0.86 → 0.0.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/convex/asset-revalidate.test.ts +186 -0
  2. package/convex/assets.ts +53 -19
  3. package/convex/cms.ts +22 -0
  4. package/convex/delivery.ts +33 -116
  5. package/convex/functions.ts +3 -3
  6. package/convex/rail-write-parity.test.ts +180 -0
  7. package/convex/redirects.ts +33 -11
  8. package/convex/revalidate.ts +7 -5
  9. package/convex/triggers.ts +53 -3
  10. package/dist/components/ai/ai-chat-panel.js +8 -1
  11. package/dist/components/ai/ai-chat-panel.js.map +1 -1
  12. package/dist/components/app-views/default-modules.d.ts +20 -20
  13. package/dist/components/app-views/default-modules.js +27 -48
  14. package/dist/components/app-views/default-modules.js.map +1 -1
  15. package/dist/components/app-views/studio-config.d.ts +5 -6
  16. package/dist/components/app-views/studio-config.js.map +1 -1
  17. package/dist/components/assets/asset-metadata.d.ts +28 -19
  18. package/dist/components/assets/asset-metadata.js +24 -31
  19. package/dist/components/assets/asset-metadata.js.map +1 -1
  20. package/dist/components/assets/asset-selection.d.ts +24 -5
  21. package/dist/components/assets/asset-selection.js +39 -9
  22. package/dist/components/assets/asset-selection.js.map +1 -1
  23. package/dist/components/assets/assets-collection-view-source.d.ts +17 -14
  24. package/dist/components/assets/assets-collection-view-source.js +25 -31
  25. package/dist/components/assets/assets-collection-view-source.js.map +1 -1
  26. package/dist/components/assets/assets-store.d.ts +11 -1
  27. package/dist/components/assets/assets-store.js +10 -0
  28. package/dist/components/assets/assets-store.js.map +1 -1
  29. package/dist/components/assets/focal-point.d.ts +0 -7
  30. package/dist/components/assets/focal-point.js +0 -10
  31. package/dist/components/assets/focal-point.js.map +1 -1
  32. package/dist/components/builder/blocks-panel.js +3 -3
  33. package/dist/components/builder/blocks-panel.js.map +1 -1
  34. package/dist/components/builder/builder-store.d.ts +25 -1
  35. package/dist/components/builder/builder-store.js +3 -2
  36. package/dist/components/builder/builder-store.js.map +1 -1
  37. package/dist/components/builder/canvas-chrome-css.js +0 -17
  38. package/dist/components/builder/canvas-chrome-css.js.map +1 -1
  39. package/dist/components/builder/canvas-chrome.d.ts +10 -12
  40. package/dist/components/builder/canvas-chrome.js +12 -13
  41. package/dist/components/builder/canvas-chrome.js.map +1 -1
  42. package/dist/components/builder/canvas-dnd.d.ts +8 -0
  43. package/dist/components/builder/canvas-dnd.js +5 -3
  44. package/dist/components/builder/canvas-dnd.js.map +1 -1
  45. package/dist/components/builder/canvas-render-context.d.ts +23 -24
  46. package/dist/components/builder/canvas-render-context.js +35 -60
  47. package/dist/components/builder/canvas-render-context.js.map +1 -1
  48. package/dist/components/builder/canvas.js +5 -11
  49. package/dist/components/builder/canvas.js.map +1 -1
  50. package/dist/components/builder/clipboard-actions.d.ts +14 -6
  51. package/dist/components/builder/clipboard-actions.js +29 -20
  52. package/dist/components/builder/clipboard-actions.js.map +1 -1
  53. package/dist/components/builder/composition-field-editor.d.ts +19 -26
  54. package/dist/components/builder/composition-field-editor.js +30 -52
  55. package/dist/components/builder/composition-field-editor.js.map +1 -1
  56. package/dist/components/builder/insert-context.d.ts +3 -1
  57. package/dist/components/builder/insert-context.js +15 -10
  58. package/dist/components/builder/insert-context.js.map +1 -1
  59. package/dist/components/builder/page-tree.d.ts +29 -0
  60. package/dist/components/builder/page-tree.js +56 -58
  61. package/dist/components/builder/page-tree.js.map +1 -1
  62. package/dist/components/builder/reuse.js +5 -2
  63. package/dist/components/builder/reuse.js.map +1 -1
  64. package/dist/components/cms/asset-field.d.ts +2 -1
  65. package/dist/components/cms/asset-field.js +39 -83
  66. package/dist/components/cms/asset-field.js.map +1 -1
  67. package/dist/components/cms/asset-picker-core.d.ts +77 -40
  68. package/dist/components/cms/asset-picker-core.js +73 -46
  69. package/dist/components/cms/asset-picker-core.js.map +1 -1
  70. package/dist/components/cms/asset-picker.d.ts +48 -19
  71. package/dist/components/cms/asset-picker.js +69 -30
  72. package/dist/components/cms/asset-picker.js.map +1 -1
  73. package/dist/components/cms/cell-edit.d.ts +3 -1
  74. package/dist/components/cms/cell-edit.js +2 -2
  75. package/dist/components/cms/cell-edit.js.map +1 -1
  76. package/dist/components/cms/cms-composition-validators.js +11 -2
  77. package/dist/components/cms/cms-composition-validators.js.map +1 -1
  78. package/dist/components/cms/cms-store.js +15 -23
  79. package/dist/components/cms/cms-store.js.map +1 -1
  80. package/dist/components/cms/editable-cell.js +3 -3
  81. package/dist/components/cms/editable-cell.js.map +1 -1
  82. package/dist/components/cms/gate-validation.d.ts +27 -2
  83. package/dist/components/cms/gate-validation.js +19 -1
  84. package/dist/components/cms/gate-validation.js.map +1 -1
  85. package/dist/components/cms/rich-text/editor.js +2 -2
  86. package/dist/components/cms/rich-text/editor.js.map +1 -1
  87. package/dist/components/cms/rich-text/insert.d.ts +7 -0
  88. package/dist/components/cms/rich-text/insert.js +7 -0
  89. package/dist/components/cms/rich-text/insert.js.map +1 -1
  90. package/dist/components/cms/use-document-versions.js +1 -0
  91. package/dist/components/cms/use-document-versions.js.map +1 -1
  92. package/dist/components/cms/versions-model.d.ts +0 -12
  93. package/dist/components/cms/versions-model.js +3 -15
  94. package/dist/components/cms/versions-model.js.map +1 -1
  95. package/dist/components/components/component-detail.js +1 -1
  96. package/dist/components/components/component-detail.js.map +1 -1
  97. package/dist/components/components/transfer-dialog.js +2 -4
  98. package/dist/components/components/transfer-dialog.js.map +1 -1
  99. package/dist/components/components/transfer.d.ts +13 -10
  100. package/dist/components/components/transfer.js +21 -19
  101. package/dist/components/components/transfer.js.map +1 -1
  102. package/dist/components/nav-issues.d.ts +23 -3
  103. package/dist/components/nav-issues.js +38 -2
  104. package/dist/components/nav-issues.js.map +1 -1
  105. package/dist/components/project-session.d.ts +9 -0
  106. package/dist/components/project-session.js +1 -1
  107. package/dist/components/project-session.js.map +1 -1
  108. package/dist/components/redirects/redirects-collection-view-source.d.ts +17 -2
  109. package/dist/components/redirects/redirects-collection-view-source.js +20 -6
  110. package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
  111. package/dist/components/section-nav.js +26 -2
  112. package/dist/components/section-nav.js.map +1 -1
  113. package/dist/components/section-surface.js +3 -2
  114. package/dist/components/section-surface.js.map +1 -1
  115. package/dist/components/settings-form.js +1 -2
  116. package/dist/components/settings-form.js.map +1 -1
  117. package/dist/components/studio-context.js +1 -18
  118. package/dist/components/studio-context.js.map +1 -1
  119. package/dist/components/studio-project-screen.js +3 -5
  120. package/dist/components/studio-project-screen.js.map +1 -1
  121. package/dist/lib/ai/apply-content.js +73 -29
  122. package/dist/lib/ai/apply-content.js.map +1 -1
  123. package/dist/lib/ai/constraints.d.ts +35 -0
  124. package/dist/lib/ai/constraints.js +104 -4
  125. package/dist/lib/ai/constraints.js.map +1 -1
  126. package/dist/lib/ai/route.js +4 -1
  127. package/dist/lib/ai/route.js.map +1 -1
  128. package/dist/lib/ai/shape.d.ts +3 -2
  129. package/dist/lib/ai/shape.js +16 -24
  130. package/dist/lib/ai/shape.js.map +1 -1
  131. package/dist/lib/ai/tools.d.ts +10 -4
  132. package/dist/lib/ai/tools.js +12 -4
  133. package/dist/lib/ai/tools.js.map +1 -1
  134. package/dist/lib/ai/types.d.ts +8 -0
  135. package/dist/lib/ai/types.js.map +1 -1
  136. package/dist/lib/ai/update-fields.js +33 -12
  137. package/dist/lib/ai/update-fields.js.map +1 -1
  138. package/dist/lib/assets/asset-placements.d.ts +35 -0
  139. package/dist/lib/assets/asset-placements.js +74 -0
  140. package/dist/lib/assets/asset-placements.js.map +1 -0
  141. package/dist/lib/assets/backfill-thumbs.d.ts +9 -8
  142. package/dist/lib/assets/backfill-thumbs.js +13 -25
  143. package/dist/lib/assets/backfill-thumbs.js.map +1 -1
  144. package/dist/lib/clipboard/paste-issues.d.ts +17 -15
  145. package/dist/lib/clipboard/paste-issues.js +8 -45
  146. package/dist/lib/clipboard/paste-issues.js.map +1 -1
  147. package/dist/lib/clipboard/paste-target.d.ts +25 -1
  148. package/dist/lib/clipboard/paste-target.js +13 -6
  149. package/dist/lib/clipboard/paste-target.js.map +1 -1
  150. package/dist/lib/cms/published-documents.js +1 -0
  151. package/dist/lib/cms/published-documents.js.map +1 -1
  152. package/dist/lib/cms-views/view-model.d.ts +3 -3
  153. package/dist/lib/data/collection-content.d.ts +22 -1
  154. package/dist/lib/data/collection-content.js +15 -0
  155. package/dist/lib/data/collection-content.js.map +1 -1
  156. package/dist/lib/data/index.d.ts +0 -3
  157. package/dist/lib/data/index.js +4 -6
  158. package/dist/lib/data/index.js.map +1 -1
  159. package/dist/lib/dnd/drop-rules.d.ts +45 -2
  160. package/dist/lib/dnd/drop-rules.js +64 -9
  161. package/dist/lib/dnd/drop-rules.js.map +1 -1
  162. package/dist/lib/engine-version.d.ts +3 -3
  163. package/dist/lib/engine-version.js +3 -3
  164. package/dist/lib/engine-version.js.map +1 -1
  165. package/dist/lib/i18n/messages.de.js +9 -56
  166. package/dist/lib/i18n/messages.de.js.map +1 -1
  167. package/dist/lib/i18n/messages.en.d.ts +8 -53
  168. package/dist/lib/i18n/messages.en.js +14 -57
  169. package/dist/lib/i18n/messages.en.js.map +1 -1
  170. package/dist/lib/locale/fallback.js +8 -2
  171. package/dist/lib/locale/fallback.js.map +1 -1
  172. package/dist/lib/locale/index.d.ts +0 -1
  173. package/dist/lib/locale/index.js +0 -1
  174. package/dist/lib/locale/index.js.map +1 -1
  175. package/dist/lib/locale/locale-store.d.ts +10 -3
  176. package/dist/lib/locale/locale-store.js +12 -5
  177. package/dist/lib/locale/locale-store.js.map +1 -1
  178. package/dist/lib/operations/session-hydration.d.ts +24 -22
  179. package/dist/lib/operations/session-hydration.js +15 -53
  180. package/dist/lib/operations/session-hydration.js.map +1 -1
  181. package/dist/lib/persistence/convex-adapter.js +63 -75
  182. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  183. package/dist/lib/persona/labels.d.ts +0 -2
  184. package/dist/lib/persona/labels.js +1 -7
  185. package/dist/lib/persona/labels.js.map +1 -1
  186. package/dist/lib/registry/index.d.ts +4 -2
  187. package/dist/lib/registry/index.js +26 -21
  188. package/dist/lib/registry/index.js.map +1 -1
  189. package/dist/lib/registry/project-components.js +0 -3
  190. package/dist/lib/registry/project-components.js.map +1 -1
  191. package/dist/lib/routes/route-issues.js +4 -5
  192. package/dist/lib/routes/route-issues.js.map +1 -1
  193. package/dist/lib/theme.js +3 -2
  194. package/dist/lib/theme.js.map +1 -1
  195. package/dist/lib/webhooks/tags.js +11 -14
  196. package/dist/lib/webhooks/tags.js.map +1 -1
  197. package/dist/lib/workspace/config-issues.d.ts +6 -0
  198. package/dist/lib/workspace/config-issues.js +8 -4
  199. package/dist/lib/workspace/config-issues.js.map +1 -1
  200. package/dist/lib/workspace/index.d.ts +2 -2
  201. package/dist/lib/workspace/index.js +2 -2
  202. package/dist/lib/workspace/index.js.map +1 -1
  203. package/dist/previews/enumerate.d.ts +27 -9
  204. package/dist/previews/enumerate.js +27 -6
  205. package/dist/previews/enumerate.js.map +1 -1
  206. package/dist/previews/manifest-format.d.ts +9 -0
  207. package/dist/previews/manifest-format.js.map +1 -1
  208. package/dist/router/match.d.ts +0 -2
  209. package/dist/router/match.js +0 -2
  210. package/dist/router/match.js.map +1 -1
  211. package/dist/router/navigate-once.d.ts +19 -4
  212. package/dist/router/navigate-once.js +22 -5
  213. package/dist/router/navigate-once.js.map +1 -1
  214. package/dist/ui/badge.d.ts +1 -1
  215. package/package.json +3 -3
  216. package/src/styles/studio.css +5 -4
  217. package/src/styles/tokens.css +72 -55
  218. package/convex/content.ts +0 -291
  219. package/dist/components/collab/collab-context.d.ts +0 -27
  220. package/dist/components/collab/collab-context.js +0 -39
  221. package/dist/components/collab/collab-context.js.map +0 -1
  222. package/dist/components/collab/collab-seed.d.ts +0 -15
  223. package/dist/components/collab/collab-seed.js +0 -20
  224. package/dist/components/collab/collab-seed.js.map +0 -1
  225. package/dist/components/extensions/extensions-view.d.ts +0 -7
  226. package/dist/components/extensions/extensions-view.js +0 -127
  227. package/dist/components/extensions/extensions-view.js.map +0 -1
  228. package/dist/lib/bindings/section-bindings.d.ts +0 -24
  229. package/dist/lib/bindings/section-bindings.js +0 -9
  230. package/dist/lib/bindings/section-bindings.js.map +0 -1
  231. package/dist/lib/data/data-store.d.ts +0 -98
  232. package/dist/lib/data/data-store.js +0 -211
  233. package/dist/lib/data/data-store.js.map +0 -1
  234. package/dist/lib/data/providers.d.ts +0 -143
  235. package/dist/lib/data/providers.js +0 -255
  236. package/dist/lib/data/providers.js.map +0 -1
  237. package/dist/lib/data/sample-capture.d.ts +0 -30
  238. package/dist/lib/data/sample-capture.js +0 -70
  239. package/dist/lib/data/sample-capture.js.map +0 -1
  240. package/dist/lib/data/sources-store.d.ts +0 -42
  241. package/dist/lib/data/sources-store.js +0 -120
  242. package/dist/lib/data/sources-store.js.map +0 -1
  243. package/dist/lib/locale/binding-fallback.d.ts +0 -35
  244. package/dist/lib/locale/binding-fallback.js +0 -73
  245. package/dist/lib/locale/binding-fallback.js.map +0 -1
  246. package/dist/lib/starters/kit-session.d.ts +0 -5
  247. package/dist/lib/starters/kit-session.js +0 -17
  248. package/dist/lib/starters/kit-session.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"project-session.js","sourceRoot":"","sources":["../../src/components/project-session.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport { getStudioOperations, loadHydratedCms, loadProjectSession } from '@elytracms/studio/lib/operations'\nimport type {\n HydratedCms,\n HydratedProjectSession,\n OperationResult,\n OperationsClient,\n} from '@elytracms/studio/lib/operations'\nimport { isPlaygroundMode } from '@elytracms/studio/lib/persistence'\nimport {\n configureContentSession,\n isContentSessionWrite,\n markContentSessionStale,\n notePlaygroundWriteForUnloadGuard,\n registerContentSessionRefresher,\n} from '@elytracms/studio/lib/data'\nimport type { ContentSessionState } from '@elytracms/studio/lib/data'\nimport { resolveDeploymentLocaleConfig, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\nimport { HydrationBoundary, useHydrationPhase } from './use-hydration-phase'\n\n/**\n * Project-session hydration gate (Milestone B sign-off fix, EC-177 gap 1).\n *\n * In Convex mode every project session must BOOT from persistence: the\n * builder from the latest persisted graph revision, the CMS from the saved\n * schema + documents, publishing from the persisted environment states.\n * This provider runs the `@elytracms/core/operations` hydration queries\n * (`lib/operations/session-hydration`) and — like `DeploymentSetupGate` — gates its\n * children on a deterministic loading state, so fixture content never\n * flashes (and is never editable) before the persisted state arrives.\n *\n * Playground mode renders children synchronously with `session: null`\n * (fixtures), byte-identical to the pre-hydration behavior — the EC-138\n * determinism contract.\n *\n * Side effect on hydration: the content preview engine\n * (`lib/data/collection-content`) is pointed at the hydrated documents\n * BEFORE children render, so \"Preview with\" lists and section previews show\n * live entries, not fixture titles.\n *\n * EC-361 — the playground gets the SAME side effect, without the render gate:\n * the seeded in-memory adapter IS a persisted backend behind the operations\n * seam (AD-17), so the content session hydrates from it through the identical\n * `loadHydratedCms` loader connected mode uses — fire-and-forget after the\n * synchronous first render (EC-138 stays intact), refreshed per CMS surface\n * mount exactly like connected's `CmsSessionGate` → `loadCms` rhythm. Canvas\n * listing/relation population therefore resolves from in-memory session\n * documents with connected-mode semantics (published-ness = the version\n * pointer; the Draft view resolves live rows) instead of the blanket-empty\n * session the playground used to run with.\n *\n * EC-461 — `CmsSessionGate`/`loadCms` hydrate the CMS surfaces from the\n * adapter in the playground too now. The old EC-320 \"static-baseline rule\"\n * (gate value pinned `null`; CMS sessions re-seeded the fixture baseline on\n * every mount) is retired: it made every cross-section navigation silently\n * revert the visible world to the seed, and a subsequent autosave from that\n * re-seeded session destroyed the adapter's newer record — the EC-276/EC-461\n * playground loss family. Only this provider's OWN `session` value keeps the\n * playground `null` fast-path (boot determinism, EC-138 below).\n *\n * EC-308/AD-17 note — this gate deliberately does NOT read through the\n * `useOperationQuery` cache seam. It is boot/session orchestration, not a\n * cacheable server read: (1) the synchronous playground first-render is the\n * EC-138 determinism contract (a pull-query is async even when disabled);\n * (2) `configureContentSession` must run synchronously BEFORE children mount,\n * which a post-render query effect cannot guarantee; (3) `CmsSessionGate` is\n * intentionally fresh-per-visit (it remounts to show edits persisted since\n * project open) — the opposite of a cached read. It still reads through the\n * operations client (no transport bypass), and uses a user-driven retry epoch,\n * not freshness machinery — so it is outside the patchwork AD-17 retires.\n */\n\nexport interface ProjectSessionStore {\n /** Hydrated persisted state (Convex mode); `null` in playground (fixtures). */\n session: HydratedProjectSession | null\n /**\n * Load the CURRENT persisted CMS state (fresh query — the CMS section\n * remounts per visit and must show edits persisted since project open).\n * EC-461: the playground resolves the seeded in-memory adapter's state the\n * same way (`null` only while nothing is persisted — e.g. an un-baselined\n * kit project), so remounted CMS sessions carry same-session edits.\n */\n loadCms(): Promise<OperationResult<HydratedCms | null>>\n}\n\nconst { Context: ProjectSessionContext, useRequired: useProjectSession } =\n createRequiredContext<ProjectSessionStore>('ProjectSession')\n\n/**\n * Compose the preview content-session state from a hydrated CMS load plus the\n * config-owned deployment seams — the ONE place hydrated content meets config:\n *\n * - `routes` (EC-291): the config-sourced routes, so the canvas `resolveLink`\n * derives a rich-text link's URL exactly as delivery does.\n * - `localeConfig` (EC-385): the active config project's declared locales\n * (AD-11 — locales are structure, structure is code), so canvas content\n * clients evaluate locale-shaped values (EC-018 fallback, localized\n * filter/sort) over the PROJECT's locale set, never the sample `en/de`\n * fixture. Without a config project (env-only wiring) the key stays absent\n * and the preview engine keeps its sample-config default, mirroring the CMS\n * surface ladder (`resolveCmsInitialLocaleConfig`).\n *\n * The parameters exist purely as a unit-test seam (house pattern, same as\n * `resolveCmsInitialCollections`); app code always uses the defaults.\n */\nexport function contentSessionOf(\n cms: HydratedCms | null,\n routes: readonly RouteRecord[] = resolveDeploymentRoutes(),\n localeConfig: LocaleConfig | undefined = resolveDeploymentLocaleConfig(),\n): ContentSessionState | null {\n if (!cms) return null\n return { ...cms, routes, ...(localeConfig ? { localeConfig } : {}) }\n}\n\n/**\n * Load the CURRENT persisted CMS state and point the preview content engine at\n * it — the framework-free core of `loadCms` (both modes run it; EC-361).\n *\n * EC-461 — the gate value is the REAL hydration result in BOTH modes now. The\n * old EC-320 playground contract (`{ ok: true, value: null }` — CMS surfaces\n * keep their static config/fixture baseline forever) was the root of the\n * playground loss family: every cross-section navigation remounted\n * `CmsProvider` from the static seed, so same-session edits vanished from the\n * CMS surfaces even though the in-memory adapter still held them — and any\n * write issued from that re-seeded session (e.g. an autosave) then clobbered\n * the adapter's newer record with the stale baseline. The seeded in-memory\n * adapter IS a persisted backend behind the operations seam (AD-17), so the\n * playground hydrates from it per CMS mount exactly like connected mode\n * hydrates from Convex; a fresh boot returns the deterministic seed, so\n * fixture reproducibility (EC-138) is preserved in substance. A failure is\n * loud twice over: logged here (for the fire-and-forget refresh callers) and\n * rendered as the gate's retryable error state.\n */\nexport async function loadCmsIntoSession(\n operations: OperationsClient,\n projectId: string,\n): Promise<OperationResult<HydratedCms | null>> {\n const result = await loadHydratedCms(operations, projectId)\n // Keep the preview engine in sync with the freshest persisted content.\n if (result.ok && result.value !== null) configureContentSession(contentSessionOf(result.value))\n if (!result.ok) console.error('[elytra] content-session load failed', result.error)\n return result\n}\n\nexport function ProjectSessionProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const playground = isPlaygroundMode()\n\n const loader = useCallback(\n () => getStudioOperations().then(({ operations }) => loadProjectSession(operations, projectId)),\n [projectId],\n )\n // Point the preview content engine at the hydrated documents BEFORE any\n // child renders (panels read it module-level, synchronously), composed with\n // the config-owned routes (EC-291) + locale config (EC-385) via\n // `contentSessionOf`. `enabled: true` below runs this in the playground\n // fast-path too (with `session: null`) — the one divergence from\n // `CmsSessionGate`, which owns no such side effect.\n const onResolved = useCallback((session: HydratedProjectSession | null) => {\n configureContentSession(contentSessionOf(session?.cms ?? null))\n }, [])\n const { phase, retry } = useHydrationPhase<HydratedProjectSession | null>(loader, {\n playground,\n playgroundValue: null,\n enabled: true,\n onResolved,\n })\n\n // Leaving the project resets the preview engine to fixtures; the next\n // project's gate reconfigures it before rendering its own children.\n useEffect(() => () => configureContentSession(null), [])\n\n // EC-361/EC-461: both modes run the SAME load — the content-session side\n // effect plus the REAL hydration value for the CMS gates. (EC-320's\n // playground null-mask is retired: it was the loss family's root — see\n // `loadCmsIntoSession`.)\n const loadCms = useCallback(async (): Promise<OperationResult<HydratedCms | null>> => {\n const { operations } = await getStudioOperations()\n return loadCmsIntoSession(operations, projectId)\n }, [projectId])\n\n // EC-460: keep the preview content session in sync with WRITES. Every\n // succeeded content-bearing command (documents/schema/assets) on the shared\n // operations event bus marks the session stale; the registered refresher —\n // this provider's own `loadCms`, i.e. the identical hydration path both\n // modes already run — re-points it (debounced, and only while a preview\n // consumer is mounted; see `content-session-refresh.ts`). Before this, the\n // session refreshed only at boot + CMS-surface mounts, so a canvas opened\n // after authoring/publishing resolved listings and relation picks against a\n // stale snapshot: the EC-276 empty-listing / 1-of-3-relations family.\n useEffect(() => {\n const unregister = registerContentSessionRefresher(() => loadCms())\n let unsubscribe: (() => void) | undefined\n let cancelled = false\n void getStudioOperations().then(({ operations }) => {\n if (cancelled) return\n unsubscribe = operations.events.subscribe((event) => {\n if (isContentSessionWrite(event)) markContentSessionStale()\n // EC-461: the first authored playground write arms the beforeunload\n // guard for the rest of the page's life (module-level — surviving this\n // provider's unmount, because the in-memory world does too).\n notePlaygroundWriteForUnloadGuard(event)\n })\n })\n return () => {\n cancelled = true\n unregister()\n unsubscribe?.()\n }\n }, [loadCms])\n\n // EC-361: playground boot hydration for the content preview engine — the same\n // loader connected mode gates on, fired WITHOUT gating so the synchronous\n // playground first render (EC-138) is preserved. `loadCms` owns the\n // `configureContentSession` side effect; the resolved value is discarded here.\n // Runs after `useHydrationPhase`'s fast-path effect (registered above) has\n // reset the session, so the hydrated state always lands last.\n useEffect(() => {\n if (playground) void loadCms()\n }, [playground, loadCms])\n\n return (\n <HydrationBoundary phase={phase} retry={retry}>\n {(session) => (\n <ProjectSessionContext.Provider value={{ session, loadCms }}>\n {children}\n </ProjectSessionContext.Provider>\n )}\n </HydrationBoundary>\n )\n}\n\nexport { useProjectSession }\n\n/**\n * Gate one CMS surface mount on a FRESH persisted CMS load — BOTH modes.\n * Render-prop: hands the hydrated content (or `null` → fixture fallback,\n * first-run / un-baselined kit) to the caller once it resolved. Unlike\n * `ProjectSessionProvider`, this gate owns no `configureContentSession` side\n * effect of its own — `loadCms` (from `useProjectSession`) already calls it\n * internally when it resolves — so no `onResolved`/`enabled` is passed here.\n *\n * EC-461: the playground gates on the SAME async hydration as connected mode\n * (over the seeded in-memory adapter, resolved in microtasks). The old\n * synchronous `null` fast-path (EC-320/EC-361) made every remounted CMS\n * session re-seed the STATIC baseline, which both hid and then destroyed\n * same-session edits — the playground loss family. A fresh playground boot\n * hydrates the deterministic seed, so fixture reproducibility is unchanged in\n * substance; only a microtask-long loading frame replaced the synchronous\n * first render HERE (`ProjectSessionProvider`'s EC-138 playground fast-path is\n * untouched).\n */\nexport function CmsSessionGate({\n children,\n}: {\n children: (cms: HydratedCms | null) => ReactNode\n}) {\n const { loadCms } = useProjectSession()\n const { phase, retry } = useHydrationPhase<HydratedCms | null>(loadCms, {\n playground: false,\n playgroundValue: null,\n })\n return (\n <HydrationBoundary phase={phase} retry={retry}>\n {children}\n </HydrationBoundary>\n )\n}\n"]}
1
+ {"version":3,"file":"project-session.js","sourceRoot":"","sources":["../../src/components/project-session.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { LocaleConfig, RouteRecord } from '@elytracms/core/cms-core'\nimport { getStudioOperations, loadHydratedCms, loadProjectSession } from '@elytracms/studio/lib/operations'\nimport type {\n HydratedCms,\n HydratedProjectSession,\n OperationResult,\n OperationsClient,\n} from '@elytracms/studio/lib/operations'\nimport { isPlaygroundMode } from '@elytracms/studio/lib/persistence'\nimport {\n configureContentSession,\n isContentSessionWrite,\n markContentSessionStale,\n notePlaygroundWriteForUnloadGuard,\n registerContentSessionRefresher,\n} from '@elytracms/studio/lib/data'\nimport type { ContentSessionState } from '@elytracms/studio/lib/data'\nimport { resolveDeploymentLocaleConfig, resolveDeploymentRoutes } from '@elytracms/studio/lib/workspace'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\nimport { HydrationBoundary, useHydrationPhase } from './use-hydration-phase'\n\n/**\n * Project-session hydration gate (Milestone B sign-off fix, EC-177 gap 1).\n *\n * In Convex mode every project session must BOOT from persistence: the\n * builder from the latest persisted graph revision, the CMS from the saved\n * schema + documents, publishing from the persisted environment states.\n * This provider runs the `@elytracms/core/operations` hydration queries\n * (`lib/operations/session-hydration`) and — like `DeploymentSetupGate` — gates its\n * children on a deterministic loading state, so fixture content never\n * flashes (and is never editable) before the persisted state arrives.\n *\n * Playground mode renders children synchronously with `session: null`\n * (fixtures), byte-identical to the pre-hydration behavior — the EC-138\n * determinism contract.\n *\n * Side effect on hydration: the content preview engine\n * (`lib/data/collection-content`) is pointed at the hydrated documents\n * BEFORE children render, so \"Preview with\" lists and section previews show\n * live entries, not fixture titles.\n *\n * EC-361 — the playground gets the SAME side effect, without the render gate:\n * the seeded in-memory adapter IS a persisted backend behind the operations\n * seam (AD-17), so the content session hydrates from it through the identical\n * `loadHydratedCms` loader connected mode uses — fire-and-forget after the\n * synchronous first render (EC-138 stays intact), refreshed per CMS surface\n * mount exactly like connected's `CmsSessionGate` → `loadCms` rhythm. Canvas\n * listing/relation population therefore resolves from in-memory session\n * documents with connected-mode semantics (published-ness = the version\n * pointer; the Draft view resolves live rows) instead of the blanket-empty\n * session the playground used to run with.\n *\n * EC-461 — `CmsSessionGate`/`loadCms` hydrate the CMS surfaces from the\n * adapter in the playground too now. The old EC-320 \"static-baseline rule\"\n * (gate value pinned `null`; CMS sessions re-seeded the fixture baseline on\n * every mount) is retired: it made every cross-section navigation silently\n * revert the visible world to the seed, and a subsequent autosave from that\n * re-seeded session destroyed the adapter's newer record — the EC-276/EC-461\n * playground loss family. Only this provider's OWN `session` value keeps the\n * playground `null` fast-path (boot determinism, EC-138 below).\n *\n * EC-308/AD-17 note — this gate deliberately does NOT read through the\n * `useOperationQuery` cache seam. It is boot/session orchestration, not a\n * cacheable server read: (1) the synchronous playground first-render is the\n * EC-138 determinism contract (a pull-query is async even when disabled);\n * (2) `configureContentSession` must run synchronously BEFORE children mount,\n * which a post-render query effect cannot guarantee; (3) `CmsSessionGate` is\n * intentionally fresh-per-visit (it remounts to show edits persisted since\n * project open) — the opposite of a cached read. It still reads through the\n * operations client (no transport bypass), and uses a user-driven retry epoch,\n * not freshness machinery — so it is outside the patchwork AD-17 retires.\n */\n\nexport interface ProjectSessionStore {\n /**\n * The project this session belongs to — the route-resolved `project.id` the\n * provider was mounted with, available synchronously from the first render.\n * EC-574: the canvas surfaces (clipboard source attribution, blocks-panel\n * fragments) used to read it from the Phase-10 collaboration store, whose only\n * other live output was a constant; this is the one per-project provider that\n * replaces it.\n */\n projectId: string\n /** Hydrated persisted state (Convex mode); `null` in playground (fixtures). */\n session: HydratedProjectSession | null\n /**\n * Load the CURRENT persisted CMS state (fresh query — the CMS section\n * remounts per visit and must show edits persisted since project open).\n * EC-461: the playground resolves the seeded in-memory adapter's state the\n * same way (`null` only while nothing is persisted — e.g. an un-baselined\n * kit project), so remounted CMS sessions carry same-session edits.\n */\n loadCms(): Promise<OperationResult<HydratedCms | null>>\n}\n\nconst { Context: ProjectSessionContext, useRequired: useProjectSession } =\n createRequiredContext<ProjectSessionStore>('ProjectSession')\n\n/**\n * Compose the preview content-session state from a hydrated CMS load plus the\n * config-owned deployment seams — the ONE place hydrated content meets config:\n *\n * - `routes` (EC-291): the config-sourced routes, so the canvas `resolveLink`\n * derives a rich-text link's URL exactly as delivery does.\n * - `localeConfig` (EC-385): the active config project's declared locales\n * (AD-11 — locales are structure, structure is code), so canvas content\n * clients evaluate locale-shaped values (EC-018 fallback, localized\n * filter/sort) over the PROJECT's locale set, never the sample `en/de`\n * fixture. Without a config project (env-only wiring) the key stays absent\n * and the preview engine keeps its sample-config default, mirroring the CMS\n * surface ladder (`resolveCmsInitialLocaleConfig`).\n *\n * The parameters exist purely as a unit-test seam (house pattern, same as\n * `resolveCmsInitialCollections`); app code always uses the defaults.\n */\nexport function contentSessionOf(\n cms: HydratedCms | null,\n routes: readonly RouteRecord[] = resolveDeploymentRoutes(),\n localeConfig: LocaleConfig | undefined = resolveDeploymentLocaleConfig(),\n): ContentSessionState | null {\n if (!cms) return null\n return { ...cms, routes, ...(localeConfig ? { localeConfig } : {}) }\n}\n\n/**\n * Load the CURRENT persisted CMS state and point the preview content engine at\n * it — the framework-free core of `loadCms` (both modes run it; EC-361).\n *\n * EC-461 — the gate value is the REAL hydration result in BOTH modes now. The\n * old EC-320 playground contract (`{ ok: true, value: null }` — CMS surfaces\n * keep their static config/fixture baseline forever) was the root of the\n * playground loss family: every cross-section navigation remounted\n * `CmsProvider` from the static seed, so same-session edits vanished from the\n * CMS surfaces even though the in-memory adapter still held them — and any\n * write issued from that re-seeded session (e.g. an autosave) then clobbered\n * the adapter's newer record with the stale baseline. The seeded in-memory\n * adapter IS a persisted backend behind the operations seam (AD-17), so the\n * playground hydrates from it per CMS mount exactly like connected mode\n * hydrates from Convex; a fresh boot returns the deterministic seed, so\n * fixture reproducibility (EC-138) is preserved in substance. A failure is\n * loud twice over: logged here (for the fire-and-forget refresh callers) and\n * rendered as the gate's retryable error state.\n */\nexport async function loadCmsIntoSession(\n operations: OperationsClient,\n projectId: string,\n): Promise<OperationResult<HydratedCms | null>> {\n const result = await loadHydratedCms(operations, projectId)\n // Keep the preview engine in sync with the freshest persisted content.\n if (result.ok && result.value !== null) configureContentSession(contentSessionOf(result.value))\n if (!result.ok) console.error('[elytra] content-session load failed', result.error)\n return result\n}\n\nexport function ProjectSessionProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const playground = isPlaygroundMode()\n\n const loader = useCallback(\n () => getStudioOperations().then(({ operations }) => loadProjectSession(operations, projectId)),\n [projectId],\n )\n // Point the preview content engine at the hydrated documents BEFORE any\n // child renders (panels read it module-level, synchronously), composed with\n // the config-owned routes (EC-291) + locale config (EC-385) via\n // `contentSessionOf`. `enabled: true` below runs this in the playground\n // fast-path too (with `session: null`) — the one divergence from\n // `CmsSessionGate`, which owns no such side effect.\n const onResolved = useCallback((session: HydratedProjectSession | null) => {\n configureContentSession(contentSessionOf(session?.cms ?? null))\n }, [])\n const { phase, retry } = useHydrationPhase<HydratedProjectSession | null>(loader, {\n playground,\n playgroundValue: null,\n enabled: true,\n onResolved,\n })\n\n // Leaving the project resets the preview engine to fixtures; the next\n // project's gate reconfigures it before rendering its own children.\n useEffect(() => () => configureContentSession(null), [])\n\n // EC-361/EC-461: both modes run the SAME load — the content-session side\n // effect plus the REAL hydration value for the CMS gates. (EC-320's\n // playground null-mask is retired: it was the loss family's root — see\n // `loadCmsIntoSession`.)\n const loadCms = useCallback(async (): Promise<OperationResult<HydratedCms | null>> => {\n const { operations } = await getStudioOperations()\n return loadCmsIntoSession(operations, projectId)\n }, [projectId])\n\n // EC-460: keep the preview content session in sync with WRITES. Every\n // succeeded content-bearing command (documents/schema/assets) on the shared\n // operations event bus marks the session stale; the registered refresher —\n // this provider's own `loadCms`, i.e. the identical hydration path both\n // modes already run — re-points it (debounced, and only while a preview\n // consumer is mounted; see `content-session-refresh.ts`). Before this, the\n // session refreshed only at boot + CMS-surface mounts, so a canvas opened\n // after authoring/publishing resolved listings and relation picks against a\n // stale snapshot: the EC-276 empty-listing / 1-of-3-relations family.\n useEffect(() => {\n const unregister = registerContentSessionRefresher(() => loadCms())\n let unsubscribe: (() => void) | undefined\n let cancelled = false\n void getStudioOperations().then(({ operations }) => {\n if (cancelled) return\n unsubscribe = operations.events.subscribe((event) => {\n if (isContentSessionWrite(event)) markContentSessionStale()\n // EC-461: the first authored playground write arms the beforeunload\n // guard for the rest of the page's life (module-level — surviving this\n // provider's unmount, because the in-memory world does too).\n notePlaygroundWriteForUnloadGuard(event)\n })\n })\n return () => {\n cancelled = true\n unregister()\n unsubscribe?.()\n }\n }, [loadCms])\n\n // EC-361: playground boot hydration for the content preview engine — the same\n // loader connected mode gates on, fired WITHOUT gating so the synchronous\n // playground first render (EC-138) is preserved. `loadCms` owns the\n // `configureContentSession` side effect; the resolved value is discarded here.\n // Runs after `useHydrationPhase`'s fast-path effect (registered above) has\n // reset the session, so the hydrated state always lands last.\n useEffect(() => {\n if (playground) void loadCms()\n }, [playground, loadCms])\n\n return (\n <HydrationBoundary phase={phase} retry={retry}>\n {(session) => (\n <ProjectSessionContext.Provider value={{ projectId, session, loadCms }}>\n {children}\n </ProjectSessionContext.Provider>\n )}\n </HydrationBoundary>\n )\n}\n\nexport { useProjectSession }\n\n/**\n * Gate one CMS surface mount on a FRESH persisted CMS load — BOTH modes.\n * Render-prop: hands the hydrated content (or `null` → fixture fallback,\n * first-run / un-baselined kit) to the caller once it resolved. Unlike\n * `ProjectSessionProvider`, this gate owns no `configureContentSession` side\n * effect of its own — `loadCms` (from `useProjectSession`) already calls it\n * internally when it resolves — so no `onResolved`/`enabled` is passed here.\n *\n * EC-461: the playground gates on the SAME async hydration as connected mode\n * (over the seeded in-memory adapter, resolved in microtasks). The old\n * synchronous `null` fast-path (EC-320/EC-361) made every remounted CMS\n * session re-seed the STATIC baseline, which both hid and then destroyed\n * same-session edits — the playground loss family. A fresh playground boot\n * hydrates the deterministic seed, so fixture reproducibility is unchanged in\n * substance; only a microtask-long loading frame replaced the synchronous\n * first render HERE (`ProjectSessionProvider`'s EC-138 playground fast-path is\n * untouched).\n */\nexport function CmsSessionGate({\n children,\n}: {\n children: (cms: HydratedCms | null) => ReactNode\n}) {\n const { loadCms } = useProjectSession()\n const { phase, retry } = useHydrationPhase<HydratedCms | null>(loadCms, {\n playground: false,\n playgroundValue: null,\n })\n return (\n <HydrationBoundary phase={phase} retry={retry}>\n {children}\n </HydrationBoundary>\n )\n}\n"]}
@@ -9,9 +9,24 @@ import type { CollectionViewCapabilities, CollectionViewSource } from '@elytracm
9
9
  * module-level constant (EC-327), mirroring `ASSET_COLLECTION_CAPABILITIES`.
10
10
  */
11
11
  export declare const REDIRECT_COLLECTION_CAPABILITIES: CollectionViewCapabilities;
12
- /** Project a stored redirect into the lens's `values` shape. */
12
+ /**
13
+ * Project a stored redirect into the lens's `values` shape. EC-311 Tier 2:
14
+ * additive schema fields round-trip through the open meta bag, spread FIRST so a
15
+ * canonical column always wins over a same-named meta key (RESIDUE-3c, EC-577):
16
+ * spread last, a stored `meta.to` displayed as the redirect's target and a
17
+ * re-save wrote it into the canonical `to`. The record schema now refuses such a
18
+ * key on write; this order keeps a row stored before that harmless.
19
+ */
13
20
  export declare function redirectToDocument(entry: RedirectEntry): CmsDocument;
14
- /** Map an edited lens document back to the redirect write contract. */
21
+ /**
22
+ * Map an edited lens document back to the redirect write (EC-577). A column the
23
+ * document does not carry is left out of the write, so the stored value is kept:
24
+ * `permanent`/`enabled` get their defaults only when the save CREATES the
25
+ * redirect, applied once inside the adapter's transaction — this lens no longer
26
+ * restates them (`?? true` here was the third copy of the default, RESIDUE-3b).
27
+ * `from`/`to` are always sent: the editor owns them, and an empty one is refused
28
+ * visibly by the write's validation rather than kept silently.
29
+ */
15
30
  export declare function documentToSaveRedirect(doc: CmsDocument): SaveRedirectInput;
16
31
  /**
17
32
  * Assemble a {@link CollectionViewSource} for the **redirects rail** (EC-303) —
@@ -38,22 +38,36 @@ export const REDIRECT_COLLECTION_CAPABILITIES = {
38
38
  * record's open `meta` bag. Derived from the built-in def so the two never drift.
39
39
  */
40
40
  const CANONICAL_REDIRECT_FIELDS = new Set(redirectsCollectionDef.fields.map((f) => f.name));
41
- /** Project a stored redirect into the lens's `values` shape. */
41
+ /**
42
+ * Project a stored redirect into the lens's `values` shape. EC-311 Tier 2:
43
+ * additive schema fields round-trip through the open meta bag, spread FIRST so a
44
+ * canonical column always wins over a same-named meta key (RESIDUE-3c, EC-577):
45
+ * spread last, a stored `meta.to` displayed as the redirect's target and a
46
+ * re-save wrote it into the canonical `to`. The record schema now refuses such a
47
+ * key on write; this order keeps a row stored before that harmless.
48
+ */
42
49
  export function redirectToDocument(entry) {
43
50
  return {
44
51
  collection: redirectsCollectionDef.id,
45
52
  id: entry.id,
46
53
  values: {
54
+ ...(entry.meta ?? {}),
47
55
  from: entry.from,
48
56
  to: entry.to,
49
57
  permanent: entry.permanent,
50
58
  enabled: entry.enabled,
51
- // EC-311 Tier 2: additive schema fields round-trip through the open meta bag.
52
- ...(entry.meta ?? {}),
53
59
  },
54
60
  };
55
61
  }
56
- /** Map an edited lens document back to the redirect write contract. */
62
+ /**
63
+ * Map an edited lens document back to the redirect write (EC-577). A column the
64
+ * document does not carry is left out of the write, so the stored value is kept:
65
+ * `permanent`/`enabled` get their defaults only when the save CREATES the
66
+ * redirect, applied once inside the adapter's transaction — this lens no longer
67
+ * restates them (`?? true` here was the third copy of the default, RESIDUE-3b).
68
+ * `from`/`to` are always sent: the editor owns them, and an empty one is refused
69
+ * visibly by the write's validation rather than kept silently.
70
+ */
57
71
  export function documentToSaveRedirect(doc) {
58
72
  const values = doc.values ?? {};
59
73
  // EC-311 Tier 2: any value keyed outside the canonical set comes from a mount's
@@ -65,8 +79,8 @@ export function documentToSaveRedirect(doc) {
65
79
  id: doc.id,
66
80
  from: typeof values.from === 'string' ? values.from : '',
67
81
  to: typeof values.to === 'string' ? values.to : '',
68
- permanent: typeof values.permanent === 'boolean' ? values.permanent : true,
69
- enabled: typeof values.enabled === 'boolean' ? values.enabled : true,
82
+ ...(typeof values.permanent === 'boolean' ? { permanent: values.permanent } : {}),
83
+ ...(typeof values.enabled === 'boolean' ? { enabled: values.enabled } : {}),
70
84
  ...(Object.keys(meta).length > 0 ? { meta } : {}),
71
85
  };
72
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"redirects-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { CmsDocument, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport { createCollectionRegistry, validateDocument } from '@elytracms/core/cms-core'\nimport type { RedirectEntry, SaveRedirectInput } from '@elytracms/core/persistence'\nimport { studioPath } from '@elytracms/studio/router'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useRedirects } from './redirects-store'\nimport { redirectsCollectionDef } from './redirects-schema'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\n\n/** The redirects rail is not localized — one implicit locale keeps the lens honest. */\nconst REDIRECTS_LOCALE: Locale = 'en'\nconst REDIRECTS_LOCALE_CONFIG: LocaleConfig = {\n default: REDIRECTS_LOCALE,\n locales: [REDIRECTS_LOCALE],\n}\n\n/**\n * The capability profile of the redirects rail (EC-303): content-gated, create\n * + remove, no publish/versions/locales — redirects are CONTENT (an editor\n * owns them), so unlike the users rail this source allows `create`, is NOT\n * admin-only, and needs no principal field-access policy. Hoisted to a\n * module-level constant (EC-327), mirroring `ASSET_COLLECTION_CAPABILITIES`.\n */\nexport const REDIRECT_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: true, // editors create redirects\n duplicate: false, // EC-442 owner call: no rail Duplicate for now (redirects deferred)\n remove: true,\n bulk: true, // EC-433: bulk delete = the same per-id remove, honestly reported\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false, // content, not administration\n}\n\n/**\n * The canonical redirect fields — the storage/delivery contract of the primitive\n * rail. Anything a mount adds via *additive* `nav.redirects({ fields })` (EC-311\n * Tier 2) is by definition NOT one of these, so its value round-trips through the\n * record's open `meta` bag. Derived from the built-in def so the two never drift.\n */\nconst CANONICAL_REDIRECT_FIELDS = new Set(redirectsCollectionDef.fields.map((f) => f.name))\n\n/** Project a stored redirect into the lens's `values` shape. */\nexport function redirectToDocument(entry: RedirectEntry): CmsDocument {\n return {\n collection: redirectsCollectionDef.id,\n id: entry.id,\n values: {\n from: entry.from,\n to: entry.to,\n permanent: entry.permanent,\n enabled: entry.enabled,\n // EC-311 Tier 2: additive schema fields round-trip through the open meta bag.\n ...(entry.meta ?? {}),\n },\n }\n}\n\n/** Map an edited lens document back to the redirect write contract. */\nexport function documentToSaveRedirect(doc: CmsDocument): SaveRedirectInput {\n const values = doc.values ?? {}\n // EC-311 Tier 2: any value keyed outside the canonical set comes from a mount's\n // additive `nav.redirects({ fields })` override — persist it in the meta bag. The\n // editor holds the complete additive set, so this is the whole bag (replace, not\n // merge). Omit `meta` entirely for the default rail so its saves are unchanged.\n const meta = metaBagProjection(values, CANONICAL_REDIRECT_FIELDS)\n return {\n id: doc.id,\n from: typeof values.from === 'string' ? values.from : '',\n to: typeof values.to === 'string' ? values.to : '',\n permanent: typeof values.permanent === 'boolean' ? values.permanent : true,\n enabled: typeof values.enabled === 'boolean' ? values.enabled : true,\n ...(Object.keys(meta).length > 0 ? { meta } : {}),\n }\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **redirects rail** (EC-303) —\n * the portable list/edit surfaces over the editorial redirects store instead of\n * the CMS store. Redirects are CONTENT (an editor owns them), so unlike the users\n * rail this source allows `create`, is NOT admin-only, and needs no principal\n * field-access policy. No publish/versions/locales/inline/import — a redirect\n * is a small record saved explicitly (a saved+enabled one is live); bulk\n * delete rides the shared rail runner (EC-433).\n *\n * Must be mounted under a `RedirectsProvider` (for {@link useRedirects}).\n *\n * `schema` defaults to the engine-owned {@link redirectsCollectionDef}; a mount may\n * pass a customized def (EC-311 — built-in fields overridden by equal name via\n * `nav.redirects({ fields })`). The canonical `from`/`to`/`permanent`/`enabled`\n * names+types are contract-locked, so the projection helpers still read the same\n * canonical values; a mount's *additive* fields (Tier 2) round-trip through the\n * record's open `meta` bag ({@link documentToSaveRedirect}/{@link redirectToDocument}).\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the redirects-store\n * reads/writes, the capability profile, and the registry-driven `validate`.\n */\nexport function useRedirectsCollectionViewSource(\n projectId: string,\n schema: CollectionDef = redirectsCollectionDef,\n): CollectionViewSource {\n const store = useRedirects()\n // EC-311: run the (possibly customized) schema's validation — required/pattern and\n // custom `validate` fns on an overridden field must actually fire in the editor.\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n\n const documents = useCallback(() => store.redirects.map(redirectToDocument), [store.redirects])\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.get(ref.id)\n return record ? redirectToDocument(record) : undefined\n },\n [store],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => store.get(ref.id)?.from || '(new redirect)',\n [store],\n )\n const validate = useCallback(\n (doc: CmsDocument) => validateDocument(registry, doc, { localeConfig: REDIRECTS_LOCALE_CONFIG }),\n [registry],\n )\n const create = useCallback(\n (): DocumentRef => ({ collection: redirectsCollectionDef.id, id: store.createDraft() }),\n [store],\n )\n const save = useCallback((doc: CmsDocument) => store.save(documentToSaveRedirect(doc)), [store])\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities: REDIRECT_COLLECTION_CAPABILITIES,\n localeConfig: REDIRECTS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections` override: redirects carry no\n // relation/asset fields, so nothing cross-collection to resolve (the\n // factory's `() => []` / `[schema]` defaults apply).\n detailPath: (id: string) => studioPath.redirect(id),\n listPath: studioPath.redirects(),\n validate,\n create,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n"]}
1
+ {"version":3,"file":"redirects-collection-view-source.js","sourceRoot":"","sources":["../../../src/components/redirects/redirects-collection-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo } from 'react'\nimport type { CmsDocument, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport { createCollectionRegistry, validateDocument } from '@elytracms/core/cms-core'\nimport type { RedirectEntry, SaveRedirectInput } from '@elytracms/core/persistence'\nimport { studioPath } from '@elytracms/studio/router'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n} from '@elytracms/studio/lib/collection-view/collection-view-source'\nimport { useRedirects } from './redirects-store'\nimport { redirectsCollectionDef } from './redirects-schema'\nimport { metaBagProjection, normalizeOkResult, useRailViewSource } from '../rail-view-source'\n\n/** The redirects rail is not localized — one implicit locale keeps the lens honest. */\nconst REDIRECTS_LOCALE: Locale = 'en'\nconst REDIRECTS_LOCALE_CONFIG: LocaleConfig = {\n default: REDIRECTS_LOCALE,\n locales: [REDIRECTS_LOCALE],\n}\n\n/**\n * The capability profile of the redirects rail (EC-303): content-gated, create\n * + remove, no publish/versions/locales — redirects are CONTENT (an editor\n * owns them), so unlike the users rail this source allows `create`, is NOT\n * admin-only, and needs no principal field-access policy. Hoisted to a\n * module-level constant (EC-327), mirroring `ASSET_COLLECTION_CAPABILITIES`.\n */\nexport const REDIRECT_COLLECTION_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: true, // editors create redirects\n duplicate: false, // EC-442 owner call: no rail Duplicate for now (redirects deferred)\n remove: true,\n bulk: true, // EC-433: bulk delete = the same per-id remove, honestly reported\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false, // content, not administration\n}\n\n/**\n * The canonical redirect fields — the storage/delivery contract of the primitive\n * rail. Anything a mount adds via *additive* `nav.redirects({ fields })` (EC-311\n * Tier 2) is by definition NOT one of these, so its value round-trips through the\n * record's open `meta` bag. Derived from the built-in def so the two never drift.\n */\nconst CANONICAL_REDIRECT_FIELDS = new Set(redirectsCollectionDef.fields.map((f) => f.name))\n\n/**\n * Project a stored redirect into the lens's `values` shape. EC-311 Tier 2:\n * additive schema fields round-trip through the open meta bag, spread FIRST so a\n * canonical column always wins over a same-named meta key (RESIDUE-3c, EC-577):\n * spread last, a stored `meta.to` displayed as the redirect's target and a\n * re-save wrote it into the canonical `to`. The record schema now refuses such a\n * key on write; this order keeps a row stored before that harmless.\n */\nexport function redirectToDocument(entry: RedirectEntry): CmsDocument {\n return {\n collection: redirectsCollectionDef.id,\n id: entry.id,\n values: {\n ...(entry.meta ?? {}),\n from: entry.from,\n to: entry.to,\n permanent: entry.permanent,\n enabled: entry.enabled,\n },\n }\n}\n\n/**\n * Map an edited lens document back to the redirect write (EC-577). A column the\n * document does not carry is left out of the write, so the stored value is kept:\n * `permanent`/`enabled` get their defaults only when the save CREATES the\n * redirect, applied once inside the adapter's transaction — this lens no longer\n * restates them (`?? true` here was the third copy of the default, RESIDUE-3b).\n * `from`/`to` are always sent: the editor owns them, and an empty one is refused\n * visibly by the write's validation rather than kept silently.\n */\nexport function documentToSaveRedirect(doc: CmsDocument): SaveRedirectInput {\n const values = doc.values ?? {}\n // EC-311 Tier 2: any value keyed outside the canonical set comes from a mount's\n // additive `nav.redirects({ fields })` override — persist it in the meta bag. The\n // editor holds the complete additive set, so this is the whole bag (replace, not\n // merge). Omit `meta` entirely for the default rail so its saves are unchanged.\n const meta = metaBagProjection(values, CANONICAL_REDIRECT_FIELDS)\n return {\n id: doc.id,\n from: typeof values.from === 'string' ? values.from : '',\n to: typeof values.to === 'string' ? values.to : '',\n ...(typeof values.permanent === 'boolean' ? { permanent: values.permanent } : {}),\n ...(typeof values.enabled === 'boolean' ? { enabled: values.enabled } : {}),\n ...(Object.keys(meta).length > 0 ? { meta } : {}),\n }\n}\n\n/**\n * Assemble a {@link CollectionViewSource} for the **redirects rail** (EC-303) —\n * the portable list/edit surfaces over the editorial redirects store instead of\n * the CMS store. Redirects are CONTENT (an editor owns them), so unlike the users\n * rail this source allows `create`, is NOT admin-only, and needs no principal\n * field-access policy. No publish/versions/locales/inline/import — a redirect\n * is a small record saved explicitly (a saved+enabled one is live); bulk\n * delete rides the shared rail runner (EC-433).\n *\n * Must be mounted under a `RedirectsProvider` (for {@link useRedirects}).\n *\n * `schema` defaults to the engine-owned {@link redirectsCollectionDef}; a mount may\n * pass a customized def (EC-311 — built-in fields overridden by equal name via\n * `nav.redirects({ fields })`). The canonical `from`/`to`/`permanent`/`enabled`\n * names+types are contract-locked, so the projection helpers still read the same\n * canonical values; a mount's *additive* fields (Tier 2) round-trip through the\n * record's open `meta` bag ({@link documentToSaveRedirect}/{@link redirectToDocument}).\n *\n * The shared scaffold (`useRailViewSource`, EC-327) owns `viewState`/`lastError`\n * plumbing and the assembled source; this hook supplies only the redirects-store\n * reads/writes, the capability profile, and the registry-driven `validate`.\n */\nexport function useRedirectsCollectionViewSource(\n projectId: string,\n schema: CollectionDef = redirectsCollectionDef,\n): CollectionViewSource {\n const store = useRedirects()\n // EC-311: run the (possibly customized) schema's validation — required/pattern and\n // custom `validate` fns on an overridden field must actually fire in the editor.\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n\n const documents = useCallback(() => store.redirects.map(redirectToDocument), [store.redirects])\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.get(ref.id)\n return record ? redirectToDocument(record) : undefined\n },\n [store],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => store.get(ref.id)?.from || '(new redirect)',\n [store],\n )\n const validate = useCallback(\n (doc: CmsDocument) => validateDocument(registry, doc, { localeConfig: REDIRECTS_LOCALE_CONFIG }),\n [registry],\n )\n const create = useCallback(\n (): DocumentRef => ({ collection: redirectsCollectionDef.id, id: store.createDraft() }),\n [store],\n )\n const save = useCallback((doc: CmsDocument) => store.save(documentToSaveRedirect(doc)), [store])\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities: REDIRECT_COLLECTION_CAPABILITIES,\n localeConfig: REDIRECTS_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n // No `referenceCandidates`/`collections` override: redirects carry no\n // relation/asset fields, so nothing cross-collection to resolve (the\n // factory's `() => []` / `[schema]` defaults apply).\n detailPath: (id: string) => studioPath.redirect(id),\n listPath: studioPath.redirects(),\n validate,\n create,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { Fragment, useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';
3
3
  import { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router';
4
4
  import { ChevronDown, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
5
5
  import { cn } from '@elytracms/studio/lib/utils';
@@ -7,9 +7,10 @@ import { readCollapsedViews, storeCollapsedViews } from '@elytracms/studio/lib/r
7
7
  import { getStudioRegistry } from '@elytracms/studio/lib/registry';
8
8
  import { projectConfigIssues, resolveDeploymentCollections, resolveDeploymentReuse, resolveDeploymentRoutes, } from '@elytracms/studio/lib/workspace';
9
9
  import { routeValidationIssues } from '@elytracms/studio/lib/routes/route-issues';
10
+ import { contentSessionOrphans, serverContentSessionOrphans, subscribeContentSession, } from '@elytracms/studio/lib/data';
10
11
  import { translate } from '@elytracms/studio/lib/i18n';
11
12
  import { Text } from '@elytracms/studio/ui';
12
- import { NavIssues, customizationItems, routeIssueItems } from './nav-issues.js';
13
+ import { NavIssues, customizationItems, orphanedCollectionItems, routeIssueItems } from './nav-issues.js';
13
14
  import { activeViewIdFromPath, isNavViewVisible, useStudioNav } from './app-views/sidebar.js';
14
15
  import { resolveReuse } from './builder/reuse.js';
15
16
  import { useRedirectList } from './redirects/redirects-store.js';
@@ -34,11 +35,13 @@ export function SectionNav({ project, collapsed, onToggleCollapsed }) {
34
35
  const reuseIssues = useReuseIssues();
35
36
  const projectId = project.id;
36
37
  const routeIssues = useRouteIssues(projectId);
38
+ const orphanIssues = useOrphanedCollectionIssues();
37
39
  // Config issues lead: "this project declares no collections" explains every
38
40
  // other emptiness on the screen, so it is the first thing to read.
39
41
  const issues = [
40
42
  ...customizationItems([...configIssues, ...navIssues, ...rendererIssues, ...reuseIssues]),
41
43
  ...routeIssues,
44
+ ...orphanIssues,
42
45
  ];
43
46
  // EC-240 (AD-18): per-nav-item, default-open permission gating. The signed-in
44
47
  // user's role drives an OPTIONAL `visible` predicate on any view; absent →
@@ -150,6 +153,27 @@ function useRouteIssues(projectId) {
150
153
  const redirects = useRedirectList(projectId).data;
151
154
  return useMemo(() => routeIssueItems(routeValidationIssues(resolveDeploymentRoutes(), redirects ?? [])), [redirects]);
152
155
  }
156
+ /**
157
+ * EC-606 — stored documents whose collection the project does not declare.
158
+ *
159
+ * Read from the content session, not the project session: this rail renders in
160
+ * `StudioShell`, OUTSIDE `ProjectSessionProvider` (`studio-project-screen.tsx`), so
161
+ * `useProjectSession` is unreachable here — and the provider's own `session` value is
162
+ * `null` in the playground anyway. The content session is the channel both modes
163
+ * already configure from the same `loadHydratedCms` result
164
+ * (`configureContentSession(contentSessionOf(cms))`), and it is subscribable, so the
165
+ * foot re-renders whenever it is re-pointed: at boot, on every CMS-surface mount, and
166
+ * on the write-sync refresh while a canvas is mounted (`content-session-refresh.ts`).
167
+ * The rail deliberately does NOT register as a refresh consumer — connected mode would
168
+ * then pay a full hydration per autosave for a finding that changes only when config
169
+ * changes or orphan rows are removed. The server snapshot is empty: there is no
170
+ * session during SSR, so hydration renders no warning and the client adds it after.
171
+ * The pure mapping is {@link orphanedCollectionItems}.
172
+ */
173
+ function useOrphanedCollectionIssues() {
174
+ const orphans = useSyncExternalStore(subscribeContentSession, contentSessionOrphans, serverContentSessionOrphans);
175
+ return useMemo(() => orphanedCollectionItems(orphans), [orphans]);
176
+ }
153
177
  /** One rail view (top-level or nested) — a Link to its home route, active by view id. */
154
178
  function NavViewLink({ projectId, view, activeViewId, collapsed, nested = false, hasActiveChild = false, collapsible = false, expanded = true, onToggleExpanded, }) {
155
179
  const isActive = view.id === activeViewId;
@@ -1 +1 @@
1
- {"version":3,"file":"section-nav.js","sourceRoot":"","sources":["../../src/components/section-nav.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\nimport { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router'\nimport { ChevronDown, PanelLeftClose, PanelLeftOpen } from 'lucide-react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { readCollapsedViews, storeCollapsedViews } from '@elytracms/studio/lib/rail'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport {\n projectConfigIssues,\n resolveDeploymentCollections,\n resolveDeploymentReuse,\n resolveDeploymentRoutes,\n} from '@elytracms/studio/lib/workspace'\nimport { routeValidationIssues } from '@elytracms/studio/lib/routes/route-issues'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Text } from '@elytracms/studio/ui'\nimport { NavIssues, customizationItems, routeIssueItems } from './nav-issues'\nimport type { NavIssueItem } from './nav-issues'\nimport { activeViewIdFromPath, isNavViewVisible, useStudioNav } from './app-views/sidebar'\nimport type { ResolvedRailView } from './app-views/sidebar'\nimport { resolveReuse } from './builder/reuse'\nimport { useRedirectList } from './redirects/redirects-store'\nimport { useFieldRenderers } from './cms/field-renderer'\nimport { useProjectRole } from './auth-context'\nimport type { NavPermissionContext } from './app-views/module'\n\nexport interface SectionNavProps {\n project: Project\n /** User-toggled collapse (EC-214) — icons only when collapsed, persisted by the shell. */\n collapsed: boolean\n onToggleCollapsed: () => void\n}\n\n/**\n * Vertical section rail (brief §8.1), driven by the configurable sidebar (EC-237):\n * a consumer-extendable tree of views / groups / separators resolved over the\n * built-in views (EC-239). Availability-aware (`unavailable` items are absent per\n * EC-221); the active item is matched by the VIEW ID in the URL, so two views\n * backed by the same surface (Content vs. a focused Categories) highlight\n * independently. The preference-persisted collapse and the strong active marker\n * (EC-214) are preserved. Customization issues (duplicate view id, invalid view,\n * non-component renderers) render at the rail foot — visible, never silent.\n */\nexport function SectionNav({ project, collapsed, onToggleCollapsed }: SectionNavProps) {\n const { pathname } = useLocation()\n const activeViewId = activeViewIdFromPath(pathname)\n const { nodes, issues: navIssues } = useStudioNav(project)\n const { issues: rendererIssues } = useFieldRenderers()\n const configIssues = useProjectConfigIssues()\n const reuseIssues = useReuseIssues()\n const projectId = project.id\n const routeIssues = useRouteIssues(projectId)\n // Config issues lead: \"this project declares no collections\" explains every\n // other emptiness on the screen, so it is the first thing to read.\n const issues = [\n ...customizationItems([...configIssues, ...navIssues, ...rendererIssues, ...reuseIssues]),\n ...routeIssues,\n ]\n\n // EC-240 (AD-18): per-nav-item, default-open permission gating. The signed-in\n // user's role drives an OPTIONAL `visible` predicate on any view; absent →\n // visible. UI reflection only — the server chokepoint stays the real boundary,\n // and a permission-hidden view is still reachable by URL (EC-221).\n const role = useProjectRole(projectId)\n const navCtx: NavPermissionContext = { project, role }\n const isVisible = (view: ResolvedRailView) => isNavViewVisible(view, navCtx)\n\n // EC-293: per-parent collapse of nested sub-views, persisted (read after mount,\n // like the rail-collapse pref — no flash script needed). A collapsed parent hides\n // its children but stays a navigable link.\n const [collapsedViews, setCollapsedViews] = useState<Set<string>>(() => new Set())\n useEffect(() => {\n setCollapsedViews(readCollapsedViews())\n }, [])\n const toggleViewCollapsed = useCallback((id: string) => {\n setCollapsedViews((prev) => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n storeCollapsedViews(next)\n return next\n })\n }, [])\n\n return (\n <nav\n aria-label={translate('shell.nav.label')}\n className={cn(\n 'flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150',\n collapsed ? 'w-[3.5rem]' : 'w-56',\n )}\n >\n {nodes.map((node, index) => {\n if (node.kind === 'separator') {\n return <div key={`sep-${index}`} className=\"my-1.5 border-t border-border/60\" aria-hidden />\n }\n if (node.kind === 'group') {\n const views = node.views.filter(\n (view) => view.availability !== 'unavailable' && isVisible(view),\n )\n if (views.length === 0) return null\n return (\n <div key={`group-${node.label}-${index}`} className=\"mt-2 first:mt-0\">\n {collapsed ? (\n <div className=\"my-1 border-t border-border/60\" aria-hidden />\n ) : (\n <Text\n size=\"xs\"\n tone=\"subtle\"\n className=\"px-3 pb-1 pt-1 font-medium uppercase tracking-wide\"\n >\n {node.label}\n </Text>\n )}\n {views.map((view) => (\n <NavViewLink\n key={view.id}\n projectId={projectId}\n view={view}\n activeViewId={activeViewId}\n collapsed={collapsed}\n />\n ))}\n </div>\n )\n }\n // EC-221: an unavailable view is absent from the rail (not greyed) — it\n // stays reachable by URL, the rail simply doesn't advertise it. EC-240: a\n // permission-hidden view is likewise absent-but-addressable.\n if (node.availability === 'unavailable' || !isVisible(node)) return null\n // EC-293: a view may nest its sub-views (CMS collections). The parent renders\n // as a link with a disclosure chevron; its children render indented below,\n // hidden when the rail is collapsed OR this parent is collapsed. The parent\n // gets a subtle active hint when one of its children is the active view.\n const hasChildren = (node.children?.length ?? 0) > 0\n const hasActiveChild = node.children?.some((child) => child.id === activeViewId) ?? false\n const viewCollapsed = collapsedViews.has(node.id)\n return (\n <Fragment key={node.id}>\n <NavViewLink\n projectId={projectId}\n view={node}\n activeViewId={activeViewId}\n collapsed={collapsed}\n hasActiveChild={hasActiveChild}\n collapsible={hasChildren && !collapsed}\n expanded={!viewCollapsed}\n onToggleExpanded={() => toggleViewCollapsed(node.id)}\n />\n {collapsed || viewCollapsed\n ? null\n : node.children?.filter(isVisible).map((child) => (\n <NavViewLink\n key={child.id}\n projectId={projectId}\n view={child}\n activeViewId={activeViewId}\n collapsed={collapsed}\n nested\n />\n ))}\n </Fragment>\n )\n })}\n\n <div className=\"mt-auto flex flex-col gap-0.5\">\n {issues.length > 0 ? <NavIssues issues={issues} collapsed={collapsed} /> : null}\n\n {/* Collapse toggle pinned to the rail foot (EC-214). */}\n <button\n type=\"button\"\n onClick={onToggleCollapsed}\n aria-label={collapsed ? translate('shell.sidebar.expand') : translate('shell.sidebar.collapse')}\n aria-pressed={collapsed}\n title={collapsed ? translate('shell.sidebar.expand') : translate('shell.sidebar.collapse')}\n className={cn(\n 'flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',\n collapsed ? 'justify-center px-0' : 'px-3',\n )}\n >\n {collapsed ? (\n <PanelLeftOpen className=\"size-4 shrink-0\" />\n ) : (\n <>\n <PanelLeftClose className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate text-left\">{translate('shell.sidebar.collapseLabel')}</span>\n </>\n )}\n </button>\n </div>\n </nav>\n )\n}\n\n/**\n * EC-479 — what the project has not declared but the studio cannot work without.\n *\n * Joins the rail foot for the same reason EC-467's and EC-469's findings do: it is a\n * project-CONFIGURATION problem, not a content problem in one document, and the rail\n * is on every studio surface. It exists at all because the engine used to hide this\n * state behind its own `cms-core` fixtures — an unconfigured studio showed sample\n * collections and sample documents as if they were the project's. Removing that\n * fallback without reporting the absence would just trade a wrong screen for a blank\n * one, which the house rule bans equally.\n *\n * Read from the config holder alone (static per studio boot, like `useReuseIssues`),\n * so it needs no CMS provider and is memoised once. The pure decision core is\n * `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\n * cannot resolve, or an ambiguous inference the studio refuses to guess through.\n * They join the rail foot because they are project-CONFIGURATION problems (like the\n * rail's own and the field renderers'), not content problems in one document — and\n * the rail is on every studio surface, so the developer cannot miss them. A project\n * with an unambiguous embed, or with none at all, contributes nothing here.\n *\n * Resolved from the config holders alone (registry + config collections + studio\n * config), all static per studio boot, so this needs no CMS provider and is\n * memoised once. The pure decision core is `resolveReuse`.\n */\nfunction useReuseIssues(): CustomizationIssue[] {\n return useMemo(\n () =>\n resolveReuse(\n getStudioRegistry().registry.list(),\n resolveDeploymentCollections(),\n resolveDeploymentReuse(),\n ).issues,\n [],\n )\n}\n\n/**\n * EC-469 — the project's URL-map issues: two routes that match the same URLs, or a\n * redirect chain that loops.\n *\n * They join the rail foot for the same reason EC-467's reusable-elements issues do:\n * they are project-level problems, not content problems in one document, so the\n * document-scoped issues pane has no place for them — and the rail is on every studio\n * surface, so the developer cannot miss them. This is emphatically not a revived\n * Routes view (EC-303 retired that, correctly): nothing here is editable, it is\n * validation of the code + data the studio already reads.\n *\n * The two halves come from different planes, which is why only the studio can show\n * both: routes are CODE (config, static per boot) while redirects are editorial DATA\n * (the rail, live). The redirects read shares the rail's own query key, so a loop\n * disappears from the foot the moment it is fixed — and while that query is loading or\n * failed, `data` is absent and only the code-side conflicts are reported. That is\n * honest: the rail itself surfaces its own load error.\n *\n * Copy follows the issues-pane convention (EC-192) and is mapped by\n * {@link routeIssueItems} — severity included since EC-493.\n */\nfunction useRouteIssues(projectId: string): NavIssueItem[] {\n const redirects = useRedirectList(projectId).data\n return useMemo(\n () => routeIssueItems(routeValidationIssues(resolveDeploymentRoutes(), redirects ?? [])),\n [redirects],\n )\n}\n\n/** One rail view (top-level or nested) — a Link to its home route, active by view id. */\nfunction NavViewLink({\n projectId,\n view,\n activeViewId,\n collapsed,\n nested = false,\n hasActiveChild = false,\n collapsible = false,\n expanded = true,\n onToggleExpanded,\n}: {\n projectId: string\n view: ResolvedRailView\n activeViewId: string | null\n collapsed: boolean\n nested?: boolean\n /** EC-293: this parent's a child is the active view → a subtle (non-accent) hint. */\n hasActiveChild?: boolean\n /** EC-293: this parent has nested children → render a disclosure chevron. */\n collapsible?: boolean\n /** Whether the nested children are shown (chevron points down) or hidden. */\n expanded?: boolean\n /** Toggle this parent's collapse (the chevron click — does not navigate). */\n onToggleExpanded?: () => void\n}) {\n const isActive = view.id === activeViewId\n const Icon = view.icon\n const link = (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: view.path }}\n title={view.label}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'group relative flex h-9 items-center gap-2.5 rounded-md text-sm transition-colors',\n // Collapsible parents reserve room on the right for the chevron toggle.\n collapsed ? 'justify-center px-0' : nested ? 'pl-7 pr-3' : collapsible ? 'pl-3 pr-9' : 'px-3',\n isActive\n ? 'bg-accent font-medium text-accent-foreground'\n : hasActiveChild\n ? 'text-foreground hover:bg-muted'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {/* Strong active marker: a leading accent bar (EC-214). */}\n {isActive ? (\n <span className=\"absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary\" aria-hidden />\n ) : null}\n <Icon className=\"size-4 shrink-0\" />\n {collapsed ? null : <span className=\"min-w-0 flex-1 truncate\">{view.label}</span>}\n {view.availability === 'planned' && !collapsed ? (\n <span className=\"size-1.5 shrink-0 rounded-full bg-warning\" aria-hidden title={translate('shell.nav.planned')} />\n ) : null}\n </Link>\n )\n // A leaf (or the collapsed icons-only rail) is just the link.\n if (!collapsible) return link\n // A parent pairs the link with a sibling chevron toggle (so the chevron collapses\n // without navigating, and the link still navigates to the parent surface).\n return (\n <div className=\"relative\">\n {link}\n <button\n type=\"button\"\n onClick={onToggleExpanded}\n aria-label={translate(expanded ? 'shell.nav.collapseView' : 'shell.nav.expandView', {\n name: view.label,\n })}\n aria-expanded={expanded}\n className=\"absolute right-1 top-1/2 grid size-6 -translate-y-1/2 place-items-center rounded text-muted-foreground/70 transition-colors hover:bg-background hover:text-foreground\"\n >\n <ChevronDown\n className={cn('size-3.5 transition-transform', expanded ? '' : '-rotate-90')}\n aria-hidden\n />\n </button>\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"section-nav.js","sourceRoot":"","sources":["../../src/components/section-nav.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Fragment, useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport { Link, STUDIO_PROJECT_ROUTE, useLocation } from '@elytracms/studio/router'\nimport { ChevronDown, PanelLeftClose, PanelLeftOpen } from 'lucide-react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport type { CustomizationIssue } from '@elytracms/core/studio-core'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { readCollapsedViews, storeCollapsedViews } from '@elytracms/studio/lib/rail'\nimport { getStudioRegistry } from '@elytracms/studio/lib/registry'\nimport {\n projectConfigIssues,\n resolveDeploymentCollections,\n resolveDeploymentReuse,\n resolveDeploymentRoutes,\n} from '@elytracms/studio/lib/workspace'\nimport { routeValidationIssues } from '@elytracms/studio/lib/routes/route-issues'\nimport {\n contentSessionOrphans,\n serverContentSessionOrphans,\n subscribeContentSession,\n} from '@elytracms/studio/lib/data'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { Text } from '@elytracms/studio/ui'\nimport { NavIssues, customizationItems, orphanedCollectionItems, routeIssueItems } from './nav-issues'\nimport type { NavIssueItem } from './nav-issues'\nimport { activeViewIdFromPath, isNavViewVisible, useStudioNav } from './app-views/sidebar'\nimport type { ResolvedRailView } from './app-views/sidebar'\nimport { resolveReuse } from './builder/reuse'\nimport { useRedirectList } from './redirects/redirects-store'\nimport { useFieldRenderers } from './cms/field-renderer'\nimport { useProjectRole } from './auth-context'\nimport type { NavPermissionContext } from './app-views/module'\n\nexport interface SectionNavProps {\n project: Project\n /** User-toggled collapse (EC-214) — icons only when collapsed, persisted by the shell. */\n collapsed: boolean\n onToggleCollapsed: () => void\n}\n\n/**\n * Vertical section rail (brief §8.1), driven by the configurable sidebar (EC-237):\n * a consumer-extendable tree of views / groups / separators resolved over the\n * built-in views (EC-239). Availability-aware (`unavailable` items are absent per\n * EC-221); the active item is matched by the VIEW ID in the URL, so two views\n * backed by the same surface (Content vs. a focused Categories) highlight\n * independently. The preference-persisted collapse and the strong active marker\n * (EC-214) are preserved. Customization issues (duplicate view id, invalid view,\n * non-component renderers) render at the rail foot — visible, never silent.\n */\nexport function SectionNav({ project, collapsed, onToggleCollapsed }: SectionNavProps) {\n const { pathname } = useLocation()\n const activeViewId = activeViewIdFromPath(pathname)\n const { nodes, issues: navIssues } = useStudioNav(project)\n const { issues: rendererIssues } = useFieldRenderers()\n const configIssues = useProjectConfigIssues()\n const reuseIssues = useReuseIssues()\n const projectId = project.id\n const routeIssues = useRouteIssues(projectId)\n const orphanIssues = useOrphanedCollectionIssues()\n // Config issues lead: \"this project declares no collections\" explains every\n // other emptiness on the screen, so it is the first thing to read.\n const issues = [\n ...customizationItems([...configIssues, ...navIssues, ...rendererIssues, ...reuseIssues]),\n ...routeIssues,\n ...orphanIssues,\n ]\n\n // EC-240 (AD-18): per-nav-item, default-open permission gating. The signed-in\n // user's role drives an OPTIONAL `visible` predicate on any view; absent →\n // visible. UI reflection only — the server chokepoint stays the real boundary,\n // and a permission-hidden view is still reachable by URL (EC-221).\n const role = useProjectRole(projectId)\n const navCtx: NavPermissionContext = { project, role }\n const isVisible = (view: ResolvedRailView) => isNavViewVisible(view, navCtx)\n\n // EC-293: per-parent collapse of nested sub-views, persisted (read after mount,\n // like the rail-collapse pref — no flash script needed). A collapsed parent hides\n // its children but stays a navigable link.\n const [collapsedViews, setCollapsedViews] = useState<Set<string>>(() => new Set())\n useEffect(() => {\n setCollapsedViews(readCollapsedViews())\n }, [])\n const toggleViewCollapsed = useCallback((id: string) => {\n setCollapsedViews((prev) => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n storeCollapsedViews(next)\n return next\n })\n }, [])\n\n return (\n <nav\n aria-label={translate('shell.nav.label')}\n className={cn(\n 'flex shrink-0 flex-col gap-0.5 border-r border-border bg-card/40 p-2 transition-[width] duration-150',\n collapsed ? 'w-[3.5rem]' : 'w-56',\n )}\n >\n {nodes.map((node, index) => {\n if (node.kind === 'separator') {\n return <div key={`sep-${index}`} className=\"my-1.5 border-t border-border/60\" aria-hidden />\n }\n if (node.kind === 'group') {\n const views = node.views.filter(\n (view) => view.availability !== 'unavailable' && isVisible(view),\n )\n if (views.length === 0) return null\n return (\n <div key={`group-${node.label}-${index}`} className=\"mt-2 first:mt-0\">\n {collapsed ? (\n <div className=\"my-1 border-t border-border/60\" aria-hidden />\n ) : (\n <Text\n size=\"xs\"\n tone=\"subtle\"\n className=\"px-3 pb-1 pt-1 font-medium uppercase tracking-wide\"\n >\n {node.label}\n </Text>\n )}\n {views.map((view) => (\n <NavViewLink\n key={view.id}\n projectId={projectId}\n view={view}\n activeViewId={activeViewId}\n collapsed={collapsed}\n />\n ))}\n </div>\n )\n }\n // EC-221: an unavailable view is absent from the rail (not greyed) — it\n // stays reachable by URL, the rail simply doesn't advertise it. EC-240: a\n // permission-hidden view is likewise absent-but-addressable.\n if (node.availability === 'unavailable' || !isVisible(node)) return null\n // EC-293: a view may nest its sub-views (CMS collections). The parent renders\n // as a link with a disclosure chevron; its children render indented below,\n // hidden when the rail is collapsed OR this parent is collapsed. The parent\n // gets a subtle active hint when one of its children is the active view.\n const hasChildren = (node.children?.length ?? 0) > 0\n const hasActiveChild = node.children?.some((child) => child.id === activeViewId) ?? false\n const viewCollapsed = collapsedViews.has(node.id)\n return (\n <Fragment key={node.id}>\n <NavViewLink\n projectId={projectId}\n view={node}\n activeViewId={activeViewId}\n collapsed={collapsed}\n hasActiveChild={hasActiveChild}\n collapsible={hasChildren && !collapsed}\n expanded={!viewCollapsed}\n onToggleExpanded={() => toggleViewCollapsed(node.id)}\n />\n {collapsed || viewCollapsed\n ? null\n : node.children?.filter(isVisible).map((child) => (\n <NavViewLink\n key={child.id}\n projectId={projectId}\n view={child}\n activeViewId={activeViewId}\n collapsed={collapsed}\n nested\n />\n ))}\n </Fragment>\n )\n })}\n\n <div className=\"mt-auto flex flex-col gap-0.5\">\n {issues.length > 0 ? <NavIssues issues={issues} collapsed={collapsed} /> : null}\n\n {/* Collapse toggle pinned to the rail foot (EC-214). */}\n <button\n type=\"button\"\n onClick={onToggleCollapsed}\n aria-label={collapsed ? translate('shell.sidebar.expand') : translate('shell.sidebar.collapse')}\n aria-pressed={collapsed}\n title={collapsed ? translate('shell.sidebar.expand') : translate('shell.sidebar.collapse')}\n className={cn(\n 'flex h-9 items-center gap-2.5 rounded-md text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',\n collapsed ? 'justify-center px-0' : 'px-3',\n )}\n >\n {collapsed ? (\n <PanelLeftOpen className=\"size-4 shrink-0\" />\n ) : (\n <>\n <PanelLeftClose className=\"size-4 shrink-0\" />\n <span className=\"min-w-0 flex-1 truncate text-left\">{translate('shell.sidebar.collapseLabel')}</span>\n </>\n )}\n </button>\n </div>\n </nav>\n )\n}\n\n/**\n * EC-479 — what the project has not declared but the studio cannot work without.\n *\n * Joins the rail foot for the same reason EC-467's and EC-469's findings do: it is a\n * project-CONFIGURATION problem, not a content problem in one document, and the rail\n * is on every studio surface. It exists at all because the engine used to hide this\n * state behind its own `cms-core` fixtures — an unconfigured studio showed sample\n * collections and sample documents as if they were the project's. Removing that\n * fallback without reporting the absence would just trade a wrong screen for a blank\n * one, which the house rule bans equally.\n *\n * Read from the config holder alone (static per studio boot, like `useReuseIssues`),\n * so it needs no CMS provider and is memoised once. The pure decision core is\n * `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\n * cannot resolve, or an ambiguous inference the studio refuses to guess through.\n * They join the rail foot because they are project-CONFIGURATION problems (like the\n * rail's own and the field renderers'), not content problems in one document — and\n * the rail is on every studio surface, so the developer cannot miss them. A project\n * with an unambiguous embed, or with none at all, contributes nothing here.\n *\n * Resolved from the config holders alone (registry + config collections + studio\n * config), all static per studio boot, so this needs no CMS provider and is\n * memoised once. The pure decision core is `resolveReuse`.\n */\nfunction useReuseIssues(): CustomizationIssue[] {\n return useMemo(\n () =>\n resolveReuse(\n getStudioRegistry().registry.list(),\n resolveDeploymentCollections(),\n resolveDeploymentReuse(),\n ).issues,\n [],\n )\n}\n\n/**\n * EC-469 — the project's URL-map issues: two routes that match the same URLs, or a\n * redirect chain that loops.\n *\n * They join the rail foot for the same reason EC-467's reusable-elements issues do:\n * they are project-level problems, not content problems in one document, so the\n * document-scoped issues pane has no place for them — and the rail is on every studio\n * surface, so the developer cannot miss them. This is emphatically not a revived\n * Routes view (EC-303 retired that, correctly): nothing here is editable, it is\n * validation of the code + data the studio already reads.\n *\n * The two halves come from different planes, which is why only the studio can show\n * both: routes are CODE (config, static per boot) while redirects are editorial DATA\n * (the rail, live). The redirects read shares the rail's own query key, so a loop\n * disappears from the foot the moment it is fixed — and while that query is loading or\n * failed, `data` is absent and only the code-side conflicts are reported. That is\n * honest: the rail itself surfaces its own load error.\n *\n * Copy follows the issues-pane convention (EC-192) and is mapped by\n * {@link routeIssueItems} — severity included since EC-493.\n */\nfunction useRouteIssues(projectId: string): NavIssueItem[] {\n const redirects = useRedirectList(projectId).data\n return useMemo(\n () => routeIssueItems(routeValidationIssues(resolveDeploymentRoutes(), redirects ?? [])),\n [redirects],\n )\n}\n\n/**\n * EC-606 — stored documents whose collection the project does not declare.\n *\n * Read from the content session, not the project session: this rail renders in\n * `StudioShell`, OUTSIDE `ProjectSessionProvider` (`studio-project-screen.tsx`), so\n * `useProjectSession` is unreachable here — and the provider's own `session` value is\n * `null` in the playground anyway. The content session is the channel both modes\n * already configure from the same `loadHydratedCms` result\n * (`configureContentSession(contentSessionOf(cms))`), and it is subscribable, so the\n * foot re-renders whenever it is re-pointed: at boot, on every CMS-surface mount, and\n * on the write-sync refresh while a canvas is mounted (`content-session-refresh.ts`).\n * The rail deliberately does NOT register as a refresh consumer — connected mode would\n * then pay a full hydration per autosave for a finding that changes only when config\n * changes or orphan rows are removed. The server snapshot is empty: there is no\n * session during SSR, so hydration renders no warning and the client adds it after.\n * The pure mapping is {@link orphanedCollectionItems}.\n */\nfunction useOrphanedCollectionIssues(): NavIssueItem[] {\n const orphans = useSyncExternalStore(\n subscribeContentSession,\n contentSessionOrphans,\n serverContentSessionOrphans,\n )\n return useMemo(() => orphanedCollectionItems(orphans), [orphans])\n}\n\n/** One rail view (top-level or nested) — a Link to its home route, active by view id. */\nfunction NavViewLink({\n projectId,\n view,\n activeViewId,\n collapsed,\n nested = false,\n hasActiveChild = false,\n collapsible = false,\n expanded = true,\n onToggleExpanded,\n}: {\n projectId: string\n view: ResolvedRailView\n activeViewId: string | null\n collapsed: boolean\n nested?: boolean\n /** EC-293: this parent's a child is the active view → a subtle (non-accent) hint. */\n hasActiveChild?: boolean\n /** EC-293: this parent has nested children → render a disclosure chevron. */\n collapsible?: boolean\n /** Whether the nested children are shown (chevron points down) or hidden. */\n expanded?: boolean\n /** Toggle this parent's collapse (the chevron click — does not navigate). */\n onToggleExpanded?: () => void\n}) {\n const isActive = view.id === activeViewId\n const Icon = view.icon\n const link = (\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: view.path }}\n title={view.label}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'group relative flex h-9 items-center gap-2.5 rounded-md text-sm transition-colors',\n // Collapsible parents reserve room on the right for the chevron toggle.\n collapsed ? 'justify-center px-0' : nested ? 'pl-7 pr-3' : collapsible ? 'pl-3 pr-9' : 'px-3',\n isActive\n ? 'bg-accent font-medium text-accent-foreground'\n : hasActiveChild\n ? 'text-foreground hover:bg-muted'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {/* Strong active marker: a leading accent bar (EC-214). */}\n {isActive ? (\n <span className=\"absolute inset-y-1.5 left-0 w-0.5 rounded-full bg-primary\" aria-hidden />\n ) : null}\n <Icon className=\"size-4 shrink-0\" />\n {collapsed ? null : <span className=\"min-w-0 flex-1 truncate\">{view.label}</span>}\n {view.availability === 'planned' && !collapsed ? (\n <span className=\"size-1.5 shrink-0 rounded-full bg-warning\" aria-hidden title={translate('shell.nav.planned')} />\n ) : null}\n </Link>\n )\n // A leaf (or the collapsed icons-only rail) is just the link.\n if (!collapsible) return link\n // A parent pairs the link with a sibling chevron toggle (so the chevron collapses\n // without navigating, and the link still navigates to the parent surface).\n return (\n <div className=\"relative\">\n {link}\n <button\n type=\"button\"\n onClick={onToggleExpanded}\n aria-label={translate(expanded ? 'shell.nav.collapseView' : 'shell.nav.expandView', {\n name: view.label,\n })}\n aria-expanded={expanded}\n className=\"absolute right-1 top-1/2 grid size-6 -translate-y-1/2 place-items-center rounded text-muted-foreground/70 transition-colors hover:bg-background hover:text-foreground\"\n >\n <ChevronDown\n className={cn('size-3.5 transition-transform', expanded ? '' : '-rotate-90')}\n aria-hidden\n />\n </button>\n </div>\n )\n}\n"]}
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router';
3
- import { availabilityOf, SECTIONS, sectionStateSchema } from '@elytracms/core/studio-core';
3
+ import { availabilityOf, sectionStateSchema } from '@elytracms/core/studio-core';
4
4
  import { Code, Row, Stack, Text } from '@elytracms/studio/ui';
5
5
  import { cn } from '@elytracms/studio/lib/utils';
6
+ import { translate } from '@elytracms/studio/lib/i18n';
6
7
  import { resolveSectionState, SectionStateView } from './section-state.js';
7
8
  /** Parse a `?state=` search param into a SectionState (undefined when absent/invalid). */
8
9
  export function parseSectionState(raw) {
@@ -26,7 +27,7 @@ export function parseSectionState(raw) {
26
27
  export function SectionSurface({ project, section, forcedState, children }) {
27
28
  const availability = availabilityOf(project.settings.sectionAvailability, section);
28
29
  const state = resolveSectionState(availability, forcedState);
29
- return (_jsx(Stack, { gap: "none", className: "min-h-full", children: state === 'working' ? children : _jsx(SectionStateView, { state: state, title: SECTIONS[section].label }) }));
30
+ return (_jsx(Stack, { gap: "none", className: "min-h-full", children: state === 'working' ? children : _jsx(SectionStateView, { state: state, title: translate(`nav.${section}.label`) }) }));
30
31
  }
31
32
  /**
32
33
  * Deterministic state switcher (brief §11). Links to the same section with each
@@ -1 +1 @@
1
- {"version":3,"file":"section-surface.js","sourceRoot":"","sources":["../../src/components/section-surface.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router'\nimport type { ReactNode } from 'react'\nimport { availabilityOf, SECTIONS, sectionStateSchema } from '@elytracms/core/studio-core'\nimport type { Project, ProjectId, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { Code, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { resolveSectionState, SectionStateView } from './section-state'\n\n/** Parse a `?state=` search param into a SectionState (undefined when absent/invalid). */\nexport function parseSectionState(raw: unknown): SectionState | undefined {\n const parsed = sectionStateSchema.safeParse(raw)\n return parsed.success ? parsed.data : undefined\n}\n\nexport interface SectionSurfaceProps {\n project: Project\n section: SectionId\n /** Forced state from `?state=`; overrides availability. */\n forcedState?: SectionState\n /** Working-state content. */\n children?: ReactNode\n}\n\n/**\n * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the\n * working content when `working`, else a consistent state view.\n *\n * EC-346: the dev {@link StateSwitcher} panel used to render here, unconditionally,\n * above every section — a debug/reproducibility harness that leaked into\n * production shells (a real studio's Assets header showed a \"state · default\n * working empty loading error…\" testing strip). It no longer mounts by default.\n * The `?state=` URL contract it exercised stays fully live — `resolveSectionState`\n * below still consumes `forcedState`, so every state remains reachable by URL for\n * tests/agents. `StateSwitcher` itself stays exported and covered (nothing was\n * deleted from the state model) so a future dev-only surface — gated behind\n * `import.meta.env.DEV` or a `defineStudio({ devTools: true })` flag — can remount it.\n */\nexport function SectionSurface({ project, section, forcedState, children }: SectionSurfaceProps) {\n const availability = availabilityOf(project.settings.sectionAvailability, section)\n const state = resolveSectionState(availability, forcedState)\n\n return (\n <Stack gap=\"none\" className=\"min-h-full\">\n {state === 'working' ? children : <SectionStateView state={state} title={SECTIONS[section].label} />}\n </Stack>\n )\n}\n\n/**\n * Deterministic state switcher (brief §11). Links to the same section with each\n * `?state=` value so agents/humans can reproduce every state by URL or click.\n */\nexport function StateSwitcher({\n projectId,\n section,\n current,\n}: {\n projectId: ProjectId\n section: SectionId\n current?: SectionState\n}) {\n const states: SectionState[] = ['working', 'empty', 'loading', 'error', 'unavailable', 'planned']\n const activeKey = current ?? 'default'\n return (\n <Row\n gap=\"1\"\n wrap\n className=\"shrink-0 border-b border-dashed border-border/70 px-4 py-2 sm:px-6\"\n data-testid=\"state-switcher\"\n >\n <Text size=\"xs\" tone=\"subtle\" mono className=\"mr-1\">\n state\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{}}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n activeKey === 'default'\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n default\n </Link>\n {states.map((s) => {\n const active = s === activeKey\n return (\n <Link\n key={s}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{ state: s }}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {s}\n </Link>\n )\n })}\n <Code tone=\"muted\" className=\"ml-2 hidden sm:inline-flex\">\n {activeKey === 'default' ? 'auto' : `?state=${activeKey}`}\n </Code>\n </Row>\n )\n}\n"]}
1
+ {"version":3,"file":"section-surface.js","sourceRoot":"","sources":["../../src/components/section-surface.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Link, STUDIO_PROJECT_ROUTE } from '@elytracms/studio/router'\nimport type { ReactNode } from 'react'\nimport { availabilityOf, sectionStateSchema } from '@elytracms/core/studio-core'\nimport type { Project, ProjectId, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { Code, Row, Stack, Text } from '@elytracms/studio/ui'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { resolveSectionState, SectionStateView } from './section-state'\n\n/** Parse a `?state=` search param into a SectionState (undefined when absent/invalid). */\nexport function parseSectionState(raw: unknown): SectionState | undefined {\n const parsed = sectionStateSchema.safeParse(raw)\n return parsed.success ? parsed.data : undefined\n}\n\nexport interface SectionSurfaceProps {\n project: Project\n section: SectionId\n /** Forced state from `?state=`; overrides availability. */\n forcedState?: SectionState\n /** Working-state content. */\n children?: ReactNode\n}\n\n/**\n * Resolves a section's deterministic state (brief §8.1, §11) and renders it: the\n * working content when `working`, else a consistent state view.\n *\n * EC-346: the dev {@link StateSwitcher} panel used to render here, unconditionally,\n * above every section — a debug/reproducibility harness that leaked into\n * production shells (a real studio's Assets header showed a \"state · default\n * working empty loading error…\" testing strip). It no longer mounts by default.\n * The `?state=` URL contract it exercised stays fully live — `resolveSectionState`\n * below still consumes `forcedState`, so every state remains reachable by URL for\n * tests/agents. `StateSwitcher` itself stays exported and covered (nothing was\n * deleted from the state model) so a future dev-only surface — gated behind\n * `import.meta.env.DEV` or a `defineStudio({ devTools: true })` flag — can remount it.\n */\nexport function SectionSurface({ project, section, forcedState, children }: SectionSurfaceProps) {\n const availability = availabilityOf(project.settings.sectionAvailability, section)\n const state = resolveSectionState(availability, forcedState)\n\n return (\n <Stack gap=\"none\" className=\"min-h-full\">\n {/* EC-574: the title is the SAME translated label the rail shows (`nav.<section>.label`),\n not the former English `SECTIONS` table — one declaration per section label. */}\n {state === 'working' ? children : <SectionStateView state={state} title={translate(`nav.${section}.label`)} />}\n </Stack>\n )\n}\n\n/**\n * Deterministic state switcher (brief §11). Links to the same section with each\n * `?state=` value so agents/humans can reproduce every state by URL or click.\n */\nexport function StateSwitcher({\n projectId,\n section,\n current,\n}: {\n projectId: ProjectId\n section: SectionId\n current?: SectionState\n}) {\n const states: SectionState[] = ['working', 'empty', 'loading', 'error', 'unavailable', 'planned']\n const activeKey = current ?? 'default'\n return (\n <Row\n gap=\"1\"\n wrap\n className=\"shrink-0 border-b border-dashed border-border/70 px-4 py-2 sm:px-6\"\n data-testid=\"state-switcher\"\n >\n <Text size=\"xs\" tone=\"subtle\" mono className=\"mr-1\">\n state\n </Text>\n <Link\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{}}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n activeKey === 'default'\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n default\n </Link>\n {states.map((s) => {\n const active = s === activeKey\n return (\n <Link\n key={s}\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId, _splat: section }}\n search={{ state: s }}\n className={cn(\n 'rounded-md px-2 py-0.5 font-mono text-xs transition-colors',\n active\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {s}\n </Link>\n )\n })}\n <Code tone=\"muted\" className=\"ml-2 hidden sm:inline-flex\">\n {activeKey === 'default' ? 'auto' : `?state=${activeKey}`}\n </Code>\n </Row>\n )\n}\n"]}
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Badge, Code, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
3
- import { exportTargetLabel } from '@elytracms/studio/lib/persona/labels';
4
3
  import { translate } from '@elytracms/studio/lib/i18n';
5
4
  /**
6
5
  * Project settings — read-only (vision AD-8). Identity, locales, and
@@ -10,7 +9,7 @@ import { translate } from '@elytracms/studio/lib/i18n';
10
9
  */
11
10
  export function SettingsForm({ project }) {
12
11
  const { settings } = project;
13
- return (_jsx("div", { className: "mx-auto w-full max-w-3xl p-4 sm:p-6", children: _jsxs(Stack, { gap: "6", children: [_jsxs(Text, { size: "sm", tone: "muted", children: ["Project identity and settings are defined in code \u2014 ", _jsx(Code, { tone: "muted", children: "elytra.config.ts" }), ". Edit that file to change the name, locales, or environments; the studio reads them at runtime."] }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.identity.title') }) }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(ReadField, { label: translate('settings.identity.name'), children: _jsx(Text, { size: "sm", children: project.name }) }), _jsx(ReadField, { label: translate('settings.identity.urlKey'), children: _jsx(Code, { tone: "muted", children: project.slug }) })] })] }), _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.locales.title') }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "muted", mono: true, children: translate('settings.locales.configuredCount', { count: settings.locales.length }) })] }), _jsx(Stack, { gap: "3", className: "p-4", children: _jsx(ReadField, { label: translate('settings.locales.configured'), children: _jsx(Row, { gap: "2", wrap: true, children: settings.locales.map((locale) => (_jsxs(Badge, { variant: locale === settings.defaultLocale ? 'primary' : 'outline', children: [locale, locale === settings.defaultLocale ? translate('settings.locales.defaultSuffix') : ''] }, locale))) }) }) })] }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.environments.title') }) }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(ReadField, { label: translate('settings.environments.label'), children: _jsx(Stack, { gap: "2", children: settings.environments.map((env) => (_jsx(Row, { gap: "2", className: "rounded-md border border-border px-3 py-2", title: env.key, children: _jsx(Text, { size: "sm", children: env.label }) }, env.key))) }) }), _jsx(ReadField, { label: translate('settings.exportTarget'), children: _jsx(Text, { size: "sm", children: exportTargetLabel(settings.exportTarget) }) })] })] })] }) }));
12
+ return (_jsx("div", { className: "mx-auto w-full max-w-3xl p-4 sm:p-6", children: _jsxs(Stack, { gap: "6", children: [_jsxs(Text, { size: "sm", tone: "muted", children: ["Project identity and settings are defined in code \u2014 ", _jsx(Code, { tone: "muted", children: "elytra.config.ts" }), ". Edit that file to change the name, locales, or environments; the studio reads them at runtime."] }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.identity.title') }) }), _jsxs(Stack, { gap: "3", className: "p-4", children: [_jsx(ReadField, { label: translate('settings.identity.name'), children: _jsx(Text, { size: "sm", children: project.name }) }), _jsx(ReadField, { label: translate('settings.identity.urlKey'), children: _jsx(Code, { tone: "muted", children: project.slug }) })] })] }), _jsxs(Panel, { children: [_jsxs(PanelHeader, { children: [_jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.locales.title') }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "muted", mono: true, children: translate('settings.locales.configuredCount', { count: settings.locales.length }) })] }), _jsx(Stack, { gap: "3", className: "p-4", children: _jsx(ReadField, { label: translate('settings.locales.configured'), children: _jsx(Row, { gap: "2", wrap: true, children: settings.locales.map((locale) => (_jsxs(Badge, { variant: locale === settings.defaultLocale ? 'primary' : 'outline', children: [locale, locale === settings.defaultLocale ? translate('settings.locales.defaultSuffix') : ''] }, locale))) }) }) })] }), _jsxs(Panel, { children: [_jsx(PanelHeader, { children: _jsx(Text, { size: "sm", weight: "semibold", children: translate('settings.environments.title') }) }), _jsx(Stack, { gap: "3", className: "p-4", children: _jsx(ReadField, { label: translate('settings.environments.label'), children: _jsx(Stack, { gap: "2", children: settings.environments.map((env) => (_jsx(Row, { gap: "2", className: "rounded-md border border-border px-3 py-2", title: env.key, children: _jsx(Text, { size: "sm", children: env.label }) }, env.key))) }) }) })] })] }) }));
14
13
  }
15
14
  function ReadField({ label, children }) {
16
15
  return (_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "xs", tone: "muted", weight: "medium", children: label }), children] }));
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.js","sourceRoot":"","sources":["../../src/components/settings-form.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { Badge, Code, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { exportTargetLabel } from '@elytracms/studio/lib/persona/labels'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Project settings — read-only (vision AD-8). Identity, locales, and\n * environments are owned by `elytra.config.ts` and read at runtime; the studio\n * never writes them back. This surface displays the resolved config so editors\n * can see how the project is wired without exposing a (fake) edit path.\n */\nexport function SettingsForm({ project }: { project: Project }) {\n const { settings } = project\n return (\n <div className=\"mx-auto w-full max-w-3xl p-4 sm:p-6\">\n <Stack gap=\"6\">\n <Text size=\"sm\" tone=\"muted\">\n Project identity and settings are defined in code — <Code tone=\"muted\">elytra.config.ts</Code>\n . Edit that file to change the name, locales, or environments; the studio reads them at\n runtime.\n </Text>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.identity.title')}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.identity.name')}>\n <Text size=\"sm\">{project.name}</Text>\n </ReadField>\n <ReadField label={translate('settings.identity.urlKey')}>\n <Code tone=\"muted\">{project.slug}</Code>\n </ReadField>\n </Stack>\n </Panel>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.locales.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"muted\" mono>\n {translate('settings.locales.configuredCount', { count: settings.locales.length })}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.locales.configured')}>\n <Row gap=\"2\" wrap>\n {settings.locales.map((locale) => (\n <Badge\n key={locale}\n variant={locale === settings.defaultLocale ? 'primary' : 'outline'}\n >\n {locale}\n {locale === settings.defaultLocale ? translate('settings.locales.defaultSuffix') : ''}\n </Badge>\n ))}\n </Row>\n </ReadField>\n </Stack>\n </Panel>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.environments.title')}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.environments.label')}>\n <Stack gap=\"2\">\n {settings.environments.map((env) => (\n <Row\n key={env.key}\n gap=\"2\"\n className=\"rounded-md border border-border px-3 py-2\"\n title={env.key}\n >\n <Text size=\"sm\">{env.label}</Text>\n </Row>\n ))}\n </Stack>\n </ReadField>\n <ReadField label={translate('settings.exportTarget')}>\n <Text size=\"sm\">{exportTargetLabel(settings.exportTarget)}</Text>\n </ReadField>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction ReadField({ label, children }: { label: string; children: ReactNode }) {\n return (\n <Stack gap=\"1\">\n <Text size=\"xs\" tone=\"muted\" weight=\"medium\">\n {label}\n </Text>\n {children}\n </Stack>\n )\n}\n"]}
1
+ {"version":3,"file":"settings-form.js","sourceRoot":"","sources":["../../src/components/settings-form.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { ReactNode } from 'react'\nimport type { Project } from '@elytracms/core/studio-core'\nimport { Badge, Code, Panel, PanelHeader, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\n\n/**\n * Project settings — read-only (vision AD-8). Identity, locales, and\n * environments are owned by `elytra.config.ts` and read at runtime; the studio\n * never writes them back. This surface displays the resolved config so editors\n * can see how the project is wired without exposing a (fake) edit path.\n */\nexport function SettingsForm({ project }: { project: Project }) {\n const { settings } = project\n return (\n <div className=\"mx-auto w-full max-w-3xl p-4 sm:p-6\">\n <Stack gap=\"6\">\n <Text size=\"sm\" tone=\"muted\">\n Project identity and settings are defined in code — <Code tone=\"muted\">elytra.config.ts</Code>\n . Edit that file to change the name, locales, or environments; the studio reads them at\n runtime.\n </Text>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.identity.title')}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.identity.name')}>\n <Text size=\"sm\">{project.name}</Text>\n </ReadField>\n <ReadField label={translate('settings.identity.urlKey')}>\n <Code tone=\"muted\">{project.slug}</Code>\n </ReadField>\n </Stack>\n </Panel>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.locales.title')}\n </Text>\n <Spacer />\n <Text size=\"xs\" tone=\"muted\" mono>\n {translate('settings.locales.configuredCount', { count: settings.locales.length })}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.locales.configured')}>\n <Row gap=\"2\" wrap>\n {settings.locales.map((locale) => (\n <Badge\n key={locale}\n variant={locale === settings.defaultLocale ? 'primary' : 'outline'}\n >\n {locale}\n {locale === settings.defaultLocale ? translate('settings.locales.defaultSuffix') : ''}\n </Badge>\n ))}\n </Row>\n </ReadField>\n </Stack>\n </Panel>\n\n <Panel>\n <PanelHeader>\n <Text size=\"sm\" weight=\"semibold\">\n {translate('settings.environments.title')}\n </Text>\n </PanelHeader>\n <Stack gap=\"3\" className=\"p-4\">\n <ReadField label={translate('settings.environments.label')}>\n <Stack gap=\"2\">\n {settings.environments.map((env) => (\n <Row\n key={env.key}\n gap=\"2\"\n className=\"rounded-md border border-border px-3 py-2\"\n title={env.key}\n >\n <Text size=\"sm\">{env.label}</Text>\n </Row>\n ))}\n </Stack>\n </ReadField>\n </Stack>\n </Panel>\n </Stack>\n </div>\n )\n}\n\nfunction ReadField({ label, children }: { label: string; children: ReactNode }) {\n return (\n <Stack gap=\"1\">\n <Text size=\"xs\" tone=\"muted\" weight=\"medium\">\n {label}\n </Text>\n {children}\n </Stack>\n )\n}\n"]}
@@ -10,24 +10,7 @@ function configProjects() {
10
10
  const state = getWorkspaceState();
11
11
  if (state.kind !== 'config')
12
12
  return [];
13
- return state.projects.map((entry) => {
14
- const project = configProject(entry);
15
- // EC-221: the plugin marketplace UI is cut from v1 (vision §5). The
16
- // Extensions surface is pulled from the nav by marking the section
17
- // unavailable — the rail omits unavailable sections. The @elytracms/plugins
18
- // package, ExtensionsView, and the `/extensions` route all remain in the tree
19
- // for Milestone E revival (reachable by URL, just not advertised in the nav).
20
- return {
21
- ...project,
22
- settings: {
23
- ...project.settings,
24
- sectionAvailability: {
25
- ...project.settings.sectionAvailability,
26
- extensions: 'unavailable',
27
- },
28
- },
29
- };
30
- });
13
+ return state.projects.map(configProject);
31
14
  }
32
15
  export function StudioProvider({ children }) {
33
16
  // The workspace config is static for the app's lifetime (bundled at build or
@@ -1 +1 @@
1
- {"version":3,"file":"studio-context.js","sourceRoot":"","sources":["../../src/components/studio-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEtD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAoB9E,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,qBAAqB,CAAc,QAAQ,CAAC,CAAA;AAEvG,gFAAgF;AAChF,SAAS,cAAc;IACrB,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAA;IACjC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAA;IACtC,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAClC,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;QACpC,oEAAoE;QACpE,mEAAmE;QACnE,4EAA4E;QAC5E,8EAA8E;QAC9E,8EAA8E;QAC9E,OAAO;YACL,GAAG,OAAO;YACV,QAAQ,EAAE;gBACR,GAAG,OAAO,CAAC,QAAQ;gBACnB,mBAAmB,EAAE;oBACnB,GAAG,OAAO,CAAC,QAAQ,CAAC,mBAAmB;oBACvC,UAAU,EAAE,aAAa;iBAC1B;aACF;SACF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAE,QAAQ,EAA2B;IAClE,6EAA6E;IAC7E,2DAA2D;IAC3D,MAAM,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAqB,GAAG,EAAE,CAAC,cAAc,EAAE,CAAC,CAAA;IACvE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAuB,YAAY,CAAC,CAAA;IAE1E,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,EAAa,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEhG,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,EAAa,EAAE,EAAE;QAC7C,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IACxF,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EACnD,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAC1C,CAAA;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,wDAAwD;IACxD,OAAO,CACL,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAClC,KAAC,mBAAmB,cAAE,QAAQ,GAAuB,GAC9B,CAC1B,CAAA;AACH,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,CAAA","sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { activateProject, configProject, emptyContext } from '@elytracms/core/studio-core'\nimport type { ActiveProjectContext, Project, ProjectId } from '@elytracms/core/studio-core'\nimport { getWorkspaceState } from '@elytracms/studio/lib/workspace'\nimport { StudioQueryProvider } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\n/**\n * Project read store (vision AD-8). Project *identity* is owned by\n * `elytra.config.ts` — the studio reads the project list from the resolved\n * workspace config, never from the backend. The active project id is route-\n * driven; this store holds the config-derived list + the recents context.\n * Lifecycle (create/rename/archive/delete) lives in code/CLI, not the UI.\n */\n\nexport interface StudioStore {\n /** Projects declared in `elytra.config.ts`, in config order. */\n projects: readonly Project[]\n /** Active-project context (recents); the active id itself is route-driven. */\n context: ActiveProjectContext\n getProject(id: ProjectId): Project | undefined\n /** Record a project as active + most-recent (idempotent). */\n activate(id: ProjectId): void\n}\n\nconst { Context: StudioContext, useRequired: useStudio } = createRequiredContext<StudioStore>('Studio')\n\n/** Resolve the config-declared projects into studio `Project`s (slug-keyed). */\nfunction configProjects(): readonly Project[] {\n const state = getWorkspaceState()\n if (state.kind !== 'config') return []\n return state.projects.map((entry) => {\n const project = configProject(entry)\n // EC-221: the plugin marketplace UI is cut from v1 (vision §5). The\n // Extensions surface is pulled from the nav by marking the section\n // unavailable — the rail omits unavailable sections. The @elytracms/plugins\n // package, ExtensionsView, and the `/extensions` route all remain in the tree\n // for Milestone E revival (reachable by URL, just not advertised in the nav).\n return {\n ...project,\n settings: {\n ...project.settings,\n sectionAvailability: {\n ...project.settings.sectionAvailability,\n extensions: 'unavailable',\n },\n },\n }\n })\n}\n\nexport function StudioProvider({ children }: { children: ReactNode }) {\n // The workspace config is static for the app's lifetime (bundled at build or\n // injected via env), so the project list is computed once.\n const [projects] = useState<readonly Project[]>(() => configProjects())\n const [context, setContext] = useState<ActiveProjectContext>(emptyContext)\n\n const getProject = useCallback((id: ProjectId) => projects.find((p) => p.id === id), [projects])\n\n const activate = useCallback((id: ProjectId) => {\n setContext((prev) => (prev.activeProjectId === id ? prev : activateProject(prev, id)))\n }, [])\n\n const store = useMemo<StudioStore>(\n () => ({ projects, context, getProject, activate }),\n [projects, context, getProject, activate],\n )\n\n // EC-308/AD-17: the single client query cache mounts here (package-owned,\n // above every per-project provider, surviving project switches) so every\n // studio server read flows through `useOperationQuery`.\n return (\n <StudioContext.Provider value={store}>\n <StudioQueryProvider>{children}</StudioQueryProvider>\n </StudioContext.Provider>\n )\n}\n\nexport { useStudio }\n"]}
1
+ {"version":3,"file":"studio-context.js","sourceRoot":"","sources":["../../src/components/studio-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEtD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAoB9E,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,qBAAqB,CAAc,QAAQ,CAAC,CAAA;AAEvG,gFAAgF;AAChF,SAAS,cAAc;IACrB,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAA;IACjC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAA;IACtC,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAE,QAAQ,EAA2B;IAClE,6EAA6E;IAC7E,2DAA2D;IAC3D,MAAM,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAqB,GAAG,EAAE,CAAC,cAAc,EAAE,CAAC,CAAA;IACvE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAuB,YAAY,CAAC,CAAA;IAE1E,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,EAAa,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEhG,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,EAAa,EAAE,EAAE;QAC7C,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IACxF,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EACnD,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAC1C,CAAA;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,wDAAwD;IACxD,OAAO,CACL,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAClC,KAAC,mBAAmB,cAAE,QAAQ,GAAuB,GAC9B,CAC1B,CAAA;AACH,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,CAAA","sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { activateProject, configProject, emptyContext } from '@elytracms/core/studio-core'\nimport type { ActiveProjectContext, Project, ProjectId } from '@elytracms/core/studio-core'\nimport { getWorkspaceState } from '@elytracms/studio/lib/workspace'\nimport { StudioQueryProvider } from '@elytracms/studio/lib/query'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\n\n/**\n * Project read store (vision AD-8). Project *identity* is owned by\n * `elytra.config.ts` — the studio reads the project list from the resolved\n * workspace config, never from the backend. The active project id is route-\n * driven; this store holds the config-derived list + the recents context.\n * Lifecycle (create/rename/archive/delete) lives in code/CLI, not the UI.\n */\n\nexport interface StudioStore {\n /** Projects declared in `elytra.config.ts`, in config order. */\n projects: readonly Project[]\n /** Active-project context (recents); the active id itself is route-driven. */\n context: ActiveProjectContext\n getProject(id: ProjectId): Project | undefined\n /** Record a project as active + most-recent (idempotent). */\n activate(id: ProjectId): void\n}\n\nconst { Context: StudioContext, useRequired: useStudio } = createRequiredContext<StudioStore>('Studio')\n\n/** Resolve the config-declared projects into studio `Project`s (slug-keyed). */\nfunction configProjects(): readonly Project[] {\n const state = getWorkspaceState()\n if (state.kind !== 'config') return []\n return state.projects.map(configProject)\n}\n\nexport function StudioProvider({ children }: { children: ReactNode }) {\n // The workspace config is static for the app's lifetime (bundled at build or\n // injected via env), so the project list is computed once.\n const [projects] = useState<readonly Project[]>(() => configProjects())\n const [context, setContext] = useState<ActiveProjectContext>(emptyContext)\n\n const getProject = useCallback((id: ProjectId) => projects.find((p) => p.id === id), [projects])\n\n const activate = useCallback((id: ProjectId) => {\n setContext((prev) => (prev.activeProjectId === id ? prev : activateProject(prev, id)))\n }, [])\n\n const store = useMemo<StudioStore>(\n () => ({ projects, context, getProject, activate }),\n [projects, context, getProject, activate],\n )\n\n // EC-308/AD-17: the single client query cache mounts here (package-owned,\n // above every per-project provider, surviving project switches) so every\n // studio server read flows through `useOperationQuery`.\n return (\n <StudioContext.Provider value={store}>\n <StudioQueryProvider>{children}</StudioQueryProvider>\n </StudioContext.Provider>\n )\n}\n\nexport { useStudio }\n"]}