Merge branch 'dev'
CI/CD / deploy (push) Failing after 4m36s
Details
CI/CD / deploy (push) Failing after 4m36s
Details
This commit is contained in:
commit
57c4bf455d
|
|
@ -157,6 +157,8 @@ export class CommunitySync extends EventTarget {
|
||||||
#disconnectedIntentionally = false;
|
#disconnectedIntentionally = false;
|
||||||
#communitySlug: string;
|
#communitySlug: string;
|
||||||
#shapes: Map<string, FolkShape> = new Map();
|
#shapes: Map<string, FolkShape> = new Map();
|
||||||
|
#shapeListeners: Map<string, { transform: EventListener; content: EventListener }> = new Map();
|
||||||
|
#changeCount = 0;
|
||||||
#pendingChanges: boolean = false;
|
#pendingChanges: boolean = false;
|
||||||
#reconnectAttempts = 0;
|
#reconnectAttempts = 0;
|
||||||
#maxReconnectAttempts = 5;
|
#maxReconnectAttempts = 5;
|
||||||
|
|
@ -532,15 +534,25 @@ export class CommunitySync extends EventTarget {
|
||||||
registerShape(shape: FolkShape): void {
|
registerShape(shape: FolkShape): void {
|
||||||
this.#shapes.set(shape.id, shape);
|
this.#shapes.set(shape.id, shape);
|
||||||
|
|
||||||
// Listen for transform events
|
// Remove stale listeners if shape is re-registered
|
||||||
shape.addEventListener("folk-transform", ((e: CustomEvent) => {
|
const old = this.#shapeListeners.get(shape.id);
|
||||||
this.#handleShapeChange(shape);
|
if (old) {
|
||||||
}) as EventListener);
|
shape.removeEventListener("folk-transform", old.transform);
|
||||||
|
shape.removeEventListener("content-change", old.content);
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for content changes (for markdown shapes)
|
// Create named listener refs so they can be removed later
|
||||||
shape.addEventListener("content-change", ((e: CustomEvent) => {
|
const transformListener = (() => {
|
||||||
this.#handleShapeChange(shape);
|
this.#handleShapeChange(shape);
|
||||||
}) as EventListener);
|
}) as EventListener;
|
||||||
|
const contentListener = (() => {
|
||||||
|
this.#handleShapeChange(shape);
|
||||||
|
}) as EventListener;
|
||||||
|
|
||||||
|
this.#shapeListeners.set(shape.id, { transform: transformListener, content: contentListener });
|
||||||
|
|
||||||
|
shape.addEventListener("folk-transform", transformListener);
|
||||||
|
shape.addEventListener("content-change", contentListener);
|
||||||
|
|
||||||
// Add to document if not exists
|
// Add to document if not exists
|
||||||
if (!this.#doc.shapes[shape.id]) {
|
if (!this.#doc.shapes[shape.id]) {
|
||||||
|
|
@ -555,6 +567,13 @@ export class CommunitySync extends EventTarget {
|
||||||
* Unregister a shape
|
* Unregister a shape
|
||||||
*/
|
*/
|
||||||
unregisterShape(shapeId: string): void {
|
unregisterShape(shapeId: string): void {
|
||||||
|
const shape = this.#shapes.get(shapeId);
|
||||||
|
const listeners = this.#shapeListeners.get(shapeId);
|
||||||
|
if (shape && listeners) {
|
||||||
|
shape.removeEventListener("folk-transform", listeners.transform);
|
||||||
|
shape.removeEventListener("content-change", listeners.content);
|
||||||
|
}
|
||||||
|
this.#shapeListeners.delete(shapeId);
|
||||||
this.#shapes.delete(shapeId);
|
this.#shapes.delete(shapeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -582,6 +601,12 @@ export class CommunitySync extends EventTarget {
|
||||||
doc.shapes[shape.id] = JSON.parse(JSON.stringify(shapeData));
|
doc.shapes[shape.id] = JSON.parse(JSON.stringify(shapeData));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Compact Automerge history periodically to prevent unbounded WASM heap growth
|
||||||
|
this.#changeCount++;
|
||||||
|
if (this.#changeCount % 500 === 0) {
|
||||||
|
this.#doc = Automerge.clone(this.#doc);
|
||||||
|
}
|
||||||
|
|
||||||
// Record for undo (skip if this is a brand-new shape — registerShape handles that)
|
// Record for undo (skip if this is a brand-new shape — registerShape handles that)
|
||||||
if (beforeData) {
|
if (beforeData) {
|
||||||
this.#pushUndo(shape.id, beforeData, this.#cloneShapeData(shape.id));
|
this.#pushUndo(shape.id, beforeData, this.#cloneShapeData(shape.id));
|
||||||
|
|
|
||||||
|
|
@ -615,7 +615,7 @@ export class FolkPrompt extends FolkShape {
|
||||||
const content = this.#promptInput?.value.trim();
|
const content = this.#promptInput?.value.trim();
|
||||||
if (!content || this.#isStreaming) return;
|
if (!content || this.#isStreaming) return;
|
||||||
|
|
||||||
// Add user message with any pending images
|
// Add user message with any pending images (cap at 30 to prevent unbounded growth with base64 images)
|
||||||
const userMessage: ChatMessage = {
|
const userMessage: ChatMessage = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
role: "user",
|
role: "user",
|
||||||
|
|
@ -624,6 +624,7 @@ export class FolkPrompt extends FolkShape {
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
};
|
};
|
||||||
this.#messages.push(userMessage);
|
this.#messages.push(userMessage);
|
||||||
|
if (this.#messages.length > 30) this.#messages = this.#messages.slice(-30);
|
||||||
|
|
||||||
// Clear input and pending images
|
// Clear input and pending images
|
||||||
if (this.#promptInput) this.#promptInput.value = "";
|
if (this.#promptInput) this.#promptInput.value = "";
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export class RStackMi extends HTMLElement {
|
||||||
#voiceDictation: SpeechDictation | null = null;
|
#voiceDictation: SpeechDictation | null = null;
|
||||||
#voiceAccumulated = "";
|
#voiceAccumulated = "";
|
||||||
#voiceSilenceTimer: ReturnType<typeof setTimeout> | null = null;
|
#voiceSilenceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
#outsideClickHandler: ((e: PointerEvent) => void) | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -67,6 +68,10 @@ export class RStackMi extends HTMLElement {
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
document.removeEventListener("keydown", this.#keyHandler);
|
document.removeEventListener("keydown", this.#keyHandler);
|
||||||
|
if (this.#outsideClickHandler) {
|
||||||
|
document.removeEventListener("pointerdown", this.#outsideClickHandler as EventListener);
|
||||||
|
this.#outsideClickHandler = null;
|
||||||
|
}
|
||||||
if (this.#placeholderTimer) clearInterval(this.#placeholderTimer);
|
if (this.#placeholderTimer) clearInterval(this.#placeholderTimer);
|
||||||
if (this.#voiceMode) this.#deactivateVoiceMode();
|
if (this.#voiceMode) this.#deactivateVoiceMode();
|
||||||
}
|
}
|
||||||
|
|
@ -261,14 +266,18 @@ export class RStackMi extends HTMLElement {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close panel on outside click — use composedPath to pierce Shadow DOM
|
// Close panel on outside click — use composedPath to pierce Shadow DOM
|
||||||
document.addEventListener("pointerdown", (e) => {
|
if (this.#outsideClickHandler) {
|
||||||
|
document.removeEventListener("pointerdown", this.#outsideClickHandler as EventListener);
|
||||||
|
}
|
||||||
|
this.#outsideClickHandler = (e: PointerEvent) => {
|
||||||
if (this.#voiceMode) return; // Keep panel open during voice conversation
|
if (this.#voiceMode) return; // Keep panel open during voice conversation
|
||||||
const path = e.composedPath();
|
const path = e.composedPath();
|
||||||
if (!path.includes(this)) {
|
if (!path.includes(this)) {
|
||||||
panel.classList.remove("open");
|
panel.classList.remove("open");
|
||||||
bar.classList.remove("focused");
|
bar.classList.remove("focused");
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
document.addEventListener("pointerdown", this.#outsideClickHandler as EventListener);
|
||||||
|
|
||||||
// Prevent internal clicks from closing
|
// Prevent internal clicks from closing
|
||||||
panel.addEventListener("pointerdown", (e) => e.stopPropagation());
|
panel.addEventListener("pointerdown", (e) => e.stopPropagation());
|
||||||
|
|
@ -687,8 +696,9 @@ export class RStackMi extends HTMLElement {
|
||||||
panel.classList.add("open");
|
panel.classList.add("open");
|
||||||
this.#shadow.getElementById("mi-bar")!.classList.add("focused");
|
this.#shadow.getElementById("mi-bar")!.classList.add("focused");
|
||||||
|
|
||||||
// Add user message
|
// Add user message (cap at 50 to prevent unbounded growth)
|
||||||
this.#messages.push({ role: "user", content: query });
|
this.#messages.push({ role: "user", content: query });
|
||||||
|
if (this.#messages.length > 50) this.#messages = this.#messages.slice(-50);
|
||||||
this.#renderMessages(messagesEl);
|
this.#renderMessages(messagesEl);
|
||||||
|
|
||||||
// Add placeholder for assistant
|
// Add placeholder for assistant
|
||||||
|
|
|
||||||
|
|
@ -3840,6 +3840,8 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest
|
||||||
// Also remove wb-svg elements from the overlay
|
// Also remove wb-svg elements from the overlay
|
||||||
const wbEl = wbOverlay?.querySelector(`[data-wb-id="${shapeId}"]`);
|
const wbEl = wbOverlay?.querySelector(`[data-wb-id="${shapeId}"]`);
|
||||||
if (wbEl) wbEl.remove();
|
if (wbEl) wbEl.remove();
|
||||||
|
// Clean up stale position tracking for deleted shapes
|
||||||
|
shapeLastPos.delete(shapeId);
|
||||||
// Hide schedule icon and reminder widget if deleted shape was selected
|
// Hide schedule icon and reminder widget if deleted shape was selected
|
||||||
selectedShapeIds.delete(shapeId);
|
selectedShapeIds.delete(shapeId);
|
||||||
updateSelectionVisuals();
|
updateSelectionVisuals();
|
||||||
|
|
@ -7472,7 +7474,7 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep-alive ping to prevent WebSocket idle timeout
|
// Keep-alive ping to prevent WebSocket idle timeout
|
||||||
setInterval(() => {
|
const keepAliveInterval = setInterval(() => {
|
||||||
try {
|
try {
|
||||||
sync.ping();
|
sync.ping();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue