@glw907/cairn-cms 0.60.1 → 0.62.2

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 (254) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/dist/components/AdminLayout.svelte +22 -0
  3. package/dist/components/CairnAdmin.svelte +3 -0
  4. package/dist/components/CairnTidySettings.svelte +2 -2
  5. package/dist/components/CairnTidySettings.svelte.d.ts +1 -1
  6. package/dist/components/EditPage.svelte +116 -39
  7. package/dist/components/HelpHome.svelte +824 -0
  8. package/dist/components/HelpHome.svelte.d.ts +22 -0
  9. package/dist/components/MarkdownHelpDialog.svelte +4 -15
  10. package/dist/components/client-ingest.d.ts +16 -8
  11. package/dist/components/client-ingest.js +12 -6
  12. package/dist/components/editor-media.js +16 -8
  13. package/dist/components/editor-placeholder.d.ts +4 -2
  14. package/dist/components/editor-tidy.d.ts +24 -12
  15. package/dist/components/editor-tidy.js +8 -4
  16. package/dist/components/index.d.ts +1 -0
  17. package/dist/components/index.js +1 -0
  18. package/dist/components/link-completion.d.ts +12 -6
  19. package/dist/components/link-completion.js +12 -6
  20. package/dist/components/markdown-directives.d.ts +9 -6
  21. package/dist/components/markdown-directives.js +9 -6
  22. package/dist/components/markdown-format.d.ts +7 -2
  23. package/dist/components/markdown-format.js +59 -28
  24. package/dist/components/markdown-reference.d.ts +8 -0
  25. package/dist/components/markdown-reference.js +22 -0
  26. package/dist/components/media-upload-outcome.d.ts +12 -6
  27. package/dist/components/objective-errors.d.ts +8 -4
  28. package/dist/components/objective-errors.js +8 -4
  29. package/dist/components/preview-doc.d.ts +4 -2
  30. package/dist/components/preview-doc.js +4 -2
  31. package/dist/components/spellcheck.d.ts +55 -29
  32. package/dist/components/spellcheck.js +39 -21
  33. package/dist/components/tidy-categorize.d.ts +20 -10
  34. package/dist/components/tidy-categorize.js +16 -8
  35. package/dist/components/tidy-validate.d.ts +12 -6
  36. package/dist/components/tidy-validate.js +20 -10
  37. package/dist/components/topbar-context.d.ts +4 -2
  38. package/dist/content/advisories.d.ts +56 -0
  39. package/dist/content/advisories.js +87 -0
  40. package/dist/content/compose.d.ts +4 -2
  41. package/dist/content/compose.js +1 -0
  42. package/dist/content/excerpt.js +4 -2
  43. package/dist/content/getting-started.d.ts +18 -0
  44. package/dist/content/getting-started.js +12 -0
  45. package/dist/content/links.d.ts +16 -8
  46. package/dist/content/links.js +12 -6
  47. package/dist/content/manifest.d.ts +36 -18
  48. package/dist/content/manifest.js +32 -16
  49. package/dist/content/media-refs.d.ts +4 -2
  50. package/dist/content/media-refs.js +4 -2
  51. package/dist/content/media-rewrite.d.ts +8 -4
  52. package/dist/content/media-rewrite.js +76 -38
  53. package/dist/content/schema.d.ts +20 -10
  54. package/dist/content/site-dictionary.d.ts +4 -2
  55. package/dist/content/site-dictionary.js +8 -4
  56. package/dist/content/types.d.ts +97 -42
  57. package/dist/delivery/content-index.d.ts +16 -8
  58. package/dist/delivery/feeds.js +4 -2
  59. package/dist/delivery/json-ld.d.ts +3 -0
  60. package/dist/delivery/json-ld.js +3 -0
  61. package/dist/delivery/manifest.d.ts +4 -2
  62. package/dist/delivery/manifest.js +4 -2
  63. package/dist/delivery/public-routes.d.ts +12 -6
  64. package/dist/delivery/public-routes.js +4 -2
  65. package/dist/delivery/seo-fields.d.ts +12 -6
  66. package/dist/delivery/seo-fields.js +8 -4
  67. package/dist/delivery/site-indexes.d.ts +4 -2
  68. package/dist/delivery/site-resolver.d.ts +4 -2
  69. package/dist/delivery/site-resolver.js +4 -2
  70. package/dist/doctor/cloudflare-api.d.ts +6 -0
  71. package/dist/doctor/cloudflare-api.js +6 -0
  72. package/dist/doctor/index.d.ts +12 -6
  73. package/dist/doctor/report.d.ts +3 -0
  74. package/dist/doctor/report.js +3 -0
  75. package/dist/doctor/run.d.ts +3 -0
  76. package/dist/doctor/run.js +3 -0
  77. package/dist/doctor/types.d.ts +10 -2
  78. package/dist/doctor/types.js +6 -0
  79. package/dist/doctor/wrangler-config.d.ts +7 -2
  80. package/dist/doctor/wrangler-config.js +3 -0
  81. package/dist/email.d.ts +4 -2
  82. package/dist/env.d.ts +0 -3
  83. package/dist/env.js +0 -3
  84. package/dist/github/branches.d.ts +4 -2
  85. package/dist/github/branches.js +4 -2
  86. package/dist/github/signing.d.ts +1 -1
  87. package/dist/github/signing.js +2 -2
  88. package/dist/log/events.d.ts +1 -1
  89. package/dist/media/bulk-delete-plan.d.ts +8 -4
  90. package/dist/media/config.d.ts +12 -6
  91. package/dist/media/config.js +16 -8
  92. package/dist/media/delivery-bucket.d.ts +4 -2
  93. package/dist/media/library-entry.d.ts +4 -2
  94. package/dist/media/library-entry.js +4 -2
  95. package/dist/media/manifest.d.ts +29 -15
  96. package/dist/media/manifest.js +29 -16
  97. package/dist/media/naming.d.ts +12 -6
  98. package/dist/media/naming.js +24 -12
  99. package/dist/media/orphan-scan.d.ts +4 -2
  100. package/dist/media/reconcile.d.ts +21 -11
  101. package/dist/media/reconcile.js +12 -6
  102. package/dist/media/reference.d.ts +8 -4
  103. package/dist/media/reference.js +12 -6
  104. package/dist/media/rewrite-plan.d.ts +12 -6
  105. package/dist/media/sniff.d.ts +4 -2
  106. package/dist/media/sniff.js +28 -14
  107. package/dist/media/store.d.ts +16 -8
  108. package/dist/media/store.js +4 -2
  109. package/dist/media/transform-url.d.ts +12 -6
  110. package/dist/media/transform-url.js +8 -4
  111. package/dist/media/usage.d.ts +8 -4
  112. package/dist/nav/site-config.d.ts +16 -8
  113. package/dist/render/component-grammar.d.ts +23 -10
  114. package/dist/render/component-grammar.js +19 -8
  115. package/dist/render/component-insert.d.ts +8 -4
  116. package/dist/render/component-insert.js +4 -2
  117. package/dist/render/component-reference.d.ts +4 -2
  118. package/dist/render/component-reference.js +4 -2
  119. package/dist/render/component-validate.d.ts +3 -0
  120. package/dist/render/component-validate.js +3 -0
  121. package/dist/render/glyph.d.ts +4 -2
  122. package/dist/render/glyph.js +4 -2
  123. package/dist/render/pipeline.d.ts +20 -10
  124. package/dist/render/pipeline.js +4 -2
  125. package/dist/render/registry.d.ts +40 -20
  126. package/dist/render/registry.js +16 -8
  127. package/dist/render/rehype-dispatch.d.ts +22 -8
  128. package/dist/render/rehype-dispatch.js +22 -8
  129. package/dist/render/remark-directives.d.ts +3 -0
  130. package/dist/render/remark-directives.js +3 -0
  131. package/dist/render/remark-figure.d.ts +4 -2
  132. package/dist/render/remark-figure.js +4 -2
  133. package/dist/render/resolve-links.d.ts +4 -2
  134. package/dist/render/resolve-links.js +4 -2
  135. package/dist/render/resolve-media.d.ts +16 -8
  136. package/dist/render/resolve-media.js +12 -6
  137. package/dist/sveltekit/admin-dispatch.d.ts +2 -0
  138. package/dist/sveltekit/admin-dispatch.js +9 -3
  139. package/dist/sveltekit/auth-routes.d.ts +3 -0
  140. package/dist/sveltekit/auth-routes.js +3 -0
  141. package/dist/sveltekit/cairn-admin.d.ts +16 -5
  142. package/dist/sveltekit/cairn-admin.js +26 -10
  143. package/dist/sveltekit/content-routes.d.ts +191 -86
  144. package/dist/sveltekit/content-routes.js +297 -107
  145. package/dist/sveltekit/editors-routes.d.ts +3 -0
  146. package/dist/sveltekit/editors-routes.js +3 -0
  147. package/dist/sveltekit/guard.d.ts +4 -2
  148. package/dist/sveltekit/guard.js +4 -2
  149. package/dist/sveltekit/https-required-page.d.ts +1 -1
  150. package/dist/sveltekit/https-required-page.js +1 -1
  151. package/dist/sveltekit/index.d.ts +1 -1
  152. package/dist/sveltekit/media-route.d.ts +1 -2
  153. package/dist/sveltekit/media-route.js +13 -8
  154. package/dist/sveltekit/nav-routes.d.ts +7 -2
  155. package/dist/sveltekit/nav-routes.js +3 -0
  156. package/dist/sveltekit/types.d.ts +4 -2
  157. package/dist/vite/index.d.ts +32 -16
  158. package/dist/vite/index.js +52 -26
  159. package/dist/vite/resolve-root.d.ts +8 -4
  160. package/dist/vite/resolve-root.js +4 -2
  161. package/package.json +7 -1
  162. package/src/lib/components/AdminLayout.svelte +22 -0
  163. package/src/lib/components/CairnAdmin.svelte +3 -0
  164. package/src/lib/components/CairnTidySettings.svelte +2 -2
  165. package/src/lib/components/ComponentForm.svelte +0 -1
  166. package/src/lib/components/EditPage.svelte +133 -41
  167. package/src/lib/components/HelpHome.svelte +850 -0
  168. package/src/lib/components/MarkdownHelpDialog.svelte +4 -15
  169. package/src/lib/components/client-ingest.ts +20 -10
  170. package/src/lib/components/editor-media.ts +20 -10
  171. package/src/lib/components/editor-placeholder.ts +12 -6
  172. package/src/lib/components/editor-tidy.ts +28 -14
  173. package/src/lib/components/index.ts +1 -0
  174. package/src/lib/components/link-completion.ts +12 -6
  175. package/src/lib/components/markdown-directives.ts +13 -8
  176. package/src/lib/components/markdown-format.ts +63 -30
  177. package/src/lib/components/markdown-reference.ts +30 -0
  178. package/src/lib/components/media-upload-outcome.ts +12 -6
  179. package/src/lib/components/objective-errors.ts +16 -8
  180. package/src/lib/components/preview-doc.ts +4 -2
  181. package/src/lib/components/spellcheck.ts +79 -41
  182. package/src/lib/components/tidy-categorize.ts +28 -14
  183. package/src/lib/components/tidy-validate.ts +28 -14
  184. package/src/lib/components/topbar-context.ts +4 -2
  185. package/src/lib/content/advisories.ts +150 -0
  186. package/src/lib/content/compose.ts +5 -2
  187. package/src/lib/content/excerpt.ts +4 -2
  188. package/src/lib/content/getting-started.ts +31 -0
  189. package/src/lib/content/links.ts +16 -8
  190. package/src/lib/content/manifest.ts +36 -18
  191. package/src/lib/content/media-refs.ts +4 -2
  192. package/src/lib/content/media-rewrite.ts +100 -50
  193. package/src/lib/content/schema.ts +20 -10
  194. package/src/lib/content/site-dictionary.ts +8 -4
  195. package/src/lib/content/types.ts +97 -42
  196. package/src/lib/delivery/content-index.ts +16 -8
  197. package/src/lib/delivery/feeds.ts +4 -2
  198. package/src/lib/delivery/json-ld.ts +3 -0
  199. package/src/lib/delivery/manifest.ts +4 -2
  200. package/src/lib/delivery/public-routes.ts +16 -8
  201. package/src/lib/delivery/seo-fields.ts +12 -6
  202. package/src/lib/delivery/site-indexes.ts +4 -2
  203. package/src/lib/delivery/site-resolver.ts +4 -2
  204. package/src/lib/doctor/cloudflare-api.ts +6 -0
  205. package/src/lib/doctor/index.ts +12 -6
  206. package/src/lib/doctor/report.ts +3 -0
  207. package/src/lib/doctor/run.ts +3 -0
  208. package/src/lib/doctor/types.ts +10 -2
  209. package/src/lib/doctor/wrangler-config.ts +7 -2
  210. package/src/lib/email.ts +4 -2
  211. package/src/lib/env.ts +0 -3
  212. package/src/lib/github/branches.ts +4 -2
  213. package/src/lib/github/signing.ts +2 -2
  214. package/src/lib/log/events.ts +1 -0
  215. package/src/lib/media/bulk-delete-plan.ts +8 -4
  216. package/src/lib/media/config.ts +24 -12
  217. package/src/lib/media/delivery-bucket.ts +4 -2
  218. package/src/lib/media/library-entry.ts +4 -2
  219. package/src/lib/media/manifest.ts +33 -18
  220. package/src/lib/media/naming.ts +24 -12
  221. package/src/lib/media/orphan-scan.ts +4 -2
  222. package/src/lib/media/reconcile.ts +21 -11
  223. package/src/lib/media/reference.ts +12 -6
  224. package/src/lib/media/rewrite-plan.ts +12 -6
  225. package/src/lib/media/sniff.ts +28 -14
  226. package/src/lib/media/store.ts +16 -8
  227. package/src/lib/media/transform-url.ts +12 -6
  228. package/src/lib/media/usage.ts +8 -4
  229. package/src/lib/nav/site-config.ts +16 -8
  230. package/src/lib/render/component-grammar.ts +23 -10
  231. package/src/lib/render/component-insert.ts +8 -4
  232. package/src/lib/render/component-reference.ts +4 -2
  233. package/src/lib/render/component-validate.ts +3 -0
  234. package/src/lib/render/glyph.ts +4 -2
  235. package/src/lib/render/pipeline.ts +20 -10
  236. package/src/lib/render/registry.ts +44 -22
  237. package/src/lib/render/rehype-dispatch.ts +22 -8
  238. package/src/lib/render/remark-directives.ts +3 -0
  239. package/src/lib/render/remark-figure.ts +4 -2
  240. package/src/lib/render/resolve-links.ts +4 -2
  241. package/src/lib/render/resolve-media.ts +16 -8
  242. package/src/lib/sveltekit/admin-dispatch.ts +10 -4
  243. package/src/lib/sveltekit/auth-routes.ts +3 -0
  244. package/src/lib/sveltekit/cairn-admin.ts +37 -15
  245. package/src/lib/sveltekit/content-routes.ts +494 -197
  246. package/src/lib/sveltekit/editors-routes.ts +3 -0
  247. package/src/lib/sveltekit/guard.ts +4 -2
  248. package/src/lib/sveltekit/https-required-page.ts +1 -1
  249. package/src/lib/sveltekit/index.ts +3 -0
  250. package/src/lib/sveltekit/media-route.ts +13 -8
  251. package/src/lib/sveltekit/nav-routes.ts +7 -2
  252. package/src/lib/sveltekit/types.ts +4 -2
  253. package/src/lib/vite/index.ts +60 -30
  254. package/src/lib/vite/resolve-root.ts +8 -4
