From ea643f1e3f6c1a7459d184cb99a5b24adceb8a6e Mon Sep 17 00:00:00 2001 From: Aaron Iker Date: Thu, 15 Jan 2026 22:21:35 +0100 Subject: [PATCH 01/57] feat(console): Style improvements for /black, View Transition fixes (#8739) Co-authored-by: Github Action --- .../console/app/src/component/light-rays.tsx | 10 +-- packages/console/app/src/routes/black.css | 50 ++++++------- .../console/app/src/routes/black/index.tsx | 70 ------------------- 3 files changed, 28 insertions(+), 102 deletions(-) diff --git a/packages/console/app/src/component/light-rays.tsx b/packages/console/app/src/component/light-rays.tsx index 36b47a4777..53daeb2f34 100644 --- a/packages/console/app/src/component/light-rays.tsx +++ b/packages/console/app/src/component/light-rays.tsx @@ -34,14 +34,14 @@ export const defaultConfig: LightRaysConfig = { raysOrigin: "top-center", raysColor: "#ffffff", raysSpeed: 1.0, - lightSpread: 1.15, - rayLength: 4.0, + lightSpread: 1.2, + rayLength: 4.5, sourceWidth: 0.1, pulsating: true, pulsatingMin: 0.9, - pulsatingMax: 1.0, - fadeDistance: 1.15, - saturation: 0.325, + pulsatingMax: 1.05, + fadeDistance: 1.25, + saturation: 0.35, followMouse: false, mouseInfluence: 0.05, noiseAmount: 0.5, diff --git a/packages/console/app/src/routes/black.css b/packages/console/app/src/routes/black.css index a0cd5712b3..d74f4118f8 100644 --- a/packages/console/app/src/routes/black.css +++ b/packages/console/app/src/routes/black.css @@ -89,44 +89,42 @@ animation: reveal-terms 300ms cubic-bezier(0.25, 0, 0.5, 1) 50ms forwards; } -::view-transition-old(action-20), -::view-transition-old(action-100), -::view-transition-old(action-200) { - animation: fade-out 100ms cubic-bezier(0.4, 0, 0.2, 1) forwards; +::view-transition-old(actions-20), +::view-transition-old(actions-100), +::view-transition-old(actions-200) { + animation: fade-out 80ms cubic-bezier(0.4, 0, 0.2, 1) forwards; } -::view-transition-new(action-20), -::view-transition-new(action-100), -::view-transition-new(action-200) { - animation: fade-in-up 200ms cubic-bezier(0.16, 1, 0.3, 1) 250ms forwards; +::view-transition-new(actions-20), +::view-transition-new(actions-100), +::view-transition-new(actions-200) { + animation: fade-in-up 200ms cubic-bezier(0.16, 1, 0.3, 1) 300ms forwards; opacity: 0; } -::view-transition-group(plan-card-20), -::view-transition-group(plan-card-100), -::view-transition-group(plan-card-200) { - animation-duration: 200ms; +::view-transition-group(card-20), +::view-transition-group(card-100), +::view-transition-group(card-200) { + animation-duration: 250ms; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } -::view-transition-image-pair(plan-card-20), -::view-transition-image-pair(plan-card-100), -::view-transition-image-pair(plan-card-200) { +::view-transition-image-pair(card-20), +::view-transition-image-pair(card-100), +::view-transition-image-pair(card-200) { isolation: isolate; + overflow: hidden; } -::view-transition-old(plan-card-20), -::view-transition-old(plan-card-100), -::view-transition-old(plan-card-200) { - animation: fade-out 120ms cubic-bezier(0.4, 0, 0.2, 1) forwards; +::view-transition-old(card-20), +::view-transition-old(card-100), +::view-transition-old(card-200) { mix-blend-mode: normal; } -::view-transition-new(plan-card-20), -::view-transition-new(plan-card-100), -::view-transition-new(plan-card-200) { - animation: fade-in 150ms cubic-bezier(0.4, 0, 0.2, 1) 50ms forwards; - opacity: 0; +::view-transition-new(card-20), +::view-transition-new(card-100), +::view-transition-new(card-200) { mix-blend-mode: normal; } @@ -362,8 +360,7 @@ gap: 12px; padding: 24px; border: 1px solid rgba(255, 255, 255, 0.17); - background-color: rgba(0, 0, 0, 0.75); - backdrop-filter: blur(4px); + background: black; background-clip: padding-box; border-radius: 4px; text-decoration: none; @@ -421,7 +418,6 @@ margin: 0 auto; position: relative; background-color: rgba(0, 0, 0, 0.75); - backdrop-filter: blur(4px); z-index: 1; @media (max-width: 480px) { diff --git a/packages/console/app/src/routes/black/index.tsx b/packages/console/app/src/routes/black/index.tsx index 8f7ee95f3a..af533f8791 100644 --- a/packages/console/app/src/routes/black/index.tsx +++ b/packages/console/app/src/routes/black/index.tsx @@ -98,76 +98,6 @@ export default function Black() { )} - - {(plan) => ( -
-
-
- -
-

- ${plan().id}{" "} - per person billed monthly - - {plan().multiplier} - -

-
    -
  • Your subscription will not start immediately
  • -
  • You will be added to the waitlist and activated soon
  • -
  • Your card will be only charged when your subscription is activated
  • -
  • Usage limits apply, heavily automated use may reach limits sooner
  • -
  • Subscriptions for individuals, contact Enterprise for teams
  • -
  • Limits may be adjusted and plans may be discontinued in the future
  • -
  • Cancel your subscription at anytime
  • -
-
- - - Continue - -
-
-
- )} -
- - {(plan) => ( -
-
-
- -
-

- ${plan().id}{" "} - per person billed monthly - - {plan().multiplier} - -

-
    -
  • Your subscription will not start immediately
  • -
  • You will be added to the waitlist and activated soon
  • -
  • Your card will be only charged when your subscription is activated
  • -
  • Usage limits apply, heavily automated use may reach limits sooner
  • -
  • Subscriptions for individuals, contact Enterprise for teams
  • -
  • Limits may be adjusted and plans may be discontinued in the future
  • -
  • Cancel your subscription at anytime
  • -
-
- - - Continue - -
-
-
- )} -

