dsh-diff-approval 0.14.0 → 0.14.1

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.
package/lib/client.js CHANGED
@@ -11946,115 +11946,115 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
11946
11946
  document.head.appendChild(tag);
11947
11947
  }
11948
11948
  var PendingPanel_module_css_default = {
11949
- "searchBar": "F1KBNa_searchBar",
11950
- "delCount": "F1KBNa_delCount",
11951
- "gutter": "F1KBNa_gutter",
11952
- "subline": "F1KBNa_subline",
11953
- "noteCentered": "F1KBNa_noteCentered",
11954
- "splitDivider": "F1KBNa_splitDivider",
11955
- "splitHScrollFill": "F1KBNa_splitHScrollFill",
11956
- "action": "F1KBNa_action",
11957
- "noticeText": "F1KBNa_noticeText",
11958
- "header": "F1KBNa_header",
11959
- "settingsRowTitle": "F1KBNa_settingsRowTitle",
11960
- "confirmCard": "F1KBNa_confirmCard",
11961
- "markerDel": "F1KBNa_markerDel",
11962
- "wrapActive": "F1KBNa_wrapActive",
11963
- "listScroll": "F1KBNa_listScroll",
11964
- "hint": "F1KBNa_hint",
11965
- "diffPath": "F1KBNa_diffPath",
11966
- "splitLadd": "F1KBNa_splitLadd",
11967
- "blockActions": "F1KBNa_blockActions",
11949
+ "confirmBackdrop": "F1KBNa_confirmBackdrop",
11950
+ "kindHint": "F1KBNa_kindHint",
11951
+ "splitRdel": "F1KBNa_splitRdel",
11968
11952
  "missingHint": "F1KBNa_missingHint",
11969
- "split": "F1KBNa_split",
11970
- "rowHead": "F1KBNa_rowHead",
11953
+ "expandExpanded": "F1KBNa_expandExpanded",
11954
+ "readError": "F1KBNa_readError",
11955
+ "diffBody": "F1KBNa_diffBody",
11956
+ "lines": "F1KBNa_lines",
11971
11957
  "langLabel": "F1KBNa_langLabel",
11972
- "note": "F1KBNa_note",
11958
+ "splitCol": "F1KBNa_splitCol",
11973
11959
  "splitRadd": "F1KBNa_splitRadd",
11974
- "confirmText": "F1KBNa_confirmText",
11975
- "expandExpanded": "F1KBNa_expandExpanded",
11960
+ "diffPath": "F1KBNa_diffPath",
11961
+ "diffActions": "F1KBNa_diffActions",
11962
+ "rowPath": "F1KBNa_rowPath",
11963
+ "listScroll": "F1KBNa_listScroll",
11964
+ "actionPrimary": "F1KBNa_actionPrimary",
11965
+ "importButton": "F1KBNa_importButton",
11966
+ "rows": "F1KBNa_rows",
11967
+ "searchCount": "F1KBNa_searchCount",
11968
+ "statusBar": "F1KBNa_statusBar",
11969
+ "footerButtons": "F1KBNa_footerButtons",
11970
+ "layer": "F1KBNa_layer",
11971
+ "badgeLabel": "F1KBNa_badgeLabel",
11976
11972
  "divergedHint": "F1KBNa_divergedHint",
11977
- "diffBody": "F1KBNa_diffBody",
11973
+ "actionError": "F1KBNa_actionError",
11974
+ "code": "F1KBNa_code",
11975
+ "splitDivider": "F1KBNa_splitDivider",
11976
+ "confirmCard": "F1KBNa_confirmCard",
11977
+ "title": "F1KBNa_title",
11978
+ "wrap": "F1KBNa_wrap",
11979
+ "statusAction": "F1KBNa_statusAction",
11980
+ "noticeButton": "F1KBNa_noticeButton",
11981
+ "flexSpacer": "F1KBNa_flexSpacer",
11982
+ "confirmActions": "F1KBNa_confirmActions",
11983
+ "searchInput": "F1KBNa_searchInput",
11984
+ "settingsRowText": "F1KBNa_settingsRowText",
11985
+ "header": "F1KBNa_header",
11986
+ "diffStats": "F1KBNa_diffStats",
11987
+ "settingsRowTitle": "F1KBNa_settingsRowTitle",
11988
+ "blockFlash": "F1KBNa_blockFlash",
11989
+ "row": "F1KBNa_row",
11990
+ "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
11978
11991
  "missing": "F1KBNa_missing",
11992
+ "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
11993
+ "searchBar": "F1KBNa_searchBar",
11994
+ "overviewRuler": "F1KBNa_overviewRuler",
11995
+ "notice": "F1KBNa_notice",
11996
+ "splitCols": "F1KBNa_splitCols",
11979
11997
  "markerAdd": "F1KBNa_markerAdd",
11998
+ "splitRoot": "F1KBNa_splitRoot",
11999
+ "blockPosition": "F1KBNa_blockPosition",
12000
+ "overviewMarker": "F1KBNa_overviewMarker",
12001
+ "noticeText": "F1KBNa_noticeText",
12002
+ "iconAction": "F1KBNa_iconAction",
12003
+ "splitHScrollRow": "F1KBNa_splitHScrollRow",
12004
+ "delCount": "F1KBNa_delCount",
12005
+ "splitLadd": "F1KBNa_splitLadd",
12006
+ "hint": "F1KBNa_hint",
12007
+ "splitHScroll": "F1KBNa_splitHScroll",
12008
+ "note": "F1KBNa_note",
12009
+ "settingsPage": "F1KBNa_settingsPage",
12010
+ "splitLdel": "F1KBNa_splitLdel",
12011
+ "rail": "F1KBNa_rail",
11980
12012
  "del": "F1KBNa_del",
12013
+ "noteCentered": "F1KBNa_noteCentered",
12014
+ "line": "F1KBNa_line",
12015
+ "action": "F1KBNa_action",
12016
+ "fileList": "F1KBNa_fileList",
12017
+ "rowMeta": "F1KBNa_rowMeta",
12018
+ "diffFlash": "F1KBNa_diffFlash",
11981
12019
  "add": "F1KBNa_add",
12020
+ "settingsSelector": "F1KBNa_settingsSelector",
11982
12021
  "detailEmpty": "F1KBNa_detailEmpty",
11983
- "badge": "F1KBNa_badge",
11984
- "title": "F1KBNa_title",
11985
- "blockPosition": "F1KBNa_blockPosition",
11986
- "badgeLabel": "F1KBNa_badgeLabel",
11987
- "headerActions": "F1KBNa_headerActions",
11988
- "panel": "F1KBNa_panel",
11989
- "group": "F1KBNa_group",
11990
- "blockFlash": "F1KBNa_blockFlash",
11991
- "settingsRow": "F1KBNa_settingsRow",
11992
- "importNote": "F1KBNa_importNote",
11993
- "diffFlash": "F1KBNa_diffFlash",
11994
- "notice": "F1KBNa_notice",
11995
- "rowPath": "F1KBNa_rowPath",
11996
- "readError": "F1KBNa_readError",
11997
- "settingsPage": "F1KBNa_settingsPage",
11998
- "settingsRowText": "F1KBNa_settingsRowText",
12022
+ "langSelect": "F1KBNa_langSelect",
11999
12023
  "fileListFloat": "F1KBNa_fileListFloat",
12000
- "bulkActions": "F1KBNa_bulkActions",
12024
+ "emptyState": "F1KBNa_emptyState",
12001
12025
  "kindTag": "F1KBNa_kindTag",
12002
12026
  "rowFailed": "F1KBNa_rowFailed",
12003
- "layer": "F1KBNa_layer",
12004
- "states": "F1KBNa_states",
12005
- "diffBodyWrap": "F1KBNa_diffBodyWrap",
12006
- "emptyState": "F1KBNa_emptyState",
12007
- "flexSpacer": "F1KBNa_flexSpacer",
12008
- "splitLdel": "F1KBNa_splitLdel",
12009
- "diffActions": "F1KBNa_diffActions",
12027
+ "addCount": "F1KBNa_addCount",
12028
+ "wrapActive": "F1KBNa_wrapActive",
12029
+ "close": "F1KBNa_close",
12030
+ "diffHeader": "F1KBNa_diffHeader",
12031
+ "badge": "F1KBNa_badge",
12032
+ "bulkActions": "F1KBNa_bulkActions",
12033
+ "expand": "F1KBNa_expand",
12034
+ "group": "F1KBNa_group",
12035
+ "rowHead": "F1KBNa_rowHead",
12010
12036
  "settingsSelectorChevron": "F1KBNa_settingsSelectorChevron",
12037
+ "settingsRow": "F1KBNa_settingsRow",
12038
+ "panel": "F1KBNa_panel",
12011
12039
  "detail": "F1KBNa_detail",
12012
- "actionError": "F1KBNa_actionError",
12013
- "actionQuietDisabled": "F1KBNa_actionQuietDisabled",
12014
- "searchInput": "F1KBNa_searchInput",
12015
- "searchCount": "F1KBNa_searchCount",
12016
- "vSpacer": "F1KBNa_vSpacer",
12017
- "code": "F1KBNa_code",
12018
- "iconAction": "F1KBNa_iconAction",
12019
- "kindHint": "F1KBNa_kindHint",
12020
- "splitHScroll": "F1KBNa_splitHScroll",
12021
- "overviewMarker": "F1KBNa_overviewMarker",
12022
- "rail": "F1KBNa_rail",
12023
- "wrap": "F1KBNa_wrap",
12024
- "splitRdel": "F1KBNa_splitRdel",
12025
- "close": "F1KBNa_close",
12026
- "rows": "F1KBNa_rows",
12040
+ "split": "F1KBNa_split",
12041
+ "importNote": "F1KBNa_importNote",
12027
12042
  "resizeHandle": "F1KBNa_resizeHandle",
12028
- "importButton": "F1KBNa_importButton",
12029
- "fileList": "F1KBNa_fileList",
12030
- "expand": "F1KBNa_expand",
12031
- "langSelect": "F1KBNa_langSelect",
12032
- "addCount": "F1KBNa_addCount",
12033
- "splitHScrollRow": "F1KBNa_splitHScrollRow",
12034
- "fullscreenBackdrop": "F1KBNa_fullscreenBackdrop",
12035
- "splitCol": "F1KBNa_splitCol",
12036
- "overviewRuler": "F1KBNa_overviewRuler",
12037
- "settingsSelector": "F1KBNa_settingsSelector",
12038
- "badgeCount": "F1KBNa_badgeCount",
12039
- "confirmActions": "F1KBNa_confirmActions",
12040
- "row": "F1KBNa_row",
12041
- "rowMeta": "F1KBNa_rowMeta",
12042
- "settingsRowDesc": "F1KBNa_settingsRowDesc",
12043
- "diffStats": "F1KBNa_diffStats",
12044
- "splitRoot": "F1KBNa_splitRoot",
12045
- "actionPrimary": "F1KBNa_actionPrimary",
12046
- "lines": "F1KBNa_lines",
12043
+ "diff": "F1KBNa_diff",
12044
+ "blockActions": "F1KBNa_blockActions",
12045
+ "confirmText": "F1KBNa_confirmText",
12046
+ "vSpacer": "F1KBNa_vSpacer",
12047
+ "gutter": "F1KBNa_gutter",
12048
+ "states": "F1KBNa_states",
12049
+ "markerDel": "F1KBNa_markerDel",
12047
12050
  "context": "F1KBNa_context",
12048
- "footerButtons": "F1KBNa_footerButtons",
12049
- "confirmBackdrop": "F1KBNa_confirmBackdrop",
12050
12051
  "diffBodySplit": "F1KBNa_diffBodySplit",
12051
- "diff": "F1KBNa_diff",
12052
- "diffHeader": "F1KBNa_diffHeader",
12053
- "splitCols": "F1KBNa_splitCols",
12054
- "line": "F1KBNa_line",
12055
- "statusAction": "F1KBNa_statusAction",
12056
- "statusBar": "F1KBNa_statusBar",
12057
- "noticeButton": "F1KBNa_noticeButton"
12052
+ "diffBodyWrap": "F1KBNa_diffBodyWrap",
12053
+ "subline": "F1KBNa_subline",
12054
+ "splitHScrollFill": "F1KBNa_splitHScrollFill",
12055
+ "settingsRowDesc": "F1KBNa_settingsRowDesc",
12056
+ "headerActions": "F1KBNa_headerActions",
12057
+ "badgeCount": "F1KBNa_badgeCount"
12058
12058
  };
