fix(app): remove more dead settings shell code (#1350)

* fix(app): remove more dead settings shell code

* fix(app): prune more dead helpers and props
This commit is contained in:
ben
2026-04-04 17:56:43 -07:00
committed by GitHub
parent d075f03e75
commit 2b740531a4
23 changed files with 6 additions and 1290 deletions

View File

@@ -2069,7 +2069,6 @@ export default function App() {
const settingsTabs = new Set<SettingsTab>([
"general",
"den",
"model",
"automations",
"skills",
"extensions",

View File

@@ -10,7 +10,6 @@ export type QuestionModalProps = {
open: boolean;
questions: QuestionInfo[];
busy: boolean;
onClose: () => void;
onReply: (answers: string[][]) => void;
};

View File

@@ -1,298 +0,0 @@
import { For, Show, createEffect, createMemo, createSignal } from "solid-js";
import { Download, RefreshCw, UploadCloud } from "lucide-solid";
import { getOpenWorkDeployment } from "../../lib/openwork-deployment";
import { t } from "../../../i18n";
import type { OpenworkInboxItem, OpenworkServerClient } from "../../lib/openwork-server";
import WebUnavailableSurface from "../web-unavailable-surface";
import { formatBytes, formatRelativeTime } from "../../utils";
export type InboxPanelProps = {
id?: string;
client: OpenworkServerClient | null;
workspaceId: string | null;
onToast?: (message: string) => void;
maxPreview?: number;
};
const INBOX_PREFIX = ".opencode/openwork/inbox/";
function safeName(item: OpenworkInboxItem): string {
return String(item.name ?? item.path ?? "file").trim() || "file";
}
function safeRelPath(item: OpenworkInboxItem): string {
const raw = String(item.path ?? item.name ?? "").trim();
if (!raw) return "";
return raw.replace(/^\/+/, "");
}
function toInboxWorkspacePath(item: OpenworkInboxItem): string {
const rel = safeRelPath(item);
return rel ? `${INBOX_PREFIX}${rel}` : INBOX_PREFIX;
}
export default function InboxPanel(props: InboxPanelProps) {
const [items, setItems] = createSignal<OpenworkInboxItem[]>([]);
const [loading, setLoading] = createSignal(false);
const [uploading, setUploading] = createSignal(false);
const [dragOver, setDragOver] = createSignal(false);
const [error, setError] = createSignal<string | null>(null);
const webDeployment = createMemo(() => getOpenWorkDeployment() === "web");
let fileInputRef: HTMLInputElement | undefined;
const maxPreview = createMemo(() => {
const raw = props.maxPreview ?? 6;
if (!Number.isFinite(raw)) return 6;
return Math.min(12, Math.max(3, Math.floor(raw)));
});
const connected = createMemo(() => Boolean(props.client && (props.workspaceId ?? "").trim()));
const helperText = () => t("inbox_panel.helper_text");
const visibleItems = createMemo(() => (items() ?? []).slice(0, maxPreview()));
const hiddenCount = createMemo(() => Math.max(0, (items() ?? []).length - visibleItems().length));
const toast = (message: string) => {
props.onToast?.(message);
};
const refresh = async () => {
const client = props.client;
const workspaceId = (props.workspaceId ?? "").trim();
if (!client || !workspaceId) {
setItems([]);
return;
}
setLoading(true);
setError(null);
try {
const result = await client.listInbox(workspaceId);
setItems(result.items ?? []);
} catch (err) {
const message =
err instanceof Error ? err.message : t("inbox_panel.load_failed");
setError(message);
setItems([]);
} finally {
setLoading(false);
}
};
const uploadFiles = async (files: File[]) => {
const client = props.client;
const workspaceId = (props.workspaceId ?? "").trim();
if (!client || !workspaceId) {
toast(t("inbox_panel.upload_needs_worker"));
return;
}
if (!files.length) return;
setUploading(true);
setError(null);
try {
const label = files.length === 1 ? files[0]?.name ?? "file" : `${files.length} files`;
toast(t("inbox_panel.uploading_label", undefined, { label }));
for (const file of files) {
await client.uploadInbox(workspaceId, file);
}
toast(t("inbox_panel.upload_success"));
await refresh();
} catch (err) {
const message =
err instanceof Error ? err.message : t("inbox_panel.upload_failed");
setError(message);
toast(message);
} finally {
setUploading(false);
}
};
const copyPath = async (item: OpenworkInboxItem) => {
const path = toInboxWorkspacePath(item);
try {
await navigator.clipboard.writeText(path);
toast(t("inbox_panel.copied_path", undefined, { path }));
} catch {
toast(t("inbox_panel.copy_failed"));
}
};
const downloadItem = async (item: OpenworkInboxItem) => {
const client = props.client;
const workspaceId = (props.workspaceId ?? "").trim();
if (!client || !workspaceId) {
toast(t("inbox_panel.connect_to_download"));
return;
}
const id = String(item.id ?? "").trim();
if (!id) {
toast(t("inbox_panel.missing_file_id"));
return;
}
try {
const result = await client.downloadInboxItem(workspaceId, id);
const blob = new Blob([result.data], { type: result.contentType ?? "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = result.filename ?? safeName(item);
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
} catch (err) {
const message = err instanceof Error ? err.message : t("inbox_panel.download_failed");
toast(message);
}
};
createEffect(() => {
// Refresh when switching workers.
void props.workspaceId;
void props.client;
void refresh();
});
return (
<WebUnavailableSurface unavailable={webDeployment()} compact>
<div id={props.id}>
<div class="flex items-center justify-between px-2 mb-3">
<span class="text-[11px] font-semibold uppercase tracking-wider text-gray-10">{t("inbox_panel.shared_folder")}</span>
<div class="flex items-center gap-2">
<Show when={(items() ?? []).length > 0}>
<span class="text-[11px] font-medium bg-gray-4/60 text-gray-10 px-1.5 rounded">
{(items() ?? []).length}
</span>
</Show>
<button
type="button"
class="rounded-md p-1 text-gray-9 hover:text-gray-11 hover:bg-gray-3 transition-colors"
onClick={() => void refresh()}
title={t("inbox_panel.refresh_tooltip")}
aria-label={t("inbox_panel.refresh_tooltip")}
disabled={!connected() || loading()}
>
<RefreshCw size={14} class={loading() ? "animate-spin" : ""} />
</button>
</div>
</div>
<input
ref={fileInputRef}
type="file"
multiple
class="hidden"
onChange={(event: Event) => {
const target = event.currentTarget as HTMLInputElement;
const files = Array.from(target.files ?? []);
if (files.length) void uploadFiles(files);
target.value = "";
}}
/>
<button
type="button"
class={`w-full border border-dashed border-gray-7 rounded-xl px-4 py-4 text-left transition-colors ${
dragOver() ? "bg-gray-3" : "bg-gray-2/60 hover:bg-gray-2"
} ${!connected() ? "opacity-70" : ""}`}
onClick={() => fileInputRef?.click()}
onDragOver={(event: DragEvent) => {
event.preventDefault();
setDragOver(true);
}}
onDragLeave={(event: DragEvent) => {
event.preventDefault();
setDragOver(false);
}}
onDrop={(event: DragEvent) => {
event.preventDefault();
setDragOver(false);
const files = Array.from(event.dataTransfer?.files ?? []);
if (files.length) void uploadFiles(files);
}}
disabled={uploading()}
title={connected() ? t("inbox_panel.drop_to_upload") : t("inbox_panel.connect_to_upload")}
>
<div class="flex flex-col items-center justify-center text-center">
<UploadCloud size={18} class="text-gray-9 mb-2" />
<span class="text-[13px] font-medium text-gray-11">
{uploading() ? t("inbox_panel.uploading") : t("inbox_panel.upload_prompt")}
</span>
<span class="mt-0.5 text-[11px] text-gray-9">{helperText()}</span>
</div>
</button>
<div class="mt-2 space-y-1">
<Show when={error()}>
<div class="text-xs text-red-11 px-1 py-1">{error()}</div>
</Show>
<Show
when={visibleItems().length > 0}
fallback={
<div class="text-xs text-gray-10 px-1 py-1">
<Show when={connected()} fallback={t("inbox_panel.connect_to_see")}>
{t("inbox_panel.no_files")}
</Show>
</div>
}
>
<For each={visibleItems()}>
{(item) => {
const name = () => safeName(item);
const rel = () => safeRelPath(item);
const bytes = () => (typeof item.size === "number" ? item.size : null);
const updatedAt = () => (typeof item.updatedAt === "number" ? item.updatedAt : null);
return (
<div class="group flex min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-gray-2 transition-colors border border-transparent hover:border-gray-6/80">
<button
type="button"
class="min-w-0 flex-1 text-left"
onClick={() => void copyPath(item)}
title={rel() ? `Copy ${INBOX_PREFIX}${rel()}` : "Copy shared folder path"}
aria-label={rel() ? `Copy ${INBOX_PREFIX}${rel()}` : "Copy shared folder path"}
disabled={!connected()}
>
<div class="truncate text-xs font-medium text-gray-11">{name()}</div>
<div class="mt-0.5 flex min-w-0 items-center gap-2 text-[11px] text-gray-9">
<Show when={bytes() != null}>
<span class="font-mono">{formatBytes(bytes() as number)}</span>
</Show>
<Show when={updatedAt() != null}>
<span>{formatRelativeTime(updatedAt() as number)}</span>
</Show>
<Show when={rel()}>
<span class="min-w-0 truncate font-mono">{rel()}</span>
</Show>
</div>
</button>
<button
type="button"
class="shrink-0 rounded-md p-1 text-gray-9 opacity-0 group-hover:opacity-100 hover:text-gray-11 hover:bg-gray-3"
onClick={() => void downloadItem(item)}
title={t("inbox_panel.download")}
aria-label={t("inbox_panel.download")}
disabled={!connected()}
>
<Download size={14} />
</button>
</div>
);
}}
</For>
</Show>
<Show when={hiddenCount() > 0}>
<div class="text-[11px] text-gray-10 px-1 py-1">{t("inbox_panel.showing_first", undefined, { count: maxPreview() })}</div>
</Show>
</div>
</div>
</WebUnavailableSurface>
);
}

View File

@@ -12,9 +12,6 @@ type StatusBarProps = {
settingsOpen: boolean;
onSendFeedback: () => void;
onOpenSettings: () => void;
onOpenMessaging: () => void;
onOpenProviders: () => Promise<void> | void;
onOpenMcp: () => void;
providerConnectedIds: string[];
statusLabel?: string;
statusDetail?: string;

View File

@@ -1,65 +0,0 @@
import type { WorkspaceInfo } from "../lib/tauri";
import { t, currentLocale } from "../../i18n";
import { ChevronDown, Folder, Globe, Loader2, Zap } from "lucide-solid";
function iconForWorkspace(preset: string, workspaceType: string) {
if (workspaceType === "remote") return Globe;
if (preset === "starter") return Zap;
if (preset === "automation") return Folder;
if (preset === "minimal") return Globe;
return Folder;
}
export default function WorkspaceChip(props: {
workspace: WorkspaceInfo;
onClick: () => void;
connecting?: boolean;
}) {
const Icon = iconForWorkspace(props.workspace.preset, props.workspace.workspaceType);
const subtitle = () =>
props.workspace.workspaceType === "remote"
? props.workspace.baseUrl ?? props.workspace.path
: props.workspace.path;
const isSandboxWorkspace =
props.workspace.workspaceType === "remote" &&
(props.workspace.sandboxBackend === "docker" ||
Boolean(props.workspace.sandboxRunId?.trim()) ||
Boolean(props.workspace.sandboxContainerName?.trim()));
const translate = (key: string) => t(key, currentLocale());
return (
<button
onClick={props.onClick}
class="flex items-center gap-2 pl-3 pr-2 py-1.5 bg-gray-2 border border-gray-6 rounded-lg hover:border-gray-7 hover:bg-gray-4 transition-all group"
>
<div
class={`p-1 rounded ${
props.workspace.workspaceType !== "remote" && props.workspace.preset === "starter"
? "bg-amber-7/10 text-amber-6"
: "bg-indigo-7/10 text-indigo-6"
}`}
>
<Icon size={14} />
</div>
<div class="flex flex-col items-start mr-2 min-w-0">
<div class="flex items-center gap-2">
<span class="text-xs font-medium text-gray-12 leading-none truncate max-w-[9.5rem]">
{props.workspace.name}
</span>
{props.workspace.workspaceType === "remote" ? (
<span class="text-[9px] uppercase tracking-wide px-1.5 py-0.5 rounded-full bg-gray-4 text-gray-11">
{isSandboxWorkspace ? translate("workspace.sandbox_badge") : translate("dashboard.remote")}
</span>
) : null}
</div>
<span class="text-[10px] text-gray-10 font-mono leading-none max-w-[120px] truncate">
{subtitle()}
</span>
</div>
<ChevronDown size={14} class="text-gray-10 group-hover:text-gray-11" />
{props.connecting ? <Loader2 size={14} class="text-gray-10 animate-spin" /> : null}
</button>
);
}

View File

@@ -1,161 +0,0 @@
import { Show, type JSX } from "solid-js";
import { t } from "../../i18n";
import {
Box,
ChevronLeft,
ChevronRight,
History,
MessageCircle,
Settings,
SlidersHorizontal,
X,
Zap,
} from "lucide-solid";
import type { SettingsTab } from "../types";
import type { OpenworkServerClient } from "../lib/openwork-server";
import InboxPanel from "./session/inbox-panel";
type Props = {
expanded: boolean;
mobile?: boolean;
showSelection?: boolean;
settingsTab?: SettingsTab;
developerMode: boolean;
activeWorkspaceLabel: string;
activeWorkspaceType: "local" | "remote";
openworkServerClient: OpenworkServerClient | null;
runtimeWorkspaceId: string | null;
inboxId: string;
onToggleExpanded: () => void;
onCloseMobile?: () => void;
onOpenAutomations: () => void;
onOpenSkills: () => void;
onOpenExtensions: () => void;
onOpenMessaging: () => void;
onOpenAdvanced: () => void;
onOpenSettings: () => void;
onInboxToast?: (message: string) => void;
};
export default function WorkspaceRightSidebar(props: Props) {
const mobile = () => props.mobile ?? false;
const showSelection = () => props.showSelection ?? true;
const closeMobile = () => props.onCloseMobile?.();
const sidebarButton = (
label: string,
icon: JSX.Element,
active: boolean,
onClick: () => void,
) => (
<button
type="button"
class={`w-full border text-[13px] font-medium transition-[background-color,border-color,box-shadow,color] ${
active
? "border-dls-border bg-dls-surface text-dls-text shadow-[var(--dls-card-shadow)]"
: "border-transparent text-gray-10 hover:border-dls-border hover:bg-dls-surface hover:text-dls-text"
} ${
props.expanded
? "flex min-h-11 items-center justify-start gap-2.5 rounded-[16px] px-3.5"
: "flex h-12 items-center justify-center rounded-[16px] px-0"
}`}
onClick={() => {
onClick();
if (mobile()) closeMobile();
}}
title={label}
aria-label={label}
>
{icon}
<Show when={props.expanded}>
<span class="flex min-w-0 flex-1 items-center gap-2">
<span class="truncate">{label}</span>
</span>
</Show>
</button>
);
return (
<div class={`flex h-full w-full flex-col overflow-hidden rounded-[24px] border border-dls-border bg-dls-sidebar p-3 ${mobile() ? "shadow-2xl" : "transition-[width] duration-200"}`}>
<div class={`flex items-center pb-3 ${props.expanded ? "justify-between gap-3" : "justify-center"}`}>
<Show when={props.expanded}>
<div class="min-w-0 px-1">
<div class="truncate text-[11px] font-medium uppercase tracking-[0.18em] text-gray-8">
{(props.activeWorkspaceLabel || t("dashboard.workspaces"))} {t("workspace_sidebar.configuration")}
</div>
</div>
</Show>
<button
type="button"
class="flex h-10 w-10 items-center justify-center rounded-[16px] text-gray-10 transition-colors hover:bg-dls-surface hover:text-dls-text"
onClick={mobile() ? closeMobile : props.onToggleExpanded}
title={mobile() ? t("workspace_sidebar.close_sidebar") : props.expanded ? t("workspace_sidebar.collapse_sidebar") : t("workspace_sidebar.expand_sidebar")}
aria-label={mobile() ? t("workspace_sidebar.close_sidebar") : props.expanded ? t("workspace_sidebar.collapse_sidebar") : t("workspace_sidebar.expand_sidebar")}
>
<Show
when={mobile()}
fallback={<Show when={props.expanded} fallback={<ChevronLeft size={18} />}><ChevronRight size={18} /></Show>}
>
<X size={18} />
</Show>
</button>
</div>
<div class={`flex-1 overflow-y-auto ${props.expanded ? "space-y-5 pt-1" : "space-y-3 pt-1"}`}>
<div class="mb-2 space-y-1">
{sidebarButton(
t("workspace_sidebar.automations"),
<History size={18} />,
showSelection() && props.settingsTab === "automations",
props.onOpenAutomations,
)}
{sidebarButton(
t("dashboard.skills"),
<Zap size={18} />,
showSelection() && props.settingsTab === "skills",
props.onOpenSkills,
)}
{sidebarButton(
t("workspace_sidebar.extensions"),
<Box size={18} />,
showSelection() && props.settingsTab === "extensions",
props.onOpenExtensions,
)}
{sidebarButton(
t("workspace_sidebar.messaging"),
<MessageCircle size={18} />,
showSelection() && props.settingsTab === "messaging",
props.onOpenMessaging,
)}
<Show when={props.developerMode}>
{sidebarButton(
t("settings.advanced_title"),
<SlidersHorizontal size={18} />,
showSelection() && props.settingsTab === "advanced",
props.onOpenAdvanced,
)}
</Show>
</div>
<Show when={props.expanded && props.activeWorkspaceType === "remote"}>
<div class="rounded-[20px] border border-dls-border bg-dls-surface p-3 shadow-[var(--dls-card-shadow)]">
<InboxPanel
id={props.inboxId}
client={props.openworkServerClient}
workspaceId={props.runtimeWorkspaceId}
onToast={props.onInboxToast}
/>
</div>
</Show>
</div>
<div class={`pt-3 ${props.expanded ? "mt-3 border-t border-dls-border/70" : "mt-2"}`}>
{sidebarButton(
t("dashboard.settings"),
<Settings size={18} />,
showSelection() && props.settingsTab === "general",
props.onOpenSettings,
)}
</div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { createContext, useContext, type ParentProps } from "solid-js";
import { createContext, type ParentProps } from "solid-js";
import type { OpenworkServerStore } from "./openwork-server-store";
@@ -11,11 +11,3 @@ export function OpenworkServerProvider(props: ParentProps<{ store: OpenworkServe
</OpenworkServerContext.Provider>
);
}
export function useOpenworkServer() {
const context = useContext(OpenworkServerContext);
if (!context) {
throw new Error("useOpenworkServer must be used within an OpenworkServerProvider");
}
return context;
}

View File

@@ -1,393 +0,0 @@
/**
* OpenWork server connection state.
*
* Encapsulates all signals, effects, and helpers related to the OpenWork
* server lifecycle: connection status, capabilities polling, host info,
* diagnostics, audit entries, and the server client instance.
*/
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
import {
createOpenworkServerClient,
hydrateOpenworkServerSettingsFromEnv,
normalizeOpenworkServerUrl,
readOpenworkServerSettings,
OpenworkServerError,
type OpenworkAuditEntry,
type OpenworkServerCapabilities,
type OpenworkServerClient,
type OpenworkServerDiagnostics,
type OpenworkServerSettings,
type OpenworkServerStatus,
} from "../lib/openwork-server";
import {
openworkServerInfo,
orchestratorStatus,
opencodeRouterInfo,
type OrchestratorStatus,
type OpenworkServerInfo,
type OpenCodeRouterInfo,
} from "../lib/tauri";
import { isTauriRuntime } from "../utils";
import type { StartupPreference } from "../types";
export type OpenworkServerStore = ReturnType<typeof createOpenworkServerStore>;
export function createOpenworkServerStore(options: {
startupPreference: () => StartupPreference | null;
developerMode: () => boolean;
documentVisible: () => boolean;
refreshEngine?: () => Promise<void>;
}) {
const [settings, setSettings] = createSignal<OpenworkServerSettings>({});
const [url, setUrl] = createSignal("");
const [status, setStatus] = createSignal<OpenworkServerStatus>("disconnected");
const [capabilities, setCapabilities] = createSignal<OpenworkServerCapabilities | null>(null);
const [checkedAt, setCheckedAt] = createSignal<number | null>(null);
const [workspaceId, setWorkspaceId] = createSignal<string | null>(null);
const [hostInfo, setHostInfo] = createSignal<OpenworkServerInfo | null>(null);
const [diagnostics, setDiagnostics] = createSignal<OpenworkServerDiagnostics | null>(null);
const [reconnectBusy, setReconnectBusy] = createSignal(false);
const [routerInfo, setRouterInfo] = createSignal<OpenCodeRouterInfo | null>(null);
const [orchStatus, setOrchStatus] = createSignal<OrchestratorStatus | null>(null);
const [auditEntries, setAuditEntries] = createSignal<OpenworkAuditEntry[]>([]);
const [auditStatus, setAuditStatus] = createSignal<"idle" | "loading" | "error">("idle");
const [auditError, setAuditError] = createSignal<string | null>(null);
const [devtoolsWorkspaceId, setDevtoolsWorkspaceId] = createSignal<string | null>(null);
// -- Derived --
const baseUrl = createMemo(() => {
const pref = options.startupPreference();
const info = hostInfo();
const settingsUrl = normalizeOpenworkServerUrl(settings().urlOverride ?? "") ?? "";
if (pref === "local") return info?.baseUrl ?? "";
if (pref === "server") return settingsUrl;
return info?.baseUrl ?? settingsUrl;
});
const auth = createMemo(() => {
const pref = options.startupPreference();
const info = hostInfo();
const settingsToken = settings().token?.trim() ?? "";
const clientToken = info?.clientToken?.trim() ?? "";
const hostToken = info?.hostToken?.trim() ?? "";
if (pref === "local") {
return { token: clientToken || undefined, hostToken: hostToken || undefined };
}
if (pref === "server") {
return { token: settingsToken || undefined, hostToken: undefined };
}
if (info?.baseUrl) {
return { token: clientToken || undefined, hostToken: hostToken || undefined };
}
return { token: settingsToken || undefined, hostToken: undefined };
});
const client = createMemo(() => {
const base = baseUrl().trim();
if (!base) return null;
const a = auth();
return createOpenworkServerClient({ baseUrl: base, token: a.token, hostToken: a.hostToken });
});
const devtoolsClient = createMemo(() => client());
// -- Effects --
// Hydrate settings from env/localStorage on mount
createEffect(() => {
if (typeof window === "undefined") return;
hydrateOpenworkServerSettingsFromEnv();
setSettings(readOpenworkServerSettings());
});
// Derive URL from preference + host info
createEffect(() => {
const pref = options.startupPreference();
const info = hostInfo();
const hostUrl = info?.connectUrl ?? info?.lanUrl ?? info?.mdnsUrl ?? info?.baseUrl ?? "";
const settingsUrl = normalizeOpenworkServerUrl(settings().urlOverride ?? "") ?? "";
if (pref === "local") {
setUrl(hostUrl);
return;
}
if (pref === "server") {
setUrl(settingsUrl);
return;
}
setUrl(hostUrl || settingsUrl);
});
// Poll server health + capabilities
createEffect(() => {
if (typeof window === "undefined") return;
if (!options.documentVisible()) return;
const serverUrl = baseUrl().trim();
const a = auth();
const token = a.token;
const ht = a.hostToken;
if (!serverUrl) {
setStatus("disconnected");
setCapabilities(null);
setCheckedAt(Date.now());
return;
}
let active = true;
let busy = false;
let timeoutId: number | undefined;
let delayMs = 10_000;
const scheduleNext = () => {
if (!active) return;
timeoutId = window.setTimeout(run, delayMs);
};
const run = async () => {
if (busy) return;
busy = true;
try {
const result = await checkServer(serverUrl, token, ht);
if (!active) return;
setStatus(result.status);
setCapabilities(result.capabilities);
delayMs =
result.status === "connected" || result.status === "limited"
? 10_000
: Math.min(delayMs * 2, 60_000);
} catch {
delayMs = Math.min(delayMs * 2, 60_000);
} finally {
if (!active) return;
setCheckedAt(Date.now());
busy = false;
scheduleNext();
}
};
run();
onCleanup(() => {
active = false;
if (timeoutId) window.clearTimeout(timeoutId);
});
});
// Poll host info (Tauri only)
createEffect(() => {
if (!isTauriRuntime()) return;
if (!options.documentVisible()) return;
let active = true;
const run = async () => {
try {
const info = await openworkServerInfo();
if (active) setHostInfo(info);
} catch {
if (active) setHostInfo(null);
}
};
run();
const interval = window.setInterval(run, 10_000);
onCleanup(() => {
active = false;
window.clearInterval(interval);
});
});
// Poll diagnostics (developer mode only)
createEffect(() => {
if (typeof window === "undefined") return;
if (!options.documentVisible()) return;
if (!options.developerMode()) {
setDiagnostics(null);
return;
}
const c = client();
if (!c || status() === "disconnected") {
setDiagnostics(null);
return;
}
let active = true;
let busy = false;
const run = async () => {
if (busy) return;
busy = true;
try {
const s = await c.status();
if (active) setDiagnostics(s);
} catch {
if (active) setDiagnostics(null);
} finally {
busy = false;
}
};
run();
const interval = window.setInterval(run, 10_000);
onCleanup(() => {
active = false;
window.clearInterval(interval);
});
});
// Poll engine (developer mode, Tauri only)
createEffect(() => {
if (!isTauriRuntime()) return;
if (!options.developerMode()) return;
if (!options.documentVisible()) return;
let busy = false;
const run = async () => {
if (busy) return;
busy = true;
try {
await options.refreshEngine?.();
} finally {
busy = false;
}
};
run();
const interval = window.setInterval(run, 10_000);
onCleanup(() => {
window.clearInterval(interval);
});
});
// Poll OpenCode Router info (developer mode, Tauri only)
createEffect(() => {
if (!isTauriRuntime()) return;
if (!options.developerMode()) {
setRouterInfo(null);
return;
}
if (!options.documentVisible()) return;
let active = true;
const run = async () => {
try {
const info = await opencodeRouterInfo();
if (active) setRouterInfo(info);
} catch {
if (active) setRouterInfo(null);
}
};
run();
const interval = window.setInterval(run, 10_000);
onCleanup(() => {
active = false;
window.clearInterval(interval);
});
});
// Poll orchestrator status (developer mode, Tauri only)
createEffect(() => {
if (!isTauriRuntime()) return;
if (!options.developerMode()) {
setOrchStatus(null);
return;
}
if (!options.documentVisible()) return;
let active = true;
const run = async () => {
try {
const s = await orchestratorStatus();
if (active) setOrchStatus(s);
} catch {
if (active) setOrchStatus(null);
}
};
run();
const interval = window.setInterval(run, 10_000);
onCleanup(() => {
active = false;
window.clearInterval(interval);
});
});
return {
// Signals (read)
settings,
url,
status,
capabilities,
checkedAt,
workspaceId,
hostInfo,
diagnostics,
reconnectBusy,
routerInfo,
orchestratorStatus: orchStatus,
auditEntries,
auditStatus,
auditError,
devtoolsWorkspaceId,
// Derived
baseUrl,
auth,
client,
devtoolsClient,
// Setters (for external use)
setSettings,
setUrl,
setStatus,
setCapabilities,
setCheckedAt,
setWorkspaceId,
setHostInfo,
setDiagnostics,
setReconnectBusy,
setRouterInfo,
setOrchestratorStatus: setOrchStatus,
setAuditEntries,
setAuditStatus,
setAuditError,
setDevtoolsWorkspaceId,
};
}
// -- Helpers --
async function checkServer(
url: string,
token?: string,
hostToken?: string,
): Promise<{ status: OpenworkServerStatus; capabilities: OpenworkServerCapabilities | null }> {
const c = createOpenworkServerClient({ baseUrl: url, token, hostToken });
try {
await c.health();
} catch (error) {
if (error instanceof OpenworkServerError && (error.status === 401 || error.status === 403)) {
return { status: "limited", capabilities: null };
}
return { status: "disconnected", capabilities: null };
}
if (!token) {
return { status: "limited", capabilities: null };
}
try {
const caps = await c.capabilities();
return { status: "connected", capabilities: caps };
} catch (error) {
if (error instanceof OpenworkServerError && (error.status === 401 || error.status === 403)) {
return { status: "limited", capabilities: null };
}
return { status: "disconnected", capabilities: null };
}
}

View File

@@ -1,33 +0,0 @@
import { createContext, useContext, type ParentProps } from "solid-js";
import type { SetStoreFunction, Store } from "solid-js/store";
import { useGlobalSync, type WorkspaceState } from "./global-sync";
type SyncContextValue = {
directory: string;
data: Store<WorkspaceState>;
set: SetStoreFunction<WorkspaceState>;
};
const SyncContext = createContext<SyncContextValue | undefined>(undefined);
export function SyncProvider(props: ParentProps & { directory: string }) {
const globalSync = useGlobalSync();
const [store, setStore] = globalSync.child(props.directory);
const value: SyncContextValue = {
directory: props.directory,
data: store,
set: setStore,
};
return <SyncContext.Provider value={value}>{props.children}</SyncContext.Provider>;
}
export function useSync() {
const context = useContext(SyncContext);
if (!context) {
throw new Error("Sync context is missing");
}
return context;
}

View File

@@ -690,30 +690,6 @@ export function clearOpenworkServerSettings() {
}
}
export function deriveOpenworkServerUrl(
opencodeBaseUrl: string,
settings?: OpenworkServerSettings,
) {
const override = settings?.urlOverride?.trim();
if (override) {
return normalizeOpenworkServerUrl(override);
}
const base = opencodeBaseUrl.trim();
if (!base) return null;
try {
const url = new URL(base);
const port = settings?.portOverride ?? DEFAULT_OPENWORK_SERVER_PORT;
url.port = String(port);
url.pathname = "";
url.search = "";
url.hash = "";
return url.origin;
} catch {
return null;
}
}
export class OpenworkServerError extends Error {
status: number;
code: string;

View File

@@ -1,71 +0,0 @@
/**
* Safe execution utilities for error handling.
*
* Replaces bare `catch {}` blocks with structured handling that
* at least logs errors in development, while keeping the same
* "swallow and continue" behavior in production.
*/
const isDev = typeof import.meta !== "undefined" && import.meta.env?.DEV;
/**
* Run an async function, returning the result or a fallback on error.
* Logs errors in development mode.
*
* @example
* const sessions = await safeAsync(() => loadSessions(), []);
*/
export async function safeAsync<T>(
fn: () => Promise<T>,
fallback: T,
label?: string,
): Promise<T> {
try {
return await fn();
} catch (error) {
if (isDev) {
console.warn(`[safeAsync]${label ? ` ${label}:` : ""}`, error);
}
return fallback;
}
}
/**
* Run a synchronous function, returning the result or a fallback on error.
* Logs errors in development mode.
*
* @example
* const parsed = safeSync(() => JSON.parse(raw), null);
*/
export function safeSync<T>(
fn: () => T,
fallback: T,
label?: string,
): T {
try {
return fn();
} catch (error) {
if (isDev) {
console.warn(`[safeSync]${label ? ` ${label}:` : ""}`, error);
}
return fallback;
}
}
/**
* Fire-and-forget an async operation. Logs errors in development.
* Use for cleanup, best-effort writes, etc.
*
* @example
* fireAndForget(() => saveDraft(content), "save draft");
*/
export function fireAndForget(
fn: () => Promise<unknown>,
label?: string,
): void {
fn().catch((error) => {
if (isDev) {
console.warn(`[fireAndForget]${label ? ` ${label}:` : ""}`, error);
}
});
}

View File

@@ -3510,12 +3510,6 @@ export default function SessionView(props: SessionViewProps) {
onSendFeedback={openFeedback}
showSettingsButton={true}
onOpenSettings={props.toggleSettings}
onOpenMessaging={() => {
props.setSettingsTab("messaging");
props.setView("settings");
}}
onOpenProviders={openProviderAuth}
onOpenMcp={openMcp}
providerConnectedIds={props.providerConnectedIds}
statusLabel={statusBarCopy().label}
statusDetail={statusBarCopy().detail}
@@ -3834,7 +3828,6 @@ export default function SessionView(props: SessionViewProps) {
open={Boolean(props.activeQuestion)}
questions={props.activeQuestion?.questions ?? []}
busy={props.questionReplyBusy}
onClose={() => {}}
onReply={(answers) => {
if (props.activeQuestion) {
props.respondQuestion(props.activeQuestion.id, answers);

View File

@@ -217,31 +217,6 @@ const DISCORD_INVITE_URL = "https://discord.gg/VEhNQXxYMB";
const BUG_REPORT_URL =
"https://github.com/different-ai/openwork/issues/new?template=bug.yml";
// OpenCodeRouter Settings Component
//
// Messaging identities + routing are managed in the Identities tab.
export function OpenCodeRouterSettings(_props: {
busy: boolean;
openworkServerStatus: OpenworkServerStatus;
openworkServerUrl: string;
openworkServerSettings: OpenworkServerSettings;
runtimeWorkspaceId: string | null;
openworkServerHostInfo: OpenworkServerInfo | null;
developerMode: boolean;
}) {
return (
<div class="bg-gray-2/30 border border-gray-6/50 rounded-2xl p-5 space-y-2">
<div class="flex items-center gap-2">
<MessageCircle size={16} class="text-gray-11" />
<div class="text-sm font-medium text-gray-12">{t("settings.messaging_section_title")}</div>
</div>
<div class="text-xs text-gray-10">
{t("settings.messaging_section_desc")}
</div>
</div>
);
}
export default function SettingsView(props: SettingsViewProps) {
const modelControls = useModelControls();
const { showThinking, toggleShowThinking } = useSessionDisplayPreferences();
@@ -807,8 +782,6 @@ export default function SettingsView(props: SettingsViewProps) {
switch (tab) {
case "den":
return translate("settings.tab_cloud");
case "model":
return translate("settings.tab_model");
case "automations":
return translate("settings.tab_automations");
case "skills":
@@ -1372,8 +1345,6 @@ export default function SettingsView(props: SettingsViewProps) {
switch (tab) {
case "den":
return translate("settings.tab_description_den");
case "model":
return translate("settings.tab_description_model");
case "automations":
return translate("settings.tab_description_automations");
case "skills":

View File

@@ -431,16 +431,8 @@ export default function SettingsShell(props: SettingsShellProps) {
props.setSettingsTab(tab);
};
const openMessaging = () => {
openSettings("messaging");
};
const openExtensions = () => {
openSettings("extensions");
};
const openAdvanced = () => {
openSettings(props.developerMode ? "advanced" : "messaging");
openSettings("advanced");
};
const revealWorkspaceInFinder = async (workspaceId: string) => {
@@ -1348,62 +1340,8 @@ export default function SettingsShell(props: SettingsShellProps) {
showSettingsButton={true}
onSendFeedback={openFeedback}
onOpenSettings={props.toggleSettings}
onOpenMessaging={openMessaging}
onOpenProviders={() => props.openProviderAuthModal()}
onOpenMcp={openExtensions}
providerConnectedIds={props.providerConnectedIds}
/>
<nav class="hidden border-t border-dls-border bg-dls-surface">
<div class={`mx-auto max-w-5xl px-4 py-3 grid gap-2 ${props.developerMode ? "grid-cols-5" : "grid-cols-4"}`}>
<button
class={`flex flex-col items-center gap-1 text-xs ${
props.settingsTab === "automations" ? "text-gray-12" : "text-gray-10"
}`}
onClick={() => openSettings("automations")}
>
<History size={18} />
{t("scheduled.title")}
</button>
<button
class={`flex flex-col items-center gap-1 text-xs ${
props.settingsTab === "skills" ? "text-gray-12" : "text-gray-10"
}`}
onClick={() => openSettings("skills")}
>
<Zap size={18} />
{t("dashboard.skills")}
</button>
<button
class={`flex flex-col items-center gap-1 text-xs ${
props.settingsTab === "extensions" ? "text-gray-12" : "text-gray-10"
}`}
onClick={() => openSettings("extensions")}
>
<Box size={18} />
{t("extensions.title")}
</button>
<button
class={`flex flex-col items-center gap-1 text-xs ${
props.settingsTab === "messaging" ? "text-gray-12" : "text-gray-10"
}`}
onClick={() => openSettings("messaging")}
>
<MessageCircle size={18} />
{t("dashboard.nav_ids")}
</button>
<Show when={props.developerMode}>
<button
class={`flex flex-col items-center gap-1 text-xs ${
props.settingsTab === "advanced" ? "text-gray-12" : "text-gray-10"
}`}
onClick={openAdvanced}
>
<SlidersHorizontal size={18} />
{t("settings.tab_advanced")}
</button>
</Show>
</div>
</nav>
</main>
</div>

View File

@@ -1 +0,0 @@
export { createExtensionsStore } from "../context/extensions";

View File

@@ -1 +0,0 @@
export { createSessionStore } from "../context/session";

View File

@@ -1 +0,0 @@
export { createSystemState } from "../system-state";

View File

@@ -161,7 +161,6 @@ export type OnboardingStep = "welcome" | "local" | "server" | "connecting";
export type SettingsTab =
| "general"
| "den"
| "model"
| "automations"
| "skills"
| "extensions"

View File

@@ -274,21 +274,6 @@ export function formatRelativeTime(timestampMs: number) {
return new Date(timestampMs).toLocaleDateString();
}
export function commandPathFromWorkspaceRoot(workspaceRoot: string, commandName: string) {
const root = workspaceRoot.trim().replace(/\/+$/, "");
const name = commandName.trim().replace(/^\/+/, "");
if (!root || !name) return null;
return `${root}/.opencode/commands/${name}.md`;
}
export function safeParseJson<T>(raw: string): T | null {
try {
return JSON.parse(raw) as T;
} catch {
return null;
}
}
export function addOpencodeCacheHint(message: string) {
const lower = message.toLowerCase();
const cacheSignals = [
@@ -439,64 +424,6 @@ export function upsertSession(list: Session[], next: Session) {
return copy;
}
export function upsertMessage(list: MessageWithParts[], nextInfo: MessageInfo) {
const idx = list.findIndex((m) => m.info.id === nextInfo.id);
if (idx === -1) {
return list.concat({ info: nextInfo, parts: [] });
}
const copy = list.slice();
copy[idx] = { ...copy[idx], info: nextInfo };
return copy;
}
export function upsertPart(list: MessageWithParts[], nextPart: Part) {
const msgIdx = list.findIndex((m) => m.info.id === nextPart.messageID);
if (msgIdx === -1) {
// avoids missing streaming events before message.updated
const placeholder: PlaceholderAssistantMessage = {
id: nextPart.messageID,
sessionID: nextPart.sessionID,
role: "assistant",
time: { created: Date.now() },
parentID: "",
modelID: "",
providerID: "",
mode: "",
agent: "",
path: { cwd: "", root: "" },
cost: 0,
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
};
return list.concat({ info: placeholder, parts: [nextPart] });
}
const copy = list.slice();
const msg = copy[msgIdx];
const parts = msg.parts.slice();
const partIdx = parts.findIndex((p) => p.id === nextPart.id);
if (partIdx === -1) {
parts.push(nextPart);
} else {
parts[partIdx] = nextPart;
}
copy[msgIdx] = { ...msg, parts };
return copy;
}
export function removePart(list: MessageWithParts[], messageID: string, partID: string) {
const msgIdx = list.findIndex((m) => m.info.id === messageID);
if (msgIdx === -1) return list;
const copy = list.slice();
const msg = copy[msgIdx];
copy[msgIdx] = { ...msg, parts: msg.parts.filter((p) => p.id !== partID) };
return copy;
}
export function normalizeSessionStatus(status: unknown) {
if (!status || typeof status !== "object") return "idle";
const record = status as Record<string, unknown>;

View File

@@ -99,22 +99,6 @@ export const Persist = {
},
};
export function removePersisted(target: { storage?: string; key: string }) {
const platform = usePlatform();
const isDesktop = platform.platform === "desktop" && !!platform.storage;
if (isDesktop) {
return platform.storage?.(target.storage)?.removeItem(target.key);
}
if (!target.storage) {
localStorage.removeItem(target.key);
return;
}
localStorageWithPrefix(target.storage).removeItem(target.key);
}
export function persisted<T>(
target: string | PersistTarget,
store: [Store<T>, SetStoreFunction<T>],

View File

@@ -78,11 +78,6 @@ export function loadPluginsFromConfig(
}
}
export function parsePluginsFromConfig(config: OpencodeConfigFile | null) {
if (!config?.content) return [] as string[];
return parsePluginListFromContent(config.content);
}
export function parsePluginListFromContent(content: string) {
try {
const parsed = parse(content) as Record<string, unknown> | undefined;

View File

@@ -8,7 +8,6 @@ import ModelPickerModal from "../../app/src/app/components/model-picker-modal";
import ShareWorkspaceModal from "../../app/src/app/components/share-workspace-modal";
import StatusBar from "../../app/src/app/components/status-bar";
import Composer from "../../app/src/app/components/session/composer";
import InboxPanel from "../../app/src/app/components/session/inbox-panel";
import MessageList from "../../app/src/app/components/session/message-list";
import WorkspaceSessionList from "../../app/src/app/components/session/workspace-session-list";
import { MCP_QUICK_CONNECT, SUGGESTED_PLUGINS } from "../../app/src/app/constants";
@@ -1536,7 +1535,6 @@ export default function NewLayoutApp() {
mcpServers={storyMcpServers()}
mcpStatus="Story-book MCP sandbox ready."
mcpLastUpdatedAt={now}
mcpStatuses={storyMcpStatuses()}
mcpConnectingName={null}
selectedMcp={selectedMcp()}
setSelectedMcp={setSelectedMcp}
@@ -1583,13 +1581,8 @@ export default function NewLayoutApp() {
developerMode
/>
<Show when={selectedWorkspaceId() === remoteWorkspace.id}>
<div class="rounded-[20px] border border-dls-border bg-dls-surface p-3 shadow-[var(--dls-card-shadow)]">
<InboxPanel
id="settings-inbox"
client={mockOpenworkServerClient}
workspaceId={selectedWorkspaceId()}
onToast={(message) => setComposerToast(message)}
/>
<div class="rounded-[20px] border border-dls-border bg-dls-surface p-3 shadow-[var(--dls-card-shadow)] text-sm text-dls-secondary">
Remote inbox preview has been removed from the app shell.
</div>
</Show>
<Show when={selectedWorkspaceId() !== remoteWorkspace.id}>
@@ -1730,20 +1723,7 @@ export default function NewLayoutApp() {
setSettingsTab("general");
setShowingSettings((prev) => !prev);
}}
onOpenMessaging={() => {
setSettingsTab("messaging");
setShowingSettings(true);
}}
onOpenProviders={() => {
setSettingsTab("general");
setShowingSettings(true);
}}
onOpenMcp={() => {
setSettingsTab("extensions");
setShowingSettings(true);
}}
providerConnectedIds={["anthropic", "openai"]}
mcpStatuses={storyMcpStatuses()}
statusLabel="Session Ready"
/>
</main>

View File

@@ -21,7 +21,6 @@ import ModelPickerModal from "../../app/src/app/components/model-picker-modal";
import ShareWorkspaceModal from "../../app/src/app/components/share-workspace-modal";
import StatusBar from "../../app/src/app/components/status-bar";
import Composer from "../../app/src/app/components/session/composer";
import InboxPanel from "../../app/src/app/components/session/inbox-panel";
import MessageList from "../../app/src/app/components/session/message-list";
import WorkspaceSessionList from "../../app/src/app/components/session/workspace-session-list";
import { createWorkspaceShellLayout } from "../../app/src/app/lib/workspace-shell-layout";
@@ -858,13 +857,8 @@ export default function StoryBookApp() {
</div>
<Show when={expanded && selectedWorkspaceId() === remoteWorkspace.id}>
<div class="rounded-[20px] border border-dls-border bg-dls-surface p-3 shadow-[var(--dls-card-shadow)]">
<InboxPanel
id="sidebar-inbox"
client={null}
workspaceId={null}
onToast={(message) => setComposerToast(message)}
/>
<div class="rounded-[20px] border border-dls-border bg-dls-surface p-3 shadow-[var(--dls-card-shadow)] text-sm text-dls-secondary">
Remote inbox preview has been removed from the app shell.
</div>
</Show>
</div>
@@ -1105,11 +1099,7 @@ export default function StoryBookApp() {
if (!rightSidebarExpanded()) toggleRightSidebar();
setRightRailNav("advanced");
}}
onOpenMessaging={() => undefined}
onOpenProviders={() => undefined}
onOpenMcp={() => undefined}
providerConnectedIds={["anthropic", "openai"]}
mcpStatuses={mcpStatuses}
statusLabel="Session Ready"
/>
</main>