Skip to content

OpenCode Desktop 2.0.28 - second file drop into the prompt is silently ignored #55216

Closed

Description

@deepakajay0477

Description

Dragging files from the file tree sidebar into the prompt only works for the first file.

The first drop inserts @path as expected. Every drop after that does nothing at all - no error, nothing added to the prompt. So I can only ever attach one file by dragging.

I reported this for v1 as #39705 and opened #49185 to fix it, but that was closed because v1 is no longer taking PRs. I've reproduced it on 2.0.28, so it looks like the v2 composer still has it.

I checked the v2 source and the cause is still there: packages/app/src/composer/editor/actions.ts line 85 picks the replace range with text.slice(0, end).lastIndexOf("@"). Once the prompt already contains a mention, that finds the existing mention's "@", and insertMention() can't replace a range that spans a mention part, so it silently does nothing.

Plugins

None

OpenCode version

2.0.28

Steps to reproduce

  1. Open OpenCode Desktop and start a session.
  2. Drag a file from the file tree sidebar into the prompt box - @file is inserted.
  3. Drag a second file in - nothing happens.
  4. Drag a third - nothing happens either.

Screenshot and/or share link

No response

Operating System

Windows 10 22H2 (build 19045)

Terminal

No response

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report and for pointing to the cause. I can confirm this on 2.0.28, and it still happens on the latest dev (e9def88).

    I didn't run the desktop app. Instead I tested the composer code the drop handler uses. A file dropped from the tree goes through handleDrop and addPart, which calls addMention without a range. I called addMention three times that way: after the first call the prompt holds @src/a.ts, and the second and third calls leave it exactly as it was, with no error. As you described, the fallback range starts at the existing mention's @, and insertMention won't replace across a mention part, so nothing is inserted.

    The test below reproduces it. Related: #49185 (your v1 fix PR), #39705 (the v1 report), and #48131 (a separate request to drag several files at once from the file tree).

    Reproduction script (drop-mentions.test.ts)
    // Reproduces #55216: the composer's drop handler (interaction.ts addPart) calls
    // draft.addMention(part) without a range for each file dropped into the prompt.
    import { expect, test } from "bun:test"
    import { createStore } from "solid-js/store"
    import type { ComposerPersistedState } from "./src/composer/types"
    import { createComposerEditorActions } from "./src/composer/editor/actions"
    
    const file = (path: string) => ({ type: "file" as const, path, content: `@${path}`, start: 0, end: 0 })
    
    test("dropping a second file into the prompt adds a second mention", () => {
      const draft = createComposerEditorActions(
        createStore<ComposerPersistedState>({ prompt: [{ type: "text", content: "", start: 0, end: 0 }], context: { items: [] } }),
      )
      draft.addMention(file("src/a.ts")) // first drop
      console.log("after drop 1:", JSON.stringify(draft.state.prompt.map((p: any) => p.content)), "cursor", draft.state.cursor)
      draft.addMention(file("src/b.ts")) // second drop
      console.log("after drop 2:", JSON.stringify(draft.state.prompt.map((p: any) => p.content)), "cursor", draft.state.cursor)
      draft.addMention(file("src/c.ts")) // third drop
      console.log("after drop 3:", JSON.stringify(draft.state.prompt.map((p: any) => p.content)), "cursor", draft.state.cursor)
      const paths = draft.state.prompt.filter((p: any) => p.type === "file").map((p: any) => p.path)
      expect(paths).toEqual(["src/a.ts", "src/b.ts", "src/c.ts"])
    })

    Run it: bun drop-mentions.test.ts

  2. argszero commented on Oct 13, 2026

    @argszero
    Contributor

    I'd like to work on this.

    I reproduced the cause in the code rather than taking it from the report, and it is exactly where you pointed:

    addMention() in packages/app/src/composer/editor/actions.ts builds its replacement range from a joined string — store().prompt.map((p) => ("content" in p ? p.content : "")).join("") — so a file mention's own @ is part of the text it searches. With no explicit range, start is text.slice(0, end).lastIndexOf("@"), which for a prompt that already holds @src/a.ts finds that mention's @.

    The range is then handed to insertMention(), whose guard is if (part.type !== "text" || start < partStart || end > position) return [part] — only text parts can be replaced. A range spanning a mention part matches no part at all, every part is returned unchanged, and insertMention hands back a prompt identical to the input. Nothing throws, nothing is reported, and the drop is silently dropped. That is the whole defect: the fallback treats "an @ exists somewhere before the cursor" as "the user is typing a trigger", which is only true when that @ sits in a text part.

    Planned fix, kept to the one behaviour:

    • only accept the searched @ as the replacement start when it lies inside a text part, i.e. a genuine typed trigger; otherwise fall back to inserting at the cursor, which is what a drop means
    • keep the explicit-range path (the autocomplete selection) exactly as it is
    • a test in the existing packages/app/src/composer/editor/actions.test.ts, driving addMention twice without a range and asserting the second call inserts rather than no-ops

    I'll open it against the current default branch when it's ready. Noting for transparency that I saw your closed #49185 for the v1 path — this is the live file on v2, where the same fallback still stands, so a fresh PR is needed either way rather than a reopen.

  3. deepakajay0477 commented on Oct 13, 2026

    @deepakajay0477
    Author

    I reported this and already have the v2 port of #49185 ready, with a regression test that fails before the fix and passes after.

    Happy to open it now, or defer to @argszero - whichever the maintainers prefer.

  4. deepakajay0477 commented on Oct 13, 2026

    @deepakajay0477
    Author

    @argszero I have this ready to open right now - the v2 port of #49185 plus a regression test that fails before the fix and passes after.

    Want me to go ahead, or would you rather take it? Happy either way, I just don't want us both building the same thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions