Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bec294b
fix(app): remove copy button from summary
adamdotdevin Jan 19, 2026
2542693
chore: generate
actions-user Jan 19, 2026
aa4b06e
tui: fix message history cleanup to prevent memory leaks
thdxr Jan 19, 2026
bfa986d
feat(app): Add ability to select project directory text to web (#9344)
DNGriffin Jan 19, 2026
054ccee
update review session empty state styling
iamdavidhill Jan 20, 2026
cf284e3
update session hover popover styling
iamdavidhill Jan 20, 2026
a05c334
retain session hover state when popover open and update border radius
iamdavidhill Jan 20, 2026
ad31b55
position session messages popover at top
iamdavidhill Jan 20, 2026
7f9ffe5
update thinking text styling in desktop app
iamdavidhill Jan 20, 2026
7b336ad
update session messages popover gutter to 28px
iamdavidhill Jan 20, 2026
6ed656a
remove top padding from edit project dialog form
iamdavidhill Jan 20, 2026
b91b76e
add 8px padding to recent sessions popover
iamdavidhill Jan 20, 2026
4ddfa86
fix(app): message list overflow & scrolling (#9530)
neriousy Jan 20, 2026
088b537
chore: generate
actions-user Jan 20, 2026
36f5ba5
fix(batch): update batch tool definition to outline correct value for…
jamesmengo Jan 20, 2026
0d49df4
fix: ensure truncation handling applies to mcp servers too
rekram1-node Jan 20, 2026
4190049
chore: remove duplicate prompt file
rekram1-node Jan 20, 2026
68d1755
fix: add space toggle hint to tool selection prompt (#9535)
cjellick Jan 20, 2026
8b37932
fix(desktop): completely disable pinch to zoom
Brendonovich Jan 20, 2026
9706aaf
rm filetime assertions from patch tool
rekram1-node Jan 20, 2026
616329a
chore: generate
actions-user Jan 20, 2026
5f03721
fix(app): persist quota
adamdotdevin Jan 19, 2026
353115a
fix(app): user message expand on click
adamdotdevin Jan 20, 2026
b711ca5
fix(app): localStorage quota
adamdotdevin Jan 20, 2026
347cd8a
chore: cleanup
adamdotdevin Jan 20, 2026
5145b72
chore: cleanup
adamdotdevin Jan 20, 2026
0596b02
chore: cleanup
adamdotdevin Jan 20, 2026
27406cf
chore: generate
actions-user Jan 20, 2026
23d71e1
ignore: update download stats 2026-01-20
actions-user Jan 20, 2026
04e60f2
fix(app): no flash of home page on start
adamdotdevin Jan 20, 2026
e521fee
release: v1.1.27
Jan 20, 2026
a04bd5c
sync: merge upstream v1.1.27 into shuvcode-dev
shuv1337 Jan 20, 2026
6aac6d1
sync: record last synced tag v1.1.27
shuv1337 Jan 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/last-synced-tag
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.1.26
v1.1.27
1 change: 1 addition & 0 deletions STATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,4 @@
| 2026-01-17 | 4,389,558 (+268,008) | 1,805,315 (+50,897) | 6,194,873 (+318,905) |
| 2026-01-18 | 4,627,623 (+238,065) | 1,839,171 (+33,856) | 6,466,794 (+271,921) |
| 2026-01-19 | 4,861,108 (+233,485) | 1,863,112 (+23,941) | 6,724,220 (+257,426) |
| 2026-01-20 | 5,128,999 (+267,891) | 1,903,665 (+40,553) | 7,032,664 (+308,444) |
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.1.26",
"version": "1.1.27",
"description": "",
"type": "module",
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/components/dialog-edit-project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function DialogEditProject(props: { project: LocalProject }) {

return (
<Dialog title="Edit project" class="w-full max-w-[480px] mx-auto">
<form onSubmit={handleSubmit} class="flex flex-col gap-6 p-6">
<form onSubmit={handleSubmit} class="flex flex-col gap-6 p-6 pt-0">
<div class="flex flex-col gap-4">
<TextField
autofocus
Expand Down
107 changes: 70 additions & 37 deletions packages/app/src/pages/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -814,10 +814,22 @@ export default function Page() {
})

const isWorking = createMemo(() => status().type !== "idle")

const autoScroll = createAutoScroll({
working: isWorking,
working: () => true,
})

createEffect(
on(
isWorking,
(working, prev) => {
if (!working || prev) return
autoScroll.forceScrollToBottom()
},
{ defer: true },
),
)

let scrollSpyFrame: number | undefined
let scrollSpyTarget: HTMLDivElement | undefined

Expand Down Expand Up @@ -1127,26 +1139,36 @@ export default function Page() {
when={!mobileReview()}
fallback={
<div class="relative h-full overflow-hidden">
<Show
when={diffsReady()}
fallback={<div class="px-4 py-4 text-text-weak">Loading changes...</div>}
>
<SessionReviewTab
diffs={diffs}
view={view}
diffStyle="unified"
onViewFile={(path) => {
const value = file.tab(path)
tabs().open(value)
file.load(path)
}}
classes={{
root: "pb-[calc(var(--prompt-height,8rem)+32px)]",
header: "px-4",
container: "px-4",
}}
/>
</Show>
<Switch>
<Match when={hasReview()}>
<Show
when={diffsReady()}
fallback={<div class="px-4 py-4 text-text-weak">Loading changes...</div>}
>
<SessionReviewTab
diffs={diffs}
view={view}
diffStyle="unified"
onViewFile={(path) => {
const value = file.tab(path)
tabs().open(value)
file.load(path)
}}
classes={{
root: "pb-[calc(var(--prompt-height,8rem)+32px)]",
header: "px-4",
container: "px-4",
}}
/>
</Show>
</Match>
<Match when={true}>
<div class="h-full px-4 pb-30 flex flex-col items-center justify-center text-center gap-6">
<Icon name="checklist" class="w-10 h-10 opacity-20" />
<div class="text-13-regular text-text-weak max-w-56">No changes in this session yet</div>
</div>
</Match>
</Switch>
</div>
}
>
Expand Down Expand Up @@ -1214,6 +1236,7 @@ export default function Page() {
data-message-id={message.id}
classList={{
"min-w-0 w-full max-w-full": true,
"md:max-w-200": !showTabs(),
"last:min-h-[calc(100vh-5.5rem-var(--prompt-height,8rem)-64px)] md:last:min-h-[calc(100vh-4.5rem-var(--prompt-height,10rem)-64px)]":
platform.platform !== "desktop",
"last:min-h-[calc(100vh-7rem-var(--prompt-height,8rem)-64px)] md:last:min-h-[calc(100vh-6rem-var(--prompt-height,10rem)-64px)]":
Expand Down Expand Up @@ -1398,22 +1421,32 @@ export default function Page() {
<Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
<Show when={activeTab() === "review"}>
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
<Show
when={diffsReady()}
fallback={<div class="px-6 py-4 text-text-weak">Loading changes...</div>}
>
<SessionReviewTab
diffs={diffs}
view={view}
diffStyle={layout.review.diffStyle()}
onDiffStyleChange={layout.review.setDiffStyle}
onViewFile={(path) => {
const value = file.tab(path)
tabs().open(value)
file.load(path)
}}
/>
</Show>
<Switch>
<Match when={hasReview()}>
<Show
when={diffsReady()}
fallback={<div class="px-6 py-4 text-text-weak">Loading changes...</div>}
>
<SessionReviewTab
diffs={diffs}
view={view}
diffStyle={layout.review.diffStyle()}
onDiffStyleChange={layout.review.setDiffStyle}
onViewFile={(path) => {
const value = file.tab(path)
tabs().open(value)
file.load(path)
}}
/>
</Show>
</Match>
<Match when={true}>
<div class="h-full px-6 pb-30 flex flex-col items-center justify-center text-center gap-6">
<Icon name="checklist" class="w-10 h-10 opacity-20" />
<div class="text-13-regular text-text-weak max-w-56">No changes in this session yet</div>
</div>
</Match>
</Switch>
</div>
</Show>
</Tabs.Content>
Expand Down
145 changes: 139 additions & 6 deletions packages/app/src/utils/persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,83 @@ type PersistTarget = {

const LEGACY_STORAGE = "default.dat"
const GLOBAL_STORAGE = "opencode.global.dat"
const LOCAL_PREFIX = "opencode."
const fallback = { disabled: false }
const cache = new Map<string, string>()

function quota(error: unknown) {
if (error instanceof DOMException) {
if (error.name === "QuotaExceededError") return true
if (error.name === "NS_ERROR_DOM_QUOTA_REACHED") return true
if (error.name === "QUOTA_EXCEEDED_ERR") return true
if (error.code === 22 || error.code === 1014) return true
return false
}

if (!error || typeof error !== "object") return false
const name = (error as { name?: string }).name
if (name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED") return true
if (name && /quota/i.test(name)) return true

const code = (error as { code?: number }).code
if (code === 22 || code === 1014) return true

const message = (error as { message?: string }).message
if (typeof message !== "string") return false
if (/quota/i.test(message)) return true
return false
}

type Evict = { key: string; size: number }

function evict(storage: Storage, keep: string, value: string) {
const total = storage.length
const indexes = Array.from({ length: total }, (_, index) => index)
const items: Evict[] = []

for (const index of indexes) {
const name = storage.key(index)
if (!name) continue
if (!name.startsWith(LOCAL_PREFIX)) continue
if (name === keep) continue
const stored = storage.getItem(name)
items.push({ key: name, size: stored?.length ?? 0 })
}

items.sort((a, b) => b.size - a.size)

for (const item of items) {
storage.removeItem(item.key)

try {
storage.setItem(keep, value)
return true
} catch (error) {
if (!quota(error)) throw error
}
}

return false
}

function write(storage: Storage, key: string, value: string) {
try {
storage.setItem(key, value)
return true
} catch (error) {
if (!quota(error)) throw error
}

try {
storage.removeItem(key)
storage.setItem(key, value)
return true
} catch (error) {
if (!quota(error)) throw error
}

return evict(storage, key, value)
}

function snapshot(value: unknown) {
return JSON.parse(JSON.stringify(value)) as unknown
Expand Down Expand Up @@ -67,10 +144,66 @@ function workspaceStorage(dir: string) {

function localStorageWithPrefix(prefix: string): SyncStorage {
const base = `${prefix}:`
const item = (key: string) => base + key
return {
getItem: (key) => {
const name = item(key)
const cached = cache.get(name)
if (fallback.disabled && cached !== undefined) return cached

const stored = localStorage.getItem(name)
if (stored === null) return cached ?? null
cache.set(name, stored)
return stored
},
setItem: (key, value) => {
const name = item(key)
cache.set(name, value)
if (fallback.disabled) return
try {
if (write(localStorage, name, value)) return
} catch {
fallback.disabled = true
return
}
fallback.disabled = true
},
removeItem: (key) => {
const name = item(key)
cache.delete(name)
if (fallback.disabled) return
localStorage.removeItem(name)
},
}
}

function localStorageDirect(): SyncStorage {
return {
getItem: (key) => localStorage.getItem(base + key),
setItem: (key, value) => localStorage.setItem(base + key, value),
removeItem: (key) => localStorage.removeItem(base + key),
getItem: (key) => {
const cached = cache.get(key)
if (fallback.disabled && cached !== undefined) return cached

const stored = localStorage.getItem(key)
if (stored === null) return cached ?? null
cache.set(key, stored)
return stored
},
setItem: (key, value) => {
cache.set(key, value)
if (fallback.disabled) return
try {
if (write(localStorage, key, value)) return
} catch {
fallback.disabled = true
return
}
fallback.disabled = true
},
removeItem: (key) => {
cache.delete(key)
if (fallback.disabled) return
localStorage.removeItem(key)
},
}
}

Expand Down Expand Up @@ -99,7 +232,7 @@ export function removePersisted(target: { storage?: string; key: string }) {
}

if (!target.storage) {
localStorage.removeItem(target.key)
localStorageDirect().removeItem(target.key)
return
}

Expand All @@ -120,12 +253,12 @@ export function persisted<T>(

const currentStorage = (() => {
if (isDesktop) return platform.storage?.(config.storage)
if (!config.storage) return localStorage
if (!config.storage) return localStorageDirect()
return localStorageWithPrefix(config.storage)
})()

const legacyStorage = (() => {
if (!isDesktop) return localStorage
if (!isDesktop) return localStorageDirect()
if (!config.storage) return platform.storage?.()
return platform.storage?.(LEGACY_STORAGE)
})()
Expand Down
2 changes: 1 addition & 1 deletion packages/console/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.1.26",
"version": "1.1.27",
"type": "module",
"license": "MIT",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/console/core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.1.26",
"version": "1.1.27",
"private": true,
"type": "module",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/console/function/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.1.26",
"version": "1.1.27",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/console/mail/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.1.26",
"version": "1.1.27",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@shuvcode/desktop",
"private": true,
"version": "1.1.26",
"version": "1.1.27",
"type": "module",
"license": "MIT",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"url": "/",
"decorations": true,
"dragDropEnabled": false,
"zoomHotkeysEnabled": true,
"zoomHotkeysEnabled": false,
"titleBarStyle": "Overlay",
"hiddenTitle": true,
"trafficLightPosition": { "x": 12.0, "y": 18.0 }
Expand Down
Loading
Loading