dsh-model-everything 0.5.2 → 0.6.1
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/lib/annotation.js +323 -0
- package/lib/client.js +1614 -44
- package/lib/diagram-render-tool.js +13 -1
- package/lib/diagram-review-tool.js +1 -1
- package/lib/diagram.js +33 -0
- package/lib/gate.js +23 -19
- package/lib/render/paint.js +3 -0
- package/lib/render/svg.js +8 -2
- package/lib/routing.js +12 -0
- package/lib/transcript.js +327 -0
- package/lib/types/annotation.d.ts +128 -0
- package/lib/types/client.d.ts +89 -6
- package/lib/types/diagram.d.ts +7 -0
- package/lib/types/gate.d.ts +8 -6
- package/lib/types/render/paint.d.ts +2 -0
- package/lib/types/transcript.d.ts +82 -0
- package/package.json +149 -148
- package/skills/model-everything/SKILL.md +3 -2
- package/skills/model-everything-diagram/SKILL.md +21 -1
- package/src/annotation.ts +333 -0
- package/src/client.ts +1841 -307
- package/src/diagram-render-tool.ts +13 -1
- package/src/diagram-review-tool.ts +1 -1
- package/src/diagram.ts +31 -0
- package/src/gate.ts +23 -19
- package/src/render/paint.ts +5 -0
- package/src/render/svg.ts +8 -2
- package/src/routing.ts +12 -0
- package/src/transcript.ts +349 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An annotation: the reader points at a node in a drawing and says something about it.
|
|
3
|
+
*
|
|
4
|
+
* The note has to reach the session as an ordinary message, because the session log is the only record a
|
|
5
|
+
* replay, a fork or a resumed agent can rebuild from. Two transports can carry it, and they consume the
|
|
6
|
+
* same text: a host command that turns the note into a follow-up (planned; the interface is
|
|
7
|
+
* `annotationMessage`), and the composer, which hands the text to the reader's own input box (works today).
|
|
8
|
+
*
|
|
9
|
+
* Splitting the text from the transport is what keeps the two interchangeable, and what lets this module be
|
|
10
|
+
* tested without a browser.
|
|
11
|
+
*/
|
|
12
|
+
/** What the reader said, and where. */
|
|
13
|
+
export interface Annotation {
|
|
14
|
+
/** The node's id, as it appears in the markup the renderer wrote. */
|
|
15
|
+
nodeId: string;
|
|
16
|
+
/** The node's label, when it differs from the id: a reader points at words, not at identifiers. */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** The note itself. Empty is meaningful: the reader marked the node and said nothing. */
|
|
19
|
+
note?: string;
|
|
20
|
+
/** Which drawing, when the reader is looking at more than one. A single source line is enough. */
|
|
21
|
+
caption?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The text that travels to the session: what was marked, and what was said, and nothing invented.
|
|
25
|
+
*
|
|
26
|
+
* A blank note is not dropped - it says the node was marked - because "the reader pointed here" and "the
|
|
27
|
+
* reader pointed here and said this" are different messages, and only one of them survives a silent trim.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* The text that travels to the session: what was marked, what was said, and nothing invented.
|
|
31
|
+
*
|
|
32
|
+
* One message for N notes, with a fixed opening so both the reader and the model know what they are looking
|
|
33
|
+
* at: a note arrives without the picture, so the block says what it is about, which drawing, and that every
|
|
34
|
+
* numbered item is a separate answer. The shape follows the annotation plugin this harness already ships -
|
|
35
|
+
* an opening sentence, numbered items, a question line - and it stays English: this is protocol between
|
|
36
|
+
* reader and model, not interface chrome that follows the page's language.
|
|
37
|
+
*
|
|
38
|
+
* Plain lines, not a markdown quote: the reader's bubble is rendered as one text node, so a quote never
|
|
39
|
+
* becomes a quote there - it would only show its own `>` characters. The bubble is folded afterwards.
|
|
40
|
+
* A blank note is not dropped: 'marked, no note' and 'marked with this note' are different messages.
|
|
41
|
+
*/
|
|
42
|
+
export declare function annotationBlock(annotations: Annotation[]): string;
|
|
43
|
+
/** One annotation, in the same block shape: a caller with a single note should not get a second format. */
|
|
44
|
+
export declare function annotationMessage(annotation: Annotation): string;
|
|
45
|
+
/** The parts of the page this module touches, so the client bundle needs no DOM lib and a test can fake them. */
|
|
46
|
+
export interface ElementLike {
|
|
47
|
+
value?: unknown;
|
|
48
|
+
querySelector?: (selector: string) => unknown;
|
|
49
|
+
textContent?: unknown;
|
|
50
|
+
focus?: () => void;
|
|
51
|
+
dispatchEvent?: (event: unknown) => boolean;
|
|
52
|
+
getAttribute?: (name: string) => string | null;
|
|
53
|
+
tagName?: unknown;
|
|
54
|
+
}
|
|
55
|
+
export interface DocumentLike {
|
|
56
|
+
querySelector: (selector: string) => unknown;
|
|
57
|
+
querySelectorAll?: (selector: string) => ArrayLike<unknown>;
|
|
58
|
+
}
|
|
59
|
+
export declare function findComposer(doc: DocumentLike | undefined): ElementLike | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* What the page has, for a status line: a probe with no console needs one place to report from.
|
|
62
|
+
*
|
|
63
|
+
* Counts only. A reader's draft is never read into the report.
|
|
64
|
+
*/
|
|
65
|
+
export declare function pageCensus(doc: DocumentLike | undefined): string;
|
|
66
|
+
/** What a write did, so the caller can say something true instead of something hopeful. */
|
|
67
|
+
export type Placed = 'filled' | 'no-composer';
|
|
68
|
+
/**
|
|
69
|
+
* Add the text to the composer, above whatever the reader has already typed.
|
|
70
|
+
*
|
|
71
|
+
* Appending rather than replacing is the only behavior that cannot lose work: a reader who was halfway
|
|
72
|
+
* through a message and clicked an annotation should find both halves in the box, not the annotation alone.
|
|
73
|
+
*/
|
|
74
|
+
export declare function fillComposer(doc: DocumentLike | undefined, text: string): Placed;
|
|
75
|
+
/** Whether the page can take a Return key for the composer at all, so a button may promise it. */
|
|
76
|
+
export declare function submitComposer(doc: DocumentLike | undefined): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* One note, two actions.
|
|
79
|
+
*
|
|
80
|
+
* Returns the sentence to show the reader: `sent` and `the box was not there` are different outcomes, and a
|
|
81
|
+
* button that reports the wrong one is worse than no button. The text is appended above a half-written draft,
|
|
82
|
+
* never in place of it.
|
|
83
|
+
*/
|
|
84
|
+
export declare function pushNote(document: DocumentLike | undefined, picked: {
|
|
85
|
+
id: string;
|
|
86
|
+
} | null, note: string, caption: string, submit: boolean): string;
|
|
87
|
+
/**
|
|
88
|
+
* The session a page is showing, read from its own address.
|
|
89
|
+
*
|
|
90
|
+
* The client services expose no "current session", and a slot's props are not guaranteed to carry one, but the
|
|
91
|
+
* page has to name the session in its URL to survive a reload - so the address is the one source that is always
|
|
92
|
+
* there. Both spellings are accepted: the `session-<uuid>` id this Harness mints and a bare uuid.
|
|
93
|
+
*/
|
|
94
|
+
export declare function sessionFromLocation(location: {
|
|
95
|
+
pathname?: string;
|
|
96
|
+
search?: string;
|
|
97
|
+
hash?: string;
|
|
98
|
+
} | undefined): string | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Put a whole annotation block in the composer, and say what happened.
|
|
101
|
+
*
|
|
102
|
+
* The block is one message for N notes: the model is told to answer each item, and the reader's input box sees
|
|
103
|
+
* the structured text rather than N raw fragments. The same failure vocabulary as a single note.
|
|
104
|
+
*/
|
|
105
|
+
export declare function pushBlock(document: DocumentLike | undefined, annotations: Annotation[], caption: string, submit: boolean): string;
|
|
106
|
+
export interface Rect {
|
|
107
|
+
left: number;
|
|
108
|
+
top: number;
|
|
109
|
+
width: number;
|
|
110
|
+
height: number;
|
|
111
|
+
}
|
|
112
|
+
/** Where the reader's input box is, for a widget that has to sit right above it. */
|
|
113
|
+
export declare function composerRect(doc: DocumentLike | undefined): Rect | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Put the block above what the reader has already typed, and say what happened.
|
|
116
|
+
*
|
|
117
|
+
* Above, because the annotations introduce the message: the reader's own words are the point, and a block
|
|
118
|
+
* appended after them reads as an afterthought. A rich editor has no portable way to insert at the start, so
|
|
119
|
+
* it takes the same path as an empty box - the block becomes the message and the reader's text follows it.
|
|
120
|
+
*/
|
|
121
|
+
export declare function prependBlock(document: DocumentLike | undefined, annotations: Annotation[], caption: string): string;
|
|
122
|
+
/**
|
|
123
|
+
* The block without its question line, for a message that has no question.
|
|
124
|
+
*
|
|
125
|
+
* The line is an invitation for the reader's own words; with the box empty it is the last thing the bubble shows
|
|
126
|
+
* and the only thing a chip would be folded away from, so it goes.
|
|
127
|
+
*/
|
|
128
|
+
export declare function withoutEmptyQuestion(block: string): string;
|
package/lib/types/client.d.ts
CHANGED
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
* compiled output in `window.__ModuleLoader__.load(...)`, which is the loader contract itself.
|
|
13
13
|
*/
|
|
14
14
|
import { type Palette, type PaletteMode } from './render/palette.js';
|
|
15
|
+
import { type DocumentLike } from './annotation.js';
|
|
15
16
|
export interface ReactLike {
|
|
16
17
|
createElement: (type: unknown, props?: Record<string, unknown> | null, ...children: unknown[]) => unknown;
|
|
17
|
-
useState?: <T>(initial: T) => [T, (next: T) => void];
|
|
18
|
+
useState?: <T>(initial: T) => [T, (next: T | ((previous: T) => T)) => void];
|
|
19
|
+
useEffect?: (effect: () => void | (() => void), deps?: ReadonlyArray<unknown>) => void;
|
|
18
20
|
}
|
|
19
21
|
export interface SlotContext {
|
|
20
22
|
slots: {
|
|
@@ -29,7 +31,6 @@ export interface PluginShape {
|
|
|
29
31
|
/** The whiteboard mount, with its scheduler injectable. `apply` calls this with the default. */
|
|
30
32
|
mount: (ctx: SlotContext, options?: MountOptions) => unknown;
|
|
31
33
|
}
|
|
32
|
-
/** A settled tool call, as the host hands it to a view. */
|
|
33
34
|
export interface ToolCallProps {
|
|
34
35
|
phase?: string;
|
|
35
36
|
toolName?: string;
|
|
@@ -53,12 +54,93 @@ export declare const WHITEBOARD_ID = "model-everything-diagram";
|
|
|
53
54
|
/** What the whiteboard opens with: a diagram the reader can edit rather than an empty box. */
|
|
54
55
|
export declare const WHITEBOARD_SAMPLE: string;
|
|
55
56
|
/**
|
|
56
|
-
* The
|
|
57
|
+
* The card for a drawing: the source the call carried, drawn by the renderer that wrote the file.
|
|
57
58
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
59
|
+
* Drawing it here rather than replaying the SVG means the card and the file are one picture from one
|
|
60
|
+
* engine, and it costs the result nothing. Lines the renderer refused are listed under the drawing: a
|
|
61
|
+
* card that hid them would show a picture that is missing something, which is the failure the tool
|
|
62
|
+
* exists to prevent.
|
|
61
63
|
*/
|
|
64
|
+
/** A create-element function with this package's calling convention, so a shared builder fits both seats. */
|
|
65
|
+
type H = (type: unknown, props?: Record<string, unknown> | null, ...children: unknown[]) => unknown;
|
|
66
|
+
/** What an annotation editor shows, shared by the two surfaces that offer one. */
|
|
67
|
+
export interface AnnotationState {
|
|
68
|
+
picked: {
|
|
69
|
+
id: string;
|
|
70
|
+
} | null;
|
|
71
|
+
note: string;
|
|
72
|
+
pending: Array<{
|
|
73
|
+
nodeId: string;
|
|
74
|
+
note: string;
|
|
75
|
+
}>;
|
|
76
|
+
status: string;
|
|
77
|
+
/** Whether the pending list is expanded under the count. */
|
|
78
|
+
open?: boolean;
|
|
79
|
+
/** Where the editor sits, in the drawing's own coordinates: beside the node the reader picked. */
|
|
80
|
+
anchor?: {
|
|
81
|
+
left: number;
|
|
82
|
+
top: number;
|
|
83
|
+
};
|
|
84
|
+
/** The correction the dock applied after measuring where the chip actually landed. */
|
|
85
|
+
dockFix?: {
|
|
86
|
+
dx: number;
|
|
87
|
+
dy: number;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export interface AnnotationActions {
|
|
91
|
+
onNote: (value: string) => void;
|
|
92
|
+
onAdd: () => void;
|
|
93
|
+
onAddAndSend: () => void;
|
|
94
|
+
onSend: () => void;
|
|
95
|
+
onClear: () => void;
|
|
96
|
+
onCancel: () => void;
|
|
97
|
+
onToggle: () => void;
|
|
98
|
+
onRemove: (index: number) => void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The annotation surface: a floating editor over the drawing, and a count of what is waiting.
|
|
102
|
+
*
|
|
103
|
+
* Floating rather than inline, because the reader is looking at a picture and the editor must not move it.
|
|
104
|
+
* The notes stay in a list until one message is composed from them: the reader's input box is theirs, and N raw
|
|
105
|
+
* fragments in it would be exactly the mess this feature is meant to avoid. Colours come from the host's tokens,
|
|
106
|
+
* so light and dark themes need no separate code.
|
|
107
|
+
*/
|
|
108
|
+
export declare function annotationPanel(h: H, state: AnnotationState, actions: AnnotationActions, options?: {
|
|
109
|
+
floating?: boolean;
|
|
110
|
+
document?: DocumentLike;
|
|
111
|
+
dockFix?: {
|
|
112
|
+
dx: number;
|
|
113
|
+
dy: number;
|
|
114
|
+
};
|
|
115
|
+
dockChip?: boolean;
|
|
116
|
+
}): unknown;
|
|
117
|
+
/** Remember what would leave with the next message, and start watching for that message. */
|
|
118
|
+
export declare function setPendingBlock(text: string): void;
|
|
119
|
+
/**
|
|
120
|
+
* The dock, as a layer on `document.body`.
|
|
121
|
+
*
|
|
122
|
+
* A `position: fixed` node still positions against the nearest transformed ancestor, and the composer sits
|
|
123
|
+
* inside one - which is why every coordinate this plugin computed landed inside the box. The shipped
|
|
124
|
+
* annotation plugin sidesteps it the same way: its chip is a body-level layer, so fixed means the viewport.
|
|
125
|
+
* Size and place come from the composer card's own rectangle, measured after every layout change.
|
|
126
|
+
*/
|
|
127
|
+
interface DockHandlers {
|
|
128
|
+
onToggle: () => void;
|
|
129
|
+
onSend: () => void;
|
|
130
|
+
onRemove: (index: number) => void;
|
|
131
|
+
}
|
|
132
|
+
/** Keep the docked count in step with the pending list. Called from a render path, so it cannot throw. */
|
|
133
|
+
export declare function updateDockLayer(items: Array<{
|
|
134
|
+
nodeId: string;
|
|
135
|
+
note: string;
|
|
136
|
+
}>, open: boolean, handlers: DockHandlers): void;
|
|
137
|
+
/**
|
|
138
|
+
* Tell this module when a message leaves, by watching the one signal the host gives: the draft empties.
|
|
139
|
+
*
|
|
140
|
+
* A note that was committed is in the draft, so after the send there is nothing left to keep - and a chip still
|
|
141
|
+
* counting a note that has already gone out is the plugin lying about its own state.
|
|
142
|
+
*/
|
|
143
|
+
export declare function onMessageSent(listener: () => void): () => void;
|
|
62
144
|
export declare function viewFor(React: ReactLike, overrides?: Partial<Palette>): (props: ToolCallProps) => unknown;
|
|
63
145
|
/**
|
|
64
146
|
* The whiteboard: paste a diagram, get a drawing, zoom it.
|
|
@@ -81,3 +163,4 @@ export interface MountOptions {
|
|
|
81
163
|
*/
|
|
82
164
|
export declare function mountWhiteboard(React: ReactLike, ctx: SlotContext, options?: MountOptions): unknown;
|
|
83
165
|
export default function factory(require: (name: string) => unknown): PluginShape;
|
|
166
|
+
export {};
|
package/lib/types/diagram.d.ts
CHANGED
|
@@ -31,4 +31,11 @@ export interface ComponentContext {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
export declare function apply(ctx: ComponentContext, config: Config): void;
|
|
34
|
+
/**
|
|
35
|
+
* Offer an annotation as a session command, so a page can send one without a composer.
|
|
36
|
+
*
|
|
37
|
+
* Optional on purpose, and failure-tolerant on purpose: a host without the commands service keeps the
|
|
38
|
+
* composer path, and a registration that throws must not take the drawing tools down with it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function registerAnnotateCommand(ctx: unknown): void;
|
|
34
41
|
export declare const inject: string[];
|
package/lib/types/gate.d.ts
CHANGED
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Why it is a separate row. The plugin's doctrine is that a capability is discoverable
|
|
12
12
|
* through its own tool description or skill text. Injection departs from that, so it is
|
|
13
|
-
*
|
|
14
|
-
* without touching the tools or the skills. `enabled` is a live switch, so the Web
|
|
15
|
-
* page flips it inside a running host.
|
|
13
|
+
* mounted as its own component: the bundle's patch enables it, and the row can be turned off
|
|
14
|
+
* or removed without touching the tools or the skills. `enabled` is a live switch, so the Web
|
|
15
|
+
* Plugins page flips it inside a running host.
|
|
16
16
|
*
|
|
17
|
-
* Why
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Why the bundle turns it on. Measured: a session that never decides to load a skill is never
|
|
18
|
+
* routed, and the catalog line is cut at 500 characters, so a domain the request names can be
|
|
19
|
+
* invisible at the moment of choosing. The cost is a fixed message in every session, which is
|
|
20
|
+
* why the switch is live and documented: turn it off where the bundle's default is wrong. The
|
|
21
|
+
* component's own `enabled` script default is off, so a bare `app.add(gate)` injects nothing.
|
|
20
22
|
*
|
|
21
23
|
* What it does not do. It routes; it checks nothing. It cannot make a skill load, and it
|
|
22
24
|
* does not restate the doctrine - the skill bodies carry that, and duplicating them here
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an annotation message looks like after it has been sent.
|
|
3
|
+
*
|
|
4
|
+
* The message is one block of plain lines: a fixed opening, numbered items, a question line, then the reader's
|
|
5
|
+
* own words. That is the right thing for the model and the wrong thing to read as a human - the reader wrote one
|
|
6
|
+
* note about one node, and what landed is a page of protocol.
|
|
7
|
+
*
|
|
8
|
+
* The host renders a user message as text inside elements it owns and re-renders. So this never assigns an
|
|
9
|
+
* element's text: that deletes its children, React reconciles from a tree that no longer matches, and whole
|
|
10
|
+
* turns come back mangled. It edits the text NODES instead - trim the run before the cut, remove the runs that
|
|
11
|
+
* held the block - and puts the chip in as a sibling, exactly the way the shipped annotation plugin does it.
|
|
12
|
+
*
|
|
13
|
+
* Everything here is defensive: a page without the elements, an element without text nodes, an element already
|
|
14
|
+
* handled. A decorator that throws would take a render pass with it.
|
|
15
|
+
*/
|
|
16
|
+
/** The first words of every annotation block, used to recognise one in the transcript. */
|
|
17
|
+
export declare const TRANSCRIPT_MARKER = "Diagram annotations -";
|
|
18
|
+
export interface TranscriptNode {
|
|
19
|
+
nodeType?: number;
|
|
20
|
+
nodeValue?: string | null;
|
|
21
|
+
textContent?: string | null;
|
|
22
|
+
style?: Record<string, string | undefined>;
|
|
23
|
+
setAttribute?: (name: string, value: string) => void;
|
|
24
|
+
getAttribute?: (name: string) => string | null;
|
|
25
|
+
addEventListener?: (type: string, handler: () => void) => void;
|
|
26
|
+
querySelectorAll?: (selector: string) => ArrayLike<TranscriptNode>;
|
|
27
|
+
getBoundingClientRect?: () => {
|
|
28
|
+
left: number;
|
|
29
|
+
top: number;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
closest?: (selector: string) => TranscriptNode | null;
|
|
34
|
+
childNodes?: ArrayLike<TranscriptNode>;
|
|
35
|
+
nextSibling?: TranscriptNode | null;
|
|
36
|
+
appendChild?: (node: TranscriptNode) => unknown;
|
|
37
|
+
removeChild?: (node: TranscriptNode) => unknown;
|
|
38
|
+
parentNode?: {
|
|
39
|
+
insertBefore?: (node: TranscriptNode, before: TranscriptNode | null) => unknown;
|
|
40
|
+
removeChild?: (node: TranscriptNode) => unknown;
|
|
41
|
+
} | null;
|
|
42
|
+
}
|
|
43
|
+
export interface TranscriptDocument {
|
|
44
|
+
querySelectorAll?: (selector: string) => ArrayLike<TranscriptNode>;
|
|
45
|
+
createElement?: (tag: string) => TranscriptNode;
|
|
46
|
+
}
|
|
47
|
+
/** One note as the block states it. */
|
|
48
|
+
export interface AnnotationItem {
|
|
49
|
+
index: number;
|
|
50
|
+
node: string;
|
|
51
|
+
note: string;
|
|
52
|
+
}
|
|
53
|
+
/** The count in the opening line, for the chip; null when the line says something unexpected. */
|
|
54
|
+
export declare function annotationCountOf(text: string): number | null;
|
|
55
|
+
/** The reader's own words: what follows the question line, or nothing when the note was the whole message. */
|
|
56
|
+
export declare function questionOf(text: string): string;
|
|
57
|
+
/** The notes the block carries, read back out of its lines, so a reader sees what the model saw. */
|
|
58
|
+
export declare function itemsOf(text: string): AnnotationItem[];
|
|
59
|
+
/**
|
|
60
|
+
* Drop everything before the cut from the text nodes, and leave the reader's own words.
|
|
61
|
+
*
|
|
62
|
+
* The nodes are the only thing written: an element's `textContent` would take its child elements with it.
|
|
63
|
+
*/
|
|
64
|
+
export declare function cutTextNodes(nodes: TranscriptNode[], cut: number): boolean;
|
|
65
|
+
/** Kept for the tests that assert the selector exists; the ancestor walk is deliberately not used. */
|
|
66
|
+
export declare const FOREIGN_SELECTOR = "[data-annotation-for-dsh], [data-annotation-overlay], [data-annotation-chip], [data-annotation-tip-layer]";
|
|
67
|
+
/**
|
|
68
|
+
* Fold every annotation message in the transcript behind its count, and answer how many were folded.
|
|
69
|
+
*
|
|
70
|
+
* Idempotent: an element that already carries the marker is skipped, so the observer can call this on every
|
|
71
|
+
* change without stacking chips. Only the element whose text is the message is touched, and only its text nodes.
|
|
72
|
+
*/
|
|
73
|
+
export declare function decorateTranscript(doc: TranscriptDocument | undefined, label?: string, title?: string): number;
|
|
74
|
+
/**
|
|
75
|
+
* One pass, and nothing that keeps running.
|
|
76
|
+
*
|
|
77
|
+
* There is deliberately no observer and no timer here. Both were tried: an observer over the document plus a
|
|
78
|
+
* one-second interval turns every DOM change into a full selector scan, and a streaming conversation changes the
|
|
79
|
+
* DOM many times a second - the container stopped responding. A caller that wants the fold has to decide when
|
|
80
|
+
* to run it, on a container it chose.
|
|
81
|
+
*/
|
|
82
|
+
export declare function startTranscriptDecoration(doc: TranscriptDocument | undefined, label?: string, title?: string): () => void;
|