Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions packages/app-bundle/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@
"packages/ui/package.json": "b1d168d0371e9094faae1107fc6c00be197f09bc69daa2247a3890d607f4b629",
"packages/app/public/amico.svg": "a14b9d543d895bcdf0758f7b9ef5908ee0acaac794446494af059b159247db8f",
"packages/app/public/oc-theme-preload.js": "27227e802b3494e7c545da903e679efdb30ccc754cd4eb5cdf08005a40d560b6",
"packages/app/src/app.tsx": "26db81a03c2f303701037cd2d3f0bf91b585193f01b6b31db5f869f4f8e76fc7",
"packages/app/src/app.tsx": "c4ae567006dcc0fd1a530ac5ccf837444e67534fbe08dda1dc85ca9e25d822bd",
"packages/app/src/design-polish.css": "42cc6efaefe9a71dedd12fcb0bf2549453d9a46097cb025cf087064b29d3ccce",
"packages/app/src/entry.tsx": "f35e1017f4c9d478d254b2a38043e5750064b6bef25169c3e07ae9f72ff1049c",
"packages/app/src/index.css": "08179e06ce2d419a2d98acc96025f91c7709062ea9f3ad245e88dc35e75ff9f7",
Expand Down Expand Up @@ -1062,7 +1062,7 @@
"packages/app/src/context/server.tsx": "08e978e4b4b3094221a2a0b92e069f39cbedb6d6cf95935881e21cde52265ee2",
"packages/app/src/context/settings.tsx": "050f1831173d9f337bb00d54c034670553cd22bb457c7b536e4d799eb35de8a3",
"packages/app/src/context/split.tsx": "891aeb290b369b327a63ce548dcaab1daca08e41fab430648787be7f84e53ec0",
"packages/app/src/context/tabs.tsx": "6ca4ae0d85d5456cdda6f9aaec7e6aab0ca8aef0296b61f8bae7ee51da150a0a",
"packages/app/src/context/tabs.tsx": "eb494b5785defdd3a4901874be355ba8ddd7bab4c009359b558c413f9ee63ffa",
"packages/app/src/context/vault-panel.ts": "f91cb5fb49a9f0d46bd2a1b131f9a218b30147e2ad3617d2a2dc4f1e14599a53",
"packages/app/src/context/workbench.tsx": "69a44da2b69989bd2c67ebffdb49941841f3f4718d6a70d659c828b57928d9eb",
"packages/app/src/context/zoom-keybind.test.ts": "a1385c93639687a7dac7d21f8f02841d82a55e42465ce3836b97b16c82b52469",
Expand Down Expand Up @@ -1107,7 +1107,7 @@
"packages/app/src/utils/amicode-hidden-project.ts": "d845a80ecad2d497528a5071f55cffab72e7b451f16d106cdb594f5bbd62c0ee",
"packages/app/src/utils/amicode-pane.ts": "46c18a2dc47c5db154f70272d25031e6834539ad03f6ab25613f082426f7c642",
"packages/app/src/utils/amicode-route-info.ts": "a0268a223e8de09fae9d4de8bee6a7774c14c87c6254cb4ed1d921db18587f44",
"packages/app/src/utils/amicode-workspace-projects.test.ts": "8d6f68cfec613bd470c991dcb5f49f6171606b09cbbd37c2fabd37a9129a3896",
"packages/app/src/utils/amicode-workspace-projects.test.ts": "8b5c32172e2039b3d9c52f2257023263fc846132e7c185e4a63ae014bc2e2bfb",
"packages/app/src/utils/amicode-workspace-projects.ts": "cbe091c463177375c93cb43676c202bd7bf06baf36fdec779f7d641558ff6bee",
"packages/app/src/utils/boot-parity.ts": "5231be428fc832cd941aafbe70e4620b696321b87a2769d28372ecd2feb9dc41",
"packages/app/src/utils/chrome-dropdown.ts": "f8b878c5df4e1afd96b0751c73daaad0a8127411030724cb8f9a5089ebb29eac",
Expand Down
10 changes: 8 additions & 2 deletions packages/app-bundle/overlay/packages/app/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "@/index.css"
import * as Sentry from "@sentry/solid"
import { requestComputeConnect } from "@/components/amicode-defaults-capsule"
import { adoptWorkspaceProjects } from "@/utils/amicode-workspace-projects"
import { adoptWorkspaceProjects, workspaceProjects } from "@/utils/amicode-workspace-projects"
import { I18nProvider } from "@opencode-ai/ui/context"
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
Expand Down Expand Up @@ -482,7 +482,13 @@ function AmicodeNavigateBridge() {
if (url.pathname === "/new-session") {
const prompt = url.searchParams.get("prompt") || undefined
const autoSend = url.searchParams.get("autoSend") === "1"
await tabs.newDraft({ server: server.key, directory: server.projects.list()[0]?.worktree ?? "" }, prompt)
// amicode#872: prefer workspace-projects (real VS Code folders) over
// the engine's project list (which returns the scaffold dir).
const wsProjects = workspaceProjects()
const directory = wsProjects.length > 0
? wsProjects[0].worktree
: server.projects.list()[0]?.worktree ?? ""
await tabs.newDraft({ server: server.key, directory }, prompt)
if (autoSend) setPendingAutoSend(true)
} else {
// Navigate to an existing session by path (e.g. /session/:id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ServerConnection, useServer } from "./server"
import { createEffect, getOwner, onCleanup, startTransition } from "solid-js"
import { useLocation, useNavigate, useParams } from "@solidjs/router"
import { usePlatform } from "./platform"
import { workspaceProjects } from "@/utils/amicode-workspace-projects"
import { uuid } from "@/utils/uuid"
import { SessionTabsRemovedDetail } from "@/components/titlebar-session-events"
import { sessionHref } from "@/utils/session-route"
Expand Down Expand Up @@ -60,9 +61,14 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const platform = usePlatform()
/** amicode(workbench): TabsProvider sits ABOVE the SDK/Sync providers, so
* a draft's project directory comes from the route or the server
* context — never from useSDK (there is no provider above us). */
* context — never from useSDK (there is no provider above us).
* amicode#872: prefer the extension's workspace-projects signal over
* the engine's project list — the engine's first project is the scaffold
* dir, which the sidebar's orphan guard correctly rejects. */
const draftDirectory = () => {
if (params.dir) return base64Decode(params.dir) ?? ""
const wsProjects = workspaceProjects()
if (wsProjects.length > 0) return wsProjects[0].worktree
return server.projects.list()[0]?.worktree ?? ""
}
const fallback = server.key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,79 @@ describe("prompt-project-selector — no false default (#673)", () => {
})
})

