gloomberb 0.17.0 → 0.17.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.
Files changed (201) hide show
  1. package/package.json +1 -1
  2. package/src/{plugins/builtin/cloud → account}/auth-dialog.tsx +7 -7
  3. package/src/api-client/data.ts +5 -1
  4. package/src/api-client/doe.ts +19 -0
  5. package/src/api-client/earnings.ts +4 -2
  6. package/src/api-client/index.ts +1 -0
  7. package/src/app/global-shortcuts.ts +10 -0
  8. package/src/app/keybindings/actions.ts +16 -0
  9. package/src/app/runtime/desktop-deeplink.ts +1 -1
  10. package/src/app/runtime/update.ts +16 -0
  11. package/src/app.tsx +1 -1
  12. package/src/architecture/core-builtin-allowlist.json +4 -9
  13. package/src/architecture/dom-cell-metrics-allowlist.json +100 -26
  14. package/src/cli/commands/market.ts +87 -17
  15. package/src/cli/commands/options-chain.ts +93 -23
  16. package/src/cli/commands/options-leaps.ts +462 -63
  17. package/src/cli/commands/search.ts +10 -7
  18. package/src/cli/commands/ticker.ts +19 -10
  19. package/src/cli/glossary.ts +1 -1
  20. package/src/cli/index.ts +14 -6
  21. package/src/cli/known-gaps.ts +22 -0
  22. package/src/cli/pane-functions/catalog.ts +45 -10
  23. package/src/cli/pane-functions/dom.ts +36 -10
  24. package/src/cli/pane-functions/function-help.ts +4 -2
  25. package/src/cli/pane-functions/headless.ts +12 -1
  26. package/src/cli/pane-functions/index.ts +44 -3
  27. package/src/cli/pane-functions/lock-note.ts +56 -0
  28. package/src/cli/pane-functions/report-notices.ts +15 -0
  29. package/src/cli/symbol-args.ts +19 -0
  30. package/src/components/command-bar/list/view.tsx +2 -1
  31. package/src/components/command-bar/workflow/builtin.ts +2 -2
  32. package/src/components/command-bar/workflow/collection-actions.ts +1 -1
  33. package/src/components/feedback-dialog.tsx +1 -1
  34. package/src/components/index.ts +3 -2
  35. package/src/components/layout/header.tsx +58 -14
  36. package/src/components/layout/shell/chrome.ts +10 -4
  37. package/src/components/layout/shell/index.tsx +1 -1
  38. package/src/components/layout/status-bar-groups.ts +1 -1
  39. package/src/components/layout/status-bar.tsx +2 -2
  40. package/src/components/markdown-text.tsx +14 -2
  41. package/src/components/reader/article-reader.tsx +1 -1
  42. package/src/components/reader/detail-reader.tsx +10 -7
  43. package/src/components/reader/reader-text.tsx +32 -17
  44. package/src/components/ui/column-grid.tsx +206 -0
  45. package/src/components/ui/display.tsx +40 -3
  46. package/src/components/ui/fill.ts +8 -0
  47. package/src/components/ui/index.ts +2 -0
  48. package/src/layout-marketplace/gallery.tsx +2 -2
  49. package/src/layout-marketplace/linked-sync.ts +1 -1
  50. package/src/market-data/market/freshness.ts +3 -0
  51. package/src/market-data/published-apac-sessions.ts +12 -2
  52. package/src/market-data/quotes/screener-live-quotes.ts +31 -7
  53. package/src/plugins/builtin/account-management/form-components.tsx +4 -7
  54. package/src/plugins/builtin/account-management/password-dialog.tsx +3 -2
  55. package/src/plugins/builtin/backtest/pane.tsx +9 -3
  56. package/src/plugins/builtin/buildout/detail/company.tsx +4 -3
  57. package/src/plugins/builtin/buildout/detail/ui.tsx +9 -12
  58. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  59. package/src/plugins/builtin/chart-composer/presets.ts +33 -1
  60. package/src/plugins/builtin/chat/channel-labels.ts +2 -2
  61. package/src/plugins/builtin/chat/content/channel-navigation.ts +1 -1
  62. package/src/plugins/builtin/chat/content/index.tsx +2 -2
  63. package/src/plugins/builtin/chat/conversations.ts +1 -1
  64. package/src/plugins/builtin/chat/index.tsx +2 -2
  65. package/src/plugins/builtin/chat/share/flow.tsx +1 -1
  66. package/src/plugins/builtin/chat/sidebar-rows.ts +1 -1
  67. package/src/plugins/builtin/chat/sidebar.tsx +2 -2
  68. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +2 -2
  69. package/src/plugins/builtin/cloud/askg/pane.tsx +2 -1
  70. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +3 -2
  71. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  72. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  73. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -1
  74. package/src/plugins/builtin/cloud/team/acm-tab.tsx +3 -3
  75. package/src/plugins/builtin/cloud/team/capability.ts +2 -2
  76. package/src/plugins/builtin/cloud/team/command.ts +4 -4
  77. package/src/plugins/builtin/cloud/team/module.tsx +3 -3
  78. package/src/plugins/builtin/cloud/team/pane-model.ts +2 -2
  79. package/src/plugins/builtin/cloud/team/pane-sections.tsx +1 -1
  80. package/src/plugins/builtin/cloud/team/pane-ui.tsx +1 -1
  81. package/src/plugins/builtin/cloud/team/pane.tsx +3 -3
  82. package/src/plugins/builtin/cloud/team/status-widget.tsx +3 -3
  83. package/src/plugins/builtin/cloud/team/team-state-host.ts +1 -1
  84. package/src/plugins/builtin/cloud/team/views.ts +2 -2
  85. package/src/plugins/builtin/cloud/thesis/board-pane.tsx +1 -1
  86. package/src/plugins/builtin/cloud/thesis/detail.tsx +12 -10
  87. package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +1 -1
  88. package/src/plugins/builtin/cloud/upgrade-command.ts +1 -1
  89. package/src/plugins/builtin/congress-trades/detail.tsx +8 -4
  90. package/src/plugins/builtin/connections/pane.tsx +16 -5
  91. package/src/plugins/builtin/cpi/pane.tsx +5 -4
  92. package/src/plugins/builtin/custom-view/pane.tsx +2 -2
  93. package/src/plugins/builtin/dividend-yield/headless.ts +8 -2
  94. package/src/plugins/builtin/dividend-yield/pane.tsx +4 -2
  95. package/src/plugins/builtin/docs/blocks.tsx +13 -2
  96. package/src/plugins/builtin/docs/pane.tsx +6 -5
  97. package/src/plugins/builtin/doe/index.tsx +11 -4
  98. package/src/plugins/builtin/doe/pane.tsx +5 -4
  99. package/src/plugins/builtin/doe/prices.ts +84 -0
  100. package/src/plugins/builtin/doe/series.ts +4 -1
  101. package/src/plugins/builtin/earnings/board-model.ts +18 -1
  102. package/src/plugins/builtin/earnings/board-table.ts +18 -3
  103. package/src/plugins/builtin/earnings/headless.ts +13 -2
  104. package/src/plugins/builtin/earnings/history-model.ts +1 -1
  105. package/src/plugins/builtin/earnings/history-table.ts +2 -2
  106. package/src/plugins/builtin/earnings-calls/headless.ts +7 -21
  107. package/src/plugins/builtin/earnings-calls/index.tsx +2 -0
  108. package/src/plugins/builtin/earnings-calls/lock-note.ts +17 -0
  109. package/src/plugins/builtin/equity-screener/headless.ts +59 -1
  110. package/src/plugins/builtin/equity-screener/live.ts +82 -31
  111. package/src/plugins/builtin/equity-screener/pane.tsx +60 -23
  112. package/src/plugins/builtin/estimate-revisions/client.ts +1 -1
  113. package/src/plugins/builtin/estimate-revisions/headless.ts +9 -2
  114. package/src/plugins/builtin/estimate-revisions/index.tsx +3 -0
  115. package/src/plugins/builtin/estimate-revisions/lock-note.ts +23 -0
  116. package/src/plugins/builtin/executives/index.tsx +18 -0
  117. package/src/plugins/builtin/exposure/charts.tsx +4 -3
  118. package/src/plugins/builtin/filing-events/index.tsx +8 -10
  119. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  120. package/src/plugins/builtin/gpu/equities.tsx +9 -6
  121. package/src/plugins/builtin/help/feedback-tab.tsx +1 -1
  122. package/src/plugins/builtin/iv-history/format.ts +3 -0
  123. package/src/plugins/builtin/iv-history/headless.ts +3 -1
  124. package/src/plugins/builtin/iv-history/index.tsx +3 -1
  125. package/src/plugins/builtin/iv-history/model.ts +3 -1
  126. package/src/plugins/builtin/iv-history/screen-pane.tsx +3 -1
  127. package/src/plugins/builtin/iv-history/universe.ts +1 -1
  128. package/src/plugins/builtin/jobs/headless.ts +6 -20
  129. package/src/plugins/builtin/jobs/index.tsx +2 -0
  130. package/src/plugins/builtin/jobs/lock-note.ts +16 -0
  131. package/src/plugins/builtin/notes/index.tsx +1 -1
  132. package/src/plugins/builtin/notes/owner.tsx +2 -2
  133. package/src/plugins/builtin/options/carry.ts +36 -0
  134. package/src/plugins/builtin/options/index.tsx +2 -1
  135. package/src/plugins/builtin/options/model-spot.ts +94 -0
  136. package/src/plugins/builtin/options/open-interest-as-of.ts +74 -0
  137. package/src/plugins/builtin/options/view.tsx +69 -11
  138. package/src/plugins/builtin/options-calculator/pane.tsx +56 -1
  139. package/src/plugins/builtin/options-calculator/premium-history.ts +34 -0
  140. package/src/plugins/builtin/options-scenario/client.ts +33 -9
  141. package/src/plugins/builtin/options-scenario/csp.ts +168 -0
  142. package/src/plugins/builtin/options-scenario/evidence.ts +3 -0
  143. package/src/plugins/builtin/options-scenario/headless.ts +24 -8
  144. package/src/plugins/builtin/options-scenario/index.tsx +3 -1
  145. package/src/plugins/builtin/options-scenario/pane.tsx +32 -6
  146. package/src/plugins/builtin/portfolio-list/mutations.ts +1 -1
  147. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
  148. package/src/plugins/builtin/research/corporate-actions-pane.tsx +4 -2
  149. package/src/plugins/builtin/research/event-model.ts +7 -4
  150. package/src/plugins/builtin/risk-factors/index.tsx +18 -0
  151. package/src/plugins/builtin/risk-factors/pane.tsx +2 -1
  152. package/src/plugins/builtin/scanner/headless.ts +9 -2
  153. package/src/plugins/builtin/scanner/index.ts +3 -0
  154. package/src/plugins/builtin/scanner/lock-note.ts +17 -0
  155. package/src/plugins/builtin/series-inventory.ts +2 -1
  156. package/src/plugins/builtin/shared/cloud-denied.ts +39 -8
  157. package/src/plugins/builtin/shared/earnings-report.ts +10 -4
  158. package/src/plugins/builtin/shared/pro-lock-note.ts +58 -0
  159. package/src/plugins/builtin/shared/series-list-detail.tsx +10 -5
  160. package/src/plugins/builtin/social-mentions/client.ts +3 -3
  161. package/src/plugins/builtin/social-mentions/headless.ts +6 -2
  162. package/src/plugins/builtin/social-mentions/index.tsx +2 -1
  163. package/src/plugins/builtin/social-mentions/lock-note.ts +15 -0
  164. package/src/plugins/builtin/supply-chain/flow.tsx +15 -13
  165. package/src/plugins/builtin/supply-chain/graph.tsx +6 -4
  166. package/src/plugins/builtin/supply-chain/pane.tsx +8 -5
  167. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +14 -1
  168. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +9 -7
  169. package/src/plugins/builtin/ticker-detail/headless.ts +4 -1
  170. package/src/plugins/builtin/ticker-detail/overview/components.tsx +105 -104
  171. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
  172. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +34 -8
  173. package/src/plugins/builtin/vol-surface/pane.tsx +6 -3
  174. package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
  175. package/src/renderers/dom/data-table/index.tsx +48 -15
  176. package/src/renderers/dom/data-table/row.tsx +1 -1
  177. package/src/renderers/dom/desktop/popover.tsx +6 -15
  178. package/src/renderers/dom/host/input.tsx +27 -3
  179. package/src/renderers/dom/host/surface-3d.tsx +6 -4
  180. package/src/renderers/dom/styles.css +26 -6
  181. package/src/renderers/electrobun/view/app-services.ts +2 -22
  182. package/src/renderers/electrobun/view/news-source.ts +42 -0
  183. package/src/series/validate.ts +5 -0
  184. package/src/shares/dialog.tsx +2 -1
  185. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  186. package/src/sources/provider-router/financials.ts +5 -0
  187. package/src/{plugins/builtin/cloud/team → team}/collections-sync.ts +6 -6
  188. package/src/{plugins/builtin/cloud/team → team}/collections.ts +3 -3
  189. package/src/{plugins/builtin/cloud/team → team}/model.ts +2 -2
  190. package/src/{plugins/builtin/cloud/team → team}/pane-request.ts +1 -1
  191. package/src/{plugins/builtin/cloud/team → team}/store.ts +3 -3
  192. package/src/types/data-provider.ts +3 -0
  193. package/src/types/financials.ts +28 -0
  194. package/src/types/plugin.ts +17 -4
  195. package/src/utils/decimal-comma.ts +18 -0
  196. package/src/utils/exchanges.ts +7 -0
  197. package/src/utils/financial-statements.ts +25 -0
  198. package/src/utils/forward-dividend.ts +39 -9
  199. package/src/utils/fundamentals.ts +41 -1
  200. package/src/utils/option-expiry.ts +16 -0
  201. package/src/utils/sec.ts +2 -1
