gloomberb 0.18.2 → 0.18.3

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 (157) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +106 -5
  3. package/src/api-client/data.ts +15 -0
  4. package/src/api-client/index.ts +2 -0
  5. package/src/api-client/request.ts +5 -3
  6. package/src/api-client/telemetry.ts +17 -0
  7. package/src/app/global-shortcuts.ts +1 -16
  8. package/src/app/runtime/update.ts +28 -8
  9. package/src/architecture/pane-tabs-allowlist.json +65 -0
  10. package/src/cli/commands/market.ts +73 -33
  11. package/src/cli/commands/ticker.ts +82 -25
  12. package/src/cli/help.ts +1 -1
  13. package/src/cli/index.ts +8 -0
  14. package/src/cli/issuer-membership.ts +103 -0
  15. package/src/cli/listing-arg.ts +15 -2
  16. package/src/cli/name-entries.ts +51 -12
  17. package/src/cli/pane-functions/capabilities.ts +2 -0
  18. package/src/cli/pane-functions/catalog.ts +16 -0
  19. package/src/cli/pane-functions/dom.ts +2 -1
  20. package/src/cli/pane-functions/headless.ts +25 -6
  21. package/src/cli/pane-functions/index.ts +64 -5
  22. package/src/cli/pane-functions/resolver.ts +19 -5
  23. package/src/cli/report-tables.ts +136 -18
  24. package/src/cli/result.ts +12 -4
  25. package/src/cli/symbol-list.ts +47 -11
  26. package/src/components/chart/composite/composite-chart.tsx +14 -5
  27. package/src/components/chart/composite/drawing-store.ts +26 -6
  28. package/src/components/chart/composite/interactions.ts +2 -1
  29. package/src/components/chart/composite/panel-surface.tsx +11 -0
  30. package/src/components/chart/composite/types.ts +6 -0
  31. package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
  32. package/src/components/command-bar/routes/root/command-items.ts +32 -6
  33. package/src/components/data-table/view.tsx +23 -3
  34. package/src/components/index.ts +1 -0
  35. package/src/components/layout/detached-pane-shell.tsx +1 -0
  36. package/src/components/layout/floating-pane.tsx +1 -0
  37. package/src/components/layout/header.tsx +228 -55
  38. package/src/components/layout/pane/header.tsx +10 -3
  39. package/src/components/layout/pane/index.tsx +1 -0
  40. package/src/components/layout/pane/pane-tabs.tsx +9 -1
  41. package/src/components/ui/stat-grid.tsx +28 -3
  42. package/src/components/ui/status.tsx +3 -1
  43. package/src/core/app-services.ts +4 -0
  44. package/src/i18n/es.ts +22 -0
  45. package/src/i18n/ja.ts +22 -0
  46. package/src/i18n/ko.ts +22 -0
  47. package/src/i18n/zh-cn.ts +22 -0
  48. package/src/i18n/zh-tw.ts +22 -0
  49. package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
  50. package/src/plugins/builtin/alerts/events.ts +1 -0
  51. package/src/plugins/builtin/alerts/research-rules.ts +1 -0
  52. package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
  53. package/src/plugins/builtin/attention/pane.tsx +16 -14
  54. package/src/plugins/builtin/awards/pane.tsx +6 -6
  55. package/src/plugins/builtin/backtest/export.ts +1 -1
  56. package/src/plugins/builtin/broker-trading/dock.tsx +4 -2
  57. package/src/plugins/builtin/broker-trading/pane.tsx +6 -4
  58. package/src/plugins/builtin/broker-trading/ticket.tsx +10 -5
  59. package/src/plugins/builtin/buildout/pane/index.tsx +9 -6
  60. package/src/plugins/builtin/catalysts/client.ts +2 -2
  61. package/src/plugins/builtin/catalysts/pane.tsx +3 -3
  62. package/src/plugins/builtin/chart-composer/pane.tsx +1 -0
  63. package/src/plugins/builtin/commodity-balances/pane.tsx +3 -4
  64. package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
  65. package/src/plugins/builtin/company-attention/pane.tsx +13 -11
  66. package/src/plugins/builtin/company-kpis/pane.tsx +4 -3
  67. package/src/plugins/builtin/correlation/headless.ts +2 -1
  68. package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
  69. package/src/plugins/builtin/correlation/settings.ts +1 -0
  70. package/src/plugins/builtin/cot/pane.tsx +4 -3
  71. package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
  72. package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
  73. package/src/plugins/builtin/doe/pane.tsx +1 -1
  74. package/src/plugins/builtin/equity-screener/headless.ts +10 -0
  75. package/src/plugins/builtin/equity-screener/model.ts +28 -0
  76. package/src/plugins/builtin/equity-screener/pane.tsx +105 -98
  77. package/src/plugins/builtin/exposure/pane.tsx +10 -9
  78. package/src/plugins/builtin/gpu/board.tsx +4 -1
  79. package/src/plugins/builtin/gpu/changes.tsx +10 -10
  80. package/src/plugins/builtin/gpu/equities.tsx +4 -1
  81. package/src/plugins/builtin/gpu/history.tsx +4 -2
  82. package/src/plugins/builtin/gpu/pane.tsx +5 -3
  83. package/src/plugins/builtin/holders/client.ts +2 -1
  84. package/src/plugins/builtin/holders/headless.ts +2 -1
  85. package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
  86. package/src/plugins/builtin/insider/base-values.ts +11 -7
  87. package/src/plugins/builtin/insider/client.ts +3 -0
  88. package/src/plugins/builtin/insider/headless.ts +19 -6
  89. package/src/plugins/builtin/insider/index.tsx +17 -8
  90. package/src/plugins/builtin/insider/issuer.ts +34 -4
  91. package/src/plugins/builtin/insider/signals-client.ts +2 -2
  92. package/src/plugins/builtin/insider/table-model.ts +35 -13
  93. package/src/plugins/builtin/insider/table.tsx +5 -2
  94. package/src/plugins/builtin/iv-history/events.ts +145 -0
  95. package/src/plugins/builtin/iv-history/format.ts +3 -0
  96. package/src/plugins/builtin/iv-history/headless.ts +91 -7
  97. package/src/plugins/builtin/jodi/pane.tsx +4 -4
  98. package/src/plugins/builtin/market-movers/client.ts +4 -3
  99. package/src/plugins/builtin/market-movers/screener.ts +4 -3
  100. package/src/plugins/builtin/notes/cli.ts +1 -1
  101. package/src/plugins/builtin/notes/export.ts +17 -6
  102. package/src/plugins/builtin/notes/files.ts +16 -5
  103. package/src/plugins/builtin/options/enrichment-model.ts +10 -9
  104. package/src/plugins/builtin/options/model-spot.ts +70 -4
  105. package/src/plugins/builtin/options/view.tsx +41 -13
  106. package/src/plugins/builtin/options-calculator/pane.tsx +3 -2
  107. package/src/plugins/builtin/options-positioning/headless.ts +11 -3
  108. package/src/plugins/builtin/options-positioning/model.ts +24 -0
  109. package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
  110. package/src/plugins/builtin/options-scenario/client.ts +19 -1
  111. package/src/plugins/builtin/options-scenario/headless.ts +13 -6
  112. package/src/plugins/builtin/options-scenario/pane.tsx +13 -5
  113. package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
  114. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
  115. package/src/plugins/builtin/power/client.ts +2 -2
  116. package/src/plugins/builtin/power/pane.tsx +36 -31
  117. package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
  118. package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
  119. package/src/plugins/builtin/seasonality/client.ts +2 -1
  120. package/src/plugins/builtin/sectors/client.ts +4 -3
  121. package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
  122. package/src/plugins/builtin/shared/research-trading.tsx +2 -0
  123. package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
  124. package/src/plugins/builtin/shared/volatility/options.ts +2 -1
  125. package/src/plugins/builtin/social-mentions/client.ts +2 -1
  126. package/src/plugins/builtin/vol-surface/client.ts +23 -3
  127. package/src/plugins/builtin/vol-surface/headless.ts +33 -14
  128. package/src/plugins/builtin/vol-surface/model.ts +30 -10
  129. package/src/plugins/builtin/vol-surface/pane.tsx +33 -13
  130. package/src/plugins/builtin/vol-surface/stored.ts +34 -3
  131. package/src/plugins/builtin/vol-surface/tables.ts +14 -0
  132. package/src/renderers/dom/desktop/controls.tsx +1 -1
  133. package/src/renderers/dom/desktop/query-bar.tsx +2 -0
  134. package/src/renderers/dom/host/tabs.tsx +16 -5
  135. package/src/renderers/dom/host/top-surface.ts +127 -0
  136. package/src/renderers/dom/styles.css +39 -0
  137. package/src/renderers/electrobun/bun/core-capabilities.ts +9 -1
  138. package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
  139. package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
  140. package/src/renderers/electrobun/view/news-source.ts +4 -2
  141. package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
  142. package/src/renderers/opentui/start.tsx +7 -0
  143. package/src/sources/gloomberb-cloud/index.ts +7 -0
  144. package/src/sources/provider-router/index.ts +16 -0
  145. package/src/telemetry/crash-reports.ts +4 -0
  146. package/src/telemetry/update-failures-node.ts +59 -0
  147. package/src/telemetry/update-failures.ts +307 -0
  148. package/src/tickers/company-name-listing.ts +9 -7
  149. package/src/tickers/company-name.ts +43 -0
  150. package/src/tickers/listing-fate.ts +30 -0
  151. package/src/types/data-provider.ts +6 -0
  152. package/src/types/headless.ts +7 -0
  153. package/src/types/issuer-listings.ts +28 -0
  154. package/src/updater/failure.ts +163 -0
  155. package/src/updater/index.ts +87 -23
  156. package/src/utils/currency-pair.ts +26 -0
  157. package/src/utils/sec.ts +68 -0