// ── #872: draft directory defaults to workspace folder ──────────────────
// Two code paths create drafts — tabs.tsx draftDirectory() and app.tsx
// AmicodeNavigateBridge. Both must prefer workspaceProjects() over
// server.projects.list() so the sidebar's orphan guard can match the path.

describe("draft directory — tabs.tsx draftDirectory (#872)", () => {
const tabsSrc = readFileSync(
resolve(__dirname, "..", "context", "tabs.tsx"),
"utf8",
)

test("draftDirectory checks workspaceProjects before server.projects", () => {
// The draftDirectory function must reference workspaceProjects
const draftFn = tabsSrc.slice(tabsSrc.indexOf("const draftDirectory"))
const fnEnd = draftFn.indexOf("\n const fallback")
const fnBody = draftFn.slice(0, fnEnd)
expect(fnBody).toContain("workspaceProjects")
})

test("tabs.tsx imports workspaceProjects from the amicode module", () => {
expect(tabsSrc).toMatch(/import\s.*workspaceProjects.*from\s+["']@\/utils\/amicode-workspace-projects["']/)
})

test("draftDirectory falls back to server.projects when workspaceProjects is empty", () => {
// The fallback path must still exist
const draftFn = tabsSrc.slice(tabsSrc.indexOf("const draftDirectory"))
const fnEnd = draftFn.indexOf("\n const fallback")
const fnBody = draftFn.slice(0, fnEnd)
expect(fnBody).toContain("server.projects.list()")
})
})

describe("draft directory — app.tsx navigate bridge (#872)", () => {
const appSrc = readFileSync(
resolve(__dirname, "..", "app.tsx"),
"utf8",
)

test("navigate bridge checks workspaceProjects before server.projects for /new-session", () => {
// Find the new-session branch in the navigate bridge
const newSessionIdx = appSrc.indexOf('url.pathname === "/new-session"')
const bridgeSlice = appSrc.slice(newSessionIdx, newSessionIdx + 600)
expect(bridgeSlice).toContain("workspaceProjects")
})

test("navigate bridge falls back to server.projects when workspaceProjects is empty", () => {
const newSessionIdx = appSrc.indexOf('url.pathname === "/new-session"')
const bridgeSlice = appSrc.slice(newSessionIdx, newSessionIdx + 600)
expect(bridgeSlice).toContain("server.projects.list()")
})
})

describe("explicit project selection overrides the default (#872 AC4)", () => {
const ctrlSrc = readFileSync(
resolve(__dirname, "..", "pages", "session", "composer", "session-composer-controls.ts"),
"utf8",
)

test("selectProject writes the selected worktree directly to the draft (updateDraft)", () => {
// The explicit-selection path must call updateDraft with { directory: worktree }
// so it overrides the draftDirectory() default entirely.
const selectFn = ctrlSrc.slice(ctrlSrc.indexOf("const selectProject"))
expect(selectFn).toContain("tabs.updateDraft(search.draftId, { server: ServerConnection.key(conn), directory: worktree })")
})

test("selectProject does not re-invoke draftDirectory", () => {
// The explicit-selection path must not go through draftDirectory — it writes
// the selected worktree directly.
const selectFn = ctrlSrc.slice(ctrlSrc.indexOf("const selectProject"))
expect(selectFn).not.toContain("draftDirectory")
})
})

describe("session-composer-controls — toggle deselect (#673)", () => {
const ctrlSrc = readFileSync(
resolve(__dirname, "..", "pages", "session", "composer", "session-composer-controls.ts"),
Expand Down
Loading