12059
12059
  //#endregion
12060
12060
  //#region lib/types/client/PendingPanel.js
@@ -12080,7 +12080,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12080
12080
  /** Interactive composer/approval cards; clicking these keeps the panel open.
12081
12081
  Deliberately the cards themselves, not the seat: the approval frame's wide
12082
12082
  side gutters are blank space, so a click there must still close. */
12083
- const KEEP_OPEN_SELECTOR = "[data-composer-card],[data-question-key] > *,[data-plan-review-key] > *";
12083
+ const KEEP_OPEN_SELECTOR = "[data-composer-card],[data-question-key] > *,[data-plan-review-key] > *,[data-approval-key] > *";
12084
12084
  /** Seat counts as docked when its bottom is this close to the window bottom. */
12085
12085
  const DOCKED_TOLERANCE_PX = 48;
12086
12086
  /** File-list pane width bounds for the manual split drag, in px. */
@@ -12473,7 +12473,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12473
12473
  });
12474
12474
  }
12475
12475
  /** The two-column (side-by-side) whole-file diff view. */
12476
- const SplitDiff = (0, react.forwardRef)(function SplitDiff({ file, model, runs, langWrap, tabWidthSpaces, busy, t, onBlockKeep, onBlockRevert }, ref) {
12476
+ const SplitDiff = (0, react.forwardRef)(function SplitDiff({ file, model, runs, langWrap, tabWidthSpaces, busy, t, selection, onBlockKeep, onBlockRevert }, ref) {
12477
12477
  const { pairs, pairOfRow } = (0, react.useMemo)(() => computeSideBySideDiff(model.diff.rows), [model]);
12478
12478
  const pairCount = pairs.length;
12479
12479
  const bodyRef = (0, react.useRef)(null);
@@ -12661,8 +12661,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
12661
12661
  return lo;
12662
12662
  };
12663
12663
  const viewport = viewportH > 0 ? viewportH : totalHeight;
12664
- const start = Math.max(0, pairAtY(scrollTop) - OVERSCAN_ROWS);
12665
- const end = Math.min(pairCount, pairAtY(scrollTop + viewport) + OVERSCAN_ROWS);
12664
+ let start = Math.max(0, pairAtY(scrollTop) - OVERSCAN_ROWS);
12665
+ let end = Math.min(pairCount, pairAtY(scrollTop + viewport) + OVERSCAN_ROWS);
12666
+ if (selection !== void 0) {
12667
+ start = Math.min(start, selection.start);
12668
+ end = Math.max(end, selection.end + 1);
12669
+ }
12666
12670
  const visiblePairs = pairs.slice(start, end);
12667
12671
  const jump = (direction) => {
12668
12672
  if (blockOfPair.length === 0) return;
@@ -13360,8 +13364,12 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13360
13364
  return lo;
13361
13365
  };
13362
13366
  const viewport = viewportHeight > 0 ? viewportHeight : totalHeight;
13363
- const start = Math.max(0, rowAtY(scrollTop) - OVERSCAN_ROWS);
13364
- const end = Math.min(rowCount, rowAtY(scrollTop + viewport) + OVERSCAN_ROWS);
13367
+ let start = Math.max(0, rowAtY(scrollTop) - OVERSCAN_ROWS);
13368
+ let end = Math.min(rowCount, rowAtY(scrollTop + viewport) + OVERSCAN_ROWS);
13369
+ if (selection !== void 0) {
13370
+ start = Math.min(start, selection.start);
13371
+ end = Math.max(end, selection.end + 1);
13372
+ }
13365
13373
  const visibleRows = rows.slice(start, end);
13366
13374
  const blockEnd = hoveredBlock === void 0 ? void 0 : model.blocks[hoveredBlock]?.end;
13367
13375
  const blockActionsTop = blockEnd === void 0 ? 0 : Math.min(offsetOf(blockEnd + 1), Math.max(0, totalHeight - BLOCK_ACTIONS_FRAME_PX));
@@ -13743,6 +13751,7 @@ XID_Start XIDS`.split(/\s/).map((p) => [w(p), p]));
13743
13751
  tabWidthSpaces,
13744
13752
  busy,
13745
13753
  t,
13754
+ selection,
13746
13755
  onBlockKeep,
13747
13756
  onBlockRevert
13748
13757
  }) : (0, react_jsx_runtime.jsxs)("div", {
package/lib/index.js CHANGED
@@ -912,7 +912,7 @@ function apply(ctx, config) {
912
912
  sessionIds: [sessionId]
913
913
  };
914
914
  await ensureLoaded();
915
- if (store.fold(entry)) await persistSession();
915
+ if (store.fold(entry)) persistSession();
916
916
  }
917
917
  /** The stable `FsError.code`, when the thrown value carries one. */
918
918
  function fsErrorCodeOf(error) {
@@ -998,13 +998,13 @@ function apply(ctx, config) {
998
998
  entry: void 0,
999
999
  fileText: void 0
1000
1000
  });
1001
- await persistSession();
1001
+ persistSession();
1002
1002
  continue;
1003
1003
  }
1004
1004
  if (live.kind === "unavailable") {
1005
1005
  store.remove(entry.path);
1006
1006
  purgeForEntry(entry.path);
1007
- await persistSession();
1007
+ persistSession();
1008
1008
  continue;
1009
1009
  }
1010
1010
  const content = live.content;
@@ -1030,7 +1030,7 @@ function apply(ctx, config) {
1030
1030
  },
1031
1031
  fileText: content
1032
1032
  });
1033
- await persistSession();
1033
+ persistSession();
1034
1034
  if (redoWasPresent) redoCleared = true;
1035
1035
  }
1036
1036
  const state = {
@@ -1135,14 +1135,47 @@ function apply(ctx, config) {
1135
1135
  * file.
1136
1136
  * @returns resolution after the write settles (successful or logged).
1137
1137
  */
1138
- async function persistSession() {
1139
- await ensureLoaded();
1138
+ const PERSIST_THROTTLE_MS = 1e3;
1139
+ let persistDirty = false;
1140
+ let persistScheduled = false;
1141
+ let lastPersistAt = 0;
1142
+ let persistTimer;
1143
+ /** Actually write the (dirty) store to disk; one coalesced write. */
1144
+ async function flushPersist() {
1145
+ if (!persistDirty) return;
1146
+ persistDirty = false;
1147
+ lastPersistAt = Date.now();
1140
1148
  try {
1149
+ await ensureLoaded();
1141
1150
  await persistence.save(store.all());
1142
1151
  } catch (error) {
1143
1152
  ctx.logger.warn(`diff-approval: persisting pending changes failed: ${errorMessage(error)}`);
1144
1153
  }
1145
1154
  }
1155
+ /** Mark the store dirty and schedule one throttled, coalesced write. Pass
1156
+ * `force` to write immediately (user actions need durable, immediate results). */
1157
+ function persistSession(force = false) {
1158
+ persistDirty = true;
1159
+ if (force) {
1160
+ if (persistScheduled) {
1161
+ clearTimeout(persistTimer);
1162
+ persistScheduled = false;
1163
+ }
1164
+ flushPersist();
1165
+ return;
1166
+ }
1167
+ if (persistScheduled) return;
1168
+ const delay = PERSIST_THROTTLE_MS - (Date.now() - lastPersistAt);
1169
+ if (delay <= 0) flushPersist();
1170
+ else {
1171
+ persistScheduled = true;
1172
+ persistTimer = setTimeout(() => {
1173
+ persistScheduled = false;
1174
+ flushPersist();
1175
+ }, delay);
1176
+ persistTimer.unref?.();
1177
+ }
1178
+ }
1146
1179
  ctx.on("tools/result", (exec, result) => {
1147
1180
  if (exec.name === "str_replace_editor") {
1148
1181
  captureEditorMutation(exec, result);
@@ -1201,7 +1234,7 @@ function apply(ctx, config) {
1201
1234
  entry: void 0,
1202
1235
  fileText: void 0
1203
1236
  });
1204
- await persistSession();
1237
+ persistSession(true);
1205
1238
  return {
1206
1239
  ok: true,
1207
1240
  value: { outcome: "kept" }
@@ -1247,7 +1280,7 @@ function apply(ctx, config) {
1247
1280
  }
1248
1281
  store.remove(target.id);
1249
1282
  if (undo !== void 0) pushUndo(target.sessionId, undo.before, undo.after);
1250
- await persistSession();
1283
+ persistSession(true);
1251
1284
  return {
1252
1285
  ok: true,
1253
1286
  value: { outcome: "reverted" }
@@ -1281,7 +1314,7 @@ function apply(ctx, config) {
1281
1314
  entry: afterEntry,
1282
1315
  fileText: void 0
1283
1316
  });
1284
- await persistSession();
1317
+ persistSession();
1285
1318
  return {
1286
1319
  ok: true,
1287
1320
  value: updatedOld === entry.newText ? {
@@ -1334,7 +1367,7 @@ function apply(ctx, config) {
1334
1367
  return rpcError(`block revert failed: ${errorMessage(error)}`);
1335
1368
  }
1336
1369
  if (undo !== void 0) pushUndo(blockTarget.sessionId, undo.before, undo.after);
1337
- await persistSession();
1370
+ persistSession();
1338
1371
  return {
1339
1372
  ok: true,
1340
1373
  value: normalizeEol(updatedNew) === normalizeEol(entry.oldText) ? {
@@ -1358,7 +1391,7 @@ function apply(ctx, config) {
1358
1391
  return rpcError(`undo failed: ${errorMessage(error)}`);
1359
1392
  }
1360
1393
  redoStack.push(pair);
1361
- await persistSession();
1394
+ persistSession(true);
1362
1395
  return {
1363
1396
  ok: true,
1364
1397
  value: {
@@ -1382,7 +1415,7 @@ function apply(ctx, config) {
1382
1415
  return rpcError(`redo failed: ${errorMessage(error)}`);
1383
1416
  }
1384
1417
  undoStack.push(pair);
1385
- await persistSession();
1418
+ persistSession(true);
1386
1419
  return {
1387
1420
  ok: true,
1388
1421
  value: {
@@ -1443,7 +1476,7 @@ function apply(ctx, config) {
1443
1476
  }
1444
1477
  }
1445
1478
  if (imported > 0) {
1446
- await persistSession();
1479
+ persistSession();
1447
1480
  const after = new Map(store.list(sessionId).map((entry) => [entry.path, entry]));
1448
1481
  const batchBefore = [];
1449
1482
  const batchAfter = [];
@@ -41,6 +41,14 @@ interface ChangeBlock {
41
41
  start: number;
42
42
  end: number;
43
43
  }
44
+ /** One selected line range in row indices, normalized low-to-high. */
45
+ interface RowRange {
46
+ start: number;
47
+ end: number;
48
+ /** Which file's lines a split selection references: 'old' (left column), 'new'
49
+ * (right column); undefined in single column (always the new file). */
50
+ side?: 'old' | 'new';
51
+ }
44
52
  /** Imperative surface the parent uses to drive block navigation from the
45
53
  * shared toolbar/keyboard in split mode (its own `focus` is private here). */
46
54
  export interface SplitDiffHandle {
@@ -56,6 +64,7 @@ export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
56
64
  tabWidthSpaces: number;
57
65
  busy: boolean;
58
66
  t: Translator;
67
+ selection: RowRange | undefined;
59
68
  onBlockKeep: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
60
69
  onBlockRevert: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
61
70
  } & import("react").RefAttributes<SplitDiffHandle>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-diff-approval",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "DeepSeek Harness plugin: pending-edit review with whole-file diff and Keep/Revert",
5
5
  "packageManager": "pnpm@11.21.0",
6
6
  "author": "Wu Zhiwei",