Workspace has no 'Save as new' affordance — user wants to fork a loaded design into a new file with a different name, but only the editor has Save as New (and only when onSaveAsNew is wired) #250

Closed
opened 2026-10-04 03:34:19 +00:00 by Mark · 1 comment
Owner

Finding

The editor's Save button has a "Save as New…" option in the dropdown (per #187 fix), but the workspace's save button does not.

grep -n 'saveAsNew' src/components/workspace/ returns 0. The workspace's save path only has handleSave (overwrite in place). If the user loaded design "flower.pes" and wants to create "flower_v2.pes" as a separate file, they have to:

  • Edit the filename in some input (not present in the right panel)
  • Or save and then rename the gallery card

grep -n 'Save As\|Save as\|saveAs' src/components/workspace/ returns 0.

Where

  • Workspace AppHeader: src/components/workspace/AppHeader.tsx (Save button)
  • Workspace save: src/components/workspace/useWorkspace.ts:handleSave (single save path)
  • No saveAsNew: grep -n 'saveAsNew' src/components/workspace/ returns 0

Suggested fix shape

Add a "Save as New…" option to the workspace's Save button (split-button pattern, same as the editor's per #187):

<div className="flex items-stretch" data-testid="workspace-save-group">
  <Button onClick={onSave} disabled={!canSave}>Save</Button>
  {canSave && (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button>...<ChevronDown /></Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem onClick={onSave}>Save (update existing)</DropdownMenuItem>
        <DropdownMenuItem onClick={onSaveAsNew}>Save as New…</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )}
</div>

onSaveAsNew opens a small modal: "Save as New. What should the new design be named?" with a text input pre-filled to <current name> copy. On confirm, the workspace calls saveDesignToGallery with loadedDesignId: null (force a new row) and the new filename.

This mirrors the editor's #187 implementation. The user can fork the loaded design in either the workspace or the editor.

Severity

Low. The user can save + rename. The fork-in-place is a polish improvement.

Discovered by

Gap analysis (2026-10-02) of workspace save affordances.

  • #187 (open) — Save as New. The editor has it; the workspace doesn't.
  • The Gallery's rename (per DesignCard.tsx:531) is the post-save rename fallback.
  • The fork-then-edit workflow is the use case: user wants to try "what if I change the density" without losing the original.
## Finding The editor's Save button has a "Save as New…" option in the dropdown (per #187 fix), but the workspace's save button does not. `grep -n 'saveAsNew' src/components/workspace/` returns 0. The workspace's save path only has `handleSave` (overwrite in place). If the user loaded design "flower.pes" and wants to create "flower_v2.pes" as a separate file, they have to: - Edit the filename in some input (not present in the right panel) - Or save and then rename the gallery card `grep -n 'Save As\|Save as\|saveAs' src/components/workspace/` returns 0. ## Where - Workspace AppHeader: `src/components/workspace/AppHeader.tsx` (Save button) - Workspace save: `src/components/workspace/useWorkspace.ts:handleSave` (single save path) - No saveAsNew: `grep -n 'saveAsNew' src/components/workspace/` returns 0 ## Suggested fix shape Add a "Save as New…" option to the workspace's Save button (split-button pattern, same as the editor's per #187): ```tsx <div className="flex items-stretch" data-testid="workspace-save-group"> <Button onClick={onSave} disabled={!canSave}>Save</Button> {canSave && ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button>...<ChevronDown /></Button> </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem onClick={onSave}>Save (update existing)</DropdownMenuItem> <DropdownMenuItem onClick={onSaveAsNew}>Save as New…</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> )} </div> ``` `onSaveAsNew` opens a small modal: "Save as New. What should the new design be named?" with a text input pre-filled to `<current name> copy`. On confirm, the workspace calls `saveDesignToGallery` with `loadedDesignId: null` (force a new row) and the new filename. This mirrors the editor's #187 implementation. The user can fork the loaded design in either the workspace or the editor. ## Severity Low. The user can save + rename. The fork-in-place is a polish improvement. ## Discovered by Gap analysis (2026-10-02) of workspace save affordances. ## Related - #187 (open) — Save as New. The editor has it; the workspace doesn't. - The Gallery's rename (per `DesignCard.tsx:531`) is the post-save rename fallback. - The fork-then-edit workflow is the use case: user wants to try "what if I change the density" without losing the original.
Author
Owner

Shipped in commit 0c3a4b1 (fix(#250)).

What landed:

  • Workspace AppHeader Save button is now a split-button (Save + caret) when the parent supplies onSaveAsNew. The caret opens a dropdown with "Save (update existing)" and "Save as New…". Back-compat preserved: when onSaveAsNew is omitted, the original single Save button renders with no wrapper.
  • New SaveAsNewDialog component (Radix Dialog) prompts for the new row's filename, pre-filling to <current> copy and refocusing the input on each open.
  • useWorkspace.saveCurrentDesignAsNew forks the design via saveDesignToGallery with loadedDesignId: null so the helper takes the "create fresh row" branch (#152) instead of updating the loaded row in place. Binding is cleared after the save so a follow-up Save click in the same session keeps creating new rows.
  • Visual: matches the workspace's existing gray-on-dark button palette (rounded-l/r border radii for the split-button border to line up).

Tests: 502 passed (5 new AppHeader tests; full suite +5 vs prior baseline). Lint + tsc clean.

Use case: user loads "flower.pes" -> clicks Save caret -> "Save as New…" -> types "flower_v2.pes" -> confirms -> fresh gallery row appears, original untouched.

Shipped in commit 0c3a4b1 (fix(#250)). **What landed:** - Workspace AppHeader Save button is now a split-button (Save + caret) when the parent supplies onSaveAsNew. The caret opens a dropdown with "Save (update existing)" and "Save as New…". Back-compat preserved: when onSaveAsNew is omitted, the original single Save button renders with no wrapper. - New SaveAsNewDialog component (Radix Dialog) prompts for the new row's filename, pre-filling to `<current> copy` and refocusing the input on each open. - useWorkspace.saveCurrentDesignAsNew forks the design via saveDesignToGallery with `loadedDesignId: null` so the helper takes the "create fresh row" branch (#152) instead of updating the loaded row in place. Binding is cleared after the save so a follow-up Save click in the same session keeps creating new rows. - Visual: matches the workspace's existing gray-on-dark button palette (rounded-l/r border radii for the split-button border to line up). **Tests:** 502 passed (5 new AppHeader tests; full suite +5 vs prior baseline). Lint + tsc clean. **Use case:** user loads "flower.pes" -> clicks Save caret -> "Save as New…" -> types "flower_v2.pes" -> confirms -> fresh gallery row appears, original untouched.
Mark closed this issue 2026-10-04 09:42:39 +00:00
Sign in to join this conversation.
No description provided.