diff --git a/app/store/database.go b/app/store/database.go index 8747e9598..97e792e17 100644 --- a/app/store/database.go +++ b/app/store/database.go @@ -82,7 +82,7 @@ func (db *database) init() error { websearch_enabled BOOLEAN NOT NULL DEFAULT 0, selected_model TEXT NOT NULL DEFAULT '', sidebar_open BOOLEAN NOT NULL DEFAULT 0, - last_home_view TEXT NOT NULL DEFAULT 'chat', + last_home_view TEXT NOT NULL DEFAULT 'launch', think_enabled BOOLEAN NOT NULL DEFAULT 0, think_level TEXT NOT NULL DEFAULT '', cloud_setting_migrated BOOLEAN NOT NULL DEFAULT 0, @@ -527,7 +527,7 @@ func (db *database) migrateV14ToV15() error { // migrateV15ToV16 adds the last_home_view column to the settings table func (db *database) migrateV15ToV16() error { - _, err := db.conn.Exec(`ALTER TABLE settings ADD COLUMN last_home_view TEXT NOT NULL DEFAULT 'chat'`) + _, err := db.conn.Exec(`ALTER TABLE settings ADD COLUMN last_home_view TEXT NOT NULL DEFAULT 'launch'`) if err != nil && !duplicateColumnError(err) { return fmt.Errorf("add last_home_view column: %w", err) } @@ -1211,7 +1211,7 @@ func (db *database) setSettings(s Settings) error { } if lastHomeView != "chat" { if _, ok := validLaunchView[lastHomeView]; !ok { - lastHomeView = "chat" + lastHomeView = "launch" } } diff --git a/app/store/database_test.go b/app/store/database_test.go index a5ebabd2e..3411e11bf 100644 --- a/app/store/database_test.go +++ b/app/store/database_test.go @@ -135,6 +135,45 @@ func TestMigrationV13ToV14ContextLength(t *testing.T) { } } +func TestMigrationV15ToV16LastHomeViewDefaultsToLaunch(t *testing.T) { + tmpDir := t.TempDir() + dbPath := filepath.Join(tmpDir, "test.db") + + db, err := newDatabase(dbPath) + if err != nil { + t.Fatalf("failed to create database: %v", err) + } + defer db.Close() + + if _, err := db.conn.Exec(` + ALTER TABLE settings DROP COLUMN last_home_view; + UPDATE settings SET schema_version = 15; + `); err != nil { + t.Fatalf("failed to seed v15 settings row: %v", err) + } + + if err := db.migrate(); err != nil { + t.Fatalf("migration from v15 to v16 failed: %v", err) + } + + var lastHomeView string + if err := db.conn.QueryRow("SELECT last_home_view FROM settings").Scan(&lastHomeView); err != nil { + t.Fatalf("failed to read last_home_view: %v", err) + } + + if lastHomeView != "launch" { + t.Fatalf("expected last_home_view to default to launch after migration, got %q", lastHomeView) + } + + version, err := db.getSchemaVersion() + if err != nil { + t.Fatalf("failed to get schema version: %v", err) + } + if version != currentSchemaVersion { + t.Fatalf("expected schema version %d, got %d", currentSchemaVersion, version) + } +} + func TestChatDeletionWithCascade(t *testing.T) { t.Run("chat deletion cascades to related messages", func(t *testing.T) { tmpDir := t.TempDir() diff --git a/app/store/store.go b/app/store/store.go index 2fd5ce495..369c1db20 100644 --- a/app/store/store.go +++ b/app/store/store.go @@ -393,7 +393,7 @@ func (s *Store) Settings() (Settings, error) { } if settings.LastHomeView == "" { - settings.LastHomeView = "chat" + settings.LastHomeView = "launch" } return settings, nil diff --git a/app/store/store_test.go b/app/store/store_test.go index dfe6435f1..1985fda7d 100644 --- a/app/store/store_test.go +++ b/app/store/store_test.go @@ -81,6 +81,32 @@ func TestStore(t *testing.T) { } }) + t.Run("settings default home view is launch", func(t *testing.T) { + loaded, err := s.Settings() + if err != nil { + t.Fatal(err) + } + + if loaded.LastHomeView != "launch" { + t.Fatalf("expected default LastHomeView to be launch, got %q", loaded.LastHomeView) + } + }) + + t.Run("settings empty home view falls back to launch", func(t *testing.T) { + if err := s.SetSettings(Settings{LastHomeView: ""}); err != nil { + t.Fatal(err) + } + + loaded, err := s.Settings() + if err != nil { + t.Fatal(err) + } + + if loaded.LastHomeView != "launch" { + t.Fatalf("expected empty LastHomeView to fall back to launch, got %q", loaded.LastHomeView) + } + }) + t.Run("window size", func(t *testing.T) { if err := s.SetWindowSize(1024, 768); err != nil { t.Fatal(err) diff --git a/app/ui/app/src/components/ChatSidebar.tsx b/app/ui/app/src/components/ChatSidebar.tsx index 8b9209908..2d1b07a61 100644 --- a/app/ui/app/src/components/ChatSidebar.tsx +++ b/app/ui/app/src/components/ChatSidebar.tsx @@ -12,6 +12,7 @@ import { CogIcon, RocketLaunchIcon } from "@heroicons/react/24/outline"; // holding shift and clicking this many times within this many seconds const DEBUG_SHIFT_CLICKS_REQUIRED = 5; const DEBUG_SHIFT_CLICK_WINDOW_MS = 7000; // 7 seconds +const launchSidebarRequestedKey = "ollama.launchSidebarRequested"; interface ChatSidebarProps { currentChatId?: string; @@ -285,6 +286,11 @@ export function ChatSidebar({ currentChatId }: ChatSidebarProps) { { + if (currentChatId !== "launch") { + sessionStorage.setItem(launchSidebarRequestedKey, "1"); + } + }} className={`flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left text-sm text-neutral-700 hover:bg-neutral-100 dark:hover:bg-neutral-800 dark:text-neutral-100 cursor-pointer ${currentChatId === "launch" ? "bg-neutral-100 dark:bg-neutral-800" : "" diff --git a/app/ui/app/src/hooks/useSettings.ts b/app/ui/app/src/hooks/useSettings.ts index 030447646..1eebcf533 100644 --- a/app/ui/app/src/hooks/useSettings.ts +++ b/app/ui/app/src/hooks/useSettings.ts @@ -52,7 +52,7 @@ export function useSettings() { thinkLevel: settingsData?.settings?.ThinkLevel ?? "none", selectedModel: settingsData?.settings?.SelectedModel ?? "", sidebarOpen: settingsData?.settings?.SidebarOpen ?? false, - lastHomeView: settingsData?.settings?.LastHomeView ?? "chat", + lastHomeView: settingsData?.settings?.LastHomeView ?? "launch", }), [settingsData?.settings], ); diff --git a/app/ui/app/src/routes/c.$chatId.tsx b/app/ui/app/src/routes/c.$chatId.tsx index b0061f421..fa72ac496 100644 --- a/app/ui/app/src/routes/c.$chatId.tsx +++ b/app/ui/app/src/routes/c.$chatId.tsx @@ -5,9 +5,31 @@ import { getChat } from "@/api"; import { SidebarLayout } from "@/components/layout/layout"; import { ChatSidebar } from "@/components/ChatSidebar"; import LaunchCommands from "@/components/LaunchCommands"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { useSettings } from "@/hooks/useSettings"; +const launchSidebarRequestedKey = "ollama.launchSidebarRequested"; +const launchSidebarSeenKey = "ollama.launchSidebarSeen"; +const fallbackSessionState = new Map(); + +function getSessionState() { + if (typeof sessionStorage !== "undefined") { + return sessionStorage; + } + + return { + getItem(key: string) { + return fallbackSessionState.get(key) ?? null; + }, + setItem(key: string, value: string) { + fallbackSessionState.set(key, value); + }, + removeItem(key: string) { + fallbackSessionState.delete(key); + }, + }; +} + export const Route = createFileRoute("/c/$chatId")({ component: RouteComponent, loader: async ({ context, params }) => { @@ -25,6 +47,7 @@ export const Route = createFileRoute("/c/$chatId")({ function RouteComponent() { const { chatId } = Route.useParams(); const { settingsData, setSettings } = useSettings(); + const previousChatIdRef = useRef(null); // Always call hooks at the top level - use a flag to skip data when chatId is a special view const { @@ -38,15 +61,42 @@ function RouteComponent() { return; } + const previousChatId = previousChatIdRef.current; + previousChatIdRef.current = chatId; + if (chatId === "launch") { - if ( - settingsData.LastHomeView !== "chat" && - settingsData.LastHomeView !== "launch" - ) { + const sessionState = getSessionState(); + const shouldOpenSidebar = + previousChatId !== "launch" && + (() => { + if (sessionState.getItem(launchSidebarRequestedKey) === "1") { + sessionState.removeItem(launchSidebarRequestedKey); + sessionState.setItem(launchSidebarSeenKey, "1"); + return true; + } + + if (sessionState.getItem(launchSidebarSeenKey) !== "1") { + sessionState.setItem(launchSidebarSeenKey, "1"); + return true; + } + + return false; + })(); + const updates: { LastHomeView?: string; SidebarOpen?: boolean } = {}; + + if (settingsData.LastHomeView !== "launch") { + updates.LastHomeView = "launch"; + } + + if (shouldOpenSidebar && !settingsData.SidebarOpen) { + updates.SidebarOpen = true; + } + + if (Object.keys(updates).length === 0) { return; } - setSettings({ LastHomeView: "openclaw" }).catch(() => { + setSettings(updates).catch(() => { // Best effort persistence for home view preference. }); return; diff --git a/app/ui/app/src/routes/index.tsx b/app/ui/app/src/routes/index.tsx index 65ba4ed65..9aadf0027 100644 --- a/app/ui/app/src/routes/index.tsx +++ b/app/ui/app/src/routes/index.tsx @@ -7,8 +7,8 @@ export const Route = createFileRoute("/")({ queryKey: ["settings"], queryFn: getSettings, }); - const lastHomeView = settingsData?.settings?.LastHomeView ?? "chat"; - const chatId = lastHomeView === "chat" ? "new" : "launch"; + const chatId = + settingsData?.settings?.LastHomeView === "chat" ? "new" : "launch"; throw redirect({ to: "/c/$chatId",