summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFilip <[email protected]>2026-02-27 12:33:22 +0100
committerGitHub <[email protected]>2026-02-27 05:33:22 -0600
commit1f108bc4010c9b6fd28d1640863de9acffb109a1 (patch)
tree5dd1b6ff9a0aa1d76f0e731df0d98415bc615f55
parent6b3118883c01ea6b1996490610b862964b9488f4 (diff)
downloadopencode-1f108bc4010c9b6fd28d1640863de9acffb109a1.tar.gz
opencode-1f108bc4010c9b6fd28d1640863de9acffb109a1.zip
feat(app): recent projects section in command pallette (#15270)
-rw-r--r--packages/app/src/components/dialog-select-directory.tsx59
1 files changed, 56 insertions, 3 deletions
diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx
index 515e640c9..91e23f8ff 100644
--- a/packages/app/src/components/dialog-select-directory.tsx
+++ b/packages/app/src/components/dialog-select-directory.tsx
@@ -8,6 +8,7 @@ import fuzzysort from "fuzzysort"
import { createMemo, createResource, createSignal } from "solid-js"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
+import { useLayout } from "@/context/layout"
import { useLanguage } from "@/context/language"
interface DialogSelectDirectoryProps {
@@ -19,6 +20,7 @@ interface DialogSelectDirectoryProps {
type Row = {
absolute: string
search: string
+ group: "recent" | "folders"
}
function cleanInput(value: string) {
@@ -101,7 +103,7 @@ function displayPath(path: string, input: string, home: string) {
return tildeOf(full, home) || full
}
-function toRow(absolute: string, home: string): Row {
+function toRow(absolute: string, home: string, group: Row["group"]): Row {
const full = trimTrailing(absolute)
const tilde = tildeOf(full, home)
const withSlash = (value: string) => {
@@ -113,7 +115,16 @@ function toRow(absolute: string, home: string): Row {
const search = Array.from(
new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
).join("\n")
- return { absolute: full, search }
+ return { absolute: full, search, group }
+}
+
+function uniqueRows(rows: Row[]) {
+ const seen = new Set<string>()
+ return rows.filter((row) => {
+ if (seen.has(row.absolute)) return false
+ seen.add(row.absolute)
+ return true
+ })
}
function useDirectorySearch(args: {
@@ -237,6 +248,7 @@ function useDirectorySearch(args: {
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
const sync = useGlobalSync()
const sdk = useGlobalSDK()
+ const layout = useLayout()
const dialog = useDialog()
const language = useLanguage()
@@ -266,9 +278,42 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
start,
})
+ const recentProjects = createMemo(() => {
+ const projects = layout.projects.list()
+ const byProject = new Map<string, number>()
+
+ for (const project of projects) {
+ let at = 0
+ const dirs = [project.worktree, ...(project.sandboxes ?? [])]
+ for (const directory of dirs) {
+ const sessions = sync.child(directory, { bootstrap: false })[0].session
+ for (const session of sessions) {
+ if (session.time.archived) continue
+ const updated = session.time.updated ?? session.time.created
+ if (updated > at) at = updated
+ }
+ }
+ byProject.set(project.worktree, at)
+ }
+
+ return projects
+ .map((project, index) => ({ project, at: byProject.get(project.worktree) ?? 0, index }))
+ .sort((a, b) => b.at - a.at || a.index - b.index)
+ .slice(0, 5)
+ .map(({ project }) => {
+ const row = toRow(project.worktree, home(), "recent")
+ const name = project.name || getFilename(project.worktree)
+ return {
+ ...row,
+ search: `${row.search}\n${name}`,
+ }
+ })
+ })
+
const items = async (value: string) => {
const results = await directories(value)
- return results.map((absolute) => toRow(absolute, home()))
+ const directoryRows = results.map((absolute) => toRow(absolute, home(), "folders"))
+ return uniqueRows([...recentProjects(), ...directoryRows])
}
function resolve(absolute: string) {
@@ -285,6 +330,14 @@ export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
items={items}
key={(x) => x.absolute}
filterKeys={["search"]}
+ groupBy={(item) => item.group}
+ sortGroupsBy={(a, b) => {
+ if (a.category === b.category) return 0
+ return a.category === "recent" ? -1 : 1
+ }}
+ groupHeader={(group) =>
+ group.category === "recent" ? language.t("home.recentProjects") : language.t("command.project.open")
+ }
ref={(r) => (list = r)}
onFilter={(value) => setFilter(cleanInput(value))}
onKeyEvent={(e, item) => {