docs(folders): put back the halves that carried the reason

Cutting each block to its first sentence sometimes kept the what and dropped the
why, which leaves a comment saying what the signature already says. Those are
deleted where the name covers them, and where the second sentence was the point
it is back: the OS error codes behind isAlreadyExists, why a virtual folder
cannot hang off a server one, the effect that snaps folder selection back to root.
This commit is contained in:
Reece
2026-09-01 16:07:32 +01:00
parent b6139d1cd0
commit bfb48c88de
7 changed files with 16 additions and 13 deletions
@@ -97,7 +97,7 @@ interface FolderContextValue {
ok: boolean;
reason?: "endpoint-missing" | "network" | "server" | "client";
}>;
/** Create a folder. */
/** Create a folder. A child takes its parent's kind; only a root create chooses. */
createFolder: (
name: string,
parentFolderId?: FolderId | null,
@@ -113,7 +113,7 @@ interface FolderContextValue {
appearance: { color?: string; icon?: string | null },
) => Promise<FolderRecord | null>;
deleteFolder: (id: FolderId) => Promise<FolderId[]>;
/** Mount a directory as a local folder, idempotent per directory. */
/** Idempotent per directory: mounting one already mounted returns its record. */
mountLocalFolder: (directory: string, name: string) => Promise<FolderRecord>;
/** Subdirectories a mount listing found under `parentId`. */
registerDiskSubfolders: (parentId: FolderId, records: FolderRecord[]) => void;
@@ -602,7 +602,7 @@ export function FolderProvider({ children }: FolderProviderProps) {
parentFolderId: FolderId | null = currentFolderId,
kind?: FolderKind,
): Promise<FolderRecord> => {
// A child's kind is its parent's.
// A child's kind is its parent's: one subtree, one system of record.
const effectiveKind: FolderKind =
parentFolderId !== null
? requireKind(parentFolderId)
@@ -21,7 +21,8 @@ export function useNewFolderFlow() {
const picked = await pickDirectory();
if (!picked) return;
const record = await folders.mountLocalFolder(picked.path, picked.name);
// The path owns folder selection.
// The path owns folder selection: setting state here races the effect that
// re-runs with the old pathname and snaps back to root.
navigate(`/files/${record.id}`);
} catch (err) {
folders.setError(
@@ -58,7 +59,8 @@ export function useNewFolderFlow() {
serverFolderBlock,
]);
// Why the single-click surfaces are disabled, or null.
// Why the single-click surfaces are disabled, or null. Only the web root blocks:
// desktop always has the picker, and subfolders inherit their kind.
const createFolderHereBlockedReason =
folders.currentFolderId === null && !canPickDirectory
? serverFolderBlock
@@ -7,7 +7,6 @@ export interface PickedDirectory {
name: string;
}
/** Whether this build can produce a directory path at all. */
export const canPickDirectory = false;
/** Ask the user for a directory; null when cancelled (or unsupported). */
@@ -1,5 +1,3 @@
/** Reading a mounted folder's contents straight off the disk. */
/** One file inside a mounted directory, as the file manager lists it. */
export interface DiskFileEntry {
/** Absolute path — the file's identity here; nothing about it is stored. */
@@ -21,7 +19,6 @@ export interface DiskListing {
directories: DiskDirEntry[];
}
/** Whether this build can list a directory at all. */
export const canListDirectory = false;
/**
@@ -58,7 +58,10 @@ class VirtualFolderStorageService {
});
}
/** Create a virtual folder under `parent` (null = root). */
/**
* Create a virtual folder under `parent` (null = root), which must itself be
* virtual: hung off a server folder, a server-side delete orphans the subtree.
*/
async createFolder(
name: string,
parentFolderId: FolderId | null,
@@ -4,7 +4,10 @@ import { connectionModeService } from "@app/services/connectionModeService";
import type { ConnectionMode } from "@app/services/connectionModeService";
import { useServerFolderBlock as useCoreServerFolderBlock } from "@core/hooks/useServerFolderBlock";
/** Desktop's blocker speaks in connection modes. */
/**
* Desktop's blocker speaks in connection modes: local mode has no server at all, so
* "storage isn't enabled" would send the user after a setting that does not exist.
*/
export function useServerFolderBlock(): string | null {
const { t } = useTranslation();
const coreReason = useCoreServerFolderBlock();
@@ -103,7 +103,6 @@ export async function listDirectory(
return { files: files.slice(0, LIST_CAP), directories };
}
/** Create a subdirectory. */
export async function makeDiskDirectory(
parent: string,
name: string,
@@ -185,7 +184,7 @@ export async function writeDiskFile(
throw new Error(`No free name for ${name} in ${directory}`);
}
/** The plugin surfaces OS errors as text. */
/** The plugin surfaces OS errors as text: EEXIST is 17, Windows 80 or 183. */
function isAlreadyExists(err: unknown): boolean {
const text = err instanceof Error ? err.message : String(err);
return /already exists|file exists|os error (17|80|183)\b/i.test(text);