Skip to content

Commit 8176baf

Browse files
authored
chore(app): solidjs refactoring (#13399)
1 parent 0a3a321 commit 8176baf

15 files changed

Lines changed: 942 additions & 308 deletions

packages/app/create-effect-simplification-spec.md

Lines changed: 515 additions & 0 deletions
Large diffs are not rendered by default.

packages/app/src/components/file-tree.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,6 @@ export default function FileTree(props: {
325325
),
326326
)
327327

328-
createEffect(() => {
329-
const dir = file.tree.state(props.path)
330-
if (!shouldListExpanded({ level, dir })) return
331-
void file.tree.list(props.path)
332-
})
333-
334328
const nodes = createMemo(() => {
335329
const nodes = file.tree.children(props.path)
336330
const current = filter()

packages/app/src/components/prompt-input.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
591591
setActive: setSlashActive,
592592
onInput: slashOnInput,
593593
onKeyDown: slashOnKeyDown,
594-
refetch: slashRefetch,
595594
} = useFilteredList<SlashCommand>({
596595
items: slashCommands,
597596
key: (x) => x?.id,
@@ -648,14 +647,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
648647
}
649648
}
650649

651-
createEffect(
652-
on(
653-
() => sync.data.command,
654-
() => slashRefetch(),
655-
{ defer: true },
656-
),
657-
)
658-
659650
// Auto-scroll active command into view when navigating with keyboard
660651
createEffect(() => {
661652
const activeId = slashActive()

packages/app/src/components/session/session-header.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -306,11 +306,10 @@ export function SessionHeader() {
306306
const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
307307
const opening = createMemo(() => openRequest.app !== undefined)
308308

309-
createEffect(() => {
310-
const value = prefs.app
311-
if (options().some((o) => o.id === value)) return
312-
setPrefs("app", options()[0]?.id ?? "finder")
313-
})
309+
const selectApp = (app: OpenApp) => {
310+
if (!options().some((item) => item.id === app)) return
311+
setPrefs("app", app)
312+
}
314313

315314
const openDir = (app: OpenApp) => {
316315
if (opening() || !canOpen() || !platform.openPath) return
@@ -458,7 +457,7 @@ export function SessionHeader() {
458457
value={current().id}
459458
onChange={(value) => {
460459
if (!OPEN_APPS.includes(value as OpenApp)) return
461-
setPrefs("app", value as OpenApp)
460+
selectApp(value as OpenApp)
462461
}}
463462
>
464463
<For each={options()}>

packages/app/src/components/terminal.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type HexColor, resolveThemeVariant, useTheme, withAlpha } from "@opencode-ai/ui/theme"
22
import { showToast } from "@opencode-ai/ui/toast"
33
import type { FitAddon, Ghostty, Terminal as Term } from "ghostty-web"
4-
import { type ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
4+
import { type ComponentProps, createEffect, createMemo, onCleanup, onMount, splitProps } from "solid-js"
55
import { SerializeAddon } from "@/addons/serialize"
66
import { matchKeybind, parseKeybind } from "@/context/command"
77
import { useLanguage } from "@/context/language"
@@ -219,7 +219,7 @@ export const Terminal = (props: TerminalProps) => {
219219
}
220220
}
221221

222-
const [terminalColors, setTerminalColors] = createSignal<TerminalColors>(getTerminalColors())
222+
const terminalColors = createMemo(getTerminalColors)
223223

224224
const scheduleFit = () => {
225225
if (disposed) return
@@ -259,8 +259,7 @@ export const Terminal = (props: TerminalProps) => {
259259
}
260260

261261
createEffect(() => {
262-
const colors = getTerminalColors()
263-
setTerminalColors(colors)
262+
const colors = terminalColors()
264263
if (!term) return
265264
setOptionIfSupported(term, "theme", colors)
266265
})

packages/app/src/context/global-sync.tsx

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { showToast } from "@opencode-ai/ui/toast"
1111
import { getFilename } from "@opencode-ai/util/path"
1212
import {
1313
createContext,
14-
createEffect,
1514
getOwner,
1615
Match,
1716
onCleanup,
@@ -35,7 +34,6 @@ import { trimSessions } from "./global-sync/session-trim"
3534
import type { ProjectMeta } from "./global-sync/types"
3635
import { SESSION_RECENT_LIMIT } from "./global-sync/types"
3736
import { sanitizeProject } from "./global-sync/utils"
38-
import { usePlatform } from "./platform"
3937
import { formatServerError } from "@/utils/server-errors"
4038

4139
type GlobalStore = {
@@ -54,7 +52,6 @@ type GlobalStore = {
5452

5553
function createGlobalSync() {
5654
const globalSDK = useGlobalSDK()
57-
const platform = usePlatform()
5855
const language = useLanguage()
5956
const owner = getOwner()
6057
if (!owner) throw new Error("GlobalSync must be created within owner")
@@ -64,7 +61,7 @@ function createGlobalSync() {
6461
const sessionLoads = new Map<string, Promise<void>>()
6562
const sessionMeta = new Map<string, { limit: number }>()
6663

67-
const [projectCache, setProjectCache, , projectCacheReady] = persisted(
64+
const [projectCache, setProjectCache, projectInit] = persisted(
6865
Persist.global("globalSync.project", ["globalSync.project.v1"]),
6966
createStore({ value: [] as Project[] }),
7067
)
@@ -80,6 +77,57 @@ function createGlobalSync() {
8077
reload: undefined,
8178
})
8279

80+
let active = true
81+
let projectWritten = false
82+
83+
onCleanup(() => {
84+
active = false
85+
})
86+
87+
const cacheProjects = () => {
88+
setProjectCache(
89+
"value",
90+
untrack(() => globalStore.project.map(sanitizeProject)),
91+
)
92+
}
93+
94+
const setProjects = (next: Project[] | ((draft: Project[]) => void)) => {
95+
projectWritten = true
96+
if (typeof next === "function") {
97+
setGlobalStore("project", produce(next))
98+
cacheProjects()
99+
return
100+
}
101+
setGlobalStore("project", next)
102+
cacheProjects()
103+
}
104+
105+
const setBootStore = ((...input: unknown[]) => {
106+
if (input[0] === "project" && Array.isArray(input[1])) {
107+
setProjects(input[1] as Project[])
108+
return input[1]
109+
}
110+
return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
111+
}) as typeof setGlobalStore
112+
113+
const set = ((...input: unknown[]) => {
114+
if (input[0] === "project" && (Array.isArray(input[1]) || typeof input[1] === "function")) {
115+
setProjects(input[1] as Project[] | ((draft: Project[]) => void))
116+
return input[1]
117+
}
118+
return (setGlobalStore as (...args: unknown[]) => unknown)(...input)
119+
}) as typeof setGlobalStore
120+
121+
if (projectInit instanceof Promise) {
122+
void projectInit.then(() => {
123+
if (!active) return
124+
if (projectWritten) return
125+
const cached = projectCache.value
126+
if (cached.length === 0) return
127+
setGlobalStore("project", cached)
128+
})
129+
}
130+
83131
const setSessionTodo = (sessionID: string, todos: Todo[] | undefined) => {
84132
if (!sessionID) return
85133
if (!todos) {
@@ -127,30 +175,6 @@ function createGlobalSync() {
127175
return sdk
128176
}
129177

130-
createEffect(() => {
131-
if (!projectCacheReady()) return
132-
if (globalStore.project.length !== 0) return
133-
const cached = projectCache.value
134-
if (cached.length === 0) return
135-
setGlobalStore("project", cached)
136-
})
137-
138-
createEffect(() => {
139-
if (!projectCacheReady()) return
140-
const projects = globalStore.project
141-
if (projects.length === 0) {
142-
const cachedLength = untrack(() => projectCache.value.length)
143-
if (cachedLength !== 0) return
144-
}
145-
setProjectCache("value", projects.map(sanitizeProject))
146-
})
147-
148-
createEffect(() => {
149-
if (globalStore.reload !== "complete") return
150-
setGlobalStore("reload", undefined)
151-
queue.refresh()
152-
})
153-
154178
async function loadSessions(directory: string) {
155179
const pending = sessionLoads.get(directory)
156180
if (pending) return pending
@@ -259,13 +283,7 @@ function createGlobalSync() {
259283
event,
260284
project: globalStore.project,
261285
refresh: queue.refresh,
262-
setGlobalProject(next) {
263-
if (typeof next === "function") {
264-
setGlobalStore("project", produce(next))
265-
return
266-
}
267-
setGlobalStore("project", next)
268-
},
286+
setGlobalProject: setProjects,
269287
})
270288
if (event.type === "server.connected" || event.type === "global.disposed") {
271289
for (const directory of Object.keys(children.children)) {
@@ -316,7 +334,7 @@ function createGlobalSync() {
316334
unknownError: language.t("error.chain.unknown"),
317335
invalidConfigurationError: language.t("error.server.invalidConfiguration"),
318336
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
319-
setGlobalStore,
337+
setGlobalStore: setBootStore,
320338
})
321339
}
322340

@@ -340,7 +358,9 @@ function createGlobalSync() {
340358
.update({ config })
341359
.then(bootstrap)
342360
.then(() => {
343-
setGlobalStore("reload", "complete")
361+
queue.refresh()
362+
setGlobalStore("reload", undefined)
363+
queue.refresh()
344364
})
345365
.catch((error) => {
346366
setGlobalStore("reload", undefined)
@@ -350,7 +370,7 @@ function createGlobalSync() {
350370

351371
return {
352372
data: globalStore,
353-
set: setGlobalStore,
373+
set,
354374
get ready() {
355375
return globalStore.ready
356376
},

packages/app/src/context/global-sync/child-store.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createRoot, createEffect, getOwner, onCleanup, runWithOwner, type Accessor, type Owner } from "solid-js"
1+
import { createRoot, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
22
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
33
import { Persist, persisted } from "@/utils/persist"
44
import type { VcsInfo } from "@opencode-ai/sdk/v2/client"
@@ -131,8 +131,7 @@ export function createChildStoreManager(input: {
131131
)
132132
if (!vcs) throw new Error("Failed to create persisted cache")
133133
const vcsStore = vcs[0]
134-
const vcsReady = vcs[3]
135-
vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcsReady })
134+
vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcs[3] })
136135

137136
const meta = runWithOwner(input.owner, () =>
138137
persisted(
@@ -154,10 +153,12 @@ export function createChildStoreManager(input: {
154153

155154
const init = () =>
156155
createRoot((dispose) => {
156+
const initialMeta = meta[0].value
157+
const initialIcon = icon[0].value
157158
const child = createStore<State>({
158159
project: "",
159-
projectMeta: meta[0].value,
160-
icon: icon[0].value,
160+
projectMeta: initialMeta,
161+
icon: initialIcon,
161162
provider: { all: [], connected: [], default: {} },
162163
config: {},
163164
path: { state: "", config: "", worktree: "", directory: "", home: "" },
@@ -181,16 +182,27 @@ export function createChildStoreManager(input: {
181182
children[directory] = child
182183
disposers.set(directory, dispose)
183184

184-
createEffect(() => {
185-
if (!vcsReady()) return
185+
const onPersistedInit = (init: Promise<string> | string | null, run: () => void) => {
186+
if (!(init instanceof Promise)) return
187+
void init.then(() => {
188+
if (children[directory] !== child) return
189+
run()
190+
})
191+
}
192+
193+
onPersistedInit(vcs[2], () => {
186194
const cached = vcsStore.value
187195
if (!cached?.branch) return
188196
child[1]("vcs", (value) => value ?? cached)
189197
})
190-
createEffect(() => {
198+
199+
onPersistedInit(meta[2], () => {
200+
if (child[0].projectMeta !== initialMeta) return
191201
child[1]("projectMeta", meta[0].value)
192202
})
193-
createEffect(() => {
203+
204+
onPersistedInit(icon[2], () => {
205+
if (child[0].icon !== initialIcon) return
194206
child[1]("icon", icon[0].value)
195207
})
196208
})

0 commit comments

Comments
 (0)