List Files through get, because query cannot see a folder before 0.16

Creating a folder on the live 0.15.5 server did nothing visible, with no error
and nothing after a reload. The folder was real all along: FileNode/query masks
its results with document_ids(false) — resources that are *not* containers — so
it returns files and never folders, and says nothing about the omission.

FileNode/get carries no such mask, so on those servers the whole tree comes
from a single get with ids:null instead. That also stops ensureFolder making a
fresh "ihasmail" folder on every signature save, having never been able to find
the one already there.
This commit is contained in:
2026-08-24 10:05:08 -07:00
parent 5befef7ed7
commit 8d90bca6b4
4 changed files with 73 additions and 28 deletions
+20 -13
View File
@@ -1,6 +1,6 @@
import { create } from "zustand";
import { CAP, JmapMethodError, client, setErrorMessage } from "@/jmap/client";
import { directoryCreate, fileCreate, fileNodeProps, normalizeFileNodes } from "@/lib/filenode";
import { directoryCreate, fileCreate, fileNodeProps, normalizeFileNodes, queryOmitsDirectories } from "@/lib/filenode";
import type { FileNode, GetResponse, Id, QueryResponse, SetResponse } from "@/jmap/types";
import { useSession } from "./session";
@@ -33,17 +33,24 @@ const byName = (a: FileNode, b: FileNode) => (a.nodeType === b.nodeType ? a.name
/** Fetch all nodes (paged, no filter) and rebuild the full children map. */
async function loadAllNodes(accountId: Id, set: (fn: (s: FilesState) => Partial<FilesState>) => void): Promise<void> {
const all: FileNode[] = [];
let position = 0;
for (let guard = 0; guard < 100; guard++) {
const res = await client.chain([
["FileNode/query", { accountId, position, limit: 500, calculateTotal: true }, "q"],
["FileNode/get", { accountId, "#ids": { resultOf: "q", name: "FileNode/query", path: "/ids" }, properties: fileNodeProps() }, "g"],
]);
const q = res.get("q")?.[0] as unknown as QueryResponse;
const g = res.get("g")?.[0] as unknown as GetResponse<FileNode>;
all.push(...normalizeFileNodes(g.list));
position += q.ids.length;
if (!q.ids.length || (q.total != null && position >= q.total)) break;
if (queryOmitsDirectories()) {
// Query would hand back files only, so every folder — including one just
// created — would be missing with nothing to say why. Ask get for the lot.
const res = await client.call<GetResponse<FileNode>>("FileNode/get", { accountId, ids: null, properties: fileNodeProps() });
all.push(...normalizeFileNodes(res.list));
} else {
let position = 0;
for (let guard = 0; guard < 100; guard++) {
const res = await client.chain([
["FileNode/query", { accountId, position, limit: 500, calculateTotal: true }, "q"],
["FileNode/get", { accountId, "#ids": { resultOf: "q", name: "FileNode/query", path: "/ids" }, properties: fileNodeProps() }, "g"],
]);
const q = res.get("q")?.[0] as unknown as QueryResponse;
const g = res.get("g")?.[0] as unknown as GetResponse<FileNode>;
all.push(...normalizeFileNodes(g.list));
position += q.ids.length;
if (!q.ids.length || (q.total != null && position >= q.total)) break;
}
}
const nodes: Record<Id, FileNode> = {};
const children: Record<string, Id[]> = { root: [] };
@@ -77,7 +84,7 @@ export const useFiles = create<FilesState>((set, get) => ({
if (!accountId) return;
set({ loading: true });
try {
if (!filtersSupported) {
if (!filtersSupported || queryOmitsDirectories()) {
await loadAllNodes(accountId, set);
return;
}