fix(app): autocomplete configured references (#34308)
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Brendan Allan <git@brendonovich.dev>
This commit is contained in:
committed by
GitHub
parent
3aa2860058
commit
3ca89ac796
@@ -68,6 +68,7 @@ import { promptPlaceholder } from "./prompt-input/placeholder"
|
|||||||
import { createPromptInputTransientState } from "./prompt-input/transient-state"
|
import { createPromptInputTransientState } from "./prompt-input/transient-state"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||||
|
import type { ReferenceInfo } from "@opencode-ai/sdk/v2/client"
|
||||||
|
|
||||||
export type PromptInputState = ReturnType<typeof usePrompt>
|
export type PromptInputState = ReturnType<typeof usePrompt>
|
||||||
|
|
||||||
@@ -640,6 +641,23 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const referenceDescription = (reference: ReferenceInfo) =>
|
||||||
|
reference.source.type === "git" ? reference.source.repository : reference.source.path
|
||||||
|
|
||||||
|
const referenceList = createMemo(() =>
|
||||||
|
sync()
|
||||||
|
.data.reference.filter((reference) => !reference.hidden)
|
||||||
|
.map(
|
||||||
|
(reference): AtOption => ({
|
||||||
|
type: "reference",
|
||||||
|
name: reference.name,
|
||||||
|
path: reference.path,
|
||||||
|
display: reference.name,
|
||||||
|
description: reference.description ?? referenceDescription(reference),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const agentList = createMemo(() =>
|
const agentList = createMemo(() =>
|
||||||
props.controls.agents.available
|
props.controls.agents.available
|
||||||
.filter((agent) => !agent.hidden && agent.mode !== "primary")
|
.filter((agent) => !agent.hidden && agent.mode !== "primary")
|
||||||
@@ -650,14 +668,28 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
if (!option) return
|
if (!option) return
|
||||||
if (option.type === "agent") {
|
if (option.type === "agent") {
|
||||||
addPart({ type: "agent", name: option.name, content: "@" + option.name, start: 0, end: 0 })
|
addPart({ type: "agent", name: option.name, content: "@" + option.name, start: 0, end: 0 })
|
||||||
} else {
|
return
|
||||||
addPart({ type: "file", path: option.path, content: "@" + option.path, start: 0, end: 0 })
|
|
||||||
}
|
}
|
||||||
|
if (option.type === "reference") {
|
||||||
|
addPart({
|
||||||
|
type: "file",
|
||||||
|
path: option.path,
|
||||||
|
content: "@" + option.name,
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
mime: "application/x-directory",
|
||||||
|
filename: option.name,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addPart({ type: "file", path: option.path, content: "@" + option.path, start: 0, end: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const atKey = (x: AtOption | undefined) => {
|
const atKey = (x: AtOption | undefined) => {
|
||||||
if (!x) return ""
|
if (!x) return ""
|
||||||
return x.type === "agent" ? `agent:${x.name}` : `file:${x.path}`
|
if (x.type === "agent") return `agent:${x.name}`
|
||||||
|
if (x.type === "reference") return `reference:${x.name}`
|
||||||
|
return `file:${x.path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -668,30 +700,33 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
onKeyDown: atOnKeyDown,
|
onKeyDown: atOnKeyDown,
|
||||||
} = useFilteredList<AtOption>({
|
} = useFilteredList<AtOption>({
|
||||||
items: async (query) => {
|
items: async (query) => {
|
||||||
|
const references = referenceList()
|
||||||
const agents = agentList()
|
const agents = agentList()
|
||||||
const open = recent()
|
const open = recent()
|
||||||
const seen = new Set(open)
|
const seen = new Set(open)
|
||||||
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
||||||
if (!query.trim()) return [...agents, ...pinned]
|
if (!query.trim()) return [...references, ...agents, ...pinned]
|
||||||
const paths = await files.searchFilesAndDirectories(query)
|
const paths = await files.searchFilesAndDirectories(query)
|
||||||
const fileOptions: AtOption[] = paths
|
const fileOptions: AtOption[] = paths
|
||||||
.filter((path) => !seen.has(path))
|
.filter((path) => !seen.has(path))
|
||||||
.map((path) => ({ type: "file", path, display: path }))
|
.map((path) => ({ type: "file", path, display: path }))
|
||||||
return [...agents, ...pinned, ...fileOptions]
|
return [...references, ...agents, ...pinned, ...fileOptions]
|
||||||
},
|
},
|
||||||
key: atKey,
|
key: atKey,
|
||||||
filterKeys: ["display"],
|
filterKeys: ["display"],
|
||||||
skipFilter: (item) => item.type === "file" && !item.recent,
|
skipFilter: (item) => item.type === "file" && !item.recent,
|
||||||
groupBy: (item) => {
|
groupBy: (item) => {
|
||||||
|
if (item.type === "reference") return "reference"
|
||||||
if (item.type === "agent") return "agent"
|
if (item.type === "agent") return "agent"
|
||||||
if (item.recent) return "recent"
|
if (item.recent) return "recent"
|
||||||
return "file"
|
return "file"
|
||||||
},
|
},
|
||||||
sortGroupsBy: (a, b) => {
|
sortGroupsBy: (a, b) => {
|
||||||
const rank = (category: string) => {
|
const rank = (category: string) => {
|
||||||
if (category === "agent") return 0
|
if (category === "reference") return 0
|
||||||
if (category === "recent") return 1
|
if (category === "agent") return 1
|
||||||
return 2
|
if (category === "recent") return 2
|
||||||
|
return 3
|
||||||
}
|
}
|
||||||
return rank(a.category) - rank(b.category)
|
return rank(a.category) - rank(b.category)
|
||||||
},
|
},
|
||||||
@@ -757,7 +792,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
const pill = document.createElement("span")
|
const pill = document.createElement("span")
|
||||||
pill.textContent = part.content
|
pill.textContent = part.content
|
||||||
pill.setAttribute("data-type", part.type)
|
pill.setAttribute("data-type", part.type)
|
||||||
if (part.type === "file") pill.setAttribute("data-path", part.path)
|
if (part.type === "file") {
|
||||||
|
pill.setAttribute("data-path", part.path)
|
||||||
|
if (part.mime) pill.setAttribute("data-mime", part.mime)
|
||||||
|
if (part.filename) pill.setAttribute("data-filename", part.filename)
|
||||||
|
}
|
||||||
if (part.type === "agent") pill.setAttribute("data-name", part.name)
|
if (part.type === "agent") pill.setAttribute("data-name", part.name)
|
||||||
pill.setAttribute("contenteditable", "false")
|
pill.setAttribute("contenteditable", "false")
|
||||||
pill.style.userSelect = "text"
|
pill.style.userSelect = "text"
|
||||||
@@ -878,6 +917,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
content,
|
content,
|
||||||
start: position,
|
start: position,
|
||||||
end: position + content.length,
|
end: position + content.length,
|
||||||
|
...(file.dataset.mime ? { mime: file.dataset.mime } : {}),
|
||||||
|
...(file.dataset.filename ? { filename: file.dataset.filename } : {}),
|
||||||
})
|
})
|
||||||
position += content.length
|
position += content.length
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,41 @@ describe("buildRequestParts", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("preserves reference aliases as directory file parts", () => {
|
||||||
|
const result = buildRequestParts({
|
||||||
|
prompt: [
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
path: "/repo/../docs",
|
||||||
|
content: "@docs",
|
||||||
|
start: 0,
|
||||||
|
end: 5,
|
||||||
|
mime: "application/x-directory",
|
||||||
|
filename: "docs",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
context: [],
|
||||||
|
images: [],
|
||||||
|
text: "@docs",
|
||||||
|
messageID: "msg_reference",
|
||||||
|
sessionID: "ses_reference",
|
||||||
|
sessionDirectory: "/repo/app",
|
||||||
|
})
|
||||||
|
|
||||||
|
const filePart = result.requestParts.find((part) => part.type === "file")
|
||||||
|
expect(filePart).toBeDefined()
|
||||||
|
if (filePart?.type === "file") {
|
||||||
|
expect(filePart.mime).toBe("application/x-directory")
|
||||||
|
expect(filePart.filename).toBe("docs")
|
||||||
|
expect(filePart.url).toBe("file:///repo/../docs")
|
||||||
|
expect(filePart.source?.type).toBe("file")
|
||||||
|
if (filePart.source?.type === "file") {
|
||||||
|
expect(filePart.source.path).toBe("/repo/../docs")
|
||||||
|
expect(filePart.source.text.value).toBe("@docs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test("deduplicates context files when prompt already includes same path", () => {
|
test("deduplicates context files when prompt already includes same path", () => {
|
||||||
const prompt: Prompt = [{ type: "file", path: "src/foo.ts", content: "@src/foo.ts", start: 0, end: 11 }]
|
const prompt: Prompt = [{ type: "file", path: "src/foo.ts", content: "@src/foo.ts", start: 0, end: 11 }]
|
||||||
|
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ export function buildRequestParts(input: BuildRequestPartsInput) {
|
|||||||
return {
|
return {
|
||||||
id: Identifier.ascending("part"),
|
id: Identifier.ascending("part"),
|
||||||
type: "file",
|
type: "file",
|
||||||
mime: "text/plain",
|
mime: attachment.mime ?? "text/plain",
|
||||||
url: `file://${encodeFilePath(path)}${fileQuery(attachment.selection)}`,
|
url: `file://${encodeFilePath(path)}${fileQuery(attachment.selection)}`,
|
||||||
filename: getFilename(attachment.path),
|
filename: attachment.filename ?? getFilename(attachment.path),
|
||||||
source: {
|
source: {
|
||||||
type: "file",
|
type: "file",
|
||||||
text: {
|
text: {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
|||||||
|
|
||||||
export type AtOption =
|
export type AtOption =
|
||||||
| { type: "agent"; name: string; display: string }
|
| { type: "agent"; name: string; display: string }
|
||||||
|
| { type: "reference"; name: string; path: string; display: string; description: string }
|
||||||
| { type: "file"; path: string; display: string; recent?: boolean }
|
| { type: "file"; path: string; display: string; recent?: boolean }
|
||||||
|
|
||||||
export interface SlashCommand {
|
export interface SlashCommand {
|
||||||
@@ -103,6 +104,23 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.type === "reference") {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
class="w-full flex items-center gap-x-2 rounded-md px-2 py-0.5"
|
||||||
|
classList={{ "bg-surface-raised-base-hover": props.atActive === key }}
|
||||||
|
onClick={() => props.onAtSelect(item)}
|
||||||
|
onMouseEnter={() => props.setAtActive(key)}
|
||||||
|
>
|
||||||
|
<FileIcon node={{ path: item.path, type: "directory" }} class="shrink-0 size-4" />
|
||||||
|
<div class="flex items-center text-14-regular min-w-0">
|
||||||
|
<span class="text-text-strong whitespace-nowrap">@{item.name}</span>
|
||||||
|
<span class="text-text-weak whitespace-nowrap truncate min-w-0 ml-2">{item.description}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const isDirectory = item.path.endsWith("/")
|
const isDirectory = item.path.endsWith("/")
|
||||||
const directory = isDirectory ? item.path : getDirectory(item.path)
|
const directory = isDirectory ? item.path : getDirectory(item.path)
|
||||||
const filename = isDirectory ? "" : getFilename(item.path)
|
const filename = isDirectory ? "" : getFilename(item.path)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ describe("bootstrapDirectory", () => {
|
|||||||
status: "loading",
|
status: "loading",
|
||||||
agent: [],
|
agent: [],
|
||||||
command: [],
|
command: [],
|
||||||
|
reference: [],
|
||||||
project: "",
|
project: "",
|
||||||
projectMeta: undefined,
|
projectMeta: undefined,
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
@@ -67,6 +68,7 @@ describe("bootstrapDirectory", () => {
|
|||||||
},
|
},
|
||||||
permission: { list: async () => ({ data: [] }) },
|
permission: { list: async () => ({ data: [] }) },
|
||||||
question: { list: async () => ({ data: [] }) },
|
question: { list: async () => ({ data: [] }) },
|
||||||
|
v2: { reference: { list: async () => ({ data: { data: [] } }) } },
|
||||||
mcp: {
|
mcp: {
|
||||||
status: async () => {
|
status: async () => {
|
||||||
mcpReads.push("status")
|
mcpReads.push("status")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
Project,
|
Project,
|
||||||
ProviderAuthResponse,
|
ProviderAuthResponse,
|
||||||
QuestionRequest,
|
QuestionRequest,
|
||||||
|
ReferenceInfo,
|
||||||
Session,
|
Session,
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
} from "@opencode-ai/sdk/v2/client"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
@@ -195,6 +196,13 @@ export const loadPathQuery = (scope: ServerScope, directory: string | null, sdk:
|
|||||||
queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
|
queryFn: () => retry(() => sdk.path.get().then((x) => x.data!)),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const loadReferencesQuery = (scope: ServerScope, directory: string, sdk: OpencodeClient) =>
|
||||||
|
queryOptions<ReferenceInfo[]>({
|
||||||
|
queryKey: [scope, directory, "references"] as const,
|
||||||
|
queryFn: () => retry(() => sdk.v2.reference.list().then((x) => x.data?.data ?? [])).catch(() => []),
|
||||||
|
placeholderData: [],
|
||||||
|
})
|
||||||
|
|
||||||
export async function bootstrapDirectory(input: {
|
export async function bootstrapDirectory(input: {
|
||||||
directory: string
|
directory: string
|
||||||
scope: ServerScope
|
scope: ServerScope
|
||||||
@@ -277,6 +285,7 @@ export async function bootstrapDirectory(input: {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
input.mcp && (() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? [])))),
|
input.mcp && (() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? [])))),
|
||||||
|
() => input.queryClient.fetchQuery(loadReferencesQuery(input.scope, input.directory, input.sdk)),
|
||||||
() =>
|
() =>
|
||||||
retry(() =>
|
retry(() =>
|
||||||
input.sdk.permission.list().then((x) => {
|
input.sdk.permission.list().then((x) => {
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export function createChildStoreManager(input: {
|
|||||||
const mcpQuery = useQuery(() => ({ ...input.queryOptions.mcp(key), enabled: mcpEnabled() }))
|
const mcpQuery = useQuery(() => ({ ...input.queryOptions.mcp(key), enabled: mcpEnabled() }))
|
||||||
const lspQuery = useQuery(() => input.queryOptions.lsp(key))
|
const lspQuery = useQuery(() => input.queryOptions.lsp(key))
|
||||||
const providerQuery = useQuery(() => input.queryOptions.providers(key))
|
const providerQuery = useQuery(() => input.queryOptions.providers(key))
|
||||||
|
const referenceQuery = useQuery(() => input.queryOptions.references(key))
|
||||||
|
|
||||||
const child = createStore<State>({
|
const child = createStore<State>({
|
||||||
project: "",
|
project: "",
|
||||||
@@ -210,6 +211,9 @@ export function createChildStoreManager(input: {
|
|||||||
status: "loading" as const,
|
status: "loading" as const,
|
||||||
agent: [],
|
agent: [],
|
||||||
command: [],
|
command: [],
|
||||||
|
get reference() {
|
||||||
|
return referenceQuery.isLoading ? [] : (referenceQuery.data ?? [])
|
||||||
|
},
|
||||||
session: [],
|
session: [],
|
||||||
sessionTotal: 0,
|
sessionTotal: 0,
|
||||||
session_status: {},
|
session_status: {},
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ export function applyDirectoryEvent(input: {
|
|||||||
push: (directory: string) => void
|
push: (directory: string) => void
|
||||||
directory: string
|
directory: string
|
||||||
loadLsp: () => void
|
loadLsp: () => void
|
||||||
|
loadReferences?: () => void
|
||||||
vcsCache?: VcsCache
|
vcsCache?: VcsCache
|
||||||
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void
|
setSessionTodo?: (sessionID: string, todos: Todo[] | undefined) => void
|
||||||
retainedLimit?: number
|
retainedLimit?: number
|
||||||
@@ -404,5 +405,9 @@ export function applyDirectoryEvent(input: {
|
|||||||
input.loadLsp()
|
input.loadLsp()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case "reference.updated": {
|
||||||
|
input.loadReferences?.()
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
Path,
|
Path,
|
||||||
PermissionRequest,
|
PermissionRequest,
|
||||||
QuestionRequest,
|
QuestionRequest,
|
||||||
|
ReferenceInfo,
|
||||||
Session,
|
Session,
|
||||||
SessionStatus,
|
SessionStatus,
|
||||||
SnapshotFileDiff,
|
SnapshotFileDiff,
|
||||||
@@ -34,6 +35,7 @@ export type State = {
|
|||||||
status: "loading" | "partial" | "complete"
|
status: "loading" | "partial" | "complete"
|
||||||
agent: Agent[]
|
agent: Agent[]
|
||||||
command: Command[]
|
command: Command[]
|
||||||
|
reference: ReferenceInfo[]
|
||||||
project: string
|
project: string
|
||||||
projectMeta: ProjectMeta | undefined
|
projectMeta: ProjectMeta | undefined
|
||||||
icon: string | undefined
|
icon: string | undefined
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export interface FileAttachmentPart extends PartBase {
|
|||||||
type: "file"
|
type: "file"
|
||||||
path: string
|
path: string
|
||||||
selection?: FileSelection
|
selection?: FileSelection
|
||||||
|
mime?: string
|
||||||
|
filename?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentPart extends PartBase {
|
export interface AgentPart extends PartBase {
|
||||||
@@ -73,7 +75,13 @@ function isPartEqual(partA: ContentPart, partB: ContentPart) {
|
|||||||
case "text":
|
case "text":
|
||||||
return partB.type === "text" && partA.content === partB.content
|
return partB.type === "text" && partA.content === partB.content
|
||||||
case "file":
|
case "file":
|
||||||
return partB.type === "file" && partA.path === partB.path && isSelectionEqual(partA.selection, partB.selection)
|
return (
|
||||||
|
partB.type === "file" &&
|
||||||
|
partA.path === partB.path &&
|
||||||
|
partA.mime === partB.mime &&
|
||||||
|
partA.filename === partB.filename &&
|
||||||
|
isSelectionEqual(partA.selection, partB.selection)
|
||||||
|
)
|
||||||
case "agent":
|
case "agent":
|
||||||
return partB.type === "agent" && partA.name === partB.name
|
return partB.type === "agent" && partA.name === partB.name
|
||||||
case "image":
|
case "image":
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
loadPathQuery,
|
loadPathQuery,
|
||||||
loadProjectsQuery,
|
loadProjectsQuery,
|
||||||
loadProvidersQuery,
|
loadProvidersQuery,
|
||||||
|
loadReferencesQuery,
|
||||||
} from "./global-sync/bootstrap"
|
} from "./global-sync/bootstrap"
|
||||||
import { createChildStoreManager } from "./global-sync/child-store"
|
import { createChildStoreManager } from "./global-sync/child-store"
|
||||||
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
|
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
|
||||||
@@ -75,6 +76,7 @@ function makeQueryOptionsApi(
|
|||||||
path: (directory: PathKey | null) =>
|
path: (directory: PathKey | null) =>
|
||||||
loadPathQuery(scope, directory, directory === null ? serverSDK() : sdkFor(directory)),
|
loadPathQuery(scope, directory, directory === null ? serverSDK() : sdkFor(directory)),
|
||||||
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, sdkFor(directory)),
|
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, sdkFor(directory)),
|
||||||
|
references: (directory: PathKey) => loadReferencesQuery(scope, directory, sdkFor(directory)),
|
||||||
mcp: (directory: PathKey) => loadMcpQuery(scope, directory, sdkFor(directory)),
|
mcp: (directory: PathKey) => loadMcpQuery(scope, directory, sdkFor(directory)),
|
||||||
lsp: (directory: PathKey) => loadLspQuery(scope, directory, sdkFor(directory)),
|
lsp: (directory: PathKey) => loadLspQuery(scope, directory, sdkFor(directory)),
|
||||||
sessions: (directory: PathKey) => ({ queryKey: [scope, directory, "loadSessions"] as const }),
|
sessions: (directory: PathKey) => ({ queryKey: [scope, directory, "loadSessions"] as const }),
|
||||||
@@ -396,6 +398,9 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
|||||||
loadLsp: () => {
|
loadLsp: () => {
|
||||||
void queryClient.fetchQuery(queryOptionsApi.lsp(key))
|
void queryClient.fetchQuery(queryOptionsApi.lsp(key))
|
||||||
},
|
},
|
||||||
|
loadReferences: () => {
|
||||||
|
void queryClient.fetchQuery(queryOptionsApi.references(key))
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user