@cowwo/dsh-icon-custom 0.8.0 → 0.8.2

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/client/client.js CHANGED
@@ -300,6 +300,32 @@ window.__ModuleLoader__.load({
300
300
  }
301
301
  return changed;
302
302
  }
303
+ /**
304
+ * The session the main view is showing.
305
+ *
306
+ * DSH 0.2.x carries NO `current` field in the session-list snapshot — it is
307
+ * written with exactly `ids` / `byId` / `phase` / `projectionsBySession`. The
308
+ * official signal is the row's `retainedBy.mainView` retention count, the same
309
+ * one ui-layout's DocumentTitle, ui-cordis, ui-open-in-app and ui-session read.
310
+ * Reading `list.current` here made `noteSeen()` a no-op, so the seen watermark
311
+ * never advanced and an unread dot could never be cleared by opening the
312
+ * session. Runtimes that do expose `current` are still honoured first.
313
+ * @param list - the session list snapshot (may be absent).
314
+ * @returns the id being viewed, or undefined.
315
+ */
316
+ function currentSessionId(list) {
317
+ if (list === null || typeof list !== "object") return undefined;
318
+ if (typeof list.current === "string" && list.current !== "") return list.current;
319
+ const byId = list.byId;
320
+ if (byId === null || typeof byId !== "object") return undefined;
321
+ for (const session of Object.values(byId)) {
322
+ if (session === null || typeof session !== "object") continue;
323
+ const retainedBy = session.retainedBy;
324
+ const count = retainedBy !== null && typeof retainedBy === "object" ? retainedBy.mainView : undefined;
325
+ if (typeof count === "number" && count > 0) return session.id;
326
+ }
327
+ return undefined;
328
+ }
303
329
  /**
304
330
  * How many sessions deserve the badge right now: endings whose reason is
305
331
  * enabled and that this browser has not seen since, plus sessions waiting
@@ -318,7 +344,7 @@ window.__ModuleLoader__.load({
318
344
  /** { id, title, waiting, kind, at } — newest first, "in wait" ahead of "ended". */
319
345
  const hits = [];
320
346
  const ids = new Set();
321
- const current = list !== null && typeof list === "object" ? list.current : undefined;
347
+ const current = currentSessionId(list);
322
348
  const byId = list !== null && typeof list === "object" && list.byId !== null && typeof list.byId === "object" ? list.byId : {};
323
349
  const titleOf = (id, entry) => (entry !== null && typeof entry === "object" && typeof entry.displayTitle === "string" && entry.displayTitle !== "" ? entry.displayTitle : id);
324
350
  // Which workspace the session lives in. The sidebar's own workspace rows are
