@agent-native/core 0.120.4 → 0.121.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.
Files changed (59) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/use-db-sync.ts +23 -21
  5. package/corpus/core/src/deploy/build.ts +1 -0
  6. package/corpus/core/src/server/core-routes-plugin.ts +3 -0
  7. package/corpus/core/src/server/gateway-access-check.ts +67 -0
  8. package/corpus/core/src/server/poll.ts +295 -9
  9. package/corpus/core/src/server/short-lived-token.ts +122 -0
  10. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +29 -4
  11. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +365 -9
  12. package/corpus/templates/slides/app/components/editor/bullet-editing.ts +350 -0
  13. package/corpus/templates/slides/app/context/DeckContext.tsx +1 -3
  14. package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
  15. package/corpus/templates/slides/app/pages/DeckEditor.tsx +5 -0
  16. package/corpus/templates/slides/changelog/2026-07-23-new-bullet-rows-created-with-enter-now-keep-the-list-item-s-.md +6 -0
  17. package/corpus/templates/slides/changelog/2026-07-23-pressing-enter-in-generated-checkbox-shape-marker-lists-now-.md +6 -0
  18. package/corpus/templates/slides/changelog/2026-07-24-typing-a-markdown-style-dash-space-at-the-start-of-a-text-bl.md +6 -0
  19. package/dist/client/use-db-sync.d.ts.map +1 -1
  20. package/dist/client/use-db-sync.js +27 -22
  21. package/dist/client/use-db-sync.js.map +1 -1
  22. package/dist/collab/awareness.d.ts +2 -2
  23. package/dist/collab/awareness.d.ts.map +1 -1
  24. package/dist/collab/struct-routes.d.ts +1 -1
  25. package/dist/deploy/build.d.ts.map +1 -1
  26. package/dist/deploy/build.js +1 -0
  27. package/dist/deploy/build.js.map +1 -1
  28. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  29. package/dist/notifications/routes.d.ts +1 -1
  30. package/dist/observability/routes.d.ts +3 -3
  31. package/dist/progress/routes.d.ts +1 -1
  32. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  33. package/dist/provider-api/actions/provider-api.d.ts +6 -6
  34. package/dist/secrets/routes.d.ts +9 -9
  35. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  36. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  37. package/dist/server/core-routes-plugin.js +3 -0
  38. package/dist/server/core-routes-plugin.js.map +1 -1
  39. package/dist/server/gateway-access-check.d.ts +12 -0
  40. package/dist/server/gateway-access-check.d.ts.map +1 -0
  41. package/dist/server/gateway-access-check.js +49 -0
  42. package/dist/server/gateway-access-check.js.map +1 -0
  43. package/dist/server/poll.d.ts +55 -1
  44. package/dist/server/poll.d.ts.map +1 -1
  45. package/dist/server/poll.js +254 -7
  46. package/dist/server/poll.js.map +1 -1
  47. package/dist/server/realtime-token.d.ts +1 -1
  48. package/dist/server/short-lived-token.d.ts +28 -0
  49. package/dist/server/short-lived-token.d.ts.map +1 -1
  50. package/dist/server/short-lived-token.js +78 -0
  51. package/dist/server/short-lived-token.js.map +1 -1
  52. package/dist/server/transcribe-voice.d.ts +1 -1
  53. package/package.json +1 -1
  54. package/src/client/use-db-sync.ts +23 -21
  55. package/src/deploy/build.ts +1 -0
  56. package/src/server/core-routes-plugin.ts +3 -0
  57. package/src/server/gateway-access-check.ts +67 -0
  58. package/src/server/poll.ts +295 -9
  59. package/src/server/short-lived-token.ts +122 -0
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Helpers for editing styled bullet lists — list-item rows built from a marker
3
+ * glyph plus text (e.g. `<div><span>●</span><span>Point</span></div>`) rather
4
+ * than real <ul>/<li> markup, which is how generated decks represent bullets.
5
+ *
6
+ * Kept in a standalone module (no React exports) so SlideEditor stays
7
+ * Fast-Refresh friendly and this logic is unit-testable.
8
+ */
9
+
10
+ /** Zero-width space: keeps the caret inside an otherwise-empty text span so
11
+ * typed characters inherit that span's font instead of the container's. */
12
+ export const ZERO_WIDTH_SPACE = "\u200B";
13
+
14
+ /** Single glyphs commonly used as bullet markers in styled (non-<ul>) lists. */
15
+ const BULLET_GLYPHS = new Set([
16
+ "\u2022", // •
17
+ "\u25CF", // ●
18
+ "\u25E6", // ◦
19
+ "\u25AA", // ▪
20
+ "\u2023", // ‣
21
+ "\u00B7", // ·
22
+ "\u2043", // ⁃
23
+ "-",
24
+ "\u2013", // –
25
+ "\u2014", // —
26
+ "*",
27
+ ]);
28
+
29
+ /** True if an element is a bullet marker — either a text glyph (a leading ●
30
+ * span) or an empty CSS shape (a small square/dot/box span used as a marker). */
31
+ export function isBulletMarker(el: Element): boolean {
32
+ return isGlyphMarker(el) || isShapeMarker(el);
33
+ }
34
+
35
+ /** A leading span whose text is only bullet glyph characters (e.g. "●"). */
36
+ function isGlyphMarker(el: Element): boolean {
37
+ const text = (el.textContent ?? "").trim();
38
+ return text.length > 0 && [...text].every((c) => BULLET_GLYPHS.has(c));
39
+ }
40
+
41
+ /** An empty, small, roughly-square span drawn as a marker via border/background
42
+ * (e.g. `<span style="width:21px;height:21px;border:2px solid ...">`), which is
43
+ * how generated decks often render checkbox/dot bullets with no text glyph. */
44
+ function isShapeMarker(el: Element): boolean {
45
+ if ((el.textContent ?? "").trim().length > 0) return false;
46
+ if (el.childElementCount > 0) return false;
47
+ const w = parseCssPx(styleValue(el, "width"));
48
+ const h = parseCssPx(styleValue(el, "height"));
49
+ if (!(w > 0 && h > 0) || w > 48 || h > 48) return false;
50
+ const ratio = w / h;
51
+ if (ratio < 0.5 || ratio > 2) return false;
52
+ const hasBorder =
53
+ parseCssPx(styleValue(el, "border-top-width")) > 0 ||
54
+ parseCssPx(styleValue(el, "border-left-width")) > 0 ||
55
+ parseCssPx(styleValue(el, "border-width")) > 0;
56
+ const bg = styleValue(el, "background-color");
57
+ const hasBg = !!bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)";
58
+ const hasRadius = parseCssPx(styleValue(el, "border-radius")) > 0;
59
+ return hasBorder || hasBg || hasRadius;
60
+ }
61
+
62
+ /** Read a style property, preferring inline styles and falling back to computed
63
+ * styles when available (jsdom-safe). */
64
+ function styleValue(el: Element, prop: string): string {
65
+ const inline = (el as HTMLElement).style?.getPropertyValue(prop);
66
+ if (inline) return inline;
67
+ if (typeof window !== "undefined" && window.getComputedStyle) {
68
+ try {
69
+ return window.getComputedStyle(el).getPropertyValue(prop);
70
+ } catch {
71
+ return "";
72
+ }
73
+ }
74
+ return "";
75
+ }
76
+
77
+ function parseCssPx(value: string): number {
78
+ const parsed = Number.parseFloat(value);
79
+ return Number.isFinite(parsed) ? parsed : 0;
80
+ }
81
+
82
+ /** The bullet-marker element enclosing a node (or the node itself), bounded by
83
+ * `root`, or null when the node isn't inside a marker glyph. */
84
+ function enclosingMarker(node: Node, root: HTMLElement): HTMLElement | null {
85
+ let el: HTMLElement | null =
86
+ node.nodeType === Node.ELEMENT_NODE
87
+ ? (node as HTMLElement)
88
+ : node.parentElement;
89
+ while (el && el !== root && root.contains(el)) {
90
+ if (isBulletMarker(el)) return el;
91
+ el = el.parentElement;
92
+ }
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * A "bullet row" is a styled list item whose first element child is a marker
98
+ * glyph. The text after it may be a <span> or a bare text node (contentEditable
99
+ * often unwraps spans while editing), and may be empty for a freshly-added
100
+ * bullet — so only the leading marker is required.
101
+ */
102
+ export function isBulletRow(el: HTMLElement): boolean {
103
+ if (el.tagName !== "DIV" && el.tagName !== "LI" && el.tagName !== "P") {
104
+ return false;
105
+ }
106
+ const first = el.firstElementChild;
107
+ return !!first && isBulletMarker(first);
108
+ }
109
+
110
+ /** Count the styled bullet rows directly inside a container. */
111
+ export function bulletRowCount(el: HTMLElement): number {
112
+ return Array.from(el.children).filter((k) => isBulletRow(k as HTMLElement))
113
+ .length;
114
+ }
115
+
116
+ /** A container whose element children are (essentially) all styled bullet rows. */
117
+ export function isBulletList(el: HTMLElement): boolean {
118
+ const kids = Array.from(el.children);
119
+ if (kids.length === 0) return false;
120
+ const rows = bulletRowCount(el);
121
+ // Tolerate one stray non-bullet child (e.g. a stray <br>/<div> left behind by
122
+ // contentEditable) as long as the container is clearly a bullet list.
123
+ return rows >= 1 && rows >= kids.length - 1;
124
+ }
125
+
126
+ /** Regex for a markdown-style bullet prefix: a dash or asterisk plus a space,
127
+ * at the very start of a block's content (e.g. "- " or "* "). */
128
+ const MARKDOWN_BULLET_PREFIX = /^[-*] $/;
129
+
130
+ /**
131
+ * If `el`'s content starts with a markdown-style "- "/"* " prefix and the
132
+ * caret sits right after it, convert `el`'s content into a styled bullet row
133
+ * — a small marker span plus a text span holding the rest of `el`'s content —
134
+ * nested inside `el`, which becomes the list container. `el` itself must stay
135
+ * the contentEditable root (nesting the row rather than turning `el` itself
136
+ * into the row) so a later Enter's cloned sibling row lands inside the same
137
+ * contentEditable boundary and is actually typeable. Returns false when `el`
138
+ * is already a bullet row/list, there's no such prefix, or the selection
139
+ * isn't a collapsed caret.
140
+ */
141
+ export function convertMarkdownPrefixToBullet(el: HTMLElement): boolean {
142
+ if (isBulletRow(el) || isBulletList(el)) return false;
143
+ const sel = window.getSelection();
144
+ if (!sel || sel.rangeCount === 0) return false;
145
+ const caretRange = sel.getRangeAt(0);
146
+ if (!caretRange.collapsed) return false;
147
+
148
+ const beforeCaretRange = document.createRange();
149
+ beforeCaretRange.selectNodeContents(el);
150
+ beforeCaretRange.setEnd(caretRange.endContainer, caretRange.endOffset);
151
+ // A freshly-placed text box seeds its content with a zero-width-space
152
+ // placeholder (see placeTextBoxAt) so it has a font to inherit before any
153
+ // real text exists. Strip it before testing so "- " typed as the very
154
+ // first characters is still recognized as a bullet prefix.
155
+ const beforeCaretText = beforeCaretRange
156
+ .toString()
157
+ .replace(new RegExp(ZERO_WIDTH_SPACE, "g"), "");
158
+ if (!MARKDOWN_BULLET_PREFIX.test(beforeCaretText)) return false;
159
+ beforeCaretRange.deleteContents();
160
+
161
+ const marker = document.createElement("span");
162
+ marker.style.fontSize = "0.3em";
163
+ marker.style.position = "relative";
164
+ marker.style.top = "-0.15em";
165
+ marker.textContent = "\u25CF";
166
+
167
+ const textSpan = document.createElement("span");
168
+ while (el.firstChild) textSpan.appendChild(el.firstChild);
169
+ const restFirstChild = textSpan.firstChild;
170
+ let placeholderZws: Text | null = null;
171
+ if (!restFirstChild) {
172
+ placeholderZws = document.createTextNode(ZERO_WIDTH_SPACE);
173
+ textSpan.appendChild(placeholderZws);
174
+ }
175
+
176
+ const row = document.createElement("div");
177
+ row.style.display = "flex";
178
+ row.style.alignItems = "baseline";
179
+ row.style.gap = "0.7em";
180
+ row.append(marker, textSpan);
181
+ el.append(row);
182
+
183
+ if (!el.style.display) el.style.display = "flex";
184
+ if (!el.style.flexDirection) el.style.flexDirection = "column";
185
+ if (!el.style.gap) el.style.gap = "0.6em";
186
+
187
+ const range = document.createRange();
188
+ if (restFirstChild) {
189
+ range.setStartBefore(restFirstChild);
190
+ } else {
191
+ // See primeNewRow: anchor the caret inside the placeholder text node
192
+ // (not an element-based position) so it keeps the text span's font
193
+ // instead of falling back to the marker's.
194
+ range.setStart(placeholderZws as Text, ZERO_WIDTH_SPACE.length);
195
+ }
196
+ range.collapse(true);
197
+ sel.removeAllRanges();
198
+ sel.addRange(range);
199
+ return true;
200
+ }
201
+
202
+ /**
203
+ * Walk up from a text leaf to the nearest enclosing styled bullet-row
204
+ * container, so Enter can add a new item to the whole list instead of being
205
+ * trapped inside one item.
206
+ */
207
+ export function findEnclosingList(
208
+ el: HTMLElement,
209
+ root: HTMLElement,
210
+ ): HTMLElement | null {
211
+ let node: HTMLElement | null = el;
212
+ while (node && root.contains(node)) {
213
+ const parentEl: HTMLElement | null = node.parentElement;
214
+ if (!parentEl) break;
215
+ if (isBulletRow(node) && isBulletList(parentEl)) return parentEl;
216
+ // Even from a bullet row whose siblings aren't all bullets, treat the
217
+ // parent as a list once it holds two or more bullet rows.
218
+ if (isBulletRow(node) && bulletRowCount(parentEl) >= 2) return parentEl;
219
+ node = parentEl;
220
+ }
221
+ return null;
222
+ }
223
+
224
+ /** The non-marker text container of a row: a dedicated text <span> if present,
225
+ * otherwise the row itself (rows whose text is a bare node). */
226
+ function rowTextContainer(
227
+ row: HTMLElement,
228
+ marker: HTMLElement | null,
229
+ ): HTMLElement {
230
+ const textSpan = Array.from(row.children).find(
231
+ (c) => c.tagName === "SPAN" && c !== marker && !isBulletMarker(c),
232
+ ) as HTMLElement | undefined;
233
+ return textSpan ?? row;
234
+ }
235
+
236
+ /**
237
+ * Seed a freshly-inserted row with the caret's trailing content and place the
238
+ * caret at the start of its editable text. `tail` is a DOM fragment (not a
239
+ * string) so inline formatting such as <strong>/<em> carried over from the
240
+ * split point is preserved. When there is no tail, a zero-width space text node
241
+ * keeps the caret inside the font-carrying text span rather than dropping it to
242
+ * the container.
243
+ */
244
+ function primeNewRow(row: HTMLElement, tail: DocumentFragment | null): void {
245
+ const marker =
246
+ row.firstElementChild && isBulletMarker(row.firstElementChild)
247
+ ? (row.firstElementChild as HTMLElement)
248
+ : null;
249
+ const container = rowTextContainer(row, marker);
250
+
251
+ // Clear existing text content, preserving the marker glyph.
252
+ if (container !== row) {
253
+ container.replaceChildren();
254
+ } else {
255
+ while (marker?.nextSibling) marker.nextSibling.remove();
256
+ if (!marker) row.replaceChildren();
257
+ }
258
+
259
+ const firstTailNode = tail?.firstChild ?? null;
260
+ if (tail && firstTailNode) container.appendChild(tail);
261
+
262
+ const sel = window.getSelection();
263
+ if (!sel) return;
264
+ const range = document.createRange();
265
+ if (firstTailNode) {
266
+ // Caret at the very start of the moved tail (before the marker is not
267
+ // possible: setStartBefore anchors relative to the tail's first node).
268
+ range.setStartBefore(firstTailNode);
269
+ } else {
270
+ const zws = document.createTextNode(ZERO_WIDTH_SPACE);
271
+ container.appendChild(zws);
272
+ range.setStart(zws, ZERO_WIDTH_SPACE.length);
273
+ }
274
+ range.collapse(true);
275
+ sel.removeAllRanges();
276
+ sel.addRange(range);
277
+ }
278
+
279
+ /**
280
+ * Insert a new list item after the caret's current row. Content after the caret
281
+ * moves into the new row (with inline formatting preserved); the marker glyph is
282
+ * preserved on both rows. Returns false when the caret isn't inside a direct row
283
+ * of the list so the caller can fall back.
284
+ */
285
+ export function insertBulletAfterCaret(list: HTMLElement): boolean {
286
+ const sel = window.getSelection();
287
+ if (!sel || sel.rangeCount === 0) return false;
288
+ const range = sel.getRangeAt(0);
289
+ if (!range.collapsed) {
290
+ // A selection that spans a row's marker glyph would delete it here, blanking
291
+ // the bullet on the surviving row (and its clone). Clamp both boundaries out
292
+ // of any enclosing marker so deletion never touches the glyphs.
293
+ const startMarker = enclosingMarker(range.startContainer, list);
294
+ if (startMarker) range.setStartAfter(startMarker);
295
+ const endMarker = enclosingMarker(range.endContainer, list);
296
+ if (endMarker) range.setEndBefore(endMarker);
297
+ if (!range.collapsed) range.deleteContents();
298
+ }
299
+
300
+ let row: HTMLElement | null = null;
301
+ let node: Node | null = range.endContainer;
302
+ while (node && node !== list) {
303
+ if (node.parentNode === list) {
304
+ row = node as HTMLElement;
305
+ break;
306
+ }
307
+ node = node.parentNode;
308
+ }
309
+ if (!row) return false;
310
+
311
+ const marker =
312
+ row.firstElementChild && isBulletMarker(row.firstElementChild)
313
+ ? (row.firstElementChild as HTMLElement)
314
+ : null;
315
+
316
+ // Never split inside the marker glyph itself: a caret at offset 0 of the "●"
317
+ // text node (e.g. clicking the marker's leading edge) would otherwise blank
318
+ // the marker and un-bullet the row. In that case add an empty bullet instead.
319
+ const caretInMarker = !!marker && marker.contains(range.endContainer);
320
+
321
+ const container = rowTextContainer(row, marker);
322
+ let tail: DocumentFragment | null = null;
323
+ if (!caretInMarker && container.contains(range.endContainer)) {
324
+ const tailRange = document.createRange();
325
+ tailRange.setStart(range.endContainer, range.endOffset);
326
+ const lastChild = container.lastChild;
327
+ if (lastChild) tailRange.setEndAfter(lastChild);
328
+ else tailRange.setEnd(container, container.childNodes.length);
329
+ // extractContents() moves the trailing DOM subtree (preserving <strong>/
330
+ // <em>) out of the original row so it can be reparented into the new one.
331
+ tail = tailRange.extractContents();
332
+ // A caret at the very end of the text (the common case) makes tailRange
333
+ // collapsed, but extractContents() on a collapsed range still clones the
334
+ // boundary text node with empty data instead of returning an empty
335
+ // fragment. Treat that as "no tail" so primeNewRow falls through to the
336
+ // zero-width-space placeholder — otherwise it moves in an empty text node
337
+ // with no character to anchor the caret's font to, and typing falls back
338
+ // to the marker span's formatting instead of the text span's.
339
+ if (tail.textContent === "") tail = null;
340
+ }
341
+
342
+ const newRow = row.cloneNode(true) as HTMLElement;
343
+ for (const el of [newRow, ...Array.from(newRow.querySelectorAll("*"))]) {
344
+ el.removeAttribute("data-builder-id");
345
+ el.removeAttribute("data-fusion-element-id");
346
+ }
347
+ row.after(newRow);
348
+ primeNewRow(newRow, tail);
349
+ return true;
350
+ }
@@ -945,9 +945,7 @@ export const defaultSlideContent: Record<SlideLayout, string> = {
945
945
  "full-image": `<div class="fmd-slide" style="padding: 0; align-items: center; justify-content: center;">
946
946
  <div class="fmd-img-placeholder" style="width: 100%; height: 100%;">Full-bleed image or screenshot</div>
947
947
  </div>`,
948
- blank: `<div class="fmd-slide" style="padding: 80px 110px; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; font-family: 'Poppins', sans-serif;">
949
- <div style="font-size: 28px; font-weight: 600; color: rgba(255,255,255,0.4); line-height: 1.3; font-family: 'Poppins', sans-serif;">Double-click to edit</div>
950
- </div>`,
948
+ blank: `<div class="fmd-slide" style="padding: 80px 110px; position: relative; font-family: 'Poppins', sans-serif;"></div>`,
951
949
  };
952
950
 
953
951
  export function DeckProvider({ children }: { children: ReactNode }) {
@@ -121,6 +121,7 @@ const messages = {
121
121
  uploadAttachedFailed: "Could not upload the attached file.",
122
122
  uploadFailed: "Upload failed",
123
123
  doubleClickEdit: "Double-click any text to edit",
124
+ dragToMove: "Drag to move",
124
125
  aiEditing: "AI editing",
125
126
  startTypingCommands: "Start typing… or press / for commands",
126
127
  pinDropHint: "Click anywhere to drop a comment pin",
@@ -210,6 +211,7 @@ const messages = {
210
211
  elementAnimations: "Element animations",
211
212
  tweaks: "Tweaks",
212
213
  drawOnSlide: "Draw on slide",
214
+ addTextBox: "Add text box",
213
215
  pinComments: "Pin comments",
214
216
  pinCommentsDescription:
215
217
  "Click spots on the slide to queue several edits, then send them all at once.",
@@ -196,6 +196,7 @@ export default function DeckEditor() {
196
196
  const [tweaksOpen, setTweaksOpen] = useState(false);
197
197
  const [drawMode, setDrawMode] = useState(false);
198
198
  const [pinMode, setPinMode] = useState(false);
199
+ const [textBoxMode, setTextBoxMode] = useState(false);
199
200
  const [pendingComment, setPendingComment] = useState<{
200
201
  quotedText: string;
201
202
  } | null>(null);
@@ -816,6 +817,8 @@ export default function DeckEditor() {
816
817
  onToggleDrawMode={() => setDrawMode((v) => !v)}
817
818
  pinMode={pinMode}
818
819
  onTogglePinMode={() => setPinMode((v) => !v)}
820
+ textBoxMode={textBoxMode}
821
+ onToggleTextBoxMode={() => setTextBoxMode((v) => !v)}
819
822
  onDuplicateDeck={() => {
820
823
  const newId = `deck-${nanoid()}`;
821
824
  const optimistic = duplicateDeck(id, newId);
@@ -992,6 +995,8 @@ export default function DeckEditor() {
992
995
  onExitDrawMode={() => setDrawMode(false)}
993
996
  pinMode={pinMode}
994
997
  onExitPinMode={() => setPinMode(false)}
998
+ textBoxMode={textBoxMode}
999
+ onExitTextBoxMode={() => setTextBoxMode(false)}
995
1000
  slideId={currentSlide.id}
996
1001
  slideTitle={(() => {
997
1002
  const m = currentSlide.content?.match(
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ New bullet rows created with Enter now keep the list item's font size instead of shrinking to the base font
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-23
4
+ ---
5
+
6
+ Pressing Enter in generated checkbox/shape-marker lists now creates a new list item
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-07-24
4
+ ---
5
+
6
+ Typing a markdown-style dash-space at the start of a text block now converts it into an editable bullet list
@@ -1 +1 @@
1
- {"version":3,"file":"use-db-sync.d.ts","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-db-sync.d.ts","sourceRoot":"","sources":["../../src/client/use-db-sync.ts"],"names":[],"mappings":"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"}
@@ -196,9 +196,12 @@ class SyncTransport {
196
196
  }
197
197
  get activeSseUrl() {
198
198
  if (this.mode === "hosted" && this.gateway) {
199
- return this.token
200
- ? `${this.gateway.sseUrl}?token=${encodeURIComponent(this.token)}`
201
- : this.gateway.sseUrl;
199
+ if (!this.token)
200
+ return this.gateway.sseUrl;
201
+ const base = `${this.gateway.sseUrl}?token=${encodeURIComponent(this.token)}`;
202
+ // Cursor lets the gateway replay the reconnect gap on connect instead of
203
+ // deferring it to the next poll; 0 on first connect means nothing to replay.
204
+ return this.versionRef > 0 ? `${base}&since=${this.versionRef}` : base;
202
205
  }
203
206
  return this.sseUrl;
204
207
  }
@@ -514,28 +517,30 @@ class SyncTransport {
514
517
  this.schedulePoll();
515
518
  };
516
519
  source.onerror = () => {
520
+ // A replaced/closed source can still fire late; ignore it so it can't
521
+ // flip the connected state or tear down the current stream.
522
+ if (this.eventSource !== source)
523
+ return;
517
524
  this.setSseConnected(false);
518
- // When the browser gives up permanently (HTTP error → readyState
519
- // CLOSED), it won't auto-reconnect. Drop the ref so a later
520
- // connectEvents() (on focus/visibility) can establish a fresh stream;
521
- // otherwise the non-null closed `eventSource` blocks reconnection and
522
- // we'd be stuck on polling-only forever.
525
+ if (this.mode === "hosted" && this.gateway) {
526
+ // Browser auto-reconnect reuses the URL frozen at construction, so it
527
+ // would replay from a stale `since`. Own the reconnect so the next
528
+ // connect rebuilds activeSseUrl from the current versionRef. CLOSED also
529
+ // refreshes the token (expired/rotated/deploy); CONNECTING keeps it. A
530
+ // successful reconnect resets the count in onopen; a hard-down gateway
531
+ // trips the threshold and health-gates to local.
532
+ if (source.readyState === EventSource.CLOSED)
533
+ this.token = null;
534
+ this.closeEvents();
535
+ this.onGatewayTransientFailure();
536
+ if (this.mode === "hosted")
537
+ this.scheduleGatewayReconnect();
538
+ return;
539
+ }
540
+ // Local mode: native EventSource reconnect is fine. Drop a CLOSED ref so a
541
+ // later connectEvents() (focus/visibility) can establish a fresh stream.
523
542
  if (source.readyState === EventSource.CLOSED) {
524
543
  this.eventSource = null;
525
- if (this.mode === "hosted" && this.gateway) {
526
- // A closed gateway stream is most likely an expired token or a
527
- // request-timeout/deploy cycle. Re-mint and reconnect with jitter;
528
- // this is NOT the poll-401 cooldown path. Each closed stream counts
529
- // toward the unhealthy threshold so a hard-down gateway (or one
530
- // rejecting our tokens) health-gates to local instead of looping
531
- // mint+connect forever; a successful reconnect resets the count in
532
- // onopen above.
533
- this.token = null;
534
- this.onGatewayTransientFailure();
535
- if (this.mode === "hosted")
536
- this.scheduleGatewayReconnect();
537
- return;
538
- }
539
544
  }
540
545
  this.schedulePoll();
541
546
  };