@khorsheed/dsh-message-timeline 0.1.0 → 0.2.0
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/CHANGELOG.md +8 -0
- package/README.en.md +18 -10
- package/README.i18n.yaml +2 -2
- package/README.md +34 -26
- package/lib/client.js +175 -39
- package/lib/client.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/client/TimelineRail.d.ts +1 -1
- package/lib/types/client/TimelineRail.d.ts.map +1 -1
- package/lib/types/client/TimelineRail.js +69 -5
- package/lib/types/client/chat-hook.d.ts +35 -0
- package/lib/types/client/chat-hook.d.ts.map +1 -0
- package/lib/types/client/chat-hook.js +15 -0
- package/lib/types/client/hidden-spans.d.ts +40 -0
- package/lib/types/client/hidden-spans.d.ts.map +1 -0
- package/lib/types/client/hidden-spans.js +40 -0
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/rail-tracker.d.ts +23 -11
- package/lib/types/client/rail-tracker.d.ts.map +1 -1
- package/lib/types/client/rail-tracker.js +98 -32
- package/lib/types/client/slots.d.ts +6 -2
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/client/timeline-kinds.d.ts +32 -0
- package/lib/types/client/timeline-kinds.d.ts.map +1 -0
- package/lib/types/client/timeline-kinds.js +36 -0
- package/package.json +17 -13
|
@@ -4,7 +4,7 @@ import type { TimelineRailProps } from './slots.ts';
|
|
|
4
4
|
* @param props - composed props (see {@link TimelineRailProps}).
|
|
5
5
|
* @returns nothing visible in the seat; the floating panel while the chat view shows.
|
|
6
6
|
*/
|
|
7
|
-
export declare function TimelineRail({
|
|
7
|
+
export declare function TimelineRail({ sessionId, includeSteering, panelWidth, initialPages, loadOlder, jumpTo, useRail, t, ...standard }: TimelineRailProps): import("react").ReactPortal | null;
|
|
8
8
|
/** Memoized export for the slot machinery (stable component identity). */
|
|
9
9
|
export declare const TimelineRailEntry: import("react").MemoExoticComponent<typeof TimelineRail>;
|
|
10
10
|
//# sourceMappingURL=TimelineRail.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimelineRail.d.ts","sourceRoot":"","sources":["../../../src/client/TimelineRail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TimelineRail.d.ts","sourceRoot":"","sources":["../../../src/client/TimelineRail.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAsBjE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,eAAe,EAAE,UAAU,EAAE,YAAY,EACzC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAC7B,GAAG,QAAQ,EACZ,EAAE,iBAAiB,sCAmMnB;AAED,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,0DAAqB,CAAA"}
|
|
@@ -15,7 +15,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
15
|
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
16
|
import { createPortal } from 'react-dom';
|
|
17
17
|
import { PANEL_WIDTH_MIN } from './config.js';
|
|
18
|
+
import { chatHookOf } from './chat-hook.js';
|
|
18
19
|
import { previewText } from './preview.js';
|
|
20
|
+
import { foldHiddenSpans, isSeqHidden } from './hidden-spans.js';
|
|
21
|
+
import { isTimelineRowKind, EDIT_KIND, RESTORED_KIND } from './timeline-kinds.js';
|
|
19
22
|
import css from './TimelineRail.module.css';
|
|
20
23
|
/** Extract the preview blocks of one user/steering node (kind-checked by the caller). */
|
|
21
24
|
function nodeContent(node) {
|
|
@@ -36,23 +39,84 @@ const DEGRADED_WIDTH_RATIO = 0.4;
|
|
|
36
39
|
* @param props - composed props (see {@link TimelineRailProps}).
|
|
37
40
|
* @returns nothing visible in the seat; the floating panel while the chat view shows.
|
|
38
41
|
*/
|
|
39
|
-
export function TimelineRail({
|
|
42
|
+
export function TimelineRail({ sessionId, includeSteering, panelWidth, initialPages, loadOlder, jumpTo, useRail, t, ...standard }) {
|
|
40
43
|
const rail = useRail(s => s);
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
// Chat data lives in the `useChat` standard prop on 0.1.2 (the rc-line
|
|
45
|
+
// session-snapshot seat is gone); the helper degrades to an empty slice
|
|
46
|
+
// when the seat is absent.
|
|
47
|
+
const useChatSlice = chatHookOf(standard);
|
|
48
|
+
const useSession = standard.useSession;
|
|
49
|
+
const order = useChatSlice(c => c.order);
|
|
50
|
+
const nodes = useChatSlice(c => c.nodes);
|
|
43
51
|
const hasMore = useSession(s => s.hasMore);
|
|
44
52
|
const loadingOlder = useSession(s => s.loadingOlder);
|
|
45
53
|
const items = useMemo(() => {
|
|
54
|
+
// The node store is an in-memory snapshot; take it once so the span fold
|
|
55
|
+
// and the message-tools append read the same snapshot.
|
|
56
|
+
let store = [];
|
|
57
|
+
let spans = [];
|
|
58
|
+
try {
|
|
59
|
+
store = nodes.values();
|
|
60
|
+
spans = foldHiddenSpans(store);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Degrade, don't explode: a failed store read leaves no spans and no
|
|
64
|
+
// appended bubbles, so the baseline order rows still render (a withdraw
|
|
65
|
+
// span reappears, and bubbles re-appear, after the store recovers).
|
|
66
|
+
}
|
|
67
|
+
// A message-tools withdraw (or an in-place edit) leaves the withdrawn
|
|
68
|
+
// originals in the store with a sibling `message-tools-withdrawn` /
|
|
69
|
+
// `message-tools-edited` node carrying the span they cover as
|
|
70
|
+
// `{ hiddenStartSeq, seq }`. Those originals can still surface in the
|
|
71
|
+
// host's `order`, but the DOM hider hides their transcript rows — a rail
|
|
72
|
+
// row for one is a dead row (clicking cannot scroll to it). Drop any such
|
|
73
|
+
// row whose anchorSeq sits inside a span, from either loop below. An
|
|
74
|
+
// ordinary session carries no span node, so the fold is empty and every
|
|
75
|
+
// order row still renders: this filter never alters the baseline.
|
|
76
|
+
const hidden = (node) => isSeqHidden(spans, node.anchorSeq);
|
|
46
77
|
const result = [];
|
|
78
|
+
const seen = new Set();
|
|
47
79
|
for (const key of order) {
|
|
48
80
|
const node = nodes.get(key);
|
|
49
81
|
if (node === undefined)
|
|
50
82
|
continue;
|
|
51
|
-
|
|
52
|
-
|
|
83
|
+
if (!isTimelineRowKind(node.kind, includeSteering))
|
|
84
|
+
continue;
|
|
85
|
+
if (hidden(node))
|
|
53
86
|
continue;
|
|
54
87
|
result.push({ key, node });
|
|
88
|
+
seen.add(key);
|
|
89
|
+
}
|
|
90
|
+
// After a message-tools in-place edit the replacement materializes as a
|
|
91
|
+
// `message-tools-edited` bubble (a restore as `message-tools-restored`)
|
|
92
|
+
// that the host's visible `order` does not always surface, even though it
|
|
93
|
+
// renders in the flow — an edit of the first message would drain the rail
|
|
94
|
+
// to zero rows. Append any such visible bubble the order omitted, provided
|
|
95
|
+
// it is not itself a withdrawn original (inside a span). Only these two
|
|
96
|
+
// bubble kinds are appended — a plain user/steering node not in `order`
|
|
97
|
+
// must never be resurrected as an orphan row. Normal sessions carry no
|
|
98
|
+
// such nodes, so this never alters the baseline.
|
|
99
|
+
for (const node of store) {
|
|
100
|
+
if (node.kind !== EDIT_KIND && node.kind !== RESTORED_KIND)
|
|
101
|
+
continue;
|
|
102
|
+
if (node.visibility === 'hidden')
|
|
103
|
+
continue;
|
|
104
|
+
if (seen.has(node.key))
|
|
105
|
+
continue;
|
|
106
|
+
if (hidden(node))
|
|
107
|
+
continue;
|
|
108
|
+
result.push({ key: node.key, node });
|
|
109
|
+
seen.add(node.key);
|
|
55
110
|
}
|
|
111
|
+
// The order loop is seq-sorted, but the appended bubbles walked the store
|
|
112
|
+
// (whose iteration order is not seq-ordered), so a bubble for an OLDER
|
|
113
|
+
// message would otherwise land at the very end, past the newest row.
|
|
114
|
+
// Sort the combined rows by anchorSeq so every row — original or appended
|
|
115
|
+
// — sits at its true transcript position. JS sort is stable, and an
|
|
116
|
+
// ordinary session has no appended bubbles, so this never reorders the
|
|
117
|
+
// baseline (which is already seq-sorted). Dropped rows are gone before the
|
|
118
|
+
// sort, so a withdrawn original never resurfaces.
|
|
119
|
+
result.sort((left, right) => left.node.anchorSeq - right.node.anchorSeq);
|
|
56
120
|
return result;
|
|
57
121
|
}, [order, nodes, includeSteering]);
|
|
58
122
|
// The lit row: the hover/arrow preselection while it moves, otherwise the
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat snapshot hook seat. Host 0.1.2 split the chat snapshot out of the
|
|
3
|
+
* session snapshot into the `useChat` session standard prop (ui-chat); its
|
|
4
|
+
* snapshot carries the fields the panel reads (`order`, `nodes`) at the top
|
|
5
|
+
* level. The rc-line `useSession(...).chat` seat went away with the baseline
|
|
6
|
+
* flip, so there is no second arm: the helper reads `useChat` and degrades to
|
|
7
|
+
* an empty slice (panel hidden, never a throw) when the prop is absent.
|
|
8
|
+
*/
|
|
9
|
+
import type { ChatConversationViewNode } from '@deepseek-ai/dsh-client-ui-chat/client';
|
|
10
|
+
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
11
|
+
/**
|
|
12
|
+
* The chat slice the panel's selectors read — the narrow face of ui-chat's
|
|
13
|
+
* ChatSnapshot the rail depends on (a structural subset, so the official
|
|
14
|
+
* `UseChat` hook is directly assignable).
|
|
15
|
+
*/
|
|
16
|
+
export interface ChatSlice {
|
|
17
|
+
/** Visible chat node keys in render order. */
|
|
18
|
+
readonly order: readonly string[];
|
|
19
|
+
/** Live per-key node store. */
|
|
20
|
+
readonly nodes: {
|
|
21
|
+
get(key: string): ChatConversationViewNode | undefined;
|
|
22
|
+
values(): readonly ChatConversationViewNode[];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Selector hook over the chat slice. */
|
|
26
|
+
export type ChatSliceHook = SnapshotSelectorHook<ChatSlice>;
|
|
27
|
+
/**
|
|
28
|
+
* Pick the live chat hook for this entry.
|
|
29
|
+
* @param props - the entry's composed props (useChat present on 0.1.2 hosts).
|
|
30
|
+
* @returns the `useChat` prop, or the empty-slice hook when the seat is absent.
|
|
31
|
+
*/
|
|
32
|
+
export declare function chatHookOf(props: {
|
|
33
|
+
useChat?: SnapshotSelectorHook<ChatSlice>;
|
|
34
|
+
}): ChatSliceHook;
|
|
35
|
+
//# sourceMappingURL=chat-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-hook.d.ts","sourceRoot":"","sources":["../../../src/client/chat-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACtF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAA;AAE5E;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE;QACd,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,wBAAwB,GAAG,SAAS,CAAA;QACtD,MAAM,IAAI,SAAS,wBAAwB,EAAE,CAAA;KAC9C,CAAA;CACF;AAED,yCAAyC;AACzC,MAAM,MAAM,aAAa,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAA;AAW3D;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAA;CAAE,GAAG,aAAa,CAE9F"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Empty slice behind the absent-prop degrade: no rows, so the panel hides. */
|
|
2
|
+
const EMPTY_SLICE = {
|
|
3
|
+
order: [],
|
|
4
|
+
nodes: { get: () => undefined, values: () => [] },
|
|
5
|
+
};
|
|
6
|
+
/** Constant hook answering every selector from the empty slice. */
|
|
7
|
+
const EMPTY_HOOK = (selector) => selector(EMPTY_SLICE);
|
|
8
|
+
/**
|
|
9
|
+
* Pick the live chat hook for this entry.
|
|
10
|
+
* @param props - the entry's composed props (useChat present on 0.1.2 hosts).
|
|
11
|
+
* @returns the `useChat` prop, or the empty-slice hook when the seat is absent.
|
|
12
|
+
*/
|
|
13
|
+
export function chatHookOf(props) {
|
|
14
|
+
return props.useChat ?? EMPTY_HOOK;
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hidden-span folding for the rail. A message-tools withdraw (or an in-place
|
|
3
|
+
* edit) shadows a span of the transcript but leaves the withdrawn original
|
|
4
|
+
* rows in the node store — and, because the projection offers no suppression
|
|
5
|
+
* seam, a withdrawn user message stays visible in the host `order` while the
|
|
6
|
+
* sibling DOM hider hides its row by CSS. A rail row for such a node is a dead
|
|
7
|
+
* row: the transcript row it would target is `display:none`, so a click cannot
|
|
8
|
+
* scroll to it.
|
|
9
|
+
*
|
|
10
|
+
* The span a withdraw or edit covers is carried right on the store by the
|
|
11
|
+
* sibling `message-tools-withdrawn` / `message-tools-edited` node as
|
|
12
|
+
* `{ hiddenStartSeq, seq }` (its `anchorSeq` is `seq`, the exclusive end, so
|
|
13
|
+
* the divider/edited bubble itself is never inside its own span). Folding
|
|
14
|
+
* those spans — exactly the pair list message-tools' `foldHiddenRanges`
|
|
15
|
+
* builds — lets the rail skip any row whose `anchorSeq` falls inside one.
|
|
16
|
+
*
|
|
17
|
+
* This module reads only generic store node data (no import from message-tools
|
|
18
|
+
* and no dependence on its type map). An ordinary session carries no such
|
|
19
|
+
* node, so the fold is empty and every order row still renders: the fix never
|
|
20
|
+
* alters the baseline path.
|
|
21
|
+
*/
|
|
22
|
+
import type { ChatConversationViewNode } from '@deepseek-ai/dsh-client-ui-chat/client';
|
|
23
|
+
/**
|
|
24
|
+
* Fold the hidden seq spans out of the store into the flat
|
|
25
|
+
* `[start, endExclusive, ...]` pair list ordered by start — the same shape
|
|
26
|
+
* message-tools' renderers and DOM hider consume (`{ hiddenStartSeq, seq }`,
|
|
27
|
+
* inclusive start, exclusive end). A malformed carrier (missing or inverted
|
|
28
|
+
* bounds) is skipped rather than poisoning the fold.
|
|
29
|
+
* @param nodes - every materialized chat node (any order).
|
|
30
|
+
* @returns the flattened hidden spans; empty when no withdraw/edit landed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function foldHiddenSpans(nodes: readonly ChatConversationViewNode[]): number[];
|
|
33
|
+
/**
|
|
34
|
+
* Whether a node seq falls inside one of the folded hidden spans.
|
|
35
|
+
* @param spans - the flattened spans from {@link foldHiddenSpans}.
|
|
36
|
+
* @param seq - the node seq to test.
|
|
37
|
+
* @returns true when the seq is hidden (a withdrawn original).
|
|
38
|
+
*/
|
|
39
|
+
export declare function isSeqHidden(spans: readonly number[], seq: number): boolean;
|
|
40
|
+
//# sourceMappingURL=hidden-spans.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hidden-spans.d.ts","sourceRoot":"","sources":["../../../src/client/hidden-spans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AAStF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,wBAAwB,EAAE,GAAG,MAAM,EAAE,CAWpF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAO1E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isHiddenSpanCarrierKind } from './timeline-kinds.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fold the hidden seq spans out of the store into the flat
|
|
4
|
+
* `[start, endExclusive, ...]` pair list ordered by start — the same shape
|
|
5
|
+
* message-tools' renderers and DOM hider consume (`{ hiddenStartSeq, seq }`,
|
|
6
|
+
* inclusive start, exclusive end). A malformed carrier (missing or inverted
|
|
7
|
+
* bounds) is skipped rather than poisoning the fold.
|
|
8
|
+
* @param nodes - every materialized chat node (any order).
|
|
9
|
+
* @returns the flattened hidden spans; empty when no withdraw/edit landed.
|
|
10
|
+
*/
|
|
11
|
+
export function foldHiddenSpans(nodes) {
|
|
12
|
+
const pairs = [];
|
|
13
|
+
for (const node of nodes) {
|
|
14
|
+
if (!isHiddenSpanCarrierKind(node.kind))
|
|
15
|
+
continue;
|
|
16
|
+
const data = node.data;
|
|
17
|
+
if (data.hiddenStartSeq === undefined || data.seq === undefined)
|
|
18
|
+
continue;
|
|
19
|
+
if (data.hiddenStartSeq >= data.seq)
|
|
20
|
+
continue;
|
|
21
|
+
pairs.push([data.hiddenStartSeq, data.seq]);
|
|
22
|
+
}
|
|
23
|
+
pairs.sort((left, right) => left[0] - right[0]);
|
|
24
|
+
return pairs.flat();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether a node seq falls inside one of the folded hidden spans.
|
|
28
|
+
* @param spans - the flattened spans from {@link foldHiddenSpans}.
|
|
29
|
+
* @param seq - the node seq to test.
|
|
30
|
+
* @returns true when the seq is hidden (a withdrawn original).
|
|
31
|
+
*/
|
|
32
|
+
export function isSeqHidden(spans, seq) {
|
|
33
|
+
for (let index = 0; index + 1 < spans.length; index += 2) {
|
|
34
|
+
const start = spans[index];
|
|
35
|
+
const end = spans[index + 1];
|
|
36
|
+
if (start !== undefined && end !== undefined && seq >= start && seq < end)
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* hidden panel when those attributes change — no official code is modified.
|
|
11
11
|
* @module @khorsheed/dsh-message-timeline/client
|
|
12
12
|
*/
|
|
13
|
-
import type {
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
14
|
import { type TimelineConfig } from './config.ts';
|
|
15
15
|
export type { TimelineConfig } from './config.ts';
|
|
16
16
|
export { resolveConfig } from './config.ts';
|
|
@@ -25,5 +25,5 @@ export declare const inject: string[];
|
|
|
25
25
|
* @param ctx - client root context.
|
|
26
26
|
* @param config - entry config; defaults apply when the runner passes none.
|
|
27
27
|
*/
|
|
28
|
-
export declare function apply(ctx:
|
|
28
|
+
export declare function apply(ctx: Context, config?: Partial<TimelineConfig>): void;
|
|
29
29
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAclD,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAMhE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAK1G,2EAA2E;AAC3E,eAAO,MAAM,MAAM,UAAkC,CAAA;AAErD;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CA+B1E"}
|
|
@@ -12,15 +12,20 @@
|
|
|
12
12
|
* (one console.warn), missing rows only clear the active marker and make
|
|
13
13
|
* jumps no-ops. Nothing throws and no official code is modified.
|
|
14
14
|
*/
|
|
15
|
-
import type {
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
16
|
import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
|
|
17
17
|
import type { TimelineRailState } from './slots.ts';
|
|
18
18
|
/**
|
|
19
19
|
* Measure the panel's viewport box from one scrollport: its rect inset by the
|
|
20
|
-
* panel padding, minus the
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* panel padding, minus the chat input card at the bottom and the conversation
|
|
21
|
+
* tab strip at the top. The tabs render just above the scrollport, but
|
|
22
|
+
* centering reads against the whole window, so the strip height leaves the
|
|
23
|
+
* box either way — otherwise the list sits visibly high.
|
|
24
|
+
* The bottom ends at the `[data-composer-card]` top — the chat box — NOT the
|
|
25
|
+
* whole `[data-composer-seat]` top: dock cards (goal/todo/queue) sit above
|
|
26
|
+
* the input inside the seat, and counting them would push the timeline up off
|
|
27
|
+
* the conversation. Falls back to the seat top, then the column bottom, when
|
|
28
|
+
* the markers are absent.
|
|
24
29
|
* The scrollport's own width rides along for the width-cap fallback when the
|
|
25
30
|
* message-flow probe is unanswered.
|
|
26
31
|
* @param scrollport - the official conversation scrollport element.
|
|
@@ -35,12 +40,19 @@ export declare function measureGeometry(scrollport: HTMLElement): {
|
|
|
35
40
|
} | null;
|
|
36
41
|
/**
|
|
37
42
|
* The viewport x of the message flow's left edge: the left of the first
|
|
38
|
-
*
|
|
39
|
-
* centered content column (max 748px, `margin: 0 auto`).
|
|
40
|
-
*
|
|
41
|
-
* stays left of it — the panel may only occupy the scrollport's left
|
|
43
|
+
* laid-out `[data-chat-flow-kind]` row, which sits flush inside the official
|
|
44
|
+
* centered content column (max 748px, `margin: 0 auto`). Laid-out flow rows
|
|
45
|
+
* share that edge, so the first meaningful one suffices. The panel's right
|
|
46
|
+
* edge stays left of it — the panel may only occupy the scrollport's left
|
|
47
|
+
* gutter.
|
|
48
|
+
*
|
|
49
|
+
* Rows that are not laid out in the flow are skipped: a message-tools edit
|
|
50
|
+
* leaves the withdrawn originals in the DOM (hidden, zero-size, or off the
|
|
51
|
+
* column at x=0), and probing their left edge yields 0 — which would make the
|
|
52
|
+
* width gate compute a negative left gutter and hide the entire rail. Any
|
|
53
|
+
* real flow row sits inside the conversation column at a positive x.
|
|
42
54
|
* @param scrollport - the official conversation scrollport element.
|
|
43
|
-
* @returns the flow's left edge, or null while no flow row is rendered.
|
|
55
|
+
* @returns the flow's left edge, or null while no laid-out flow row is rendered.
|
|
44
56
|
*/
|
|
45
57
|
export declare function flowLeftX(scrollport: HTMLElement): number | null;
|
|
46
58
|
/**
|
|
@@ -80,5 +92,5 @@ export interface RailTracker {
|
|
|
80
92
|
* @param includeSteering - whether steering rows count as user dots.
|
|
81
93
|
* @returns the tracker face.
|
|
82
94
|
*/
|
|
83
|
-
export declare function installRailTracker(ctx:
|
|
95
|
+
export declare function installRailTracker(ctx: Context, includeSteering: boolean): RailTracker;
|
|
84
96
|
//# sourceMappingURL=rail-tracker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rail-tracker.d.ts","sourceRoot":"","sources":["../../../src/client/rail-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"rail-tracker.d.ts","sourceRoot":"","sources":["../../../src/client/rail-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AAGtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAgBnD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAyB5H;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAQhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAa7F;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,UAAU,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAOrE;AAED,yFAAyF;AACzF,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAA;IACjD,qFAAqF;IACrF,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,0CAA0C;IAC1C,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,GAAG,WAAW,CA2KtF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTimelineRowKind } from './timeline-kinds.js';
|
|
1
2
|
/** Horizontal inset of the rail from the scrollport's left edge (px). */
|
|
2
3
|
const RAIL_LEFT_INSET = 6;
|
|
3
4
|
/** Vertical padding above and below the rail inside the scrollport (px). */
|
|
@@ -11,10 +12,15 @@ const IDLE = {
|
|
|
11
12
|
};
|
|
12
13
|
/**
|
|
13
14
|
* Measure the panel's viewport box from one scrollport: its rect inset by the
|
|
14
|
-
* panel padding, minus the
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* panel padding, minus the chat input card at the bottom and the conversation
|
|
16
|
+
* tab strip at the top. The tabs render just above the scrollport, but
|
|
17
|
+
* centering reads against the whole window, so the strip height leaves the
|
|
18
|
+
* box either way — otherwise the list sits visibly high.
|
|
19
|
+
* The bottom ends at the `[data-composer-card]` top — the chat box — NOT the
|
|
20
|
+
* whole `[data-composer-seat]` top: dock cards (goal/todo/queue) sit above
|
|
21
|
+
* the input inside the seat, and counting them would push the timeline up off
|
|
22
|
+
* the conversation. Falls back to the seat top, then the column bottom, when
|
|
23
|
+
* the markers are absent.
|
|
18
24
|
* The scrollport's own width rides along for the width-cap fallback when the
|
|
19
25
|
* message-flow probe is unanswered.
|
|
20
26
|
* @param scrollport - the official conversation scrollport element.
|
|
@@ -25,8 +31,6 @@ export function measureGeometry(scrollport) {
|
|
|
25
31
|
const rect = scrollport.getBoundingClientRect();
|
|
26
32
|
if (rect.width === 0 && rect.height === 0)
|
|
27
33
|
return null;
|
|
28
|
-
const composer = scrollport.querySelector('[data-composer-seat]');
|
|
29
|
-
const composerHeight = composer?.getBoundingClientRect().height ?? 0;
|
|
30
34
|
let topInset = RAIL_VERTICAL_PADDING;
|
|
31
35
|
for (const tabs of scrollport.ownerDocument.querySelectorAll('[role="tablist"]')) {
|
|
32
36
|
const tabsRect = tabs.getBoundingClientRect();
|
|
@@ -36,25 +40,46 @@ export function measureGeometry(scrollport) {
|
|
|
36
40
|
break;
|
|
37
41
|
}
|
|
38
42
|
}
|
|
43
|
+
const card = scrollport.querySelector('[data-composer-card]');
|
|
44
|
+
const seat = scrollport.querySelector('[data-composer-seat]');
|
|
45
|
+
const bottom = card !== null
|
|
46
|
+
? card.getBoundingClientRect().top - RAIL_VERTICAL_PADDING
|
|
47
|
+
: seat !== null
|
|
48
|
+
? seat.getBoundingClientRect().top - RAIL_VERTICAL_PADDING
|
|
49
|
+
: rect.bottom - RAIL_VERTICAL_PADDING;
|
|
39
50
|
return {
|
|
40
51
|
left: rect.left + RAIL_LEFT_INSET,
|
|
41
52
|
top: rect.top + topInset,
|
|
42
|
-
height: Math.max(0,
|
|
53
|
+
height: Math.max(0, bottom - rect.top - topInset),
|
|
43
54
|
width: rect.width,
|
|
44
55
|
};
|
|
45
56
|
}
|
|
46
57
|
/**
|
|
47
58
|
* The viewport x of the message flow's left edge: the left of the first
|
|
48
|
-
*
|
|
49
|
-
* centered content column (max 748px, `margin: 0 auto`).
|
|
50
|
-
*
|
|
51
|
-
* stays left of it — the panel may only occupy the scrollport's left
|
|
59
|
+
* laid-out `[data-chat-flow-kind]` row, which sits flush inside the official
|
|
60
|
+
* centered content column (max 748px, `margin: 0 auto`). Laid-out flow rows
|
|
61
|
+
* share that edge, so the first meaningful one suffices. The panel's right
|
|
62
|
+
* edge stays left of it — the panel may only occupy the scrollport's left
|
|
63
|
+
* gutter.
|
|
64
|
+
*
|
|
65
|
+
* Rows that are not laid out in the flow are skipped: a message-tools edit
|
|
66
|
+
* leaves the withdrawn originals in the DOM (hidden, zero-size, or off the
|
|
67
|
+
* column at x=0), and probing their left edge yields 0 — which would make the
|
|
68
|
+
* width gate compute a negative left gutter and hide the entire rail. Any
|
|
69
|
+
* real flow row sits inside the conversation column at a positive x.
|
|
52
70
|
* @param scrollport - the official conversation scrollport element.
|
|
53
|
-
* @returns the flow's left edge, or null while no flow row is rendered.
|
|
71
|
+
* @returns the flow's left edge, or null while no laid-out flow row is rendered.
|
|
54
72
|
*/
|
|
55
73
|
export function flowLeftX(scrollport) {
|
|
56
|
-
const row
|
|
57
|
-
|
|
74
|
+
for (const row of scrollport.querySelectorAll('[data-chat-flow-kind]')) {
|
|
75
|
+
const rect = row.getBoundingClientRect();
|
|
76
|
+
if (rect.width === 0 && rect.height === 0)
|
|
77
|
+
continue;
|
|
78
|
+
if (rect.left <= 0)
|
|
79
|
+
continue;
|
|
80
|
+
return rect.left;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
58
83
|
}
|
|
59
84
|
/**
|
|
60
85
|
* Resolve the key of the user-message row the reading position belongs to:
|
|
@@ -72,7 +97,7 @@ export function activeRowKey(scrollport, includeSteering) {
|
|
|
72
97
|
let lastAbove = null;
|
|
73
98
|
for (const row of scrollport.querySelectorAll('[data-chat-flow-kind]')) {
|
|
74
99
|
const kind = row.dataset.chatFlowKind;
|
|
75
|
-
if (kind
|
|
100
|
+
if (kind === undefined || !isTimelineRowKind(kind, includeSteering))
|
|
76
101
|
continue;
|
|
77
102
|
if (row.getBoundingClientRect().bottom <= viewTop) {
|
|
78
103
|
lastAbove = row.dataset.chatAnchorKey ?? lastAbove;
|
|
@@ -114,6 +139,12 @@ export function installRailTracker(ctx, includeSteering) {
|
|
|
114
139
|
let resizeObserver;
|
|
115
140
|
let rafPending = false;
|
|
116
141
|
let warned = false;
|
|
142
|
+
// Bind-generation token: every teardown (a rebind or a dispose) bumps it, so
|
|
143
|
+
// a still-pending bind frame that closed over an older token becomes stale
|
|
144
|
+
// and bails. Without this, a bind scheduled before a dispose/rebind would
|
|
145
|
+
// run afterward — binding the global scrollport under a stale session or,
|
|
146
|
+
// worse, re-adding listeners and a ResizeObserver to a disposed tracker.
|
|
147
|
+
let bindToken = 0;
|
|
117
148
|
const nextFrame = typeof requestAnimationFrame === 'function'
|
|
118
149
|
? requestAnimationFrame
|
|
119
150
|
: (callback) => { setTimeout(callback, 16); };
|
|
@@ -130,10 +161,37 @@ export function installRailTracker(ctx, includeSteering) {
|
|
|
130
161
|
for (const fn of [...listeners])
|
|
131
162
|
fn();
|
|
132
163
|
};
|
|
164
|
+
const attach = (el) => {
|
|
165
|
+
if (scrollport !== null)
|
|
166
|
+
scrollport.removeEventListener('scroll', onScroll);
|
|
167
|
+
resizeObserver?.disconnect();
|
|
168
|
+
scrollport = el;
|
|
169
|
+
scrollport.addEventListener('scroll', onScroll, { passive: true });
|
|
170
|
+
if (typeof ResizeObserver === 'function') {
|
|
171
|
+
resizeObserver = new ResizeObserver(scheduleUpdate);
|
|
172
|
+
resizeObserver.observe(el);
|
|
173
|
+
const composer = el.querySelector('[data-composer-seat]');
|
|
174
|
+
if (composer !== null)
|
|
175
|
+
resizeObserver.observe(composer);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
133
178
|
const update = () => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
179
|
+
// Self-heal the scrollport binding: while unbound (or bound to a node the
|
|
180
|
+
// host remounted away — the conversation view re-renders under a stable
|
|
181
|
+
// session), re-probe the DOM. On rc hosts currentProvideInfo announced
|
|
182
|
+
// the remount; on 0.1.2 the feed is gone, so the observer/resize/scroll
|
|
183
|
+
// cadence that already lands here owns re-resolution on both lines.
|
|
184
|
+
let el = scrollport;
|
|
185
|
+
if (el === null || !el.isConnected) {
|
|
186
|
+
if (activeSession === undefined)
|
|
187
|
+
return;
|
|
188
|
+
const found = document.querySelector('[data-conversation-scroll]');
|
|
189
|
+
if (found === null)
|
|
190
|
+
return;
|
|
191
|
+
attach(found);
|
|
192
|
+
el = found;
|
|
193
|
+
}
|
|
194
|
+
const geometry = measureGeometry(el);
|
|
137
195
|
if (geometry === null)
|
|
138
196
|
return;
|
|
139
197
|
publish({
|
|
@@ -143,9 +201,9 @@ export function installRailTracker(ctx, includeSteering) {
|
|
|
143
201
|
top: geometry.top,
|
|
144
202
|
height: geometry.height,
|
|
145
203
|
scrollportWidth: geometry.width,
|
|
146
|
-
flowLeft: flowLeftX(
|
|
147
|
-
activeKey: activeRowKey(
|
|
148
|
-
chatView:
|
|
204
|
+
flowLeft: flowLeftX(el),
|
|
205
|
+
activeKey: activeRowKey(el, includeSteering),
|
|
206
|
+
chatView: el.querySelector('[data-chat-flow]') !== null,
|
|
149
207
|
});
|
|
150
208
|
};
|
|
151
209
|
const scheduleUpdate = () => {
|
|
@@ -159,6 +217,7 @@ export function installRailTracker(ctx, includeSteering) {
|
|
|
159
217
|
};
|
|
160
218
|
const onScroll = () => { scheduleUpdate(); };
|
|
161
219
|
const teardownBind = () => {
|
|
220
|
+
bindToken++;
|
|
162
221
|
if (scrollport !== null)
|
|
163
222
|
scrollport.removeEventListener('scroll', onScroll);
|
|
164
223
|
scrollport = null;
|
|
@@ -175,7 +234,14 @@ export function installRailTracker(ctx, includeSteering) {
|
|
|
175
234
|
publish(IDLE);
|
|
176
235
|
return;
|
|
177
236
|
}
|
|
237
|
+
const token = bindToken;
|
|
178
238
|
nextFrame(() => {
|
|
239
|
+
// This frame was scheduled by an earlier bind whose generation has since
|
|
240
|
+
// been superseded by a rebind or a dispose: ignore it entirely rather
|
|
241
|
+
// than binding the scrollport under a stale session or binding into a
|
|
242
|
+
// disposed tracker.
|
|
243
|
+
if (token !== bindToken)
|
|
244
|
+
return;
|
|
179
245
|
const found = document.querySelector('[data-conversation-scroll]');
|
|
180
246
|
if (found === null) {
|
|
181
247
|
if (!warned) {
|
|
@@ -185,15 +251,7 @@ export function installRailTracker(ctx, includeSteering) {
|
|
|
185
251
|
publish(IDLE);
|
|
186
252
|
return;
|
|
187
253
|
}
|
|
188
|
-
|
|
189
|
-
scrollport.addEventListener('scroll', onScroll, { passive: true });
|
|
190
|
-
if (typeof ResizeObserver === 'function') {
|
|
191
|
-
resizeObserver = new ResizeObserver(scheduleUpdate);
|
|
192
|
-
resizeObserver.observe(scrollport);
|
|
193
|
-
const composer = scrollport.querySelector('[data-composer-seat]');
|
|
194
|
-
if (composer !== null)
|
|
195
|
-
resizeObserver.observe(composer);
|
|
196
|
-
}
|
|
254
|
+
attach(found);
|
|
197
255
|
update();
|
|
198
256
|
});
|
|
199
257
|
};
|
|
@@ -201,7 +259,15 @@ export function installRailTracker(ctx, includeSteering) {
|
|
|
201
259
|
bind(ctx.sessions.list.getSnapshot().current);
|
|
202
260
|
};
|
|
203
261
|
const stopList = ctx.sessions.list.subscribe(bindCurrent);
|
|
204
|
-
|
|
262
|
+
// Host 0.1.2-alpha.1 removed ISessions.currentProvideInfo (commit
|
|
263
|
+
// be531688f3 — the provide-channel projection went with the runtime
|
|
264
|
+
// package). rc hosts still publish it, and it is the precise remount
|
|
265
|
+
// signal there, so subscribe when the feed exists and skip it when the
|
|
266
|
+
// service predates/omits it — update() below re-resolves a missing or
|
|
267
|
+
// detached scrollport from the MutationObserver/resize cadence, which
|
|
268
|
+
// covers the remount signal on both lines.
|
|
269
|
+
const provideFeed = ctx.sessions.currentProvideInfo;
|
|
270
|
+
const stopProvide = provideFeed?.subscribe(bindCurrent);
|
|
205
271
|
bindCurrent();
|
|
206
272
|
// Layout fallbacks beyond the scrollport's own ResizeObserver: a window
|
|
207
273
|
// resize re-measures, and a body MutationObserver catches panel folds and
|
|
@@ -230,7 +296,7 @@ export function installRailTracker(ctx, includeSteering) {
|
|
|
230
296
|
},
|
|
231
297
|
dispose: () => {
|
|
232
298
|
stopList();
|
|
233
|
-
stopProvide();
|
|
299
|
+
stopProvide?.();
|
|
234
300
|
if (typeof window !== 'undefined')
|
|
235
301
|
window.removeEventListener('resize', onWindowResize);
|
|
236
302
|
mutationObserver?.disconnect();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* face (jump/loadOlder plus the rail geometry hook) and the composed props of
|
|
4
4
|
* the `conversation.session.header.utilities` entry.
|
|
5
5
|
*/
|
|
6
|
-
import type { ChatConversationViewNode } from '@deepseek-ai/dsh-client-
|
|
6
|
+
import type { ChatConversationViewNode } from '@deepseek-ai/dsh-client-ui-chat/client';
|
|
7
7
|
import type { HostObservable, InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
8
8
|
/**
|
|
9
9
|
* Live rail geometry plus the currently visible user message, published by
|
|
@@ -18,7 +18,11 @@ export interface TimelineRailState {
|
|
|
18
18
|
left: number;
|
|
19
19
|
/** Viewport y of the rail's top edge (below the session header). */
|
|
20
20
|
top: number;
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Rail height in px: the scrollport from below the tab strip down to the
|
|
23
|
+
* chat input card top — dock cards (goal/todo/queue) above the input never
|
|
24
|
+
* shorten it, so the timeline stays flush with the chat box.
|
|
25
|
+
*/
|
|
22
26
|
height: number;
|
|
23
27
|
/** Scrollport width in px (the degraded width cap when the flow probe fails). */
|
|
24
28
|
scrollportWidth: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../../../src/client/slots.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACtF,OAAO,KAAK,EACV,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EACtD,MAAM,kCAAkC,CAAA;
|
|
1
|
+
{"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../../../src/client/slots.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACtF,OAAO,KAAK,EACV,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EACtD,MAAM,kCAAkC,CAAA;AAUzC;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,yFAAyF;IACzF,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,sEAAsE;IACtE,KAAK,EAAE,OAAO,CAAA;IACd,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,oEAAoE;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,yEAAyE;IACzE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,0DAA0D;AAC1D,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,eAAe,EAAE,OAAO,CAAA;IACxB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAA;IAClB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAA;IACpB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,wEAAwE;IACxE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,KAAK,EAAE;QACL,qEAAqE;QACrE,IAAI,EAAE,cAAc,CAAC,iBAAiB,CAAC,CAAA;KACxC,CAAA;CACF;AAED,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACxC;AAED,gDAAgD;AAChD,MAAM,MAAM,iBAAiB,GAC3B,YAAY,CAAC,uCAAuC,CAAC,GACnD,UAAU,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,kBAAkB,CAAC,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline node-kind classification. The rail's projection (which store nodes
|
|
3
|
+
* become rows) and its DOM tracking (which flow rows read as the reading
|
|
4
|
+
* position) must agree on the set of "timeline row" kinds, and the hidden-
|
|
5
|
+
* span fold must agree on the set of "span carrier" kinds. Rather than repeat
|
|
6
|
+
* these inline predicates at every site — where a future kind could be added
|
|
7
|
+
* to one place and silently missed in another — they live here as the single
|
|
8
|
+
* source of truth. Both are pure and read only the `kind` string, so they hold
|
|
9
|
+
* no dependency on the message-tools package and work for any session.
|
|
10
|
+
*/
|
|
11
|
+
/** A message-tools edited-bubble row (an in-place edit replacement). */
|
|
12
|
+
export declare const EDIT_KIND = "message-tools-edited";
|
|
13
|
+
/** A message-tools restored row (a withdrawn message replayed at the tail). */
|
|
14
|
+
export declare const RESTORED_KIND = "message-tools-restored";
|
|
15
|
+
/**
|
|
16
|
+
* Whether a node kind renders as a timeline row. The rail lists user messages
|
|
17
|
+
* (and optionally steering), plus the message-tools edited/restored bubbles
|
|
18
|
+
* that the host `order` does not always surface.
|
|
19
|
+
* @param kind - the node's kind string.
|
|
20
|
+
* @param includeSteering - whether steering rows count as user messages.
|
|
21
|
+
* @returns true when the kind belongs on the timeline.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isTimelineRowKind(kind: string, includeSteering: boolean): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a node kind carries a hidden span (`{ hiddenStartSeq, seq }`) that
|
|
26
|
+
* the rail must fold to drop the covered originals. The withdrawal divider and
|
|
27
|
+
* the edited bubble both declare the span they shadow.
|
|
28
|
+
* @param kind - the node's kind string.
|
|
29
|
+
* @returns true when the kind declares a hidden span.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isHiddenSpanCarrierKind(kind: string): boolean;
|
|
32
|
+
//# sourceMappingURL=timeline-kinds.d.ts.map
|