Skip to content

Commit be699df

Browse files
committed
Merge branch 'shuvcode-dev' into integration
Closes #316 Syncs upstream v1.1.27, fixes desktop zoom, localStorage quota, message list overflow, and session popover styling.
2 parents 22ed4a3 + a91d094 commit be699df

File tree

39 files changed

+350
-206
lines changed

39 files changed

+350
-206
lines changed

.github/last-synced-tag

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v1.1.26
1+
v1.1.27

STATS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,4 @@
205205
| 2026-01-17 | 4,389,558 (+268,008) | 1,805,315 (+50,897) | 6,194,873 (+318,905) |
206206
| 2026-01-18 | 4,627,623 (+238,065) | 1,839,171 (+33,856) | 6,466,794 (+271,921) |
207207
| 2026-01-19 | 4,861,108 (+233,485) | 1,863,112 (+23,941) | 6,724,220 (+257,426) |
208+
| 2026-01-20 | 5,128,999 (+267,891) | 1,903,665 (+40,553) | 7,032,664 (+308,444) |

packages/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opencode-ai/app",
3-
"version": "1.1.26",
3+
"version": "1.1.27",
44
"description": "",
55
"type": "module",
66
"exports": {

packages/app/src/components/dialog-edit-project.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
8282

8383
return (
8484
<Dialog title="Edit project" class="w-full max-w-[480px] mx-auto">
85-
<form onSubmit={handleSubmit} class="flex flex-col gap-6 p-6">
85+
<form onSubmit={handleSubmit} class="flex flex-col gap-6 p-6 pt-0">
8686
<div class="flex flex-col gap-4">
8787
<TextField
8888
autofocus

packages/app/src/pages/session.tsx

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -814,10 +814,22 @@ export default function Page() {
814814
})
815815

816816
const isWorking = createMemo(() => status().type !== "idle")
817+
817818
const autoScroll = createAutoScroll({
818-
working: isWorking,
819+
working: () => true,
819820
})
820821

822+
createEffect(
823+
on(
824+
isWorking,
825+
(working, prev) => {
826+
if (!working || prev) return
827+
autoScroll.forceScrollToBottom()
828+
},
829+
{ defer: true },
830+
),
831+
)
832+
821833
let scrollSpyFrame: number | undefined
822834
let scrollSpyTarget: HTMLDivElement | undefined
823835

@@ -1127,26 +1139,36 @@ export default function Page() {
11271139
when={!mobileReview()}
11281140
fallback={
11291141
<div class="relative h-full overflow-hidden">
1130-
<Show
1131-
when={diffsReady()}
1132-
fallback={<div class="px-4 py-4 text-text-weak">Loading changes...</div>}
1133-
>
1134-
<SessionReviewTab
1135-
diffs={diffs}
1136-
view={view}
1137-
diffStyle="unified"
1138-
onViewFile={(path) => {
1139-
const value = file.tab(path)
1140-
tabs().open(value)
1141-
file.load(path)
1142-
}}
1143-
classes={{
1144-
root: "pb-[calc(var(--prompt-height,8rem)+32px)]",
1145-
header: "px-4",
1146-
container: "px-4",
1147-
}}
1148-
/>
1149-
</Show>
1142+
<Switch>
1143+
<Match when={hasReview()}>
1144+
<Show
1145+
when={diffsReady()}
1146+
fallback={<div class="px-4 py-4 text-text-weak">Loading changes...</div>}
1147+
>
1148+
<SessionReviewTab
1149+
diffs={diffs}
1150+
view={view}
1151+
diffStyle="unified"
1152+
onViewFile={(path) => {
1153+
const value = file.tab(path)
1154+
tabs().open(value)
1155+
file.load(path)
1156+
}}
1157+
classes={{
1158+
root: "pb-[calc(var(--prompt-height,8rem)+32px)]",
1159+
header: "px-4",
1160+
container: "px-4",
1161+
}}
1162+
/>
1163+
</Show>
1164+
</Match>
1165+
<Match when={true}>
1166+
<div class="h-full px-4 pb-30 flex flex-col items-center justify-center text-center gap-6">
1167+
<Icon name="checklist" class="w-10 h-10 opacity-20" />
1168+
<div class="text-13-regular text-text-weak max-w-56">No changes in this session yet</div>
1169+
</div>
1170+
</Match>
1171+
</Switch>
11501172
</div>
11511173
}
11521174
>
@@ -1214,6 +1236,7 @@ export default function Page() {
12141236
data-message-id={message.id}
12151237
classList={{
12161238
"min-w-0 w-full max-w-full": true,
1239+
"md:max-w-200": !showTabs(),
12171240
"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)]":
12181241
platform.platform !== "desktop",
12191242
"last:min-h-[calc(100vh-7rem-var(--prompt-height,8rem)-64px)] md:last:min-h-[calc(100vh-6rem-var(--prompt-height,10rem)-64px)]":
@@ -1398,22 +1421,32 @@ export default function Page() {
13981421
<Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
13991422
<Show when={activeTab() === "review"}>
14001423
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
1401-
<Show
1402-
when={diffsReady()}
1403-
fallback={<div class="px-6 py-4 text-text-weak">Loading changes...</div>}
1404-
>
1405-
<SessionReviewTab
1406-
diffs={diffs}
1407-
view={view}
1408-
diffStyle={layout.review.diffStyle()}
1409-
onDiffStyleChange={layout.review.setDiffStyle}
1410-
onViewFile={(path) => {
1411-
const value = file.tab(path)
1412-
tabs().open(value)
1413-
file.load(path)
1414-
}}
1415-
/>
1416-
</Show>
1424+
<Switch>
1425+
<Match when={hasReview()}>
1426+
<Show
1427+
when={diffsReady()}
1428+
fallback={<div class="px-6 py-4 text-text-weak">Loading changes...</div>}
1429+
>
1430+
<SessionReviewTab
1431+
diffs={diffs}
1432+
view={view}
1433+
diffStyle={layout.review.diffStyle()}
1434+
onDiffStyleChange={layout.review.setDiffStyle}
1435+
onViewFile={(path) => {
1436+
const value = file.tab(path)
1437+
tabs().open(value)
1438+
file.load(path)
1439+
}}
1440+
/>
1441+
</Show>
1442+
</Match>
1443+
<Match when={true}>
1444+
<div class="h-full px-6 pb-30 flex flex-col items-center justify-center text-center gap-6">
1445+
<Icon name="checklist" class="w-10 h-10 opacity-20" />
1446+
<div class="text-13-regular text-text-weak max-w-56">No changes in this session yet</div>
1447+
</div>
1448+
</Match>
1449+
</Switch>
14171450
</div>
14181451
</Show>
14191452
</Tabs.Content>

packages/app/src/utils/persist.ts

Lines changed: 139 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,83 @@ type PersistTarget = {
1616

1717
const LEGACY_STORAGE = "default.dat"
1818
const GLOBAL_STORAGE = "opencode.global.dat"
19+
const LOCAL_PREFIX = "opencode."
20+
const fallback = { disabled: false }
21+
const cache = new Map<string, string>()
22+
23+
function quota(error: unknown) {
24+
if (error instanceof DOMException) {
25+
if (error.name === "QuotaExceededError") return true
26+
if (error.name === "NS_ERROR_DOM_QUOTA_REACHED") return true
27+
if (error.name === "QUOTA_EXCEEDED_ERR") return true
28+
if (error.code === 22 || error.code === 1014) return true
29+
return false
30+
}
31+
32+
if (!error || typeof error !== "object") return false
33+
const name = (error as { name?: string }).name
34+
if (name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED") return true
35+
if (name && /quota/i.test(name)) return true
36+
37+
const code = (error as { code?: number }).code
38+
if (code === 22 || code === 1014) return true
39+
40+
const message = (error as { message?: string }).message
41+
if (typeof message !== "string") return false
42+
if (/quota/i.test(message)) return true
43+
return false
44+
}
45+
46+
type Evict = { key: string; size: number }
47+
48+
function evict(storage: Storage, keep: string, value: string) {
49+
const total = storage.length
50+
const indexes = Array.from({ length: total }, (_, index) => index)
51+
const items: Evict[] = []
52+
53+
for (const index of indexes) {
54+
const name = storage.key(index)
55+
if (!name) continue
56+
if (!name.startsWith(LOCAL_PREFIX)) continue
57+
if (name === keep) continue
58+
const stored = storage.getItem(name)
59+
items.push({ key: name, size: stored?.length ?? 0 })
60+
}
61+
62+
items.sort((a, b) => b.size - a.size)
63+
64+
for (const item of items) {
65+
storage.removeItem(item.key)
66+
67+
try {
68+
storage.setItem(keep, value)
69+
return true
70+
} catch (error) {
71+
if (!quota(error)) throw error
72+
}
73+
}
74+
75+
return false
76+
}
77+
78+
function write(storage: Storage, key: string, value: string) {
79+
try {
80+
storage.setItem(key, value)
81+
return true
82+
} catch (error) {
83+
if (!quota(error)) throw error
84+
}
85+
86+
try {
87+
storage.removeItem(key)
88+
storage.setItem(key, value)
89+
return true
90+
} catch (error) {
91+
if (!quota(error)) throw error
92+
}
93+
94+
return evict(storage, key, value)
95+
}
1996

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

68145
function localStorageWithPrefix(prefix: string): SyncStorage {
69146
const base = `${prefix}:`
147+
const item = (key: string) => base + key
148+
return {
149+
getItem: (key) => {
150+
const name = item(key)
151+
const cached = cache.get(name)
152+
if (fallback.disabled && cached !== undefined) return cached
153+
154+
const stored = localStorage.getItem(name)
155+
if (stored === null) return cached ?? null
156+
cache.set(name, stored)
157+
return stored
158+
},
159+
setItem: (key, value) => {
160+
const name = item(key)
161+
cache.set(name, value)
162+
if (fallback.disabled) return
163+
try {
164+
if (write(localStorage, name, value)) return
165+
} catch {
166+
fallback.disabled = true
167+
return
168+
}
169+
fallback.disabled = true
170+
},
171+
removeItem: (key) => {
172+
const name = item(key)
173+
cache.delete(name)
174+
if (fallback.disabled) return
175+
localStorage.removeItem(name)
176+
},
177+
}
178+
}
179+
180+
function localStorageDirect(): SyncStorage {
70181
return {
71-
getItem: (key) => localStorage.getItem(base + key),
72-
setItem: (key, value) => localStorage.setItem(base + key, value),
73-
removeItem: (key) => localStorage.removeItem(base + key),
182+
getItem: (key) => {
183+
const cached = cache.get(key)
184+
if (fallback.disabled && cached !== undefined) return cached
185+
186+
const stored = localStorage.getItem(key)
187+
if (stored === null) return cached ?? null
188+
cache.set(key, stored)
189+
return stored
190+
},
191+
setItem: (key, value) => {
192+
cache.set(key, value)
193+
if (fallback.disabled) return
194+
try {
195+
if (write(localStorage, key, value)) return
196+
} catch {
197+
fallback.disabled = true
198+
return
199+
}
200+
fallback.disabled = true
201+
},
202+
removeItem: (key) => {
203+
cache.delete(key)
204+
if (fallback.disabled) return
205+
localStorage.removeItem(key)
206+
},
74207
}
75208
}
76209

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

101234
if (!target.storage) {
102-
localStorage.removeItem(target.key)
235+
localStorageDirect().removeItem(target.key)
103236
return
104237
}
105238

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

121254
const currentStorage = (() => {
122255
if (isDesktop) return platform.storage?.(config.storage)
123-
if (!config.storage) return localStorage
256+
if (!config.storage) return localStorageDirect()
124257
return localStorageWithPrefix(config.storage)
125258
})()
126259

127260
const legacyStorage = (() => {
128-
if (!isDesktop) return localStorage
261+
if (!isDesktop) return localStorageDirect()
129262
if (!config.storage) return platform.storage?.()
130263
return platform.storage?.(LEGACY_STORAGE)
131264
})()

packages/console/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opencode-ai/console-app",
3-
"version": "1.1.26",
3+
"version": "1.1.27",
44
"type": "module",
55
"license": "MIT",
66
"scripts": {

packages/console/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "@opencode-ai/console-core",
4-
"version": "1.1.26",
4+
"version": "1.1.27",
55
"private": true,
66
"type": "module",
77
"license": "MIT",

packages/console/function/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opencode-ai/console-function",
3-
"version": "1.1.26",
3+
"version": "1.1.27",
44
"$schema": "https://json.schemastore.org/package.json",
55
"private": true,
66
"type": "module",

packages/console/mail/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@opencode-ai/console-mail",
3-
"version": "1.1.26",
3+
"version": "1.1.27",
44
"dependencies": {
55
"@jsx-email/all": "2.2.3",
66
"@jsx-email/cli": "1.4.3",

0 commit comments

Comments
 (0)