@nexus-aethra/dshell-usage 0.1.6

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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/lib/client/heatmap.js +96 -0
  3. package/lib/client/heatmap.js.map +1 -0
  4. package/lib/client/index.js +36 -0
  5. package/lib/client/index.js.map +1 -0
  6. package/lib/client/locales.js +95 -0
  7. package/lib/client/locales.js.map +1 -0
  8. package/lib/client/nav-icon.js +88 -0
  9. package/lib/client/nav-icon.js.map +1 -0
  10. package/lib/client/page.js +289 -0
  11. package/lib/client/page.js.map +1 -0
  12. package/lib/client.js +1146 -0
  13. package/lib/client.js.map +1 -0
  14. package/lib/index-store.js +250 -0
  15. package/lib/index-store.js.map +1 -0
  16. package/lib/index.js +41 -0
  17. package/lib/index.js.map +1 -0
  18. package/lib/protocol.js +9 -0
  19. package/lib/protocol.js.map +1 -0
  20. package/lib/route.js +63 -0
  21. package/lib/route.js.map +1 -0
  22. package/lib/scan.js +187 -0
  23. package/lib/scan.js.map +1 -0
  24. package/lib/types/client/heatmap.d.ts +69 -0
  25. package/lib/types/client/heatmap.d.ts.map +1 -0
  26. package/lib/types/client/index.d.ts +14 -0
  27. package/lib/types/client/index.d.ts.map +1 -0
  28. package/lib/types/client/locales.d.ts +62 -0
  29. package/lib/types/client/locales.d.ts.map +1 -0
  30. package/lib/types/client/nav-icon.d.ts +28 -0
  31. package/lib/types/client/nav-icon.d.ts.map +1 -0
  32. package/lib/types/client/page.d.ts +18 -0
  33. package/lib/types/client/page.d.ts.map +1 -0
  34. package/lib/types/index-store.d.ts +87 -0
  35. package/lib/types/index-store.d.ts.map +1 -0
  36. package/lib/types/index.d.ts +19 -0
  37. package/lib/types/index.d.ts.map +1 -0
  38. package/lib/types/protocol.d.ts +115 -0
  39. package/lib/types/protocol.d.ts.map +1 -0
  40. package/lib/types/route.d.ts +19 -0
  41. package/lib/types/route.d.ts.map +1 -0
  42. package/lib/types/scan.d.ts +94 -0
  43. package/lib/types/scan.d.ts.map +1 -0
  44. package/package.json +73 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nexus Aethra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The heatmap's layout: a fixed calendar rectangle of day cells.
