@cowwo/dsh-icon-custom 0.20.0 → 0.20.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.
Files changed (2) hide show
  1. package/client/client.js +76 -4
  2. package/package.json +1 -1
package/client/client.js CHANGED
@@ -313,13 +313,20 @@ window.__ModuleLoader__.load({
313
313
  // page should show. A cached/proxied page, a stale HTML rewrite, or a
314
314
  // browser extension can all leave the wrong link behind; this makes the
315
315
  // client authoritative about the icon it composites onto.
316
+ const previousHref = desiredIconHref();
316
317
  iconState = {
317
318
  active: known && status.active === true,
318
319
  rev: known && typeof status.rev === "string" && status.rev !== "" ? status.rev : null,
319
320
  png: known && status.png === true,
320
321
  mime: known && typeof status.mime === "string" ? status.mime : null
321
322
  };
322
- reconcileFavicon();
323
+ // A different base image must be rebuilt from the NEW artwork right now:
324
+ // the ordinary reconcile is not enough, because a compose already in flight
325
+ // would land carrying the old base and nothing would touch the icon again
326
+ // until the next 5-second watchdog tick. Same move the settings page makes
327
+ // after an upload (`refreshFaviconBase`).
328
+ if (desiredIconHref() !== previousHref) refreshFaviconBase();
329
+ else reconcileFavicon();
323
330
  }
324
331
 
325
332
  //#endregion
@@ -916,8 +923,20 @@ window.__ModuleLoader__.load({
916
923
  // saying they do not want this class of news at all.
917
924
  if (!includeSubagents && value.subagentCaused === true) continue;
918
925
  const endAt = typeof value.endAt === "number" ? value.endAt : 0;
919
- const seenAt = marks[id];
920
- if (endAt <= 0 || typeof seenAt !== "number" || endAt <= seenAt) continue;
926
+ // No watermark is the NORMAL first sight of a session: `ensureSeenBaseline`
927
+ // writes one from an effect AFTER this render, so skipping the row here
928
+ // would report 0 on the one frame the user actually sees — and the page
929
+ // would keep that 0 for its whole life (the baseline write pushed no
930
+ // re-render, so nothing recomputed it).
931
+ // Fall back to EXACTLY the value that effect writes — this browser's last
932
+ // time here (`seenBootAt`), or the Host's now when it has none — so the
933
+ // first frame and the write agree and the number cannot flicker.
934
+ // Guarded with `typeof` so a regression test may lift this function out
935
+ // of the module: an absent mirror simply means "no baseline".
936
+ const seenAt = typeof marks[id] === "number"
937
+ ? marks[id]
938
+ : (typeof seenBootAt === "number" && seenBootAt > 0 ? seenBootAt : (typeof hostNow === "function" ? hostNow() : Date.now()));
939
+ if (endAt <= 0 || endAt <= seenAt) continue;
921
940
  // Unknown reasons (a newer DSH adds one) stay hidden until the user
922
941
  // turns them on, so a vocabulary growth can never surprise the badge.
923
942
  // A reason the user turned OFF behaves the same way — with ONE
@@ -1623,6 +1642,45 @@ window.__ModuleLoader__.load({
1623
1642
  let faviconPendingSignature = "";
1624
1643
  /** Whether the last compose failed: the watchdog must retry instead of trusting the DOM. */
1625
1644
  let faviconFailed = false;
1645
+ /**
1646
+ * Short backoff after a failed compose: 300 ms → 1 s → 2 s, then the 5-second
1647
+ * watchdog owns it.
1648
+ *
1649
+ * The watchdog alone is too slow to feel like "the tab icon follows the
1650
+ * number": a base image that is merely SLOW (a cold cache, a proxy waking up, a
1651
+ * laptop resuming) left the badge missing for up to five seconds after a reload
1652
+ * — and a reload is exactly when the number is freshest. Three quick attempts
1653
+ * cover that window; a genuinely missing icon still falls through to the
1654
+ * watchdog, which keeps warning once per reason. Any success resets the ladder.
1655
+ */
1656
+ const FAVICON_RETRY_DELAYS_MS = [300, 1000, 2000];
1657
+ let faviconRetryTimer = 0;
1658
+ let faviconRetryStep = 0;
1659
+ function clearFaviconRetry() {
1660
+ if (faviconRetryTimer === 0) return;
1661
+ window.clearTimeout(faviconRetryTimer);
1662
+ faviconRetryTimer = 0;
1663
+ }
1664
+ /**
1665
+ * Queue one retry of the compose that just failed.
1666
+ *
1667
+ * At most one timer is ever pending, and the ladder stops after the last rung
1668
+ * (the 5-second watchdog is the safety net below it). The callback re-checks
1669
+ * `faviconFailed`: a compose that already succeeded, or the teardown that
1670
+ * restored the shipped icons, has cleared it — and rebuilding then would paint
1671
+ * a number back onto a page that was deliberately left alone.
1672
+ */
1673
+ function scheduleFaviconRetry() {
1674
+ if (faviconRetryTimer !== 0) return;
1675
+ if (faviconRetryStep >= FAVICON_RETRY_DELAYS_MS.length) return;
1676
+ const delay = FAVICON_RETRY_DELAYS_MS[faviconRetryStep];
1677
+ faviconRetryStep++;
1678
+ faviconRetryTimer = window.setTimeout(() => {
1679
+ faviconRetryTimer = 0;
1680
+ if (faviconFailed !== true) return; // already repaired, or torn down
1681
+ composeFavicon(effectiveCount(), badgeSize, true);
1682
+ }, delay);
1683
+ }
1626
1684
  /** Which icon the plugin considers current (filled from the status RPC). */
1627
1685
  let iconState = { active: false, rev: null, png: false, mime: null };
1628
1686
  /** The icon URL this page SHOULD be showing, per the plugin's own status. */
@@ -1929,7 +1987,17 @@ window.__ModuleLoader__.load({
1929
1987
  if (signature === faviconPendingSignature) return; // this very picture is being fetched
1930
1988
  if (force !== true && signature === faviconDrawnSignature && faviconFailed !== true) return; // already on the tab
1931
1989
  try {
1932
- Promise.resolve(applyBadgeToFavicon(count, size)).catch(warnFaviconCompose);
1990
+ Promise.resolve(applyBadgeToFavicon(count, size)).then(
1991
+ () => {
1992
+ // The compose reports its own failure through `faviconFailed` (it never
1993
+ // rejects), so this is where one becomes a SHORT retry instead of a
1994
+ // wait for the next watchdog tick. A good compose resets the ladder.
1995
+ if (faviconFailed === true) { scheduleFaviconRetry(); return; }
1996
+ faviconRetryStep = 0;
1997
+ clearFaviconRetry();
1998
+ },
1999
+ (error) => { warnFaviconCompose(error); }
2000
+ );
1933
2001
  } catch (error) {
1934
2002
  // An `async` callee never throws synchronously, so this arm is dead TODAY —
1935
2003
  // it is here so a future refactor (an early-returning sync path) cannot
@@ -3133,6 +3201,10 @@ window.__ModuleLoader__.load({
3133
3201
  if (!ensureSeenBaseline(ids)) return;
3134
3202
  pruneSeenState();
3135
3203
  saveSeenState();
3204
+ // The watermark moved under a render that was already counted without it:
3205
+ // repaint in the same breath, exactly like the Host-merge path below, so
3206
+ // nothing on screen can keep a number the new baseline already answers.
3207
+ emitUnreadPoke();
3136
3208
  }, [list]);
3137
3209
  // What the page says about the screen, refreshed on the two moments that can
3138
3210
  // change it without the session list changing: a global panel replacing the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cowwo/dsh-icon-custom",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "description": "自定义浏览器标签页图标:在设置页上传 SVG/PNG/ICO,立即生效并持久保存,自动适配安卓/iOS/平板 PWA 图标,显示未读红点与进行中黄点(工作区数字/会话行/左上角/标签页,数字可点开清单、点条目定位到侧栏),并可把未读数显示为系统应用图标角标(Windows 任务栏 / macOS Dock,含通知权限申请)。· Custom browser tab favicon: upload SVG/PNG/ICO from the settings page, applies instantly and persists, auto-sizes PWA icons for Android/iOS/tablets, shows unread red dots and running yellow marks (workspace number / session row / top-left / tab icon, both counts open a list whose rows locate the session in the sidebar), and projects the unread count onto the system app-icon badge (Windows taskbar / macOS Dock, notifications permission included).",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",