glotfile 0.8.0 → 0.8.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.
@@ -1579,7 +1579,8 @@ function attrEscape(s) {
1579
1579
  return xmlEscape2(s).replace(/"/g, """);
1580
1580
  }
1581
1581
  function angularXMeta(placeholders, name) {
1582
- return /^[A-Z][A-Z0-9_]*$/.test(name) ? placeholders?.[name] : void 0;
1582
+ const meta = placeholders?.[name];
1583
+ return /^[A-Z][A-Z0-9_]*$/.test(name) || meta?.origin === "x" ? meta : void 0;
1583
1584
  }
1584
1585
  function renderInterpolations(text, ids, placeholders) {
1585
1586
  let out = "";
@@ -4823,18 +4824,20 @@ function decodeInline(raw, addMeta) {
4823
4824
  const meta = {};
4824
4825
  if (attrs["ctype"]) meta.type = attrs["ctype"];
4825
4826
  if (equiv !== void 0) meta.example = equiv;
4827
+ if (!ANGULAR_CONVENTION_ID.test(id)) meta.origin = "x";
4826
4828
  addMeta(id, meta);
4827
4829
  }
4828
4830
  last = m.index + m[0].length;
4829
4831
  }
4830
4832
  return out + decodeEntities(raw.slice(last));
4831
4833
  }
