@khorsheed/dsh-message-timeline 0.2.3 → 0.2.5

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.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The first visible loaded row of a turn, of any node kind: the landing
3
+ * target for a turn with no user-kind row (an automated turn renders a
4
+ * `turn-trigger` card, not a user bubble), mirroring the official rail's
5
+ * `user ?? loaded[0]` anchor. Only nodes in the host's visible `order`
6
+ * qualify — a store-only or hidden node is not a jump target.
7
+ * @param order - visible chat node keys in render order.
8
+ * @param nodes - the live node store.
9
+ * @param seq - the turn's `turn/start` seq (inclusive).
10
+ * @param end - the next turn's start seq (exclusive), or Infinity for the last turn.
11
+ * @returns the anchor key, or null when the turn has no visible loaded row.
12
+ */
13
+ export function firstVisibleKeyInTurn(order, nodes, seq, end) {
14
+ for (const key of order) {
15
+ const node = nodes.get(key);
16
+ if (node === undefined)
17
+ continue;
18
+ if (node.anchorSeq < seq)
19
+ continue;
20
+ if (node.anchorSeq >= end)
21
+ break;
22
+ if (node.visibility === 'hidden')
23
+ continue;
24
+ return key;
25
+ }
26
+ return null;
27
+ }
28
+ /**
29
+ * Wrap one loaded item as a rail row.
30
+ * @param item - the window row.
31
+ * @param turn - its owning outline turn, or undefined.
32
+ * @returns the loaded rail row.
33
+ */
34
+ function toLoadedRow(item, turn) {
35
+ return { kind: 'loaded', id: item.key, key: item.key, node: item.node, anchorSeq: item.node.anchorSeq, turn };
36
+ }
37
+ /**
38
+ * Merge loaded rows with the outline into the full row list. Both inputs are
39
+ * transcript-ordered (loaded rows anchorSeq-ascending, the outline strictly
40
+ * increasing by turn), so one pass interleaves them without a final sort: an
41
+ * outline entry whose seq range holds at least one loaded row is covered and
42
+ * emits nothing; every other entry emits its outline row at the range's
43
+ * position. A loaded row belongs to the last entry whose start it follows —
44
+ * when the window runs ahead of the projection feed, the newest rows read as
45
+ * the last named turn until the feed catches up (suppressing its outline row
46
+ * beats duplicating the turn).
47
+ * @param loaded - the window's user rows, anchorSeq-ascending.
48
+ * @param outline - narrowed outline entries, or undefined (capability absent).
49
+ * @returns the merged row list, transcript-ordered.
50
+ */
51
+ export function mergeRows(loaded, outline) {
52
+ if (outline === undefined)
53
+ return loaded.map(item => toLoadedRow(item, undefined));
54
+ const rows = [];
55
+ let cursor = 0;
56
+ for (const [index, entry] of outline.entries()) {
57
+ const end = outline.at(index + 1)?.seq ?? Number.POSITIVE_INFINITY;
58
+ let covered = false;
59
+ for (;;) {
60
+ const item = loaded[cursor];
61
+ if (item === undefined || item.node.anchorSeq >= end)
62
+ break;
63
+ rows.push(toLoadedRow(item, item.node.anchorSeq >= entry.seq ? entry.turn : undefined));
64
+ covered = covered || item.node.anchorSeq >= entry.seq;
65
+ cursor += 1;
66
+ }
67
+ if (!covered) {
68
+ rows.push({
69
+ kind: 'outline',
70
+ id: `turn:${entry.turn}`,
71
+ turn: entry.turn,
72
+ seq: entry.seq,
73
+ anchorSeq: entry.seq,
74
+ prompt: entry.prompt,
75
+ });
76
+ }
77
+ }
78
+ for (;;) {
79
+ const item = loaded[cursor];
80
+ if (item === undefined)
81
+ break;
82
+ rows.push(toLoadedRow(item, undefined));
83
+ cursor += 1;
84
+ }
85
+ return rows;
86
+ }
@@ -4,6 +4,7 @@
4
4
  * the `conversation.session.header.utilities` entry.
5
5
  */
6
6
  import type { ChatConversationViewNode } from '@deepseek-ai/dsh-client-ui-chat/client';
