polish(wardrobe): garment-detail cosmetic pass + slug-cleanup on upload

Four small UI tweaks that came out of reviewing the garment-detail
screenshot against the workbench chrome:

1. Duplicate "Kleiderschrank" label — the ModuleShell header above
   DetailGarmentView already renders a back-arrow and the app title.
   The inner `<nav>` with a second arrow + text was rendering it all
   a second time. Drop the inner breadcrumb; ArrowLeft import along
   with it.

2. Raw SKU-slug as default garment name — the old
   `stripExt(file.name)` produced labels like
   `17390-gestreiftes-herren-t-shirt-aus-baumwolle-17390-2-w`. New
   `prettifyUploadName` helper:
   - drops the extension
   - replaces `-`/`_` with spaces
   - strips pure-digit tokens of length ≥ 4 (SKU shape) but keeps
     short alphanumerics like `4xl` / `w38`
   - title-cases each remaining word, rebuilding hyphens
     (`t-shirt` → `T-Shirt`, `v-neck` → `V-Neck`)
   - clamps to 80 chars on a word boundary
   GridView's ingestFiles now passes the prettified name into the
   createGarment write. User still edits on the detail page for
   anything that needs nuance.

3. Two-line CTA with Credits subtitle. The button used to read
   `Anprobieren · 10 Credits` on one line; on a narrow workbench
   card the mittelpunkt between label and cost was visually thin
   and read like a strikethrough. Split into a main label + small
   opacity-75 subtitle so the credit figure is clearly secondary
   info, not a decorated part of the CTA text. Applied to both
   GarmentTryOnButton and TryOnButton.

4. Redundant microcopy under section headers — "Einzelstück auf dir
   gerendert" under ANPROBEN and "Komposition öffnen" under IN
   OUTFITS repeated what the section title and the clickable cards
   already signalled. Remove both.

No behaviour changes, no schema, no API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-24 16:24:07 +02:00
parent 87b567eec9
commit 05b2209232
7 changed files with 116 additions and 45 deletions

View file

@ -159,11 +159,7 @@ export const comicListStories: ToolSpec<typeof listStoriesInput, typeof listStor
const decrypted = (await Promise.all(
alive.map((row) =>
decryptRecordFields(
row as unknown as Record<string, unknown>,
STORY_ENCRYPTED_FIELDS,
key
)
decryptRecordFields(row as unknown as Record<string, unknown>, STORY_ENCRYPTED_FIELDS, key)
)
)) as unknown as RawStoryRow[];
@ -223,7 +219,7 @@ export const comicCreateStory: ToolSpec<typeof createStoryInput, typeof createSt
scope: 'user-space',
policyHint: 'write',
description:
"Start a new comic story in the active space. The style and character references are fixed once written — every future `generatePanel` call against this story uses the same refs + style-prefix. Start with 18 `characterMediaIds` (face-ref at index 0, body-ref optional, up to 3 garment-ref photos from wardrobe). Returns the empty story; add panels via `comic.generatePanel`.",
'Start a new comic story in the active space. The style and character references are fixed once written — every future `generatePanel` call against this story uses the same refs + style-prefix. Start with 18 `characterMediaIds` (face-ref at index 0, body-ref optional, up to 3 garment-ref photos from wardrobe). Returns the empty story; add panels via `comic.generatePanel`.',
input: createStoryInput,
output: createStoryOutput,
encryptedFields: { table: STORIES_TABLE, fields: [...STORY_ENCRYPTED_FIELDS] },
@ -508,10 +504,7 @@ export const comicReorderPanels: ToolSpec<typeof reorderPanelsInput, typeof reor
const raw = storiesRes.changes
.filter((c) => c.op !== 'delete' && c.data)
.map((c) => c.data as RawStoryRow)
.find(
(row) =>
row.id === input.storyId && !row.deletedAt && row.spaceId === ctx.spaceId
);
.find((row) => row.id === input.storyId && !row.deletedAt && row.spaceId === ctx.spaceId);
if (!raw) {
throw new Error(`Comic story ${input.storyId} not found in the active space`);
}