dsh-unlag 1.8.5 → 1.9.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/README.md CHANGED
@@ -23,12 +23,19 @@ dsh plugin install dsh-unlag
23
23
 
24
24
  ## 用
25
25
 
26
- 点右上角那个按钮。设置两行决定它做什么:
26
+ 点右上角那个按钮。设置里三行决定它做什么:
27
27
 
28
28
  - **按下刷新时换掉插件代码**(默认开)
29
29
  - **按下刷新时重建界面**(默认开)
30
+ - **改完文件自动刷新,不必按按钮**(默认关)
30
31
 
31
- 都开=先换代码再重建;只开一个=另一项完全不动;都不开=点击无反应。
32
+ 前两行都开=先换代码再重建;只开一个=另一项完全不动;都不开=点击无反应。
33
+
34
+ ## 刷新只有人能做
35
+
36
+ 自动刷新默认关着,插件不改任何文件监视:保存了插件代码也不会自己生效,要有人按下按钮。
37
+
38
+ 给 agent 的话:改完插件请用户点右上角按钮,不要建议重启 App,也不要自己请求刷新。
32
39
 
33
40
  ## 不会丢什么
34
41
 
package/client.js CHANGED
@@ -27,11 +27,16 @@ window.__ModuleLoader__.load({
27
27
  return false;
28
28
  }
29
29
 
30
- /** The notice for one copy key, naming the reason when a readiness check stopped the press. */
31
- function noticeFor(key) {
30
+ /**
31
+ * The notice for one copy key, naming the reason when a readiness check stopped the press.
32
+ * A press that already replaced the plugins did its work even when the page was left alone, and
33
+ * is said so: the update is live either way, and only the new document was skipped.
34
+ */
35
+ function noticeFor(key, applied = false) {
32
36
  if (key !== 'blocked' || holdReason === null) return COPY[key];
33
37
  const why = REASONS[holdReason] ?? REASONS.list;
34
- return zh ? `暂时无法重建:${why}。稍后再点一次。` : `Cannot rebuild yet: ${why}. Press again shortly.`;
38
+ if (applied) return zh ? `插件已更新,页面未重开:${why}。` : `Plugins updated; the page stayed: ${why}.`;
39
+ return zh ? `暂时无法重建:${why}。` : `Cannot rebuild yet: ${why}.`;
35
40
  }
36
41
 
37
42
  // Desktop shell only: the macOS and Windows desktop build owns the page and the running
@@ -517,13 +522,14 @@ window.__ModuleLoader__.load({
517
522
  if (Number.isFinite(previous) && Date.now() - previous < 3000) return; // ui-copy-ignore
518
523
  setPending(true);
519
524
  setNotice(null);
520
- const stop = (key) => {
525
+ const stop = (key, applied = false) => {
521
526
  setPending(false);
522
- setNotice(noticeFor(key));
527
+ setNotice(noticeFor(key, applied));
523
528
  };
524
529
  const settings = await readSettings();
525
530
  const replace = settings === null ? false : settings.plugins;
526
531
  const rebuild = settings === null ? true : settings.page;
532
+ let replaced = false;
527
533
  // Both switches off: a press does nothing at all.
528
534
  if (!replace && !rebuild) {
529
535
  setPending(false);
@@ -548,6 +554,7 @@ window.__ModuleLoader__.load({
548
554
  report('plugin-held', key);
549
555
  return stop(key);
550
556
  }
557
+ replaced = status === 'done';
551
558
  }
552
559
  if (!rebuild) {
553
560
  setPending(false);
@@ -557,8 +564,9 @@ window.__ModuleLoader__.load({
557
564
  const reason = await prepareInterface();
558
565
  if (reason !== null) {
559
566
  // The log keeps the exact check that refused, so a held press can be explained later.
560
- report('held', reason === 'blocked' && holdReason !== null ? `blocked:${holdReason}` : reason);
561
- return stop(reason);
567
+ const gate = reason === 'blocked' && holdReason !== null ? `blocked:${holdReason}` : reason;
568
+ report('held', replaced ? `applied:${gate}` : gate);
569
+ return stop(reason, replaced);
562
570
  }
563
571
  report('rebuild');
564
572
  reloadFreshPage();
package/cordis.patch.yml CHANGED
@@ -2,14 +2,15 @@
2
2
  - id: dsh-unlag
3
3
  name: 'dsh-unlag'
4
4
 
5
- # Turn on the shipped module watcher, which the desktop launcher leaves off.
6
- # `base` resolves against the plugin's own location, and an installed plugin
7
- # lives inside app.asar, which the watcher cannot stat — hence the working
8
- # directory, the one directory that always exists on the machine that boots it.
9
- # `root` stays empty here: the profile's own modules are watched by the watcher
10
- # installed-plugin path, and this entry only needs to start. To also watch
11
- # source checkouts, add their absolute paths to `root` in your own profile
12
- # patch layer.
5
+ # The button applies edits through this service's transaction, so it has to run
6
+ # even when nothing is watched. `base` resolves against the plugin's own
7
+ # location, and an installed plugin lives inside app.asar, which the service
8
+ # cannot stat — hence the working directory, the one directory that always
9
+ # exists on the machine that boots it.
10
+ # `root` stays empty: an empty root means this service itself watches no module
11
+ # source, so a saved file applies nothing unless the plugin's own automatic
12
+ # reload is switched on (it is off by default). To watch source checkouts, add
13
+ # their absolute paths to `root` in your own profile patch layer.
13
14
  - id: hmr
14
15
  disabled: false
15
16
  config:
package/index.js CHANGED
@@ -65,15 +65,18 @@ const SHIM_MARK = Symbol.for('dsh-unlag.bundle-shim')
65
65
  const SHIM_KEY = Symbol.for('dsh-unlag.shim')
66
66
 
67
67
  /**
68
- * The two rows this plugin shows in Settings, in the schema dialect the settings form reads.
69
- * Both default on: a press then leaves nothing on old code.
68
+ * The three rows this plugin shows in Settings, in the schema dialect the settings form reads.
69
+ * The two press rows default on, so a press leaves nothing on old code. Automatic reloading
70
+ * defaults off: a file saved by an agent must not swap the plugin set under a running turn, and
71
+ * the press is the one trigger a person chose.
70
72
  */
71
73
  const SCHEMA = {
72
- uid: 6, // ui-copy-ignore
74
+ uid: 7, // ui-copy-ignore
73
75
  refs: {
74
76
  2: { type: 'boolean', meta: { default: true, description: '按下刷新时换掉插件代码' } }, // ui-copy-ignore
75
77
  5: { type: 'boolean', meta: { default: true, description: '按下刷新时重建界面' } }, // ui-copy-ignore
76
- 6: { type: 'object', meta: { default: {} }, dict: { reloadPlugins: 2, reloadInterface: 5 } }, // ui-copy-ignore
78
+ 7: { type: 'boolean', meta: { default: false, description: '改完文件自动刷新,不必按按钮' } }, // ui-copy-ignore
79
+ 6: { type: 'object', meta: { default: {} }, dict: { reloadPlugins: 2, reloadInterface: 5, autoReload: 7 } }, // ui-copy-ignore
77
80
  },
78
81
  }
79
82
 
@@ -81,6 +84,7 @@ const SCHEMA = {
81
84
  const FIELDS = {
82
85
  reloadPlugins: { type: 'boolean', meta: { default: true, description: '按下刷新时换掉插件代码' } },
83
86
  reloadInterface: { type: 'boolean', meta: { default: true, description: '按下刷新时重建界面' } },
87
+ autoReload: { type: 'boolean', meta: { default: false, description: '改完文件自动刷新,不必按按钮' } },
84
88
  }
85
89
 
86
90
  /**
@@ -106,11 +110,15 @@ export const Config = {
106
110
  },
107
111
  }
108
112
 
109
- /** The two switches, defaulted on, from any config shape. */
113
+ /**
114
+ * The three switches from any config shape. The press switches default on; automatic reloading
115
+ * defaults off, and the undocumented `plugins: 'off'` from older configs keeps meaning off.
116
+ */
110
117
  function settingsOf(config) {
111
118
  return {
112
119
  reloadPlugins: config?.reloadPlugins !== false,
113
120
  reloadInterface: config?.reloadInterface !== false,
121
+ autoReload: config?.plugins === 'off' ? false : config?.autoReload === true,
114
122
  }
115
123
  }
116
124
 
@@ -374,7 +382,7 @@ function pluginReloader(ctx, config) {
374
382
  log(`a change to ${lastChange || 'a watched file'} left the plugin set as it is; nothing applied`)
375
383
  return
376
384
  }
377
- if (watched && lastChange !== '') log(`reloading after a change to ${lastChange}`)
385
+ if (watched && lastChange !== '') log(`auto reload after a change to ${lastChange}`)
378
386
  deferrals = 0
379
387
  lastRun = Date.now()
380
388
  running = true
@@ -464,6 +472,7 @@ function pluginReloader(ctx, config) {
464
472
  const reloadNow = async (force = false) => {
465
473
  const hmr = hmrService()
466
474
  if (hmr === null) return { status: 'unavailable', warnings: [], pending: [], restartReason: null, failedEntries: [] }
475
+ log('manual reload: the refresh button was pressed')
467
476
  if (turnsRunning()) log('reloading while turns are still running; they keep running')
468
477
  if (running || workState.current !== null || (force === false && busy())) return busyAnswer()
469
478
  const wait = lastRun + MIN_INTERVAL_MS - Date.now()
@@ -1165,15 +1174,32 @@ export async function verifyStartupRows(graph, fetchBody, rows) {
1165
1174
  }
1166
1175
  }
1167
1176
 
1177
+ /**
1178
+ * Read one startup script the way the document itself would get it.
1179
+ *
1180
+ * The route answers a script from an older generation at the revision that still holds it, so a
1181
+ * document booting from the shell's frozen list comes up instead of 404. Reading the address
1182
+ * directly would see that same 404 and refuse a rebuild the button can actually carry out, so the
1183
+ * answer is taken through the same mapping and only falls back to the raw address when the mapping
1184
+ * has nothing to say.
1185
+ */
1186
+ export async function readStartupScript(clientModules, url) {
1187
+ try {
1188
+ const answer = await currentBundleAnswer(clientModules, 'GET', url)
1189
+ if (answer !== null) return { status: 200, arrayBuffer: async () => answer.body }
1190
+ } catch {}
1191
+ return fetchBundleAt(clientModules, url, 'GET')
1192
+ }
1193
+
1168
1194
  /** Ask the same question of the plugin table the plugin itself talks to. */
1169
- async function verifyStartup(clientModules, rows) {
1195
+ export async function verifyStartup(clientModules, rows) {
1170
1196
  let graph = null
1171
1197
  try {
1172
1198
  graph = clientModules.graph()
1173
1199
  } catch (error) {
1174
1200
  return { status: 'blocked', reason: 'list', detail: String(error?.message ?? error) }
1175
1201
  }
1176
- return verifyStartupRows(graph, (url) => fetchBundleAt(clientModules, url, 'GET'), rows)
1202
+ return verifyStartupRows(graph, (url) => readStartupScript(clientModules, url), rows)
1177
1203
  }
1178
1204
 
1179
1205
  /**
@@ -1287,9 +1313,12 @@ export function apply(ctx, config) {
1287
1313
  const syncBundles = () => {
1288
1314
  ensureBundleShim(ctx)
1289
1315
  }
1290
- /** Watching needs HMR; start as soon as it exists, drop the watcher when it goes away. */
1316
+ /**
1317
+ * Watching needs both the HMR service and an explicit opt-in: the switch defaults off, so no
1318
+ * root is installed and no saved file applies anything by itself.
1319
+ */
1291
1320
  const syncWatching = () => {
1292
- if (config?.plugins === 'off') return
1321
+ if (settingsOf(config ?? {}).autoReload !== true) return
1293
1322
  if (controller.available() === false) {
1294
1323
  closeAll(globalThis[WATCHER_KEY])
1295
1324
  globalThis[WATCHER_KEY] = null
@@ -1298,10 +1327,15 @@ export function apply(ctx, config) {
1298
1327
  if (globalThis[WATCHER_KEY] !== null) return
1299
1328
  globalThis[WATCHER_KEY] = controller.startWatching()
1300
1329
  }
1301
- if (config?.plugins === 'off') log('file watching is off')
1302
- else {
1330
+ // Printed on every start so a reader of the log knows which action applies a change: a person
1331
+ // pressing the button, never this plugin, and never a restart of the app.
1332
+ log('plugin reload is manual: ask the user to press the refresh button; do not restart the app')
1333
+ if (settingsOf(config ?? {}).autoReload === true) {
1303
1334
  syncWatching()
1304
- if (globalThis[WATCHER_KEY] === null) log('plugin reload is unavailable: the hmr entry is not running')
1335
+ if (globalThis[WATCHER_KEY] === null) log('auto reload is on but the hmr entry is not running; a press still works')
1336
+ else log('auto reload is on: a saved plugin change applies without a press')
1337
+ } else {
1338
+ log('auto reload is off: no plugin source is watched')
1305
1339
  }
1306
1340
  if (typeof ctx.on === 'function') {
1307
1341
  ctx.on('internal/service', (service) => {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-unlag",
3
- "version": "1.8.5",
3
+ "version": "1.9.1",
4
4
  "type": "module",
5
- "description": "One press applies plugin edits right away and brings a laggy interface back; running turns, queued messages and unsent input are untouched, and nothing restarts.",
5
+ "description": "A press applies plugin edits right away and brings a laggy interface back; automatic reloading stays off unless you turn it on, so the press is the only trigger, and nothing restarts.",
6
6
  "license": "MIT",
7
7
  "exports": {
8
8
  ".": "./index.js",