@youtyan/code-viewer 0.1.35 → 0.1.36

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.
@@ -197,6 +197,47 @@ function addAnnotationEntry(state, input, now, makeId = makeAnnotationId) {
197
197
  created_session: createdSession
198
198
  };
199
199
  }
200
+ function renameAnnotationSession(state, id, title) {
201
+ const session = state.sessions.find((s) => s.id === id);
202
+ if (!session)
203
+ return { state, renamed: false };
204
+ const next = title.trim().slice(0, ANNOTATION_TITLE_MAX_CHARS) || "Untitled session";
205
+ return {
206
+ state: {
207
+ version: 1,
208
+ sessions: state.sessions.map((s) => s.id === id ? { ...s, title: next } : s)
209
+ },
210
+ renamed: true
211
+ };
212
+ }
213
+ function updateAnnotationEntry(state, id, patch) {
214
+ const session = state.sessions.find((s) => s.entries.some((e) => e.id === id));
215
+ if (!session)
216
+ return { ok: false, error: "annotation not found" };
217
+ if (patch.body !== undefined) {
218
+ if (!patch.body.trim())
219
+ return { ok: false, error: "body is required" };
220
+ if (Buffer.byteLength(patch.body, "utf8") > ANNOTATION_BODY_MAX_BYTES)
221
+ return { ok: false, error: "body is too large" };
222
+ }
223
+ let updated = null;
224
+ const sessions = state.sessions.map((s) => s.id === session.id ? {
225
+ ...s,
226
+ entries: s.entries.map((e) => {
227
+ if (e.id !== id)
228
+ return e;
229
+ updated = {
230
+ ...e,
231
+ ...patch.title !== undefined ? { title: patch.title.trim() || undefined } : {},
232
+ ...patch.body !== undefined ? { body: patch.body } : {}
233
+ };
234
+ return updated;
235
+ })
236
+ } : s);
237
+ if (!updated)
238
+ return { ok: false, error: "annotation not found" };
239
+ return { ok: true, state: { version: 1, sessions }, entry: updated };
240
+ }
200
241
  function deleteAnnotationById(state, id) {
201
242
  for (const session of state.sessions) {
202
243
  if (session.id === id) {
@@ -1211,6 +1252,41 @@ function parseAnnotateArgs(argv) {
1211
1252
  }
1212
1253
  };
1213
1254
  }
1255
+ if (subcommand === "rename") {
1256
+ const id = rest[1];
1257
+ if (!id)
1258
+ return { ok: false, error: "rename requires a session id" };
1259
+ const title = options.get("--title");
1260
+ if (!title)
1261
+ return { ok: false, error: "rename requires --title <text>" };
1262
+ return {
1263
+ ok: true,
1264
+ args: { command: { kind: "rename", id, title }, cwd, server }
1265
+ };
1266
+ }
1267
+ if (subcommand === "edit") {
1268
+ const id = rest[1];
1269
+ if (!id)
1270
+ return { ok: false, error: "edit requires an annotation id" };
1271
+ const body = options.get("--body");
1272
+ const bodyFile = options.get("--body-file");
1273
+ if (body !== undefined && bodyFile !== undefined)
1274
+ return { ok: false, error: "use either --body or --body-file" };
1275
+ return {
1276
+ ok: true,
1277
+ args: {
1278
+ command: {
1279
+ kind: "edit",
1280
+ id,
1281
+ title: options.get("--title"),
1282
+ body,
1283
+ bodyFile
1284
+ },
1285
+ cwd,
1286
+ server
1287
+ }
1288
+ };
1289
+ }
1214
1290
  if (subcommand === "list") {
1215
1291
  return {
1216
1292
  ok: true,
@@ -1391,6 +1467,37 @@ async function runAnnotateCli(argv) {
1391
1467
  printList(state);
1392
1468
  return;
1393
1469
  }
1470
+ if (command.kind === "rename") {
1471
+ await request(serverUrl, "POST", {
1472
+ action: "rename",
1473
+ id: command.id,
1474
+ title: command.title
1475
+ });
1476
+ console.log(`renamed session ${command.id} to "${command.title}"`);
1477
+ return;
1478
+ }
1479
+ if (command.kind === "edit") {
1480
+ let bodyText = command.body;
1481
+ if (command.bodyFile !== undefined)
1482
+ bodyText = readFileSync4(command.bodyFile, "utf8");
1483
+ if (bodyText === undefined) {
1484
+ const stdin = await readStdin();
1485
+ if (stdin.trim())
1486
+ bodyText = stdin;
1487
+ }
1488
+ if (bodyText === undefined && command.title === undefined) {
1489
+ console.error("edit requires --title, --body, --body-file, or stdin");
1490
+ process.exit(1);
1491
+ }
1492
+ const result = await request(serverUrl, "POST", {
1493
+ action: "update",
1494
+ id: command.id,
1495
+ title: command.title,
1496
+ body: bodyText
1497
+ });
1498
+ console.log(`updated annotation ${result.entry.id} (${result.entry.path}${formatLine(result.entry.line)})`);
1499
+ return;
1500
+ }
1394
1501
  if (command.kind === "delete") {
1395
1502
  const result = await request(serverUrl, "POST", {
1396
1503
  action: "delete",
@@ -1421,6 +1528,9 @@ Usage:
1421
1528
  code-viewer annotate add --file <path> [--line <n>|<n>-<m>]
1422
1529
  [--from <ref>] [--to <ref>] [--title <text>] [--session <id>]
1423
1530
  [--body <markdown> | --body-file <path>] (or pipe body via stdin)
1531
+ code-viewer annotate rename <session-id> --title <text>
1532
+ code-viewer annotate edit <id> [--title <text>]
1533
+ [--body <markdown> | --body-file <path>] (or pipe body via stdin)
1424
1534
  code-viewer annotate list [--json]
1425
1535
  code-viewer annotate delete <id>
1426
1536
  code-viewer annotate clear
@@ -1488,6 +1598,20 @@ location and renders your explanation directly under the annotated lines.
1488
1598
  - The human can share a walkthrough as a URL; one session = one shareable
1489
1599
  walkthrough. Do not mix unrelated topics in one session.
1490
1600
 
1601
+ ## Fixing mistakes and follow-ups
1602
+
1603
+ - The human may paste a reference block copied from the viewer that starts
1604
+ with "code-viewer のコード注釈について依頼があります" and lists the
1605
+ annotation id, location, and session, followed by their question.
1606
+ Read the current body first: code-viewer annotate list --json
1607
+ - Revise a wrong annotation IN PLACE (do not delete + re-add; the id and
1608
+ its position in the walkthrough are preserved):
1609
+ code-viewer annotate edit <id> --body "<corrected markdown>"
1610
+ (long bodies: --body-file <path> or pipe via stdin; --title also works)
1611
+ - Post a follow-up answer next to the original instead of replacing it:
1612
+ code-viewer annotate add --session <session-id> --file <path> --line <n> --title "回答: ..." --body "<markdown>"
1613
+ - Rename a session: code-viewer annotate rename <session-id> --title <text>
1614
+
1491
1615
  ## Cleanup
1492
1616
 
1493
1617
  - delete <id> removes one annotation or a whole session by its id.
@@ -1500,7 +1624,7 @@ var init_annotate_cli = __esm(() => {
1500
1624
  init_server_registry();
1501
1625
  });
1502
1626
 
1503
- // web-src/directory-name.ts
1627
+ // web-src/core/directory-name.ts
1504
1628
  function normalizeNewDirectoryName(name) {
1505
1629
  if (typeof name !== "string")
1506
1630
  return null;
@@ -1517,7 +1641,7 @@ function normalizeNewDirectoryName(name) {
1517
1641
  return trimmed;
1518
1642
  }
1519
1643
 
1520
- // web-src/routes.ts
1644
+ // web-src/core/routes.ts
1521
1645
  var SPA_PATHS, APP_ENTRY_PATHS;
1522
1646
  var init_routes = __esm(() => {
1523
1647
  SPA_PATHS = ["/todif", "/todiff", "/file", "/help"];
@@ -3784,6 +3908,32 @@ async function handleAnnotations(req) {
3784
3908
  }
3785
3909
  return json({ ok: true, removed: result.removed });
3786
3910
  }
3911
+ if (action === "rename") {
3912
+ const id = typeof body.id === "string" ? body.id : "";
3913
+ const title = typeof body.title === "string" ? body.title : "";
3914
+ if (!id)
3915
+ return text("invalid id", 400);
3916
+ const result = renameAnnotationSession(loadAnnotationsState(cwd), id, title);
3917
+ if (!result.renamed)
3918
+ return text("session not found", 404);
3919
+ saveAnnotationsState(cwd, result.state);
3920
+ annotationSse("update", id);
3921
+ return json({ ok: true });
3922
+ }
3923
+ if (action === "update") {
3924
+ const id = typeof body.id === "string" ? body.id : "";
3925
+ if (!id)
3926
+ return text("invalid id", 400);
3927
+ const result = updateAnnotationEntry(loadAnnotationsState(cwd), id, {
3928
+ title: typeof body.title === "string" ? body.title : undefined,
3929
+ body: typeof body.body === "string" ? body.body : undefined
3930
+ });
3931
+ if (result.ok === false)
3932
+ return text(result.error, 400);
3933
+ saveAnnotationsState(cwd, result.state);
3934
+ annotationSse("update", undefined, id);
3935
+ return json({ ok: true, entry: result.entry });
3936
+ }
3787
3937
  if (action === "clear") {
3788
3938
  saveAnnotationsState(cwd, emptyAnnotationsState());
3789
3939
  annotationSse("clear");
@@ -3977,12 +4127,24 @@ data: ok
3977
4127
  started_at: new Date().toISOString()
3978
4128
  });
3979
4129
  process.on("exit", () => removeServerRegistry(cwd, process.pid));
3980
- for (const signal of ["SIGINT", "SIGTERM"]) {
4130
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
3981
4131
  process.on(signal, () => {
3982
4132
  removeServerRegistry(cwd, process.pid);
3983
4133
  process.exit(0);
3984
4134
  });
3985
4135
  }
4136
+ if (process.env.CODE_VIEWER_DEV === "1") {
4137
+ const parentPid = process.ppid;
4138
+ setInterval(() => {
4139
+ try {
4140
+ process.kill(parentPid, 0);
4141
+ } catch {
4142
+ console.log("dev wrapper exited; shutting down preview server");
4143
+ removeServerRegistry(cwd, process.pid);
4144
+ process.exit(0);
4145
+ }
4146
+ }, 1000).unref();
4147
+ }
3986
4148
  startDevAssetReload({
3987
4149
  enabled: process.env.CODE_VIEWER_DEV === "1",
3988
4150
  webRoot: WEB_ROOT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {
@@ -65,6 +65,5 @@
65
65
  },
66
66
  "engines": {
67
67
  "node": ">=20.0.0"
68
- },
69
- "dependencies": {}
68
+ }
70
69
  }