chore: merge upstream anomalyco/opencode dev - #6
Conversation
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis release updates package versions and model endpoints, migrates V2 toasts to Solid Sonner, adds attachment deduplication and localized feedback, centralizes tab migration, and updates session-review navigation and sidebar behavior. ChangesRelease and model configuration
Toast system migration
Attachment deduplication
Tab state migration
Session review navigation
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
1 similar comment
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
Found 4 test failures on Blacksmith runners: Failures
|
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/app/src/context/tab-migration.ts`:
- Around line 8-9: Update the server selection logic in the tab migration to use
fallback when tab.server is absent or not a string, rather than returning an
empty tab list for invalid persisted values. Preserve valid string server
values, and add a regression case covering a legacy tab with server: null to
verify it is retained with fallback.
In `@packages/app/src/i18n/uk.ts`:
- Line 302: Update the duplicate-attachment title translations to use attachment
terminology instead of upload terminology: in packages/app/src/i18n/uk.ts lines
302-302, use “Це вкладення вже додано” or equivalent; in
packages/app/src/i18n/zh.ts lines 319-319, use “此附件已添加” or equivalent.
In `@packages/app/src/utils/toast.tsx`:
- Around line 36-41: Update the toast ID tracking around showToastV2 and
dismissToast so each issued ID records whether it belongs to the v2 registry,
using the existing v2Ids mechanism. Make dismissToast consult and remove that
per-ID origin record before routing dismissal, falling back to the legacy
registry for IDs not recorded as v2, rather than reading the mutable v2 flag.
In `@packages/core/src/models-dev.ts`:
- Around line 154-157: Update the cache path construction in the model-loading
flow around Flag.OPENCODE_MODELS_URL and loadFromDisk so the default opencode.ai
source no longer reuses the legacy models.json cache. Use a
source/version-specific filename or explicitly migrate and invalidate the legacy
cache, preserving existing cache behavior for current sources; add a test
covering the upgrade from the models.dev cache.
In `@packages/session-ui/src/v2/components/session-review-v2.tsx`:
- Around line 167-177: Update canCycle in the session review navigation logic to
return true only when props.files contains more than one file. Preserve the
existing prev, next, and cycle behavior for multi-file lists while disabling
navigation for empty and single-file lists.
In `@packages/ui/src/v2/components/toast-v2.tsx`:
- Line 11: Update the ToastV2 component’s swipeDirections handling: either
forward the extracted swipeDirections value to the underlying toaster/region or
stop extracting it so it remains in rest. Remove the hardcoded ["bottom"]
behavior at the affected render path so consumer-provided
ToastV2RegionProps.swipeDirections take effect.
- Around line 20-42: Update the MutationObserver setup around connect so
document.body remains observed while watching current .toast-v2-region nodes; do
not disconnect the body observer when connecting to regions. Detect when all
observed regions disappear or are replaced, reset connected to false, and let
the next body mutation re-run connect and sync so recreated regions receive
inert/tabIndex updates.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e84b2ef9-71f6-4daf-a79c-562799cd865c
📒 Files selected for processing (71)
infra/console.tsnix/hashes.jsonpackage.jsonpackages/app/package.jsonpackages/app/src/components/prompt-input-v2.tsxpackages/app/src/components/titlebar.tsxpackages/app/src/context/tab-migration.tspackages/app/src/context/tabs.test.tspackages/app/src/context/tabs.tsxpackages/app/src/i18n/ar.tspackages/app/src/i18n/br.tspackages/app/src/i18n/bs.tspackages/app/src/i18n/da.tspackages/app/src/i18n/de.tspackages/app/src/i18n/en.tspackages/app/src/i18n/es.tspackages/app/src/i18n/fr.tspackages/app/src/i18n/ja.tspackages/app/src/i18n/ko.tspackages/app/src/i18n/no.tspackages/app/src/i18n/pl.tspackages/app/src/i18n/ru.tspackages/app/src/i18n/th.tspackages/app/src/i18n/tr.tspackages/app/src/i18n/uk.tspackages/app/src/i18n/zh.tspackages/app/src/i18n/zht.tspackages/app/src/pages/layout.tsxpackages/app/src/utils/toast.tsxpackages/app/test-browser/prompt-attachments.test.tspackages/app/test-browser/toast-owner.test.tspackages/cli/package.jsonpackages/cli/script/generate.tspackages/codemode/package.jsonpackages/console/app/package.jsonpackages/console/core/package.jsonpackages/console/function/package.jsonpackages/console/mail/package.jsonpackages/console/support/package.jsonpackages/core/package.jsonpackages/core/src/models-dev.tspackages/desktop/package.jsonpackages/effect-drizzle-sqlite/package.jsonpackages/effect-sqlite-node/package.jsonpackages/enterprise/package.jsonpackages/function/package.jsonpackages/http-recorder/package.jsonpackages/llm/package.jsonpackages/llm/script/recording-cost-report.tspackages/plugin/package.jsonpackages/sdk/js/package.jsonpackages/server/package.jsonpackages/session-ui/package.jsonpackages/session-ui/src/v2/components/prompt-input/attachments.csspackages/session-ui/src/v2/components/prompt-input/attachments.tspackages/session-ui/src/v2/components/prompt-input/index.tsxpackages/session-ui/src/v2/components/session-review-v2.csspackages/session-ui/src/v2/components/session-review-v2.tsxpackages/slack/package.jsonpackages/stats/app/package.jsonpackages/stats/app/src/routes/model-catalog.tspackages/stats/core/package.jsonpackages/stats/server/package.jsonpackages/tui/package.jsonpackages/ui/package.jsonpackages/ui/src/v2/components/toast-v2.csspackages/ui/src/v2/components/toast-v2.stories.tsxpackages/ui/src/v2/components/toast-v2.tsxpackages/ui/vite.config.tspackages/web/package.jsonsdks/vscode/package.json
| if ("server" in tab && typeof tab.server !== "string") return [] | ||
| const server = ("server" in tab ? tab.server : fallback) as ServerConnection.Key |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Restore fallback handling for invalid persisted server values.
The prior migration backfilled missing and invalid server values, but this now drops otherwise valid legacy tabs (for example, { type: "session", sessionId: "a", server: null }). That silently loses persisted tabs during upgrade. Use fallback whenever the stored server is absent or non-string, and add a regression case.
Proposed fix
- if ("server" in tab && typeof tab.server !== "string") return []
- const server = ("server" in tab ? tab.server : fallback) as ServerConnection.Key
+ const savedServer = "server" in tab ? tab.server : undefined
+ const server = (typeof savedServer === "string" ? savedServer : fallback) as ServerConnection.Key📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ("server" in tab && typeof tab.server !== "string") return [] | |
| const server = ("server" in tab ? tab.server : fallback) as ServerConnection.Key | |
| const savedServer = "server" in tab ? tab.server : undefined | |
| const server = (typeof savedServer === "string" ? savedServer : fallback) as ServerConnection.Key |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/app/src/context/tab-migration.ts` around lines 8 - 9, Update the
server selection logic in the tab migration to use fallback when tab.server is
absent or not a string, rather than returning an empty tab list for invalid
persisted values. Preserve valid string server values, and add a regression case
covering a legacy tab with server: null to verify it is retained with fallback.
| "prompt.action.stop": "Зупинити", | ||
|
|
||
| "prompt.toast.pasteUnsupported.title": "Непідтримуване вкладення", | ||
| "prompt.toast.attachmentDuplicate.title": "Цей файл уже завантажено", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use attachment terminology for duplicate feedback. The localized messages describe an upload, although the feature detects an attachment already present locally.
packages/app/src/i18n/uk.ts#L302-L302: change the text toЦе вкладення вже доданоor equivalent.packages/app/src/i18n/zh.ts#L319-L319: change the text to此附件已添加or equivalent.
📍 Affects 2 files
packages/app/src/i18n/uk.ts#L302-L302(this comment)packages/app/src/i18n/zh.ts#L319-L319
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/app/src/i18n/uk.ts` at line 302, Update the duplicate-attachment
title translations to use attachment terminology instead of upload terminology:
in packages/app/src/i18n/uk.ts lines 302-302, use “Це вкладення вже додано” or
equivalent; in packages/app/src/i18n/zh.ts lines 319-319, use “此附件已添加” or
equivalent.
| // v1 and v2 ids come from separate registries, so dismissal has to use the same | ||
| // implementation that issued the id. | ||
| export function dismissToast(toastId: number) { | ||
| if (!v2) return legacyToaster.dismiss(toastId) | ||
| return toasterV2.dismiss(toastId) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Dismissal routes on the current flag, not the flag that issued the id.
v2 is mutable module state (setV2Toast), while persistent toasts (session alerts, update-available, workspace reset progress) can outlive a flag flip. After a flip, dismissToast(id) hits the other registry and the toast is never removed — the very failure the comment above it warns about. Track the origin with the id instead of re-reading the flag.
🛡️ Sketch: remember which registry issued each id
+const v2Ids = new Set<number>()
+
export function dismissToast(toastId: number) {
- if (!v2) return legacyToaster.dismiss(toastId)
- return toasterV2.dismiss(toastId)
+ if (!v2Ids.delete(toastId)) return legacyToaster.dismiss(toastId)
+ return toasterV2.dismiss(toastId)
}Record the id in v2Ids at the showToastV2(...) call site (Lines 24-33).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/app/src/utils/toast.tsx` around lines 36 - 41, Update the toast ID
tracking around showToastV2 and dismissToast so each issued ID records whether
it belongs to the v2 registry, using the existing v2Ids mechanism. Make
dismissToast consult and remove that per-ID origin record before routing
dismissal, falling back to the legacy registry for IDs not recorded as v2,
rather than reading the mutable v2 flag.
| const source = Flag.OPENCODE_MODELS_URL || "https://models.opencode.ai" | ||
| const filepath = path.join( | ||
| Global.Path.cache, | ||
| source === "https://models.dev" ? "models.json" : `models-${Hash.fast(source)}.json`, | ||
| source === "https://models.opencode.ai" ? "models.json" : `models-${Hash.fast(source)}.json`, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Use a versioned cache key or migrate the legacy models.json cache.
The previous default source and the new source both resolve to Global.Path.cache/models.json, while loadFromDisk returns that file before fetching. Existing users can therefore load a cache populated from models.dev as if it came from models.opencode.ai, leaving the new catalog stale until a later refresh.
Use a source/version-specific filename or explicitly invalidate the legacy cache during migration, and add an upgrade-path test.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/models-dev.ts` around lines 154 - 157, Update the cache
path construction in the model-loading flow around Flag.OPENCODE_MODELS_URL and
loadFromDisk so the default opencode.ai source no longer reuses the legacy
models.json cache. Use a source/version-specific filename or explicitly migrate
and invalidate the legacy cache, preserving existing cache behavior for current
sources; add a test covering the upgrade from the models.dev cache.
| const prev = () => { | ||
| return props.files[fileIndex() - 1] | ||
| if (!canCycle()) return | ||
| return props.files[(fileIndex() - 1 + props.files.length) % props.files.length] | ||
| } | ||
|
|
||
| const next = () => { | ||
| return props.files[fileIndex() + 1] | ||
| if (!canCycle()) return | ||
| return props.files[(fileIndex() + 1) % props.files.length] | ||
| } | ||
|
|
||
| const canCycle = () => props.files.length > 0 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
canCycle() allows cycling with a single file.
canCycle only checks props.files.length > 0. With exactly one file, prev()/next() still resolve via modulo to that same file, so the nav buttons stay enabled and the arrow-key shortcut fires cycle() on a no-op destination (single-file diffs are a common case for session review). Consider length > 1.
🔧 Proposed fix
- const canCycle = () => props.files.length > 0
+ const canCycle = () => props.files.length > 1📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const prev = () => { | |
| return props.files[fileIndex() - 1] | |
| if (!canCycle()) return | |
| return props.files[(fileIndex() - 1 + props.files.length) % props.files.length] | |
| } | |
| const next = () => { | |
| return props.files[fileIndex() + 1] | |
| if (!canCycle()) return | |
| return props.files[(fileIndex() + 1) % props.files.length] | |
| } | |
| const canCycle = () => props.files.length > 0 | |
| const prev = () => { | |
| if (!canCycle()) return | |
| return props.files[(fileIndex() - 1 + props.files.length) % props.files.length] | |
| } | |
| const next = () => { | |
| if (!canCycle()) return | |
| return props.files[(fileIndex() + 1) % props.files.length] | |
| } | |
| const canCycle = () => props.files.length > 1 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/session-ui/src/v2/components/session-review-v2.tsx` around lines 167
- 177, Update canCycle in the session review navigation logic to return true
only when props.files contains more than one file. Preserve the existing prev,
next, and cycle behavior for multi-file lists while disabling navigation for
empty and single-file lists.
| export interface ToastV2RegionProps extends ToasterProps {} | ||
|
|
||
| function ToastV2Region(props: ToastV2RegionProps) { | ||
| const [local, rest] = splitProps(props, ["class", "className", "style", "toastOptions", "swipeDirections"]) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
swipeDirections is split out but never forwarded, so the prop is silently ignored.
ToastV2RegionProps extends ToasterProps, yet swipeDirections is removed from rest at Line 11 and Line 52 hardcodes ["bottom"]. Consumers passing swipeDirections get no effect. Either merge it or stop extracting it.
🐛 Proposed fix
- swipeDirections={["bottom"]}
+ swipeDirections={local.swipeDirections ?? ["bottom"]}Also applies to: 52-52
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/v2/components/toast-v2.tsx` at line 11, Update the ToastV2
component’s swipeDirections handling: either forward the extracted
swipeDirections value to the underlying toaster/region or stop extracting it so
it remains in rest. Remove the hardcoded ["bottom"] behavior at the affected
render path so consumer-provided ToastV2RegionProps.swipeDirections take effect.
| let connected = false | ||
| const connect = () => { | ||
| const regions = document.querySelectorAll(".toast-v2-region") | ||
| if (!regions.length) return | ||
| observer.disconnect() | ||
| regions.forEach((region) => { | ||
| observer.observe(region, { | ||
| subtree: true, | ||
| childList: true, | ||
| attributes: true, | ||
| attributeFilter: ["data-visible"], | ||
| }) | ||
| }) | ||
| connected = true | ||
| sync() | ||
| } | ||
| const observer = new MutationObserver(() => { | ||
| if (!connected) connect() | ||
| else sync() | ||
| }) | ||
| observer.observe(document.body, { subtree: true, childList: true }) | ||
| queueMicrotask(connect) | ||
| onCleanup(() => observer.disconnect()) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Region sync stops permanently if the toast region is replaced.
connect() disconnects the body observer and only observes the current .toast-v2-region nodes, and connected is never reset. If Sonner tears down and recreates the region (or the portal remounts), no observer watches document.body anymore, so inert/tabIndex are never resynced and hidden stacked toasts stay keyboard-focusable.
🐛 Proposed fix: keep watching the body and re-connect on region churn
let connected = false
const connect = () => {
const regions = document.querySelectorAll(".toast-v2-region")
- if (!regions.length) return
- observer.disconnect()
+ if (!regions.length) {
+ connected = false
+ return
+ }
regions.forEach((region) => {
observer.observe(region, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ["data-visible"],
})
})
connected = true
sync()
}
const observer = new MutationObserver(() => {
- if (!connected) connect()
- else sync()
+ if (!connected) connect()
+ else sync()
})
observer.observe(document.body, { subtree: true, childList: true })With the body observation retained, connected flips back to false whenever the region disappears and the next body mutation re-attaches the attribute observers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/v2/components/toast-v2.tsx` around lines 20 - 42, Update the
MutationObserver setup around connect so document.body remains observed while
watching current .toast-v2-region nodes; do not disconnect the body observer
when connecting to regions. Detect when all observed regions disappear or are
replaced, reset connected to false, and let the next body mutation re-run
connect and sync so recreated regions receive inert/tabIndex updates.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light.png)
Issue for this PR
Closes anomalyco#7 (supersedes #5)
Type of change
Upstream sync (chore): merges
anomalyco/opencode:devintodev.What does this PR do?
Resolves the merge conflicts blocking #5 by merging
anomalyco/opencode:dev(v1.18.10,8c38d26) intodevwith conflicts resolved. That PR's head lives in the upstream repo, so the conflicts can't be fixed there; merging this supersedes it and pull[bot] should close it oncedevcontains the upstream commits.Conflict resolutions follow the precedent from previous upstream merges (e.g.
f6697a3):packages/opencode/package.jsonandbun.lock: kept the fork identity (@bolt-builder/bolt-cli, version0.0.1,private: false) over upstream'sopencode@1.18.10. The lockfile is regenerated from upstream's resolutions (git show 8c38d26:bun.lock+bun install --lockfile-only) so external pins match upstream; this fixes thestorybook buildfailure caused by the fork's lock drifting tostorybook-solidjs-vite@10.6.0(upstream pins10.1.1).sdks/vscode/package.json: kept Bolt branding, took upstream's version bump to1.18.10.nix/hashes.json: took upstream's hashes; thenix-hashesworkflow recomputes them automatically on push todevsincebun.lockchanged.How did you verify your code works?
The conflict resolutions mirror the ones used in previous upstream merges (e.g.
f6697a3), and CI on this PR validates the merged tree. No fork-local code is changed beyond resolving the conflicts.Screenshots / recordings
Not a UI change in the fork; all UI changes come verbatim from upstream commits.
Checklist
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is enabled.Summary by CodeRabbit
New Features
Bug Fixes
Chores