@@ -875,11 +901,28 @@ window.__ModuleLoader__.load({
875
901
  * count simply drops back to 0.
876
902
  */
877
903
  function BadgeSource(props) {
878
- // Both hooks are official root-scope standard props: the session list
879
- // (which carries every session's `lastTurnEnd` projection value) and the
880
- // pending-interaction registry the sidebar row marker already uses.
904
+ // The hooks below are official root-scope standard props: the session list
905
+ // (which carries every session's `lastTurnEnd` projection value), the
906
+ // session-status map the sidebar row marker already uses, and the workspace
907
+ // snapshot that names each row.
881
908
  const list = typeof props.useSessions === "function" ? props.useSessions((state) => state) : undefined;
882
- const pending = typeof props.useSessionPendingInteraction === "function" ? props.useSessionPendingInteraction((map) => map) : undefined;
909
+ // 0.2.x has no `useSessionPendingInteraction` — the string occurs nowhere in
910
+ // the runtime, so this half used to contribute nothing at all. The official
911
+ // replacement is the session-status map, whose rows carry `pendingInteraction`
912
+ // (alongside `completionUnread`, which the shipped sidebar reads too). Reshape
913
+ // it into the `id → { kind }` map `collectUnread` expects.
914
+ const status = typeof props.useSessionStatus === "function" ? props.useSessionStatus((map) => map) : undefined;
915
+ const pending = React.useMemo(() => {
916
+ const out = new Map();
917
+ if (status === null || status === undefined || typeof status.forEach !== "function") return out;
918
+ status.forEach((value, id) => {
919
+ const interaction = value !== null && typeof value === "object" ? value.pendingInteraction : undefined;
920
+ if (interaction === null || interaction === undefined) return;
921
+ const kind = typeof interaction === "object" && typeof interaction.kind === "string" ? interaction.kind : "";
922
+ out.set(id, { kind });
923
+ });
924
+ return out;
925
+ }, [status]);
883
926
  const workspaces = typeof props.useWorkspaces === "function" ? props.useWorkspaces((state) => state) : undefined;
884
927
  const [config, setConfig] = React.useState(unreadConfig);
885
928
  React.useEffect(() => subscribeUnreadConfig(setConfig), []);
@@ -891,7 +934,7 @@ window.__ModuleLoader__.load({
891
934
  saveSeenState();
892
935
  }, [list]);
893
936
  // Looking at a session marks it seen; the count also excludes it outright.
894
- const currentId = list !== null && typeof list === "object" ? list.current : undefined;
937
+ const currentId = currentSessionId(list);
895
938
  React.useEffect(() => {
896
939
  if (noteSeen(currentId)) saveSeenState();
897
940
  }, [currentId]);
@@ -1447,7 +1490,10 @@ window.__ModuleLoader__.load({
1447
1490
  }
1448
1491
  //#endregion
1449
1492
 
1450
- const inject = ["slots", "locale", "connection", "sessions"];
1493
+ // `sessions` is gone from this list: its only use was the removed
1494
+ // `ctx.sessions.open(id)`. Navigation now goes through an optional
1495
+ // `ctx.get("uiWorkspace")` lookup instead of a hard dependency.
1496
+ const inject = ["slots", "locale", "connection"];
1451
1497
  function apply(ctx) {
1452
1498
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-icon-custom: dictionaries");
1453
1499
  const t = ctx.locale.bind(NS);
@@ -1495,8 +1541,15 @@ window.__ModuleLoader__.load({
1495
1541
  // Additive seats for "where exactly?" — a foot entry beside the shipped
1496
1542
  // Settings/Cordis buttons, and the frame-wide overlay for its list. A fresh
1497
1543
  // id is added beside the others; nothing shipped is shadowed.
1544
+ //
1545
+ // 0.2.x has no `ctx.sessions.open(id)` (the ClientSessions service exposes
1546
+ // retain/using/retainInfo/refreshProjections/search/fork/scope/binding — no
1547
+ // navigation). Official code jumps to a session with
1548
+ // `ctx.uiWorkspace.openSession(id)` (ui-chat does exactly this), so the entry
1549
+ // is looked up optionally: navigation is best effort and must never make the
1550
+ // favicon half depend on the workspace UI being mounted.
1498
1551
  const openSession = (id) => {
1499
- try { ctx.sessions.open(id); } catch { /* navigation is best effort */ }
1552
+ try { ctx.get("uiWorkspace")?.openSession(id); } catch { /* navigation is best effort */ }
1500
1553
  };
1501
1554
  ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register(
1502
1555
  { name: "sidebar.footer.action", id: "icon-custom-unread", order: 20, label: () => t("unreadPanelShort") },
@@ -1,4 +1,13 @@
1
1
  /* Hand-written typert host manifest for dsh-icon-custom (strict face).
2
+ *
3
+ * DSH 0.2.x contract: every strict codec carries a `create()` FACTORY returning
4
+ * its zod schema — the gateway calls `codec.create().parse(value)` and the
5
+ * registry caches the result. The 0.1.x bare `schema:` field is gone; keeping
6
+ * it makes `validateTypertManifest` throw `has no create() factory`, so the
7
+ * host endpoints below never register and the settings page silently stops
8
+ * working (0.8.0 shipped this way — see the 0.8.1 fix). Do not reintroduce
9
+ * `schema:` here: run the compatibility check before publishing.
10
+ *
2
11
  * The request/response shapes are unchanged from 0.6.x. From 0.7.0 the host
3
12
  * additionally derives a canonical PNG set (see lib/index.js); `status.mime` /
4
13
  * `status.ext` still reflect the *uploaded* format, while the PWA/logo path
@@ -69,7 +78,7 @@ export const TYPERT = {
69
78
  result: {
70
79
  mode: 'strict',
71
80
  typeSymbol: 'dsh-icon-custom#iconCustom/getStatus:result',
72
- schema: statusSchema
81
+ create: () => statusSchema
73
82
  },
74
83
  sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
75
84
  },
@@ -87,14 +96,14 @@ export const TYPERT = {
87
96
  codec: {
88
97
  mode: 'strict',
89
98
  typeSymbol: 'dsh-icon-custom#iconCustom/setIcon:request',
90
- schema: setIconRequestSchema
99
+ create: () => setIconRequestSchema
91
100
  }
92
101
  }
93
102
  ],
94
103
  result: {
95
104
  mode: 'strict',
96
105
  typeSymbol: 'dsh-icon-custom#iconCustom/setIcon:result',
97
- schema: setIconResultSchema
106
+ create: () => setIconResultSchema
98
107
  },
99
108
  sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
100
109
  },
@@ -112,14 +121,14 @@ export const TYPERT = {
112
121
  codec: {
113
122
  mode: 'strict',
114
123
  typeSymbol: 'dsh-icon-custom#iconCustom/setPwa:request',
115
- schema: setPwaRequestSchema
124
+ create: () => setPwaRequestSchema
116
125
  }
117
126
  }
118
127
  ],
119
128
  result: {
120
129
  mode: 'strict',
121
130
  typeSymbol: 'dsh-icon-custom#iconCustom/setPwa:result',
122
- schema: statusSchema
131
+ create: () => statusSchema
123
132
  },
124
133
  sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
125
134
  },
@@ -137,14 +146,14 @@ export const TYPERT = {
137
146
  codec: {
138
147
  mode: 'strict',
139
148
  typeSymbol: 'dsh-icon-custom#iconCustom/setLogo:request',
140
- schema: setLogoRequestSchema
149
+ create: () => setLogoRequestSchema
141
150
  }
142
151
  }
143
152
  ],
144
153
  result: {
145
154
  mode: 'strict',
146
155
  typeSymbol: 'dsh-icon-custom#iconCustom/setLogo:result',
147
- schema: statusSchema
156
+ create: () => statusSchema
148
157
  },
149
158
  sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
150
159
  },
@@ -158,7 +167,7 @@ export const TYPERT = {
158
167
  result: {
159
168
  mode: 'strict',
160
169
  typeSymbol: 'dsh-icon-custom#iconCustom/resetIcon:result',
161
- schema: statusSchema
170
+ create: () => statusSchema
162
171
  },
163
172
  sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
164
173
  },
@@ -172,7 +181,7 @@ export const TYPERT = {
172
181
  result: {
173
182
  mode: 'strict',
174
183
  typeSymbol: 'dsh-icon-custom#iconCustom/getUnreadRule:result',
175
- schema: unreadRuleSchema
184
+ create: () => unreadRuleSchema
176
185
  },
177
186
  sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
178
187
  },
@@ -190,14 +199,14 @@ export const TYPERT = {
190
199
  codec: {
191
200
  mode: 'strict',
192
201
  typeSymbol: 'dsh-icon-custom#iconCustom/setUnreadRule:request',
193
- schema: setUnreadRuleRequestSchema
202
+ create: () => setUnreadRuleRequestSchema
194
203
  }
195
204
  }
196
205
  ],
197
206
  result: {
198
207
  mode: 'strict',
199
208
  typeSymbol: 'dsh-icon-custom#iconCustom/setUnreadRule:result',
200
- schema: unreadRuleSchema
209
+ create: () => unreadRuleSchema
201
210
  },
202
211
  sourceLocation: { file: 'lib/index.js', line: 1, column: 1 }
203
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cowwo/dsh-icon-custom",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "自定义浏览器标签页图标:在设置页上传 SVG/PNG/ICO,立即生效并持久保存,自动适配安卓/iOS/平板 PWA 图标。· Custom browser tab favicon: upload SVG/PNG/ICO from the settings page, applies instantly and persists, auto-sizes PWA icons for Android/iOS/tablets.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",