@elytracms/studio 0.0.78 → 0.0.79

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 (85) hide show
  1. package/dist/components/app-views/default-modules.js +5 -1
  2. package/dist/components/app-views/default-modules.js.map +1 -1
  3. package/dist/components/assets/asset-thumb.js +12 -11
  4. package/dist/components/assets/asset-thumb.js.map +1 -1
  5. package/dist/components/builder/blocks-panel.d.ts +11 -5
  6. package/dist/components/builder/blocks-panel.js +18 -24
  7. package/dist/components/builder/blocks-panel.js.map +1 -1
  8. package/dist/components/builder/canvas-render-context.d.ts +22 -0
  9. package/dist/components/builder/canvas-render-context.js +65 -9
  10. package/dist/components/builder/canvas-render-context.js.map +1 -1
  11. package/dist/components/builder/reuse.d.ts +25 -0
  12. package/dist/components/builder/reuse.js +11 -2
  13. package/dist/components/builder/reuse.js.map +1 -1
  14. package/dist/components/cms/asset-field.js +52 -19
  15. package/dist/components/cms/asset-field.js.map +1 -1
  16. package/dist/components/cms/asset-picker-core.d.ts +40 -0
  17. package/dist/components/cms/asset-picker-core.js +21 -0
  18. package/dist/components/cms/asset-picker-core.js.map +1 -1
  19. package/dist/components/cms/asset-picker.d.ts +11 -2
  20. package/dist/components/cms/asset-picker.js +34 -13
  21. package/dist/components/cms/asset-picker.js.map +1 -1
  22. package/dist/components/cms/cell.js +2 -1
  23. package/dist/components/cms/cell.js.map +1 -1
  24. package/dist/components/cms/cms-store.d.ts +8 -1
  25. package/dist/components/cms/cms-store.js +8 -7
  26. package/dist/components/cms/cms-store.js.map +1 -1
  27. package/dist/components/cms/collection-table.js +7 -1
  28. package/dist/components/cms/collection-table.js.map +1 -1
  29. package/dist/components/cms/date-input-core.d.ts +64 -0
  30. package/dist/components/cms/date-input-core.js +78 -0
  31. package/dist/components/cms/date-input-core.js.map +1 -0
  32. package/dist/components/cms/date-input.d.ts +43 -0
  33. package/dist/components/cms/date-input.js +109 -0
  34. package/dist/components/cms/date-input.js.map +1 -0
  35. package/dist/components/cms/document-diff-view.js +2 -1
  36. package/dist/components/cms/document-diff-view.js.map +1 -1
  37. package/dist/components/cms/document-staging.d.ts +15 -4
  38. package/dist/components/cms/document-staging.js +26 -15
  39. package/dist/components/cms/document-staging.js.map +1 -1
  40. package/dist/components/cms/editable-cell.js +39 -5
  41. package/dist/components/cms/editable-cell.js.map +1 -1
  42. package/dist/components/cms/field-control.js +38 -7
  43. package/dist/components/cms/field-control.js.map +1 -1
  44. package/dist/components/cms/scalar-value-editor.js +4 -1
  45. package/dist/components/cms/scalar-value-editor.js.map +1 -1
  46. package/dist/components/cms/slug-auto-derive.d.ts +41 -0
  47. package/dist/components/cms/slug-auto-derive.js +14 -0
  48. package/dist/components/cms/slug-auto-derive.js.map +1 -0
  49. package/dist/components/components/transfer-dialog.js +3 -1
  50. package/dist/components/components/transfer-dialog.js.map +1 -1
  51. package/dist/components/project-session.d.ts +44 -22
  52. package/dist/components/project-session.js +72 -36
  53. package/dist/components/project-session.js.map +1 -1
  54. package/dist/lib/assets/asset-url.d.ts +20 -0
  55. package/dist/lib/assets/asset-url.js +25 -0
  56. package/dist/lib/assets/asset-url.js.map +1 -0
  57. package/dist/lib/assets/browser-blob-storage.d.ts +80 -0
  58. package/dist/lib/assets/browser-blob-storage.js +86 -0
  59. package/dist/lib/assets/browser-blob-storage.js.map +1 -0
  60. package/dist/lib/assets/index.d.ts +2 -0
  61. package/dist/lib/assets/index.js +2 -0
  62. package/dist/lib/assets/index.js.map +1 -1
  63. package/dist/lib/data/collection-content.d.ts +20 -1
  64. package/dist/lib/data/collection-content.js +51 -1
  65. package/dist/lib/data/collection-content.js.map +1 -1
  66. package/dist/lib/data/content-session-refresh.d.ts +74 -0
  67. package/dist/lib/data/content-session-refresh.js +138 -0
  68. package/dist/lib/data/content-session-refresh.js.map +1 -0
  69. package/dist/lib/data/index.d.ts +2 -0
  70. package/dist/lib/data/index.js +2 -0
  71. package/dist/lib/data/index.js.map +1 -1
  72. package/dist/lib/data/playground-unload-guard.d.ts +22 -0
  73. package/dist/lib/data/playground-unload-guard.js +73 -0
  74. package/dist/lib/data/playground-unload-guard.js.map +1 -0
  75. package/dist/lib/i18n/messages.de.js +3 -1
  76. package/dist/lib/i18n/messages.de.js.map +1 -1
  77. package/dist/lib/i18n/messages.en.d.ts +2 -0
  78. package/dist/lib/i18n/messages.en.js +5 -1
  79. package/dist/lib/i18n/messages.en.js.map +1 -1
  80. package/dist/lib/persistence/index.js +6 -1
  81. package/dist/lib/persistence/index.js.map +1 -1
  82. package/dist/ui/input.d.ts +8 -3
  83. package/dist/ui/input.js +6 -1
  84. package/dist/ui/input.js.map +1 -1
  85. package/package.json +2 -2
@@ -453,7 +453,11 @@ function CmsScreen({ project, collectionId, documentId, state, field, focusColle
453
453
  // EC-424: resolved once so `initialDocuments`' reconciliation (below)
454
454
  // stays in lockstep with the SAME collections `initialCollections` uses.
455
455
  const initialCollections = resolveCmsInitialCollections(cms, kit);
456
- return (_jsx(CmsProvider, { projectId: projectId, initialCollections: initialCollections, initialDocuments: resolveCmsInitialDocuments(cms, kit, initialCollections), initialPublishedKeys: cms?.publishedKeys, initialTimestamps: cms?.timestamps, initialPublishTimestamps: cms?.publishTimestamps, initialChangedKeys: cms?.changedKeys, initialLocaleConfig: resolveCmsInitialLocaleConfig(cms, kit, project), children: _jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(CmsPersistErrorBanner, {}), _jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "min-w-0 flex-1 overflow-hidden", children: _jsx(CmsContent, { projectId: projectId, collectionId: collectionId, documentId: documentId, field: field, focusCollections: focusCollections }) }), _jsx(AiCmsPanel, {})] })] }) }, projectId));
456
+ return (_jsx(CmsProvider, { projectId: projectId, initialCollections: initialCollections, initialDocuments: resolveCmsInitialDocuments(cms, kit, initialCollections), initialPublishedKeys: cms?.publishedKeys, initialTimestamps: cms?.timestamps, initialPublishTimestamps: cms?.publishTimestamps, initialChangedKeys: cms?.changedKeys, initialLocaleConfig: resolveCmsInitialLocaleConfig(cms, kit, project),
457
+ // EC-461: a hydrated mount (cms non-null — both modes now) seeds the
458
+ // CURRENT persisted state, so staged docs are superseded by it; only
459
+ // a static kit/fixture seed lets staged entries win.
460
+ seedReflectsWrites: cms !== null, children: _jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(CmsPersistErrorBanner, {}), _jsxs("div", { className: "flex min-h-0 flex-1 overflow-hidden", children: [_jsx("div", { className: "min-w-0 flex-1 overflow-hidden", children: _jsx(CmsContent, { projectId: projectId, collectionId: collectionId, documentId: documentId, field: field, focusCollections: focusCollections }) }), _jsx(AiCmsPanel, {})] })] }) }, projectId));
457
461
  } }));
458
462
  }
459
463
  /** Resolves the active CMS screen inside the provider (needs the live store). */
