tandem-editor 0.2.11 → 0.3.0

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.
@@ -1,17 +1,17 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Tandem - Collaborative Editor</title>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Tandem - Collaborative Editor</title>
7
7
  <style>
8
8
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
9
  html, body, #root { height: 100%; }
10
10
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
11
- </style>
12
- <script type="module" crossorigin src="/assets/index-CfGlbY9B.js"></script>
13
- </head>
14
- <body>
15
- <div id="root"></div>
16
- </body>
17
- </html>
11
+ </style>
12
+ <script type="module" crossorigin src="/assets/index-BXWLR51Y.js"></script>
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/shared/constants.ts
12
- var DEFAULT_WS_PORT, DEFAULT_MCP_PORT, SUPPORTED_EXTENSIONS, MAX_FILE_SIZE, MAX_WS_PAYLOAD, IDLE_TIMEOUT, SESSION_MAX_AGE, INTERRUPTION_MODE_DEFAULT, CHARS_PER_PAGE, LARGE_FILE_PAGE_THRESHOLD, VERY_LARGE_FILE_PAGE_THRESHOLD, CTRL_ROOM, Y_MAP_ANNOTATIONS, Y_MAP_AWARENESS, Y_MAP_USER_AWARENESS, Y_MAP_CHAT, Y_MAP_DOCUMENT_META, Y_MAP_SAVED_AT_VERSION, NOTIFICATION_BUFFER_SIZE, TUTORIAL_ANNOTATION_PREFIX, CHANNEL_EVENT_BUFFER_SIZE, CHANNEL_EVENT_BUFFER_AGE_MS, CHANNEL_SSE_KEEPALIVE_MS;
12
+ var DEFAULT_WS_PORT, DEFAULT_MCP_PORT, SUPPORTED_EXTENSIONS, MAX_FILE_SIZE, MAX_WS_PAYLOAD, IDLE_TIMEOUT, SESSION_MAX_AGE, TANDEM_MODE_DEFAULT, SELECTION_DWELL_DEFAULT_MS, CHARS_PER_PAGE, LARGE_FILE_PAGE_THRESHOLD, VERY_LARGE_FILE_PAGE_THRESHOLD, CTRL_ROOM, Y_MAP_ANNOTATIONS, Y_MAP_AWARENESS, Y_MAP_USER_AWARENESS, Y_MAP_MODE, Y_MAP_CHAT, Y_MAP_DOCUMENT_META, Y_MAP_SAVED_AT_VERSION, NOTIFICATION_BUFFER_SIZE, TUTORIAL_ANNOTATION_PREFIX, CHANNEL_EVENT_BUFFER_SIZE, CHANNEL_EVENT_BUFFER_AGE_MS, CHANNEL_SSE_KEEPALIVE_MS;
13
13
  var init_constants = __esm({
14
14
  "src/shared/constants.ts"() {
15
15
  "use strict";
@@ -20,7 +20,8 @@ var init_constants = __esm({
20
20
  MAX_WS_PAYLOAD = 10 * 1024 * 1024;
21
21
  IDLE_TIMEOUT = 30 * 60 * 1e3;
22
22
  SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1e3;
23
- INTERRUPTION_MODE_DEFAULT = "all";
23
+ TANDEM_MODE_DEFAULT = "tandem";
24
+ SELECTION_DWELL_DEFAULT_MS = 1e3;
24
25
  CHARS_PER_PAGE = 3e3;
25
26
  LARGE_FILE_PAGE_THRESHOLD = 50;
26
27
  VERY_LARGE_FILE_PAGE_THRESHOLD = 100;
@@ -28,6 +29,7 @@ var init_constants = __esm({
28
29
  Y_MAP_ANNOTATIONS = "annotations";
29
30
  Y_MAP_AWARENESS = "awareness";
30
31
  Y_MAP_USER_AWARENESS = "userAwareness";
32
+ Y_MAP_MODE = "mode";
31
33
  Y_MAP_CHAT = "chat";
32
34
  Y_MAP_DOCUMENT_META = "documentMeta";
33
35
  Y_MAP_SAVED_AT_VERSION = "savedAtVersion";
@@ -1640,7 +1642,7 @@ var init_positions2 = __esm({
1640
1642
 
1641
1643
  // src/shared/types.ts
1642
1644
  import { z } from "zod";
1643
- var AnnotationTypeSchema, AnnotationStatusSchema, AnnotationPrioritySchema, InterruptionModeSchema, HighlightColorSchema, SeveritySchema, AuthorSchema, AnnotationActionSchema, ExportFormatSchema, DocumentFormatSchema, ToolErrorCodeSchema;
1645
+ var AnnotationTypeSchema, AnnotationStatusSchema, AnnotationPrioritySchema, HighlightColorSchema, SeveritySchema, TandemModeSchema, AuthorSchema, AnnotationActionSchema, ExportFormatSchema, DocumentFormatSchema, ToolErrorCodeSchema;
1644
1646
  var init_types2 = __esm({
1645
1647
  "src/shared/types.ts"() {
1646
1648
  "use strict";
@@ -1655,9 +1657,9 @@ var init_types2 = __esm({
1655
1657
  ]);
1656
1658
  AnnotationStatusSchema = z.enum(["pending", "accepted", "dismissed"]);
1657
1659
  AnnotationPrioritySchema = z.enum(["normal", "urgent"]);
1658
- InterruptionModeSchema = z.enum(["all", "urgent-only", "paused"]);
1659
1660
  HighlightColorSchema = z.enum(["yellow", "red", "green", "blue", "purple"]);
1660
1661
  SeveritySchema = z.enum(["info", "warning", "error", "success"]);
1662
+ TandemModeSchema = z.enum(["solo", "tandem"]);
1661
1663
  AuthorSchema = z.enum(["user", "claude", "import"]);
1662
1664
  AnnotationActionSchema = z.enum(["accept", "dismiss"]);
1663
1665
  ExportFormatSchema = z.enum(["markdown", "json"]);
@@ -3569,26 +3571,37 @@ function attachObservers(docName, doc) {
3569
3571
  annotationsMap.observe(annotationsObs);
3570
3572
  cleanups.push(() => annotationsMap.unobserve(annotationsObs));
3571
3573
  const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);
3574
+ let selectionDwellTimer = null;
3572
3575
  const awarenessObs = (event, txn) => {
3573
3576
  if (txn.origin === MCP_ORIGIN) return;
3574
3577
  if (event.keysChanged.has("selection")) {
3575
3578
  const selection = userAwareness.get("selection");
3579
+ if (selectionDwellTimer) {
3580
+ clearTimeout(selectionDwellTimer);
3581
+ selectionDwellTimer = null;
3582
+ }
3576
3583
  if (!selection || selection.from === selection.to) return;
3577
- pushEvent({
3578
- id: generateEventId(),
3579
- type: "selection:changed",
3580
- timestamp: Date.now(),
3581
- documentId: docName,
3582
- payload: {
3583
- from: selection.from,
3584
- to: selection.to,
3585
- selectedText: selection.selectedText ?? ""
3586
- }
3587
- });
3584
+ selectionDwellTimer = setTimeout(() => {
3585
+ selectionDwellTimer = null;
3586
+ pushEvent({
3587
+ id: generateEventId(),
3588
+ type: "selection:changed",
3589
+ timestamp: Date.now(),
3590
+ documentId: docName,
3591
+ payload: {
3592
+ from: selection.from,
3593
+ to: selection.to,
3594
+ selectedText: selection.selectedText ?? ""
3595
+ }
3596
+ });
3597
+ }, SELECTION_DWELL_DEFAULT_MS);
3588
3598
  }
3589
3599
  };
3590
3600
  userAwareness.observe(awarenessObs);
3591
- cleanups.push(() => userAwareness.unobserve(awarenessObs));
3601
+ cleanups.push(() => {
3602
+ userAwareness.unobserve(awarenessObs);
3603
+ if (selectionDwellTimer) clearTimeout(selectionDwellTimer);
3604
+ });
3592
3605
  docObservers.set(docName, cleanups);
3593
3606
  console.error(`[EventQueue] Attached observers for document: ${docName}`);
3594
3607
  }
@@ -4298,15 +4311,12 @@ function registerDocumentTools(server) {
4298
4311
  withErrorBoundary("tandem_status", async () => {
4299
4312
  const activeId = getActiveDocId();
4300
4313
  const active = activeId ? openDocs2.get(activeId) : null;
4301
- let interruptionMode = INTERRUPTION_MODE_DEFAULT;
4302
- if (activeId) {
4303
- const doc = getOrCreateDocument(activeId);
4304
- const awareness = doc.getMap(Y_MAP_USER_AWARENESS);
4305
- interruptionMode = awareness.get("interruptionMode") ?? INTERRUPTION_MODE_DEFAULT;
4306
- }
4314
+ const ctrlDoc = getOrCreateDocument(CTRL_ROOM);
4315
+ const ctrlAwareness = ctrlDoc.getMap(Y_MAP_USER_AWARENESS);
4316
+ const mode = TandemModeSchema.catch(TANDEM_MODE_DEFAULT).parse(ctrlAwareness.get(Y_MAP_MODE));
4307
4317
  return mcpSuccess({
4308
4318
  running: true,
4309
- interruptionMode,
4319
+ mode,
4310
4320
  activeDocument: active ? { documentId: active.id, filePath: active.filePath, format: active.format } : null,
4311
4321
  openDocuments: Array.from(openDocs2.values()).map((d) => ({
4312
4322
  documentId: d.id,
@@ -4467,6 +4477,15 @@ function createAnnotation(map, ydoc, type, anchored, content, extras) {
4467
4477
  ...extras
4468
4478
  };
4469
4479
  ydoc.transact(() => map.set(id, annotation), MCP_ORIGIN);
4480
+ const snippet = annotation.textSnapshot ? `: "${annotation.textSnapshot.slice(0, 60)}${annotation.textSnapshot.length > 60 ? "\u2026" : ""}"` : "";
4481
+ pushNotification({
4482
+ id: generateNotificationId(),
4483
+ type: "review-pending",
4484
+ severity: "info",
4485
+ message: `New ${type[0].toUpperCase() + type.slice(1)}${snippet}`,
4486
+ dedupKey: `review-pending:${type}`,
4487
+ timestamp: Date.now()
4488
+ });
4470
4489
  return id;
4471
4490
  }
4472
4491
  function collectAnnotations(map) {
@@ -4781,6 +4800,7 @@ function registerAnnotationTools(server) {
4781
4800
 
4782
4801
  // src/server/mcp/api-routes.ts
4783
4802
  init_constants();
4803
+ init_types2();
4784
4804
  init_document_model();
4785
4805
  init_file_opener();
4786
4806
  init_document_service();
@@ -4968,6 +4988,7 @@ function registerApplyTools(server) {
4968
4988
  }
4969
4989
 
4970
4990
  // src/server/mcp/api-routes.ts
4991
+ init_provider();
4971
4992
  function isHostAllowed(host) {
4972
4993
  const reqHost = (host ?? "").split(":")[0];
4973
4994
  return reqHost === "localhost" || reqHost === "127.0.0.1";
@@ -5169,6 +5190,12 @@ function registerApiRoutes(app, largeBody) {
5169
5190
  sendApiError(res, err);
5170
5191
  }
5171
5192
  });
5193
+ app.get("/api/mode", apiMiddleware, (_req, res) => {
5194
+ const ctrlDoc = getOrCreateDocument(CTRL_ROOM);
5195
+ const awareness = ctrlDoc.getMap(Y_MAP_USER_AWARENESS);
5196
+ const mode = TandemModeSchema.catch(TANDEM_MODE_DEFAULT).parse(awareness.get(Y_MAP_MODE));
5197
+ res.json({ mode });
5198
+ });
5172
5199
  app.options("/api/apply-changes", apiMiddleware);
5173
5200
  app.post("/api/apply-changes", apiMiddleware, largeBody, async (req, res) => {
5174
5201
  const { documentId, author, backupPath } = req.body ?? {};
@@ -5200,6 +5227,7 @@ function registerApiRoutes(app, largeBody) {
5200
5227
  // src/server/mcp/awareness.ts
5201
5228
  init_provider();
5202
5229
  import { z as z5 } from "zod";
5230
+ init_types2();
5203
5231
  init_utils();
5204
5232
  init_constants();
5205
5233
  init_queue();
@@ -5307,7 +5335,8 @@ function registerAwarenessTools(server) {
5307
5335
  const userAwareness = doc.getMap(Y_MAP_USER_AWARENESS);
5308
5336
  const selection = userAwareness.get("selection");
5309
5337
  const activity = userAwareness.get("activity");
5310
- const interruptionMode = userAwareness.get("interruptionMode") ?? INTERRUPTION_MODE_DEFAULT;
5338
+ const ctrlAwareness = ctrlDoc.getMap(Y_MAP_USER_AWARENESS);
5339
+ const mode = TandemModeSchema.catch(TANDEM_MODE_DEFAULT).parse(ctrlAwareness.get(Y_MAP_MODE));
5311
5340
  const hasSelection = selection && selection.from !== selection.to;
5312
5341
  const selectedText = hasSelection ? safeSlice2(fullText, selection.from, selection.to) : null;
5313
5342
  const parts = [];
@@ -5335,7 +5364,7 @@ function registerAwarenessTools(server) {
5335
5364
  return mcpSuccess({
5336
5365
  summary,
5337
5366
  hasNew,
5338
- interruptionMode,
5367
+ mode,
5339
5368
  userActions,
5340
5369
  userResponses,
5341
5370
  chatMessages,