@cfbender/cesium 0.5.1 → 0.5.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.
@@ -13,6 +13,9 @@ export interface ThemePalette {
13
13
  olive: string;
14
14
  codeBg: string;
15
15
  codeFg: string;
16
+ diffAdd: string; // line-tint and connector color for additions
17
+ diffRemove: string; // line-tint and connector color for deletions
18
+ diffChange: string; // connector color for replacements
16
19
  }
17
20
 
18
21
  export interface ThemeFonts {
@@ -56,6 +59,9 @@ export const THEME_PRESETS: Readonly<Record<ThemePresetName, ThemePalette>> = {
56
59
  olive: "#8FA86E",
57
60
  codeBg: "#2B1F22",
58
61
  codeFg: "#DDD3C7",
62
+ diffAdd: "#6D9E60",
63
+ diffRemove: "#C75B5B",
64
+ diffChange: "#D4A85A",
59
65
  },
60
66
  // claret-light: deep-rose-on-warm-cream — derived from claret.nvim light palette.
61
67
  // (this is the old "claret" palette, now renamed)
@@ -72,6 +78,9 @@ export const THEME_PRESETS: Readonly<Record<ThemePresetName, ThemePalette>> = {
72
78
  olive: "#5A6B40",
73
79
  codeBg: "#180810",
74
80
  codeFg: "#DDD3C7",
81
+ diffAdd: "#5A6B40",
82
+ diffRemove: "#9E3838",
83
+ diffChange: "#B07A2A",
75
84
  },
76
85
  // claret: alias for claret-dark (backward compat)
77
86
  claret: {
@@ -87,6 +96,9 @@ export const THEME_PRESETS: Readonly<Record<ThemePresetName, ThemePalette>> = {
87
96
  olive: "#8FA86E",
88
97
  codeBg: "#2B1F22",
89
98
  codeFg: "#DDD3C7",
99
+ diffAdd: "#6D9E60",
100
+ diffRemove: "#C75B5B",
101
+ diffChange: "#D4A85A",
90
102
  },
91
103
  // Warm: ivory/clay/oat — the html-effectiveness reference palette.
92
104
  warm: {
@@ -102,6 +114,9 @@ export const THEME_PRESETS: Readonly<Record<ThemePresetName, ThemePalette>> = {
102
114
  olive: "#788C5D",
103
115
  codeBg: "#141413",
104
116
  codeFg: "#E8E6DE",
117
+ diffAdd: "#788C5D",
118
+ diffRemove: "#C0392B",
119
+ diffChange: "#D97757",
105
120
  },
106
121
  // Cool: desaturated blue-grey — technical, trustworthy.
107
122
  cool: {
@@ -117,6 +132,9 @@ export const THEME_PRESETS: Readonly<Record<ThemePresetName, ThemePalette>> = {
117
132
  olive: "#4E8A6A",
118
133
  codeBg: "#1B2333",
119
134
  codeFg: "#D8E0ED",
135
+ diffAdd: "#4E8A6A",
136
+ diffRemove: "#B6443A",
137
+ diffChange: "#3A7BB8",
120
138
  },
121
139
  // Mono: black/white/grey — editorial, high-contrast.
122
140
  mono: {
@@ -132,6 +150,9 @@ export const THEME_PRESETS: Readonly<Record<ThemePresetName, ThemePalette>> = {
132
150
  olive: "#5A7A5A",
133
151
  codeBg: "#111111",
134
152
  codeFg: "#EBEBEB",
153
+ diffAdd: "#5A7A5A",
154
+ diffRemove: "#A03A2B",
155
+ diffChange: "#666666",
135
156
  },
136
157
  // Paper: sepia/cream — soft, book-like, warm and aged.
137
158
  paper: {
@@ -147,6 +168,9 @@ export const THEME_PRESETS: Readonly<Record<ThemePresetName, ThemePalette>> = {
147
168
  olive: "#607848",
148
169
  codeBg: "#2A2218",
149
170
  codeFg: "#E8DEC8",
171
+ diffAdd: "#607848",
172
+ diffRemove: "#A0392B",
173
+ diffChange: "#B05A2A",
150
174
  },
151
175
  };
152
176
 
@@ -198,6 +222,9 @@ export function themeToCssVars(theme: ThemeTokens): string {
198
222
  --olive: ${colors.olive};
199
223
  --code-bg: ${colors.codeBg};
200
224
  --code-fg: ${colors.codeFg};
225
+ --diff-add: ${colors.diffAdd};
226
+ --diff-remove: ${colors.diffRemove};
227
+ --diff-change: ${colors.diffChange};
201
228
  --serif: ${fonts.serif};
202
229
  --sans: ${fonts.sans};
203
230
  --mono: ${fonts.mono};
@@ -936,6 +963,110 @@ textarea.cs-text { font-family: var(--mono); }
936
963
  font-weight: 600;
937
964
  z-index: 10;
938
965
  }
966
+
967
+ /* diff block */
968
+ .diff-block {
969
+ margin: var(--space-6, 1.5rem) 0;
970
+ border: 1.5px solid var(--rule);
971
+ border-radius: 12px;
972
+ overflow: hidden;
973
+ background: var(--code-bg);
974
+ font-family: var(--mono);
975
+ font-size: 13px;
976
+ color: var(--code-fg);
977
+ }
978
+ .diff-block.fallback pre {
979
+ margin: 0;
980
+ padding: 12px 14px;
981
+ white-space: pre;
982
+ overflow-x: auto;
983
+ }
984
+ .diff-header {
985
+ display: flex;
986
+ justify-content: space-between;
987
+ align-items: center;
988
+ padding: 8px 14px;
989
+ border-bottom: 1px solid color-mix(in oklab, var(--rule), transparent 40%);
990
+ background: color-mix(in oklab, var(--code-bg), var(--code-fg) 5%);
991
+ font-size: 12px;
992
+ color: color-mix(in oklab, var(--code-fg), transparent 30%);
993
+ }
994
+ .diff-filename { font-weight: 500; }
995
+ .diff-stat { font-variant-numeric: tabular-nums; display: inline-flex; gap: 8px; }
996
+ .diff-stat .add { color: var(--diff-add); }
997
+ .diff-stat .rem { color: var(--diff-remove); }
998
+
999
+ .diff-grid {
1000
+ display: grid;
1001
+ grid-template-columns: 1fr 60px 1fr;
1002
+ align-items: start;
1003
+ }
1004
+ .diff-side {
1005
+ list-style: none;
1006
+ margin: 0;
1007
+ padding: 8px 0;
1008
+ overflow-x: auto;
1009
+ min-width: 0;
1010
+ }
1011
+ .diff-line {
1012
+ display: grid;
1013
+ grid-template-columns: 3.25em 1fr;
1014
+ gap: 0;
1015
+ height: 22px;
1016
+ line-height: 22px;
1017
+ white-space: pre;
1018
+ }
1019
+ .diff-line .num {
1020
+ text-align: right;
1021
+ padding-right: 12px;
1022
+ color: color-mix(in oklab, var(--code-fg), transparent 60%);
1023
+ user-select: none;
1024
+ font-variant-numeric: tabular-nums;
1025
+ }
1026
+ .diff-line .content {
1027
+ padding-right: 14px;
1028
+ overflow: hidden;
1029
+ text-overflow: ellipsis;
1030
+ }
1031
+ .diff-line.add { background: color-mix(in oklab, transparent, var(--diff-add) 14%); }
1032
+ .diff-line.remove { background: color-mix(in oklab, transparent, var(--diff-remove) 14%); }
1033
+ .diff-line.hunk-sep {
1034
+ background: color-mix(in oklab, var(--code-bg), var(--code-fg) 8%);
1035
+ color: color-mix(in oklab, var(--code-fg), transparent 50%);
1036
+ font-style: italic;
1037
+ font-size: 11px;
1038
+ }
1039
+
1040
+ .diff-connector {
1041
+ position: relative;
1042
+ align-self: stretch;
1043
+ padding: 8px 0;
1044
+ background: color-mix(in oklab, var(--code-bg), var(--code-fg) 2%);
1045
+ border-left: 1px solid color-mix(in oklab, var(--rule), transparent 60%);
1046
+ border-right: 1px solid color-mix(in oklab, var(--rule), transparent 60%);
1047
+ }
1048
+ .diff-connector svg {
1049
+ display: block;
1050
+ width: 100%;
1051
+ }
1052
+ .diff-conn { stroke-width: 1; }
1053
+ .diff-conn.add { fill: var(--diff-add); stroke: var(--diff-add); fill-opacity: 0.22; }
1054
+ .diff-conn.remove { fill: var(--diff-remove); stroke: var(--diff-remove); fill-opacity: 0.22; }
1055
+ .diff-conn.change { fill: var(--diff-change); stroke: var(--diff-change); fill-opacity: 0.18; }
1056
+
1057
+ .diff-block figcaption {
1058
+ padding: 8px 14px;
1059
+ border-top: 1px solid color-mix(in oklab, var(--rule), transparent 40%);
1060
+ font-size: 12px;
1061
+ font-family: var(--sans);
1062
+ color: color-mix(in oklab, var(--code-fg), transparent 30%);
1063
+ }
1064
+
1065
+ @media (max-width: 720px) {
1066
+ .diff-grid { grid-template-columns: 1fr; }
1067
+ .diff-connector { display: none; }
1068
+ .diff-side.before { border-bottom: 1px solid color-mix(in oklab, var(--rule), transparent 60%); }
1069
+ }
939
1070
  `;
940
1071
  }
941
1072
 
@@ -439,7 +439,9 @@ function isPublishKind(val: unknown): val is PublishKind {
439
439
  // ─── Block validation ─────────────────────────────────────────────────────────
440
440
 
441
441
  type BlockValidationError = { path: string; message: string };
442
- type BlockValidationResult = { ok: true; blocks: Block[] } | { ok: false; errors: BlockValidationError[] };
442
+ type BlockValidationResult =
443
+ | { ok: true; blocks: Block[] }
444
+ | { ok: false; errors: BlockValidationError[] };
443
445
 
444
446
  function validateBlock(raw: unknown, path: string, depth: number): BlockValidationError[] {
445
447
  const errors: BlockValidationError[] = [];
@@ -484,7 +486,10 @@ function validateBlock(raw: unknown, path: string, depth: number): BlockValidati
484
486
  errors.push({ path, message: "section block requires children array" });
485
487
  } else {
486
488
  if (depth > 3) {
487
- errors.push({ path, message: `section nesting depth exceeds maximum of 3 (current depth: ${depth})` });
489
+ errors.push({
490
+ path,
491
+ message: `section nesting depth exceeds maximum of 3 (current depth: ${depth})`,
492
+ });
488
493
  } else {
489
494
  const children = b["children"] as unknown[];
490
495
  for (let i = 0; i < children.length; i++) {
@@ -518,7 +523,10 @@ function validateBlock(raw: unknown, path: string, depth: number): BlockValidati
518
523
  }
519
524
  case "code": {
520
525
  if (typeof b["lang"] !== "string" || (b["lang"] as string).trim() === "") {
521
- errors.push({ path, message: 'code block requires a non-empty lang (use "text" if unknown)' });
526
+ errors.push({
527
+ path,
528
+ message: 'code block requires a non-empty lang (use "text" if unknown)',
529
+ });
522
530
  }
523
531
  if (typeof b["code"] !== "string") {
524
532
  errors.push({ path, message: "code block requires code field" });
@@ -580,7 +588,10 @@ function validateBlock(raw: unknown, path: string, depth: number): BlockValidati
580
588
  const hasSvg = typeof b["svg"] === "string";
581
589
  const hasHtml = typeof b["html"] === "string";
582
590
  if (hasSvg && hasHtml) {
583
- errors.push({ path, message: "diagram block must have exactly one of svg or html, not both" });
591
+ errors.push({
592
+ path,
593
+ message: "diagram block must have exactly one of svg or html, not both",
594
+ });
584
595
  } else if (!hasSvg && !hasHtml) {
585
596
  errors.push({ path, message: "diagram block requires exactly one of svg or html" });
586
597
  }
@@ -592,6 +603,35 @@ function validateBlock(raw: unknown, path: string, depth: number): BlockValidati
592
603
  }
593
604
  break;
594
605
  }
606
+ case "diff": {
607
+ const hasPatch = "patch" in b && b["patch"] !== undefined;
608
+ const hasBefore = "before" in b && b["before"] !== undefined;
609
+ const hasAfter = "after" in b && b["after"] !== undefined;
610
+
611
+ if (hasPatch && (hasBefore || hasAfter)) {
612
+ errors.push({
613
+ path,
614
+ message: "provide exactly one of patch or before/after, not both",
615
+ });
616
+ } else if (!hasPatch && !hasBefore && !hasAfter) {
617
+ errors.push({
618
+ path,
619
+ message: "diff block requires either patch or before+after",
620
+ });
621
+ } else if (hasPatch) {
622
+ if (typeof b["patch"] !== "string" || (b["patch"] as string).trim() === "") {
623
+ errors.push({ path, message: "diff block patch must be a non-empty string" });
624
+ }
625
+ } else {
626
+ // before/after arm
627
+ if (hasBefore && !hasAfter) {
628
+ errors.push({ path, message: "diff block before/after arm requires both fields" });
629
+ } else if (!hasBefore && hasAfter) {
630
+ errors.push({ path, message: "diff block before/after arm requires both fields" });
631
+ }
632
+ }
633
+ break;
634
+ }
595
635
  }
596
636
 
597
637
  // Deep field validation against catalog schema — catches unknown fields (with "did you mean"
@@ -611,7 +651,10 @@ function validateBlocksArray(raw: unknown): BlockValidationResult {
611
651
  }
612
652
 
613
653
  if (raw.length > 1000) {
614
- return { ok: false, errors: [{ path: "blocks", message: "blocks array exceeds maximum of 1000 blocks" }] };
654
+ return {
655
+ ok: false,
656
+ errors: [{ path: "blocks", message: "blocks array exceeds maximum of 1000 blocks" }],
657
+ };
615
658
  }
616
659
 
617
660
  const allErrors: BlockValidationError[] = [];
@@ -627,7 +670,10 @@ function validateBlocksArray(raw: unknown): BlockValidationResult {
627
670
  if (blockType === "hero") {
628
671
  heroCount++;
629
672
  if (i !== 0) {
630
- allErrors.push({ path: `blocks[${i}]`, message: "hero block must be the first block if present" });
673
+ allErrors.push({
674
+ path: `blocks[${i}]`,
675
+ message: "hero block must be the first block if present",
676
+ });
631
677
  }
632
678
  }
633
679
  if (blockType === "tldr") {
@@ -740,9 +786,7 @@ export function validatePublishInput(input: unknown): ValidationResult<PublishIn
740
786
  // blocks branch
741
787
  const blocksResult = validateBlocksArray(raw["blocks"]);
742
788
  if (!blocksResult.ok) {
743
- const errorMessages = blocksResult.errors
744
- .map((e) => `${e.path}: ${e.message}`)
745
- .join("; ");
789
+ const errorMessages = blocksResult.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
746
790
  return { ok: false, error: `blocks validation failed — ${errorMessages}` };
747
791
  }
748
792
  return {
@@ -379,7 +379,11 @@ export async function ensureServerRunning(cfg: LifecycleConfig): Promise<Running
379
379
 
380
380
  // Use a spawn-only lock to prevent concurrent spawns. Release it immediately
381
381
  // after spawning so the child can acquire its own (.server-start.lock) lock.
382
- const spawnLock = await acquireLock({ lockPath: spawnLockPath, timeoutMs: 15_000, staleMs: 30_000 });
382
+ const spawnLock = await acquireLock({
383
+ lockPath: spawnLockPath,
384
+ timeoutMs: 15_000,
385
+ staleMs: 30_000,
386
+ });
383
387
  try {
384
388
  // Re-check after acquiring lock
385
389
  const existingAfterLock = readPidFile(pidFilePath);
@@ -236,9 +236,8 @@ function indexJs(): string {
236
236
  function renderEntryCard(entry: IndexEntry): string {
237
237
  const isSuperseded = entry.supersededBy !== null ? "1" : "0";
238
238
  const kindPill = `<span class="pill">${esc(entry.kind)}</span>`;
239
- const inputModeBadge = entry.inputMode !== undefined
240
- ? ` <span class="tag">${esc(entry.inputMode)}</span>`
241
- : "";
239
+ const inputModeBadge =
240
+ entry.inputMode !== undefined ? ` <span class="tag">${esc(entry.inputMode)}</span>` : "";
242
241
  const dateStr = `<span class="card-date">${esc(formatDate(entry.createdAt))}</span>`;
243
242
  const supersededBadge =
244
243
  entry.supersedes !== null
@@ -117,9 +117,7 @@ export function createPublishTool(
117
117
  html: tool.schema
118
118
  .string()
119
119
  .optional()
120
- .describe(
121
- "Body HTML — escape valve / legacy mode. Provide exactly one of html or blocks.",
122
- ),
120
+ .describe("Body HTML — escape valve / legacy mode. Provide exactly one of html or blocks."),
123
121
  blocks: tool.schema
124
122
  .array(tool.schema.any())
125
123
  .optional()
@@ -101,21 +101,17 @@ export async function generateStyleguideMarkdown(): Promise<string> {
101
101
  "- Inline: `**bold**`, `*italic*`, `` `code` ``, `[text](href)` (relative or anchor only).",
102
102
  );
103
103
  lines.push(
104
- "- HTML safelist: `<kbd>`, `<span class=\"pill\">`, `<span class=\"tag\">`. Anything else is escaped.",
104
+ '- HTML safelist: `<kbd>`, `<span class="pill">`, `<span class="tag">`. Anything else is escaped.',
105
105
  );
106
106
  lines.push("");
107
107
  lines.push("## When to reach for raw_html / diagram");
108
108
  lines.push("");
109
- lines.push(
110
- "- `diagram` — inline SVG visualizations or bespoke composed HTML diagrams.",
111
- );
109
+ lines.push("- `diagram` — inline SVG visualizations or bespoke composed HTML diagrams.");
112
110
  lines.push(
113
111
  "- `raw_html` — anything genuinely creative that doesn't fit a known block type." +
114
112
  " Include a `purpose` string describing what you're building.",
115
113
  );
116
- lines.push(
117
- "- Critique flags raw_html overuse (>2 blocks or >30% of body characters).",
118
- );
114
+ lines.push("- Critique flags raw_html overuse (>2 blocks or >30% of body characters).");
119
115
 
120
116
  return lines.join("\n");
121
117
  }