@@ -2,6 +2,7 @@ import { TickerBadgeList } from "../../../../components";
2
2
  import { MarkdownText } from "../../../../components/markdown-text";
3
3
  import { Button } from "../../../../components/ui/button";
4
4
  import { ExternalLinkText } from "../../../../components/ui/external-link";
5
+ import { ClippedText } from "../../../../components/ui/display";
5
6
  import type { InlineTickerCatalogEntry } from "../../../../state/hooks/inline-tickers";
6
7
  import { colors } from "../../../../theme/colors";
7
8
  import { Box, Text } from "../../../../ui";
@@ -39,7 +40,7 @@ export function DetailSpecGrid({ items, width, marginTop = 1 }: { items: DetailS
39
40
  return (
40
41
  <Box key={item.label} flexDirection="row" width={columnWidth} height={1} overflow="hidden">
41
42
  <Text fg={colors.textMuted}>{labelText}</Text>
42
- <Text fg={item.color ?? colors.text}>{truncateWithEllipsis(text(item.value, ""), valueWidth)}</Text>
43
+ <ClippedText fg={item.color ?? colors.text} text={text(item.value, "")} width={valueWidth} cut={truncateWithEllipsis} />
43
44
  </Box>
44
45
  );
45
46
  })}
@@ -55,7 +56,7 @@ export function InlineSources({ domains, width }: { domains: readonly string[];
55
56
  return (
56
57
  <Box marginTop={1} flexDirection="row" width={width} height={1} overflow="hidden">
57
58
  <Text fg={colors.textMuted}>{label}</Text>
58
- <Text fg={colors.textDim}>{truncateWithEllipsis(domains.join(", "), Math.max(0, width - label.length))}</Text>
59
+ <ClippedText fg={colors.textDim} text={domains.join(", ")} width={Math.max(0, width - label.length)} cut={truncateWithEllipsis} />
59
60
  </Box>
60
61
  );
61
62
  }
@@ -74,7 +75,7 @@ export function DetailListLine({
74
75
  return (
75
76
  <Box flexDirection="row" width={width} height={1} overflow="hidden">
76
77
  <Text fg={colors.textMuted}>{prefix}</Text>
77
- <Text fg={colors.textDim}>{truncateWithEllipsis(values.join(", "), Math.max(0, width - prefix.length))}</Text>
78
+ <ClippedText fg={colors.textDim} text={values.join(", ")} width={Math.max(0, width - prefix.length)} cut={truncateWithEllipsis} />
78
79
  </Box>
79
80
  );
80
81
  }
@@ -112,9 +113,7 @@ export function RelatedCompaniesLine({
112
113
  fallbackColor={colors.textBright}
113
114
  />
114
115
  ) : null}
115
- <Text fg={colors.textDim}>
116
- {truncateWithEllipsis(`${names.length > 0 ? names.join(", ") : ""}${overflow}`, Math.max(0, width - prefix.length - badgeWidth))}
117
- </Text>
116
+ <ClippedText fg={colors.textDim} text={`${names.length > 0 ? names.join(", ") : ""}${overflow}`} width={Math.max(0, width - prefix.length - badgeWidth)} cut={truncateWithEllipsis} />
118
117
  </Box>
119
118
  );
120
119
  }
