From 33121cd73d7fe70ae210d4ff092f67c1acbef004 Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Sun, 12 Jul 2026 06:07:15 +0200 Subject: [PATCH] Move pond settings to a TopBar gear, pin the trash link to the sidebar bottom (M10 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - owners get a Settings icon next to the pond name while a pond route is active (same lucide set, localized aria-label/tooltip via the existing labels:link key); gone on non-pond routes and for non-owners - the trash stays a text link but pins to the sidebar's bottom (.sidebar is a flex column now; .sidebar__footer uses margin-top:auto) - trash.spec: delete flows go through the #101 overflow menu (was missed in 65f30a5 — the pack is not part of CI) - markdown.spec: replace the wait for the 'saved' status removed in #36 with polling the export endpoint (pre-existing local failure, same category as the known image.spec one) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 --- apps/web/e2e/markdown.spec.ts | 10 +++++++++- apps/web/e2e/trash.spec.ts | 12 +++++++++--- apps/web/src/layout/Sidebar.tsx | 20 ++++++++++---------- apps/web/src/layout/TopBar.tsx | 25 ++++++++++++++++++++++++- apps/web/src/styles/base.css | 11 +++++++++-- 5 files changed, 61 insertions(+), 17 deletions(-) diff --git a/apps/web/e2e/markdown.spec.ts b/apps/web/e2e/markdown.spec.ts index 6312d96..2b8549d 100644 --- a/apps/web/e2e/markdown.spec.ts +++ b/apps/web/e2e/markdown.spec.ts @@ -117,7 +117,15 @@ test('page menu downloads the page as Markdown matching its content', async ({ b await enterEditMode(page); await page.locator('.ProseMirror').click(); await page.keyboard.type('export me please'); - await expect(page.getByRole('status')).toHaveText(/saved|gespeichert/i, { timeout: 10000 }); + + // There is no "saved" status since #36 (collab autosaves continuously) — + // poll the export endpoint until the typed content has been persisted. + await expect + .poll( + async () => (await context.request.get(`/api/v1/pages/${pageId}/export/markdown`)).text(), + { timeout: 10000 }, + ) + .toBe('export me please'); const exported = await context.request.get(`/api/v1/pages/${pageId}/export/markdown`); expect(exported.headers()['content-disposition']).toContain(`${pageSlug}.md`); diff --git a/apps/web/e2e/trash.spec.ts b/apps/web/e2e/trash.spec.ts index 3aa7102..ab230c2 100644 --- a/apps/web/e2e/trash.spec.ts +++ b/apps/web/e2e/trash.spec.ts @@ -43,7 +43,9 @@ test('deleting a page hides it from the sidebar and shows a trash hint on direct await page.goto(`/p/${pondSlug}/${pageSlug}`); await page.getByRole('button', { name: /edit|bearbeiten/i }).click(); - await page.getByRole('button', { name: /move to trash|papierkorb verschieben/i }).click(); + // Delete sits behind the TopBar overflow menu since #101. + await page.getByRole('button', { name: /more actions|weitere aktionen/i }).click(); + await page.getByRole('menuitem', { name: /move to trash|papierkorb verschieben/i }).click(); // Deleting navigates away; going back to the same URL now 404s with a hint. await page.goto(`/p/${pondSlug}/${pageSlug}`); @@ -64,7 +66,9 @@ test('restoring a page from the trash brings it back', async ({ browser }) => { await page.goto(`/p/${pondSlug}/${pageSlug}`); await page.getByRole('button', { name: /edit|bearbeiten/i }).click(); - await page.getByRole('button', { name: /move to trash|papierkorb verschieben/i }).click(); + // Delete sits behind the TopBar overflow menu since #101. + await page.getByRole('button', { name: /more actions|weitere aktionen/i }).click(); + await page.getByRole('menuitem', { name: /move to trash|papierkorb verschieben/i }).click(); await page.goto(`/p/${pondSlug}/trash`); const item = trashItem(page, title); @@ -87,7 +91,9 @@ test('purging a page from the trash removes it for good', async ({ browser }) => await page.goto(`/p/${pondSlug}/${pageSlug}`); await page.getByRole('button', { name: /edit|bearbeiten/i }).click(); - await page.getByRole('button', { name: /move to trash|papierkorb verschieben/i }).click(); + // Delete sits behind the TopBar overflow menu since #101. + await page.getByRole('button', { name: /more actions|weitere aktionen/i }).click(); + await page.getByRole('menuitem', { name: /move to trash|papierkorb verschieben/i }).click(); await page.goto(`/p/${pondSlug}/trash`); const item = trashItem(page, title); diff --git a/apps/web/src/layout/Sidebar.tsx b/apps/web/src/layout/Sidebar.tsx index 92b2132..3bc62d8 100644 --- a/apps/web/src/layout/Sidebar.tsx +++ b/apps/web/src/layout/Sidebar.tsx @@ -126,16 +126,6 @@ export function Sidebar({ collapsed }: SidebarProps): React.JSX.Element { ))} )} - {isOwner && ( - - {tLabels('link')} - - )} - {isOwner && ( - - {t('editor:trash.link')} - - )} {flat.length > 0 && ( @@ -282,6 +272,16 @@ export function Sidebar({ collapsed }: SidebarProps): React.JSX.Element {

{announcement}

+ + {/* The trash stays a text link, pinned to the sidebar's bottom + (M10 follow-up; pond settings moved to the TopBar gear icon). */} + {isOwner && ( +
+ + {t('editor:trash.link')} + +
+ )} )} diff --git a/apps/web/src/layout/TopBar.tsx b/apps/web/src/layout/TopBar.tsx index 9b48dd1..2c4a7af 100644 --- a/apps/web/src/layout/TopBar.tsx +++ b/apps/web/src/layout/TopBar.tsx @@ -1,13 +1,17 @@ -import { Menu, Search } from 'lucide-react'; +import type { PondView } from '@dorfteich/shared'; +import { useQuery } from '@tanstack/react-query'; +import { Menu, Search, Settings } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../auth/auth-context'; +import { apiGet } from '../lib/api'; import { SearchPalette } from '../search/SearchPalette'; import { NotificationsBell } from '../notifications/NotificationsBell'; import { usePageActionsSlot } from './page-actions'; import { PondSwitcher } from './PondSwitcher'; +import { useCurrentPondRoute } from './use-pond-route'; /** True when focus is in a field where "/" should type, not open search. */ function isTypingTarget(target: EventTarget | null): boolean { @@ -28,6 +32,15 @@ export function TopBar({ sidebarCollapsed, onToggleSidebar }: TopBarProps): Reac const [menuOpen, setMenuOpen] = useState(false); const [searchOpen, setSearchOpen] = useState(false); const { setElement: setPageActionsElement, setPresenceElement } = usePageActionsSlot(); + // Pond-settings shortcut next to the pond name (M10 follow-up): owners get + // a gear icon while a pond route is active. Shares the sidebar's query key. + const { pondSlug } = useCurrentPondRoute(); + const pond = useQuery({ + queryKey: ['pond', pondSlug], + queryFn: () => apiGet(`/ponds/${pondSlug}`), + enabled: Boolean(user && pondSlug), + }); + const isPondOwner = Boolean(user && pond.data && user.id === pond.data.ownerId); async function handleLogout(): Promise { setMenuOpen(false); @@ -63,6 +76,16 @@ export function TopBar({ sidebarCollapsed, onToggleSidebar }: TopBarProps): Reac Dorfteich {user && } + {isPondOwner && pondSlug && ( + + + + )} {/* Page-scoped slots, rendered only for signed-in users: live presence (#102) and the page's icon actions (#101) portal in while a page diff --git a/apps/web/src/styles/base.css b/apps/web/src/styles/base.css index 7d0d3f7..c7882ea 100644 --- a/apps/web/src/styles/base.css +++ b/apps/web/src/styles/base.css @@ -88,6 +88,9 @@ button { .sidebar { width: var(--sidebar-width); flex-shrink: 0; + /* Column flex so the trash footer can pin to the bottom (margin-top: auto). */ + display: flex; + flex-direction: column; border-right: 1px solid var(--color-border); background: var(--color-bg-subtle); padding: var(--space-4); @@ -176,6 +179,9 @@ button { .sidebar__new-page { font-size: 0.9rem; + /* The sidebar is a flex column now; keep the stretched button reading + like the left-aligned link it used to be. */ + text-align: left; } .sidebar__new-page-form { @@ -1447,8 +1453,9 @@ button { cursor: pointer; } -.sidebar__settings-link { - font-size: 0.85rem; +.sidebar__footer { + margin-top: auto; + padding-top: var(--space-4); } /* Pond settings page + label manager. */