gloomberb 0.7.1 → 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 +47 -7
- 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 +67 -11
- 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/ui/host.tsx +8 -2
- package/src/ui/index.tsx +1 -0
- 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
package/src/utils/api-client.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface ChatMessage {
|
|
|
48
48
|
replyToId: string | null;
|
|
49
49
|
createdAt: string;
|
|
50
50
|
user: { id: string; username: string; displayName: string };
|
|
51
|
-
replyTo?: { content: string; user: { username: string } } | null;
|
|
51
|
+
replyTo?: { content: string; user: { id?: string; username: string } } | null;
|
|
52
52
|
clientStatus?: "sending" | "failed";
|
|
53
53
|
clientError?: string | null;
|
|
54
54
|
}
|
|
@@ -59,6 +59,29 @@ export interface ChatChannel {
|
|
|
59
59
|
created_at: string;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
export interface ChatChannelState {
|
|
63
|
+
channelId: string;
|
|
64
|
+
notificationsEnabled: boolean;
|
|
65
|
+
lastReadMessageId: string | null;
|
|
66
|
+
unreadCount: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ChatNotification {
|
|
70
|
+
id: string;
|
|
71
|
+
type: "reply" | "mention" | "channel";
|
|
72
|
+
channelId: string;
|
|
73
|
+
messageId: string;
|
|
74
|
+
createdAt: string;
|
|
75
|
+
message: ChatMessage;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ChatStateResponse {
|
|
79
|
+
channels: ChatChannel[];
|
|
80
|
+
onlineCount: number;
|
|
81
|
+
channelStates: ChatChannelState[];
|
|
82
|
+
notifications: ChatNotification[];
|
|
83
|
+
}
|
|
84
|
+
|
|
62
85
|
export interface AuthUser {
|
|
63
86
|
id: string;
|
|
64
87
|
name: string;
|
|
@@ -352,6 +375,21 @@ function normalizeChatMessages(messages: ChatMessage[]): ChatMessage[] {
|
|
|
352
375
|
return messages.map((message) => normalizeChatMessage(message));
|
|
353
376
|
}
|
|
354
377
|
|
|
378
|
+
function normalizeChatNotification(notification: ChatNotification): ChatNotification {
|
|
379
|
+
return {
|
|
380
|
+
...notification,
|
|
381
|
+
createdAt: normalizeTimestamp(notification.createdAt),
|
|
382
|
+
message: normalizeChatMessage(notification.message),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function normalizeChatState(response: ChatStateResponse): ChatStateResponse {
|
|
387
|
+
return {
|
|
388
|
+
...response,
|
|
389
|
+
notifications: response.notifications.map(normalizeChatNotification),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
355
393
|
function normalizeTweet(tweet: CloudTweetPayload): CloudTweetPayload {
|
|
356
394
|
return {
|
|
357
395
|
...tweet,
|
|
@@ -370,6 +408,8 @@ function normalizeTweetSearchResponse(response: CloudTweetSearchResponse): Cloud
|
|
|
370
408
|
}
|
|
371
409
|
|
|
372
410
|
type ChannelListener = (message: ChatMessage) => void;
|
|
411
|
+
type ChatNotificationListener = (notification: ChatNotification) => void;
|
|
412
|
+
type ChatPresenceListener = (onlineCount: number) => void;
|
|
373
413
|
type QuoteListener = (target: QuoteStreamTarget, quote: CloudQuotePayload) => void;
|
|
374
414
|
type SessionCookieName = (typeof SESSION_COOKIE_NAMES)[number];
|
|
375
415
|
|
|
@@ -408,6 +448,8 @@ class GloomApiClient {
|
|
|
408
448
|
private reconnectDelayMs = 1000;
|
|
409
449
|
|
|
410
450
|
private readonly channelListeners = new Map<string, Set<ChannelListener>>();
|
|
451
|
+
private readonly chatNotificationListeners = new Set<ChatNotificationListener>();
|
|
452
|
+
private readonly chatPresenceListeners = new Set<ChatPresenceListener>();
|
|
411
453
|
private readonly quoteListeners = new Map<string, Set<QuoteListener>>();
|
|
412
454
|
private readonly quoteTargets = new Map<string, QuoteStreamTarget>();
|
|
413
455
|
private readonly pendingQuoteSubscribes = new Map<string, QuoteStreamTarget>();
|
|
@@ -650,7 +692,12 @@ class GloomApiClient {
|
|
|
650
692
|
if (this.ws?.readyState !== WebSocket.OPEN) return;
|
|
651
693
|
if (payload && typeof payload === "object" && "type" in (payload as Record<string, unknown>)) {
|
|
652
694
|
const type = (payload as Record<string, unknown>).type;
|
|
653
|
-
if (
|
|
695
|
+
if (
|
|
696
|
+
type === "market.subscribe"
|
|
697
|
+
|| type === "market.unsubscribe"
|
|
698
|
+
|| type === "chat.subscribe"
|
|
699
|
+
|| type === "chat.unsubscribe"
|
|
700
|
+
) {
|
|
654
701
|
cloudApiLog.info("send websocket message", payload);
|
|
655
702
|
}
|
|
656
703
|
}
|
|
@@ -773,6 +820,21 @@ class GloomApiClient {
|
|
|
773
820
|
return;
|
|
774
821
|
}
|
|
775
822
|
|
|
823
|
+
if (parsed?.type === "chat.notification" && parsed.data) {
|
|
824
|
+
const notification = normalizeChatNotification(parsed.data as ChatNotification);
|
|
825
|
+
for (const listener of this.chatNotificationListeners) {
|
|
826
|
+
listener(notification);
|
|
827
|
+
}
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (parsed?.type === "chat.presence" && typeof parsed.onlineCount === "number") {
|
|
832
|
+
for (const listener of this.chatPresenceListeners) {
|
|
833
|
+
listener(parsed.onlineCount);
|
|
834
|
+
}
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
|
|
776
838
|
if (parsed?.type === "market.quote" && parsed.quote && typeof parsed.symbol === "string") {
|
|
777
839
|
const key = marketKey(parsed.symbol, parsed.exchange);
|
|
778
840
|
const target = this.quoteTargets.get(key) ?? {
|
|
@@ -853,6 +915,32 @@ class GloomApiClient {
|
|
|
853
915
|
return this.request<ChatChannel[]>("/chat/channels");
|
|
854
916
|
}
|
|
855
917
|
|
|
918
|
+
async getChatPresence(): Promise<{ onlineCount: number }> {
|
|
919
|
+
return this.request<{ onlineCount: number }>("/chat/presence");
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async getChatState(): Promise<ChatStateResponse> {
|
|
923
|
+
const state = await this.request<ChatStateResponse>("/chat/state");
|
|
924
|
+
return normalizeChatState(state);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
async updateChatChannelState(
|
|
928
|
+
channelId: string,
|
|
929
|
+
body: { notificationsEnabled?: boolean; readThroughMessageId?: string },
|
|
930
|
+
): Promise<ChatChannelState> {
|
|
931
|
+
return this.request<ChatChannelState>(`/chat/channels/${channelId}/state`, {
|
|
932
|
+
method: "PATCH",
|
|
933
|
+
body: JSON.stringify(body),
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async markChatNotificationsDelivered(notificationIds: string[]): Promise<{ delivered: number }> {
|
|
938
|
+
return this.request<{ delivered: number }>("/chat/notifications/delivered", {
|
|
939
|
+
method: "POST",
|
|
940
|
+
body: JSON.stringify({ notificationIds }),
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
|
|
856
944
|
async getMessages(
|
|
857
945
|
channelId: string,
|
|
858
946
|
opts?: { after?: string; before?: string; limit?: number },
|
|
@@ -866,10 +954,10 @@ class GloomApiClient {
|
|
|
866
954
|
return normalizeChatMessages(messages);
|
|
867
955
|
}
|
|
868
956
|
|
|
869
|
-
async sendMessage(channelId: string, content: string, replyToId?: string): Promise<ChatMessage> {
|
|
957
|
+
async sendMessage(channelId: string, content: string, replyToId?: string, clientMessageId?: string): Promise<ChatMessage> {
|
|
870
958
|
const message = await this.request<ChatMessage>(`/chat/channels/${channelId}/messages`, {
|
|
871
959
|
method: "POST",
|
|
872
|
-
body: JSON.stringify({ content, replyToId }),
|
|
960
|
+
body: JSON.stringify({ content, replyToId, clientMessageId }),
|
|
873
961
|
});
|
|
874
962
|
return normalizeChatMessage(message);
|
|
875
963
|
}
|
|
@@ -878,7 +966,7 @@ class GloomApiClient {
|
|
|
878
966
|
channelId: string,
|
|
879
967
|
onMessage: (msg: ChatMessage) => void,
|
|
880
968
|
onError?: (err: string) => void,
|
|
881
|
-
): { send: (content: string, replyToId?: string) => Promise<ChatMessage>; close: () => void } {
|
|
969
|
+
): { send: (content: string, replyToId?: string, clientMessageId?: string) => Promise<ChatMessage>; close: () => void } {
|
|
882
970
|
if (!channelId) {
|
|
883
971
|
return {
|
|
884
972
|
send: async () => {
|
|
@@ -898,9 +986,9 @@ class GloomApiClient {
|
|
|
898
986
|
}
|
|
899
987
|
|
|
900
988
|
return {
|
|
901
|
-
send: async (content: string, replyToId?: string) => {
|
|
989
|
+
send: async (content: string, replyToId?: string, clientMessageId?: string) => {
|
|
902
990
|
try {
|
|
903
|
-
const message = await this.sendMessage(channelId, content, replyToId);
|
|
991
|
+
const message = await this.sendMessage(channelId, content, replyToId, clientMessageId);
|
|
904
992
|
onMessage(message);
|
|
905
993
|
return message;
|
|
906
994
|
} catch (error) {
|
|
@@ -914,6 +1002,7 @@ class GloomApiClient {
|
|
|
914
1002
|
current.delete(onMessage);
|
|
915
1003
|
if (current.size === 0) {
|
|
916
1004
|
this.channelListeners.delete(channelId);
|
|
1005
|
+
this.sendSocketMessage({ type: "chat.unsubscribe", channelId });
|
|
917
1006
|
}
|
|
918
1007
|
if (!this.shouldKeepSocketOpen()) {
|
|
919
1008
|
this.teardownSocket();
|
|
@@ -922,6 +1011,20 @@ class GloomApiClient {
|
|
|
922
1011
|
};
|
|
923
1012
|
}
|
|
924
1013
|
|
|
1014
|
+
subscribeChatNotifications(listener: ChatNotificationListener): () => void {
|
|
1015
|
+
this.chatNotificationListeners.add(listener);
|
|
1016
|
+
return () => {
|
|
1017
|
+
this.chatNotificationListeners.delete(listener);
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
subscribeChatPresence(listener: ChatPresenceListener): () => void {
|
|
1022
|
+
this.chatPresenceListeners.add(listener);
|
|
1023
|
+
return () => {
|
|
1024
|
+
this.chatPresenceListeners.delete(listener);
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
925
1028
|
subscribeQuotes(
|
|
926
1029
|
targets: QuoteStreamTarget[],
|
|
927
1030
|
onQuote: (target: QuoteStreamTarget, quote: CloudQuotePayload) => void,
|
|
@@ -997,6 +1100,8 @@ class GloomApiClient {
|
|
|
997
1100
|
channelTargets: this.channelListeners.size,
|
|
998
1101
|
});
|
|
999
1102
|
this.channelListeners.clear();
|
|
1103
|
+
this.chatNotificationListeners.clear();
|
|
1104
|
+
this.chatPresenceListeners.clear();
|
|
1000
1105
|
this.quoteListeners.clear();
|
|
1001
1106
|
this.quoteTargets.clear();
|
|
1002
1107
|
this.pendingQuoteSubscribes.clear();
|
|
@@ -5,7 +5,18 @@ export function fuzzyMatch(query: string, target: string): { match: boolean; sco
|
|
|
5
5
|
const t = target.toLowerCase();
|
|
6
6
|
|
|
7
7
|
if (q.length === 0) return { match: true, score: 0 };
|
|
8
|
-
if (t
|
|
8
|
+
if (t === q) return { match: true, score: 2500 - q.length };
|
|
9
|
+
|
|
10
|
+
const tokens = t.split(/\s+/).filter(Boolean);
|
|
11
|
+
const exactTokenIndex = tokens.findIndex((token) => token === q);
|
|
12
|
+
if (exactTokenIndex >= 0) return { match: true, score: 2200 - q.length - exactTokenIndex };
|
|
13
|
+
|
|
14
|
+
const prefixTokenIndex = tokens.findIndex((token) => token.startsWith(q));
|
|
15
|
+
if (prefixTokenIndex >= 0) {
|
|
16
|
+
const token = tokens[prefixTokenIndex]!;
|
|
17
|
+
return { match: true, score: 1000 - q.length - prefixTokenIndex - (token.length - q.length) };
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
if (t.includes(q)) return { match: true, score: 500 - q.length }; // substring match
|
|
10
21
|
|
|
11
22
|
let qi = 0;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface KeyboardModifierEventLike {
|
|
2
|
+
ctrl?: boolean;
|
|
3
|
+
meta?: boolean;
|
|
4
|
+
super?: boolean;
|
|
5
|
+
alt?: boolean;
|
|
6
|
+
option?: boolean;
|
|
7
|
+
shift?: boolean;
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function hasKeyboardModifier(event: KeyboardModifierEventLike): boolean {
|
|
12
|
+
return !!(
|
|
13
|
+
event.ctrl
|
|
14
|
+
|| event.meta
|
|
15
|
+
|| event.super
|
|
16
|
+
|| event.alt
|
|
17
|
+
|| event.option
|
|
18
|
+
|| event.shift
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isPlainKeyboardEvent(event: KeyboardModifierEventLike): boolean {
|
|
23
|
+
return !hasKeyboardModifier(event);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isPlainKey(
|
|
27
|
+
event: KeyboardModifierEventLike,
|
|
28
|
+
...names: string[]
|
|
29
|
+
): boolean {
|
|
30
|
+
return isPlainKeyboardEvent(event) && names.includes(event.name ?? "");
|
|
31
|
+
}
|
|
@@ -82,7 +82,7 @@ describe("ticker-search utilities", () => {
|
|
|
82
82
|
]),
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
expect(results.map((item) => item.id)).toEqual(["goto:AAPL", "search:APP"]);
|
|
85
|
+
expect(results.map((item) => item.id)).toEqual(["goto:AAPL", "search:APP:NASDAQ:TEST"]);
|
|
86
86
|
expect(findExactTickerSearchMatch(results, "AAPL")?.id).toBe("goto:AAPL");
|
|
87
87
|
});
|
|
88
88
|
|
|
@@ -97,7 +97,7 @@ describe("ticker-search utilities", () => {
|
|
|
97
97
|
]),
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
expect(results.map((item) => item.id)).toEqual(["goto:AAPL", "search:APLE"]);
|
|
100
|
+
expect(results.map((item) => item.id)).toEqual(["goto:AAPL", "search:APLE:NASDAQ:TEST"]);
|
|
101
101
|
expect(results[0]).toMatchObject({
|
|
102
102
|
id: "goto:AAPL",
|
|
103
103
|
detail: "Apple Inc",
|
|
@@ -140,7 +140,7 @@ export function createProviderTickerSearchCandidates(
|
|
|
140
140
|
const symbol = getSearchResultSymbol(result);
|
|
141
141
|
const saved = localTickers.has(symbol);
|
|
142
142
|
return [{
|
|
143
|
-
id:
|
|
143
|
+
id: buildProviderCandidateId(result, symbol),
|
|
144
144
|
label: symbol,
|
|
145
145
|
symbol,
|
|
146
146
|
detail: [result.name, result.brokerLabel, result.type].filter(Boolean).join(" | "),
|
|
@@ -357,6 +357,7 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
|
|
|
357
357
|
item,
|
|
358
358
|
index,
|
|
359
359
|
normalizedSymbol: normalizeSearchText(item.symbol || item.label),
|
|
360
|
+
symbolMatchRank: scoreSymbolMatchRank(intent, item),
|
|
360
361
|
textScore,
|
|
361
362
|
score: textScore + priorityScore + (textScore > 0 && saved ? 900 : 0),
|
|
362
363
|
};
|
|
@@ -375,6 +376,7 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
|
|
|
375
376
|
return !matchedLocalSymbols.has(normalizedSymbol);
|
|
376
377
|
})
|
|
377
378
|
.sort((a, b) => {
|
|
379
|
+
if (b.symbolMatchRank !== a.symbolMatchRank) return b.symbolMatchRank - a.symbolMatchRank;
|
|
378
380
|
if (b.score !== a.score) return b.score - a.score;
|
|
379
381
|
const aSaved = isSavedSearchItem(a.item);
|
|
380
382
|
const bSaved = isSavedSearchItem(b.item);
|
|
@@ -463,6 +465,16 @@ function buildProviderSearchResultKey(result: InstrumentSearchResult): string {
|
|
|
463
465
|
].join("|");
|
|
464
466
|
}
|
|
465
467
|
|
|
468
|
+
function buildProviderCandidateId(result: InstrumentSearchResult, symbol: string): string {
|
|
469
|
+
return [
|
|
470
|
+
"search",
|
|
471
|
+
normalizeTickerSymbol(symbol),
|
|
472
|
+
normalizeSearchText(result.exchange || result.primaryExchange || result.type || ""),
|
|
473
|
+
normalizeSearchText(result.currency || ""),
|
|
474
|
+
normalizeSearchText(result.providerId || ""),
|
|
475
|
+
].filter(Boolean).join(":");
|
|
476
|
+
}
|
|
477
|
+
|
|
466
478
|
function getProviderHintRichness(result: InstrumentSearchResult): number {
|
|
467
479
|
let score = 0;
|
|
468
480
|
if (result.name) score += Math.min(120, result.name.length);
|
|
@@ -728,6 +740,41 @@ function scoreSearchAlias(intent: SearchQueryIntent, alias: string): number {
|
|
|
728
740
|
return score;
|
|
729
741
|
}
|
|
730
742
|
|
|
743
|
+
function scoreSymbolMatchRank(
|
|
744
|
+
intent: SearchQueryIntent,
|
|
745
|
+
item: Pick<TickerSearchRankableItem, "label"> & Partial<TickerSearchRankableItem>,
|
|
746
|
+
): number {
|
|
747
|
+
if (!intent.normalizedQuery && !intent.compactQuery) return 0;
|
|
748
|
+
const displaySymbol = normalizeSearchText(item.symbol || item.label);
|
|
749
|
+
const compactDisplaySymbol = compactSearchText(item.symbol || item.label);
|
|
750
|
+
|
|
751
|
+
if (
|
|
752
|
+
displaySymbol === intent.normalizedQuery
|
|
753
|
+
|| (intent.compactQuery && compactDisplaySymbol === intent.compactQuery)
|
|
754
|
+
) {
|
|
755
|
+
return 3;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const aliases = getItemSearchAliases(item);
|
|
759
|
+
if (aliases.some((alias) => {
|
|
760
|
+
const normalizedAlias = normalizeSearchText(alias);
|
|
761
|
+
const compactAlias = compactSearchText(alias);
|
|
762
|
+
return normalizedAlias === intent.normalizedQuery
|
|
763
|
+
|| (intent.compactQuery && compactAlias === intent.compactQuery);
|
|
764
|
+
})) {
|
|
765
|
+
return 2;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (
|
|
769
|
+
displaySymbol.startsWith(intent.normalizedQuery)
|
|
770
|
+
|| (intent.compactQuery && compactDisplaySymbol.startsWith(intent.compactQuery))
|
|
771
|
+
) {
|
|
772
|
+
return 1;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
|
|
731
778
|
function scoreAssetPreference(intent: SearchQueryIntent, instrumentClass?: TickerSearchInstrumentClass): number {
|
|
732
779
|
const itemClass = instrumentClass || "other";
|
|
733
780
|
if (!intent.assetPreference) {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.7.
|
|
1
|
+
export const VERSION = "0.7.3";
|