feat(feedback): "Idee teilen" lebt jetzt im PillNav-Usermenü

Ersetzt den schwebenden "Idee?"-Pill durch einen Eintrag im rechten
Usermenü (Profil / Credits / Idee teilen / Logout). Ein Affordance an
einer Stelle statt zwei nebeneinander.

- PillNavigation: neuer onFeedback-Prop + Lightbulb-Icon. Wenn gesetzt,
  ersetzt der Eintrag den Legacy-/feedback-Link in accountLinks und
  taucht zusätzlich oben in den userMenuBarItems (barMode) auf.
- UserMenuPanel: AccountLink kennt jetzt onClick? als Alternative zu
  href? — Action-Chips schließen das Panel direkt nach dem Klick.
- (app)/+layout: GlobalFeedbackPill-Mount entfernt, FeedbackQuickModal
  wird state-gebunden gerendert (moduleContext aus Pfad/?app= abgeleitet
  wie bisher in der alten Pill).
- GlobalFeedbackPill.svelte gelöscht — niemand referenziert sie mehr.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-28 15:12:27 +02:00
parent eaa1d7432b
commit 94d3277e2e
4 changed files with 65 additions and 109 deletions

View file

@ -1,100 +0,0 @@
<!--
GlobalFeedbackPill — fallback feedback affordance for routes outside
ModuleShell (settings, profile, dashboards). Sits bottom-right, tucked
above the bottom-stack chrome.
Auto-detects module-context from the URL (e.g. `/todo` → `todo`,
`/?app=notes` → `notes`); otherwise leaves moduleContext undefined.
Hides itself on /onboarding and on /feedback + /community pages where
the affordance would be redundant.
-->
<script lang="ts">
import { page } from '$app/stores';
import { Lightbulb } from '@mana/shared-icons';
import { authStore } from '$lib/stores/auth.svelte';
import FeedbackQuickModal from './FeedbackQuickModal.svelte';
let open = $state(false);
let path = $derived($page.url.pathname);
let activeAppParam = $derived($page.url.searchParams.get('app'));
let hidden = $derived(
path.startsWith('/onboarding') ||
path.startsWith('/feedback') ||
path.startsWith('/community') ||
!authStore.user
);
let moduleContext = $derived.by(() => {
// Path-based detection: /todo, /notes, /picture, …
const seg = path.split('/').filter(Boolean)[0];
const fromPath = seg && !seg.startsWith('(') ? seg : null;
// Workbench `?app=` param wins (homepage scene mode).
return activeAppParam ?? fromPath ?? undefined;
});
function handleClick() {
open = true;
}
</script>
{#if !hidden}
<button
type="button"
class="pill"
onclick={handleClick}
title="Idee oder Feedback?"
aria-label="Feedback geben"
>
<Lightbulb size={18} weight="bold" />
<span class="label">Idee?</span>
</button>
<FeedbackQuickModal {open} {moduleContext} onClose={() => (open = false)} />
{/if}
<style>
.pill {
position: fixed;
right: 1rem;
bottom: calc(var(--bottom-chrome-height, 5rem) + 1rem);
z-index: 50;
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 0.875rem;
border: 1px solid hsl(var(--color-border));
background: hsl(var(--color-card));
color: hsl(var(--color-foreground));
border-radius: 999px;
font-size: 0.8125rem;
font-weight: 600;
cursor: pointer;
box-shadow:
0 6px 16px hsl(0 0% 0% / 0.12),
0 2px 6px hsl(0 0% 0% / 0.08);
transition:
transform 0.15s ease,
box-shadow 0.15s ease,
border-color 0.15s ease;
}
.pill:hover {
transform: translateY(-1px);
border-color: hsl(var(--color-primary) / 0.5);
box-shadow:
0 10px 22px hsl(0 0% 0% / 0.16),
0 3px 8px hsl(0 0% 0% / 0.1);
color: hsl(var(--color-primary));
}
@media (max-width: 480px) {
.pill .label {
display: none;
}
.pill {
padding: 0.5rem;
}
}
</style>

View file

@ -9,7 +9,7 @@
import { page } from '$app/stores';
import type { Component, Snippet } from 'svelte';
import ToastContainer from '$lib/components/ToastContainer.svelte';
import GlobalFeedbackPill from '$lib/components/feedback/GlobalFeedbackPill.svelte';
import FeedbackQuickModal from '$lib/components/feedback/FeedbackQuickModal.svelte';
import { onDestroy, setContext, tick } from 'svelte';
import { createReminderScheduler } from '@mana/shared-stores';
import { todoReminderSource } from '$lib/modules/todo/reminder-source';
@ -251,6 +251,17 @@
let isBottomBarVisible = $state(false);
let activeBar = $state<PillBarConfig | null>(null);
// Quick-feedback modal — opened from the user-menu chip ("Idee teilen").
// Replaces the older floating "Idee?" pill so feedback lives in the same
// affordance as Profile / Credits / Logout.
let feedbackModalOpen = $state(false);
let feedbackModuleContext = $derived.by(() => {
const path = $page.url.pathname;
const seg = path.split('/').filter(Boolean)[0];
const fromPath = seg && !seg.startsWith('(') ? seg : null;
return $page.url.searchParams.get('app') ?? fromPath ?? undefined;
});
function closeAllBars() {
isTagStripVisible = false;
isQuickInputVisible = false;
@ -1061,6 +1072,7 @@
creditsHref="/?app=credits"
themesHref="/?app=themes"
helpHref="/?app=help"
onFeedback={() => (feedbackModalOpen = true)}
{spotlightActions}
{contentSearcher}
positioning="static"
@ -1139,9 +1151,12 @@
/>
{/if}
<!-- Global "Idee?" feedback pill — self-hides on /onboarding,
/feedback, /community, and for unauthenticated users. -->
<GlobalFeedbackPill />
<!-- Quick-feedback modal — opened from the user-menu chip. -->
<FeedbackQuickModal
open={feedbackModalOpen}
moduleContext={feedbackModuleContext}
onClose={() => (feedbackModalOpen = false)}
/>
</AuthGate>
<ToastContainer />