From 7298102e93e2c65bee04ea1bf2b8329cc2423cea Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Tue, 10 Nov 2026 10:35:35 +0100 Subject: [PATCH] Folder picker: create a new folder (new automation, new chat) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 +++++++ web/src/components/NewSession.tsx | 20 ++++++++++++++++++++ web/src/i18n/de.ts | 1 + web/src/i18n/en.ts | 1 + web/src/whatsnew.ts | 4 ++++ 5 files changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e0e629..06204ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to CodeCraft. Versions follow [Semantic Versioning](https://semver.org/); until 1.0, minor versions may contain breaking changes. +## [Unreleased] + +### Added + +- **New folder in the folder picker:** creating an automation or a chat, *New folder* creates a + subfolder of the shown folder and selects it. + ## [v202611-00013] – 2026-11-10 ### Added diff --git a/web/src/components/NewSession.tsx b/web/src/components/NewSession.tsx index 5a8c70f..0b65ebc 100644 --- a/web/src/components/NewSession.tsx +++ b/web/src/components/NewSession.tsx @@ -1,6 +1,7 @@ import { Icon } from "./Icon"; import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { post, uploadFile } from "../api"; +import { promptDialog, toast } from "../dialogs"; import { useAgents, useFetch } from "../hooks"; import { useT, type Key } from "../i18n"; import type { ImageData } from "../types"; @@ -602,6 +603,20 @@ export function FolderPicker({ path, onPick }: { path: string; onPick: (p: strin // eslint-disable-next-line react-hooks/exhaustive-deps }, [listing?.path]); const rel = listing ? listing.path.slice(listing.workspace.length) || "/" : ""; + /** Create a subfolder of the shown folder and select it. */ + const newFolder = async () => { + if (!listing) return; + const name = (await promptDialog({ title: t("files.newFolder"), message: t("files.newFolderName") }))?.trim(); + if (!name) return; + if (name.includes("/") || name === "." || name === "..") return toast(t("new.invalidFolderName")); + const target = `${listing.path.replace(/\/$/, "")}/${name}`; + try { + await post("/files/create", { path: target, dir: true }); + setBrowse(target); + } catch (e: any) { + toast(e.message); + } + }; return (
{roots.length > 1 && ( @@ -622,6 +637,11 @@ export function FolderPicker({ path, onPick }: { path: string; onPick: (p: strin {t("new.up")} )} + {listing && ( + + )}
{listing?.dirs.length === 0 &&
{t("new.noSubdirs")}
} diff --git a/web/src/i18n/de.ts b/web/src/i18n/de.ts index 1084b9f..d6efd90 100644 --- a/web/src/i18n/de.ts +++ b/web/src/i18n/de.ts @@ -554,6 +554,7 @@ export const de: Messages = { "jobs.tplMode": "Modus", "new.title": "Neue Session", "new.directory": "Projektverzeichnis", + "new.invalidFolderName": "Ordnernamen dürfen kein „/“ enthalten.", "new.up": "Hoch", "new.noSubdirs": "Keine Unterverzeichnisse", "new.model": "Modell", diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index dc9383c..a15f420 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -553,6 +553,7 @@ export const en = { "jobs.tplMode": "Mode", "new.title": "New session", "new.directory": "Project directory", + "new.invalidFolderName": "Folder names cannot contain “/”.", "new.up": "Up", "new.noSubdirs": "No subdirectories", "new.model": "Model", diff --git a/web/src/whatsnew.ts b/web/src/whatsnew.ts index 9a2e111..45e5b7a 100644 --- a/web/src/whatsnew.ts +++ b/web/src/whatsnew.ts @@ -3,6 +3,10 @@ * under "unreleased"; scripts/release.sh renames the key to the release version. */ export const WHATS_NEW: Record = { + unreleased: { + en: ["Folder picker (new automation, new chat): create a new folder right there"], + de: ["Ordnerauswahl (neue Automatisierung, neuer Chat): direkt einen neuen Ordner anlegen"], + }, "202611-00013": { en: [ "New automation editor: instructions, a graphical schedule and an assistant chat that writes the instructions with you",