tandem-editor 0.2.8 → 0.2.10

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.
@@ -9,7 +9,7 @@
9
9
  html, body, #root { height: 100%; }
10
10
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
11
11
  </style>
12
- <script type="module" crossorigin src="/assets/index-ChvL-huP.js"></script>
12
+ <script type="module" crossorigin src="/assets/index-CfGlbY9B.js"></script>
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -228,6 +228,25 @@ var init_platform = __esm({
228
228
  import fs from "fs/promises";
229
229
  import path2 from "path";
230
230
  import * as Y2 from "yjs";
231
+ async function atomicWrite(sessionPath, content) {
232
+ const tmpPath = `${sessionPath}.tmp`;
233
+ await fs.writeFile(tmpPath, content, "utf-8");
234
+ for (let attempt = 0; attempt < RENAME_MAX_RETRIES; attempt++) {
235
+ try {
236
+ await fs.rename(tmpPath, sessionPath);
237
+ return;
238
+ } catch (err) {
239
+ const code = err.code;
240
+ if ((code === "EPERM" || code === "EACCES") && attempt < RENAME_MAX_RETRIES - 1) {
241
+ await new Promise((r) => setTimeout(r, RENAME_RETRY_BASE_MS * 2 ** attempt));
242
+ continue;
243
+ }
244
+ await fs.unlink(tmpPath).catch(() => {
245
+ });
246
+ throw err;
247
+ }
248
+ }
249
+ }
231
250
  function sessionKey(filePath) {
232
251
  return encodeURIComponent(filePath.replace(/\\/g, "/"));
233
252
  }
@@ -255,15 +274,7 @@ async function saveSession(filePath, format, doc) {
255
274
  sessionDirReady = true;
256
275
  }
257
276
  const sessionPath = path2.join(SESSION_DIR, `${key}.json`);
258
- const tmpPath = `${sessionPath}.tmp`;
259
- await fs.writeFile(tmpPath, JSON.stringify(data), "utf-8");
260
- try {
261
- await fs.rename(tmpPath, sessionPath);
262
- } catch (err) {
263
- await fs.unlink(tmpPath).catch(() => {
264
- });
265
- throw err;
266
- }
277
+ await atomicWrite(sessionPath, JSON.stringify(data));
267
278
  }
268
279
  async function loadSession(filePath) {
269
280
  const key = sessionKey(filePath);
@@ -334,15 +345,7 @@ async function saveCtrlSession(doc) {
334
345
  const ydocState = Buffer.from(state).toString("base64");
335
346
  const data = { ydocState, lastAccessed: Date.now() };
336
347
  const sessionPath = path2.join(SESSION_DIR, `${CTRL_SESSION_KEY}.json`);
337
- const tmpPath = `${sessionPath}.tmp`;
338
- await fs.writeFile(tmpPath, JSON.stringify(data), "utf-8");
339
- try {
340
- await fs.rename(tmpPath, sessionPath);
341
- } catch (err) {
342
- await fs.unlink(tmpPath).catch(() => {
343
- });
344
- throw err;
345
- }
348
+ await atomicWrite(sessionPath, JSON.stringify(data));
346
349
  }
347
350
  async function loadCtrlSession() {
348
351
  const sessionPath = path2.join(SESSION_DIR, `${CTRL_SESSION_KEY}.json`);
@@ -441,7 +444,7 @@ function stopAutoSave() {
441
444
  }
442
445
  autoSaveCallback = null;
443
446
  }
444
- var AUTO_SAVE_INTERVAL, sessionDirReady, CTRL_SESSION_KEY, autoSaveTimer, autoSaveCallback;
447
+ var AUTO_SAVE_INTERVAL, RENAME_MAX_RETRIES, RENAME_RETRY_BASE_MS, sessionDirReady, CTRL_SESSION_KEY, autoSaveTimer, autoSaveCallback;
445
448
  var init_manager = __esm({
446
449
  "src/server/session/manager.ts"() {
447
450
  "use strict";
@@ -449,6 +452,8 @@ var init_manager = __esm({
449
452
  init_constants();
450
453
  init_queue();
451
454
  AUTO_SAVE_INTERVAL = 60 * 1e3;
455
+ RENAME_MAX_RETRIES = 3;
456
+ RENAME_RETRY_BASE_MS = 50;
452
457
  sessionDirReady = false;
453
458
  CTRL_SESSION_KEY = CTRL_ROOM;
454
459
  autoSaveTimer = null;
@@ -2681,7 +2686,7 @@ import path4 from "path";
2681
2686
  function getAdapter(format) {
2682
2687
  return adapters[format] ?? plaintextAdapter;
2683
2688
  }
2684
- async function atomicWrite(filePath, content) {
2689
+ async function atomicWrite2(filePath, content) {
2685
2690
  const tempPath = path4.join(path4.dirname(filePath), `.tandem-tmp-${Date.now()}`);
2686
2691
  await fs2.writeFile(tempPath, content, "utf-8");
2687
2692
  await fs2.rename(tempPath, filePath);
@@ -3772,7 +3777,7 @@ async function convertToMarkdown(documentId, outputPath) {
3772
3777
  resolvedOutput = path7.join(sourceDir, `${baseName}.md`);
3773
3778
  }
3774
3779
  resolvedOutput = await findAvailablePath(resolvedOutput);
3775
- await atomicWrite(resolvedOutput, markdown);
3780
+ await atomicWrite2(resolvedOutput, markdown);
3776
3781
  try {
3777
3782
  const openResult = await openFileByPath(resolvedOutput);
3778
3783
  return {
@@ -4065,7 +4070,7 @@ function registerDocumentTools(server) {
4065
4070
  });
4066
4071
  }
4067
4072
  const output = adapter.save(r.doc);
4068
- await atomicWrite(r.filePath, output);
4073
+ await atomicWrite2(r.filePath, output);
4069
4074
  await saveSession(r.filePath, format, r.doc);
4070
4075
  const meta = r.doc.getMap(Y_MAP_DOCUMENT_META);
4071
4076
  r.doc.transact(() => meta.set(Y_MAP_SAVED_AT_VERSION, Date.now()), MCP_ORIGIN);
@@ -5908,42 +5913,41 @@ async function main() {
5908
5913
  } catch (err) {
5909
5914
  console.error(`[Tandem] ${err instanceof Error ? err.message : err} \u2014 proceeding anyway`);
5910
5915
  }
5911
- const [srv] = await Promise.all([
5912
- startMcpServerHttp(mcpPort),
5913
- startHocuspocus(wsPort).then(() => {
5914
- console.error(`[Tandem] Hocuspocus WebSocket server running on ws://localhost:${wsPort}`);
5915
- })
5916
- ]);
5917
- httpServer = srv;
5916
+ const projectRoot = path10.resolve(path10.dirname(fileURLToPath2(import.meta.url)), "../..");
5918
5917
  try {
5919
5918
  const versionStatus = await checkVersionChange(APP_VERSION, LAST_SEEN_VERSION_FILE);
5920
5919
  if (versionStatus === "upgraded") {
5921
- const changelogPath = path10.resolve(
5922
- path10.dirname(fileURLToPath2(import.meta.url)),
5923
- "../../CHANGELOG.md"
5924
- );
5925
- await openFileByPath(changelogPath);
5920
+ await openFileByPath(path10.join(projectRoot, "CHANGELOG.md"));
5926
5921
  console.error(`[Tandem] Opened CHANGELOG.md (upgraded to v${APP_VERSION})`);
5927
5922
  }
5928
5923
  } catch (err) {
5929
5924
  console.error("[Tandem] Version check / changelog open failed (non-fatal):", err);
5930
5925
  }
5931
- if (getOpenDocs().size === 0 && !process.env.TANDEM_NO_SAMPLE) {
5932
- const samplePath = path10.resolve(
5933
- path10.dirname(fileURLToPath2(import.meta.url)),
5934
- "../../sample/welcome.md"
5935
- );
5936
- openFileByPath(samplePath).then(() => {
5937
- const doc = getOrCreateDocument(docIdFromPath(samplePath));
5938
- injectTutorialAnnotations(doc);
5939
- }).catch((err) => {
5926
+ if (docCount() === 0 && !process.env.TANDEM_NO_SAMPLE) {
5927
+ const samplePath = path10.join(projectRoot, "sample/welcome.md");
5928
+ try {
5929
+ await openFileByPath(samplePath);
5930
+ try {
5931
+ const doc = getOrCreateDocument(docIdFromPath(samplePath));
5932
+ injectTutorialAnnotations(doc);
5933
+ } catch (err) {
5934
+ console.error("[Tandem] Failed to inject tutorial annotations:", err);
5935
+ }
5936
+ } catch (err) {
5940
5937
  if (err.code === "ENOENT") {
5941
5938
  console.error("[Tandem] Sample file not found (skipping):", samplePath);
5942
5939
  } else {
5943
5940
  console.error("[Tandem] Failed to auto-open sample document:", err);
5944
5941
  }
5945
- });
5942
+ }
5946
5943
  }
5944
+ const [srv] = await Promise.all([
5945
+ startMcpServerHttp(mcpPort),
5946
+ startHocuspocus(wsPort).then(() => {
5947
+ console.error(`[Tandem] Hocuspocus WebSocket server running on ws://localhost:${wsPort}`);
5948
+ })
5949
+ ]);
5950
+ httpServer = srv;
5947
5951
  console.error("");
5948
5952
  console.error(` Tandem v${APP_VERSION}`);
5949
5953
  console.error("");