Compare commits

...

2 Commits

Author SHA1 Message Date
Brendan Allan ea2db98dbe Merge branch 'dev' into persisted-suspense 2026-07-07 14:08:32 +08:00
Brendan Allan 6910eee7fb refactor(app): add suspense support to persisted 2026-06-29 08:51:29 +08:00
30 changed files with 269 additions and 269 deletions
+3 -3
View File
@@ -72,7 +72,7 @@ const SessionRoute = () => {
return (
<Show when={tabs.ready()}>
{(_) => {
const persisted = tabs.store.filter((item) => item.type === "session")
const persisted = tabs.store().filter((item) => item.type === "session")
return <Navigate href={sessionHref(legacySessionServer(persisted, sessionID, server.key), sessionID)} />
}}
</Show>
@@ -140,7 +140,7 @@ function DraftRoute() {
return (
<Show when={tabs.ready()}>
<Show
when={tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)}
when={tabs.store().find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)}
keyed
fallback={<Navigate href="/" />}
>
@@ -568,7 +568,7 @@ function LegacyTargetSessionRoute() {
<Navigate
href={sessionHref(
legacySessionServer(
tabs.store.filter((item) => item.type === "session"),
tabs.store().filter((item) => item.type === "session"),
params.id,
server.key,
),
+6 -5
View File
@@ -11,6 +11,7 @@ import {
createResource,
Switch,
Match,
type Accessor,
type JSX,
} from "solid-js"
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
@@ -117,21 +118,21 @@ export type PromptInputControls = {
export function createPromptInputHistory(): PromptInputHistory {
const [normal, setNormal] = createStore<PromptHistoryState>({ entries: [] })
const [shell, setShell] = createStore<PromptHistoryState>({ entries: [] })
return createPromptInputHistoryStore(normal, setNormal, shell, setShell)
return createPromptInputHistoryStore(() => normal, setNormal, () => shell, setShell)
}
type PromptHistoryState = { entries: PromptHistoryStoredEntry[] }
function createPromptInputHistoryStore(
normal: Store<PromptHistoryState>,
normal: Accessor<PromptHistoryState>,
setNormal: SetStoreFunction<PromptHistoryState>,
shell: Store<PromptHistoryState>,
shell: Accessor<PromptHistoryState>,
setShell: SetStoreFunction<PromptHistoryState>,
): PromptInputHistory {
return {
entries: (mode) => (mode === "shell" ? shell.entries : normal.entries),
entries: (mode) => (mode === "shell" ? shell().entries : normal().entries),
add(prompt, mode, comments) {
const current = mode === "shell" ? shell : normal
const current = mode === "shell" ? shell() : normal()
const setCurrent = mode === "shell" ? setShell : setNormal
const next = prependHistoryEntry(current.entries, prompt, comments)
if (next === current.entries) return
@@ -229,7 +229,7 @@ export function SessionHeader() {
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
const current = createMemo(
() =>
options().find((o) => o.id === prefs.app) ??
options().find((o) => o.id === prefs().app) ??
options()[0] ??
({ id: "finder", label: fileManager().label, icon: fileManager().icon } as const),
)
@@ -40,7 +40,7 @@ function SessionTabSlot(props: {
let ref!: HTMLDivElement
const sdk = createMemo(() => props.serverCtx()?.sdk ?? null)
const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId))
const persisted = createMemo(() => tabs.info[props.id])
const persisted = createMemo(() => tabs.info()[props.id])
const [loadedSession] = createResource(
() => {
const ctx = props.serverCtx()
+12 -12
View File
@@ -275,10 +275,10 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
const matchRoute = (route: LayoutRoute) => {
if (route.type === "home") return
if (route.type === "draft") {
return tabsStore.find((item) => item.type === "draft" && item.draftID === route.draftID)
return tabsStore().find((item) => item.type === "draft" && item.draftID === route.draftID)
}
if (route.type === "session") {
const main = tabsStore.find(
const main = tabsStore().find(
(item) =>
item.type === "session" && item.server === route.server && item.sessionId === route.sessionId,
)
@@ -286,7 +286,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
const s = session()
if (s?.parentID) {
const parentID = s.parentID
const parent = tabsStore.find(
const parent = tabsStore().find(
(item) => item.type === "session" && item.server === route.server && item.sessionId === parentID,
)
if (parent) return parent
@@ -395,7 +395,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
keybind: "mod+w",
hidden: true,
onSelect: () => {
tabsStoreActions.closeTab(tabsStore.findIndex((tab) => current === tab))
tabsStoreActions.closeTab(tabsStore().findIndex((tab) => current === tab))
},
},
{
@@ -412,13 +412,13 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
keybind: `mod+option+ArrowLeft,ctrl+shift+tab`,
hidden: true,
onSelect: () => {
let index = tabsStore.findIndex((tab) => tab === currentTab())
let index = tabsStore().findIndex((tab) => tab === currentTab())
if (index === -1) return
index -= 1
if (index === -1) index = tabsStore.length - 1
if (index === -1) index = tabsStore().length - 1
const next = tabsStore[index]
const next = tabsStore()[index]
if (next) tabs.select(next)
},
},
@@ -429,13 +429,13 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
keybind: `mod+option+ArrowRight,ctrl+tab`,
hidden: true,
onSelect: () => {
let index = tabsStore.findIndex((tab) => tab === currentTab())
let index = tabsStore().findIndex((tab) => tab === currentTab())
if (index === -1) return
index += 1
if (index === tabsStore.length) index = 0
if (index === tabsStore().length) index = 0
const next = tabsStore[index]
const next = tabsStore()[index]
if (next) tabs.select(next)
},
},
@@ -482,7 +482,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
</TooltipV2>
<TitlebarTabStrip
tabs={tabsStore}
tabs={tabsStore()}
currentTab={currentTab}
forceTruncate={tabsAreOverflowing()}
onOverflowChange={setTabsAreOverflowing}
@@ -491,7 +491,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
el?.scrollIntoView({ behavior: "instant" })
}}
onClose={(tab) => {
const index = tabsStore.findIndex((item) => tabKey(item) === tabKey(tab))
const index = tabsStore().findIndex((item) => tabKey(item) === tabKey(tab))
if (index !== -1) tabsStoreActions.closeTab(index)
}}
onReorder={(keys) => tabsStoreActions.reorder(keys)}
+3 -3
View File
@@ -253,8 +253,8 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const warnedDuplicates = new Set<string>()
type CommandCatalog = Record<string, CommandCatalogItem>
const [catalog, setCatalog, _, catalogReady] = persisted(
Persist.global("command.catalog.v1"),
const [catalog, setCatalog, catalogReady] = persisted(
Persist.global("command.catalog().v1"),
createStore<CommandCatalog>({}),
)
@@ -416,7 +416,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const keybindConfig = (id: string) => {
if (id === PALETTE_ID) return settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND
const base = actionId(id)
return options().find((x) => actionId(x.id) === base)?.keybind ?? bind(base, catalog[base]?.keybind)
return options().find((x) => actionId(x.id) === base)?.keybind ?? bind(base, catalog()[base]?.keybind)
}
return {
+2 -2
View File
@@ -181,13 +181,13 @@ export function createCommentSessionForTest(comments: Record<string, LineComment
function createCommentSession(scope: ServerScope, dir: string, id: string | undefined) {
const legacy = `${dir}/comments${id ? "/" + id : ""}.v1`
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
Persist.serverScoped(scope, dir, id, "comments", [legacy]),
createStore<CommentStore>({
comments: {},
}),
)
const session = createCommentSessionState(store, setStore)
const session = createCommentSessionState(store(), setStore)
return {
ready,
+5 -5
View File
@@ -37,7 +37,7 @@ function equalSelectedLines(a: SelectedLineRange | null | undefined, b: Selected
function createViewSession(scope: ServerScope, dir: string, id: string | undefined) {
const legacyViewKey = `${dir}/file${id ? "/" + id : ""}.v1`
const [view, setView, _, ready] = persisted(
const [view, setView, ready] = persisted(
Persist.serverScoped(scope, dir, id, "file-view", [legacyViewKey]),
createStore<{
file: Record<string, FileViewState>
@@ -49,7 +49,7 @@ function createViewSession(scope: ServerScope, dir: string, id: string | undefin
const meta = { pruned: false }
const pruneView = (keep?: string) => {
const keys = Object.keys(view.file)
const keys = Object.keys(view().file)
if (keys.length <= MAX_VIEW_FILES) return
const drop = keys.filter((key) => key !== keep).slice(0, keys.length - MAX_VIEW_FILES)
@@ -71,9 +71,9 @@ function createViewSession(scope: ServerScope, dir: string, id: string | undefin
pruneView()
})
const scrollTop = (path: string) => view.file[path]?.scrollTop
const scrollLeft = (path: string) => view.file[path]?.scrollLeft
const selectedLines = (path: string) => view.file[path]?.selectedLines
const scrollTop = (path: string) => view().file[path]?.scrollTop
const scrollLeft = (path: string) => view().file[path]?.scrollLeft
const selectedLines = (path: string) => view().file[path]?.selectedLines
const setScrollTop = (path: string, top: number) => {
setView(
@@ -9,9 +9,8 @@ import { ServerScope } from "@/utils/server-scope"
let createChildStoreManager: typeof import("./child-store").createChildStoreManager
const querySingles: Array<() => { queryKey?: unknown[]; enabled?: boolean }> = []
const persist: typeof import("@/utils/persist").persisted = (_target, store) => [
store[0],
() => store[0],
store[1],
null,
Object.assign(() => true, { promise: undefined }),
]
@@ -1,4 +1,4 @@
import { createRoot, createSignal, getOwner, onCleanup, runWithOwner, type Owner } from "solid-js"
import { createRoot, createSignal, getOwner, onCleanup, runWithOwner, type Accessor, type Owner } from "solid-js"
import { createStore, type SetStoreFunction, type Store } from "solid-js/store"
import { Persist, persisted } from "@/utils/persist"
import type { VcsInfo } from "@opencode-ai/sdk/v2/client"
@@ -157,7 +157,7 @@ export function createChildStoreManager(input: {
)
if (!vcs) throw new Error(input.translate("error.childStore.persistedCacheCreateFailed"))
const vcsStore = vcs[0]
vcsCache.set(key, { store: vcsStore, setStore: vcs[1], ready: vcs[3] })
vcsCache.set(key, { store: vcsStore, setStore: vcs[1], ready: vcs[2] })
const meta = runWithOwner(input.owner, () =>
input.persist(
@@ -166,7 +166,7 @@ export function createChildStoreManager(input: {
),
)
if (!meta) throw new Error(input.translate("error.childStore.persistedProjectMetadataCreateFailed"))
metaCache.set(key, { store: meta[0], setStore: meta[1], ready: meta[3] })
metaCache.set(key, { store: meta[0], setStore: meta[1], ready: meta[2] })
const icon = runWithOwner(input.owner, () =>
input.persist(
@@ -175,12 +175,12 @@ export function createChildStoreManager(input: {
),
)
if (!icon) throw new Error(input.translate("error.childStore.persistedProjectIconCreateFailed"))
iconCache.set(key, { store: icon[0], setStore: icon[1], ready: icon[3] })
iconCache.set(key, { store: icon[0], setStore: icon[1], ready: icon[2] })
const init = () =>
createRoot((dispose) => {
const initialMeta = meta[0].value
const initialIcon = icon[0].value
const initialMeta = meta[0]().value
const initialIcon = icon[0]().value
const [mcpEnabled, setMcpEnabled] = createSignal(false)
const pathQuery = useQuery(() => input.queryOptions.path(key))
@@ -241,7 +241,7 @@ export function createChildStoreManager(input: {
get lsp() {
return lspQuery.isLoading ? [] : (lspQuery.data ?? [])
},
vcs: vcsStore.value,
vcs: vcsStore().value,
limit: 5,
message: {},
part: {},
@@ -251,28 +251,28 @@ export function createChildStoreManager(input: {
disposers.set(key, dispose)
mcpToggles.set(key, setMcpEnabled)
const onPersistedInit = (init: Promise<string> | string | null, run: () => void) => {
if (!(init instanceof Promise)) return
void init.then(() => {
const onPersistedInit = (ready: Accessor<boolean> & { promise: undefined | Promise<any> }, run: () => void) => {
if (!ready.promise) return
void ready.promise.then(() => {
if (children[key] !== child) return
run()
})
}
onPersistedInit(vcs[2], () => {
const cached = vcsStore.value
const cached = vcsStore().value
if (!cached?.branch) return
child[1]("vcs", (value) => value ?? cached)
})
onPersistedInit(meta[2], () => {
if (child[0].projectMeta !== initialMeta) return
child[1]("projectMeta", meta[0].value)
child[1]("projectMeta", meta[0]().value)
})
onPersistedInit(icon[2], () => {
if (child[0].icon !== initialIcon) return
child[1]("icon", icon[0].value)
child[1]("icon", icon[0]().value)
})
})
@@ -549,7 +549,7 @@ describe("applyDirectoryEvent", () => {
directory: "/tmp",
loadLsp() {},
vcsCache: {
store: cacheStore,
store: () => cacheStore,
setStore: setCacheStore,
ready: () => true,
},
@@ -19,7 +19,7 @@ import type {
} from "@opencode-ai/sdk/v2/client"
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
import type { Accessor } from "solid-js"
import type { SetStoreFunction, Store } from "solid-js/store"
import type { SetStoreFunction } from "solid-js/store"
export type ProjectMeta = {
name?: string
@@ -85,19 +85,19 @@ export type State = {
}
export type VcsCache = {
store: Store<{ value: VcsInfo | undefined }>
store: Accessor<{ value: VcsInfo | undefined }>
setStore: SetStoreFunction<{ value: VcsInfo | undefined }>
ready: Accessor<boolean>
}
export type MetaCache = {
store: Store<{ value: ProjectMeta | undefined }>
store: Accessor<{ value: ProjectMeta | undefined }>
setStore: SetStoreFunction<{ value: ProjectMeta | undefined }>
ready: Accessor<boolean>
}
export type IconCache = {
store: Store<{ value: string | undefined }>
store: Accessor<{ value: string | undefined }>
setStore: SetStoreFunction<{ value: string | undefined }>
ready: Accessor<boolean>
}
+3 -3
View File
@@ -144,7 +144,7 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple
const platform = usePlatform()
const dialog = useDialog()
const settings = useSettings()
const [store, setStore, _, ready] = persisted("highlights.v1", createStore<Store>({ version: undefined }))
const [store, setStore, ready] = persisted("highlights.v1", createStore<Store>({ version: undefined }))
const [range, setRange] = createStore({
from: undefined as string | undefined,
@@ -208,7 +208,7 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple
if (!platform.version) return
state.started = true
const previous = store.version
const previous = store().version
if (!previous) {
setStore("version", platform.version)
return
@@ -225,7 +225,7 @@ export const { use: useHighlights, provider: HighlightsProvider } = createSimple
from: () => range.from,
to: () => range.to,
get last() {
return store.version
return store().version
},
markSeen,
}
+2 -2
View File
@@ -201,14 +201,14 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
gate: false,
init: (props: { locale?: Locale }) => {
const initial = props.locale ?? readStoredLocale() ?? detectLocale()
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
Persist.global("language", ["language.v1"]),
createStore({
locale: initial,
}),
)
const locale = createMemo<Locale>(() => normalizeLocale(store.locale))
const locale = createMemo<Locale>(() => normalizeLocale(store().locale))
const intl = createMemo(() => INTL[locale()])
const [dict] = createResource(locale, loadDict, {
+45 -45
View File
@@ -171,7 +171,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
if (value.type === "home") return value
if (value.server) return value
if (value.type === "draft") {
const draft = tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === value.draftID)
const draft = tabs.store().find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === value.draftID)
if (draft) return { ...value, server: draft.server }
}
return { ...value, server: server.key }
@@ -268,7 +268,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
}
const target = Persist.serverGlobal(serverSdk().scope, "layout", ["layout.v6"])
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
{ ...target, migrate },
createStore({
sidebar: {
@@ -350,8 +350,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
keep,
max: MAX_SESSION_KEYS,
used: usage.used,
view: Object.keys(store.sessionView),
tabs: Object.keys(store.sessionTabs),
view: Object.keys(store().sessionView),
tabs: Object.keys(store().sessionTabs),
})
if (drop.length === 0) return
@@ -385,9 +385,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
const scroll = createScrollPersistence({
debounceMs: 250,
getSnapshot: (sessionKey) => store.sessionView[sessionKey]?.scroll,
getSnapshot: (sessionKey) => store().sessionView[sessionKey]?.scroll,
onFlush: (sessionKey, next) => {
const current = store.sessionView[sessionKey]
const current = store().sessionView[sessionKey]
const keep = usage.active ?? sessionKey
if (!current) {
setStore("sessionView", sessionKey, { scroll: next })
@@ -597,18 +597,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
route,
ready,
home: {
selection: createMemo(() => store.home.selection),
selection: createMemo(() => store().home.selection),
setSelection(selection: HomeProjectSelection) {
setStore("home", "selection", reconcile(selection))
},
},
handoff: {
tabs: createMemo(() => store.handoff?.tabs),
tabs: createMemo(() => store().handoff?.tabs),
setTabs(dir: string, id: string) {
setStore("handoff", "tabs", { scope: serverSdk().scope, dir, id, at: Date.now() })
},
clearTabs() {
if (!store.handoff?.tabs) return
if (!store().handoff?.tabs) return
setStore("handoff", "tabs", undefined)
},
},
@@ -642,7 +642,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
},
sidebar: {
opened: createMemo(() => store.sidebar.opened),
opened: createMemo(() => store().sidebar.opened),
open() {
setStore("sidebar", "opened", true)
},
@@ -652,31 +652,31 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
toggle() {
setStore("sidebar", "opened", (x) => !x)
},
width: createMemo(() => store.sidebar.width),
width: createMemo(() => store().sidebar.width),
resize(width: number) {
setStore("sidebar", "width", width)
},
workspaces(directory: string) {
return () => store.sidebar.workspaces[directory] ?? store.sidebar.workspacesDefault ?? false
return () => store().sidebar.workspaces[directory] ?? store().sidebar.workspacesDefault ?? false
},
setWorkspaces(directory: string, value: boolean) {
setStore("sidebar", "workspaces", directory, value)
},
toggleWorkspaces(directory: string) {
const current = store.sidebar.workspaces[directory] ?? store.sidebar.workspacesDefault ?? false
const current = store().sidebar.workspaces[directory] ?? store().sidebar.workspacesDefault ?? false
setStore("sidebar", "workspaces", directory, !current)
},
},
terminal: {
height: createMemo(() => store.terminal.height),
height: createMemo(() => store().terminal.height),
resize(height: number) {
setStore("terminal", "height", height)
},
},
review: {
diffStyle: createMemo(() => store.review?.diffStyle ?? "split"),
diffStyle: createMemo(() => store().review?.diffStyle ?? "split"),
setDiffStyle(diffStyle: ReviewDiffStyle) {
if (!store.review) {
if (!store().review) {
setStore("review", { diffStyle, panelOpened: DEFAULT_REVIEW_PANEL_OPENED })
return
}
@@ -684,39 +684,39 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
},
fileTree: {
opened: createMemo(() => store.fileTree?.opened ?? true),
width: createMemo(() => store.fileTree?.width ?? DEFAULT_FILE_TREE_WIDTH),
tab: createMemo(() => store.fileTree?.tab ?? "changes"),
opened: createMemo(() => store().fileTree?.opened ?? true),
width: createMemo(() => store().fileTree?.width ?? DEFAULT_FILE_TREE_WIDTH),
tab: createMemo(() => store().fileTree?.tab ?? "changes"),
setTab(tab: "changes" | "all") {
if (!store.fileTree) {
if (!store().fileTree) {
setStore("fileTree", { opened: true, width: DEFAULT_FILE_TREE_WIDTH, tab })
return
}
setStore("fileTree", "tab", tab)
},
open() {
if (!store.fileTree) {
if (!store().fileTree) {
setStore("fileTree", { opened: true, width: DEFAULT_FILE_TREE_WIDTH, tab: "changes" })
return
}
setStore("fileTree", "opened", true)
},
close() {
if (!store.fileTree) {
if (!store().fileTree) {
setStore("fileTree", { opened: false, width: DEFAULT_FILE_TREE_WIDTH, tab: "changes" })
return
}
setStore("fileTree", "opened", false)
},
toggle() {
if (!store.fileTree) {
if (!store().fileTree) {
setStore("fileTree", { opened: true, width: DEFAULT_FILE_TREE_WIDTH, tab: "changes" })
return
}
setStore("fileTree", "opened", (x) => !x)
},
resize(width: number) {
if (!store.fileTree) {
if (!store().fileTree) {
setStore("fileTree", { opened: true, width, tab: "changes" })
return
}
@@ -724,9 +724,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
},
session: {
width: createMemo(() => store.session?.width ?? DEFAULT_SESSION_WIDTH),
width: createMemo(() => store().session?.width ?? DEFAULT_SESSION_WIDTH),
resize(width: number) {
if (!store.session) {
if (!store().session) {
setStore("session", { width })
return
}
@@ -734,7 +734,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
},
mobileSidebar: {
opened: createMemo(() => store.mobileSidebar?.opened ?? false),
opened: createMemo(() => store().mobileSidebar?.opened ?? false),
show() {
setStore("mobileSidebar", "opened", true)
},
@@ -749,7 +749,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
set(sessionKey: string, messageID: string) {
const at = Date.now()
touch(sessionKey)
const current = store.sessionView[sessionKey]
const current = store().sessionView[sessionKey]
if (!current) {
setStore("sessionView", sessionKey, {
scroll: {},
@@ -770,7 +770,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
)
},
consume(sessionKey: string) {
const current = store.sessionView[sessionKey]
const current = store().sessionView[sessionKey]
const message = current?.pendingMessage
const at = current?.pendingMessageAt
if (!message || !at) return
@@ -790,13 +790,13 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
view(sessionKey: string | Accessor<string>) {
const key = createSessionKeyReader(sessionKey, ensureKey)
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED)
const s = createMemo(() => store().sessionView[key()] ?? { scroll: {} })
const terminalOpened = createMemo(() => store().terminal?.opened ?? false)
const reviewPanelOpened = createMemo(() => store().review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED)
const reviewPanelSource = createMemo(() => (reviewPanelOpened() ? ephemeral.reviewPanelSource : "other"))
function setTerminalOpened(next: boolean) {
const current = store.terminal
const current = store().terminal
if (!current) {
setStore("terminal", { height: DEFAULT_TERMINAL_HEIGHT, opened: next })
return
@@ -809,7 +809,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
function setReviewPanelOpened(next: boolean, source: ReviewPanelSource) {
const nextSource = next ? source : "other"
const current = store.review
const current = store().review
if (!current) {
batch(() => {
setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
@@ -840,7 +840,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
get: () => s().todoCollapsed ?? false,
set(collapsed: boolean) {
const session = key()
const current = store.sessionView[session]
const current = store().sessionView[session]
if (!current) {
setStore("sessionView", session, { scroll: {}, todoCollapsed: collapsed })
} else {
@@ -878,7 +878,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setOpen(open: string[]) {
const session = key()
const next = Array.from(new Set(open))
const current = store.sessionView[session]
const current = store().sessionView[session]
if (!current) {
setStore("sessionView", session, {
scroll: {},
@@ -892,7 +892,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
openPath(path: string) {
const session = key()
const current = store.sessionView[session]
const current = store().sessionView[session]
if (!current) {
setStore("sessionView", session, {
scroll: {},
@@ -911,7 +911,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
closePath(path: string) {
const session = key()
const current = store.sessionView[session]?.reviewOpen
const current = store().sessionView[session]?.reviewOpen
if (!current) return
const index = current.indexOf(path)
@@ -928,7 +928,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
togglePath(path: string) {
const session = key()
const current = store.sessionView[session]?.reviewOpen
const current = store().sessionView[session]?.reviewOpen
if (!current || !current.includes(path)) {
this.openPath(path)
return
@@ -942,7 +942,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
tabs(sessionKey: string | Accessor<string>) {
const key = createSessionKeyReader(sessionKey, ensureKey)
const path = createMemo(() => sessionPath(key()))
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
const tabs = createMemo(() => store().sessionTabs[key()] ?? { all: [] })
const normalize = (tab: string) => normalizeSessionTab(path(), tab)
const normalizeAll = (all: string[]) => normalizeSessionTabList(path(), all)
return {
@@ -952,7 +952,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setActive(tab: string | undefined) {
const session = key()
const next = tab ? normalize(tab) : tab
if (!store.sessionTabs[session]) {
if (!store().sessionTabs[session]) {
setStore("sessionTabs", session, { all: [], active: next })
} else {
setStore("sessionTabs", session, "active", next)
@@ -961,7 +961,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setAll(all: string[]) {
const session = key()
const next = normalizeAll(all).filter((tab) => tab !== "review")
if (!store.sessionTabs[session]) {
if (!store().sessionTabs[session]) {
setStore("sessionTabs", session, { all: next, active: undefined })
} else {
setStore("sessionTabs", session, "all", next)
@@ -969,12 +969,12 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
async open(tab: string) {
const session = key()
const next = nextSessionTabsForOpen(store.sessionTabs[session], normalize(tab))
const next = nextSessionTabsForOpen(store().sessionTabs[session], normalize(tab))
setStore("sessionTabs", session, next)
},
close(tab: string) {
const session = key()
const current = store.sessionTabs[session]
const current = store().sessionTabs[session]
if (!current) return
if (tab === "review") {
@@ -998,7 +998,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
move(tab: string, to: number) {
const session = key()
const current = store.sessionTabs[session]
const current = store().sessionTabs[session]
if (!current) return
const index = current.all.findIndex((f) => f === tab)
if (index === -1) return
+3 -3
View File
@@ -125,7 +125,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const scope = createMemo<State | undefined>(() => {
const session = id()
if (!session) return store.draft ?? store.promoting
return saved.session[session] ?? handoff.get(handoffKey(serverSDK().scope, sdk().directory, session))
return saved().session[session] ?? handoff.get(handoffKey(serverSDK().scope, sdk().directory, session))
})
createEffect(() => {
@@ -135,7 +135,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const key = handoffKey(serverSDK().scope, sdk().directory, session)
const next = handoff.get(key)
if (!next) return
if (saved.session[session] !== undefined) {
if (saved().session[session] !== undefined) {
handoff.delete(key)
setStore("promoting", undefined)
return
@@ -395,7 +395,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const session = id()
if (!session) return
if (msg.sessionID !== session) return
if (saved.session[session] !== undefined) return
if (saved().session[session] !== undefined) return
if (handoff.has(handoffKey(serverSDK().scope, sdk().directory, session))) return
setSaved("session", session, {
+8 -8
View File
@@ -28,7 +28,7 @@ export const { use: useModels, provider: ModelsProvider } = createSimpleContext(
init: (props: { directory?: Accessor<string | undefined> } = {}) => {
const providers = useProviders(props.directory)
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
Persist.global("model", ["model.v1"]),
createStore<Store>({
user: [],
@@ -89,7 +89,7 @@ export const { use: useModels, provider: ModelsProvider } = createSimpleContext(
const visibility = createMemo(() => {
const map = new Map<string, Visibility>()
for (const item of store.user) map.set(`${item.providerID}:${item.modelID}`, item.visibility)
for (const item of store().user) map.set(`${item.providerID}:${item.modelID}`, item.visibility)
return map
})
@@ -104,12 +104,12 @@ export const { use: useModels, provider: ModelsProvider } = createSimpleContext(
const find = (key: ModelKey) => list().find((m) => m.id === key.modelID && m.provider.id === key.providerID)
function update(model: ModelKey, state: Visibility) {
const index = store.user.findIndex((x) => x.modelID === model.modelID && x.providerID === model.providerID)
const index = store().user.findIndex((x) => x.modelID === model.modelID && x.providerID === model.providerID)
if (index >= 0) {
setStore("user", index, (current) => ({ ...current, visibility: state }))
return
}
setStore("user", store.user.length, { ...model, visibility: state })
setStore("user", store().user.length, { ...model, visibility: state })
}
const visible = (model: ModelKey) => {
@@ -128,17 +128,17 @@ export const { use: useModels, provider: ModelsProvider } = createSimpleContext(
}
const push = (model: ModelKey) => {
const uniq = uniqueBy([model, ...store.recent], (x) => `${x.providerID}:${x.modelID}`)
const uniq = uniqueBy([model, ...store().recent], (x) => `${x.providerID}:${x.modelID}`)
if (uniq.length > RECENT_LIMIT) uniq.pop()
setStore("recent", uniq)
}
const variantKey = (model: ModelKey) => `${model.providerID}/${model.modelID}`
const getVariant = (model: ModelKey) => store.variant?.[variantKey(model)]
const getVariant = (model: ModelKey) => store().variant?.[variantKey(model)]
const setVariant = (model: ModelKey, value: string | undefined) => {
const key = variantKey(model)
if (!store.variant) {
if (!store().variant) {
setStore("variant", { [key]: value })
return
}
@@ -147,7 +147,7 @@ export const { use: useModels, provider: ModelsProvider } = createSimpleContext(
const [recentModels] = createResource(
async () => {
const recent = store.recent
const recent = store().recent
await ready.promise
return recent
},
+6 -6
View File
@@ -127,7 +127,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
const activeServer = createMemo(() => {
if (params.serverKey) return requireServerKey(params.serverKey)
if (search.draftId) {
const draft = tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)
const draft = tabs.store().find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)
if (draft) return draft.server
}
return server.key
@@ -222,13 +222,13 @@ function createServerNotificationState(input: {
const currentDirectory = input.directory
const currentSession = input.sessionID
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
Persist.serverGlobal(serverSDK().scope, "notification", ["notification.v1"]),
createStore({
list: [] as Notification[],
}),
)
const [index, setIndex] = createStore<NotificationIndex>(buildNotificationIndex(store.list))
const [index, setIndex] = createStore<NotificationIndex>(buildNotificationIndex(store().list))
const meta = { pruned: false, disposed: false }
@@ -285,7 +285,7 @@ function createServerNotificationState(input: {
if (!ready()) return
if (meta.pruned) return
meta.pruned = true
const list = pruneNotifications(store.list)
const list = pruneNotifications(store().list)
batch(() => {
setStore("list", list)
setIndex(reconcile(buildNotificationIndex(list), { merge: false }))
@@ -293,9 +293,9 @@ function createServerNotificationState(input: {
})
const append = (notification: Notification) => {
const list = pruneNotifications([...store.list, notification])
const list = pruneNotifications([...store().list, notification])
const keep = new Set(list)
const removed = store.list.filter((n) => !keep.has(n))
const removed = store().list.filter((n) => !keep.has(n))
batch(() => {
if (keep.has(notification)) appendToIndex(notification)
+5 -5
View File
@@ -59,7 +59,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
return hasPermissionPromptRules(store.config.permission)
})
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
{
...Persist.serverGlobal(serverSDK().scope, "permission", ["permission.v3"]),
migrate(value) {
@@ -91,7 +91,7 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
const perm = childStore.config.permission
if (typeof perm === "string" && perm === "allow") {
const key = directoryAcceptKey(directory)
if (store.autoAccept[key] === undefined) {
if (store().autoAccept[key] === undefined) {
setStore(
produce((draft) => {
draft.autoAccept[key] = true
@@ -143,16 +143,16 @@ export const { use: usePermission, provider: PermissionProvider } = createSimple
function isAutoAccepting(sessionID: string, directory?: string) {
const session = directory ? serverSync().child(directory, { bootstrap: false })[0].session : []
return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
return autoRespondsPermission(store().autoAccept, session, { sessionID }, directory)
}
function isAutoAcceptingDirectory(directory: string) {
return isDirectoryAutoAccepting(store.autoAccept, directory)
return isDirectoryAutoAccepting(store().autoAccept, directory)
}
function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
const session = directory ? serverSync().child(directory, { bootstrap: false })[0].session : []
return autoRespondsPermission(store.autoAccept, session, permission, directory)
return autoRespondsPermission(store().autoAccept, session, permission, directory)
}
function bumpEnableVersion(sessionID: string, directory?: string) {
+3 -3
View File
@@ -206,12 +206,12 @@ function promptTarget(serverScope: ServerScope, scope: Scope) {
}
export function createPromptSession(serverScope: ServerScope, scope: Scope) {
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
promptTarget(serverScope, scope),
createStore<PromptStore>(promptStore()),
)
return { ready, ...createPromptStateValue(store, setStore) }
return { ready, ...createPromptStateValue(store(), setStore) }
}
export function createPromptReady(session: Accessor<PromptSession>) {
@@ -327,7 +327,7 @@ export const { use: usePrompt, provider: PromptProvider } = createSimpleContext(
const scope = () =>
search.draftId ? { draftID: search.draftId } : { dir: base64Encode(sdk().directory), id: params.id }
const load = (scope: Scope) => {
const current = settings.general.newLayoutDesigns() ? selectPromptTab(tabs.store, scope, serverKey()) : undefined
const current = settings.general.newLayoutDesigns() ? selectPromptTab(tabs.store(), scope, serverKey()) : undefined
if (current) {
return createTabPromptState(tabs, current, serverSDK().scope, scope)
}
+7 -7
View File
@@ -260,7 +260,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
canonicalLocalServer?: ServerConnection.Key
servers?: Array<ServerConnection.Any>
}) => {
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
{
...Persist.global("server", ["server.v3"]),
migrate: (value) => migrateCanonicalLocalServerState(value, props.canonicalLocalServer),
@@ -276,7 +276,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
const url = (x: StoredServer) => (typeof x === "string" ? x : "type" in x ? x.http.url : x.url)
const allServers = createMemo((): Array<ServerConnection.Any> => {
return resolveServerList({ stored: store.list, props: props.servers })
return resolveServerList({ stored: store().list, props: props.servers })
})
const [state, setState] = createStore({
@@ -292,11 +292,11 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
if (!url_) return
const conn: ServerConnection.Http = { ...input, authToken: undefined, http: { ...input.http, url: url_ } }
return batch(() => {
const existing = store.list.findIndex((x) => url(x) === url_)
const existing = store().list.findIndex((x) => url(x) === url_)
if (existing !== -1) {
setStore("list", existing, conn)
} else {
setStore("list", store.list.length, conn)
setStore("list", store().list.length, conn)
}
setState("active", ServerConnection.key(conn))
return conn
@@ -305,7 +305,7 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
function remove(key: ServerConnection.Key) {
const next = nextServerAfterRemoval(allServers(), key, props.defaultServer)
const list = store.list.filter((x) => url(x) !== key)
const list = store().list.filter((x) => url(x) !== key)
batch(() => {
setStore("list", list)
if (state.active === key) setState("active", next)
@@ -318,12 +318,12 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext(
)
const scope = (key = state.active) => ServerScope.fromServerKey(key, props.canonicalLocalServer)
const projects = createServerProjects({ scope, store, setStore })
const projects = createServerProjects({ scope, store: store(), setStore })
const projectStores = new Map<ServerConnection.Key, ReturnType<typeof createServerProjects>>()
const projectsForServer = (key: ServerConnection.Key) => {
const existing = projectStores.get(key)
if (existing) return existing
const next = createServerProjects({ scope: () => scope(key), store, setStore })
const next = createServerProjects({ scope: () => scope(key), store: store(), setStore })
projectStores.set(key, next)
return next
}
+34 -34
View File
@@ -152,45 +152,45 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
name: "Settings",
gate: false,
init: () => {
const [store, setStore, _, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
const showFileTree = withFallback(() => store.general?.showFileTree, defaultSettings.general.showFileTree)
const showSearch = withFallback(() => store.general?.showSearch, defaultSettings.general.showSearch)
const showStatus = withFallback(() => store.general?.showStatus, defaultSettings.general.showStatus)
const [store, setStore, ready] = persisted("settings.v3", createStore<Settings>(defaultSettings))
const showFileTree = withFallback(() => store().general?.showFileTree, defaultSettings.general.showFileTree)
const showSearch = withFallback(() => store().general?.showSearch, defaultSettings.general.showSearch)
const showStatus = withFallback(() => store().general?.showStatus, defaultSettings.general.showStatus)
const showCustomAgents = withFallback(
() => store.general?.showCustomAgents,
() => store().general?.showCustomAgents,
defaultSettings.general.showCustomAgents,
)
const newLayoutDesigns = withFallback(() => store.general?.newLayoutDesigns, newLayoutDesignsDefault)
const newLayoutDesigns = withFallback(() => store().general?.newLayoutDesigns, newLayoutDesignsDefault)
const visible = (preference: () => boolean) => createMemo(() => !newLayoutDesigns() || preference())
createEffect(() => {
if (typeof document === "undefined") return
const root = document.documentElement
root.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.mono))
root.style.setProperty("--font-family-sans", sansFontFamily(store.appearance?.sans))
root.style.setProperty("--font-family-mono", monoFontFamily(store().appearance?.mono))
root.style.setProperty("--font-family-sans", sansFontFamily(store().appearance?.sans))
})
createEffect(() => {
if (store.general?.followup !== "queue") return
if (store().general?.followup !== "queue") return
setStore("general", "followup", "steer")
})
return {
ready,
get current() {
return store
return store()
},
general: {
autoSave: withFallback(() => store.general?.autoSave, defaultSettings.general.autoSave),
autoSave: withFallback(() => store().general?.autoSave, defaultSettings.general.autoSave),
setAutoSave(value: boolean) {
setStore("general", "autoSave", value)
},
releaseNotes: withFallback(() => store.general?.releaseNotes, defaultSettings.general.releaseNotes),
releaseNotes: withFallback(() => store().general?.releaseNotes, defaultSettings.general.releaseNotes),
setReleaseNotes(value: boolean) {
setStore("general", "releaseNotes", value)
},
followup: withFallback(
() => (store.general?.followup === "queue" ? "steer" : store.general?.followup),
() => (store().general?.followup === "queue" ? "steer" : store().general?.followup),
defaultSettings.general.followup,
),
setFollowup(value: "queue" | "steer") {
@@ -200,7 +200,7 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setShowFileTree(value: boolean) {
setStore("general", "showFileTree", value)
},
showNavigation: withFallback(() => store.general?.showNavigation, defaultSettings.general.showNavigation),
showNavigation: withFallback(() => store().general?.showNavigation, defaultSettings.general.showNavigation),
setShowNavigation(value: boolean) {
setStore("general", "showNavigation", value)
},
@@ -212,26 +212,26 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setShowStatus(value: boolean) {
setStore("general", "showStatus", value)
},
showTerminal: withFallback(() => store.general?.showTerminal, defaultSettings.general.showTerminal),
showTerminal: withFallback(() => store().general?.showTerminal, defaultSettings.general.showTerminal),
setShowTerminal(value: boolean) {
setStore("general", "showTerminal", value)
},
showReasoningSummaries: withFallback(
() => store.general?.showReasoningSummaries,
() => store().general?.showReasoningSummaries,
defaultSettings.general.showReasoningSummaries,
),
setShowReasoningSummaries(value: boolean) {
setStore("general", "showReasoningSummaries", value)
},
shellToolPartsExpanded: withFallback(
() => store.general?.shellToolPartsExpanded,
() => store().general?.shellToolPartsExpanded,
defaultSettings.general.shellToolPartsExpanded,
),
setShellToolPartsExpanded(value: boolean) {
setStore("general", "shellToolPartsExpanded", value)
},
editToolPartsExpanded: withFallback(
() => store.general?.editToolPartsExpanded,
() => store().general?.editToolPartsExpanded,
defaultSettings.general.editToolPartsExpanded,
),
setEditToolPartsExpanded(value: boolean) {
@@ -242,7 +242,7 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setStore("general", "showCustomAgents", value)
},
mobileTitlebarPosition: withFallback(
() => store.general?.mobileTitlebarPosition,
() => store().general?.mobileTitlebarPosition,
defaultSettings.general.mobileTitlebarPosition,
),
setMobileTitlebarPosition(value: "top" | "bottom") {
@@ -260,25 +260,25 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
customAgents: visible(showCustomAgents),
},
appearance: {
fontSize: withFallback(() => store.appearance?.fontSize, defaultSettings.appearance.fontSize),
fontSize: withFallback(() => store().appearance?.fontSize, defaultSettings.appearance.fontSize),
setFontSize(value: number) {
setStore("appearance", "fontSize", value)
},
font: withFallback(() => store.appearance?.mono, defaultSettings.appearance.mono),
font: withFallback(() => store().appearance?.mono, defaultSettings.appearance.mono),
setFont(value: string) {
setStore("appearance", "mono", value.trim() ? value : "")
},
uiFont: withFallback(() => store.appearance?.sans, defaultSettings.appearance.sans),
uiFont: withFallback(() => store().appearance?.sans, defaultSettings.appearance.sans),
setUIFont(value: string) {
setStore("appearance", "sans", value.trim() ? value : "")
},
terminalFont: withFallback(() => store.appearance?.terminal, defaultSettings.appearance.terminal),
terminalFont: withFallback(() => store().appearance?.terminal, defaultSettings.appearance.terminal),
setTerminalFont(value: string) {
setStore("appearance", "terminal", value.trim() ? value : "")
},
},
keybinds: {
get: (action: string) => store.keybinds?.[action],
get: (action: string) => store().keybinds?.[action],
set(action: string, keybind: string) {
setStore("keybinds", action, keybind)
},
@@ -295,50 +295,50 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
},
},
permissions: {
autoApprove: withFallback(() => store.permissions?.autoApprove, defaultSettings.permissions.autoApprove),
autoApprove: withFallback(() => store().permissions?.autoApprove, defaultSettings.permissions.autoApprove),
setAutoApprove(value: boolean) {
setStore("permissions", "autoApprove", value)
},
},
notifications: {
agent: withFallback(() => store.notifications?.agent, defaultSettings.notifications.agent),
agent: withFallback(() => store().notifications?.agent, defaultSettings.notifications.agent),
setAgent(value: boolean) {
setStore("notifications", "agent", value)
},
permissions: withFallback(() => store.notifications?.permissions, defaultSettings.notifications.permissions),
permissions: withFallback(() => store().notifications?.permissions, defaultSettings.notifications.permissions),
setPermissions(value: boolean) {
setStore("notifications", "permissions", value)
},
errors: withFallback(() => store.notifications?.errors, defaultSettings.notifications.errors),
errors: withFallback(() => store().notifications?.errors, defaultSettings.notifications.errors),
setErrors(value: boolean) {
setStore("notifications", "errors", value)
},
},
sounds: {
agentEnabled: withFallback(() => store.sounds?.agentEnabled, defaultSettings.sounds.agentEnabled),
agentEnabled: withFallback(() => store().sounds?.agentEnabled, defaultSettings.sounds.agentEnabled),
setAgentEnabled(value: boolean) {
setStore("sounds", "agentEnabled", value)
},
agent: withFallback(() => store.sounds?.agent, defaultSettings.sounds.agent),
agent: withFallback(() => store().sounds?.agent, defaultSettings.sounds.agent),
setAgent(value: string) {
setStore("sounds", "agent", value)
},
permissionsEnabled: withFallback(
() => store.sounds?.permissionsEnabled,
() => store().sounds?.permissionsEnabled,
defaultSettings.sounds.permissionsEnabled,
),
setPermissionsEnabled(value: boolean) {
setStore("sounds", "permissionsEnabled", value)
},
permissions: withFallback(() => store.sounds?.permissions, defaultSettings.sounds.permissions),
permissions: withFallback(() => store().sounds?.permissions, defaultSettings.sounds.permissions),
setPermissions(value: string) {
setStore("sounds", "permissions", value)
},
errorsEnabled: withFallback(() => store.sounds?.errorsEnabled, defaultSettings.sounds.errorsEnabled),
errorsEnabled: withFallback(() => store().sounds?.errorsEnabled, defaultSettings.sounds.errorsEnabled),
setErrorsEnabled(value: boolean) {
setStore("sounds", "errorsEnabled", value)
},
errors: withFallback(() => store.sounds?.errors, defaultSettings.sounds.errors),
errors: withFallback(() => store().sounds?.errors, defaultSettings.sounds.errors),
setErrors(value: string) {
setStore("sounds", "errors", value)
},
+32 -30
View File
@@ -55,7 +55,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const server = useServer()
const platform = usePlatform()
const fallback = server.key
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
{
...Persist.window("tabs"),
migrate: (value: unknown) => {
@@ -68,9 +68,9 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
},
createStore<Tab[]>([]),
)
const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
const [recent, setRecent, recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
const [info, setInfo] = persisted(Persist.window("tabs.info"), createStore<Record<string, TabInfo>>({}))
const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([]))
const [closed, setClosed, closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([]))
const params = useParams()
const navigate = useNavigate()
@@ -81,7 +81,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
let recentWrite = 0
let recentValue: string | undefined
const recentKey = () => (recentWrite ? recentValue : recent.key)
const recentKey = () => (recentWrite ? recentValue : recent().key)
const setRecentKey = (key: string | undefined) => {
const write = ++recentWrite
@@ -109,7 +109,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
}
const removeInfo = (key: string) => {
if (!info[key]) return
if (!info()[key]) return
setInfo(
produce((draft) => {
delete draft[key]
@@ -122,9 +122,9 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
createEffect(() => {
if (!ready() || !recentReady()) return
const servers = new Set(server.list.map(ServerConnection.key))
const next = store.filter((tab) => servers.has(tab.server))
if (next.length !== store.length) {
for (const tab of store) {
const next = store().filter((tab) => servers.has(tab.server))
if (next.length !== store().length) {
for (const tab of store()) {
if (!servers.has(tab.server)) {
const key = tabKey(tab)
memory.remove(key)
@@ -133,9 +133,9 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
}
setStore(() => next)
}
if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined)
if (recent().key && !next.some((tab) => tabKey(tab) === recent().key)) setRecentKey(undefined)
const keys = new Set(next.map(tabKey))
for (const key of Object.keys(info)) {
for (const key of Object.keys(info())) {
if (!keys.has(key)) removeInfo(key)
}
})
@@ -143,8 +143,8 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
createEffect(() => {
if (!closedReady()) return
const servers = new Set(server.list.map(ServerConnection.key))
const next = closed.filter((entry) => servers.has(entry.tab.server))
if (next.length !== closed.length) setClosed(() => next)
const next = closed().filter((entry) => servers.has(entry.tab.server))
if (next.length !== closed().length) setClosed(() => next)
})
const navigateTab = (tab: Tab) => {
@@ -154,11 +154,11 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
}
const removeTab = (index: number) => {
const tab = store[index]
const tab = store()[index]
if (!tab) return
const key = tabKey(tab)
const draftID = tab.type === "draft" ? tab.draftID : undefined
const nextTab = nextTabAfterClose(store, index, recentKey() === key && location.pathname !== "/")
const nextTab = nextTabAfterClose(store(), index, recentKey() === key && location.pathname !== "/")
closing.add(key)
void startTransition(() => {
setStore(
@@ -180,7 +180,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const actions = {
addSessionTab: (tab: Omit<SessionTab, "type">) => {
const next = { type: "session" as const, ...tab }
const existing = store.find((item) => tabKey(item) === tabKey(next))
const existing = store().find((item) => tabKey(item) === tabKey(next))
if (existing) return existing
void startTransition(() => {
setStore(
@@ -203,7 +203,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
)
},
draft(draftID: string) {
const tab = store.find((item) => item.type === "draft" && item.draftID === draftID)
const tab = store().find((item) => item.type === "draft" && item.draftID === draftID)
if (!tab || tab.type !== "draft") throw new Error(`Draft not found: ${draftID}`)
return tab
},
@@ -228,7 +228,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
},
promoteDraft(draftID: string, session: Omit<SessionTab, "type">) {
// Keep the replacement and navigation atomic so /new-session never renders
// after its backing draft tab has been removed from the store.
// after its backing draft tab has been removed from the store().
const active = location.pathname === "/new-session" && location.query.draftId === draftID
const next = { type: "session" as const, ...session }
void startTransition(() => {
@@ -238,7 +238,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
if (index !== -1) tabs[index] = next
}),
)
if (recent.key === `draft:${draftID}`) setRecentKey(tabKey(next))
if (recent().key === `draft:${draftID}`) setRecentKey(tabKey(next))
if (active) navigateTab(next)
})
memory.remove(`draft:${draftID}`)
@@ -249,7 +249,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
// Cleanup paths (missing sessions, archive, server removal) go through
// removeTab and friends directly and are not recorded.
closeTab(index: number) {
const tab = store[index]
const tab = store()[index]
if (!tab) return
if (tab.type === "session") updateClosed((stack) => pushClosedTab(stack, tab, index))
removeTab(index)
@@ -259,12 +259,12 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
void closedReady.promise?.then(() => actions.reopenClosedTab())
return
}
const result = takeClosedTab(closed, store)
if (result.stack.length === closed.length) return
const result = takeClosedTab(closed(), store())
if (result.stack.length === closed().length) return
setClosed(() => result.stack)
const entry = result.entry
if (!entry) return
const index = Math.min(entry.index, store.length)
const index = Math.min(entry.index, store().length)
void startTransition(() => {
setStore(
produce((tabs) => {
@@ -277,26 +277,27 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
},
removeSessionTab(input: Omit<SessionTab, "type">) {
updateClosed((stack) => removeClosedTabs(stack, input.server, [input.sessionId]))
const index = store.findIndex(
const index = store().findIndex(
(tab) => tab.type === "session" && tab.server === input.server && tab.sessionId === input.sessionId,
)
if (index !== -1) removeTab(index)
},
removeServer(key: ServerConnection.Key) {
updateClosed((stack) => stack.filter((entry) => entry.tab.server !== key))
const drafts = store.flatMap((tab) => (tab.type === "draft" && tab.server === key ? [tab.draftID] : []))
const removed = store.filter((tab) => tab.server === key).map(tabKey)
const drafts = store().flatMap((tab) => (tab.type === "draft" && tab.server === key ? [tab.draftID] : []))
const removed = store().filter((tab) => tab.server === key).map(tabKey)
setStore((tabs) => tabs.filter((tab) => tab.server !== key))
for (const key of removed) memory.remove(key)
for (const key of removed) removeInfo(key)
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
const recentKey = recent().key
if (recentKey && removed.includes(recentKey)) setRecentKey(undefined)
for (const draftID of drafts) removeDraftPersisted(draftID)
if (server.key === key) navigate("/")
},
removeSessions: (input: SessionTabsRemovedDetail) => {
const targetServer = input.server ?? server.key
updateClosed((stack) => removeClosedTabs(stack, targetServer, input.sessionIDs))
const removed = store
const removed = store()
.filter(
(tab) => tab.type === "session" && tab.server === targetServer && input.sessionIDs.includes(tab.sessionId),
)
@@ -340,7 +341,8 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
else navigate("/")
}),
)
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
const recentKey = recent().key
if (recentKey && removed.includes(recentKey)) setRecentKey(undefined)
})
for (const key of removed) memory.remove(key)
for (const key of removed) removeInfo(key)
@@ -348,7 +350,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
rememberSessionInfo(tab: SessionTab, session: Session) {
const key = tabKey(tab)
const next = { title: session.title, directory: session.directory }
const current = info[key]
const current = info()[key]
if (current?.title === next.title && current.directory === next.directory) return
setInfo(key, next)
},
@@ -359,7 +361,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
},
toggleHome(input: { home: boolean; current?: Tab }) {
if (input.home) {
const tab = store.find((tab) => tabKey(tab) === recentKey())
const tab = store().find((tab) => tabKey(tab) === recentKey())
if (tab) navigateTab(tab)
return
}
+25 -25
View File
@@ -151,7 +151,7 @@ function createWorkspaceTerminalSession(
) {
const legacy = scope === ServerScope.local ? getLegacyTerminalStorageKeys(dir, legacySessionID) : []
const [store, setStore, _, ready] = persisted(
const [store, setStore, ready] = persisted(
{
...terminalPersistTarget(scope, dir, legacy),
migrate: migrateTerminalState,
@@ -166,7 +166,7 @@ function createWorkspaceTerminalSession(
const pickNextTerminalNumber = () => {
const existingTitleNumbers = new Set(
store.all.flatMap((pty) => {
store().all.flatMap((pty) => {
const direct = Number.isFinite(pty.titleNumber) && pty.titleNumber > 0 ? pty.titleNumber : undefined
if (direct !== undefined) return [direct]
const parsed = numberFromTitle(pty.title)
@@ -183,10 +183,10 @@ function createWorkspaceTerminalSession(
}
const removeExited = (id: string) => {
const all = store.all
const all = store().all
const index = all.findIndex((x) => x.id === id)
if (index === -1) return
const active = store.active === id ? (index === 0 ? all[1]?.id : all[0]?.id) : store.active
const active = store().active === id ? (index === 0 ? all[1]?.id : all[0]?.id) : store().active
batch(() => {
setStore("active", active)
setStore(
@@ -204,8 +204,8 @@ function createWorkspaceTerminalSession(
onCleanup(unsub)
const update = (client: DirectorySDK["client"], pty: Partial<LocalPTY> & { id: string }) => {
const index = store.all.findIndex((x) => x.id === pty.id)
const previous = index >= 0 ? store.all[index] : undefined
const index = store().all.findIndex((x) => x.id === pty.id)
const previous = index >= 0 ? store().all[index] : undefined
if (index >= 0) {
setStore("all", index, (item) => ({ ...item, ...pty }))
}
@@ -217,7 +217,7 @@ function createWorkspaceTerminalSession(
})
.catch((error: unknown) => {
if (previous) {
const currentIndex = store.all.findIndex((item) => item.id === pty.id)
const currentIndex = store().all.findIndex((item) => item.id === pty.id)
if (currentIndex >= 0) setStore("all", currentIndex, previous)
}
console.error("Failed to update terminal", error)
@@ -225,8 +225,8 @@ function createWorkspaceTerminalSession(
}
const clone = async (client: DirectorySDK["client"], id: string) => {
const index = store.all.findIndex((x) => x.id === id)
const pty = store.all[index]
const index = store().all.findIndex((x) => x.id === id)
const pty = store().all[index]
if (!pty) return
const next = await client.pty
.create({
@@ -238,7 +238,7 @@ function createWorkspaceTerminalSession(
})
if (!next?.data) return
const active = store.active === pty.id
const active = store().active === pty.id
batch(() => {
setStore("all", index, {
@@ -259,8 +259,8 @@ function createWorkspaceTerminalSession(
return {
ready,
all: createMemo(() => store.all),
active: createMemo(() => store.active),
all: createMemo(() => store().all),
active: createMemo(() => store().active),
clear() {
batch(() => {
setStore("active", undefined)
@@ -280,7 +280,7 @@ function createWorkspaceTerminalSession(
title: pty.data?.title ?? defaultTitle(nextNumber),
titleNumber: nextNumber,
}
setStore("all", store.all.length, newTerminal)
setStore("all", store().all.length, newTerminal)
setStore("active", id)
})
.catch((error: unknown) => {
@@ -291,7 +291,7 @@ function createWorkspaceTerminalSession(
update(sdk.client, pty)
},
trim(id: string) {
const index = store.all.findIndex((x) => x.id === id)
const index = store().all.findIndex((x) => x.id === id)
if (index === -1) return
setStore("all", index, (pty) => trimTerminal(pty))
},
@@ -309,7 +309,7 @@ function createWorkspaceTerminalSession(
const client = sdk.client
return {
trim(id: string) {
const index = store.all.findIndex((x) => x.id === id)
const index = store().all.findIndex((x) => x.id === id)
if (index === -1) return
setStore("all", index, (pty) => trimTerminal(pty))
},
@@ -325,23 +325,23 @@ function createWorkspaceTerminalSession(
setStore("active", id)
},
next() {
const index = store.all.findIndex((x) => x.id === store.active)
const index = store().all.findIndex((x) => x.id === store().active)
if (index === -1) return
const nextIndex = (index + 1) % store.all.length
setStore("active", store.all[nextIndex]?.id)
const nextIndex = (index + 1) % store().all.length
setStore("active", store().all[nextIndex]?.id)
},
previous() {
const index = store.all.findIndex((x) => x.id === store.active)
const index = store().all.findIndex((x) => x.id === store().active)
if (index === -1) return
const prevIndex = index === 0 ? store.all.length - 1 : index - 1
setStore("active", store.all[prevIndex]?.id)
const prevIndex = index === 0 ? store().all.length - 1 : index - 1
setStore("active", store().all[prevIndex]?.id)
},
async close(id: string) {
const index = store.all.findIndex((f) => f.id === id)
const index = store().all.findIndex((f) => f.id === id)
if (index !== -1) {
batch(() => {
if (store.active === id) {
const next = index > 0 ? store.all[index - 1]?.id : store.all[1]?.id
if (store().active === id) {
const next = index > 0 ? store().all[index - 1]?.id : store().all[1]?.id
setStore("active", next)
}
setStore(
@@ -358,7 +358,7 @@ function createWorkspaceTerminalSession(
})
},
move(id: string, to: number) {
const index = store.all.findIndex((f) => f.id === id)
const index = store().all.findIndex((f) => f.id === id)
if (index === -1) return
setStore(
"all",
+4 -4
View File
@@ -685,14 +685,14 @@ function HomeProjectColumn(props: {
const global = useGlobal()
const dialog = useDialog()
const controller = useServerManagementController({ navigateOnAdd: false })
const [_state, setState, _, ready] = persisted(
const [_state, setState, ready] = persisted(
Persist.global("home.servers", ["home.servers.v1"]),
createStore({ collapsed: {} as Record<string, boolean> }),
)
const [state] = createResource(
() => ready.promise ?? Promise.resolve(),
(p) => p.then(() => _state),
{ initialValue: _state },
(p) => p.then(() => _state()),
{ initialValue: _state() },
)
return (
@@ -1117,7 +1117,7 @@ function HomeSessionLeading(props: {
revealProjectOnHover: boolean
}) {
const tabs = useTabs()
const hasOpenTab = createMemo(() => sessionHasOpenTab(tabs.store, props.server, props.session))
const hasOpenTab = createMemo(() => sessionHasOpenTab(tabs.store(), props.server, props.session))
return (
<div class="relative shrink-0">
<Show when={hasOpenTab()}>
+19 -19
View File
@@ -85,7 +85,7 @@ import { SidebarContent } from "./layout/sidebar-shell"
export default function LegacyLayout(props: ParentProps) {
const serverSDK = useServerSDK()
const [store, setStore, , ready] = persisted(
const [store, setStore, ready] = persisted(
Persist.serverGlobal(serverSDK().scope, "layout.page", ["layout.page.v1"]),
createStore({
lastProjectSession: {} as { [directory: string]: { directory: string; id: string; at: number } },
@@ -556,11 +556,11 @@ export default function LegacyLayout(props: ParentProps) {
const workspaceName = (directory: string, projectId?: string, branch?: string) => {
const key = pathKey(directory)
const direct = store.workspaceName[key] ?? store.workspaceName[directory]
const direct = store().workspaceName[key] ?? store().workspaceName[directory]
if (direct) return direct
if (!projectId) return
if (!branch) return
return store.workspaceBranchName[projectId]?.[branch]
return store().workspaceBranchName[projectId]?.[branch]
}
const setWorkspaceName = (directory: string, next: string, projectId?: string, branch?: string) => {
@@ -568,7 +568,7 @@ export default function LegacyLayout(props: ParentProps) {
setStore("workspaceName", key, next)
if (!projectId) return
if (!branch) return
if (!store.workspaceBranchName[projectId]) {
if (!store().workspaceBranchName[projectId]) {
setStore("workspaceBranchName", projectId, {})
}
setStore("workspaceBranchName", projectId, branch, next)
@@ -591,7 +591,7 @@ export default function LegacyLayout(props: ParentProps) {
const activeDir = currentDir()
return workspaceIds(project).filter((directory) => {
const expanded = store.workspaceExpanded[directory] ?? directory === project.worktree
const expanded = store().workspaceExpanded[directory] ?? directory === project.worktree
const active = pathKey(directory) === pathKey(activeDir)
return expanded || active
})
@@ -601,7 +601,7 @@ export default function LegacyLayout(props: ParentProps) {
if (!pageReady()) return
if (!layoutReady()) return
const projects = layout.projects.list()
for (const [directory, expanded] of Object.entries(store.workspaceExpanded)) {
for (const [directory, expanded] of Object.entries(store().workspaceExpanded)) {
if (!expanded) continue
const key = pathKey(directory)
const project = projects.find(
@@ -1127,7 +1127,7 @@ export default function LegacyLayout(props: ParentProps) {
.find((item) => pathKey(item.worktree) === key || item.sandboxes?.some((sandbox) => pathKey(sandbox) === key))
if (project) return project.worktree
const known = Object.entries(store.workspaceOrder).find(
const known = Object.entries(store().workspaceOrder).find(
([root, dirs]) => pathKey(root) === key || dirs.some((item) => pathKey(item) === key),
)
if (known) return known[0]
@@ -1150,7 +1150,7 @@ export default function LegacyLayout(props: ParentProps) {
}
function clearLastProjectSession(root: string) {
if (!store.lastProjectSession[root]) return
if (!store().lastProjectSession[root]) return
setStore(
"lastProjectSession",
produce((draft) => {
@@ -1162,7 +1162,7 @@ export default function LegacyLayout(props: ParentProps) {
function syncSessionRoute(directory: string, id: string, root = activeProjectRoot(directory)) {
rememberSessionRoute(directory, id, root)
notification.session.markViewed(id)
const expanded = untrack(() => store.workspaceExpanded[directory])
const expanded = untrack(() => store().workspaceExpanded[directory])
if (expanded === false) {
setStore("workspaceExpanded", directory, true)
}
@@ -1176,7 +1176,7 @@ export default function LegacyLayout(props: ParentProps) {
server.projects.touch(root)
const project = layout.projects.list().find((item) => item.worktree === root)
let dirs = project
? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store.workspaceOrder[root])
? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store().workspaceOrder[root])
: [root]
const canOpen = (value: string | undefined) => {
if (!value) return false
@@ -1188,7 +1188,7 @@ export default function LegacyLayout(props: ParentProps) {
.client.worktree.list({ directory: root })
.then((x) => x.data ?? [])
.catch(() => [] as string[])
dirs = effectiveWorkspaceOrder(root, [root, ...listed], store.workspaceOrder[root])
dirs = effectiveWorkspaceOrder(root, [root, ...listed], store().workspaceOrder[root])
return canOpen(target)
}
const openSession = async (target: { directory: string; id: string }) => {
@@ -1210,7 +1210,7 @@ export default function LegacyLayout(props: ParentProps) {
return true
}
const projectSession = store.lastProjectSession[root]
const projectSession = store().lastProjectSession[root]
if (projectSession?.id) {
await refreshDirs(projectSession.directory)
const opened = await openSession(projectSession)
@@ -1401,7 +1401,7 @@ export default function LegacyLayout(props: ParentProps) {
if (!result) return
if (pathKey(store.lastProjectSession[root]?.directory ?? "") === pathKey(directory)) {
if (pathKey(store().lastProjectSession[root]?.directory ?? "") === pathKey(directory)) {
clearLastProjectSession(root)
}
@@ -1424,7 +1424,7 @@ export default function LegacyLayout(props: ParentProps) {
const nextKey = pathKey(nextCurrent)
const project = layout.projects.list().find((item) => item.worktree === root)
const dirs = project
? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store.workspaceOrder[root])
? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store().workspaceOrder[root])
: [root]
const valid = dirs.some((item) => pathKey(item) === nextKey)
@@ -1771,7 +1771,7 @@ export default function LegacyLayout(props: ParentProps) {
: undefined
const pending = extra ? WorktreeState.get(serverSDK().scope, extra)?.status === "pending" : false
const ordered = effectiveWorkspaceOrder(local, dirs, store.workspaceOrder[project.worktree])
const ordered = effectiveWorkspaceOrder(local, dirs, store().workspaceOrder[project.worktree])
if (pending && extra) return [local, extra, ...ordered.filter((item) => item !== local)]
if (!extra) return ordered
if (pending) return ordered
@@ -1875,7 +1875,7 @@ export default function LegacyLayout(props: ParentProps) {
setEditor,
InlineEditor,
isBusy,
workspaceExpanded: (directory, local) => store.workspaceExpanded[directory] ?? local,
workspaceExpanded: (directory, local) => store().workspaceExpanded[directory] ?? local,
setWorkspaceExpanded: (directory, value) => setStore("workspaceExpanded", directory, value),
showResetWorkspaceDialog: (root, directory) =>
dialog.show(() => <DialogResetWorkspace root={root} directory={directory} />),
@@ -2171,7 +2171,7 @@ export default function LegacyLayout(props: ParentProps) {
<DragOverlay>
<WorkspaceDragOverlay
sidebarProject={sidebarProject}
activeWorkspace={() => store.activeWorkspace}
activeWorkspace={() => store().activeWorkspace}
workspaceLabel={workspaceLabel}
/>
</DragOverlay>
@@ -2187,7 +2187,7 @@ export default function LegacyLayout(props: ParentProps) {
<div
class="shrink-0 px-3 py-3"
classList={{
hidden: store.gettingStartedDismissed || !(providers.all().size > 0 && providers.paid().length === 0),
hidden: store().gettingStartedDismissed || !(providers.all().size > 0 && providers.paid().length === 0),
}}
>
<div class="rounded-xl bg-background-base shadow-xs-border-base" data-component="getting-started">
@@ -2217,7 +2217,7 @@ export default function LegacyLayout(props: ParentProps) {
}
const projects = () => layout.projects.list()
const projectOverlay = () => <ProjectDragOverlay projects={projects} activeProject={() => store.activeProject} />
const projectOverlay = () => <ProjectDragOverlay projects={projects} activeProject={() => store().activeProject} />
const sidebarContent = (mobile?: boolean) => (
<SidebarContent
mobile={mobile}
+7 -7
View File
@@ -545,7 +545,7 @@ export default function Page() {
})
const [followup, setFollowup] = persisted(
Persist.serverWorkspace(serverSDK().scope, sdk().directory, "followup", ["followup.v1"]),
Persist.serverWorkspace(serverSDK().scope, sdk().directory, "followup", ["followup().v1"]),
createStore<{
items: Record<string, FollowupItem[] | undefined>
failed: Record<string, string | undefined>
@@ -1614,19 +1614,19 @@ export default function Page() {
const queuedFollowups = createMemo(() => {
const id = params.id
if (!id) return emptyFollowups
return followup.items[id] ?? emptyFollowups
return followup().items[id] ?? emptyFollowups
})
const editingFollowup = createMemo(() => {
const id = params.id
if (!id) return
return followup.edit[id]
return followup().edit[id]
})
const followupMutation = useMutation(() => ({
mutationFn: async (input: { sessionID: string; id: string; manual?: boolean }) => {
const owner = sessionOwnership.capture()
const item = (followup.items[input.sessionID] ?? []).find((entry) => entry.id === input.id)
const item = (followup().items[input.sessionID] ?? []).find((entry) => entry.id === input.id)
if (!item) return
if (input.manual) setFollowup("paused", input.sessionID, undefined)
@@ -1696,7 +1696,7 @@ export default function Page() {
const sendFollowup = (sessionID: string, id: string, opts?: { manual?: boolean }) => {
if (sync().session.get(sessionID)?.parentID) return Promise.resolve()
const item = (followup.items[sessionID] ?? []).find((entry) => entry.id === id)
const item = (followup().items[sessionID] ?? []).find((entry) => entry.id === id)
if (!item) return Promise.resolve()
if (followupBusy(sessionID)) return Promise.resolve()
@@ -1818,8 +1818,8 @@ export default function Page() {
const item = queuedFollowups()[0]
if (!item) return
if (followupBusy(sessionID)) return
if (followup.failed[sessionID] === item.id) return
if (followup.paused[sessionID]) return
if (followup().failed[sessionID] === item.id) return
if (followup().paused[sessionID]) return
if (isChildSession()) return
if (composer.blocked()) return
if (busy(sessionID)) return
@@ -61,9 +61,9 @@ export function useUsageExceededDialogs() {
const keys = goUpsellKeys(evt.properties.status)
if (!keys) return
const seen = goUpsellState[keys.lastSeenAt]
const seen = goUpsellState()[keys.lastSeenAt]
if (seen && Date.now() - seen < GO_UPSELL_WINDOW) return
if (goUpsellState[keys.dontShow]) return
if (goUpsellState()[keys.dontShow]) return
if (action.reason === "free_tier_limit") {
dialog.show(() => (
@@ -22,11 +22,11 @@ export function createReviewPanelV2State() {
const [filter, setFilter] = createSignal("")
return {
sidebarOpened: () => store.sidebarOpened,
sidebarWidth: () => store.sidebarWidth,
sidebarOpened: () => store().sidebarOpened,
sidebarWidth: () => store().sidebarWidth,
filter,
setFilter,
expandMode: () => store.expandMode,
expandMode: () => store().expandMode,
setExpandMode: (mode: SessionReviewExpandMode) => setStore("expandMode", mode),
resizeSidebar: (width: number) =>
setStore(
+9 -11
View File
@@ -6,11 +6,9 @@ import type { SetStoreFunction, Store } from "solid-js/store"
import { pathKey } from "@/utils/path-key"
import { ScopedKey, ServerScope, type ServerScope as ServerScopeValue } from "@/utils/server-scope"
type InitType = Promise<string> | string | null
type PersistedWithReady<T> = [
Store<T>,
Accessor<T>,
SetStoreFunction<T>,
InitType,
Accessor<boolean> & { promise: undefined | Promise<any> },
]
@@ -641,20 +639,20 @@ export function persisted<T>(
const [state, setState, init] = makePersisted(store, { name: config.key, storage })
const isAsync = init instanceof Promise
const [ready] = createResource(
const [storeResource] = createResource(
() => init,
async (initValue) => {
if (initValue instanceof Promise) await initValue
return true
async () => {
if (init instanceof Promise) await init
return state
},
{ initialValue: !isAsync },
{ initialValue: state },
)
return [
state,
() => storeResource() as T,
setState,
init,
Object.assign(() => (ready.loading ? false : ready.latest === true), {
Object.assign(() => !isAsync || !storeResource.loading, {
promise: init instanceof Promise ? init : undefined,
}),
]