@@ -1,5 +1,8 @@
1
1
  import type { Editor } from '../auth/types.js';
2
2
  import type { RequestContext } from './types.js';
3
+ /**
4
+ *
5
+ */
3
6
  export declare function createEditorRoutes(): {
4
7
  editorsLoad: (event: RequestContext) => Promise<{
5
8
  editors: Editor[];
@@ -10,6 +10,9 @@ const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
10
10
  function parseRole(value) {
11
11
  return value === 'owner' ? 'owner' : 'editor';
12
12
  }
13
+ /**
14
+ *
15
+ */
13
16
  export function createEditorRoutes() {
14
17
  /** GET /admin/editors. Owner-only. Returns the allowlist and the acting owner's email. */
15
18
  async function editorsLoad(event) {
@@ -2,9 +2,11 @@ import type { Editor } from '../auth/types.js';
2
2
  import type { HandleInput, RequestContext } from './types.js';
3
3
  /** The SvelteKit `Handle` that guards `/admin/**` and hardens admin responses. */
4
4
  export declare function createAuthGuard(): ({ event, resolve }: HandleInput) => Promise<Response>;
5
- /** For a protected load/action: the session the guard already resolved, or a login redirect.
5
+ /**
6
+ * For a protected load/action: the session the guard already resolved, or a login redirect.
6
7
  * The parameter is the minimal structural need (just `locals`), so every engine event shape
7
- * (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it. */
8
+ * (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it.
9
+ */
8
10
  export declare function requireSession(event: {
9
11
  locals: {
10
12
  editor?: Editor | null;
@@ -87,9 +87,11 @@ export function createAuthGuard() {
87
87
  return response;
88
88
  };
89
89
  }
90
- /** For a protected load/action: the session the guard already resolved, or a login redirect.
90
+ /**
91
+ * For a protected load/action: the session the guard already resolved, or a login redirect.
91
92
  * The parameter is the minimal structural need (just `locals`), so every engine event shape
92
- * (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it. */
93
+ * (RequestContext, the content routes' ContentEvent) and a real RequestEvent all satisfy it.
94
+ */
93
95
  export function requireSession(event) {
94
96
  const editor = event.locals.editor;
95
97
  if (!editor)
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Render the full HTML document for the HTTPS-required page.
3
- * @param httpsUrl The same request rebuilt over https, offered as the one-click recovery link.
3
+ * @param httpsUrl - The same request rebuilt over https, offered as the one-click recovery link.
4
4
  */
5
5
  export declare function httpsRequiredPage(httpsUrl: string): string;
@@ -8,7 +8,7 @@ import { escapeHtml } from '../escape.js';
8
8
  import { renderStaticAdminPage } from './static-admin-page.js';
9
9
  /**
10
10
  * Render the full HTML document for the HTTPS-required page.
11
- * @param httpsUrl The same request rebuilt over https, offered as the one-click recovery link.
11
+ * @param httpsUrl - The same request rebuilt over https, offered as the one-click recovery link.
12
12
  */
13
13
  export function httpsRequiredPage(httpsUrl) {
14
14
  const href = escapeHtml(httpsUrl);
@@ -3,7 +3,7 @@ export { createAuthRoutes, type AuthRoutesConfig, type RequestResult } from './a
3
3
  export { createEditorRoutes } from './editors-routes.js';
4
4
  export { createContentRoutes } from './content-routes.js';
5
5
  export { createMediaRoute } from './media-route.js';
6
- export type { NavConcept, LayoutData, EntrySummary, ListData, EditData, MediaUsageInfo, MediaLibraryData, ContentEvent, ContentRoutesDeps, SaveFailure, DeleteRefusal, RenameFailure, MediaDeleteRefusal, MediaUpdateFailure, MediaReplaceFailure, MediaAltPropagateFailure, MediaBulkFailure, ContentFormFailure, UploadResult, } from './content-routes.js';
6
+ export type { NavConcept, LayoutData, EntrySummary, ListData, EditData, AdvisoryNotice, AdvisoryAction, HelpData, MediaUsageInfo, MediaLibraryData, ContentEvent, ContentRoutesDeps, SaveFailure, DeleteRefusal, RenameFailure, MediaDeleteRefusal, MediaUpdateFailure, MediaReplaceFailure, MediaAltPropagateFailure, MediaBulkFailure, ContentFormFailure, UploadResult, } from './content-routes.js';
7
7
  export { createNavRoutes } from './nav-routes.js';
8
8
  export type { NavLoadData, NavPageOption, NavRoutesDeps } from './nav-routes.js';
9
9
  export { parseAdminPath, type AdminView } from './admin-dispatch.js';
@@ -6,7 +6,6 @@ import type { ResolvedAssetConfig } from '../media/config.js';
6
6
  * The handler validates the hash and extension before any R2 call, derives the object key from the
7
7
  * validated values only (never trusting the URL's fan-out), guards the Cloudflare Images self-loop,
8
8
  * and sets the security headers on every served response.
9
- *
10
- * @param resolved the adapter's resolved media config; when media is off the handler always 404s.
9
+ * @param resolved - the adapter's resolved media config; when media is off the handler always 404s.
11
10
  */
12
11
  export declare function createMediaRoute(resolved: ResolvedAssetConfig): RequestHandler;
@@ -4,12 +4,16 @@ import { r2Key } from '../media/naming.js';
4
4
  import { log } from '../log/index.js';
5
5
  /** A 16-character lowercase hex content-hash prefix, validated before any R2 lookup. */
6
6
  const HASH_RE = /^[0-9a-f]{16}$/;
7
- /** The closed delivery extension allow-list. A filename ext outside this set is a 404 with no R2
8
- * read, so the route can never serve a type it cannot vouch for. */
7
+ /**
8
+ * The closed delivery extension allow-list. A filename ext outside this set is a 404 with no R2
9
+ * read, so the route can never serve a type it cannot vouch for.
10
+ */
9
11
  const DELIVERY_EXTS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif']);
10
- /** The load-bearing XSS control: set on every non-404 response, so a served object can never run as
12
+ /**
13
+ * The load-bearing XSS control: set on every non-404 response, so a served object can never run as
11
14
  * active content. `Content-Type` comes from the stored, server-validated metadata via
12
- * `writeHttpMetadata`; these override or add to it. */
15
+ * `writeHttpMetadata`; these override or add to it.
16
+ */
13
17
  function applySecurityHeaders(headers, etag) {
14
18
  headers.set('X-Content-Type-Options', 'nosniff');
15
19
  headers.set('Content-Disposition', 'inline');
@@ -22,8 +26,10 @@ function applySecurityHeaders(headers, etag) {
22
26
  if (!headers.has('Content-Type'))
23
27
  headers.set('Content-Type', 'application/octet-stream');
24
28
  }
25
- /** True when the returned object carries a body (a full or ranged read), narrowing it to the body
26
- * variant. R2 returns a body-less object on an `If-None-Match` hit. */
29
+ /**
30
+ * True when the returned object carries a body (a full or ranged read), narrowing it to the body
31
+ * variant. R2 returns a body-less object on an `If-None-Match` hit.
32
+ */
27
33
  function hasBody(obj) {
28
34
  return 'body' in obj && obj.body != null;
29
35
  }
@@ -33,8 +39,7 @@ function hasBody(obj) {
33
39
  * The handler validates the hash and extension before any R2 call, derives the object key from the
34
40
  * validated values only (never trusting the URL's fan-out), guards the Cloudflare Images self-loop,
35
41
  * and sets the security headers on every served response.
36
- *
37
- * @param resolved the adapter's resolved media config; when media is off the handler always 404s.
42
+ * @param resolved - the adapter's resolved media config; when media is off the handler always 404s.
38
43
  */
39
44
  export function createMediaRoute(resolved) {
40
45
  return async (event) => {
@@ -21,10 +21,15 @@ export interface NavLoadData {
21
21
  }
22
22
  /** Injectable dependencies; tests stub the token mint to avoid signing a real key. */
23
23
  export interface NavRoutesDeps {
24
- /** Mint a GitHub App installation token from the Worker env. Defaults to the real signer.
25
- * A bare string works too; the routes await whatever comes back. */
24
+ /**
25
+ * Mint a GitHub App installation token from the Worker env. Defaults to the real signer.
26
+ * A bare string works too; the routes await whatever comes back.
27
+ */
26
28
  mintToken?: (env: GithubKeyEnv) => string | Promise<string>;
27
29
  }
30
+ /**
31
+ *
32
+ */
28
33
  export declare function createNavRoutes(runtime: CairnRuntime, deps?: NavRoutesDeps): {
29
34
  navLoad: (event: ContentEvent) => Promise<NavLoadData>;
30
35
  navSave: (event: ContentEvent) => Promise<never>;
@@ -9,6 +9,9 @@ import { isConflict } from '../github/types.js';
9
9
  import { log } from '../log/index.js';
10
10
  import { parseSiteConfig, extractMenu, validateNavTree, setMenu } from '../nav/site-config.js';
11
11
  import { requireSession } from './guard.js';
12
+ /**
13
+ *
14
+ */
12
15
  export function createNavRoutes(runtime, deps = {}) {
13
16
  const mintToken = deps.mintToken ?? ((env) => cachedInstallationToken(appCredentials(runtime.backend, env)));
14
17
  /** List page-like concepts (routable, not dated) for the URL picker. Best-effort per concept. */
@@ -23,9 +23,11 @@ export interface PlatformContext<Env> {
23
23
  waitUntil(promise: Promise<unknown>): void;
24
24
  };
25
25
  }
26
- /** The structural core every engine event type extends, parameterized by the Worker env the
26
+ /**
27
+ * The structural core every engine event type extends, parameterized by the Worker env the
27
28
  * surface reads. Each shared field is defined once here; the extensions add only what their
28
- * surface needs (cookies, params, setHeaders). */
29
+ * surface needs (cookies, params, setHeaders).
30
+ */
29
31
  export interface EventBase<Env> {
30
32
  url: URL;
31
33
  request: Request;
@@ -1,6 +1,8 @@
1
1
  import type { Plugin, PluginOption } from 'vite';
2
- /** Options for {@link cairnManifest}. Paths are app-root-absolute (the form `import.meta.glob` wants),
3
- * so they match the build's own resolution. */
2
+ /**
3
+ * Options for {@link cairnManifest}. Paths are app-root-absolute (the form `import.meta.glob` wants),
4
+ * so they match the build's own resolution.
5
+ */
4
6
  export interface CairnManifestOptions {
5
7
  /** The module exporting the `cairn` adapter and the parsed `siteConfig`, app-root-absolute. */
6
8
  configModule: string;
@@ -9,28 +11,38 @@ export interface CairnManifestOptions {
9
11
  /** The committed manifest path, app-root-absolute. Defaults to `/src/content/.cairn/index.json`. */
10
12
  manifestPath?: string;
11
13
  }
12
- /** Flatten the consumer's plugins option and drop the cairnManifest plugin at any nesting depth, so
14
+ /**
15
+ * Flatten the consumer's plugins option and drop the cairnManifest plugin at any nesting depth, so
13
16
  * the nested verify server can never re-enter its buildStart. Vite supports (and flattens) nested
14
17
  * plugin arrays, and findCairnOptions recurses into them, so a flat single-level filter would miss a
15
18
  * cairnManifest nested inside a shared preset's sub-array and let it survive into the nested server.
16
- * This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates. */
19
+ * This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates.
20
+ */
17
21
  export declare function stripCairnManifest(plugins: PluginOption | PluginOption[]): PluginOption[];
18
- /** Verify the committed manifest against the corpus from a Vite context, throwing on drift. The bin
19
- * and the plugin share this; the spike proved it runs cleanly inside the consumer's config. */
22
+ /**
23
+ * Verify the committed manifest against the corpus from a Vite context, throwing on drift. The bin
24
+ * and the plugin share this; the spike proved it runs cleanly inside the consumer's config.
25
+ */
20
26
  export declare function verifyManifestFromVite(opts: CairnManifestOptions, root: string): Promise<void>;
21
- /** Regenerate the serialized manifest from the corpus in a Vite context, sharing the build's
22
- * resolution. The cairn-manifest bin (a later task) will call this and write the result. */
27
+ /**
28
+ * Regenerate the serialized manifest from the corpus in a Vite context, sharing the build's
29
+ * resolution. The cairn-manifest bin (a later task) will call this and write the result.
30
+ */
23
31
  export declare function buildManifestFromVite(opts: CairnManifestOptions, root: string): Promise<string>;
24
- /** The cairnManifest plugin. It serves the verify virtual module to the app graph and, in
25
- * buildStart, evaluates it through a nested Vite SSR load so a manifest drift fails the build. */
32
+ /**
33
+ * The cairnManifest plugin. It serves the verify virtual module to the app graph and, in
34
+ * buildStart, evaluates it through a nested Vite SSR load so a manifest drift fails the build.
35
+ */
26
36
  export declare function cairnManifest(opts: CairnManifestOptions): Plugin;
27
- /** Regenerate the committed manifest from the consumer's corpus and write it to the configured
37
+ /**
38
+ * Regenerate the committed manifest from the consumer's corpus and write it to the configured
28
39
  * manifestPath. It searches for the consumer's Vite config from `cwd`, derives the authoritative
29
40
  * Vite root from the loaded config (so a configured `root` or a non-root cwd resolves correctly),
30
41
  * reads the cairnManifest plugin's options off the instance, evaluates the write-mode virtual
31
42
  * module through the build's own resolution, and writes the serialized manifest under the Vite
32
43
  * root. The cairn-manifest bin calls this; it is exported so the write logic is testable apart
33
- * from the CLI shell. */
44
+ * from the CLI shell.
45
+ */
34
46
  export declare function writeManifest(cwd?: string): Promise<void>;
35
47
  /** The repo and sender facts cairn-doctor derives off the consumer's adapter. */
36
48
  export interface AdapterFacts {
@@ -40,14 +52,18 @@ export interface AdapterFacts {
40
52
  repo?: string;
41
53
  /** `cairn.sender.from`. */
42
54
  from?: string;
43
- /** `cairn.assets.bucketBinding`, the media R2 binding name; undefined when the adapter declares no
44
- * assets. The doctor's conditional media-bucket check reads it. */
55
+ /**
56
+ * `cairn.assets.bucketBinding`, the media R2 binding name; undefined when the adapter declares no
57
+ * assets. The doctor's conditional media-bucket check reads it.
58
+ */
45
59
  mediaBucketBinding?: string;
46
60
  }
47
- /** Read `{ owner, repo, from }` off the consumer's adapter by evaluating a tiny virtual module
61
+ /**
62
+ * Read `{ owner, repo, from }` off the consumer's adapter by evaluating a tiny virtual module
48
63
  * through the consumer's own Vite resolution, the same machinery the cairn-manifest bin uses.
49
64
  * cairn-doctor calls this to fill inputs the operator did not pass. Derivation is best-effort:
50
65
  * any failure (no Vite config, no cairnManifest plugin, a config module that throws) returns
51
66
  * null, so the doctor degrades to flags instead of crashing. This runs only on the bin path,
52
- * never in a Worker. */
67
+ * never in a Worker.
68
+ */
53
69
  export declare function readAdapterFacts(cwd?: string): Promise<AdapterFacts | null>;
@@ -1,16 +1,20 @@
1
1
  import { writeFile, mkdir } from 'node:fs/promises';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { resolveViteRoot } from './resolve-root.js';
4
- /** The key the cairnManifest plugin stashes its options under, so the write path can read them off the
5
- * plugin instance in the consumer's loaded config without re-parsing the config file. */
4
+ /**
5
+ * The key the cairnManifest plugin stashes its options under, so the write path can read them off the
6
+ * plugin instance in the consumer's loaded config without re-parsing the config file.
7
+ */
6
8
  const CAIRN_OPTIONS = Symbol.for('cairn-cms.manifest-options');
7
9
  const VIRTUAL_ID = 'virtual:cairn-manifest';
8
10
  const RESOLVED_ID = '\0' + VIRTUAL_ID;
9
11
  /** The default committed manifest path, app-root-absolute. */
10
12
  const DEFAULT_MANIFEST_PATH = '/src/content/.cairn/index.json';
11
- /** Build the virtual module source. In verify mode it throws on drift; in write mode it exports the
13
+ /**
14
+ * Build the virtual module source. In verify mode it throws on drift; in write mode it exports the
12
15
  * serialized manifest as `result`. The module runs in the app graph, so its `import.meta.glob`,
13
- * package, and `?raw` resolution is the build's own. */
16
+ * package, and `?raw` resolution is the build's own.
17
+ */
14
18
  function virtualSource(opts, mode) {
15
19
  const manifestPath = opts.manifestPath ?? DEFAULT_MANIFEST_PATH;
16
20
  const globEntries = Object.entries(opts.content)
@@ -31,11 +35,13 @@ const built = buildSiteManifest(cairn, siteConfig, globs);
31
35
  export const result = ${resultExpr};
32
36
  `;
33
37
  }
34
- /** Evaluate a virtual module source inside the consumer's own Vite resolution, then return the
38
+ /**
39
+ * Evaluate a virtual module source inside the consumer's own Vite resolution, then return the
35
40
  * module's `result`. It reuses the consumer's loaded config (so `$lib`, the config module,
36
41
  * `import.meta.glob`, and `?raw` resolve exactly as the build does) and strips the cairnManifest
37
42
  * plugin from the nested server's plugin list, so its buildStart never recurses. This runs at
38
- * build time and in the bins, never in the request lifecycle. */
43
+ * build time and in the bins, never in the request lifecycle.
44
+ */
39
45
  async function evalVirtual(source, root) {
40
46
  const { createServer, loadConfigFromFile } = await import('vite');
41
47
  // Load the consumer's real Vite config so the nested server inherits SvelteKit's resolution
@@ -60,17 +66,21 @@ async function evalVirtual(source, root) {
60
66
  await server.close();
61
67
  }
62
68
  }
63
- /** True for any plugin object whose name is the cairnManifest plugin, so the nested server drops it
69
+ /**
70
+ * True for any plugin object whose name is the cairnManifest plugin, so the nested server drops it
64
71
  * and cannot recurse into another buildStart. The consumer's plugin list may nest arrays and hold
65
- * falsy slots, so guard the shape. */
72
+ * falsy slots, so guard the shape.
73
+ */
66
74
  function isCairnManifestPlugin(p) {
67
75
  return !!p && typeof p === 'object' && 'name' in p && p.name === 'cairn-manifest';
68
76
  }
69
- /** Flatten the consumer's plugins option and drop the cairnManifest plugin at any nesting depth, so
77
+ /**
78
+ * Flatten the consumer's plugins option and drop the cairnManifest plugin at any nesting depth, so
70
79
  * the nested verify server can never re-enter its buildStart. Vite supports (and flattens) nested
71
80
  * plugin arrays, and findCairnOptions recurses into them, so a flat single-level filter would miss a
72
81
  * cairnManifest nested inside a shared preset's sub-array and let it survive into the nested server.
73
- * This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates. */
82
+ * This mirrors findCairnOptions's recursion. Falsy slots pass through, which Vite tolerates.
83
+ */
74
84
  export function stripCairnManifest(plugins) {
75
85
  if (Array.isArray(plugins))
76
86
  return plugins.flatMap(stripCairnManifest);
@@ -78,18 +88,24 @@ export function stripCairnManifest(plugins) {
78
88
  return [];
79
89
  return [plugins];
80
90
  }
81
- /** Verify the committed manifest against the corpus from a Vite context, throwing on drift. The bin
82
- * and the plugin share this; the spike proved it runs cleanly inside the consumer's config. */
91
+ /**
92
+ * Verify the committed manifest against the corpus from a Vite context, throwing on drift. The bin
93
+ * and the plugin share this; the spike proved it runs cleanly inside the consumer's config.
94
+ */
83
95
  export async function verifyManifestFromVite(opts, root) {
84
96
  await evalVirtual(virtualSource(opts, 'verify'), root);
85
97
  }
86
- /** Regenerate the serialized manifest from the corpus in a Vite context, sharing the build's
87
- * resolution. The cairn-manifest bin (a later task) will call this and write the result. */
98
+ /**
99
+ * Regenerate the serialized manifest from the corpus in a Vite context, sharing the build's
100
+ * resolution. The cairn-manifest bin (a later task) will call this and write the result.
101
+ */
88
102
  export async function buildManifestFromVite(opts, root) {
89
103
  return evalVirtual(virtualSource(opts, 'write'), root);
90
104
  }
91
- /** The cairnManifest plugin. It serves the verify virtual module to the app graph and, in
92
- * buildStart, evaluates it through a nested Vite SSR load so a manifest drift fails the build. */
105
+ /**
106
+ * The cairnManifest plugin. It serves the verify virtual module to the app graph and, in
107
+ * buildStart, evaluates it through a nested Vite SSR load so a manifest drift fails the build.
108
+ */
93
109
  export function cairnManifest(opts) {
94
110
  let root = process.cwd();
95
111
  const plugin = {
@@ -121,13 +137,15 @@ export function cairnManifest(opts) {
121
137
  plugin[CAIRN_OPTIONS] = opts;
122
138
  return plugin;
123
139
  }
124
- /** Regenerate the committed manifest from the consumer's corpus and write it to the configured
140
+ /**
141
+ * Regenerate the committed manifest from the consumer's corpus and write it to the configured
125
142
  * manifestPath. It searches for the consumer's Vite config from `cwd`, derives the authoritative
126
143
  * Vite root from the loaded config (so a configured `root` or a non-root cwd resolves correctly),
127
144
  * reads the cairnManifest plugin's options off the instance, evaluates the write-mode virtual
128
145
  * module through the build's own resolution, and writes the serialized manifest under the Vite
129
146
  * root. The cairn-manifest bin calls this; it is exported so the write logic is testable apart
130
- * from the CLI shell. */
147
+ * from the CLI shell.
148
+ */
131
149
  export async function writeManifest(cwd = process.cwd()) {
132
150
  const { loadConfigFromFile } = await import('vite');
133
151
  const loaded = await loadConfigFromFile({ command: 'build', mode: 'production' }, undefined, cwd);
@@ -147,8 +165,10 @@ export async function writeManifest(cwd = process.cwd()) {
147
165
  await mkdir(dirname(outPath), { recursive: true });
148
166
  await writeFile(outPath, serialized);
149
167
  }
150
- /** Walk a Vite plugins option (which may nest arrays, hold falsy slots, or be a thenable) and return
151
- * the stashed cairnManifest options from the first matching plugin, or null if there is none. */
168
+ /**
169
+ * Walk a Vite plugins option (which may nest arrays, hold falsy slots, or be a thenable) and return
170
+ * the stashed cairnManifest options from the first matching plugin, or null if there is none.
171
+ */
152
172
  function findCairnOptions(plugins) {
153
173
  if (!plugins)
154
174
  return null;
@@ -165,8 +185,10 @@ function findCairnOptions(plugins) {
165
185
  }
166
186
  return null;
167
187
  }
168
- /** A minimal plugin that serves only the given virtual module source, for the nested SSR load. It
169
- * carries no buildStart, so the nested server never recurses into the verify. */
188
+ /**
189
+ * A minimal plugin that serves only the given virtual module source, for the nested SSR load. It
190
+ * carries no buildStart, so the nested server never recurses into the verify.
191
+ */
170
192
  function cairnVirtualOnly(source) {
171
193
  return {
172
194
  name: 'cairn-manifest-virtual',
@@ -180,10 +202,12 @@ function cairnVirtualOnly(source) {
180
202
  },
181
203
  };
182
204
  }
183
- /** Build the virtual module that reads only the adapter facts the doctor derives. It imports the
205
+ /**
206
+ * Build the virtual module that reads only the adapter facts the doctor derives. It imports the
184
207
  * configured config module and exports the string-typed `owner`, `repo`, `from`, and the media
185
208
  * `bucketBinding` as JSON, so nothing else of the adapter (least of all a secret) crosses the
186
- * boundary. */
209
+ * boundary.
210
+ */
187
211
  function adapterFactsSource(opts) {
188
212
  return `
189
213
  import { cairn } from ${JSON.stringify(opts.configModule)};
@@ -198,12 +222,14 @@ if (typeof assets.bucketBinding === 'string') facts.mediaBucketBinding = assets.
198
222
  export const result = JSON.stringify(facts);
199
223
  `;
200
224
  }
201
- /** Read `{ owner, repo, from }` off the consumer's adapter by evaluating a tiny virtual module
225
+ /**
226
+ * Read `{ owner, repo, from }` off the consumer's adapter by evaluating a tiny virtual module
202
227
  * through the consumer's own Vite resolution, the same machinery the cairn-manifest bin uses.
203
228
  * cairn-doctor calls this to fill inputs the operator did not pass. Derivation is best-effort:
204
229
  * any failure (no Vite config, no cairnManifest plugin, a config module that throws) returns
205
230
  * null, so the doctor degrades to flags instead of crashing. This runs only on the bin path,
206
- * never in a Worker. */
231
+ * never in a Worker.
232
+ */
207
233
  export async function readAdapterFacts(cwd = process.cwd()) {
208
234
  try {
209
235
  const { loadConfigFromFile } = await import('vite');
@@ -1,5 +1,7 @@
1
- /** The shape of `loadConfigFromFile`'s result that the root derivation reads: the config file's own
2
- * path and its `root` field. Typed structurally so the helper is testable without a real load. */
1
+ /**
2
+ * The shape of `loadConfigFromFile`'s result that the root derivation reads: the config file's own
3
+ * path and its `root` field. Typed structurally so the helper is testable without a real load.
4
+ */
3
5
  export interface LoadedViteConfig {
4
6
  /** The resolved path of the config file Vite loaded. */
5
7
  path: string;
@@ -8,9 +10,11 @@ export interface LoadedViteConfig {
8
10
  root?: string;
9
11
  };
10
12
  }
11
- /** The authoritative Vite root for the manifest bin, derived from the loaded config the way Vite
13
+ /**
14
+ * The authoritative Vite root for the manifest bin, derived from the loaded config the way Vite
12
15
  * resolves a relative `root`: against the config file's own directory, not cwd. An absolute `root`
13
16
  * stands as given, and no `root` falls back to `cwd` (the directory the bin was run from). This
14
17
  * separates the config-search dir (cwd) from the Vite root, so a non-root cwd or a config that
15
- * sets `root` reads and writes the manifest under the real app root. */
18
+ * sets `root` reads and writes the manifest under the real app root.
19
+ */
16
20
  export declare function resolveViteRoot(loaded: LoadedViteConfig, cwd: string): string;
@@ -1,11 +1,13 @@
1
1
  // The manifest bin's root derivation, split out so it is unit-testable without widening the public
2
2
  // /vite surface (only src/lib/vite/index.ts is the package subpath; this sibling is internal).
3
3
  import { dirname, isAbsolute, resolve } from 'node:path';
4
- /** The authoritative Vite root for the manifest bin, derived from the loaded config the way Vite
4
+ /**
5
+ * The authoritative Vite root for the manifest bin, derived from the loaded config the way Vite
5
6
  * resolves a relative `root`: against the config file's own directory, not cwd. An absolute `root`
6
7
  * stands as given, and no `root` falls back to `cwd` (the directory the bin was run from). This
7
8
  * separates the config-search dir (cwd) from the Vite root, so a non-root cwd or a config that
8
- * sets `root` reads and writes the manifest under the real app root. */
9
+ * sets `root` reads and writes the manifest under the real app root.
10
+ */
9
11
  export function resolveViteRoot(loaded, cwd) {
10
12
  const root = loaded.config.root;
11
13
  if (!root)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glw907/cairn-cms",
3
- "version": "0.60.1",
3
+ "version": "0.62.2",
4
4
  "description": "Embedded, magic-link, GitHub-committing CMS for SvelteKit/Cloudflare sites.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -34,6 +34,8 @@
34
34
  "check:docs": "node scripts/docs-links.mjs",
35
35
  "check:version": "node scripts/check-version.mjs",
36
36
  "check:prose": "node scripts/check-admin-prose.mjs",
37
+ "lint": "eslint src/lib",
38
+ "check:comments": "bash scripts/check-comments.sh",
37
39
  "prepare": "npm run package",
38
40
  "check": "svelte-check --tsconfig ./tsconfig.json",
39
41
  "test": "vitest run",
@@ -158,6 +160,9 @@
158
160
  "@vitest/browser": "^4.1.7",
159
161
  "@vitest/browser-playwright": "^4.1.7",
160
162
  "daisyui": "^5.5.23",
163
+ "eslint": "^9.39.4",
164
+ "eslint-plugin-jsdoc": "^63.0.7",
165
+ "eslint-plugin-tsdoc": "^0.5.2",
161
166
  "lightningcss": "^1.32.0",
162
167
  "playwright": "^1.60.0",
163
168
  "postcss": "^8.5.15",
@@ -167,6 +172,7 @@
167
172
  "svelte-check": "^4",
168
173
  "tailwindcss": "^4.3.0",
169
174
  "typescript": "^6.0.3",
175
+ "typescript-eslint": "^8.62.0",
170
176
  "vite": "^8.0",
171
177
  "vitest": "^4.1",
172
178
  "vitest-browser-svelte": "^2.1.1",
@@ -23,6 +23,7 @@ identical on every host regardless of the site's own theme.
23
23
  import ImageIcon from '@lucide/svelte/icons/image';
24
24
  import BlocksIcon from '@lucide/svelte/icons/blocks';
25
25
  import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
26
+ import HelpCircleIcon from '@lucide/svelte/icons/circle-help';
26
27
  import './cairn-admin.css';
27
28
 
28
29
  interface Props {
@@ -186,6 +187,7 @@ identical on every host regardless of the site's own theme.
186
187
 
187
188
  const paletteCommands = $derived<Command[]>([
188
189
  ...coreItems.map((item) => ({ label: item.label, icon: item.icon, href: item.href })),
190
+ { label: 'Help', icon: HelpCircleIcon, href: '/admin/help' },
189
191
  { label: 'View the live site', icon: ExternalLinkIcon, href: '/', external: true },
190
192
  theme === 'cairn-admin'
191
193
  ? { label: 'Switch to dark mode', icon: MoonIcon, action: toggleTheme }
@@ -478,6 +480,26 @@ identical on every host regardless of the site's own theme.
478
480
  {/each}
479
481
  </div>
480
482
 
483
+ <!-- Help is a standing utility destination, pinned at the foot of the nav and set apart from
484
+ the content concepts by a top hairline. It is always present, labeled in plain text, and
485
+ styled as a peer of the nav items above it. -->
486
+ <div class="flex-none border-t border-[var(--cairn-card-border)] px-2 py-2">
487
+ <ul class="menu menu-sm w-full gap-0.5 p-0">
488
+ <li>
489
+ <a
490
+ href="/admin/help"
491
+ class={isActive('/admin/help')
492
+ ? 'bg-primary/10 font-semibold text-primary'
493
+ : 'font-medium text-[var(--color-subtle)]'}
494
+ aria-current={isActive('/admin/help') ? 'page' : undefined}
495
+ >
496
+ <HelpCircleIcon class="h-4 w-4" aria-hidden="true" />
497
+ Help
498
+ </a>
499
+ </li>
500
+ </ul>
501
+ </div>
502
+
481
503
  <div class="flex-none border-t border-[var(--cairn-card-border)] px-5 py-4">
482
504
  <div class="flex items-center gap-3">
483
505
  <div class="avatar avatar-placeholder">
@@ -15,6 +15,7 @@ mount inside `AdminLayout`. No styling or wrapper elements of its own.
15
15
  import NavTree from './NavTree.svelte';
16
16
  import CairnMediaLibrary from './CairnMediaLibrary.svelte';
17
17
  import CairnTidySettings from './CairnTidySettings.svelte';
18
+ import HelpHome from './HelpHome.svelte';
18
19
  import type { AdminData } from '../sveltekit/cairn-admin.js';
19
20
  import type { ContentFormFailure } from '../sveltekit/content-routes.js';
20
21
  import type { ComponentRegistry } from '../render/registry.js';
@@ -72,6 +73,8 @@ mount inside `AdminLayout`. No styling or wrapper elements of its own.
72
73
  <CairnMediaLibrary data={data.page} {form} />
73
74
  {:else if data.view === 'settings'}
74
75
  <CairnTidySettings data={data.page} />
76
+ {:else if data.view === 'help'}
77
+ <HelpHome data={data.page} />
75
78
  {/if}
76
79
  </AdminLayout>
77
80
  {/if}
@@ -8,7 +8,7 @@ Two tiers with a truthful visibility gate:
8
8
  that tidy is enabled, a key is configured, and which model runs, but cannot edit any of it. The
9
9
  literal deploy-time tokens sit in a marked "For your developer" sub-block.
10
10
  - The EDITOR tier (the per-convention config) renders ONLY when tidy is enabled AND the key is
11
- present (`data.enabled`). When tidy is not enabled, the editor tier is genuinely ABSENT, replaced
11
+ present (`data.enabled`). When tidy is not enabled, the editor tier is ABSENT, replaced
12
12
  by an honest labelled gate region with a read-only "what your developer needs to do" checklist and
13
13
  a "spellcheck still works" reassurance. No teasing disabled controls sit in the tab order.
14
14
 
@@ -515,7 +515,7 @@ home), diffable and shared across editors.
515
515
  </div>
516
516
  </form>
517
517
  {:else}
518
- <!-- THE VISIBILITY GATE: tidy NOT enabled by the developer. The convention list is genuinely
518
+ <!-- THE VISIBILITY GATE: tidy NOT enabled by the developer. The convention list is
519
519
  absent, not disabled. One honest labelled region names the deploy-time task and who does it,
520
520
  with no disabled controls in the tab order. -->
521
521
  <div role="region" aria-label="Tidy is not set up" class="mt-6 flex flex-col items-center gap-3 rounded-2xl border border-[var(--cairn-card-border)] bg-base-100 p-10 text-center shadow-[var(--cairn-shadow)]">
@@ -56,7 +56,6 @@ binds out its live `values` and `incomplete` so the dialog can render that previ
56
56
  });
57
57
 
58
58
  const attributes = $derived(def.attributes ?? []);
59
- // Non-repeatable slots render here; the repeatable list is handled separately.
60
59
  const flatSlots = $derived((def.slots ?? []).filter((s) => s.kind !== 'repeatable'));
61
60
  const repeatableSlots = $derived((def.slots ?? []).filter((s) => s.kind === 'repeatable'));
62
61