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/README.md CHANGED
@@ -87,7 +87,6 @@ export default defineCanopyConfig({
87
87
  gitBotAuthorEmail: 'canopy@example.com',
88
88
  editor: {
89
89
  title: 'CanopyCMS Editor', // optional UI defaults
90
- subtitle: 'Edit content',
91
90
  theme: {
92
91
  colors: { brand: '#4f46e5' },
93
92
  },
@@ -10,6 +10,7 @@ import { getDefaultEntryType } from '../content-store.js';
10
10
  import { extractEntryTypeFromFilename } from '../content-id-index.js';
11
11
  import { RESTRICTED_REFERENCE_MARKER } from '../entry-schema.js';
12
12
  import { extractEntryLinkIds } from '../entry-link-resolver.js';
13
+ import { trimSlashes } from '../paths/normalize.js';
13
14
  import { getErrorMessage } from '../utils/error.js';
14
15
  import { flattenGroupFields } from '../utils/flatten-group-fields.js';
15
16
  import { resolveEntryTitle } from '../utils/title-field.js';
@@ -183,7 +184,8 @@ export async function decideReferenceLinks(referenceLink, entries, subjectOf, en
183
184
  * this export wrote for the target (`files`: content id to output path), under `mountPath`.
184
185
  */
185
186
  export function createReferenceRendering(idIndex, flatSchema, entryLinkUrl, markdownCopies, linkFor) {
186
- const mount = `/${markdownCopies.mountPath.replace(/^\/+|\/+$/g, '')}`.replace(/^\/$/, '');
187
+ const mountSegment = trimSlashes(markdownCopies.mountPath);
188
+ const mount = mountSegment === '' ? '' : `/${mountSegment}`;
187
189
  const entryTypeOf = (id) => {
188
190
  const location = typeof id === 'string' ? idIndex.findById(id) : null;
189
191
  if (!location?.collection)
@@ -194,9 +194,9 @@ const listAssetsHandler = async (ctx, _req, params) => {
194
194
  * `uploadedBy` records the FIRST uploader only: finalizeAsset dedups on content hash, so a second
195
195
  * person uploading an identical file gains no delete rights over it — benign (a 403 where they
196
196
  * expected success, never the reverse; see
197
- * .claude/future-tasks/asset-listing-cross-branch-exposure.md).
197
+ * safeinsights/canopycms#572).
198
198
  *
199
- * Coupled to the blob-GC follow-up in asset-review-followups.md: this permission is safe because
199
+ * Coupled to the blob-GC follow-up in safeinsights/canopycms#575: this permission is safe because
200
200
  * delete is a de-list, not a destroy — nothing another branch references breaks. If GC ever makes
201
201
  * delete destroy the blob, this needs a reference check too.
202
202
  */
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import type { ApiResponse } from './types.js';
3
- /** @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md. */
3
+ /** @internal No importer; deletion candidate in safeinsights/canopycms#725. */
4
4
  export interface MarkAsMergedParams {
5
5
  branch: string;
6
6
  }
@@ -514,7 +514,7 @@ export const deleteBranchHandler = async (ctx, req, params) => {
514
514
  // writableBranch/submittableBranch guards elsewhere use 403 for the same
515
515
  // "protected" category (tracked as a deferred, deliberately out-of-scope
516
516
  // cleanup in
517
- // .claude/future-tasks/protected-branch-followup-cleanups.md); extending
517
+ // safeinsights/canopycms#781); extending
518
518
  // this check to 'approved' keeps it consistent with its own immediate
519
519
  // neighbour rather than introducing a fresh 400/403 split between two
520
520
  // arms of the same guard.
@@ -506,7 +506,7 @@ const writeContentHandler = async (gc, ctx, req, params, body) => {
506
506
  ok: false,
507
507
  status: 409,
508
508
  error: err.message,
509
- ...(err.outcome === 'unknown' ? { code: 'WRITE_OUTCOME_UNKNOWN' } : {}),
509
+ code: err.outcome === 'unknown' ? 'WRITE_OUTCOME_UNKNOWN' : 'BRANCH_SYNCING',
510
510
  };
511
511
  }
512
512
  // [F1] Also not an editor collision: this content ID is quarantined on two files
@@ -14,7 +14,7 @@ export declare const DEFAULT_ENTRIES_LIMIT = 50;
14
14
  * The generic 409 for a conflicting change to an entry, usually its version moving on since the
15
15
  * caller read it. Shared with the editor, whose own pre-save staleness check reports the same.
16
16
  */
17
- export declare const ENTRY_CHANGED_MESSAGE = "This entry changed since you opened it. Reload to see the latest version (your unsaved edits will be lost).";
17
+ export declare const ENTRY_CHANGED_MESSAGE = "This entry changed since you opened it. Choose \"Refresh from server\" to see the latest version (your unsaved edits will be lost).";
18
18
  /**
19
19
  * The 409 for an update whose entry is gone: deleted or moved since the caller read it (API code
20
20
  * `ENTRY_GONE`). The save is refused rather than re-creating the entry; re-creating it is a create
@@ -14,7 +14,7 @@ export const DEFAULT_ENTRIES_LIMIT = 50;
14
14
  * The generic 409 for a conflicting change to an entry, usually its version moving on since the
15
15
  * caller read it. Shared with the editor, whose own pre-save staleness check reports the same.
16
16
  */
17
- export const ENTRY_CHANGED_MESSAGE = 'This entry changed since you opened it. Reload to see the latest version (your unsaved edits will be lost).';
17
+ export const ENTRY_CHANGED_MESSAGE = 'This entry changed since you opened it. Choose "Refresh from server" to see the latest version (your unsaved edits will be lost).';
18
18
  /**
19
19
  * The 409 for an update whose entry is gone: deleted or moved since the caller read it (API code
20
20
  * `ENTRY_GONE`). The save is refused rather than re-creating the entry; re-creating it is a create
@@ -5,7 +5,7 @@ import type { LogicalPath, PhysicalPath, Slug, ContentId } from '../paths/types.
5
5
  /**
6
6
  * Summary of an entry type for client display.
7
7
  * Simplified from EntryTypeConfig - doesn't include full field definitions.
8
- * @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md.
8
+ * @internal No importer; deletion candidate in safeinsights/canopycms#725.
9
9
  */
10
10
  export interface EntryTypeSummary {
11
11
  name: string;
@@ -28,7 +28,7 @@ export interface CollectionItem {
28
28
  exists?: boolean;
29
29
  canEdit?: boolean;
30
30
  }
31
- /** @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md. */
31
+ /** @internal No importer; deletion candidate in safeinsights/canopycms#725. */
32
32
  export interface ListEntriesParams {
33
33
  branch: string;
34
34
  collection?: LogicalPath;
@@ -154,10 +154,10 @@ declare const batchGetUserMetadataBodySchema: z.ZodObject<{
154
154
  userIds: string[];
155
155
  }>;
156
156
  export type UpdatePermissionsBody = z.infer<typeof updatePermissionsBodySchema>;
157
- /** @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md. */
157
+ /** @internal No importer; deletion candidate in safeinsights/canopycms#725. */
158
158
  export type SearchUsersParams = z.infer<typeof searchUsersParamsSchema>;
159
159
  export type BatchGetUserMetadataBody = z.infer<typeof batchGetUserMetadataBodySchema>;
160
- /** @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md. */
160
+ /** @internal No importer; deletion candidate in safeinsights/canopycms#725. */
161
161
  export type GetUserMetadataParams = z.infer<typeof getUserMetadataParamsSchema>;
162
162
  /**
163
163
  * Exported routes for router registration
@@ -63,6 +63,7 @@ export interface ApiResponse<TData = unknown> {
63
63
  * server runs (a 412, see operating-mode/editor-mode-check.ts); only a rebuild fixes it.
64
64
  * `SETTINGS_CHANGED`: a groups or permissions save named a version the file has moved on from (a
65
65
  * 409, see `SettingsVersionConflictError`); nothing was saved, and a reload shows the other edit.
66
- * The other settings 409, a busy file lock, carries no code.
66
+ * The other settings 409, a busy file lock, carries no code. `BRANCH_SYNCING`: an entry save was
67
+ * refused because the branch is mid-rebase (a 409); nothing was saved, and a retry soon can pass.
67
68
  */
68
- export type ApiErrorCode = 'SCHEMA_UNAVAILABLE' | 'WORKER_FAILED' | 'WRITE_OUTCOME_UNKNOWN' | 'ENTRY_GONE' | 'EDITOR_MODE_MISMATCH' | 'SETTINGS_CHANGED';
69
+ export type ApiErrorCode = 'SCHEMA_UNAVAILABLE' | 'WORKER_FAILED' | 'WRITE_OUTCOME_UNKNOWN' | 'ENTRY_GONE' | 'EDITOR_MODE_MISMATCH' | 'SETTINGS_CHANGED' | 'BRANCH_SYNCING';
package/dist/cli/cli.js CHANGED
@@ -1223,7 +1223,7 @@ CanopyCMS worker run-once (mode: ${mode}, auth: ${authMode})
1223
1223
  }
1224
1224
  console.log("\nDone");
1225
1225
  }
1226
- var __defProp2, __getOwnPropNames2, __esm2, __export2, active, init_logger, scalarFieldTypes, markdownFieldTypes, primitiveFieldTypes, fieldTypes, init_types, SAFE_HTML_TAGS, COMPONENT_NAME, MDX_BINDINGS, FORBIDDEN_ATTRIBUTES, init_mdx_allowlist, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, mdxPropAllowSchema, mdxAllowlistSchema, markdownFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, SENTINEL_BASE, DOT_SEGMENT, init_sanitize_href, uploadTargetUrlSchema, previewPrefixSchema, init_url, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, basePathSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, init_config2, init_error2, init_fs2, templates_exports, __dirname, TEMPLATES_DIR, SCAFFOLD_STACK_NAME, init_templates, project_detect_exports, COMMANDS, MAX_WORKSPACE_DEPTH, PLACEHOLDER_GITHUB_REPO, CDK_DEPENDENCIES, init_project_detect, task_queue_config_exports, init_task_queue_config, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, cms_task_queue_exports, debugLogger, cmsTaskQueueLogger, init_cms_task_queue, ProdClientSafeStrategy, DevClientSafeStrategy, warned, VALID_DEPLOYMENT_NAME, isValidDeploymentName, ProdStrategy, DevStrategy, strategyCache;
1226
+ var __defProp2, __getOwnPropNames2, __esm2, __export2, active, init_logger, scalarFieldTypes, markdownFieldTypes, primitiveFieldTypes, fieldTypes, init_types, SAFE_HTML_TAGS, COMPONENT_NAME, MDX_BINDINGS, FORBIDDEN_ATTRIBUTES, init_mdx_allowlist, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, mdxPropAllowSchema, mdxAllowlistSchema, markdownFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, SENTINEL_BASE, DOT_SEGMENT, init_sanitize_href, uploadTargetUrlSchema, previewPrefixSchema, init_url, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, basePathSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, init_config2, init_error2, init_fs2, templates_exports, __dirname, TEMPLATES_DIR, SCAFFOLD_STACK_NAME, init_templates, project_detect_exports, COMMANDS, MAX_WORKSPACE_DEPTH, PLACEHOLDER_GITHUB_REPO, CDK_DEPENDENCIES, init_project_detect, task_queue_config_exports, init_task_queue_config, envFlag, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, cms_task_queue_exports, debugLogger, cmsTaskQueueLogger, init_cms_task_queue, ProdClientSafeStrategy, DevClientSafeStrategy, warned, VALID_DEPLOYMENT_NAME, isValidDeploymentName, ProdStrategy, DevStrategy, strategyCache;
1227
1227
  var init_init = __esm({
1228
1228
  "dist/cli/init.js"() {
1229
1229
  "use strict";
@@ -1603,7 +1603,6 @@ var init_init = __esm({
1603
1603
  });
1604
1604
  editorConfigSchema = z5.object({
1605
1605
  title: z5.string().optional(),
1606
- subtitle: z5.string().optional(),
1607
1606
  theme: z5.unknown().optional(),
1608
1607
  previewBase: z5.record(z5.union([z5.string(), z5.literal(false)])).optional(),
1609
1608
  previewPrefix: previewPrefixSchema.optional(),
@@ -1793,6 +1792,7 @@ var init_init = __esm({
1793
1792
  init_debug = __esm2({
1794
1793
  "dist/utils/debug.js"() {
1795
1794
  "use strict";
1795
+ envFlag = (name) => typeof process !== "undefined" && process.env[name] === "true";
1796
1796
  LOG_LEVELS = {
1797
1797
  DEBUG: 0,
1798
1798
  INFO: 1,
@@ -1805,7 +1805,7 @@ var init_init = __esm({
1805
1805
  this.options = options;
1806
1806
  }
1807
1807
  shouldLog(level) {
1808
- const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
1808
+ const enabled = this.options.enabled ?? envFlag("CANOPYCMS_DEBUG");
1809
1809
  if (!enabled)
1810
1810
  return false;
1811
1811
  const minLevel = this.options.minLevel ?? "DEBUG";
@@ -1881,7 +1881,7 @@ var init_init = __esm({
1881
1881
  }
1882
1882
  };
1883
1883
  testLogger = createDebugLogger({
1884
- enabled: process.env.E2E_DEBUG === "true",
1884
+ enabled: envFlag("E2E_DEBUG"),
1885
1885
  prefix: "E2E",
1886
1886
  throwOnError: false
1887
1887
  });
@@ -6212,7 +6212,8 @@ async function decideReferenceLinks(referenceLink, entries, subjectOf, entryLink
6212
6212
  return (id) => decisions.get(id);
6213
6213
  }
6214
6214
  function createReferenceRendering(idIndex, flatSchema, entryLinkUrl, markdownCopies, linkFor) {
6215
- const mount = `/${markdownCopies.mountPath.replace(/^\/+|\/+$/g, "")}`.replace(/^\/$/, "");
6215
+ const mountSegment = trimSlashes(markdownCopies.mountPath);
6216
+ const mount = mountSegment === "" ? "" : `/${mountSegment}`;
6216
6217
  const entryTypeOf = (id) => {
6217
6218
  const location = typeof id === "string" ? idIndex.findById(id) : null;
6218
6219
  if (!location?.collection)
@@ -7775,7 +7776,7 @@ async function generateAIContentCLI(options) {
7775
7776
  console.log(` Output: ${result.outputDir}
7776
7777
  `);
7777
7778
  }
7778
- var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env, forEachReferenceField, init_validation, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, LOG_LEVELS2, DebugLogger2, testLogger2, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, heldRoots, DEFAULT_SEO_FIELD_NAMES, asRecord, asText, asFlag, asImageUrl, asOgType, asTwitterCard, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned4, SchemaUnavailableError, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord2, RESERVED_SETTINGS_BRANCH_PREFIX, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, INLINE_LINK_TARGET, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
7779
+ var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env, forEachReferenceField, init_validation, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, envFlag2, LOG_LEVELS2, DebugLogger2, testLogger2, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, heldRoots, DEFAULT_SEO_FIELD_NAMES, asRecord, asText, asFlag, asImageUrl, asOgType, asTwitterCard, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned4, SchemaUnavailableError, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord2, RESERVED_SETTINGS_BRANCH_PREFIX, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, INLINE_LINK_TARGET, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
7779
7780
  var init_generate_ai_content = __esm({
7780
7781
  "dist/cli/generate-ai-content.js"() {
7781
7782
  "use strict";
@@ -8293,7 +8294,6 @@ var init_generate_ai_content = __esm({
8293
8294
  });
8294
8295
  editorConfigSchema2 = z6.object({
8295
8296
  title: z6.string().optional(),
8296
- subtitle: z6.string().optional(),
8297
8297
  theme: z6.unknown().optional(),
8298
8298
  previewBase: z6.record(z6.union([z6.string(), z6.literal(false)])).optional(),
8299
8299
  previewPrefix: previewPrefixSchema2.optional(),
@@ -8626,6 +8626,7 @@ var init_generate_ai_content = __esm({
8626
8626
  BLOCK_DISCRIMINATOR_KEYS = ["template", "_type"];
8627
8627
  BLOCK_STRUCTURAL_KEYS = new Set(BLOCK_DISCRIMINATOR_KEYS);
8628
8628
  init_item_keys();
8629
+ envFlag2 = (name) => typeof process !== "undefined" && process.env[name] === "true";
8629
8630
  LOG_LEVELS2 = {
8630
8631
  DEBUG: 0,
8631
8632
  INFO: 1,
@@ -8638,7 +8639,7 @@ var init_generate_ai_content = __esm({
8638
8639
  this.options = options;
8639
8640
  }
8640
8641
  shouldLog(level) {
8641
- const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
8642
+ const enabled = this.options.enabled ?? envFlag2("CANOPYCMS_DEBUG");
8642
8643
  if (!enabled)
8643
8644
  return false;
8644
8645
  const minLevel = this.options.minLevel ?? "DEBUG";
@@ -8714,7 +8715,7 @@ var init_generate_ai_content = __esm({
8714
8715
  }
8715
8716
  };
8716
8717
  testLogger2 = createDebugLogger2({
8717
- enabled: process.env.E2E_DEBUG === "true",
8718
+ enabled: envFlag2("E2E_DEBUG"),
8718
8719
  prefix: "E2E",
8719
8720
  throwOnError: false
8720
8721
  });
@@ -11067,7 +11068,7 @@ var init_generate_ai_content = __esm({
11067
11068
  * `data` from a process-global content-keyed cache, so `resolveSingleReferenceOnce`'s
11068
11069
  * top-level spread severs exactly one level and NESTED frontmatter objects alias across
11069
11070
  * occurrences, calls and requests. Pre-existing `read()` behavior, deliberately out of scope
11070
- * here; see `.claude/future-tasks/graymatter-cache-shared-frontmatter.md`.
11071
+ * here; see safeinsights/canopycms#708.
11071
11072
  *
11072
11073
  * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
11073
11074
  * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
@@ -11204,6 +11205,7 @@ var init_generate_ai_content = __esm({
11204
11205
  BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
11205
11206
  ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
11206
11207
  INLINE_LINK_TARGET = new RegExp(`\\]\\(entry:(${BASE58_CHAR}{12})(?:#[^\\s)>"'\\]]*)?(?:[ \\t]+"[^"\\n]*")?\\)`, "g");
11208
+ init_normalize();
11207
11209
  init_error3();
11208
11210
  init_field_traversal();
11209
11211
  init_error3();
@@ -15577,7 +15579,6 @@ var init_config4 = __esm({
15577
15579
  });
15578
15580
  editorConfigSchema3 = z14.object({
15579
15581
  title: z14.string().optional(),
15580
- subtitle: z14.string().optional(),
15581
15582
  theme: z14.unknown().optional(),
15582
15583
  previewBase: z14.record(z14.union([z14.string(), z14.literal(false)])).optional(),
15583
15584
  previewPrefix: previewPrefixSchema3.optional(),
@@ -16026,10 +16027,11 @@ var init_git = __esm({
16026
16027
  function createDebugLogger3(options) {
16027
16028
  return new DebugLogger3(options);
16028
16029
  }
16029
- var LOG_LEVELS3, DebugLogger3, testLogger3;
16030
+ var envFlag3, LOG_LEVELS3, DebugLogger3, testLogger3;
16030
16031
  var init_debug2 = __esm({
16031
16032
  "dist/utils/debug.js"() {
16032
16033
  "use strict";
16034
+ envFlag3 = (name) => typeof process !== "undefined" && process.env[name] === "true";
16033
16035
  LOG_LEVELS3 = {
16034
16036
  DEBUG: 0,
16035
16037
  INFO: 1,
@@ -16042,7 +16044,7 @@ var init_debug2 = __esm({
16042
16044
  this.options = options;
16043
16045
  }
16044
16046
  shouldLog(level) {
16045
- const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
16047
+ const enabled = this.options.enabled ?? envFlag3("CANOPYCMS_DEBUG");
16046
16048
  if (!enabled)
16047
16049
  return false;
16048
16050
  const minLevel = this.options.minLevel ?? "DEBUG";
@@ -16118,7 +16120,7 @@ var init_debug2 = __esm({
16118
16120
  }
16119
16121
  };
16120
16122
  testLogger3 = createDebugLogger3({
16121
- enabled: process.env.E2E_DEBUG === "true",
16123
+ enabled: envFlag3("E2E_DEBUG"),
16122
16124
  prefix: "E2E",
16123
16125
  throwOnError: false
16124
16126
  });
@@ -864,7 +864,6 @@ var init_config = __esm({
864
864
  });
865
865
  editorConfigSchema = z6.object({
866
866
  title: z6.string().optional(),
867
- subtitle: z6.string().optional(),
868
867
  theme: z6.unknown().optional(),
869
868
  previewBase: z6.record(z6.union([z6.string(), z6.literal(false)])).optional(),
870
869
  previewPrefix: previewPrefixSchema.optional(),
@@ -1407,6 +1406,7 @@ function isBlockStructuralKey(key) {
1407
1406
  init_item_keys();
1408
1407
 
1409
1408
  // dist/utils/debug.js
1409
+ var envFlag = (name) => typeof process !== "undefined" && process.env[name] === "true";
1410
1410
  var LOG_LEVELS = {
1411
1411
  DEBUG: 0,
1412
1412
  INFO: 1,
@@ -1419,7 +1419,7 @@ var DebugLogger = class {
1419
1419
  this.options = options;
1420
1420
  }
1421
1421
  shouldLog(level) {
1422
- const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
1422
+ const enabled = this.options.enabled ?? envFlag("CANOPYCMS_DEBUG");
1423
1423
  if (!enabled)
1424
1424
  return false;
1425
1425
  const minLevel = this.options.minLevel ?? "DEBUG";
@@ -1498,7 +1498,7 @@ function createDebugLogger(options) {
1498
1498
  return new DebugLogger(options);
1499
1499
  }
1500
1500
  var testLogger = createDebugLogger({
1501
- enabled: process.env.E2E_DEBUG === "true",
1501
+ enabled: envFlag("E2E_DEBUG"),
1502
1502
  prefix: "E2E",
1503
1503
  throwOnError: false
1504
1504
  });
@@ -5699,7 +5699,7 @@ var ContentStore = class {
5699
5699
  * `data` from a process-global content-keyed cache, so `resolveSingleReferenceOnce`'s
5700
5700
  * top-level spread severs exactly one level and NESTED frontmatter objects alias across
5701
5701
  * occurrences, calls and requests. Pre-existing `read()` behavior, deliberately out of scope
5702
- * here; see `.claude/future-tasks/graymatter-cache-shared-frontmatter.md`.
5702
+ * here; see safeinsights/canopycms#708.
5703
5703
  *
5704
5704
  * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
5705
5705
  * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
@@ -6571,6 +6571,7 @@ function splitByCodeRegions(text) {
6571
6571
  }
6572
6572
 
6573
6573
  // dist/ai/references.js
6574
+ init_normalize();
6574
6575
  init_error();
6575
6576
  init_field_traversal();
6576
6577
  function createReferenceTargetResolver(store) {
@@ -6676,7 +6677,8 @@ async function decideReferenceLinks(referenceLink, entries, subjectOf, entryLink
6676
6677
  return (id) => decisions.get(id);
6677
6678
  }
6678
6679
  function createReferenceRendering(idIndex, flatSchema, entryLinkUrl, markdownCopies, linkFor) {
6679
- const mount = `/${markdownCopies.mountPath.replace(/^\/+|\/+$/g, "")}`.replace(/^\/$/, "");
6680
+ const mountSegment = trimSlashes(markdownCopies.mountPath);
6681
+ const mount = mountSegment === "" ? "" : `/${mountSegment}`;
6680
6682
  const entryTypeOf = (id) => {
6681
6683
  const location = typeof id === "string" ? idIndex.findById(id) : null;
6682
6684
  if (!location?.collection)
package/dist/cli/init.js CHANGED
@@ -475,7 +475,6 @@ var init_config = __esm({
475
475
  });
476
476
  editorConfigSchema = z5.object({
477
477
  title: z5.string().optional(),
478
- subtitle: z5.string().optional(),
479
478
  theme: z5.unknown().optional(),
480
479
  previewBase: z5.record(z5.union([z5.string(), z5.literal(false)])).optional(),
481
480
  previewPrefix: previewPrefixSchema.optional(),
@@ -908,10 +907,11 @@ var init_task_queue_config = __esm({
908
907
  function createDebugLogger(options) {
909
908
  return new DebugLogger(options);
910
909
  }
911
- var LOG_LEVELS, DebugLogger, testLogger;
910
+ var envFlag, LOG_LEVELS, DebugLogger, testLogger;
912
911
  var init_debug = __esm({
913
912
  "dist/utils/debug.js"() {
914
913
  "use strict";
914
+ envFlag = (name) => typeof process !== "undefined" && process.env[name] === "true";
915
915
  LOG_LEVELS = {
916
916
  DEBUG: 0,
917
917
  INFO: 1,
@@ -924,7 +924,7 @@ var init_debug = __esm({
924
924
  this.options = options;
925
925
  }
926
926
  shouldLog(level) {
927
- const enabled = this.options.enabled ?? process.env.CANOPYCMS_DEBUG === "true";
927
+ const enabled = this.options.enabled ?? envFlag("CANOPYCMS_DEBUG");
928
928
  if (!enabled)
929
929
  return false;
930
930
  const minLevel = this.options.minLevel ?? "DEBUG";
@@ -1000,7 +1000,7 @@ var init_debug = __esm({
1000
1000
  }
1001
1001
  };
1002
1002
  testLogger = createDebugLogger({
1003
- enabled: process.env.E2E_DEBUG === "true",
1003
+ enabled: envFlag("E2E_DEBUG"),
1004
1004
  prefix: "E2E",
1005
1005
  throwOnError: false
1006
1006
  });
package/dist/client.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export type { CanopyClientConfig, EditorSignInProps } from './config/index.js';
2
2
  export type { CustomFieldRenderers, CustomFieldRenderProps } from './editor/FormRenderer.js';
3
- export * from './editor/EditorPanes.js';
4
3
  export * from './editor/EntryNavigator.js';
5
4
  export * from './editor/Editor.js';
6
5
  export { PreviewFrame } from './editor/PreviewFrame.js';
package/dist/client.js CHANGED
@@ -1,5 +1,4 @@
1
1
  'use client';
2
- export * from './editor/EditorPanes.js';
3
2
  export * from './editor/EntryNavigator.js';
4
3
  export * from './editor/Editor.js';
5
4
  export { PreviewFrame } from './editor/PreviewFrame.js';
@@ -75,7 +75,6 @@ export declare const CanopyConfigSchema: z.ZodObject<{
75
75
  danglingReferences: z.ZodOptional<z.ZodEnum<["error", "warn"]>>;
76
76
  editor: z.ZodOptional<z.ZodObject<{
77
77
  title: z.ZodOptional<z.ZodString>;
78
- subtitle: z.ZodOptional<z.ZodString>;
79
78
  theme: z.ZodOptional<z.ZodUnknown>;
80
79
  previewBase: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodLiteral<false>]>>>;
81
80
  previewPrefix: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
@@ -85,7 +84,6 @@ export declare const CanopyConfigSchema: z.ZodObject<{
85
84
  SignInComponent: z.ZodOptional<z.ZodType<import("react").ComponentType<EditorSignInProps>, z.ZodTypeDef, import("react").ComponentType<EditorSignInProps>>>;
86
85
  }, "strip", z.ZodTypeAny, {
87
86
  title?: string | undefined;
88
- subtitle?: string | undefined;
89
87
  theme?: unknown;
90
88
  previewBase?: Record<string, string | false> | undefined;
91
89
  previewPrefix?: string | undefined;
@@ -95,7 +93,6 @@ export declare const CanopyConfigSchema: z.ZodObject<{
95
93
  SignInComponent?: import("react").ComponentType<EditorSignInProps> | undefined;
96
94
  }, {
97
95
  title?: string | undefined;
98
- subtitle?: string | undefined;
99
96
  theme?: unknown;
100
97
  previewBase?: Record<string, string | false> | undefined;
101
98
  previewPrefix?: string | undefined;
@@ -158,7 +155,6 @@ export declare const CanopyConfigSchema: z.ZodObject<{
158
155
  danglingReferences?: "error" | "warn" | undefined;
159
156
  editor?: {
160
157
  title?: string | undefined;
161
- subtitle?: string | undefined;
162
158
  theme?: unknown;
163
159
  previewBase?: Record<string, string | false> | undefined;
164
160
  previewPrefix?: string | undefined;
@@ -214,7 +210,6 @@ export declare const CanopyConfigSchema: z.ZodObject<{
214
210
  danglingReferences?: "error" | "warn" | undefined;
215
211
  editor?: {
216
212
  title?: string | undefined;
217
- subtitle?: string | undefined;
218
213
  theme?: unknown;
219
214
  previewBase?: Record<string, string | false> | undefined;
220
215
  previewPrefix?: string | undefined;
@@ -50,7 +50,6 @@ const devConfigSchema = z.object({
50
50
  });
51
51
  const editorConfigSchema = z.object({
52
52
  title: z.string().optional(),
53
- subtitle: z.string().optional(),
54
53
  theme: z.unknown().optional(),
55
54
  previewBase: z.record(z.union([z.string(), z.literal(false)])).optional(),
56
55
  previewPrefix: previewPrefixSchema.optional(),
@@ -322,7 +322,6 @@ export interface EditorSignInProps {
322
322
  }
323
323
  export interface CanopyEditorConfig {
324
324
  title?: string;
325
- subtitle?: string;
326
325
  theme?: unknown;
327
326
  /**
328
327
  * Preview-pane routes for sites whose routes don't mirror their content tree; otherwise an entry
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Absolute path of the generation marker for a branch-clone root.
3
- * @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md.
3
+ * @internal No importer; deletion candidate in safeinsights/canopycms#725.
4
4
  */
5
5
  export declare function contentIndexGenerationPath(root: string): string;
6
6
  /**
@@ -20,7 +20,7 @@ import { SCHEMA_GENERATION_RESOURCE } from './branch-schema-cache.js';
20
20
  const RESOURCE = 'content-index';
21
21
  /**
22
22
  * Absolute path of the generation marker for a branch-clone root.
23
- * @internal No importer; deletion candidate in knip-no-importer-deletion-candidates.md.
23
+ * @internal No importer; deletion candidate in safeinsights/canopycms#725.
24
24
  */
25
25
  export function contentIndexGenerationPath(root) {
26
26
  return resourceGenerationPath(root, RESOURCE);
@@ -193,7 +193,7 @@ export async function listEntries(branchRoot, flatSchema, contentRootName, optio
193
193
  // One deliberate copy of the rule remains: `content-tree.ts`'s `defaultBuildPath`,
194
194
  // exported for adopters to extend and also handling the collection case
195
195
  // `computeEntryUrl` does not model. Folding it in is tracked in
196
- // `.claude/future-tasks/default-build-path-url-rule-copy.md`.
196
+ // safeinsights/canopycms#646.
197
197
  const urlPath = computeEntryUrl(entry.collectionPath, entry.slug, contentRootName);
198
198
  const raw = entry.data;
199
199
  const meta = {
@@ -115,7 +115,7 @@ export const createContentReader = (options) => {
115
115
  // deliberately does not resolve the former, so a caller linking `path` linked to a 404.
116
116
  // Same index decision as computeEntryUrl/listEntries/buildPreviewSrc, through the same
117
117
  // predicate. NOTE this builder still keeps its own content-root strip and its own encoding
118
- // rather than delegating wholesale -- see .claude/future-tasks/default-build-path-url-rule-copy.md.
118
+ // rather than delegating wholesale -- see safeinsights/canopycms#646.
119
119
  const encodedSlug = isIndexSlug(opts.slug) ? '' : encodeSlug(opts.slug);
120
120
  const url = encodedSlug ? `${trimmed}/${encodedSlug}` : trimmed || '/';
121
121
  return appendBranch(url);
@@ -552,7 +552,7 @@ export declare class ContentStore {
552
552
  * `data` from a process-global content-keyed cache, so `resolveSingleReferenceOnce`'s
553
553
  * top-level spread severs exactly one level and NESTED frontmatter objects alias across
554
554
  * occurrences, calls and requests. Pre-existing `read()` behavior, deliberately out of scope
555
- * here; see `.claude/future-tasks/graymatter-cache-shared-frontmatter.md`.
555
+ * here; see safeinsights/canopycms#708.
556
556
  *
557
557
  * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
558
558
  * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
@@ -1606,7 +1606,7 @@ export class ContentStore {
1606
1606
  * `data` from a process-global content-keyed cache, so `resolveSingleReferenceOnce`'s
1607
1607
  * top-level spread severs exactly one level and NESTED frontmatter objects alias across
1608
1608
  * occurrences, calls and requests. Pre-existing `read()` behavior, deliberately out of scope
1609
- * here; see `.claude/future-tasks/graymatter-cache-shared-frontmatter.md`.
1609
+ * here; see safeinsights/canopycms#708.
1610
1610
  *
1611
1611
  * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
1612
1612
  * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
@@ -15,7 +15,7 @@
15
15
  * Divergence is a CONDITION, not an event: it holds until someone runs `sync push`, so the report
16
16
  * must not repeat into a scrolling request log (`reportOnce`) yet must be hard to scroll past (the
17
17
  * gutter block). A log cannot hold a condition; see
18
- * `.claude/future-tasks/dev-divergence-in-app-surface.md` for the in-app surface that would
18
+ * safeinsights/canopycms#654 for the in-app surface that would
19
19
  * replace the terminal as its primary home.
20
20
  *
21
21
  * All logic lives here; framework adapters call startDevContentWatcher() once at dev startup (see
@@ -15,7 +15,7 @@
15
15
  * Divergence is a CONDITION, not an event: it holds until someone runs `sync push`, so the report
16
16
  * must not repeat into a scrolling request log (`reportOnce`) yet must be hard to scroll past (the
17
17
  * gutter block). A log cannot hold a condition; see
18
- * `.claude/future-tasks/dev-divergence-in-app-surface.md` for the in-app surface that would
18
+ * safeinsights/canopycms#654 for the in-app surface that would
19
19
  * replace the terminal as its primary home.
20
20
  *
21
21
  * All logic lives here; framework adapters call startDevContentWatcher() once at dev startup (see
@@ -68,6 +68,8 @@ export interface BranchManagerProps {
68
68
  user?: UserContext;
69
69
  /** The branch the editor has open: listed first, marked, and offered its workflow action rather than Open. */
70
70
  currentBranchName?: string;
71
+ /** Why the open branch's Submit or Withdraw must wait, such as a Save all still writing it. */
72
+ currentWorkflowBlockedReason?: string;
71
73
  onSelect?: (name: string) => void;
72
74
  /** Awaited before the spinner starts, so a confirmation it shows never reads as a hung create. `false` abandons the create. */
73
75
  onBeforeCreate?: () => Promise<boolean>;
@@ -129,10 +129,11 @@ const isReviewerOfOthersSubmission = (b, user) => !!user &&
129
129
  * shows its workflow action, or Request changes to a reviewer looking at someone else's
130
130
  * submission; the others show Open, which the current one never needs. No action is shown twice.
131
131
  */
132
- const BranchCard = ({ branch: b, isCurrent, user, onSelect, onSubmit, onWithdraw, onRequestChanges, onDelete, onGetUserMetadata, onGetOwnerMetadata = onGetUserMetadata, }) => {
132
+ const BranchCard = ({ branch: b, isCurrent, workflowBlockedReason, user, onSelect, onSubmit, onWithdraw, onRequestChanges, onDelete, onGetUserMetadata, onGetOwnerMetadata = onGetUserMetadata, }) => {
133
133
  const statusBadge = branchStatusPresentation(b.status, b.isProtected);
134
134
  const perms = getBranchPermissions(b, user);
135
- const workflow = workflowAction(b, perms, onSubmit, onWithdraw);
135
+ const action = workflowAction(b, perms, onSubmit, onWithdraw);
136
+ const workflow = action && workflowBlockedReason ? { ...action, blockedReason: workflowBlockedReason } : action;
136
137
  const userIsAdmin = isAdmin(user?.groups);
137
138
  const requestChanges = isReviewer(user?.groups) && b.status === 'submitted'
138
139
  ? {
@@ -167,7 +168,7 @@ const BranchCard = ({ branch: b, isCurrent, user, onSelect, onSubmit, onWithdraw
167
168
  ? 'This branch is in review. Withdraw it from review before deleting it.'
168
169
  : undefined, onClick: () => onDelete?.(b.name), children: "Delete branch" })] }))] })] }))] })] }) }));
169
170
  };
170
- export const BranchManager = ({ branches, mode, user, currentBranchName, onSelect, onBeforeCreate, onCreate, onDelete, onSubmit, onWithdraw, onRequestChanges, onClose: _, comments = [], currentUserId, canResolve = false, onAddComment, onResolveThread, highlightThreadId, onGetUserMetadata, onGetOwnerMetadata, onGetCommentUserMetadata, }) => {
171
+ export const BranchManager = ({ branches, mode, user, currentBranchName, currentWorkflowBlockedReason, onSelect, onBeforeCreate, onCreate, onDelete, onSubmit, onWithdraw, onRequestChanges, onClose: _, comments = [], currentUserId, canResolve = false, onAddComment, onResolveThread, highlightThreadId, onGetUserMetadata, onGetOwnerMetadata, onGetCommentUserMetadata, }) => {
171
172
  const supportsBranching = clientOperatingStrategy(mode).supportsBranching();
172
173
  const [showCreateForm, setShowCreateForm] = useState(false);
173
174
  const [newBranchName, setNewBranchName] = useState('');
@@ -207,5 +208,5 @@ export const BranchManager = ({ branches, mode, user, currentBranchName, onSelec
207
208
  ...branches.filter((b) => b.name !== currentBranchName),
208
209
  ]
209
210
  : branches;
210
- return (_jsxs(Stack, { h: "100%", style: { display: 'flex', flexDirection: 'column' }, gap: 0, "data-testid": "branch-manager", children: [currentUserId && onAddComment && onResolveThread && (_jsx(Stack, { pt: "sm", children: _jsx(BranchComments, { comments: comments, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, onGetUserMetadata: onGetCommentUserMetadata }) })), !!supportsBranching && (_jsxs(Stack, { gap: "sm", pt: "sm", children: [_jsx(Button, { variant: "light", size: "sm", fullWidth: true, onClick: () => setShowCreateForm(!showCreateForm), disabled: creating, "data-testid": "create-branch-button", children: showCreateForm ? 'Cancel' : `${EDITOR_ACTIONS.newBranch}…` }), _jsx(Collapse, { in: showCreateForm, children: _jsx(Paper, { withBorder: true, p: "md", children: _jsxs(Stack, { gap: "sm", children: [_jsx(TextInput, { label: "Branch name", placeholder: "feature/my-branch", value: newBranchName, onChange: (e) => setNewBranchName(e.target.value), required: true, disabled: creating, "data-testid": "branch-name-input" }), _jsx(TextInput, { label: "Title", placeholder: "Brief description", value: newBranchTitle, onChange: (e) => setNewBranchTitle(e.target.value), disabled: creating, "data-testid": "branch-title-input" }), _jsx(Textarea, { label: "Description", placeholder: "Detailed description of the changes", value: newBranchDescription, onChange: (e) => setNewBranchDescription(e.target.value), minRows: 2, disabled: creating, "data-testid": "branch-description-textarea" }), _jsx(Button, { size: "sm", onClick: handleCreate, disabled: !newBranchName.trim(), loading: creating, fullWidth: true, "data-testid": "create-branch-submit", children: "Create branch" })] }) }) })] })), _jsx(ScrollArea, { style: { flex: 1 }, pb: "md", children: branches.length === 0 ? (_jsx(Text, { size: "sm", c: "dimmed", py: "md", children: !supportsBranching ? 'Branch management is not available.' : 'No branches available.' })) : (_jsx(Stack, { gap: "sm", children: orderedBranches.map((b) => (_jsx(BranchCard, { branch: b, isCurrent: b.name === currentBranchName, user: user, onSelect: onSelect, onSubmit: onSubmit, onWithdraw: onWithdraw, onRequestChanges: onRequestChanges, onDelete: onDelete, onGetUserMetadata: onGetUserMetadata, onGetOwnerMetadata: onGetOwnerMetadata }, b.name))) })) })] }));
211
+ return (_jsxs(Stack, { h: "100%", style: { display: 'flex', flexDirection: 'column' }, gap: 0, "data-testid": "branch-manager", children: [currentUserId && onAddComment && onResolveThread && (_jsx(Stack, { pt: "sm", children: _jsx(BranchComments, { comments: comments, currentUserId: currentUserId, canResolve: canResolve, onAddComment: onAddComment, onResolveThread: onResolveThread, highlightThreadId: highlightThreadId, onGetUserMetadata: onGetCommentUserMetadata }) })), !!supportsBranching && (_jsxs(Stack, { gap: "sm", pt: "sm", children: [_jsx(Button, { variant: "light", size: "sm", fullWidth: true, onClick: () => setShowCreateForm(!showCreateForm), disabled: creating, "data-testid": "create-branch-button", children: showCreateForm ? 'Cancel' : `${EDITOR_ACTIONS.newBranch}…` }), _jsx(Collapse, { in: showCreateForm, children: _jsx(Paper, { withBorder: true, p: "md", children: _jsxs(Stack, { gap: "sm", children: [_jsx(TextInput, { label: "Branch name", placeholder: "feature/my-branch", value: newBranchName, onChange: (e) => setNewBranchName(e.target.value), required: true, disabled: creating, "data-testid": "branch-name-input" }), _jsx(TextInput, { label: "Title", placeholder: "Brief description", value: newBranchTitle, onChange: (e) => setNewBranchTitle(e.target.value), disabled: creating, "data-testid": "branch-title-input" }), _jsx(Textarea, { label: "Description", placeholder: "Detailed description of the changes", value: newBranchDescription, onChange: (e) => setNewBranchDescription(e.target.value), minRows: 2, disabled: creating, "data-testid": "branch-description-textarea" }), _jsx(Button, { size: "sm", onClick: handleCreate, disabled: !newBranchName.trim(), loading: creating, fullWidth: true, "data-testid": "create-branch-submit", children: "Create branch" })] }) }) })] })), _jsx(ScrollArea, { style: { flex: 1 }, pb: "md", children: branches.length === 0 ? (_jsx(Text, { size: "sm", c: "dimmed", py: "md", children: !supportsBranching ? 'Branch management is not available.' : 'No branches available.' })) : (_jsx(Stack, { gap: "sm", children: orderedBranches.map((b) => (_jsx(BranchCard, { branch: b, isCurrent: b.name === currentBranchName, workflowBlockedReason: b.name === currentBranchName ? currentWorkflowBlockedReason : undefined, user: user, onSelect: onSelect, onSubmit: onSubmit, onWithdraw: onWithdraw, onRequestChanges: onRequestChanges, onDelete: onDelete, onGetUserMetadata: onGetUserMetadata, onGetOwnerMetadata: onGetOwnerMetadata }, b.name))) })) })] }));
211
212
  };