@glw907/cairn-cms 0.59.0 → 0.60.1
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 +60 -0
- package/dist/components/AdminLayout.svelte +130 -229
- package/dist/components/CairnAdmin.svelte +12 -41
- package/dist/components/CairnLogo.svelte +1 -6
- package/dist/components/CairnMediaLibrary.svelte +821 -1210
- package/dist/components/CairnTidySettings.svelte +486 -0
- package/dist/components/CairnTidySettings.svelte.d.ts +32 -0
- package/dist/components/ComponentForm.svelte +110 -185
- package/dist/components/ComponentInsertDialog.svelte +163 -283
- package/dist/components/ConceptList.svelte +111 -191
- package/dist/components/ConfirmPage.svelte +5 -12
- package/dist/components/CsrfField.svelte +5 -11
- package/dist/components/DeleteDialog.svelte +15 -42
- package/dist/components/EditPage.svelte +786 -918
- package/dist/components/EditorToolbar.svelte +108 -170
- package/dist/components/IconPicker.svelte +23 -53
- package/dist/components/LinkPicker.svelte +34 -58
- package/dist/components/LoginPage.svelte +14 -27
- package/dist/components/ManageEditors.svelte +3 -15
- package/dist/components/MarkdownEditor.svelte +688 -789
- package/dist/components/MarkdownEditor.svelte.d.ts +44 -0
- package/dist/components/MarkdownHelpDialog.svelte +8 -12
- package/dist/components/MediaCaptureCard.svelte +18 -57
- package/dist/components/MediaFigureControl.svelte +32 -71
- package/dist/components/MediaHeroField.svelte +210 -329
- package/dist/components/MediaInsertPopover.svelte +156 -283
- package/dist/components/MediaPicker.svelte +67 -131
- package/dist/components/NavTree.svelte +46 -78
- package/dist/components/RenameDialog.svelte +16 -43
- package/dist/components/ShortcutsDialog.svelte +9 -13
- package/dist/components/ShortcutsGrid.svelte +1 -2
- package/dist/components/TidyReview.svelte +355 -0
- package/dist/components/TidyReview.svelte.d.ts +47 -0
- package/dist/components/WebLinkDialog.svelte +19 -40
- package/dist/components/cairn-admin.css +768 -0
- package/dist/components/editor-tidy.d.ts +31 -0
- package/dist/components/editor-tidy.js +199 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/markdown-directives.d.ts +16 -0
- package/dist/components/markdown-directives.js +34 -0
- package/dist/components/objective-errors.d.ts +30 -0
- package/dist/components/objective-errors.js +113 -0
- package/dist/components/spellcheck-assets/dictionary-en-us.txt +104743 -0
- package/dist/components/spellcheck-assets/spellchecker-wasm-LICENSE.txt +21 -0
- package/dist/components/spellcheck-assets/spellchecker-wasm.wasm +0 -0
- package/dist/components/spellcheck-worker.d.ts +80 -0
- package/dist/components/spellcheck-worker.js +161 -0
- package/dist/components/spellcheck.d.ts +148 -0
- package/dist/components/spellcheck.js +553 -0
- package/dist/components/tidy-categorize.d.ts +67 -0
- package/dist/components/tidy-categorize.js +392 -0
- package/dist/components/tidy-diff.d.ts +60 -0
- package/dist/components/tidy-diff.js +147 -0
- package/dist/components/tidy-validate.d.ts +37 -0
- package/dist/components/tidy-validate.js +174 -0
- package/dist/content/compose.d.ts +1 -1
- package/dist/content/compose.js +11 -0
- package/dist/content/site-dictionary.d.ts +31 -0
- package/dist/content/site-dictionary.js +82 -0
- package/dist/content/types.d.ts +25 -0
- package/dist/delivery/CairnHead.svelte +8 -11
- package/dist/doctor/checks-local.d.ts +1 -0
- package/dist/doctor/checks-local.js +55 -6
- package/dist/doctor/index.js +2 -1
- package/dist/log/events.d.ts +1 -1
- package/dist/nav/site-config.d.ts +98 -0
- package/dist/nav/site-config.js +132 -0
- package/dist/sveltekit/admin-dispatch.d.ts +2 -0
- package/dist/sveltekit/admin-dispatch.js +6 -2
- package/dist/sveltekit/cairn-admin.d.ts +13 -1
- package/dist/sveltekit/cairn-admin.js +22 -3
- package/dist/sveltekit/content-routes.d.ts +135 -1
- package/dist/sveltekit/content-routes.js +351 -3
- package/dist/sveltekit/tidy-prompt.d.ts +11 -0
- package/dist/sveltekit/tidy-prompt.js +118 -0
- package/package.json +11 -2
- package/src/lib/components/CairnAdmin.svelte +3 -0
- package/src/lib/components/CairnTidySettings.svelte +553 -0
- package/src/lib/components/EditPage.svelte +371 -2
- package/src/lib/components/MarkdownEditor.svelte +168 -1
- package/src/lib/components/TidyReview.svelte +463 -0
- package/src/lib/components/cairn-admin.css +25 -0
- package/src/lib/components/editor-tidy.ts +241 -0
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/markdown-directives.ts +35 -0
- package/src/lib/components/objective-errors.ts +155 -0
- package/src/lib/components/spellcheck-assets/dictionary-en-us.txt +104743 -0
- package/src/lib/components/spellcheck-assets/spellchecker-wasm-LICENSE.txt +21 -0
- package/src/lib/components/spellcheck-assets/spellchecker-wasm.wasm +0 -0
- package/src/lib/components/spellcheck-worker.ts +279 -0
- package/src/lib/components/spellcheck.ts +693 -0
- package/src/lib/components/tidy-categorize.ts +460 -0
- package/src/lib/components/tidy-diff.ts +196 -0
- package/src/lib/components/tidy-validate.ts +202 -0
- package/src/lib/content/compose.ts +11 -1
- package/src/lib/content/site-dictionary.ts +84 -0
- package/src/lib/content/types.ts +25 -0
- package/src/lib/doctor/checks-local.ts +59 -5
- package/src/lib/doctor/index.ts +2 -0
- package/src/lib/log/events.ts +7 -1
- package/src/lib/nav/site-config.ts +197 -0
- package/src/lib/sveltekit/admin-dispatch.ts +7 -3
- package/src/lib/sveltekit/cairn-admin.ts +32 -4
- package/src/lib/sveltekit/content-routes.ts +504 -4
- package/src/lib/sveltekit/tidy-prompt.ts +153 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
The two-tier tidy settings screen (spec 2.8, Task 15). It follows the approved settings mockup
|
|
4
|
+
(2026-06-20-editor-copyedit-settings-final-mockup.html).
|
|
5
|
+
|
|
6
|
+
Two tiers with a truthful visibility gate:
|
|
7
|
+
- The DEVELOPER tier (the master switch, the API key, the model) is shown READ-ONLY: an editor sees
|
|
8
|
+
that tidy is enabled, a key is configured, and which model runs, but cannot edit any of it. The
|
|
9
|
+
literal deploy-time tokens sit in a marked "For your developer" sub-block.
|
|
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
|
|
12
|
+
by an honest labelled gate region with a read-only "what your developer needs to do" checklist and
|
|
13
|
+
a "spellcheck still works" reassurance. No teasing disabled controls sit in the tab order.
|
|
14
|
+
|
|
15
|
+
The resting state is the safe default: Fixes on, every style convention off, every variant collapsed.
|
|
16
|
+
Each binary toggle is the shipped check-and-tint aria-pressed button; each variant chooser is the
|
|
17
|
+
shipped pick-one recipe (role="radiogroup" over role="radio" with aria-checked, roving tabindex, the
|
|
18
|
+
check glyph as the non-color cue), never aria-pressed for a pick-one. A generated summary line and the
|
|
19
|
+
section counts live in always-present role="status" / aria-live="polite" regions, so a toggle
|
|
20
|
+
announces the new total; the per-keystroke diff examples are aria-hidden so the region is not chatty.
|
|
21
|
+
|
|
22
|
+
The save commits the conventions block to the same committed YAML the nav editor writes (one config
|
|
23
|
+
home), diffable and shared across editors.
|
|
24
|
+
-->
|
|
25
|
+
<script lang="ts">
|
|
26
|
+
import { untrack } from 'svelte';
|
|
27
|
+
import CsrfField from './CsrfField.svelte';
|
|
28
|
+
import CheckIcon from '@lucide/svelte/icons/check';
|
|
29
|
+
import CircleIcon from '@lucide/svelte/icons/circle';
|
|
30
|
+
import SettingsIcon from '@lucide/svelte/icons/settings';
|
|
31
|
+
import LockIcon from '@lucide/svelte/icons/lock';
|
|
32
|
+
import CodeIcon from '@lucide/svelte/icons/code-xml';
|
|
33
|
+
import ListIcon from '@lucide/svelte/icons/list';
|
|
34
|
+
import TriangleAlertIcon from '@lucide/svelte/icons/triangle-alert';
|
|
35
|
+
import InfoIcon from '@lucide/svelte/icons/info';
|
|
36
|
+
import ArrowRightIcon from '@lucide/svelte/icons/arrow-right';
|
|
37
|
+
import SparklesIcon from '@lucide/svelte/icons/sparkles';
|
|
38
|
+
import type { SettingsData } from '../sveltekit/content-routes.js';
|
|
39
|
+
import type { TidyConventions } from '../nav/site-config.js';
|
|
40
|
+
|
|
41
|
+
interface Props {
|
|
42
|
+
/** The two-tier settings load: the read-only developer facts, the truthful gate flag, and the
|
|
43
|
+
* resolved editor-tier conventions. */
|
|
44
|
+
data: SettingsData;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let { data }: Props = $props();
|
|
48
|
+
|
|
49
|
+
// The working copy of the editor-tier conventions: every control binds to this, and the save posts
|
|
50
|
+
// it. Seeded once from the load's resolved conventions, so the resting state IS the committed state.
|
|
51
|
+
// A fresh load remounts the screen (the route key), so seeding from the initial prop is correct.
|
|
52
|
+
let conv = $state<TidyConventions>(untrack(() => ({ ...data.conventions })));
|
|
53
|
+
|
|
54
|
+
// A multi-position style row: its config key, label, the variants (value + short label), and the
|
|
55
|
+
// diff example for the chosen variant.
|
|
56
|
+
type Variant = { value: string; label: string };
|
|
57
|
+
type StyleRow = {
|
|
58
|
+
key: keyof TidyConventions;
|
|
59
|
+
name: string;
|
|
60
|
+
/** The variants when the row is a multi-position toggle; absent for a plain on/off (en-dash). */
|
|
61
|
+
variants?: Variant[];
|
|
62
|
+
/** The radiogroup's label ("Write times as"). */
|
|
63
|
+
variantLabel?: string;
|
|
64
|
+
/** The generic "what it does" example, shown when the row is off (a hypothetical). */
|
|
65
|
+
egBefore: string;
|
|
66
|
+
egAfter: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// The style conventions, in mockup order. Each maps to one config field. The multi-position rows
|
|
70
|
+
// carry their variants; en-dash is a plain on/off.
|
|
71
|
+
const styleRows: StyleRow[] = [
|
|
72
|
+
{
|
|
73
|
+
key: 'oxfordComma',
|
|
74
|
+
name: 'Oxford comma',
|
|
75
|
+
variantLabel: 'Use the Oxford comma',
|
|
76
|
+
variants: [
|
|
77
|
+
{ value: 'always', label: 'Always' },
|
|
78
|
+
{ value: 'complex-only', label: 'Only in complex lists' },
|
|
79
|
+
{ value: 'never', label: 'Never' },
|
|
80
|
+
],
|
|
81
|
+
egBefore: 'wax, skins and poles',
|
|
82
|
+
egAfter: 'wax, skins, and poles',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 'emDash',
|
|
86
|
+
name: 'Em-dash style',
|
|
87
|
+
variantLabel: 'Write em dashes as',
|
|
88
|
+
variants: [
|
|
89
|
+
{ value: 'spaced', label: 'Spaced' },
|
|
90
|
+
{ value: 'closed', label: 'Closed' },
|
|
91
|
+
],
|
|
92
|
+
egBefore: 'grooming--early',
|
|
93
|
+
egAfter: 'grooming—early',
|
|
94
|
+
},
|
|
95
|
+
{ key: 'enDashRanges', name: 'En-dash in number ranges', egBefore: '9-11 am', egAfter: '9–11 am' },
|
|
96
|
+
{
|
|
97
|
+
key: 'ellipsis',
|
|
98
|
+
name: 'Ellipsis',
|
|
99
|
+
variantLabel: 'Write ellipses as',
|
|
100
|
+
variants: [
|
|
101
|
+
{ value: 'single-char', label: 'One character' },
|
|
102
|
+
{ value: 'three-dots', label: 'Three dots' },
|
|
103
|
+
],
|
|
104
|
+
egBefore: 'later...',
|
|
105
|
+
egAfter: 'later…',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: 'timeFormat',
|
|
109
|
+
name: 'Time format',
|
|
110
|
+
variantLabel: 'Write times as',
|
|
111
|
+
variants: [
|
|
112
|
+
{ value: '5 PM', label: '5 PM' },
|
|
113
|
+
{ value: '5pm', label: '5pm' },
|
|
114
|
+
{ value: '5 p.m.', label: '5 p.m.' },
|
|
115
|
+
],
|
|
116
|
+
egBefore: 'doors at 5pm',
|
|
117
|
+
egAfter: 'doors at 5 PM',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: 'numberStyle',
|
|
121
|
+
name: 'Number style',
|
|
122
|
+
variantLabel: 'Write numbers as',
|
|
123
|
+
variants: [
|
|
124
|
+
{ value: 'under-ten', label: 'Spell out under ten' },
|
|
125
|
+
{ value: 'under-hundred', label: 'Spell out under 100' },
|
|
126
|
+
{ value: 'always-numerals', label: 'Always numerals' },
|
|
127
|
+
],
|
|
128
|
+
egBefore: '7 inches of snow',
|
|
129
|
+
egAfter: 'seven inches of snow',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
key: 'measurements',
|
|
133
|
+
name: 'Measurements and units',
|
|
134
|
+
variantLabel: 'Write units as',
|
|
135
|
+
variants: [
|
|
136
|
+
{ value: 'abbreviate', label: 'Abbreviate' },
|
|
137
|
+
{ value: 'spell-out', label: 'Spell out' },
|
|
138
|
+
],
|
|
139
|
+
egBefore: '15 centimeters',
|
|
140
|
+
egAfter: '15 cm',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
key: 'percent',
|
|
144
|
+
name: 'Percent',
|
|
145
|
+
variantLabel: 'Write percent as',
|
|
146
|
+
variants: [
|
|
147
|
+
{ value: 'sign', label: 'Sign (%)' },
|
|
148
|
+
{ value: 'word', label: 'Word (percent)' },
|
|
149
|
+
],
|
|
150
|
+
egBefore: '30 percent',
|
|
151
|
+
egAfter: '30%',
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// The advanced (higher-risk) rows: plain on/off booleans behind a disclosure.
|
|
156
|
+
const advancedRows: { key: keyof TidyConventions; name: string; egBefore: string; egAfter: string }[] = [
|
|
157
|
+
{ key: 'smartQuotes', name: 'Curly quotes', egBefore: '"groomed"', egAfter: '“groomed”' },
|
|
158
|
+
{ key: 'brandCaps', name: 'Brand and proper-noun capitals', egBefore: 'github', egAfter: 'GitHub' },
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
// --- whether a row is on, generic over the config shape ---
|
|
162
|
+
// A boolean field is on when true; a multi-position field is on when it carries a variant.
|
|
163
|
+
function rowOn(key: keyof TidyConventions): boolean {
|
|
164
|
+
const v = conv[key];
|
|
165
|
+
return typeof v === 'boolean' ? v : v !== undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The default variant a multi-position toggle takes when turned on: the first listed (the mockup's
|
|
169
|
+
// leading position). A plain on/off uses true.
|
|
170
|
+
function defaultVariant(row: StyleRow): string | boolean {
|
|
171
|
+
return row.variants ? row.variants[0].value : true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function toggleStyle(row: StyleRow) {
|
|
175
|
+
if (rowOn(row.key)) {
|
|
176
|
+
// Off: a multi-position field collapses to undefined; a boolean field to false.
|
|
177
|
+
(conv[row.key] as unknown) = row.variants ? undefined : false;
|
|
178
|
+
} else {
|
|
179
|
+
(conv[row.key] as unknown) = defaultVariant(row);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function toggleBool(key: keyof TidyConventions) {
|
|
184
|
+
(conv[key] as unknown) = !rowOn(key);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function pickVariant(key: keyof TidyConventions, value: string) {
|
|
188
|
+
(conv[key] as unknown) = value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --- the live counts and the generated summary, in the role="status" regions ---
|
|
192
|
+
const styleOnCount = $derived(
|
|
193
|
+
styleRows.filter((r) => rowOn(r.key)).length + advancedRows.filter((r) => rowOn(r.key)).length,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// The "fix" clause names the always-on objective set plus any on style convention; the "leaves
|
|
197
|
+
// alone" clause names what stays untouched. Both are generated from the live config, so the line is
|
|
198
|
+
// always true for any combination.
|
|
199
|
+
const summaryFixes = $derived.by(() => {
|
|
200
|
+
const parts: string[] = [];
|
|
201
|
+
if (conv.fixes) parts.push('spelling', 'grammar', 'doubled words', 'spacing', 'capitals', 'end punctuation');
|
|
202
|
+
if (rowOn('oxfordComma')) parts.push('commas');
|
|
203
|
+
if (rowOn('timeFormat')) parts.push('time format');
|
|
204
|
+
if (rowOn('numberStyle')) parts.push('number style');
|
|
205
|
+
if (rowOn('measurements')) parts.push('units');
|
|
206
|
+
if (rowOn('percent')) parts.push('percent');
|
|
207
|
+
if (rowOn('emDash') || rowOn('enDashRanges')) parts.push('dashes');
|
|
208
|
+
if (rowOn('ellipsis')) parts.push('ellipses');
|
|
209
|
+
if (rowOn('smartQuotes')) parts.push('quotes');
|
|
210
|
+
if (rowOn('brandCaps')) parts.push('brand names');
|
|
211
|
+
return parts.length ? joinList(parts) : 'nothing yet';
|
|
212
|
+
});
|
|
213
|
+
const summaryLeaves = $derived.by(() => {
|
|
214
|
+
const parts: string[] = [];
|
|
215
|
+
if (!rowOn('oxfordComma')) parts.push('commas');
|
|
216
|
+
if (!rowOn('emDash') && !rowOn('enDashRanges')) parts.push('dashes');
|
|
217
|
+
if (!rowOn('numberStyle')) parts.push('number style');
|
|
218
|
+
if (!rowOn('measurements')) parts.push('units');
|
|
219
|
+
if (!rowOn('percent')) parts.push('percent');
|
|
220
|
+
if (!rowOn('smartQuotes')) parts.push('quotes');
|
|
221
|
+
if (!rowOn('brandCaps')) parts.push('brand names');
|
|
222
|
+
return parts.length ? joinList(parts) : 'nothing';
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
function joinList(parts: string[]): string {
|
|
226
|
+
if (parts.length === 1) return parts[0];
|
|
227
|
+
if (parts.length === 2) return `${parts[0]} and ${parts[1]}`;
|
|
228
|
+
return `${parts.slice(0, -1).join(', ')}, and ${parts[parts.length - 1]}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// --- section masters and the safe-default reset ---
|
|
232
|
+
function styleAllOn() {
|
|
233
|
+
for (const row of styleRows) if (!rowOn(row.key)) (conv[row.key] as unknown) = defaultVariant(row);
|
|
234
|
+
for (const row of advancedRows) (conv[row.key] as unknown) = true;
|
|
235
|
+
}
|
|
236
|
+
function styleAllOff() {
|
|
237
|
+
for (const row of styleRows) (conv[row.key] as unknown) = row.variants ? undefined : false;
|
|
238
|
+
for (const row of advancedRows) (conv[row.key] as unknown) = false;
|
|
239
|
+
}
|
|
240
|
+
function fixesAllOff() {
|
|
241
|
+
conv.fixes = false;
|
|
242
|
+
}
|
|
243
|
+
function fixesAllOn() {
|
|
244
|
+
conv.fixes = true;
|
|
245
|
+
}
|
|
246
|
+
// Reset to the safe resting default: Fixes on, every style and advanced toggle off, every variant
|
|
247
|
+
// collapsed. Never named a house style.
|
|
248
|
+
function resetSafeDefault() {
|
|
249
|
+
conv = { fixes: true, enDashRanges: false, smartQuotes: false, brandCaps: false };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// --- the radiogroup roving-tabindex handler, the CairnMediaLibrary triage recipe ---
|
|
253
|
+
// The selected radio is the only tab stop; Arrow/Home/End move the selection and the focus with
|
|
254
|
+
// wraparound. A declared radiogroup owes this keyboard model.
|
|
255
|
+
let radioEls = $state<Record<string, HTMLButtonElement[]>>(
|
|
256
|
+
Object.fromEntries(styleRows.filter((r) => r.variants).map((r) => [String(r.key), []])),
|
|
257
|
+
);
|
|
258
|
+
function onRadioKeydown(e: KeyboardEvent, row: StyleRow, i: number) {
|
|
259
|
+
if (!row.variants) return;
|
|
260
|
+
const n = row.variants.length;
|
|
261
|
+
let next = i;
|
|
262
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') next = (i + 1) % n;
|
|
263
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') next = (i - 1 + n) % n;
|
|
264
|
+
else if (e.key === 'Home') next = 0;
|
|
265
|
+
else if (e.key === 'End') next = n - 1;
|
|
266
|
+
else return;
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
pickVariant(row.key, row.variants[next].value);
|
|
269
|
+
radioEls[String(row.key)]?.[next]?.focus();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// The conventions payload the save posts: the live working copy as JSON.
|
|
273
|
+
const conventionsJson = $derived(JSON.stringify(conv));
|
|
274
|
+
|
|
275
|
+
// The shared class for a check-and-tint on/off button (the binary-state idiom, no DaisyUI .toggle).
|
|
276
|
+
function onoffClass(on: boolean): string {
|
|
277
|
+
return `inline-flex h-[30px] items-center gap-1.5 rounded-lg border px-2.5 text-xs font-semibold ${
|
|
278
|
+
on
|
|
279
|
+
? 'border-primary/30 bg-primary/10 text-primary'
|
|
280
|
+
: 'border-[var(--cairn-card-border)] bg-base-100 text-[var(--color-muted)] hover:border-primary/35 hover:text-base-content'
|
|
281
|
+
}`;
|
|
282
|
+
}
|
|
283
|
+
function segClass(on: boolean): string {
|
|
284
|
+
return `inline-flex items-center gap-1.5 px-3 py-1.5 text-xs ${on ? 'bg-primary/10 text-primary font-medium' : 'text-[var(--color-muted)]'}`;
|
|
285
|
+
}
|
|
286
|
+
</script>
|
|
287
|
+
|
|
288
|
+
<div class="mx-auto max-w-3xl px-2 py-2">
|
|
289
|
+
<!-- The office heading recipe: the display face, no eyebrow above the h1 -->
|
|
290
|
+
<h1 class="text-2xl font-bold tracking-tight">Tidy</h1>
|
|
291
|
+
<p class="mt-1.5 max-w-prose text-[0.9375rem] leading-relaxed text-[var(--color-muted)]">
|
|
292
|
+
A light copy-edit from Claude. Choose what tidy is allowed to change. You always review every
|
|
293
|
+
change as a diff before it lands.
|
|
294
|
+
</p>
|
|
295
|
+
|
|
296
|
+
{#if data.saved}
|
|
297
|
+
<div role="status" class="alert alert-success mt-4 text-sm">Tidy settings saved.</div>
|
|
298
|
+
{/if}
|
|
299
|
+
{#if data.error}
|
|
300
|
+
<div role="alert" class="alert alert-error mt-4 text-sm">{data.error}</div>
|
|
301
|
+
{/if}
|
|
302
|
+
|
|
303
|
+
<!-- DEVELOPER TIER, read-only: the three deploy-time facts the editor depends on, model included as
|
|
304
|
+
a stated fact (never an editable control). Shown in both the enabled and gate states. -->
|
|
305
|
+
{#if data.enabled}
|
|
306
|
+
<div class="mt-6 flex items-start gap-3 rounded-2xl border border-[var(--cairn-card-border)] bg-base-200 p-4">
|
|
307
|
+
<span class="mt-0.5 inline-flex h-9 w-9 flex-none items-center justify-center rounded-xl bg-base-content/[0.07] text-[var(--color-muted)]">
|
|
308
|
+
<CodeIcon class="h-5 w-5" aria-hidden="true" />
|
|
309
|
+
</span>
|
|
310
|
+
<div class="min-w-0 flex-1">
|
|
311
|
+
<div class="text-[0.8125rem] font-semibold">Tidy is set up for this site</div>
|
|
312
|
+
<div class="mt-0.5 text-xs leading-relaxed text-[var(--color-muted)]">
|
|
313
|
+
Your developer turned tidy on and chose how it runs. You cannot change these here.
|
|
314
|
+
</div>
|
|
315
|
+
<div class="mt-2.5 flex flex-col gap-1.5">
|
|
316
|
+
<div class="flex items-baseline gap-2 text-[0.8125rem]">
|
|
317
|
+
<span class="inline-flex min-w-[8.5rem] flex-none items-center gap-1.5 font-semibold text-[var(--color-positive-ink)]"><CheckIcon class="h-3.5 w-3.5 flex-none" aria-hidden="true" />Tidy</span>
|
|
318
|
+
<span>On for this site</span>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="flex items-baseline gap-2 text-[0.8125rem]">
|
|
321
|
+
<span class="inline-flex min-w-[8.5rem] flex-none items-center gap-1.5 font-semibold text-[var(--color-positive-ink)]"><CheckIcon class="h-3.5 w-3.5 flex-none" aria-hidden="true" />API key</span>
|
|
322
|
+
<span>Set, and kept on the server</span>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="flex items-baseline gap-2 text-[0.8125rem]">
|
|
325
|
+
<span class="inline-flex min-w-[8.5rem] flex-none items-center gap-1.5 font-semibold text-[var(--color-positive-ink)]"><CheckIcon class="h-3.5 w-3.5 flex-none" aria-hidden="true" />Model</span>
|
|
326
|
+
<span>{data.modelLabel} <span class="text-[var(--color-muted)]">· the careful default for a light copy-edit</span></span>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
<div class="mt-3 border-t border-dashed border-[var(--cairn-card-border)] pt-2.5">
|
|
330
|
+
<span class="inline-flex items-center gap-1.5 text-[0.625rem] font-semibold uppercase tracking-wide text-[var(--color-muted)]"><CodeIcon class="h-3 w-3" aria-hidden="true" />For your developer</span>
|
|
331
|
+
<div class="mt-1 text-xs leading-relaxed text-[var(--color-muted)]">
|
|
332
|
+
Tidy is on (<code class="rounded bg-[var(--cairn-code-chip)] px-1 font-mono text-[0.9em]">tidy.enabled</code>), the key rides in an Anthropic Worker secret (<code class="rounded bg-[var(--cairn-code-chip)] px-1 font-mono text-[0.9em]">ANTHROPIC_API_KEY</code>), and the model is <code class="rounded bg-[var(--cairn-code-chip)] px-1 font-mono text-[0.9em]">{data.model}</code>. Switch to <code class="rounded bg-[var(--cairn-code-chip)] px-1 font-mono text-[0.9em]">claude-haiku-4-5</code> for a cheaper, faster run.
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
<span class="mt-0.5 inline-flex flex-none items-center gap-1.5 whitespace-nowrap rounded-full border border-[var(--cairn-card-border)] px-2.5 py-1 text-[0.625rem] font-semibold text-[var(--color-muted)]"><LockIcon class="h-3 w-3" aria-hidden="true" />Set by your developer</span>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<!-- THE GENERATED SUMMARY LINE, inside the live region. Rendered unconditionally so it can
|
|
340
|
+
announce when it changes. -->
|
|
341
|
+
<div role="status" aria-live="polite" class="mb-6 mt-6 flex items-start gap-3 rounded-2xl border border-primary/[0.16] bg-primary/[0.05] p-3.5">
|
|
342
|
+
<span class="mt-0.5 inline-flex h-7 w-7 flex-none items-center justify-center rounded-lg bg-primary/[0.12] text-primary" aria-hidden="true"><ListIcon class="h-4 w-4" /></span>
|
|
343
|
+
<div class="min-w-0 flex-1 text-[0.8125rem] leading-relaxed">
|
|
344
|
+
<span class="font-semibold">Tidy will fix</span> {summaryFixes}.
|
|
345
|
+
<span class="text-[var(--color-muted)]"><b class="font-semibold text-[var(--color-subtle)]">It leaves alone</b> {summaryLeaves}.</span>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<form method="POST" action="?/saveSettings">
|
|
350
|
+
<CsrfField />
|
|
351
|
+
<input type="hidden" name="conventions" value={conventionsJson} />
|
|
352
|
+
|
|
353
|
+
<!-- SECTION: FIXES (the objective errors, one group toggle) -->
|
|
354
|
+
<section class="mb-6">
|
|
355
|
+
<div class="mb-3 flex items-end gap-3 px-0.5">
|
|
356
|
+
<div class="min-w-0 flex-1">
|
|
357
|
+
<h2 class="flex items-center gap-2 text-lg font-bold tracking-tight">
|
|
358
|
+
Fixes
|
|
359
|
+
<span role="status" aria-live="polite" class="rounded-full bg-base-content/[0.06] px-2 py-0.5 text-xs font-semibold tabular-nums text-[var(--color-muted)]">{conv.fixes ? 'On' : 'Off'}<span class="sr-only">, the fixes group is {conv.fixes ? 'on' : 'off'}</span></span>
|
|
360
|
+
</h2>
|
|
361
|
+
<p class="mt-1 max-w-prose text-[0.8125rem] leading-relaxed text-[var(--color-muted)]">Plain errors, not style choices. On by default. Leave them on unless you have a reason not to.</p>
|
|
362
|
+
</div>
|
|
363
|
+
<div class="flex flex-none items-center gap-1">
|
|
364
|
+
<button type="button" class="px-0.5 py-1 text-xs text-[var(--color-muted)] underline underline-offset-2 hover:text-primary" onclick={conv.fixes ? fixesAllOff : fixesAllOn}>{conv.fixes ? 'Turn off' : 'Turn on'}</button>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
<div class="overflow-hidden rounded-2xl border border-[var(--color-positive-ink)]/[0.22] bg-base-100 shadow-[var(--cairn-shadow)]">
|
|
368
|
+
<div class="flex items-center gap-4 p-3.5 {conv.fixes ? '' : 'opacity-60'}">
|
|
369
|
+
<div class="min-w-0 flex-1">
|
|
370
|
+
<div class="text-[0.9375rem] font-semibold leading-snug {conv.fixes ? '' : 'text-[var(--color-muted)]'}">Spelling, grammar, doubled words, spacing, capitals, end punctuation</div>
|
|
371
|
+
<div class="mt-1.5 flex flex-wrap items-center gap-1.5 font-mono text-[0.8125rem] leading-snug" aria-hidden="true">
|
|
372
|
+
<span class="mr-0.5 text-[0.6875rem] font-semibold uppercase tracking-wide text-[var(--color-muted)]">changes</span>
|
|
373
|
+
<span class="rounded-sm bg-[color-mix(in_oklab,var(--cairn-error-ink)_18%,transparent)] px-0.5 text-[var(--cairn-error-ink)] line-through">accomodate</span>
|
|
374
|
+
<span class="text-[0.6875rem] text-[var(--color-muted)]">to</span>
|
|
375
|
+
<span class="rounded-sm bg-[color-mix(in_oklab,var(--color-positive-ink)_20%,transparent)] px-0.5 text-[var(--color-positive-ink)]">accommodate</span>
|
|
376
|
+
</div>
|
|
377
|
+
<!-- the "kept as written" cue: regional spelling is never normalized, dialect-aware -->
|
|
378
|
+
<div class="mt-1.5 flex flex-wrap items-center gap-1.5 font-mono text-[0.8125rem] leading-snug" aria-hidden="true">
|
|
379
|
+
<span class="mr-0.5 text-[0.6875rem] font-semibold uppercase tracking-wide text-[var(--color-positive-ink)]">keeps</span>
|
|
380
|
+
<span class="rounded-sm bg-[var(--cairn-code-chip)] px-1">colour</span>
|
|
381
|
+
<span class="text-[0.6875rem] text-[var(--color-muted)]">and</span>
|
|
382
|
+
<span class="rounded-sm bg-[var(--cairn-code-chip)] px-1">organise</span>
|
|
383
|
+
<span class="text-[0.6875rem] text-[var(--color-muted)]">as written, following your site's English</span>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<span class="flex-none">
|
|
387
|
+
<button type="button" class={onoffClass(conv.fixes)} aria-pressed={conv.fixes} aria-label="Fixes" onclick={() => toggleBool('fixes')}>
|
|
388
|
+
{#if conv.fixes}<CheckIcon class="h-3.5 w-3.5" aria-hidden="true" />On{:else}<CircleIcon class="h-3 w-3 opacity-60" aria-hidden="true" />Off{/if}
|
|
389
|
+
</button>
|
|
390
|
+
</span>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
</section>
|
|
394
|
+
|
|
395
|
+
<!-- SECTION: STYLE CONVENTIONS (default off; on reveals an inline variant chooser) -->
|
|
396
|
+
<section class="mb-6">
|
|
397
|
+
<div class="mb-3 flex items-end gap-3 px-0.5">
|
|
398
|
+
<div class="min-w-0 flex-1">
|
|
399
|
+
<h2 class="flex items-center gap-2 text-lg font-bold tracking-tight">
|
|
400
|
+
Style conventions
|
|
401
|
+
<span role="status" aria-live="polite" class="rounded-full bg-base-content/[0.06] px-2 py-0.5 text-xs font-semibold tabular-nums text-[var(--color-muted)]">{styleOnCount} on<span class="sr-only">, {styleOnCount} style {styleOnCount === 1 ? 'convention' : 'conventions'} on</span></span>
|
|
402
|
+
</h2>
|
|
403
|
+
<p class="mt-1 max-w-prose text-[0.8125rem] leading-relaxed text-[var(--color-muted)]">Optional. cairn leaves your style alone until you turn one of these on. Turn one on to pick how it should read everywhere.</p>
|
|
404
|
+
</div>
|
|
405
|
+
<div class="flex flex-none items-center gap-1">
|
|
406
|
+
<button type="button" class="px-0.5 py-1 text-xs text-[var(--color-muted)] underline underline-offset-2 hover:text-primary" onclick={styleAllOn}>Turn all on</button>
|
|
407
|
+
<span class="text-xs text-[var(--color-muted)] opacity-40" aria-hidden="true">·</span>
|
|
408
|
+
<button type="button" class="px-0.5 py-1 text-xs text-[var(--color-muted)] underline underline-offset-2 hover:text-primary" onclick={styleAllOff}>Turn all off</button>
|
|
409
|
+
<span class="text-xs text-[var(--color-muted)] opacity-40" aria-hidden="true">·</span>
|
|
410
|
+
<button type="button" class="px-0.5 py-1 text-xs text-[var(--color-muted)] underline underline-offset-2 hover:text-primary" onclick={resetSafeDefault}>Reset to typos only</button>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
<div class="overflow-hidden rounded-2xl border border-[var(--cairn-card-border)] bg-base-100 shadow-[var(--cairn-shadow)]">
|
|
414
|
+
{#each styleRows as row, ri (row.key)}
|
|
415
|
+
{@const on = rowOn(row.key)}
|
|
416
|
+
<div class="flex gap-4 p-3.5 {ri > 0 ? 'border-t border-[var(--cairn-card-border)]' : ''} {on && row.variants ? 'items-start' : 'items-center'}">
|
|
417
|
+
<div class="min-w-0 flex-1">
|
|
418
|
+
<div class="text-[0.9375rem] font-semibold leading-snug">{row.name}</div>
|
|
419
|
+
{#if on && row.variants}
|
|
420
|
+
<!-- the inline variant chooser, revealed when the row is on: the shipped pick-one
|
|
421
|
+
recipe (radiogroup + radio + aria-checked + roving tabindex + check glyph) -->
|
|
422
|
+
<div class="mt-3 flex flex-col gap-2">
|
|
423
|
+
<div id={`tidy-var-${String(row.key)}`} class="text-[0.6875rem] font-semibold uppercase tracking-wide text-[var(--color-muted)]">{row.variantLabel}</div>
|
|
424
|
+
<div role="radiogroup" aria-labelledby={`tidy-var-${String(row.key)}`} class="inline-flex flex-wrap self-start overflow-hidden rounded-lg border border-[var(--cairn-card-border)] bg-base-100">
|
|
425
|
+
{#each row.variants as variant, vi (variant.value)}
|
|
426
|
+
{@const checked = conv[row.key] === variant.value}
|
|
427
|
+
<button
|
|
428
|
+
bind:this={radioEls[String(row.key)][vi]}
|
|
429
|
+
type="button"
|
|
430
|
+
role="radio"
|
|
431
|
+
aria-checked={checked}
|
|
432
|
+
tabindex={checked ? 0 : -1}
|
|
433
|
+
class="{segClass(checked)} {vi > 0 ? 'border-l border-[var(--cairn-card-border)]' : ''}"
|
|
434
|
+
onclick={() => pickVariant(row.key, variant.value)}
|
|
435
|
+
onkeydown={(e) => onRadioKeydown(e, row, vi)}
|
|
436
|
+
>
|
|
437
|
+
{#if checked}<CheckIcon class="h-3 w-3 flex-none" aria-hidden="true" />{/if}{variant.label}
|
|
438
|
+
</button>
|
|
439
|
+
{/each}
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
{:else}
|
|
443
|
+
<div class="mt-1.5 flex flex-wrap items-center gap-1.5 font-mono text-[0.8125rem] leading-snug {on ? '' : 'opacity-55'}" aria-hidden="true">
|
|
444
|
+
<span class="mr-0.5 text-[0.6875rem] font-semibold uppercase tracking-wide text-[var(--color-muted)]">changes</span>
|
|
445
|
+
<span class="rounded-sm bg-[color-mix(in_oklab,var(--cairn-error-ink)_18%,transparent)] px-0.5 text-[var(--cairn-error-ink)] line-through">{row.egBefore}</span>
|
|
446
|
+
<span class="text-[0.6875rem] text-[var(--color-muted)]">to</span>
|
|
447
|
+
<span class="rounded-sm bg-[color-mix(in_oklab,var(--color-positive-ink)_20%,transparent)] px-0.5 text-[var(--color-positive-ink)]">{row.egAfter}</span>
|
|
448
|
+
</div>
|
|
449
|
+
{/if}
|
|
450
|
+
</div>
|
|
451
|
+
<span class="flex-none {on && row.variants ? 'mt-0.5' : ''}">
|
|
452
|
+
<button type="button" class={onoffClass(on)} aria-pressed={on} aria-label={row.name} onclick={() => toggleStyle(row)}>
|
|
453
|
+
{#if on}<CheckIcon class="h-3.5 w-3.5" aria-hidden="true" />On{:else}<CircleIcon class="h-3 w-3 opacity-60" aria-hidden="true" />Off{/if}
|
|
454
|
+
</button>
|
|
455
|
+
</span>
|
|
456
|
+
</div>
|
|
457
|
+
{/each}
|
|
458
|
+
</div>
|
|
459
|
+
</section>
|
|
460
|
+
|
|
461
|
+
<!-- ADVANCED (default off, gated behind a disclosure, with a short risk note) -->
|
|
462
|
+
<section class="mb-6">
|
|
463
|
+
<details class="overflow-hidden rounded-2xl border border-[var(--cairn-card-border)] bg-base-100 shadow-[var(--cairn-shadow)]">
|
|
464
|
+
<summary class="flex cursor-pointer list-none items-center gap-3 p-3.5">
|
|
465
|
+
<span class="inline-flex h-7 w-7 flex-none items-center justify-center rounded-lg bg-base-content/[0.06] text-[var(--color-muted)]"><SettingsIcon class="h-4 w-4" aria-hidden="true" /></span>
|
|
466
|
+
<span class="min-w-0 flex-1">
|
|
467
|
+
<span class="flex items-center gap-2 text-[0.9375rem] font-semibold">Advanced <span class="rounded-full bg-warning/[0.14] px-2 py-0.5 text-[0.625rem] font-semibold uppercase tracking-wide text-[var(--cairn-warning-ink)]">Higher risk</span></span>
|
|
468
|
+
<span class="mt-0.5 block text-[0.8125rem] leading-snug text-[var(--color-muted)]">Two more changes that need a careful eye. Off by default. Open this only if you want them.</span>
|
|
469
|
+
</span>
|
|
470
|
+
<ArrowRightIcon class="h-4 w-4 flex-none text-[var(--color-muted)]" aria-hidden="true" />
|
|
471
|
+
</summary>
|
|
472
|
+
<div class="border-t border-[var(--cairn-card-border)]">
|
|
473
|
+
<div class="flex items-start gap-2.5 border-b border-[var(--cairn-card-border)] bg-warning/[0.08] p-3.5 text-[0.8125rem] leading-relaxed">
|
|
474
|
+
<TriangleAlertIcon class="mt-0.5 h-4 w-4 flex-none text-[var(--cairn-warning-ink)]" aria-hidden="true" />
|
|
475
|
+
<span>These two reach a little further than the rest, so check the diff with care. <b class="font-semibold">Curly quotes can trip on apostrophes</b>, and brand names only fix from a list cairn keeps. Review every change before it lands, the same as always.</span>
|
|
476
|
+
</div>
|
|
477
|
+
{#each advancedRows as row, ai (row.key)}
|
|
478
|
+
{@const on = rowOn(row.key)}
|
|
479
|
+
<div class="flex items-center gap-4 p-3.5 {ai > 0 ? 'border-t border-[var(--cairn-card-border)]' : ''}">
|
|
480
|
+
<div class="min-w-0 flex-1">
|
|
481
|
+
<div class="text-[0.9375rem] font-semibold leading-snug">{row.name}</div>
|
|
482
|
+
<div class="mt-1.5 flex flex-wrap items-center gap-1.5 font-mono text-[0.8125rem] leading-snug {on ? '' : 'opacity-55'}" aria-hidden="true">
|
|
483
|
+
<span class="mr-0.5 text-[0.6875rem] font-semibold uppercase tracking-wide text-[var(--color-muted)]">changes</span>
|
|
484
|
+
<span class="rounded-sm bg-[color-mix(in_oklab,var(--cairn-error-ink)_18%,transparent)] px-0.5 text-[var(--cairn-error-ink)] line-through">{row.egBefore}</span>
|
|
485
|
+
<span class="text-[0.6875rem] text-[var(--color-muted)]">to</span>
|
|
486
|
+
<span class="rounded-sm bg-[color-mix(in_oklab,var(--color-positive-ink)_20%,transparent)] px-0.5 text-[var(--color-positive-ink)]">{row.egAfter}</span>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
<span class="flex-none">
|
|
490
|
+
<button type="button" class={onoffClass(on)} aria-pressed={on} aria-label={row.name} onclick={() => toggleBool(row.key)}>
|
|
491
|
+
{#if on}<CheckIcon class="h-3.5 w-3.5" aria-hidden="true" />On{:else}<CircleIcon class="h-3 w-3 opacity-60" aria-hidden="true" />Off{/if}
|
|
492
|
+
</button>
|
|
493
|
+
</span>
|
|
494
|
+
</div>
|
|
495
|
+
{/each}
|
|
496
|
+
</div>
|
|
497
|
+
</details>
|
|
498
|
+
</section>
|
|
499
|
+
|
|
500
|
+
<!-- THE "NOT HERE YET" NOTE: honest, non-interactive -->
|
|
501
|
+
<div class="mb-2 rounded-2xl border border-dashed border-[var(--cairn-card-border)] bg-base-content/[0.015] p-4">
|
|
502
|
+
<div class="flex items-center gap-2 text-[0.8125rem] font-semibold"><InfoIcon class="h-4 w-4 text-[var(--color-muted)]" aria-hidden="true" />Not here yet</div>
|
|
503
|
+
<div class="mt-1.5 text-[0.8125rem] leading-relaxed text-[var(--color-muted)]">Two more conventions are held back for now. Both can change how your writing sounds, not just how it looks, so cairn leaves them out until they are safe to offer.</div>
|
|
504
|
+
<ul class="mt-2 flex flex-col gap-1.5">
|
|
505
|
+
<li class="flex items-start gap-2 text-[0.8125rem] leading-snug text-[var(--color-muted)]"><span class="flex-none font-semibold text-base-content">Your own custom rules</span><span class="flex-none opacity-50" aria-hidden="true">·</span><span>free-text instructions can reach into voice</span></li>
|
|
506
|
+
<li class="flex items-start gap-2 text-[0.8125rem] leading-snug text-[var(--color-muted)]"><span class="flex-none font-semibold text-base-content">Heading capitals</span><span class="flex-none opacity-50" aria-hidden="true">·</span><span>retitling your headings is a bigger change than it looks</span></li>
|
|
507
|
+
</ul>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<div class="flex items-center gap-3 pt-4">
|
|
511
|
+
<span class="flex min-w-0 flex-1 items-center gap-1.5 text-xs leading-snug text-[var(--color-muted)]">
|
|
512
|
+
<ArrowRightIcon class="h-3.5 w-3.5 flex-none" aria-hidden="true" />Saving commits your choices to the site config, so every editor shares them.
|
|
513
|
+
</span>
|
|
514
|
+
<button type="submit" class="btn btn-primary btn-sm">Save changes</button>
|
|
515
|
+
</div>
|
|
516
|
+
</form>
|
|
517
|
+
{:else}
|
|
518
|
+
<!-- THE VISIBILITY GATE: tidy NOT enabled by the developer. The convention list is genuinely
|
|
519
|
+
absent, not disabled. One honest labelled region names the deploy-time task and who does it,
|
|
520
|
+
with no disabled controls in the tab order. -->
|
|
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)]">
|
|
522
|
+
<span class="inline-flex h-12 w-12 items-center justify-center rounded-full bg-base-content/[0.06] text-[var(--color-muted)]"><SparklesIcon class="h-6 w-6" aria-hidden="true" /></span>
|
|
523
|
+
<div class="text-xl font-bold tracking-tight">Tidy is not set up yet</div>
|
|
524
|
+
<div class="max-w-[50ch] text-sm leading-relaxed text-[var(--color-muted)]">
|
|
525
|
+
Tidy uses Claude to copy-edit your drafts, so it sends your writing to Anthropic and costs a
|
|
526
|
+
little per use. That makes it a developer setup, not a switch in here. Once it is on, this page
|
|
527
|
+
is where you choose what it can change.
|
|
528
|
+
</div>
|
|
529
|
+
<div class="mt-1.5 flex w-full max-w-md flex-col gap-2.5 text-left">
|
|
530
|
+
<div class="flex items-start gap-2.5 rounded-xl border border-[var(--cairn-card-border)] bg-base-200 p-3 {data.tidyEnabled ? 'opacity-60' : ''}">
|
|
531
|
+
<span class="flex-none {data.tidyEnabled ? 'text-[var(--color-positive-ink)]' : 'text-[var(--color-subtle)]'}">
|
|
532
|
+
{#if data.tidyEnabled}<CheckIcon class="mt-0.5 h-4 w-4" aria-hidden="true" />{:else}<span class="inline-flex h-5 w-5 items-center justify-center rounded-full bg-base-content/[0.09] text-[0.6875rem] font-semibold">1</span>{/if}
|
|
533
|
+
</span>
|
|
534
|
+
<span class="text-[0.8125rem] leading-snug">Your developer turns tidy on for the site.<span class="mt-0.5 block text-[var(--color-muted)]">It is one setting in the site config.</span></span>
|
|
535
|
+
</div>
|
|
536
|
+
<div class="flex items-start gap-2.5 rounded-xl border border-[var(--cairn-card-border)] bg-base-200 p-3 {data.keyConfigured ? 'opacity-60' : ''}">
|
|
537
|
+
<span class="flex-none {data.keyConfigured ? 'text-[var(--color-positive-ink)]' : 'text-[var(--color-subtle)]'}">
|
|
538
|
+
{#if data.keyConfigured}<CheckIcon class="mt-0.5 h-4 w-4" aria-hidden="true" />{:else}<span class="inline-flex h-5 w-5 items-center justify-center rounded-full bg-base-content/[0.09] text-[0.6875rem] font-semibold">2</span>{/if}
|
|
539
|
+
</span>
|
|
540
|
+
<span class="text-[0.8125rem] leading-snug">Your developer adds an Anthropic API key.<span class="mt-0.5 block text-[var(--color-muted)]">It stays on the server and never reaches the browser.</span></span>
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
<div class="w-full max-w-md text-left">
|
|
544
|
+
<span class="inline-flex items-center gap-1.5 text-[0.625rem] font-semibold uppercase tracking-wide text-[var(--color-muted)]"><CodeIcon class="h-3 w-3" aria-hidden="true" />For your developer</span>
|
|
545
|
+
<div class="mt-1 text-xs leading-relaxed text-[var(--color-muted)]">Set <code class="rounded bg-[var(--cairn-code-chip)] px-1 font-mono text-[0.9em]">tidy.enabled: true</code> in the site config and add the Anthropic key as the <code class="rounded bg-[var(--cairn-code-chip)] px-1 font-mono text-[0.9em]">ANTHROPIC_API_KEY</code> Worker secret. The setup guide has the steps.</div>
|
|
546
|
+
</div>
|
|
547
|
+
<div class="mt-1 flex max-w-lg items-center gap-2.5 rounded-xl border border-[color-mix(in_oklab,var(--color-positive-ink)_22%,var(--cairn-card-border))] bg-[color-mix(in_oklab,var(--color-positive-ink)_8%,var(--color-base-100))] p-3 text-[0.8125rem] text-[var(--color-muted)]">
|
|
548
|
+
<CheckIcon class="h-4 w-4 flex-none text-[var(--color-positive-ink)]" aria-hidden="true" />
|
|
549
|
+
<span><b class="font-semibold text-base-content">Spellcheck is already working.</b> It runs in your browser, so it needs no setup and underlines misspellings as you type.</span>
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
{/if}
|
|
553
|
+
</div>
|