lumiverse-spindle-types 0.5.11 → 0.5.13

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/dom.ts +116 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumiverse-spindle-types",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "types": "./src/index.ts",
5
5
  "keywords": [
6
6
  "lumiverse",
package/src/dom.ts CHANGED
@@ -1,11 +1,48 @@
1
1
  import type { RequestInitDTO } from "./api";
2
2
  import type { SpindleComponentsHelper } from "./components";
3
3
 
4
+ /** A chat-message DOM element paired with its stable message id. */
5
+ export interface SpindleMessageElement {
6
+ /** Stable message id (matches the message's id in the host's data store). */
7
+ messageId: string;
8
+ /** Currently-mounted bubble root element for that message. */
9
+ element: Element;
10
+ }
11
+
4
12
  /** DOM helper API provided to frontend extension modules. */
5
13
  export interface SpindleDOMHelper {
6
- /** Inject sanitized HTML into the host document at the given target. */
14
+ /**
15
+ * Inject sanitized HTML into the host document at the given target and
16
+ * return the wrapper element containing the parsed content.
17
+ *
18
+ * **Element identity is preserved across chat-list virtualization.**
19
+ * When the injection lands inside a chat-message bubble, the host
20
+ * registers the wrapper and *moves* (not recreates) it back into place
21
+ * when the bubble next mounts. That means form-control state, event
22
+ * listeners bound to the wrapper subtree, and any refs the extension is
23
+ * holding all survive scroll-away/scroll-back cycles. Extensions can
24
+ * cache the returned Element and trust it indefinitely until they
25
+ * explicitly retire it via `uninject()` or `cleanup()`.
26
+ *
27
+ * To deliberately remove an injection, call `uninject(wrapper)` — NOT
28
+ * `wrapper.remove()`. The latter detaches the wrapper but leaves the
29
+ * registry record in place, so the host will resurrect the wrapper on
30
+ * the next bubble remount.
31
+ *
32
+ * Injections outside any chat-message bubble (sidebar, modals, toolbar)
33
+ * are unaffected by virtualization and don't go through replay — they
34
+ * stay attached wherever the extension put them.
35
+ */
7
36
  inject(target: string | Element, html: string, position?: InsertPosition): Element;
8
37
 
38
+ /**
39
+ * Retire an injection previously returned by `inject()`. Removes the
40
+ * wrapper from the DOM and drops its replay registration so the host
41
+ * won't restore it on future bubble remounts. No-op if the element
42
+ * isn't a recognised Spindle injection wrapper.
43
+ */
44
+ uninject(element: Element): void;
45
+
9
46
  /** Create a style element in the host document. Returns a removal function. */
10
47
  addStyle(css: string): () => void;
11
48
 
@@ -24,6 +61,52 @@ export interface SpindleDOMHelper {
24
61
  /** Query all matches inside the extension-owned host DOM. */
25
62
  queryAll(selector: string): Element[];
26
63
 
64
+ /**
65
+ * Resolve the chat message that contains the given DOM element. Walks
66
+ * up the DOM tree from `target` looking for the host's message-bubble
67
+ * anchor and returns the stable message id of the containing message,
68
+ * or `null` when `target` isn't inside any chat message (e.g. it's in
69
+ * the sidebar, a modal, or a floating widget).
70
+ *
71
+ * Prefer this over reading host-private DOM attributes directly — the
72
+ * underlying attribute name is an implementation detail of the host
73
+ * and may change; this method is the stable public contract.
74
+ *
75
+ * Typical use: an extension injects content into a message bubble and
76
+ * needs the stable id to persist per-message state, key event
77
+ * handlers on the message id, or call `ctx.messages.renderWidget()`
78
+ * with the right id.
79
+ */
80
+ getMessageId(target: Element): string | null;
81
+
82
+ /**
83
+ * Find the chat-message bubble element currently mounted in the DOM
84
+ * for the given stable message id. Returns `null` when the message
85
+ * isn't currently rendered — the chat message list is virtualized, so
86
+ * only bubbles near the viewport (plus a small overscan window) have
87
+ * DOM at any moment.
88
+ *
89
+ * Typical use: extension wants to attach content to a specific known
90
+ * message id. If `null` comes back, the bubble isn't currently
91
+ * mounted. Injections previously made via `dom.inject()` against a
92
+ * bubble element are auto-replayed by the host (with the original
93
+ * Element identity preserved — see `inject()` for the full contract)
94
+ * when that bubble next mounts, so extensions don't need to re-inject
95
+ * on scroll themselves.
96
+ */
97
+ findMessageElement(messageId: string): Element | null;
98
+
99
+ /**
100
+ * Enumerate every chat message bubble currently mounted in the DOM,
101
+ * paired with its stable message id. The returned list reflects only
102
+ * what the virtualizer has rendered (typically the viewport plus a
103
+ * small overscan window), so it changes as the user scrolls.
104
+ *
105
+ * Typical use: extension sweeps every visible message on setup or in
106
+ * response to a chat-changed event to apply per-message decoration.
107
+ */
108
+ listMessageElements(): SpindleMessageElement[];
109
+
27
110
  /** Remove all DOM created by the extension helper. */
28
111
  cleanup(): void;
29
112
  }
@@ -652,6 +735,38 @@ export interface SpindleFrontendContext {
652
735
  ): () => void;
653
736
  /** Remove a previously rendered message widget. */
654
737
  removeWidget(messageId: string, widgetId: string): void;
738
+ /**
739
+ * Get the latest (most recent) message id in the active chat, or
740
+ * `null` if the chat is empty / no chat is active. Reflects the
741
+ * full chat history — works even when the latest bubble is
742
+ * currently scrolled off-screen (the chat list is virtualized, so
743
+ * the DOM might not contain the bubble even though the message
744
+ * exists logically).
745
+ *
746
+ * Typical use: extensions that decorate or react to the most recent
747
+ * message (trackers, summarizers, "show last response details"
748
+ * widgets) call this on setup + on each new-message event to find
749
+ * the id they should target, then pair with `dom.findMessageElement`
750
+ * or `dom.inject()` to attach DOM content. Injections registered
751
+ * via `dom.inject()` auto-replay when the bubble next mounts so the
752
+ * extension doesn't have to re-attach on scroll itself.
753
+ */
754
+ getLatestMessageId(): string | null;
755
+ /**
756
+ * Get the message id at the given chronological index in the
757
+ * active chat (0 = oldest, length-1 = newest). Negative indices
758
+ * count from the end Python-style: -1 = latest, -2 = second-
759
+ * latest. Returns `null` if the index is out of range or no chat
760
+ * is active.
761
+ */
762
+ getMessageIdAtIndex(index: number): string | null;
763
+ /**
764
+ * Enumerate every message id in the active chat in chronological
765
+ * order (oldest first, newest last). Reflects the full chat
766
+ * history, not just messages currently mounted in the DOM. See
767
+ * `dom.listMessageElements()` for the mounted-only DOM view.
768
+ */
769
+ listMessageIds(): string[];
655
770
  };
656
771
  characters: {
657
772
  /** Read a character through the host app's authenticated API. */