gloomberb 0.7.2 → 0.7.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.
- package/README.md +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +49 -15
- package/src/components/chart/chart-types.ts +2 -2
- package/src/components/chart/chart.test.ts +27 -8
- package/src/components/chart/comparison-stock-chart.tsx +84 -121
- package/src/components/chart/native/chart-rasterizer.ts +43 -17
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +1 -1
- package/src/components/chart/stock-chart.tsx +59 -41
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +528 -383
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +1 -0
- package/src/components/command-bar/view-model.ts +2 -1
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +21 -3
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +11 -51
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator.ts +8 -1
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +258 -44
- package/src/plugins/builtin/chat.test.tsx +257 -6
- package/src/plugins/builtin/chat.tsx +225 -60
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/news-wire/index.tsx +1 -1
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +11 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +117 -18
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +282 -2
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +252 -41
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +0 -6
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +442 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +6 -4
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/state/app-bootstrap.ts +3 -3
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +128 -0
- package/src/utils/api-client.ts +112 -7
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
|
@@ -10,16 +10,15 @@ import {
|
|
|
10
10
|
import { createAppServices, type AppServices } from "../../../core/app-services";
|
|
11
11
|
import { getDataDir, initDataDir, saveConfig, resetAllData, exportConfig, importConfig, setConfigStoreHost } from "../../../data/config-store";
|
|
12
12
|
import * as nodeConfigStoreHost from "../../../data/config-store-node";
|
|
13
|
-
import {
|
|
13
|
+
import { findPaneInstance, type AppConfig } from "../../../types/config";
|
|
14
14
|
import type { AppSessionSnapshot } from "../../../core/state/session-persistence";
|
|
15
|
-
import type
|
|
15
|
+
import { syncConfigActiveLayoutState, type PaneRuntimeState } from "../../../core/state/app-state";
|
|
16
16
|
import type { DesktopDockPreviewState, DesktopSharedStateSnapshot, DesktopThemePreviewState } from "../../../types/desktop-window";
|
|
17
17
|
import type { ReleaseInfo, UpdateCheckResult, UpdateProgress } from "../../../updater";
|
|
18
18
|
import { buildSoundCommand } from "../../../notifications/app-notifier";
|
|
19
19
|
import { isPaneDetached } from "../../../plugins/pane-manager";
|
|
20
20
|
import { ELECTROBUN_CONTEXT_MENU_ACTION, type ElectrobunDesktopRpcSchema } from "../shared/protocol";
|
|
21
21
|
import { decodeRpcValue, encodeRpcValue } from "../view/rpc-codec";
|
|
22
|
-
import { startMainThreadMonitor } from "../../../utils/main-thread-monitor";
|
|
23
22
|
import { contextMenuSelectionMessage } from "./context-menu-click";
|
|
24
23
|
import { getContextMenuRequestId, normalizeContextMenuItems } from "./context-menu-normalize";
|
|
25
24
|
import { createDesktopWorkspace, type DesktopWorkspace } from "./desktop-workspace";
|
|
@@ -56,7 +55,6 @@ console.log = (...args) => console.error(...args);
|
|
|
56
55
|
console.info = (...args) => console.error(...args);
|
|
57
56
|
console.warn = (...args) => console.error(...args);
|
|
58
57
|
|
|
59
|
-
startMainThreadMonitor("electrobun.bun", { mirrorToConsole: true });
|
|
60
58
|
setConfigStoreHost(nodeConfigStoreHost);
|
|
61
59
|
setNativeIbkrGatewayModuleLoader(() => import("../../../plugins/ibkr/gateway-service-native"));
|
|
62
60
|
|
|
@@ -136,17 +134,21 @@ function forEachReadyWindowRpc(callback: (rpc: DesktopRpc) => void): void {
|
|
|
136
134
|
}
|
|
137
135
|
}
|
|
138
136
|
|
|
137
|
+
function openMainWindowDevTools(): void {
|
|
138
|
+
mainWindow?.webview.openDevTools();
|
|
139
|
+
}
|
|
140
|
+
|
|
139
141
|
function scopeClientId(rpc: DesktopRpc, id: string): string {
|
|
140
142
|
return `${getRpcWindowKey(rpc) ?? "window"}:${id}`;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
function syncActiveLayout(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
function syncActiveLayout(
|
|
146
|
+
config: AppConfig,
|
|
147
|
+
paneState: Record<string, PaneRuntimeState> = config.layouts[config.activeLayoutIndex]?.paneState ?? {},
|
|
148
|
+
focusedPaneId: string | null = config.layouts[config.activeLayoutIndex]?.focusedPaneId ?? null,
|
|
149
|
+
activePanel: "left" | "right" = config.layouts[config.activeLayoutIndex]?.activePanel ?? "left",
|
|
150
|
+
): AppConfig {
|
|
151
|
+
return syncConfigActiveLayoutState(config, paneState, focusedPaneId, activePanel);
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
function normalizeInitWindowTarget(
|
|
@@ -416,6 +418,25 @@ async function runDesktopUpdate(rpc: DesktopRpc, currentVersion: string): Promis
|
|
|
416
418
|
}
|
|
417
419
|
}
|
|
418
420
|
|
|
421
|
+
interface PluginStateBackendSetEntry {
|
|
422
|
+
pluginId: string;
|
|
423
|
+
key: string;
|
|
424
|
+
value: unknown;
|
|
425
|
+
schemaVersion?: number;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function normalizePluginStateSetEntry(entry: unknown): PluginStateBackendSetEntry | null {
|
|
429
|
+
if (!entry || typeof entry !== "object") return null;
|
|
430
|
+
const record = entry as Record<string, unknown>;
|
|
431
|
+
if (typeof record.pluginId !== "string" || typeof record.key !== "string") return null;
|
|
432
|
+
return {
|
|
433
|
+
pluginId: record.pluginId,
|
|
434
|
+
key: record.key,
|
|
435
|
+
value: record.value,
|
|
436
|
+
schemaVersion: typeof record.schemaVersion === "number" ? record.schemaVersion : undefined,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
419
440
|
function syncBackendCloudAuthState(pluginId: string, key: string, value: unknown): void {
|
|
420
441
|
if (pluginId !== "gloomberb-cloud" || (key !== "session" && key !== "resume:session")) return;
|
|
421
442
|
|
|
@@ -670,7 +691,7 @@ async function commitDesktopSnapshot(
|
|
|
670
691
|
snapshot: DesktopSharedStateSnapshot,
|
|
671
692
|
options: { persistConfig?: boolean; reconcileWindows?: boolean } = {},
|
|
672
693
|
): Promise<DesktopSharedStateSnapshot> {
|
|
673
|
-
const nextConfig = syncActiveLayout(snapshot.config);
|
|
694
|
+
const nextConfig = syncActiveLayout(snapshot.config, snapshot.paneState, snapshot.focusedPaneId, snapshot.activePanel);
|
|
674
695
|
setCurrentConfig(nextConfig);
|
|
675
696
|
desktopWorkspace = requireDesktopWorkspace();
|
|
676
697
|
desktopWorkspace.replaceConfig(nextConfig, { layoutChanged: snapshot.layoutChanged });
|
|
@@ -1049,6 +1070,19 @@ async function handleBackendRequest(
|
|
|
1049
1070
|
requireServices().persistence.pluginState.set(payload.pluginId as string, payload.key as string, payload.value, payload.schemaVersion as number | undefined);
|
|
1050
1071
|
syncBackendCloudAuthState(payload.pluginId as string, payload.key as string, payload.value);
|
|
1051
1072
|
return null;
|
|
1073
|
+
case "pluginState.setMany": {
|
|
1074
|
+
const entries = Array.isArray(payload.entries)
|
|
1075
|
+
? payload.entries.flatMap((entry) => {
|
|
1076
|
+
const normalized = normalizePluginStateSetEntry(entry);
|
|
1077
|
+
return normalized ? [normalized] : [];
|
|
1078
|
+
})
|
|
1079
|
+
: [];
|
|
1080
|
+
requireServices().persistence.pluginState.setMany(entries);
|
|
1081
|
+
for (const entry of entries) {
|
|
1082
|
+
syncBackendCloudAuthState(entry.pluginId, entry.key, entry.value);
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1052
1086
|
case "pluginState.delete":
|
|
1053
1087
|
requireServices().persistence.pluginState.delete(payload.pluginId as string, payload.key as string);
|
|
1054
1088
|
syncBackendCloudAuthState(payload.pluginId as string, payload.key as string, null);
|
|
@@ -1142,7 +1176,12 @@ Electrobun.events.on("context-menu-clicked", (event: unknown) => {
|
|
|
1142
1176
|
|
|
1143
1177
|
ApplicationMenu.on("application-menu-clicked", (event: unknown) => {
|
|
1144
1178
|
const command = applicationMenuCommand(event);
|
|
1145
|
-
if (!command
|
|
1179
|
+
if (!command) return;
|
|
1180
|
+
if (command.type === "open-devtools") {
|
|
1181
|
+
openMainWindowDevTools();
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (!readyWindowRpcs.has(MAIN_WINDOW_RPC_KEY)) return;
|
|
1146
1185
|
windowRpcs.get(MAIN_WINDOW_RPC_KEY)?.send["application-menu.select"]({ command });
|
|
1147
1186
|
});
|
|
1148
1187
|
|
|
@@ -24,6 +24,7 @@ import { getRendererBuiltinPlugins } from "../../../plugins/catalog-ui";
|
|
|
24
24
|
const servicesLog = debugLog.createLogger("services");
|
|
25
25
|
const ASSET_DATA_CAPABILITY_ID = "asset-data.asset-data-router";
|
|
26
26
|
const NEWS_CAPABILITY_ID = "news.core";
|
|
27
|
+
const PLUGIN_STATE_BACKEND_FLUSH_DELAY_MS = 25;
|
|
27
28
|
|
|
28
29
|
class RemoteTickerRepository {
|
|
29
30
|
async loadAllTickers(): Promise<TickerRecord[]> {
|
|
@@ -274,14 +275,19 @@ class RemoteSessionStore {
|
|
|
274
275
|
}
|
|
275
276
|
}
|
|
276
277
|
|
|
278
|
+
interface PluginStatePersistEntry {
|
|
279
|
+
pluginId: string;
|
|
280
|
+
key: string;
|
|
281
|
+
value: unknown;
|
|
282
|
+
schemaVersion: number;
|
|
283
|
+
}
|
|
284
|
+
|
|
277
285
|
class RemotePluginStateStore {
|
|
278
286
|
private readonly state = new Map<string, Map<string, unknown>>();
|
|
279
|
-
private readonly schedulers = new Map<string, ReturnType<typeof createPersistScheduler<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
schemaVersion: number;
|
|
284
|
-
}>>>();
|
|
287
|
+
private readonly schedulers = new Map<string, ReturnType<typeof createPersistScheduler<PluginStatePersistEntry>>>();
|
|
288
|
+
private readonly pendingBackendSaves = new Map<string, PluginStatePersistEntry>();
|
|
289
|
+
private backendSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
|
290
|
+
private backendSaveInFlight: Promise<void> = Promise.resolve();
|
|
285
291
|
|
|
286
292
|
constructor(initial: Record<string, Record<string, unknown>>) {
|
|
287
293
|
for (const [pluginId, values] of Object.entries(initial)) {
|
|
@@ -304,7 +310,11 @@ class RemotePluginStateStore {
|
|
|
304
310
|
delete(pluginId: string, key: string): void {
|
|
305
311
|
this.state.get(pluginId)?.delete(key);
|
|
306
312
|
this.getScheduler(pluginId, key).cancel();
|
|
307
|
-
|
|
313
|
+
this.pendingBackendSaves.delete(this.schedulerKey(pluginId, key));
|
|
314
|
+
void this.backendSaveInFlight
|
|
315
|
+
.catch(() => {})
|
|
316
|
+
.then(() => backendRequest("pluginState.delete", { pluginId, key }))
|
|
317
|
+
.catch(() => {});
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
keys(pluginId: string): string[] {
|
|
@@ -317,20 +327,52 @@ class RemotePluginStateStore {
|
|
|
317
327
|
|
|
318
328
|
async flush(): Promise<void> {
|
|
319
329
|
await Promise.all([...this.schedulers.values()].map((scheduler) => scheduler.flush()));
|
|
330
|
+
await this.flushBackendSaves();
|
|
320
331
|
}
|
|
321
332
|
|
|
322
333
|
private getScheduler(pluginId: string, key: string) {
|
|
323
|
-
const schedulerKey =
|
|
334
|
+
const schedulerKey = this.schedulerKey(pluginId, key);
|
|
324
335
|
let scheduler = this.schedulers.get(schedulerKey);
|
|
325
336
|
if (!scheduler) {
|
|
326
337
|
scheduler = createPersistScheduler({
|
|
327
338
|
delayMs: PLUGIN_STATE_SAVE_DEBOUNCE_MS,
|
|
328
|
-
save: (entry) =>
|
|
339
|
+
save: (entry) => {
|
|
340
|
+
this.scheduleBackendSave(entry);
|
|
341
|
+
},
|
|
329
342
|
});
|
|
330
343
|
this.schedulers.set(schedulerKey, scheduler);
|
|
331
344
|
}
|
|
332
345
|
return scheduler;
|
|
333
346
|
}
|
|
347
|
+
|
|
348
|
+
private schedulerKey(pluginId: string, key: string): string {
|
|
349
|
+
return `${pluginId}\u0000${key}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private scheduleBackendSave(entry: PluginStatePersistEntry): void {
|
|
353
|
+
this.pendingBackendSaves.set(this.schedulerKey(entry.pluginId, entry.key), entry);
|
|
354
|
+
if (this.backendSaveTimer) return;
|
|
355
|
+
this.backendSaveTimer = setTimeout(() => {
|
|
356
|
+
void this.flushBackendSaves();
|
|
357
|
+
}, PLUGIN_STATE_BACKEND_FLUSH_DELAY_MS);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private async flushBackendSaves(): Promise<void> {
|
|
361
|
+
if (this.backendSaveTimer) {
|
|
362
|
+
clearTimeout(this.backendSaveTimer);
|
|
363
|
+
this.backendSaveTimer = null;
|
|
364
|
+
}
|
|
365
|
+
if (this.pendingBackendSaves.size === 0) return this.backendSaveInFlight;
|
|
366
|
+
|
|
367
|
+
const entries = [...this.pendingBackendSaves.values()];
|
|
368
|
+
this.pendingBackendSaves.clear();
|
|
369
|
+
const save = this.backendSaveInFlight
|
|
370
|
+
.catch(() => {})
|
|
371
|
+
.then(() => backendRequest<void>("pluginState.setMany", { entries }))
|
|
372
|
+
.catch(() => {});
|
|
373
|
+
this.backendSaveInFlight = save;
|
|
374
|
+
return save;
|
|
375
|
+
}
|
|
334
376
|
}
|
|
335
377
|
|
|
336
378
|
class RemotePersistence {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
import { Electroview } from "electrobun/view";
|
|
3
|
-
import { measurePerfAsync } from "../../../utils/perf-marks";
|
|
4
3
|
import {
|
|
5
4
|
type ApplicationMenuSelectMessage,
|
|
6
5
|
type CapabilityEventMessage,
|
|
@@ -119,13 +118,11 @@ async function waitForBridgeReady(): Promise<void> {
|
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
export async function backendRequest<T = unknown>(method: string, payload: unknown = null): Promise<T> {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
128
|
-
}, { method });
|
|
121
|
+
await waitForBridgeReady();
|
|
122
|
+
const result = await rpc.request["backend.request"]({
|
|
123
|
+
method,
|
|
124
|
+
payload: encodeRpcValue(payload),
|
|
125
|
+
});
|
|
129
126
|
return decodeRpcValue<T>(result);
|
|
130
127
|
}
|
|
131
128
|
|
|
@@ -40,8 +40,8 @@ function controlBorderColor(focused = false, active = false): string {
|
|
|
40
40
|
|
|
41
41
|
function controlShadow(active = false): string {
|
|
42
42
|
return active
|
|
43
|
-
?
|
|
44
|
-
:
|
|
43
|
+
? `0 0 0 1px ${blendHex(colors.bg, colors.borderFocused, 0.18)}, inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.06)}`
|
|
44
|
+
: `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.04)}`;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function buttonPalette(props: Pick<ButtonProps, "variant" | "active" | "disabled">) {
|
|
@@ -76,7 +76,7 @@ function buttonPalette(props: Pick<ButtonProps, "variant" | "active" | "disabled
|
|
|
76
76
|
case "ghost":
|
|
77
77
|
return {
|
|
78
78
|
fg: colors.textDim,
|
|
79
|
-
bg: "
|
|
79
|
+
bg: "transparent",
|
|
80
80
|
border: panelBorder(),
|
|
81
81
|
};
|
|
82
82
|
case "secondary":
|
|
@@ -339,7 +339,7 @@ function listRowStyle(selected: boolean): CSSProperties {
|
|
|
339
339
|
return {
|
|
340
340
|
borderRadius: CONTROL_RADIUS,
|
|
341
341
|
border: `1px solid ${selected ? colors.borderFocused : "transparent"}`,
|
|
342
|
-
boxShadow: selected ?
|
|
342
|
+
boxShadow: selected ? `inset 0 1px 0 ${blendHex(colors.bg, colors.textBright, 0.06)}` : undefined,
|
|
343
343
|
cursor: "pointer",
|
|
344
344
|
};
|
|
345
345
|
}
|
|
@@ -5,7 +5,6 @@ import { App } from "../../../app";
|
|
|
5
5
|
import { UiHostProvider } from "../../../ui/host";
|
|
6
6
|
import { debugLog } from "../../../utils/debug-log";
|
|
7
7
|
import { measurePerfAsync } from "../../../utils/perf-marks";
|
|
8
|
-
import { startMainThreadMonitor } from "../../../utils/main-thread-monitor";
|
|
9
8
|
import { backendRequest, initElectrobunBackend } from "./backend-rpc";
|
|
10
9
|
import { installElectrobunAiHost } from "./ai-host";
|
|
11
10
|
import { installElectrobunBrokerRemoteClient } from "./broker-remote-client";
|
|
@@ -31,23 +30,9 @@ if (!rootElement) {
|
|
|
31
30
|
|
|
32
31
|
const root = createRoot(rootElement);
|
|
33
32
|
const bootLog = debugLog.createLogger("electrobun-web-boot");
|
|
34
|
-
const ELECTROBUN_WEB_CONSOLE_LOG_SOURCES = [
|
|
35
|
-
"app",
|
|
36
|
-
"main-thread",
|
|
37
|
-
"perf",
|
|
38
|
-
"refresh-queue",
|
|
39
|
-
"services",
|
|
40
|
-
"startup",
|
|
41
|
-
"electrobun-web-boot",
|
|
42
|
-
];
|
|
43
|
-
debugLog.mirrorToConsole({
|
|
44
|
-
sources: ELECTROBUN_WEB_CONSOLE_LOG_SOURCES,
|
|
45
|
-
});
|
|
46
|
-
const stopMainThreadMonitor = startMainThreadMonitor("electrobun.web", { mirrorToConsole: true });
|
|
47
33
|
|
|
48
34
|
rootElement.tabIndex = -1;
|
|
49
35
|
root.render(<div className="gloom-loading">Starting Gloomberb...</div>);
|
|
50
|
-
bootLog.warn("diagnostic console mirror enabled", { sources: ELECTROBUN_WEB_CONSOLE_LOG_SOURCES });
|
|
51
36
|
|
|
52
37
|
function focusWebSurface(): void {
|
|
53
38
|
window.focus();
|
|
@@ -111,7 +96,6 @@ async function boot() {
|
|
|
111
96
|
}
|
|
112
97
|
|
|
113
98
|
boot().catch((error) => {
|
|
114
|
-
stopMainThreadMonitor();
|
|
115
99
|
root.render(
|
|
116
100
|
<div className="gloom-fatal">
|
|
117
101
|
<h1>Gloomberb failed to start</h1>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@keyframes gloom-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
2
|
-
html, body, #root { width: 100%; height: 100%; min-height: 100%; overflow: hidden; background:
|
|
2
|
+
html, body, #root { width: 100%; height: 100%; min-height: 100%; overflow: hidden; background: var(--gloom-bg); }
|
|
3
3
|
#root:focus { outline: none; }
|
|
4
4
|
body {
|
|
5
5
|
--cell-w: 8px;
|
|
@@ -8,7 +8,7 @@ body {
|
|
|
8
8
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
9
9
|
font-size: 12px;
|
|
10
10
|
line-height: var(--cell-h);
|
|
11
|
-
color:
|
|
11
|
+
color: var(--gloom-text);
|
|
12
12
|
cursor: default;
|
|
13
13
|
user-select: none;
|
|
14
14
|
color-scheme: dark;
|
|
@@ -62,26 +62,26 @@ button, [data-gloom-interactive="true"] { cursor: pointer; }
|
|
|
62
62
|
border: 0;
|
|
63
63
|
border-radius: 6px;
|
|
64
64
|
overflow: hidden;
|
|
65
|
-
box-shadow: 0 18px 38px
|
|
65
|
+
box-shadow: 0 18px 38px color-mix(in srgb, var(--gloom-bg) 46%, transparent);
|
|
66
66
|
}
|
|
67
67
|
[data-gloom-role="pane-window"][data-floating="true"]::after {
|
|
68
68
|
content: "";
|
|
69
69
|
position: absolute;
|
|
70
70
|
inset: 0;
|
|
71
|
-
border: 1px solid var(--pane-border-color,
|
|
71
|
+
border: 1px solid var(--pane-border-color, var(--gloom-border));
|
|
72
72
|
border-radius: inherit;
|
|
73
73
|
pointer-events: none;
|
|
74
74
|
z-index: 100;
|
|
75
75
|
}
|
|
76
76
|
[data-gloom-role="pane-window"][data-floating="true"][data-focused="true"] {
|
|
77
|
-
box-shadow: 0 18px 40px
|
|
77
|
+
box-shadow: 0 18px 40px color-mix(in srgb, var(--gloom-bg) 50%, transparent);
|
|
78
78
|
}
|
|
79
79
|
[data-gloom-role="pane-window"][data-floating="false"] {
|
|
80
80
|
border: 0;
|
|
81
81
|
border-radius: 0;
|
|
82
82
|
}
|
|
83
83
|
[data-gloom-role="pane-window"][data-floating="false"][data-focused="true"] {
|
|
84
|
-
box-shadow: inset 0 0 0 1px var(--pane-border-color,
|
|
84
|
+
box-shadow: inset 0 0 0 1px var(--pane-border-color, var(--gloom-border-focused));
|
|
85
85
|
}
|
|
86
86
|
[data-gloom-role="pane-header"] {
|
|
87
87
|
align-items: center;
|
|
@@ -125,7 +125,7 @@ body.gloom-dragging {
|
|
|
125
125
|
min-height: var(--cell-h);
|
|
126
126
|
max-height: var(--cell-h);
|
|
127
127
|
align-items: center;
|
|
128
|
-
border-top: 1px solid color-mix(in srgb,
|
|
128
|
+
border-top: 1px solid color-mix(in srgb, var(--gloom-border) 72%, transparent);
|
|
129
129
|
font-size: 11.5px;
|
|
130
130
|
}
|
|
131
131
|
[data-gloom-role="status-bar"] span {
|
|
@@ -137,7 +137,7 @@ body.gloom-dragging {
|
|
|
137
137
|
min-height: var(--cell-h);
|
|
138
138
|
max-height: var(--cell-h);
|
|
139
139
|
align-items: center;
|
|
140
|
-
border-top: 1px solid color-mix(in srgb, var(--pane-footer-border-color,
|
|
140
|
+
border-top: 1px solid color-mix(in srgb, var(--pane-footer-border-color, var(--gloom-border)) 72%, transparent);
|
|
141
141
|
font-size: 11.5px;
|
|
142
142
|
overflow: hidden;
|
|
143
143
|
}
|
|
@@ -153,7 +153,7 @@ body.gloom-dragging {
|
|
|
153
153
|
border-radius: 4px;
|
|
154
154
|
}
|
|
155
155
|
[data-gloom-role="pane-hint"]:hover {
|
|
156
|
-
background:
|
|
156
|
+
background: color-mix(in srgb, var(--gloom-text-bright) 8%, transparent);
|
|
157
157
|
}
|
|
158
158
|
[data-gloom-role="pane-action"],
|
|
159
159
|
[data-gloom-role="pane-close"] {
|
|
@@ -162,7 +162,7 @@ body.gloom-dragging {
|
|
|
162
162
|
}
|
|
163
163
|
[data-gloom-role="pane-action"]:hover,
|
|
164
164
|
[data-gloom-role="pane-close"]:hover {
|
|
165
|
-
background:
|
|
165
|
+
background: color-mix(in srgb, var(--gloom-text-bright) 8%, transparent);
|
|
166
166
|
}
|
|
167
167
|
[data-gloom-role="resize-handle"] {
|
|
168
168
|
cursor: nwse-resize;
|
|
@@ -191,7 +191,7 @@ body.gloom-dragging {
|
|
|
191
191
|
cursor: pointer;
|
|
192
192
|
}
|
|
193
193
|
[data-gloom-role="tab-close"]:hover {
|
|
194
|
-
background:
|
|
194
|
+
background: color-mix(in srgb, var(--gloom-text-bright) 10%, transparent);
|
|
195
195
|
}
|
|
196
196
|
[data-gloom-scrollbar-x],
|
|
197
197
|
[data-gloom-scrollbar-y] {
|
|
@@ -199,7 +199,7 @@ body.gloom-dragging {
|
|
|
199
199
|
scrollbar-width: thin;
|
|
200
200
|
}
|
|
201
201
|
[data-gloom-scrollbar-active="true"] {
|
|
202
|
-
scrollbar-color:
|
|
202
|
+
scrollbar-color: color-mix(in srgb, var(--gloom-text) 26%, transparent) transparent;
|
|
203
203
|
}
|
|
204
204
|
[data-gloom-scrollbar-x]::-webkit-scrollbar,
|
|
205
205
|
[data-gloom-scrollbar-y]::-webkit-scrollbar {
|
|
@@ -227,7 +227,7 @@ body.gloom-dragging {
|
|
|
227
227
|
}
|
|
228
228
|
[data-gloom-scrollbar-active="true"]::-webkit-scrollbar-thumb,
|
|
229
229
|
[data-gloom-scrollbar-active="true"]::-webkit-scrollbar-thumb:hover {
|
|
230
|
-
background:
|
|
230
|
+
background: color-mix(in srgb, var(--gloom-text) 24%, transparent);
|
|
231
231
|
background-clip: content-box;
|
|
232
232
|
}
|
|
233
233
|
[data-gloom-scrollbar-x]::-webkit-scrollbar-corner,
|
|
@@ -284,8 +284,8 @@ body.gloom-dragging {
|
|
|
284
284
|
top: 50%;
|
|
285
285
|
height: 1px;
|
|
286
286
|
}
|
|
287
|
-
.gloom-loading, .gloom-fatal { padding: 24px; color:
|
|
288
|
-
.gloom-fatal pre { white-space: pre-wrap; color:
|
|
287
|
+
.gloom-loading, .gloom-fatal { padding: 24px; color: var(--gloom-text); }
|
|
288
|
+
.gloom-fatal pre { white-space: pre-wrap; color: var(--gloom-negative); }
|
|
289
289
|
.gloom-toast-viewport {
|
|
290
290
|
position: fixed;
|
|
291
291
|
right: 16px;
|
|
@@ -300,7 +300,7 @@ body.gloom-dragging {
|
|
|
300
300
|
max-width: 420px;
|
|
301
301
|
padding: 10px 12px;
|
|
302
302
|
border: 1px solid;
|
|
303
|
-
box-shadow: 0 12px 24px
|
|
303
|
+
box-shadow: 0 12px 24px color-mix(in srgb, var(--gloom-bg) 28%, transparent);
|
|
304
304
|
}
|
|
305
305
|
.gloom-toast-action {
|
|
306
306
|
margin-top: 8px;
|
|
@@ -313,17 +313,19 @@ body.gloom-dragging {
|
|
|
313
313
|
display: flex;
|
|
314
314
|
align-items: center;
|
|
315
315
|
justify-content: center;
|
|
316
|
-
background:
|
|
316
|
+
background: color-mix(in srgb, var(--gloom-bg) 64%, transparent);
|
|
317
317
|
z-index: 9999;
|
|
318
318
|
}
|
|
319
319
|
.gloom-dialog {
|
|
320
320
|
max-width: min(920px, calc(100vw - 48px));
|
|
321
321
|
max-height: calc(100vh - 48px);
|
|
322
322
|
overflow: auto;
|
|
323
|
-
border: 1px solid
|
|
323
|
+
border: 1px solid var(--gloom-border);
|
|
324
324
|
border-radius: 6px;
|
|
325
|
-
background:
|
|
326
|
-
color:
|
|
325
|
+
background: var(--gloom-panel);
|
|
326
|
+
color: var(--gloom-text);
|
|
327
327
|
padding: 0;
|
|
328
|
-
box-shadow:
|
|
328
|
+
box-shadow:
|
|
329
|
+
0 18px 48px color-mix(in srgb, var(--gloom-bg) 46%, transparent),
|
|
330
|
+
inset 0 1px 0 color-mix(in srgb, var(--gloom-text-bright) 5%, transparent);
|
|
329
331
|
}
|
|
@@ -129,7 +129,7 @@ function commonStyle(props: Record<string, unknown>): CSSProperties {
|
|
|
129
129
|
marginBottom: cellInset(props.marginBottom ?? props.marginY ?? props.margin, "y"),
|
|
130
130
|
gap: typeof props.gap === "number" ? `${props.gap * gapUnit}px` : props.gap as CSSProperties["gap"],
|
|
131
131
|
overflow: props.overflow as CSSProperties["overflow"],
|
|
132
|
-
border: props.border ? `1px solid ${typeof props.borderColor === "string" ? props.borderColor : "
|
|
132
|
+
border: props.border ? `1px solid ${typeof props.borderColor === "string" ? props.borderColor : "var(--gloom-border)"}` : undefined,
|
|
133
133
|
boxSizing: "border-box",
|
|
134
134
|
minInlineSize: zeroMinInlineSize ? 0 : undefined,
|
|
135
135
|
minBlockSize: zeroMinBlockSize ? 0 : undefined,
|
|
@@ -551,7 +551,7 @@ function ChartCrosshair({
|
|
|
551
551
|
height: 7,
|
|
552
552
|
borderRadius: 7,
|
|
553
553
|
border: `1px solid ${crosshair.color}`,
|
|
554
|
-
backgroundColor:
|
|
554
|
+
backgroundColor: `color-mix(in srgb, ${crosshair.color} 16%, transparent)`,
|
|
555
555
|
boxSizing: "border-box",
|
|
556
556
|
transform: "translate(-50%, -50%)",
|
|
557
557
|
pointerEvents: "none",
|
|
@@ -610,7 +610,7 @@ function textInputStyle(props: Record<string, unknown>, multiline: boolean): CSS
|
|
|
610
610
|
resize: "none",
|
|
611
611
|
border: "none",
|
|
612
612
|
outline: "none",
|
|
613
|
-
color: typeof textColor === "string" ? textColor : "
|
|
613
|
+
color: typeof textColor === "string" ? textColor : "var(--gloom-text)",
|
|
614
614
|
backgroundColor: typeof backgroundColor === "string" ? backgroundColor : "transparent",
|
|
615
615
|
whiteSpace: multiline && props.wrapText ? "pre-wrap" : "pre",
|
|
616
616
|
overflow: multiline ? "auto" : "hidden",
|
|
@@ -1209,7 +1209,9 @@ function WebTabs({
|
|
|
1209
1209
|
|
|
1210
1210
|
const resolveTabBackground = (active: boolean, hovered: boolean) => {
|
|
1211
1211
|
if (active && variant === "pill") {
|
|
1212
|
-
return hovered
|
|
1212
|
+
return hovered
|
|
1213
|
+
? `color-mix(in srgb, ${palette.activeBg} 76%, ${palette.hoverBg})`
|
|
1214
|
+
: palette.activeBg;
|
|
1213
1215
|
}
|
|
1214
1216
|
return hovered ? palette.hoverBg : "transparent";
|
|
1215
1217
|
};
|
|
@@ -4,6 +4,7 @@ import type { ReactNode } from "react";
|
|
|
4
4
|
import { resetTerminalInputState } from "../../utils/terminal-input-reset";
|
|
5
5
|
import type { KeyEventLike } from "../../react/input";
|
|
6
6
|
import type { NativeRendererHost, RendererHost } from "../../ui/host";
|
|
7
|
+
import { colors } from "../../theme/colors";
|
|
7
8
|
|
|
8
9
|
export { useKeyboard, useRenderer, useTerminalDimensions };
|
|
9
10
|
export type { CliRenderer };
|
|
@@ -54,7 +55,7 @@ export async function createOpenTuiHost(): Promise<OpenTuiHost> {
|
|
|
54
55
|
|
|
55
56
|
const renderer = await createCliRenderer({
|
|
56
57
|
exitOnCtrlC: true,
|
|
57
|
-
backgroundColor:
|
|
58
|
+
backgroundColor: colors.bg,
|
|
58
59
|
enableMouseMovement: true,
|
|
59
60
|
});
|
|
60
61
|
const root = createRoot(renderer);
|
|
@@ -66,7 +66,7 @@ export async function startOpenTuiApp(): Promise<void> {
|
|
|
66
66
|
<OpenTuiDialogHostProvider
|
|
67
67
|
size="medium"
|
|
68
68
|
dialogOptions={{ style: { backgroundColor: colors.bg, borderColor: colors.borderFocused, borderStyle: "single", paddingX: 2, paddingY: 1 } }}
|
|
69
|
-
backdropColor=
|
|
69
|
+
backdropColor={colors.bg}
|
|
70
70
|
backdropOpacity={0.8}
|
|
71
71
|
>
|
|
72
72
|
<App
|
|
@@ -4,6 +4,7 @@ import { testRender as openTuiTestRender } from "@opentui/react/test-utils";
|
|
|
4
4
|
import { createRoot as openTuiCreateRoot, useRenderer } from "@opentui/react";
|
|
5
5
|
import { UiHostProvider, type NativeRendererHost, type RendererHost } from "../../ui";
|
|
6
6
|
import { ToastHostProvider } from "../../ui/toast";
|
|
7
|
+
import { colors } from "../../theme/colors";
|
|
7
8
|
import { OpenTuiInputHostProvider } from "./input-host";
|
|
8
9
|
import { openTuiUiHost } from "./ui-host";
|
|
9
10
|
import { OpenTuiDialogHostProvider } from "./dialog-host";
|
|
@@ -14,8 +15,8 @@ export function TestDialogProvider({ children }: { children: ReactNode }) {
|
|
|
14
15
|
<DialogProvider
|
|
15
16
|
dialogOptions={{
|
|
16
17
|
style: {
|
|
17
|
-
backgroundColor:
|
|
18
|
-
borderColor:
|
|
18
|
+
backgroundColor: colors.bg,
|
|
19
|
+
borderColor: colors.borderFocused,
|
|
19
20
|
borderStyle: "single",
|
|
20
21
|
},
|
|
21
22
|
}}
|
|
@@ -54,9 +55,9 @@ export function OpenTuiTestProviders({ children }: { children: ReactNode }) {
|
|
|
54
55
|
<OpenTuiInputHostProvider>
|
|
55
56
|
<ToastHostProvider host={openTuiToastHost}>
|
|
56
57
|
<OpenTuiDialogHostProvider
|
|
57
|
-
backgroundColor=
|
|
58
|
-
containerBorderColor=
|
|
59
|
-
focusedBorderColor=
|
|
58
|
+
backgroundColor={colors.bg}
|
|
59
|
+
containerBorderColor={colors.border}
|
|
60
|
+
focusedBorderColor={colors.borderFocused}
|
|
60
61
|
>
|
|
61
62
|
{children}
|
|
62
63
|
</OpenTuiDialogHostProvider>
|
|
@@ -37,11 +37,11 @@ interface RefreshPlanEntry {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const MAX_BACKGROUND_WARMUP_TICKERS = 12;
|
|
40
|
-
const
|
|
41
|
-
"market_cap",
|
|
40
|
+
const PORTFOLIO_FULL_SNAPSHOT_COLUMN_IDS = new Set([
|
|
42
41
|
"pe",
|
|
43
42
|
"forward_pe",
|
|
44
43
|
"dividend_yield",
|
|
44
|
+
"sparkline",
|
|
45
45
|
]);
|
|
46
46
|
const startupLog = debugLog.createLogger("startup");
|
|
47
47
|
|
|
@@ -157,7 +157,7 @@ function buildRefreshPlan(
|
|
|
157
157
|
const columnIds = Array.isArray(instance.settings?.columnIds)
|
|
158
158
|
? instance.settings.columnIds.filter((value): value is string => typeof value === "string")
|
|
159
159
|
: [];
|
|
160
|
-
return columnIds.some((columnId) =>
|
|
160
|
+
return columnIds.some((columnId) => PORTFOLIO_FULL_SNAPSHOT_COLUMN_IDS.has(columnId))
|
|
161
161
|
? "financials"
|
|
162
162
|
: "quote";
|
|
163
163
|
};
|