@elytracms/studio 0.0.97 → 0.0.99

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 (107) hide show
  1. package/convex/assets.ts +16 -2
  2. package/convex/ingest-attempt.test.ts +161 -0
  3. package/convex/ingest.test.ts +269 -16
  4. package/convex/ingest.ts +238 -47
  5. package/convex/path-segment-refusal.test.ts +201 -0
  6. package/convex/prototype-key.test.ts +92 -0
  7. package/convex/records-column-transform.convex.test.ts +147 -0
  8. package/convex/records-zod-refinements.convex.test.ts +94 -0
  9. package/convex/schema.ts +1 -1
  10. package/dist/addon.d.ts +5 -3
  11. package/dist/addon.js +4 -3
  12. package/dist/addon.js.map +1 -1
  13. package/dist/components/app-views/default-modules.d.ts +10 -3
  14. package/dist/components/app-views/default-modules.js +5 -1
  15. package/dist/components/app-views/default-modules.js.map +1 -1
  16. package/dist/components/app-views/module.d.ts +15 -8
  17. package/dist/components/app-views/module.js.map +1 -1
  18. package/dist/components/builder/reuse-context.d.ts +29 -1
  19. package/dist/components/builder/reuse-context.js +58 -12
  20. package/dist/components/builder/reuse-context.js.map +1 -1
  21. package/dist/components/cms/cms-store.d.ts +5 -6
  22. package/dist/components/cms/cms-store.js +12 -24
  23. package/dist/components/cms/cms-store.js.map +1 -1
  24. package/dist/components/cms/field-control.d.ts +12 -1
  25. package/dist/components/cms/field-control.js +31 -6
  26. package/dist/components/cms/field-control.js.map +1 -1
  27. package/dist/components/cms/import-batch.d.ts +23 -0
  28. package/dist/components/cms/import-batch.js +43 -0
  29. package/dist/components/cms/import-batch.js.map +1 -0
  30. package/dist/components/cms/rich-text/editor.js +13 -10
  31. package/dist/components/cms/rich-text/editor.js.map +1 -1
  32. package/dist/components/cms/rich-text/value.d.ts +9 -0
  33. package/dist/components/cms/rich-text/value.js +13 -0
  34. package/dist/components/cms/rich-text/value.js.map +1 -1
  35. package/dist/components/cms/use-document-draft.d.ts +39 -3
  36. package/dist/components/cms/use-document-draft.js +46 -16
  37. package/dist/components/cms/use-document-draft.js.map +1 -1
  38. package/dist/components/cms/use-publish-controller.d.ts +7 -0
  39. package/dist/components/cms/use-publish-controller.js +17 -3
  40. package/dist/components/cms/use-publish-controller.js.map +1 -1
  41. package/dist/components/cms/versions-pane.js +8 -5
  42. package/dist/components/cms/versions-pane.js.map +1 -1
  43. package/dist/components/delivery-health-banner.d.ts +34 -16
  44. package/dist/components/delivery-health-banner.js +59 -27
  45. package/dist/components/delivery-health-banner.js.map +1 -1
  46. package/dist/components/issue-severity.d.ts +3 -2
  47. package/dist/components/issue-severity.js.map +1 -1
  48. package/dist/components/nav-issues-registry.d.ts +39 -24
  49. package/dist/components/nav-issues-registry.js +50 -21
  50. package/dist/components/nav-issues-registry.js.map +1 -1
  51. package/dist/components/nav-issues.d.ts +23 -50
  52. package/dist/components/nav-issues.js +31 -59
  53. package/dist/components/nav-issues.js.map +1 -1
  54. package/dist/components/notifications/notice-center.d.ts +61 -0
  55. package/dist/components/notifications/notice-center.js +119 -0
  56. package/dist/components/notifications/notice-center.js.map +1 -0
  57. package/dist/components/notifications/notice-model.d.ts +137 -0
  58. package/dist/components/notifications/notice-model.js +107 -0
  59. package/dist/components/notifications/notice-model.js.map +1 -0
  60. package/dist/components/notifications/outcome-notice.d.ts +16 -0
  61. package/dist/components/notifications/outcome-notice.js +34 -0
  62. package/dist/components/notifications/outcome-notice.js.map +1 -0
  63. package/dist/components/notifications/shell-findings.d.ts +36 -0
  64. package/dist/components/notifications/shell-findings.js +140 -0
  65. package/dist/components/notifications/shell-findings.js.map +1 -0
  66. package/dist/components/rail-view-source.js +5 -0
  67. package/dist/components/rail-view-source.js.map +1 -1
  68. package/dist/components/records/record-lens.js.map +1 -1
  69. package/dist/components/records/record-rail.d.ts +10 -2
  70. package/dist/components/records/record-rail.js +74 -4
  71. package/dist/components/records/record-rail.js.map +1 -1
  72. package/dist/components/section-nav.d.ts +15 -3
  73. package/dist/components/section-nav.js +39 -147
  74. package/dist/components/section-nav.js.map +1 -1
  75. package/dist/components/studio-project-screen.js.map +1 -1
  76. package/dist/components/studio-shell.js +8 -3
  77. package/dist/components/studio-shell.js.map +1 -1
  78. package/dist/components/use-narrow-viewport.d.ts +11 -0
  79. package/dist/components/use-narrow-viewport.js +25 -0
  80. package/dist/components/use-narrow-viewport.js.map +1 -0
  81. package/dist/components/users/role-policy-drift-issues.d.ts +14 -7
  82. package/dist/components/users/role-policy-drift-issues.js +17 -8
  83. package/dist/components/users/role-policy-drift-issues.js.map +1 -1
  84. package/dist/lib/collection-view/collection-view-source.d.ts +4 -2
  85. package/dist/lib/collection-view/collection-view-source.js.map +1 -1
  86. package/dist/lib/engine-version.d.ts +1 -1
  87. package/dist/lib/engine-version.js +1 -1
  88. package/dist/lib/engine-version.js.map +1 -1
  89. package/dist/lib/i18n/messages.de.js +24 -3
  90. package/dist/lib/i18n/messages.de.js.map +1 -1
  91. package/dist/lib/i18n/messages.en.d.ts +22 -2
  92. package/dist/lib/i18n/messages.en.js +27 -4
  93. package/dist/lib/i18n/messages.en.js.map +1 -1
  94. package/dist/lib/notices/toast-store.d.ts +61 -0
  95. package/dist/lib/notices/toast-store.js +116 -0
  96. package/dist/lib/notices/toast-store.js.map +1 -0
  97. package/dist/lib/operations/cms-persistence.d.ts +32 -7
  98. package/dist/lib/operations/cms-persistence.js +511 -71
  99. package/dist/lib/operations/cms-persistence.js.map +1 -1
  100. package/dist/lib/persistence/convex-adapter.js +61 -30
  101. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  102. package/dist/lib/persona/labels.js +1 -0
  103. package/dist/lib/persona/labels.js.map +1 -1
  104. package/dist/ui/toast.d.ts +24 -16
  105. package/dist/ui/toast.js +23 -32
  106. package/dist/ui/toast.js.map +1 -1
  107. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"file":"rail-view-source.js","sourceRoot":"","sources":["../../src/components/rail-view-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAWtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAOnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AA4BlD;0EAC0E;AAC1E,MAAM,UAAU,iBAAiB,CAAC,MAAgB;IAChD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;AACxE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA+B,EAC/B,cAAmC;IAEnC,MAAM,IAAI,GAA4B,EAAE,CAAA;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;IACjD,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;gFAEgF;AAChF,MAAM,UAAU,uBAAuB,CACrC,KAAsD,EACtD,SAAqD,EACrD,QAAyC;IAEzC,+DAA+D;IAC/D,OAAO,CAAC,GAAS,EAAE,KAAc,EAAE,EAAE;QACnC,KAAK,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;YACrC,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;YAC/B,IAAI,KAAK;gBAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC5B,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;AACH,CAAC;AAmBD;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAoB,EACpB,GAAsB,EACtB,MAAkB,EAClB,QAAuB;IAEvB,OAAO,aAAa,CAClB;QACE,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,qEAAqE;QACrE,yEAAyE;QACzE,wEAAwE;QACxE,yBAAyB;QACzB,UAAU,EAAE,GAAG,EAAE,CAAC,SAAS;QAC3B,WAAW,EAAE,GAAG,EAAE,CAAC,SAAS;QAC5B,QAAQ,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,CAC5B,UAAU,KAAK,QAAQ;YACrB,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC;YAC5B,CAAC,CAAC,OAAO,CAAC,OAAO,CAA2B;gBACxC,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE;oBACL,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,QAAQ,UAAU,iCAAiC;iBAC7D;aACF,CAAC;KACT,EACD,KAAK,CAAC,YAAY,EAClB,GAAG,EACH,MAAM,EACN,QAAQ,CACT,CAAA;AACH,CAAC;AAuED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAkC;IACvE,MAAM,EACJ,SAAS,EACT,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,GAAG,EACH,OAAO,EACP,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,eAAe,EACf,WAAW,EACX,eAAe,EACf,QAAQ,EACR,MAAM,EACN,SAAS,EACT,cAAc,EACd,SAAS,EACT,YAAY,EACZ,IAAI,EACJ,MAAM,EACN,IAAI,GACL,GAAG,KAAK,CAAA;IACT,OAAO;QACL,YAAY,EAAE,MAAM,CAAC,EAAE;QACvB,MAAM;QACN,YAAY;QACZ,SAAS;QACT,YAAY;QACZ,YAAY,EAAE,YAAY,CAAC,OAAO;QAClC,SAAS;QACT,GAAG;QACH,OAAO;QACP,mBAAmB,EAAE,mBAAmB,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QACtD,WAAW,EAAE,WAAW,IAAI,CAAC,MAAM,CAAC;QACpC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,eAAe,EAAE,eAAe,IAAI,CAAC,CAAC,KAAe,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,IAAI,KAAK,CAAC;QACxF,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,QAAQ;QACR,SAAS;QACT,cAAc;QACd,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS;QAC1B,YAAY;QACZ,MAAM;QACN,4EAA4E;QAC5E,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;QAChC,MAAM;QACN,8EAA8E;QAC9E,GAAG,CAAC,YAAY,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/C,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAgD;IAEhD,MAAM,EACJ,SAAS,EACT,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,GAAG,EACH,OAAO,EACP,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,eAAe,EACf,WAAW,EACX,eAAe,EACf,QAAQ,EACR,MAAM,EACN,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,SAAS,EACjB,UAAU,EACV,mBAAmB,EACnB,qBAAqB,GACtB,GAAG,OAAO,CAAA;IAEX,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAiB,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7F,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAwB,IAAI,CAAC,CAAA;IAEvE,MAAM,YAAY,GAAG,WAAW,CAAuC,CAAC,MAAM,EAAE,EAAE;QAChF,eAAe,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;IACnF,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAEhE,MAAM,IAAI,GAAG,WAAW,CAAC,uBAAuB,CAAC,OAAO,EAAE,mBAAmB,EAAE,YAAY,CAAC,EAAE;QAC5F,OAAO;QACP,mBAAmB;KACpB,CAAC,CAAA;IACF,MAAM,MAAM,GAAG,WAAW,CACxB,uBAAuB,CAAC,SAAS,EAAE,qBAAqB,EAAE,YAAY,CAAC,EACvE,CAAC,SAAS,EAAE,qBAAqB,CAAC,CACnC,CAAA;IAED,2EAA2E;IAC3E,uEAAuE;IACvE,6EAA6E;IAC7E,0EAA0E;IAC1E,MAAM,IAAI,GAAG,WAAW,CACtB,CAAC,GAAsB,EAAE,MAAkB,EAAE,QAAuB,EAAuB,EAAE,CAC3F,iBAAiB,CACf;QACE,YAAY,EAAE,MAAM,CAAC,EAAE;QACvB,SAAS,EAAE,SAAS,EAAE;QACtB,OAAO;QACP,QAAQ;QACR,eAAe,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,IAAI,SAAS,CAAC,CAAC,GAAG,CAAC,CAAA;YACnD,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAA;YAC3C,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;QACtE,CAAC;KACF,EACD,GAAG,EACH,MAAM,EACN,QAAQ,CACT,EACH,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,qBAAqB,CAAC,CACxF,CAAA;IAED,OAAO,OAAO,CACZ,GAAG,EAAE,CACH,sBAAsB,CAAC;QACrB,SAAS;QACT,MAAM;QACN,YAAY;QACZ,YAAY;QACZ,SAAS;QACT,GAAG;QACH,OAAO;QACP,mBAAmB;QACnB,WAAW;QACX,UAAU;QACV,QAAQ;QACR,eAAe;QACf,WAAW;QACX,eAAe;QACf,QAAQ;QACR,MAAM;QACN,SAAS;QACT,cAAc;QACd,SAAS;QACT,YAAY;QACZ,IAAI;QACJ,MAAM;QACN,IAAI;KACL,CAAC,EACJ;QACE,SAAS;QACT,MAAM;QACN,YAAY;QACZ,YAAY;QACZ,SAAS;QACT,GAAG;QACH,OAAO;QACP,mBAAmB;QACnB,WAAW;QACX,UAAU;QACV,QAAQ;QACR,eAAe;QACf,WAAW;QACX,eAAe;QACf,QAAQ;QACR,MAAM;QACN,SAAS;QACT,cAAc;QACd,SAAS;QACT,YAAY;QACZ,IAAI;QACJ,MAAM;QACN,IAAI;KACL,CACF,CAAA;AACH,CAAC","sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport type {\n CmsDocument,\n CmsValidationIssue,\n CollectionDef,\n DocumentRef,\n FieldDef,\n LocaleConfig,\n} from '@elytracms/core/cms-core'\nimport type { OperationError, OperationResult } from '@elytracms/core/operations'\nimport type { ReferenceTarget } from '@elytracms/core/persistence'\nimport { defaultViewState } from '../lib/cms-views'\nimport type { TableViewState } from '../lib/cms-views'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n FieldAccess,\n} from '../lib/collection-view/collection-view-source'\nimport { runBulkAction } from './cms/bulk-actions'\nimport type { BulkAction, BulkReport, BulkSetField } from './cms/bulk-actions'\n\n/**\n * The shared scaffold behind the primitive-rail `CollectionViewSource` hooks\n * (EC-327) — assets, users, redirects. All three hand-rolled identical\n * `viewState`/`lastError` state, an identical `setViewState` updater-fn\n * wrapper, an identical `if (!result.ok) setLastError(...)` error-mapping\n * shape (assets' `remove` diverges — see {@link OkResult} below), and a large\n * final `useMemo` assembling the same ~20-field {@link CollectionViewSource}.\n * The 4th rail (`useCmsCollectionViewSource`) does NOT reduplicate this — it\n * delegates to the ambient `CmsStore` — so this scaffold is scoped to the\n * three self-contained rails, not a fully generic replacement for the CMS\n * adapter.\n *\n * Genuinely divergent per-rail behaviour is a PARAMETER, never hidden here:\n * users' principal-based `fieldAccess`, users' no-op vs. redirects/assets'\n * registry-driven `validate`, redirects-only `create`, and — the one save/\n * remove result-shape mismatch — assets' `remove()` returns an\n * `outcome`-discriminated result where users/redirects use `{ ok }`; each rail\n * supplies its own `normalizeSaveResult`/`normalizeRemoveResult` to reconcile\n * whichever shape its store returns into `lastError`.\n */\n\n/** The common `{ ok: true; … } | { ok: false; message }` store-result shape —\n * users' and redirects' save/remove, and assets' `updateMetadata` (save). */\nexport type OkResult = { ok: true } | { ok: false; message: string }\n\n/** The default normalizer for an {@link OkResult}: a failure maps to a\n * surfaced {@link OperationError}; a success maps to no error at all. */\nexport function normalizeOkResult(result: OkResult): OperationError | null {\n return result.ok ? null : { code: 'unknown', message: result.message }\n}\n\n/**\n * Project a lens document's values into a meta bag (EC-311/EC-240/EC-303):\n * every entry whose key is NOT one of the rail's canonical/identity field\n * names round-trips through the record's open `meta` bag. Shared by the users\n * (`IDENTITY_FIELDS`) and redirects (`CANONICAL_REDIRECT_FIELDS`) rails — the\n * assets rail's projection is schema-FIELDS-driven inclusion (iterating the\n * schema's declared fields, not the document's value keys), a genuinely\n * different shape that stays bespoke in `assets-collection-view-source.ts`.\n */\nexport function metaBagProjection(\n values: Record<string, unknown>,\n canonicalNames: ReadonlySet<string>,\n): Record<string, unknown> {\n const meta: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n if (!canonicalNames.has(key)) meta[key] = value\n }\n return meta\n}\n\n/** Wrap a raw store write (save/remove) so a normalized failure surfaces via\n * `setError` — the factory's error-mapping plumbing, pulled out as a plain\n * function (no hook) so it is unit-testable without a `renderHook` harness. */\nexport function createErrorMappedWriter<TArg, TResult, TExtra = never>(\n write: (arg: TArg, extra?: TExtra) => Promise<TResult>,\n normalize: (result: TResult) => OperationError | null,\n setError: (error: OperationError) => void,\n): (arg: TArg, extra?: TExtra) => void {\n // `extra` (EC-616): a record save's base, forwarded untouched.\n return (arg: TArg, extra?: TExtra) => {\n void write(arg, extra).then((result) => {\n const error = normalize(result)\n if (error) setError(error)\n })\n }\n}\n\n/** The store-facing input of {@link runRailBulkAction} — plain data/functions,\n * so the rail bulk path is unit-testable without a hook harness. */\nexport interface RailBulkInput {\n collectionId: string\n /** Current rail rows (lens documents) — the runner's lookup + validation input. */\n documents: readonly CmsDocument[]\n titleOf(ref: DocumentRef): string\n validate(doc: CmsDocument): CmsValidationIssue[]\n /**\n * Dispatch ONE already-confirmed delete (the EC-173 reference dialog ran\n * before anything reaches this) and reconcile the rail store's result shape\n * into an operation-shaped outcome — a failure here is a visible per-item\n * failure in the report, never a swallowed one.\n */\n removeConfirmed(ref: DocumentRef): Promise<OperationResult<unknown>>\n}\n\n/**\n * The primitive rails' bulk runner (EC-433): the SAME `runBulkAction` machinery\n * the CMS rail uses ([[client-data-flow-one-cache]] — no parallel data flow),\n * wired to the rail's own store delete. Only `delete` is dispatchable — the\n * rails have no publish lifecycle and no bulk set-field — and an unsupported\n * action fails each item HONESTLY in the report rather than silently no-oping\n * (the exact bug this replaces: the assets rail's bulk delete confirmed into a\n * missing `source.bulk` and dropped the whole action on the floor).\n */\nexport function runRailBulkAction(\n input: RailBulkInput,\n ids: readonly string[],\n action: BulkAction,\n setField?: BulkSetField,\n): Promise<BulkReport> {\n return runBulkAction(\n {\n documents: input.documents,\n titleOf: input.titleOf,\n validate: input.validate,\n // A rail store patches its own cache/state inside a succeeded remove\n // (assets/users/redirects all do), and publish/unpublish/set-field never\n // succeed here (dispatch rejects them), so the session-apply hooks have\n // nothing left to apply.\n applyState: () => undefined,\n applyDelete: () => undefined,\n dispatch: (bulkAction, ref) =>\n bulkAction === 'delete'\n ? input.removeConfirmed(ref)\n : Promise.resolve<OperationResult<unknown>>({\n ok: false,\n error: {\n code: 'unknown',\n message: `Bulk ${bulkAction} is not supported in this view.`,\n },\n }),\n },\n input.collectionId,\n ids,\n action,\n setField,\n )\n}\n\n/** The fields shared by {@link RailAdapter} and the pure assembly input —\n * everything the factory copies straight onto the assembled source, or\n * defaults when a rail omits it. */\ninterface RailViewSourceCore {\n readonly projectId: string\n readonly schema: CollectionDef\n readonly capabilities: CollectionViewCapabilities\n readonly localeConfig: LocaleConfig\n documents(): CmsDocument[]\n get(ref: DocumentRef): CmsDocument | undefined\n titleOf(ref: DocumentRef): string\n /** Default `() => []` — none of the three rails carry cross-collection relation fields (v1). */\n referenceCandidates?(collectionId: string): CmsDocument[]\n /** Default `[schema]` — the rail's own schema is the only collection in scope. */\n collections?: readonly CollectionDef[]\n detailPath?(id: string): string\n listPath?: string\n /** Default `field => field.form?.readOnly ?? false`. */\n isFieldReadOnly?(field: FieldDef): boolean\n /** Principal-based tri-state — only the users rail supplies one. */\n fieldAccess?(field: FieldDef, record?: CmsDocument): FieldAccess\n /**\n * How this rail's records are addressed in the reference index (EC-433) —\n * only the assets rail supplies one (`{ type: 'asset' }`); absent ⇒ the\n * confirm dialog's default document-shaped lookup.\n */\n referenceTarget?(ref: DocumentRef): ReferenceTarget\n validate(doc: CmsDocument): CmsValidationIssue[]\n create(): DocumentRef\n}\n\n/** A rail's input to {@link useRailViewSource}: its store-facing reads/writes,\n * capability profile, and lens projections. `TSaveResult`/`TRemoveResult` are\n * each rail's own store-result shape — reconciled to `lastError` by the\n * rail's own `normalizeSaveResult`/`normalizeRemoveResult`. */\nexport interface RailAdapter<TSaveResult, TRemoveResult> extends RailViewSourceCore {\n /** `base`: the record as the editor opened it (EC-616 — see `RecordWrites.save`). */\n save(doc: CmsDocument, base?: CmsDocument): Promise<TSaveResult>\n remove(ref: DocumentRef): Promise<TRemoveResult>\n /**\n * The delete the BULK path dispatches (EC-433) — always post-confirmation\n * (the EC-173 reference dialog ran first). Defaults to {@link remove}; the\n * assets rail overrides it to pass `confirmInUse: true` so a confirmed bulk\n * delete of a referenced asset actually deletes (mirroring the detail\n * surface's confirmed delete) instead of re-blocking on the usage gate.\n */\n bulkRemove?(ref: DocumentRef): Promise<TRemoveResult>\n normalizeSaveResult(result: TSaveResult): OperationError | null\n normalizeRemoveResult(result: TRemoveResult): OperationError | null\n}\n\n/** The already-resolved state + error-wrapped writes {@link useRailViewSource}\n * hands to the pure assembly function below. */\ninterface RailViewSourceAssemblyInput extends RailViewSourceCore {\n lastError: OperationError | null\n clearLastError(): void\n viewState: TableViewState\n setViewState: CollectionViewSource['setViewState']\n save(doc: CmsDocument, base?: CmsDocument): void\n remove(ref: DocumentRef): void\n /**\n * The rail bulk runner (EC-433). Assembled onto the source ONLY when\n * `capabilities.bulk` is set — the `CollectionViewSource` contract is\n * \"method present iff the flag is\" — so a bulk-less rail's list never\n * half-offers the affordance.\n */\n bulk?: CollectionViewSource['bulk']\n}\n\n/**\n * Pure assembly of a {@link CollectionViewSource} from a rail's resolved\n * fields + state. Exported (and taking plain data/functions, no hook) so it is\n * directly unit-testable — this workspace runs vitest in `environment: 'node'`\n * (no `renderHook`); {@link useRailViewSource} is a thin `useState`/`useMemo`\n * wrapper around it.\n */\nexport function assembleRailViewSource(input: RailViewSourceAssemblyInput): CollectionViewSource {\n const {\n projectId,\n schema,\n capabilities,\n localeConfig,\n documents,\n get,\n titleOf,\n referenceCandidates,\n collections,\n detailPath,\n listPath,\n isFieldReadOnly,\n fieldAccess,\n referenceTarget,\n validate,\n create,\n lastError,\n clearLastError,\n viewState,\n setViewState,\n save,\n remove,\n bulk,\n } = input\n return {\n collectionId: schema.id,\n schema,\n capabilities,\n projectId,\n localeConfig,\n activeLocale: localeConfig.default,\n documents,\n get,\n titleOf,\n referenceCandidates: referenceCandidates ?? (() => []),\n collections: collections ?? [schema],\n ...(detailPath ? { detailPath } : {}),\n ...(listPath !== undefined ? { listPath } : {}),\n isFieldReadOnly: isFieldReadOnly ?? ((field: FieldDef) => field.form?.readOnly ?? false),\n ...(fieldAccess ? { fieldAccess } : {}),\n ...(referenceTarget ? { referenceTarget } : {}),\n validate,\n lastError,\n clearLastError,\n viewState: () => viewState,\n setViewState,\n create,\n // EC-595 A2: a rail record is written whole, on the editor's explicit Save.\n writes: { kind: 'record', save },\n remove,\n // Present iff the capability flag is set (the CollectionViewSource contract).\n ...(capabilities.bulk && bulk ? { bulk } : {}),\n }\n}\n\n/**\n * The shared scaffold factory (EC-327) behind the assets/users/redirects rail\n * `CollectionViewSource` hooks: `viewState`/`lastError` state, the\n * `setViewState` updater-fn wrapper, the error-mapped save/remove wiring (via\n * the rail's own result normalizer), and the final assembled source. See the\n * module doc for what stays a per-rail parameter rather than being absorbed\n * here.\n */\nexport function useRailViewSource<TSaveResult, TRemoveResult>(\n adapter: RailAdapter<TSaveResult, TRemoveResult>,\n): CollectionViewSource {\n const {\n projectId,\n schema,\n capabilities,\n localeConfig,\n documents,\n get,\n titleOf,\n referenceCandidates,\n collections,\n detailPath,\n listPath,\n isFieldReadOnly,\n fieldAccess,\n referenceTarget,\n validate,\n create,\n save: rawSave,\n remove: rawRemove,\n bulkRemove,\n normalizeSaveResult,\n normalizeRemoveResult,\n } = adapter\n\n const [viewState, setViewStateRaw] = useState<TableViewState>(() => defaultViewState(schema))\n const [lastError, setLastError] = useState<OperationError | null>(null)\n\n const setViewState = useCallback<CollectionViewSource['setViewState']>((update) => {\n setViewStateRaw((prev) => (typeof update === 'function' ? update(prev) : update))\n }, [])\n const clearLastError = useCallback(() => setLastError(null), [])\n\n const save = useCallback(createErrorMappedWriter(rawSave, normalizeSaveResult, setLastError), [\n rawSave,\n normalizeSaveResult,\n ])\n const remove = useCallback(\n createErrorMappedWriter(rawRemove, normalizeRemoveResult, setLastError),\n [rawRemove, normalizeRemoveResult],\n )\n\n // The rail bulk runner (EC-433): the shared `runBulkAction` machinery over\n // the rail's own confirmed delete, its store result reconciled into an\n // operation-shaped outcome (a failing remove is a visible per-item failure).\n // Assembled onto the source only when `capabilities.bulk` is set (below).\n const bulk = useCallback(\n (ids: readonly string[], action: BulkAction, setField?: BulkSetField): Promise<BulkReport> =>\n runRailBulkAction(\n {\n collectionId: schema.id,\n documents: documents(),\n titleOf,\n validate,\n removeConfirmed: async (ref) => {\n const result = await (bulkRemove ?? rawRemove)(ref)\n const error = normalizeRemoveResult(result)\n return error ? { ok: false, error } : { ok: true, value: undefined }\n },\n },\n ids,\n action,\n setField,\n ),\n [schema.id, documents, titleOf, validate, bulkRemove, rawRemove, normalizeRemoveResult],\n )\n\n return useMemo(\n () =>\n assembleRailViewSource({\n projectId,\n schema,\n capabilities,\n localeConfig,\n documents,\n get,\n titleOf,\n referenceCandidates,\n collections,\n detailPath,\n listPath,\n isFieldReadOnly,\n fieldAccess,\n referenceTarget,\n validate,\n create,\n lastError,\n clearLastError,\n viewState,\n setViewState,\n save,\n remove,\n bulk,\n }),\n [\n projectId,\n schema,\n capabilities,\n localeConfig,\n documents,\n get,\n titleOf,\n referenceCandidates,\n collections,\n detailPath,\n listPath,\n isFieldReadOnly,\n fieldAccess,\n referenceTarget,\n validate,\n create,\n lastError,\n clearLastError,\n viewState,\n setViewState,\n save,\n remove,\n bulk,\n ],\n )\n}\n"]}
