Skip to content

feat(ui): optional auto-save with manual save, Ctrl+S and unsaved-changes indicator - #227

Open
tomasmorello wants to merge 4 commits into
RA341:mainfrom
tomasmorello:feat/auto-save-manual-save
Open

feat(ui): optional auto-save with manual save, Ctrl+S and unsaved-changes indicator#227
tomasmorello wants to merge 4 commits into
RA341:mainfrom
tomasmorello:feat/auto-save-manual-save

Conversation

@tomasmorello

Copy link
Copy Markdown

Closes #226

What

Makes auto-save optional. Current auto-save-on-every-keystroke stays the default;
users can now switch to manual saving to avoid persisting half-finished edits.

Changes

  • Auto-save toggle in the editor toolbar, persisted in localStorage (default: on).
  • Manual save: disk-icon button (enabled only when there are unsaved changes)
    and Ctrl/Cmd+S (also flushes the debounce when auto-save is on; prevents the
    browser save dialog).
  • Unsaved drafts survive tab switches: edits are kept in memory per file and
    restored when returning to the tab.
  • Unsaved indicator: amber dot on the left of the file in the tree, kept
    distinct from the existing docker status indicator on the right.
  • Undo/redo preserved across tab switches: each file now uses its own Monaco
    model (keepCurrentModel) instead of recreating the editor + setValue on every
    switch. As a side effect this also fixed a listener leak where
    onDidChangeContent handlers were re-registered without disposal.
  • Dirty state is tracked against the on-disk baseline, so reverting all edits
    (Ctrl+Z back to the original) correctly clears the "unsaved" state.

Design notes / limitations

  • Drafts are in-memory only: preserved across tab switches, lost on a full page
    reload
    (surfaced in the toggle's tooltip). Monaco has no public API to persist
    the undo stack, so this is by design.
  • With keepCurrentModel, models are not disposed while the app is open ‚Äî memory
    grows slightly per opened file until reload. Negligible for this use case;
    disposing on tab-close could be a follow-up.
  • Auto-save remains the default, so existing behaviour is unchanged unless the user
    opts out.

How to test

  1. Auto-save on (default): edit a file ‚Üí it saves after the debounce; the amber
    dot appears then clears on save.
  2. Auto-save off: edit ‚Üí "Unsaved" + amber dot; switch tabs and back ‚Üí edits are
    still there; save with the disk button or Ctrl+S ‚Üí indicator clears.
  3. Revert: edit, then Ctrl+Z back to the original ‚Üí "Unsaved" clears.
  4. Undo across tabs: edit file A, switch to B, back to A ‚Üí Ctrl+Z still undoes.
  5. Ctrl+S does not trigger the browser's save dialog.

Scope

UI only — all changes are under ui/src/pages/compose. No backend/proto changes.

Previously the editor always uploaded file contents 500ms after every
keystroke, making it easy to persist half-finished or broken configs.

- add a persisted auto-save flag (on by default, stored in localStorage)
  with a toggle switch in the editor toolbar
- add a diskette save button to the text editor toolbar and the
  dockman.yml viewer header, enabled only when there are unsaved changes
- add CTRL+S / CMD+S to save pending changes immediately; with auto-save
  enabled it flushes the debounce instead of waiting
- track per-file dirty state and show a new 'Unsaved' indicator; warn
  via beforeunload when closing the tab with unsaved changes
- fix a listener leak in MonacoEditor where onDidChangeContent handlers
  were re-registered on every reload without disposing the old ones
When auto-save is off, edits are now kept as an in-memory draft per file,
so switching between tabs no longer discards unsaved changes. The draft is
restored into the editor on return and cleared once the file is saved.

Files with unsaved changes are marked with an amber dot on the left of the
file tree, kept visually distinct from the docker status indicator on the
right.
Give each file its own monaco model (unique per host/alias context) and
keep it alive via keepCurrentModel, instead of recreating the editor and
calling setValue on every file switch. This preserves the full undo/redo
stack and unsaved content when moving between tabs.

Content is reloaded when the active filename changes, and the auto-save
tooltip now notes that unsaved changes survive tab switches but are lost
on a full page reload.
Dirty state was set on any content change and never cleared until save,
so undoing all edits (CTRL+Z back to the original) still showed the file
as unsaved. Track the on-disk content as a baseline and mark a file dirty
only when the editor content actually differs from it.
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

@tomasmorello is attempting to deploy a commit to the r334's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: make auto-save optional (manual save + Ctrl+S) with an unsaved-changes indicator

1 participant