canopycms 0.1.0-int.115 → 0.1.0

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 (145) hide show
  1. package/CHANGELOG.md +267 -231
  2. package/README.md +0 -1
  3. package/dist/ai/references.js +3 -1
  4. package/dist/api/assets.js +2 -2
  5. package/dist/api/branch-merge.d.ts +1 -1
  6. package/dist/api/branch.js +1 -1
  7. package/dist/api/content.js +1 -1
  8. package/dist/api/entries-constants.d.ts +1 -1
  9. package/dist/api/entries-constants.js +1 -1
  10. package/dist/api/entries.d.ts +2 -2
  11. package/dist/api/permissions.d.ts +2 -2
  12. package/dist/api/types.d.ts +3 -2
  13. package/dist/cli/cli.js +16 -14
  14. package/dist/cli/generate-ai-content.js +7 -5
  15. package/dist/cli/init.js +4 -4
  16. package/dist/client.d.ts +0 -1
  17. package/dist/client.js +0 -1
  18. package/dist/config/schemas/config.d.ts +0 -5
  19. package/dist/config/schemas/config.js +0 -1
  20. package/dist/config/types.d.ts +0 -1
  21. package/dist/content-index-generation.d.ts +1 -1
  22. package/dist/content-index-generation.js +1 -1
  23. package/dist/content-listing.js +1 -1
  24. package/dist/content-reader.js +1 -1
  25. package/dist/content-store.d.ts +1 -1
  26. package/dist/content-store.js +1 -1
  27. package/dist/dev-content-watcher.d.ts +1 -1
  28. package/dist/dev-content-watcher.js +1 -1
  29. package/dist/editor/BranchManager.d.ts +2 -0
  30. package/dist/editor/BranchManager.js +5 -4
  31. package/dist/editor/CanopyEditor.d.ts +1 -1
  32. package/dist/editor/CanopyEditor.js +1 -2
  33. package/dist/editor/CommentsPanel.js +3 -6
  34. package/dist/editor/Editor.d.ts +0 -2
  35. package/dist/editor/Editor.js +329 -181
  36. package/dist/editor/EditorAuthGate.js +8 -3
  37. package/dist/editor/EntryNavigator.d.ts +10 -0
  38. package/dist/editor/EntryNavigator.js +51 -2
  39. package/dist/editor/FormHeader.js +1 -1
  40. package/dist/editor/PreviewFrame.d.ts +5 -1
  41. package/dist/editor/PreviewFrame.js +3 -2
  42. package/dist/editor/PreviewPane.d.ts +47 -0
  43. package/dist/editor/PreviewPane.js +147 -0
  44. package/dist/editor/PreviewToolbarTools.d.ts +22 -0
  45. package/dist/editor/PreviewToolbarTools.js +73 -0
  46. package/dist/editor/comments/form-comments.js +2 -1
  47. package/dist/editor/components/BranchLockBanner.d.ts +22 -0
  48. package/dist/editor/components/BranchLockBanner.js +39 -0
  49. package/dist/editor/components/BranchPill.d.ts +32 -0
  50. package/dist/editor/components/BranchPill.js +33 -0
  51. package/dist/editor/components/ConfirmDeleteModal.d.ts +3 -1
  52. package/dist/editor/components/ConfirmDeleteModal.js +3 -2
  53. package/dist/editor/components/EditorHeader.d.ts +50 -108
  54. package/dist/editor/components/EditorHeader.js +117 -138
  55. package/dist/editor/components/EditorRail.d.ts +49 -0
  56. package/dist/editor/components/EditorRail.js +65 -0
  57. package/dist/editor/components/EntryBreadcrumb.d.ts +33 -0
  58. package/dist/editor/components/EntryBreadcrumb.js +56 -0
  59. package/dist/editor/components/QuickOpenPalette.d.ts +1 -1
  60. package/dist/editor/components/QuickOpenPalette.js +3 -2
  61. package/dist/editor/components/RenameEntryModal.d.ts +3 -1
  62. package/dist/editor/components/RenameEntryModal.js +3 -2
  63. package/dist/editor/components/SaveAllResult.d.ts +25 -0
  64. package/dist/editor/components/SaveAllResult.js +86 -0
  65. package/dist/editor/components/SaveSplitButton.d.ts +30 -0
  66. package/dist/editor/components/SaveSplitButton.js +108 -0
  67. package/dist/editor/components/index.d.ts +2 -2
  68. package/dist/editor/components/index.js +2 -2
  69. package/dist/editor/context/EditorStateContext.d.ts +4 -4
  70. package/dist/editor/context/EditorStateContext.js +4 -4
  71. package/dist/editor/copy.d.ts +3 -0
  72. package/dist/editor/copy.js +3 -0
  73. package/dist/editor/editor-utils.d.ts +11 -6
  74. package/dist/editor/editor-utils.js +8 -17
  75. package/dist/editor/fields/MarkdownField.js +3 -0
  76. package/dist/editor/hooks/save-all.d.ts +68 -0
  77. package/dist/editor/hooks/save-all.js +61 -0
  78. package/dist/editor/hooks/useCommentSystem.d.ts +2 -0
  79. package/dist/editor/hooks/useCommentSystem.js +3 -0
  80. package/dist/editor/hooks/useDraftManager.d.ts +24 -1
  81. package/dist/editor/hooks/useDraftManager.js +297 -16
  82. package/dist/editor/hooks/useEditorLayout.d.ts +15 -11
  83. package/dist/editor/hooks/useEditorLayout.js +13 -25
  84. package/dist/editor/hooks/useEntryManager.d.ts +4 -0
  85. package/dist/editor/hooks/useEntryManager.js +30 -7
  86. package/dist/editor/media/MediaLibrary.d.ts +1 -1
  87. package/dist/editor/preview-path.d.ts +12 -0
  88. package/dist/editor/preview-path.js +45 -1
  89. package/dist/editor/preview-scale.d.ts +27 -0
  90. package/dist/editor/preview-scale.js +34 -0
  91. package/dist/editor/shell/ActivityRail.d.ts +37 -0
  92. package/dist/editor/shell/ActivityRail.js +91 -0
  93. package/dist/editor/shell/EditorShell.d.ts +17 -0
  94. package/dist/editor/shell/EditorShell.js +31 -0
  95. package/dist/editor/shell/PaneLayout.d.ts +28 -0
  96. package/dist/editor/shell/PaneLayout.js +116 -0
  97. package/dist/editor/shell/SidePanelHost.d.ts +35 -0
  98. package/dist/editor/shell/SidePanelHost.js +184 -0
  99. package/dist/editor/shell/TopBar.d.ts +12 -0
  100. package/dist/editor/shell/TopBar.js +14 -0
  101. package/dist/editor/shell/host-chrome.d.ts +16 -0
  102. package/dist/editor/shell/host-chrome.js +69 -0
  103. package/dist/editor/theme.js +18 -3
  104. package/dist/git-manager.d.ts +1 -1
  105. package/dist/git-manager.js +2 -2
  106. package/dist/operating-mode/types.d.ts +1 -1
  107. package/dist/resolve-canopy-user.d.ts +1 -1
  108. package/dist/resolve-canopy-user.js +1 -1
  109. package/dist/schema/resolver.d.ts +1 -1
  110. package/dist/schema/resolver.js +1 -1
  111. package/dist/schema/schema-store.d.ts +1 -1
  112. package/dist/schema/schema-store.js +2 -2
  113. package/dist/url-collision.js +3 -3
  114. package/dist/url-exclusivity-fixtures.d.ts +1 -1
  115. package/dist/url-exclusivity-fixtures.js +1 -1
  116. package/dist/utils/content-write-lock.d.ts +1 -1
  117. package/dist/utils/content-write-lock.js +1 -1
  118. package/dist/utils/debug.d.ts +1 -1
  119. package/dist/utils/debug.js +5 -3
  120. package/dist/utils/git.d.ts +4 -0
  121. package/dist/utils/git.js +27 -5
  122. package/dist/validation/block-structural-keys.d.ts +1 -1
  123. package/dist/validation/block-structural-keys.js +1 -1
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/dist/worker/cms-worker.d.ts +1 -1
  127. package/dist/worker/cms-worker.js +2 -2
  128. package/dist/worker/github-auth.d.ts +1 -1
  129. package/dist/worker/github-auth.js +1 -1
  130. package/dist/worker/github-gateway-wire.d.ts +2 -1
  131. package/dist/worker/github-gateway-wire.js +7 -3
  132. package/dist/worker/github-gateway.js +1 -1
  133. package/dist/worker/github-mirror.d.ts +2 -2
  134. package/dist/worker/github-mirror.js +2 -2
  135. package/dist/worker/history-rewrite.d.ts +1 -1
  136. package/dist/worker/history-rewrite.js +1 -1
  137. package/dist/worker/self-checks.d.ts +1 -1
  138. package/dist/worker/shared-repo-git.js +1 -1
  139. package/package.json +5 -4
  140. package/dist/editor/EditorPanes.d.ts +0 -20
  141. package/dist/editor/EditorPanes.js +0 -124
  142. package/dist/editor/components/EditorFooter.d.ts +0 -11
  143. package/dist/editor/components/EditorFooter.js +0 -9
  144. package/dist/editor/components/EditorSidebar.d.ts +0 -70
  145. package/dist/editor/components/EditorSidebar.js +0 -50
package/CHANGELOG.md CHANGED
@@ -5,38 +5,11 @@ packages release together, at one version.
5
5
 
6
6
  ## This build
7
7
 
