@opengeni/react 6.1.0 → 7.0.0-canary.35625901699001
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 +83 -2
- package/dist/approvals.d.ts +2 -1
- package/dist/artifacts-document.js +1 -1
- package/dist/artifacts-presentation.js +1 -1
- package/dist/artifacts-spreadsheet.js +1 -1
- package/dist/artifacts.js +3 -3
- package/dist/{browser-viewer-NTD6UIPR.js → browser-viewer-LGH4IP47.js} +3 -3
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/capability-logo-fallback.d.ts +3 -0
- package/dist/{chunk-NOSYBS2D.js → chunk-2VHARTBL.js} +2 -1
- package/dist/{chunk-VRBL3F5E.js → chunk-5GAL23FF.js} +10 -2
- package/dist/{chunk-VRBL3F5E.js.map → chunk-5GAL23FF.js.map} +1 -1
- package/dist/chunk-5UONOQ3Y.js +635 -0
- package/dist/chunk-5UONOQ3Y.js.map +1 -0
- package/dist/{chunk-NYESIADM.js → chunk-AKOMC3VO.js} +3 -3
- package/dist/chunk-AKOMC3VO.js.map +1 -0
- package/dist/chunk-BRM6FVZA.js +302 -0
- package/dist/chunk-BRM6FVZA.js.map +1 -0
- package/dist/{chunk-KANFU34B.js → chunk-DBPVUGQT.js} +11 -2
- package/dist/chunk-DBPVUGQT.js.map +1 -0
- package/dist/chunk-FHIGTPME.js +1589 -0
- package/dist/chunk-FHIGTPME.js.map +1 -0
- package/dist/{chunk-5M6VTFJ5.js → chunk-GBPCZH3Y.js} +2 -2
- package/dist/{chunk-YBM6CQRU.js → chunk-GEN54DU5.js} +4 -4
- package/dist/{chunk-UMECRJRV.js → chunk-HJ5VJ4JB.js} +21 -12
- package/dist/chunk-HJ5VJ4JB.js.map +1 -0
- package/dist/{chunk-R4KRSFGA.js → chunk-IP4BUBKH.js} +3 -3
- package/dist/{chunk-NLTS7JHI.js → chunk-JPHDWKZE.js} +5 -5
- package/dist/{chunk-TDSEILUH.js → chunk-MI6ALLLD.js} +1297 -1535
- package/dist/chunk-MI6ALLLD.js.map +1 -0
- package/dist/{chunk-ICA6R6TW.js → chunk-OWHPKAOV.js} +28 -20
- package/dist/chunk-OWHPKAOV.js.map +1 -0
- package/dist/{chunk-24M4YBCL.js → chunk-PNAVEK2U.js} +4 -4
- package/dist/{chunk-L2NBTKRX.js → chunk-TXCBE32L.js} +203 -19
- package/dist/chunk-TXCBE32L.js.map +1 -0
- package/dist/{chunk-45XEUVTK.js → chunk-UXE5SSNN.js} +293 -463
- package/dist/chunk-UXE5SSNN.js.map +1 -0
- package/dist/{chunk-PEI6RANI.js → chunk-VR4Z73EU.js} +203 -9
- package/dist/chunk-VR4Z73EU.js.map +1 -0
- package/dist/{chunk-HUIKIDM5.js → chunk-XMXRI5JK.js} +65 -18
- package/dist/chunk-XMXRI5JK.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
- package/dist/components/chat-composer.d.ts +13 -3
- package/dist/components/markdown.d.ts +6 -3
- package/dist/components/message-timeline.d.ts +7 -2
- package/dist/components/preview-loading.d.ts +2 -0
- package/dist/components/session-capability-frame.d.ts +24 -0
- package/dist/components/session-chrome.d.ts +1 -2
- package/dist/components/session-commands.d.ts +6 -0
- package/dist/components/session-connection-request.d.ts +8 -0
- package/dist/components/session-conversation.d.ts +3 -0
- package/dist/components/session-mcp-capability-card.d.ts +22 -0
- package/dist/components/timeline-search.d.ts +34 -0
- package/dist/composer.js +7 -7
- package/dist/connect-account-identity.d.ts +8 -0
- package/dist/connect-accounts.d.ts +5 -0
- package/dist/connect-chooser.d.ts +6 -0
- package/dist/connect-panel.d.ts +9 -2
- package/dist/connect-setup.d.ts +5 -1
- package/dist/connect.d.ts +5 -21
- package/dist/connect.js +1268 -584
- package/dist/connect.js.map +1 -1
- package/dist/connection-catalog.d.ts +6 -1
- package/dist/connection-discovery.d.ts +12 -0
- package/dist/connection-installed.d.ts +1 -0
- package/dist/connection-logo.d.ts +5 -2
- package/dist/connection-service-logo.d.ts +8 -0
- package/dist/connection-service-presentation.d.ts +13 -0
- package/dist/connection-skeleton.d.ts +5 -0
- package/dist/discovery-cache.d.ts +5 -0
- package/dist/hooks/use-codex-accounts.d.ts +11 -5
- package/dist/hooks/use-connect.d.ts +22 -0
- package/dist/hooks/use-session-events.d.ts +5 -0
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-workspace-sessions.d.ts +2 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +771 -793
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +2 -2
- package/dist/machines.js +2 -2
- package/dist/mcp-connection-status.d.ts +14 -0
- package/dist/{model-policy-picker-menu-3R336DP2.js → model-policy-picker-menu-RVOQ4ZZG.js} +4 -4
- package/dist/model-policy.d.ts +6 -2
- package/dist/model-policy.js +1 -1
- package/dist/{platform-activity-row-DUE2W74J.js → platform-activity-row-HLOH7FW4.js} +9 -4
- package/dist/platform-activity-row-HLOH7FW4.js.map +1 -0
- package/dist/plugin-discovery.d.ts +6 -0
- package/dist/{queue-surface-implementation-LK3S4BIW.js → queue-surface-implementation-3RIFK2BS.js} +4 -4
- package/dist/realtime.js +22 -22
- package/dist/realtime.js.map +1 -1
- package/dist/session-auth-recommendation.d.ts +5 -0
- package/dist/session-capability-policy.d.ts +12 -0
- package/dist/session-ui.d.ts +2 -0
- package/dist/session-ui.js +14 -12
- package/dist/session.d.ts +1 -0
- package/dist/session.js +6 -4
- package/dist/skill-discovery.d.ts +11 -0
- package/dist/timeline/compaction-copy.d.ts +10 -0
- package/dist/timeline/projection.d.ts +3 -1
- package/dist/timeline/tool-display-name.d.ts +2 -1
- package/dist/timeline/turn-summary.d.ts +2 -2
- package/dist/timeline/types.d.ts +24 -2
- package/dist/{timeline-annotation-markers-DRSTLEM6.js → timeline-annotation-markers-OY6OWR3V.js} +2 -2
- package/dist/{timeline-annotation-selection-IHLUGVND.js → timeline-annotation-selection-BOXA2TIN.js} +2 -2
- package/dist/{timeline-annotations-dialog-6Q53N5FG.js → timeline-annotations-dialog-RGY3MHL3.js} +3 -3
- package/package.json +3 -3
- package/src/approvals.ts +8 -1
- package/src/assets/README.md +8 -0
- package/src/assets/gmail.ico +0 -0
- package/src/assets/outlook.ico +0 -0
- package/src/capability-catalog-row.tsx +94 -0
- package/src/capability-logo-fallback.ts +22 -0
- package/src/client.ts +0 -9
- package/src/components/approval-surface.tsx +12 -2
- package/src/components/artifacts/document-editor.tsx +63 -6
- package/src/components/artifacts/presentation-editor.tsx +214 -9
- package/src/components/artifacts/spreadsheet-canvas.ts +3 -8
- package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +10 -11
- package/src/components/chat-composer.tsx +26 -3
- package/src/components/composer.tsx +10 -4
- package/src/components/human-input-form.tsx +12 -2
- package/src/components/markdown.tsx +133 -9
- package/src/components/message-timeline.tsx +104 -36
- package/src/components/model-picker.tsx +11 -3
- package/src/components/model-policy-picker-menu.tsx +10 -3
- package/src/components/preview-loading.tsx +154 -0
- package/src/components/sandbox-workspace.tsx +20 -36
- package/src/components/session-capability-frame.tsx +163 -0
- package/src/components/session-chrome.tsx +46 -77
- package/src/components/session-commands.tsx +18 -0
- package/src/components/session-connection-request.tsx +97 -0
- package/src/components/session-conversation.tsx +10 -5
- package/src/components/session-mcp-capability-card.tsx +532 -0
- package/src/components/session-status.tsx +2 -2
- package/src/components/timeline-anchor.tsx +9 -8
- package/src/components/timeline-search.tsx +168 -0
- package/src/components/user-message-body.tsx +7 -0
- package/src/connect-account-identity.tsx +34 -0
- package/src/connect-accounts.tsx +114 -52
- package/src/connect-chooser.tsx +171 -39
- package/src/connect-panel.tsx +73 -9
- package/src/connect-setup.tsx +72 -25
- package/src/connect.ts +13 -22
- package/src/connection-assets.d.ts +4 -0
- package/src/connection-catalog.tsx +61 -39
- package/src/connection-discovery.tsx +235 -0
- package/src/connection-installed.tsx +2 -0
- package/src/connection-logo.tsx +18 -10
- package/src/connection-service-logo.tsx +55 -0
- package/src/connection-service-presentation.ts +46 -0
- package/src/connection-skeleton.tsx +16 -0
- package/src/conversation-timeline.ts +1 -0
- package/src/discovery-cache.ts +42 -0
- package/src/hooks/use-codex-accounts.ts +33 -14
- package/src/hooks/use-composer.ts +4 -5
- package/src/hooks/use-connect.ts +23 -0
- package/src/hooks/use-session-events.ts +114 -1
- package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
- package/src/hooks/use-workspace-sessions.ts +10 -0
- package/src/index.ts +22 -2
- package/src/mcp-connection-status.ts +35 -0
- package/src/model-policy.ts +21 -0
- package/src/plugin-details.tsx +8 -5
- package/src/plugin-discovery.tsx +92 -72
- package/src/realtime/realtime-control.tsx +6 -8
- package/src/session-auth-recommendation.ts +48 -0
- package/src/session-capability-policy.ts +83 -0
- package/src/session-ui.ts +2 -0
- package/src/session.ts +1 -0
- package/src/skill-discovery.tsx +101 -56
- package/src/timeline/compaction-copy.ts +40 -0
- package/src/timeline/platform-activity-row.tsx +8 -3
- package/src/timeline/projection.ts +78 -2
- package/src/timeline/rolling-activity.tsx +4 -2
- package/src/timeline/shared.tsx +6 -1
- package/src/timeline/startup-timings.tsx +1 -1
- package/src/timeline/tool-display-name.ts +8 -6
- package/src/timeline/tool-renderers.tsx +5 -3
- package/src/timeline/turn-summary.tsx +17 -3
- package/src/timeline/types.ts +24 -2
- package/styles/compiled.css +325 -27
- package/styles/connect.css +90 -15
- package/styles/index.css +46 -0
- package/styles/session-capability.css +42 -0
- package/dist/chunk-45XEUVTK.js.map +0 -1
- package/dist/chunk-HQV2I5HV.js +0 -124
- package/dist/chunk-HQV2I5HV.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-ICA6R6TW.js.map +0 -1
- package/dist/chunk-IHQII5WO.js +0 -886
- package/dist/chunk-IHQII5WO.js.map +0 -1
- package/dist/chunk-KANFU34B.js.map +0 -1
- package/dist/chunk-L2NBTKRX.js.map +0 -1
- package/dist/chunk-NYESIADM.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-TDSEILUH.js.map +0 -1
- package/dist/chunk-UMECRJRV.js.map +0 -1
- package/dist/hooks/use-packs.d.ts +0 -32
- package/dist/platform-activity-row-DUE2W74J.js.map +0 -1
- package/src/hooks/use-packs.ts +0 -157
- /package/dist/{browser-viewer-NTD6UIPR.js.map → browser-viewer-LGH4IP47.js.map} +0 -0
- /package/dist/{chunk-NOSYBS2D.js.map → chunk-2VHARTBL.js.map} +0 -0
- /package/dist/{chunk-5M6VTFJ5.js.map → chunk-GBPCZH3Y.js.map} +0 -0
- /package/dist/{chunk-YBM6CQRU.js.map → chunk-GEN54DU5.js.map} +0 -0
- /package/dist/{chunk-R4KRSFGA.js.map → chunk-IP4BUBKH.js.map} +0 -0
- /package/dist/{chunk-NLTS7JHI.js.map → chunk-JPHDWKZE.js.map} +0 -0
- /package/dist/{chunk-24M4YBCL.js.map → chunk-PNAVEK2U.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-3R336DP2.js.map → model-policy-picker-menu-RVOQ4ZZG.js.map} +0 -0
- /package/dist/{queue-surface-implementation-LK3S4BIW.js.map → queue-surface-implementation-3RIFK2BS.js.map} +0 -0
- /package/dist/{timeline-annotation-markers-DRSTLEM6.js.map → timeline-annotation-markers-OY6OWR3V.js.map} +0 -0
- /package/dist/{timeline-annotation-selection-IHLUGVND.js.map → timeline-annotation-selection-BOXA2TIN.js.map} +0 -0
- /package/dist/{timeline-annotations-dialog-6Q53N5FG.js.map → timeline-annotations-dialog-RGY3MHL3.js.map} +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { createContext, useContext, useLayoutEffect, useRef, type RefObject } from "react";
|
|
2
|
+
import type { TimelineItem } from "../timeline/types";
|
|
3
|
+
import { annotatableText, rangeFromOffsets, visibleClientRect } from "./timeline-annotation-shared";
|
|
4
|
+
|
|
5
|
+
export type TimelineSearchTarget = {
|
|
6
|
+
sequence: number;
|
|
7
|
+
eventId?: string | undefined;
|
|
8
|
+
/** Literal, case-insensitive text. No regex interpretation. */
|
|
9
|
+
query: string;
|
|
10
|
+
/** Zero-based occurrence within the source message. Defaults to zero. */
|
|
11
|
+
occurrence?: number | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Zero-based UTF-16 offset of the match in the original message text, as
|
|
14
|
+
* reported by backend all-occurrence search (messageMatchOffset). When set
|
|
15
|
+
* it takes precedence over `occurrence` and is validated against `query`,
|
|
16
|
+
* so a stale offset degrades to no highlight rather than a wrong one.
|
|
17
|
+
*/
|
|
18
|
+
offset?: number | undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Scoped to the target's group; disclosure expansion persists after closing find. */
|
|
22
|
+
export const TimelineSearchRevealContext = createContext<string | null>(null);
|
|
23
|
+
export const useTimelineSearchReveal = () => useContext(TimelineSearchRevealContext);
|
|
24
|
+
|
|
25
|
+
export function isTimelineSearchTarget(item: TimelineItem, target: TimelineSearchTarget): boolean {
|
|
26
|
+
const sources =
|
|
27
|
+
item.sourceEvents ??
|
|
28
|
+
("annotationSource" in item && item.annotationSource ? [item.annotationSource] : []);
|
|
29
|
+
return sources.some(
|
|
30
|
+
(source) =>
|
|
31
|
+
source.sequence === target.sequence && (!target.eventId || source.eventId === target.eventId),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Scan without allocating an offset array proportional to a huge message. */
|
|
36
|
+
export function literalSearchOffset(text: string, query: string, occurrence = 0): number {
|
|
37
|
+
if (!query || !Number.isSafeInteger(occurrence) || occurrence < 0) return -1;
|
|
38
|
+
// Regex escaping preserves UTF-16 offsets even for case folds whose lowercase
|
|
39
|
+
// string representation changes length (for example dotted capital I).
|
|
40
|
+
const pattern = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "giu");
|
|
41
|
+
let match: RegExpExecArray | null;
|
|
42
|
+
let index = 0;
|
|
43
|
+
while ((match = pattern.exec(text))) {
|
|
44
|
+
if (index++ === occurrence) return match.index;
|
|
45
|
+
}
|
|
46
|
+
return -1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validate a caller-supplied UTF-16 offset against the text and query. The
|
|
51
|
+
* sticky flag pins the match to exactly `offset`; like literalSearchOffset,
|
|
52
|
+
* regex escaping keeps offsets in UTF-16 units even for length-changing case
|
|
53
|
+
* folds. Returns -1 when the offset is stale or the text no longer matches.
|
|
54
|
+
*/
|
|
55
|
+
export function matchAtOffset(text: string, query: string, offset: number): number {
|
|
56
|
+
if (!query || !Number.isSafeInteger(offset) || offset < 0 || offset > text.length) return -1;
|
|
57
|
+
const pattern = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "iyu");
|
|
58
|
+
pattern.lastIndex = offset;
|
|
59
|
+
// Unicode regex engines may rewind a lastIndex inside a surrogate pair.
|
|
60
|
+
return pattern.exec(text)?.index === offset ? offset : -1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Resolve a target to a UTF-16 offset: explicit offset wins, else the ordinal scan. */
|
|
64
|
+
export function searchMatchOffset(text: string, target: TimelineSearchTarget): number {
|
|
65
|
+
if (target.offset != null) return matchAtOffset(text, target.query, target.offset);
|
|
66
|
+
return literalSearchOffset(text, target.query, target.occurrence);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type HighlightRegistry = { set(name: string, value: unknown): void; delete(name: string): void };
|
|
70
|
+
let nextHighlightId = 0;
|
|
71
|
+
|
|
72
|
+
/** Range highlighting does not mutate React-owned text or its layout. */
|
|
73
|
+
export function useTimelineSearchNavigation(
|
|
74
|
+
root: RefObject<HTMLDivElement | null>,
|
|
75
|
+
itemId: string | undefined,
|
|
76
|
+
target: TimelineSearchTarget | null | undefined,
|
|
77
|
+
releasePin: () => void,
|
|
78
|
+
revision: unknown,
|
|
79
|
+
) {
|
|
80
|
+
const highlightName = useRef("");
|
|
81
|
+
if (!highlightName.current) highlightName.current = `og-search-${++nextHighlightId}`;
|
|
82
|
+
const key = target
|
|
83
|
+
? JSON.stringify([
|
|
84
|
+
itemId,
|
|
85
|
+
target.sequence,
|
|
86
|
+
target.eventId,
|
|
87
|
+
target.query,
|
|
88
|
+
target.occurrence ?? 0,
|
|
89
|
+
target.offset ?? null,
|
|
90
|
+
])
|
|
91
|
+
: null;
|
|
92
|
+
const navigated = useRef<string | null>(null);
|
|
93
|
+
useLayoutEffect(() => {
|
|
94
|
+
if (!key) navigated.current = null;
|
|
95
|
+
if (!key || !target || !itemId || !root.current) return;
|
|
96
|
+
releasePin();
|
|
97
|
+
const scroller = root.current;
|
|
98
|
+
const registry = (
|
|
99
|
+
globalThis.CSS as (typeof CSS & { highlights?: HighlightRegistry }) | undefined
|
|
100
|
+
)?.highlights;
|
|
101
|
+
const HighlightClass = (
|
|
102
|
+
globalThis as unknown as { Highlight?: new (...ranges: Range[]) => unknown }
|
|
103
|
+
).Highlight;
|
|
104
|
+
const style = document.createElement("style");
|
|
105
|
+
style.textContent = `::highlight(${highlightName.current}) { background: var(--og-accent, #facc15); color: var(--og-accent-fg, #171717); }`;
|
|
106
|
+
scroller.append(style);
|
|
107
|
+
let frame = 0;
|
|
108
|
+
const update = () => {
|
|
109
|
+
const source = [...scroller.querySelectorAll<HTMLElement>("[data-og-search-item]")].find(
|
|
110
|
+
(element) => element.dataset.ogSearchItem === itemId,
|
|
111
|
+
);
|
|
112
|
+
if (!source) return;
|
|
113
|
+
// A virtualized renderer can explicitly identify the selected occurrence
|
|
114
|
+
// when earlier occurrences are not mounted. Never accept a stale marker
|
|
115
|
+
// belonging to a different sequence, occurrence, or query.
|
|
116
|
+
const materialized = [
|
|
117
|
+
...source.querySelectorAll<HTMLElement>("[data-og-search-occurrence]"),
|
|
118
|
+
].find(
|
|
119
|
+
(element) =>
|
|
120
|
+
element.dataset.ogSearchSequence === String(target.sequence) &&
|
|
121
|
+
element.dataset.ogSearchQuery === target.query &&
|
|
122
|
+
(target.offset != null
|
|
123
|
+
? element.dataset.ogSearchOffset === String(target.offset)
|
|
124
|
+
: element.dataset.ogSearchOccurrence === String(target.occurrence ?? 0)),
|
|
125
|
+
);
|
|
126
|
+
const { text, spans } = annotatableText(materialized ?? source);
|
|
127
|
+
// Inside a materialized chunk the match starts at the chunk start.
|
|
128
|
+
// Raw source offsets must NEVER be interpreted as rendered DOM offsets:
|
|
129
|
+
// even matching characters can identify a different repeated occurrence.
|
|
130
|
+
const offset = materialized
|
|
131
|
+
? matchAtOffset(text, target.query, 0)
|
|
132
|
+
: target.offset != null
|
|
133
|
+
? -1
|
|
134
|
+
: searchMatchOffset(text, target);
|
|
135
|
+
const range =
|
|
136
|
+
offset < 0 ? null : rangeFromOffsets(spans, offset, offset + target.query.length);
|
|
137
|
+
if (range && registry && HighlightClass)
|
|
138
|
+
registry.set(highlightName.current, new HighlightClass(range));
|
|
139
|
+
else registry?.delete(highlightName.current);
|
|
140
|
+
const rect = range ? visibleClientRect(range) : null;
|
|
141
|
+
// A custom renderer may mount the message body later (or materialize a
|
|
142
|
+
// virtualized chunk in response to renderMessageText's search context).
|
|
143
|
+
// Finding its empty shell is not proof that the occurrence was revealed.
|
|
144
|
+
if (navigated.current === key || !range || !rect) return;
|
|
145
|
+
const box = rect;
|
|
146
|
+
if (!box.height && !box.width) return;
|
|
147
|
+
scroller.scrollTop +=
|
|
148
|
+
box.top - scroller.getBoundingClientRect().top - scroller.clientHeight / 2;
|
|
149
|
+
navigated.current = key;
|
|
150
|
+
};
|
|
151
|
+
const schedule = () => {
|
|
152
|
+
cancelAnimationFrame(frame);
|
|
153
|
+
frame = requestAnimationFrame(update);
|
|
154
|
+
};
|
|
155
|
+
const observer = new MutationObserver(schedule);
|
|
156
|
+
observer.observe(scroller, { childList: true, subtree: true, characterData: true });
|
|
157
|
+
const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
|
|
158
|
+
resize?.observe(scroller);
|
|
159
|
+
schedule();
|
|
160
|
+
return () => {
|
|
161
|
+
observer.disconnect();
|
|
162
|
+
resize?.disconnect();
|
|
163
|
+
cancelAnimationFrame(frame);
|
|
164
|
+
registry?.delete(highlightName.current);
|
|
165
|
+
style.remove();
|
|
166
|
+
};
|
|
167
|
+
}, [key, root, itemId, target, releasePin, revision]);
|
|
168
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ReactNode,
|
|
10
10
|
} from "react";
|
|
11
11
|
import { cn } from "../lib/cn";
|
|
12
|
+
import { useTimelineSearchReveal } from "./timeline-search";
|
|
12
13
|
|
|
13
14
|
const FALLBACK_LINE_THRESHOLD = 12;
|
|
14
15
|
const FALLBACK_TEXT_THRESHOLD = 900;
|
|
@@ -268,6 +269,12 @@ export function UserMessageBody({
|
|
|
268
269
|
const [expanded, setExpanded] = useState(
|
|
269
270
|
() => disclosure?.expandedByMessageId.get(messageId) ?? false,
|
|
270
271
|
);
|
|
272
|
+
const searchReveal = useTimelineSearchReveal();
|
|
273
|
+
useLayoutEffect(() => {
|
|
274
|
+
if (!searchReveal) return;
|
|
275
|
+
setExpanded(true);
|
|
276
|
+
disclosure?.expandedByMessageId.set(messageId, true);
|
|
277
|
+
}, [searchReveal, messageId, disclosure]);
|
|
271
278
|
const collapsibleRef = useRef(userMessageLikelyNeedsDisclosure(text));
|
|
272
279
|
const expandedRef = useRef(expanded);
|
|
273
280
|
expandedRef.current = expanded;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ConnectAccount } from "@opengeni/connect";
|
|
2
|
+
import type { CapabilityCatalogItem, OpenGeniClient } from "@opengeni/sdk";
|
|
3
|
+
import { ConnectionServiceLogo } from "./connection-service-logo";
|
|
4
|
+
import {
|
|
5
|
+
accountServiceCapability,
|
|
6
|
+
connectionServicePresentation,
|
|
7
|
+
} from "./connection-service-presentation";
|
|
8
|
+
|
|
9
|
+
/** Service presentation never changes which exact credential is acted on. */
|
|
10
|
+
export function ConnectAccountIdentity({
|
|
11
|
+
account,
|
|
12
|
+
client,
|
|
13
|
+
capabilities,
|
|
14
|
+
}: {
|
|
15
|
+
account: ConnectAccount;
|
|
16
|
+
client?: OpenGeniClient | undefined;
|
|
17
|
+
capabilities: CapabilityCatalogItem[];
|
|
18
|
+
}) {
|
|
19
|
+
const item = accountServiceCapability(account, capabilities);
|
|
20
|
+
const service = connectionServicePresentation({ id: account.providerId, label: account.label });
|
|
21
|
+
const name = item?.name ?? service.name;
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<ConnectionServiceLogo client={client} item={item} name={name} fallback={service.logo} />
|
|
25
|
+
<div>
|
|
26
|
+
<strong>{name}</strong>
|
|
27
|
+
<small>
|
|
28
|
+
{account.ownership === "personal" ? "Only you" : "Workspace connection"}
|
|
29
|
+
{name !== account.label ? ` · ${account.label}` : ""}
|
|
30
|
+
</small>
|
|
31
|
+
</div>
|
|
32
|
+
</>
|
|
33
|
+
);
|
|
34
|
+
}
|
package/src/connect-accounts.tsx
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import type { ConnectAccount, ConnectController } from "@opengeni/connect";
|
|
3
|
-
import { useConnect } from "./connect";
|
|
3
|
+
import { useConnect } from "./hooks/use-connect";
|
|
4
|
+
import { ConnectAccountIdentity } from "./connect-account-identity";
|
|
5
|
+
import { ConnectionSkeleton } from "./connection-skeleton";
|
|
6
|
+
import type { CapabilityCatalogItem, OpenGeniClient } from "@opengeni/sdk";
|
|
4
7
|
|
|
5
8
|
export type ConnectAccountsProps = {
|
|
6
9
|
controller: ConnectController;
|
|
7
10
|
className?: string;
|
|
8
11
|
/** Enables explicit account-bound reconnect. Host supplies the exact destination. */
|
|
9
12
|
returnUrl?: string;
|
|
13
|
+
/** Uses authenticated catalogue assets and exact connection references for service identity. */
|
|
14
|
+
client?: OpenGeniClient | undefined;
|
|
15
|
+
/** Reconcile surrounding service state after a confirmed account disconnect. */
|
|
16
|
+
onDisconnected?: (() => void) | undefined;
|
|
10
17
|
};
|
|
11
18
|
|
|
12
19
|
/** Local credential revocation only. Unknown or changed versions must be
|
|
@@ -21,33 +28,50 @@ export function ConnectAccounts(props: ConnectAccountsProps) {
|
|
|
21
28
|
return <ScopedAccounts key={generation} {...props} />;
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
function ScopedAccounts({
|
|
31
|
+
function ScopedAccounts({
|
|
32
|
+
controller,
|
|
33
|
+
className,
|
|
34
|
+
returnUrl,
|
|
35
|
+
client,
|
|
36
|
+
onDisconnected,
|
|
37
|
+
}: ConnectAccountsProps) {
|
|
25
38
|
const view = useConnect(controller);
|
|
26
39
|
const [accounts, setAccounts] = useState<ConnectAccount[] | null>(null);
|
|
27
40
|
const [selected, setSelected] = useState<string | null>(null);
|
|
28
41
|
const [busy, setBusy] = useState(false);
|
|
29
42
|
const [error, setError] = useState(false);
|
|
30
43
|
const [reload, setReload] = useState(0);
|
|
44
|
+
const [catalog, setCatalog] = useState<{
|
|
45
|
+
client: OpenGeniClient;
|
|
46
|
+
items: CapabilityCatalogItem[];
|
|
47
|
+
} | null>(null);
|
|
31
48
|
const mutation = useRef<AbortController | null>(null);
|
|
32
49
|
useEffect(() => () => mutation.current?.abort(), []);
|
|
33
50
|
useEffect(() => {
|
|
34
51
|
const abort = new AbortController();
|
|
35
52
|
setAccounts(null);
|
|
53
|
+
setCatalog(null);
|
|
36
54
|
setSelected(null);
|
|
37
55
|
setError(false);
|
|
38
56
|
void Promise.resolve()
|
|
39
57
|
.then(() => {
|
|
40
58
|
abort.signal.throwIfAborted();
|
|
41
|
-
return
|
|
59
|
+
return Promise.all([
|
|
60
|
+
controller.transport.accounts(controller.workspaceId, { signal: abort.signal }),
|
|
61
|
+
client?.listCapabilities(controller.workspaceId).catch(() => null) ?? null,
|
|
62
|
+
]);
|
|
42
63
|
})
|
|
43
|
-
.then((result) => {
|
|
44
|
-
if (!abort.signal.aborted)
|
|
64
|
+
.then(([result, metadata]) => {
|
|
65
|
+
if (!abort.signal.aborted) {
|
|
66
|
+
setAccounts(structuredClone(result.filter((account) => account.status !== "disabled")));
|
|
67
|
+
if (client && metadata) setCatalog({ client, items: metadata.items });
|
|
68
|
+
}
|
|
45
69
|
})
|
|
46
70
|
.catch(() => {
|
|
47
71
|
if (!abort.signal.aborted) setError(true);
|
|
48
72
|
});
|
|
49
73
|
return () => abort.abort();
|
|
50
|
-
}, [controller, reload]);
|
|
74
|
+
}, [client, controller, reload]);
|
|
51
75
|
|
|
52
76
|
const disconnect = async (account: ConnectAccount) => {
|
|
53
77
|
const expectedVersion = account.version;
|
|
@@ -68,7 +92,10 @@ function ScopedAccounts({ controller, className, returnUrl }: ConnectAccountsPro
|
|
|
68
92
|
expectedVersion,
|
|
69
93
|
signal: abort.signal,
|
|
70
94
|
});
|
|
71
|
-
if (!abort.signal.aborted)
|
|
95
|
+
if (!abort.signal.aborted) {
|
|
96
|
+
setReload((value) => value + 1);
|
|
97
|
+
onDisconnected?.();
|
|
98
|
+
}
|
|
72
99
|
} catch {
|
|
73
100
|
if (!abort.signal.aborted) {
|
|
74
101
|
// Outcome may be unknown. Drop the inventory so a second click cannot
|
|
@@ -106,64 +133,99 @@ function ScopedAccounts({ controller, className, returnUrl }: ConnectAccountsPro
|
|
|
106
133
|
aria-label="Connected accounts"
|
|
107
134
|
aria-busy={busy || view.busy || (!accounts && !error)}
|
|
108
135
|
>
|
|
136
|
+
<h3>Your connections</h3>
|
|
109
137
|
{error && (
|
|
110
138
|
<p role="alert">Account state could not be confirmed. Reload before trying again.</p>
|
|
111
139
|
)}
|
|
112
|
-
{!accounts && !error && <
|
|
140
|
+
{!accounts && !error && <ConnectionSkeleton label="Loading accounts" />}
|
|
113
141
|
{accounts?.length === 0 && <p role="status">No connected accounts.</p>}
|
|
114
142
|
{accounts && accounts.length > 0 && (
|
|
115
143
|
<ul>
|
|
116
144
|
{accounts.map((account) => (
|
|
117
|
-
<li key={`${account.providerId}:${account.id}`}>
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
<li className="og-connect-account" key={`${account.providerId}:${account.id}`}>
|
|
146
|
+
<details className="og-connect-account-details">
|
|
147
|
+
<summary className="og-connect-account-heading">
|
|
148
|
+
<ConnectAccountIdentity
|
|
149
|
+
account={account}
|
|
150
|
+
client={client}
|
|
151
|
+
capabilities={catalog?.client === client ? (catalog?.items ?? []) : []}
|
|
152
|
+
/>
|
|
153
|
+
<span className="og-connect-account-status" data-status={account.status}>
|
|
154
|
+
{account.status === "connected"
|
|
155
|
+
? "✓ Connected"
|
|
156
|
+
: account.status === "auth_needed"
|
|
157
|
+
? "Reconnect needed"
|
|
158
|
+
: "Disconnected"}
|
|
159
|
+
</span>
|
|
160
|
+
<svg
|
|
161
|
+
className="og-connect-account-chevron"
|
|
162
|
+
aria-hidden="true"
|
|
163
|
+
width="16"
|
|
164
|
+
height="16"
|
|
165
|
+
viewBox="0 0 24 24"
|
|
166
|
+
fill="none"
|
|
167
|
+
stroke="currentColor"
|
|
168
|
+
strokeWidth="1.5"
|
|
140
169
|
>
|
|
141
|
-
|
|
142
|
-
</
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
170
|
+
<path d="m9 6 6 6-6 6" />
|
|
171
|
+
</svg>
|
|
172
|
+
</summary>
|
|
173
|
+
<div className="og-connect-account-actions">
|
|
174
|
+
{returnUrl && account.status === "auth_needed" && (
|
|
175
|
+
<button
|
|
176
|
+
type="button"
|
|
177
|
+
aria-label={`Reconnect ${account.label}`}
|
|
178
|
+
disabled={busy || view.busy}
|
|
179
|
+
onClick={() => void reconnect(account)}
|
|
180
|
+
>
|
|
181
|
+
Reconnect
|
|
182
|
+
</button>
|
|
183
|
+
)}
|
|
184
|
+
{selected === `${account.providerId}:${account.id}` ? (
|
|
185
|
+
<div role="group" aria-label={`Disconnect ${account.label}`}>
|
|
186
|
+
<p>
|
|
187
|
+
This removes local OpenGeni access. It does not revoke consent at the
|
|
188
|
+
provider.
|
|
189
|
+
</p>
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
disabled={busy || view.busy}
|
|
193
|
+
onClick={() => void disconnect(account)}
|
|
194
|
+
>
|
|
195
|
+
Confirm disconnect
|
|
196
|
+
</button>
|
|
197
|
+
<button type="button" disabled={busy} onClick={() => setSelected(null)}>
|
|
198
|
+
Keep account
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
) : (
|
|
202
|
+
<button
|
|
203
|
+
type="button"
|
|
204
|
+
aria-label={`Disconnect ${account.label}`}
|
|
205
|
+
disabled={
|
|
206
|
+
busy ||
|
|
207
|
+
view.busy ||
|
|
208
|
+
account.status === "disabled" ||
|
|
209
|
+
!Number.isSafeInteger(account.version) ||
|
|
210
|
+
account.version! < 1
|
|
211
|
+
}
|
|
212
|
+
onClick={() => setSelected(`${account.providerId}:${account.id}`)}
|
|
213
|
+
>
|
|
214
|
+
Disconnect
|
|
215
|
+
</button>
|
|
216
|
+
)}
|
|
146
217
|
</div>
|
|
147
|
-
|
|
148
|
-
<button
|
|
149
|
-
type="button"
|
|
150
|
-
disabled={
|
|
151
|
-
busy ||
|
|
152
|
-
view.busy ||
|
|
153
|
-
account.status === "disabled" ||
|
|
154
|
-
!Number.isSafeInteger(account.version) ||
|
|
155
|
-
account.version! < 1
|
|
156
|
-
}
|
|
157
|
-
onClick={() => setSelected(`${account.providerId}:${account.id}`)}
|
|
158
|
-
>
|
|
159
|
-
Disconnect {account.label}
|
|
160
|
-
</button>
|
|
161
|
-
)}
|
|
218
|
+
</details>
|
|
162
219
|
</li>
|
|
163
220
|
))}
|
|
164
221
|
</ul>
|
|
165
222
|
)}
|
|
166
|
-
<button
|
|
223
|
+
<button
|
|
224
|
+
hidden={!error}
|
|
225
|
+
type="button"
|
|
226
|
+
disabled={busy}
|
|
227
|
+
onClick={() => setReload((value) => value + 1)}
|
|
228
|
+
>
|
|
167
229
|
Reload accounts
|
|
168
230
|
</button>
|
|
169
231
|
</section>
|