Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions .superset/lib/setup/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,22 @@ setup_main() {
step_failed "Allocate port base"
fi

# Step 8: Start Electric SQL
if ! step_start_electric; then
step_failed "Start Electric SQL"
# Step 8: Prepare Electric SQL env
if ! step_prepare_electric; then
step_failed "Prepare Electric SQL"
fi

# Step 9: Write .env file
if ! step_write_env; then
step_failed "Write .env file"
fi

# Step 10: Setup local MCP in .mcp.json (opt-in)
# Step 10: Start Electric SQL
if ! step_start_electric; then
step_failed "Start Electric SQL"
fi

# Step 11: Setup local MCP in .mcp.json (opt-in)
if [ "$SETUP_LOCAL_MCP" = "1" ]; then
if ! step_setup_local_mcp; then
step_failed "Setup local MCP"
Expand Down
47 changes: 31 additions & 16 deletions .superset/lib/setup/steps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,28 @@ SQL
return 0
}

step_prepare_electric() {
WORKSPACE_NAME="${WORKSPACE_NAME:-$(basename "$PWD")}"

# Sanitize workspace name for Docker (valid chars only, max 64 chars)
local container_suffix
container_suffix=$(echo "$WORKSPACE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
ELECTRIC_CONTAINER=$(echo "superset-electric-$container_suffix" | cut -c1-64)
ELECTRIC_SECRET="${ELECTRIC_SECRET:-local_electric_dev_secret}"

# Step 7 allocates SUPERSET_PORT_BASE; Electric must use that reserved port.
if [ -z "${SUPERSET_PORT_BASE:-}" ]; then
error "SUPERSET_PORT_BASE not set before preparing Electric"
return 1
fi

ELECTRIC_PORT=$((SUPERSET_PORT_BASE + 9))
ELECTRIC_URL="http://localhost:$ELECTRIC_PORT/v1/shape"

export ELECTRIC_CONTAINER ELECTRIC_PORT ELECTRIC_URL ELECTRIC_SECRET
return 0
}

step_start_electric() {
echo "⚡ Starting Electric SQL container..."

Expand All @@ -229,13 +251,11 @@ step_start_electric() {
return 1
fi

WORKSPACE_NAME="${WORKSPACE_NAME:-$(basename "$PWD")}"

# Sanitize workspace name for Docker (valid chars only, max 64 chars)
local container_suffix
container_suffix=$(echo "$WORKSPACE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
ELECTRIC_CONTAINER=$(echo "superset-electric-$container_suffix" | cut -c1-64)
ELECTRIC_SECRET="${ELECTRIC_SECRET:-local_electric_dev_secret}"
if [ -z "${ELECTRIC_CONTAINER:-}" ] || [ -z "${ELECTRIC_PORT:-}" ] || [ -z "${ELECTRIC_URL:-}" ]; then
if ! step_prepare_electric; then
return 1
fi
fi

# Stop and remove existing container if it exists
if docker ps -a --format '{{.Names}}' | grep -q "^${ELECTRIC_CONTAINER}$"; then
Expand All @@ -244,14 +264,12 @@ step_start_electric() {
docker rm "$ELECTRIC_CONTAINER" &> /dev/null || true
fi

# Step 6 allocates SUPERSET_PORT_BASE; Electric must use that reserved port.
if [ -z "${SUPERSET_PORT_BASE:-}" ]; then
error "SUPERSET_PORT_BASE not set before starting Electric"
if lsof -nP -iTCP:"$ELECTRIC_PORT" -sTCP:LISTEN &> /dev/null; then
error "Electric port $ELECTRIC_PORT is already in use"
return 1
fi

local port_flag
ELECTRIC_PORT=$((SUPERSET_PORT_BASE + 9))
port_flag="-p $ELECTRIC_PORT:3000"

echo " Clearing stale Electric replication sessions..."
Expand Down Expand Up @@ -295,14 +313,11 @@ step_start_electric() {

if [ "$ready" = false ]; then
error "Electric failed to become active within 60s (last status: $health_status). Check logs: docker logs $ELECTRIC_CONTAINER"
echo " Stopping inactive Electric container to free port $ELECTRIC_PORT..."
docker stop "$ELECTRIC_CONTAINER" &> /dev/null || true
return 1
fi

ELECTRIC_URL="http://localhost:$ELECTRIC_PORT/v1/shape"

# Export for use in other steps
export ELECTRIC_CONTAINER ELECTRIC_PORT ELECTRIC_URL ELECTRIC_SECRET

success "Electric SQL running at $ELECTRIC_URL"
return 0
}
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/src/lib/electron-app/factories/app/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ PLATFORM.IS_WINDOWS &&

app.commandLine.appendSwitch("force-color-profile", "srgb");

if (env.NODE_ENV === "development" && process.env.RENDERER_REMOTE_DEBUG_PORT) {
app.commandLine.appendSwitch(
"remote-debugging-port",
process.env.RENDERER_REMOTE_DEBUG_PORT,
);
}

// Each xterm pane holds one WebGL context. v2 parking keeps panes alive
// across workspace switches, so cumulative contexts can reach the low
// hundreds — past Chromium's default cap of 16, Blink force-evicts the
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/src/main/windows/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { workspaces, worktrees } from "@superset/local-db";
import { eq } from "drizzle-orm";
import type { BrowserWindow } from "electron";
import { app, Notification, nativeTheme } from "electron";
import log from "electron-log/main";
import { createWindow } from "lib/electron-app/factories/windows/create";
import { createAppRouter } from "lib/trpc/routers";
import { localDb } from "main/lib/local-db";
Expand Down Expand Up @@ -135,6 +136,40 @@ export async function MainWindow() {
window.webContents.setBackgroundThrottling(false);
}

if (isDev) {
window.webContents.on(
"console-message",
(_event, level, message, line, sourceId) => {
const shouldForward =
level >= 2 ||
message.includes("[stress]") ||
message.includes("[main]");
if (!shouldForward) return;

const details = sourceId ? ` (${sourceId}:${line})` : "";
const formatted = `[renderer-console] ${message}${details}`;
if (level >= 3) {
log.error(formatted);
} else if (level >= 2) {
log.warn(formatted);
} else {
log.info(formatted);
}
},
);

window.on("unresponsive", () => {
log.warn("[main-window] Renderer became unresponsive", {
url: window.webContents.getURL(),
});
});
window.on("responsive", () => {
log.info("[main-window] Renderer became responsive", {
url: window.webContents.getURL(),
});
});
}

if (ipcHandler) {
ipcHandler.attachWindow(window);
} else {
Expand Down Expand Up @@ -289,6 +324,7 @@ export async function MainWindow() {

window.webContents.on("render-process-gone", (_event, details) => {
console.error("[main-window] Renderer process gone:", details);
log.error("[main-window] Renderer process gone", details);
});

window.webContents.on("preload-error", (_event, preloadPath, error) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useEffect, useRef } from "react";
import { env } from "renderer/env.renderer";

interface RenderStressOptions {
windowMs?: number;
warnAt?: number;
getDetails?: () => Record<string, unknown>;
}

const DEFAULT_WINDOW_MS = 5_000;
const DEFAULT_WARN_AT = 40;

export function useRenderStressInstrumentation(
name: string,
options: RenderStressOptions = {},
): void {
const stateRef = useRef({ count: 0, windowStartedAt: 0, warned: false });

useEffect(() => {
if (env.NODE_ENV !== "development") return;

const now = performance.now();
const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
const warnAt = options.warnAt ?? DEFAULT_WARN_AT;
const state = stateRef.current;

if (state.windowStartedAt === 0 || now - state.windowStartedAt > windowMs) {
state.count = 0;
state.windowStartedAt = now;
state.warned = false;
}

state.count += 1;

if (!state.warned && state.count >= warnAt) {
state.warned = true;
console.warn(
"[stress] high renderer commit rate",
JSON.stringify({
name,
count: state.count,
windowMs,
...(options.getDetails?.() ?? {}),
}),
);
}
});
}

export function logStressEvent(
name: string,
details?: Record<string, unknown>,
): void {
if (env.NODE_ENV !== "development") return;
console.debug("[stress]", name, JSON.stringify(details ?? {}));
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,64 @@
const backgroundTerminalIds = new Set<string>();
const backgroundTerminalMarkersByWorkspace = new Map<string, Set<string>>();
const markerListeners = new Set<() => void>();

export function markTerminalForBackground(terminalId: string): void {
function emitMarkerChange(): void {
for (const listener of markerListeners) {
listener();
}
}

function getWorkspaceMarkers(workspaceId: string): Set<string> {
const existing = backgroundTerminalMarkersByWorkspace.get(workspaceId);
if (existing) return existing;

const markers = new Set<string>();
backgroundTerminalMarkersByWorkspace.set(workspaceId, markers);
return markers;
}

export function markTerminalForBackground(
terminalId: string,
workspaceId?: string,
): void {
backgroundTerminalIds.add(terminalId);

if (!workspaceId) return;

const markers = getWorkspaceMarkers(workspaceId);
if (markers.has(terminalId)) return;

markers.add(terminalId);
emitMarkerChange();
}

export function consumeTerminalBackgroundIntent(terminalId: string): boolean {
return backgroundTerminalIds.delete(terminalId);
}

export function clearTerminalBackgroundMarker(
workspaceId: string,
terminalId: string,
): void {
const markers = backgroundTerminalMarkersByWorkspace.get(workspaceId);
if (!markers?.delete(terminalId)) return;

if (markers.size === 0) {
backgroundTerminalMarkersByWorkspace.delete(workspaceId);
}
emitMarkerChange();
}

export function getTerminalBackgroundMarkerIdsKey(workspaceId: string): string {
const markers = backgroundTerminalMarkersByWorkspace.get(workspaceId);
return JSON.stringify(markers ? [...markers].sort() : []);
}

export function subscribeTerminalBackgroundMarkers(
listener: () => void,
): () => void {
markerListeners.add(listener);
return () => {
markerListeners.delete(listener);
};
}
Loading
Loading