8
- Version `0.1.0-int.115`, built from [`f8b50b3`](https://github.com/safeinsights/canopycms/tree/f8b50b30170954bf52fb670afe6796d2cb8fa275).
9
-
10
- ### New since int.114
11
-
12
- - [AI content can withhold or redirect a link to an entry with no page](#ai-content-can-withhold-or-redirect-a-link-to-an-entry-with-no-page)
13
- - [toPlainText takes an entry body and keeps prose after a leading --- — behaviour change](#toplaintext-takes-an-entry-body-and-keeps-prose-after-a-leading------behaviour-change)
14
- - [People without a name show "Name unavailable"](#people-without-a-name-show-name-unavailable)
15
- - [Branch access lists stay with admins and reviewers — behaviour change](#branch-access-lists-stay-with-admins-and-reviewers--behaviour-change)
16
- - [A branch lockdown outranks the branch's access list — security fix](#a-branch-lockdown-outranks-the-branchs-access-list--security-fix)
17
- - [The base-branch conflict notice stays until the branch merges — behaviour change](#the-base-branch-conflict-notice-stays-until-the-branch-merges--behaviour-change)
18
- - [The editor calls the content drawer "All content"](#the-editor-calls-the-content-drawer-all-content)
19
- - [The Branches drawer shows one action per branch, and comments reply in place](#the-branches-drawer-shows-one-action-per-branch-and-comments-reply-in-place)
20
- - [An object's Clear offers Undo, and refusals say why](#an-objects-clear-offers-undo-and-refusals-say-why)
21
- - [The collection dialog says what saves at once](#the-collection-dialog-says-what-saves-at-once)
22
- - [Comments and branches name their people for every editor — behaviour change on two APIs](#comments-and-branches-name-their-people-for-every-editor--behaviour-change-on-two-apis)
23
- - [A date picker, a form header, and a toolbar that wraps](#a-date-picker-a-form-header-and-a-toolbar-that-wraps)
24
- - [Editor colours reach 4.5:1 in light and dark mode](#editor-colours-reach-451-in-light-and-dark-mode)
25
- - [Editor contrast, and blocked buttons stay focusable](#editor-contrast-and-blocked-buttons-stay-focusable)
26
- - [Editor focus rings and checked controls reach 3:1 for any brand](#editor-focus-rings-and-checked-controls-reach-31-for-any-brand)
27
- - [Editor requests time out instead of hanging](#editor-requests-time-out-instead-of-hanging)
28
- - [Field editing: descriptions, list-card titles, and public/ images, continued](#field-editing-descriptions-list-card-titles-and-public-images-continued)
29
- - [List items and blocks collapse, and reorder from one menu](#list-items-and-blocks-collapse-and-reorder-from-one-menu)
30
- - ["Add entry" follows path permissions; admin panels and labels tidied](#add-entry-follows-path-permissions-admin-panels-and-labels-tidied)
31
- - [Quick open, a navigator filter, title-first new entries, and media search](#quick-open-a-navigator-filter-title-first-new-entries-and-media-search)
32
- - [A save never brings back a deleted or moved entry — behaviour change](#a-save-never-brings-back-a-deleted-or-moved-entry--behaviour-change)
33
- - [Submit names unsaved entries; unanswered writes recover more](#submit-names-unsaved-entries-unanswered-writes-recover-more)
34
- - [Unlabelled fields and block templates show their names as words](#unlabelled-fields-and-block-templates-show-their-names-as-words)
35
- - [Stable releases follow the changes they carry, and ship their changelog](#stable-releases-follow-the-changes-they-carry-and-ship-their-changelog)
36
- - [List items and blocks carry a \_key, and field comments follow it — behaviour change](#list-items-and-blocks-carry-a-_key-and-field-comments-follow-it--behaviour-change)
37
- - [stampOnPublish dates a new entry at its first submit](#stamponpublish-dates-a-new-entry-at-its-first-submit)
38
-
39
- If you upgraded from a build on another branch, compare the `<!-- changeset: … -->` slugs in your previous build's `CHANGELOG.md` (`npm pack canopycms@<old>` fetches it) with this one's: entries whose slug your old file lacks are new to you. Legacy entries above the changeset entries carry hand-written int labels.
8
+ Version `0.1.0`, built from [`39a755c`](https://github.com/safeinsights/canopycms/tree/39a755c116a52b6d16e0b09e9dde03c67a081754).
9
+
10
+ Its entries are [0.1.0](#010), under **Released**.
11
+
12
+ If you upgraded from a build on another branch, compare the `<!-- changeset: … -->` slugs in your previous build's `CHANGELOG.md` (`npm pack canopycms@<old>` fetches it) with this one's: entries whose slug your old file lacks are new to you.
40
13
 
41
14
  ## How to use this document
42
15
 
@@ -51,7 +24,7 @@ Each entry has up to three parts:
51
24
  `node_modules/canopycms/CHANGELOG.md`, assembled for that build: its **This build** section lists
52
25
  what is new since the previous int on the same branch, or on a stable release links its entries, and its links point at the commit the build
53
26
  came from. The copy in the repository holds released history and the entries from before changesets;
54
- every entry written since is a file in [`.changeset/`](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/.changeset/README.md) until a stable release
27
+ every entry written since is a file in [`.changeset/`](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/.changeset/README.md) until a stable release
55
28
  moves it here.
56
29
 
57
30
  **Find your starting point.** From a stable pin, start at the first version under **Released**
@@ -80,89 +53,16 @@ Nothing in this section is edited by hand: each entry is a `.changeset/` file, w
80
53
  assembles here and labels with the first int that carries it, and a stable release moves the
81
54
  entries under **Released**.
82
55
 
83
- <!-- legacy:start -->
84
-
85
- The entries below predate changesets and keep their hand-written labels.
86
-
87
- | int | Area | Change | Action |
88
- | --- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
89
- | 93 | Branches | [Path rules match logical paths](#path-permission-rules-now-take-effect-below-the-content-root--security-fix-breaking-for-rules-written-with-on-disk-names) | Review rules |
90
- | 93 | Branches | [`.canopy-meta/` must not be committed](#canopy-meta-must-not-be-committed-and-system-health-says-so) | If tracked |
91
- | 93 | Branches | [`autoCreateSettingsPR` removed](#settings-saves-only-push-the-settings-branch--breaking-config-autocreatesettingspr-is-removed) | Required |
92
- | 93 | Reading | [Listings take `branch`; reads report `slug`/`urlPath`](#content-reads-listings-take-a-branch-and-reads-report-slug-and-urlpath) (102) | Index pages |
93
- | 93 | Editing | [Field descriptions, list-card titles, `public/` images](#field-editing-descriptions-list-card-titles-and-public-images) (96, 103) | None |
94
- | 93 | Branches | [Commits name editors; submit refuses an empty branch](#commits-name-their-editors-and-submit-refuses-a-branch-with-nothing-to-submit) (108, 110) | Scripts |
95
- | 94 | Branches | [Reads never create a branch](#reads-never-create-a-requested-branch--security-fix-breaking-for-some-direct-createcontentreader-callers) | Direct callers |
96
- | 94 | Preview | [Preview URLs: one prefix, `trailingSlash`, own page](#preview-urls-take-one-prefix-follow-trailingslash-and-load-each-entrys-own-page--breaking-env) (98) | Required |
97
- | 94 | Preview | [`createPreviewPage`, `/preview` entries](#the-preview-route-and-preview-entries--breaking-imports) (95, 101, 103) | Required |
98
- | 94 | Refs | [Restricted and missing references](#a-reference-resolves-to-its-target-a-restricted-stub-or-a-missing-stub--security-fix-breaking-types-and-build) (106) | Required |
99
- | 94 | CDK | [`attachTo`, editor response headers](#canopycms-cdk-canopycmsserviceattachto-and-editor-response-headers--behaviour-change-if-you-frame-the-cms) | If hand-wired or framed |
100
- | 94 | Ops | [System health shows the build](#system-health-shows-which-build-is-running) | Optional |
101
- | 100 | Branches | [Crash-safe branch creation](#fast-crash-safe-branch-creation--behaviour-change-branch-clones-hold-only-the-content-root) | Upgrade together |
102
- | 100 | Ops | [Prod workspace defaults to `/mnt/efs`](#the-prod-workspace-defaults-to-mntefs--behaviour-change-if-you-run-prod-without-canopycms_workspace_root) | Non-CDK prod |
103
- | 100 | Assets | [`AssetSupport` serves from S3 only](#canopycms-cdk-assetsupport-serves-images-from-s3-only--breaking) | Required |
104
- | 100 | Assets | [`collect-asset-refs`, `materialize-assets`](#collect-asset-refs-and-materialize-assets-store-a-builds-images-before-release) (104) | With S3-only |
105
- | 100 | Assets | [Editor images via signed-in route](#the-editor-and-its-live-preview-load-images-through-the-signed-in-asset-route) | CSP, origin |
106
- | 100 | Assets | [`assetUrl` applies crop](#asseturl-applies-an-image-values-crop--behaviour-change-for-cropped-images) | Scale `<img>` |
107
- | 100 | Assets | [`media.publicBaseUrl` removed; wider limits](#mediapublicbaseurl-is-removed-and-image-limits-are-wider--breaking-config) | Delete key |
108
- | 101 | Editing | [Saves rewrite only what changed](#a-save-rewrites-only-what-changed) (103) | None |
109
- | 102 | Auth | [`unauthenticatedStatus: 419`](#unauthenticatedstatus-answer-signed-out-api-calls-with-419) | Basic-auth sites |
110
- | 105 | Editing | [MDXEditor 4.3](#the-markdown-editor-runs-mdxeditor-43) | Direct deps |
111
- | 106 | Refs | [AI content links references](#ai-content-links-a-reference-to-its-target--behaviour-change-for-ai-output-and-ai-config-callbacks) | AI config |
112
- | 106 | Refs | [Deleting a referenced entry asks](#deleting-a-referenced-entry-asks-first--behaviour-change-on-the-delete-api) | Scripts |
113
- | 107 | Schema | [Unknown schemas cost one entry type](#a-schema-the-running-code-lacks-costs-one-entry-type-and-holds-the-worker--behaviour-change) | Rare |
114
- | 108 | MDX | [MDX that runs code is refused](#mdx-content-that-runs-code-is-refused-at-save--breaking-behaviour) (109, 110) | Required |
115
- | 108 | Worker | [On-demand worker, drain; `spotMaxPrice` removed](#canopycms-cdk-the-worker-drains-before-replacement-and-runs-on-demand--breaking-props-spotmaxprice-is-removed-behaviour-and-cost-change) | If set |
116
- | 109 | Worker | [Hardened worker instance](#canopycms-cdk-the-worker-instance-is-hardened--an-existing-stack-upgrades-in-two-deploys-behaviour-and-cost-change) | Two deploys |
117
- | 109 | Worker | [Failed or stopped worker says why](#a-failed-or-stopped-worker-says-why--behaviour-change-on-the-not-ready-503-new-worker-apis) | Custom entrypoint |
118
- | 109 | Worker | [CI worker roll; worker-down alarm](#canopycms-cdk-ci-can-roll-the-worker-with-a-parameter-and-alarm-when-it-stops-syncing--new-opt-in) | Optional |
119
- | 109 | Preview | [Typed `fieldProps`](#preview-fieldprops-is-typed-with-server-safe-helpers--breaking-types-and-schemas) | Required |
120
- | 110 | Worker | [Worker needs a state directory](#canopycms-cdk-the-worker-needs-a-state-directory--deploy-the-template-before-the-bundle) | Template first |
121
- | 110 | Ops | [Prod detects an unset `defaultBaseBranch`](#prod-detects-an-unset-defaultbasebranch-instead-of-assuming-main--behaviour-change-startup-can-fail) | Base ≠ repo default |
122
- | 111 | CDK | [Example workflow's triggers and checks](#the-aws-example-workflow-gains-the-templates-triggers-and-dependency-checks) | If copied by hand |
123
- | 111 | Ops | [Duplicate-ID scan only on request](#get-adminbranch-health-scans-for-duplicate-content-ids-only-on-request--behaviour-change) | Admin-API scripts |
124
- | 111 | Auth | [CMS image builds a prod editor; mismatch blocks](#the-cms-image-builds-a-prod-editor-and-a-mode-mismatch-blocks-the-editor--behaviour-change-a-hand-built-image-can-fail-its-build) | Hand-built images |
125
- | 111 | Auth | [Auth plugins look users up in batches](#auth-plugins-look-users-up-in-batches) | Custom plugins |
126
- | 112 | Worker | [Bundle states the template it needs](#canopycms-cdk-a-worker-bundle-states-the-template-it-needs--template-first-for-the-gate-only) | Template first |
127
- | 112 | Worker | [Poisoned `remote.git` re-clones](#a-poisoned-remotegit-re-clones-itself) | None |
128
- | 112 | Worker | [Nano worker boots reliably](#canopycms-cdk-a-t4gnano-worker-boots-reliably--behaviour-change-a-deploy-replaces-the-worker) | Deploy |
129
- | 112 | Worker | [Worker never pushes base or default branch](#the-worker-never-pushes-the-base-or-default-branch--security-fix) (114) | Protect base on GitHub |
130
- | 112 | Worker | [Worker log is root-owned](#canopycms-cdk-the-workers-log-is-root-owned--security-fix) | Deploy; hand-installed units |
131
- | 113 | Worker | [Push logs say whether GitHub moved](#the-workers-push-logs-say-whether-github-moved) | None |
132
- | 113 | Editing | [Unsaved ACL changes show; closing asks](#unsaved-group-and-permission-changes-show-and-closing-asks-first) | None |
133
- | 113 | Worker | [Credential in a per-command git config](#the-worker-hands-git-its-github-credential-in-a-per-command-file--security-fix-dev-behaviour-change) | Dev: proxy or CA in env |
134
- | 114 | Worker | [GitHub objects cross as bundles](#the-worker-moves-objects-between-github-and-remotegit-as-bundles) | None |
135
- | 114 | AI | [`noindex` entries leave AI content](#ai-content-leaves-out-noindex-entries--behaviour-change-for-ai-output) | `exclude.noindex: false` |
136
- | 114 | Worker | [Worker follows a rebased or deleted branch](#the-worker-follows-a-rebased-or-deleted-github-branch) | None |
137
- | 114 | Worker | [Worker holds no secret (contract 2)](#canopycms-cdk-the-worker-holds-no-secret--security-fix-template-first-contract-2) | Template first |
138
- | 115 | AI | [referenceLink decides where AI-export entry links point](#ai-content-can-withhold-or-redirect-a-link-to-an-entry-with-no-page) | Optional |
139
- | 115 | AI | [toPlainText no longer strips front matter](#toplaintext-takes-an-entry-body-and-keeps-prose-after-a-leading------behaviour-change) | Whole-file callers split first |
140
- | 115 | Auth | [Clerk users without a name no longer show their id](#people-without-a-name-show-name-unavailable) | None |
141
- | 115 | Branches | [Non-privileged callers get only their own branch ACL entries](#branch-access-lists-stay-with-admins-and-reviewers--behaviour-change) | None |
142
- | 115 | Branches | [managerOrAdminAllowed now refuses editors an access list names](#a-branch-lockdown-outranks-the-branchs-access-list--security-fix) | Optional |
143
- | 115 | Branches | [Conflict notice persists until merge](#the-base-branch-conflict-notice-stays-until-the-branch-merges--behaviour-change) | None |
144
- | 115 | Editing | [Breadcrumb root and file menu say "All content"](#the-editor-calls-the-content-drawer-all-content) | Editor UI tests |
145
- | 115 | Editing | [Branches drawer and comment replies](#the-branches-drawer-shows-one-action-per-branch-and-comments-reply-in-place) | Editor UI tests |
146
- | 115 | Editing | [Clear offers Undo; refusals and conflicts explain themselves](#an-objects-clear-offers-undo-and-refusals-say-why) | None |
147
- | 115 | Editing | [Collection dialog says what saves at once](#the-collection-dialog-says-what-saves-at-once) | Editor UI tests |
148
- | 115 | Editing | [Comments and branches name their people](#comments-and-branches-name-their-people-for-every-editor--behaviour-change-on-two-apis) | Direct API callers |
149
- | 115 | Editing | [Date picker, form header, wrapping toolbar](#a-date-picker-a-form-header-and-a-toolbar-that-wraps) | Editor UI tests |
150
- | 115 | Editing | [Editor colours reach contrast in both schemes](#editor-colours-reach-451-in-light-and-dark-mode) | Visual snapshot tests |
151
- | 115 | Editing | [Editor contrast; blocked buttons stay focusable](#editor-contrast-and-blocked-buttons-stay-focusable) | Browser tests |
152
- | 115 | Editing | [Focus ring, control borders, light badges, sentence-case badges](#editor-focus-rings-and-checked-controls-reach-31-for-any-brand) | Visual snapshot tests |
153
- | 115 | Editing | [Editor requests time out](#editor-requests-time-out-instead-of-hanging) | Slow-API browser tests |
154
- | 115 | Editing | [Field descriptions, list-card titles, `public/` images, continued](#field-editing-descriptions-list-card-titles-and-public-images-continued) | None |
155
- | 115 | Editing | [List items and blocks collapse](#list-items-and-blocks-collapse-and-reorder-from-one-menu) | Editor UI tests |
156
- | 115 | Editing | [Navigator hides "Add entry" where path permissions refuse it](#add-entry-follows-path-permissions-admin-panels-and-labels-tidied) | None |
157
- | 115 | Editing | [Quick open, navigator filter, title-first entries, media search](#quick-open-a-navigator-filter-title-first-new-entries-and-media-search) | Editor UI tests |
158
- | 115 | Editing | [Saving a deleted or moved entry is refused](#a-save-never-brings-back-a-deleted-or-moved-entry--behaviour-change) | None |
159
- | 115 | Editing | [Submit warns about unsaved entries; unanswered writes keep input](#submit-names-unsaved-entries-unanswered-writes-recover-more) | None |
160
- | 115 | Editing | [Unlabelled field names are humanised](#unlabelled-fields-and-block-templates-show-their-names-as-words) | Editor UI tests |
161
- | 115 | Ops | [Stable versions follow changesets; CHANGELOG.md ships](#stable-releases-follow-the-changes-they-carry-and-ship-their-changelog) | None |
162
- | 115 | Reading | [List items and blocks gain a persisted `_key`](#list-items-and-blocks-carry-a-_key-and-field-comments-follow-it--behaviour-change) | Rename fields named _key |
163
- | 115 | Schema | [Date new entries at their first submit](#stamponpublish-dates-a-new-entry-at-its-first-submit) | Optional |
164
-
165
- ### Preview URLs take one prefix, follow `trailingSlash`, and load each entry's own page — **breaking (env)**
56
+ ---
57
+
58
+ ## Released
59
+
60
+ ### 0.1.0
61
+
62
+ <!-- changeset: legacy-unreleased -->
63
+ <!-- changeset: nice-boxes-battle -->
64
+
65
+ #### Preview URLs take one prefix, follow `trailingSlash`, and load each entry's own page — **breaking (env)**
166
66
 
167
67
  **What changed.**
168
68
 
@@ -182,7 +82,7 @@ served at `/` without an `index` slug needs `'content/<slug>': '/'`, or re-model
182
82
  **Now deletable.** `previewBase` keys that only added a shared prefix or restate a root entry's own
183
83
  URL.
184
84
 
185
- ### The preview route and `/preview` entries — **breaking (imports)**
85
+ #### The preview route and `/preview` entries — **breaking (imports)**
186
86
 
187
87
  **What changed.**
188
88
 
@@ -208,7 +108,7 @@ URL.
208
108
  2. For a branch preview: wrap views with `withCanopyPreview` in a `'use client'` module, add
209
109
  `app/preview/[[...path]]/page.server.tsx`, set `editor.previewPrefix: '/preview'`, and serve that
210
110
  route with `frame-ancestors 'self'`, not `X-Frame-Options: DENY` (see
211
- [Live Preview](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#live-preview)).
111
+ [Live Preview](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#live-preview)).
212
112
  3. A test reading view content from the route's server HTML must load the page in a browser.
213
113
  Server output rendered during a preview request carries the signed-in asset prefix; keep it out
214
114
  of cross-request caches (`unstable_cache`, module memos).
@@ -216,14 +116,14 @@ URL.
216
116
  **Now deletable.** A hand-built branch-preview route, and CSS layer or specificity workarounds that
217
117
  kept site styles ahead of Mantine's in the preview.
218
118
 
219
- ### Preview `fieldProps` is typed, with server-safe helpers — **breaking (types and schemas)**
119
+ #### Preview `fieldProps` is typed, with server-safe helpers — **breaking (types and schemas)**
220
120
 
221
121
  **What changed.** (int.109) `fieldProps` checks each path against the view's content type, and
222
122
  `canopycms` exports `FieldProps`, `FieldAttrs`, `fieldAttrs` and `scopeFieldProps`, callable from
223
123
  server components. A string path marks the form's spelling (`'a.0.b'` marks `a[0].b`), and an empty
224
124
  path marks nothing. The editor counts and logs marks naming no field. `createEntrySchemaRegistry`
225
125
  refuses a field name that is empty, all digits or contains `.`, `[` or `]`. See
226
- [Live Preview](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#live-preview).
126
+ [Live Preview](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#live-preview).
227
127
 
228
128
  **To adopt.** Fix each path that stops compiling; the error lists valid ones. Rewrite computed paths
229
129
  as literal segments, or type that component's prop as plain `FieldProps`. Pass `fieldProps` as
@@ -233,7 +133,7 @@ as literal segments, or type that component's prop as plain `FieldProps`. Pass `
233
133
  `fieldProps` defaults; schema-walking tests for misspelled mark paths, once components are typed
234
134
  (the editor flags another block template's field).
235
135
 
236
- ### A reference resolves to its target, a restricted stub, or a missing stub — **security fix; breaking (types and build)**
136
+ #### A reference resolves to its target, a restricted stub, or a missing stub — **security fix; breaking (types and build)**
237
137
 
238
138
  **What changed.**
239
139
 
@@ -271,7 +171,7 @@ optional if it was required.
271
171
  content-integrity test checking reference fields against entry ids, and top-level-only reference
272
172
  fields kept for the preview's sake.
273
173
 
274
- ### AI content links a reference to its target — **behaviour change for `/ai` output and AI config callbacks**
174
+ #### AI content links a reference to its target — **behaviour change for `/ai` output and AI config callbacks**
275
175
 
276
176
  **What changed.** (int.106) A `reference` field renders as a link to its target, not the stored id:
277
177
  a byline reading `**Author:** 5NVkkrB1MJUv` now links the name `Alice` to that entry's page, then to
@@ -293,7 +193,7 @@ matching: search your AI config for each reference field's name.
293
193
 
294
194
  **Now deletable.** A `fieldTransforms` entry that looked up a reference id to print its title.
295
195
 
296
- ### AI content leaves out `noindex` entries — **behaviour change for `/ai` output**
196
+ #### AI content leaves out `noindex` entries — **behaviour change for `/ai` output**
297
197
 
298
198
  **What changed.** (int.114) AI content leaves out entries marked `noindex`, both from
299
199
  `generate-ai-content` and from the `/ai` route, using the same predicate as the sitemap and robots
@@ -305,11 +205,11 @@ or a failed render) renders as its plain text, with no URL; any other form of th
305
205
  fields are nested or renamed, pass the AI config's `seo` the same object you pass
306
206
  `createNextCanopyContext({ seo })`, so the sitemap and AI content read the flag from the same
307
207
  place. A field that links to a hidden section by plain URL needs a field transform; the README
308
- shows [the recipe](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#ai-content-configuration).
208
+ shows [the recipe](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#ai-content-configuration).
309
209
 
310
210
  **Now deletable.** An `exclude.where` that re-read the `noindex` flag by hand.
311
211
 
312
- ### Deleting a referenced entry asks first — **behaviour change on the delete API**
212
+ #### Deleting a referenced entry asks first — **behaviour change on the delete API**
313
213
 
314
214
  **What changed.** (int.106) Deleting an entry that other entries reference (by field or `entry:`
315
215
  link) returns 409 with `data.referencedBy`: the referencing entries the user may read, and a
@@ -318,7 +218,7 @@ link) returns 409 with `data.referencedBy`: the referencing entries the user may
318
218
 
319
219
  **To adopt.** Only scripts calling the delete endpoint: pass the flag or handle the 409.
320
220
 
321
- ### `mdx` content that runs code is refused at save — **breaking (behaviour)**
221
+ #### `mdx` content that runs code is refused at save — **breaking (behaviour)**
322
222
 
323
223
  **What changed.** (int.108) An `mdx` field or body refuses `{…}` expressions other than comments and
324
224
  plain values, `import`/`export`, and tags, attributes and URL schemes outside a safe set; other
@@ -327,7 +227,7 @@ A `markdown` field with `renderAs: 'mdx'` is checked as MDX, and `mdxAllow` narr
327
227
  field or site-wide. New: field options `renderAs`, `mdxAllow`; config key `mdxAllow`; type
328
228
  `MdxAllowlist`. (int.110) A component prop allowance can be `'string'` (or
329
229
  `{ type: 'string', maxLength }`): a quoted value only, never bare or `{…}`. See
330
- [MDX content cannot run code](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#mdx-content-cannot-run-code).
230
+ [MDX content cannot run code](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#mdx-content-cannot-run-code).
331
231
 
332
232
  **To adopt.** Set `renderAs: 'mdx'` on each `markdown` field your site compiles as MDX, and
333
233
  `mdxAllow` to what your renderer takes. Set `executable: true` only on a field whose editors you
@@ -339,7 +239,7 @@ build lists it.
339
239
  `javascript:` links in MDX or markdown rendered as MDX, and the path-prefix matching behind its
340
240
  entry-type gate. Keep a rule checking that the body compiles.
341
241
 
342
- ### A save rewrites only what changed
242
+ #### A save rewrites only what changed
343
243
 
344
244
  **What changed.**
345
245
 
@@ -359,7 +259,7 @@ entry-type gate. Keep a rule checking that the body compiles.
359
259
  **Now deletable.** A formatter pass or `.prettierignore` entry over CMS-written content that only
360
260
  undoes the editor's re-folding, restyling or date rewriting.
361
261
 
362
- ### The markdown editor runs MDXEditor 4.3
262
+ #### The markdown editor runs MDXEditor 4.3
363
263
 
364
264
  **What changed.** (int.105) `@mdxeditor/editor` is `^4.3.2` (Lexical 0.48), up from `^3.52.4`.
365
265
  Two-paragraph list items, multi-block quotes and code fences in any language edit in rich text.
@@ -367,7 +267,7 @@ Two-paragraph list items, multi-block quotes and code fences in any language edi
367
267
  **To adopt.** Nothing, unless your app depends on `@mdxeditor/editor` or `lexical` directly: align
368
268
  those versions so one copy loads.
369
269
 
370
- ### Unsaved group and permission changes show, and closing asks first
270
+ #### Unsaved group and permission changes show, and closing asks first
371
271
 
372
272
  **What changed.** (int.113) Manage Groups and Manage Permissions still save as one batch, but now
373
273
  mark unsaved changes, keep the save bar in view, and ask before closing or leaving drops them. The
@@ -375,7 +275,7 @@ group dialog's buttons read "Add" and "Apply", since neither saves.
375
275
 
376
276
  **To adopt.** Nothing.
377
277
 
378
- ### Field editing: descriptions, list-card titles, and `public/` images
278
+ #### Field editing: descriptions, list-card titles, and `public/` images
379
279
 
380
280
  **What changed.**
381
281
 
@@ -393,7 +293,7 @@ image, upload a raster copy to the media library. For card titles:
393
293
 
394
294
  **Now deletable.** Editor hints worked into a field's `label` because `description` never showed.
395
295
 
396
- ### Content reads: listings take a `branch`, and reads report `slug` and `urlPath`
296
+ #### Content reads: listings take a `branch`, and reads report `slug` and `urlPath`
397
297
 
398
298
  **What changed.**
399
299
 
@@ -406,7 +306,7 @@ branch.
406
306
 
407
307
  **Now deletable.** Code parsing a slug or URL back out of `path` or `entry.path`.
408
308
 
409
- ### Path-permission rules now take effect below the content root — **security fix; breaking for rules written with on-disk names**
309
+ #### Path-permission rules now take effect below the content root — **security fix; breaking for rules written with on-disk names**
410
310
 
411
311
  **What changed.** (int.93) Rules match an entry's logical path (`content/blog/my-post`). Enforcement
412
312
  checked the id-suffixed on-disk path, so collection and entry rules matched nothing; only a glob
@@ -419,7 +319,7 @@ extension in logical form (`content/blog/**`, `content/about`).
419
319
 
420
320
  **Now deletable.** A content-root grant working around inert collection grants.
421
321
 
422
- ### Reads never create a requested branch — **security fix; breaking for some direct `createContentReader` callers**
322
+ #### Reads never create a requested branch — **security fix; breaking for some direct `createContentReader` callers**
423
323
 
424
324
  **What changed.** (int.94) A branch other than the active one must exist and be readable by the
425
325
  user, or `read()`/`readByUrlPath()` treat it as not found; no workspace is created per `?branch=`
@@ -430,7 +330,7 @@ passes `allowCreateBranch: true`, never with a request's branch.
430
330
 
431
331
  **Now deletable.** Checks that allow-list `?branch=` before it reaches `read()`.
432
332
 
433
- ### Fast, crash-safe branch creation — **behaviour change: branch clones hold only the content root**
333
+ #### Fast, crash-safe branch creation — **behaviour change: branch clones hold only the content root**
434
334
 
435
335
  **What changed.** (int.100) A branch is built under a staging name and appears only when complete.
436
336
  The worker repairs branch directories an interrupted create or delete left wedged, and owns
@@ -444,7 +344,7 @@ changed `contentRoot` reaches existing clones within a worker cycle of the API r
444
344
  **Now deletable.** Manual EFS cleanup of provisioning locks or half-made branch directories, and
445
345
  `remote.git` gc scripts.
446
346
 
447
- ### `.canopy-meta/` must not be committed, and System health says so
347
+ #### `.canopy-meta/` must not be committed, and System health says so
448
348
 
449
349
  **What changed.** (int.93) The schema cache lives in each branch clone's `.git/canopycms/`. Sync,
450
350
  editor submits and `canopycms sync` ignore `.canopy-meta/`, and System health shows the base
@@ -458,13 +358,13 @@ repaired or re-created.
458
358
  **Now deletable.** Any local step that resets or reformats `.canopy-meta/` files before a commit or
459
359
  a format check.
460
360
 
461
- ### Commits name their editors, and submit refuses a branch with nothing to submit
361
+ #### Commits name their editors, and submit refuses a branch with nothing to submit
462
362
 
463
363
  **What changed.**
464
364
 
465
365
  - (int.93) Submit commits gain an `Edited-by: Name (id)` trailer; PR bodies gain a section that
466
366
  re-submits replace, keeping human text. Options: `gitEditedByTrailers`, `gitCoAuthoredByTrailers`
467
- ([reference](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#definecanopyconfig-options)).
367
+ ([reference](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#definecanopyconfig-options)).
468
368
  - (int.110) Submit-commit trailers name every user who saved since the last submit commit, and
469
369
  the PR-body section every user who edited the branch, not only the submitter; settings commits
470
370
  (permissions, groups) carry the acting user's trailer.
@@ -473,7 +373,7 @@ a format check.
473
373
 
474
374
  **To adopt.** Scripts calling submit: save a change first, or handle the 400.
475
375
 
476
- ### Settings saves only push the settings branch — **breaking (config): `autoCreateSettingsPR` is removed**
376
+ #### Settings saves only push the settings branch — **breaking (config): `autoCreateSettingsPR` is removed**
477
377
 
478
378
  **What changed.** (int.93) The orphan settings branch can never get a PR, so groups and permissions
479
379
  saves only push the branch, and no failing PR task lands in System health.
@@ -482,20 +382,20 @@ saves only push the branch, and no failing PR task lands in System health.
482
382
 
483
383
  **Now deletable.** Filters that hid the failed settings-PR tasks.
484
384
 
485
- ### A schema the running code lacks costs one entry type, and holds the worker — **behaviour change**
385
+ #### A schema the running code lacks costs one entry type, and holds the worker — **behaviour change**
486
386
 
487
387
  **What changed.** (int.107) When synced content names an entry schema the running code lacks, only
488
388
  that entry type goes unavailable: the editor says so, and its entries answer 503 with
489
389
  `code: 'SCHEMA_UNAVAILABLE'`. Builds, static deploys and `generate-ai-content` still fail. The
490
390
  worker holds the base branch for up to 30 minutes while that lasts; see
491
- [New schemas wait for the editor deploy](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#new-schemas-wait-for-the-editor-deploy).
391
+ [New schemas wait for the editor deploy](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#new-schemas-wait-for-the-editor-deploy).
492
392
  Public types gain optional `unavailable` on `EntryTypeConfig` and `FlatSchemaItem`, optional
493
393
  `ApiResponse.code`, and a required `SchemaResolutionResult.issues`.
494
394
 
495
395
  **To adopt.** Code that builds a `SchemaResolutionResult` adds `issues`. A custom worker entrypoint
496
396
  can pass `schemaHoldMaxMs` to `CmsWorker`.
497
397
 
498
- ### `canopycms-cdk`: the worker drains before replacement and runs on-demand — **breaking (props): `spotMaxPrice` is removed; behaviour and cost change**
398
+ #### `canopycms-cdk`: the worker drains before replacement and runs on-demand — **breaking (props): `spotMaxPrice` is removed; behaviour and cost change**
499
399
 
500
400
  **What changed.** (int.108) The worker is one on-demand `t4g.nano` by default (about $3 a month,
501
401
  against spot's $1–2); a spot shortage could leave no worker: on a first deploy the editor's API answers 503 "CMS worker not
@@ -512,7 +412,7 @@ drain applies from the deploy after this one.
512
412
 
513
413
  **Now deletable.** Any override stripping `InstanceMarketOptions` from the worker's launch template.
514
414
 
515
- ### `canopycms-cdk`: the worker instance is hardened — **an existing stack upgrades in two deploys; behaviour and cost change**
415
+ #### `canopycms-cdk`: the worker instance is hardened — **an existing stack upgrades in two deploys; behaviour and cost change**
516
416
 
517
417
  **Upgrade an existing stack in two deploys:**
518
418
 
@@ -526,7 +426,7 @@ hang until the drain heartbeat lets termination continue. Its in-flight task is
526
426
  new stack needs no steps.
527
427
 
528
428
  **What changed.** (int.109) Detailed in
529
- [The worker instance](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#the-worker-instance): IMDSv2 with a hop limit of 1; the
429
+ [The worker instance](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#the-worker-instance): IMDSv2 with a hop limit of 1; the
530
430
  bundle is one file whose sha256 user data checks, readable as that one object; a file-system policy
531
431
  refusing clients without TLS or IAM, replacing the worker's
532
432
  `AmazonElasticFileSystemClientReadWriteAccess`; an encrypted gp3 root volume; a boot-time upgrade to
@@ -542,7 +442,7 @@ key, grant the Auto Scaling service-linked role on it. Pass `workerMaxInstanceLi
542
442
  **Now deletable.** Any override adding `MetadataOptions`, an encrypted root volume or an EFS
543
443
  `FileSystemPolicy`, or narrowing the worker role's asset-bucket grant.
544
444
 
545
- ### `canopycms-cdk`: the worker needs a state directory — **deploy the template before the bundle**
445
+ #### `canopycms-cdk`: the worker needs a state directory — **deploy the template before the bundle**
546
446
 
547
447
  **What changed.** (int.110) The worker's git keeps the GitHub credential in a private mirror
548
448
  under the unit's `StateDirectory=`, and the worker does not start without it.
@@ -552,14 +452,14 @@ new template before CI rolls an int.110 or later bundle; a parameter-only change
552
452
  the new unit, so the new bundle exits at start. A hand-installed unit needs
553
453
  `StateDirectory=canopy-worker` under `[Service]`.
554
454
 
555
- ### A poisoned `remote.git` re-clones itself
455
+ #### A poisoned `remote.git` re-clones itself
556
456
 
557
457
  **What changed.** (int.112) A `remote.git` missing its base branch is re-cloned unless an unpushed ref would be
558
458
  lost; the refusal names those refs. A tampered config still refuses and needs an operator.
559
459
 
560
460
  **To adopt.** Nothing.
561
461
 
562
- ### A failed or stopped worker says why — **behaviour change on the not-ready 503; new worker APIs**
462
+ #### A failed or stopped worker says why — **behaviour change on the not-ready 503; new worker APIs**
563
463
 
564
464
  **What changed.** (int.109) While the latest start's recorded failure stands, the prod not-ready 503
565
465
  is `WORKER_FAILED`: no `Retry-After`, the failure named to admins, account ids masked.
@@ -571,7 +471,7 @@ failure and exits non-zero on `selfStopped`, with a code outside `RestartPrevent
571
471
 
572
472
  **Now deletable.** A watchdog that restarts an idle worker process.
573
473
 
574
- ### `canopycms-cdk`: CI can roll the worker with a parameter, and alarm when it stops syncing — **new, opt-in**
474
+ #### `canopycms-cdk`: CI can roll the worker with a parameter, and alarm when it stops syncing — **new, opt-in**
575
475
 
576
476
  **What changed.** (int.109)
577
477
 
@@ -580,28 +480,28 @@ failure and exits non-zero on `selfStopped`, with a code outside `RestartPrevent
580
480
  with its hash, as `worker/dist/index.js` and `index.js.sha256`. The default, `'asset'`, is
581
481
  unchanged.
582
482
  - `CanopyCmsService` alarms when the worker logs no git sync for 30 minutes. See
583
- [Worker-down alarm](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#worker-down-alarm).
483
+ [Worker-down alarm](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#worker-down-alarm).
584
484
 
585
485
  **To adopt.** Optional. For parameter-only change sets, see
586
- [Rolling the worker from CI](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#rolling-the-worker-from-ci). For the alarm, pass
486
+ [Rolling the worker from CI](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#rolling-the-worker-from-ci). For the alarm, pass
587
487
  `alarmTopic`.
588
488
 
589
489
  **Now deletable.** A manual `cdk deploy` after each canopycms bump whose only purpose is moving the
590
490
  worker, and a hand-built alarm on the worker log group.
591
491
 
592
- ### `canopycms-cdk`: a `t4g.nano` worker boots reliably — **behaviour change: a deploy replaces the worker**
492
+ #### `canopycms-cdk`: a `t4g.nano` worker boots reliably — **behaviour change: a deploy replaces the worker**
593
493
 
594
494
  **What changed.** (int.112) The boot's `dnf upgrade` could be OOM-killed on every retry. A 1 GiB
595
495
  swap file now goes on first; an upgrade that still fails starts the worker unpatched and notifies
596
496
  `alarmTopic` if set; a retried step names itself when it gives up. See
597
- [The worker instance](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#the-worker-instance).
497
+ [The worker instance](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#the-worker-instance).
598
498
 
599
499
  **To adopt.** Deploy, which replaces the worker instance. The bundle needs none of this, so it is
600
500
  not Template first and the worker contract stays at 1.
601
501
 
602
502
  **Now deletable.** An instance type above `t4g.nano` chosen only to get the boot through.
603
503
 
604
- ### `canopycms-cdk`: the worker's log is root-owned — **security fix**
504
+ #### `canopycms-cdk`: the worker's log is root-owned — **security fix**
605
505
 
606
506
  **What changed.** (int.112) `/var/log/canopy-worker` and `worker.log` are root-owned, and the unit
607
507
  drops `LogsDirectory=`. systemd opens the log as root, following symlinks, before the sandbox
@@ -619,7 +519,7 @@ contract is unchanged. A hand-installed unit needs the same change:
619
519
 
620
520
  **Now deletable.** Nothing.
621
521
 
622
- ### The worker never pushes the base or default branch — **security fix**
522
+ #### The worker never pushes the base or default branch — **security fix**
623
523
 
624
524
  **What changed.** (int.112) The worker refuses to push, plainly or replacing history, to the base
625
525
  branch or GitHub's default branch, whatever a task asks, and fails the task permanently.
@@ -635,14 +535,14 @@ its bypass list. Set `CANOPYCMS_BASE_BRANCH` (the construct's `baseBranch`) expl
635
535
  it, the gateway protects only GitHub's default branch and your list; the worker still refuses the
636
536
  base it detected, warning when that base is neither.
637
537
 
638
- ### The worker's push logs say whether GitHub moved
538
+ #### The worker's push logs say whether GitHub moved
639
539
 
640
540
  **What changed.** (int.113) `Pushed … to GitHub` is logged only when the branch moved, saying
641
541
  where from; an unmoved branch logs at debug level. An unchanged settings branch is not pushed.
642
542
 
643
543
  **To adopt.** Nothing.
644
544
 
645
- ### The worker hands git its GitHub credential in a per-command file — **security fix; dev behaviour change**
545
+ #### The worker hands git its GitHub credential in a per-command file — **security fix; dev behaviour change**
646
546
 
647
547
  **What changed.** (int.113) The token leaves git URLs: each GitHub fetch and push reads it from a
648
548
  `0600` file named by `GIT_CONFIG_GLOBAL`, deleted afterwards. For those commands the file replaces
@@ -652,7 +552,7 @@ stops applying and no keychain credential is tried.
652
552
  **To adopt.** Nothing in production. In dev, move a global-config proxy or CA to `HTTPS_PROXY` or
653
553
  `GIT_SSL_CAINFO`.
654
554
 
655
- ### The worker moves objects between GitHub and `remote.git` as bundles
555
+ #### The worker moves objects between GitHub and `remote.git` as bundles
656
556
 
657
557
  **What changed.** (int.114) The worker moves objects between GitHub and
658
558
  `remote.git` as git bundles. A new instance's first sync fetches the whole repository from GitHub,
@@ -660,9 +560,9 @@ and operations may reuse GitHub fetches under 30 seconds old.
660
560
 
661
561
  **To adopt.** Nothing. Seeding `remote.git` stages a copy of the repository beside the gateway's
662
562
  mirror, so the root volume needs room for two
663
- ([details](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#the-worker-instance)).
563
+ ([details](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#the-worker-instance)).
664
564
 
665
- ### The worker follows a rebased or deleted GitHub branch
565
+ #### The worker follows a rebased or deleted GitHub branch
666
566
 
667
567
  **What changed.** (int.114) When a branch is rebased and force-pushed on GitHub, or deleted there, the
668
568
  worker now updates or deletes its own copy of it instead of warning about it on every sync. A rebased
@@ -671,7 +571,7 @@ health lists it with what to do.
671
571
 
672
572
  **To adopt.** Nothing.
673
573
 
674
- ### `canopycms-cdk`: a worker bundle states the template it needs — **template first for the gate only**
574
+ #### `canopycms-cdk`: a worker bundle states the template it needs — **template first for the gate only**
675
575
 
676
576
  **What changed.** (int.112) The worker unit carries a contract version
677
577
  (`Environment=CANOPYCMS_WORKER_CONTRACT=<n>`). Parameter mode outputs it as `WorkerContract`, and
@@ -682,13 +582,13 @@ changes the template too (the fallback bundle's key and hash), so only the contr
682
582
  bundle-only roll is safe.
683
583
 
684
584
  **To adopt.** Parameter mode: gate bundle-only rolls on the contract
685
- ([recipe](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#rolling-the-worker-from-ci)). The gate refuses a stack without the
585
+ ([recipe](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#rolling-the-worker-from-ci)). The gate refuses a stack without the
686
586
  `WorkerContract` output, so `cdk deploy` the template once, which adds it. A hand-installed unit
687
587
  copies its `Environment=CANOPYCMS_WORKER_CONTRACT=` line from `worker/canopy-worker.service`.
688
588
 
689
589
  **Now deletable.** Deciding from the template diff whether a bundle-only roll is safe.
690
590
 
691
- ### `canopycms-cdk`: the worker holds no secret — **security fix; template first (contract 2)**
591
+ #### `canopycms-cdk`: the worker holds no secret — **security fix; template first (contract 2)**
692
592
 
693
593
  **What changed.** (int.114) The worker runs with no network and no secret. `canopy-github`, a gateway
694
594
  service under its own user with no view of EFS, holds the GitHub credential and the Clerk key, and
@@ -699,10 +599,10 @@ units from the bundle's contract: 2 gets the split, an older bundle the single u
699
599
  template, then roll the bundle; on an older template it stops with `template too old for this
700
600
  bundle`. Inspect EFS only through `sudo /opt/canopy-worker/canopy-inspect <command>`, never from a root shell. A hand install
701
601
  takes `worker/`'s three units and `gateway.env.example`
702
- ([steps](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#installing-the-units-by-hand)). After the first deploy, run the
703
- [checklist](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#first-deploy-checklist).
602
+ ([steps](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#installing-the-units-by-hand)). After the first deploy, run the
603
+ [checklist](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#first-deploy-checklist).
704
604
 
705
- ### `canopycms-cdk`: `CanopyCmsService.attachTo`, and editor response headers — **behaviour change if you frame the CMS**
605
+ #### `canopycms-cdk`: `CanopyCmsService.attachTo`, and editor response headers — **behaviour change if you frame the CMS**
706
606
 
707
607
  **What changed.** (int.94) `cmsService.attachTo(distribution, { viewerRequestFunction?, behaviorOverrides? })`
708
608
  adds the editor's behaviors (`/edit`, `/edit/*`, `/api/canopycms/*`) to a distribution you already
@@ -711,7 +611,7 @@ response headers policy. `CanopyCmsDistribution` sends the same headers: `frame-
711
611
  `X-Frame-Options: SAMEORIGIN`, `nosniff`, HSTS and `X-Robots-Tag: noindex`. Unless your app sends
712
612
  its own framing headers, no other origin can frame the editor or any page the CMS domain serves. Every such page is
713
613
  marked `noindex`. The editor's default preview is same-origin and unaffected. See
714
- [Serving the editor from a distribution you already own](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#serving-the-editor-from-a-distribution-you-already-own).
614
+ [Serving the editor from a distribution you already own](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#serving-the-editor-from-a-distribution-you-already-own).
715
615
 
716
616
  **To adopt.** If you wired the Function URL into your own distribution by hand, delete those
717
617
  behaviors and call `attachTo`, passing your CMS build's `assetPrefix` as `editorAssetPrefix` and a
@@ -723,12 +623,12 @@ gate, and read the synth warning if your distribution has custom error responses
723
623
  `x-forwarded-host` function made for them, and any response-headers policy added only to stop
724
624
  framing.
725
625
 
726
- ### `unauthenticatedStatus`: answer signed-out API calls with 419
626
+ #### `unauthenticatedStatus`: answer signed-out API calls with 419
727
627
 
728
628
  **To adopt.** (int.102) Set `unauthenticatedStatus: 419` if your pages sit behind HTTP Basic auth on
729
629
  the editor's origin; the editor still detects sign-out. Otherwise nothing.
730
630
 
731
- ### Auth plugins look users up in batches
631
+ #### Auth plugins look users up in batches
732
632
 
733
633
  **What changed.** (int.111) `AuthPlugin` gains an optional `getUsersMetadata(userIds)`, and the
734
634
  editor batches its user-badge lookups.
@@ -736,7 +636,7 @@ editor batches its user-badge lookups.
736
636
  **To adopt.** Nothing. A custom auth plugin may implement it; otherwise the server falls back to
737
637
  bounded single lookups.
738
638
 
739
- ### The CMS image builds a prod editor, and a mode mismatch blocks the editor — **behaviour change: a hand-built image can fail its build**
639
+ #### The CMS image builds a prod editor, and a mode mismatch blocks the editor — **behaviour change: a hand-built image can fail its build**
740
640
 
741
641
  **What changed.** (int.111) `Dockerfile.cms` defaults `NEXT_PUBLIC_CANOPY_MODE` to `prod`, not
742
642
  `dev`, and fails its build on any value but `prod` or `dev`. An editor built for the other mode than its
@@ -745,7 +645,7 @@ server runs now gets a blocking screen naming that variable, not a sign-in that
745
645
  **To adopt.** A hand-built image running a dev-mode server passes
746
646
  `--build-arg NEXT_PUBLIC_CANOPY_MODE=dev`.
747
647
 
748
- ### Prod detects an unset `defaultBaseBranch` instead of assuming `main` — **behaviour change: startup can fail**
648
+ #### Prod detects an unset `defaultBaseBranch` instead of assuming `main` — **behaviour change: startup can fail**
749
649
 
750
650
  **What changed.** (int.110) Unset, prod reads the base branch from the HEAD of the workspace's `remote.git`,
751
651
  which the worker points at the branch it uses. Before the worker creates `remote.git`, requests
@@ -759,7 +659,7 @@ does not follow a later change of GitHub's default branch, so set it before a cu
759
659
 
760
660
  **Now deletable.** A `defaultBaseBranch: 'main'` in a repository whose default branch is `main`.
761
661
 
762
- ### The prod workspace defaults to `/mnt/efs` — **behaviour change if you run prod without `CANOPYCMS_WORKSPACE_ROOT`**
662
+ #### The prod workspace defaults to `/mnt/efs` — **behaviour change if you run prod without `CANOPYCMS_WORKSPACE_ROOT`**
763
663
 
764
664
  **What changed.** (int.100) With `CANOPYCMS_WORKSPACE_ROOT` unset, prod mode keeps its branches,
765
665
  settings, task queue and auth cache under `/mnt/efs` instead of `/mnt/efs/workspace`
@@ -770,7 +670,7 @@ the worker, so CDK deployments are unaffected.
770
670
  `/mnt/efs/workspace` to keep your existing workspace, or move that directory's contents up to
771
671
  `/mnt/efs`.
772
672
 
773
- ### System health shows which build is running
673
+ #### System health shows which build is running
774
674
 
775
675
  **What changed.** (int.94) System health gains a Build section (canopycms version, source revision,
776
676
  worker version, media-storage state) and warns when API and worker versions differ.
@@ -783,7 +683,7 @@ worker version, media-storage state) and warns when API and worker versions diff
783
683
 
784
684
  **Now deletable.** Hand-rolled version or commit stamping, or a build-info endpoint.
785
685
 
786
- ### The AWS example workflow gains the template's triggers and dependency checks
686
+ #### The AWS example workflow gains the template's triggers and dependency checks
787
687
 
788
688
  **What changed.** (int.111) Like the one `init-deploy aws` writes, `examples/aws-deployment/deploy-cms.yml`
789
689
  now deploys on `next.config.*`, `middleware.ts` and `public/**` changes, and checks `canopycms` and
@@ -791,7 +691,7 @@ now deploys on `next.config.*`, `middleware.ts` and `public/**` changes, and che
791
691
 
792
692
  **To adopt.** If you copied it by hand, add those `paths:` and packages.
793
693
 
794
- ### `GET /admin/branch-health` scans for duplicate content IDs only on request — **behaviour change**
694
+ #### `GET /admin/branch-health` scans for duplicate content IDs only on request — **behaviour change**
795
695
 
796
696
  **What changed.** (int.111) The duplicate-ID scan runs only with `?duplicates=1`, under a 20 s
797
697
  budget. Each healthy entry then carries `duplicateIdScan` (`none`, `found` or `unknown`), replacing
@@ -799,7 +699,7 @@ budget. Each healthy entry then carries `duplicateIdScan` (`none`, `found` or `u
799
699
 
800
700
  **To adopt.** Scripts reading `duplicateContentIds`: pass `duplicates=1`, read `duplicateIdScan`.
801
701
 
802
- ### `canopycms-cdk`: `AssetSupport` serves images from S3 only — **breaking**
702
+ #### `canopycms-cdk`: `AssetSupport` serves images from S3 only — **breaking**
803
703
 
804
704
  **What changed.** (int.100) `/assets/*` and `/assets/t/*` come from the bucket alone, with an
805
705
  optional `replicaBucket` for 5xx failover. An unmaterialized URL is a 403; no transform Lambda or
@@ -816,7 +716,7 @@ opt-in and those props, and pass `replicaBucket` if you replicate `assets/`.
816
716
 
817
717
  **Now deletable.** Your `assets/t/` expiry rule, before leaving lazy mode.
818
718
 
819
- ### `collect-asset-refs` and `materialize-assets` store a build's images before release
719
+ #### `collect-asset-refs` and `materialize-assets` store a build's images before release
820
720
 
821
721
  **What changed.** (int.100) `canopycms collect-asset-refs <outDir>` writes the image keys a static
822
722
  build references to `<outDir>/canopy-asset-refs.json`. `canopycms materialize-assets --refs <file>`
@@ -831,7 +731,7 @@ Run `materialize-assets` before the release is served, with the S3 grants the RE
831
731
 
832
732
  **Now deletable.** A pre-release warm-up step that requests image URLs to get them transformed.
833
733
 
834
- ### The editor and its live preview load images through the signed-in asset route
734
+ #### The editor and its live preview load images through the signed-in asset route
835
735
 
836
736
  **What changed.** (int.100) The editor loads asset-store images from
837
737
  `GET /api/canopycms/assets/raw/…` under your `basePath`, which transforms on demand and, on S3,
@@ -848,7 +748,7 @@ same-origin or set `lazyPublicTransforms: true`.
848
748
 
849
749
  **Now deletable.** Code passing `assetBaseUrl` into the editor.
850
750
 
851
- ### `assetUrl` applies an image value's crop — **behaviour change for cropped images**
751
+ #### `assetUrl` applies an image value's crop — **behaviour change for cropped images**
852
752
 
853
753
  **What changed.** (int.100) `assetUrl`/`assetSrcSet` apply an `image` value's `crop`; `opts.crop`
854
754
  overrides.
@@ -857,7 +757,7 @@ overrides.
857
757
 
858
758
  **Now deletable.** Copying `value.crop` into `opts.crop`.
859
759
 
860
- ### `media.publicBaseUrl` is removed, and image limits are wider — **breaking (config)**
760
+ #### `media.publicBaseUrl` is removed, and image limits are wider — **breaking (config)**
861
761
 
862
762
  **What changed.** (int.100) Nothing read `media.publicBaseUrl`, and the strict `media` schema fails a
863
763
  config that sets it. Uploads go up to 24 MP (6000×4000), up from 16.7 MP. Widths are any integer up
@@ -869,9 +769,7 @@ asset URLs.
869
769
 
870
770
  **Now deletable.** Anything that only fed `publicBaseUrl`, and rounding widths to a multiple of 160.
871
771
 
872
- <!-- legacy:end -->
873
-
874
- ### AI content can withhold or redirect a link to an entry with no page
772
+ #### AI content can withhold or redirect a link to an entry with no page
875
773
 
876
774
  <!-- changeset: ai-reference-link-hook -->
877
775
 
@@ -889,12 +787,12 @@ one the exclusions leave out. A throw or any other return is logged and treated
889
787
  **To adopt.** If your site's routes, sitemap or bylines decide that some entries have no page,
890
788
  apply the same rule here, for example `referenceLink: (target, { body }) =>
891
789
  !body?.trim() ? false : undefined` in `defineAIContentConfig` from `canopycms/ai`. See
892
- [AI Content Configuration](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#ai-content-configuration).
790
+ [AI Content Configuration](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#ai-content-configuration).
893
791
 
894
792
  **Now deletable.** A `fieldTransforms` entry that re-renders a reference field only to drop or
895
793
  change its link.
896
794
 
897
- ### `toPlainText` takes an entry body and keeps prose after a leading `---` — **behaviour change**
795
+ #### `toPlainText` takes an entry body and keeps prose after a leading `---` — **behaviour change**
898
796
 
899
797
  <!-- changeset: to-plain-text-body-only -->
900
798
 
@@ -918,7 +816,7 @@ import { toPlainText } from 'canopycms/ai'
918
816
  const text = toPlainText(matter(file, {}).content)
919
817
  ```
920
818
 
921
- ### People without a name show "Name unavailable"
819
+ #### People without a name show "Name unavailable"
922
820
 
923
821
  <!-- changeset: people-without-a-name-show-name-unavailable -->
924
822
 
@@ -926,7 +824,7 @@ _First in int.115._
926
824
 
927
825
  **What changed.** The Clerk plugin no longer uses the Clerk user id as a user's name: a user with no full name or username has an empty name, and the editor shows "Name unavailable" wherever that person appears. Core and the editor also treat a blank name, or one equal to the user id, as no name: `/users/:userId`, `/users/batch` and the `users` lists on comments and branches return an empty `name` for that person. The users snapshot the worker writes follows the same rule.
928
826
 
929
- ### Branch access lists stay with admins and reviewers — **behaviour change**
827
+ #### Branch access lists stay with admins and reviewers — **behaviour change**
930
828
 
931
829
  <!-- changeset: branch-access-list-withheld-from-non-privileged -->
932
830
 
@@ -938,7 +836,7 @@ access updates) sends a caller who is not an admin or reviewer only the `access.
938
836
  `withheldAccess` field. A branch card's access list shows the people the viewer can name, then
939
837
  "and N others", instead of a row of "Name unavailable" avatars.
940
838
 
941
- ### A branch lockdown outranks the branch's access list — **security fix**
839
+ #### A branch lockdown outranks the branch's access list — **security fix**
942
840
 
943
841
  <!-- changeset: branch-lockdown-outranks-branch-acl -->
944
842
 
@@ -954,7 +852,7 @@ the editor's Submit and Withdraw controls follow the same rule.
954
852
  branch: those editors lose access. To give it back, set `managerOrAdminAllowed` to `false` in
955
853
  that branch's `branch.json`, and the access list decides again.
956
854
 
957
- ### The base-branch conflict notice stays until the branch merges — **behaviour change**
855
+ #### The base-branch conflict notice stays until the branch merges — **behaviour change**
958
856
 
959
857
  <!-- changeset: conflict-notice-persists-until-merge -->
960
858
 
@@ -964,10 +862,10 @@ _First in int.115._
964
862
  entry the base branch also changed, the "updated on the base branch" notice and the branch's
965
863
  conflict count now stay until the branch merges, so a reviewer still sees which entries replaced a
966
864
  base change. They used to clear on the next sync, about five minutes later. README's new [Drafts
967
- and Unpublishing](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#drafts-and-unpublishing) section covers long-lived branches and
865
+ and Unpublishing](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#drafts-and-unpublishing) section covers long-lived branches and
968
866
  unpublishing.
969
867
 
970
- ### The editor calls the content drawer "All content"
868
+ #### The editor calls the content drawer "All content"
971
869
 
972
870
  <!-- changeset: all-content-label -->
973
871
 
@@ -977,7 +875,7 @@ _First in int.115._
977
875
 
978
876
  **To adopt.** Nothing, unless your browser tests find either by the text "All entries".
979
877
 
980
- ### The Branches drawer shows one action per branch, and comments reply in place
878
+ #### The Branches drawer shows one action per branch, and comments reply in place
981
879
 
982
880
  <!-- changeset: branches-drawer-and-comment-replies -->
983
881
 
@@ -991,7 +889,7 @@ form has a "Field comments" button, so tests that find buttons by `/comment/i` m
991
889
  one; and the editor listens for Ctrl+Alt+M (⌘⌥M on macOS) on the whole document, taking it while a
992
890
  form is open.
993
891
 
994
- ### An object's Clear offers Undo, and refusals say why
892
+ #### An object's Clear offers Undo, and refusals say why
995
893
 
996
894
  <!-- changeset: clear-undo-and-clearer-refusals -->
997
895
 
@@ -1006,7 +904,7 @@ conflict in yellow; the API marks it with the new error code `SETTINGS_CHANGED`.
1006
904
  `sanitizeHref` returns its fallback for `undefined` or `null`, which a live preview's draft can
1007
905
  pass for a link field it doesn't hold yet, instead of throwing.
1008
906
 
1009
- ### The collection dialog says what saves at once
907
+ #### The collection dialog says what saves at once
1010
908
 
1011
909
  <!-- changeset: collection-dialog-says-what-saves -->
1012
910
 
@@ -1019,7 +917,7 @@ inline instead of as toasts.
1019
917
 
1020
918
  **To adopt.** Nothing, unless browser tests click the old buttons or wait for those toasts.
1021
919
 
1022
- ### Comments and branches name their people for every editor — **behaviour change on two APIs**
920
+ #### Comments and branches name their people for every editor — **behaviour change on two APIs**
1023
921
 
1024
922
  <!-- changeset: comments-and-branches-name-their-people -->
1025
923
 
@@ -1032,7 +930,7 @@ Your auth plugin answers through the `/users/batch` cache; a failed lookup leave
1032
930
 
1033
931
  **To adopt.** Nothing, unless you call either API yourself: read `data.users`.
1034
932
 
1035
- ### A date picker, a form header, and a toolbar that wraps
933
+ #### A date picker, a form header, and a toolbar that wraps
1036
934
 
1037
935
  <!-- changeset: date-picker-form-header-wrapping-toolbar -->
1038
936
 
@@ -1046,7 +944,7 @@ editor styles.
1046
944
 
1047
945
  **To adopt.** Nothing. Browser tests that typed into a `datetime-local` input pick a date instead.
1048
946
 
1049
- ### Editor colours reach 4.5:1 in light and dark mode
947
+ #### Editor colours reach 4.5:1 in light and dark mode
1050
948
 
1051
949
  <!-- changeset: editor-colours-reach-contrast-in-both-schemes -->
1052
950
 
@@ -1068,7 +966,7 @@ defaults.
1068
966
  **Now deletable.** Local overrides that darkened light buttons, badges, placeholders or input
1069
967
  borders for contrast.
1070
968
 
1071
- ### Editor contrast, and blocked buttons stay focusable
969
+ #### Editor contrast, and blocked buttons stay focusable
1072
970
 
1073
971
  <!-- changeset: editor-contrast-and-focusable-blocked-buttons -->
1074
972
 
@@ -1087,7 +985,7 @@ old "Reload File" or "Discard File Draft" use the new labels.
1087
985
 
1088
986
  **Now deletable.** Local overrides that darkened the same text or buttons.
1089
987
 
1090
- ### Editor focus rings and checked controls reach 3:1 for any brand
988
+ #### Editor focus rings and checked controls reach 3:1 for any brand
1091
989
 
1092
990
  <!-- changeset: editor-focus-ring-and-control-contrast -->
1093
991
 
@@ -1106,7 +1004,7 @@ their tint instead of the primary colour, and Markdown popups have a border in d
1106
1004
  text, such as the System Health panel's "corrupt metadata", now "Corrupt metadata". A
1107
1005
  `themeOverride` that sets `primaryShade` moves where the ring starts.
1108
1006
 
1109
- ### Editor requests time out instead of hanging
1007
+ #### Editor requests time out instead of hanging
1110
1008
 
1111
1009
  <!-- changeset: editor-requests-time-out -->
1112
1010
 
@@ -1122,7 +1020,7 @@ write in flight says why.
1122
1020
  **To adopt.** Nothing, unless browser tests hold an API response for longer than a deadline to
1123
1021
  observe a busy state; keep such holds under it.
1124
1022
 
1125
- ### Field editing: descriptions, list-card titles, and public/ images, continued
1023
+ #### Field editing: descriptions, list-card titles, and public/ images, continued
1126
1024
 
1127
1025
  <!-- changeset: list-card-titles-and-block-descriptions -->
1128
1026
 
@@ -1135,7 +1033,7 @@ takes its title from its first non-blank `string` field, then "Item N" (or its `
1135
1033
  "Team member N"). A block template's `description` now renders; see
1136
1034
  [List items and blocks collapse](#list-items-and-blocks-collapse-and-reorder-from-one-menu).
1137
1035
 
1138
- ### List items and blocks collapse, and reorder from one menu
1036
+ #### List items and blocks collapse, and reorder from one menu
1139
1037
 
1140
1038
  <!-- changeset: list-items-and-blocks-collapse -->
1141
1039
 
@@ -1152,7 +1050,7 @@ a second value) or "Add section".
1152
1050
 
1153
1051
  **Now deletable.** Template labels that repeated the template's description.
1154
1052
 
1155
- ### "Add entry" follows path permissions; admin panels and labels tidied
1053
+ #### "Add entry" follows path permissions; admin panels and labels tidied
1156
1054
 
1157
1055
  <!-- changeset: navigator-add-follows-path-access-and-ux-batch2-rest -->
1158
1056
 
@@ -1167,7 +1065,7 @@ labels such as "All entries", "Manage permissions" and "Create group" are now in
1167
1065
  Discarding an entry's changes toasts only for a read-only branch's hidden draft, where the form shows
1168
1066
  no change.
1169
1067
 
1170
- ### Quick open, a navigator filter, title-first new entries, and media search
1068
+ #### Quick open, a navigator filter, title-first new entries, and media search
1171
1069
 
1172
1070
  <!-- changeset: quick-open-navigator-filter-media-search -->
1173
1071
 
@@ -1183,7 +1081,7 @@ library searches, sorts and filters all its files, in a grid or a list.
1183
1081
  **To adopt.** Nothing, unless browser tests add an entry by typing only a slug: fill in "Title"
1184
1082
  too when the entry type has one.
1185
1083
 
1186
- ### A save never brings back a deleted or moved entry — **behaviour change**
1084
+ #### A save never brings back a deleted or moved entry — **behaviour change**
1187
1085
 
1188
1086
  <!-- changeset: save-never-recreates-deleted-entry -->
1189
1087
 
@@ -1196,7 +1094,7 @@ its new name with the changes still in the form, ready to save again. For a dele
1196
1094
  the changes and offers "Save as new entry", a deliberate create. This also covers an editor's own
1197
1095
  delete or rename that got no answer. Creating an entry is unchanged.
1198
1096
 
1199
- ### Submit names unsaved entries; unanswered writes recover more
1097
+ #### Submit names unsaved entries; unanswered writes recover more
1200
1098
 
1201
1099
  <!-- changeset: submit-warns-unsaved-and-unanswered-writes-recover -->
1202
1100
 
@@ -1212,7 +1110,7 @@ out). The save-outcome toasts name their
1212
1110
  entry. A lookup sent as a POST that times out says to try again. Removing a staged entry type
1213
1111
  from a new collection goes at once, with Undo in the dialog.
1214
1112
 
1215
- ### Unlabelled fields and block templates show their names as words
1113
+ #### Unlabelled fields and block templates show their names as words
1216
1114
 
1217
1115
  <!-- changeset: unlabelled-fields-read-as-words -->
1218
1116
 
@@ -1225,7 +1123,7 @@ block picker and the Remove toast.
1225
1123
  **To adopt.** Nothing, unless your browser tests find such a field by its raw name. Give it a
1226
1124
  `label` to choose the words yourself.
1227
1125
 
1228
- ### Stable releases follow the changes they carry, and ship their changelog
1126
+ #### Stable releases follow the changes they carry, and ship their changelog
1229
1127
 
1230
1128
  <!-- changeset: stable-releases-from-changesets -->
1231
1129
 
@@ -1238,7 +1136,7 @@ as int builds already do.
1238
1136
 
1239
1137
  **To adopt.** Nothing. With `0.1.0` and later, a `^0.1.0` range takes patches without action.
1240
1138
 
1241
- ### List items and blocks carry a `_key`, and field comments follow it — **behaviour change**
1139
+ #### List items and blocks carry a `_key`, and field comments follow it — **behaviour change**
1242
1140
 
1243
1141
  <!-- changeset: list-items-and-blocks-carry-keys -->
1244
1142
 
@@ -1261,7 +1159,7 @@ a `validateEntry` hook included, checks content against a strict schema, allow `
1261
1159
  items and blocks. Don't spread a list item onto a DOM element (`<li {...item}>`), or it renders
1262
1160
  a `_key` attribute. `_key` also suits React's `key` when rendering a list.
1263
1161
 
1264
- ### `stampOnPublish` dates a new entry at its first submit
1162
+ #### `stampOnPublish` dates a new entry at its first submit
1265
1163
 
1266
1164
  <!-- changeset: datetime-stamp-on-publish -->
1267
1165
 
@@ -1276,13 +1174,151 @@ editor carries an unsaved draft of a stamped entry onto its stamped version.
1276
1174
  The flag is refused off a top-level `datetime`, on `list: true`, and with `required: true`.
1277
1175
 
1278
1176
  **To adopt.** Add `stampOnPublish: true` to the date field your site sorts or labels by, such as
1279
- `{ name: 'publishedAt', type: 'datetime', stampOnPublish: true }`. See [Stamping a publication date](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#stamping-a-publication-date).
1177
+ `{ name: 'publishedAt', type: 'datetime', stampOnPublish: true }`. See [Stamping a publication date](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#stamping-a-publication-date).
1280
1178
 
1281
1179
  **Now deletable.** A build step or hook that writes a missing publication date into new entries.
1282
1180
 
1283
- ---
1181
+ #### A left activity rail and a docked content panel
1284
1182
 
1285
- ## Released
1183
+ <!-- changeset: editor-activity-rail -->
1184
+
1185
+ _First in 0.1.0._
1186
+
1187
+ **What changed.** A rail down the editor's left edge opens the content panel, quick open, the
1188
+ Branches and Comments drawers and the media library, with Settings and the account control at its
1189
+ foot. Comments open from the rail only, with the unresolved count on the button, and the media
1190
+ library has its own button instead of a Settings menu item. In a wide side-by-side window the
1191
+ content panel docks beside the preview and stays open as you move between entries. Its width and
1192
+ whether it is open survive a reload. Narrower than 1024px, or stacked, it lays over the form
1193
+ only, so the preview stays in view. Below 1024px the editor also starts stacked until you choose
1194
+ a layout. Notifications moved to the bottom-right corner, opposite the rail.
1195
+
1196
+ #### `editor.subtitle` removed; the tab names the open entry — **breaking (`editor.subtitle` removed)**
1197
+
1198
+ <!-- changeset: editor-subtitle-removed-tab-title -->
1199
+
1200
+ _First in 0.1.0._
1201
+
1202
+ **What changed.** `editor.subtitle` is no longer a config option, and `Editor` no longer takes
1203
+ `subtitle` or `siteSubtitle`: nothing has shown them since the top bar replaced the old header.
1204
+ While the editor is mounted, the browser tab reads "Entry label · Site title", or the site title
1205
+ with no entry open, and your page's own title comes back when it unmounts, unless a page navigated to has set its own.
1206
+
1207
+ **To adopt.** Delete `editor.subtitle` from your config; it was no longer shown.
1208
+
1209
+ #### The editor takes over the window — **breaking (`EditorPanes` export removed)**
1210
+
1211
+ <!-- changeset: editor-takes-over-the-window -->
1212
+
1213
+ _First in 0.1.0._
1214
+
1215
+ **What changed.** The editor renders as one full-window layer above your page, so a header or
1216
+ footer your root layout renders no longer pushes it down or makes the page scroll. While it is
1217
+ mounted, its sign-in and loading screens included, your layout's elements are hidden and out of
1218
+ the Tab order, and the page does not scroll; unmount restores them. The footer is gone: the version and an "About CanopyCMS" link are
1219
+ in the Settings menu. The two read-only banners sit in their own row under the header. The
1220
+ preview/form divider is now keyboard-resizable within the same 15–85% the split was always
1221
+ stored at, and `react-split-pane` is no longer a dependency.
1222
+ `EditorPanes`, `EditorPanesProps`, the `PaneLayout` type and the `SPLIT_PERCENT_*` and `DEFAULT_*_SPLIT_PERCENT` constants are
1223
+ no longer exported from `canopycms/client`. See
1224
+ [Your root layout](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#editor-customization).
1225
+
1226
+ **To adopt.** Nothing, unless you import `EditorPanes` or those constants: render `CanopyEditor`
1227
+ instead, which lays out its own panes. Hiding your site chrome on the editor route is now
1228
+ optional, but keeping analytics and global fetches out of it is still worthwhile.
1229
+
1230
+ **Now deletable.** A wrapper that drops the site header and footer on the editor route only to
1231
+ stop them shifting the editor.
1232
+
1233
+ #### The editor's top bar, with Save all — **behaviour change**
1234
+
1235
+ <!-- changeset: editor-top-bar-and-save-all -->
1236
+
1237
+ _First in 0.1.0._
1238
+
1239
+ **What changed.** The editor's header is a 48px top bar. Its breadcrumb's crumbs navigate: "All
1240
+ content" opens the content panel, and a collection crumb opens it on that collection. The open
1241
+ entry's crumb holds the entry menu, whose "Reload entry" is now "Refresh from server". A
1242
+ "Read-only" chip says why the entry or branch takes no edits. The branch pill shows the branch,
1243
+ its status and how many entries are unsaved, and its menu lists them; its "Reload all entries" is
1244
+ now "Refresh branch" and re-reads the entry list too. Beside Save, a caret offers "Save all", which
1245
+ saves every unsaved entry on the branch and names any it could not save, with the reason, keeping
1246
+ their changes. The site title and `editor.subtitle` no longer appear in the header; the rail's
1247
+ monogram carries the site title. Below 62em the breadcrumb takes its own row and the actions wrap, so no control is clipped. A content save refused because the
1248
+ branch is mid-rebase now carries `code: 'BRANCH_SYNCING'`.
1249
+
1250
+ **To adopt.** Nothing, unless your browser tests find the header by its old text: "Reload entry",
1251
+ "Reload all entries", "Recently modified", "No entry selected", or the file menu's "All content"
1252
+ item, which the breadcrumb's "All content" replaces.
1253
+
1254
+ #### Rename and delete from the entry menu; Save says "Saved" itself — **behaviour change**
1255
+
1256
+ <!-- changeset: entry-menu-rename-delete-inline-saved -->
1257
+
1258
+ _First in 0.1.0._
1259
+
1260
+ **What changed.** The open entry's menu, on its name in the top bar, offers "Rename…" and
1261
+ "Delete entry", through the same dialogs as the content panel's. On a locked branch both stay in
1262
+ the menu, blocked, with the reason; for an entry a path rule refuses they are hidden, as in the
1263
+ content panel. A Save reads "Saving…" on the button, then "Saved" for two seconds, announced to
1264
+ screen readers, instead of a green "Saved" toast; on a phone the button shows a spinner, then a
1265
+ check. Failures still show their toast or alert, and Save all its result. Save is the top bar's
1266
+ one filled button; "Submit for review" and "Withdraw from review" are tinted. From either menu,
1267
+ deleting the open entry leaves no entry open instead of opening the first, and renaming it keeps
1268
+ it open under its new name. The delete dialog names the entry.
1269
+
1270
+ **To adopt.** Nothing, unless your browser tests wait for a "Saved" toast after Save: wait for the
1271
+ save request's response, or for the Save button to read "Saved".
1272
+
1273
+ #### The preview has a toolbar with device widths, reload and the saved version
1274
+
1275
+ <!-- changeset: preview-toolbar -->
1276
+
1277
+ _First in 0.1.0._
1278
+
1279
+ **What changed.** A toolbar above the editor's preview shows the page's path on your site
1280
+ (without `editor.previewPrefix` or `?branch=`), which copies on click. Its width selector
1281
+ renders the page at Fit, Desktop (1280 px), Tablet (768 px) or Mobile (390 px): the iframe takes
1282
+ exactly that CSS width, so your site's own breakpoints apply, scaled down when the pane is
1283
+ narrower. The choice persists per browser. Reload reloads the preview, the same action as the
1284
+ "Live updates off" Retry. "Open saved version" opens the page's `?branch=` URL in a new tab,
1285
+ which shows the branch's last saved state, without unsaved edits. A preview error now sits
1286
+ between the toolbar and the page instead of over it, naming the field by its labels. A custom
1287
+ `renderPreview` gets no toolbar. `PreviewFrame` takes two new optional props, `iframeStyle` and
1288
+ `onReload`.
1289
+
1290
+ #### The preview toolbar holds the highlight toggle and maximizes the preview; the layout toggle moves to the rail
1291
+
1292
+ <!-- changeset: preview-toolbar-controls -->
1293
+
1294
+ _First in 0.1.0._
1295
+
1296
+ **What changed.** The highlight toggle moves from the strip at the editor's right edge into the
1297
+ preview toolbar, and the layout toggle into the rail, above Settings, so it works with any
1298
+ preview or none; the strip is gone. The layout button shows the current layout and switches to
1299
+ the other; while the preview is maximized it says to restore the preview first. Layout, preview
1300
+ width and highlights persist per browser. Maximize lays the preview over the preview and form
1301
+ area, without reloading it, and drafts keep reaching it. Restore brings the form back, and so
1302
+ does Esc unless focus is inside the page. Anything that needs the form restores it too: a click
1303
+ on a highlighted element, which scrolls to its field, opening the content panel over the form,
1304
+ or a save refused for field errors. Dragging the divider between preview and form shows the
1305
+ preview's width over it, and resizing the panes, by drag or key, switches the width to Fit.
1306
+ Admins with highlights on see an "N unmapped" chip when the page marks fields that don't exist
1307
+ in the entry. It explains what that means and points to the browser console, which names the
1308
+ nearest field of each. Editors see no such diagnostics. A custom `renderPreview`, and an entry
1309
+ with no preview page, get no toolbar.
1310
+
1311
+ #### Linear-time mount paths and push refusals, and a fixed message for non-Error gateway failures — **security fix**
1312
+
1313
+ <!-- changeset: codeql-release-alerts -->
1314
+
1315
+ _First in 0.1.0._
1316
+
1317
+ **What changed.** The AI export trims slashes from `AIContentConfig.mountPath`, and the worker
1318
+ and its GitHub gateway recognise GitHub's refusal of a workflow push, in time linear in their
1319
+ length; a long run of slashes or a repeated phrase took seconds before. When the gateway fails with
1320
+ a thrown value that is not an `Error`, the worker gets a fixed message and the gateway's log gets
1321
+ the value, scrubbed and capped, since it could hold a stack.
1286
1322
 
1287
1323
  ### 0.0.67
1288
1324
 
@@ -1296,7 +1332,7 @@ never ended. `canopycms-auth-clerk` skips it while the editor is mounted, and ex
1296
1332
  **To adopt.** Nothing for the editor, if your edit page uses `useClerkAuthConfig()` or
1297
1333
  `ClerkSignIn`. If your CMS build renders `<SignIn>`, `UserButton` or `OrganizationSwitcher` on your
1298
1334
  own pages, mount the hook as in
1299
- [Clerk components on your own pages](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/packages/canopycms-auth-clerk/README.md#clerk-components-on-your-own-pages).
1335
+ [Clerk components on your own pages](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/packages/canopycms-auth-clerk/README.md#clerk-components-on-your-own-pages).
1300
1336
  Your own Server Actions in the CMS build still 403 behind OAC.
1301
1337
 
1302
1338
  **Now deletable.** Any local patch of `window.__internal_onBeforeSetActive` (or 6.x's
@@ -1337,12 +1373,12 @@ fast naming the ARN, the field and the keys present, never a value.
1337
1373
  **To adopt.** Nothing, unless one of those secrets holds a JSON document: then set the matching
1338
1374
  prop. The scaffolded stack fills them from the repository _variables_
1339
1375
  `CANOPY_GITHUB_TOKEN_SECRET_JSON_FIELD` and `CLERK_SECRET_KEY_SECRET_JSON_FIELD`
1340
- ([why the prefix](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#repository-secrets-and-variables)); a stack scaffolded earlier
1376
+ ([why the prefix](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#repository-secrets-and-variables)); a stack scaffolded earlier
1341
1377
  adds the props to `infrastructure/bin/app.ts` and `infrastructure/lib/cms-stack.ts` and the two `env`
1342
1378
  lines to `.github/workflows/deploy-cms.yml`, or re-runs the generator and diffs. `cdk synth` refuses a `…JsonField` prop without its `…SecretArn`, and an ARN carrying the ECS `:KEY::` suffix
1343
1379
  (also in `secretsArns`). Don't use CDK's `secretValueFromJson`: it resolves the plaintext into the deployed
1344
1380
  resource's configuration, readable by anyone who can describe it. Only `CLERK_SECRET_KEY` goes through Secrets Manager; `CLERK_JWT_KEY` and the publishable
1345
- key are public material ([Security Model](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#security-model)).
1381
+ key are public material ([Security Model](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#security-model)).
1346
1382
 
1347
1383
  **Now deletable.** A wrapper that fetches the secret, parses it and re-exports one field into the
1348
1384
  worker's environment, such as a `jq` step in user data or a wrapper entrypoint around
@@ -1354,7 +1390,7 @@ worker's environment, such as a `jq` step in user data or a wrapper entrypoint a
1354
1390
  `githubToken` is not deprecated and stays the documented default; `canopycms` neither depends on
1355
1391
  nor imports `@octokit/auth-app`. Register one App per site, not one shared across repositories:
1356
1392
  anyone holding an App's key can mint a token for any of its installations
1357
- ([why](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/ARCHITECTURE.md#why-one-github-app-per-site-not-one-shared-across-an-organisation)).
1393
+ ([why](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/ARCHITECTURE.md#why-one-github-app-per-site-not-one-shared-across-an-organisation)).
1358
1394
 
1359
1395
  **To adopt with `canopycms-cdk`** (existing stacks need no edit). `CanopyCmsServiceProps` takes
1360
1396
  `githubAppId`, `githubAppInstallationId`, `githubAppPrivateKeySecretArn` and
@@ -1367,7 +1403,7 @@ anyone holding an App's key can mint a token for any of its installations
1367
1403
  `verify` checks an existing installation's permissions: run it, since an App missing a
1368
1404
  permission only shows up later, as `sync-failed`. `create` needs an interactive terminal and
1369
1405
  never edits an existing JSON secret (create one and point the JSON-field prop at it)
1370
- ([details](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#register-it-with-canopycms-init-github-app)).
1406
+ ([details](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#register-it-with-canopycms-init-github-app)).
1371
1407
  2. Set the three props and **remove `githubTokenSecretArn`** (with its JSON field). A partial set
1372
1408
  of the three is refused at synth, and so is an App alongside a token.
1373
1409
  3. In the generated workflow, store them as `CANOPY_GITHUB_APP_ID`,
@@ -1378,7 +1414,7 @@ anyone holding an App's key can mint a token for any of its installations
1378
1414
 
1379
1415
  The private key is ARN-only; passing the key itself is refused at synth. Its ARN joins the worker's
1380
1416
  IAM policy automatically, so you need not add it to `secretsArns`. See
1381
- [Authenticating as a GitHub App](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#authenticating-as-a-github-app).
1417
+ [Authenticating as a GitHub App](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#authenticating-as-a-github-app).
1382
1418
 
1383
1419
  ```bash
1384
1420
  canopycms init-github-app create -- \
@@ -1426,7 +1462,7 @@ downloads the `.pem` from the App's settings page.
1426
1462
 
1427
1463
  **What changed.** The worker re-reads a Secrets Manager secret (the GitHub token or the Clerk secret
1428
1464
  key) when the operation using it fails. Timings, costs and the store-before-revoke order are in
1429
- [Rotating a secret](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#rotating-a-secret).
1465
+ [Rotating a secret](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#rotating-a-secret).
1430
1466
 
1431
1467
  **To adopt.** Nothing for credentials from `*_SECRET_ARN`, which is every scaffolded deployment. A
1432
1468
  GitHub App private key is still read only at boot: store the new key, replace the instance, and
@@ -1480,12 +1516,12 @@ also check the bucket policy for its grant.
1480
1516
  5. `withCanopy()` adds sharp's libvips to Next's file tracing (not for a static export) and, on
1481
1517
  Next 16 and later, sets `turbopack: {}` when your config has neither `turbopack` nor your own
1482
1518
  `webpack` and it can read your Next version. A webpack build still fails its image transforms
1483
- ([Dual Build Support](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#dual-build-support)).
1519
+ ([Dual Build Support](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#dual-build-support)).
1484
1520
 
1485
1521
  **Breaking:** a stack setting `platform` on `fromImageAsset` without `architecture` gets an arm64
1486
1522
  function while `platform` still decides the image, so `Platform.LINUX_AMD64` builds an image the
1487
1523
  function cannot run. A stack setting neither moves to arm64 with a matching image, built under QEMU
1488
- on an x86 host ([Where the image is built](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#where-the-image-is-built)).
1524
+ on an x86 host ([Where the image is built](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#where-the-image-is-built)).
1489
1525
 
1490
1526
  **To adopt.**
1491
1527
 
@@ -1514,7 +1550,7 @@ reason; `platform` on `fromImageAsset`; and, with `withCanopy()`, a hand-written
1514
1550
 
1515
1551
  **What changed.** `media.uploadUrl` (s3 adapter; an absolute http(s) URL or a site-relative path)
1516
1552
  replaces the `url` that `beginUpload()` returns, leaving the presign's `fields` untouched, so
1517
- uploads can be same-origin. See [Media Configuration](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#media-configuration) for the
1553
+ uploads can be same-origin. See [Media Configuration](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#media-configuration) for the
1518
1554
  CloudFront behavior it expects.
1519
1555
 
1520
1556
  **To adopt.** Optional. Set it from an environment variable: a site-relative value works only where
@@ -1539,7 +1575,7 @@ needs `lazyPublicTransforms: true`) let you pass a deterministically named role,
1539
1575
  bucket's stack computes `arn:aws:iam::<account>:role/<name>` from literals instead of a
1540
1576
  `Fn::GetStackOutput` reference only the CDK CLI resolves. The constructs re-attach
1541
1577
  `AWSLambdaBasicExecutionRole` (and the VPC policy) that `lambda.Function` drops from a role you
1542
- pass. See [Cross-account asset bucket](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#cross-account-asset-bucket).
1578
+ pass. See [Cross-account asset bucket](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#cross-account-asset-bucket).
1543
1579
 
1544
1580
  **To adopt.** Optional:
1545
1581
 
@@ -1649,7 +1685,7 @@ public JWKS PEM, used only to verify signatures.
1649
1685
  to variables (Settings → Secrets and variables → Actions → Variables); `bin/app.ts` refuses the
1650
1686
  deploy at synth while it is unset. Check that `bin/app.ts` passes `CLERK_SECRET_KEY` as
1651
1687
  `clerkSecretKeySecretArn` (read by the worker), never in the Lambda's `environment`, unless you keep
1652
- `clerkMiddleware` ([Security Model](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/docs/deploying-to-aws.md#security-model)).
1688
+ `clerkMiddleware` ([Security Model](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/docs/deploying-to-aws.md#security-model)).
1653
1689
 
1654
1690
  #### `readByUrlPath` answers only where `listEntries` publishes — **breaking (routing)**
1655
1691
 
@@ -1748,7 +1784,7 @@ basePath: process.env.NEXT_PUBLIC_BASE_PATH,
1748
1784
  Then pass `baseUrl` to `assetUrl` only if Next serves `/assets` (local adapter, `next dev`, S3 with
1749
1785
  no distribution): `assetUrl(image, { width: 960, baseUrl: BASE_PATH })`. Assets on CloudFront via
1750
1786
  `AssetSupport` stay at the distribution root; pass no `baseUrl` (mount table under "Where `/assets`
1751
- is mounted" in [Media Configuration](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#media-configuration)). Two traps:
1787
+ is mounted" in [Media Configuration](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#media-configuration)). Two traps:
1752
1788
 
1753
1789
  - Don't pass a deployment `basePath` to `contentStaticParams({ basePath })`; that option filters by
1754
1790
  a nested route prefix and emits zero params, building green.
@@ -1907,7 +1943,7 @@ Under `exactOptionalPropertyTypes: true`, omit keys instead of assigning `undefi
1907
1943
  `withTrailingSlash` and `isAbsoluteUrl` from `canopycms/server`; `defineSeoFieldGroup()` from
1908
1944
  `canopycms`; `generateContentSitemap` and `entryToMetadata` from `canopycms-next`, bound on
1909
1945
  `createNextCanopyContext`'s result. See
1910
- [Sitemap and SEO Metadata](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#sitemap-and-seo-metadata). Every routable entry type is in
1946
+ [Sitemap and SEO Metadata](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#sitemap-and-seo-metadata). Every routable entry type is in
1911
1947
  the sitemap unless `exclude`d or `noindex`; one `noindex` predicate drives both robots metadata and
1912
1948
  sitemap exclusion. `trailingSlash` is never inferred. `lastModified` defaults to `updatedAt`, a
1913
1949
  filesystem mtime. An empty SEO field counts as unset. `siteUrl` must be absolute, colliding URLs are
@@ -1964,7 +2000,7 @@ underscore-prefixed files are skipped.
1964
2000
 
1965
2001
  **What changed.** New scaffolds write `defaultBranchAccess: 'deny'` and
1966
2002
  `defaultPathAccess: { read: 'allow' }`
1967
- ([Public read on server deployments](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#public-read-on-server-deployments)).
2003
+ ([Public read on server deployments](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#public-read-on-server-deployments)).
1968
2004
 
1969
2005
  **To adopt.** An older scaffold's `defaultBranchAccess: 'allow'` is wider than recommended;
1970
2006
  consider `'deny'`. If you deleted `defaultPathAccess: { read: 'allow' }`, anonymous visitors get no content until you
@@ -2017,7 +2053,7 @@ re-checked an entry's type (a `switch` on `meta.entryType` can merge them into o
2017
2053
  **What changed.** `BlockComponentRegistry<Blocks>` (types only, from `canopycms`; `BlockValueOf<Blocks, 'hero'>`
2018
2054
  gives one template's value type) requires exactly
2019
2055
  one component per block template name when written as an object literal. See
2020
- [Block Component Registries](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#block-component-registries) and
2056
+ [Block Component Registries](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#block-component-registries) and
2021
2057
  `apps/example1/app/components/PostView.tsx`.
2022
2058
 
2023
2059
  ```ts
@@ -2039,7 +2075,7 @@ templates against the schema.
2039
2075
 
2040
2076
  **What changed.** Spreading a `const` field array into several schemas, and nesting
2041
2077
  `defineInlineFieldGroup()` inside a block template, are documented
2042
- ([Reusable Field Fragments](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#reusable-field-fragments)); `defineFieldFragment()` is an
2078
+ ([Reusable Field Fragments](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#reusable-field-fragments)); `defineFieldFragment()` is an
2043
2079
  identity helper for them.
2044
2080
 
2045
2081
  ```ts
@@ -2058,7 +2094,7 @@ field by composing from the same `const` field object.
2058
2094
  A block template holding a `reference` field is a shared block; `read()`/`readByUrlPath()` resolve
2059
2095
  it, and listings do with
2060
2096
  [`resolveReferences`](#listings-resolve-references-and-a-resolved-reference-carries-urlpath-and-optionally-its-body-16--breaking-type-level)
2061
- (0.0.64). See [Shared / Referenced Blocks](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#shared--referenced-blocks).
2097
+ (0.0.64). See [Shared / Referenced Blocks](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#shared--referenced-blocks).
2062
2098
 
2063
2099
  **Now deletable.** A second `read()` in page code to unwrap a shared block's reference.
2064
2100
 
@@ -2080,7 +2116,7 @@ Not retro-documented, apart from these:
2080
2116
 
2081
2117
  #### The registry is keyed by entry-type name (0.0.42)
2082
2118
 
2083
- **What changed.** `createEntrySchemaRegistry` keys are the entry-type names that `.collection.json` files name in `entry.schema`, and `EntryTypesFromRegistry` derives the typed entry-type map from them. Schema-variable keys (`{ postSchema }`) still work but derive nothing; [README's registry convention](https://github.com/safeinsights/canopycms/blob/f8b50b30170954bf52fb670afe6796d2cb8fa275/README.md#convention-why-key-the-registry-by-entry-type-name) says when to keep them.
2119
+ **What changed.** `createEntrySchemaRegistry` keys are the entry-type names that `.collection.json` files name in `entry.schema`, and `EntryTypesFromRegistry` derives the typed entry-type map from them. Schema-variable keys (`{ postSchema }`) still work but derive nothing; [README's registry convention](https://github.com/safeinsights/canopycms/blob/39a755c116a52b6d16e0b09e9dde03c67a081754/README.md#convention-why-key-the-registry-by-entry-type-name) says when to keep them.
2084
2120
 
2085
2121
  **To adopt.**
2086
2122