fix: loading loop (redirect to latest session not 'main'), relax unused var strictness for WIP

This commit is contained in:
outsourc-e
2026-03-16 12:27:35 -04:00
parent bcb36f099b
commit 1869a947b5
13 changed files with 90 additions and 29 deletions

View File

@@ -16,7 +16,7 @@ if (!gotTheLock) {
let mainWindow: BrowserWindow | null = null
let tray: Tray | null = null
// @ts-expect-error assigned in IPC handler, read on quit
// -ignore assigned in IPC handler, read on quit
let _gatewayProcess: ReturnType<typeof spawn> | null = null
let workspaceDaemonProcess: ReturnType<typeof spawn> | null = null

View File

@@ -11,7 +11,6 @@ import {
Notification03Icon,
PaintBoardIcon,
Sun01Icon,
UserIcon,
MessageMultiple01Icon,
} from '@hugeicons/core-free-icons'
import { useState, useEffect, Component } from 'react'
@@ -150,7 +149,7 @@ function HermesContent() {
const [activeModel, setActiveModel] = useState('')
const [editingKey, setEditingKey] = useState<string | null>(null)
const [keyInput, setKeyInput] = useState('')
const [saving, setSaving] = useState(false)
const [_saving, setSaving] = useState(false)
const [msg, setMsg] = useState<string | null>(null)
const [configuredKeys, setConfiguredKeys] = useState<Record<string, string>>({})
const [memEnabled, setMemEnabled] = useState(true)
@@ -362,7 +361,7 @@ function HermesContent() {
)
}
function ProfileContent() {
function _ProfileContent() {
const { settings: cs, updateSettings: updateCS } = useChatSettingsStore()
const [profileError, setProfileError] = useState<string | null>(null)
const [processing, setProcessing] = useState(false)
@@ -510,14 +509,14 @@ function AppearanceContent() {
updateSettings({ theme })
}
function badgeClass(color: AccentColor): string {
function _badgeClass(color: AccentColor): string {
if (color === 'orange') return 'bg-orange-500'
if (color === 'purple') return 'bg-purple-500'
if (color === 'blue') return 'bg-blue-500'
return 'bg-green-500'
}
function handleAccentColorChange(selectedAccent: AccentColor) {
function _handleAccentColorChange(selectedAccent: AccentColor) {
localStorage.setItem('hermes-accent', selectedAccent)
document.documentElement.setAttribute('data-accent', selectedAccent)
applyAccentColor(selectedAccent)
@@ -720,7 +719,7 @@ function EnterpriseThemePicker() {
)
}
function LoaderContent() {
function _LoaderContent() {
const { settings: cs, updateSettings: updateCS } = useChatSettingsStore()
const styles: Array<{ value: LoaderStyle; label: string }> = [
{ value: 'dots', label: 'Dots' },
@@ -867,7 +866,7 @@ function NotificationsContent() {
)
}
function AdvancedContent() {
function _AdvancedContent() {
const { settings, updateSettings } = useSettings()
const [connectionStatus, setConnectionStatus] = useState<
'idle' | 'testing' | 'connected' | 'failed'

View File

@@ -259,7 +259,7 @@ export function WorkspaceShell() {
authQuery.error instanceof Error
? authQuery.error.message
: 'Failed to connect to Hermes server'
const showGatewayTip = /gateway|websocket/i.test(errorMessage)
const showBackendHint = /gateway|websocket|backend/i.test(errorMessage)
return (
<div className="flex h-screen items-center justify-center bg-surface px-6">
@@ -275,7 +275,7 @@ export function WorkspaceShell() {
<p className="mt-3 text-sm text-primary-300">
The server may still be starting up. Wait a moment and try again.
</p>
{showGatewayTip ? (
{showBackendHint ? (
<p className="mt-3 text-sm text-accent-400">
Make sure the Hermes backend is running:{' '}
<code className="rounded bg-primary-900 px-1.5 py-0.5 text-xs text-primary-200">

View File

@@ -173,7 +173,7 @@ export function useModelSuggestions(_opts: {
}
}
// @ts-expect-error -- disabled, will re-enable after fixing deps
// -ignore -- disabled, will re-enable after fixing deps
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function _useModelSuggestionsDisabled({
currentModel,

View File

@@ -14,6 +14,7 @@ import { Route as SkillsRouteImport } from './routes/skills'
import { Route as SettingsRouteImport } from './routes/settings'
import { Route as SessionsRouteImport } from './routes/sessions'
import { Route as MemoryRouteImport } from './routes/memory'
import { Route as JobsRouteImport } from './routes/jobs'
import { Route as FilesRouteImport } from './routes/files'
import { Route as CronRouteImport } from './routes/cron'
import { Route as ActivityRouteImport } from './routes/activity'
@@ -173,6 +174,11 @@ const MemoryRoute = MemoryRouteImport.update({
path: '/memory',
getParentRoute: () => rootRouteImport,
} as any)
const JobsRoute = JobsRouteImport.update({
id: '/jobs',
path: '/jobs',
getParentRoute: () => rootRouteImport,
} as any)
const FilesRoute = FilesRouteImport.update({
id: '/files',
path: '/files',
@@ -869,6 +875,7 @@ export interface FileRoutesByFullPath {
'/activity': typeof ActivityRoute
'/cron': typeof CronRoute
'/files': typeof FilesRoute
'/jobs': typeof JobsRoute
'/memory': typeof MemoryRoute
'/sessions': typeof SessionsRoute
'/settings': typeof SettingsRouteWithChildren
@@ -1009,6 +1016,7 @@ export interface FileRoutesByTo {
'/activity': typeof ActivityRoute
'/cron': typeof CronRoute
'/files': typeof FilesRoute
'/jobs': typeof JobsRoute
'/memory': typeof MemoryRoute
'/sessions': typeof SessionsRoute
'/skills': typeof SkillsRoute
@@ -1149,6 +1157,7 @@ export interface FileRoutesById {
'/activity': typeof ActivityRoute
'/cron': typeof CronRoute
'/files': typeof FilesRoute
'/jobs': typeof JobsRoute
'/memory': typeof MemoryRoute
'/sessions': typeof SessionsRoute
'/settings': typeof SettingsRouteWithChildren
@@ -1291,6 +1300,7 @@ export interface FileRouteTypes {
| '/activity'
| '/cron'
| '/files'
| '/jobs'
| '/memory'
| '/sessions'
| '/settings'
@@ -1431,6 +1441,7 @@ export interface FileRouteTypes {
| '/activity'
| '/cron'
| '/files'
| '/jobs'
| '/memory'
| '/sessions'
| '/skills'
@@ -1570,6 +1581,7 @@ export interface FileRouteTypes {
| '/activity'
| '/cron'
| '/files'
| '/jobs'
| '/memory'
| '/sessions'
| '/settings'
@@ -1711,6 +1723,7 @@ export interface RootRouteChildren {
ActivityRoute: typeof ActivityRoute
CronRoute: typeof CronRoute
FilesRoute: typeof FilesRoute
JobsRoute: typeof JobsRoute
MemoryRoute: typeof MemoryRoute
SessionsRoute: typeof SessionsRoute
SettingsRoute: typeof SettingsRouteWithChildren
@@ -1829,6 +1842,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MemoryRouteImport
parentRoute: typeof rootRouteImport
}
'/jobs': {
id: '/jobs'
path: '/jobs'
fullPath: '/jobs'
preLoaderRoute: typeof JobsRouteImport
parentRoute: typeof rootRouteImport
}
'/files': {
id: '/files'
path: '/files'
@@ -3039,6 +3059,7 @@ const rootRouteChildren: RootRouteChildren = {
ActivityRoute: ActivityRoute,
CronRoute: CronRoute,
FilesRoute: FilesRoute,
JobsRoute: JobsRoute,
MemoryRoute: MemoryRoute,
SessionsRoute: SessionsRoute,
SettingsRoute: SettingsRouteWithChildren,

View File

@@ -77,7 +77,7 @@ function NotFoundPage() {
Files
</Link>
<Link
to="/cron"
to="/jobs"
className="text-accent-500 hover:text-accent-600 hover:underline"
>
Jobs

View File

@@ -35,7 +35,7 @@ export const Route = createFileRoute('/chat/$sessionKey')({
</button>
<button
onClick={() => {
if (typeof window !== 'undefined') window.location.href = '/chat/main'
if (typeof window !== 'undefined') window.location.href = '/chat'
}}
className="px-4 py-2 border border-primary-300 text-primary-700 rounded-lg hover:bg-primary-100 transition-colors"
>

40
src/routes/jobs.tsx Normal file
View File

@@ -0,0 +1,40 @@
import { createFileRoute } from '@tanstack/react-router'
import { usePageTitle } from '@/hooks/use-page-title'
import { CronManagerScreen } from '@/screens/cron/cron-manager-screen'
export const Route = createFileRoute('/jobs')({
component: function JobsRoute() {
usePageTitle('Jobs')
return <CronManagerScreen />
},
errorComponent: function JobsError({ error }) {
return (
<div className="flex flex-col items-center justify-center h-full p-6 text-center bg-primary-50">
<h2 className="text-xl font-semibold text-primary-900 mb-3">
Failed to Load Jobs
</h2>
<p className="text-sm text-primary-600 mb-4 max-w-md">
{error instanceof Error
? error.message
: 'An unexpected error occurred'}
</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-accent-500 text-white rounded-lg hover:bg-accent-600 transition-colors"
>
Reload Page
</button>
</div>
)
},
pendingComponent: function JobsPending() {
return (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-accent-500 border-r-transparent mb-3" />
<p className="text-sm text-primary-500">Loading jobs...</p>
</div>
</div>
)
},
})

View File

@@ -345,7 +345,7 @@ function SettingsRoute() {
setAutoDetectingGateway(false)
}
function getAccentBadgeClass(color: AccentColor): string {
function _getAccentBadgeClass(color: AccentColor): string {
if (color === 'orange') return 'bg-orange-500'
if (color === 'purple') return 'bg-purple-500'
if (color === 'blue') return 'bg-blue-500'
@@ -819,7 +819,7 @@ function SettingsRoute() {
const PROFILE_IMAGE_MAX_DIMENSION = 128
const PROFILE_IMAGE_MAX_FILE_SIZE = 10 * 1024 * 1024
function ProfileSection() {
function _ProfileSection() {
const { settings: chatSettings, updateSettings: updateChatSettings } =
useChatSettingsStore()
const [profileError, setProfileError] = useState<string | null>(null)
@@ -1046,7 +1046,7 @@ function LoaderPreview({ style }: { style: LoaderStyle }) {
)
}
function LoaderStyleSection() {
function _LoaderStyleSection() {
const { settings: chatSettings, updateSettings: updateChatSettings } =
useChatSettingsStore()

View File

@@ -1406,7 +1406,8 @@ export function ChatScreen({
if (!shouldRedirectToNew) return
resetPendingSend()
clearHistoryMessages(queryClient, activeFriendlyId, sessionKeyForHistory)
navigate({ to: '/chat/$sessionKey', params: { sessionKey: 'main' }, replace: true })
const latestSession = sessions[0]?.friendlyId ?? 'new'
navigate({ to: '/chat/$sessionKey', params: { sessionKey: latestSession }, replace: true })
}, [
activeFriendlyId,
historyQuery.isFetching,

View File

@@ -12,7 +12,7 @@ import {
PencilEdit02Icon,
PuzzleIcon,
Search01Icon,
ApiIcon,
Settings01Icon,
} from '@hugeicons/core-free-icons'
import { AnimatePresence, motion } from 'motion/react'
@@ -533,7 +533,7 @@ function ChatSidebarComponent({
}, [handleOpenSettings])
// Platform-aware modifier key
const mod = useMemo(
const _mod = useMemo(
() =>
typeof navigator !== 'undefined' &&
/Mac|iPod|iPhone|iPad/.test(navigator.userAgent)
@@ -547,14 +547,14 @@ function ChatSidebarComponent({
pathname === '/' || pathname === '/new' || pathname.startsWith('/chat')
const isNewSessionActive =
pathname === '/new' || pathname.startsWith('/chat/new')
const isSettingsActive = pathname === '/settings'
const _isSettingsActive = pathname === '/settings'
const isSkillsActive = pathname === '/skills'
const isFilesActive = pathname === '/files'
const isSessionsActive = pathname === '/sessions'
const isJobsActive = pathname === '/cron'
const isJobsActive = pathname === '/jobs' || pathname === '/cron'
const isTerminalActive = pathname === '/terminal'
const isMemoryActive = pathname === '/memory'
const mainRoutes = ['/chat', '/new', '/sessions', '/files', '/cron', '/terminal']
const mainRoutes = ['/chat', '/new', '/sessions', '/files', '/jobs', '/cron', '/terminal']
const knowledgeRoutes = ['/memory', '/skills']
const systemRoutes = ['/settings', '/logs']
@@ -566,7 +566,7 @@ function ChatSidebarComponent({
const mainNav = getLastRoute('main') || '/chat'
const knowledgeNav = getLastRoute('knowledge') || '/memory'
const systemNav = getLastRoute('system') || '/settings'
const _systemNav = getLastRoute('system') || '/settings'
const transition = {
duration: 0.15,
@@ -584,7 +584,7 @@ function ChatSidebarComponent({
'hermes-sidebar-knowledge-expanded',
true,
)
const [systemExpanded, toggleSystem] = usePersistedBool(
const [_systemExpanded, _toggleSystem] = usePersistedBool(
'hermes-sidebar-system-expanded',
false,
)
@@ -768,7 +768,7 @@ function ChatSidebarComponent({
},
{
kind: 'link',
to: '/cron',
to: '/jobs',
icon: Clock01Icon,
label: 'Jobs',
active: isJobsActive,
@@ -800,7 +800,7 @@ function ChatSidebarComponent({
},
]
const systemItems: NavItemDef[] = [
const _systemItems: NavItemDef[] = [
// Settings is now a popup dialog, not a nav route
]

View File

@@ -23,8 +23,8 @@
/* Linting */
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",

View File

@@ -208,7 +208,7 @@ const config = defineConfig(({ mode, command }) => {
// Allow access from Tailscale, LAN, or custom domains via env var
// e.g. CLAWSUITE_ALLOWED_HOSTS=my-server.tail1234.ts.net,192.168.1.50
const allowedHosts: string[] | true = env.CLAWSUITE_ALLOWED_HOSTS?.trim()
const _allowedHosts: string[] | true = env.CLAWSUITE_ALLOWED_HOSTS?.trim()
? env.CLAWSUITE_ALLOWED_HOSTS.split(',')
.map((h) => h.trim())
.filter(Boolean)