3
+ *
4
+ * The grid is filled the way a page is read — top to bottom, then the next
5
+ * column — so the newest day always sits in the BOTTOM-RIGHT corner. That is
6
+ * the shape a reader expects from a "last 26 weeks" block, and it is why the
7
+ * cells are not weekday-aligned: aligning columns to weeks would leave the most
8
+ * recent day wherever its weekday happened to fall, which reads as a hole in the
9
+ * corner rather than as "this is now".
10
+ *
11
+ * Pure and separate from the page for the same reason the fold is separate from
12
+ * the store: the interesting part is date arithmetic (which day a cell holds,
13
+ * which cells the history actually covers, how a token count becomes a shade),
14
+ * and that is testable without a browser while a chart is not.
15
+ *
16
+ * Days are the host's own `YYYY-MM-DD` local days, and the anchor is the host's
17
+ * today — the index bucketed by LOCAL day when it scanned, so the grid reads
18
+ * them as local dates too, or a bucket would slide across a cell boundary near
19
+ * midnight.
20
+ */
21
+ /** How many shades a non-empty day can take, above "nothing recorded". */
22
+ export const HEAT_LEVELS = 4;
23
+ /** The shape of the grid: columns of a week, however many weeks fit. */
24
+ export const HEAT_ROWS = 7;
25
+ /** The local date of a `YYYY-MM-DD` label, at local midnight. */
26
+ function dateOf(day) {
27
+ return new Date(`${day}T00:00:00`);
28
+ }
29
+ /** The `YYYY-MM-DD` label of a local date, the way the index spells days. */
30
+ function labelOf(date) {
31
+ const pad = (value) => String(value).padStart(2, '0');
32
+ return `${String(date.getFullYear())}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
33
+ }
34
+ /**
35
+ * Lay the recorded days out in a full rectangle of `weeks` columns.
36
+ *
37
+ * The newest cell is the anchor: the last column's bottom cell holds the anchor
38
+ * day, and every earlier cell steps back one day, so the grid reads oldest at
39
+ * the top-left and newest at the bottom-right. The rectangle is always full —
40
+ * cells the history does not reach are present but empty — because a grid that
41
+ * grew and shrank with the data would move the corner as the history changed.
42
+ *
43
+ * @param days - one entry per recorded day, any order.
44
+ * @param weeks - how many columns to draw.
45
+ * @param anchor - the host's today, `YYYY-MM-DD`; the bottom-right cell.
46
+ * @returns the columns, left to right.
47
+ */
48
+ export function heatmapColumns(days, weeks, anchor) {
49
+ const byDay = new Map(days.map(day => [day.day, day]));
50
+ const max = Math.max(1, ...days.map(day => day.total));
51
+ const end = dateOf(anchor);
52
+ const columns = [];
53
+ for (let column = 0; column < weeks; column += 1) {
54
+ const cells = [];
55
+ for (let row = 0; row < HEAT_ROWS; row += 1) {
56
+ // Counted back from the anchor, so the loop's last cell is the anchor
57
+ // itself however many columns the grid has.
58
+ const back = (weeks - 1 - column) * HEAT_ROWS + (HEAT_ROWS - 1 - row);
59
+ const date = new Date(end);
60
+ date.setDate(date.getDate() - back);
61
+ const label = labelOf(date);
62
+ if (date.getTime() > end.getTime()) {
63
+ cells.push(undefined);
64
+ continue;
65
+ }
66
+ const recorded = byDay.get(label);
67
+ cells.push(recorded === undefined
68
+ ? undefined
69
+ : {
70
+ ...recorded,
71
+ level: recorded.total === 0
72
+ ? 0
73
+ : Math.min(HEAT_LEVELS, 1 + Math.floor((recorded.total / max) * (HEAT_LEVELS - 1))),
74
+ });
75
+ }
76
+ columns.push(cells);
77
+ }
78
+ return columns;
79
+ }
80
+ /**
81
+ * Whether the history reaches further back than the grid draws.
82
+ *
83
+ * @param days - the same days handed to {@link heatmapColumns}.
84
+ * @param weeks - the same width.
85
+ * @param anchor - the same anchor.
86
+ * @returns whether any recorded day fell off the left edge.
87
+ */
88
+ export function heatmapTruncated(days, weeks, anchor) {
89
+ if (days.length === 0)
90
+ return false;
91
+ const oldest = days.reduce((earliest, day) => (day.day < earliest ? day.day : earliest), days[0].day);
92
+ const start = dateOf(anchor);
93
+ start.setDate(start.getDate() - (weeks * HEAT_ROWS - 1));
94
+ return dateOf(oldest).getTime() < start.getTime();
95
+ }
96
+ //# sourceMappingURL=heatmap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heatmap.js","sourceRoot":"","sources":["../../src/client/heatmap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The usage page's client face: the locale dictionaries and the Settings
3
+ * navigation row that mounts the page.
4
+ *
5
+ * `settings.section` is the seat dsh's own Plugins page uses, so this page is a
6
+ * peer of it rather than a tab inside it — the charts want the full settings
7
+ * column, and a reader looking for "what did this cost" looks in the
8
+ * navigation, not under a plugin list.
9
+ */
10
+ import { en, zh } from './locales.js';
11
+ import { UsageSection } from './page.js';
12
+ import { watchUsageNavIcon } from './nav-icon.js';
13
+ /** The locale namespace, matching the key `locales.ts` declares. */
14
+ const NS = 'dshellUsage';
15
+ export const name = '@nexus-aethra/dshell-usage/client';
16
+ export const inject = ['slots', 'locale'];
17
+ export function apply(ctx) {
18
+ const t = ctx.locale.bind(NS);
19
+ // Registered through an effect so a composition that unloads this plugin
20
+ // takes its copy with it.
21
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dshell-usage: dictionaries');
22
+ // Ordered after the Plugins page (15) so the navigation reads
23
+ // sessions → plugins → usage, and named with its own id so it is added beside
24
+ // the shipped entries rather than replacing one.
25
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
26
+ name: 'settings.section',
27
+ id: 'usage',
28
+ order: 40,
29
+ label: () => t('nav'),
30
+ locale: NS,
31
+ }, UsageSection));
32
+ // The row's own icon, because this seat carries none: dsh draws a generic
33
+ // gear for any section id it does not ship (see nav-icon.ts).
34
+ ctx.effect(() => watchUsageNavIcon(() => t('nav')), 'dshell-usage: navigation icon');
35
+ }
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEjD,oEAAoE;AACpE,MAAM,EAAE,GAAG,aAAa,CAAA;AAExB,MAAM,CAAC,MAAM,IAAI,GAAG,mCAAmC,CAAA;AAEvD,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAU,CAAA;AAElD,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC7B,yEAAyE;IACzE,0BAA0B;IAC1B,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,4BAA4B,CAAC,CAAA;IACnF,8DAA8D;IAC9D,8EAA8E;IAC9E,iDAAiD;IACjD,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC5D,IAAI,EAAE,kBAAkB;QACxB,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,EAAE;QACT,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;QACrB,MAAM,EAAE,EAAE;KACX,EAAE,YAAY,CAAC,CAAC,CAAA;IACjB,0EAA0E;IAC1E,8DAA8D;IAC9D,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,+BAA+B,CAAC,CAAA;AACtF,CAAC"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * `dshellUsage` namespace dictionaries, and the namespace's declaration.
3
+ *
4
+ * The namespace merge lives with its key set so that any module naming
5
+ * `TranslateNS<'dshellUsage'>` or `PropsLocale<'dshellUsage'>` needs only this
6
+ * file. Keys are lowercase and dotted: `nav.*` (the settings navigation row),
7
+ * `range.*` (the window selector), `chart.*` (the two charts' captions),
8
+ * `table.*` (the route breakdown), and `state.*` (empty, busy, freshness, and
9
+ * the load failure).
10
+ */
11
+ /** Simplified Chinese dictionary and key-set source of truth. */
12
+ export const zh = {
13
+ // The Settings navigation row this page owns.
14
+ 'nav': '用量',
15
+ 'title': '模型 token 消耗',
16
+ 'subtitle': '按模型汇总每个会话上报的用量。索引建在 dshell 自己的数据目录里,首次打开会扫一遍全部历史会话,之后只补新增的部分。',
17
+ // The window selector. `{days}` is the day count.
18
+ 'range.7': '近 7 天',
19
+ 'range.30': '近 30 天',
20
+ 'range.all': '全部',
21
+ 'range.label': '时间范围',
22
+ 'rebuild': '重新聚合',
23
+ 'rebuild.busy': '正在聚合…',
24
+ 'chart.heatmap': '每日热力(近 26 周内)',
25
+ 'chart.heatmapDay': '{day} · {total} tok · {turns} 次调用',
26
+ 'chart.heatmapLess': '少',
27
+ 'chart.heatmapMore': '多',
28
+ 'chart.heatmapPeak': '最忙一天 {total} tok',
29
+ 'chart.heatmapTruncated': '只画最近 26 周',
30
+ 'chart.curve': '每日消耗(按模型)',
31
+ 'chart.pie': '模型占比',
32
+ 'summary.total': '合计',
33
+ 'summary.uncached': '未缓存输入',
34
+ 'summary.output': '输出',
35
+ 'summary.cacheRead': '缓存读',
36
+ 'summary.turns': '调用',
37
+ 'summary.hitRate': '缓存命中率',
38
+ 'table.title': '按模型明细',
39
+ 'table.model': '模型',
40
+ 'table.input': '输入',
41
+ 'table.output': '输出',
42
+ 'table.cacheRead': '缓存读',
43
+ 'table.cacheWrite': '缓存写',
44
+ 'table.turns': '调用',
45
+ 'table.total': '合计',
46
+ 'state.empty.title': '还没有可统计的用量',
47
+ 'state.empty.body': '索引里没有任何一条带用量上报的模型调用。跑一次 agent 会话后再点「重新聚合」。',
48
+ 'state.freshness': '索引更新于 {day}',
49
+ 'state.skipped': '有 {count} 个会话的日志读不了,未计入(详情见悬停提示)。',
50
+ 'state.scanned': '本次扫描列出 {sessions} 个会话,读取了 {read} 个,命中 {turns} 次调用。',
51
+ 'state.error': '读取用量失败:{reason}',
52
+ 'state.loading': '正在读取索引…',
53
+ };
54
+ /** English dictionary, keyed by the same set. */
55
+ export const en = {
56
+ 'nav': 'Usage',
57
+ 'title': 'Model token usage',
58
+ 'subtitle': 'Per-model totals folded from the usage every session reports. The index lives in dshell\'s own data directory: the first visit scans every session once, and later visits add only what is new.',
59
+ 'range.7': 'Last 7 days',
60
+ 'range.30': 'Last 30 days',
61
+ 'range.all': 'All time',
62
+ 'range.label': 'Range',
63
+ 'rebuild': 'Rebuild',
64
+ 'rebuild.busy': 'Rebuilding…',
65
+ 'chart.heatmap': 'Daily heatmap (last 26 weeks)',
66
+ 'chart.heatmapDay': '{day} · {total} tok · {turns} calls',
67
+ 'chart.heatmapLess': 'Less',
68
+ 'chart.heatmapMore': 'More',
69
+ 'chart.heatmapPeak': 'Busiest day {total} tok',
70
+ 'chart.heatmapTruncated': 'Showing the last 26 weeks',
71
+ 'chart.curve': 'Daily usage by model',
72
+ 'chart.pie': 'Share by model',
73
+ 'summary.total': 'Total',
74
+ 'summary.uncached': 'Uncached input',
75
+ 'summary.output': 'Output',
76
+ 'summary.cacheRead': 'Cache read',
77
+ 'summary.turns': 'Calls',
78
+ 'summary.hitRate': 'Cache hit rate',
79
+ 'table.title': 'By model',
80
+ 'table.model': 'Model',
81
+ 'table.input': 'Input',
82
+ 'table.output': 'Output',
83
+ 'table.cacheRead': 'Cache read',
84
+ 'table.cacheWrite': 'Cache write',
85
+ 'table.turns': 'Calls',
86
+ 'table.total': 'Total',
87
+ 'state.empty.title': 'No usage to count yet',
88
+ 'state.empty.body': 'The index holds no model call that reported usage. Run an agent session, then press Rebuild.',
89
+ 'state.freshness': 'Index updated {day}',
90
+ 'state.skipped': '{count} session logs could not be read and are not counted (hover for the reason).',
91
+ 'state.scanned': 'This scan listed {sessions} sessions, read {read}, and counted {turns} calls.',
92
+ 'state.error': 'Reading usage failed: {reason}',
93
+ 'state.loading': 'Reading the index…',
94
+ };
95
+ //# sourceMappingURL=locales.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locales.js","sourceRoot":"","sources":["../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,iEAAiE;AACjE,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,8CAA8C;IAC9C,KAAK,EAAE,IAAI;IAEX,OAAO,EAAE,aAAa;IACtB,UAAU,EAAE,+DAA+D;IAE3E,kDAAkD;IAClD,SAAS,EAAE,OAAO;IAClB,UAAU,EAAE,QAAQ;IACpB,WAAW,EAAE,IAAI;IACjB,aAAa,EAAE,MAAM;IAErB,SAAS,EAAE,MAAM;IACjB,cAAc,EAAE,OAAO;IAEvB,eAAe,EAAE,eAAe;IAChC,kBAAkB,EAAE,mCAAmC;IACvD,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,kBAAkB;IACvC,wBAAwB,EAAE,WAAW;IACrC,aAAa,EAAE,WAAW;IAC1B,WAAW,EAAE,MAAM;IAEnB,eAAe,EAAE,IAAI;IACrB,kBAAkB,EAAE,OAAO;IAC3B,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,KAAK;IAC1B,eAAe,EAAE,IAAI;IACrB,iBAAiB,EAAE,OAAO;IAE1B,aAAa,EAAE,OAAO;IACtB,aAAa,EAAE,IAAI;IACnB,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,IAAI;IACpB,iBAAiB,EAAE,KAAK;IACxB,kBAAkB,EAAE,KAAK;IACzB,aAAa,EAAE,IAAI;IACnB,aAAa,EAAE,IAAI;IAEnB,mBAAmB,EAAE,WAAW;IAChC,kBAAkB,EAAE,4CAA4C;IAChE,iBAAiB,EAAE,aAAa;IAChC,eAAe,EAAE,mCAAmC;IACpD,eAAe,EAAE,oDAAoD;IACrE,aAAa,EAAE,iBAAiB;IAChC,eAAe,EAAE,SAAS;CAClB,CAAA;AAKV,iDAAiD;AACjD,MAAM,CAAC,MAAM,EAAE,GAAmC;IAChD,KAAK,EAAE,OAAO;IAEd,OAAO,EAAE,mBAAmB;IAC5B,UAAU,EAAE,iMAAiM;IAE7M,SAAS,EAAE,aAAa;IACxB,UAAU,EAAE,cAAc;IAC1B,WAAW,EAAE,UAAU;IACvB,aAAa,EAAE,OAAO;IAEtB,SAAS,EAAE,SAAS;IACpB,cAAc,EAAE,aAAa;IAE7B,eAAe,EAAE,+BAA+B;IAChD,kBAAkB,EAAE,qCAAqC;IACzD,mBAAmB,EAAE,MAAM;IAC3B,mBAAmB,EAAE,MAAM;IAC3B,mBAAmB,EAAE,yBAAyB;IAC9C,wBAAwB,EAAE,2BAA2B;IACrD,aAAa,EAAE,sBAAsB;IACrC,WAAW,EAAE,gBAAgB;IAE7B,eAAe,EAAE,OAAO;IACxB,kBAAkB,EAAE,gBAAgB;IACpC,gBAAgB,EAAE,QAAQ;IAC1B,mBAAmB,EAAE,YAAY;IACjC,eAAe,EAAE,OAAO;IACxB,iBAAiB,EAAE,gBAAgB;IAEnC,aAAa,EAAE,UAAU;IACzB,aAAa,EAAE,OAAO;IACtB,aAAa,EAAE,OAAO;IACtB,cAAc,EAAE,QAAQ;IACxB,iBAAiB,EAAE,YAAY;IAC/B,kBAAkB,EAAE,aAAa;IACjC,aAAa,EAAE,OAAO;IACtB,aAAa,EAAE,OAAO;IAEtB,mBAAmB,EAAE,uBAAuB;IAC5C,kBAAkB,EAAE,8FAA8F;IAClH,iBAAiB,EAAE,qBAAqB;IACxC,eAAe,EAAE,oFAAoF;IACrG,eAAe,EAAE,+EAA+E;IAChG,aAAa,EAAE,gCAAgC;IAC/C,eAAe,EAAE,oBAAoB;CACtC,CAAA"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The usage entry's own icon in the settings navigation.
3
+ *
4
+ * dsh's settings shell picks a nav icon from a map of the section ids IT ships
5
+ * (`account`, `models`, `plugins`, …) and draws a generic gear for anything
6
+ * else, and rc.2's `settings.section` seat carries no icon of its own — so a
7
+ * contributed section cannot ask for one and ours read as "some settings".
8
+ *
9
+ * Hence the one DOM patch in this package: find our own row by the label we
10
+ * registered, and put a gauge where the gear was. It is the same technique
11
+ * dshell's sidebar section uses (and the artwork is copied verbatim from
12
+ * `@deepseek-ai/dsh-client-ui-primitives`, medium weight, 16 viewBox, 1.3px
13
+ * stroke) because a surface mounted into a node dsh owns cannot render React
14
+ * components into it.
15
+ *
16
+ * The row is re-created whenever the dialog opens, so a MutationObserver
17
+ * watches for it; the patch is idempotent (it re-checks the label and a marker
18
+ * attribute) and only touches the svg node React will not write to again.
19
+ * Delete this file the day `settings.section` accepts an icon.
20
+ */
21
+ /**
22
+ * The gauge artwork, verbatim from the primitives package
23
+ * (`IconGaugeOutlineMedium`): the same three paths, in the same order.
24
+ */
25
+ const GAUGE_PATHS = [
26
+ { d: 'M3.4041 13.096C2.49514 12.187 1.87614 11.0288 1.62537 9.76798C1.37459 8.50716 1.50331 7.20028 1.99525 6.01261C2.48719 4.82494 3.32025 3.80981 4.3891 3.09557C5.45795 2.38134 6.71458 2.00008 8.0001 2C9.28563 2.00008 10.5423 2.38134 11.6111 3.09557C12.68 3.80981 13.513 4.82494 14.005 6.01261C14.4969 7.20028 14.6256 8.50716 14.3748 9.76798C14.1241 11.0288 13.5051 12.187 12.5961 13.096', stroke: true },
27
+ { d: 'M8 8.49994L11.6114 4.88855', stroke: true },
28
+ { d: 'M8 9.75C8.69036 9.75 9.25 9.19036 9.25 8.5C9.25 7.80964 8.69036 7.25 8 7.25C7.30964 7.25 6.75 7.80964 6.75 8.5C6.75 9.19036 7.30964 9.75 8 9.75Z', stroke: false },
29
+ ];
30
+ const SVG_NS = 'http://www.w3.org/2000/svg';
31
+ /** The marker that says a row already carries our artwork. */
32
+ const MARKER = 'data-dshell-usage-icon';
33
+ /** One gauge, sized and coloured like the icons beside it. */
34
+ function gauge(size, className) {
35
+ const svg = document.createElementNS(SVG_NS, 'svg');
36
+ svg.setAttribute('width', String(size));
37
+ svg.setAttribute('height', String(size));
38
+ svg.setAttribute('viewBox', '0 0 16 16');
39
+ svg.setAttribute('fill', 'none');
40
+ svg.setAttribute('stroke-width', '1.3');
41
+ svg.setAttribute('aria-hidden', 'true');
42
+ if (className !== null)
43
+ svg.setAttribute('class', className);
44
+ for (const spec of GAUGE_PATHS) {
45
+ const path = document.createElementNS(SVG_NS, 'path');
46
+ path.setAttribute('d', spec.d);
47
+ if (spec.stroke)
48
+ path.setAttribute('stroke', 'currentColor');
49
+ else
50
+ path.setAttribute('fill', 'currentColor');
51
+ svg.append(path);
52
+ }
53
+ return svg;
54
+ }
55
+ /**
56
+ * Keep the settings navigation's usage row drawn with the gauge.
57
+ *
58
+ * @param label - the section label as the shell renders it, in the live locale.
59
+ * @returns the disposer that stops watching.
60
+ */
61
+ export function watchUsageNavIcon(label) {
62
+ if (typeof document === 'undefined')
63
+ return () => { };
64
+ const patch = () => {
65
+ for (const dialog of document.querySelectorAll('[role="dialog"]')) {
66
+ for (const row of dialog.querySelectorAll('nav button')) {
67
+ const text = row.textContent?.trim() ?? '';
68
+ if (text !== label())
69
+ continue;
70
+ const svg = row.querySelector('svg');
71
+ if (svg === null || svg.getAttribute(MARKER) !== null)
72
+ continue;
73
+ // React owns both children by position and neither is re-rendered while
74
+ // the dialog lives, so the replacement is what the reader sees; the
75
+ // pristine node would only come back with a full re-mount, which the
76
+ // observer catches.
77
+ const replacement = gauge(Number(svg.getAttribute('width') ?? 16) || 16, svg.getAttribute('class'));
78
+ replacement.setAttribute(MARKER, '');
79
+ svg.replaceWith(replacement);
80
+ }
81
+ }
82
+ };
83
+ patch();
84
+ const observer = new MutationObserver(() => { patch(); });
85
+ observer.observe(document.body, { childList: true, subtree: true });
86
+ return () => { observer.disconnect(); };
87
+ }
88
+ //# sourceMappingURL=nav-icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav-icon.js","sourceRoot":"","sources":["../../src/client/nav-icon.ts"],"names":[],"mappings":"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"}