@@ -1 +1 @@
1
- {"version":3,"file":"default-modules.js","sourceRoot":"","sources":["../../../src/components/app-views/default-modules.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGjC,OAAO,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,KAAK,EACL,UAAU,EACV,MAAM,EACN,QAAQ,IAAI,YAAY,EACxB,QAAQ,EACR,KAAK,GACN,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AAQjC,OAAO,EAAE,iCAAiC,EAAE,MAAM,6BAA6B,CAAA;AAE/E,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAA;AAE/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAE,4BAA4B,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAA;AAE7G,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,aAAa,EACb,QAAQ,GAMT,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AA4GzD,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,MAAM,CAAA;AACjG,CAAC;AAED,0FAA0F;AAC1F,SAAS,SAAS,CAAC,MAAqB;IACtC,IAAI,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC,EAAE,CAAA;IAC/B,IAAI,MAAM,CAAC,UAAU;QAAE,OAAO,OAAO,MAAM,CAAC,UAAU,EAAE,CAAA;IACxD,IAAI,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,OAAO,OAAO,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;IACnD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,SAAwB,EAAE;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAA;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAA;IACpD,OAAO;QACL,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC;QACrB,4EAA4E;QAC5E,kFAAkF;QAClF,gFAAgF;QAChF,yDAAyD;QACzD,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,eAAe,CAAC,CAAA;QACnD,CAAC;QACD,IAAI,WAAW;YACb,OAAO,MAAM,CAAC,WAAW,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAA;QAC/D,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,QAAQ;QAC7B,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,IAAI,KAAK;QACxD,4EAA4E;QAC5E,sFAAsF;QACtF,SAAS,EAAE,MAAM;YACf,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,CACX,QAAQ,CAAC,MAAM,KAAK,CAAC;gBACnB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;gBAC5C,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;oBACrB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAE,EAAE;oBACpD,CAAC,CAAC,IAAI;YACd,CAAC,CAAC,WAAW;QACf,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CACf,KAAC,SAAS,IACR,OAAO,EAAE,GAAG,CAAC,OAAO,EACpB,YAAY,EAAE,GAAG,CAAC,QAAQ,CAAC,YAAY,IAAI,IAAI,EAC/C,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,IAAI,EAC3C,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EACvB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EACvB,gBAAgB,EAAE,KAAK,GACvB,CACH;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,UAAyB;IAClD,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IACzC,OAAO,QAAQ,CACb,OAAO,CAAC;QACN,UAAU,EAAE,UAAU,CAAC,EAAE;QACzB,kFAAkF;QAClF,iFAAiF;QACjF,KAAK,EAAE,cAAc,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;QAC5D,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;KAC3E,CAAC,CACH,CAAA;AACH,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,cAAc,CAAC,SAA4B,EAAE;IAC3D,OAAO;QACL,EAAE,EAAE,YAAY;QAChB,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,4BAA4B,CAAC,CAAA;QAChD,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,MAAM;QAC3B,mBAAmB,EAAE,YAAY;QACjC,SAAS,EAAE,kBAAkB;QAC7B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CACf,KAAC,gBAAgB,IACf,SAAS,EAAE,GAAG,CAAC,SAAS,EACxB,WAAW,EAAE,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,IAAI,EAC7C,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,GACvB,CACH;KACF,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,SAA8B,EAAE;IAC5D,oFAAoF;IACpF,oFAAoF;IACpF,sFAAsF;IACtF,sFAAsF;IACtF,MAAM,MAAM,GAAG,mBAAmB,CAAC,sBAAsB,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAA;IAC/F,OAAO;QACL,EAAE,EAAE,WAAW;QACf,IAAI,KAAK;YACP,gFAAgF;YAChF,8EAA8E;YAC9E,kFAAkF;YAClF,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YACrD,MAAM,OAAO,GAAG,cAAc,CAAC,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YAClE,OAAO,CACL,MAAM,CAAC,KAAK;gBACZ,CAAC,UAAU,IAAI,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC,CACvF,CAAA;QACH,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,2BAA2B,CAAC,CAAA;QAC/C,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,QAAQ;QAC7B,mBAAmB,EAAE,KAAK;QAC1B,SAAS,EAAE,iBAAiB;QAC5B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACnD,KAAC,gBAAgB,IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,qBAAqB,CAAC,GAAI,CACvF,CAAC,CAAC,CAAC,CACF,KAAC,eAAe,IACd,SAAS,EAAE,GAAG,CAAC,SAAS,EACxB,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,IAAI,EAC3C,MAAM,EAAE,MAAM,GACd,CACH;KACJ,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAiD,EACjD,MAAwB;IAExB,MAAM,IAAI,GAAG,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,eAAe,CAAA;IAC5E,OAAO,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAA;AACvE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,SAA2B,EAAE;IACtD,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,kBAAkB,CAAC,CAAA;QACtD,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,wBAAwB,CAAC,CAAA;QAC5C,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,KAAK;QAC1B,mBAAmB,EAAE,QAAQ;QAC7B,SAAS,EAAE,cAAc;QACzB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CACf,KAAC,cAAc,IAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAC,QAAQ,EAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,YACjF,GAAG,CAAC,QAAQ,CAAC,OAAO,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAEnD,KAAC,YAAY,IACX,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAChF,OAAO,SACP,CACH,CAAC,CAAC,CAAC,CAEF,KAAC,cAAc,cACZ,CAAC,GAAG,EAAE,EAAE;oBACP,MAAM,GAAG,GAAG,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;oBAC7C,MAAM,MAAM,GAAG,mBAAmB,CAAC,4BAA4B,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAA;oBAClF,OAAO,CACL,KAAC,YAAY,IACX,SAAS,EAAE,GAAG,CAAC,SAAS,EACxB,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,IAAI,EACrC,MAAM,EAAE,MAAM,GACd,CACH,CAAA;gBACH,CAAC,GACc,CAClB,GACc,CAClB;KACF,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,SAA4B,EAAE;IACtD,OAAO;QACL,EAAE,EAAE,OAAO;QACX,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,iBAAiB,CAAC,CAAA;QACrD,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,uBAAuB,CAAC,CAAA;QAC3C,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,KAAK;QAC1B,mBAAmB,EAAE,OAAO;QAC5B,gFAAgF;QAChF,4EAA4E;QAC5E,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACnD,KAAC,gBAAgB,IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,iBAAiB,CAAC,GAAI,CACnF,CAAC,CAAC,CAAC,CACF,KAAC,WAAW,IAAC,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,IAAI,GAAI,CAC/E;KACJ,CAAA;AACH,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,cAAc,CAAC,SAA4B,EAAE;IAC3D,OAAO;QACL,EAAE,EAAE,YAAY;QAChB,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,4BAA4B,CAAC,CAAA;QAChD,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,MAAM;QAC3B,mBAAmB,EAAE,YAAY;QACjC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACnD,KAAC,gBAAgB,IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,sBAAsB,CAAC,GAAI,CACxF,CAAC,CAAC,CAAC,CACF,KAAC,cAAc,KAAG,CACnB;KACJ,CAAA;AACH,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,YAAY,CAAC,SAA4B,EAAE;IACzD,OAAO;QACL,EAAE,EAAE,UAAU;QACd,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,oBAAoB,CAAC,CAAA;QACxD,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,0BAA0B,CAAC,CAAA;QAC9C,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,YAAY;QACjC,mBAAmB,EAAE,UAAU;QAC/B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CACf,KAAC,cAAc,IAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAC,UAAU,EAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,YACpF,KAAC,YAAY,IAAC,OAAO,EAAE,GAAG,CAAC,OAAO,GAAI,GACvB,CAClB;KACF,CAAA;AACH,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,+DAA+D;QAC5E,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAIf,KAAC,YAAY,IACX,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE;gBACN,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;oBACrB,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;oBACjD,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC;aACrC,EACD,OAAO,SACP,CACH;KACF,CAAA;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,UAAU,CAAC,MAAwB;IACjD,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QACtC,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,KAAK;QAC/C,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,UAAU;QAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3F,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAA6B;IACrD,cAAc,EAAE;IAChB,OAAO,EAAE;IACT,aAAa,EAAE;IACf,UAAU,EAAE;IACZ,cAAc,EAAE;IAChB,SAAS,EAAE;IACX,YAAY,EAAE;IACd,WAAW,EAAE;CACd,CAAA;AAED;;;;;;;GAOG;AACH;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,IAAmB,EAAE,MAAmC;IACjF,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAChC,8EAA8E;IAC9E,qFAAqF;IACrF,8DAA8D;IAC9D,OAAO,MAAM,CAAC,gBAAgB,CAAC,EAAmB,EAAE;QAClD,GAAG,MAAM,CAAC,yBAAyB,CAAC,IAAI,CAAC;QACzC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;KACzF,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB;;;;OAIG;IACH,GAAG,EAAE,CAAC,SAAwB,EAAE,EAAmB,EAAE;QACnD,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAA;QACvD,0EAA0E;QAC1E,IAAI,MAAM,CAAC,UAAU;YAAE,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC5C,oFAAoF;QACpF,MAAM,IAAI,GAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QACpD,IAAI,MAAM,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAA;;YAElD,IAAI,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,EAAE,CAC5B,mBAAmB,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;QAC/E,OAAO,IAAI,CAAA;IACb,CAAC;IACD,sCAAsC;IACtC,UAAU,EAAE,CAAC,SAA4B,EAAE,EAAmB,EAAE,CAC9D,QAAQ,CAAC,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7D;mFAC+E;IAC/E,MAAM,EAAE,CAAC,SAA2B,EAAE,EAAmB,EAAE,CACzD,QAAQ,CAAC,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACzD,mFAAmF;IACnF,SAAS,EAAE,CAAC,SAA8B,EAAE,EAAmB,EAAE,CAC/D,QAAQ,CAAC,iBAAiB,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,wBAAwB;IACxB,QAAQ,EAAE,CAAC,SAA4B,EAAE,EAAmB,EAAE,CAC5D,QAAQ,CAAC,iBAAiB,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3D,kCAAkC;IAClC,UAAU,EAAE,CAAC,SAA4B,EAAE,EAAmB,EAAE,CAC9D,QAAQ,CAAC,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7D,uCAAuC;IACvC,KAAK,EAAE,CAAC,SAA4B,EAAE,EAAmB,EAAE,CACzD,QAAQ,CAAC,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACxD,+EAA+E;IAC/E,IAAI,EAAE,CAAC,MAAwB,EAAmB,EAAE,CAClD,QAAQ,CAAC,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACzD,6DAA6D;IAC7D,KAAK,EAAE,CAAC,KAAa,EAAE,QAA2B,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC;IACjF,uCAAuC;IACvC,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE;CACxB,CAAA;AAEV,oEAAoE;AAEpE;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,WAAW,EACX,KAAK,GAKN;IACC,IAAI,KAAK,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,sBAAsB,CAAC,GAAI,CAAA;IACrF,CAAC;IACD,OAAO,CACL,eAAK,SAAS,EAAC,qBAAqB,aAClC,gBAAO,SAAS,EAAC,0EAA0E,YACzF,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,WAAW,IAAI,SAAS,GAAI,GAChF,EACR,cAAK,SAAS,EAAC,8BAA8B,YAC1C,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,CACtB,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,GAAI,CAC1C,CAAC,CAAC,CAAC,CACF,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,GAAI,CACpE,GACG,IACF,CACP,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,4BAA4B,CAC1C,GAAuB,EACvB,GAAgC,EAChC,aAAsB,gBAAgB,EAAE;IAExC,OAAO,GAAG,EAAE,WAAW,IAAI,GAAG,EAAE,WAAW,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,4BAA4B,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAC1G,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,0BAA0B,CACxC,GAAuB,EACvB,GAAgC,EAChC,cAA2C,4BAA4B,CAAC,GAAG,EAAE,GAAG,CAAC;IAEjF,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC,SAAS,CAAA;IAC7B,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC,SAAS,CAAA;IAC7B,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAC9D,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAC7F,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACjC,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QACtD,OAAO,UAAU,CAAC,CAAC,CAAC,iCAAiC,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAC9E,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,6BAA6B,CAC3C,GAAuB,EACvB,GAAgC,EAChC,OAAgB,EAChB,gBAA0C,6BAA6B,EAAE;IAEzE,IAAI,aAAa;QAAE,OAAO,aAAa,CAAA;IACvC,IAAI,GAAG;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,aAAa,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAA;IACnG,OAAO,GAAG,EAAE,YAAY,CAAA;AAC1B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,EACjB,OAAO,EACP,YAAY,EACZ,UAAU,EACV,KAAK,EACL,KAAK,EACL,gBAAgB,GAQjB;IACC,MAAM,SAAS,GAAG,OAAO,CAAC,EAAE,CAAA;IAC5B,IAAI,KAAK,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,GAAI,CAAA;IAC9E,CAAC;IAED,8FAA8F;IAC9F,MAAM,GAAG,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAA;IAEzC,OAAO,CAIL,KAAC,cAAc,cACZ,CAAC,GAAG,EAAE,EAAE;YACP,sEAAsE;YACtE,yEAAyE;YACzE,MAAM,kBAAkB,GAAG,4BAA4B,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YACjE,OAAO,CACL,KAAC,WAAW,IAEV,SAAS,EAAE,SAAS,EACpB,kBAAkB,EAAE,kBAAkB,EACtC,gBAAgB,EAAE,0BAA0B,CAAC,GAAG,EAAE,GAAG,EAAE,kBAAkB,CAAC,EAC1E,oBAAoB,EAAE,GAAG,EAAE,aAAa,EACxC,iBAAiB,EAAE,GAAG,EAAE,UAAU,EAClC,wBAAwB,EAAE,GAAG,EAAE,iBAAiB,EAChD,kBAAkB,EAAE,GAAG,EAAE,WAAW,EACpC,mBAAmB,EAAE,6BAA6B,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,YAErE,eAAK,SAAS,EAAC,8BAA8B,aAC3C,KAAC,qBAAqB,KAAG,EAKzB,eAAK,SAAS,EAAC,qCAAqC,aAClD,cAAK,SAAS,EAAC,gCAAgC,YAC7C,KAAC,UAAU,IACT,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,KAAK,EACZ,gBAAgB,EAAE,gBAAgB,GAClC,GACE,EACN,KAAC,UAAU,KAAG,IACV,IACF,IA5BD,SAAS,CA6BF,CACf,CAAA;QACH,CAAC,GACc,CAClB,CAAA;AACH,CAAC;AAED,iFAAiF;AACjF,SAAS,UAAU,CAAC,EAClB,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,EACL,gBAAgB,GAOjB;IACC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IAEpB,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,KAAC,QAAQ,IAAC,SAAS,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,GAAI,CAAA;IAExG,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,MAAM,UAAU,GAAG,GAAG,CAAC,aAAa,CAAC,YAAY,CAAC,CAAA;QAClD,4EAA4E;QAC5E,4EAA4E;QAC5E,2EAA2E;QAC3E,IAAI,UAAU,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1C,OAAO,KAAC,aAAa,IAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,GAAI,CAAA;QAC5E,CAAC;QACD,OAAO,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,GAAI,CAAA;IAC9E,CAAC;IAED,OAAO,CACL,KAAC,YAAY,IACX,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,KAAK,GAChB,CACH,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,EAAE,SAAS,EAAE,YAAY,EAA+C;IAC7F,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,UAAU,GAAG,GAAG,CAAC,aAAa,CAAC,YAAY,CAAC,CAAA;IAClD,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;IAC1C,MAAM,UAAU,GAAG,UAAU;QAC3B,CAAC,CAAC,wBAAwB,CAAC,UAAU,EAAE,IAAI,CAAC;QAC5C,CAAC,CAAE,EAAE,IAAI,EAAE,OAAO,EAAY,CAAA;IAEhC,+EAA+E;IAC/E,0EAA0E;IAC1E,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO;YAAE,GAAG,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAA;IAC5E,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAA;IAExC,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,CACL,KAAC,YAAY,IAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,GAAI,CAClG,CAAA;IACH,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,GAAI,CAAA;IAC9E,CAAC;IACD,OAAO,CACL,cAAK,SAAS,EAAC,oCAAoC,YACjD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,4BAA4B,EAAE;gBACvC,KAAK,EAAE,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC;aAC7D,CAAC,GACG,GACH,CACP,CAAA;AACH,CAAC;AAED,yEAAyE;AACzE,SAAS,qBAAqB;IAC5B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,IAAI,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO,IAAI,CAAA;IAClC,OAAO,CACL,cAAK,SAAS,EAAC,qCAAqC,YAClD,KAAC,oBAAoB,IAAC,KAAK,EAAE,GAAG,CAAC,YAAY,EAAE,SAAS,EAAE,GAAG,CAAC,iBAAiB,GAAI,GAC/E,CACP,CAAA;AACH,CAAC","sourcesContent":["import { useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport {\n Blocks,\n Database,\n FileCog,\n Image,\n LayoutGrid,\n Puzzle,\n Settings as SettingsIcon,\n Signpost,\n Users,\n} from 'lucide-react'\nimport {\n assetCollection,\n extendCollectionDef,\n isSingleton,\n resolveSingletonDocument,\n sampleDocuments,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n CollectionFieldOverride,\n CollectionFormMeta,\n LocaleConfig,\n} from '@elytracms/core/cms-core'\nimport { reconcileSeedDocumentLocalization } from '@elytracms/core/persistence'\nimport type { Project, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { NavigateOnce, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { getInstantiatedKit } from '@elytracms/studio/lib/starters/kit-session'\nimport type { InstantiatedKit } from '@elytracms/core/starter-kits'\nimport { isPlaygroundMode } from '@elytracms/studio/lib/persistence'\nimport { resolveDeploymentCollections, resolveDeploymentLocaleConfig } from '@elytracms/studio/lib/workspace'\nimport type { HydratedCms } from '@elytracms/studio/lib/operations'\nimport {\n cmsDeepLink,\n componentsDeepLink,\n usersDeepLink,\n redirectsDeepLink,\n assetsDeepLink,\n groupNode,\n separatorNode,\n viewNode,\n type AppViewContext,\n type AppViewDeepLink,\n type AppViewModule,\n type NavPermission,\n type SidebarViewNode,\n} from './module'\nimport { SectionSurface } from '../section-surface'\nimport { SectionStateView } from '../section-state'\nimport { SettingsForm } from '../settings-form'\nimport { AssetsModule } from '../assets/assets-module'\nimport { UsersModule } from '../users/users-module'\nimport { RedirectsModule } from '../redirects/redirects-module'\nimport { redirectsCollectionDef } from '../redirects/redirects-schema'\nimport { ExtensionsView } from '../extensions/extensions-view'\nimport { RegistryBrowser } from '../components/registry-browser'\nimport { ComponentsIndex } from '../components/components-index'\nimport { ComponentDetail } from '../components/component-detail'\nimport { CmsSessionGate } from '../project-session'\nimport { CmsProvider, useCms } from '../cms/cms-store'\nimport { AiCmsPanel } from '../ai/ai-cms-panel'\nimport { focusCollectionList } from '../cms/collection-focus'\nimport { CmsIndex } from '../cms/cms-index'\nimport { CollectionTable } from '../cms/collection-table'\nimport { DetailEditor } from '../cms/detail-editor'\nimport { OperationErrorBanner } from '../operation-error'\n\n/**\n * EC-237/239 — the studio's BUILT-IN views + the consumer-facing `nav` namespace,\n * an instance of the EC-236 customization seam.\n *\n * Each built-in surface (the former `StudioSectionResolver` case body) is promoted\n * to a self-contained {@link AppViewModule} factory. The `nav` namespace is the\n * single, typed builder a consuming repo's `studio.config.tsx` uses to declare its\n * section rail — `nav.cms(...)`, `nav.group(...)`, `nav.view(...)` — passing the\n * view objects (and a focus config) DIRECTLY, never a string id. The CMS surface\n * is a factory ({@link cmsView}) so a focused view (\"Catalog\", \"Categories\") is the\n * SAME surface instantiated with config, not a copy — the EC-239 reuse proof.\n */\n\n// --- The CMS surface, reusable via a config factory (the EC-239 reuse seam) ---\n\n/** Config for a CMS-backed view — `collections`/`collection` focus the lens (EC-239). */\nexport interface CmsViewConfig {\n /** Explicit stable id (the route segment). Omitted → derived from the focus. */\n id?: string\n label?: string\n description?: string\n icon?: LucideIcon\n /**\n * Scope the rail auto-expansion (EC-293) to these collection ids, in order. The\n * parent expands into one focused-lens child per id; the parent index shows the\n * same subset. Omitted → every collection. Ignored for a single-collection lens.\n */\n collections?: readonly string[]\n /** Single-collection lens: a LEAF rail item that opens straight into this collection. */\n collection?: string\n /**\n * EC-293 escape hatch: explicit rail children for this CMS parent, overriding the\n * default per-collection auto-expansion. Ignored for a single-collection lens.\n */\n children?: SidebarViewNode[]\n /** The availability section this view rides on (EC-221); defaults to `cms`. */\n availabilitySection?: SectionId\n /** OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18). See {@link BuiltinViewConfig.visible}. */\n visible?: NavPermission\n}\n\n/** Common label/icon overrides for a built-in view (`nav.redirects({ label: 'URLs' })`). */\nexport interface BuiltinViewConfig {\n label?: string\n icon?: LucideIcon\n /**\n * OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18): hide this\n * rail item for principals it returns `false` for; absent → always visible. The\n * policy (which roles) lives in the consuming project, never the engine. E.g.\n * `nav.users({ visible: ({ role }) => role === 'admin' })`.\n */\n visible?: NavPermission\n}\n\n/**\n * Config for the **redirects** rail (EC-311) — a built-in view whose engine-owned\n * schema is customizable. Beyond the shared label/icon/visible, a mount may rename\n * the rail (`form`) and **override built-in fields by equal name** (`fields`):\n * naming, permissions (`form.readOnly`), rendering (`form.control` → `fieldRenderers`)\n * and validation. The canonical `from`/`to`/`permanent`/`enabled` names+types are\n * contract-locked (a type change on one is a loud config error), but a *net-new*\n * field is allowed (EC-311 Tier 2): it's appended and persists in the redirect\n * record's `meta` bag — editorial only, so it never reaches delivery/routing.\n */\nexport interface RedirectsViewConfig extends BuiltinViewConfig {\n /** Rename the rail (e.g. `{ label: 'URLs' }`). */\n form?: Partial<CollectionFormMeta>\n /** Override built-in redirect fields, matched by `name`. */\n fields?: readonly CollectionFieldOverride[]\n}\n\n/**\n * Config for the **assets** rail (EC-319), customizable by the SAME EC-311\n * mechanism as redirects (`form`/`fields`) — but the BASE schema it overrides is\n * different in kind: redirects' base is an engine-owned, fixed `CollectionDef`\n * known at config-build time; the assets base is the DEPLOYMENT's own\n * `kind:'asset'` collection (a project's `cms/collections/asset.ts`), only\n * knowable once the deployment resolves (hydrated session → kit → playground\n * config — see {@link resolveAssetsSchema}). So the merge runs at RENDER time\n * (inside `assetsView`'s `CmsSessionGate`), not at `nav.assets(config)` call\n * time: a locked-violation override (e.g. changing a declared field's type)\n * surfaces the first time the Assets section renders, not at `defineStudio`\n * mount — the one place this rail's customization contract differs from\n * redirects', a direct consequence of its base schema being project-declared.\n */\nexport interface AssetsViewConfig extends BuiltinViewConfig {\n /** Rename the rail (e.g. `{ label: 'Media' }`). */\n form?: Partial<CollectionFormMeta>\n /** Override the project's declared asset fields, matched by `name`. */\n fields?: readonly CollectionFieldOverride[]\n}\n\n/** Config for a fully custom view (`nav.view({...})`) — the net-new surface escape hatch. */\nexport interface CustomViewConfig {\n /** Explicit stable id (the route segment). Omitted → a slug of the label. */\n id?: string\n label: string\n description?: string\n icon?: LucideIcon\n render: (ctx: AppViewContext) => ReactNode\n parsePath?: (segments: string[]) => AppViewDeepLink | null\n availabilitySection?: SectionId\n /** OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18). See {@link BuiltinViewConfig.visible}. */\n visible?: NavPermission\n}\n\nfunction slugify(label: string): string {\n return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'view'\n}\n\n/** Derive a stable, deterministic id from the CMS focus (overridable via `config.id`). */\nfunction cmsViewId(config: CmsViewConfig): string {\n if (config.id) return config.id\n if (config.collection) return `cms:${config.collection}`\n if (config.collections && config.collections.length > 0) {\n return `cms:${[...config.collections].join('-')}`\n }\n return 'cms'\n}\n\n/**\n * Build a CMS view — the default `cms` view, or a focused instance. Every instance\n * shares one surface ({@link CmsScreen}); `collections` narrows the sidebar + index\n * to a subset, `collection` is a single-collection lens that opens straight into\n * that collection's list. The id is derived from the focus (deterministic, stable\n * across rail reorders) unless `id` is given — so two focused views never collide.\n */\nexport function cmsView(config: CmsViewConfig = {}): AppViewModule {\n const single = config.collection\n const focus = single ? [single] : config.collections\n return {\n id: cmsViewId(config),\n // Lazy label/description (EC-241): the sidebar is built when the consumer's\n // `studio.config` module loads — BEFORE `createStudio` seeds the i18n config — so\n // resolving here would bake English. A getter defers resolution to render, when\n // the config is present and the browser locale is known.\n get label() {\n return config.label ?? translate('nav.cms.label')\n },\n get description() {\n return config.description ?? translate('nav.cms.description')\n },\n icon: config.icon ?? Database,\n availabilitySection: config.availabilitySection ?? 'cms',\n // A single-collection lens routes `[]`→that collection, `[d]`→its document;\n // an unfocused / multi-collection view uses the full index/collection/document arity.\n parsePath: single\n ? (segments) =>\n segments.length === 0\n ? { collectionId: single, documentId: null }\n : segments.length === 1\n ? { collectionId: single, documentId: segments[0]! }\n : null\n : cmsDeepLink,\n render: (ctx) => (\n <CmsScreen\n project={ctx.project}\n collectionId={ctx.deepLink.collectionId ?? null}\n documentId={ctx.deepLink.documentId ?? null}\n state={ctx.search.state}\n field={ctx.search.field}\n focusCollections={focus}\n />\n ),\n }\n}\n\n/**\n * A focused-lens rail child for one collection (EC-293) — the SAME `cms:<id>` view a\n * single-collection `nav.cms({ collection })` produces, carrying the collection's own\n * label + a singleton-aware icon (the former `CollectionsSidebar` row, lifted into\n * the rail). CMS auto-expansion maps the project's collections through this.\n */\nfunction cmsCollectionLeaf(collection: CollectionDef): SidebarViewNode {\n const singleton = isSingleton(collection)\n return viewNode(\n cmsView({\n collection: collection.id,\n // Non-hook `localizedLabel` resolves the (possibly `{ en, de }`) collection label\n // against the active UI locale, so a German editor sees German collection names.\n label: localizedLabel(collection.form?.label, collection.id),\n icon: singleton ? FileCog : collection.kind === 'asset' ? Image : Database,\n }),\n )\n}\n\n/** The components registry browser view. */\nexport function componentsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'components',\n get label() {\n return config.label ?? translate('nav.components.label')\n },\n get description() {\n return translate('nav.components.description')\n },\n icon: config.icon ?? Blocks,\n availabilitySection: 'components',\n parsePath: componentsDeepLink,\n render: (ctx) => (\n <ComponentsScreen\n projectId={ctx.projectId}\n componentId={ctx.deepLink.componentId ?? null}\n state={ctx.search.state}\n />\n ),\n }\n}\n\n/**\n * The Redirects view (EC-303) — editorial redirects as DATA, an editor-owned CRUD\n * surface (create/edit/delete) over the portable list/edit surfaces. Routes are no\n * longer shown in the admin (they're code; native `next.config`/`vercel` own static\n * forwards, evaluated before these). Rides on the CMS availability (a content/\n * routing concern). `[]` is the list; `[redirectId]` opens that redirect's detail.\n */\nexport function redirectsView(config: RedirectsViewConfig = {}): AppViewModule {\n // EC-311: merge any field/label overrides over the engine-owned def ONCE, at config\n // build (config-as-code — a locked violation throws here, at `defineStudio` mount).\n // Tier 2: `additiveFields: 'allow'` — a net-new field is appended and persists in the\n // redirect record's `meta` bag (the canonical from/to/permanent/enabled stay locked).\n const schema = extendCollectionDef(redirectsCollectionDef, config, { additiveFields: 'allow' })\n return {\n id: 'redirects',\n get label() {\n // Prefer an explicit rail label, then a consumer-*customized* collection label,\n // else the localized catalog nav label. Never let the built-in English schema\n // label ('Redirects') preempt translation — compare against the built-in default.\n const customized = localizedLabel(schema.form?.label)\n const builtin = localizedLabel(redirectsCollectionDef.form?.label)\n return (\n config.label ??\n (customized && customized !== builtin ? customized : translate('nav.redirects.label'))\n )\n },\n get description() {\n return translate('nav.redirects.description')\n },\n icon: config.icon ?? Signpost,\n availabilitySection: 'cms',\n parsePath: redirectsDeepLink,\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title={translate('nav.redirects.label')} />\n ) : (\n <RedirectsModule\n projectId={ctx.projectId}\n redirectId={ctx.deepLink.redirectId ?? null}\n schema={schema}\n />\n ),\n }\n}\n\n/**\n * Resolve the assets rail's EFFECTIVE schema (EC-319/EC-311): the deployment's\n * OWN `kind:'asset'` collection — hydrated session → kit → playground config,\n * the exact same precedence {@link resolveCmsInitialCollections} gives every\n * other CMS collection — falling back to the engine's `assetCollection` fixture\n * only when nothing resolves (mirroring `CmsProvider`'s own fallback). A mount's\n * `nav.assets({ form, fields })` then merges over THAT base (EC-311, additive\n * fields allowed — a project's asset record already carries an open `meta` bag,\n * EC-301). Pure — no React — so it's unit-testable without `CmsSessionGate`.\n */\nexport function resolveAssetsSchema(\n collections: readonly CollectionDef[] | undefined,\n config: AssetsViewConfig,\n): CollectionDef {\n const base = collections?.find((c) => c.kind === 'asset') ?? assetCollection\n return extendCollectionDef(base, config, { additiveFields: 'allow' })\n}\n\n/**\n * The Assets view (EC-319) — the universal list (+ grid/list toggle) and a\n * bespoke per-asset detail surface, over the asset library store. `[]` is the\n * list; `[assetId]` opens that asset's detail (routed via `studioPath.asset`).\n * A `?asset=` deep link (EC-172 \"used by\" links) redirects straight to the\n * asset's detail — the surface those links used to scroll a card into and\n * auto-open, now a first-class route.\n */\nexport function assetsView(config: AssetsViewConfig = {}): AppViewModule {\n return {\n id: 'assets',\n get label() {\n return config.label ?? translate('nav.assets.label')\n },\n get description() {\n return translate('nav.assets.description')\n },\n icon: config.icon ?? Image,\n availabilitySection: 'assets',\n parsePath: assetsDeepLink,\n render: (ctx) => (\n <SectionSurface project={ctx.project} section=\"assets\" forcedState={ctx.search.state}>\n {ctx.deepLink.assetId === null && ctx.search.asset ? (\n // EC-441: `NavigateOnce` — render-time redirects fire once per intent.\n <NavigateOnce\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId: ctx.projectId, _splat: studioPath.asset(ctx.search.asset) }}\n replace\n />\n ) : (\n // The AssetsProvider is mounted at the project level (EC-195).\n <CmsSessionGate>\n {(cms) => {\n const kit = getInstantiatedKit(ctx.projectId)\n const schema = resolveAssetsSchema(resolveCmsInitialCollections(cms, kit), config)\n return (\n <AssetsModule\n projectId={ctx.projectId}\n assetId={ctx.deepLink.assetId ?? null}\n schema={schema}\n />\n )\n }}\n </CmsSessionGate>\n )}\n </SectionSurface>\n ),\n }\n}\n\n/**\n * The studio Users view (EC-240) — an admin-only roster of who has access to this\n * deployment and their role. A first-class section (its own rail entry), modeled\n * like the Assets library; the surface itself gates to admins and renders a\n * \"restricted\" state otherwise. Rides on the `settings` availability (an\n * admin/config concern, so it appears/hides with a project's settings surface).\n */\nexport function usersView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'users',\n get label() {\n return config.label ?? translate('nav.users.label')\n },\n get description() {\n return translate('nav.users.description')\n },\n icon: config.icon ?? Users,\n availabilitySection: 'users',\n // EC-240: `[]` is the roster; `[userId]` opens that member in the portable edit\n // shell (AD-19 reuse — the same `DetailEditor` body over the users source).\n parsePath: usersDeepLink,\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title={translate('nav.users.label')} />\n ) : (\n <UsersModule projectId={ctx.projectId} userId={ctx.deepLink.userId ?? null} />\n ),\n }\n}\n\n/** The extensions marketplace view. */\nexport function extensionsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'extensions',\n get label() {\n return config.label ?? translate('nav.extensions.label')\n },\n get description() {\n return translate('nav.extensions.description')\n },\n icon: config.icon ?? Puzzle,\n availabilitySection: 'extensions',\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title={translate('nav.extensions.label')} />\n ) : (\n <ExtensionsView />\n ),\n }\n}\n\n/** The project settings view. */\nexport function settingsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'settings',\n get label() {\n return config.label ?? translate('nav.settings.label')\n },\n get description() {\n return translate('nav.settings.description')\n },\n icon: config.icon ?? SettingsIcon,\n availabilitySection: 'settings',\n render: (ctx) => (\n <SectionSurface project={ctx.project} section=\"settings\" forcedState={ctx.search.state}>\n <SettingsForm project={ctx.project} />\n </SectionSurface>\n ),\n }\n}\n\n/** The legacy `builder` redirect — a hidden, routable view (so `/builder?page=` still lands). */\nexport function builderView(): AppViewModule {\n return {\n id: 'builder',\n label: 'Builder',\n description: 'Legacy builder route — redirects to the page document editor.',\n icon: Blocks,\n hidden: true,\n render: (ctx) => (\n // Retired surface (EC-187 slice 6): a page is a document in the `page`\n // collection. `?page=<id>` lands on that document; else the page list.\n // EC-441: `NavigateOnce` — render-time redirects fire once per intent.\n <NavigateOnce\n to={STUDIO_PROJECT_ROUTE}\n params={{\n projectId: ctx.projectId,\n _splat: ctx.search.page\n ? studioPath.cmsDocument('page', ctx.search.page)\n : studioPath.cmsCollection('page'),\n }}\n replace\n />\n ),\n }\n}\n\n/** Build a fully custom view (the `nav.view(...)` escape hatch). */\nexport function customView(config: CustomViewConfig): AppViewModule {\n return {\n id: config.id ?? slugify(config.label),\n label: config.label,\n description: config.description ?? config.label,\n icon: config.icon ?? LayoutGrid,\n render: config.render,\n ...(config.parsePath ? { parsePath: config.parsePath } : {}),\n ...(config.availabilitySection ? { availabilitySection: config.availabilitySection } : {}),\n }\n}\n\n/**\n * The studio's built-in views in canonical rail order (components · CMS · routes ·\n * assets · extensions · settings), plus the hidden `builder` redirect (routable but\n * unlisted). The default rail (no `defineStudio({ sidebar })`) is these as plain\n * entries; the router registry always includes them so deep-links resolve even\n * when a consumer's rail omits one.\n */\nexport const DEFAULT_VIEWS: readonly AppViewModule[] = [\n componentsView(),\n cmsView(),\n redirectsView(),\n assetsView(),\n extensionsView(),\n usersView(),\n settingsView(),\n builderView(),\n]\n\n/**\n * The `nav` namespace — the single typed builder for `studio.config.tsx`'s section\n * rail. Each call returns a {@link SidebarViewNode} (or a group / separator) that\n * carries its view DIRECTLY: `nav.cms({ collection })` is a focused CMS view,\n * `nav.view({ render })` a net-new surface, `nav.group(label, [...])` a header with\n * sub-views, `nav.separator()` a divider. No string ids, no `moduleId` — the rail\n * is the views.\n */\n/**\n * Forward the OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18)\n * from a `nav.*` config onto the built view, so gating is settable on EVERY nav\n * item — not just Users. Absent → the view is unchanged (always visible).\n */\nfunction withNavPermission(view: AppViewModule, config: { visible?: NavPermission }): AppViewModule {\n if (!config.visible) return view\n // Copy property DESCRIPTORS (not a spread) so a built-in view's lazy `label`/\n // `description` getters (EC-241) survive — a spread would invoke them at build time,\n // baking English before `createStudio` seeds the i18n config.\n return Object.defineProperties({} as AppViewModule, {\n ...Object.getOwnPropertyDescriptors(view),\n visible: { value: config.visible, enumerable: true, configurable: true, writable: true },\n })\n}\n\nexport const nav = {\n /**\n * The CMS view. `nav.cms()` / `nav.cms({ collections })` is a PARENT that nests its\n * collections in the rail (each a focused `cms:<id>` lens); `nav.cms({ collection })`\n * is a single-collection LEAF; `nav.cms({ children })` supplies explicit children.\n */\n cms: (config: CmsViewConfig = {}): SidebarViewNode => {\n const view = withNavPermission(cmsView(config), config)\n // A single-collection lens is a leaf — it opens that collection directly.\n if (config.collection) return viewNode(view)\n // Otherwise a parent that expands its collections as nested rail children (EC-293).\n const node: SidebarViewNode = { kind: 'view', view }\n if (config.children) node.children = config.children\n else\n node.expand = (collections) =>\n focusCollectionList(collections, config.collections).map(cmsCollectionLeaf)\n return node\n },\n /** The component registry browser. */\n components: (config: BuiltinViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(componentsView(config), config)),\n /** The assets rail — the universal list/grid + detail (EC-319); built-in\n * fields customizable by name (EC-311), the same shape as `nav.redirects`. */\n assets: (config: AssetsViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(assetsView(config), config)),\n /** Editorial redirects (EC-303); built-in fields customizable by name (EC-311). */\n redirects: (config: RedirectsViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(redirectsView(config), config)),\n /** Project settings. */\n settings: (config: BuiltinViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(settingsView(config), config)),\n /** The extensions marketplace. */\n extensions: (config: BuiltinViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(extensionsView(config), config)),\n /** The admin users roster (EC-240). */\n users: (config: BuiltinViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(usersView(config), config)),\n /** A fully custom view — pass your own `render` (and optional `parsePath`). */\n view: (config: CustomViewConfig): SidebarViewNode =>\n viewNode(withNavPermission(customView(config), config)),\n /** A labelled group of views (a header with sub-entries). */\n group: (label: string, children: SidebarViewNode[]) => groupNode(label, children),\n /** A divider between rail sections. */\n separator: () => separatorNode(),\n} as const\n\n// --- Surfaces (the view render bodies; CMS gains a focus lens) ---\n\n/**\n * Components section (EC-050): a registry-browser sidebar beside the active\n * screen — the index (no selection) or a component's detail. Forced states\n * (?state=) replace the whole surface.\n */\nfunction ComponentsScreen({\n projectId,\n componentId,\n state,\n}: {\n projectId: string\n componentId: string | null\n state?: SectionState\n}) {\n if (state && state !== 'working') {\n return <SectionStateView state={state} title={translate('nav.components.label')} />\n }\n return (\n <div className=\"flex h-full min-h-0\">\n <aside className=\"hidden h-full min-h-0 w-[300px] shrink-0 border-r border-border md:block\">\n <RegistryBrowser projectId={projectId} activeComponentId={componentId ?? undefined} />\n </aside>\n <div className=\"min-h-0 flex-1 overflow-auto\">\n {componentId === null ? (\n <ComponentsIndex projectId={projectId} />\n ) : (\n <ComponentDetail projectId={projectId} componentId={componentId} />\n )}\n </div>\n </div>\n )\n}\n\n/**\n * The CMS schema source for a project mount (bug fix, playground-collections):\n * a hydrated session wins (Convex), then a kit's imported schema (EC-119), then —\n * in playground mode only — the active project's config-declared collections\n * (AD-11, `resolveDeploymentCollections`). Without this last fallback, a non-kit\n * playground project has no hydrated session (`null`, by design — see\n * `project-session.tsx`) and no kit, so `CmsProvider` fell through to the core's\n * hardcoded `sampleFilterableCollections` fixture instead of the project's own\n * on-disk schema. Convex mode's first-run `null` is untouched — it still seeds\n * from the fixture there, as before. `playground` defaults to the live\n * {@link isPlaygroundMode} but is overridable so this stays unit testable\n * without stubbing the env (same seam as `resolveBackendConfig`).\n */\nexport function resolveCmsInitialCollections(\n cms: HydratedCms | null,\n kit: InstantiatedKit | undefined,\n playground: boolean = isPlaygroundMode(),\n): CollectionDef[] | undefined {\n return cms?.collections ?? kit?.collections ?? (playground ? resolveDeploymentCollections() : undefined)\n}\n\n/**\n * The CMS documents source for a project mount (EC-424) — the document-side\n * twin of {@link resolveCmsInitialCollections}, same ladder: a hydrated session\n * wins (Convex), then a kit's imported documents, then — in playground mode\n * only, and only when a config project actually resolved collections — the\n * engine's own `sampleDocuments` fixture RECONCILED against them.\n *\n * Without this, a config project whose OWN schema diverges from the shared\n * fixture's localization (e.g. this repo's demo declares `post.slug` localized,\n * EC-315, while the engine's `postCollection` fixture deliberately does not)\n * left `CmsProvider` falling straight through to the un-reconciled\n * `sampleDocuments` default — the CMS surfaces' `initialCollections` already\n * reflected the config schema (via the fallback above), but `initialDocuments`\n * did not, so every seeded post showed EC-390's shape-aware validation's\n * `plain-value` warning + `missing-localized-value` error, standing noise a\n * project never actually wrote.\n *\n * This is the STATIC-baseline twin of `reconcileSeedDocumentLocalization`\n * (`@elytracms/core/persistence`, wired into the seeded persistence adapter for\n * Convex-mode/delivery reads): no adapter round-trip, so the EC-138/EC-320\n * synchronous-first-render contract (`project-session.tsx`: \"CMS surfaces keep\n * their fixture baseline\") is untouched. `collections` defaults to the SAME\n * resolution `resolveCmsInitialCollections` returns, so the two stay in lockstep\n * by construction; only reconciles when that resolved to a non-empty config\n * schema (env-only playground, with nothing config-declared, is unaffected).\n */\nexport function resolveCmsInitialDocuments(\n cms: HydratedCms | null,\n kit: InstantiatedKit | undefined,\n collections: CollectionDef[] | undefined = resolveCmsInitialCollections(cms, kit),\n): CmsDocument[] | undefined {\n if (cms) return cms.documents\n if (kit) return kit.documents\n if (!collections || collections.length === 0) return undefined\n const collectionsById = new Map(collections.map((collection) => [collection.id, collection]))\n return sampleDocuments.map((doc) => {\n const collection = collectionsById.get(doc.collection)\n return collection ? reconcileSeedDocumentLocalization(doc, collection) : doc\n })\n}\n\n/**\n * The CMS locale config for a project mount (EC-357 finding #3). Locales are\n * structure and structure is code: when the active `elytra.config.ts` project\n * declares locales, they drive the ambient locale switcher in EVERY mode. The old\n * wiring only applied the project's locale settings on a HYDRATED session, so a\n * playground boot — or a connected FIRST RUN with nothing persisted yet — fell\n * back to a kit's locales or the core's sample `en/de` fixture and the switcher\n * showed `EN·/DE` against a `locales: ['de']` config. Without a config project\n * (env-only wiring) the previous ladder is unchanged: hydrated project settings,\n * then the kit's locales, then the sample fixture (via `CmsProvider`'s default).\n * `configLocales` is a parameter purely so the ladder is unit-testable (same seam\n * pattern as {@link resolveCmsInitialCollections}).\n */\nexport function resolveCmsInitialLocaleConfig(\n cms: HydratedCms | null,\n kit: InstantiatedKit | undefined,\n project: Project,\n configLocales: LocaleConfig | undefined = resolveDeploymentLocaleConfig(),\n): LocaleConfig | undefined {\n if (configLocales) return configLocales\n if (cms) return { default: project.settings.defaultLocale, locales: [...project.settings.locales] }\n return kit?.localeConfig\n}\n\n/**\n * CMS section: hydrates the persisted (or kit-seeded) collections + documents, then\n * renders the active screen full-width (index, a collection table, or a document\n * editor). EC-293: the collection list lives in the section rail (nested under the\n * CMS view), so there is no in-surface collections sidebar. Forced states (?state=)\n * replace the whole surface. `focusCollections` (EC-239) narrows the index to a\n * subset, so a focused view (\"Catalog\") reuses this surface with config.\n */\nfunction CmsScreen({\n project,\n collectionId,\n documentId,\n state,\n field,\n focusCollections,\n}: {\n project: Project\n collectionId: string | null\n documentId: string | null\n state?: SectionState\n field?: string\n focusCollections?: readonly string[]\n}) {\n const projectId = project.id\n if (state && state !== 'working') {\n return <SectionStateView state={state} title={translate('nav.cms.label')} />\n }\n\n // A project created from a starter kit (EC-119) opens the CMS on the kit's schemas + content.\n const kit = getInstantiatedKit(projectId)\n\n return (\n // EC-177 gap 1: in Convex mode every CMS mount loads the CURRENT persisted\n // schema + documents (fixtures only when nothing is persisted — first run).\n // Playground renders synchronously with `null` → fixture seeds, as before.\n <CmsSessionGate>\n {(cms) => {\n // EC-424: resolved once so `initialDocuments`' reconciliation (below)\n // stays in lockstep with the SAME collections `initialCollections` uses.\n const initialCollections = resolveCmsInitialCollections(cms, kit)\n return (\n <CmsProvider\n key={projectId}\n projectId={projectId}\n initialCollections={initialCollections}\n initialDocuments={resolveCmsInitialDocuments(cms, kit, initialCollections)}\n initialPublishedKeys={cms?.publishedKeys}\n initialTimestamps={cms?.timestamps}\n initialPublishTimestamps={cms?.publishTimestamps}\n initialChangedKeys={cms?.changedKeys}\n initialLocaleConfig={resolveCmsInitialLocaleConfig(cms, kit, project)}\n >\n <div className=\"flex h-full min-h-0 flex-col\">\n <CmsPersistErrorBanner />\n {/* EC-293: the collection list now lives in the section rail (nested\n under the CMS view), so the surface is full-width — the old 240px\n CollectionsSidebar aside is gone. EC-199: the AI assistant mounts as\n a right-hand sibling (both list + editor), so the content is a row. */}\n <div className=\"flex min-h-0 flex-1 overflow-hidden\">\n <div className=\"min-w-0 flex-1 overflow-hidden\">\n <CmsContent\n projectId={projectId}\n collectionId={collectionId}\n documentId={documentId}\n field={field}\n focusCollections={focusCollections}\n />\n </div>\n <AiCmsPanel />\n </div>\n </div>\n </CmsProvider>\n )\n }}\n </CmsSessionGate>\n )\n}\n\n/** Resolves the active CMS screen inside the provider (needs the live store). */\nfunction CmsContent({\n projectId,\n collectionId,\n documentId,\n field,\n focusCollections,\n}: {\n projectId: string\n collectionId: string | null\n documentId: string | null\n field?: string\n focusCollections?: readonly string[]\n}) {\n const cms = useCms()\n\n if (collectionId === null) return <CmsIndex projectId={projectId} focusCollections={focusCollections} />\n\n if (documentId === null) {\n const collection = cms.getCollection(collectionId)\n // EC-274: a singleton is a UI lens — at the collection URL it opens its one\n // document directly *while in its expected shape*, but degrades to the list\n // (with a \"for review\" lane) on drift. SingletonGate decides per the data.\n if (collection && isSingleton(collection)) {\n return <SingletonGate projectId={projectId} collectionId={collectionId} />\n }\n return <CollectionTable projectId={projectId} collectionId={collectionId} />\n }\n\n return (\n <DetailEditor\n projectId={projectId}\n collectionId={collectionId}\n documentId={documentId}\n focusPath={field}\n />\n )\n}\n\n/**\n * EC-274 — the singleton lens gate. At a singleton collection's URL, resolve its\n * lone document and open it directly; on drift (> 1 document) fall back to the\n * list with the \"for review\" lane so an editor can resolve the conflict.\n */\nfunction SingletonGate({ projectId, collectionId }: { projectId: string; collectionId: string }) {\n const cms = useCms()\n const collection = cms.getCollection(collectionId)\n const docs = cms.documentsOf(collectionId)\n const resolution = collection\n ? resolveSingletonDocument(collection, docs)\n : ({ kind: 'empty' } as const)\n\n // Materialize the lone document on first visit (an empty draft, persisted like\n // any create). Idempotent in the store; only fires while genuinely empty.\n useEffect(() => {\n if (resolution.kind === 'empty') cms.ensureSingletonDocument(collectionId)\n }, [resolution.kind, cms, collectionId])\n\n if (resolution.kind === 'ready') {\n return (\n <DetailEditor projectId={projectId} collectionId={collectionId} documentId={resolution.doc.id} />\n )\n }\n if (resolution.kind === 'drift') {\n return <CollectionTable projectId={projectId} collectionId={collectionId} />\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('appViews.singleton.opening', {\n label: localizedLabel(collection?.form?.label, collectionId),\n })}\n </Text>\n </div>\n )\n}\n\n/** Failed/denied persisted CMS mutations are always visible (EC-141). */\nfunction CmsPersistErrorBanner() {\n const cms = useCms()\n if (!cms.persistError) return null\n return (\n <div className=\"shrink-0 border-b border-border p-2\">\n <OperationErrorBanner error={cms.persistError} onDismiss={cms.clearPersistError} />\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"default-modules.js","sourceRoot":"","sources":["../../../src/components/app-views/default-modules.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGjC,OAAO,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,KAAK,EACL,UAAU,EACV,MAAM,EACN,QAAQ,IAAI,YAAY,EACxB,QAAQ,EACR,KAAK,GACN,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,eAAe,GAChB,MAAM,0BAA0B,CAAA;AAQjC,OAAO,EAAE,iCAAiC,EAAE,MAAM,6BAA6B,CAAA;AAE/E,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAA;AAE/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAE,4BAA4B,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAA;AAE7G,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,aAAa,EACb,QAAQ,GAMT,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AA4GzD,SAAS,OAAO,CAAC,KAAa;IAC5B,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,MAAM,CAAA;AACjG,CAAC;AAED,0FAA0F;AAC1F,SAAS,SAAS,CAAC,MAAqB;IACtC,IAAI,MAAM,CAAC,EAAE;QAAE,OAAO,MAAM,CAAC,EAAE,CAAA;IAC/B,IAAI,MAAM,CAAC,UAAU;QAAE,OAAO,OAAO,MAAM,CAAC,UAAU,EAAE,CAAA;IACxD,IAAI,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,OAAO,OAAO,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;IACnD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,SAAwB,EAAE;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAA;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAA;IACpD,OAAO;QACL,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC;QACrB,4EAA4E;QAC5E,kFAAkF;QAClF,gFAAgF;QAChF,yDAAyD;QACzD,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,eAAe,CAAC,CAAA;QACnD,CAAC;QACD,IAAI,WAAW;YACb,OAAO,MAAM,CAAC,WAAW,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAA;QAC/D,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,QAAQ;QAC7B,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,IAAI,KAAK;QACxD,4EAA4E;QAC5E,sFAAsF;QACtF,SAAS,EAAE,MAAM;YACf,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,CACX,QAAQ,CAAC,MAAM,KAAK,CAAC;gBACnB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;gBAC5C,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;oBACrB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAE,EAAE;oBACpD,CAAC,CAAC,IAAI;YACd,CAAC,CAAC,WAAW;QACf,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CACf,KAAC,SAAS,IACR,OAAO,EAAE,GAAG,CAAC,OAAO,EACpB,YAAY,EAAE,GAAG,CAAC,QAAQ,CAAC,YAAY,IAAI,IAAI,EAC/C,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,IAAI,EAC3C,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EACvB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EACvB,gBAAgB,EAAE,KAAK,GACvB,CACH;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,UAAyB;IAClD,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;IACzC,OAAO,QAAQ,CACb,OAAO,CAAC;QACN,UAAU,EAAE,UAAU,CAAC,EAAE;QACzB,kFAAkF;QAClF,iFAAiF;QACjF,KAAK,EAAE,cAAc,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC;QAC5D,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;KAC3E,CAAC,CACH,CAAA;AACH,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,cAAc,CAAC,SAA4B,EAAE;IAC3D,OAAO;QACL,EAAE,EAAE,YAAY;QAChB,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,4BAA4B,CAAC,CAAA;QAChD,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,MAAM;QAC3B,mBAAmB,EAAE,YAAY;QACjC,SAAS,EAAE,kBAAkB;QAC7B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CACf,KAAC,gBAAgB,IACf,SAAS,EAAE,GAAG,CAAC,SAAS,EACxB,WAAW,EAAE,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,IAAI,EAC7C,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,GACvB,CACH;KACF,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,SAA8B,EAAE;IAC5D,oFAAoF;IACpF,oFAAoF;IACpF,sFAAsF;IACtF,sFAAsF;IACtF,MAAM,MAAM,GAAG,mBAAmB,CAAC,sBAAsB,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAA;IAC/F,OAAO;QACL,EAAE,EAAE,WAAW;QACf,IAAI,KAAK;YACP,gFAAgF;YAChF,8EAA8E;YAC9E,kFAAkF;YAClF,MAAM,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YACrD,MAAM,OAAO,GAAG,cAAc,CAAC,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YAClE,OAAO,CACL,MAAM,CAAC,KAAK;gBACZ,CAAC,UAAU,IAAI,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC,CACvF,CAAA;QACH,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,2BAA2B,CAAC,CAAA;QAC/C,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,QAAQ;QAC7B,mBAAmB,EAAE,KAAK;QAC1B,SAAS,EAAE,iBAAiB;QAC5B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACnD,KAAC,gBAAgB,IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,qBAAqB,CAAC,GAAI,CACvF,CAAC,CAAC,CAAC,CACF,KAAC,eAAe,IACd,SAAS,EAAE,GAAG,CAAC,SAAS,EACxB,UAAU,EAAE,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,IAAI,EAC3C,MAAM,EAAE,MAAM,GACd,CACH;KACJ,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAiD,EACjD,MAAwB;IAExB,MAAM,IAAI,GAAG,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,eAAe,CAAA;IAC5E,OAAO,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAA;AACvE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,SAA2B,EAAE;IACtD,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,kBAAkB,CAAC,CAAA;QACtD,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,wBAAwB,CAAC,CAAA;QAC5C,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,KAAK;QAC1B,mBAAmB,EAAE,QAAQ;QAC7B,SAAS,EAAE,cAAc;QACzB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CACf,KAAC,cAAc,IAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAC,QAAQ,EAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,YACjF,GAAG,CAAC,QAAQ,CAAC,OAAO,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAEnD,KAAC,YAAY,IACX,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAChF,OAAO,SACP,CACH,CAAC,CAAC,CAAC,CAEF,KAAC,cAAc,cACZ,CAAC,GAAG,EAAE,EAAE;oBACP,MAAM,GAAG,GAAG,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;oBAC7C,MAAM,MAAM,GAAG,mBAAmB,CAAC,4BAA4B,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAA;oBAClF,OAAO,CACL,KAAC,YAAY,IACX,SAAS,EAAE,GAAG,CAAC,SAAS,EACxB,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,IAAI,EACrC,MAAM,EAAE,MAAM,GACd,CACH,CAAA;gBACH,CAAC,GACc,CAClB,GACc,CAClB;KACF,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,SAA4B,EAAE;IACtD,OAAO;QACL,EAAE,EAAE,OAAO;QACX,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,iBAAiB,CAAC,CAAA;QACrD,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,uBAAuB,CAAC,CAAA;QAC3C,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,KAAK;QAC1B,mBAAmB,EAAE,OAAO;QAC5B,gFAAgF;QAChF,4EAA4E;QAC5E,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACnD,KAAC,gBAAgB,IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,iBAAiB,CAAC,GAAI,CACnF,CAAC,CAAC,CAAC,CACF,KAAC,WAAW,IAAC,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,IAAI,GAAI,CAC/E;KACJ,CAAA;AACH,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,cAAc,CAAC,SAA4B,EAAE;IAC3D,OAAO;QACL,EAAE,EAAE,YAAY;QAChB,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,4BAA4B,CAAC,CAAA;QAChD,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,MAAM;QAC3B,mBAAmB,EAAE,YAAY;QACjC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CACnD,KAAC,gBAAgB,IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,sBAAsB,CAAC,GAAI,CACxF,CAAC,CAAC,CAAC,CACF,KAAC,cAAc,KAAG,CACnB;KACJ,CAAA;AACH,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,YAAY,CAAC,SAA4B,EAAE;IACzD,OAAO;QACL,EAAE,EAAE,UAAU;QACd,IAAI,KAAK;YACP,OAAO,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,oBAAoB,CAAC,CAAA;QACxD,CAAC;QACD,IAAI,WAAW;YACb,OAAO,SAAS,CAAC,0BAA0B,CAAC,CAAA;QAC9C,CAAC;QACD,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,YAAY;QACjC,mBAAmB,EAAE,UAAU;QAC/B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CACf,KAAC,cAAc,IAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAC,UAAU,EAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,YACpF,KAAC,YAAY,IAAC,OAAO,EAAE,GAAG,CAAC,OAAO,GAAI,GACvB,CAClB;KACF,CAAA;AACH,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,+DAA+D;QAC5E,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAIf,KAAC,YAAY,IACX,EAAE,EAAE,oBAAoB,EACxB,MAAM,EAAE;gBACN,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;oBACrB,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;oBACjD,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC;aACrC,EACD,OAAO,SACP,CACH;KACF,CAAA;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,UAAU,CAAC,MAAwB;IACjD,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QACtC,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,KAAK;QAC/C,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,UAAU;QAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5D,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3F,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAA6B;IACrD,cAAc,EAAE;IAChB,OAAO,EAAE;IACT,aAAa,EAAE;IACf,UAAU,EAAE;IACZ,cAAc,EAAE;IAChB,SAAS,EAAE;IACX,YAAY,EAAE;IACd,WAAW,EAAE;CACd,CAAA;AAED;;;;;;;GAOG;AACH;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,IAAmB,EAAE,MAAmC;IACjF,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,IAAI,CAAA;IAChC,8EAA8E;IAC9E,qFAAqF;IACrF,8DAA8D;IAC9D,OAAO,MAAM,CAAC,gBAAgB,CAAC,EAAmB,EAAE;QAClD,GAAG,MAAM,CAAC,yBAAyB,CAAC,IAAI,CAAC;QACzC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;KACzF,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB;;;;OAIG;IACH,GAAG,EAAE,CAAC,SAAwB,EAAE,EAAmB,EAAE;QACnD,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAA;QACvD,0EAA0E;QAC1E,IAAI,MAAM,CAAC,UAAU;YAAE,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC5C,oFAAoF;QACpF,MAAM,IAAI,GAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QACpD,IAAI,MAAM,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAA;;YAElD,IAAI,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,EAAE,CAC5B,mBAAmB,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;QAC/E,OAAO,IAAI,CAAA;IACb,CAAC;IACD,sCAAsC;IACtC,UAAU,EAAE,CAAC,SAA4B,EAAE,EAAmB,EAAE,CAC9D,QAAQ,CAAC,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7D;mFAC+E;IAC/E,MAAM,EAAE,CAAC,SAA2B,EAAE,EAAmB,EAAE,CACzD,QAAQ,CAAC,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACzD,mFAAmF;IACnF,SAAS,EAAE,CAAC,SAA8B,EAAE,EAAmB,EAAE,CAC/D,QAAQ,CAAC,iBAAiB,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,wBAAwB;IACxB,QAAQ,EAAE,CAAC,SAA4B,EAAE,EAAmB,EAAE,CAC5D,QAAQ,CAAC,iBAAiB,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3D,kCAAkC;IAClC,UAAU,EAAE,CAAC,SAA4B,EAAE,EAAmB,EAAE,CAC9D,QAAQ,CAAC,iBAAiB,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7D,uCAAuC;IACvC,KAAK,EAAE,CAAC,SAA4B,EAAE,EAAmB,EAAE,CACzD,QAAQ,CAAC,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACxD,+EAA+E;IAC/E,IAAI,EAAE,CAAC,MAAwB,EAAmB,EAAE,CAClD,QAAQ,CAAC,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACzD,6DAA6D;IAC7D,KAAK,EAAE,CAAC,KAAa,EAAE,QAA2B,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC;IACjF,uCAAuC;IACvC,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE;CACxB,CAAA;AAEV,oEAAoE;AAEpE;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,WAAW,EACX,KAAK,GAKN;IACC,IAAI,KAAK,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,sBAAsB,CAAC,GAAI,CAAA;IACrF,CAAC;IACD,OAAO,CACL,eAAK,SAAS,EAAC,qBAAqB,aAClC,gBAAO,SAAS,EAAC,0EAA0E,YACzF,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,WAAW,IAAI,SAAS,GAAI,GAChF,EACR,cAAK,SAAS,EAAC,8BAA8B,YAC1C,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,CACtB,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,GAAI,CAC1C,CAAC,CAAC,CAAC,CACF,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,GAAI,CACpE,GACG,IACF,CACP,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,4BAA4B,CAC1C,GAAuB,EACvB,GAAgC,EAChC,aAAsB,gBAAgB,EAAE;IAExC,OAAO,GAAG,EAAE,WAAW,IAAI,GAAG,EAAE,WAAW,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,4BAA4B,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAC1G,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,0BAA0B,CACxC,GAAuB,EACvB,GAAgC,EAChC,cAA2C,4BAA4B,CAAC,GAAG,EAAE,GAAG,CAAC;IAEjF,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC,SAAS,CAAA;IAC7B,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC,SAAS,CAAA;IAC7B,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAC9D,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IAC7F,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACjC,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QACtD,OAAO,UAAU,CAAC,CAAC,CAAC,iCAAiC,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAC9E,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,6BAA6B,CAC3C,GAAuB,EACvB,GAAgC,EAChC,OAAgB,EAChB,gBAA0C,6BAA6B,EAAE;IAEzE,IAAI,aAAa;QAAE,OAAO,aAAa,CAAA;IACvC,IAAI,GAAG;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,aAAa,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAA;IACnG,OAAO,GAAG,EAAE,YAAY,CAAA;AAC1B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,EACjB,OAAO,EACP,YAAY,EACZ,UAAU,EACV,KAAK,EACL,KAAK,EACL,gBAAgB,GAQjB;IACC,MAAM,SAAS,GAAG,OAAO,CAAC,EAAE,CAAA;IAC5B,IAAI,KAAK,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,GAAI,CAAA;IAC9E,CAAC;IAED,8FAA8F;IAC9F,MAAM,GAAG,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAA;IAEzC,OAAO,CAIL,KAAC,cAAc,cACZ,CAAC,GAAG,EAAE,EAAE;YACP,sEAAsE;YACtE,yEAAyE;YACzE,MAAM,kBAAkB,GAAG,4BAA4B,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YACjE,OAAO,CACL,KAAC,WAAW,IAEV,SAAS,EAAE,SAAS,EACpB,kBAAkB,EAAE,kBAAkB,EACtC,gBAAgB,EAAE,0BAA0B,CAAC,GAAG,EAAE,GAAG,EAAE,kBAAkB,CAAC,EAC1E,oBAAoB,EAAE,GAAG,EAAE,aAAa,EACxC,iBAAiB,EAAE,GAAG,EAAE,UAAU,EAClC,wBAAwB,EAAE,GAAG,EAAE,iBAAiB,EAChD,kBAAkB,EAAE,GAAG,EAAE,WAAW,EACpC,mBAAmB,EAAE,6BAA6B,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC;gBACrE,qEAAqE;gBACrE,qEAAqE;gBACrE,qDAAqD;gBACrD,kBAAkB,EAAE,GAAG,KAAK,IAAI,YAEhC,eAAK,SAAS,EAAC,8BAA8B,aAC3C,KAAC,qBAAqB,KAAG,EAKzB,eAAK,SAAS,EAAC,qCAAqC,aAClD,cAAK,SAAS,EAAC,gCAAgC,YAC7C,KAAC,UAAU,IACT,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,KAAK,EACZ,gBAAgB,EAAE,gBAAgB,GAClC,GACE,EACN,KAAC,UAAU,KAAG,IACV,IACF,IAhCD,SAAS,CAiCF,CACf,CAAA;QACH,CAAC,GACc,CAClB,CAAA;AACH,CAAC;AAED,iFAAiF;AACjF,SAAS,UAAU,CAAC,EAClB,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,EACL,gBAAgB,GAOjB;IACC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IAEpB,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,KAAC,QAAQ,IAAC,SAAS,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,GAAI,CAAA;IAExG,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,MAAM,UAAU,GAAG,GAAG,CAAC,aAAa,CAAC,YAAY,CAAC,CAAA;QAClD,4EAA4E;QAC5E,4EAA4E;QAC5E,2EAA2E;QAC3E,IAAI,UAAU,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1C,OAAO,KAAC,aAAa,IAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,GAAI,CAAA;QAC5E,CAAC;QACD,OAAO,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,GAAI,CAAA;IAC9E,CAAC;IAED,OAAO,CACL,KAAC,YAAY,IACX,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,KAAK,GAChB,CACH,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,EAAE,SAAS,EAAE,YAAY,EAA+C;IAC7F,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,MAAM,UAAU,GAAG,GAAG,CAAC,aAAa,CAAC,YAAY,CAAC,CAAA;IAClD,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;IAC1C,MAAM,UAAU,GAAG,UAAU;QAC3B,CAAC,CAAC,wBAAwB,CAAC,UAAU,EAAE,IAAI,CAAC;QAC5C,CAAC,CAAE,EAAE,IAAI,EAAE,OAAO,EAAY,CAAA;IAEhC,+EAA+E;IAC/E,0EAA0E;IAC1E,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO;YAAE,GAAG,CAAC,uBAAuB,CAAC,YAAY,CAAC,CAAA;IAC5E,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC,CAAA;IAExC,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,CACL,KAAC,YAAY,IAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,GAAI,CAClG,CAAA;IACH,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,KAAC,eAAe,IAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,GAAI,CAAA;IAC9E,CAAC;IACD,OAAO,CACL,cAAK,SAAS,EAAC,oCAAoC,YACjD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,SAAS,CAAC,4BAA4B,EAAE;gBACvC,KAAK,EAAE,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC;aAC7D,CAAC,GACG,GACH,CACP,CAAA;AACH,CAAC;AAED,yEAAyE;AACzE,SAAS,qBAAqB;IAC5B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAA;IACpB,IAAI,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO,IAAI,CAAA;IAClC,OAAO,CACL,cAAK,SAAS,EAAC,qCAAqC,YAClD,KAAC,oBAAoB,IAAC,KAAK,EAAE,GAAG,CAAC,YAAY,EAAE,SAAS,EAAE,GAAG,CAAC,iBAAiB,GAAI,GAC/E,CACP,CAAA;AACH,CAAC","sourcesContent":["import { useEffect } from 'react'\nimport type { ReactNode } from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport {\n Blocks,\n Database,\n FileCog,\n Image,\n LayoutGrid,\n Puzzle,\n Settings as SettingsIcon,\n Signpost,\n Users,\n} from 'lucide-react'\nimport {\n assetCollection,\n extendCollectionDef,\n isSingleton,\n resolveSingletonDocument,\n sampleDocuments,\n} from '@elytracms/core/cms-core'\nimport type {\n CmsDocument,\n CollectionDef,\n CollectionFieldOverride,\n CollectionFormMeta,\n LocaleConfig,\n} from '@elytracms/core/cms-core'\nimport { reconcileSeedDocumentLocalization } from '@elytracms/core/persistence'\nimport type { Project, SectionId, SectionState } from '@elytracms/core/studio-core'\nimport { NavigateOnce, STUDIO_PROJECT_ROUTE, studioPath } from '@elytracms/studio/router'\nimport { Text } from '@elytracms/studio/ui'\nimport { localizedLabel, translate } from '@elytracms/studio/lib/i18n'\nimport { getInstantiatedKit } from '@elytracms/studio/lib/starters/kit-session'\nimport type { InstantiatedKit } from '@elytracms/core/starter-kits'\nimport { isPlaygroundMode } from '@elytracms/studio/lib/persistence'\nimport { resolveDeploymentCollections, resolveDeploymentLocaleConfig } from '@elytracms/studio/lib/workspace'\nimport type { HydratedCms } from '@elytracms/studio/lib/operations'\nimport {\n cmsDeepLink,\n componentsDeepLink,\n usersDeepLink,\n redirectsDeepLink,\n assetsDeepLink,\n groupNode,\n separatorNode,\n viewNode,\n type AppViewContext,\n type AppViewDeepLink,\n type AppViewModule,\n type NavPermission,\n type SidebarViewNode,\n} from './module'\nimport { SectionSurface } from '../section-surface'\nimport { SectionStateView } from '../section-state'\nimport { SettingsForm } from '../settings-form'\nimport { AssetsModule } from '../assets/assets-module'\nimport { UsersModule } from '../users/users-module'\nimport { RedirectsModule } from '../redirects/redirects-module'\nimport { redirectsCollectionDef } from '../redirects/redirects-schema'\nimport { ExtensionsView } from '../extensions/extensions-view'\nimport { RegistryBrowser } from '../components/registry-browser'\nimport { ComponentsIndex } from '../components/components-index'\nimport { ComponentDetail } from '../components/component-detail'\nimport { CmsSessionGate } from '../project-session'\nimport { CmsProvider, useCms } from '../cms/cms-store'\nimport { AiCmsPanel } from '../ai/ai-cms-panel'\nimport { focusCollectionList } from '../cms/collection-focus'\nimport { CmsIndex } from '../cms/cms-index'\nimport { CollectionTable } from '../cms/collection-table'\nimport { DetailEditor } from '../cms/detail-editor'\nimport { OperationErrorBanner } from '../operation-error'\n\n/**\n * EC-237/239 — the studio's BUILT-IN views + the consumer-facing `nav` namespace,\n * an instance of the EC-236 customization seam.\n *\n * Each built-in surface (the former `StudioSectionResolver` case body) is promoted\n * to a self-contained {@link AppViewModule} factory. The `nav` namespace is the\n * single, typed builder a consuming repo's `studio.config.tsx` uses to declare its\n * section rail — `nav.cms(...)`, `nav.group(...)`, `nav.view(...)` — passing the\n * view objects (and a focus config) DIRECTLY, never a string id. The CMS surface\n * is a factory ({@link cmsView}) so a focused view (\"Catalog\", \"Categories\") is the\n * SAME surface instantiated with config, not a copy — the EC-239 reuse proof.\n */\n\n// --- The CMS surface, reusable via a config factory (the EC-239 reuse seam) ---\n\n/** Config for a CMS-backed view — `collections`/`collection` focus the lens (EC-239). */\nexport interface CmsViewConfig {\n /** Explicit stable id (the route segment). Omitted → derived from the focus. */\n id?: string\n label?: string\n description?: string\n icon?: LucideIcon\n /**\n * Scope the rail auto-expansion (EC-293) to these collection ids, in order. The\n * parent expands into one focused-lens child per id; the parent index shows the\n * same subset. Omitted → every collection. Ignored for a single-collection lens.\n */\n collections?: readonly string[]\n /** Single-collection lens: a LEAF rail item that opens straight into this collection. */\n collection?: string\n /**\n * EC-293 escape hatch: explicit rail children for this CMS parent, overriding the\n * default per-collection auto-expansion. Ignored for a single-collection lens.\n */\n children?: SidebarViewNode[]\n /** The availability section this view rides on (EC-221); defaults to `cms`. */\n availabilitySection?: SectionId\n /** OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18). See {@link BuiltinViewConfig.visible}. */\n visible?: NavPermission\n}\n\n/** Common label/icon overrides for a built-in view (`nav.redirects({ label: 'URLs' })`). */\nexport interface BuiltinViewConfig {\n label?: string\n icon?: LucideIcon\n /**\n * OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18): hide this\n * rail item for principals it returns `false` for; absent → always visible. The\n * policy (which roles) lives in the consuming project, never the engine. E.g.\n * `nav.users({ visible: ({ role }) => role === 'admin' })`.\n */\n visible?: NavPermission\n}\n\n/**\n * Config for the **redirects** rail (EC-311) — a built-in view whose engine-owned\n * schema is customizable. Beyond the shared label/icon/visible, a mount may rename\n * the rail (`form`) and **override built-in fields by equal name** (`fields`):\n * naming, permissions (`form.readOnly`), rendering (`form.control` → `fieldRenderers`)\n * and validation. The canonical `from`/`to`/`permanent`/`enabled` names+types are\n * contract-locked (a type change on one is a loud config error), but a *net-new*\n * field is allowed (EC-311 Tier 2): it's appended and persists in the redirect\n * record's `meta` bag — editorial only, so it never reaches delivery/routing.\n */\nexport interface RedirectsViewConfig extends BuiltinViewConfig {\n /** Rename the rail (e.g. `{ label: 'URLs' }`). */\n form?: Partial<CollectionFormMeta>\n /** Override built-in redirect fields, matched by `name`. */\n fields?: readonly CollectionFieldOverride[]\n}\n\n/**\n * Config for the **assets** rail (EC-319), customizable by the SAME EC-311\n * mechanism as redirects (`form`/`fields`) — but the BASE schema it overrides is\n * different in kind: redirects' base is an engine-owned, fixed `CollectionDef`\n * known at config-build time; the assets base is the DEPLOYMENT's own\n * `kind:'asset'` collection (a project's `cms/collections/asset.ts`), only\n * knowable once the deployment resolves (hydrated session → kit → playground\n * config — see {@link resolveAssetsSchema}). So the merge runs at RENDER time\n * (inside `assetsView`'s `CmsSessionGate`), not at `nav.assets(config)` call\n * time: a locked-violation override (e.g. changing a declared field's type)\n * surfaces the first time the Assets section renders, not at `defineStudio`\n * mount — the one place this rail's customization contract differs from\n * redirects', a direct consequence of its base schema being project-declared.\n */\nexport interface AssetsViewConfig extends BuiltinViewConfig {\n /** Rename the rail (e.g. `{ label: 'Media' }`). */\n form?: Partial<CollectionFormMeta>\n /** Override the project's declared asset fields, matched by `name`. */\n fields?: readonly CollectionFieldOverride[]\n}\n\n/** Config for a fully custom view (`nav.view({...})`) — the net-new surface escape hatch. */\nexport interface CustomViewConfig {\n /** Explicit stable id (the route segment). Omitted → a slug of the label. */\n id?: string\n label: string\n description?: string\n icon?: LucideIcon\n render: (ctx: AppViewContext) => ReactNode\n parsePath?: (segments: string[]) => AppViewDeepLink | null\n availabilitySection?: SectionId\n /** OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18). See {@link BuiltinViewConfig.visible}. */\n visible?: NavPermission\n}\n\nfunction slugify(label: string): string {\n return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'view'\n}\n\n/** Derive a stable, deterministic id from the CMS focus (overridable via `config.id`). */\nfunction cmsViewId(config: CmsViewConfig): string {\n if (config.id) return config.id\n if (config.collection) return `cms:${config.collection}`\n if (config.collections && config.collections.length > 0) {\n return `cms:${[...config.collections].join('-')}`\n }\n return 'cms'\n}\n\n/**\n * Build a CMS view — the default `cms` view, or a focused instance. Every instance\n * shares one surface ({@link CmsScreen}); `collections` narrows the sidebar + index\n * to a subset, `collection` is a single-collection lens that opens straight into\n * that collection's list. The id is derived from the focus (deterministic, stable\n * across rail reorders) unless `id` is given — so two focused views never collide.\n */\nexport function cmsView(config: CmsViewConfig = {}): AppViewModule {\n const single = config.collection\n const focus = single ? [single] : config.collections\n return {\n id: cmsViewId(config),\n // Lazy label/description (EC-241): the sidebar is built when the consumer's\n // `studio.config` module loads — BEFORE `createStudio` seeds the i18n config — so\n // resolving here would bake English. A getter defers resolution to render, when\n // the config is present and the browser locale is known.\n get label() {\n return config.label ?? translate('nav.cms.label')\n },\n get description() {\n return config.description ?? translate('nav.cms.description')\n },\n icon: config.icon ?? Database,\n availabilitySection: config.availabilitySection ?? 'cms',\n // A single-collection lens routes `[]`→that collection, `[d]`→its document;\n // an unfocused / multi-collection view uses the full index/collection/document arity.\n parsePath: single\n ? (segments) =>\n segments.length === 0\n ? { collectionId: single, documentId: null }\n : segments.length === 1\n ? { collectionId: single, documentId: segments[0]! }\n : null\n : cmsDeepLink,\n render: (ctx) => (\n <CmsScreen\n project={ctx.project}\n collectionId={ctx.deepLink.collectionId ?? null}\n documentId={ctx.deepLink.documentId ?? null}\n state={ctx.search.state}\n field={ctx.search.field}\n focusCollections={focus}\n />\n ),\n }\n}\n\n/**\n * A focused-lens rail child for one collection (EC-293) — the SAME `cms:<id>` view a\n * single-collection `nav.cms({ collection })` produces, carrying the collection's own\n * label + a singleton-aware icon (the former `CollectionsSidebar` row, lifted into\n * the rail). CMS auto-expansion maps the project's collections through this.\n */\nfunction cmsCollectionLeaf(collection: CollectionDef): SidebarViewNode {\n const singleton = isSingleton(collection)\n return viewNode(\n cmsView({\n collection: collection.id,\n // Non-hook `localizedLabel` resolves the (possibly `{ en, de }`) collection label\n // against the active UI locale, so a German editor sees German collection names.\n label: localizedLabel(collection.form?.label, collection.id),\n icon: singleton ? FileCog : collection.kind === 'asset' ? Image : Database,\n }),\n )\n}\n\n/** The components registry browser view. */\nexport function componentsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'components',\n get label() {\n return config.label ?? translate('nav.components.label')\n },\n get description() {\n return translate('nav.components.description')\n },\n icon: config.icon ?? Blocks,\n availabilitySection: 'components',\n parsePath: componentsDeepLink,\n render: (ctx) => (\n <ComponentsScreen\n projectId={ctx.projectId}\n componentId={ctx.deepLink.componentId ?? null}\n state={ctx.search.state}\n />\n ),\n }\n}\n\n/**\n * The Redirects view (EC-303) — editorial redirects as DATA, an editor-owned CRUD\n * surface (create/edit/delete) over the portable list/edit surfaces. Routes are no\n * longer shown in the admin (they're code; native `next.config`/`vercel` own static\n * forwards, evaluated before these). Rides on the CMS availability (a content/\n * routing concern). `[]` is the list; `[redirectId]` opens that redirect's detail.\n */\nexport function redirectsView(config: RedirectsViewConfig = {}): AppViewModule {\n // EC-311: merge any field/label overrides over the engine-owned def ONCE, at config\n // build (config-as-code — a locked violation throws here, at `defineStudio` mount).\n // Tier 2: `additiveFields: 'allow'` — a net-new field is appended and persists in the\n // redirect record's `meta` bag (the canonical from/to/permanent/enabled stay locked).\n const schema = extendCollectionDef(redirectsCollectionDef, config, { additiveFields: 'allow' })\n return {\n id: 'redirects',\n get label() {\n // Prefer an explicit rail label, then a consumer-*customized* collection label,\n // else the localized catalog nav label. Never let the built-in English schema\n // label ('Redirects') preempt translation — compare against the built-in default.\n const customized = localizedLabel(schema.form?.label)\n const builtin = localizedLabel(redirectsCollectionDef.form?.label)\n return (\n config.label ??\n (customized && customized !== builtin ? customized : translate('nav.redirects.label'))\n )\n },\n get description() {\n return translate('nav.redirects.description')\n },\n icon: config.icon ?? Signpost,\n availabilitySection: 'cms',\n parsePath: redirectsDeepLink,\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title={translate('nav.redirects.label')} />\n ) : (\n <RedirectsModule\n projectId={ctx.projectId}\n redirectId={ctx.deepLink.redirectId ?? null}\n schema={schema}\n />\n ),\n }\n}\n\n/**\n * Resolve the assets rail's EFFECTIVE schema (EC-319/EC-311): the deployment's\n * OWN `kind:'asset'` collection — hydrated session → kit → playground config,\n * the exact same precedence {@link resolveCmsInitialCollections} gives every\n * other CMS collection — falling back to the engine's `assetCollection` fixture\n * only when nothing resolves (mirroring `CmsProvider`'s own fallback). A mount's\n * `nav.assets({ form, fields })` then merges over THAT base (EC-311, additive\n * fields allowed — a project's asset record already carries an open `meta` bag,\n * EC-301). Pure — no React — so it's unit-testable without `CmsSessionGate`.\n */\nexport function resolveAssetsSchema(\n collections: readonly CollectionDef[] | undefined,\n config: AssetsViewConfig,\n): CollectionDef {\n const base = collections?.find((c) => c.kind === 'asset') ?? assetCollection\n return extendCollectionDef(base, config, { additiveFields: 'allow' })\n}\n\n/**\n * The Assets view (EC-319) — the universal list (+ grid/list toggle) and a\n * bespoke per-asset detail surface, over the asset library store. `[]` is the\n * list; `[assetId]` opens that asset's detail (routed via `studioPath.asset`).\n * A `?asset=` deep link (EC-172 \"used by\" links) redirects straight to the\n * asset's detail — the surface those links used to scroll a card into and\n * auto-open, now a first-class route.\n */\nexport function assetsView(config: AssetsViewConfig = {}): AppViewModule {\n return {\n id: 'assets',\n get label() {\n return config.label ?? translate('nav.assets.label')\n },\n get description() {\n return translate('nav.assets.description')\n },\n icon: config.icon ?? Image,\n availabilitySection: 'assets',\n parsePath: assetsDeepLink,\n render: (ctx) => (\n <SectionSurface project={ctx.project} section=\"assets\" forcedState={ctx.search.state}>\n {ctx.deepLink.assetId === null && ctx.search.asset ? (\n // EC-441: `NavigateOnce` — render-time redirects fire once per intent.\n <NavigateOnce\n to={STUDIO_PROJECT_ROUTE}\n params={{ projectId: ctx.projectId, _splat: studioPath.asset(ctx.search.asset) }}\n replace\n />\n ) : (\n // The AssetsProvider is mounted at the project level (EC-195).\n <CmsSessionGate>\n {(cms) => {\n const kit = getInstantiatedKit(ctx.projectId)\n const schema = resolveAssetsSchema(resolveCmsInitialCollections(cms, kit), config)\n return (\n <AssetsModule\n projectId={ctx.projectId}\n assetId={ctx.deepLink.assetId ?? null}\n schema={schema}\n />\n )\n }}\n </CmsSessionGate>\n )}\n </SectionSurface>\n ),\n }\n}\n\n/**\n * The studio Users view (EC-240) — an admin-only roster of who has access to this\n * deployment and their role. A first-class section (its own rail entry), modeled\n * like the Assets library; the surface itself gates to admins and renders a\n * \"restricted\" state otherwise. Rides on the `settings` availability (an\n * admin/config concern, so it appears/hides with a project's settings surface).\n */\nexport function usersView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'users',\n get label() {\n return config.label ?? translate('nav.users.label')\n },\n get description() {\n return translate('nav.users.description')\n },\n icon: config.icon ?? Users,\n availabilitySection: 'users',\n // EC-240: `[]` is the roster; `[userId]` opens that member in the portable edit\n // shell (AD-19 reuse — the same `DetailEditor` body over the users source).\n parsePath: usersDeepLink,\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title={translate('nav.users.label')} />\n ) : (\n <UsersModule projectId={ctx.projectId} userId={ctx.deepLink.userId ?? null} />\n ),\n }\n}\n\n/** The extensions marketplace view. */\nexport function extensionsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'extensions',\n get label() {\n return config.label ?? translate('nav.extensions.label')\n },\n get description() {\n return translate('nav.extensions.description')\n },\n icon: config.icon ?? Puzzle,\n availabilitySection: 'extensions',\n render: (ctx) =>\n ctx.search.state && ctx.search.state !== 'working' ? (\n <SectionStateView state={ctx.search.state} title={translate('nav.extensions.label')} />\n ) : (\n <ExtensionsView />\n ),\n }\n}\n\n/** The project settings view. */\nexport function settingsView(config: BuiltinViewConfig = {}): AppViewModule {\n return {\n id: 'settings',\n get label() {\n return config.label ?? translate('nav.settings.label')\n },\n get description() {\n return translate('nav.settings.description')\n },\n icon: config.icon ?? SettingsIcon,\n availabilitySection: 'settings',\n render: (ctx) => (\n <SectionSurface project={ctx.project} section=\"settings\" forcedState={ctx.search.state}>\n <SettingsForm project={ctx.project} />\n </SectionSurface>\n ),\n }\n}\n\n/** The legacy `builder` redirect — a hidden, routable view (so `/builder?page=` still lands). */\nexport function builderView(): AppViewModule {\n return {\n id: 'builder',\n label: 'Builder',\n description: 'Legacy builder route — redirects to the page document editor.',\n icon: Blocks,\n hidden: true,\n render: (ctx) => (\n // Retired surface (EC-187 slice 6): a page is a document in the `page`\n // collection. `?page=<id>` lands on that document; else the page list.\n // EC-441: `NavigateOnce` — render-time redirects fire once per intent.\n <NavigateOnce\n to={STUDIO_PROJECT_ROUTE}\n params={{\n projectId: ctx.projectId,\n _splat: ctx.search.page\n ? studioPath.cmsDocument('page', ctx.search.page)\n : studioPath.cmsCollection('page'),\n }}\n replace\n />\n ),\n }\n}\n\n/** Build a fully custom view (the `nav.view(...)` escape hatch). */\nexport function customView(config: CustomViewConfig): AppViewModule {\n return {\n id: config.id ?? slugify(config.label),\n label: config.label,\n description: config.description ?? config.label,\n icon: config.icon ?? LayoutGrid,\n render: config.render,\n ...(config.parsePath ? { parsePath: config.parsePath } : {}),\n ...(config.availabilitySection ? { availabilitySection: config.availabilitySection } : {}),\n }\n}\n\n/**\n * The studio's built-in views in canonical rail order (components · CMS · routes ·\n * assets · extensions · settings), plus the hidden `builder` redirect (routable but\n * unlisted). The default rail (no `defineStudio({ sidebar })`) is these as plain\n * entries; the router registry always includes them so deep-links resolve even\n * when a consumer's rail omits one.\n */\nexport const DEFAULT_VIEWS: readonly AppViewModule[] = [\n componentsView(),\n cmsView(),\n redirectsView(),\n assetsView(),\n extensionsView(),\n usersView(),\n settingsView(),\n builderView(),\n]\n\n/**\n * The `nav` namespace — the single typed builder for `studio.config.tsx`'s section\n * rail. Each call returns a {@link SidebarViewNode} (or a group / separator) that\n * carries its view DIRECTLY: `nav.cms({ collection })` is a focused CMS view,\n * `nav.view({ render })` a net-new surface, `nav.group(label, [...])` a header with\n * sub-views, `nav.separator()` a divider. No string ids, no `moduleId` — the rail\n * is the views.\n */\n/**\n * Forward the OPTIONAL, default-open nav-permission predicate (EC-240 / AD-18)\n * from a `nav.*` config onto the built view, so gating is settable on EVERY nav\n * item — not just Users. Absent → the view is unchanged (always visible).\n */\nfunction withNavPermission(view: AppViewModule, config: { visible?: NavPermission }): AppViewModule {\n if (!config.visible) return view\n // Copy property DESCRIPTORS (not a spread) so a built-in view's lazy `label`/\n // `description` getters (EC-241) survive — a spread would invoke them at build time,\n // baking English before `createStudio` seeds the i18n config.\n return Object.defineProperties({} as AppViewModule, {\n ...Object.getOwnPropertyDescriptors(view),\n visible: { value: config.visible, enumerable: true, configurable: true, writable: true },\n })\n}\n\nexport const nav = {\n /**\n * The CMS view. `nav.cms()` / `nav.cms({ collections })` is a PARENT that nests its\n * collections in the rail (each a focused `cms:<id>` lens); `nav.cms({ collection })`\n * is a single-collection LEAF; `nav.cms({ children })` supplies explicit children.\n */\n cms: (config: CmsViewConfig = {}): SidebarViewNode => {\n const view = withNavPermission(cmsView(config), config)\n // A single-collection lens is a leaf — it opens that collection directly.\n if (config.collection) return viewNode(view)\n // Otherwise a parent that expands its collections as nested rail children (EC-293).\n const node: SidebarViewNode = { kind: 'view', view }\n if (config.children) node.children = config.children\n else\n node.expand = (collections) =>\n focusCollectionList(collections, config.collections).map(cmsCollectionLeaf)\n return node\n },\n /** The component registry browser. */\n components: (config: BuiltinViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(componentsView(config), config)),\n /** The assets rail — the universal list/grid + detail (EC-319); built-in\n * fields customizable by name (EC-311), the same shape as `nav.redirects`. */\n assets: (config: AssetsViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(assetsView(config), config)),\n /** Editorial redirects (EC-303); built-in fields customizable by name (EC-311). */\n redirects: (config: RedirectsViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(redirectsView(config), config)),\n /** Project settings. */\n settings: (config: BuiltinViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(settingsView(config), config)),\n /** The extensions marketplace. */\n extensions: (config: BuiltinViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(extensionsView(config), config)),\n /** The admin users roster (EC-240). */\n users: (config: BuiltinViewConfig = {}): SidebarViewNode =>\n viewNode(withNavPermission(usersView(config), config)),\n /** A fully custom view — pass your own `render` (and optional `parsePath`). */\n view: (config: CustomViewConfig): SidebarViewNode =>\n viewNode(withNavPermission(customView(config), config)),\n /** A labelled group of views (a header with sub-entries). */\n group: (label: string, children: SidebarViewNode[]) => groupNode(label, children),\n /** A divider between rail sections. */\n separator: () => separatorNode(),\n} as const\n\n// --- Surfaces (the view render bodies; CMS gains a focus lens) ---\n\n/**\n * Components section (EC-050): a registry-browser sidebar beside the active\n * screen — the index (no selection) or a component's detail. Forced states\n * (?state=) replace the whole surface.\n */\nfunction ComponentsScreen({\n projectId,\n componentId,\n state,\n}: {\n projectId: string\n componentId: string | null\n state?: SectionState\n}) {\n if (state && state !== 'working') {\n return <SectionStateView state={state} title={translate('nav.components.label')} />\n }\n return (\n <div className=\"flex h-full min-h-0\">\n <aside className=\"hidden h-full min-h-0 w-[300px] shrink-0 border-r border-border md:block\">\n <RegistryBrowser projectId={projectId} activeComponentId={componentId ?? undefined} />\n </aside>\n <div className=\"min-h-0 flex-1 overflow-auto\">\n {componentId === null ? (\n <ComponentsIndex projectId={projectId} />\n ) : (\n <ComponentDetail projectId={projectId} componentId={componentId} />\n )}\n </div>\n </div>\n )\n}\n\n/**\n * The CMS schema source for a project mount (bug fix, playground-collections):\n * a hydrated session wins (Convex), then a kit's imported schema (EC-119), then —\n * in playground mode only — the active project's config-declared collections\n * (AD-11, `resolveDeploymentCollections`). Without this last fallback, a non-kit\n * playground project has no hydrated session (`null`, by design — see\n * `project-session.tsx`) and no kit, so `CmsProvider` fell through to the core's\n * hardcoded `sampleFilterableCollections` fixture instead of the project's own\n * on-disk schema. Convex mode's first-run `null` is untouched — it still seeds\n * from the fixture there, as before. `playground` defaults to the live\n * {@link isPlaygroundMode} but is overridable so this stays unit testable\n * without stubbing the env (same seam as `resolveBackendConfig`).\n */\nexport function resolveCmsInitialCollections(\n cms: HydratedCms | null,\n kit: InstantiatedKit | undefined,\n playground: boolean = isPlaygroundMode(),\n): CollectionDef[] | undefined {\n return cms?.collections ?? kit?.collections ?? (playground ? resolveDeploymentCollections() : undefined)\n}\n\n/**\n * The CMS documents source for a project mount (EC-424) — the document-side\n * twin of {@link resolveCmsInitialCollections}, same ladder: a hydrated session\n * wins (Convex), then a kit's imported documents, then — in playground mode\n * only, and only when a config project actually resolved collections — the\n * engine's own `sampleDocuments` fixture RECONCILED against them.\n *\n * Without this, a config project whose OWN schema diverges from the shared\n * fixture's localization (e.g. this repo's demo declares `post.slug` localized,\n * EC-315, while the engine's `postCollection` fixture deliberately does not)\n * left `CmsProvider` falling straight through to the un-reconciled\n * `sampleDocuments` default — the CMS surfaces' `initialCollections` already\n * reflected the config schema (via the fallback above), but `initialDocuments`\n * did not, so every seeded post showed EC-390's shape-aware validation's\n * `plain-value` warning + `missing-localized-value` error, standing noise a\n * project never actually wrote.\n *\n * This is the STATIC-baseline twin of `reconcileSeedDocumentLocalization`\n * (`@elytracms/core/persistence`, wired into the seeded persistence adapter for\n * Convex-mode/delivery reads): no adapter round-trip, so the EC-138/EC-320\n * synchronous-first-render contract (`project-session.tsx`: \"CMS surfaces keep\n * their fixture baseline\") is untouched. `collections` defaults to the SAME\n * resolution `resolveCmsInitialCollections` returns, so the two stay in lockstep\n * by construction; only reconciles when that resolved to a non-empty config\n * schema (env-only playground, with nothing config-declared, is unaffected).\n */\nexport function resolveCmsInitialDocuments(\n cms: HydratedCms | null,\n kit: InstantiatedKit | undefined,\n collections: CollectionDef[] | undefined = resolveCmsInitialCollections(cms, kit),\n): CmsDocument[] | undefined {\n if (cms) return cms.documents\n if (kit) return kit.documents\n if (!collections || collections.length === 0) return undefined\n const collectionsById = new Map(collections.map((collection) => [collection.id, collection]))\n return sampleDocuments.map((doc) => {\n const collection = collectionsById.get(doc.collection)\n return collection ? reconcileSeedDocumentLocalization(doc, collection) : doc\n })\n}\n\n/**\n * The CMS locale config for a project mount (EC-357 finding #3). Locales are\n * structure and structure is code: when the active `elytra.config.ts` project\n * declares locales, they drive the ambient locale switcher in EVERY mode. The old\n * wiring only applied the project's locale settings on a HYDRATED session, so a\n * playground boot — or a connected FIRST RUN with nothing persisted yet — fell\n * back to a kit's locales or the core's sample `en/de` fixture and the switcher\n * showed `EN·/DE` against a `locales: ['de']` config. Without a config project\n * (env-only wiring) the previous ladder is unchanged: hydrated project settings,\n * then the kit's locales, then the sample fixture (via `CmsProvider`'s default).\n * `configLocales` is a parameter purely so the ladder is unit-testable (same seam\n * pattern as {@link resolveCmsInitialCollections}).\n */\nexport function resolveCmsInitialLocaleConfig(\n cms: HydratedCms | null,\n kit: InstantiatedKit | undefined,\n project: Project,\n configLocales: LocaleConfig | undefined = resolveDeploymentLocaleConfig(),\n): LocaleConfig | undefined {\n if (configLocales) return configLocales\n if (cms) return { default: project.settings.defaultLocale, locales: [...project.settings.locales] }\n return kit?.localeConfig\n}\n\n/**\n * CMS section: hydrates the persisted (or kit-seeded) collections + documents, then\n * renders the active screen full-width (index, a collection table, or a document\n * editor). EC-293: the collection list lives in the section rail (nested under the\n * CMS view), so there is no in-surface collections sidebar. Forced states (?state=)\n * replace the whole surface. `focusCollections` (EC-239) narrows the index to a\n * subset, so a focused view (\"Catalog\") reuses this surface with config.\n */\nfunction CmsScreen({\n project,\n collectionId,\n documentId,\n state,\n field,\n focusCollections,\n}: {\n project: Project\n collectionId: string | null\n documentId: string | null\n state?: SectionState\n field?: string\n focusCollections?: readonly string[]\n}) {\n const projectId = project.id\n if (state && state !== 'working') {\n return <SectionStateView state={state} title={translate('nav.cms.label')} />\n }\n\n // A project created from a starter kit (EC-119) opens the CMS on the kit's schemas + content.\n const kit = getInstantiatedKit(projectId)\n\n return (\n // EC-177 gap 1: in Convex mode every CMS mount loads the CURRENT persisted\n // schema + documents (fixtures only when nothing is persisted — first run).\n // Playground renders synchronously with `null` → fixture seeds, as before.\n <CmsSessionGate>\n {(cms) => {\n // EC-424: resolved once so `initialDocuments`' reconciliation (below)\n // stays in lockstep with the SAME collections `initialCollections` uses.\n const initialCollections = resolveCmsInitialCollections(cms, kit)\n return (\n <CmsProvider\n key={projectId}\n projectId={projectId}\n initialCollections={initialCollections}\n initialDocuments={resolveCmsInitialDocuments(cms, kit, initialCollections)}\n initialPublishedKeys={cms?.publishedKeys}\n initialTimestamps={cms?.timestamps}\n initialPublishTimestamps={cms?.publishTimestamps}\n initialChangedKeys={cms?.changedKeys}\n initialLocaleConfig={resolveCmsInitialLocaleConfig(cms, kit, project)}\n // EC-461: a hydrated mount (cms non-null — both modes now) seeds the\n // CURRENT persisted state, so staged docs are superseded by it; only\n // a static kit/fixture seed lets staged entries win.\n seedReflectsWrites={cms !== null}\n >\n <div className=\"flex h-full min-h-0 flex-col\">\n <CmsPersistErrorBanner />\n {/* EC-293: the collection list now lives in the section rail (nested\n under the CMS view), so the surface is full-width — the old 240px\n CollectionsSidebar aside is gone. EC-199: the AI assistant mounts as\n a right-hand sibling (both list + editor), so the content is a row. */}\n <div className=\"flex min-h-0 flex-1 overflow-hidden\">\n <div className=\"min-w-0 flex-1 overflow-hidden\">\n <CmsContent\n projectId={projectId}\n collectionId={collectionId}\n documentId={documentId}\n field={field}\n focusCollections={focusCollections}\n />\n </div>\n <AiCmsPanel />\n </div>\n </div>\n </CmsProvider>\n )\n }}\n </CmsSessionGate>\n )\n}\n\n/** Resolves the active CMS screen inside the provider (needs the live store). */\nfunction CmsContent({\n projectId,\n collectionId,\n documentId,\n field,\n focusCollections,\n}: {\n projectId: string\n collectionId: string | null\n documentId: string | null\n field?: string\n focusCollections?: readonly string[]\n}) {\n const cms = useCms()\n\n if (collectionId === null) return <CmsIndex projectId={projectId} focusCollections={focusCollections} />\n\n if (documentId === null) {\n const collection = cms.getCollection(collectionId)\n // EC-274: a singleton is a UI lens — at the collection URL it opens its one\n // document directly *while in its expected shape*, but degrades to the list\n // (with a \"for review\" lane) on drift. SingletonGate decides per the data.\n if (collection && isSingleton(collection)) {\n return <SingletonGate projectId={projectId} collectionId={collectionId} />\n }\n return <CollectionTable projectId={projectId} collectionId={collectionId} />\n }\n\n return (\n <DetailEditor\n projectId={projectId}\n collectionId={collectionId}\n documentId={documentId}\n focusPath={field}\n />\n )\n}\n\n/**\n * EC-274 — the singleton lens gate. At a singleton collection's URL, resolve its\n * lone document and open it directly; on drift (> 1 document) fall back to the\n * list with the \"for review\" lane so an editor can resolve the conflict.\n */\nfunction SingletonGate({ projectId, collectionId }: { projectId: string; collectionId: string }) {\n const cms = useCms()\n const collection = cms.getCollection(collectionId)\n const docs = cms.documentsOf(collectionId)\n const resolution = collection\n ? resolveSingletonDocument(collection, docs)\n : ({ kind: 'empty' } as const)\n\n // Materialize the lone document on first visit (an empty draft, persisted like\n // any create). Idempotent in the store; only fires while genuinely empty.\n useEffect(() => {\n if (resolution.kind === 'empty') cms.ensureSingletonDocument(collectionId)\n }, [resolution.kind, cms, collectionId])\n\n if (resolution.kind === 'ready') {\n return (\n <DetailEditor projectId={projectId} collectionId={collectionId} documentId={resolution.doc.id} />\n )\n }\n if (resolution.kind === 'drift') {\n return <CollectionTable projectId={projectId} collectionId={collectionId} />\n }\n return (\n <div className=\"grid h-full place-items-center p-8\">\n <Text size=\"sm\" tone=\"muted\">\n {translate('appViews.singleton.opening', {\n label: localizedLabel(collection?.form?.label, collectionId),\n })}\n </Text>\n </div>\n )\n}\n\n/** Failed/denied persisted CMS mutations are always visible (EC-141). */\nfunction CmsPersistErrorBanner() {\n const cms = useCms()\n if (!cms.persistError) return null\n return (\n <div className=\"shrink-0 border-b border-border p-2\">\n <OperationErrorBanner error={cms.persistError} onDismiss={cms.clearPersistError} />\n </div>\n )\n}\n"]}
@@ -5,6 +5,7 @@ import { Text } from '@elytracms/studio/ui';
5
5
  import { translate } from '@elytracms/studio/lib/i18n';
6
6
  import { cn } from '@elytracms/studio/lib/utils';
7
7
  import { useOptionalAssets } from './assets-store.js';
8
+ import { isDisplayableAssetUrl } from '@elytracms/studio/lib/assets';
8
9
  import { CENTER_FOCAL_POINT, focalPointFromPoint, focalPointObjectPosition, } from './focal-point.js';
9
10
  /** Resolve an asset record by id from the ambient library, if one is mounted. */
10
11
  export function useAssetRecord(assetId) {
@@ -15,29 +16,29 @@ export function useAssetRecord(assetId) {
15
16
  return store.assets.find((a) => a.id === assetId);
16
17
  }, [assetId, store]);
17
18
  }
18
- /** True when the record is an image with a browser-resolvable URL (EC-212). */
19
+ /** True when the record is an image with a browser-displayable URL (EC-212, EC-459). */
19
20
  function isResolvableImage(record) {
20
21
  return (record !== undefined &&
21
22
  record.contentType.startsWith('image/') &&
22
- typeof record.url === 'string' &&
23
- record.url.startsWith('http'));
23
+ isDisplayableAssetUrl(record.url));
24
24
  }
25
- const isHttp = (url) => typeof url === 'string' && url.startsWith('http');
26
25
  /**
27
26
  * The best studio thumbnail src for an image record (EC-400): prefer the stored
28
- * downscaled `thumb` variant when it is browser-resolvable, else fall back to the
29
- * full original `url`. A missing/broken thumb therefore degrades to the original —
30
- * never a broken tile (scope §4). Non-image / unresolvable records yield
31
- * `undefined` (the icon placeholder). Focal-point editing + the canvas keep the
32
- * original elsewhere (delivery fidelity) — this preference is thumbnail-only.
27
+ * downscaled `thumb` variant when it is browser-displayable, else fall back to
28
+ * the full original `url` (EC-459: `http(s)`, `blob:`, and `data:` all qualify —
29
+ * an opaque backend locator like `memory://` does not). A missing/broken thumb
30
+ * therefore degrades to the original — never a broken tile (scope §4). Non-image
31
+ * / unresolvable records yield `undefined` (the icon placeholder). Focal-point
32
+ * editing + the canvas keep the original elsewhere (delivery fidelity) — this
33
+ * preference is thumbnail-only.
33
34
  */
34
35
  function thumbnailSrc(record) {
35
36
  if (!record || !record.contentType.startsWith('image/'))
36
37
  return undefined;
37
38
  const thumbUrl = record.thumb?.url;
38
- if (isHttp(thumbUrl))
39
+ if (isDisplayableAssetUrl(thumbUrl))
39
40
  return thumbUrl;
40
- if (isHttp(record.url))
41
+ if (isDisplayableAssetUrl(record.url))
41
42
  return record.url;
42
43
  return undefined;
43
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"asset-thumb.js","sourceRoot":"","sources":["../../../src/components/assets/asset-thumb.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useRef, useState } from 'react'\nimport { Image } from 'lucide-react'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useOptionalAssets } from './assets-store'\nimport {\n CENTER_FOCAL_POINT,\n focalPointFromPoint,\n focalPointObjectPosition,\n} from './focal-point'\nimport type { FocalPoint } from './focal-point'\n\n/** Resolve an asset record by id from the ambient library, if one is mounted. */\nexport function useAssetRecord(assetId: string | null | undefined): AssetRecord | undefined {\n const store = useOptionalAssets()\n return useMemo(() => {\n if (!assetId || !store || store.status !== 'ready') return undefined\n return store.assets.find((a) => a.id === assetId)\n }, [assetId, store])\n}\n\n/** True when the record is an image with a browser-resolvable URL (EC-212). */\nfunction isResolvableImage(record: AssetRecord | undefined): record is AssetRecord & { url: string } {\n return (\n record !== undefined &&\n record.contentType.startsWith('image/') &&\n typeof record.url === 'string' &&\n record.url.startsWith('http')\n )\n}\n\nconst isHttp = (url: string | undefined): url is string =>\n typeof url === 'string' && url.startsWith('http')\n\n/**\n * The best studio thumbnail src for an image record (EC-400): prefer the stored\n * downscaled `thumb` variant when it is browser-resolvable, else fall back to the\n * full original `url`. A missing/broken thumb therefore degrades to the original —\n * never a broken tile (scope §4). Non-image / unresolvable records yield\n * `undefined` (the icon placeholder). Focal-point editing + the canvas keep the\n * original elsewhere (delivery fidelity) — this preference is thumbnail-only.\n */\nfunction thumbnailSrc(record: AssetRecord | undefined): string | undefined {\n if (!record || !record.contentType.startsWith('image/')) return undefined\n const thumbUrl = record.thumb?.url\n if (isHttp(thumbUrl)) return thumbUrl\n if (isHttp(record.url)) return record.url\n return undefined\n}\n\n/**\n * A read-only image thumbnail honouring the asset's focal point (EC-248): the\n * preview crops with the same `object-position` delivery uses (EC-230), so the\n * editor sees exactly what a focal-point change does. Prefers the EC-400\n * downscaled `thumb` variant (light egress) and falls back to the original.\n * Non-image / unresolvable assets fall back to the labelled icon, matching the\n * table cell's preview.\n */\nexport function AssetThumb({\n record,\n className,\n alt,\n}: {\n record: AssetRecord | undefined\n className?: string\n alt?: string\n}) {\n const src = thumbnailSrc(record)\n if (record && src) {\n return (\n <img\n src={src}\n alt={alt ?? record.alt ?? record.title ?? record.filename}\n loading=\"lazy\"\n style={{ objectPosition: focalPointObjectPosition(record.focalPoint) }}\n className={cn('rounded border border-border bg-muted object-cover', className)}\n />\n )\n }\n return (\n <div\n className={cn(\n 'flex items-center justify-center rounded border border-border bg-muted text-muted-foreground',\n className,\n )}\n >\n <Image className=\"size-4\" aria-hidden />\n </div>\n )\n}\n\n/**\n * The focal-point picker (EC-248/EC-301): click or **drag** anywhere on the image\n * to place the normalized 0–1 hotspot delivery threads to `object-position`\n * (EC-230). The marker follows the pointer live while dragging and only commits on\n * release (`onPreview` updates the marker, `onChange` commits). `onChange(null)`\n * resets to centre. Shows a placeholder until the image loads; renders a muted note\n * when the asset has no resolvable image.\n */\nexport function FocalPointPicker({\n record,\n point,\n onChange,\n onPreview,\n disabled,\n}: {\n record: AssetRecord | undefined\n point: FocalPoint | undefined\n onChange: (point: FocalPoint | null) => void\n /** Called continuously while dragging to move the marker without committing. */\n onPreview?: (point: FocalPoint) => void\n disabled?: boolean\n}) {\n const marker = point ?? CENTER_FOCAL_POINT\n const dragging = useRef(false)\n const [loaded, setLoaded] = useState(false)\n\n if (!isResolvableImage(record)) {\n return (\n <Text size=\"xs\" tone=\"subtle\">\n {translate('assets.focalPoint.noPreview')}\n </Text>\n )\n }\n\n const pointFrom = (e: React.PointerEvent<HTMLElement>): FocalPoint =>\n focalPointFromPoint(e.currentTarget.getBoundingClientRect(), e.clientX, e.clientY)\n\n function onPointerDown(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled) return\n dragging.current = true\n e.currentTarget.setPointerCapture(e.pointerId)\n ;(onPreview ?? onChange)(pointFrom(e))\n }\n function onPointerMove(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled || !dragging.current) return\n ;(onPreview ?? onChange)(pointFrom(e))\n }\n function onPointerUp(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled || !dragging.current) return\n dragging.current = false\n e.currentTarget.releasePointerCapture(e.pointerId)\n onChange(pointFrom(e)) // commit on release only\n }\n\n return (\n <button\n type=\"button\"\n aria-label={translate('assets.focalPoint.setLabel')}\n disabled={disabled}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n className={cn(\n 'relative block w-full touch-none select-none overflow-hidden rounded border border-border bg-muted',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',\n disabled ? 'cursor-default opacity-60' : 'cursor-crosshair',\n )}\n >\n {/* Width-constrained, natural aspect (no object-fit), so the button wraps\n the image tightly and the bounding rect equals the image box — the\n normalized pointer coordinates are then exact, with no letterbox offset. */}\n <img\n src={record.url}\n alt={record.alt ?? record.filename}\n draggable={false}\n onLoad={() => setLoaded(true)}\n className={cn('block h-auto w-full select-none transition-opacity', loaded ? 'opacity-100' : 'opacity-0')}\n />\n {!loaded ? (\n <span className=\"absolute inset-0 grid aspect-video place-items-center bg-muted\">\n <Image className=\"size-6 animate-pulse text-muted-foreground\" aria-hidden />\n </span>\n ) : (\n <span\n aria-hidden\n className=\"pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1.5px_rgba(0,0,0,0.55)] ring-1 ring-black/40\"\n style={{ left: `${marker.x * 100}%`, top: `${marker.y * 100}%` }}\n />\n )}\n </button>\n )\n}\n\n/** Common crop aspect ratios shown as focal-aware previews (EC-301). */\nconst FOCAL_CROP_RATIOS = [\n { label: '9:16', className: 'aspect-[9/16]' },\n { label: '3:4', className: 'aspect-[3/4]' },\n { label: '1:1', className: 'aspect-square' },\n { label: '4:3', className: 'aspect-[4/3]' },\n { label: '16:9', className: 'aspect-video' },\n] as const\n\n/**\n * A row of common-ratio crop previews (EC-301), each cropping the image with the\n * same `object-position` delivery uses — so the editor sees how the focal point\n * holds the subject across portrait/square/landscape crops. Updates live as the\n * point is dragged.\n */\nexport function FocalCropPreviews({\n record,\n point,\n}: {\n record: AssetRecord | undefined\n point: FocalPoint | undefined\n}) {\n if (!isResolvableImage(record)) return null\n const objectPosition = focalPointObjectPosition(point)\n return (\n <div className=\"flex items-end gap-2\">\n {FOCAL_CROP_RATIOS.map((ratio) => (\n <div key={ratio.label} className=\"flex min-w-0 flex-col items-center gap-1\">\n <div className={cn('h-14 overflow-hidden rounded border border-border bg-muted', ratio.className)}>\n <img\n src={record.url}\n alt=\"\"\n draggable={false}\n style={{ objectPosition }}\n className=\"h-full w-full object-cover\"\n />\n </div>\n <Text size=\"xs\" tone=\"subtle\">\n {ratio.label}\n </Text>\n </div>\n ))}\n </div>\n )\n}\n"]}
1
+ {"version":3,"file":"asset-thumb.js","sourceRoot":"","sources":["../../../src/components/assets/asset-thumb.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMemo, useRef, useState } from 'react'\nimport { Image } from 'lucide-react'\nimport type { AssetRecord } from '@elytracms/core/persistence'\nimport { Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { useOptionalAssets } from './assets-store'\nimport { isDisplayableAssetUrl } from '@elytracms/studio/lib/assets'\nimport {\n CENTER_FOCAL_POINT,\n focalPointFromPoint,\n focalPointObjectPosition,\n} from './focal-point'\nimport type { FocalPoint } from './focal-point'\n\n/** Resolve an asset record by id from the ambient library, if one is mounted. */\nexport function useAssetRecord(assetId: string | null | undefined): AssetRecord | undefined {\n const store = useOptionalAssets()\n return useMemo(() => {\n if (!assetId || !store || store.status !== 'ready') return undefined\n return store.assets.find((a) => a.id === assetId)\n }, [assetId, store])\n}\n\n/** True when the record is an image with a browser-displayable URL (EC-212, EC-459). */\nfunction isResolvableImage(record: AssetRecord | undefined): record is AssetRecord & { url: string } {\n return (\n record !== undefined &&\n record.contentType.startsWith('image/') &&\n isDisplayableAssetUrl(record.url)\n )\n}\n\n/**\n * The best studio thumbnail src for an image record (EC-400): prefer the stored\n * downscaled `thumb` variant when it is browser-displayable, else fall back to\n * the full original `url` (EC-459: `http(s)`, `blob:`, and `data:` all qualify —\n * an opaque backend locator like `memory://` does not). A missing/broken thumb\n * therefore degrades to the original — never a broken tile (scope §4). Non-image\n * / unresolvable records yield `undefined` (the icon placeholder). Focal-point\n * editing + the canvas keep the original elsewhere (delivery fidelity) — this\n * preference is thumbnail-only.\n */\nfunction thumbnailSrc(record: AssetRecord | undefined): string | undefined {\n if (!record || !record.contentType.startsWith('image/')) return undefined\n const thumbUrl = record.thumb?.url\n if (isDisplayableAssetUrl(thumbUrl)) return thumbUrl\n if (isDisplayableAssetUrl(record.url)) return record.url\n return undefined\n}\n\n/**\n * A read-only image thumbnail honouring the asset's focal point (EC-248): the\n * preview crops with the same `object-position` delivery uses (EC-230), so the\n * editor sees exactly what a focal-point change does. Prefers the EC-400\n * downscaled `thumb` variant (light egress) and falls back to the original.\n * Non-image / unresolvable assets fall back to the labelled icon, matching the\n * table cell's preview.\n */\nexport function AssetThumb({\n record,\n className,\n alt,\n}: {\n record: AssetRecord | undefined\n className?: string\n alt?: string\n}) {\n const src = thumbnailSrc(record)\n if (record && src) {\n return (\n <img\n src={src}\n alt={alt ?? record.alt ?? record.title ?? record.filename}\n loading=\"lazy\"\n style={{ objectPosition: focalPointObjectPosition(record.focalPoint) }}\n className={cn('rounded border border-border bg-muted object-cover', className)}\n />\n )\n }\n return (\n <div\n className={cn(\n 'flex items-center justify-center rounded border border-border bg-muted text-muted-foreground',\n className,\n )}\n >\n <Image className=\"size-4\" aria-hidden />\n </div>\n )\n}\n\n/**\n * The focal-point picker (EC-248/EC-301): click or **drag** anywhere on the image\n * to place the normalized 0–1 hotspot delivery threads to `object-position`\n * (EC-230). The marker follows the pointer live while dragging and only commits on\n * release (`onPreview` updates the marker, `onChange` commits). `onChange(null)`\n * resets to centre. Shows a placeholder until the image loads; renders a muted note\n * when the asset has no resolvable image.\n */\nexport function FocalPointPicker({\n record,\n point,\n onChange,\n onPreview,\n disabled,\n}: {\n record: AssetRecord | undefined\n point: FocalPoint | undefined\n onChange: (point: FocalPoint | null) => void\n /** Called continuously while dragging to move the marker without committing. */\n onPreview?: (point: FocalPoint) => void\n disabled?: boolean\n}) {\n const marker = point ?? CENTER_FOCAL_POINT\n const dragging = useRef(false)\n const [loaded, setLoaded] = useState(false)\n\n if (!isResolvableImage(record)) {\n return (\n <Text size=\"xs\" tone=\"subtle\">\n {translate('assets.focalPoint.noPreview')}\n </Text>\n )\n }\n\n const pointFrom = (e: React.PointerEvent<HTMLElement>): FocalPoint =>\n focalPointFromPoint(e.currentTarget.getBoundingClientRect(), e.clientX, e.clientY)\n\n function onPointerDown(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled) return\n dragging.current = true\n e.currentTarget.setPointerCapture(e.pointerId)\n ;(onPreview ?? onChange)(pointFrom(e))\n }\n function onPointerMove(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled || !dragging.current) return\n ;(onPreview ?? onChange)(pointFrom(e))\n }\n function onPointerUp(e: React.PointerEvent<HTMLButtonElement>) {\n if (disabled || !dragging.current) return\n dragging.current = false\n e.currentTarget.releasePointerCapture(e.pointerId)\n onChange(pointFrom(e)) // commit on release only\n }\n\n return (\n <button\n type=\"button\"\n aria-label={translate('assets.focalPoint.setLabel')}\n disabled={disabled}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n className={cn(\n 'relative block w-full touch-none select-none overflow-hidden rounded border border-border bg-muted',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',\n disabled ? 'cursor-default opacity-60' : 'cursor-crosshair',\n )}\n >\n {/* Width-constrained, natural aspect (no object-fit), so the button wraps\n the image tightly and the bounding rect equals the image box — the\n normalized pointer coordinates are then exact, with no letterbox offset. */}\n <img\n src={record.url}\n alt={record.alt ?? record.filename}\n draggable={false}\n onLoad={() => setLoaded(true)}\n className={cn('block h-auto w-full select-none transition-opacity', loaded ? 'opacity-100' : 'opacity-0')}\n />\n {!loaded ? (\n <span className=\"absolute inset-0 grid aspect-video place-items-center bg-muted\">\n <Image className=\"size-6 animate-pulse text-muted-foreground\" aria-hidden />\n </span>\n ) : (\n <span\n aria-hidden\n className=\"pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_0_0_1.5px_rgba(0,0,0,0.55)] ring-1 ring-black/40\"\n style={{ left: `${marker.x * 100}%`, top: `${marker.y * 100}%` }}\n />\n )}\n </button>\n )\n}\n\n/** Common crop aspect ratios shown as focal-aware previews (EC-301). */\nconst FOCAL_CROP_RATIOS = [\n { label: '9:16', className: 'aspect-[9/16]' },\n { label: '3:4', className: 'aspect-[3/4]' },\n { label: '1:1', className: 'aspect-square' },\n { label: '4:3', className: 'aspect-[4/3]' },\n { label: '16:9', className: 'aspect-video' },\n] as const\n\n/**\n * A row of common-ratio crop previews (EC-301), each cropping the image with the\n * same `object-position` delivery uses — so the editor sees how the focal point\n * holds the subject across portrait/square/landscape crops. Updates live as the\n * point is dragged.\n */\nexport function FocalCropPreviews({\n record,\n point,\n}: {\n record: AssetRecord | undefined\n point: FocalPoint | undefined\n}) {\n if (!isResolvableImage(record)) return null\n const objectPosition = focalPointObjectPosition(point)\n return (\n <div className=\"flex items-end gap-2\">\n {FOCAL_CROP_RATIOS.map((ratio) => (\n <div key={ratio.label} className=\"flex min-w-0 flex-col items-center gap-1\">\n <div className={cn('h-14 overflow-hidden rounded border border-border bg-muted', ratio.className)}>\n <img\n src={record.url}\n alt=\"\"\n draggable={false}\n style={{ objectPosition }}\n className=\"h-full w-full object-cover\"\n />\n </div>\n <Text size=\"xs\" tone=\"subtle\">\n {ratio.label}\n </Text>\n </div>\n ))}\n </div>\n )\n}\n"]}
@@ -47,11 +47,17 @@ export declare function BlocksPanel({ context, open, onOpenChange, }: {
47
47
  onOpenChange: (open: boolean) => void;
48
48
  }): import("react").JSX.Element | null;
49
49
  /**
50
- * One component tile (exported for markup tests). Not a native `<button>`: the
51
- * tile CONTAINS interactive controls (the copy-exact button and the variants
52
- * chip), and nested `<button>`s are invalid HTML — so it stays a keyboard-
53
- * operable `role="button"` element whose own key handler ignores events
54
- * bubbling from those inner controls.
50
+ * One component tile (exported for markup tests).
51
+ *
52
+ * EC-463: the tile's insert affordance is a REAL `<button>`. It used to be a
53
+ * `div[role="button"]` with a hand-rolled Enter/Space handler, because a tile
54
+ * carries two more controls (copy-exact, the variants chip) and nested
55
+ * `<button>`s are invalid HTML — a nested interactive control is also an
56
+ * accessibility failure, not just a validator complaint. The fix is the standard
57
+ * "stretched primary action" card: the button is an inset overlay UNDER the
58
+ * visual content, the content is `pointer-events-none`, and the two secondary
59
+ * controls sit above it. One frame, identical layout, native activation
60
+ * semantics — and `find role button` resolves it like every other control.
55
61
  */
56
62
  export declare function BlockTileButton({ tile, onInsert, onCopy, onDragStart, onDragEnd, }: {
57
63
  tile: BlockTile;
@@ -139,34 +139,28 @@ function TileGroup({ heading, tiles, onInsert, onCopy, onTileDragStart, onTileDr
139
139
  return (_jsxs("section", { className: "mb-3", "aria-label": heading, children: [_jsxs(Row, { gap: "2", className: "px-1 py-1.5", children: [_jsx(Code, { tone: "muted", className: "capitalize", children: heading }), _jsx(Text, { size: "xs", tone: "muted", children: tiles.length })] }), _jsx("div", { className: "grid grid-cols-3 gap-2", children: tiles.map((tile) => (_jsx(BlockTileButton, { tile: tile, onInsert: onInsert, onCopy: onCopy, onDragStart: (e) => onTileDragStart(tile, e), onDragEnd: onTileDragEnd }, tile.key))) })] }));
140
140
  }
141
141
  /**
142
- * One component tile (exported for markup tests). Not a native `<button>`: the
143
- * tile CONTAINS interactive controls (the copy-exact button and the variants
144
- * chip), and nested `<button>`s are invalid HTML — so it stays a keyboard-
145
- * operable `role="button"` element whose own key handler ignores events
146
- * bubbling from those inner controls.
142
+ * One component tile (exported for markup tests).
143
+ *
144
+ * EC-463: the tile's insert affordance is a REAL `<button>`. It used to be a
145
+ * `div[role="button"]` with a hand-rolled Enter/Space handler, because a tile
146
+ * carries two more controls (copy-exact, the variants chip) and nested
147
+ * `<button>`s are invalid HTML — a nested interactive control is also an
148
+ * accessibility failure, not just a validator complaint. The fix is the standard
149
+ * "stretched primary action" card: the button is an inset overlay UNDER the
150
+ * visual content, the content is `pointer-events-none`, and the two secondary
151
+ * controls sit above it. One frame, identical layout, native activation
152
+ * semantics — and `find role button` resolves it like every other control.
147
153
  */
148
154
  export function BlockTileButton({ tile, onInsert, onCopy, onDragStart, onDragEnd, }) {
149
155
  const degraded = tile.issues.length > 0;
150
156
  const defaultVariant = tile.variants[0];
151
- return (_jsxs("div", { role: "button", tabIndex: 0, draggable: true, onDragStart: onDragStart, onDragEnd: onDragEnd, onClick: () => onInsert(defaultVariant), onKeyDown: (event) => {
152
- // Only the tile itself: Enter/Space on the inner copy button or the
153
- // variants chip must operate THAT control, never also insert.
154
- if (event.target !== event.currentTarget)
155
- return;
156
- if (event.key === 'Enter' || event.key === ' ') {
157
- event.preventDefault();
158
- onInsert(defaultVariant);
159
- }
160
- }, "data-block-tile": tile.key, "data-block-degraded": degraded || undefined, "aria-label": translate('builder.blocksPanel.insertComponentAria', { title: tile.title }), title: tile.description, className: cn(
157
+ return (_jsxs("div", { draggable: true, onDragStart: onDragStart, onDragEnd: onDragEnd, "data-block-tile": tile.key, "data-block-degraded": degraded || undefined, className: cn(
161
158
  // AD-3: this outer border is the tile's ONE frame — the preview inset
162
159
  // below is deliberately borderless.
163
- 'group/tile relative flex cursor-pointer flex-col gap-1.5 rounded-lg border bg-card p-2 text-left outline-none transition-colors', 'hover:border-ring/60 hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring/60', degraded ? 'border-amber-500/50' : 'border-border'), children: [_jsx("button", { type: "button", "aria-label": translate('builder.blocksPanel.copyExactAria', {
160
+ 'group/tile relative flex cursor-pointer flex-col gap-1.5 rounded-lg border bg-card p-2 text-left transition-colors', 'hover:border-ring/60 hover:bg-muted/60', degraded ? 'border-amber-500/50' : 'border-border'), children: [_jsx("button", { type: "button", "data-block-insert": tile.key, onClick: () => onInsert(defaultVariant), "aria-label": translate('builder.blocksPanel.insertComponentAria', { title: tile.title }), title: tile.description, className: "absolute inset-0 z-0 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring/60" }), _jsx("button", { type: "button", "aria-label": translate('builder.blocksPanel.copyExactAria', {
164
161
  title: tile.title,
165
162
  variant: defaultVariant.name,
166
- }), title: translate('builder.blocksPanel.copyExactConfiguration'), "data-block-copy": tile.key, onClick: (event) => {
167
- event.stopPropagation();
168
- onCopy(tile, defaultVariant);
169
- }, className: cn('absolute right-1.5 top-1.5 z-10 rounded-md border border-border bg-popover/90 p-1 text-muted-foreground', 'opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/tile:opacity-100'), children: _jsx(ClipboardCopy, { className: "size-3.5", "aria-hidden": true }) }), tile.previewUrl ? (_jsx(PreviewImageThumbnail, { src: tile.previewUrl, alt: tile.title })) : (_jsx(PlaceholderThumbnail, { title: tile.title })), _jsxs(Row, { gap: "1", className: "w-full min-w-0", children: [_jsx("span", { className: "truncate text-xs font-medium", children: tile.title }), tile.variants.length > 1 ? (_jsx(VariantsChip, { tile: tile, onInsert: onInsert, onCopy: onCopy })) : null] }), degraded ? (_jsxs(Row, { gap: "1", className: "w-full min-w-0 text-amber-700 dark:text-amber-400", children: [_jsx(AlertTriangle, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { className: "truncate text-[0.7rem]", title: tile.issues.join('\n'), children: tile.issues[0] })] })) : null] }));
163
+ }), title: translate('builder.blocksPanel.copyExactConfiguration'), "data-block-copy": tile.key, onClick: () => onCopy(tile, defaultVariant), className: cn('absolute right-1.5 top-1.5 z-20 rounded-md border border-border bg-popover/90 p-1 text-muted-foreground', 'opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover/tile:opacity-100'), children: _jsx(ClipboardCopy, { className: "size-3.5", "aria-hidden": true }) }), _jsxs("div", { className: "pointer-events-none relative z-10 flex flex-col gap-1.5", children: [tile.previewUrl ? (_jsx(PreviewImageThumbnail, { src: tile.previewUrl, alt: tile.title })) : (_jsx(PlaceholderThumbnail, { title: tile.title })), _jsxs(Row, { gap: "1", className: "w-full min-w-0", children: [_jsx("span", { className: "truncate text-xs font-medium", children: tile.title }), tile.variants.length > 1 ? (_jsx(VariantsChip, { tile: tile, onInsert: onInsert, onCopy: onCopy })) : null] }), degraded ? (_jsxs(Row, { gap: "1", className: "w-full min-w-0 text-amber-700 dark:text-amber-400", children: [_jsx(AlertTriangle, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { className: "truncate text-[0.7rem]", title: tile.issues.join('\n'), children: tile.issues[0] })] })) : null] })] }));
170
164
  }
171
165
  /**
172
166
  * The compact variant affordance (EC-439 AD-1): an "n variants" chip in the
@@ -182,10 +176,10 @@ function VariantsChip({ tile, onInsert, onCopy, }) {
182
176
  return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", "data-block-variants": tile.key, "data-block-variants-match": matched || undefined, "aria-label": translate('builder.blocksPanel.variantsAria', {
183
177
  count: tile.variants.length,
184
178
  title: tile.title,
185
- }), title: laterVariantDegraded ? translate('builder.blocksPanel.variantIssuesHint') : undefined,
186
- // The chip sits inside the role="button" tile: opening the flyout
187
- // must never ALSO insert the default.
188
- onClick: (event) => event.stopPropagation(), className: cn('ml-auto flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[0.65rem] leading-none transition-colors', matched
179
+ }), title: laterVariantDegraded ? translate('builder.blocksPanel.variantIssuesHint') : undefined, className: cn(
180
+ // EC-463: the tile body is `pointer-events-none` so the stretched
181
+ // insert button underneath owns plain clicks — the chip opts back in.
182
+ 'pointer-events-auto relative z-20', 'ml-auto flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[0.65rem] leading-none transition-colors', matched
189
183
  ? 'bg-primary/15 text-primary'
190
184
  : 'bg-muted text-muted-foreground hover:bg-muted/70 hover:text-foreground'), children: [laterVariantDegraded ? (_jsx(AlertTriangle, { "aria-hidden": true, className: "size-2.5 text-amber-600 dark:text-amber-400" })) : null, translate('builder.blocksPanel.variantCount', { count: tile.variants.length })] }) }), _jsxs(PopoverContent, { className: "w-64 p-1.5", onClick: (event) => event.stopPropagation(), children: [_jsx(Text, { size: "xs", tone: "muted", className: "px-1.5 pb-1 pt-0.5", children: translate('builder.blocksPanel.variantsHeading') }), _jsx(VariantRows, { tile: tile, onInsert: onInsert, onCopy: onCopy })] })] }));
191
185
  }