mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
Add router-based navigation and refresh workspace switch overlay (#244)
* feat: enable URL-based navigation for web QA and restore workspace switch branding * fix: use solid router Route list for compatibility * fix: mount app inside root route for router context * fix: patch solid router exports * fix: render views via switch while syncing URL * push * fix: allow remote workspace creation in web
This commit is contained in:
9
opencode.jsonc
Normal file
9
opencode.jsonc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"chrome-devtools": {
|
||||
"type": "local",
|
||||
"command": ["npx", "-y", "chrome-devtools-mcp@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,10 @@
|
||||
"better-sqlite3",
|
||||
"esbuild",
|
||||
"protobufjs"
|
||||
]
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"@solidjs/router@0.15.4": "patches/@solidjs__router@0.15.4.patch"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.27.0"
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@solid-primitives/event-bus": "^1.1.2",
|
||||
"@solid-primitives/storage": "^4.3.3",
|
||||
"@solidjs/router": "^0.15.4",
|
||||
"@tauri-apps/api": "^2.0.0",
|
||||
"@tauri-apps/plugin-dialog": "~2.6.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.3",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
Match,
|
||||
Show,
|
||||
Switch,
|
||||
createEffect,
|
||||
createMemo,
|
||||
@@ -10,6 +9,8 @@ import {
|
||||
untrack,
|
||||
} from "solid-js";
|
||||
|
||||
import { useLocation, useNavigate } from "@solidjs/router";
|
||||
|
||||
import type { Agent, Provider } from "@opencode-ai/sdk/v2/client";
|
||||
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
@@ -105,35 +106,73 @@ import {
|
||||
export default function App() {
|
||||
type ProviderAuthMethod = { type: "oauth" | "api"; label: string };
|
||||
|
||||
const initialView: View = (() => {
|
||||
if (typeof window === "undefined") return "onboarding";
|
||||
try {
|
||||
return window.localStorage.getItem("openwork.onboardingComplete") === "1"
|
||||
? "dashboard"
|
||||
: "onboarding";
|
||||
} catch {
|
||||
return "onboarding";
|
||||
}
|
||||
})();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [view, _setView] = createSignal<View>(initialView);
|
||||
const [creatingSession, setCreatingSession] = createSignal(false);
|
||||
const [sessionViewLockUntil, setSessionViewLockUntil] = createSignal(0);
|
||||
const setView = (next: View) => {
|
||||
// Guard: Don't allow view to change to dashboard while creating session.
|
||||
const currentView = createMemo<View>(() => {
|
||||
const path = location.pathname.toLowerCase();
|
||||
if (path.startsWith("/onboarding")) return "onboarding";
|
||||
if (path.startsWith("/session")) return "session";
|
||||
return "dashboard";
|
||||
});
|
||||
|
||||
const [tab, setTabState] = createSignal<DashboardTab>("home");
|
||||
|
||||
const goToDashboard = (nextTab: DashboardTab, options?: { replace?: boolean }) => {
|
||||
setTabState(nextTab);
|
||||
navigate(`/dashboard/${nextTab}`, options);
|
||||
};
|
||||
|
||||
const setTab = (nextTab: DashboardTab) => {
|
||||
if (currentView() === "dashboard") {
|
||||
goToDashboard(nextTab);
|
||||
return;
|
||||
}
|
||||
setTabState(nextTab);
|
||||
};
|
||||
|
||||
const setView = (next: View, sessionId?: string) => {
|
||||
if (next === "dashboard" && creatingSession()) {
|
||||
return;
|
||||
}
|
||||
if (next === "dashboard" && Date.now() < sessionViewLockUntil()) {
|
||||
return;
|
||||
}
|
||||
_setView(next);
|
||||
if (next === "onboarding") {
|
||||
navigate("/onboarding");
|
||||
return;
|
||||
}
|
||||
if (next === "session") {
|
||||
if (sessionId) {
|
||||
goToSession(sessionId);
|
||||
return;
|
||||
}
|
||||
const fallback = activeSessionId();
|
||||
if (fallback) {
|
||||
goToSession(fallback);
|
||||
return;
|
||||
}
|
||||
navigate("/session");
|
||||
return;
|
||||
}
|
||||
goToDashboard(tab());
|
||||
};
|
||||
|
||||
const goToSession = (sessionId: string, options?: { replace?: boolean }) => {
|
||||
const trimmed = sessionId.trim();
|
||||
if (!trimmed) {
|
||||
navigate("/session", options);
|
||||
return;
|
||||
}
|
||||
navigate(`/session/${trimmed}`, options);
|
||||
};
|
||||
|
||||
const [mode, setMode] = createSignal<Mode | null>(null);
|
||||
const [onboardingStep, setOnboardingStep] =
|
||||
createSignal<OnboardingStep>("mode");
|
||||
const [rememberModeChoice, setRememberModeChoice] = createSignal(false);
|
||||
const [tab, setTab] = createSignal<DashboardTab>("home");
|
||||
const [themeMode, setThemeMode] = createSignal<ThemeMode>(getInitialThemeMode());
|
||||
|
||||
const [engineSource, setEngineSource] = createSignal<"path" | "sidecar">(
|
||||
@@ -900,7 +939,7 @@ export default function App() {
|
||||
createEffect(() => {
|
||||
// If we lose the client (disconnect / stop engine), don't strand the user
|
||||
// in a session view that can't operate.
|
||||
if (view() !== "session") return;
|
||||
if (currentView() !== "session") return;
|
||||
if (isDemoMode()) return;
|
||||
if (creatingSession()) return;
|
||||
if (client()) return;
|
||||
@@ -1065,7 +1104,7 @@ export default function App() {
|
||||
setDefaultModel(next);
|
||||
setModelPickerOpen(false);
|
||||
|
||||
if (typeof window !== "undefined" && view() === "session") {
|
||||
if (typeof window !== "undefined" && currentView() === "session") {
|
||||
requestAnimationFrame(() => {
|
||||
window.dispatchEvent(new CustomEvent("openwork:focusPrompt"));
|
||||
});
|
||||
@@ -1344,7 +1383,12 @@ export default function App() {
|
||||
console.log("[DEBUG] current baseUrl:", baseUrl());
|
||||
console.log("[DEBUG] engine info:", engine());
|
||||
if (isDemoMode()) {
|
||||
setView("session");
|
||||
const demoId = activeSessionId();
|
||||
if (demoId) {
|
||||
goToSession(demoId);
|
||||
} else {
|
||||
setView("session");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1452,7 +1496,7 @@ export default function App() {
|
||||
// Now switch view AFTER session is selected
|
||||
mark("view set to session");
|
||||
// setSessionViewLockUntil(Date.now() + 1200);
|
||||
setView("session");
|
||||
goToSession(session.id);
|
||||
} catch (e) {
|
||||
mark("error caught", e);
|
||||
const message = e instanceof Error ? e.message : t("app.unknown_error", currentLocale());
|
||||
@@ -2034,8 +2078,8 @@ export default function App() {
|
||||
workspaceStore.setEngineInstallLogs(notes || null);
|
||||
},
|
||||
onOpenSettings: () => {
|
||||
setView("dashboard");
|
||||
setTab("settings");
|
||||
setView("dashboard");
|
||||
},
|
||||
themeMode: themeMode(),
|
||||
setThemeMode,
|
||||
@@ -2044,7 +2088,7 @@ export default function App() {
|
||||
const dashboardProps = () => ({
|
||||
tab: tab(),
|
||||
setTab,
|
||||
view: view(),
|
||||
view: currentView(),
|
||||
setView,
|
||||
mode: mode(),
|
||||
baseUrl: baseUrl(),
|
||||
@@ -2186,87 +2230,174 @@ export default function App() {
|
||||
setLanguage: setLocale,
|
||||
});
|
||||
|
||||
const sessionProps = () => ({
|
||||
selectedSessionId: activeSessionId(),
|
||||
setView,
|
||||
setTab,
|
||||
activeWorkspaceDisplay: activeWorkspaceDisplay(),
|
||||
setWorkspaceSearch: workspaceStore.setWorkspaceSearch,
|
||||
setWorkspacePickerOpen: workspaceStore.setWorkspacePickerOpen,
|
||||
headerStatus: headerStatus(),
|
||||
busyHint: busyHint(),
|
||||
selectedSessionModelLabel: selectedSessionModelLabel(),
|
||||
openSessionModelPicker: openSessionModelPicker,
|
||||
activePlugins: sidebarPluginList(),
|
||||
activePluginStatus: sidebarPluginStatus(),
|
||||
createSessionAndOpen: createSessionAndOpen,
|
||||
sendPromptAsync: sendPrompt,
|
||||
newTaskDisabled: newTaskDisabled(),
|
||||
sessions: activeSessions().map((session) => ({
|
||||
id: session.id,
|
||||
title: session.title,
|
||||
slug: session.slug,
|
||||
})),
|
||||
selectSession: isDemoMode() ? selectDemoSession : selectSession,
|
||||
messages: activeMessages(),
|
||||
todos: activeTodos(),
|
||||
busyLabel: busyLabel(),
|
||||
developerMode: developerMode(),
|
||||
showThinking: showThinking(),
|
||||
groupMessageParts,
|
||||
summarizeStep,
|
||||
expandedStepIds: expandedStepIds(),
|
||||
setExpandedStepIds: setExpandedStepIds,
|
||||
expandedSidebarSections: expandedSidebarSections(),
|
||||
setExpandedSidebarSections: setExpandedSidebarSections,
|
||||
artifacts: activeArtifacts(),
|
||||
workingFiles: activeWorkingFiles(),
|
||||
authorizedDirs: activeAuthorizedDirs(),
|
||||
busy: busy(),
|
||||
prompt: prompt(),
|
||||
setPrompt: setPrompt,
|
||||
sendPrompt: sendPrompt,
|
||||
activePermission: activePermissionMemo(),
|
||||
permissionReplyBusy: permissionReplyBusy(),
|
||||
respondPermission: respondPermission,
|
||||
respondPermissionAndRemember: respondPermissionAndRemember,
|
||||
safeStringify: safeStringify,
|
||||
showTryNotionPrompt: tryNotionPromptVisible() && notionIsActive(),
|
||||
openConnect: openConnectFlow,
|
||||
startProviderAuth: startProviderAuth,
|
||||
openProviderAuthModal: openProviderAuthModal,
|
||||
closeProviderAuthModal: closeProviderAuthModal,
|
||||
providerAuthModalOpen: providerAuthModalOpen(),
|
||||
providerAuthBusy: providerAuthBusy(),
|
||||
providerAuthError: providerAuthError(),
|
||||
providerAuthMethods: providerAuthMethods(),
|
||||
providers: providers(),
|
||||
providerConnectedIds: providerConnectedIds(),
|
||||
listAgents: listAgents,
|
||||
setSessionAgent: setSessionAgent,
|
||||
saveSession: saveSessionExport,
|
||||
sessionStatusById: activeSessionStatusById(),
|
||||
onTryNotionPrompt: () => {
|
||||
setPrompt("setup my crm");
|
||||
setTryNotionPromptVisible(false);
|
||||
setNotionSkillInstalled(true);
|
||||
try {
|
||||
window.localStorage.setItem("openwork.notionSkillInstalled", "1");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
sessionStatus: selectedSessionStatus(),
|
||||
renameSession: renameSessionTitle,
|
||||
error: error(),
|
||||
});
|
||||
|
||||
const dashboardTabs = new Set<DashboardTab>([
|
||||
"home",
|
||||
"sessions",
|
||||
"templates",
|
||||
"skills",
|
||||
"plugins",
|
||||
"mcp",
|
||||
"settings",
|
||||
]);
|
||||
|
||||
const resolveDashboardTab = (value?: string | null) => {
|
||||
const normalized = value?.trim().toLowerCase() ?? "";
|
||||
if (dashboardTabs.has(normalized as DashboardTab)) {
|
||||
return normalized as DashboardTab;
|
||||
}
|
||||
return "home";
|
||||
};
|
||||
|
||||
const initialRoute = () => {
|
||||
if (typeof window === "undefined") return "/onboarding";
|
||||
try {
|
||||
return window.localStorage.getItem("openwork.onboardingComplete") === "1"
|
||||
? "/dashboard/home"
|
||||
: "/onboarding";
|
||||
} catch {
|
||||
return "/onboarding";
|
||||
}
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const rawPath = location.pathname.trim();
|
||||
const path = rawPath.toLowerCase();
|
||||
|
||||
if (path === "" || path === "/") {
|
||||
navigate(initialRoute(), { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.startsWith("/dashboard")) {
|
||||
const [, , tabSegment] = path.split("/");
|
||||
const resolvedTab = resolveDashboardTab(tabSegment);
|
||||
|
||||
if (resolvedTab !== tab()) {
|
||||
setTabState(resolvedTab);
|
||||
}
|
||||
if (!tabSegment || tabSegment !== resolvedTab) {
|
||||
goToDashboard(resolvedTab, { replace: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.startsWith("/session")) {
|
||||
const [, , sessionSegment] = rawPath.split("/");
|
||||
const id = (sessionSegment ?? "").trim();
|
||||
|
||||
if (!id) {
|
||||
const fallback = activeSessionId();
|
||||
if (fallback) {
|
||||
goToSession(fallback, { replace: true });
|
||||
} else {
|
||||
goToDashboard("sessions", { replace: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDemoMode()) {
|
||||
if (activeSessionId() !== id) {
|
||||
selectDemoSession(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedSessionId() !== id) {
|
||||
void selectSession(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.startsWith("/onboarding")) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate("/dashboard/home", { replace: true });
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Switch>
|
||||
<Match when={view() === "onboarding"}>
|
||||
<Match when={currentView() === "onboarding"}>
|
||||
<OnboardingView {...onboardingProps()} />
|
||||
</Match>
|
||||
<Match when={view() === "session"}>
|
||||
<SessionView
|
||||
selectedSessionId={activeSessionId()}
|
||||
setView={setView}
|
||||
setTab={setTab}
|
||||
activeWorkspaceDisplay={activeWorkspaceDisplay()}
|
||||
setWorkspaceSearch={workspaceStore.setWorkspaceSearch}
|
||||
setWorkspacePickerOpen={workspaceStore.setWorkspacePickerOpen}
|
||||
headerStatus={headerStatus()}
|
||||
busyHint={busyHint()}
|
||||
selectedSessionModelLabel={selectedSessionModelLabel()}
|
||||
openSessionModelPicker={openSessionModelPicker}
|
||||
activePlugins={sidebarPluginList()}
|
||||
activePluginStatus={sidebarPluginStatus()}
|
||||
createSessionAndOpen={createSessionAndOpen}
|
||||
sendPromptAsync={sendPrompt}
|
||||
newTaskDisabled={newTaskDisabled()}
|
||||
sessions={activeSessions().map((session) => ({
|
||||
id: session.id,
|
||||
title: session.title,
|
||||
slug: session.slug,
|
||||
}))}
|
||||
selectSession={isDemoMode() ? selectDemoSession : selectSession}
|
||||
messages={activeMessages()}
|
||||
todos={activeTodos()}
|
||||
busyLabel={busyLabel()}
|
||||
developerMode={developerMode()}
|
||||
showThinking={showThinking()}
|
||||
groupMessageParts={groupMessageParts}
|
||||
summarizeStep={summarizeStep}
|
||||
expandedStepIds={expandedStepIds()}
|
||||
setExpandedStepIds={setExpandedStepIds}
|
||||
expandedSidebarSections={expandedSidebarSections()}
|
||||
setExpandedSidebarSections={setExpandedSidebarSections}
|
||||
artifacts={activeArtifacts()}
|
||||
workingFiles={activeWorkingFiles()}
|
||||
authorizedDirs={activeAuthorizedDirs()}
|
||||
busy={busy()}
|
||||
prompt={prompt()}
|
||||
setPrompt={setPrompt}
|
||||
sendPrompt={sendPrompt}
|
||||
activePermission={activePermissionMemo()}
|
||||
permissionReplyBusy={permissionReplyBusy()}
|
||||
respondPermission={respondPermission}
|
||||
respondPermissionAndRemember={respondPermissionAndRemember}
|
||||
safeStringify={safeStringify}
|
||||
showTryNotionPrompt={tryNotionPromptVisible() && notionIsActive()}
|
||||
openConnect={openConnectFlow}
|
||||
startProviderAuth={startProviderAuth}
|
||||
openProviderAuthModal={openProviderAuthModal}
|
||||
closeProviderAuthModal={closeProviderAuthModal}
|
||||
providerAuthModalOpen={providerAuthModalOpen()}
|
||||
providerAuthBusy={providerAuthBusy()}
|
||||
providerAuthError={providerAuthError()}
|
||||
providerAuthMethods={providerAuthMethods()}
|
||||
providers={providers()}
|
||||
providerConnectedIds={providerConnectedIds()}
|
||||
listAgents={listAgents}
|
||||
setSessionAgent={setSessionAgent}
|
||||
saveSession={saveSessionExport}
|
||||
sessionStatusById={activeSessionStatusById()}
|
||||
onTryNotionPrompt={() => {
|
||||
setPrompt("setup my crm");
|
||||
setTryNotionPromptVisible(false);
|
||||
setNotionSkillInstalled(true);
|
||||
try {
|
||||
window.localStorage.setItem("openwork.notionSkillInstalled", "1");
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}}
|
||||
sessionStatus={selectedSessionStatus()}
|
||||
renameSession={renameSessionTitle}
|
||||
error={error()}
|
||||
/>
|
||||
<Match when={currentView() === "session"}>
|
||||
<SessionView {...sessionProps()} />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<DashboardView {...dashboardProps()} />
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
import { Show, createMemo } from "solid-js";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
|
||||
import { Folder, Globe, Zap } from "lucide-solid";
|
||||
import { t, currentLocale } from "../../i18n";
|
||||
import OpenWorkLogo from "./openwork-logo";
|
||||
|
||||
import type { WorkspaceInfo } from "../lib/tauri";
|
||||
|
||||
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 WorkspaceSwitchOverlay(props: {
|
||||
open: boolean;
|
||||
workspace: WorkspaceInfo | null;
|
||||
@@ -58,10 +48,6 @@ export default function WorkspaceSwitchOverlay(props: {
|
||||
return props.workspace.directory?.trim() ?? "";
|
||||
});
|
||||
|
||||
const Icon = createMemo(() =>
|
||||
iconForWorkspace(props.workspace?.preset ?? "starter", props.workspace?.workspaceType ?? "local")
|
||||
);
|
||||
|
||||
return (
|
||||
<Show when={props.open}>
|
||||
<div class="fixed inset-0 z-[60] overflow-hidden bg-gray-1 text-gray-12 motion-safe:animate-in motion-safe:fade-in motion-safe:duration-300">
|
||||
@@ -99,8 +85,8 @@ export default function WorkspaceSwitchOverlay(props: {
|
||||
class="absolute -inset-1 rounded-full border border-gray-6/30 motion-safe:animate-spin motion-reduce:opacity-60"
|
||||
style={{ "animation-duration": "9s", "animation-direction": "reverse" }}
|
||||
/>
|
||||
<div class="relative h-24 w-24 rounded-3xl bg-gray-2/80 border border-gray-6/70 shadow-2xl flex items-center justify-center text-gray-12">
|
||||
<Dynamic component={Icon()} size={26} />
|
||||
<div class="relative h-24 w-24 rounded-3xl bg-gray-1/90 border border-gray-5/60 shadow-2xl flex items-center justify-center">
|
||||
<OpenWorkLogo size={44} class="drop-shadow-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -408,8 +408,8 @@ export function createWorkspaceStore(options: {
|
||||
|
||||
options.refreshSkills({ force: true }).catch(() => undefined);
|
||||
if (!options.selectedSessionId()) {
|
||||
options.setView("dashboard");
|
||||
options.setTab("home");
|
||||
options.setView("dashboard");
|
||||
}
|
||||
|
||||
// If the user successfully connected, treat onboarding as complete so we
|
||||
@@ -466,8 +466,8 @@ export function createWorkspaceStore(options: {
|
||||
|
||||
setWorkspacePickerOpen(false);
|
||||
setCreateWorkspaceOpen(false);
|
||||
options.setView("dashboard");
|
||||
options.setTab("home");
|
||||
options.setView("dashboard");
|
||||
markOnboardingComplete();
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : safeStringify(e);
|
||||
@@ -484,11 +484,6 @@ export function createWorkspaceStore(options: {
|
||||
directory?: string | null;
|
||||
displayName?: string | null;
|
||||
}) {
|
||||
if (!isTauriRuntime()) {
|
||||
options.setError(t("app.error.tauri_required", currentLocale()));
|
||||
return false;
|
||||
}
|
||||
|
||||
const baseUrl = input.baseUrl.trim();
|
||||
if (!baseUrl) {
|
||||
options.setError(t("app.error.remote_base_url_required", currentLocale()));
|
||||
@@ -518,14 +513,37 @@ export function createWorkspaceStore(options: {
|
||||
options.setBusyLabel("status.creating_workspace");
|
||||
options.setBusyStartedAt(Date.now());
|
||||
|
||||
const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
|
||||
const displayName = input.displayName?.trim() || null;
|
||||
const workspaceId = `remote:${normalizedBaseUrl}:${resolvedDirectory}`;
|
||||
|
||||
try {
|
||||
const ws = await workspaceCreateRemote({
|
||||
baseUrl,
|
||||
directory: resolvedDirectory ? resolvedDirectory : null,
|
||||
displayName: input.displayName?.trim() ? input.displayName.trim() : null,
|
||||
});
|
||||
setWorkspaces(ws.workspaces);
|
||||
syncActiveWorkspaceId(ws.activeId);
|
||||
if (isTauriRuntime()) {
|
||||
const ws = await workspaceCreateRemote({
|
||||
baseUrl: normalizedBaseUrl,
|
||||
directory: resolvedDirectory ? resolvedDirectory : null,
|
||||
displayName,
|
||||
});
|
||||
setWorkspaces(ws.workspaces);
|
||||
syncActiveWorkspaceId(ws.activeId);
|
||||
} else {
|
||||
const nextWorkspace: WorkspaceInfo = {
|
||||
id: workspaceId,
|
||||
name: displayName ?? normalizedBaseUrl,
|
||||
path: "",
|
||||
preset: "remote",
|
||||
workspaceType: "remote",
|
||||
baseUrl: normalizedBaseUrl,
|
||||
directory: resolvedDirectory || null,
|
||||
displayName,
|
||||
};
|
||||
|
||||
setWorkspaces((prev) => {
|
||||
const withoutMatch = prev.filter((workspace) => workspace.id !== workspaceId);
|
||||
return [...withoutMatch, nextWorkspace];
|
||||
});
|
||||
syncActiveWorkspaceId(workspaceId);
|
||||
}
|
||||
|
||||
setProjectDir(resolvedDirectory);
|
||||
setWorkspaceConfig(null);
|
||||
|
||||
@@ -34,7 +34,7 @@ export type DashboardViewProps = {
|
||||
tab: DashboardTab;
|
||||
setTab: (tab: DashboardTab) => void;
|
||||
view: "dashboard" | "session" | "onboarding";
|
||||
setView: (view: "dashboard" | "session" | "onboarding") => void;
|
||||
setView: (view: "dashboard" | "session" | "onboarding", sessionId?: string) => void;
|
||||
mode: "host" | "client" | null;
|
||||
baseUrl: string;
|
||||
clientConnected: boolean;
|
||||
@@ -211,9 +211,9 @@ export default function DashboardView(props: DashboardViewProps) {
|
||||
const openSessionFromList = (sessionId: string) => {
|
||||
// Defer view switch to avoid click-through on the same event frame.
|
||||
window.setTimeout(() => {
|
||||
props.setView("session");
|
||||
props.setTab("sessions");
|
||||
void props.selectSession(sessionId);
|
||||
props.setTab("sessions");
|
||||
props.setView("session", sessionId);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import FlyoutItem from "../components/flyout-item";
|
||||
|
||||
export type SessionViewProps = {
|
||||
selectedSessionId: string | null;
|
||||
setView: (view: View) => void;
|
||||
setView: (view: View, sessionId?: string) => void;
|
||||
setTab: (tab: DashboardTab) => void;
|
||||
activeWorkspaceDisplay: WorkspaceDisplay;
|
||||
setWorkspaceSearch: (value: string) => void;
|
||||
@@ -655,8 +655,8 @@ export default function SessionView(props: SessionViewProps) {
|
||||
<div class="text-lg font-medium">No session selected</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
props.setView("dashboard");
|
||||
props.setTab("sessions");
|
||||
props.setView("dashboard");
|
||||
}}
|
||||
>
|
||||
Back to dashboard
|
||||
@@ -672,8 +672,8 @@ export default function SessionView(props: SessionViewProps) {
|
||||
variant="ghost"
|
||||
class="!p-2 rounded-full"
|
||||
onClick={() => {
|
||||
props.setView("dashboard");
|
||||
props.setTab("sessions");
|
||||
props.setView("dashboard");
|
||||
}}
|
||||
>
|
||||
<ArrowRight class="rotate-180 w-5 h-5" />
|
||||
@@ -713,11 +713,11 @@ export default function SessionView(props: SessionViewProps) {
|
||||
}}
|
||||
sessions={props.sessions}
|
||||
selectedSessionId={props.selectedSessionId}
|
||||
onSelectSession={async (id) => {
|
||||
await props.selectSession(id);
|
||||
props.setView("session");
|
||||
props.setTab("sessions");
|
||||
}}
|
||||
onSelectSession={async (id) => {
|
||||
await props.selectSession(id);
|
||||
props.setView("session", id);
|
||||
props.setTab("sessions");
|
||||
}}
|
||||
sessionStatusById={props.sessionStatusById}
|
||||
onCreateSession={props.createSessionAndOpen}
|
||||
newTaskDisabled={props.newTaskDisabled}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function createTemplateState(options: {
|
||||
setSessionModelById: (value: Record<string, ModelRef> | ((current: Record<string, ModelRef>) => Record<string, ModelRef>)) => void;
|
||||
defaultModel: Accessor<ModelRef>;
|
||||
modelVariant: Accessor<string | null>;
|
||||
setView: (view: "onboarding" | "dashboard" | "session") => void;
|
||||
setView: (view: "onboarding" | "dashboard" | "session", sessionId?: string) => void;
|
||||
isDemoMode: Accessor<boolean>;
|
||||
activeWorkspaceRoot: Accessor<string>;
|
||||
setBusy: (value: boolean) => void;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* @refresh reload */
|
||||
import { render } from "solid-js/web";
|
||||
import { HashRouter, Route, Router } from "@solidjs/router";
|
||||
|
||||
import { bootstrapTheme } from "./app/theme";
|
||||
import "./app/index.css";
|
||||
@@ -15,6 +16,8 @@ if (!root) {
|
||||
throw new Error("Root element not found");
|
||||
}
|
||||
|
||||
const RouterComponent = isTauriRuntime() ? HashRouter : Router;
|
||||
|
||||
const platform: Platform = {
|
||||
platform: isTauriRuntime() ? "desktop" : "web",
|
||||
openLink(url: string) {
|
||||
@@ -79,7 +82,9 @@ const platform: Platform = {
|
||||
render(
|
||||
() => (
|
||||
<PlatformProvider value={platform}>
|
||||
<AppEntry />
|
||||
<RouterComponent root={AppEntry}>
|
||||
<Route path="*all" component={() => null} />
|
||||
</RouterComponent>
|
||||
</PlatformProvider>
|
||||
),
|
||||
root,
|
||||
|
||||
20
patches/@solidjs__router@0.15.4.patch
Normal file
20
patches/@solidjs__router@0.15.4.patch
Normal file
@@ -0,0 +1,20 @@
|
||||
diff --git a/dist/index.d.ts b/dist/index.d.ts
|
||||
index 2acf9f2213ac91f71bf844c53bf63be28ce7d79d..49eac35140e3e004963ad5f9b2ffe315a740339f 100644
|
||||
--- a/dist/index.d.ts
|
||||
+++ b/dist/index.d.ts
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./routers/index.js";
|
||||
+export * from "./routers/index.js";
|
||||
export * from "./components.jsx";
|
||||
export * from "./lifecycle.js";
|
||||
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute } from "./routing.js";
|
||||
diff --git a/dist/index.jsx b/dist/index.jsx
|
||||
index c9079ba9cf5244de3c4a3fe938d33c67ab13fc00..305f052987ae0b2aec0edb163fec6d0dcf663581 100644
|
||||
--- a/dist/index.jsx
|
||||
+++ b/dist/index.jsx
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./routers/index.js";
|
||||
+export * from "./routers/index.js";
|
||||
export * from "./components.jsx";
|
||||
export * from "./lifecycle.js";
|
||||
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute } from "./routing.js";
|
||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -4,6 +4,11 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
patchedDependencies:
|
||||
'@solidjs/router@0.15.4':
|
||||
hash: 1db11a7c28fe4da76187d42efaffc6b9a70ad370462fffb794ff90e67744d770
|
||||
path: patches/@solidjs__router@0.15.4.patch
|
||||
|
||||
importers:
|
||||
|
||||
.: {}
|
||||
@@ -22,6 +27,9 @@ importers:
|
||||
'@solid-primitives/storage':
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3(solid-js@1.9.10)
|
||||
'@solidjs/router':
|
||||
specifier: ^0.15.4
|
||||
version: 0.15.4(patch_hash=1db11a7c28fe4da76187d42efaffc6b9a70ad370462fffb794ff90e67744d770)(solid-js@1.9.10)
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2.0.0
|
||||
version: 2.9.1
|
||||
@@ -892,6 +900,11 @@ packages:
|
||||
peerDependencies:
|
||||
solid-js: ^1.6.12
|
||||
|
||||
'@solidjs/router@0.15.4':
|
||||
resolution: {integrity: sha512-WOpgg9a9T638cR+5FGbFi/IV4l2FpmBs1GpIMSPa0Ce9vyJN7Wts+X2PqMf9IYn0zUj2MlSJtm1gp7/HI/n5TQ==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.8.6
|
||||
|
||||
'@tailwindcss/node@4.1.18':
|
||||
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
|
||||
|
||||
@@ -2353,6 +2366,10 @@ snapshots:
|
||||
dependencies:
|
||||
solid-js: 1.9.10
|
||||
|
||||
'@solidjs/router@0.15.4(patch_hash=1db11a7c28fe4da76187d42efaffc6b9a70ad370462fffb794ff90e67744d770)(solid-js@1.9.10)':
|
||||
dependencies:
|
||||
solid-js: 1.9.10
|
||||
|
||||
'@tailwindcss/node@4.1.18':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
|
||||
Reference in New Issue
Block a user