feat(workbench): ?app=<appId> deep-links into the active scene

After consolidating Settings into a workbench app, links from the
command menu, pill-nav, AI-tier dropdown, onboarding CTAs, and the
sub-route back-buttons had nowhere specific to go — they pointed at
`/` and dumped users at their home scene with no way to auto-open the
settings app.

- Home (+page.svelte) reads ?app=<id> on mount. If the id is a
  registered workbench app and not already in the active scene,
  addApp() it. Then scroll the carousel to that page. Strip the
  query out of history.replaceState so refresh doesn't re-open.
- Update all settings redirects (command menu, onboarding,
  AI-settings dropdown, settings/sync + settings/my-data back-links
  and breadcrumbs) to `/?app=settings`.

Unlocks: future deep-links to any app (profile, themes, spiral,
credits, …) without needing a standalone route. `?app=X&section=Y`
is the natural next step — left for when an app needs it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-16 01:10:10 +02:00
parent a08e45ca16
commit 4f76d3926e
6 changed files with 26 additions and 10 deletions

View file

@ -184,7 +184,7 @@ export function useAiTierItems() {
id: 'ai-settings',
label: 'KI-Einstellungen',
icon: 'settings',
onClick: () => goto('/'),
onClick: () => goto('/?app=settings'),
},
]);

View file

@ -84,7 +84,7 @@
</a>
<a
href="/"
href="/?app=settings"
class="p-4 rounded-xl bg-card border hover:border-primary/50 hover:shadow-md transition-all group"
>
<div class="flex items-center gap-3">

View file

@ -746,7 +746,7 @@
id: 'settings',
label: 'Einstellungen',
category: 'Navigation',
onExecute: () => goto('/'),
onExecute: () => goto('/?app=settings'),
},
];
</script>

View file

@ -16,6 +16,8 @@
import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui';
import { Pencil, Copy, Trash, Image, Sparkle } from '@mana/shared-icons';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { tick } from 'svelte';
import { _, locale } from 'svelte-i18n';
import { buildContextMenuItems, createWorkbenchContextMenu } from '$lib/context-menu';
import type { WorkbenchScene } from '$lib/types/workbench-scenes';
@ -61,8 +63,24 @@
});
// ── Scene store wiring ──────────────────────────────────
onMount(() => {
workbenchScenesStore.initialize();
onMount(async () => {
await workbenchScenesStore.initialize();
// Deep-link: `/?app=settings` (or any registered appId) opens the
// app in the active scene (or focuses it if already open) and
// scrolls it into view. Used by command menu, pill-nav settings
// link, onboarding CTAs, sync-status banner — anywhere we used
// to navigate to `/settings` before the route was removed.
const target = $page.url.searchParams.get('app');
if (target && getApp(target)) {
const already = workbenchScenesStore.openApps.find((a) => a.appId === target);
if (!already) await workbenchScenesStore.addApp(target);
await tick();
scrollToPage(target);
// Clean the query out of the URL so refresh doesn't re-trigger.
const clean = new URL($page.url);
clean.searchParams.delete('app');
history.replaceState({}, '', clean);
}
});
onDestroy(() => {
workbenchScenesStore.dispose();

View file

@ -186,7 +186,7 @@
<div class="flex items-center justify-between gap-4">
<div>
<Breadcrumbs
items={[{ label: 'Home', href: '/' }, { label: 'Meine Daten' }]}
items={[{ label: 'Einstellungen', href: '/?app=settings' }, { label: 'Meine Daten' }]}
/>
<h1 class="text-2xl font-bold">Meine Daten</h1>
<p class="text-muted-foreground">

View file

@ -106,7 +106,7 @@
</script>
<div>
<PageHeader title="Cloud Sync" backHref="/" sticky size="lg" />
<PageHeader title="Cloud Sync" backHref="/?app=settings" sticky size="lg" />
{#if syncBilling.loading}
<div class="flex items-center justify-center py-12">
@ -268,9 +268,7 @@
<!-- Back link -->
<div class="mt-6">
<a href="/" class="text-sm text-primary hover:underline">
← Zurück
</a>
<a href="/?app=settings" class="text-sm text-primary hover:underline"> ← Zurück </a>
</div>
{/if}
</div>