From bfb48c88de86c007bf5067d55fb974306cbfb1ea Mon Sep 17 00:00:00 2001 From: Reece Date: Tue, 1 Sep 2026 16:07:32 +0100 Subject: [PATCH] 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. --- frontend/editor/src/core/contexts/FolderContext.tsx | 6 +++--- frontend/editor/src/core/hooks/useNewFolderFlow.ts | 6 ++++-- frontend/editor/src/core/services/directoryPicker.ts | 1 - frontend/editor/src/core/services/localFolderContents.ts | 3 --- frontend/editor/src/core/services/virtualFolderStorage.ts | 5 ++++- frontend/editor/src/desktop/hooks/useServerFolderBlock.ts | 5 ++++- frontend/editor/src/desktop/services/localFolderContents.ts | 3 +-- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/frontend/editor/src/core/contexts/FolderContext.tsx b/frontend/editor/src/core/contexts/FolderContext.tsx index d9124fb1ca..29c50088f8 100644 --- a/frontend/editor/src/core/contexts/FolderContext.tsx +++ b/frontend/editor/src/core/contexts/FolderContext.tsx @@ -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; deleteFolder: (id: FolderId) => Promise; - /** 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; /** 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 => { - // 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) diff --git a/frontend/editor/src/core/hooks/useNewFolderFlow.ts b/frontend/editor/src/core/hooks/useNewFolderFlow.ts index 769db63c96..a5ab6b85e9 100644 --- a/frontend/editor/src/core/hooks/useNewFolderFlow.ts +++ b/frontend/editor/src/core/hooks/useNewFolderFlow.ts @@ -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 diff --git a/frontend/editor/src/core/services/directoryPicker.ts b/frontend/editor/src/core/services/directoryPicker.ts index bbbd0d9df0..c529eccb1f 100644 --- a/frontend/editor/src/core/services/directoryPicker.ts +++ b/frontend/editor/src/core/services/directoryPicker.ts @@ -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). */ diff --git a/frontend/editor/src/core/services/localFolderContents.ts b/frontend/editor/src/core/services/localFolderContents.ts index 994a7ae694..54866f9e16 100644 --- a/frontend/editor/src/core/services/localFolderContents.ts +++ b/frontend/editor/src/core/services/localFolderContents.ts @@ -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; /** diff --git a/frontend/editor/src/core/services/virtualFolderStorage.ts b/frontend/editor/src/core/services/virtualFolderStorage.ts index e7bfd347e3..3068cc5290 100644 --- a/frontend/editor/src/core/services/virtualFolderStorage.ts +++ b/frontend/editor/src/core/services/virtualFolderStorage.ts @@ -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, diff --git a/frontend/editor/src/desktop/hooks/useServerFolderBlock.ts b/frontend/editor/src/desktop/hooks/useServerFolderBlock.ts index 79f601a0b5..9a16c63ff7 100644 --- a/frontend/editor/src/desktop/hooks/useServerFolderBlock.ts +++ b/frontend/editor/src/desktop/hooks/useServerFolderBlock.ts @@ -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(); diff --git a/frontend/editor/src/desktop/services/localFolderContents.ts b/frontend/editor/src/desktop/services/localFolderContents.ts index bd47a44399..1d071b74bb 100644 --- a/frontend/editor/src/desktop/services/localFolderContents.ts +++ b/frontend/editor/src/desktop/services/localFolderContents.ts @@ -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);