diff --git a/apps/mana/apps/web/src/lib/components/workbench/scenes/SceneRenameDialog.svelte b/apps/mana/apps/web/src/lib/components/workbench/scenes/SceneRenameDialog.svelte
deleted file mode 100644
index 9981700a9..000000000
--- a/apps/mana/apps/web/src/lib/components/workbench/scenes/SceneRenameDialog.svelte
+++ /dev/null
@@ -1,208 +0,0 @@
-
-
-
-
-
-{#if show}
-
-
-
e.stopPropagation()}
- >
-
{title}
-
-
-
-{/if}
-
-
diff --git a/apps/mana/apps/web/src/routes/(app)/+page.svelte b/apps/mana/apps/web/src/routes/(app)/+page.svelte
index aec8d127b..e4b7c8284 100644
--- a/apps/mana/apps/web/src/routes/(app)/+page.svelte
+++ b/apps/mana/apps/web/src/routes/(app)/+page.svelte
@@ -2,7 +2,6 @@
import AppPage from '$lib/components/workbench/AppPage.svelte';
import AppPagePicker from '$lib/components/workbench/AppPagePicker.svelte';
import SceneAppBar from '$lib/components/workbench/SceneAppBar.svelte';
- import SceneRenameDialog from '$lib/components/workbench/scenes/SceneRenameDialog.svelte';
import SceneHeader from '$lib/components/workbench/scenes/SceneHeader.svelte';
import ConfirmDialog from '$lib/components/workbench/scenes/ConfirmDialog.svelte';
import { PageCarousel, type CarouselPage } from '$lib/components/page-carousel';
@@ -120,6 +119,32 @@
if (el) el.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
}
+ function scrollCarouselToStart() {
+ // Reset the horizontal scroll position so the SceneHeader (first
+ // item in the track) is visible — used after switching scenes so
+ // the user lands on the new scene's intro rather than wherever
+ // the previous scene was scrolled to.
+ const track = document.querySelector('.fokus-track');
+ track?.scrollTo({ left: 0, behavior: 'smooth' });
+ }
+
+ // ── Reset scroll on scene switch ────────────────────────
+ // Watches activeSceneId. On the first tick (initial mount) we skip
+ // the scroll because that's just the hydration pass — otherwise we
+ // would fight the carousel's own centre-the-first-card layout.
+ let lastSceneId: string | null = null;
+ $effect(() => {
+ const id = activeSceneId;
+ if (lastSceneId === null) {
+ lastSceneId = id;
+ return;
+ }
+ if (id !== lastSceneId) {
+ lastSceneId = id;
+ scrollCarouselToStart();
+ }
+ });
+
// ── Keyboard shortcuts 1-9 / 0 ─────────────────────────
// 1-9 scroll to the Nth open app in the active scene.
// 0 opens the new-app picker (which scrolls itself into view).
@@ -269,30 +294,25 @@
}
// ── Scene CRUD dialogs ──────────────────────────────────
- type SceneDialogMode = {
- kind: 'rename';
- id: string;
- name: string;
- description: string;
- };
- let sceneDialog = $state(null);
let sceneToDelete = $state<{ id: string; name: string } | null>(null);
function handleRequestRename(id: string) {
+ // Unified rename path: scroll the carousel to the scene header
+ // and focus its contenteditable . Previously opened a modal
+ // dialog that read from the store while the live header might
+ // already hold unsaved typing — the two paths could overwrite
+ // each other. Inline is the single source of truth now.
const scene = scenes.find((s) => s.id === id);
if (!scene) return;
- sceneDialog = {
- kind: 'rename',
- id,
- name: scene.name,
- description: scene.description ?? '',
- };
- }
- async function handleSubmitSceneDialog(name: string, description: string) {
- const mode = sceneDialog;
- if (!mode) return;
- await workbenchScenesStore.renameScene(mode.id, name, description.trim() || null);
- sceneDialog = null;
+ if (id !== activeSceneId) workbenchScenesStore.setActiveScene(id);
+ scrollCarouselToStart();
+ // Next tick: the header is mounted + the scroll has started;
+ // querying now lands on the active scene's h1 which
+ // contenteditable plaintext-only focuses cleanly.
+ setTimeout(() => {
+ const h1 = document.querySelector('.scene-header .scene-name[contenteditable]');
+ h1?.focus();
+ }, 120);
}
function handleDuplicateScene(id: string) {
workbenchScenesStore.duplicateScene(id);
@@ -358,16 +378,6 @@
onClose={() => ctxMenu.close()}
/>
- (sceneDialog = null)}
- />
-