scribe-cms 0.0.5 → 0.0.7

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.
@@ -12,6 +12,7 @@ var genai = require('@google/genai');
12
12
  var dotenv = require('dotenv');
13
13
  var nodeServer = require('@hono/node-server');
14
14
  var hono = require('hono');
15
+ var core = require('@inquirer/core');
15
16
  var prompts = require('@inquirer/prompts');
16
17
  var url = require('url');
17
18
 
@@ -25,7 +26,6 @@ var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
25
26
 
26
27
  var __defProp = Object.defineProperty;
27
28
  var __getOwnPropNames = Object.getOwnPropertyNames;
28
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
29
29
  var __esm = (fn, res) => function __init() {
30
30
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
31
31
  };
@@ -33,7 +33,6 @@ var __export = (target, all) => {
33
33
  for (var name in all)
34
34
  __defProp(target, name, { get: all[name], enumerable: true });
35
35
  };
36
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
37
36
 
38
37
  // ../../node_modules/tsup/assets/cjs_shims.js
39
38
  var getImportMetaUrl, importMetaUrl;
@@ -71,12 +70,19 @@ function addColumnIfMissing(db, table, column, ddlType) {
71
70
  db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${ddlType}`);
72
71
  }
73
72
  }
73
+ function readSchemaVersion(db) {
74
+ const row = db.prepare(`SELECT value FROM meta WHERE key = 'schema_version'`).get();
75
+ return row ? Number.parseInt(row.value, 10) || 0 : 0;
76
+ }
74
77
  function migrate(db) {
78
+ const previousVersion = readSchemaVersion(db);
75
79
  for (const sql of MIGRATIONS) {
76
80
  db.exec(sql);
77
81
  }
78
- addColumnIfMissing(db, "revisions", "frontmatter_json", "TEXT");
79
- addColumnIfMissing(db, "revisions", "body", "TEXT");
82
+ if (previousVersion < 4) {
83
+ db.exec(`DROP TABLE IF EXISTS revisions`);
84
+ }
85
+ addColumnIfMissing(db, "translations", "snapshot_id", "INTEGER");
80
86
  db.prepare(
81
87
  `INSERT INTO meta(key, value) VALUES('schema_version', ?)
82
88
  ON CONFLICT(key) DO UPDATE SET value = excluded.value`
@@ -89,7 +95,7 @@ var SCHEMA_VERSION, MIGRATIONS;
89
95
  var init_sqlite = __esm({
90
96
  "src/storage/sqlite.ts"() {
91
97
  init_cjs_shims();
92
- SCHEMA_VERSION = 3;
98
+ SCHEMA_VERSION = 4;
93
99
  MIGRATIONS = [
94
100
  `CREATE TABLE IF NOT EXISTS meta (
95
101
  key TEXT PRIMARY KEY,
@@ -109,20 +115,6 @@ var init_sqlite = __esm({
109
115
  )`,
110
116
  `CREATE INDEX IF NOT EXISTS idx_translations_type_locale
111
117
  ON translations(content_type, locale)`,
112
- `CREATE TABLE IF NOT EXISTS revisions (
113
- id INTEGER PRIMARY KEY AUTOINCREMENT,
114
- content_type TEXT NOT NULL,
115
- en_slug TEXT NOT NULL,
116
- locale TEXT,
117
- revision_kind TEXT NOT NULL,
118
- en_hash TEXT NOT NULL,
119
- body_hash TEXT NOT NULL,
120
- created_at TEXT NOT NULL,
121
- model TEXT,
122
- body_preview TEXT
123
- )`,
124
- `CREATE INDEX IF NOT EXISTS idx_revisions_lookup
125
- ON revisions(content_type, en_slug, locale, created_at DESC)`,
126
118
  `CREATE TABLE IF NOT EXISTS slug_aliases (
127
119
  content_type TEXT NOT NULL,
128
120
  canonical_en_slug TEXT NOT NULL,
@@ -139,7 +131,19 @@ var init_sqlite = __esm({
139
131
  locale_slug TEXT NOT NULL,
140
132
  captured_at TEXT NOT NULL,
141
133
  PRIMARY KEY (content_type, alias_en_slug, locale)
142
- )`
134
+ )`,
135
+ `CREATE TABLE IF NOT EXISTS en_snapshots (
136
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
137
+ content_type TEXT NOT NULL,
138
+ en_slug TEXT NOT NULL,
139
+ en_hash TEXT NOT NULL,
140
+ frontmatter_json TEXT NOT NULL,
141
+ body TEXT NOT NULL,
142
+ created_at TEXT NOT NULL,
143
+ UNIQUE (content_type, en_slug, en_hash)
144
+ )`,
145
+ `CREATE INDEX IF NOT EXISTS idx_en_snapshots_lookup
146
+ ON en_snapshots(content_type, en_slug, created_at DESC)`
143
147
  ];
144
148
  }
145
149
  });
@@ -613,18 +617,51 @@ init_sqlite();
613
617
 
614
618
  // src/storage/translations.ts
615
619
  init_cjs_shims();
620
+ function getOrCreateEnSnapshot(db, input) {
621
+ db.prepare(
622
+ `INSERT OR IGNORE INTO en_snapshots (
623
+ content_type, en_slug, en_hash, frontmatter_json, body, created_at
624
+ ) VALUES (?, ?, ?, ?, ?, ?)`
625
+ ).run(
626
+ input.contentType,
627
+ input.enSlug,
628
+ input.enHash,
629
+ JSON.stringify(input.frontmatter),
630
+ input.body,
631
+ input.createdAt
632
+ );
633
+ const row = db.prepare(
634
+ `SELECT id FROM en_snapshots
635
+ WHERE content_type = ? AND en_slug = ? AND en_hash = ?`
636
+ ).get(input.contentType, input.enSlug, input.enHash);
637
+ return row.id;
638
+ }
639
+ function getEnSnapshot(db, snapshotId) {
640
+ return db.prepare(`SELECT * FROM en_snapshots WHERE id = ?`).get(snapshotId);
641
+ }
642
+ function listEnSnapshotsForEnSlug(db, contentType, enSlug) {
643
+ return db.prepare(
644
+ `SELECT s.*, GROUP_CONCAT(t.locale) AS locales
645
+ FROM en_snapshots s
646
+ LEFT JOIN translations t ON t.snapshot_id = s.id
647
+ WHERE s.content_type = ? AND s.en_slug = ?
648
+ GROUP BY s.id
649
+ ORDER BY s.created_at DESC, s.id DESC`
650
+ ).all(contentType, enSlug);
651
+ }
616
652
  function upsertTranslation(db, input) {
617
653
  db.prepare(
618
654
  `INSERT INTO translations (
619
- content_type, en_slug, locale, slug, frontmatter_json, body, en_hash, translated_at, model
620
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
655
+ content_type, en_slug, locale, slug, frontmatter_json, body, en_hash, translated_at, model, snapshot_id
656
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
621
657
  ON CONFLICT(content_type, en_slug, locale) DO UPDATE SET
622
658
  slug = excluded.slug,
623
659
  frontmatter_json = excluded.frontmatter_json,
624
660
  body = excluded.body,
625
661
  en_hash = excluded.en_hash,
626
662
  translated_at = excluded.translated_at,
627
- model = excluded.model`
663
+ model = excluded.model,
664
+ snapshot_id = excluded.snapshot_id`
628
665
  ).run(
629
666
  input.contentType,
630
667
  input.enSlug,
@@ -634,7 +671,8 @@ function upsertTranslation(db, input) {
634
671
  input.body,
635
672
  input.enHash,
636
673
  input.translatedAt,
637
- input.model
674
+ input.model,
675
+ input.snapshotId
638
676
  );
639
677
  }
640
678
  function getTranslation(db, contentType, enSlug, locale) {
@@ -656,41 +694,6 @@ function listTranslationsForLocale(db, contentType, locale) {
656
694
  function bulkLoadTranslations(db) {
657
695
  return db.prepare(`SELECT * FROM translations ORDER BY content_type, en_slug, locale`).all();
658
696
  }
659
- function appendRevision(db, input) {
660
- const result = db.prepare(
661
- `INSERT INTO revisions (
662
- content_type, en_slug, locale, revision_kind, en_hash, body_hash, created_at,
663
- model, body_preview, frontmatter_json, body
664
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
665
- ).run(
666
- input.contentType,
667
- input.enSlug,
668
- input.locale,
669
- input.revisionKind,
670
- input.enHash,
671
- input.bodyHash,
672
- input.createdAt,
673
- input.model ?? null,
674
- input.bodyPreview ?? null,
675
- input.frontmatterJson ?? null,
676
- input.body ?? null
677
- );
678
- return Number(result.lastInsertRowid);
679
- }
680
- function listRevisions(db, contentType, enSlug, locale) {
681
- if (locale) {
682
- return db.prepare(
683
- `SELECT * FROM revisions
684
- WHERE content_type = ? AND en_slug = ? AND locale = ?
685
- ORDER BY created_at DESC, id DESC`
686
- ).all(contentType, enSlug, locale);
687
- }
688
- return db.prepare(
689
- `SELECT * FROM revisions
690
- WHERE content_type = ? AND en_slug = ?
691
- ORDER BY created_at DESC, id DESC`
692
- ).all(contentType, enSlug);
693
- }
694
697
 
695
698
  // src/loader/normalize-en.ts
696
699
  init_cjs_shims();
@@ -1638,22 +1641,45 @@ function validateDocumentAssets(config, input) {
1638
1641
 
1639
1642
  // src/validate/validate-slug-suffix.ts
1640
1643
  init_cjs_shims();
1644
+
1645
+ // src/core/localized-slug.ts
1646
+ init_cjs_shims();
1647
+ function findLocaleSuffixInSlug(slug, localeCodes) {
1648
+ const sorted = [...localeCodes].sort((a, b) => b.length - a.length);
1649
+ for (const code of sorted) {
1650
+ if (slug.endsWith(`-${code}`)) {
1651
+ return code;
1652
+ }
1653
+ }
1654
+ return void 0;
1655
+ }
1656
+ function stripLocaleSuffixFromSlug(slug, localeCodes) {
1657
+ const matchedCode = findLocaleSuffixInSlug(slug, localeCodes);
1658
+ if (!matchedCode) {
1659
+ return { slug, stripped: false };
1660
+ }
1661
+ return {
1662
+ slug: slug.slice(0, -(matchedCode.length + 1)),
1663
+ stripped: true,
1664
+ matchedCode
1665
+ };
1666
+ }
1667
+
1668
+ // src/validate/validate-slug-suffix.ts
1641
1669
  function validateTranslationSlugSuffixes(config, db) {
1642
1670
  const issues = [];
1643
1671
  const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
1644
1672
  for (const row of bulkLoadTranslations(db)) {
1645
1673
  if (row.locale === config.defaultLocale) continue;
1646
- for (const code of localeCodes) {
1647
- if (row.slug.endsWith(`-${code}`)) {
1648
- issues.push({
1649
- contentTypeId: row.content_type,
1650
- enSlug: row.en_slug,
1651
- locale: row.locale,
1652
- slug: row.slug,
1653
- message: `Translation slug "${row.slug}" ends with locale code "-${code}"`
1654
- });
1655
- break;
1656
- }
1674
+ const matchedCode = findLocaleSuffixInSlug(row.slug, localeCodes);
1675
+ if (matchedCode) {
1676
+ issues.push({
1677
+ contentTypeId: row.content_type,
1678
+ enSlug: row.en_slug,
1679
+ locale: row.locale,
1680
+ slug: row.slug,
1681
+ message: `Translation slug "${row.slug}" ends with locale code "-${matchedCode}"`
1682
+ });
1657
1683
  }
1658
1684
  }
1659
1685
  return issues;
@@ -1801,7 +1827,7 @@ function validateProject(config) {
1801
1827
  try {
1802
1828
  for (const issue of validateTranslationSlugSuffixes(config, dbForSuffix)) {
1803
1829
  issues.push({
1804
- level: "error",
1830
+ level: "warning",
1805
1831
  contentType: issue.contentTypeId,
1806
1832
  enSlug: issue.enSlug,
1807
1833
  locale: issue.locale,
@@ -1827,9 +1853,6 @@ function computePageEnHash(translatableFrontmatter, body) {
1827
1853
  const payload = JSON.stringify({ frontmatter: translatableFrontmatter, body });
1828
1854
  return sha256(payload);
1829
1855
  }
1830
- function computeBodyHash(body) {
1831
- return sha256(body);
1832
- }
1833
1856
 
1834
1857
  // src/translate/worklist.ts
1835
1858
  init_sqlite();
@@ -1894,25 +1917,20 @@ function resolveLocalesFromPreset(config, preset, explicitLocales) {
1894
1917
  // src/translate/page-translator.ts
1895
1918
  init_cjs_shims();
1896
1919
 
1897
- // src/history/record-revision.ts
1920
+ // src/history/record-snapshot.ts
1898
1921
  init_cjs_shims();
1899
1922
  init_sqlite();
1900
- function recordRevision(config, input) {
1901
- const db = openStore(config, "readwrite");
1902
- const id = appendRevision(db, {
1923
+ function recordEnSnapshot(config, input, db) {
1924
+ const ownDb = db ?? openStore(config, "readwrite");
1925
+ const id = getOrCreateEnSnapshot(ownDb, {
1903
1926
  contentType: input.contentType,
1904
1927
  enSlug: input.enSlug,
1905
- locale: input.locale,
1906
- revisionKind: input.revisionKind,
1907
1928
  enHash: input.enHash,
1908
- bodyHash: computeBodyHash(input.body),
1909
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1910
- model: input.model,
1911
- bodyPreview: input.body.slice(0, 200),
1912
- frontmatterJson: input.frontmatter ? JSON.stringify(input.frontmatter) : null,
1913
- body: input.body
1929
+ frontmatter: input.frontmatter,
1930
+ body: input.body,
1931
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1914
1932
  });
1915
- db.close();
1933
+ if (!db) ownDb.close();
1916
1934
  return id;
1917
1935
  }
1918
1936
 
@@ -2060,6 +2078,9 @@ function buildPageTranslationPrompt(input) {
2060
2078
  ...input.contextLabel ? [`Document: ${input.contextLabel}`, ""] : [],
2061
2079
  "## Rules",
2062
2080
  ...input.resolved.rules.map((rule) => `- ${rule}`),
2081
+ ...input.slugStrategy === "localized" ? [
2082
+ `- The slug MUST be written in ${localeName}, derived from the ${localeName} title and its meaning \u2014 never the English slug. Transliterate non-Latin ${localeName} into ASCII Latin.`
2083
+ ] : [],
2063
2084
  "",
2064
2085
  "## Output format",
2065
2086
  "Return ONLY valid JSON with keys:",
@@ -2147,9 +2168,11 @@ init_cjs_shims();
2147
2168
  function slugStrategyRules(slugStrategy, preserveTerms) {
2148
2169
  if (slugStrategy === "localized") {
2149
2170
  const rules = [
2150
- "Provide a localized URL slug in JSON field `slug`.",
2171
+ "Provide a URL slug in JSON field `slug` that is TRANSLATED into the target language.",
2172
+ "Base the slug on the meaning of the translated title \u2014 do NOT reuse the English slug words.",
2151
2173
  "Slug MUST be ASCII only: a-z, 0-9, hyphens. No uppercase, accents, underscores, or spaces.",
2152
- "For non-Latin locales, transliterate into Latin script."
2174
+ "For non-Latin languages, write the words in the target language and transliterate them into Latin script (e.g. Russian -> romanized Russian, not English).",
2175
+ "Do NOT append locale codes to the slug (e.g. -fr, -he, -zh-cn). Locale routing is handled by the URL prefix, not the slug."
2153
2176
  ];
2154
2177
  if (preserveTerms?.length) {
2155
2178
  rules.push(
@@ -2308,12 +2331,26 @@ async function translatePage(config, item, options = {}) {
2308
2331
  model,
2309
2332
  responseSchema: responseSchema ?? void 0
2310
2333
  });
2311
- const slug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
2334
+ const rawSlug = type.slugStrategy === "localized" ? result.parsed.slug ?? existing?.slug ?? item.enSlug : item.enSlug;
2335
+ const localeCodes = config.locales.filter((l) => l !== config.defaultLocale);
2336
+ const { slug, stripped, matchedCode } = stripLocaleSuffixFromSlug(rawSlug, localeCodes);
2337
+ const slugAdjusted = stripped && matchedCode ? { from: rawSlug, to: slug, matchedCode } : void 0;
2312
2338
  const validated = validateTranslatedFrontmatter(enDoc, result.parsed.frontmatter, type.schema);
2313
2339
  if (!validated.ok) {
2314
2340
  throw new Error(`Translation validation failed: ${validated.error}`);
2315
2341
  }
2316
2342
  const writeDb = openStore(config, "readwrite");
2343
+ const snapshotId = recordEnSnapshot(
2344
+ config,
2345
+ {
2346
+ contentType: type.id,
2347
+ enSlug: item.enSlug,
2348
+ enHash: currentEnHash,
2349
+ frontmatter: payload.frontmatter,
2350
+ body: payload.body
2351
+ },
2352
+ writeDb
2353
+ );
2317
2354
  upsertTranslation(writeDb, {
2318
2355
  contentType: type.id,
2319
2356
  enSlug: item.enSlug,
@@ -2323,19 +2360,10 @@ async function translatePage(config, item, options = {}) {
2323
2360
  body: result.parsed.body,
2324
2361
  enHash: currentEnHash,
2325
2362
  translatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2326
- model: result.model
2363
+ model: result.model,
2364
+ snapshotId
2327
2365
  });
2328
2366
  writeDb.close();
2329
- recordRevision(config, {
2330
- contentType: type.id,
2331
- enSlug: item.enSlug,
2332
- locale: item.locale,
2333
- revisionKind: "translation",
2334
- enHash: currentEnHash,
2335
- body: result.parsed.body,
2336
- frontmatter: validated.frontmatter,
2337
- model: result.model
2338
- });
2339
2367
  const estimatedCostUsd = estimateTranslationCostUsd(
2340
2368
  normalizeGeminiDisplayName(result.model),
2341
2369
  result.usage.inputTokens,
@@ -2347,7 +2375,8 @@ async function translatePage(config, item, options = {}) {
2347
2375
  model: result.model,
2348
2376
  usage: result.usage,
2349
2377
  estimatedCostUsd,
2350
- durationMs: Date.now() - startedAt
2378
+ durationMs: Date.now() - startedAt,
2379
+ slugAdjusted
2351
2380
  };
2352
2381
  } catch (error) {
2353
2382
  return {
@@ -2574,38 +2603,31 @@ function renderFrontmatterTable(frontmatter, schema) {
2574
2603
  <tbody>${rows || `<tr><td colspan="2" class="dim">\u2014</td></tr>`}</tbody>
2575
2604
  </table>`;
2576
2605
  }
2577
- function renderRevisionSnapshot(revision) {
2578
- if (revision.frontmatter_json && revision.body) {
2579
- let frontmatter = {};
2580
- try {
2581
- frontmatter = JSON.parse(revision.frontmatter_json);
2582
- } catch {
2583
- frontmatter = {};
2584
- }
2585
- const fmRows = flattenFrontmatter(frontmatter).map(
2586
- (row) => `<tr><td class="k">${escapeHtml(row.key)}</td><td class="v">${escapeHtml(row.value)}</td></tr>`
2587
- ).join("");
2588
- return `<table class="kv"><tbody>${fmRows}</tbody></table>
2589
- <pre class="code">${escapeHtml(revision.body)}</pre>`;
2590
- }
2591
- return `<p class="dim">${escapeHtml(revision.body_preview ?? "(no snapshot)")}</p>`;
2592
- }
2593
- function renderRevisionTimeline(revisions) {
2594
- if (revisions.length === 0) {
2595
- return `<p class="dim">No history.</p>`;
2596
- }
2597
- const items = revisions.map(
2598
- (row) => `<tr>
2599
- <td class="dim">${escapeHtml(row.created_at.slice(0, 19))}</td>
2600
- <td>${escapeHtml(row.revision_kind)}</td>
2601
- <td class="dim">${row.model ? escapeHtml(row.model) : "\u2014"}</td>
2602
- <td class="dim mono">${escapeHtml(row.en_hash.slice(0, 8))}</td>
2603
- <td><details><summary>view</summary>${renderRevisionSnapshot(row)}</details></td>
2604
- </tr>`
2606
+ function renderEnSnapshotPreview(snapshot) {
2607
+ let frontmatter = {};
2608
+ try {
2609
+ frontmatter = JSON.parse(snapshot.frontmatter_json);
2610
+ } catch {
2611
+ frontmatter = {};
2612
+ }
2613
+ const fmRows = flattenFrontmatter(frontmatter).map(
2614
+ (row) => `<tr><td class="k">${escapeHtml(row.key)}</td><td class="v">${escapeHtml(row.value)}</td></tr>`
2605
2615
  ).join("");
2606
- return `<table class="data"><thead><tr>
2607
- <th>When</th><th>Kind</th><th>Model</th><th>Hash</th><th></th>
2608
- </tr></thead><tbody>${items}</tbody></table>`;
2616
+ return `<table class="kv"><tbody>${fmRows}</tbody></table>
2617
+ <pre class="code">${escapeHtml(snapshot.body)}</pre>`;
2618
+ }
2619
+ function renderTranslationSnapshotPanel(snapshot, currentEnHash) {
2620
+ if (!snapshot) {
2621
+ return `<p class="dim">No EN snapshot linked to this translation.</p>`;
2622
+ }
2623
+ const staleNote = currentEnHash && currentEnHash !== snapshot.en_hash ? `<p class="dim">Current EN hash differs from snapshot (${escapeHtml(currentEnHash.slice(0, 12))} vs ${escapeHtml(snapshot.en_hash.slice(0, 12))}).</p>` : "";
2624
+ return `<dl class="meta">
2625
+ <dt>snapshot</dt><dd>#${snapshot.id}</dd>
2626
+ <dt>captured</dt><dd>${escapeHtml(snapshot.created_at.slice(0, 19))}</dd>
2627
+ <dt>en_hash</dt><dd class="mono">${escapeHtml(snapshot.en_hash.slice(0, 12))}</dd>
2628
+ </dl>
2629
+ ${staleNote}
2630
+ <details><summary>EN source at translation time</summary>${renderEnSnapshotPreview(snapshot)}</details>`;
2609
2631
  }
2610
2632
  function renderLayout(title, body, project, options = {}) {
2611
2633
  const typeLinks = project.listTypes().map((type) => {
@@ -2916,10 +2938,10 @@ async function startStudio(project, options = {}) {
2916
2938
  <dt>slug</dt><dd>${escapeHtml(translation.slug)}</dd>
2917
2939
  <dt>en_hash</dt><dd>${escapeHtml(currentEnHash?.slice(0, 12) ?? "\u2014")} / ${escapeHtml(storedEnHash?.slice(0, 12) ?? "\u2014")}</dd>
2918
2940
  </dl>`;
2919
- const revisions = listRevisions(db, typeId, enSlug, locale);
2941
+ const snapshot = translation.snapshot_id != null ? getEnSnapshot(db, translation.snapshot_id) : void 0;
2920
2942
  historyPanel = `<div class="section">
2921
- <div class="section-head">History</div>
2922
- <div class="section-body">${renderRevisionTimeline(revisions)}</div>
2943
+ <div class="section-head">EN snapshot</div>
2944
+ <div class="section-body">${renderTranslationSnapshotPanel(snapshot, currentEnHash)}</div>
2923
2945
  </div>`;
2924
2946
  } else {
2925
2947
  contentPanel = `<p class="dim" style="padding:12px">No translation for ${escapeHtml(locale)}.</p>`;
@@ -2980,16 +3002,6 @@ async function startStudio(project, options = {}) {
2980
3002
 
2981
3003
  // cli/prompt-translate.ts
2982
3004
  init_cjs_shims();
2983
-
2984
- // ../../node_modules/@inquirer/core/dist/lib/errors.js
2985
- init_cjs_shims();
2986
- var CancelPromptError = class extends Error {
2987
- constructor() {
2988
- super(...arguments);
2989
- __publicField(this, "name", "CancelPromptError");
2990
- __publicField(this, "message", "Prompt was canceled");
2991
- }
2992
- };
2993
3005
  function isInteractive() {
2994
3006
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
2995
3007
  }
@@ -3000,7 +3012,7 @@ async function runPrompt(prompt) {
3000
3012
  try {
3001
3013
  return await prompt;
3002
3014
  } catch (error) {
3003
- if (error instanceof CancelPromptError) process.exit(0);
3015
+ if (error instanceof core.CancelPromptError) process.exit(0);
3004
3016
  throw error;
3005
3017
  }
3006
3018
  }
@@ -3101,8 +3113,17 @@ function statusForResult(result, dryRun) {
3101
3113
  if (dryRun) return yellow("would translate");
3102
3114
  return green("translated");
3103
3115
  }
3116
+ function slugAdjustedMessage(result) {
3117
+ if (!result.slugAdjusted) return void 0;
3118
+ const { from, to, matchedCode } = result.slugAdjusted;
3119
+ return yellow(
3120
+ `slug adjusted: "${from}" \u2192 "${to}" (stripped -${matchedCode})`
3121
+ );
3122
+ }
3104
3123
  function detailForResult(result) {
3105
3124
  const parts = [];
3125
+ const slugMsg = slugAdjustedMessage(result);
3126
+ if (slugMsg) parts.push(slugMsg);
3106
3127
  if (result.durationMs !== void 0) parts.push(`${(result.durationMs / 1e3).toFixed(1)}s`);
3107
3128
  if (result.usage) {
3108
3129
  parts.push(`${formatTokenCount(result.usage.inputTokens)} in`);
@@ -3136,6 +3157,10 @@ function createTranslateProgressReporter(options = {}) {
3136
3157
  const status = statusForResult(event.result, dryRun);
3137
3158
  const detail = detailForResult(event.result);
3138
3159
  console.log(`${label}: ${status}${detail ? ` (${detail.replace(/\x1b\[[0-9;]*m/g, "")})` : ""}`);
3160
+ const slugMsg = slugAdjustedMessage(event.result);
3161
+ if (slugMsg) {
3162
+ console.log(`[warning] ${labelForResult(event.result)} ${slugMsg.replace(/\x1b\[[0-9;]*m/g, "")}`);
3163
+ }
3139
3164
  if (event.result.failed && event.result.error) {
3140
3165
  console.error(event.result.error);
3141
3166
  }
@@ -3239,6 +3264,14 @@ function createTranslateProgressReporter(options = {}) {
3239
3264
  for (const result of event.results.filter((entry) => entry.failed)) {
3240
3265
  process.stdout.write("\x1B[2K" + red(`${labelForResult(result)}: ${result.error ?? "failed"}`) + "\n");
3241
3266
  }
3267
+ for (const result of event.results.filter((entry) => entry.slugAdjusted)) {
3268
+ const slugMsg = slugAdjustedMessage(result);
3269
+ if (slugMsg) {
3270
+ process.stdout.write(
3271
+ "\x1B[2K" + yellow(`[warning] ${labelForResult(result)} ${slugMsg.replace(/\x1b\[[0-9;]*m/g, "")}`) + "\n"
3272
+ );
3273
+ }
3274
+ }
3242
3275
  break;
3243
3276
  }
3244
3277
  },
@@ -3384,7 +3417,7 @@ Commands:
3384
3417
  validate Validate EN files and sqlite consistency
3385
3418
  export-static Write raw MDX files for static hosting
3386
3419
  translate Translate stale/missing locale pages
3387
- history <type> <slug> Show revision timeline
3420
+ history <type> <slug> Show EN snapshot timeline
3388
3421
  studio Start read-only local studio
3389
3422
  version Print scribe-cms version
3390
3423
 
@@ -3490,11 +3523,13 @@ Translate flags:
3490
3523
  }
3491
3524
  const { openStore: openStore2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
3492
3525
  const db = openStore2(config, "readonly");
3493
- const rows = listRevisions(db, typeId, enSlug, locale);
3526
+ const rows = listEnSnapshotsForEnSlug(db, typeId, enSlug);
3494
3527
  db.close();
3495
3528
  for (const row of rows) {
3529
+ const locales = row.locales ?? "";
3530
+ if (locale && !locales.split(",").includes(locale)) continue;
3496
3531
  console.log(
3497
- `${row.created_at} ${row.revision_kind} locale=${row.locale ?? "en"} en_hash=${row.en_hash.slice(0, 8)} body_hash=${row.body_hash.slice(0, 8)}`
3532
+ `${row.created_at} snapshot=#${row.id} en_hash=${row.en_hash.slice(0, 8)} locales=${locales || "\u2014"}`
3498
3533
  );
3499
3534
  }
3500
3535
  break;