@@ -12,6 +12,7 @@ import { WebIcon, WebIconButton } from "../desktop/icons";
12
12
  import { WebMenu } from "../desktop/menu";
13
13
  import { WebPopover } from "../desktop/popover";
14
14
  import { useHorizontalOverflow } from "./overflow-fade";
15
+ import { useTopSurfaceColor } from "./top-surface";
15
16
  import { hideDragShield, showDragShield } from "./drag-shield";
16
17
 
17
18
  type CssVars = CSSProperties & Record<`--${string}`, string>;
@@ -44,9 +45,10 @@ export function WebTabs({
44
45
  // A tab bar occupies exactly the one row every caller reserves for it. Any
45
46
  // extra pixels here are pixels the pane's children are told they own and the
46
47
  // pane then clips, which is how a chart's time axis ends up behind a footer.
47
- // Title-bar tabs fill the header to its bottom edge, so the active tab's
48
- // underline lies on the header's bottom rule. Every variant draws the one tab
49
- // look in styles.css: text, and the active tab on an accent underline.
48
+ // Title-bar tabs fill the header to its bottom edge, where the active tab's
49
+ // box covers the header's bottom rule and opens into the pane. Every variant
50
+ // draws the one tab look in styles.css: text, the active tab on an accent
51
+ // underline, and title-bar tabs as boxes instead of underlines.
50
52
  const listHeight = header ? "100%" : variant === "underline" || compact ? WEB_CELL_HEIGHT : "100%";
51
53
 
52
54
  useEffect(() => () => dragCleanupRef.current?.(), []);
@@ -67,6 +69,9 @@ export function WebTabs({
67
69
  useEffect(() => {
68
70
  if (!overflowing) setOverflowMenuOpen(false);
69
71
  }, [overflowing]);
72
+ // The active title-bar tab opens into the pane, so it takes the colour of
73
+ // whatever is directly under it.
74
+ useTopSurfaceColor(tabListRef, header, [activeValue, tabs]);
70
75
 
71
76
  const selectFromOverflowMenu = (value: string) => {
72
77
  setOverflowMenuOpen(false);
@@ -173,12 +178,14 @@ export function WebTabs({
173
178
  <div
174
179
  ref={tabListRef}
175
180
  data-gloom-role="tab-list"
181
+ data-gloom-variant={header ? "header" : undefined}
176
182
  role="tablist"
177
183
  onWheel={overflow.onWheel}
178
184
  style={{
179
185
  display: "flex",
180
186
  flexDirection: "row",
181
- alignItems: "stretch",
187
+ // Title-bar tabs stand on the header's bottom edge, clear of its top.
188
+ alignItems: header ? "flex-end" : "stretch",
182
189
  gap: 0,
183
190
  width: "100%",
184
191
  height: listHeight,
@@ -217,7 +224,10 @@ export function WebTabs({
217
224
  transform: `translateX(${dragTranslateX}px)${sourceDragging ? " scale(1.03)" : ""}`,
218
225
  zIndex: dragSourceValue === tab.value ? 2 : undefined,
219
226
  willChange: dragSourceValue ? "transform" : undefined,
220
- transition: `color 110ms ease, transform ${dragSourceValue && dragSourceValue !== tab.value ? "var(--tab-reorder-duration, 160ms)" : "0ms"} cubic-bezier(0.77, 0, 0.175, 1)`,
227
+ // An inactive title-bar tab fades its hover fill in. The active one's
228
+ // box changes with the bar's rule and the body under it, which do not
229
+ // fade, so it does not either.
230
+ transition: `color 110ms ease${header && !active ? ", background-color 110ms ease" : ""}, transform ${dragSourceValue && dragSourceValue !== tab.value ? "var(--tab-reorder-duration, 160ms)" : "0ms"} cubic-bezier(0.77, 0, 0.175, 1)`,
221
231
  cursor: disabled ? "default" : dragSourceValue ? "grabbing" : reorderable ? "grab" : "pointer",
222
232
  ...(dragTarget ? { color: "var(--gloom-text)" } : {}),
223
233
  } satisfies CssVars;
@@ -230,6 +240,7 @@ export function WebTabs({
230
240
  data-active={active ? "true" : undefined}
231
241
  data-dense={dense || compact ? "true" : undefined}
232
242
  data-reorderable={reorderable ? "true" : undefined}
243
+ data-drag-target={dragTarget ? "true" : undefined}
233
244
  data-tab-value={tab.value}
234
245
  type="button"
235
246
  role="tab"
@@ -0,0 +1,127 @@
1
+ import { useLayoutEffect, type RefObject } from "react";
2
+
3
+ type Rgba = [number, number, number, number];
4
+
5
+ const THROTTLE_MS = 120;
6
+ /** Rows and cells follow the cursor and selection; a tab must not take their colour. */
7
+ const TRANSIENT_ROLES = /^data-table-(row|cell|header-cell)$/;
8
+ const ACTIVE_BG = "--pane-tab-active-bg";
9
+
10
+ function parseColor(value: string): Rgba | null {
11
+ const trimmed = value.trim();
12
+ if (!trimmed || trimmed === "transparent") return [0, 0, 0, 0];
13
+ const rgb = /^rgba?\(([^)]+)\)$/.exec(trimmed);
14
+ if (rgb) {
15
+ const parts = rgb[1]!.split(/[\s,/]+/).filter(Boolean).map(Number);
16
+ if (parts.length < 3 || parts.some(Number.isNaN)) return null;
17
+ return [parts[0]!, parts[1]!, parts[2]!, parts[3] ?? 1];
18
+ }
19
+ const srgb = /^color\(srgb\s+([^)]+)\)$/.exec(trimmed);
20
+ if (srgb) {
21
+ const parts = srgb[1]!.split(/[\s/]+/).filter(Boolean).map(Number);
22
+ if (parts.length < 3 || parts.some(Number.isNaN)) return null;
23
+ return [parts[0]! * 255, parts[1]! * 255, parts[2]! * 255, parts[3] ?? 1];
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function over(top: Rgba, base: Rgba): Rgba {
29
+ const alpha = top[3];
30
+ return [
31
+ top[0] * alpha + base[0] * (1 - alpha),
32
+ top[1] * alpha + base[1] * (1 - alpha),
33
+ top[2] * alpha + base[2] * (1 - alpha),
34
+ 1,
35
+ ];
36
+ }
37
+
38
+ /**
39
+ * The colour the active title-bar tab takes so it reads as the top of the pane
40
+ * body: whatever surface sits directly under it (the query bar's band, a detail
41
+ * header, a table header, the body itself), translucent layers composited.
42
+ */
43
+ function measureTopSurface(body: HTMLElement, x: number): string | null {
44
+ const bodyRect = body.getBoundingClientRect();
45
+ if (bodyRect.width <= 0 || bodyRect.height <= 0) return null;
46
+ const y = bodyRect.top + 1;
47
+ let top: Element = document.elementsFromPoint(x, y).find((element) => body.contains(element)) ?? body;
48
+ // A marked surface (query bar, detail header) is taken whole, so hovering a
49
+ // control inside it never tints the tab.
50
+ const surface = top.closest("[data-gloom-top-surface]");
51
+ if (surface && body.contains(surface)) top = surface;
52
+
53
+ const layers: Rgba[] = [];
54
+ for (let element: Element | null = top; element; element = element.parentElement) {
55
+ const role = element.getAttribute("data-gloom-role") ?? "";
56
+ const rect = element.getBoundingClientRect();
57
+ const startsAtTop = Math.abs(rect.top - bodyRect.top) <= 1.5;
58
+ const spansBody = rect.width >= bodyRect.width * 0.6;
59
+ if (element === body || element === surface || (startsAtTop && spansBody && !TRANSIENT_ROLES.test(role))) {
60
+ const color = parseColor(getComputedStyle(element).backgroundColor);
61
+ if (color && color[3] > 0) layers.push(color);
62
+ }
63
+ if (element === body) break;
64
+ }
65
+ // From the body outwards to the topmost surface.
66
+ let color: Rgba | null = null;
67
+ for (let index = layers.length - 1; index >= 0; index -= 1) {
68
+ const layer = layers[index]!;
69
+ color = color ? over(layer, color) : layer[3] >= 1 ? layer : null;
70
+ }
71
+ if (!color) return null;
72
+ return `rgb(${Math.round(color[0])}, ${Math.round(color[1])}, ${Math.round(color[2])})`;
73
+ }
74
+
75
+ /**
76
+ * Keeps `--pane-tab-active-bg` on a title-bar tab list equal to the surface
77
+ * under its active tab, so the tab opens into the pane. Without a measure the
78
+ * header's own value (the body colour) applies. A change to the body itself
79
+ * (focus and theme recolour it) is measured at once, before it paints; changes
80
+ * inside it (a detail opens, a query bar appears) at most every 120 ms; a
81
+ * resize once it stops, so dragging a pane's edge costs no measures.
82
+ */
83
+ export function useTopSurfaceColor(listRef: RefObject<HTMLElement | null>, enabled: boolean, deps: readonly unknown[]) {
84
+ useLayoutEffect(() => {
85
+ const list = listRef.current;
86
+ if (!enabled || !list) return;
87
+ const body = list.closest("[data-gloom-role=pane-window], [data-gloom-role=detached-pane-window]")
88
+ ?.querySelector<HTMLElement>(":scope > [data-gloom-role=pane-body]");
89
+ if (!body) return;
90
+
91
+ let timer: ReturnType<typeof setTimeout> | null = null;
92
+ const measure = () => {
93
+ if (timer != null) clearTimeout(timer);
94
+ timer = null;
95
+ const active = list.querySelector<HTMLElement>("[data-active=true]");
96
+ if (!active) return;
97
+ const rect = active.getBoundingClientRect();
98
+ const color = measureTopSurface(body, rect.left + rect.width / 2);
99
+ if (color === (list.style.getPropertyValue(ACTIVE_BG) || null)) return;
100
+ if (color) list.style.setProperty(ACTIVE_BG, color);
101
+ else list.style.removeProperty(ACTIVE_BG);
102
+ };
103
+ const schedule = () => {
104
+ if (timer == null) timer = setTimeout(measure, THROTTLE_MS);
105
+ };
106
+ const settle = () => {
107
+ if (timer != null) clearTimeout(timer);
108
+ timer = setTimeout(measure, THROTTLE_MS);
109
+ };
110
+ measure();
111
+ const resize = new ResizeObserver(settle);
112
+ resize.observe(body);
113
+ // Live data rewrites text constantly; only structure and inline styles
114
+ // (visibility, focus colours, theme) can change what sits under the tab.
115
+ const mutations = new MutationObserver((records) => {
116
+ if (records.some((record) => record.target === body)) measure();
117
+ else schedule();
118
+ });
119
+ mutations.observe(body, { subtree: true, childList: true, attributes: true, attributeFilter: ["style", "data-gloom-top-surface"] });
120
+ return () => {
121
+ if (timer != null) clearTimeout(timer);
122
+ resize.disconnect();
123
+ mutations.disconnect();
124
+ };
125
+ // eslint-disable-next-line react-hooks/exhaustive-deps
126
+ }, [enabled, ...deps]);
127
+ }
@@ -507,6 +507,45 @@ button, [data-gloom-interactive="true"] { cursor: pointer; }
507
507
  [data-gloom-role="tab-button"][data-active="true"]::after { opacity: 1; }
508
508
  [data-gloom-role="tab-button"]:disabled { color: var(--gloom-text-muted); cursor: default; }
509
509
  [data-gloom-role="tab-button"]:focus-visible { outline: none; }
510
+ /* Title-bar tabs: the same text, sizes and colours, drawn as raised boxes
511
+ instead of underlines. The active tab is a box with rounded top corners,
512
+ open at the bottom: its top and sides in the header rule's colour (the
513
+ accent while the pane is focused), its fill the surface under it, laid over
514
+ the rule so it opens into the pane. An inactive tab takes the hover fill in
515
+ the same shape. The pane header sets the colours (pane/header.tsx). */
516
+ [data-gloom-role="tab-list"][data-gloom-variant="header"] {
517
+ /* Header colour left above a box, and the box's line: the header rule's width. */
518
+ --pane-tab-top-gap: 2px;
519
+ --pane-tab-rule: 1px;
520
+ }
521
+ [data-gloom-role="tab-list"][data-gloom-variant="header"] > [data-gloom-role="tab-button"] {
522
+ height: calc(100% - var(--pane-tab-top-gap));
523
+ /* The sides take their line from the padding, so a label sits where it
524
+ would without the box. The gap and the top line push the label down; the
525
+ bottom padding lifts it back onto the header's centre line, halfway
526
+ between its top rule (a divider or a floating border, when there is one)
527
+ and its bottom rule, where the title sits. */
528
+ padding: 0 calc(var(--gloom-space-sm) - var(--pane-tab-rule)) calc(var(--pane-tab-top-gap) + 2 * var(--pane-tab-rule) - var(--pane-tab-top-inset, 0px));
529
+ border: var(--pane-tab-rule) solid transparent;
530
+ border-bottom: 0;
531
+ border-radius: var(--gloom-radius-md) var(--gloom-radius-md) 0 0;
532
+ /* The line lies on the bar, as the rule does, so a translucent hairline is
533
+ the same colour on both. */
534
+ background-clip: padding-box;
535
+ }
536
+ [data-gloom-role="tab-list"][data-gloom-variant="header"] > [data-gloom-role="tab-button"]::after { content: none; }
537
+ [data-gloom-role="tab-list"][data-gloom-variant="header"] > [data-gloom-role="tab-button"][data-active="true"] {
538
+ background-color: var(--pane-tab-active-bg, var(--gloom-bg));
539
+ border-color: var(--pane-tab-border, var(--gloom-hairline));
540
+ }
541
+ [data-gloom-role="tab-list"][data-gloom-variant="header"] > [data-gloom-role="tab-button"]:not([data-active="true"]):not(:disabled):is(:hover, :focus-visible, [data-drag-target="true"]) {
542
+ background-color: var(--gloom-hover-fill);
543
+ }
544
+ /* A tab's close mark stays inside the box: in a 20px title bar the box is
545
+ shorter than the icon button, whose fill would cover the box's top line. */
546
+ [data-gloom-role="tab-list"][data-gloom-variant="header"] [data-gloom-role="tab-close"] {
547
+ max-height: 100%;
548
+ }
510
549
  /* A tab's close mark and a strip's plus are the icon button: 20 by 18, the control radius, the hover fill. */
511
550
  [data-gloom-role="tab-add"] {
512
551
  border: 0;
@@ -8,6 +8,7 @@ import type { AppServices } from "../../../core/app-services";
8
8
  import { diskNotesFilesIO, isNotesFile } from "../../../plugins/builtin/notes/files";
9
9
  import type { BrokerAdapter } from "../../../types/broker";
10
10
  import type { AppConfig, BrokerInstanceConfig } from "../../../types/config";
11
+ import { errorMessage } from "../../../utils/errors";
11
12
 
12
13
  const DESKTOP_CORE_PLUGIN_ID = "desktop-core";
13
14
 
@@ -221,7 +222,14 @@ function createNotesFilesCapability(): PluginCapability {
221
222
  await io(input).delete(file(input));
222
223
  return null;
223
224
  }, "action"),
224
- listKeys: op((input: any) => io(input).listKeys({ strict: input.strict === true })),
225
+ listKeys: op(async (input: any) => {
226
+ const skipped: Array<{ key: string; reason: string }> = [];
227
+ const keys = await io(input).listKeys({
228
+ strict: input.strict === true,
229
+ ...(input.reportSkipped === true ? { skipped: (key: string, error: unknown) => skipped.push({ key, reason: errorMessage(error) }) } : {}),
230
+ });
231
+ return input.reportSkipped === true ? { keys, skipped } : keys;
232
+ }),
225
233
  },
226
234
  };
227
235
  }
@@ -15,6 +15,8 @@ import { startRemoteControlServer, type RemoteControlServer } from "../../../../
15
15
  import type { RemoteControlRequest, RemoteControlResponse } from "../../../../remote/types";
16
16
  import { installCrashReporter } from "../../../../telemetry/crash-reports";
17
17
  import { createNodeCrashReporterHost } from "../../../../telemetry/crash-reports-node";
18
+ import { installUpdateFailureReporter, reviewLastUpdateAttempt } from "../../../../telemetry/update-failures";
19
+ import { createNodeUpdateFailureHost } from "../../../../telemetry/update-failures-node";
18
20
  import type { AppConfig } from "../../../../types/config";
19
21
  import type { DesktopSharedStateSnapshot } from "../../../../types/desktop-window";
20
22
  import type {
@@ -37,6 +39,7 @@ import {
37
39
  } from "../external-plugins";
38
40
  import { MAIN_WINDOW_RPC_KEY } from "../window/focus";
39
41
  import { handleDesktopBackendRequest } from "./backend-requests";
42
+ import { detectDesktopInstallKind, removeUpdateAsides } from "./update";
40
43
  import { DesktopCapabilityBridge } from "./capability-bridge";
41
44
  import { DesktopDetachedWindowManager } from "./detached-windows";
42
45
  import { handleDesktopHostRequest } from "./host-requests";
@@ -138,6 +141,13 @@ export class DesktopBackend {
138
141
  if (!this.crashReporterInstalled) {
139
142
  this.crashReporterInstalled = true;
140
143
  installCrashReporter(createNodeCrashReporterHost({ surface: "desktop", getConfig: () => this.config }));
144
+ // This process runs the desktop update, so it reports how the last one ended and any that fail.
145
+ installUpdateFailureReporter(createNodeUpdateFailureHost({
146
+ installKind: detectDesktopInstallKind(),
147
+ getDataDir: () => this.config?.dataDir ?? null,
148
+ }));
149
+ reviewLastUpdateAttempt();
150
+ void removeUpdateAsides();
141
151
  }
142
152
  // The native menu shows the same accelerators the keys use, so a rebind
143
153
  // rebuilds it.