fix(web): render the create-bot modal outside the side panel; tidy Shell leftovers (#131)

The centered Create new Bot modal was rendered inside the side panel's
condition, so the empty states that open it (nothing selected, or a
project without bots) only showed it while a panel happened to be open.
It is a portal to the document body, so it now renders after the panel.

- The project drag grip's accessible name goes through Lingui (German
  "Ziehen zum Umsortieren") instead of a hard-coded English label.
- Drop the project drag state that was set but never read, and imports
  Shell.tsx no longer uses.


Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cynarAI 2026-10-04 16:43:50 +02:00 committed by GitHub
parent e928600c98
commit 68b1e63c46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 79 additions and 59 deletions

View File

@ -4,6 +4,7 @@
*/ */
import { useSortable } from "@dnd-kit/sortable"; import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
import { useLingui } from "@lingui/react/macro";
import { createContext, memo, useContext, useMemo } from "react"; import { createContext, memo, useContext, useMemo } from "react";
interface SortableGroupProps { interface SortableGroupProps {
@ -30,13 +31,14 @@ export const ProjectDragGrip = memo(function ProjectDragGrip({
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const { t } = useLingui();
const handle = useSortableGroupHandle(); const handle = useSortableGroupHandle();
return ( return (
<button <button
type="button" type="button"
ref={handle?.setActivatorNodeRef} ref={handle?.setActivatorNodeRef}
className={className} className={className}
aria-label="Drag to reorder" aria-label={t`Drag to reorder`}
{...handle?.attributes} {...handle?.attributes}
{...handle?.listeners} {...handle?.listeners}
> >

View File

@ -2100,6 +2100,10 @@ msgstr "Skill-Entwurf"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "Entwerfe die Spezifikation…" msgstr "Entwerfe die Spezifikation…"
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr "Ziehen zum Umsortieren"
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "Bild ziehen, ablegen oder einfügen" msgstr "Bild ziehen, ablegen oder einfügen"

View File

@ -2100,6 +2100,10 @@ msgstr "Draft skill"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "Drafting the spec…" msgstr "Drafting the spec…"
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr "Drag to reorder"
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "Drag, drop, or paste an image" msgstr "Drag, drop, or paste an image"

View File

@ -2100,6 +2100,10 @@ msgstr "Habilidad en borrador"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "" msgstr ""
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr ""
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "Arrastra, suelta o pega una imagen" msgstr "Arrastra, suelta o pega una imagen"

View File

@ -2100,6 +2100,10 @@ msgstr "ड्राफ़्ट स्किल"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "" msgstr ""
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr ""
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "छवि खींचें, छोड़ें या पेस्ट करें" msgstr "छवि खींचें, छोड़ें या पेस्ट करें"

View File

@ -2100,6 +2100,10 @@ msgstr "작업 기술 초안"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "" msgstr ""
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr ""
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "이미지를 끌어다 놓거나 붙여넣으세요" msgstr "이미지를 끌어다 놓거나 붙여넣으세요"

View File

@ -2100,6 +2100,10 @@ msgstr "Habilidade de rascunho"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "" msgstr ""
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr ""
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "Arraste, solte ou cole uma imagem" msgstr "Arraste, solte ou cole uma imagem"

View File

@ -2100,6 +2100,10 @@ msgstr "Черновик навыка"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "" msgstr ""
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr ""
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "Перетащите или вставьте изображение" msgstr "Перетащите или вставьте изображение"

View File

@ -2100,6 +2100,10 @@ msgstr "Taslak beceri"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "" msgstr ""
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr ""
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "Görüntüyü sürükleyip bırakın veya yapıştırın" msgstr "Görüntüyü sürükleyip bırakın veya yapıştırın"

View File

@ -2100,6 +2100,10 @@ msgstr "技能草稿"
msgid "Drafting the spec…" msgid "Drafting the spec…"
msgstr "" msgstr ""
#: src/components/SortableGroup.tsx
msgid "Drag to reorder"
msgstr ""
#: src/pages/shell/avatar-studio-popover.tsx #: src/pages/shell/avatar-studio-popover.tsx
msgid "Drag, drop, or paste an image" msgid "Drag, drop, or paste an image"
msgstr "拖放或粘贴图片" msgstr "拖放或粘贴图片"

View File

@ -1,10 +1,5 @@
import { closestCenter, DndContext, DragOverlay } from "@dnd-kit/core"; import { closestCenter, DndContext } from "@dnd-kit/core";
import { import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS as DndCSS } from "@dnd-kit/utilities";
import { Trans, useLingui } from "@lingui/react/macro"; import { Trans, useLingui } from "@lingui/react/macro";
import type { import type {
Bot, Bot,
@ -72,13 +67,10 @@ import {
ArrowLeft, ArrowLeft,
Calendar, Calendar,
Globe, Globe,
LayoutGrid,
PanelRight, PanelRight,
Pencil, Pencil,
Plus, Plus,
RefreshCw,
Search, Search,
Settings,
Trash2, Trash2,
X, X,
} from "lucide-react"; } from "lucide-react";
@ -1320,9 +1312,7 @@ export function ShellPage() {
mainConversationEntry, mainConversationEntry,
projectSidebarGroups, projectSidebarGroups,
sensors, sensors,
setDraggedProjectId,
handleProjectDragEnd, handleProjectDragEnd,
handleProjectDragStart,
} = useSidebarModel({ } = useSidebarModel({
query, query,
spaces, spaces,
@ -2943,8 +2933,6 @@ export function ShellPage() {
sensors={sensors} sensors={sensors}
collisionDetection={closestCenter} collisionDetection={closestCenter}
onDragEnd={handleProjectDragEnd} onDragEnd={handleProjectDragEnd}
onDragStart={handleProjectDragStart}
onDragCancel={() => setDraggedProjectId(null)}
> >
<SortableContext <SortableContext
items={projectSidebarGroups.filter((g) => g.title).map((g) => g.spaceId)} items={projectSidebarGroups.filter((g) => g.title).map((g) => g.spaceId)}
@ -3544,44 +3532,6 @@ export function ShellPage() {
onImport={(manifest) => importBot(manifest)} onImport={(manifest) => importBot(manifest)}
/> />
) : null} ) : null}
{/* Centered Create Bot Modal */}
{showCreateBotModal &&
createPortal(
<div className="fixed inset-0 z-50 flex items-center justify-center">
<button
type="button"
className="absolute inset-0 bg-overlay"
aria-label={t`Close`}
onClick={() => setShowCreateBotModal(false)}
/>
<div className="relative z-10 max-h-[90vh] w-[480px] overflow-y-auto rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-2xl">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-foreground">
<Trans>Create new Bot</Trans>
</h2>
<button
type="button"
onClick={() => setShowCreateBotModal(false)}
className="text-muted-foreground hover:text-foreground"
aria-label={t`Close`}
>
<X strokeWidth={1.8} className="size-icon-md" />
</button>
</div>
<CreateBotForm
onCancel={() => setShowCreateBotModal(false)}
onCreate={async (input) => {
await createBot(input);
}}
onImport={async (manifest) => {
await importBot(manifest);
setShowCreateBotModal(false);
}}
/>
</div>
</div>,
document.body,
)}
{panel === "settings" && active ? ( {panel === "settings" && active ? (
<BotSettings <BotSettings
key={active.id} key={active.id}
@ -3773,6 +3723,44 @@ export function ShellPage() {
</div> </div>
) : null} ) : null}
</aside> </aside>
{/* Centered Create Bot Modal */}
{showCreateBotModal &&
createPortal(
<div className="fixed inset-0 z-50 flex items-center justify-center">
<button
type="button"
className="absolute inset-0 bg-overlay"
aria-label={t`Close`}
onClick={() => setShowCreateBotModal(false)}
/>
<div className="relative z-10 max-h-[90vh] w-[480px] overflow-y-auto rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-2xl">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-foreground">
<Trans>Create new Bot</Trans>
</h2>
<button
type="button"
onClick={() => setShowCreateBotModal(false)}
className="text-muted-foreground hover:text-foreground"
aria-label={t`Close`}
>
<X strokeWidth={1.8} className="size-icon-md" />
</button>
</div>
<CreateBotForm
onCancel={() => setShowCreateBotModal(false)}
onCreate={async (input) => {
await createBot(input);
}}
onImport={async (manifest) => {
await importBot(manifest);
setShowCreateBotModal(false);
}}
/>
</div>
</div>,
document.body,
)}
<Suspense fallback={null}> <Suspense fallback={null}>
{contextChat && botMenu ? ( {contextChat && botMenu ? (

View File

@ -32,7 +32,6 @@ export function useSidebarModel({
setProjectOrder(orderedIds); setProjectOrder(orderedIds);
writeProjectSidebarOrder(orderedIds); writeProjectSidebarOrder(orderedIds);
}, []); }, []);
const [draggedProjectId, setDraggedProjectId] = useState<string | null>(null);
const sensors = useSensors( const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { distance: 8 } }), useSensor(MouseSensor, { activationConstraint: { distance: 8 } }),
useSensor(TouchSensor, { activationConstraint: { distance: 8 } }), useSensor(TouchSensor, { activationConstraint: { distance: 8 } }),
@ -242,9 +241,6 @@ export function useSidebarModel({
}, },
[projectSidebarGroups, handleReorderProjects], [projectSidebarGroups, handleReorderProjects],
); );
const handleProjectDragStart = useCallback((event: { active: { id: string | number } }) => {
setDraggedProjectId(String(event.active.id));
}, []);
return { return {
homeSpaceBots, homeSpaceBots,
@ -252,8 +248,6 @@ export function useSidebarModel({
mainConversationEntry, mainConversationEntry,
projectSidebarGroups, projectSidebarGroups,
sensors, sensors,
setDraggedProjectId,
handleProjectDragEnd, handleProjectDragEnd,
handleProjectDragStart,
}; };
} }