fix(app): restore attachment classification (#49932)

Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
This commit is contained in:
opencode-agent[bot]
2026-09-19 09:13:29 +00:00
committed by GitHub
parent 839aa25c3e
commit 5848ee0d24
4 changed files with 66 additions and 3 deletions
@@ -166,6 +166,45 @@ test.describe("session timeline projection", () => {
await expect(longNotice.locator("[title]")).toHaveAttribute("title", `Switched to ${longName}`)
await expect.poll(() => longNotice.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true)
})
test("does not repeat a review comment file as an attachment", async ({ page }) => {
const message = userMessage([
userText("what's goin on here", { id: "prt_user_review_comment" }),
{
id: "prt_user_review_file",
type: "file",
mime: "text/plain",
filename: "LiveRuntime.ts",
url: "file:///repo/LiveRuntime.ts?start=14&end=32",
},
{
id: "prt_user_unrelated_file",
type: "file",
mime: "text/plain",
filename: "notes.txt",
url: "data:text/plain;base64,bm90ZXM=",
},
])
message.metadata = {
displayText: "what's goin on here",
comments: [
{
path: "LiveRuntime.ts",
comment: "what's goin on here",
selection: { startLine: 14, startChar: 0, endLine: 32, endChar: 0 },
origin: "review",
},
],
}
await setupTimeline(page, { messages: [message, assistantMessage()] })
const user = page.locator('[data-component="user-message"]')
await expect(user.getByText("LiveRuntime.ts:14-32", { exact: true })).toBeVisible()
const attachments = user.locator('[data-slot="user-message-attachments"]')
await expect(attachments.getByText("LiveRuntime.ts", { exact: true })).toHaveCount(0)
await expect(attachments.getByText("notes.txt", { exact: true })).toBeVisible()
})
})
function patchFile(file: string, status: "added" | "modified" | "deleted") {
@@ -1,7 +1,24 @@
import { describe, expect, test } from "bun:test"
import { typeLabel } from "./message-file"
import type { PromptFileAttachment } from "@opencode/client/promise"
import { attached, typeLabel } from "./message-file"
function file(source: PromptFileAttachment["source"], mention = false): PromptFileAttachment {
return {
data: "",
mime: "text/plain",
source,
...(mention ? { mention: { text: "@README.md", start: 0, end: 10 } } : {}),
}
}
describe("message-file", () => {
test("only treats data-backed files without mentions as attachments", () => {
expect(attached(file({ type: "inline" }))).toBe(true)
expect(attached(file({ type: "uri", uri: "data:text/plain;base64,SGVsbG8=" }))).toBe(true)
expect(attached(file({ type: "uri", uri: "file:///repo/README.md" }))).toBe(false)
expect(attached(file({ type: "inline" }, true))).toBe(false)
})
test("labels attachment types from the basename extension", () => {
expect(typeLabel("list.md", "text/plain", "File")).toBe("Markdown")
expect(typeLabel("/repo/src/main.ts", "text/plain", "File")).toBe("TypeScript")
@@ -1,5 +1,12 @@
import { bundledLanguagesInfo } from "shiki"
import { getFilename } from "@opencode/util/path"
import type { PromptFileAttachment } from "@opencode/client/promise"
export function attached(file: PromptFileAttachment) {
if (file.mention) return false
if (file.source.type === "inline") return true
return file.source.uri.startsWith("data:")
}
// language metadata only; grammars stay behind shiki's lazy imports
const LANGUAGE_NAMES = new Map<string, string>(
@@ -27,7 +27,7 @@ import type {
SessionMessageUser,
} from "@opencode/client/promise"
import type { SessionUserActions, SessionUserAttachmentReference, SessionUserComment } from "../actions"
import { typeLabel } from "../components/message-file"
import { attached, typeLabel } from "../components/message-file"
export async function writeClipboard(text: string): Promise<boolean> {
const body = typeof document === "undefined" ? undefined : document.body
@@ -214,7 +214,7 @@ export function CurrentUserMessageDisplay(props: {
const dialog = useDialog()
const i18n = useI18n()
const [state, setState] = createStore({ copied: false, reverting: false })
const attachments = createMemo(() => (props.message.files ?? []).filter((file) => !file.mention))
const attachments = createMemo(() => (props.message.files ?? []).filter(attached))
const references = createMemo(() => props.references ?? [])
const inlineFiles = createMemo(() => (props.message.files ?? []).filter((file) => !!file.mention))
const agents = createMemo(() => props.message.agents ?? [])