4832
- var LOCALE_RE5, FILE_RE, angularXliff2;
4834
+ var LOCALE_RE5, FILE_RE, ANGULAR_CONVENTION_ID, angularXliff2;
4833
4835
  var init_angular_xliff2 = __esm({
4834
4836
  "src/server/import/parsers/angular-xliff.ts"() {
4835
4837
  "use strict";
4836
4838
  LOCALE_RE5 = /^[a-z]{2,3}([_-][A-Za-z]{2,4}){0,2}$/;
4837
4839
  FILE_RE = /^messages(?:\.(.+))?\.xlf$/;
4840
+ ANGULAR_CONVENTION_ID = /^[A-Z][A-Z0-9_]*$/;
4838
4841
  angularXliff2 = {
4839
4842
  name: "angular-xliff",
4840
4843
  parse(localeRoot, opts) {
@@ -6619,10 +6622,139 @@ var init_ui_prefs = __esm({
6619
6622
  }
6620
6623
  });
6621
6624
 
6625
+ // src/server/events.ts
6626
+ function createEventHub() {
6627
+ const senders = /* @__PURE__ */ new Set();
6628
+ return {
6629
+ subscribe(send) {
6630
+ senders.add(send);
6631
+ return () => senders.delete(send);
6632
+ },
6633
+ broadcast(event, data) {
6634
+ for (const send of [...senders]) {
6635
+ try {
6636
+ send(event, data);
6637
+ } catch {
6638
+ }
6639
+ }
6640
+ },
6641
+ size() {
6642
+ return senders.size;
6643
+ }
6644
+ };
6645
+ }
6646
+ var init_events = __esm({
6647
+ "src/server/events.ts"() {
6648
+ "use strict";
6649
+ }
6650
+ });
6651
+
6652
+ // src/server/watch.ts
6653
+ import { statSync as statSync8, readdirSync as readdirSync14 } from "fs";
6654
+ import { join as join17 } from "path";
6655
+ import { createHash as createHash2 } from "crypto";
6656
+ function hashState(state) {
6657
+ return createHash2("sha1").update(serializeJson(state, state.config.format)).digest("hex");
6658
+ }
6659
+ function signature(statePath) {
6660
+ const fmt = detectFormat(statePath);
6661
+ if (fmt === "none") return "none";
6662
+ if (fmt === "single") {
6663
+ const s = statSync8(statePath);
6664
+ return `single:${s.size}:${s.mtimeMs}`;
6665
+ }
6666
+ const dir = splitDirFor(statePath);
6667
+ const parts = [];
6668
+ for (const rel of ["config.json", "keys.json"]) {
6669
+ try {
6670
+ const s = statSync8(join17(dir, rel));
6671
+ parts.push(`${rel}:${s.size}:${s.mtimeMs}`);
6672
+ } catch {
6673
+ }
6674
+ }
6675
+ try {
6676
+ for (const name of readdirSync14(join17(dir, "locales")).sort()) {
6677
+ if (!name.endsWith(".json")) continue;
6678
+ const s = statSync8(join17(dir, "locales", name));
6679
+ parts.push(`${name}:${s.size}:${s.mtimeMs}`);
6680
+ }
6681
+ } catch {
6682
+ }
6683
+ return `split:${parts.join("|")}`;
6684
+ }
6685
+ function createStateWatcher(opts) {
6686
+ const intervalMs = opts.intervalMs ?? 750;
6687
+ let statePath = opts.statePath;
6688
+ let lastSig = "";
6689
+ let lastHash = "";
6690
+ let timer;
6691
+ function baseline() {
6692
+ try {
6693
+ lastSig = signature(statePath);
6694
+ lastHash = hashState(loadState(statePath));
6695
+ } catch {
6696
+ lastSig = "";
6697
+ lastHash = "";
6698
+ }
6699
+ }
6700
+ function check() {
6701
+ let sig;
6702
+ try {
6703
+ sig = signature(statePath);
6704
+ } catch {
6705
+ return;
6706
+ }
6707
+ if (sig === lastSig) return;
6708
+ let hash;
6709
+ try {
6710
+ hash = hashState(loadState(statePath));
6711
+ } catch {
6712
+ lastSig = sig;
6713
+ return;
6714
+ }
6715
+ lastSig = sig;
6716
+ if (hash !== lastHash) {
6717
+ lastHash = hash;
6718
+ opts.onChange();
6719
+ }
6720
+ }
6721
+ function noteWrite(state) {
6722
+ try {
6723
+ lastSig = signature(statePath);
6724
+ } catch {
6725
+ lastSig = "";
6726
+ }
6727
+ lastHash = hashState(state);
6728
+ }
6729
+ function retarget(next) {
6730
+ statePath = next;
6731
+ baseline();
6732
+ }
6733
+ function start() {
6734
+ if (timer) return;
6735
+ timer = setInterval(check, intervalMs);
6736
+ timer.unref?.();
6737
+ }
6738
+ function stop() {
6739
+ if (timer) clearInterval(timer);
6740
+ timer = void 0;
6741
+ }
6742
+ baseline();
6743
+ return { check, noteWrite, retarget, start, stop };
6744
+ }
6745
+ var init_watch = __esm({
6746
+ "src/server/watch.ts"() {
6747
+ "use strict";
6748
+ init_state();
6749
+ init_format();
6750
+ init_storage();
6751
+ }
6752
+ });
6753
+
6622
6754
  // src/server/api.ts
6623
6755
  import { Hono } from "hono";
6624
6756
  import { streamSSE } from "hono/streaming";
6625
- import { readFileSync as readFileSync23, existsSync as existsSync13, readdirSync as readdirSync14, statSync as statSync8, rmSync as rmSync6 } from "fs";
6757
+ import { readFileSync as readFileSync23, existsSync as existsSync13, readdirSync as readdirSync15, statSync as statSync9, rmSync as rmSync6 } from "fs";
6626
6758
  import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
6627
6759
  function projectName(root) {
6628
6760
  const nameFile = resolve9(root, ".idea", ".name");
@@ -6654,6 +6786,14 @@ function createApi(deps) {
6654
6786
  const app = new Hono();
6655
6787
  const load = () => loadState(deps.statePath);
6656
6788
  const projectRoot = dirname3(resolve9(deps.statePath));
6789
+ const hub = deps.eventHub ?? createEventHub();
6790
+ const watcher = createStateWatcher({
6791
+ statePath: deps.statePath,
6792
+ intervalMs: deps.watchIntervalMs,
6793
+ onChange: () => hub.broadcast("state-changed", JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString() }))
6794
+ });
6795
+ deps.onWatcher?.(watcher);
6796
+ if (deps.watch) watcher.start();
6657
6797
  let translateQueue = Promise.resolve();
6658
6798
  const withTranslateLock = (fn) => {
6659
6799
  const next = translateQueue.then(fn, fn);
@@ -6675,12 +6815,30 @@ function createApi(deps) {
6675
6815
  };
6676
6816
  const persist = (s) => {
6677
6817
  saveState(deps.statePath, s);
6818
+ watcher.noteWrite(s);
6678
6819
  scheduleAutoExport(s);
6679
6820
  };
6680
6821
  const logChange = (entry) => appendLog(projectRoot, { ...entry, at: (/* @__PURE__ */ new Date()).toISOString() });
6681
6822
  const valueText = (s, key, locale) => s.keys[key]?.values[locale]?.value;
6682
6823
  const uiPrefsPath = deps.uiPrefsPath ?? defaultUiPrefsPath();
6683
6824
  app.get("/state", (c) => c.json(load()));
6825
+ app.get("/events", (c) => streamSSE(c, async (stream) => {
6826
+ const send = (event, data) => {
6827
+ void stream.writeSSE({ event, data });
6828
+ };
6829
+ const unsubscribe = hub.subscribe(send);
6830
+ stream.onAbort(unsubscribe);
6831
+ await stream.writeSSE({ event: "ready", data: "" });
6832
+ try {
6833
+ while (!stream.aborted) {
6834
+ await stream.sleep(3e4);
6835
+ if (stream.aborted) break;
6836
+ await stream.writeSSE({ event: "ping", data: "" });
6837
+ }
6838
+ } finally {
6839
+ unsubscribe();
6840
+ }
6841
+ }));
6684
6842
  app.get("/ui-prefs", (c) => c.json(loadUiPrefs(uiPrefsPath)));
6685
6843
  app.put("/ui-prefs", async (c) => {
6686
6844
  const body = await c.req.json();
@@ -6768,7 +6926,7 @@ function createApi(deps) {
6768
6926
  if (depth > 4) return;
6769
6927
  let entries = [];
6770
6928
  try {
6771
- entries = readdirSync14(dir);
6929
+ entries = readdirSync15(dir);
6772
6930
  } catch {
6773
6931
  return;
6774
6932
  }
@@ -6782,7 +6940,7 @@ function createApi(deps) {
6782
6940
  filePath = abs;
6783
6941
  } else {
6784
6942
  try {
6785
- if (statSync8(abs).isDirectory()) walk(abs, depth + 1);
6943
+ if (statSync9(abs).isDirectory()) walk(abs, depth + 1);
6786
6944
  } catch {
6787
6945
  }
6788
6946
  continue;
@@ -6813,6 +6971,7 @@ function createApi(deps) {
6813
6971
  if (!existsSync13(resolved)) return c.json({ error: "file not found" }, 400);
6814
6972
  loadState(resolved);
6815
6973
  deps.statePath = resolved;
6974
+ watcher.retarget(resolved);
6816
6975
  return c.json({ ok: true, path: resolved, name: basename(resolved), dir: projectRoot, project: basename(projectRoot) });
6817
6976
  });
6818
6977
  app.post("/keys", async (c) => {
@@ -7816,6 +7975,8 @@ var init_api = __esm({
7816
7975
  init_ui_prefs();
7817
7976
  init_local_settings();
7818
7977
  init_atomic_write();
7978
+ init_events();
7979
+ init_watch();
7819
7980
  sanitize = (s) => s.replace(/[^\w.\-]+/g, "_");
7820
7981
  screenshotDirName = (statePath) => basename(statePath).replace(/\.[^.]+$/, "") + "-screenshots";
7821
7982
  }
@@ -7830,7 +7991,7 @@ __export(server_exports, {
7830
7991
  import { Hono as Hono2 } from "hono";
7831
7992
  import { serve } from "@hono/node-server";
7832
7993
  import { fileURLToPath } from "url";
7833
- import { dirname as dirname4, join as join17, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
7994
+ import { dirname as dirname4, join as join18, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
7834
7995
  import { readFile, stat } from "fs/promises";
7835
7996
  import { createServer } from "net";
7836
7997
  import open from "open";
@@ -7847,7 +8008,12 @@ async function readFileResponse(absPath) {
7847
8008
  }
7848
8009
  function buildApp(opts) {
7849
8010
  const app = new Hono2();
7850
- const apiDeps = { statePath: opts.statePath, autoExport: true };
8011
+ const apiDeps = {
8012
+ statePath: opts.statePath,
8013
+ autoExport: true,
8014
+ watch: opts.watch,
8015
+ onWatcher: opts.onWatcher
8016
+ };
7851
8017
  app.route("/api", createApi(apiDeps));
7852
8018
  app.get("/:dir/*", async (c, next) => {
7853
8019
  const dirSeg = c.req.param("dir");
@@ -7873,7 +8039,7 @@ function buildApp(opts) {
7873
8039
  const file = await readFileResponse(target);
7874
8040
  if (file) return file;
7875
8041
  }
7876
- const index = await readFileResponse(join17(root, "index.html"));
8042
+ const index = await readFileResponse(join18(root, "index.html"));
7877
8043
  if (index) return index;
7878
8044
  return c.notFound();
7879
8045
  });
@@ -7898,13 +8064,19 @@ function findAvailablePort(start) {
7898
8064
  });
7899
8065
  }
7900
8066
  async function startServer(opts) {
7901
- const app = buildApp(opts);
8067
+ let watcher;
8068
+ const app = buildApp({ ...opts, watch: opts.watch ?? true, onWatcher: (w) => {
8069
+ watcher = w;
8070
+ } });
7902
8071
  const port = await findAvailablePort(opts.dev ? DEV_PORT : DEFAULT_PORT);
7903
8072
  return new Promise((resolveP) => {
7904
8073
  const server = serve({ fetch: app.fetch, hostname: "127.0.0.1", port }, (info) => {
7905
8074
  const url = `http://127.0.0.1:${info.port}`;
7906
8075
  if (opts.open !== false && !opts.dev) void open(url);
7907
- resolveP({ url, close: () => server.close() });
8076
+ resolveP({ url, close: () => {
8077
+ watcher?.stop();
8078
+ server.close();
8079
+ } });
7908
8080
  backgroundScan(opts.statePath);
7909
8081
  });
7910
8082
  });
@@ -7937,7 +8109,7 @@ var init_server = __esm({
7937
8109
  init_scanner();
7938
8110
  init_usage();
7939
8111
  here = dirname4(fileURLToPath(import.meta.url));
7940
- DEFAULT_UI_DIR = join17(here, "..", "ui");
8112
+ DEFAULT_UI_DIR = join18(here, "..", "ui");
7941
8113
  MIME = {
7942
8114
  ".html": "text/html; charset=utf-8",
7943
8115
  ".js": "text/javascript; charset=utf-8",
@@ -7987,7 +8159,7 @@ init_usage();
7987
8159
  init_context();
7988
8160
  init_run2();
7989
8161
  init_outputs();
7990
- import { resolve as resolve11, dirname as dirname5, join as join18 } from "path";
8162
+ import { resolve as resolve11, dirname as dirname5, join as join19 } from "path";
7991
8163
  import { readFileSync as readFileSync24, existsSync as existsSync14, mkdirSync as mkdirSync6, cpSync } from "fs";
7992
8164
  import { fileURLToPath as fileURLToPath2 } from "url";
7993
8165
 
@@ -8751,10 +8923,10 @@ function runSplit(args) {
8751
8923
  `Split catalog into ${splitDirFor(args.statePath)}/ (config.json, keys.json, locales/ \u2014 up to ${state.config.locales.length} locale files). Removed ${args.statePath}.`
8752
8924
  );
8753
8925
  }
8754
- var SKILL_SRC = join18(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "skill");
8926
+ var SKILL_SRC = join19(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "skill");
8755
8927
  function runSkill(args) {
8756
8928
  if (args.print) {
8757
- console.log(readFileSync24(join18(SKILL_SRC, "SKILL.md"), "utf8").trimEnd());
8929
+ console.log(readFileSync24(join19(SKILL_SRC, "SKILL.md"), "utf8").trimEnd());
8758
8930
  return;
8759
8931
  }
8760
8932
  const dest = resolve11(process.cwd(), ".claude", "skills", "glotfile");
@@ -8916,7 +9088,7 @@ ${formatOpts([...options, ...GLOBAL_OPTS])}`);
8916
9088
  );
8917
9089
  }
8918
9090
  function printVersion() {
8919
- const pkgPath = join18(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
9091
+ const pkgPath = join19(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
8920
9092
  console.log(JSON.parse(readFileSync24(pkgPath, "utf8")).version);
8921
9093
  }
8922
9094
  async function main(argv) {
@@ -2,7 +2,7 @@
2
2
  import { Hono as Hono2 } from "hono";
3
3
  import { serve } from "@hono/node-server";
4
4
  import { fileURLToPath } from "url";
5
- import { dirname as dirname4, join as join17, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
5
+ import { dirname as dirname4, join as join18, resolve as resolve10, extname as extname3, sep as sep3 } from "path";
6
6
  import { readFile, stat } from "fs/promises";
7
7
  import { createServer } from "net";
8
8
  import open from "open";
@@ -2594,7 +2594,8 @@ function attrEscape(s) {
2594
2594
  return xmlEscape2(s).replace(/"/g, """);
2595
2595
  }
2596
2596
  function angularXMeta(placeholders, name) {
2597
- return /^[A-Z][A-Z0-9_]*$/.test(name) ? placeholders?.[name] : void 0;
2597
+ const meta = placeholders?.[name];
2598
+ return /^[A-Z][A-Z0-9_]*$/.test(name) || meta?.origin === "x" ? meta : void 0;
2598
2599
  }
2599
2600
  function renderInterpolations(text, ids, placeholders) {
2600
2601
  let out = "";
@@ -2841,7 +2842,7 @@ function checkOutputs(state, root) {
2841
2842
  }
2842
2843
 
2843
2844
  // src/server/api.ts
2844
- import { readFileSync as readFileSync23, existsSync as existsSync13, readdirSync as readdirSync14, statSync as statSync8, rmSync as rmSync6 } from "fs";
2845
+ import { readFileSync as readFileSync23, existsSync as existsSync13, readdirSync as readdirSync15, statSync as statSync9, rmSync as rmSync6 } from "fs";
2845
2846
  import { dirname as dirname3, resolve as resolve9, basename, relative as relative4, sep as sep2 } from "path";
2846
2847
 
2847
2848
  // src/server/ai/anthropic.ts
@@ -4788,6 +4789,7 @@ function parseAttrs(s) {
4788
4789
  for (const m of s.matchAll(/([\w-]+)="([^"]*)"/g)) out[m[1]] = decodeEntities(m[2]);
4789
4790
  return out;
4790
4791
  }
4792
+ var ANGULAR_CONVENTION_ID = /^[A-Z][A-Z0-9_]*$/;
4791
4793
  function decodeLocations(body) {
4792
4794
  const out = [];
4793
4795
  const seen = /* @__PURE__ */ new Set();
@@ -4821,6 +4823,7 @@ function decodeInline(raw, addMeta) {
4821
4823
  const meta = {};
4822
4824
  if (attrs["ctype"]) meta.type = attrs["ctype"];
4823
4825
  if (equiv !== void 0) meta.example = equiv;
4826
+ if (!ANGULAR_CONVENTION_ID.test(id)) meta.origin = "x";
4824
4827
  addMeta(id, meta);
4825
4828
  }
4826
4829
  last = m.index + m[0].length;
@@ -6043,6 +6046,122 @@ function aiConfigError(ai) {
6043
6046
  return null;
6044
6047
  }
6045
6048
 
6049
+ // src/server/events.ts
6050
+ function createEventHub() {
6051
+ const senders = /* @__PURE__ */ new Set();
6052
+ return {
6053
+ subscribe(send) {
6054
+ senders.add(send);
6055
+ return () => senders.delete(send);
6056
+ },
6057
+ broadcast(event, data) {
6058
+ for (const send of [...senders]) {
6059
+ try {
6060
+ send(event, data);
6061
+ } catch {
6062
+ }
6063
+ }
6064
+ },
6065
+ size() {
6066
+ return senders.size;
6067
+ }
6068
+ };
6069
+ }
6070
+
6071
+ // src/server/watch.ts
6072
+ import { statSync as statSync8, readdirSync as readdirSync14 } from "fs";
6073
+ import { join as join17 } from "path";
6074
+ import { createHash as createHash2 } from "crypto";
6075
+ function hashState(state) {
6076
+ return createHash2("sha1").update(serializeJson(state, state.config.format)).digest("hex");
6077
+ }
6078
+ function signature(statePath) {
6079
+ const fmt = detectFormat(statePath);
6080
+ if (fmt === "none") return "none";
6081
+ if (fmt === "single") {
6082
+ const s = statSync8(statePath);
6083
+ return `single:${s.size}:${s.mtimeMs}`;
6084
+ }
6085
+ const dir = splitDirFor(statePath);
6086
+ const parts = [];
6087
+ for (const rel of ["config.json", "keys.json"]) {
6088
+ try {
6089
+ const s = statSync8(join17(dir, rel));
6090
+ parts.push(`${rel}:${s.size}:${s.mtimeMs}`);
6091
+ } catch {
6092
+ }
6093
+ }
6094
+ try {
6095
+ for (const name of readdirSync14(join17(dir, "locales")).sort()) {
6096
+ if (!name.endsWith(".json")) continue;
6097
+ const s = statSync8(join17(dir, "locales", name));
6098
+ parts.push(`${name}:${s.size}:${s.mtimeMs}`);
6099
+ }
6100
+ } catch {
6101
+ }
6102
+ return `split:${parts.join("|")}`;
6103
+ }
6104
+ function createStateWatcher(opts) {
6105
+ const intervalMs = opts.intervalMs ?? 750;
6106
+ let statePath = opts.statePath;
6107
+ let lastSig = "";
6108
+ let lastHash = "";
6109
+ let timer;
6110
+ function baseline() {
6111
+ try {
6112
+ lastSig = signature(statePath);
6113
+ lastHash = hashState(loadState(statePath));
6114
+ } catch {
6115
+ lastSig = "";
6116
+ lastHash = "";
6117
+ }
6118
+ }
6119
+ function check() {
6120
+ let sig;
6121
+ try {
6122
+ sig = signature(statePath);
6123
+ } catch {
6124
+ return;
6125
+ }
6126
+ if (sig === lastSig) return;
6127
+ let hash;
6128
+ try {
6129
+ hash = hashState(loadState(statePath));
6130
+ } catch {
6131
+ lastSig = sig;
6132
+ return;
6133
+ }
6134
+ lastSig = sig;
6135
+ if (hash !== lastHash) {
6136
+ lastHash = hash;
6137
+ opts.onChange();
6138
+ }
6139
+ }
6140
+ function noteWrite(state) {
6141
+ try {
6142
+ lastSig = signature(statePath);
6143
+ } catch {
6144
+ lastSig = "";
6145
+ }
6146
+ lastHash = hashState(state);
6147
+ }
6148
+ function retarget(next) {
6149
+ statePath = next;
6150
+ baseline();
6151
+ }
6152
+ function start() {
6153
+ if (timer) return;
6154
+ timer = setInterval(check, intervalMs);
6155
+ timer.unref?.();
6156
+ }
6157
+ function stop() {
6158
+ if (timer) clearInterval(timer);
6159
+ timer = void 0;
6160
+ }
6161
+ baseline();
6162
+ return { check, noteWrite, retarget, start, stop };
6163
+ }
6164
+
6046
6165
  // src/server/api.ts
6047
6166
  var sanitize = (s) => s.replace(/[^\w.\-]+/g, "_");
6048
6167
  var screenshotDirName = (statePath) => basename(statePath).replace(/\.[^.]+$/, "") + "-screenshots";
@@ -6076,6 +6195,14 @@ function createApi(deps) {
6076
6195
  const app = new Hono();
6077
6196
  const load = () => loadState(deps.statePath);
6078
6197
  const projectRoot = dirname3(resolve9(deps.statePath));
6198
+ const hub = deps.eventHub ?? createEventHub();
6199
+ const watcher = createStateWatcher({
6200
+ statePath: deps.statePath,
6201
+ intervalMs: deps.watchIntervalMs,
6202
+ onChange: () => hub.broadcast("state-changed", JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString() }))
6203
+ });
6204
+ deps.onWatcher?.(watcher);
6205
+ if (deps.watch) watcher.start();
6079
6206
  let translateQueue = Promise.resolve();
6080
6207
  const withTranslateLock = (fn) => {
6081
6208
  const next = translateQueue.then(fn, fn);
@@ -6097,12 +6224,30 @@ function createApi(deps) {
6097
6224
  };
6098
6225
  const persist = (s) => {
6099
6226
  saveState(deps.statePath, s);
6227
+ watcher.noteWrite(s);
6100
6228
  scheduleAutoExport(s);
6101
6229
  };
6102
6230
  const logChange = (entry) => appendLog(projectRoot, { ...entry, at: (/* @__PURE__ */ new Date()).toISOString() });
6103
6231
  const valueText = (s, key, locale) => s.keys[key]?.values[locale]?.value;
6104
6232
  const uiPrefsPath = deps.uiPrefsPath ?? defaultUiPrefsPath();
6105
6233
  app.get("/state", (c) => c.json(load()));
6234
+ app.get("/events", (c) => streamSSE(c, async (stream) => {
6235
+ const send = (event, data) => {
6236
+ void stream.writeSSE({ event, data });
6237
+ };
6238
+ const unsubscribe = hub.subscribe(send);
6239
+ stream.onAbort(unsubscribe);
6240
+ await stream.writeSSE({ event: "ready", data: "" });
6241
+ try {
6242
+ while (!stream.aborted) {
6243
+ await stream.sleep(3e4);
6244
+ if (stream.aborted) break;
6245
+ await stream.writeSSE({ event: "ping", data: "" });
6246
+ }
6247
+ } finally {
6248
+ unsubscribe();
6249
+ }
6250
+ }));
6106
6251
  app.get("/ui-prefs", (c) => c.json(loadUiPrefs(uiPrefsPath)));
6107
6252
  app.put("/ui-prefs", async (c) => {
6108
6253
  const body = await c.req.json();
@@ -6190,7 +6335,7 @@ function createApi(deps) {
6190
6335
  if (depth > 4) return;
6191
6336
  let entries = [];
6192
6337
  try {
6193
- entries = readdirSync14(dir);
6338
+ entries = readdirSync15(dir);
6194
6339
  } catch {
6195
6340
  return;
6196
6341
  }
@@ -6204,7 +6349,7 @@ function createApi(deps) {
6204
6349
  filePath = abs;
6205
6350
  } else {
6206
6351
  try {
6207
- if (statSync8(abs).isDirectory()) walk(abs, depth + 1);
6352
+ if (statSync9(abs).isDirectory()) walk(abs, depth + 1);
6208
6353
  } catch {
6209
6354
  }
6210
6355
  continue;
@@ -6235,6 +6380,7 @@ function createApi(deps) {
6235
6380
  if (!existsSync13(resolved)) return c.json({ error: "file not found" }, 400);
6236
6381
  loadState(resolved);
6237
6382
  deps.statePath = resolved;
6383
+ watcher.retarget(resolved);
6238
6384
  return c.json({ ok: true, path: resolved, name: basename(resolved), dir: projectRoot, project: basename(projectRoot) });
6239
6385
  });
6240
6386
  app.post("/keys", async (c) => {
@@ -7209,7 +7355,7 @@ function createApi(deps) {
7209
7355
 
7210
7356
  // src/server/server.ts
7211
7357
  var here = dirname4(fileURLToPath(import.meta.url));
7212
- var DEFAULT_UI_DIR = join17(here, "..", "ui");
7358
+ var DEFAULT_UI_DIR = join18(here, "..", "ui");
7213
7359
  var MIME = {
7214
7360
  ".html": "text/html; charset=utf-8",
7215
7361
  ".js": "text/javascript; charset=utf-8",
@@ -7237,7 +7383,12 @@ async function readFileResponse(absPath) {
7237
7383
  }
7238
7384
  function buildApp(opts) {
7239
7385
  const app = new Hono2();
7240
- const apiDeps = { statePath: opts.statePath, autoExport: true };
7386
+ const apiDeps = {
7387
+ statePath: opts.statePath,
7388
+ autoExport: true,
7389
+ watch: opts.watch,
7390
+ onWatcher: opts.onWatcher
7391
+ };
7241
7392
  app.route("/api", createApi(apiDeps));
7242
7393
  app.get("/:dir/*", async (c, next) => {
7243
7394
  const dirSeg = c.req.param("dir");
@@ -7263,7 +7414,7 @@ function buildApp(opts) {
7263
7414
  const file = await readFileResponse(target);
7264
7415
  if (file) return file;
7265
7416
  }
7266
- const index = await readFileResponse(join17(root, "index.html"));
7417
+ const index = await readFileResponse(join18(root, "index.html"));
7267
7418
  if (index) return index;
7268
7419
  return c.notFound();
7269
7420
  });
@@ -7299,13 +7450,19 @@ function findAvailablePort(start) {
7299
7450
  });
7300
7451
  }
7301
7452
  async function startServer(opts) {
7302
- const app = buildApp(opts);
7453
+ let watcher;
7454
+ const app = buildApp({ ...opts, watch: opts.watch ?? true, onWatcher: (w) => {
7455
+ watcher = w;
7456
+ } });
7303
7457
  const port = await findAvailablePort(opts.dev ? DEV_PORT : DEFAULT_PORT);
7304
7458
  return new Promise((resolveP) => {
7305
7459
  const server = serve({ fetch: app.fetch, hostname: "127.0.0.1", port }, (info) => {
7306
7460
  const url = `http://127.0.0.1:${info.port}`;
7307
7461
  if (opts.open !== false && !opts.dev) void open(url);
7308
- resolveP({ url, close: () => server.close() });
7462
+ resolveP({ url, close: () => {
7463
+ watcher?.stop();
7464
+ server.close();
7465
+ } });
7309
7466
  backgroundScan(opts.statePath);
7310
7467
  });
7311
7468
  });