@@ -135,7 +134,7 @@ export function SourceDetailLines({
135
134
  {entries.map((entry, index) => {
136
135
  const prefix = entry.domain ? `${entry.domain}${entry.tier ? ` T${entry.tier}` : ""}` : "";
137
136
  const body = entry.title ?? entry.note ?? "";
138
- const bodyText = truncateWithEllipsis(prefix ? `: ${body}` : body, Math.max(0, width - prefix.length));
137
+ const bodyText = prefix ? `: ${body}` : body;
139
138
  return (
140
139
  <Box key={`${entry.domain ?? "source"}:${index}`} flexDirection="row" height={1} width={width} overflow="hidden">
141
140
  {prefix ? (
@@ -145,7 +144,7 @@ export function SourceDetailLines({
145
144
  <Text fg={colors.textMuted}>{prefix}</Text>
146
145
  )
147
146
  ) : null}
148
- <Text fg={colors.textMuted}>{bodyText}</Text>
147
+ <ClippedText fg={colors.textMuted} text={bodyText} width={Math.max(0, width - prefix.length)} cut={truncateWithEllipsis} />
149
148
  </Box>
150
149
  );
151
150
  })}
@@ -217,7 +216,7 @@ export function CompanyCell({
217
216
  }) {
218
217
  const symbol = tickerSymbol(company.ticker);
219
218
  if (!symbol) {
220
- return <Text fg={selected ? colors.selectedText : colors.text}>{truncateWithEllipsis(company.name, width)}</Text>;
219
+ return <ClippedText fg={selected ? colors.selectedText : colors.text} text={company.name} width={width} cut={truncateWithEllipsis} />;
221
220
  }
222
221
  const badgeWidth = Math.min(11, width);
223
222
  return (
@@ -227,9 +226,7 @@ export function CompanyCell({
227
226
  width={badgeWidth}
228
227
  fallbackColor={selected ? colors.selectedText : colors.textBright}
229
228
  />
230
- <Text fg={selected ? colors.selectedText : colors.text}>
231
- {truncateWithEllipsis(company.name, Math.max(0, width - badgeWidth))}
232
- </Text>
229
+ <ClippedText fg={selected ? colors.selectedText : colors.text} text={company.name} width={Math.max(0, width - badgeWidth)} cut={truncateWithEllipsis} />
233
230
  </Box>
234
231
  );
235
232
  }
@@ -260,7 +260,7 @@ const chartComposerTemplates: PaneTemplateDef[] = [
260
260
  type: "text",
261
261
  // Empty falls back to the active ticker in createInstance, as `G` with no argument does.
262
262
  required: false,
263
- body: ["Enter comma-separated SYMBOL:field or FRED:series expressions, or leave empty to chart the active ticker."],
263
+ body: ["Enter comma-separated SYMBOL:field or FRED:series expressions, or leave empty to chart the active ticker. A/B charts the ratio. EUR/USD stays one series."],
264
264
  }],
265
265
  canCreate: () => true,
266
266
  createInstance: (context, options) => {
@@ -7,13 +7,17 @@ import {
7
7
  import type { ChartResolution, TimeRange } from "../../../components/chart/core/types";
8
8
  import { MAX_CHART_COMPOSER_SERIES } from "./chart-spec";
9
9
  import { buildCustomSeries, buildSeriesSpec, panelsForSeries } from "./chart-spec-edit";
10
+ import { currencyPairSpelling } from "../../../utils/currency-pair";
11
+ import { dashedCoinPair } from "../../../utils/crypto-pair";
10
12
  import {
11
13
  CHART_FIELD_IDS,
12
14
  normalizeInstrument,
13
15
  parseChartExpression,
16
+ parseSeriesExpression,
14
17
  resolveChartFieldAlias,
18
+ type ParsedSeriesExpression,
15
19
  } from "./series-expression";
16
- import { setBuiltinStudies } from "./studies";
20
+ import { setBuiltinStudies, setPairStudies, type PairStudySelection } from "./studies";
17
21
 
18
22
  function chartSpec(
19
23
  series: ChartSeriesSpec[],
@@ -33,7 +37,35 @@ export function buildEmptyChartPreset(): ChartSpec {
33
37
  return chartSpec([]);
34
38
  }
35
39
 
40
+ /**
41
+ * `GAA/VOO`, `GAA / VOO`, and `CL=F - ES=F` are a ratio or a spread of two series.
42
+ * A slash that is the symbol stays one series (`EUR/USD`, `BTC/USD`), and so does
43
+ * a field written with a slash (`AAPL:EV/EBITDA`). A comma-separated list stays a list.
44
+ */
45
+ function derivedPair(expression: string): { series: ParsedSeriesExpression[]; formula: PairStudySelection } | null {
46
+ const trimmed = expression.trim();
47
+ if (!trimmed || /[;,\n]/.test(trimmed)) return null;
48
+ const spaced = /^(.*?)\s+(\/|-)\s+(.*)$/.exec(trimmed);
49
+ if (spaced) {
50
+ const left = parseSeriesExpression(spaced[1]!);
51
+ const right = parseSeriesExpression(spaced[3]!);
52
+ if (!left || !right) return null;
53
+ return { series: [left, right], formula: spaced[2] === "/" ? "ratio" : "spread" };
54
+ }
55
+ const tight = /^([^/\s]+)\/([^/\s]+)$/.exec(trimmed);
56
+ if (!tight) return null;
57
+ const [leftToken, rightToken] = [tight[1]!, tight[2]!];
58
+ const quote = rightToken.split(":")[0]!;
59
+ if (currencyPairSpelling(`${leftToken}/${quote}`) || dashedCoinPair(`${leftToken}/${quote}`)) return null;
60
+ const left = parseSeriesExpression(leftToken);
61
+ const right = parseSeriesExpression(rightToken);
62
+ if (!left || !right) return null;
63
+ return { series: [left, right], formula: "ratio" };
64
+ }
65
+
36
66
  export function buildCustomChartPreset(expression: string, fallbackSymbol?: string | null): ChartSpec {
67
+ const derived = derivedPair(expression);
68
+ if (derived) return setPairStudies(chartSpec(buildCustomSeries(derived.series)), [derived.formula]);
37
69
  const parsed = parseChartExpression(expression);
38
70
  if (parsed.length === 0) return fallbackSymbol ? buildPriceChartPreset(fallbackSymbol) : buildEmptyChartPreset();
39
71
  return chartSpec(buildCustomSeries(parsed));
@@ -1,8 +1,8 @@
1
1
  import type { ChatChannel } from "../../../api-client";
2
2
  import { t } from "../../../i18n";
3
3
  import { normalizeChannelId } from "./controller/state";
4
- import { teamPrefix } from "../cloud/team/model";
5
- import { teamStore } from "../cloud/team/store";
4
+ import { teamPrefix } from "../../../team/model";
5
+ import { teamStore } from "../../../team/store";
6
6
 
7
7
  /** `MD·` for a team channel, empty otherwise. Team markers never depend on color. */
8
8
  function teamChannelPrefix(channel: ChatChannel | undefined, fallbackId: string): string {
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore
2
2
  import type { MutableRefObject } from "react";
3
3
  import type { ChatChannel } from "../../../../api-client";
4
4
  import { useThrottledCommitValue } from "../../../../react/use-throttled-commit-value";
5
- import { teamStore } from "../../cloud/team/store";
5
+ import { teamStore } from "../../../../team/store";
6
6
  import { chatSidebarStore } from "../sidebar-store";
7
7
  import {
8
8
  buildChatSidebarRows,
@@ -55,10 +55,10 @@ import {
55
55
  import type { ChatUserAnchor, ChatUserContextMenuEvent } from "../message/types";
56
56
  import type { ContextMenuItem } from "../../../../types/context-menu";
57
57
  import { usePluginAppActions } from "../../../runtime";
58
- import { openTeamPane } from "../../cloud/team/pane-request";
58
+ import { openTeamPane } from "../../../../team/pane-request";
59
59
  import { openChatPaneLink } from "../pane-links/open";
60
60
  import type { ChatMessagePaneLink } from "../pane-links/model";
61
- import { teamStore } from "../../cloud/team/store";
61
+ import { teamStore } from "../../../../team/store";
62
62
  import { requestAccountManagementTab } from "../../account-management/navigation";
63
63
  import {
64
64
  CHAT_MESSAGE_EDIT_WINDOW_MS,
@@ -1,5 +1,5 @@
1
1
  import type { ChatChannel, ChatUserSummary } from "../../../api-client";
2
- import { teamStore } from "../cloud/team/store";
2
+ import { teamStore } from "../../../team/store";
3
3
  import { formatChatPaneTitle } from "./channel-labels";
4
4
 
5
5
  /** What the chat controller holds about one conversation, read without a request. */
@@ -1,6 +1,6 @@
1
1
  import type { PluginModule } from "../plugin-module";
2
- import { teamChannelId } from "../cloud/team/model";
3
- import { teamStore } from "../cloud/team/store";
2
+ import { teamChannelId } from "../../../team/model";
3
+ import { teamStore } from "../../../team/store";
4
4
  import { formatChatPaneTitle } from "./channel-labels";
5
5
  import {
6
6
  DEFAULT_CHAT_CHANNEL_ID,
@@ -2,7 +2,7 @@ import { apiClient } from "../../../../api-client";
2
2
  import { pngDataUrl } from "../../../../shares/pane-image";
3
3
  import { registerPaneChatSharing } from "../../../../shares/chat";
4
4
  import type { GloomPluginContext } from "../../../../types/plugin";
5
- import { requestAuthDialog } from "../../cloud/auth-dialog";
5
+ import { requestAuthDialog } from "../../../../account/auth-dialog";
6
6
  import { MAX_CHAT_IMAGE_BYTES } from "../attachments/model";
7
7
  import { LAST_VISITED_CHAT_CHANNEL_KEY } from "../channels";
8
8
  import { chatController } from "../controller";
@@ -1,5 +1,5 @@
1
1
  import type { ChatChannel, TeamSummary } from "../../../api-client";
2
- import { sortTeamChannels } from "../cloud/team/model";
2
+ import { sortTeamChannels } from "../../../team/model";
3
3
  import type { ChatSidebarSection } from "./sidebar-store";
4
4
 
5
5
  interface SidebarHeaderBase {
@@ -14,8 +14,8 @@ import { blendHex, colors } from "../../../theme/colors";
14
14
  import { t } from "../../../i18n";
15
15
  import type { ChatChannel } from "../../../api-client";
16
16
  import { truncateWithEllipsis } from "../../../utils/text-wrap";
17
- import { teamAccentHex, teamPrefix } from "../cloud/team/model";
18
- import { teamStore } from "../cloud/team/store";
17
+ import { teamAccentHex, teamPrefix } from "../../../team/model";
18
+ import { teamStore } from "../../../team/store";
19
19
  import type { ChatController } from "./controller";
20
20
  import { chatSidebarStore } from "./sidebar-store";
21
21
  import { buildChatSidebarRows } from "./sidebar-rows";
@@ -29,8 +29,8 @@ import { displayWidth, truncateToDisplayWidth } from "../../../utils/format";
29
29
  import { usePluginAppActions, usePluginPaneState } from "../../runtime";
30
30
  import { requestPriceAlertFocus } from "../alerts/focus";
31
31
  import { SignInWall } from "../cloud/auth-actions";
32
- import { openTeamPane } from "../cloud/team/pane-request";
33
- import { teamStore } from "../cloud/team/store";
32
+ import { openTeamPane } from "../../../team/pane-request";
33
+ import { teamStore } from "../../../team/store";
34
34
  import { chatController, type ChatController } from "./controller";
35
35
  import { applyUnreadInboxItemToConfig } from "./pane-state";
36
36
  import { buildUnreadListRows, type UnreadInboxItem, type UnreadListRow } from "./unread-inbox";
@@ -113,6 +113,7 @@ import {
113
113
  turnTimelineIds,
114
114
  UNDO_KEY,
115
115
  } from "./tool-timeline";
116
+ import { ClippedText } from "../../../../components/ui/display";
116
117
 
117
118
  export const ASKG_PANE_ID = "askg";
118
119
 
@@ -345,7 +346,7 @@ function ConfirmationPrompt({
345
346
  </Box>
346
347
  {lines.map((line, index) => (
347
348
  <Box key={index} height={1}>
348
- <Text fg={colors.text}>{truncateWithEllipsis(line, Math.max(10, width - 2))}</Text>
349
+ <ClippedText fg={colors.text} text={line} width={Math.max(10, width - 2)} cut={truncateWithEllipsis} />
349
350
  </Box>
350
351
  ))}
351
352
  <Box flexDirection="row" height={2} paddingTop={1}>
@@ -30,6 +30,7 @@ import {
30
30
  type ToolMark,
31
31
  type ToolRowView,
32
32
  } from "./tool-display";
33
+ import { ClippedText } from "../../../../components/ui/display";
33
34
 
34
35
  /** Undo a write; not `u`, which installs an app update whenever one is waiting. */
35
36
  export const UNDO_KEY = "z";
@@ -146,7 +147,7 @@ export function ToolTimelineRow({
146
147
  </ActionRow>
147
148
  {view.line && !expanded ? (
148
149
  <Box paddingLeft={2} height={1} overflow="hidden">
149
- <Text fg={failed ? colors.textDim : colors.textMuted}>{truncateWithEllipsis(view.line, lineWidth)}</Text>
150
+ <ClippedText fg={failed ? colors.textDim : colors.textMuted} text={view.line} width={lineWidth} cut={truncateWithEllipsis} />
150
151
  </Box>
151
152
  ) : null}
152
153
  {showDetails ? (
@@ -351,7 +352,7 @@ export function ToolResultDetail({
351
352
  return (
352
353
  <Box flexDirection="column" flexShrink={0} width={width} height={height}>
353
354
  <Box flexDirection="row" height={1} paddingX={1}>
354
- <Text fg={colors.textDim}>{truncateWithEllipsis(heading, Math.max(10, width - (canOpenPane ? 18 : 2) - view.meta.length - 2))}</Text>
355
+ <ClippedText fg={colors.textDim} text={heading} width={Math.max(10, width - (canOpenPane ? 18 : 2) - view.meta.length - 2)} cut={truncateWithEllipsis} />
355
356
  <Box flexGrow={1} />
356
357
  {view.meta ? <Text fg={colors.textMuted}>{`${view.meta} `}</Text> : null}
357
358
  {canOpenPane ? (
@@ -16,7 +16,7 @@ import { usePluginAppActions } from "../../runtime";
16
16
  import { colors } from "../../../theme/colors";
17
17
  import { t, tf } from "../../../i18n";
18
18
  import { useCloudPlanAction, useCloudUpgradeAction } from "../shared/cloud-upgrade";
19
- import { requestAuthDialog } from "./auth-dialog";
19
+ import { requestAuthDialog } from "../../../account/auth-dialog";
20
20
  import type { AccountMode } from "../../../account/auth-model";
21
21
  import { recordWallCtaClicked } from "../../../api-client/research-activity";
22
22
  import { WallTeaser } from "../shared/wall-teaser";
@@ -1,7 +1,7 @@
1
1
  import type { GloomPluginContext } from "../../../types/plugin";
2
2
  import { apiClient } from "../../../api-client";
3
3
  import { chatController } from "../chat/controller";
4
- import { requestAuthDialog } from "./auth-dialog";
4
+ import { requestAuthDialog } from "../../../account/auth-dialog";
5
5
  import { requestDeviceSignInDialog } from "../../../account/device-signin-dialog";
6
6
 
7
7
  export function registerCloudAuthCommands(ctx: GloomPluginContext): void {
@@ -27,7 +27,7 @@ import { Box, Text, TextAttributes, useRendererHost, useUiCapabilities } from ".
27
27
  import { useDialog, useDialogKeyboard, type DialogApi, type PromptContext } from "../../../../ui/dialog";
28
28
  import { isPlainKey } from "../../../../utils/keyboard";
29
29
  import { useCloudUpgradeAction } from "../../shared/cloud-upgrade";
30
- import { requestAuthDialog } from "../auth-dialog";
30
+ import { requestAuthDialog } from "../../../../account/auth-dialog";
31
31
  import {
32
32
  currentMcpEndpointUrl,
33
33
  MCP_CLIENTS,
@@ -6,9 +6,9 @@ import { colors } from "../../../../theme/colors";
6
6
  import { Box, Text } from "../../../../ui";
7
7
  import { isPlainKey } from "../../../../utils/keyboard";
8
8
  import { usePluginAppActions } from "../../../runtime";
9
- import { describeTeam, teamAccentHex, teamLabel, userHandle } from "./model";
10
- import { openTeamPane } from "./pane-request";
11
- import { teamStore } from "./store";
9
+ import { describeTeam, teamAccentHex, teamLabel, userHandle } from "../../../../team/model";
10
+ import { openTeamPane } from "../../../../team/pane-request";
11
+ import { teamStore } from "../../../../team/store";
12
12
 
13
13
  const NEW_TEAM_ID = "__new__";
14
14
 
@@ -1,6 +1,6 @@
1
1
  import type { PluginCapability } from "../../../../capabilities";
2
- import { teamAccentHex } from "./model";
3
- import { teamStore, type TeamStoreSnapshot } from "./store";
2
+ import { teamAccentHex } from "../../../../team/model";
3
+ import { teamStore, type TeamStoreSnapshot } from "../../../../team/store";
4
4
 
5
5
  const CLOUD_TEAM_CAPABILITY_ID = "cloud.team";
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { apiClient } from "../../../../api-client";
2
2
  import type { CommandResultDef, GloomPluginContext } from "../../../../types/plugin";
3
- import { requestAuthDialog } from "../auth-dialog";
3
+ import { requestAuthDialog } from "../../../../account/auth-dialog";
4
4
  import {
5
5
  canInviteToTeam,
6
6
  canManageTeam,
@@ -9,9 +9,9 @@ import {
9
9
  teamChannelId,
10
10
  teamLabel,
11
11
  userHandle,
12
- } from "./model";
13
- import { openTeamPane, type TeamPaneSection } from "./pane-request";
14
- import { teamStore } from "./store";
12
+ } from "../../../../team/model";
13
+ import { openTeamPane, type TeamPaneSection } from "../../../../team/pane-request";
14
+ import { teamStore } from "../../../../team/store";
15
15
 
16
16
  const TEAM_SUBCOMMAND = /^(invite|new|settings|members|channels|leave|focus|chat)\b\s*(.*)$/i;
17
17
 
@@ -2,11 +2,11 @@ import type { PluginModule } from "../../plugin-module";
2
2
  import { chatController } from "../../chat/controller";
3
3
  import { createCloudTeamCapability } from "./capability";
4
4
  import { registerTeamCommands } from "./command";
5
- import { teamChannelId } from "./model";
5
+ import { teamChannelId } from "../../../../team/model";
6
6
  import { TeamPane } from "./pane";
7
- import { TEAM_PANE_ID, TEAM_PANE_TEMPLATE_ID, openTeamPane } from "./pane-request";
7
+ import { TEAM_PANE_ID, TEAM_PANE_TEMPLATE_ID, openTeamPane } from "../../../../team/pane-request";
8
8
  import { TeamStatusWidget } from "./status-widget";
9
- import { teamStore } from "./store";
9
+ import { teamStore } from "../../../../team/store";
10
10
  import { installTeamStateHost } from "./team-state-host";
11
11
  import { createCloudViewsCapability, teamViewsStore } from "./views";
12
12
 
@@ -10,8 +10,8 @@ import {
10
10
  deriveTeamShortName,
11
11
  normalizeTeamShortName,
12
12
  teamChannelId,
13
- } from "./model";
14
- import type { TeamPaneSection } from "./pane-request";
13
+ } from "../../../../team/model";
14
+ import type { TeamPaneSection } from "../../../../team/pane-request";
15
15
 
16
16
  export const TEAM_PANE_SECTIONS: readonly { value: TeamPaneSection; label: string }[] = [
17
17
  { value: "members", label: "Members" },
@@ -19,7 +19,7 @@ import {
19
19
  teamChannelId,
20
20
  teamPrefix,
21
21
  userHandle,
22
- } from "./model";
22
+ } from "../../../../team/model";
23
23
  import {
24
24
  draftProblem,
25
25
  setDraftName,
@@ -3,7 +3,7 @@ import type { TeamAccentColor } from "../../../../api-client";
3
3
  import { Badge, Button, Checkbox, FieldLabel, SectionHeading, TextField, type ButtonVariant } from "../../../../components";
4
4
  import { colors } from "../../../../theme/colors";
5
5
  import { Box, Span, Text, TextAttributes, type BoxRenderable } from "../../../../ui";
6
- import { TEAM_ACCENT_COLORS, teamAccentHex, teamPrefix } from "./model";
6
+ import { TEAM_ACCENT_COLORS, teamAccentHex, teamPrefix } from "../../../../team/model";
7
7
 
8
8
  /**
9
9
  * Every control in the pane registers under a field id. The pane keeps one
@@ -41,7 +41,7 @@ import {
41
41
  teamIdFromChannelId,
42
42
  teamPrefix,
43
43
  userHandle,
44
- } from "./model";
44
+ } from "../../../../team/model";
45
45
  import {
46
46
  TEAM_PANE_SECTIONS,
47
47
  cycleAccent,
@@ -61,7 +61,7 @@ import {
61
61
  subscribeRequestedTeamPaneView,
62
62
  type TeamPaneSection,
63
63
  type TeamPaneView,
64
- } from "./pane-request";
64
+ } from "../../../../team/pane-request";
65
65
  import {
66
66
  ChannelsSection,
67
67
  CreateTeamForm,
@@ -70,7 +70,7 @@ import {
70
70
  SettingsSection,
71
71
  } from "./pane-sections";
72
72
  import { Muted, PaneButton, TeamPaneFocusContext, type TeamPaneFocus } from "./pane-ui";
73
- import { teamStore } from "./store";
73
+ import { teamStore } from "../../../../team/store";
74
74
 
75
75
  type Message = { tone: "info" | "success" | "error"; text: string } | null;
76
76
 
@@ -4,9 +4,9 @@ import { colors } from "../../../../theme/colors";
4
4
  import { Box, Span, Text, TextAttributes, useCommandBarShortcut } from "../../../../ui";
5
5
  import { usePluginAppActions } from "../../../runtime";
6
6
  import { chatController } from "../../chat/controller";
7
- import { countTeamUpdates, teamAccentHex, teamIdFromChannelId } from "./model";
8
- import { openTeamPane } from "./pane-request";
9
- import { teamStore } from "./store";
7
+ import { countTeamUpdates, teamAccentHex, teamIdFromChannelId } from "../../../../team/model";
8
+ import { openTeamPane } from "../../../../team/pane-request";
9
+ import { teamStore } from "../../../../team/store";
10
10
 
11
11
  /**
12
12
  * One chip per team in the status bar, in the team's accent, with the unread
@@ -1,6 +1,6 @@
1
1
  import { apiClient, type TeamPluginStateEntry, type TeamPluginStateEvent } from "../../../../api-client";
2
2
  import { setTeamStateHost, type TeamStateHost } from "../../../team-state";
3
- import { teamStore } from "./store";
3
+ import { teamStore } from "../../../../team/store";
4
4
 
5
5
  type Listener = (entry: { value: unknown; revision: number; updatedBy: string; updatedAt: string } | null) => void
6
6
 
@@ -8,8 +8,8 @@ import {
8
8
  setViewRefResolver,
9
9
  type ViewSpec,
10
10
  } from "../../custom-view";
11
- import { teamPrefix } from "./model";
12
- import { teamStore } from "./store";
11
+ import { teamPrefix } from "../../../../team/model";
12
+ import { teamStore } from "../../../../team/store";
13
13
 
14
14
  const CLOUD_VIEWS_CAPABILITY_ID = "cloud.views";
15
15
 
@@ -24,7 +24,7 @@ import { usePluginAppActions } from "../../../runtime";
24
24
  import { SignInWall } from "../auth-actions";
25
25
  import { useCloudUpgradeAction } from "../../shared/cloud-upgrade";
26
26
  import { usePlanAccess } from "../../../../api-client/plan-access";
27
- import { teamStore } from "../team/store";
27
+ import { teamStore } from "../../../../team/store";
28
28
  import { ThesisDetail } from "./detail";
29
29
  import { useBookExposure } from "./exposure";
30
30
  import * as flows from "./flows";
@@ -10,7 +10,7 @@ import { isPlainKey } from "../../../../utils/keyboard";
10
10
  import { usePluginAppActions } from "../../../runtime";
11
11
  import { useCloudUpgradeAction } from "../../shared/cloud-upgrade";
12
12
  import { usePlanAccess } from "../../../../api-client/plan-access";
13
- import { teamStore } from "../team/store";
13
+ import { teamStore } from "../../../../team/store";
14
14
  import * as flows from "./flows";
15
15
  import {
16
16
  catalystState,
@@ -31,6 +31,7 @@ import {
31
31
  } from "./model";
32
32
  import { promptChoice } from "./prompts";
33
33
  import { thesisStore } from "./store";
34
+ import { ClippedText } from "../../../../components/ui/display";
34
35
 
35
36
  type Row =
36
37
  | { kind: "heading"; id: string; label: string; section: Section }
@@ -360,14 +361,14 @@ export function ThesisDetail({ thesis, width, height, focused, footerId, onDelet
360
361
  if (row.kind === "heading") {
361
362
  return (
362
363
  <Box flexDirection="row" width={contentWidth}>
363
- <Text fg={colors.textDim} attributes={TextAttributes.BOLD}>{truncateToDisplayWidth(row.label, contentWidth)}</Text>
364
+ <ClippedText fg={colors.textDim} attributes={TextAttributes.BOLD} text={row.label} width={contentWidth} />
364
365
  </Box>
365
366
  );
366
367
  }
367
368
  if (row.kind === "empty" || row.kind === "text") {
368
369
  return (
369
370
  <Box flexDirection="row" width={contentWidth}>
370
- <Text fg={colors.textDim}>{` ${truncateToDisplayWidth(row.text, contentWidth - 3)}`}</Text>
371
+ <ClippedText fg={colors.textDim} lead=" " text={row.text} width={contentWidth - 3} />
371
372
  </Box>
372
373
  );
373
374
  }
@@ -449,13 +450,13 @@ export function ThesisDetail({ thesis, width, height, focused, footerId, onDelet
449
450
  // row stays one line and the list never has to re-measure.
450
451
  const stripLines = useMemo(() => {
451
452
  const row = selectedRow;
452
- if (!row) return [] as Array<{ text: string; dim: boolean }>;
453
- const lines: Array<{ text: string; dim: boolean }> = [];
453
+ if (!row) return [] as Array<{ text: string; dim: boolean; clip?: boolean }>;
454
+ const lines: Array<{ text: string; dim: boolean; clip?: boolean }> = [];
454
455
  if (row.kind === "signal") {
455
456
  const source = row.item.source;
456
457
  const sourceText = source?.title ?? (source?.kind === "user" ? "Filed by a teammate" : source?.kind ?? "");
457
458
  const when = source?.at ? ` · ${source.at.slice(0, 10)}` : "";
458
- if (sourceText) lines.push({ text: truncateToDisplayWidth(`${sourceText}${when}`, contentWidth), dim: true });
459
+ if (sourceText) lines.push({ text: `${sourceText}${when}`, dim: true, clip: true });
459
460
  for (const line of wrap(row.item.reason, contentWidth, 3)) lines.push({ text: line, dim: false });
460
461
  if (row.item.confidence !== null) lines.push({ text: `confidence ${Math.round(row.item.confidence * 100)}%`, dim: true });
461
462
  } else if (row.kind === "pillar") {
@@ -475,11 +476,11 @@ export function ThesisDetail({ thesis, width, height, focused, footerId, onDelet
475
476
  {thesis.status !== "active" && <Badge label={thesis.status} tone="neutral" />}
476
477
  <Text fg={due ? colors.warning : colors.textDim}>{reviewLine}</Text>
477
478
  {nextDue && nextDays !== null && (
478
- <Text fg={nextDays <= 14 ? colors.warning : colors.textDim}>{truncateToDisplayWidth(`Next: ${nextDue.text} ${describeDays(nextDays)}`, Math.max(10, contentWidth - 40))}</Text>
479
+ <ClippedText fg={nextDays <= 14 ? colors.warning : colors.textDim} text={`Next: ${nextDue.text} ${describeDays(nextDays)}`} width={Math.max(10, contentWidth - 40)} />
479
480
  )}
480
481
  </Box>
481
482
  <Box height={1}>
482
- <Text fg={colors.textDim}>{truncateToDisplayWidth(meta, contentWidth)}</Text>
483
+ <ClippedText fg={colors.textDim} text={meta} width={contentWidth} />
483
484
  </Box>
484
485
  {summaryLines.map((line, index) => (
485
486
  <Box key={index} height={1}>
@@ -488,7 +489,7 @@ export function ThesisDetail({ thesis, width, height, focused, footerId, onDelet
488
489
  ))}
489
490
  {evidence && (
490
491
  <Box height={1}>
491
- <Text fg={colors.textMuted}>{truncateToDisplayWidth(evidence, contentWidth)}</Text>
492
+ <ClippedText fg={colors.textMuted} text={evidence} width={contentWidth} />
492
493
  </Box>
493
494
  )}
494
495
  <Box height={1} />
@@ -526,7 +527,8 @@ export function ThesisDetail({ thesis, width, height, focused, footerId, onDelet
526
527
  <Box flexDirection="column" height={stripHeight} marginTop={1}>
527
528
  {stripLines.map((line, index) => (
528
529
  <Box key={index} height={1}>
529
- <Text fg={line.dim ? colors.textDim : colors.text}>{line.text}</Text>
530
+ {line.clip ? <ClippedText fg={line.dim ? colors.textDim : colors.text} text={line.text} width={contentWidth} />
531
+ : <Text fg={line.dim ? colors.textDim : colors.text}>{line.text}</Text>}
530
532
  </Box>
531
533
  ))}
532
534
  </Box>
@@ -10,7 +10,7 @@ import { usePluginAppActions, usePluginPaneState } from "../../../runtime";
10
10
  import { SignInWall } from "../auth-actions";
11
11
  import { useCloudUpgradeAction } from "../../shared/cloud-upgrade";
12
12
  import { usePlanAccess } from "../../../../api-client/plan-access";
13
- import { teamStore } from "../team/store";
13
+ import { teamStore } from "../../../../team/store";
14
14
  import { ThesisDetail } from "./detail";
15
15
  import * as flows from "./flows";
16
16
  import { thesesCovering } from "./model";
@@ -3,7 +3,7 @@ import type { GloomPluginContext } from "../../../types/plugin";
3
3
  import { requestAccountManagementTab } from "../account-management/navigation";
4
4
  import { openCloudUpgradeUrl } from "../shared/cloud-upgrade";
5
5
  import { resolvePlanAccess } from "../../../api-client/plan-access";
6
- import { requestAuthDialog } from "./auth-dialog";
6
+ import { requestAuthDialog } from "../../../account/auth-dialog";
7
7
 
8
8
  export function registerCloudUpgradeCommand(ctx: GloomPluginContext): void {
9
9
  ctx.registerCommand({