Merge branch 'dev'

This commit is contained in:
Jeff Emmett 2026-03-02 23:41:57 -08:00
commit 94eb6ed5bc
6 changed files with 52 additions and 22 deletions

View File

@ -383,11 +383,19 @@ export class FolkShape extends FolkElement {
this.#updateCursors(); this.#updateCursors();
this.x = Number(this.getAttribute("x")) || 0; // Only initialize from HTML attributes if they exist — when properties
this.y = Number(this.getAttribute("y")) || 0; // are set via JS before DOM insertion, attributes are absent and reading
this.width = Number(this.getAttribute("width")) || "auto"; // them would overwrite the already-set values with 0/"auto".
this.height = Number(this.getAttribute("height")) || "auto"; const attrX = this.getAttribute("x");
this.rotation = (Number(this.getAttribute("rotation")) || 0) * (Math.PI / 180); const attrY = this.getAttribute("y");
const attrW = this.getAttribute("width");
const attrH = this.getAttribute("height");
const attrR = this.getAttribute("rotation");
if (attrX !== null) this.x = Number(attrX) || 0;
if (attrY !== null) this.y = Number(attrY) || 0;
if (attrW !== null) this.width = Number(attrW) || "auto";
if (attrH !== null) this.height = Number(attrH) || "auto";
if (attrR !== null) this.rotation = (Number(attrR) || 0) * (Math.PI / 180);
this.#rect.transformOrigin = { x: 0, y: 0 }; this.#rect.transformOrigin = { x: 0, y: 0 };
this.#rect.rotateOrigin = { x: 0.5, y: 0.5 }; this.#rect.rotateOrigin = { x: 0.5, y: 0.5 };

View File

@ -292,11 +292,12 @@ export class FolkWrapper extends FolkShape {
<div class="tags"></div> <div class="tags"></div>
`; `;
// Replace existing content structure // Replace existing content structure — save parent ref before clearing
const existingSlot = root.querySelector("slot"); const existingSlot = root.querySelector("slot");
if (existingSlot?.parentElement) { const slotParent = existingSlot?.parentElement;
existingSlot.parentElement.innerHTML = ""; if (slotParent) {
existingSlot.parentElement.appendChild(wrapper); slotParent.innerHTML = "";
slotParent.appendChild(wrapper);
} }
// Get references // Get references

View File

@ -533,7 +533,7 @@ app.post("/api/image-gen", async (c) => {
}; };
const styledPrompt = (stylePrompts[style] || "") + prompt; const styledPrompt = (stylePrompts[style] || "") + prompt;
const res = await fetch("https://queue.fal.run/fal-ai/flux-pro/v1.1", { const res = await fetch("https://fal.run/fal-ai/flux-pro/v1.1", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Key ${FAL_KEY}`, Authorization: `Key ${FAL_KEY}`,
@ -567,7 +567,7 @@ app.post("/api/video-gen/t2v", async (c) => {
const { prompt, duration } = await c.req.json(); const { prompt, duration } = await c.req.json();
if (!prompt) return c.json({ error: "prompt required" }, 400); if (!prompt) return c.json({ error: "prompt required" }, 400);
const res = await fetch("https://queue.fal.run/fal-ai/wan/v2.1", { const res = await fetch("https://fal.run/fal-ai/wan/v2.1", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Key ${FAL_KEY}`, Authorization: `Key ${FAL_KEY}`,
@ -600,7 +600,7 @@ app.post("/api/video-gen/i2v", async (c) => {
const { image, prompt, duration } = await c.req.json(); const { image, prompt, duration } = await c.req.json();
if (!image) return c.json({ error: "image required" }, 400); if (!image) return c.json({ error: "image required" }, 400);
const res = await fetch("https://queue.fal.run/fal-ai/kling-video/v1/standard/image-to-video", { const res = await fetch("https://fal.run/fal-ai/kling-video/v1/standard/image-to-video", {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Key ${FAL_KEY}`, Authorization: `Key ${FAL_KEY}`,

View File

@ -135,7 +135,7 @@ export class TabCache {
this.panes.set(moduleId, pane); this.panes.set(moduleId, pane);
// Load module-specific assets // Load module-specific assets
this.loadAssets(content.scripts, content.styles); this.loadAssets(content.scripts, content.styles, content.inlineStyles, moduleId);
// Update shell state // Update shell state
this.currentModuleId = moduleId; this.currentModuleId = moduleId;
@ -159,6 +159,7 @@ export class TabCache {
title: string; title: string;
scripts: string[]; scripts: string[];
styles: string[]; styles: string[];
inlineStyles: string[];
} | null { } | null {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html"); const doc = parser.parseFromString(html, "text/html");
@ -197,11 +198,20 @@ export class TabCache {
styles.push(href); styles.push(href);
}); });
return { body, title, scripts, styles }; // Extract inline <style> blocks (exclude shell-embedded styles)
const inlineStyles: string[] = [];
doc.querySelectorAll("head style").forEach((s) => {
const css = s.textContent || "";
// Skip the shell's own embedded styles (tab-pane, spinner, etc.)
if (css.includes(".rspace-tab-pane")) return;
if (css.trim()) inlineStyles.push(css);
});
return { body, title, scripts, styles, inlineStyles };
} }
/** Load script and style assets (idempotent — browsers deduplicate module scripts) */ /** Load script and style assets (idempotent — browsers deduplicate module scripts) */
private loadAssets(scripts: string[], styles: string[]): void { private loadAssets(scripts: string[], styles: string[], inlineStyles: string[] = [], moduleId?: string): void {
for (const src of scripts) { for (const src of scripts) {
const el = document.createElement("script"); const el = document.createElement("script");
el.type = "module"; el.type = "module";
@ -217,6 +227,16 @@ export class TabCache {
el.href = href; el.href = href;
document.head.appendChild(el); document.head.appendChild(el);
} }
// Inject inline <style> blocks (tagged for deduplication)
for (let i = 0; i < inlineStyles.length; i++) {
const tag = `tab-style-${moduleId || "unknown"}-${i}`;
if (document.querySelector(`style[data-tab-style="${tag}"]`)) continue;
const el = document.createElement("style");
el.dataset.tabStyle = tag;
el.textContent = inlineStyles[i];
document.head.appendChild(el);
}
} }
/** Show a specific pane and update shell state */ /** Show a specific pane and update shell state */

View File

@ -3655,6 +3655,8 @@
// Find and remove the nearest SVG element under cursor // Find and remove the nearest SVG element under cursor
const hit = document.elementFromPoint(e.clientX, e.clientY); const hit = document.elementFromPoint(e.clientX, e.clientY);
if (hit && hit !== wbOverlay && wbOverlay.contains(hit)) { if (hit && hit !== wbOverlay && wbOverlay.contains(hit)) {
const wbId = hit.getAttribute("data-wb-id");
if (wbId) sync.hardDeleteShape(wbId);
hit.remove(); hit.remove();
} }
} }
@ -3766,15 +3768,14 @@
} }
}); });
// Eraser: click on existing SVG strokes to delete them + remove from Automerge // Eraser click fallback — deletion is handled in pointerdown above,
// but catch any clicks that slip through (e.g. keyboard-triggered)
wbOverlay.addEventListener("click", (e) => { wbOverlay.addEventListener("click", (e) => {
if (wbTool !== "eraser") return; if (wbTool !== "eraser") return;
const hit = e.target; const hit = e.target;
if (hit && hit !== wbOverlay && wbOverlay.contains(hit)) { if (hit && hit !== wbOverlay && wbOverlay.contains(hit)) {
const wbId = hit.getAttribute("data-wb-id"); const wbId = hit.getAttribute("data-wb-id");
if (wbId) { if (wbId) sync.hardDeleteShape(wbId);
sync.hardDeleteShape(wbId);
}
hit.remove(); hit.remove();
} }
}); });

View File

@ -40,12 +40,12 @@ self.addEventListener("fetch", (event) => {
// Skip non-http(s) schemes (chrome-extension://, etc.) — they can't be cached // Skip non-http(s) schemes (chrome-extension://, etc.) — they can't be cached
if (!url.protocol.startsWith("http")) return; if (!url.protocol.startsWith("http")) return;
// Skip WebSocket and API requests entirely // Skip WebSocket and API requests entirely (including module APIs like /space/module/api/...)
if ( if (
event.request.url.startsWith("ws://") || event.request.url.startsWith("ws://") ||
event.request.url.startsWith("wss://") || event.request.url.startsWith("wss://") ||
url.pathname.startsWith("/ws/") || url.pathname.startsWith("/ws/") ||
url.pathname.startsWith("/api/") url.pathname.includes("/api/")
) { ) {
return; return;
} }
@ -101,7 +101,7 @@ self.addEventListener("fetch", (event) => {
} }
return response; return response;
}) })
.catch(() => cached as Response); .catch(() => cached || new Response("Offline", { status: 503 }));
return cached || fetchPromise; return cached || fetchPromise;
}) })
); );