chore: generate
This commit is contained in:
parent
7f33576f46
commit
f6197cefe1
12 changed files with 136 additions and 39 deletions
|
|
@ -547,10 +547,12 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
}, timeoutMs)
|
}, timeoutMs)
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await Promise.race([WorktreeState.wait(sdk.scope, sessionDirectory), abortWait, timeout]).finally(() => {
|
const result = await Promise.race([WorktreeState.wait(sdk.scope, sessionDirectory), abortWait, timeout]).finally(
|
||||||
if (timer.id === undefined) return
|
() => {
|
||||||
clearTimeout(timer.id)
|
if (timer.id === undefined) return
|
||||||
})
|
clearTimeout(timer.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
pending.delete(pendingKey(session.id))
|
pending.delete(pendingKey(session.id))
|
||||||
if (controller.signal.aborted) return false
|
if (controller.signal.aborted) return false
|
||||||
if (result.status === "failed") throw new Error(result.message)
|
if (result.status === "failed") throw new Error(result.message)
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,11 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
|
||||||
(key) => {
|
(key) => {
|
||||||
const decoded = decodeSessionKey(key)
|
const decoded = decodeSessionKey(key)
|
||||||
return createRoot((dispose) => ({
|
return createRoot((dispose) => ({
|
||||||
value: createCommentSession(serverSDK.scope, decoded.dir, decoded.id === WORKSPACE_KEY ? undefined : decoded.id),
|
value: createCommentSession(
|
||||||
|
serverSDK.scope,
|
||||||
|
decoded.dir,
|
||||||
|
decoded.id === WORKSPACE_KEY ? undefined : decoded.id,
|
||||||
|
),
|
||||||
dispose,
|
dispose,
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,9 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
||||||
|
|
||||||
const scope = createMemo(() => sdk.directory)
|
const scope = createMemo(() => sdk.directory)
|
||||||
const path = createPathHelpers(scope)
|
const path = createPathHelpers(scope)
|
||||||
const tabs = layout.tabs(() => SessionStateKey.from(serverSDK.scope, SessionRouteKey.fromRoute(params.dir, params.id)))
|
const tabs = layout.tabs(() =>
|
||||||
|
SessionStateKey.from(serverSDK.scope, SessionRouteKey.fromRoute(params.dir, params.id)),
|
||||||
|
)
|
||||||
|
|
||||||
const inflight = new Map<string, Promise<void>>()
|
const inflight = new Map<string, Promise<void>>()
|
||||||
const [store, setStore] = createStore<{
|
const [store, setStore] = createStore<{
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,10 @@ describe("query keys", () => {
|
||||||
|
|
||||||
expect([...loadPathQuery(ServerScope.local, "/repo", client).queryKey]).toEqual(["local", "/repo", "path"])
|
expect([...loadPathQuery(ServerScope.local, "/repo", client).queryKey]).toEqual(["local", "/repo", "path"])
|
||||||
expect([...loadPathQuery(remote, "/repo", client).queryKey]).toEqual(["https://debian.example", "/repo", "path"])
|
expect([...loadPathQuery(remote, "/repo", client).queryKey]).toEqual(["https://debian.example", "/repo", "path"])
|
||||||
expect([...loadProvidersQuery(remote, null, client).queryKey]).toEqual(["https://debian.example", null, "providers"])
|
expect([...loadProvidersQuery(remote, null, client).queryKey]).toEqual([
|
||||||
|
"https://debian.example",
|
||||||
|
null,
|
||||||
|
"providers",
|
||||||
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,12 @@ describe("session prefetch", () => {
|
||||||
at: 123,
|
at: 123,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(getSessionPrefetch(scope, "/tmp/a", "ses_1")).toEqual({ limit: 200, cursor: "abc", complete: false, at: 123 })
|
expect(getSessionPrefetch(scope, "/tmp/a", "ses_1")).toEqual({
|
||||||
|
limit: 200,
|
||||||
|
cursor: "abc",
|
||||||
|
complete: false,
|
||||||
|
at: 123,
|
||||||
|
})
|
||||||
expect(getSessionPrefetch(scope, "/tmp/b", "ses_1")).toBeUndefined()
|
expect(getSessionPrefetch(scope, "/tmp/b", "ses_1")).toBeUndefined()
|
||||||
|
|
||||||
clearSessionPrefetch(scope, "/tmp/a", ["ses_1"])
|
clearSessionPrefetch(scope, "/tmp/a", ["ses_1"])
|
||||||
|
|
@ -57,9 +62,33 @@ describe("session prefetch", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("clears a whole directory", () => {
|
test("clears a whole directory", () => {
|
||||||
setSessionPrefetch({ scope, directory: "/tmp/d", sessionID: "ses_1", limit: 10, cursor: "a", complete: true, at: 1 })
|
setSessionPrefetch({
|
||||||
setSessionPrefetch({ scope, directory: "/tmp/d", sessionID: "ses_2", limit: 20, cursor: "b", complete: false, at: 2 })
|
scope,
|
||||||
setSessionPrefetch({ scope, directory: "/tmp/e", sessionID: "ses_1", limit: 30, cursor: "c", complete: true, at: 3 })
|
directory: "/tmp/d",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
limit: 10,
|
||||||
|
cursor: "a",
|
||||||
|
complete: true,
|
||||||
|
at: 1,
|
||||||
|
})
|
||||||
|
setSessionPrefetch({
|
||||||
|
scope,
|
||||||
|
directory: "/tmp/d",
|
||||||
|
sessionID: "ses_2",
|
||||||
|
limit: 20,
|
||||||
|
cursor: "b",
|
||||||
|
complete: false,
|
||||||
|
at: 2,
|
||||||
|
})
|
||||||
|
setSessionPrefetch({
|
||||||
|
scope,
|
||||||
|
directory: "/tmp/e",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
limit: 30,
|
||||||
|
cursor: "c",
|
||||||
|
complete: true,
|
||||||
|
at: 3,
|
||||||
|
})
|
||||||
|
|
||||||
clearSessionPrefetchDirectory(scope, "/tmp/d")
|
clearSessionPrefetchDirectory(scope, "/tmp/d")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,11 @@ export const loadLspQuery = (scope: ServerScope, directory: string, sdk: Opencod
|
||||||
queryFn: () => sdk.lsp.status().then((r) => r.data ?? []),
|
queryFn: () => sdk.lsp.status().then((r) => r.data ?? []),
|
||||||
})
|
})
|
||||||
|
|
||||||
function makeQueryOptionsApi(scope: ServerScope, serverSDK: () => OpencodeClient, sdkFor: (dir: PathKey) => OpencodeClient) {
|
function makeQueryOptionsApi(
|
||||||
|
scope: ServerScope,
|
||||||
|
serverSDK: () => OpencodeClient,
|
||||||
|
sdkFor: (dir: PathKey) => OpencodeClient,
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
globalConfig: () => loadGlobalConfigQuery(scope, serverSDK()),
|
globalConfig: () => loadGlobalConfigQuery(scope, serverSDK()),
|
||||||
projects: () => loadProjectsQuery(scope, serverSDK()),
|
projects: () => loadProjectsQuery(scope, serverSDK()),
|
||||||
|
|
@ -447,7 +451,9 @@ export function createServerSyncContextInner(_serverSDK?: ServerSDK) {
|
||||||
// Invalidate all provider queries so newly configured custom providers
|
// Invalidate all provider queries so newly configured custom providers
|
||||||
// appear immediately in the available provider list across all directories.
|
// appear immediately in the available provider list across all directories.
|
||||||
queryClient.invalidateQueries({ queryKey: [serverSDK.scope, null, "providers"] })
|
queryClient.invalidateQueries({ queryKey: [serverSDK.scope, null, "providers"] })
|
||||||
queryClient.invalidateQueries({ predicate: (query) => query.queryKey[0] === serverSDK.scope && query.queryKey[2] === "providers" })
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) => query.queryKey[0] === serverSDK.scope && query.queryKey[2] === "providers",
|
||||||
|
})
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,11 @@ export function createServerProjects<T extends ServerProjectState>(input: {
|
||||||
setStore("projects", scope, [{ worktree: directory, expanded: true }, ...current()])
|
setStore("projects", scope, [{ worktree: directory, expanded: true }, ...current()])
|
||||||
},
|
},
|
||||||
close(directory: string) {
|
close(directory: string) {
|
||||||
setStore("projects", input.scope(), current().filter((project) => project.worktree !== directory))
|
setStore(
|
||||||
|
"projects",
|
||||||
|
input.scope(),
|
||||||
|
current().filter((project) => project.worktree !== directory),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
expand(directory: string) {
|
expand(directory: string) {
|
||||||
const index = current().findIndex((project) => project.worktree === directory)
|
const index = current().findIndex((project) => project.worktree === directory)
|
||||||
|
|
|
||||||
|
|
@ -118,14 +118,23 @@ function createHomeSessionStatus(input: {
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
const sessionStore = createMemo(() => input.sync().child(input.record().session.directory, { bootstrap: false })[0])
|
const sessionStore = createMemo(() => input.sync().child(input.record().session.directory, { bootstrap: false })[0])
|
||||||
const unseenCount = createMemo(() => (input.activeServer() ? notification.session.unseenCount(input.record().session.id) : 0))
|
const unseenCount = createMemo(() =>
|
||||||
const hasError = createMemo(() => input.activeServer() && notification.session.unseenHasError(input.record().session.id))
|
input.activeServer() ? notification.session.unseenCount(input.record().session.id) : 0,
|
||||||
|
)
|
||||||
|
const hasError = createMemo(
|
||||||
|
() => input.activeServer() && notification.session.unseenHasError(input.record().session.id),
|
||||||
|
)
|
||||||
const hasPermissions = createMemo(
|
const hasPermissions = createMemo(
|
||||||
() =>
|
() =>
|
||||||
input.activeServer() &&
|
input.activeServer() &&
|
||||||
!!sessionPermissionRequest(sessionStore().session, sessionStore().permission, input.record().session.id, (item) => {
|
!!sessionPermissionRequest(
|
||||||
return !permission.autoResponds(item, input.record().session.directory)
|
sessionStore().session,
|
||||||
}),
|
sessionStore().permission,
|
||||||
|
input.record().session.id,
|
||||||
|
(item) => {
|
||||||
|
return !permission.autoResponds(item, input.record().session.directory)
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
const serverStatus = createMemo(() =>
|
const serverStatus = createMemo(() =>
|
||||||
homeSessionServerStatus(input.activeServer(), () => ({
|
homeSessionServerStatus(input.activeServer(), () => ({
|
||||||
|
|
@ -278,7 +287,13 @@ function HomeDesign() {
|
||||||
|
|
||||||
function selectProject(conn: ServerConnection.Any, directory: string) {
|
function selectProject(conn: ServerConnection.Any, directory: string) {
|
||||||
const key = ServerConnection.key(conn)
|
const key = ServerConnection.key(conn)
|
||||||
if (!global.createServerCtx(conn).projects.list().some((project) => project.worktree === directory)) return
|
if (
|
||||||
|
!global
|
||||||
|
.createServerCtx(conn)
|
||||||
|
.projects.list()
|
||||||
|
.some((project) => project.worktree === directory)
|
||||||
|
)
|
||||||
|
return
|
||||||
setSelection(toggleHomeProjectSelection(state.selection, key, directory))
|
setSelection(toggleHomeProjectSelection(state.selection, key, directory))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -534,11 +549,7 @@ function HomeProjectColumn(props: {
|
||||||
</div>
|
</div>
|
||||||
<Show when={healthy()}>
|
<Show when={healthy()}>
|
||||||
<div class="mx-3 h-px bg-v2-border-border-base" />
|
<div class="mx-3 h-px bg-v2-border-border-base" />
|
||||||
<HomeProjectList
|
<HomeProjectList {...props} server={item} projects={serverCtx.projects.list()} />
|
||||||
{...props}
|
|
||||||
server={item}
|
|
||||||
projects={serverCtx.projects.list()}
|
|
||||||
/>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -586,7 +597,10 @@ function HomeProjectList(props: {
|
||||||
<HomeProjectRow
|
<HomeProjectRow
|
||||||
project={project}
|
project={project}
|
||||||
server={props.server}
|
server={props.server}
|
||||||
selected={props.selected.server === ServerConnection.key(props.server) && props.selected.directory === project.worktree}
|
selected={
|
||||||
|
props.selected.server === ServerConnection.key(props.server) &&
|
||||||
|
props.selected.directory === project.worktree
|
||||||
|
}
|
||||||
unseenCount={props.unseenCount(props.server, project)}
|
unseenCount={props.unseenCount(props.server, project)}
|
||||||
selectProject={props.selectProject}
|
selectProject={props.selectProject}
|
||||||
openNewSession={props.openNewSession}
|
openNewSession={props.openNewSession}
|
||||||
|
|
@ -662,7 +676,10 @@ function HomeProjectRow(props: {
|
||||||
<MenuV2.Item onSelect={() => props.editProject(props.server, props.project)}>
|
<MenuV2.Item onSelect={() => props.editProject(props.server, props.project)}>
|
||||||
{props.language.t("common.edit")}
|
{props.language.t("common.edit")}
|
||||||
</MenuV2.Item>
|
</MenuV2.Item>
|
||||||
<MenuV2.Item disabled={props.unseenCount === 0} onSelect={() => props.clearNotifications(props.server, props.project)}>
|
<MenuV2.Item
|
||||||
|
disabled={props.unseenCount === 0}
|
||||||
|
onSelect={() => props.clearNotifications(props.server, props.project)}
|
||||||
|
>
|
||||||
{props.language.t("sidebar.project.clearNotifications")}
|
{props.language.t("sidebar.project.clearNotifications")}
|
||||||
</MenuV2.Item>
|
</MenuV2.Item>
|
||||||
<MenuV2.Separator />
|
<MenuV2.Separator />
|
||||||
|
|
@ -902,7 +919,11 @@ function HomeSessionSearchResultRow(props: {
|
||||||
onHighlight: () => void
|
onHighlight: () => void
|
||||||
onSelect: (session: Session) => void
|
onSelect: (session: Session) => void
|
||||||
}) {
|
}) {
|
||||||
const status = createHomeSessionStatus({ record: () => props.record, sync: () => props.sync, activeServer: () => props.activeServer })
|
const status = createHomeSessionStatus({
|
||||||
|
record: () => props.record,
|
||||||
|
sync: () => props.sync,
|
||||||
|
activeServer: () => props.activeServer,
|
||||||
|
})
|
||||||
const title = createMemo(() => sessionTitle(props.record.session.title) || props.record.session.id)
|
const title = createMemo(() => sessionTitle(props.record.session.title) || props.record.session.id)
|
||||||
|
|
||||||
const key = () => homeSessionSearchKey(props.record)
|
const key = () => homeSessionSearchKey(props.record)
|
||||||
|
|
@ -993,7 +1014,11 @@ function HomeSessionRow(props: {
|
||||||
activeServer: boolean
|
activeServer: boolean
|
||||||
openSession: (session: Session) => void
|
openSession: (session: Session) => void
|
||||||
}) {
|
}) {
|
||||||
const status = createHomeSessionStatus({ record: () => props.record, sync: () => props.sync, activeServer: () => props.activeServer })
|
const status = createHomeSessionStatus({
|
||||||
|
record: () => props.record,
|
||||||
|
sync: () => props.sync,
|
||||||
|
activeServer: () => props.activeServer,
|
||||||
|
})
|
||||||
const title = createMemo(() => sessionTitle(props.record.session.title) || props.record.session.id)
|
const title = createMemo(() => sessionTitle(props.record.session.title) || props.record.session.id)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,9 @@ describe("layout workspace helpers", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("scopes home project selection by server", () => {
|
test("scopes home project selection by server", () => {
|
||||||
expect(toggleHomeProjectSelection(undefined, serverKey("https://debian.example"), "/home/luke/repos/amazon")).toEqual({
|
expect(
|
||||||
|
toggleHomeProjectSelection(undefined, serverKey("https://debian.example"), "/home/luke/repos/amazon"),
|
||||||
|
).toEqual({
|
||||||
server: serverKey("https://debian.example"),
|
server: serverKey("https://debian.example"),
|
||||||
directory: "/home/luke/repos/amazon",
|
directory: "/home/luke/repos/amazon",
|
||||||
})
|
})
|
||||||
|
|
@ -270,11 +272,19 @@ describe("layout workspace helpers", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("defers home project navigation until its server is active", () => {
|
test("defers home project navigation until its server is active", () => {
|
||||||
expect(homeProjectNavigation(serverKey("sidecar"), serverKey("https://debian.example"), "/YW1hem9u/session")).toEqual({
|
expect(
|
||||||
|
homeProjectNavigation(serverKey("sidecar"), serverKey("https://debian.example"), "/YW1hem9u/session"),
|
||||||
|
).toEqual({
|
||||||
server: serverKey("https://debian.example"),
|
server: serverKey("https://debian.example"),
|
||||||
href: "/YW1hem9u/session",
|
href: "/YW1hem9u/session",
|
||||||
})
|
})
|
||||||
expect(homeProjectNavigation(serverKey("https://debian.example"), serverKey("https://debian.example"), "/YW1hem9u/session")).toEqual({
|
expect(
|
||||||
|
homeProjectNavigation(
|
||||||
|
serverKey("https://debian.example"),
|
||||||
|
serverKey("https://debian.example"),
|
||||||
|
"/YW1hem9u/session",
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
href: "/YW1hem9u/session",
|
href: "/YW1hem9u/session",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ import { useNotification } from "@/context/notification"
|
||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
|
import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
|
||||||
|
|
||||||
export function useSessionTabAvatarState(directory: Accessor<string>, sessionId: Accessor<string>, active: Accessor<boolean> = () => true) {
|
export function useSessionTabAvatarState(
|
||||||
|
directory: Accessor<string>,
|
||||||
|
sessionId: Accessor<string>,
|
||||||
|
active: Accessor<boolean> = () => true,
|
||||||
|
) {
|
||||||
const globalSync = useServerSync()
|
const globalSync = useServerSync()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ import { ScopedKey, ServerScope, SessionRouteKey, SessionStateKey, migrateLegacy
|
||||||
|
|
||||||
describe("ServerScope", () => {
|
describe("ServerScope", () => {
|
||||||
test("uses a stable local scope for the canonical sidecar", () => {
|
test("uses a stable local scope for the canonical sidecar", () => {
|
||||||
expect(String(ServerScope.fromServerKey("sidecar" as Parameters<typeof ServerScope.fromServerKey>[0]))).toBe("local")
|
expect(String(ServerScope.fromServerKey("sidecar" as Parameters<typeof ServerScope.fromServerKey>[0]))).toBe(
|
||||||
|
"local",
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("keeps configured loopback servers distinct from the canonical sidecar", () => {
|
test("keeps configured loopback servers distinct from the canonical sidecar", () => {
|
||||||
expect(String(ServerScope.fromServerKey("http://localhost:4096" as Parameters<typeof ServerScope.fromServerKey>[0]))).toBe(
|
expect(
|
||||||
"http://localhost:4096",
|
String(ServerScope.fromServerKey("http://localhost:4096" as Parameters<typeof ServerScope.fromServerKey>[0])),
|
||||||
)
|
).toBe("http://localhost:4096")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("uses a stable local scope for an explicit canonical web server", () => {
|
test("uses a stable local scope for an explicit canonical web server", () => {
|
||||||
|
|
@ -52,6 +54,8 @@ describe("migrateLegacySessionStateKeys", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("rejects invalid identity fragments", () => {
|
test("rejects invalid identity fragments", () => {
|
||||||
expect(() => ScopedKey.from(ServerScope.local, "bad\0directory")).toThrow("Scoped key part cannot contain null bytes")
|
expect(() => ScopedKey.from(ServerScope.local, "bad\0directory")).toThrow(
|
||||||
|
"Scoped key part cannot contain null bytes",
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@ function compose(scope: ServerScope, parts: string[]) {
|
||||||
export const ServerScope = {
|
export const ServerScope = {
|
||||||
local: "local" as ServerScope,
|
local: "local" as ServerScope,
|
||||||
fromServerKey(key: ServerConnection.Key, canonicalLocalServer?: ServerConnection.Key) {
|
fromServerKey(key: ServerConnection.Key, canonicalLocalServer?: ServerConnection.Key) {
|
||||||
return fragment("Server scope", key === "sidecar" || key === canonicalLocalServer ? ServerScope.local : key) as ServerScope
|
return fragment(
|
||||||
|
"Server scope",
|
||||||
|
key === "sidecar" || key === canonicalLocalServer ? ServerScope.local : key,
|
||||||
|
) as ServerScope
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue