feat(workbench): scene description field, drop scene icon

Schema + store preparation for the new scene header on the homepage
(next commit). Scenes get an optional free-text description stamped
as its own field — LWW through the existing mana-sync pipeline, no
new sync contract. The unused icon field is removed everywhere:

- types/workbench-scenes.ts: description?: string | null replaces icon
- stores/workbench-scenes.svelte.ts: createScene, renameScene,
  duplicateScene, toScene, patchScene all updated. New method
  setSceneDescription(id, value) mirrors renameScene so the caller
  can change just the description without re-submitting the name.
- components/workbench/scenes/SceneTabs.svelte: the tab-bar rendered
  {#if scene.icon} before the name — scene.name is unique enough to
  identify a scene, and the UI direction is away from emoji chrome.

Existing scene rows in Dexie simply omit description (undefined → null
on read); the icon field on old rows is ignored and will age out of
the schema the next time the row is written.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-15 19:41:50 +02:00
parent 0da74587ce
commit 714c235798
3 changed files with 18 additions and 16 deletions

View file

@ -113,9 +113,6 @@
oncontextmenu={(e) => openMenu(e, scene)}
title={scene.name}
>
{#if scene.icon}
<span class="scene-icon">{scene.icon}</span>
{/if}
<span class="scene-name">{scene.name}</span>
</button>
{/each}
@ -182,10 +179,6 @@
.scene-pill.dragging {
opacity: 0.4;
}
.scene-icon {
font-size: 0.95rem;
line-height: 1;
}
.scene-name {
overflow: hidden;
text-overflow: ellipsis;

View file

@ -62,7 +62,7 @@ function toScene(local: LocalWorkbenchScene): WorkbenchScene {
return {
id: local.id,
name: local.name,
icon: local.icon,
description: local.description ?? null,
openApps: local.openApps ?? [],
order: local.order,
wallpaper: local.wallpaper,
@ -98,7 +98,9 @@ function pickActiveId(scenes: WorkbenchScene[], current: string | null): string
async function patchScene(
id: string,
patch: Partial<Pick<LocalWorkbenchScene, 'name' | 'icon' | 'openApps' | 'order' | 'wallpaper'>>
patch: Partial<
Pick<LocalWorkbenchScene, 'name' | 'description' | 'openApps' | 'order' | 'wallpaper'>
>
) {
// Strip Svelte 5 $state proxies — IndexedDB's structured clone can't serialize them.
const clean = $state.snapshot({ ...patch, updatedAt: nowIso() });
@ -182,7 +184,7 @@ export const workbenchScenesStore = {
async createScene(opts: {
name: string;
icon?: string;
description?: string | null;
seedApps?: WorkbenchSceneApp[];
setActive?: boolean;
}): Promise<string> {
@ -192,7 +194,7 @@ export const workbenchScenesStore = {
const row: LocalWorkbenchScene = {
id,
name: opts.name.trim() || 'Neue Szene',
icon: opts.icon,
description: opts.description ?? null,
openApps: opts.seedApps ? ($state.snapshot(opts.seedApps) as WorkbenchSceneApp[]) : [],
order: maxOrder + 1,
createdAt: now,
@ -206,10 +208,17 @@ export const workbenchScenesStore = {
return id;
},
async renameScene(id: string, name: string, icon?: string) {
async renameScene(id: string, name: string, description?: string | null) {
const trimmed = name.trim();
if (!trimmed) return;
await patchScene(id, { name: trimmed, ...(icon !== undefined ? { icon } : {}) });
await patchScene(id, {
name: trimmed,
...(description !== undefined ? { description } : {}),
});
},
async setSceneDescription(id: string, description: string | null) {
await patchScene(id, { description });
},
async duplicateScene(id: string) {
@ -217,7 +226,7 @@ export const workbenchScenesStore = {
if (!src) return;
await this.createScene({
name: `${src.name} Kopie`,
icon: src.icon,
description: src.description ?? null,
seedApps: src.openApps,
setActive: true,
});

View file

@ -25,8 +25,8 @@ export interface WorkbenchSceneApp {
export interface WorkbenchScene {
id: string;
name: string;
/** Optional emoji shown in the scene tab. */
icon?: string;
/** Short free-text description shown under the name in the scene header. */
description?: string | null;
openApps: WorkbenchSceneApp[];
/** Sort order in the scene tab bar. */
order: number;