Prices shown don't include applicable tax ยท Terms of Service From b14622352e542147e5864ff3b6b092b6e5b4d309 Mon Sep 17 00:00:00 2001 From: Qunhong Zeng <871206929@qq.com> Date: Fri, 16 Jan 2026 05:24:13 +0800 Subject: [PATCH 02/57] fix(session): ensure agent exists before processing title in session summary (#8662) --- packages/opencode/src/session/summary.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 2bd1b0da60..dbca218f9c 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -77,6 +77,7 @@ export namespace SessionSummary { const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart if (textPart && !userMsg.summary?.title) { const agent = await Agent.get("title") + if (!agent) return const stream = await LLM.stream({ agent, user: userMsg, From 9862303eedff320ebe632602b6728037feb44371 Mon Sep 17 00:00:00 2001 From: Ricardo Valero de la Rosa <55701657+ricardo-valero@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:45:03 -0600 Subject: [PATCH 03/57] fix: update hix hashes for all systems (#8732) Co-authored-by: Github Action Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- .github/workflows/update-nix-hashes.yml | 73 +++++++++++++------------ nix/hashes.json | 6 +- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/.github/workflows/update-nix-hashes.yml b/.github/workflows/update-nix-hashes.yml index 46ea12d187..20d7d5d9d7 100644 --- a/.github/workflows/update-nix-hashes.yml +++ b/.github/workflows/update-nix-hashes.yml @@ -17,11 +17,11 @@ on: - "packages/*/package.json" jobs: - update-linux: + update-flake: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: ubuntu-latest env: - SYSTEM: x86_64-linux + TITLE: flake.lock steps: - name: Checkout repository @@ -33,39 +33,32 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - name: Setup Nix - uses: DeterminateSystems/nix-installer-action@v20 + uses: nixbuild/nix-quick-install-action@v34 - name: Configure git run: | git config --global user.email "action@github.com" git config --global user.name "Github Action" - - name: Update flake.lock + - name: Update ${{ env.TITLE }} run: | set -euo pipefail - echo "๐Ÿ“ฆ Updating flake.lock..." + echo "๐Ÿ“ฆ Updating $TITLE..." nix flake update - echo "โœ… flake.lock updated successfully" + echo "โœ… $TITLE updated successfully" - - name: Update node_modules hash for x86_64-linux - run: | - set -euo pipefail - echo "๐Ÿ”„ Updating node_modules hash for x86_64-linux..." - nix/scripts/update-hashes.sh - echo "โœ… node_modules hash for x86_64-linux updated successfully" - - - name: Commit Linux hash changes + - name: Commit ${{ env.TITLE }} changes env: TARGET_BRANCH: ${{ github.head_ref || github.ref_name }} run: | set -euo pipefail - echo "๐Ÿ” Checking for changes in tracked Nix files..." + echo "๐Ÿ” Checking for changes in tracked files..." summarize() { local status="$1" { - echo "### Nix Hash Update (x86_64-linux)" + echo "### Nix $TITLE" echo "" echo "- ref: ${GITHUB_REF_NAME}" echo "- status: ${status}" @@ -75,11 +68,10 @@ jobs: fi echo "" >> "$GITHUB_STEP_SUMMARY" } - - FILES=(flake.lock flake.nix nix/node-modules.nix nix/hashes.json) + FILES=(flake.lock flake.nix) STATUS="$(git status --short -- "${FILES[@]}" || true)" if [ -z "$STATUS" ]; then - echo "โœ… No changes detected. Hashes are already up to date." + echo "โœ… No changes detected." summarize "no changes" exit 0 fi @@ -89,7 +81,7 @@ jobs: echo "๐Ÿ”— Staging files..." git add "${FILES[@]}" echo "๐Ÿ’พ Committing changes..." - git commit -m "Update Nix flake.lock and x86_64-linux hash" + git commit -m "Update $TITLE" echo "โœ… Changes committed" BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}" @@ -101,12 +93,25 @@ jobs: summarize "committed $(git rev-parse --short HEAD)" - update-macos: - needs: update-linux + update-node-modules-hash: + needs: update-flake if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: macos-latest + strategy: + fail-fast: false + matrix: + include: + - system: x86_64-linux + host: ubuntu-latest + - system: aarch64-linux + host: ubuntu-22.04-arm + - system: x86_64-darwin + host: macos-15-intel + - system: aarch64-darwin + host: macos-latest + runs-on: ${{ matrix.host }} env: - SYSTEM: aarch64-darwin + SYSTEM: ${{ matrix.system }} + TITLE: node_modules hash (${{ matrix.system }}) steps: - name: Checkout repository @@ -118,7 +123,7 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - name: Setup Nix - uses: DeterminateSystems/nix-installer-action@v20 + uses: nixbuild/nix-quick-install-action@v34 - name: Configure git run: | @@ -132,25 +137,25 @@ jobs: BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}" git pull origin "$BRANCH" - - name: Update node_modules hash for aarch64-darwin + - name: Update ${{ env.TITLE }} run: | set -euo pipefail - echo "๐Ÿ”„ Updating node_modules hash for aarch64-darwin..." + echo "๐Ÿ”„ Updating $TITLE..." nix/scripts/update-hashes.sh - echo "โœ… node_modules hash for aarch64-darwin updated successfully" + echo "โœ… $TITLE updated successfully" - - name: Commit macOS hash changes + - name: Commit ${{ env.TITLE }} changes env: TARGET_BRANCH: ${{ github.head_ref || github.ref_name }} run: | set -euo pipefail - echo "๐Ÿ” Checking for changes in tracked Nix files..." + echo "๐Ÿ” Checking for changes in tracked files..." summarize() { local status="$1" { - echo "### Nix Hash Update (aarch64-darwin)" + echo "### Nix $TITLE" echo "" echo "- ref: ${GITHUB_REF_NAME}" echo "- status: ${status}" @@ -164,7 +169,7 @@ jobs: FILES=(nix/hashes.json) STATUS="$(git status --short -- "${FILES[@]}" || true)" if [ -z "$STATUS" ]; then - echo "โœ… No changes detected. Hash is already up to date." + echo "โœ… No changes detected." summarize "no changes" exit 0 fi @@ -174,7 +179,7 @@ jobs: echo "๐Ÿ”— Staging files..." git add "${FILES[@]}" echo "๐Ÿ’พ Committing changes..." - git commit -m "Update aarch64-darwin hash" + git commit -m "Update $TITLE" echo "โœ… Changes committed" BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}" diff --git a/nix/hashes.json b/nix/hashes.json index a84eec5b47..652b24fece 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,6 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-Fl1BdjNSg19LJVSgDMiBX8JuTaGlL2I5T+rqLfjSeO4=", - "aarch64-darwin": "sha256-7UajHu40n7JKqurU/+CGlitErsVFA2qDneUytI8+/zQ=" + "x86_64-linux": "sha256-4ndHIlS9t1ynRdFszJ1nvcu3YhunhuOc7jcuHI1FbnM=", + "aarch64-linux": "sha256-H9eUk/yVrQqVrAYONlb6As7mjkPXtOauBVfMBeVAmRo=", + "aarch64-darwin": "sha256-C0E9KAEj3GI83HwirIL2zlXYIe92T+7Iv6F51BB6slY=", + "x86_64-darwin": "sha256-wj5fZnyfu6Sf1HcqvsQM3M7dl5BKRAHmoqm1Ai1cL2M=" } } From da3dea0429a3526241119974f18dfcbe8bcf286f Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:11:55 -0600 Subject: [PATCH 04/57] fix(app): persist workspace order and collapsed state --- packages/app/src/pages/layout.tsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 72224f3495..2a36a99f25 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -16,6 +16,7 @@ import { import { A, useNavigate, useParams } from "@solidjs/router" import { useLayout, getAvatarColors, LocalProject } from "@/context/layout" import { useGlobalSync } from "@/context/global-sync" +import { Persist, persisted } from "@/utils/persist" import { base64Decode, base64Encode } from "@opencode-ai/util/encode" import { Avatar } from "@opencode-ai/ui/avatar" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" @@ -62,13 +63,16 @@ import { Titlebar } from "@/components/titlebar" import { useServer } from "@/context/server" export default function Layout(props: ParentProps) { - const [store, setStore] = createStore({ - lastSession: {} as { [directory: string]: string }, - activeProject: undefined as string | undefined, - activeWorkspace: undefined as string | undefined, - workspaceOrder: {} as Record, - workspaceExpanded: {} as Record, - }) + const [store, setStore, , ready] = persisted( + Persist.global("layout.page", ["layout.page.v1"]), + createStore({ + lastSession: {} as { [directory: string]: string }, + activeProject: undefined as string | undefined, + activeWorkspace: undefined as string | undefined, + workspaceOrder: {} as Record, + workspaceExpanded: {} as Record, + }), + ) let scrollContainerRef: HTMLDivElement | undefined const xlQuery = window.matchMedia("(min-width: 1280px)") @@ -289,6 +293,7 @@ export default function Layout(props: ParentProps) { }) createEffect(() => { + if (!ready()) return const project = currentProject() if (!project) return @@ -708,7 +713,7 @@ export default function Layout(props: ParentProps) { const id = params.id setStore("lastSession", directory, id) notification.session.markViewed(id) - untrack(() => setStore("workspaceExpanded", directory, true)) + untrack(() => setStore("workspaceExpanded", directory, (value) => value ?? true)) requestAnimationFrame(() => scrollToSession(id)) }) From 47d43aaf2db7ecb0c6cd5a406efa6a43db656596 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 15 Jan 2026 14:46:26 -0600 Subject: [PATCH 05/57] feat(app): persist workspace branch --- packages/app/src/context/global-sync.tsx | 57 +++++++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index a0b2570568..94c39d2f0c 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -19,15 +19,27 @@ import { type QuestionRequest, createOpencodeClient, } from "@opencode-ai/sdk/v2/client" -import { createStore, produce, reconcile } from "solid-js/store" +import { createStore, produce, reconcile, type SetStoreFunction, type Store } from "solid-js/store" import { Binary } from "@opencode-ai/util/binary" import { retry } from "@opencode-ai/util/retry" import { useGlobalSDK } from "./global-sdk" import { ErrorPage, type InitError } from "../pages/error" -import { batch, createContext, useContext, onCleanup, onMount, type ParentProps, Switch, Match } from "solid-js" +import { + batch, + createContext, + createEffect, + useContext, + onCleanup, + onMount, + type Accessor, + type ParentProps, + Switch, + Match, +} from "solid-js" import { showToast } from "@opencode-ai/ui/toast" import { getFilename } from "@opencode-ai/util/path" import { usePlatform } from "./platform" +import { Persist, persisted } from "@/utils/persist" type State = { status: "loading" | "partial" | "complete" @@ -68,9 +80,16 @@ type State = { } } +type VcsCache = { + store: Store<{ value: VcsInfo | undefined }> + setStore: SetStoreFunction<{ value: VcsInfo | undefined }> + ready: Accessor +} + function createGlobalSync() { const globalSDK = useGlobalSDK() const platform = usePlatform() + const vcsCache = new Map() const [globalStore, setGlobalStore] = createStore<{ ready: boolean error?: InitError @@ -86,10 +105,16 @@ function createGlobalSync() { provider_auth: {}, }) - const children: Record>> = {} + const children: Record, SetStoreFunction]> = {} function child(directory: string) { if (!directory) console.error("No directory provided") if (!children[directory]) { + const cache = persisted( + Persist.workspace(directory, "vcs", ["vcs.v1"]), + createStore({ value: undefined as VcsInfo | undefined }), + ) + vcsCache.set(directory, { store: cache[0], setStore: cache[1], ready: cache[3] }) + children[directory] = createStore({ project: "", provider: { all: [], connected: [], default: {} }, @@ -107,14 +132,16 @@ function createGlobalSync() { question: {}, mcp: {}, lsp: [], - vcs: undefined, + vcs: cache[0].value, limit: 5, message: {}, part: {}, }) bootstrapInstance(directory) } - return children[directory] + const childStore = children[directory] + if (!childStore) throw new Error("Failed to create store") + return childStore } async function loadSessions(directory: string) { @@ -157,6 +184,8 @@ function createGlobalSync() { async function bootstrapInstance(directory: string) { if (!directory) return const [store, setStore] = child(directory) + const cache = vcsCache.get(directory) + if (!cache) return const sdk = createOpencodeClient({ baseUrl: globalSDK.url, fetch: platform.fetch, @@ -164,6 +193,13 @@ function createGlobalSync() { throwOnError: true, }) + createEffect(() => { + if (!cache.ready()) return + const cached = cache.store.value + if (!cached?.branch) return + setStore("vcs", (value) => value ?? cached) + }) + const blockingRequests = { project: () => sdk.project.current().then((x) => setStore("project", x.data!.id)), provider: () => @@ -193,7 +229,11 @@ function createGlobalSync() { loadSessions(directory), sdk.mcp.status().then((x) => setStore("mcp", x.data!)), sdk.lsp.status().then((x) => setStore("lsp", x.data!)), - sdk.vcs.get().then((x) => setStore("vcs", x.data)), + sdk.vcs.get().then((x) => { + const next = x.data ?? store.vcs + setStore("vcs", next) + if (next?.branch) cache.setStore("value", next) + }), sdk.permission.list().then((x) => { const grouped: Record = {} for (const perm of x.data ?? []) { @@ -406,7 +446,10 @@ function createGlobalSync() { break } case "vcs.branch.updated": { - setStore("vcs", { branch: event.properties.branch }) + const next = { branch: event.properties.branch } + setStore("vcs", next) + const cache = vcsCache.get(directory) + if (cache) cache.setStore("value", next) break } case "permission.asked": { From 472a6cc83e1ec51e7c8db4fc40d0f4f36e8dae3a Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 15 Jan 2026 15:24:31 -0600 Subject: [PATCH 06/57] fix(app): sidebar toggle on desktop --- packages/app/src/components/titlebar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 2192ed0e4a..a1ce45a97e 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -17,6 +17,7 @@ export function Titlebar() { const reserve = createMemo( () => platform.platform === "desktop" && (platform.os === "windows" || platform.os === "linux"), ) + const web = createMemo(() => platform.platform === "web") const getWin = () => { if (platform.platform !== "desktop") return @@ -88,7 +89,7 @@ export function Titlebar() { onClick={layout.mobileSidebar.toggle} />

-
+
+
0 && props.notify ? { "-webkit-mask-image": mask, "mask-image": mask } @@ -982,7 +982,7 @@ export default function Layout(props: ParentProps) { + + + {(session) => } @@ -1200,6 +1215,7 @@ export default function Layout(props: ParentProps) { .filter((session) => !session.parentID) .toSorted(sortSessions), ) + const loading = createMemo(() => workspaceStore.status !== "complete" && sessions().length === 0) const hasMore = createMemo(() => workspaceStore.sessionTotal > workspaceStore.session.length) const loadMore = async () => { setWorkspaceStore("limit", (limit) => limit + 5) @@ -1214,6 +1230,9 @@ export default function Layout(props: ParentProps) { class="size-full flex flex-col py-2 overflow-y-auto no-scrollbar" >