mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-08 02:37:45 +02:00
defluff (frontend + backend): strip em-dashes + shorten docstrings + drop dead UI files (cosmetic only, no schedule code)
This commit is contained in:
@@ -1,17 +1,6 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
/**
|
||||
* React context that signals whether the Dashboard is currently the active
|
||||
* route (i.e. visible to the user) vs hidden in the background.
|
||||
*
|
||||
* Defaults to `true` so any standalone usage of dashboard children outside
|
||||
* the DashboardHost wrapper just behaves normally.
|
||||
*
|
||||
* Heavy/expensive Dashboard children read this via `useDashboardActive()`
|
||||
* and short-circuit their work when the dashboard is hidden — that's how
|
||||
* we keep CPU usage near-zero while the user is on /actions or /settings
|
||||
* with the Dashboard mounted but invisible.
|
||||
*/
|
||||
/** True when Dashboard is the visible route; heavy children short-circuit when false. */
|
||||
const DashboardActiveContext = createContext<boolean>(true);
|
||||
|
||||
export const DashboardActiveProvider = DashboardActiveContext.Provider;
|
||||
|
||||
@@ -6,30 +6,17 @@ import { fetchTools } from '@/shared/state/toolsSlice';
|
||||
import { API_BASE } from '@/shared/config';
|
||||
import { report } from '@/shared/serviceClient';
|
||||
|
||||
// Listens for openswarm://auth?token=...&plan=...&expires=... URLs coming
|
||||
// from the Electron main process via window.openswarm.onAuthUrl. Parses the
|
||||
// payload and dispatches activateSubscription so the backend validates and
|
||||
// persists the bearer.
|
||||
//
|
||||
// Safe no-op in web/browser contexts where window.openswarm isn't defined.
|
||||
/** Subscribe to openswarm:// auth/oauth deep-links from Electron main; no-op in browser. */
|
||||
export function useDeepLink(): void {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
const api = (window as any).openswarm as OpenSwarmAPI | undefined;
|
||||
// Both listeners are optional — useDeepLink no-ops in browser/web context
|
||||
// where window.openswarm is undefined.
|
||||
if (!api) return;
|
||||
|
||||
const unsubscribe = api.onAuthUrl?.((rawUrl: string) => {
|
||||
try {
|
||||
// openswarm://auth?token=... (host = "auth", search carries fields).
|
||||
// Two flavors land here, distinguished by the `signin` flag:
|
||||
// - signin=true → free-tier sign-in (Google OAuth / magic link)
|
||||
// - (default) → Stripe checkout subscription activation
|
||||
// Note: the bearer-handoff page in lib/authMint.ts (cloud) POSTs
|
||||
// directly to localhost so this deep-link path is currently a
|
||||
// backstop for older flows. Both branches here remain wired up.
|
||||
// openswarm://auth?token=... ; signin=true => free sign-in, else Stripe activation.
|
||||
const url = new URL(rawUrl);
|
||||
if (url.host !== 'auth' && url.pathname !== '//auth' && url.pathname !== '/auth') {
|
||||
console.warn('[deep-link] Unknown openswarm:// host:', url.host);
|
||||
@@ -47,9 +34,7 @@ export function useDeepLink(): void {
|
||||
const expires = url.searchParams.get('expires');
|
||||
|
||||
if (isSignin) {
|
||||
// v1.0.29 only supports Google sign-in. signinMethodRaw is read
|
||||
// for forward compatibility / analytics if other methods are
|
||||
// added later.
|
||||
// 1.0.29 only ships Google sign-in; read for forward compat.
|
||||
void signinMethodRaw;
|
||||
report('signin', 'deep_link_received', { method: 'google' });
|
||||
|
||||
@@ -82,8 +67,7 @@ export function useDeepLink(): void {
|
||||
.unwrap()
|
||||
.then((res) => {
|
||||
report('subscription', 'activated', { plan: res.plan });
|
||||
// Re-fetch the model list so the Claude models (via OpenSwarm
|
||||
// Pro proxy) show up in the chat picker right away.
|
||||
// Refresh models so Pro-proxy Claude models appear in the picker immediately.
|
||||
dispatch(fetchModels());
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -97,15 +81,12 @@ export function useDeepLink(): void {
|
||||
}
|
||||
});
|
||||
|
||||
// OAuth claim deep-link listener. The Electron main process routes
|
||||
// openswarm://oauth/{provider}/complete to its own IPC channel so we
|
||||
// can claim tokens immediately rather than routing through Settings.
|
||||
let unsubscribeOauth: (() => void) | undefined;
|
||||
if (api?.onOauthClaim) {
|
||||
unsubscribeOauth = api.onOauthClaim(async (rawUrl: string) => {
|
||||
try {
|
||||
// openswarm://oauth/{provider}/complete?session_id=...&tool_id=...
|
||||
const url = new URL(rawUrl);
|
||||
// Expected: openswarm://oauth/{provider}/complete?session_id=...&tool_id=...
|
||||
if (url.host !== 'oauth' || !url.pathname.endsWith('/complete')) {
|
||||
console.warn('[deep-link] Unexpected oauth-claim URL:', rawUrl);
|
||||
return;
|
||||
@@ -131,7 +112,6 @@ export function useDeepLink(): void {
|
||||
return;
|
||||
}
|
||||
report('oauth', 'claim_succeeded');
|
||||
// Refresh tools so the UI reflects the newly-connected tool.
|
||||
dispatch(fetchTools());
|
||||
} catch (e) {
|
||||
console.error('[deep-link] OAuth claim threw:', e);
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
// Mounts a single global listener that records each user interaction
|
||||
// timestamp into Redux. One installer per app — call from Main.tsx after
|
||||
// the store is provided.
|
||||
//
|
||||
// Debounces at 1-second granularity so we don't spam Redux on every
|
||||
// keystroke. Coarse enough for "idle dim after N minutes" UX; fine enough
|
||||
// that the timestamp on session close is accurate to the second.
|
||||
// Records user-interaction timestamps into Redux, 1s-debounced. Mount once from Main.tsx.
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useAppDispatch } from '@/shared/hooks';
|
||||
|
||||
@@ -4,23 +4,7 @@ import { useLocation } from 'react-router-dom';
|
||||
const STORAGE_KEY = 'openswarm_last_dashboard_id';
|
||||
const WINDOW_KEY = '__openswarm_last_dashboard_id';
|
||||
|
||||
/**
|
||||
* Tracks the last visited dashboard id in a "sticky" way: once a dashboard
|
||||
* has been visited, the id stays set even when the user navigates to other
|
||||
* routes. This is the foundation for keeping the Dashboard component mounted
|
||||
* across non-dashboard route navigation (hide-don't-unmount pattern).
|
||||
*
|
||||
* The Dashboard component reads its dashboardId from this hook (via a prop
|
||||
* passed by AppShell) instead of from `useParams()`, so the id never goes
|
||||
* undefined when the URL changes to /actions etc. This prevents the
|
||||
* dashboardId useEffect from re-firing on every incidental route change,
|
||||
* which would cause `resetLayout` + `fetchLayout` and visibly reload the
|
||||
* browser cards.
|
||||
*
|
||||
* Returns a tuple of `[lastDashboardId, setLastDashboardId]`. The setter
|
||||
* is exposed so explicit dashboard close/delete handlers can clear it
|
||||
* (which causes the Dashboard to fully unmount and tear down its webviews).
|
||||
*/
|
||||
/** Sticky last-visited dashboard id so Dashboard stays mounted across non-dashboard nav. */
|
||||
export function useLastDashboardId(): [string | null, (id: string | null) => void] {
|
||||
const location = useLocation();
|
||||
const [lastId, setLastIdState] = useState<string | null>(() => {
|
||||
@@ -31,8 +15,7 @@ export function useLastDashboardId(): [string | null, (id: string | null) => voi
|
||||
}
|
||||
});
|
||||
|
||||
// Watch the URL — when it matches /dashboard/:id, update the sticky id.
|
||||
// Critically: do NOT clear the sticky id when the URL stops matching.
|
||||
// Watch URL; update sticky id on /dashboard/:id. Do NOT clear when URL stops matching.
|
||||
useEffect(() => {
|
||||
const match = location.pathname.match(/^\/dashboard\/([^/]+)/);
|
||||
if (match && match[1] && match[1] !== lastId) {
|
||||
|
||||
@@ -5,7 +5,6 @@ const QUERY = '(prefers-reduced-motion: reduce)';
|
||||
function subscribe(callback: () => void): () => void {
|
||||
if (typeof window === 'undefined' || !window.matchMedia) return () => {};
|
||||
const mql = window.matchMedia(QUERY);
|
||||
// Modern + legacy event names both supported.
|
||||
mql.addEventListener('change', callback);
|
||||
return () => mql.removeEventListener('change', callback);
|
||||
}
|
||||
@@ -19,30 +18,12 @@ function getServerSnapshot(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the OS-level "Reduce motion" preference is on.
|
||||
* Mac: System Settings → Accessibility → Display → Reduce Motion.
|
||||
* Windows: Settings → Ease of Access → Display → Show animations.
|
||||
*
|
||||
* Reactive — flips immediately if the user toggles the OS setting
|
||||
* mid-session (rare but supported).
|
||||
*/
|
||||
/** True when the OS "Reduce motion" preference is on; reactive to OS toggles. */
|
||||
export function useReducedMotion(): boolean {
|
||||
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: returns 0 when reduced-motion is on, otherwise the supplied
|
||||
* duration. Use inline at animation sites:
|
||||
*
|
||||
* const dur = useMotionDuration(DURATION_MS.quick);
|
||||
* <Fade timeout={dur}>...</Fade>
|
||||
*
|
||||
* For animations that convey causality (modal open, drawer slide), prefer a
|
||||
* tiny non-zero floor so the user still perceives the transition:
|
||||
*
|
||||
* const dur = useMotionDuration(DURATION_MS.standard, { floor: 40 });
|
||||
*/
|
||||
/** Returns 0 (or `opts.floor`) when reduced-motion is on, else `ms`. */
|
||||
export function useMotionDuration(ms: number, opts: { floor?: number } = {}): number {
|
||||
const reduced = useReducedMotion();
|
||||
if (!reduced) return ms;
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
// Route-change tracker.
|
||||
//
|
||||
// Reports a `nav.route_changed` event on every React Router location
|
||||
// change so the cloud can aggregate visits per route. Reuses the
|
||||
// existing report() surface — no new outbound paths added. The desktop
|
||||
// just sends the path; the cloud counts.
|
||||
//
|
||||
// Mount inside a Router (must be a child of HashRouter / BrowserRouter)
|
||||
// so useLocation() resolves.
|
||||
// Reports nav.route_changed on each React Router location change. Mount inside a Router.
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
@@ -14,8 +6,7 @@ import { report } from '@/shared/serviceClient';
|
||||
|
||||
export function useRouteTracker(): void {
|
||||
const location = useLocation();
|
||||
// Skip the very first render — the App opens at "/" and we don't want
|
||||
// to report a phantom navigation that didn't happen.
|
||||
// Skip first render so the App's "/" open doesn't fire a phantom nav.
|
||||
const skippedFirst = useRef(false);
|
||||
const lastPath = useRef<string>('');
|
||||
|
||||
@@ -28,8 +19,6 @@ export function useRouteTracker(): void {
|
||||
}
|
||||
if (path === lastPath.current) return;
|
||||
lastPath.current = path;
|
||||
// The path is a route name (e.g. /dashboard, /settings) — never the
|
||||
// full URL. No query strings, no hash fragments beyond the route id.
|
||||
report('nav', 'route_changed', { path });
|
||||
}, [location.hash, location.pathname]);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,4 @@
|
||||
// Single source of truth for "what URL should the preview webview point at?"
|
||||
//
|
||||
// New-mode webapp_template workspaces have no root index.html — the live
|
||||
// preview lives behind the workspace's own Vite dev server, whose port is
|
||||
// announced by the backend's runtime:status WS frame. Old-mode flat
|
||||
// workspaces still serve files through the legacy /api/outputs/.../serve/
|
||||
// endpoints. This hook hides the difference: it attaches to the runtime
|
||||
// (ref-counted server-side, so multiple subscribers share one process),
|
||||
// listens for status, and exposes the live frontend_url + new-mode flag.
|
||||
// Consumers compute their final URL with `pickPreviewUrl()` below.
|
||||
//
|
||||
// Used by both ViewEditor (editor tab) and DashboardViewCard (dashboard
|
||||
// canvas). Earlier each component had its own copy of this effect and
|
||||
// only the editor had the new-mode logic — that's why dashboard cards
|
||||
// for webapp_template apps were rendering the literal "File not found
|
||||
// in output" JSON. One hook now, both consumers stay in sync.
|
||||
// Hides legacy /serve/ vs new-mode Vite-runtime split for preview URLs; ref-counted spawn.
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { API_BASE, getAuthToken } from '@/shared/config';
|
||||
@@ -27,25 +12,14 @@ export interface RuntimeLogLine {
|
||||
export interface RuntimePreviewState {
|
||||
frontendUrl: string | null;
|
||||
isNewMode: boolean;
|
||||
// True for the first ~400ms after subscribing — gives the runtime WS a
|
||||
// chance to send its initial runtime:status frame before consumers
|
||||
// decide to render a "Starting preview…" placeholder. Without this
|
||||
// gate, dashboard cards flashed the placeholder every remount even
|
||||
// when Vite was already up, because frontendUrl resets to null on
|
||||
// mount and arrives one tick later.
|
||||
// True until the runtime:status frame lands; prevents placeholder flash on remount when Vite is up.
|
||||
isHydrating: boolean;
|
||||
}
|
||||
|
||||
export interface RuntimePreviewOptions {
|
||||
// Workspace to attach to. null/undefined → no-op (no spawn, no WS).
|
||||
workspaceId: string | null | undefined;
|
||||
// Gate the spawn. Lets callers defer paying the runtime cost until
|
||||
// the user actually wants the preview (ViewEditor only spawns once
|
||||
// the user clicks Preview or Terminal). Dashboard cards default to
|
||||
// true since the preview pane is always visible.
|
||||
/** Gate the spawn so callers can defer paying runtime cost until preview is wanted. */
|
||||
enabled?: boolean;
|
||||
// Optional sink for log lines. Editor's terminal panel uses this;
|
||||
// dashboard cards don't need it and can omit.
|
||||
onLog?: (line: RuntimeLogLine) => void;
|
||||
}
|
||||
|
||||
@@ -54,9 +28,7 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
const [frontendUrl, setFrontendUrl] = useState<string | null>(null);
|
||||
const [isNewMode, setIsNewMode] = useState(false);
|
||||
const [isHydrating, setIsHydrating] = useState(true);
|
||||
// Pin the latest onLog so we don't tear down + respawn the runtime
|
||||
// every time the callback identity changes. The effect only depends
|
||||
// on workspaceId + enabled.
|
||||
// Pin latest onLog so callback identity changes don't tear down/respawn the runtime.
|
||||
const onLogRef = useRef(onLog);
|
||||
onLogRef.current = onLog;
|
||||
|
||||
@@ -70,12 +42,7 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
setFrontendUrl(null);
|
||||
setIsNewMode(false);
|
||||
setIsHydrating(true);
|
||||
// Drop the hydrating flag after the WS has had time to deliver its
|
||||
// initial runtime:status frame. With the backend's 80ms poll
|
||||
// interval, status almost always arrives in 20-100ms; 150ms is
|
||||
// generous enough that warm starts never flash the booting
|
||||
// placeholder, while not making genuinely-cold runtimes wait an
|
||||
// extra half second before showing "Starting preview…".
|
||||
// 150ms: warm starts deliver status in 20-100ms; long enough to skip placeholder flash, short enough to not stall cold starts.
|
||||
const hydrationTimer = setTimeout(() => {
|
||||
if (!cancelled) setIsHydrating(false);
|
||||
}, 150);
|
||||
@@ -91,7 +58,7 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
headers,
|
||||
});
|
||||
} catch (_) {
|
||||
// Spawn errors surface via the log WS. Don't double-report.
|
||||
// Spawn errors surface via the log WS; don't double-report.
|
||||
}
|
||||
if (cancelled) return;
|
||||
try {
|
||||
@@ -105,7 +72,6 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
const fu = msg.data?.frontend_url ?? null;
|
||||
setFrontendUrl(fu || null);
|
||||
setIsNewMode(!!msg.data?.is_new_mode);
|
||||
// Status arrived; hand off to the real ready/booting gate.
|
||||
setIsHydrating(false);
|
||||
} else if (msg.event === 'runtime:log') {
|
||||
const stream = msg.data?.stream || 'stdout';
|
||||
@@ -118,8 +84,7 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
}
|
||||
};
|
||||
} catch (_) {
|
||||
// WS construction failed (CSP, bad URL, etc). Caller stays in
|
||||
// its "no preview yet" state — same shape as a slow Vite cold start.
|
||||
// WS construction failed; caller stays in "no preview yet" state.
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -130,9 +95,7 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
setFrontendUrl(null);
|
||||
setIsNewMode(false);
|
||||
setIsHydrating(true);
|
||||
// detach is ref-counted on the backend — only the last subscriber
|
||||
// actually tears down the runtime, the rest are no-ops. We fire
|
||||
// and forget; errors here would be transient and don't affect UX.
|
||||
// detach is ref-counted on the backend; fire-and-forget.
|
||||
fetch(`${API_BASE}/outputs/workspace/${workspaceId}/runtime/stop`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
@@ -145,40 +108,25 @@ export function useRuntimePreviewUrl(opts: RuntimePreviewOptions): RuntimePrevie
|
||||
|
||||
export interface PickPreviewUrlOptions {
|
||||
workspaceId: string | null | undefined;
|
||||
// Legacy fallback URL for old-mode flat workspaces. Pass the URL the
|
||||
// component used BEFORE the new-mode split (ViewEditor uses
|
||||
// `${SERVE_BASE}/workspace/${ws}/serve/index.html`, dashboard cards
|
||||
// use `${SERVE_BASE}/${output_id}/serve/index.html`). When the runtime
|
||||
// says we're in new-mode AND Vite is up, we override with frontendUrl.
|
||||
/** Pre-new-mode URL the component used (serve/index.html); overridden by frontendUrl when ready. */
|
||||
legacyUrl: string | undefined;
|
||||
frontendUrl: string | null;
|
||||
isNewMode: boolean;
|
||||
}
|
||||
|
||||
export interface PickPreviewUrlResult {
|
||||
// Final URL the preview should load. `undefined` means "show placeholder
|
||||
// instead" — happens when the workspace is new-mode but Vite hasn't
|
||||
// bound yet (cold start, npm install in progress, runtime crashed).
|
||||
/** undefined => render placeholder (new-mode and Vite not bound yet). */
|
||||
url: string | undefined;
|
||||
// True iff we're in new-mode and frontendUrl hasn't arrived. UI uses
|
||||
// this to render a "Starting preview…" affordance instead of letting
|
||||
// the webview attempt the legacy URL (which 404s in new-mode).
|
||||
isBooting: boolean;
|
||||
}
|
||||
|
||||
export function pickPreviewUrl(opts: PickPreviewUrlOptions): PickPreviewUrlResult {
|
||||
const { legacyUrl, frontendUrl, isNewMode, workspaceId } = opts;
|
||||
if (!workspaceId) {
|
||||
// No workspace id at all (output never seeded one). Use whatever
|
||||
// legacy URL the caller computed — typical for old flat outputs
|
||||
// that were created before workspace_id became standard.
|
||||
return { url: legacyUrl, isBooting: false };
|
||||
}
|
||||
if (isNewMode && !frontendUrl) {
|
||||
return { url: undefined, isBooting: true };
|
||||
}
|
||||
// Prefer frontendUrl when present (works for both new-mode that's up
|
||||
// AND any future caller that gives us a Vite URL). Fall back to the
|
||||
// legacy serve URL for old-mode workspaces.
|
||||
return { url: frontendUrl ?? legacyUrl, isBooting: false };
|
||||
}
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
// Window blur/focus tracking — analytics signal for "user switched to
|
||||
// another app" (temp-churn measurement).
|
||||
//
|
||||
// Wires the IPC channel that electron/main.js fires on the BrowserWindow's
|
||||
// blur/focus events into the existing `report()` analytics pipeline. Each
|
||||
// blur emits `app focus_lost` with the elapsed-ms-since-last-focus, and
|
||||
// each focus emits `app focus_gained` with elapsed-ms-since-last-blur.
|
||||
//
|
||||
// Together these answer: how often do users leave OpenSwarm mid-session,
|
||||
// for how long, and at what cadence?
|
||||
//
|
||||
// No-op in browser/web context where window.openswarm is undefined.
|
||||
// Reports app focus_lost/focus_gained from Electron blur/focus IPC; no-op in browser.
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { report } from '@/shared/serviceClient';
|
||||
|
||||
Reference in New Issue
Block a user