7
+ import type { ISession } from '@deepseek-ai/dsh-api-session-controller/client';
7
8
  import type { HostObservable, InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
8
9
  /**
9
10
  * Live rail geometry plus the currently visible user message, published by
@@ -43,12 +44,24 @@ export interface TimelineRailInjected {
43
44
  includeSteering: boolean;
44
45
  /** Timeline panel width in px (config). */
45
46
  panelWidth: number;
46
- /** History pages to prefetch when the panel opens (config). */
47
+ /**
48
+ * History pages to prefetch when the panel opens (config). Applies only to
49
+ * the window-paging fallback: when the host serves the turnOutline
50
+ * projection the list starts complete and nothing prefetches.
51
+ */
47
52
  initialPages: number;
48
- /** Pull one older history page for the rendered session. */
53
+ /** Pull one older history page for the rendered session (fallback path). */
49
54
  loadOlder: () => Promise<void>;
50
- /** Scroll the transcript to the user message row addressed by `key`. */
51
- jumpTo: (key: string) => void;
55
+ /**
56
+ * Page history back until the window covers `seq` (the session jump
57
+ * loader): how an outline-only row's turn becomes loaded.
58
+ */
59
+ loadThrough: ISession['loadThrough'];
60
+ /**
61
+ * Scroll the transcript to the user message row addressed by `key`.
62
+ * @returns whether the row was found and scrolled.
63
+ */
64
+ jumpTo: (key: string) => boolean;
52
65
  hooks: {
53
66
  /** Live panel geometry and active marker for the current session. */
54
67
  rail: HostObservable<TimelineRailState>;
@@ -12,10 +12,15 @@
12
12
  export declare const EDIT_KIND = "message-tools-edited";
13
13
  /** A message-tools restored row (a withdrawn message replayed at the tail). */
14
14
  export declare const RESTORED_KIND = "message-tools-restored";
15
+ /** An automated turn's trigger card (restart recovery, scheduled job, …). */
16
+ export declare const TRIGGER_KIND = "turn-trigger";
15
17
  /**
16
18
  * 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
+ * (and optionally steering), the message-tools edited/restored bubbles that
20
+ * the host `order` does not always surface, and automated-turn trigger cards
21
+ * (whose turns have no human prompt but real transcript content). The same
22
+ * set drives the reading-position anchor, so landing on a trigger card keeps
23
+ * the selection on its turn instead of moving to the next user row.
19
24
  * @param kind - the node's kind string.
20
25
  * @param includeSteering - whether steering rows count as user messages.
21
26
  * @returns true when the kind belongs on the timeline.
@@ -12,17 +12,22 @@
12
12
  export const EDIT_KIND = 'message-tools-edited';
13
13
  /** A message-tools restored row (a withdrawn message replayed at the tail). */
14
14
  export const RESTORED_KIND = 'message-tools-restored';
15
+ /** An automated turn's trigger card (restart recovery, scheduled job, …). */
16
+ export const TRIGGER_KIND = 'turn-trigger';
15
17
  /**
16
18
  * 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
+ * (and optionally steering), the message-tools edited/restored bubbles that
20
+ * the host `order` does not always surface, and automated-turn trigger cards
21
+ * (whose turns have no human prompt but real transcript content). The same
22
+ * set drives the reading-position anchor, so landing on a trigger card keeps
23
+ * the selection on its turn instead of moving to the next user row.
19
24
  * @param kind - the node's kind string.
20
25
  * @param includeSteering - whether steering rows count as user messages.
21
26
  * @returns true when the kind belongs on the timeline.
22
27
  */
23
28
  export function isTimelineRowKind(kind, includeSteering) {
24
29
  return kind === 'user' || (includeSteering && kind === 'steering')
25
- || kind === EDIT_KIND || kind === RESTORED_KIND;
30
+ || kind === EDIT_KIND || kind === RESTORED_KIND || kind === TRIGGER_KIND;
26
31
  }
27
32
  /**
28
33
  * Whether a node kind carries a hidden span (`{ hiddenStartSeq, seq }`) that
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@khorsheed/dsh-message-timeline",
3
3
  "description": "Message timeline: a flat floating list over the conversation scrollport that jumps to any user message, with a tick-only rest state",
4
- "version": "0.2.3",
4
+ "version": "0.2.5",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -10,10 +10,6 @@
10
10
  "types": "./lib/types/index.d.ts",
11
11
  "default": "./lib/index.js"
12
12
  },
13
- "./invariant": {
14
- "types": "./lib/types/invariant.d.ts",
15
- "default": "./lib/invariant.js"
16
- },
17
13
  "./client": {
18
14
  "types": "./lib/types/client/index.d.ts",
19
15
  "default": "./lib/client.js"
@@ -36,19 +32,28 @@
36
32
  },
37
33
  "compat": {
38
34
  "minHost": "0.1.2-rc.1",
39
- "verifiedHost": "0.1.2-rc.1"
35
+ "notes": "Whole-session list relies on the host turnOutline projection (dsh-session-turn-outline, mounted by the official web bundle since 0.1.2-alpha.3 — below the minHost floor); assemblies without the unit degrade to window paging (prefetch on open plus panel-top loadOlder), fully functional but the first message appears only once paged in.",
36
+ "verifiedHost": "0.2.1-rc.1"
40
37
  }
41
38
  },
42
39
  "license": "MIT",
40
+ "engines": {
41
+ "dsh": ">=0.1.2-rc.1 <0.3.0-0"
42
+ },
43
43
  "peerDependencies": {
44
44
  "@deepseek-ai/cordis": "^4.0.1",
45
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
46
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
47
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
48
- "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
49
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
45
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
46
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
47
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
48
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
49
+ "@deepseek-ai/dsh-session-turn-outline": "^0.1.2-alpha.3 || ^0.2.0-rc.1 || ^0.2.1-rc.1",
50
50
  "react": "^18.2.0"
51
51
  },
52
+ "peerDependenciesMeta": {
53
+ "@deepseek-ai/dsh-session-turn-outline": {
54
+ "optional": true
55
+ }
56
+ },
52
57
  "files": [
53
58
  "lib",
54
59
  "cordis.patch.yml",
package/lib/invariant.js DELETED
@@ -1,28 +0,0 @@
1
- //#region lib/types/invariant.js
2
- /**
3
- * Package-owned invariant companion for `@khorsheed/dsh-message-timeline`.
4
- * @module @khorsheed/dsh-message-timeline/invariant
5
- */
6
- const PACKAGE_NAME = "@khorsheed/dsh-message-timeline";
7
- /** Cordis companion plugin name. */
8
- const name = "client-message-timeline-invariant";
9
- /** Service required before the companion can reserve package ownership. */
10
- const inject = ["invariants"];
11
- /**
12
- * No runtime invariant: the rail contribution is one slot entry whose disposal
13
- * is proven by the HMR-safety spec — the plugin writes no session state, emits
14
- * no cordis events, and reads the conversation snapshot through the framework
15
- * hook only. Its DOM probe targets the official chat row attributes
16
- * ([data-chat-anchor-key] / [data-conversation-scroll]) read-only and
17
- * degrades to a hidden rail when they change, so no second authority exists to
18
- * check at runtime.
19
- */
20
- const install = () => {};
21
- /**
22
- * Register this package's invariant companion.
23
- * @param ctx - Cordis context carrying the invariant service.
24
- * @returns the installed registration's disposer after setup succeeds.
25
- */
26
- const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
27
- //#endregion
28
- export { apply, inject, name };
@@ -1,16 +0,0 @@
1
- /**
2
- * Package-owned invariant companion for `@khorsheed/dsh-message-timeline`.
3
- * @module @khorsheed/dsh-message-timeline/invariant
4
- */
5
- import type { Context } from '@deepseek-ai/cordis';
6
- /** Cordis companion plugin name. */
7
- export declare const name = "client-message-timeline-invariant";
8
- /** Service required before the companion can reserve package ownership. */
9
- export declare const inject: string[];
10
- /**
11
- * Register this package's invariant companion.
12
- * @param ctx - Cordis context carrying the invariant service.
13
- * @returns the installed registration's disposer after setup succeeds.
14
- */
15
- export declare const apply: (ctx: Context) => Promise<() => void>;
16
- //# sourceMappingURL=invariant.d.ts.map
@@ -1,26 +0,0 @@
1
- /**
2
- * Package-owned invariant companion for `@khorsheed/dsh-message-timeline`.
3
- * @module @khorsheed/dsh-message-timeline/invariant
4
- */
5
- const PACKAGE_NAME = '@khorsheed/dsh-message-timeline';
6
- /** Cordis companion plugin name. */
7
- export const name = 'client-message-timeline-invariant';
8
- /** Service required before the companion can reserve package ownership. */
9
- export const inject = ['invariants'];
10
- /**
11
- * No runtime invariant: the rail contribution is one slot entry whose disposal
12
- * is proven by the HMR-safety spec — the plugin writes no session state, emits
13
- * no cordis events, and reads the conversation snapshot through the framework
14
- * hook only. Its DOM probe targets the official chat row attributes
15
- * ([data-chat-anchor-key] / [data-conversation-scroll]) read-only and
16
- * degrades to a hidden rail when they change, so no second authority exists to
17
- * check at runtime.
18
- */
19
- const install = () => { };
20
- /**
21
- * Register this package's invariant companion.
22
- * @param ctx - Cordis context carrying the invariant service.
23
- * @returns the installed registration's disposer after setup succeeds.
24
- */
25
- export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
26
- /* jscpd:ignore-end */