Disable macOS spellchecker and simplify reactive lists

This commit is contained in:
Shantur Rathore
2025-10-30 14:40:37 +00:00
parent 4dbb9c1d99
commit 7e25f20e74
4 changed files with 115 additions and 161 deletions

View File

@@ -4,6 +4,8 @@ import { createApplicationMenu } from "./menu"
import { setupInstanceIPC } from "./ipc" import { setupInstanceIPC } from "./ipc"
import { setupStorageIPC } from "./storage" import { setupStorageIPC } from "./storage"
app.commandLine.appendSwitch("disable-spell-checking")
// Setup IPC handlers before creating windows // Setup IPC handlers before creating windows
setupStorageIPC() setupStorageIPC()
@@ -27,6 +29,9 @@ function createWindow() {
}, },
}) })
// Disable macOS spell server to avoid input lag
mainWindow.webContents.session.setSpellCheckerEnabled(false)
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
mainWindow.loadURL("http://localhost:3000") mainWindow.loadURL("http://localhost:3000")
mainWindow.webContents.openDevTools() mainWindow.webContents.openDevTools()

View File

@@ -18,18 +18,15 @@ const InstanceTabs: Component<InstanceTabsProps> = (props) => {
<div class="tab-bar tab-bar-instance"> <div class="tab-bar tab-bar-instance">
<div class="tab-container" role="tablist"> <div class="tab-container" role="tablist">
<div class="flex items-center gap-1 overflow-x-auto"> <div class="flex items-center gap-1 overflow-x-auto">
<For each={Array.from(props.instances.keys())}> <For each={Array.from(props.instances.entries())}>
{(id) => { {([id, instance]) => (
const instance = props.instances.get(id)
return (
<InstanceTab <InstanceTab
instance={instance!} instance={instance}
active={id === props.activeInstanceId} active={id === props.activeInstanceId}
onSelect={() => props.onSelect(id)} onSelect={() => props.onSelect(id)}
onClose={() => props.onClose(id)} onClose={() => props.onClose(id)}
/> />
) )}
}}
</For> </For>
<button <button
class="new-tab-button" class="new-tab-button"
@@ -40,7 +37,7 @@ const InstanceTabs: Component<InstanceTabsProps> = (props) => {
<Plus class="w-4 h-4" /> <Plus class="w-4 h-4" />
</button> </button>
</div> </div>
<Show when={props.instances.size > 1}> <Show when={Array.from(props.instances.entries()).length > 1}>
<div class="flex-shrink-0 ml-4"> <div class="flex-shrink-0 ml-4">
<KeyboardHint <KeyboardHint
shortcuts={[keyboardRegistry.get("instance-prev")!, keyboardRegistry.get("instance-next")!].filter( shortcuts={[keyboardRegistry.get("instance-prev")!, keyboardRegistry.get("instance-next")!].filter(

View File

@@ -17,10 +17,6 @@ const LogsView: Component<LogsViewProps> = (props) => {
const instance = () => instances().get(props.instanceId) const instance = () => instances().get(props.instanceId)
const logs = () => instance()?.logs ?? [] const logs = () => instance()?.logs ?? []
let renderedCount = 0
let initialSyncDone = false
let emptyStateEl: HTMLDivElement | null = null
onMount(() => { onMount(() => {
if (scrollRef && savedState) { if (scrollRef && savedState) {
scrollRef.scrollTop = savedState.scrollTop scrollRef.scrollTop = savedState.scrollTop
@@ -36,6 +32,11 @@ const LogsView: Component<LogsViewProps> = (props) => {
} }
}) })
createEffect(() => {
if (autoScroll() && scrollRef && logs().length > 0) {
scrollRef.scrollTop = scrollRef.scrollHeight
}
})
const handleScroll = () => { const handleScroll = () => {
if (!scrollRef) return if (!scrollRef) return
@@ -75,78 +76,6 @@ const LogsView: Component<LogsViewProps> = (props) => {
} }
} }
const createLogElement = (entry: LogEntry) => {
const row = document.createElement("div")
row.className = "log-entry"
const timestamp = document.createElement("span")
timestamp.className = "log-timestamp"
timestamp.textContent = formatTime(entry.timestamp)
const message = document.createElement("span")
message.className = `log-message ${getLevelColor(entry.level)}`
message.textContent = entry.message
row.append(timestamp, message)
return row
}
createEffect(() => {
const entries = logs()
if (!scrollRef) return
if (entries.length < renderedCount) {
scrollRef.innerHTML = ""
renderedCount = 0
initialSyncDone = false
if (emptyStateEl && emptyStateEl.parentElement) {
emptyStateEl.parentElement.removeChild(emptyStateEl)
}
}
if (entries.length === 0) {
renderedCount = 0
if (!emptyStateEl) {
emptyStateEl = document.createElement("div")
emptyStateEl.className = "log-empty-state"
emptyStateEl.textContent = "Waiting for server output..."
}
if (emptyStateEl.parentElement !== scrollRef) {
scrollRef.appendChild(emptyStateEl)
}
return
}
if (emptyStateEl && emptyStateEl.parentElement === scrollRef) {
scrollRef.removeChild(emptyStateEl)
}
for (let i = renderedCount; i < entries.length; i++) {
const entry = entries[i]
scrollRef.appendChild(createLogElement(entry))
}
renderedCount = entries.length
if (!initialSyncDone) {
if (savedState) {
const maxScrollTop = Math.max(scrollRef.scrollHeight - scrollRef.clientHeight, 0)
const target = Math.min(savedState.scrollTop, maxScrollTop)
scrollRef.scrollTop = target
setAutoScroll(savedState.autoScroll)
} else {
scrollRef.scrollTop = scrollRef.scrollHeight
setAutoScroll(true)
}
initialSyncDone = true
return
}
if (autoScroll()) {
scrollRef.scrollTop = scrollRef.scrollHeight
}
})
return ( return (
<div class="log-container"> <div class="log-container">
<div class="log-header"> <div class="log-header">
@@ -178,7 +107,21 @@ const LogsView: Component<LogsViewProps> = (props) => {
ref={scrollRef} ref={scrollRef}
onScroll={handleScroll} onScroll={handleScroll}
class="log-content" class="log-content"
></div> >
<Show
when={logs().length > 0}
fallback={<div class="log-empty-state">Waiting for server output...</div>}
>
<For each={logs()}>
{(entry) => (
<div class="log-entry">
<span class="log-timestamp">{formatTime(entry.timestamp)}</span>
<span class={`log-message ${getLevelColor(entry.level)}`}>{entry.message}</span>
</div>
)}
</For>
</Show>
</div>
<Show when={!autoScroll()}> <Show when={!autoScroll()}>
<button <button

View File

@@ -152,26 +152,42 @@ const SessionList: Component<SessionListProps> = (props) => {
}) })
const sessionSections = createMemo(() => { const sessionSections = createMemo(() => {
const parentIds: string[] = [] const parentItems: SessionListItem[] = []
const childIds: string[] = [] const childItems: SessionListItem[] = []
for (const [id, session] of props.sessions.entries()) { for (const [id, session] of props.sessions.entries()) {
const item: SessionListItem = {
id,
title: session.title || "Untitled",
isActive: id === props.activeSessionId,
isParent: session.parentId === null,
onSelect: () => props.onSelect(id),
onClose: session.parentId === null ? () => props.onClose(id) : undefined,
}
if (session.parentId === null) { if (session.parentId === null) {
parentIds.push(id) parentItems.push(item)
} else { } else {
childIds.push(id) childItems.push(item)
} }
} }
childIds.sort((a, b) => { childItems.sort((a, b) => {
const sessionA = props.sessions.get(a) const sessionA = props.sessions.get(a.id)
const sessionB = props.sessions.get(b) const sessionB = props.sessions.get(b.id)
if (!sessionA || !sessionB) return 0 if (!sessionA || !sessionB) return 0
return sessionB.time.updated - sessionA.time.updated return sessionB.time.updated - sessionA.time.updated
}) })
parentIds.push("info") parentItems.push({
return { parentIds, childIds } id: "info",
title: "Info",
isSpecial: true,
isActive: props.activeSessionId === "info",
onSelect: () => props.onSelect("info"),
})
return { parentItems, childItems }
}) })
return ( return (
@@ -205,48 +221,30 @@ const SessionList: Component<SessionListProps> = (props) => {
<div class="session-section-header px-3 py-2 text-xs font-semibold text-primary/70 uppercase tracking-wide"> <div class="session-section-header px-3 py-2 text-xs font-semibold text-primary/70 uppercase tracking-wide">
User Session & Info User Session & Info
</div> </div>
<For each={sessionSections().parentIds}> <For each={sessionSections().parentItems}>
{(id) => { {(item) => (
if (id === "info") {
return (
<div class="session-list-item group"> <div class="session-list-item group">
<button <button
class={`session-item-base ${ class={`session-item-base ${
props.activeSessionId === "info" ? "session-item-active" : "session-item-inactive" item.isActive ? "session-item-active" : "session-item-inactive"
} session-item-special`} } ${item.isSpecial ? "session-item-special" : ""}`}
onClick={() => props.onSelect("info")} onClick={item.onSelect}
title="Info" title={item.title}
role="button" role="button"
aria-selected={props.activeSessionId === "info"} aria-selected={item.isActive}
> >
<Show when={item.isSpecial} fallback={<MessageSquare class="w-4 h-4 flex-shrink-0" />}>
<Info class="w-4 h-4 flex-shrink-0" /> <Info class="w-4 h-4 flex-shrink-0" />
<span class="session-item-title truncate">Info</span> </Show>
</button>
</div>
)
}
const session = props.sessions.get(id) <span class="session-item-title truncate">{item.title}</span>
if (!session) return null
return ( <Show when={!item.isSpecial && item.onClose}>
<div class="session-list-item group">
<button
class={`session-item-base ${
id === props.activeSessionId ? "session-item-active" : "session-item-inactive"
}`}
onClick={() => props.onSelect(id)}
title={session.title || "Untitled"}
role="button"
aria-selected={id === props.activeSessionId}
>
<MessageSquare class="w-4 h-4 flex-shrink-0" />
<span class="session-item-title truncate">{session.title || "Untitled"}</span>
<span <span
class="session-item-close opacity-0 group-hover:opacity-100 hover:bg-status-error hover:text-white rounded p-0.5 transition-all" class="session-item-close opacity-0 group-hover:opacity-100 hover:bg-status-error hover:text-white rounded p-0.5 transition-all"
onClick={(event) => { onClick={(event) => {
event.stopPropagation() event.stopPropagation()
props.onClose(id) item.onClose?.()
}} }}
role="button" role="button"
tabIndex={0} tabIndex={0}
@@ -254,40 +252,51 @@ const SessionList: Component<SessionListProps> = (props) => {
> >
<X class="w-3 h-3" /> <X class="w-3 h-3" />
</span> </span>
</Show>
</button> </button>
</div> </div>
) )}
}}
</For> </For>
</div> </div>
<Show when={sessionSections().childIds.length > 0}> <Show when={sessionSections().childItems.length > 0}>
<div class="session-section"> <div class="session-section">
<div class="session-section-header px-3 py-2 text-xs font-semibold text-primary/70 uppercase tracking-wide"> <div class="session-section-header px-3 py-2 text-xs font-semibold text-primary/70 uppercase tracking-wide">
Agent Sessions Agent Sessions
</div> </div>
<For each={sessionSections().childIds}> <For each={sessionSections().childItems}>
{(id) => { {(item) => (
const session = props.sessions.get(id)
if (!session) return null
return (
<div class="session-list-item group"> <div class="session-list-item group">
<button <button
class={`session-item-base ${ class={`session-item-base ${
id === props.activeSessionId ? "session-item-active" : "session-item-inactive" item.isActive ? "session-item-active" : "session-item-inactive"
}`} } ${item.isSpecial ? "session-item-special" : ""}`}
onClick={() => props.onSelect(id)} onClick={item.onSelect}
title={session.title || "Untitled"} title={item.title}
role="button" role="button"
aria-selected={id === props.activeSessionId} aria-selected={item.isActive}
> >
<MessageSquare class="w-4 h-4 flex-shrink-0" /> <MessageSquare class="w-4 h-4 flex-shrink-0" />
<span class="session-item-title truncate">{session.title || "Untitled"}</span>
<span class="session-item-title truncate">{item.title}</span>
<Show when={!item.isSpecial && item.onClose}>
<span
class="session-item-close opacity-0 group-hover:opacity-100 hover:bg-status-error hover:text-white rounded p-0.5 transition-all"
onClick={(event) => {
event.stopPropagation()
item.onClose?.()
}}
role="button"
tabIndex={0}
aria-label="Close session"
>
<X class="w-3 h-3" />
</span>
</Show>
</button> </button>
</div> </div>
) )}
}}
</For> </For>
</div> </div>
</Show> </Show>