@glw907/cairn-cms 0.62.1 → 0.68.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.
- package/CHANGELOG.md +143 -0
- package/dist/auth/types.d.ts +7 -0
- package/dist/components/ComponentInsertDialog.svelte +17 -6
- package/dist/components/ConceptList.svelte +25 -4
- package/dist/components/cairn-admin.css +175 -2
- package/dist/content/advisories.d.ts +5 -0
- package/dist/content/advisories.js +17 -9
- package/dist/content/field-rules.d.ts +15 -0
- package/dist/content/field-rules.js +39 -0
- package/dist/content/fields.d.ts +121 -0
- package/dist/content/fields.js +30 -0
- package/dist/content/fieldset.d.ts +86 -0
- package/dist/content/fieldset.js +233 -0
- package/dist/content/schema.js +16 -20
- package/dist/delivery/public-routes.d.ts +8 -0
- package/dist/delivery/public-routes.js +10 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/log/events.d.ts +1 -1
- package/dist/media/index.d.ts +1 -1
- package/dist/media/index.js +1 -1
- package/dist/media/manifest.d.ts +11 -0
- package/dist/media/manifest.js +13 -0
- package/dist/render/highlight.d.ts +9 -0
- package/dist/render/highlight.js +206 -0
- package/dist/render/pipeline.js +12 -1
- package/dist/render/registry.d.ts +10 -2
- package/dist/render/registry.js +21 -1
- package/dist/render/rehype-dispatch.d.ts +2 -6
- package/dist/render/rehype-dispatch.js +2 -6
- package/dist/render/sanitize-schema.d.ts +10 -0
- package/dist/render/sanitize-schema.js +29 -0
- package/dist/sveltekit/content-routes.js +9 -7
- package/dist/sveltekit/guard.js +10 -0
- package/package.json +13 -2
- package/src/lib/auth/types.ts +7 -0
- package/src/lib/components/ComponentInsertDialog.svelte +17 -6
- package/src/lib/components/ConceptList.svelte +41 -4
- package/src/lib/content/advisories.ts +24 -15
- package/src/lib/content/field-rules.ts +40 -0
- package/src/lib/content/fields.ts +127 -0
- package/src/lib/content/fieldset.ts +307 -0
- package/src/lib/content/schema.ts +9 -13
- package/src/lib/delivery/public-routes.ts +19 -1
- package/src/lib/index.ts +7 -0
- package/src/lib/log/events.ts +1 -0
- package/src/lib/media/index.ts +1 -0
- package/src/lib/media/manifest.ts +14 -0
- package/src/lib/render/highlight.ts +259 -0
- package/src/lib/render/pipeline.ts +12 -1
- package/src/lib/render/registry.ts +30 -3
- package/src/lib/render/rehype-dispatch.ts +2 -6
- package/src/lib/render/sanitize-schema.ts +31 -0
- package/src/lib/sveltekit/content-routes.ts +9 -7
- package/src/lib/sveltekit/guard.ts +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,149 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are recorded here, most recent first.
|
|
4
4
|
|
|
5
|
+
## 0.68.0
|
|
6
|
+
|
|
7
|
+
<!-- release-size: minor -->
|
|
8
|
+
|
|
9
|
+
The second pre-cutover engine-hardening pass clears eight engine-misc items: two admin accessibility
|
|
10
|
+
fixes, an engine default-icon fallback, and gate, doc, and tooling hygiene.
|
|
11
|
+
|
|
12
|
+
The component picker dialog now caps its height at 85vh and scrolls its catalog within a held header
|
|
13
|
+
and footer, so a long catalog no longer takes the page over. A repeated content-lifecycle error in the
|
|
14
|
+
concept list now re-announces to a screen reader: the errors route through one polite live region that
|
|
15
|
+
re-speaks an identical message through an invisible nonce, and the visible alerts drop their redundant
|
|
16
|
+
`role` so the message announces once.
|
|
17
|
+
|
|
18
|
+
The component registry ships a default role-to-glyph fallback for the conventional admonition roles
|
|
19
|
+
(`note`, `tip`, `important`, `warning`, `caution`, `info`, `danger`). A component that declares an icon
|
|
20
|
+
field but no `defaultIconByRole` entry for a role now resolves the engine default, which a site's icon
|
|
21
|
+
set styles; a component's own `defaultIconByRole` still wins. The `ComponentDef.icon` and
|
|
22
|
+
`defaultIconByRole` guidance now states the "logically representative, prefer distinct" rule.
|
|
23
|
+
|
|
24
|
+
Three gates and one doc tightened: the admin-prose gate now scans the `.ts` copy modules it skipped, a
|
|
25
|
+
new `check:dev-package` gate type-checks and comment-lints `packages/**` in CI, the two
|
|
26
|
+
`rehype-dispatch` helpers gained real doc contracts, and the friction log marks its killed and shipped
|
|
27
|
+
items resolved so it stops resurfacing dead work.
|
|
28
|
+
|
|
29
|
+
No consumer action is required. The accessibility fixes and the icon fallback are additive; a site using
|
|
30
|
+
the registry's `defaultIcon` may now see an engine default glyph where it previously saw none.
|
|
31
|
+
|
|
32
|
+
## 0.67.0
|
|
33
|
+
|
|
34
|
+
<!-- release-size: minor -->
|
|
35
|
+
|
|
36
|
+
The Contract v2 `fieldset` validator reaches constraint parity with `defineFields`, the first of two
|
|
37
|
+
pre-cutover engine-hardening passes. Both validators now call one shared constraint module, so they
|
|
38
|
+
cannot drift, and a v1-vs-v2 parity matrix proves they agree on the overlapping field types.
|
|
39
|
+
|
|
40
|
+
The `fieldset` validator gains the checks it lacked. A `text` or `textarea` field now enforces its
|
|
41
|
+
`min`, `max`, `length`, and `pattern`, and a `date` field enforces its `min` and `max`, with the same
|
|
42
|
+
messages `defineFields` produces. A malformed `pattern` now fails at `fieldset()` call time, not on
|
|
43
|
+
every save, the way `defineFields` already compiled patterns at declaration. The validator also reads a
|
|
44
|
+
parsed value, not only a form string: a numeric `number` (a finite `0` included), a `Date` on a
|
|
45
|
+
`datetime` field, the way the `date` field already coerced a parsed `Date`. A `multiselect` given a lone
|
|
46
|
+
scalar (a single hand-edited `tags: news`) coerces it to a single-element list rather than dropping it
|
|
47
|
+
or reporting a misleading "required".
|
|
48
|
+
|
|
49
|
+
No consumer action is required. The `fieldset` surface is still additive and not yet wired into the
|
|
50
|
+
adapter or editor, and the new behavior brings it in line with the long-standing `defineFields` checks.
|
|
51
|
+
|
|
52
|
+
## 0.66.0
|
|
53
|
+
|
|
54
|
+
<!-- release-size: minor -->
|
|
55
|
+
|
|
56
|
+
Contract v2 begins with an additive `fields.*` field vocabulary, exported beside the existing
|
|
57
|
+
`defineFields` model. The new surface is opt-in and does not yet wire into the adapter or editor, so a
|
|
58
|
+
site on the current field model is unaffected.
|
|
59
|
+
|
|
60
|
+
A concept can declare its fields as a record of `fields.*` constructors, each returning a plain-data
|
|
61
|
+
descriptor. The scalars are `text`, `textarea`, `number`, `select`, `multiselect`, `url`, `email`,
|
|
62
|
+
`date`, `datetime`, and `boolean`, with `image` as the rich leaf. `fieldset(record)` derives a
|
|
63
|
+
server-side validator from those descriptors, returning field-keyed errors or normalized data, and
|
|
64
|
+
exposes Standard Schema v1 at its boundary. `InferFieldset` reads the inferred frontmatter type from a
|
|
65
|
+
fieldset, and `initialValues` resolves each field's `default` for the editor form, including the
|
|
66
|
+
`'today'` sentinel on a date field through an injected clock. The new root-barrel exports are `fields`,
|
|
67
|
+
`fieldset`, `initialValues`, and the types `FieldDescriptor`, `Fieldset`, `InferFieldset`,
|
|
68
|
+
`FieldsetOptions`, and `BehaviorTable`.
|
|
69
|
+
|
|
70
|
+
No consumer action is required. The vocabulary is a foundation; the contract-v2 cutover, a later
|
|
71
|
+
breaking release, migrates concepts off `defineFields` and carries the "Consumers must:" line then.
|
|
72
|
+
|
|
73
|
+
## 0.65.0
|
|
74
|
+
|
|
75
|
+
<!-- release-size: minor -->
|
|
76
|
+
|
|
77
|
+
Build-time syntax highlighting moves into the engine render pipeline, and the public side gains the
|
|
78
|
+
Waymark design foundation in the showcase template (the scaffolder's Part B2).
|
|
79
|
+
|
|
80
|
+
Fenced code is now highlighted at build time. The render pipeline runs Shiki at build and SSR and
|
|
81
|
+
emits role-bound `.cairn-tok-*` token classes with no inline style and no client highlighter, so the
|
|
82
|
+
reading route ships no highlighter JavaScript and the colors come from the site's theme. The engine
|
|
83
|
+
owns the `.cairn-tok-*` class contract (the way it owns `.cairn-place-*` for figures); a site styles
|
|
84
|
+
the classes from its own `--cairn-code-*` variables. Adds `shiki` and `hast-util-to-string` to the
|
|
85
|
+
engine's dependencies.
|
|
86
|
+
|
|
87
|
+
GFM task-list checkboxes now carry an `aria-label` from their item text, so a screen reader names the
|
|
88
|
+
read-only control. This clears an axe `label` violation on every site while keeping the real disabled
|
|
89
|
+
input the design calls for.
|
|
90
|
+
|
|
91
|
+
No consumer action is required. A site gets highlighting automatically; to color the tokens, style the
|
|
92
|
+
`.cairn-tok-*` classes from a `--cairn-code-*` ramp (the Waymark showcase template does this, bound to
|
|
93
|
+
the DaisyUI roles). The broader Waymark design foundation (the oklch token layer, the bespoke reading
|
|
94
|
+
surface, the chrome, the `/styleguide` route, and the dual-gamut contrast, token-resolution, and
|
|
95
|
+
re-skin CI gates) ships in `examples/showcase`, the deployable starter, not the published engine.
|
|
96
|
+
|
|
97
|
+
## 0.64.0
|
|
98
|
+
|
|
99
|
+
<!-- release-size: minor -->
|
|
100
|
+
|
|
101
|
+
A small pre-Part-B DX pass fixes two engine warts the scaffolder's template would otherwise bake in,
|
|
102
|
+
and retires a third item that was already resolved.
|
|
103
|
+
|
|
104
|
+
`readCommittedManifest`, exported from `/media`, reads a committed media manifest from an
|
|
105
|
+
`import.meta.glob` result and degrades a missing file to an empty manifest. A fresh site with no
|
|
106
|
+
`src/content/.cairn/media.json` no longer fails its build: the static import that crashed gives way to
|
|
107
|
+
the glob, which returns `{}` for no match. The showcase reads its manifest this way.
|
|
108
|
+
|
|
109
|
+
A new `media.resolver_absent` log event (level `warn`) makes a silently-broken public-image setup
|
|
110
|
+
diagnosable. The public route emits it once, at construction, when media is configured on but no
|
|
111
|
+
`resolveMedia` reached it, so a forgotten resolver wiring becomes a queryable Workers Logs event
|
|
112
|
+
instead of a bare `media:` token on every hero image. `PublicRoutesDeps` gains an optional
|
|
113
|
+
`assetsEnabled` flag a site threads from its resolved asset config.
|
|
114
|
+
|
|
115
|
+
No consumer action is required. A site that wants the no-crash manifest read can adopt
|
|
116
|
+
`readCommittedManifest`, and a site that wants the resolver diagnostic threads `assetsEnabled` into
|
|
117
|
+
`createPublicRoutes`.
|
|
118
|
+
|
|
119
|
+
## 0.63.0
|
|
120
|
+
|
|
121
|
+
<!-- release-size: minor -->
|
|
122
|
+
|
|
123
|
+
The local-development fake backend moves out of the engine and the showcase into a separate, dev-only
|
|
124
|
+
package, `@glw907/cairn-cms-dev`, the first part of the `create-cairn-site` scaffolder. The package
|
|
125
|
+
holds the in-memory GitHub, R2, D1, and Anthropic doubles and a blessed `devBackendHandle()` that
|
|
126
|
+
installs them and an owner-session bypass, so a site runs `/admin` locally with no cloud accounts. A
|
|
127
|
+
consumer installs it as a `devDependency` and activates it from `hooks.server.ts` behind a
|
|
128
|
+
build-foldable `dev` gate, so a production build eliminates it from the bundle.
|
|
129
|
+
|
|
130
|
+
The auth guard gains a fail-closed tripwire. If `CAIRN_DEV_BACKEND` is set in a deployed runtime, the
|
|
131
|
+
guard refuses the request with a 503 and logs `guard.rejected` with `reason: "dev_backend_in_prod"`.
|
|
132
|
+
It reads the flag from both the Worker `platform.env` and `process.env`, so it fires on Cloudflare and
|
|
133
|
+
adapter-node alike. `AuthEnv` carries a new optional `CAIRN_DEV_BACKEND?: string | boolean` field for
|
|
134
|
+
it.
|
|
135
|
+
|
|
136
|
+
No consumer action is required. The tripwire fires only when the flag is set, and the new package is
|
|
137
|
+
opt-in for sites that want the local dev backend.
|
|
138
|
+
|
|
139
|
+
## 0.62.2
|
|
140
|
+
|
|
141
|
+
The edit-load address-collision advisory now checks the published corpus only. It fires when an entry
|
|
142
|
+
you are editing collides with an entry already published on `main`, and it no longer reads sibling
|
|
143
|
+
`cairn/<concept>/<id>` branches when an editor opens an entry, so opening the editor adds no GitHub
|
|
144
|
+
reads. The publish-time re-check is unchanged: it stays full cross-branch and still emits the
|
|
145
|
+
`publish.address_collision` log event when a publish overrides another entry's address. No consumer
|
|
146
|
+
action is required.
|
|
147
|
+
|
|
5
148
|
## 0.62.1
|
|
6
149
|
|
|
7
150
|
The entry editor gains an advisory channel and its first notice: a cross-branch address-collision
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ export interface AuthEnv {
|
|
|
11
11
|
AUTH_DB?: D1Database;
|
|
12
12
|
/** Canonical origin for confirmation links, never read from a request header (spec 7.1, risk H3). */
|
|
13
13
|
PUBLIC_ORIGIN?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Dev-backend tripwire flag. The dev backend sets this in local development; if it is ever set in
|
|
16
|
+
* a deployed runtime the guard refuses (the build-foldable `dev` gate should have eliminated the
|
|
17
|
+
* dev backend, so a set flag signals a polluted environment). A string from a Worker var or a
|
|
18
|
+
* boolean.
|
|
19
|
+
*/
|
|
20
|
+
CAIRN_DEV_BACKEND?: string | boolean;
|
|
14
21
|
/** Cloudflare Email Sending binding. */
|
|
15
22
|
EMAIL?: {
|
|
16
23
|
send(message: {
|
|
@@ -197,11 +197,16 @@ function onSearchKeydown(e) {
|
|
|
197
197
|
|
|
198
198
|
{#if defs.length > 0}
|
|
199
199
|
<dialog class="modal" aria-labelledby="cairn-insert-dialog-title" bind:this={dialog} onclose={onClose} oncancel={onCancel}>
|
|
200
|
-
|
|
200
|
+
<!-- The box caps at 85vh and is a flex column so its header holds while only the body scrolls,
|
|
201
|
+
per the design system's dialog-sizing recipe. The cap rides Tailwind utilities (the utilities
|
|
202
|
+
layer) so it beats DaisyUI's `.modal-box` max-height: 100vh; a components-layer rule loses the
|
|
203
|
+
cascade. overflow-hidden keeps the box from being a second scroll container. Matches TidyReview. -->
|
|
204
|
+
<div class="modal-box flex max-h-[85vh] flex-col overflow-hidden {twoPane ? 'max-w-3xl' : ''}">
|
|
201
205
|
<!-- The shared header: at the configure step it carries the Back control and the
|
|
202
206
|
"Insert > group" eyebrow breadcrumb above the component label; while browsing it is the
|
|
203
|
-
plain "Insert a component" title.
|
|
204
|
-
|
|
207
|
+
plain "Insert a component" title. It holds (flex-none) while the body scrolls, per the
|
|
208
|
+
design system's dialog-sizing recipe. -->
|
|
209
|
+
<div class="mb-3 flex flex-none items-center gap-3">
|
|
205
210
|
{#if picked && !editing}
|
|
206
211
|
<button type="button" class="btn btn-ghost btn-sm btn-square" aria-label="Back to components" onclick={back}>
|
|
207
212
|
<svg class="h-4 w-4" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M165.7 202.3a8 8 0 0 1-11.4 11.4l-80-80a8 8 0 0 1 0-11.4l80-80a8 8 0 0 1 11.4 11.4L91.3 128Z" /></svg>
|
|
@@ -219,6 +224,9 @@ function onSearchKeydown(e) {
|
|
|
219
224
|
</div>
|
|
220
225
|
|
|
221
226
|
{#if picked}
|
|
227
|
+
<!-- The configure body is the box's scroll container (flex-1, min-h-0): the shared header
|
|
228
|
+
above holds while the form scrolls within the 85vh cap. -->
|
|
229
|
+
<div class="-mr-1 flex min-h-0 flex-1 flex-col overflow-y-auto pr-1">
|
|
222
230
|
{#key picked}
|
|
223
231
|
{#if twoPane}
|
|
224
232
|
<!-- Two panes: the form on the left, the live preview on the right. Below the breakpoint
|
|
@@ -275,9 +283,10 @@ function onSearchKeydown(e) {
|
|
|
275
283
|
{@render configureForm(picked)}
|
|
276
284
|
{/if}
|
|
277
285
|
{/key}
|
|
286
|
+
</div>
|
|
278
287
|
{:else}
|
|
279
288
|
{#if showSearch}
|
|
280
|
-
<div class="mb-3 flex items-center gap-2 rounded-field border border-[var(--cairn-card-border)] bg-base-100 px-3 py-2">
|
|
289
|
+
<div class="mb-3 flex flex-none items-center gap-2 rounded-field border border-[var(--cairn-card-border)] bg-base-100 px-3 py-2">
|
|
281
290
|
<svg class="ec-glyph h-4 w-4 text-[var(--color-muted)]" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M229.7 218.3 179.6 168.2A92.2 92.2 0 1 0 168.2 179.6l50.1 50.1a8 8 0 0 0 11.4-11.4ZM40 112a72 72 0 1 1 72 72 72.1 72.1 0 0 1-72-72Z" /></svg>
|
|
282
291
|
<input
|
|
283
292
|
type="search"
|
|
@@ -301,8 +310,10 @@ function onSearchKeydown(e) {
|
|
|
301
310
|
<button type="button" class="text-[0.8125rem] font-medium text-primary underline [text-underline-offset:2px]" onclick={() => (query = '')}>Clear search</button>
|
|
302
311
|
</div>
|
|
303
312
|
{:else}
|
|
304
|
-
<!-- One scroll region holds every group, so the arrow keys roam the whole catalog.
|
|
305
|
-
|
|
313
|
+
<!-- One scroll region holds every group, so the arrow keys roam the whole catalog. It
|
|
314
|
+
is the box's scroll container (flex-1, min-h-0): the header above holds while the
|
|
315
|
+
list scrolls within the 85vh cap. -->
|
|
316
|
+
<div data-cairn-pk-list class="-mr-1 min-h-0 flex-1 overflow-y-auto pr-1">
|
|
306
317
|
{#each groups as group (group.heading)}
|
|
307
318
|
<div class="mt-3 first:mt-0">
|
|
308
319
|
{#if group.heading}
|
|
@@ -120,6 +120,22 @@ const sortButton = `inline-flex items-center gap-1 ${headerLabel} hover:text-bas
|
|
|
120
120
|
const publishedAllMessage = $derived(
|
|
121
121
|
data.publishedAll !== null && data.publishedAll > 0 ? `Published ${data.publishedAll} ${data.publishedAll === 1 ? "entry" : "entries"}.` : ""
|
|
122
122
|
);
|
|
123
|
+
const lifecycleError = $derived(
|
|
124
|
+
deleteRefused ? `This ${data.label.toLowerCase()} could not be deleted. ${deleteRefused.inboundLinks.length} ${deleteRefused.inboundLinks.length === 1 ? "page links" : "pages link"} to it.` : data.formError ?? data.error ?? ""
|
|
125
|
+
);
|
|
126
|
+
let announceNonce = $state(0);
|
|
127
|
+
function nonce() {
|
|
128
|
+
return announceNonce % 2 === 0 ? "" : "";
|
|
129
|
+
}
|
|
130
|
+
let lastSubmit;
|
|
131
|
+
$effect(() => {
|
|
132
|
+
const submit = form ?? data;
|
|
133
|
+
if (submit !== lastSubmit) {
|
|
134
|
+
lastSubmit = submit;
|
|
135
|
+
if (lifecycleError) announceNonce++;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
const liveError = $derived(lifecycleError ? `${lifecycleError}${nonce()}` : "");
|
|
123
139
|
</script>
|
|
124
140
|
|
|
125
141
|
<!-- The non-color selected cue for the triage controls (WCAG 1.4.1): a small check glyph that
|
|
@@ -145,20 +161,25 @@ const publishedAllMessage = $derived(
|
|
|
145
161
|
{#if}-gated role element inserted fresh is announced inconsistently, so the visible alert
|
|
146
162
|
below keeps its styling without a role and the message is announced once. -->
|
|
147
163
|
<div class="sr-only" aria-live="polite">{publishedAllMessage}</div>
|
|
164
|
+
<!-- One persistent polite region announces the lifecycle errors, re-announcing a repeat through the
|
|
165
|
+
nonce. The visible alerts below keep their styling and drop the live `role` (a fresh-inserted
|
|
166
|
+
role element announces inconsistently and clobbers a repeat), so the message is announced once. -->
|
|
167
|
+
<div class="sr-only" aria-live="polite">{liveError}</div>
|
|
148
168
|
{#if publishedAllMessage}
|
|
149
169
|
<div class="alert alert-success mb-4 text-sm">{publishedAllMessage}</div>
|
|
150
170
|
{/if}
|
|
151
171
|
{#if data.formError}
|
|
152
|
-
<div
|
|
172
|
+
<div class="alert alert-error mb-4 text-sm">{data.formError}</div>
|
|
153
173
|
{/if}
|
|
154
174
|
{#if data.error}
|
|
155
|
-
<div
|
|
175
|
+
<div class="alert alert-warning mb-4 text-sm">{data.error}</div>
|
|
156
176
|
{/if}
|
|
157
177
|
|
|
158
178
|
{#if deleteRefused}
|
|
159
179
|
<!-- A `?/delete` was refused: name the blockers up front, matching the editor's refusal banner,
|
|
160
|
-
so the author sees why without re-opening a dialog.
|
|
161
|
-
|
|
180
|
+
so the author sees why without re-opening a dialog. The polite region above announces it, so
|
|
181
|
+
the box itself carries no role or label (a bare div with an aria-label gets no accessible name). -->
|
|
182
|
+
<div class="alert alert-error mb-4 flex-col items-start text-sm">
|
|
162
183
|
<p class="font-medium">This {data.label.toLowerCase()} could not be deleted.</p>
|
|
163
184
|
<p>{deleteRefused.inboundLinks.length} {deleteRefused.inboundLinks.length === 1 ? 'page links' : 'pages link'} to it. Remove or repoint the {deleteRefused.inboundLinks.length === 1 ? 'link' : 'links'} listed below, then delete again.</p>
|
|
164
185
|
<ul class="mt-1 w-full">
|
|
@@ -3361,6 +3361,10 @@
|
|
|
3361
3361
|
top: calc(var(--spacing) * 16);
|
|
3362
3362
|
}
|
|
3363
3363
|
|
|
3364
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .top-\[-3rem\] {
|
|
3365
|
+
top: -3rem;
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3364
3368
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .top-\[0\.875rem\] {
|
|
3365
3369
|
top: .875rem;
|
|
3366
3370
|
}
|
|
@@ -3389,6 +3393,10 @@
|
|
|
3389
3393
|
left: calc(var(--spacing) * 2);
|
|
3390
3394
|
}
|
|
3391
3395
|
|
|
3396
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .left-\[var\(--cairn-space-s\)\] {
|
|
3397
|
+
left: var(--cairn-space-s);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3392
3400
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .join {
|
|
3393
3401
|
--join-ss: 0;
|
|
3394
3402
|
--join-se: 0;
|
|
@@ -3703,6 +3711,10 @@
|
|
|
3703
3711
|
z-index: 40;
|
|
3704
3712
|
}
|
|
3705
3713
|
|
|
3714
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .z-50 {
|
|
3715
|
+
z-index: 50;
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3706
3718
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .order-1 {
|
|
3707
3719
|
order: 1;
|
|
3708
3720
|
}
|
|
@@ -4075,6 +4087,10 @@
|
|
|
4075
4087
|
margin-top: calc(var(--spacing) * 16);
|
|
4076
4088
|
}
|
|
4077
4089
|
|
|
4090
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .mt-\[var\(--cairn-space-2xl\)\] {
|
|
4091
|
+
margin-top: var(--cairn-space-2xl);
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4078
4094
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .mt-auto {
|
|
4079
4095
|
margin-top: auto;
|
|
4080
4096
|
}
|
|
@@ -4083,6 +4099,10 @@
|
|
|
4083
4099
|
margin-top: 1px;
|
|
4084
4100
|
}
|
|
4085
4101
|
|
|
4102
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .-mr-1 {
|
|
4103
|
+
margin-right: calc(var(--spacing) * -1);
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4086
4106
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .mr-0\.5 {
|
|
4087
4107
|
margin-right: calc(var(--spacing) * .5);
|
|
4088
4108
|
}
|
|
@@ -4144,6 +4164,10 @@
|
|
|
4144
4164
|
margin-bottom: calc(var(--spacing) * 7);
|
|
4145
4165
|
}
|
|
4146
4166
|
|
|
4167
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .mb-\[var\(--cairn-space-s\)\] {
|
|
4168
|
+
margin-bottom: var(--cairn-space-s);
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4147
4171
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .-ml-px {
|
|
4148
4172
|
margin-left: -1px;
|
|
4149
4173
|
}
|
|
@@ -4702,6 +4726,14 @@
|
|
|
4702
4726
|
height: .6875rem;
|
|
4703
4727
|
}
|
|
4704
4728
|
|
|
4729
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .h-\[1\.4rem\] {
|
|
4730
|
+
height: 1.4rem;
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4733
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .h-\[1\.55rem\] {
|
|
4734
|
+
height: 1.55rem;
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4705
4737
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .h-\[26px\] {
|
|
4706
4738
|
height: 26px;
|
|
4707
4739
|
}
|
|
@@ -4758,6 +4790,10 @@
|
|
|
4758
4790
|
max-height: 100%;
|
|
4759
4791
|
}
|
|
4760
4792
|
|
|
4793
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .min-h-0 {
|
|
4794
|
+
min-height: calc(var(--spacing) * 0);
|
|
4795
|
+
}
|
|
4796
|
+
|
|
4761
4797
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .min-h-6 {
|
|
4762
4798
|
min-height: calc(var(--spacing) * 6);
|
|
4763
4799
|
}
|
|
@@ -4916,6 +4952,14 @@
|
|
|
4916
4952
|
width: .6875rem;
|
|
4917
4953
|
}
|
|
4918
4954
|
|
|
4955
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .w-\[1\.4rem\] {
|
|
4956
|
+
width: 1.4rem;
|
|
4957
|
+
}
|
|
4958
|
+
|
|
4959
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .w-\[1\.55rem\] {
|
|
4960
|
+
width: 1.55rem;
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4919
4963
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .w-\[3\.25rem\] {
|
|
4920
4964
|
width: 3.25rem;
|
|
4921
4965
|
}
|
|
@@ -4968,6 +5012,10 @@
|
|
|
4968
5012
|
max-width: 30%;
|
|
4969
5013
|
}
|
|
4970
5014
|
|
|
5015
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .max-w-\[38rem\] {
|
|
5016
|
+
max-width: 38rem;
|
|
5017
|
+
}
|
|
5018
|
+
|
|
4971
5019
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .max-w-\[40ch\] {
|
|
4972
5020
|
max-width: 40ch;
|
|
4973
5021
|
}
|
|
@@ -4992,6 +5040,14 @@
|
|
|
4992
5040
|
max-width: 640px;
|
|
4993
5041
|
}
|
|
4994
5042
|
|
|
5043
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .max-w-\[var\(--cairn-measure\)\] {
|
|
5044
|
+
max-width: var(--cairn-measure);
|
|
5045
|
+
}
|
|
5046
|
+
|
|
5047
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .max-w-\[var\(--cairn-measure-wide\)\] {
|
|
5048
|
+
max-width: var(--cairn-measure-wide);
|
|
5049
|
+
}
|
|
5050
|
+
|
|
4995
5051
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .max-w-full {
|
|
4996
5052
|
max-width: 100%;
|
|
4997
5053
|
}
|
|
@@ -5040,7 +5096,7 @@
|
|
|
5040
5096
|
flex: none;
|
|
5041
5097
|
}
|
|
5042
5098
|
|
|
5043
|
-
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .shrink {
|
|
5099
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .flex-shrink, :where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .shrink {
|
|
5044
5100
|
flex-shrink: 1;
|
|
5045
5101
|
}
|
|
5046
5102
|
|
|
@@ -5250,6 +5306,18 @@
|
|
|
5250
5306
|
gap: calc(var(--spacing) * 6);
|
|
5251
5307
|
}
|
|
5252
5308
|
|
|
5309
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .gap-\[0\.55rem\] {
|
|
5310
|
+
gap: .55rem;
|
|
5311
|
+
}
|
|
5312
|
+
|
|
5313
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .gap-\[var\(--cairn-space-m\)\] {
|
|
5314
|
+
gap: var(--cairn-space-m);
|
|
5315
|
+
}
|
|
5316
|
+
|
|
5317
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .gap-\[var\(--cairn-space-s\)\] {
|
|
5318
|
+
gap: var(--cairn-space-s);
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5253
5321
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .gap-px {
|
|
5254
5322
|
gap: 1px;
|
|
5255
5323
|
}
|
|
@@ -5476,6 +5544,15 @@
|
|
|
5476
5544
|
}
|
|
5477
5545
|
}
|
|
5478
5546
|
|
|
5547
|
+
@layer daisyui.l1.l2 {
|
|
5548
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .badge-outline {
|
|
5549
|
+
color: var(--badge-color);
|
|
5550
|
+
--badge-bg: #0000;
|
|
5551
|
+
background-image: none;
|
|
5552
|
+
border-color: currentColor;
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
|
|
5479
5556
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .border-\[color-mix\(in_oklab\,var\(--cairn-card-border\)_70\%\,transparent\)\] {
|
|
5480
5557
|
border-color: var(--cairn-card-border);
|
|
5481
5558
|
}
|
|
@@ -5546,7 +5623,7 @@
|
|
|
5546
5623
|
}
|
|
5547
5624
|
}
|
|
5548
5625
|
|
|
5549
|
-
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .border-\[var\(--cairn-card-border\)\] {
|
|
5626
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .border-\[color\:var\(--cairn-card-border\)\], :where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .border-\[var\(--cairn-card-border\)\] {
|
|
5550
5627
|
border-color: var(--cairn-card-border);
|
|
5551
5628
|
}
|
|
5552
5629
|
|
|
@@ -6245,6 +6322,18 @@
|
|
|
6245
6322
|
padding-inline: calc(var(--spacing) * 7);
|
|
6246
6323
|
}
|
|
6247
6324
|
|
|
6325
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .px-\[0\.1rem\] {
|
|
6326
|
+
padding-inline: .1rem;
|
|
6327
|
+
}
|
|
6328
|
+
|
|
6329
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .px-\[0\.9rem\] {
|
|
6330
|
+
padding-inline: .9rem;
|
|
6331
|
+
}
|
|
6332
|
+
|
|
6333
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .px-\[var\(--cairn-space-m\)\] {
|
|
6334
|
+
padding-inline: var(--cairn-space-m);
|
|
6335
|
+
}
|
|
6336
|
+
|
|
6248
6337
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .px-px {
|
|
6249
6338
|
padding-inline: 1px;
|
|
6250
6339
|
}
|
|
@@ -6313,10 +6402,26 @@
|
|
|
6313
6402
|
padding-block: calc(var(--spacing) * 16);
|
|
6314
6403
|
}
|
|
6315
6404
|
|
|
6405
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .py-\[0\.3rem\] {
|
|
6406
|
+
padding-block: .3rem;
|
|
6407
|
+
}
|
|
6408
|
+
|
|
6409
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .py-\[0\.5rem\] {
|
|
6410
|
+
padding-block: .5rem;
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6316
6413
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .py-\[5px\] {
|
|
6317
6414
|
padding-block: 5px;
|
|
6318
6415
|
}
|
|
6319
6416
|
|
|
6417
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .py-\[var\(--cairn-space-xl\)\] {
|
|
6418
|
+
padding-block: var(--cairn-space-xl);
|
|
6419
|
+
}
|
|
6420
|
+
|
|
6421
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .py-\[var\(--cairn-space-xs\)\] {
|
|
6422
|
+
padding-block: var(--cairn-space-xs);
|
|
6423
|
+
}
|
|
6424
|
+
|
|
6320
6425
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .py-px {
|
|
6321
6426
|
padding-block: 1px;
|
|
6322
6427
|
}
|
|
@@ -6341,6 +6446,18 @@
|
|
|
6341
6446
|
padding-top: calc(var(--spacing) * 4);
|
|
6342
6447
|
}
|
|
6343
6448
|
|
|
6449
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .pt-\[var\(--cairn-space-l\)\] {
|
|
6450
|
+
padding-top: var(--cairn-space-l);
|
|
6451
|
+
}
|
|
6452
|
+
|
|
6453
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .pt-\[var\(--cairn-space-s\)\] {
|
|
6454
|
+
padding-top: var(--cairn-space-s);
|
|
6455
|
+
}
|
|
6456
|
+
|
|
6457
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .pr-1 {
|
|
6458
|
+
padding-right: calc(var(--spacing) * 1);
|
|
6459
|
+
}
|
|
6460
|
+
|
|
6344
6461
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .pr-3 {
|
|
6345
6462
|
padding-right: calc(var(--spacing) * 3);
|
|
6346
6463
|
}
|
|
@@ -6353,6 +6470,10 @@
|
|
|
6353
6470
|
padding-bottom: calc(var(--spacing) * 1.5);
|
|
6354
6471
|
}
|
|
6355
6472
|
|
|
6473
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .pb-\[var\(--cairn-space-xl\)\] {
|
|
6474
|
+
padding-bottom: var(--cairn-space-xl);
|
|
6475
|
+
}
|
|
6476
|
+
|
|
6356
6477
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .pl-3 {
|
|
6357
6478
|
padding-left: calc(var(--spacing) * 3);
|
|
6358
6479
|
}
|
|
@@ -6377,6 +6498,10 @@
|
|
|
6377
6498
|
text-align: right;
|
|
6378
6499
|
}
|
|
6379
6500
|
|
|
6501
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .font-\[family-name\:var\(--font-body\)\] {
|
|
6502
|
+
font-family: var(--font-body);
|
|
6503
|
+
}
|
|
6504
|
+
|
|
6380
6505
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .font-\[family-name\:var\(--font-display\)\] {
|
|
6381
6506
|
font-family: var(--font-display);
|
|
6382
6507
|
}
|
|
@@ -6482,12 +6607,34 @@
|
|
|
6482
6607
|
font-size: 1.0625rem;
|
|
6483
6608
|
}
|
|
6484
6609
|
|
|
6610
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-\[length\:var\(--cairn-step--1\)\] {
|
|
6611
|
+
font-size: var(--cairn-step--1);
|
|
6612
|
+
}
|
|
6613
|
+
|
|
6614
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-\[length\:var\(--cairn-step-1\)\] {
|
|
6615
|
+
font-size: var(--cairn-step-1);
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6618
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-\[length\:var\(--cairn-step-5\)\] {
|
|
6619
|
+
font-size: var(--cairn-step-5);
|
|
6620
|
+
}
|
|
6621
|
+
|
|
6485
6622
|
@layer daisyui.l1.l2 {
|
|
6486
6623
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .textarea-sm {
|
|
6487
6624
|
font-size: max(var(--font-size, .75rem), .75rem);
|
|
6488
6625
|
}
|
|
6489
6626
|
}
|
|
6490
6627
|
|
|
6628
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .leading-\[var\(--cairn-leading-snug\)\] {
|
|
6629
|
+
--tw-leading: var(--cairn-leading-snug);
|
|
6630
|
+
line-height: var(--cairn-leading-snug);
|
|
6631
|
+
}
|
|
6632
|
+
|
|
6633
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .leading-\[var\(--cairn-leading-tight\)\] {
|
|
6634
|
+
--tw-leading: var(--cairn-leading-tight);
|
|
6635
|
+
line-height: var(--cairn-leading-tight);
|
|
6636
|
+
}
|
|
6637
|
+
|
|
6491
6638
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .leading-relaxed {
|
|
6492
6639
|
--tw-leading: var(--leading-relaxed);
|
|
6493
6640
|
line-height: var(--leading-relaxed);
|
|
@@ -6548,6 +6695,16 @@
|
|
|
6548
6695
|
letter-spacing: .12em;
|
|
6549
6696
|
}
|
|
6550
6697
|
|
|
6698
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .tracking-\[var\(--cairn-tracking-eyebrow\)\] {
|
|
6699
|
+
--tw-tracking: var(--cairn-tracking-eyebrow);
|
|
6700
|
+
letter-spacing: var(--cairn-tracking-eyebrow);
|
|
6701
|
+
}
|
|
6702
|
+
|
|
6703
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .tracking-\[var\(--cairn-tracking-tight\)\] {
|
|
6704
|
+
--tw-tracking: var(--cairn-tracking-tight);
|
|
6705
|
+
letter-spacing: var(--cairn-tracking-tight);
|
|
6706
|
+
}
|
|
6707
|
+
|
|
6551
6708
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .tracking-tight {
|
|
6552
6709
|
--tw-tracking: var(--tracking-tight);
|
|
6553
6710
|
letter-spacing: var(--tracking-tight);
|
|
@@ -6598,6 +6755,18 @@
|
|
|
6598
6755
|
}
|
|
6599
6756
|
}
|
|
6600
6757
|
|
|
6758
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-\[Npx\] {
|
|
6759
|
+
color: Npx;
|
|
6760
|
+
}
|
|
6761
|
+
|
|
6762
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-\[Nrem\] {
|
|
6763
|
+
color: Nrem;
|
|
6764
|
+
}
|
|
6765
|
+
|
|
6766
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-\[color\:var\(--cairn-muted\)\] {
|
|
6767
|
+
color: var(--cairn-muted);
|
|
6768
|
+
}
|
|
6769
|
+
|
|
6601
6770
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .text-\[var\(--cairn-error-ink\)\] {
|
|
6602
6771
|
color: var(--cairn-error-ink);
|
|
6603
6772
|
}
|
|
@@ -7358,6 +7527,10 @@
|
|
|
7358
7527
|
}
|
|
7359
7528
|
}
|
|
7360
7529
|
|
|
7530
|
+
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .focus\:top-\[var\(--cairn-space-s\)\]:focus {
|
|
7531
|
+
top: var(--cairn-space-s);
|
|
7532
|
+
}
|
|
7533
|
+
|
|
7361
7534
|
:where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .focus-visible\:border-\[color-mix\(in_oklab\,var\(--color-primary\)_70\%\,transparent\)\]:focus-visible {
|
|
7362
7535
|
border-color: var(--color-primary);
|
|
7363
7536
|
}
|
|
@@ -32,6 +32,11 @@ export interface AddressEntry {
|
|
|
32
32
|
}
|
|
33
33
|
/** Permalink to the distinct entries that resolve to it, across main and every open branch. */
|
|
34
34
|
export type AddressIndex = Map<string, AddressEntry[]>;
|
|
35
|
+
/**
|
|
36
|
+
* The address index over main only: a synchronous reverse map of each manifest entry's resolved
|
|
37
|
+
* permalink. No backend read, so an edit-load can build it for free from the manifest it already holds.
|
|
38
|
+
*/
|
|
39
|
+
export declare function mainAddressIndex(manifest: Manifest): AddressIndex;
|
|
35
40
|
/**
|
|
36
41
|
* Build the permalink-keyed address index over main (from each manifest entry's resolved permalink)
|
|
37
42
|
* plus every open cairn/* branch (resolved from its edited markdown).
|
|
@@ -14,17 +14,11 @@ function push(index, permalink, entry) {
|
|
|
14
14
|
index.set(permalink, [entry]);
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* The build fails open: a branch read that throws and a permalink that cannot resolve are both caught
|
|
21
|
-
* and skipped, so a transient failure degrades to a thinner index, never a thrown editor or a blocked
|
|
22
|
-
* publish. The branches are read in one Promise.all, the way buildUsageIndex reads them.
|
|
17
|
+
* The address index over main only: a synchronous reverse map of each manifest entry's resolved
|
|
18
|
+
* permalink. No backend read, so an edit-load can build it for free from the manifest it already holds.
|
|
23
19
|
*/
|
|
24
|
-
export
|
|
20
|
+
export function mainAddressIndex(manifest) {
|
|
25
21
|
const index = new Map();
|
|
26
|
-
// The main arm: the manifest already carries each entry's resolved permalink, so this is a pure
|
|
27
|
-
// reverse map with no per-file read.
|
|
28
22
|
for (const entry of manifest.entries) {
|
|
29
23
|
push(index, entry.permalink, {
|
|
30
24
|
concept: entry.concept,
|
|
@@ -33,6 +27,20 @@ export async function buildAddressIndex(repo, token, concepts, manifest) {
|
|
|
33
27
|
source: 'main',
|
|
34
28
|
});
|
|
35
29
|
}
|
|
30
|
+
return index;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build the permalink-keyed address index over main (from each manifest entry's resolved permalink)
|
|
34
|
+
* plus every open cairn/* branch (resolved from its edited markdown).
|
|
35
|
+
*
|
|
36
|
+
* The build fails open: a branch read that throws and a permalink that cannot resolve are both caught
|
|
37
|
+
* and skipped, so a transient failure degrades to a thinner index, never a thrown editor or a blocked
|
|
38
|
+
* publish. The branches are read in one Promise.all, the way buildUsageIndex reads them.
|
|
39
|
+
*/
|
|
40
|
+
export async function buildAddressIndex(repo, token, concepts, manifest) {
|
|
41
|
+
// The main arm: the manifest already carries each entry's resolved permalink, so seed from the
|
|
42
|
+
// synchronous main-only index and union the branch arm on top.
|
|
43
|
+
const index = mainAddressIndex(manifest);
|
|
36
44
|
// The branch arm: read each open cairn/* branch's one edited file and resolve its permalink. The
|
|
37
45
|
// path is derivable from the branch name, so no tree-listing is needed.
|
|
38
46
|
const names = await listBranches(repo, PENDING_PREFIX, token);
|