From 4f604b38399bea3ce08fdba3ce2ad8c7603ed4ea Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:46:53 -0600 Subject: [PATCH] fix(desktop): color grouping --- packages/desktop/src/context/session.tsx | 125 +++++++++++++++++------ packages/ui/src/components/diff.tsx | 7 +- 2 files changed, 101 insertions(+), 31 deletions(-) diff --git a/packages/desktop/src/context/session.tsx b/packages/desktop/src/context/session.tsx index 61fed945..02c859a5 100644 --- a/packages/desktop/src/context/session.tsx +++ b/packages/desktop/src/context/session.tsx @@ -1,6 +1,6 @@ import { createStore, produce } from "solid-js/store" import { createSimpleContext } from "./helper" -import { batch, createEffect, createMemo } from "solid-js" +import { batch, createEffect, createMemo, createSignal, on } from "solid-js" import { useSync } from "./sync" import { makePersisted } from "@solid-primitives/storage" import { TextSelection, useLocal } from "./local" @@ -13,26 +13,74 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex const sync = useSync() const local = useLocal() - const [store, setStore] = makePersisted( + const seed = props.sessionId ?? "new-session" + + const [persist, setPersist] = makePersisted( createStore<{ - prompt: Prompt - cursorPosition?: number messageId?: string tabs: { active?: string opened: string[] } }>({ - prompt: [{ type: "text", content: "", start: 0, end: 0 }], tabs: { opened: [], }, }), { - name: props.sessionId ?? "new-session", + name: seed, }, ) + const [promptStore, setPromptStore] = createStore<{ + prompt: Prompt + cursor?: number + }>({ + prompt: clonePrompt(DEFAULT_PROMPT), + }) + + const key = createMemo(() => props.sessionId ?? "new-session") + const [ready, setReady] = createSignal(false) + const prefix = "session-prompt:" + + createEffect( + on( + key, + (value) => { + setReady(false) + const record = localStorage.getItem(prefix + value) + if (!record) { + setPromptStore("prompt", clonePrompt(DEFAULT_PROMPT)) + setPromptStore("cursor", undefined) + setReady(true) + return + } + const payload = JSON.parse(record) as { prompt?: Prompt; cursor?: number } + const parts = payload.prompt ?? DEFAULT_PROMPT + const cursor = typeof payload.cursor === "number" ? payload.cursor : undefined + setPromptStore("prompt", clonePrompt(parts)) + setPromptStore("cursor", cursor) + setReady(true) + }, + { defer: true }, + ), + ) + + createEffect(() => { + if (!ready()) return + const value = key() + const isDefault = isPromptEqual(promptStore.prompt, DEFAULT_PROMPT) + if (isDefault && (promptStore.cursor === undefined || promptStore.cursor <= 0)) { + localStorage.removeItem(prefix + value) + return + } + const next = JSON.stringify({ + prompt: clonePrompt(promptStore.prompt), + cursor: promptStore.cursor, + }) + localStorage.setItem(prefix + value, next) + }) + createEffect(() => { if (!props.sessionId) return sync.session.sync(props.sessionId) @@ -49,8 +97,8 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex return userMessages()?.at(0) }) const activeMessage = createMemo(() => { - if (!store.messageId) return lastUserMessage() - return userMessages()?.find((m) => m.id === store.messageId) + if (!persist.messageId) return lastUserMessage() + return userMessages()?.find((m) => m.id === persist.messageId) }) const working = createMemo(() => { if (!props.sessionId) return false @@ -101,13 +149,14 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex working, diffs, prompt: { - current: createMemo(() => store.prompt), - cursor: createMemo(() => store.cursorPosition), - dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)), + current: createMemo(() => promptStore.prompt), + cursor: createMemo(() => promptStore.cursor), + dirty: createMemo(() => !isPromptEqual(promptStore.prompt, DEFAULT_PROMPT)), set(prompt: Prompt, cursorPosition?: number) { + const next = clonePrompt(prompt) batch(() => { - setStore("prompt", prompt) - if (cursorPosition !== undefined) setStore("cursorPosition", cursorPosition) + setPromptStore("prompt", next) + if (cursorPosition !== undefined) setPromptStore("cursor", cursorPosition) }) }, }, @@ -117,7 +166,7 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex last: lastUserMessage, active: activeMessage, setActive(id: string | undefined) { - setStore("messageId", id) + setPersist("messageId", id) }, }, usage: { @@ -126,53 +175,52 @@ export const { use: useSession, provider: SessionProvider } = createSimpleContex context, }, layout: { - tabs: store.tabs, + tabs: persist.tabs, setActiveTab(tab: string | undefined) { - setStore("tabs", "active", tab) + setPersist("tabs", "active", tab) }, setOpenedTabs(tabs: string[]) { - setStore("tabs", "opened", tabs) + setPersist("tabs", "opened", tabs) }, async openTab(tab: string) { if (tab === "chat") { - setStore("tabs", "active", undefined) + setPersist("tabs", "active", undefined) return } if (tab.startsWith("file://")) { await local.file.open(tab.replace("file://", "")) } if (tab !== "review") { - if (!store.tabs.opened.includes(tab)) { - setStore("tabs", "opened", [...store.tabs.opened, tab]) + if (!persist.tabs.opened.includes(tab)) { + setPersist("tabs", "opened", [...persist.tabs.opened, tab]) } } - setStore("tabs", "active", tab) + setPersist("tabs", "active", tab) }, closeTab(tab: string) { batch(() => { - setStore( + setPersist( "tabs", "opened", - store.tabs.opened.filter((x) => x !== tab), + persist.tabs.opened.filter((x) => x !== tab), ) - if (store.tabs.active === tab) { - const index = store.tabs.opened.findIndex((f) => f === tab) - const previous = store.tabs.opened[Math.max(0, index - 1)] - setStore("tabs", "active", previous) + if (persist.tabs.active === tab) { + const index = persist.tabs.opened.findIndex((f) => f === tab) + const previous = persist.tabs.opened[Math.max(0, index - 1)] + setPersist("tabs", "active", previous) } }) }, moveTab(tab: string, to: number) { - const index = store.tabs.opened.findIndex((f) => f === tab) + const index = persist.tabs.opened.findIndex((f) => f === tab) if (index === -1) return - setStore( + setPersist( "tabs", "opened", produce((opened) => { opened.splice(to, 0, opened.splice(index, 1)[0]) }), ) - // setStore("node", path, "pinned", true) }, }, } @@ -215,3 +263,20 @@ export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean { } return true } + +function cloneSelection(selection?: TextSelection) { + if (!selection) return undefined + return { ...selection } +} + +function clonePart(part: ContentPart): ContentPart { + if (part.type === "text") return { ...part } + return { + ...part, + selection: cloneSelection(part.selection), + } +} + +function clonePrompt(prompt: Prompt): Prompt { + return prompt.map(clonePart) +} diff --git a/packages/ui/src/components/diff.tsx b/packages/ui/src/components/diff.tsx index a53d5b3f..d0c0036e 100644 --- a/packages/ui/src/components/diff.tsx +++ b/packages/ui/src/components/diff.tsx @@ -231,6 +231,12 @@ registerCustomTheme("OpenCode", () => { foreground: "var(--syntax-type)", }, }, + { + scope: ["meta.object.member"], + settings: { + foreground: "var(--syntax-primitive)", + }, + }, { scope: [ "variable.parameter.function", @@ -238,7 +244,6 @@ registerCustomTheme("OpenCode", () => { "meta.block", "meta.tag.attributes", "entity.name.constant", - "meta.object.member", "meta.embedded.expression", "meta.template.expression", "string.other.begin.yaml",