1
+ {"version":3,"file":"rail-view-source.js","sourceRoot":"","sources":["../../src/components/rail-view-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useState } from 'react'\nimport type {\n CmsDocument,\n CmsValidationIssue,\n CollectionDef,\n DocumentRef,\n FieldDef,\n LocaleConfig,\n} from '@elytracms/core/cms-core'\nimport type { OperationError, OperationResult } from '@elytracms/core/operations'\nimport type { ReferenceTarget } from '@elytracms/core/persistence'\nimport { defaultViewState } from '../lib/cms-views'\nimport type { TableViewState } from '../lib/cms-views'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n FieldAccess,\n} from '../lib/collection-view/collection-view-source'\nimport { runBulkAction } from './cms/bulk-actions'\nimport { useOutcomeNotice } from './notifications/outcome-notice'\nimport type { BulkAction, BulkReport, BulkSetField } from './cms/bulk-actions'\n\n/**\n * The shared scaffold behind the primitive-rail `CollectionViewSource` hooks\n * (EC-327) — assets, users, redirects. All three hand-rolled identical\n * `viewState`/`lastError` state, an identical `setViewState` updater-fn\n * wrapper, an identical `if (!result.ok) setLastError(...)` error-mapping\n * shape (assets' `remove` diverges — see {@link OkResult} below), and a large\n * final `useMemo` assembling the same ~20-field {@link CollectionViewSource}.\n * The 4th rail (`useCmsCollectionViewSource`) does NOT reduplicate this — it\n * delegates to the ambient `CmsStore` — so this scaffold is scoped to the\n * three self-contained rails, not a fully generic replacement for the CMS\n * adapter.\n *\n * Genuinely divergent per-rail behaviour is a PARAMETER, never hidden here:\n * users' principal-based `fieldAccess`, users' no-op vs. redirects/assets'\n * registry-driven `validate`, redirects-only `create`, and — the one save/\n * remove result-shape mismatch — assets' `remove()` returns an\n * `outcome`-discriminated result where users/redirects use `{ ok }`; each rail\n * supplies its own `normalizeSaveResult`/`normalizeRemoveResult` to reconcile\n * whichever shape its store returns into `lastError`.\n */\n\n/** The common `{ ok: true; … } | { ok: false; message }` store-result shape —\n * users' and redirects' save/remove, and assets' `updateMetadata` (save). */\nexport type OkResult = { ok: true } | { ok: false; message: string }\n\n/** The default normalizer for an {@link OkResult}: a failure maps to a\n * surfaced {@link OperationError}; a success maps to no error at all. */\nexport function normalizeOkResult(result: OkResult): OperationError | null {\n return result.ok ? null : { code: 'unknown', message: result.message }\n}\n\n/**\n * Project a lens document's values into a meta bag (EC-311/EC-240/EC-303):\n * every entry whose key is NOT one of the rail's canonical/identity field\n * names round-trips through the record's open `meta` bag. Shared by the users\n * (`IDENTITY_FIELDS`) and redirects (`CANONICAL_REDIRECT_FIELDS`) rails — the\n * assets rail's projection is schema-FIELDS-driven inclusion (iterating the\n * schema's declared fields, not the document's value keys), a genuinely\n * different shape that stays bespoke in `assets-collection-view-source.ts`.\n */\nexport function metaBagProjection(\n values: Record<string, unknown>,\n canonicalNames: ReadonlySet<string>,\n): Record<string, unknown> {\n const meta: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n if (!canonicalNames.has(key)) meta[key] = value\n }\n return meta\n}\n\n/** Wrap a raw store write (save/remove) so a normalized failure surfaces via\n * `setError` — the factory's error-mapping plumbing, pulled out as a plain\n * function (no hook) so it is unit-testable without a `renderHook` harness. */\nexport function createErrorMappedWriter<TArg, TResult, TExtra = never>(\n write: (arg: TArg, extra?: TExtra) => Promise<TResult>,\n normalize: (result: TResult) => OperationError | null,\n setError: (error: OperationError) => void,\n): (arg: TArg, extra?: TExtra) => void {\n // `extra` (EC-616): a record save's base, forwarded untouched.\n return (arg: TArg, extra?: TExtra) => {\n void write(arg, extra).then((result) => {\n const error = normalize(result)\n if (error) setError(error)\n })\n }\n}\n\n/** The store-facing input of {@link runRailBulkAction} — plain data/functions,\n * so the rail bulk path is unit-testable without a hook harness. */\nexport interface RailBulkInput {\n collectionId: string\n /** Current rail rows (lens documents) — the runner's lookup + validation input. */\n documents: readonly CmsDocument[]\n titleOf(ref: DocumentRef): string\n validate(doc: CmsDocument): CmsValidationIssue[]\n /**\n * Dispatch ONE already-confirmed delete (the EC-173 reference dialog ran\n * before anything reaches this) and reconcile the rail store's result shape\n * into an operation-shaped outcome — a failure here is a visible per-item\n * failure in the report, never a swallowed one.\n */\n removeConfirmed(ref: DocumentRef): Promise<OperationResult<unknown>>\n}\n\n/**\n * The primitive rails' bulk runner (EC-433): the SAME `runBulkAction` machinery\n * the CMS rail uses ([[client-data-flow-one-cache]] — no parallel data flow),\n * wired to the rail's own store delete. Only `delete` is dispatchable — the\n * rails have no publish lifecycle and no bulk set-field — and an unsupported\n * action fails each item HONESTLY in the report rather than silently no-oping\n * (the exact bug this replaces: the assets rail's bulk delete confirmed into a\n * missing `source.bulk` and dropped the whole action on the floor).\n */\nexport function runRailBulkAction(\n input: RailBulkInput,\n ids: readonly string[],\n action: BulkAction,\n setField?: BulkSetField,\n): Promise<BulkReport> {\n return runBulkAction(\n {\n documents: input.documents,\n titleOf: input.titleOf,\n validate: input.validate,\n // A rail store patches its own cache/state inside a succeeded remove\n // (assets/users/redirects all do), and publish/unpublish/set-field never\n // succeed here (dispatch rejects them), so the session-apply hooks have\n // nothing left to apply.\n applyState: () => undefined,\n applyDelete: () => undefined,\n dispatch: (bulkAction, ref) =>\n bulkAction === 'delete'\n ? input.removeConfirmed(ref)\n : Promise.resolve<OperationResult<unknown>>({\n ok: false,\n error: {\n code: 'unknown',\n message: `Bulk ${bulkAction} is not supported in this view.`,\n },\n }),\n },\n input.collectionId,\n ids,\n action,\n setField,\n )\n}\n\n/** The fields shared by {@link RailAdapter} and the pure assembly input —\n * everything the factory copies straight onto the assembled source, or\n * defaults when a rail omits it. */\ninterface RailViewSourceCore {\n readonly projectId: string\n readonly schema: CollectionDef\n readonly capabilities: CollectionViewCapabilities\n readonly localeConfig: LocaleConfig\n documents(): CmsDocument[]\n get(ref: DocumentRef): CmsDocument | undefined\n titleOf(ref: DocumentRef): string\n /** Default `() => []` — none of the three rails carry cross-collection relation fields (v1). */\n referenceCandidates?(collectionId: string): CmsDocument[]\n /** Default `[schema]` — the rail's own schema is the only collection in scope. */\n collections?: readonly CollectionDef[]\n detailPath?(id: string): string\n listPath?: string\n /** Default `field => field.form?.readOnly ?? false`. */\n isFieldReadOnly?(field: FieldDef): boolean\n /** Principal-based tri-state — only the users rail supplies one. */\n fieldAccess?(field: FieldDef, record?: CmsDocument): FieldAccess\n /**\n * How this rail's records are addressed in the reference index (EC-433) —\n * only the assets rail supplies one (`{ type: 'asset' }`); absent ⇒ the\n * confirm dialog's default document-shaped lookup.\n */\n referenceTarget?(ref: DocumentRef): ReferenceTarget\n validate(doc: CmsDocument): CmsValidationIssue[]\n create(): DocumentRef\n}\n\n/** A rail's input to {@link useRailViewSource}: its store-facing reads/writes,\n * capability profile, and lens projections. `TSaveResult`/`TRemoveResult` are\n * each rail's own store-result shape — reconciled to `lastError` by the\n * rail's own `normalizeSaveResult`/`normalizeRemoveResult`. */\nexport interface RailAdapter<TSaveResult, TRemoveResult> extends RailViewSourceCore {\n /** `base`: the record as the editor opened it (EC-616 — see `RecordWrites.save`). */\n save(doc: CmsDocument, base?: CmsDocument): Promise<TSaveResult>\n remove(ref: DocumentRef): Promise<TRemoveResult>\n /**\n * The delete the BULK path dispatches (EC-433) — always post-confirmation\n * (the EC-173 reference dialog ran first). Defaults to {@link remove}; the\n * assets rail overrides it to pass `confirmInUse: true` so a confirmed bulk\n * delete of a referenced asset actually deletes (mirroring the detail\n * surface's confirmed delete) instead of re-blocking on the usage gate.\n */\n bulkRemove?(ref: DocumentRef): Promise<TRemoveResult>\n normalizeSaveResult(result: TSaveResult): OperationError | null\n normalizeRemoveResult(result: TRemoveResult): OperationError | null\n}\n\n/** The already-resolved state + error-wrapped writes {@link useRailViewSource}\n * hands to the pure assembly function below. */\ninterface RailViewSourceAssemblyInput extends RailViewSourceCore {\n lastError: OperationError | null\n clearLastError(): void\n viewState: TableViewState\n setViewState: CollectionViewSource['setViewState']\n save(doc: CmsDocument, base?: CmsDocument): void\n remove(ref: DocumentRef): void\n /**\n * The rail bulk runner (EC-433). Assembled onto the source ONLY when\n * `capabilities.bulk` is set — the `CollectionViewSource` contract is\n * \"method present iff the flag is\" — so a bulk-less rail's list never\n * half-offers the affordance.\n */\n bulk?: CollectionViewSource['bulk']\n}\n\n/**\n * Pure assembly of a {@link CollectionViewSource} from a rail's resolved\n * fields + state. Exported (and taking plain data/functions, no hook) so it is\n * directly unit-testable — this workspace runs vitest in `environment: 'node'`\n * (no `renderHook`); {@link useRailViewSource} is a thin `useState`/`useMemo`\n * wrapper around it.\n */\nexport function assembleRailViewSource(input: RailViewSourceAssemblyInput): CollectionViewSource {\n const {\n projectId,\n schema,\n capabilities,\n localeConfig,\n documents,\n get,\n titleOf,\n referenceCandidates,\n collections,\n detailPath,\n listPath,\n isFieldReadOnly,\n fieldAccess,\n referenceTarget,\n validate,\n create,\n lastError,\n clearLastError,\n viewState,\n setViewState,\n save,\n remove,\n bulk,\n } = input\n return {\n collectionId: schema.id,\n schema,\n capabilities,\n projectId,\n localeConfig,\n activeLocale: localeConfig.default,\n documents,\n get,\n titleOf,\n referenceCandidates: referenceCandidates ?? (() => []),\n collections: collections ?? [schema],\n ...(detailPath ? { detailPath } : {}),\n ...(listPath !== undefined ? { listPath } : {}),\n isFieldReadOnly: isFieldReadOnly ?? ((field: FieldDef) => field.form?.readOnly ?? false),\n ...(fieldAccess ? { fieldAccess } : {}),\n ...(referenceTarget ? { referenceTarget } : {}),\n validate,\n lastError,\n clearLastError,\n viewState: () => viewState,\n setViewState,\n create,\n // EC-595 A2: a rail record is written whole, on the editor's explicit Save.\n writes: { kind: 'record', save },\n remove,\n // Present iff the capability flag is set (the CollectionViewSource contract).\n ...(capabilities.bulk && bulk ? { bulk } : {}),\n }\n}\n\n/**\n * The shared scaffold factory (EC-327) behind the assets/users/redirects rail\n * `CollectionViewSource` hooks: `viewState`/`lastError` state, the\n * `setViewState` updater-fn wrapper, the error-mapped save/remove wiring (via\n * the rail's own result normalizer), and the final assembled source. See the\n * module doc for what stays a per-rail parameter rather than being absorbed\n * here.\n */\nexport function useRailViewSource<TSaveResult, TRemoveResult>(\n adapter: RailAdapter<TSaveResult, TRemoveResult>,\n): CollectionViewSource {\n const {\n projectId,\n schema,\n capabilities,\n localeConfig,\n documents,\n get,\n titleOf,\n referenceCandidates,\n collections,\n detailPath,\n listPath,\n isFieldReadOnly,\n fieldAccess,\n referenceTarget,\n validate,\n create,\n save: rawSave,\n remove: rawRemove,\n bulkRemove,\n normalizeSaveResult,\n normalizeRemoveResult,\n } = adapter\n\n const [viewState, setViewStateRaw] = useState<TableViewState>(() => defaultViewState(schema))\n const [lastError, setLastError] = useState<OperationError | null>(null)\n\n const setViewState = useCallback<CollectionViewSource['setViewState']>((update) => {\n setViewStateRaw((prev) => (typeof update === 'function' ? update(prev) : update))\n }, [])\n const clearLastError = useCallback(() => setLastError(null), [])\n // EC-690: a failed save/remove is ALSO an error notice of the shell's issues tray, for as long\n // as this rail holds it — registered here, at its owner, once (the edit shell only renders it);\n // dismissing it in the tray clears it here too.\n useOutcomeNotice(lastError, clearLastError)\n\n const save = useCallback(createErrorMappedWriter(rawSave, normalizeSaveResult, setLastError), [\n rawSave,\n normalizeSaveResult,\n ])\n const remove = useCallback(\n createErrorMappedWriter(rawRemove, normalizeRemoveResult, setLastError),\n [rawRemove, normalizeRemoveResult],\n )\n\n // The rail bulk runner (EC-433): the shared `runBulkAction` machinery over\n // the rail's own confirmed delete, its store result reconciled into an\n // operation-shaped outcome (a failing remove is a visible per-item failure).\n // Assembled onto the source only when `capabilities.bulk` is set (below).\n const bulk = useCallback(\n (ids: readonly string[], action: BulkAction, setField?: BulkSetField): Promise<BulkReport> =>\n runRailBulkAction(\n {\n collectionId: schema.id,\n documents: documents(),\n titleOf,\n validate,\n removeConfirmed: async (ref) => {\n const result = await (bulkRemove ?? rawRemove)(ref)\n const error = normalizeRemoveResult(result)\n return error ? { ok: false, error } : { ok: true, value: undefined }\n },\n },\n ids,\n action,\n setField,\n ),\n [schema.id, documents, titleOf, validate, bulkRemove, rawRemove, normalizeRemoveResult],\n )\n\n return useMemo(\n () =>\n assembleRailViewSource({\n projectId,\n schema,\n capabilities,\n localeConfig,\n documents,\n get,\n titleOf,\n referenceCandidates,\n collections,\n detailPath,\n listPath,\n isFieldReadOnly,\n fieldAccess,\n referenceTarget,\n validate,\n create,\n lastError,\n clearLastError,\n viewState,\n setViewState,\n save,\n remove,\n bulk,\n }),\n [\n projectId,\n schema,\n capabilities,\n localeConfig,\n documents,\n get,\n titleOf,\n referenceCandidates,\n collections,\n detailPath,\n listPath,\n isFieldReadOnly,\n fieldAccess,\n referenceTarget,\n validate,\n create,\n lastError,\n clearLastError,\n viewState,\n setViewState,\n save,\n remove,\n bulk,\n ],\n )\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"record-lens.js","sourceRoot":"","sources":["../../../src/components/records/record-lens.ts"],"names":[],"mappings":"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","sourcesContent":["import { collectionDefSchema, semanticValueEqual } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport { metaPatchBetween } from '@elytracms/core/persistence'\nimport type { AnyRecordKind, RecordEntryOf, RecordWriteOf } from '@elytracms/core/persistence'\nimport { metaBagProjection } from '../rail-view-source'\n\n/**\n * EC-616 — the projections between a record kind and the portable list/edit surfaces\n * (AD-19): a record rail edits a record as a LENS document (`{ collection, id, values }`)\n * over a `CollectionDef`. Pure — no React — so the mapping that decides what a save\n * writes is unit-testable on its own (it is the one place the write is derived from the\n * edited lens; a slip here silently corrupts a record).\n *\n * - **Read** ({@link recordToLensDocument}): the stored `meta` bag spread FIRST, then\n * the kind's columns — a canonical column always wins over a same-named meta key\n * (RESIDUE-3c: spread last, a stored `meta.to` displayed as the redirect's target and a\n * re-save wrote it into the canonical `to`).\n * - **Write** ({@link lensDocumentToRecordWrite}): every lens value keyed by a column is\n * that column; every other key round-trips through the open `meta` bag (EC-311 Tier 2\n * — a mount's additive field). A column the document does not carry is left out, so the\n * stored value is kept (EC-577: a create default applies once, in the adapter's\n * transaction — never restated here). A REQUIRED text column is always sent (`''` when\n * blank): the editor owns it, and an empty one is refused visibly by the write's\n * validation rather than kept silently. `meta` is omitted when no additive value exists,\n * so a rail with no additive fields never touches the bag.\n */\n\n/** The kind's columns (its declared fields; `meta` is the bag, not a column). */\nexport function recordColumnNames(kind: AnyRecordKind): ReadonlySet<string> {\n return new Set(Object.keys(kind.fields))\n}\n\n/** A stored record as the lens document the portable surfaces edit. */\nexport function recordToLensDocument<K extends AnyRecordKind>(kind: K, entry: RecordEntryOf<K>): CmsDocument {\n const record = entry as Record<string, unknown>\n const columns: Record<string, unknown> = {}\n for (const column of recordColumnNames(kind)) {\n if (record[column] !== undefined) columns[column] = record[column]\n }\n return {\n collection: kind.kind,\n id: String(record.id),\n values: { ...((record.meta as Record<string, unknown> | undefined) ?? {}), ...columns },\n }\n}\n\n/**\n * A column's zod definition, read through `_zod.def` rather than `instanceof`: a kind is\n * declared in whichever package owns it (an addon), whose `zod` may be a different copy\n * than this one — `instanceof` across copies is silently false.\n */\ninterface ZodDefView {\n type: string\n innerType?: { _zod: { def: ZodDefView } }\n entries?: Record<string, unknown>\n}\nfunction defOf(field: unknown): ZodDefView {\n return (field as { _zod: { def: ZodDefView } })._zod.def\n}\n\n/** Is this column a required text column (no default, not optional)? */\nfunction isRequiredText(field: unknown): boolean {\n return defOf(field).type === 'string'\n}\n\n/** The column's own definition, below any optional/default/nullable wrapper. */\nfunction unwrapped(field: unknown): ZodDefView {\n let def = defOf(field)\n while ((def.type === 'optional' || def.type === 'default' || def.type === 'nullable') && def.innerType) {\n def = def.innerType._zod.def\n }\n return def\n}\n\n/** A lens value as the write sends it for one column (a required text column is always a string). */\nfunction columnWriteValue(field: unknown, value: unknown): unknown {\n return isRequiredText(field) ? (typeof value === 'string' ? value : '') : value\n}\n\n/**\n * An edited lens document as the record write (see the module header).\n *\n * **With `base`** — the lens document of the record as the editor OPENED it — the write\n * carries only what the editor changed (EC-616, the multi-session baseline): a column whose\n * value differs from the base, and the changed `meta` keys as a per-key `metaPatch` (a key the\n * editor removed is `null`). Every other column is ABSENT, so the EC-577 rule keeps the stored\n * value — a column another writer changed after the editor opened the record survives the\n * editor's save. With nothing changed the write is just the id (a no-op edit).\n *\n * **Without `base`** (a record the editor is creating) the write is whole, as it always was.\n */\nexport function lensDocumentToRecordWrite<K extends AnyRecordKind>(\n kind: K,\n doc: CmsDocument,\n base?: CmsDocument,\n): RecordWriteOf<K> {\n const values = doc.values ?? {}\n const write: Record<string, unknown> = { id: doc.id }\n const columns = recordColumnNames(kind)\n if (!base) {\n for (const [column, field] of Object.entries(kind.fields)) {\n const value = columnWriteValue(field, values[column])\n if (value !== undefined) write[column] = value\n }\n const meta = metaBagProjection(values, columns)\n if (Object.keys(meta).length > 0) write.meta = meta\n return write as RecordWriteOf<K>\n }\n const before = base.values ?? {}\n for (const [column, field] of Object.entries(kind.fields)) {\n const value = values[column]\n if (semanticValueEqual(value, before[column])) continue\n // A cleared optional column is `null` on the wire (EC-577: clear), never a silent absence.\n write[column] = value === undefined ? (isRequiredText(field) ? '' : null) : columnWriteValue(field, value)\n }\n // The one per-key diff (core `metaPatchBetween`), shared with the asset editors (EC-634).\n const metaPatch = metaPatchBetween(metaBagProjection(before, columns), metaBagProjection(values, columns))\n if (Object.keys(metaPatch).length > 0) write.metaPatch = metaPatch\n return write as RecordWriteOf<K>\n}\n\n/** The lens field type a column edits as (unwrapping optional/default); `undefined` = not lens-editable. */\nfunction lensFieldType(field: unknown): FieldDef['type'] | undefined {\n switch (unwrapped(field).type) {\n case 'string':\n return 'text'\n case 'boolean':\n return 'boolean'\n case 'number':\n return 'number'\n case 'enum':\n return 'select'\n default:\n return undefined\n }\n}\n\nfunction humanize(name: string): string {\n const spaced = name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ')\n return spaced.charAt(0).toUpperCase() + spaced.slice(1)\n}\n\n/**\n * The DEFAULT lens of a kind: one field per column whose type the portable form edits\n * (text, boolean, number, select — derived from the column's zod type), labelled from its\n * name. A column of another shape (an object, a list) is not in the lens: the rail never\n * writes it, so the stored value is kept on every save (EC-577). A mount refines the lens\n * by name through `nav.rail(kind, { fields })` (EC-311 — the same override mechanism the\n * built-in rails use), or passes its own `schema`.\n */\nexport function recordLensDef(kind: AnyRecordKind, options: { label?: string; titleField?: string } = {}): CollectionDef {\n const fields: Record<string, unknown>[] = []\n for (const [name, field] of Object.entries(kind.fields)) {\n const type = lensFieldType(field)\n if (!type) continue\n const entry: Record<string, unknown> = { name, type, form: { label: humanize(name) } }\n if (type === 'select') {\n entry.options = Object.values(unwrapped(field).entries ?? {}).map((value) => ({ value: String(value), label: String(value) }))\n }\n fields.push(entry)\n }\n const titleField = options.titleField ?? fields.find((field) => field.type === 'text')?.name\n return collectionDefSchema.parse({\n id: kind.kind,\n // A record kind is not a CMS `kind`; 'document' just satisfies the lens schema.\n kind: 'document',\n ...(titleField ? { titleField } : {}),\n form: { label: options.label ?? humanize(kind.module) },\n fields,\n })\n}\n"]}
1
+ {"version":3,"file":"record-lens.js","sourceRoot":"","sources":["../../../src/components/records/record-lens.ts"],"names":[],"mappings":"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","sourcesContent":["import { collectionDefSchema, semanticValueEqual } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, FieldDef } from '@elytracms/core/cms-core'\nimport { metaPatchBetween } from '@elytracms/core/persistence'\nimport type { AnyRecordKind, RecordEntryOf, RecordWriteOf } from '@elytracms/core/persistence'\nimport { metaBagProjection } from '../rail-view-source'\n\n/**\n * EC-616 — the projections between a record kind and the portable list/edit surfaces\n * (AD-19): a record rail edits a record as a LENS document (`{ collection, id, values }`)\n * over a `CollectionDef`. Pure — no React — so the mapping that decides what a save\n * writes is unit-testable on its own (it is the one place the write is derived from the\n * edited lens; a slip here silently corrupts a record).\n *\n * - **Read** ({@link recordToLensDocument}): the stored `meta` bag spread FIRST, then\n * the kind's columns — a canonical column always wins over a same-named meta key\n * (RESIDUE-3c: spread last, a stored `meta.to` displayed as the redirect's target and a\n * re-save wrote it into the canonical `to`).\n * - **Write** ({@link lensDocumentToRecordWrite}): every lens value keyed by a column is\n * that column; every other key round-trips through the open `meta` bag (EC-311 Tier 2\n * — a mount's additive field). A column the document does not carry is left out, so the\n * stored value is kept (EC-577: a create default applies once, in the adapter's\n * transaction — never restated here). A REQUIRED text column is always sent (`''` when\n * blank): the editor owns it, and an empty one is refused visibly by the write's\n * validation rather than kept silently. `meta` is omitted when no additive value exists,\n * so a rail with no additive fields never touches the bag.\n */\n\n/** The kind's columns (its declared fields; `meta` is the bag, not a column). */\nexport function recordColumnNames(kind: AnyRecordKind): ReadonlySet<string> {\n return new Set(Object.keys(kind.fields))\n}\n\n/** A stored record as the lens document the portable surfaces edit. */\nexport function recordToLensDocument<K extends AnyRecordKind>(kind: K, entry: RecordEntryOf<K>): CmsDocument {\n const record = entry as Record<string, unknown>\n const columns: Record<string, unknown> = {}\n for (const column of recordColumnNames(kind)) {\n if (record[column] !== undefined) columns[column] = record[column]\n }\n return {\n collection: kind.kind,\n id: String(record.id),\n values: { ...((record.meta as Record<string, unknown> | undefined) ?? {}), ...columns },\n }\n}\n\n/**\n * A column's zod definition, read through `_zod.def`: a kind is declared in whichever\n * package owns it (an addon), whose `zod` may be a different copy than this one, and the\n * def is the one field every zod 4 copy shares. (EC-678 correction, as EC-639 corrected\n * `persistence/record-write.ts`: zod 4's `instanceof` is trait-based and answers TRUE across\n * copies — measured 2026-10-28 with the repo's `zod-copy-older` / `zod-copy-newer` against\n * its own `zod`, control `ZodString` vs `ZodNumber` false — so it was never \"silently false\";\n * it simply cannot tell the copies apart. Reading the def is the shape this lens needs.)\n */\ninterface ZodDefView {\n type: string\n innerType?: { _zod: { def: ZodDefView } }\n entries?: Record<string, unknown>\n}\nfunction defOf(field: unknown): ZodDefView {\n return (field as { _zod: { def: ZodDefView } })._zod.def\n}\n\n/** Is this column a required text column (no default, not optional)? */\nfunction isRequiredText(field: unknown): boolean {\n return defOf(field).type === 'string'\n}\n\n/** The column's own definition, below any optional/default/nullable wrapper. */\nfunction unwrapped(field: unknown): ZodDefView {\n let def = defOf(field)\n while ((def.type === 'optional' || def.type === 'default' || def.type === 'nullable') && def.innerType) {\n def = def.innerType._zod.def\n }\n return def\n}\n\n/** A lens value as the write sends it for one column (a required text column is always a string). */\nfunction columnWriteValue(field: unknown, value: unknown): unknown {\n return isRequiredText(field) ? (typeof value === 'string' ? value : '') : value\n}\n\n/**\n * An edited lens document as the record write (see the module header).\n *\n * **With `base`** — the lens document of the record as the editor OPENED it — the write\n * carries only what the editor changed (EC-616, the multi-session baseline): a column whose\n * value differs from the base, and the changed `meta` keys as a per-key `metaPatch` (a key the\n * editor removed is `null`). Every other column is ABSENT, so the EC-577 rule keeps the stored\n * value — a column another writer changed after the editor opened the record survives the\n * editor's save. With nothing changed the write is just the id (a no-op edit).\n *\n * **Without `base`** (a record the editor is creating) the write is whole, as it always was.\n */\nexport function lensDocumentToRecordWrite<K extends AnyRecordKind>(\n kind: K,\n doc: CmsDocument,\n base?: CmsDocument,\n): RecordWriteOf<K> {\n const values = doc.values ?? {}\n const write: Record<string, unknown> = { id: doc.id }\n const columns = recordColumnNames(kind)\n if (!base) {\n for (const [column, field] of Object.entries(kind.fields)) {\n const value = columnWriteValue(field, values[column])\n if (value !== undefined) write[column] = value\n }\n const meta = metaBagProjection(values, columns)\n if (Object.keys(meta).length > 0) write.meta = meta\n return write as RecordWriteOf<K>\n }\n const before = base.values ?? {}\n for (const [column, field] of Object.entries(kind.fields)) {\n const value = values[column]\n if (semanticValueEqual(value, before[column])) continue\n // A cleared optional column is `null` on the wire (EC-577: clear), never a silent absence.\n write[column] = value === undefined ? (isRequiredText(field) ? '' : null) : columnWriteValue(field, value)\n }\n // The one per-key diff (core `metaPatchBetween`), shared with the asset editors (EC-634).\n const metaPatch = metaPatchBetween(metaBagProjection(before, columns), metaBagProjection(values, columns))\n if (Object.keys(metaPatch).length > 0) write.metaPatch = metaPatch\n return write as RecordWriteOf<K>\n}\n\n/** The lens field type a column edits as (unwrapping optional/default); `undefined` = not lens-editable. */\nfunction lensFieldType(field: unknown): FieldDef['type'] | undefined {\n switch (unwrapped(field).type) {\n case 'string':\n return 'text'\n case 'boolean':\n return 'boolean'\n case 'number':\n return 'number'\n case 'enum':\n return 'select'\n default:\n return undefined\n }\n}\n\nfunction humanize(name: string): string {\n const spaced = name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ')\n return spaced.charAt(0).toUpperCase() + spaced.slice(1)\n}\n\n/**\n * The DEFAULT lens of a kind: one field per column whose type the portable form edits\n * (text, boolean, number, select — derived from the column's zod type), labelled from its\n * name. A column of another shape (an object, a list) is not in the lens: the rail never\n * writes it, so the stored value is kept on every save (EC-577). A mount refines the lens\n * by name through `nav.rail(kind, { fields })` (EC-311 — the same override mechanism the\n * built-in rails use), or passes its own `schema`.\n */\nexport function recordLensDef(kind: AnyRecordKind, options: { label?: string; titleField?: string } = {}): CollectionDef {\n const fields: Record<string, unknown>[] = []\n for (const [name, field] of Object.entries(kind.fields)) {\n const type = lensFieldType(field)\n if (!type) continue\n const entry: Record<string, unknown> = { name, type, form: { label: humanize(name) } }\n if (type === 'select') {\n entry.options = Object.values(unwrapped(field).entries ?? {}).map((value) => ({ value: String(value), label: String(value) }))\n }\n fields.push(entry)\n }\n const titleField = options.titleField ?? fields.find((field) => field.type === 'text')?.name\n return collectionDefSchema.parse({\n id: kind.kind,\n // A record kind is not a CMS `kind`; 'document' just satisfies the lens schema.\n kind: 'document',\n ...(titleField ? { titleField } : {}),\n form: { label: options.label ?? humanize(kind.module) },\n fields,\n })\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { CollectionDef } from '@elytracms/core/cms-core';
2
+ import type { CmsDocument, CmsValidationIssue, CollectionDef } from '@elytracms/core/cms-core';
3
3
  import type { AnyRecordKind, RecordEntryOf, RecordWriteOf } from '@elytracms/core/persistence';
4
4
  import type { CollectionViewCapabilities, CollectionViewSource } from '../../lib/collection-view/collection-view-source.js';
5
5
  /** The default capability profile of a record rail: create, edit, remove, bulk delete — content, not administration. */
@@ -8,7 +8,7 @@ export declare const RECORD_RAIL_CAPABILITIES: CollectionViewCapabilities;
8
8
  export declare function recordListKey(kind: AnyRecordKind, projectId: string): readonly [string, string, string];
9
9
  /**
10
10
  * A kind's records in a project, read through the shared operations cache — the ONE query
11
- * a rail and any reader outside it (the rail foot's URL-map validation reads redirects)
11
+ * a rail and any reader outside it (the issues tray's URL-map validation reads redirects)
12
12
  * share. One key, one cache entry: a save's `cache.patch` updates both at once.
13
13
  */
14
14
  export declare function useRecordList<K extends AnyRecordKind>(kind: K, projectId: string): import("@tanstack/react-query").UseQueryResult<RecordEntryOf<K>[]>;
@@ -55,6 +55,14 @@ export declare function RecordRailProvider<K extends AnyRecordKind>({ kind, proj
55
55
  * applies the defaults itself for a write that leaves them out (EC-577).
56
56
  */
57
57
  export declare function recordDraftColumns(kind: AnyRecordKind): Record<string, unknown>;
58
+ /**
59
+ * The set findings of `doc` as the editor sees it NOW: the stored set with this record's
60
+ * columns replaced by the draft's, so fixing a duplicate `from` clears the finding as you
61
+ * type. An unsaved "New" draft joins the set as an extra row; it is only ever told about a
62
+ * DUPLICATE (a half-typed draft is not an "unreadable stored redirect" — its required fields
63
+ * report that, field by field). Pure; exported for the tests.
64
+ */
65
+ export declare function recordSetIssues(kind: AnyRecordKind, records: readonly Record<string, unknown>[], doc: CmsDocument): CmsValidationIssue[];
58
66
  /** How a rail addresses its list and one record's detail (the view's own route segment). */
59
67
  export interface RecordRailPaths {
60
68
  listPath: string;
@@ -1,6 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useMemo, useRef, useState } from 'react';
3
3
  import { createCollectionRegistry, parseLocaleConfig, validateDocument } from '@elytracms/core/cms-core';
4
+ import { redirectRecordKind } from '@elytracms/core/persistence';
5
+ import { routeValidationIssues } from '../../lib/routes/route-issues.js';
6
+ import { resolveDeploymentCollections, resolveDeploymentRoutes } from '../../lib/workspace/index.js';
4
7
  import { createUuidIdFactory } from '@elytracms/core/determinism';
5
8
  import { getStudioOperations } from '../../lib/operations/index.js';
6
9
  import { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query/index.js';
@@ -9,7 +12,7 @@ import { localizedLabel, translate } from '../../lib/i18n/index.js';
9
12
  import { normalizeOkResult, useRailViewSource } from '../rail-view-source.js';
10
13
  import { RailStoreStatus } from '../rail-store-status.js';
11
14
  import { CollectionViewSurface } from '../collection-view/collection-view-surface.js';
12
- import { lensDocumentToRecordWrite, recordToLensDocument } from './record-lens.js';
15
+ import { lensDocumentToRecordWrite, recordColumnNames, recordToLensDocument } from './record-lens.js';
13
16
  /**
14
17
  * EC-616 — the generic RECORD RAIL: a record kind (`defineRecordKind`) on the portable
15
18
  * list/edit surfaces (AD-19), read and written through the operations chokepoint
@@ -17,7 +20,7 @@ import { lensDocumentToRecordWrite, recordToLensDocument } from './record-lens.j
17
20
  * the engine's redirects rail is exactly this over `redirectRecordKind`.
18
21
  *
19
22
  * - **Store** ({@link RecordRailProvider}): the kind's records through ONE query key per
20
- * kind and project ({@link useRecordList} — the rail foot and the rail share it); the
23
+ * kind and project ({@link useRecordList} — the issues tray and the rail share it); the
21
24
  * "New" flow keeps an unsaved record as a LOCAL draft until the first explicit save, so
22
25
  * an abandoned "New" never writes a stub and never shows in the list.
23
26
  * - **Source** ({@link useRecordCollectionViewSource}): the lens projections
@@ -48,7 +51,7 @@ export function recordListKey(kind, projectId) {
48
51
  }
49
52
  /**
50
53
  * A kind's records in a project, read through the shared operations cache — the ONE query
51
- * a rail and any reader outside it (the rail foot's URL-map validation reads redirects)
54
+ * a rail and any reader outside it (the issues tray's URL-map validation reads redirects)
52
55
  * share. One key, one cache entry: a save's `cache.patch` updates both at once.
53
56
  */
54
57
  export function useRecordList(kind, projectId) {
@@ -172,6 +175,59 @@ function sortForRail(kind, records) {
172
175
  return left < right ? -1 : left > right ? 1 : 0;
173
176
  });
174
177
  }
178
+ /**
179
+ * EC-690 (critic point 5) — the findings a record has only as a member of its kind's SET,
180
+ * judged by the ONE producer the studio's issues tray reads, so the record's own Issues pane
181
+ * and the tray can never disagree. Before this, the losing row of a duplicate redirect pair
182
+ * said "No issues" while the rail foot named it (measured live, wave-0100): the rail
183
+ * validated one record without its siblings.
184
+ *
185
+ * Keyed by record kind; a kind with no set rule has no set findings. Redirects: core's one
186
+ * normalised redirect set and router (`routeValidationIssues` — duplicates after
187
+ * trailing-slash normalisation, loops, unreadable rows), over the project's routes.
188
+ */
189
+ const RECORD_SET_RULES = {
190
+ [redirectRecordKind.kind]: (records) => routeValidationIssues({ routes: resolveDeploymentRoutes(), collections: resolveDeploymentCollections() }, records),
191
+ };
192
+ /** The set rule's issues located AT one record (`[<domain>, <id>]`), keyed by record id. */
193
+ function issuesById(issues) {
194
+ const byId = new Map();
195
+ for (const issue of issues) {
196
+ const id = issue.path[1];
197
+ if (typeof id !== 'string')
198
+ continue;
199
+ byId.set(id, [...(byId.get(id) ?? []), issue]);
200
+ }
201
+ return byId;
202
+ }
203
+ /**
204
+ * The set findings of `doc` as the editor sees it NOW: the stored set with this record's
205
+ * columns replaced by the draft's, so fixing a duplicate `from` clears the finding as you
206
+ * type. An unsaved "New" draft joins the set as an extra row; it is only ever told about a
207
+ * DUPLICATE (a half-typed draft is not an "unreadable stored redirect" — its required fields
208
+ * report that, field by field). Pure; exported for the tests.
209
+ */
210
+ export function recordSetIssues(kind, records, doc) {
211
+ const rule = RECORD_SET_RULES[kind.kind];
212
+ if (!rule)
213
+ return [];
214
+ const columns = recordColumnNames(kind);
215
+ const index = records.findIndex((record) => record.id === doc.id);
216
+ const draftColumns = Object.fromEntries(Object.entries(doc.values ?? {}).filter(([name]) => columns.has(name)));
217
+ const own = { ...(index === -1 ? {} : records[index]), ...draftColumns, id: doc.id };
218
+ const set = index === -1 ? [...records, own] : records.map((record, i) => (i === index ? own : record));
219
+ const located = issuesById(rule(set)).get(doc.id) ?? [];
220
+ return index === -1 ? located.filter((issue) => issue.meta?.duplicateOf !== undefined) : located;
221
+ }
222
+ /** True when the draft's columns are the stored record's — the memoised stored verdict applies. */
223
+ function draftMatchesStored(kind, stored, doc) {
224
+ const values = doc.values ?? {};
225
+ for (const column of recordColumnNames(kind)) {
226
+ if (!Object.is(values[column], stored[column]))
227
+ return false;
228
+ }
229
+ return true;
230
+ }
175
231
  /**
176
232
  * The {@link CollectionViewSource} of a record rail (must be mounted under its
177
233
  * {@link RecordRailProvider}). `schema` is the rail's lens — the kind's default lens or a
@@ -196,7 +252,21 @@ export function useRecordCollectionViewSource(kind, projectId, schema, paths, ca
196
252
  return title;
197
253
  return translate('records.untitled', { label: localizedLabel(singular) ?? kind.kind });
198
254
  }, [store, titleField, singular, kind]);
199
- const validate = useCallback((doc) => validateDocument(registry, doc, { localeConfig: RAIL_LOCALE_CONFIG }), [registry]);
255
+ // EC-690: the record's own fields AND its set-level findings (a duplicate redirect, a loop) —
256
+ // the stored set's verdict is computed once per list; a draft that changed a column is
257
+ // re-judged against its siblings.
258
+ const records = store.records;
259
+ const storedSetIssues = useMemo(() => (RECORD_SET_RULES[kind.kind] ? issuesById(RECORD_SET_RULES[kind.kind](records)) : undefined), [kind, records]);
260
+ const validate = useCallback((doc) => {
261
+ const own = validateDocument(registry, doc, { localeConfig: RAIL_LOCALE_CONFIG });
262
+ if (!storedSetIssues)
263
+ return own;
264
+ const stored = records.find((record) => record.id === doc.id);
265
+ const set = stored && draftMatchesStored(kind, stored, doc)
266
+ ? (storedSetIssues.get(doc.id) ?? [])
267
+ : recordSetIssues(kind, records, doc);
268
+ return set.length > 0 ? [...own, ...set] : own;
269
+ }, [registry, storedSetIssues, records, kind]);
200
270
  const create = useCallback(() => ({ collection: kind.kind, id: store.createDraft() }), [kind, store]);
201
271
  // EC-616 (multi-session): an edit of a STORED record writes only what the editor changed
202
272
  // against the record they opened (`base`, from the edit shell) — the EC-577 rule keeps every
@@ -1 +1 @@
1
- {"version":3,"file":"record-rail.js","sourceRoot":"","sources":["../../../src/components/records/record-rail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { createCollectionRegistry, parseLocaleConfig, validateDocument } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport type { AnyRecordKind, RecordEntryOf, RecordWriteOf } from '@elytracms/core/persistence'\nimport { createUuidIdFactory } from '@elytracms/core/determinism'\nimport { getStudioOperations } from '../../lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query'\nimport { createRequiredContext } from '../../lib/required-context'\nimport { localizedLabel, translate } from '../../lib/i18n'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n} from '../../lib/collection-view/collection-view-source'\nimport { normalizeOkResult, useRailViewSource } from '../rail-view-source'\nimport { RailStoreStatus } from '../rail-store-status'\nimport { CollectionViewSurface } from '../collection-view/collection-view-surface'\nimport { lensDocumentToRecordWrite, recordToLensDocument } from './record-lens'\n\n/**\n * EC-616 — the generic RECORD RAIL: a record kind (`defineRecordKind`) on the portable\n * list/edit surfaces (AD-19), read and written through the operations chokepoint\n * (`operations.records(kind)`) and the one query cache (AD-17). A rail is `nav.rail(kind)`;\n * the engine's redirects rail is exactly this over `redirectRecordKind`.\n *\n * - **Store** ({@link RecordRailProvider}): the kind's records through ONE query key per\n * kind and project ({@link useRecordList} — the rail foot and the rail share it); the\n * \"New\" flow keeps an unsaved record as a LOCAL draft until the first explicit save, so\n * an abandoned \"New\" never writes a stub and never shows in the list.\n * - **Source** ({@link useRecordCollectionViewSource}): the lens projections\n * (`record-lens.ts`), the lens's own validation, the rail's capabilities.\n * - **Surface** ({@link RecordRailModule}): the list, or one record's detail.\n */\n\n/** A record rail is not localized — one implicit locale keeps the lens honest. */\nconst RAIL_LOCALE: Locale = 'en'\nconst RAIL_LOCALE_CONFIG: LocaleConfig = parseLocaleConfig({ default: RAIL_LOCALE, locales: [RAIL_LOCALE] })\n\n/** The default capability profile of a record rail: create, edit, remove, bulk delete — content, not administration. */\nexport const RECORD_RAIL_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: true,\n duplicate: false,\n remove: true,\n bulk: true,\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false,\n}\n\n/** The one query key of a kind's records in a project. */\nexport function recordListKey(kind: AnyRecordKind, projectId: string): readonly [string, string, string] {\n return ['records', kind.kind, projectId]\n}\n\n/**\n * A kind's records in a project, read through the shared operations cache — the ONE query\n * a rail and any reader outside it (the rail foot's URL-map validation reads redirects)\n * share. One key, one cache entry: a save's `cache.patch` updates both at once.\n */\nexport function useRecordList<K extends AnyRecordKind>(kind: K, projectId: string) {\n return useOperationQuery<RecordEntryOf<K>[]>({\n queryKey: recordListKey(kind, projectId),\n loader: async (operations) => {\n const result = await operations.records(kind).list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n },\n })\n}\n\nexport interface RecordRailStore<K extends AnyRecordKind = AnyRecordKind> {\n kind: K\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n /** The persisted records, in the kind's order. Unsaved drafts are excluded. */\n records: readonly RecordEntryOf<K>[]\n /** One record: a local unsaved draft if present, else a persisted record. */\n get(id: string): RecordEntryOf<K> | undefined\n /** Mint a blank in-memory draft and return its id (the \"New\" navigation target). */\n createDraft(): string\n /** Persist a create/modify; drops the matching draft on success. */\n save(input: RecordWriteOf<K>): Promise<{ ok: true; record: RecordEntryOf<K> } | { ok: false; message: string }>\n /** Delete a persisted record, or silently discard an unsaved draft. */\n remove(id: string): Promise<{ ok: true } | { ok: false; message: string }>\n /** Re-list from the backend. */\n refresh(): Promise<void>\n}\n\nconst { Context: RecordRailContext, useRequired: useRecordRailContext } =\n createRequiredContext<RecordRailStore>('RecordRail', { article: 'a' })\n\n/** The record rail store of the nearest {@link RecordRailProvider}. */\nexport function useRecordRail<K extends AnyRecordKind>(): RecordRailStore<K> {\n return useRecordRailContext() as unknown as RecordRailStore<K>\n}\n\nlet mintDraftUuid: (() => string) | undefined\n\nexport function RecordRailProvider<K extends AnyRecordKind>({\n kind,\n projectId,\n children,\n}: {\n kind: K\n projectId: string\n children: ReactNode\n}) {\n const query = useRecordList(kind, projectId)\n const cache = useOperationCache()\n const persisted = query.data ?? EMPTY\n const { status, errorMessage } = listStoreState(query)\n // In-memory unsaved \"New\" drafts, keyed by id. Not listed; only get()-able.\n const [drafts, setDrafts] = useState<Record<string, RecordEntryOf<K>>>({})\n const draftsRef = useRef(drafts)\n draftsRef.current = drafts\n\n const refetch = query.refetch\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const get = useCallback(\n (id: string) =>\n drafts[id] ?? (persisted as RecordEntryOf<K>[]).find((record) => (record as { id: string }).id === id),\n [drafts, persisted],\n )\n\n const createDraft = useCallback(() => {\n // The draft id becomes the record's real id on its first save: the production id\n // factory (EC-578 — never a clock or a per-session counter, which collide).\n mintDraftUuid ??= createUuidIdFactory()\n const id = `${kind.idPrefix}_${mintDraftUuid()}`\n const now = new Date().toISOString()\n const draft = { ...recordDraftColumns(kind), id, projectId, createdAt: now, updatedAt: now } as unknown as RecordEntryOf<K>\n setDrafts((prev) => ({ ...prev, [id]: draft }))\n return id\n }, [kind, projectId])\n\n const save = useCallback<RecordRailStore<K>['save']>(\n async (input) => {\n const { operations } = await getStudioOperations()\n const result = await operations.records(kind).save({ projectId, ...input } as never)\n if (!result.ok) return { ok: false, message: result.error.message }\n const saved = result.value as RecordEntryOf<K>\n const savedId = (saved as { id: string }).id\n // The server returns the kind's order on the next read; place the save now and\n // let the patched list keep its order key (re-read on the next fetch).\n cache.patch<RecordEntryOf<K>[]>(recordListKey(kind, projectId), (prev) => {\n const rest = (prev ?? []).filter((record) => (record as { id: string }).id !== savedId)\n return sortForRail(kind, [...rest, saved])\n })\n setDrafts((prev) => {\n if (!(savedId in prev)) return prev\n const next = { ...prev }\n delete next[savedId]\n return next\n })\n return { ok: true, record: saved }\n },\n [kind, projectId, cache],\n )\n\n const remove = useCallback<RecordRailStore<K>['remove']>(\n async (id) => {\n // An unsaved draft was never persisted — discard it locally, no backend call.\n if (draftsRef.current[id]) {\n setDrafts((prev) => {\n const next = { ...prev }\n delete next[id]\n return next\n })\n return { ok: true }\n }\n const { operations } = await getStudioOperations()\n const result = await operations.records(kind).remove({ projectId, id })\n if (!result.ok) return { ok: false, message: result.error.message }\n cache.patch<RecordEntryOf<K>[]>(recordListKey(kind, projectId), (prev) =>\n (prev ?? []).filter((record) => record.id !== id),\n )\n return { ok: true }\n },\n [kind, projectId, cache],\n )\n\n const store = useMemo<RecordRailStore<K>>(\n () => ({\n kind,\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n records: persisted as RecordEntryOf<K>[],\n get,\n createDraft,\n save,\n remove,\n refresh,\n }),\n [kind, projectId, status, errorMessage, persisted, get, createDraft, save, remove, refresh],\n )\n\n return <RecordRailContext.Provider value={store as unknown as RecordRailStore}>{children}</RecordRailContext.Provider>\n}\n\nconst EMPTY: readonly never[] = []\n\n/**\n * The columns a blank \"New\" draft shows: each column's CREATE default (a new redirect\n * reads enabled + permanent — the reason you create one is for it to work), and `''` for\n * a required text column. The save then sends what the editor saw; the adapter still\n * applies the defaults itself for a write that leaves them out (EC-577).\n */\nexport function recordDraftColumns(kind: AnyRecordKind): Record<string, unknown> {\n const columns: Record<string, unknown> = {}\n for (const [name, field] of Object.entries(kind.fields)) {\n const parsed = (field as { safeParse(value: unknown): { success: boolean; data?: unknown } }).safeParse(undefined)\n if (parsed.success && parsed.data !== undefined) columns[name] = parsed.data\n else if ((field as { _zod: { def: { type: string } } })._zod.def.type === 'string') columns[name] = ''\n }\n return columns\n}\n\n/** The kind's own list order for a locally patched list (`orderBy`, else creation time). */\nfunction sortForRail<R>(kind: AnyRecordKind, records: R[]): R[] {\n const column = kind.orderBy ?? 'createdAt'\n return [...records].sort((a, b) => {\n const left = String((a as Record<string, unknown>)[column] ?? '')\n const right = String((b as Record<string, unknown>)[column] ?? '')\n return left < right ? -1 : left > right ? 1 : 0\n })\n}\n\n/** How a rail addresses its list and one record's detail (the view's own route segment). */\nexport interface RecordRailPaths {\n listPath: string\n detailPath(id: string): string\n}\n\n/**\n * The {@link CollectionViewSource} of a record rail (must be mounted under its\n * {@link RecordRailProvider}). `schema` is the rail's lens — the kind's default lens or a\n * mount-customized one (EC-311 overrides by name; additive fields persist in `meta`).\n */\nexport function useRecordCollectionViewSource<K extends AnyRecordKind>(\n kind: K,\n projectId: string,\n schema: CollectionDef,\n paths: RecordRailPaths,\n capabilities: CollectionViewCapabilities = RECORD_RAIL_CAPABILITIES,\n): CollectionViewSource {\n const store = useRecordRail<K>()\n // Run the (possibly customized) lens's validation — required/pattern and custom\n // `validate` fns on an overridden field must actually fire in the editor (EC-311).\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n const singular = schema.form?.labelSingular\n const titleField = typeof schema.titleField === 'string' ? schema.titleField : undefined\n\n const documents = useCallback(\n () => store.records.map((record) => recordToLensDocument(kind, record)),\n [kind, store.records],\n )\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.get(ref.id)\n return record ? recordToLensDocument(kind, record) : undefined\n },\n [kind, store],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = store.get(ref.id) as Record<string, unknown> | undefined\n const title = titleField ? record?.[titleField] : undefined\n if (typeof title === 'string' && title.length > 0) return title\n return translate('records.untitled', { label: localizedLabel(singular) ?? kind.kind })\n },\n [store, titleField, singular, kind],\n )\n const validate = useCallback(\n (doc: CmsDocument) => validateDocument(registry, doc, { localeConfig: RAIL_LOCALE_CONFIG }),\n [registry],\n )\n const create = useCallback((): DocumentRef => ({ collection: kind.kind, id: store.createDraft() }), [kind, store])\n // EC-616 (multi-session): an edit of a STORED record writes only what the editor changed\n // against the record they opened (`base`, from the edit shell) — the EC-577 rule keeps every\n // other column, so another writer's change made meanwhile survives this save. A record the\n // editor is creating (an unsaved draft) is written whole.\n const save = useCallback(\n (doc: CmsDocument, base?: CmsDocument) => {\n const stored = store.records.some((record) => (record as { id: string }).id === doc.id)\n return store.save(lensDocumentToRecordWrite(kind, doc, stored ? base : undefined))\n },\n [kind, store],\n )\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities,\n localeConfig: RAIL_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n detailPath: paths.detailPath,\n listPath: paths.listPath,\n validate,\n create,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n\nexport interface RecordRailModuleProps<K extends AnyRecordKind> {\n kind: K\n projectId: string\n /** `null` → the list; an id → that record's detail. */\n recordId: string | null\n schema: CollectionDef\n paths: RecordRailPaths\n capabilities?: CollectionViewCapabilities\n /** The rail's label, for its loading / failure states. */\n label: string\n /** Optional copy for the loading / failure states (default: the generic catalog keys). */\n messages?: { loading?: () => string; loadFailed?: () => string }\n}\n\n/** The surface of a record rail: the portable list, or one record's detail. */\nexport function RecordRailModule<K extends AnyRecordKind>(props: RecordRailModuleProps<K>) {\n return (\n <RecordRailProvider kind={props.kind} projectId={props.projectId}>\n <RecordRailSurface {...props} />\n </RecordRailProvider>\n )\n}\n\nfunction RecordRailSurface<K extends AnyRecordKind>({\n kind,\n projectId,\n recordId,\n schema,\n paths,\n capabilities,\n label,\n messages,\n}: RecordRailModuleProps<K>) {\n const store = useRecordRail<K>()\n const source = useRecordCollectionViewSource(kind, projectId, schema, paths, capabilities)\n\n if (store.status !== 'ready') {\n return (\n <RailStoreStatus\n store={store}\n errorLabel={messages?.loadFailed?.() ?? translate('records.loadFailed', { label })}\n loadingLabel={messages?.loading?.() ?? translate('records.loading', { label })}\n errorTestId={`${kind.kind}-rail-error`}\n />\n )\n }\n // One record's detail opens in the portable edit shell (AD-19); the list otherwise.\n return <CollectionViewSurface source={source} projectId={projectId} documentId={recordId} />\n}\n"]}
1
+ {"version":3,"file":"record-rail.js","sourceRoot":"","sources":["../../../src/components/records/record-rail.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useCallback, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { createCollectionRegistry, parseLocaleConfig, validateDocument } from '@elytracms/core/cms-core'\nimport type { CmsDocument, CmsValidationIssue, CollectionDef, DocumentRef, Locale, LocaleConfig } from '@elytracms/core/cms-core'\nimport { redirectRecordKind } from '@elytracms/core/persistence'\nimport type { AnyRecordKind, RecordEntryOf, RecordWriteOf, RedirectEntry } from '@elytracms/core/persistence'\nimport { routeValidationIssues } from '../../lib/routes/route-issues'\nimport { resolveDeploymentCollections, resolveDeploymentRoutes } from '../../lib/workspace'\nimport { createUuidIdFactory } from '@elytracms/core/determinism'\nimport { getStudioOperations } from '../../lib/operations'\nimport { listStoreState, useOperationCache, useOperationQuery } from '../../lib/query'\nimport { createRequiredContext } from '../../lib/required-context'\nimport { localizedLabel, translate } from '../../lib/i18n'\nimport type {\n CollectionViewCapabilities,\n CollectionViewSource,\n} from '../../lib/collection-view/collection-view-source'\nimport { normalizeOkResult, useRailViewSource } from '../rail-view-source'\nimport { RailStoreStatus } from '../rail-store-status'\nimport { CollectionViewSurface } from '../collection-view/collection-view-surface'\nimport { lensDocumentToRecordWrite, recordColumnNames, recordToLensDocument } from './record-lens'\n\n/**\n * EC-616 — the generic RECORD RAIL: a record kind (`defineRecordKind`) on the portable\n * list/edit surfaces (AD-19), read and written through the operations chokepoint\n * (`operations.records(kind)`) and the one query cache (AD-17). A rail is `nav.rail(kind)`;\n * the engine's redirects rail is exactly this over `redirectRecordKind`.\n *\n * - **Store** ({@link RecordRailProvider}): the kind's records through ONE query key per\n * kind and project ({@link useRecordList} — the issues tray and the rail share it); the\n * \"New\" flow keeps an unsaved record as a LOCAL draft until the first explicit save, so\n * an abandoned \"New\" never writes a stub and never shows in the list.\n * - **Source** ({@link useRecordCollectionViewSource}): the lens projections\n * (`record-lens.ts`), the lens's own validation, the rail's capabilities.\n * - **Surface** ({@link RecordRailModule}): the list, or one record's detail.\n */\n\n/** A record rail is not localized — one implicit locale keeps the lens honest. */\nconst RAIL_LOCALE: Locale = 'en'\nconst RAIL_LOCALE_CONFIG: LocaleConfig = parseLocaleConfig({ default: RAIL_LOCALE, locales: [RAIL_LOCALE] })\n\n/** The default capability profile of a record rail: create, edit, remove, bulk delete — content, not administration. */\nexport const RECORD_RAIL_CAPABILITIES: CollectionViewCapabilities = {\n publish: false,\n versions: false,\n locales: false,\n inlineEdit: false,\n create: true,\n duplicate: false,\n remove: true,\n bulk: true,\n liveUrl: false,\n hierarchy: false,\n importExport: false,\n adminOnly: false,\n}\n\n/** The one query key of a kind's records in a project. */\nexport function recordListKey(kind: AnyRecordKind, projectId: string): readonly [string, string, string] {\n return ['records', kind.kind, projectId]\n}\n\n/**\n * A kind's records in a project, read through the shared operations cache — the ONE query\n * a rail and any reader outside it (the issues tray's URL-map validation reads redirects)\n * share. One key, one cache entry: a save's `cache.patch` updates both at once.\n */\nexport function useRecordList<K extends AnyRecordKind>(kind: K, projectId: string) {\n return useOperationQuery<RecordEntryOf<K>[]>({\n queryKey: recordListKey(kind, projectId),\n loader: async (operations) => {\n const result = await operations.records(kind).list({ projectId })\n if (!result.ok) throw new Error(result.error.message)\n return [...result.value]\n },\n })\n}\n\nexport interface RecordRailStore<K extends AnyRecordKind = AnyRecordKind> {\n kind: K\n projectId: string\n status: 'loading' | 'ready' | 'error'\n errorMessage?: string\n /** The persisted records, in the kind's order. Unsaved drafts are excluded. */\n records: readonly RecordEntryOf<K>[]\n /** One record: a local unsaved draft if present, else a persisted record. */\n get(id: string): RecordEntryOf<K> | undefined\n /** Mint a blank in-memory draft and return its id (the \"New\" navigation target). */\n createDraft(): string\n /** Persist a create/modify; drops the matching draft on success. */\n save(input: RecordWriteOf<K>): Promise<{ ok: true; record: RecordEntryOf<K> } | { ok: false; message: string }>\n /** Delete a persisted record, or silently discard an unsaved draft. */\n remove(id: string): Promise<{ ok: true } | { ok: false; message: string }>\n /** Re-list from the backend. */\n refresh(): Promise<void>\n}\n\nconst { Context: RecordRailContext, useRequired: useRecordRailContext } =\n createRequiredContext<RecordRailStore>('RecordRail', { article: 'a' })\n\n/** The record rail store of the nearest {@link RecordRailProvider}. */\nexport function useRecordRail<K extends AnyRecordKind>(): RecordRailStore<K> {\n return useRecordRailContext() as unknown as RecordRailStore<K>\n}\n\nlet mintDraftUuid: (() => string) | undefined\n\nexport function RecordRailProvider<K extends AnyRecordKind>({\n kind,\n projectId,\n children,\n}: {\n kind: K\n projectId: string\n children: ReactNode\n}) {\n const query = useRecordList(kind, projectId)\n const cache = useOperationCache()\n const persisted = query.data ?? EMPTY\n const { status, errorMessage } = listStoreState(query)\n // In-memory unsaved \"New\" drafts, keyed by id. Not listed; only get()-able.\n const [drafts, setDrafts] = useState<Record<string, RecordEntryOf<K>>>({})\n const draftsRef = useRef(drafts)\n draftsRef.current = drafts\n\n const refetch = query.refetch\n const refresh = useCallback(async () => {\n await refetch()\n }, [refetch])\n\n const get = useCallback(\n (id: string) =>\n drafts[id] ?? (persisted as RecordEntryOf<K>[]).find((record) => (record as { id: string }).id === id),\n [drafts, persisted],\n )\n\n const createDraft = useCallback(() => {\n // The draft id becomes the record's real id on its first save: the production id\n // factory (EC-578 — never a clock or a per-session counter, which collide).\n mintDraftUuid ??= createUuidIdFactory()\n const id = `${kind.idPrefix}_${mintDraftUuid()}`\n const now = new Date().toISOString()\n const draft = { ...recordDraftColumns(kind), id, projectId, createdAt: now, updatedAt: now } as unknown as RecordEntryOf<K>\n setDrafts((prev) => ({ ...prev, [id]: draft }))\n return id\n }, [kind, projectId])\n\n const save = useCallback<RecordRailStore<K>['save']>(\n async (input) => {\n const { operations } = await getStudioOperations()\n const result = await operations.records(kind).save({ projectId, ...input } as never)\n if (!result.ok) return { ok: false, message: result.error.message }\n const saved = result.value as RecordEntryOf<K>\n const savedId = (saved as { id: string }).id\n // The server returns the kind's order on the next read; place the save now and\n // let the patched list keep its order key (re-read on the next fetch).\n cache.patch<RecordEntryOf<K>[]>(recordListKey(kind, projectId), (prev) => {\n const rest = (prev ?? []).filter((record) => (record as { id: string }).id !== savedId)\n return sortForRail(kind, [...rest, saved])\n })\n setDrafts((prev) => {\n if (!(savedId in prev)) return prev\n const next = { ...prev }\n delete next[savedId]\n return next\n })\n return { ok: true, record: saved }\n },\n [kind, projectId, cache],\n )\n\n const remove = useCallback<RecordRailStore<K>['remove']>(\n async (id) => {\n // An unsaved draft was never persisted — discard it locally, no backend call.\n if (draftsRef.current[id]) {\n setDrafts((prev) => {\n const next = { ...prev }\n delete next[id]\n return next\n })\n return { ok: true }\n }\n const { operations } = await getStudioOperations()\n const result = await operations.records(kind).remove({ projectId, id })\n if (!result.ok) return { ok: false, message: result.error.message }\n cache.patch<RecordEntryOf<K>[]>(recordListKey(kind, projectId), (prev) =>\n (prev ?? []).filter((record) => record.id !== id),\n )\n return { ok: true }\n },\n [kind, projectId, cache],\n )\n\n const store = useMemo<RecordRailStore<K>>(\n () => ({\n kind,\n projectId,\n status,\n ...(errorMessage !== undefined ? { errorMessage } : {}),\n records: persisted as RecordEntryOf<K>[],\n get,\n createDraft,\n save,\n remove,\n refresh,\n }),\n [kind, projectId, status, errorMessage, persisted, get, createDraft, save, remove, refresh],\n )\n\n return <RecordRailContext.Provider value={store as unknown as RecordRailStore}>{children}</RecordRailContext.Provider>\n}\n\nconst EMPTY: readonly never[] = []\n\n/**\n * The columns a blank \"New\" draft shows: each column's CREATE default (a new redirect\n * reads enabled + permanent — the reason you create one is for it to work), and `''` for\n * a required text column. The save then sends what the editor saw; the adapter still\n * applies the defaults itself for a write that leaves them out (EC-577).\n */\nexport function recordDraftColumns(kind: AnyRecordKind): Record<string, unknown> {\n const columns: Record<string, unknown> = {}\n for (const [name, field] of Object.entries(kind.fields)) {\n const parsed = (field as { safeParse(value: unknown): { success: boolean; data?: unknown } }).safeParse(undefined)\n if (parsed.success && parsed.data !== undefined) columns[name] = parsed.data\n else if ((field as { _zod: { def: { type: string } } })._zod.def.type === 'string') columns[name] = ''\n }\n return columns\n}\n\n/** The kind's own list order for a locally patched list (`orderBy`, else creation time). */\nfunction sortForRail<R>(kind: AnyRecordKind, records: R[]): R[] {\n const column = kind.orderBy ?? 'createdAt'\n return [...records].sort((a, b) => {\n const left = String((a as Record<string, unknown>)[column] ?? '')\n const right = String((b as Record<string, unknown>)[column] ?? '')\n return left < right ? -1 : left > right ? 1 : 0\n })\n}\n\n/**\n * EC-690 (critic point 5) — the findings a record has only as a member of its kind's SET,\n * judged by the ONE producer the studio's issues tray reads, so the record's own Issues pane\n * and the tray can never disagree. Before this, the losing row of a duplicate redirect pair\n * said \"No issues\" while the rail foot named it (measured live, wave-0100): the rail\n * validated one record without its siblings.\n *\n * Keyed by record kind; a kind with no set rule has no set findings. Redirects: core's one\n * normalised redirect set and router (`routeValidationIssues` — duplicates after\n * trailing-slash normalisation, loops, unreadable rows), over the project's routes.\n */\nconst RECORD_SET_RULES: Readonly<Record<string, (records: readonly Record<string, unknown>[]) => readonly CmsValidationIssue[]>> = {\n [redirectRecordKind.kind]: (records) =>\n routeValidationIssues(\n { routes: resolveDeploymentRoutes(), collections: resolveDeploymentCollections() },\n records as unknown as readonly RedirectEntry[],\n ),\n}\n\n/** The set rule's issues located AT one record (`[<domain>, <id>]`), keyed by record id. */\nfunction issuesById(issues: readonly CmsValidationIssue[]): Map<string, CmsValidationIssue[]> {\n const byId = new Map<string, CmsValidationIssue[]>()\n for (const issue of issues) {\n const id = issue.path[1]\n if (typeof id !== 'string') continue\n byId.set(id, [...(byId.get(id) ?? []), issue])\n }\n return byId\n}\n\n/**\n * The set findings of `doc` as the editor sees it NOW: the stored set with this record's\n * columns replaced by the draft's, so fixing a duplicate `from` clears the finding as you\n * type. An unsaved \"New\" draft joins the set as an extra row; it is only ever told about a\n * DUPLICATE (a half-typed draft is not an \"unreadable stored redirect\" — its required fields\n * report that, field by field). Pure; exported for the tests.\n */\nexport function recordSetIssues(\n kind: AnyRecordKind,\n records: readonly Record<string, unknown>[],\n doc: CmsDocument,\n): CmsValidationIssue[] {\n const rule = RECORD_SET_RULES[kind.kind]\n if (!rule) return []\n const columns = recordColumnNames(kind)\n const index = records.findIndex((record) => record.id === doc.id)\n const draftColumns = Object.fromEntries(Object.entries(doc.values ?? {}).filter(([name]) => columns.has(name)))\n const own = { ...(index === -1 ? {} : records[index]), ...draftColumns, id: doc.id }\n const set = index === -1 ? [...records, own] : records.map((record, i) => (i === index ? own : record))\n const located = issuesById(rule(set)).get(doc.id) ?? []\n return index === -1 ? located.filter((issue) => issue.meta?.duplicateOf !== undefined) : located\n}\n\n/** True when the draft's columns are the stored record's — the memoised stored verdict applies. */\nfunction draftMatchesStored(kind: AnyRecordKind, stored: Record<string, unknown>, doc: CmsDocument): boolean {\n const values = doc.values ?? {}\n for (const column of recordColumnNames(kind)) {\n if (!Object.is(values[column], stored[column])) return false\n }\n return true\n}\n\n/** How a rail addresses its list and one record's detail (the view's own route segment). */\nexport interface RecordRailPaths {\n listPath: string\n detailPath(id: string): string\n}\n\n/**\n * The {@link CollectionViewSource} of a record rail (must be mounted under its\n * {@link RecordRailProvider}). `schema` is the rail's lens — the kind's default lens or a\n * mount-customized one (EC-311 overrides by name; additive fields persist in `meta`).\n */\nexport function useRecordCollectionViewSource<K extends AnyRecordKind>(\n kind: K,\n projectId: string,\n schema: CollectionDef,\n paths: RecordRailPaths,\n capabilities: CollectionViewCapabilities = RECORD_RAIL_CAPABILITIES,\n): CollectionViewSource {\n const store = useRecordRail<K>()\n // Run the (possibly customized) lens's validation — required/pattern and custom\n // `validate` fns on an overridden field must actually fire in the editor (EC-311).\n const registry = useMemo(() => createCollectionRegistry([schema]), [schema])\n const singular = schema.form?.labelSingular\n const titleField = typeof schema.titleField === 'string' ? schema.titleField : undefined\n\n const documents = useCallback(\n () => store.records.map((record) => recordToLensDocument(kind, record)),\n [kind, store.records],\n )\n const get = useCallback(\n (ref: DocumentRef): CmsDocument | undefined => {\n const record = store.get(ref.id)\n return record ? recordToLensDocument(kind, record) : undefined\n },\n [kind, store],\n )\n const titleOf = useCallback(\n (ref: DocumentRef): string => {\n const record = store.get(ref.id) as Record<string, unknown> | undefined\n const title = titleField ? record?.[titleField] : undefined\n if (typeof title === 'string' && title.length > 0) return title\n return translate('records.untitled', { label: localizedLabel(singular) ?? kind.kind })\n },\n [store, titleField, singular, kind],\n )\n // EC-690: the record's own fields AND its set-level findings (a duplicate redirect, a loop) —\n // the stored set's verdict is computed once per list; a draft that changed a column is\n // re-judged against its siblings.\n const records = store.records as unknown as readonly Record<string, unknown>[]\n const storedSetIssues = useMemo(\n () => (RECORD_SET_RULES[kind.kind] ? issuesById(RECORD_SET_RULES[kind.kind]!(records)) : undefined),\n [kind, records],\n )\n const validate = useCallback(\n (doc: CmsDocument) => {\n const own = validateDocument(registry, doc, { localeConfig: RAIL_LOCALE_CONFIG })\n if (!storedSetIssues) return own\n const stored = records.find((record) => record.id === doc.id)\n const set =\n stored && draftMatchesStored(kind, stored, doc)\n ? (storedSetIssues.get(doc.id) ?? [])\n : recordSetIssues(kind, records, doc)\n return set.length > 0 ? [...own, ...set] : own\n },\n [registry, storedSetIssues, records, kind],\n )\n const create = useCallback((): DocumentRef => ({ collection: kind.kind, id: store.createDraft() }), [kind, store])\n // EC-616 (multi-session): an edit of a STORED record writes only what the editor changed\n // against the record they opened (`base`, from the edit shell) — the EC-577 rule keeps every\n // other column, so another writer's change made meanwhile survives this save. A record the\n // editor is creating (an unsaved draft) is written whole.\n const save = useCallback(\n (doc: CmsDocument, base?: CmsDocument) => {\n const stored = store.records.some((record) => (record as { id: string }).id === doc.id)\n return store.save(lensDocumentToRecordWrite(kind, doc, stored ? base : undefined))\n },\n [kind, store],\n )\n const remove = useCallback((ref: DocumentRef) => store.remove(ref.id), [store])\n\n return useRailViewSource({\n projectId,\n schema,\n capabilities,\n localeConfig: RAIL_LOCALE_CONFIG,\n documents,\n get,\n titleOf,\n detailPath: paths.detailPath,\n listPath: paths.listPath,\n validate,\n create,\n save,\n remove,\n normalizeSaveResult: normalizeOkResult,\n normalizeRemoveResult: normalizeOkResult,\n })\n}\n\nexport interface RecordRailModuleProps<K extends AnyRecordKind> {\n kind: K\n projectId: string\n /** `null` → the list; an id → that record's detail. */\n recordId: string | null\n schema: CollectionDef\n paths: RecordRailPaths\n capabilities?: CollectionViewCapabilities\n /** The rail's label, for its loading / failure states. */\n label: string\n /** Optional copy for the loading / failure states (default: the generic catalog keys). */\n messages?: { loading?: () => string; loadFailed?: () => string }\n}\n\n/** The surface of a record rail: the portable list, or one record's detail. */\nexport function RecordRailModule<K extends AnyRecordKind>(props: RecordRailModuleProps<K>) {\n return (\n <RecordRailProvider kind={props.kind} projectId={props.projectId}>\n <RecordRailSurface {...props} />\n </RecordRailProvider>\n )\n}\n\nfunction RecordRailSurface<K extends AnyRecordKind>({\n kind,\n projectId,\n recordId,\n schema,\n paths,\n capabilities,\n label,\n messages,\n}: RecordRailModuleProps<K>) {\n const store = useRecordRail<K>()\n const source = useRecordCollectionViewSource(kind, projectId, schema, paths, capabilities)\n\n if (store.status !== 'ready') {\n return (\n <RailStoreStatus\n store={store}\n errorLabel={messages?.loadFailed?.() ?? translate('records.loadFailed', { label })}\n loadingLabel={messages?.loading?.() ?? translate('records.loading', { label })}\n errorTestId={`${kind.kind}-rail-error`}\n />\n )\n }\n // One record's detail opens in the portable edit shell (AD-19); the list otherwise.\n return <CollectionViewSurface source={source} projectId={projectId} documentId={recordId} />\n}\n"]}
@@ -4,6 +4,11 @@ export interface SectionNavProps {
4
4
  /** User-toggled collapse (EC-214) — icons only when collapsed, persisted by the shell. */
5
5
  collapsed: boolean;
6
6
  onToggleCollapsed: () => void;
7
+ /**
8
+ * EC-690: whether the collapse toggle is offered. `false` on a narrow viewport, where the
9
+ * shell keeps the rail icons-only whatever the stored preference (the content needs the room).
10
+ */
11
+ toggleable?: boolean;
7
12
  }
8
13
  /**
9
14
  * Vertical section rail (brief §8.1), driven by the configurable sidebar (EC-237):
@@ -12,7 +17,14 @@ export interface SectionNavProps {
12
17
  * EC-221); the active item is matched by the VIEW ID in the URL, so two views
13
18
  * backed by the same surface (Content vs. a focused Categories) highlight
14
19
  * independently. The preference-persisted collapse and the strong active marker
15
- * (EC-214) are preserved. Customization issues (duplicate view id, invalid view,
16
- * non-component renderers) render at the rail foot — visible, never silent.
20
+ * (EC-214) are preserved.
21
+ *
22
+ * EC-690: the rail renders NO findings any more — they live in the shell header's issues
23
+ * badge and tray (owner ruling (a), 2026-10-28: one badge, in the header, none at the rail
24
+ * foot). Measured before (wave-0100): two duplicate-redirect cards took 478 px of the foot,
25
+ * and because the rail had no scroll container flex-shrink squashed every link from 36 to
26
+ * 20 px and pushed the collapse toggle off-screen, at 1280×800 as at 390×844. Now the views
27
+ * scroll in their own region (`min-h-0` + `overflow-y-auto`, links `shrink-0`), and the
28
+ * collapse toggle is pinned below it.
17
29
  */
18
- export declare function SectionNav({ project, collapsed, onToggleCollapsed }: SectionNavProps): import("react").JSX.Element;
30
+ export declare function SectionNav({ project, collapsed, onToggleCollapsed, toggleable }: SectionNavProps): import("react").JSX.Element;