Skip to content

Remote edit drafts: persistence, recovery, and a tabbed panel - #555

Draft
nschimme wants to merge 13 commits into
MUME:masterfrom
nschimme:remote-edit-draft-management-18138830753454044249
Draft

nschimme wants to merge 13 commits into
MUME:masterfrom
nschimme:remote-edit-draft-management-18138830753454044249

Conversation

@nschimme

@nschimme nschimme commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Persist MPI remote edits as auto-saved drafts (2s debounce / 15s throttle) so a crash or dropped connection never loses text; drafts survive restart via RemoteEditDraftStore (files natively, QSettings on WebAssembly).
  • Move RemoteEdit ownership from Proxy to MainWindow, decoupled via GMCP signals; remove MpiFilter.
  • Replace per-session editor windows with a "Remote Edits Panel" dock hosting tabs; editor shortcuts are scoped per page so they don't fight MainWindow's.
  • Unsent drafts are listed in the panel (View / Discard), announced once on connect, and restored in place: a new edit whose title matches a pending draft offers a restore banner (internal editor) or a prompt before launch (external editor).
  • Add _edits command to list/cancel/discard sessions from the terminal.

Test plan

  • Edit from MUME, type, disconnect: in-tab banner appears, Submit disabled, draft kept.
  • Reconnect and re-run the same edit: restore banner offers the draft; Restore replaces text and Submit succeeds.
  • Same with external editor enabled: prompt before launch seeds the file with the chosen text.
  • Cancel an unchanged edit: no draft left behind. Submit success deletes the draft.
  • Ctrl+S in a tab submits; Ctrl+S on the map saves the map; no "Ambiguous shortcut" warnings.
  • WASM: draft survives a page reload.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S5DQbj1bGLZZLvDvbb9AsG

Summary by Sourcery

Preserve remote edit work across failures and restarts while consolidating editing and recovery into a tabbed panel.

New Features:

  • Persist unsent remote edit drafts across disconnects and application restarts, with native file storage and WebAssembly settings storage.
  • Provide a dockable, tabbed Remote Edits Panel for active editors, viewers, external sessions, and recovered drafts.
  • Add draft recovery flows for internal and external editors, including disconnect status, restore offers, and explicit discard actions.
  • Add the _edits command for listing, inspecting, cancelling, discarding, and simulating remote edit sessions.

Bug Fixes:

  • Prevent remote edit content from being lost when connections drop, editors close, or the application shuts down.
  • Ensure successful submissions remove their saved drafts while failed submissions retain them for recovery.
  • Avoid shortcut conflicts between remote edit pages and MainWindow actions.

Enhancements:

  • Move remote edit ownership into MainWindow and route GMCP communication through signals instead of the proxy’s MPI filter.
  • Add debounced and throttled automatic draft saving for internal editors and preserve external editor files for recovery.

Chores:

  • Remove the obsolete MpiFilter and its proxy integration.

nschimme and others added 9 commits September 13, 2026 14:21
- Consolidate MPI session and draft management into RemoteEdit
- Decouple Proxy from RemoteEdit using signals/slots
- Move RemoteEdit ownership to MainWindow
- Implement draft file persistence in MMapper/Editor/
- Implement atomic auto-save with debounce (2s) and throttle (15s)
- Implement draft recovery scan at startup
- Integrate RemoteEdit tasks with TasksPanel and AsyncTask system
- Add 'Show Editor' functionality for active and recovered tasks
- Centralize MPI session and draft management in RemoteEdit
- MainWindow now owns the long-lived RemoteEdit instance
- Decouple Proxy from RemoteEdit via direct GMCP subscription in RemoteEdit
- Mark all active and recovered edits as AsyncTasks for unified management
- Implement draft persistence in MMapper/Editor directory
- Implement atomic auto-save with debounce (2s) and throttle (15s)
- Add draft recovery scan at startup and immediately after disconnects
- Integrate 'Show Editor' in TasksPanel to raise windows or open read-only drafts
- Ensure drafts are purged only upon confirmed write/cancel from server
- Handle clean and unclean disconnects to preserve draft state
- Establish MMapper/Editor/ scratch directory for draft persistence.
- Centralize RemoteEdit management in MainWindow and decouple from Proxy via GMCP signals.
- Implement atomic auto-save with 2000ms debounce and 15000ms throttle.
- Add startup recovery sequence to scan for orphaned draft files.
- Ensure thread safety in background tasks using weak_ptr for session access.
- Preserve draft files across disconnections until explicit server confirmation.
- Update TasksPanel with "Show Editor" functionality for active and recovered drafts.
- Enhance UX for external editor tasks with status notifications.
- Add virtual isRunning() to RemoteEditSession base class.
- Override isRunning() in RemoteEditExternalSession to check process state.
- Use virtual isRunning() in RemoteEdit::raiseSession to avoid conditional compilation issues with external sessions.
- Fix variable shadowing in slot_parseGmcpInput by renaming local error message variable.
- Ensure consistent behavior across all platforms including Wasm and Snap.
- Establish MMapper/Editor/ scratch directory for draft persistence.
- Centralize RemoteEdit management in MainWindow and decouple from Proxy via GMCP signals.
- Implement atomic auto-save with 2000ms debounce and 15000ms throttle.
- Add startup recovery sequence to scan for orphaned draft files.
- Ensure thread safety in background tasks using weak_ptr for session access.
- Preserve draft files across disconnections until explicit server confirmation.
- Transition disconnected closed sessions to recovered state in the task list.
- Simplified UX: Removed 'raise' feature and clipboard copy on disconnect.
MudTelnet intercepted MUME.Client.Edit/View/Write/CancelEdit GMCP
messages and routed them through a legacy Proxy/MpiFilter shim whose
connection to RemoteEdit was dropped in an earlier refactor, so edit
requests never reached RemoteEdit::slot_remoteEdit. Let these messages
flow through the normal GMCP relay instead, which RemoteEdit already
listens to directly (the same pattern every other GMCP consumer uses),
and remove the now fully dead MpiFilter/MpiFilterToMud plumbing. The
"MMapper is opening an Editor/Viewer window" notice moves into
RemoteEdit::addSession to preserve the existing user-facing output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcHMW6qCb9YobxuXvDSXuU
Cancelling an active edit task (from the Tasks panel, or implicitly on
app quit via cancel_all()) called ProgressCounter::setCurrentTask()
directly on the main thread after cancellation had already been
requested on that task's handle. Every other use of this cooperative
cancellation mechanism throws only from inside a background-worker
lambda, caught at the corresponding std::future::get() -- these
RemoteEdit call sites had neither, so the exception escaped to
std::terminate(). Guard the four affected call sites (cancel(),
sendToMume(), trySaveLocally(), onDisconnected()) with a
try/catch that treats an already-cancelled task as a no-op.

Also fix RemoteEditExternalSession never receiving its draft file
path: RemoteEdit::addSession() called setDraftFileName() only after
the session was already constructed, so RemoteEditProcess always saw
an empty path and fell back to an unrelated temp file, breaking
crash-recovery for external-editor sessions and leaking that temp
file. Provision the draft file name before constructing the session
and pass it into the constructor instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcHMW6qCb9YobxuXvDSXuU
Cancelling a connected edit now sends the GMCP cancel and deletes its
draft, so abandoned edits no longer resurrect as recovered drafts on
every launch; disconnected edits and recovery windows keep their draft.
A failed MUME.Client.Write notifies the user and removes the session
instead of leaving a windowless zombie, and Proxy destruction marks
sessions disconnected again.

Recovery windows no longer offer Submit (their session id belongs to a
dead MUME session). Draft filenames include a timestamp so reused MUME
session ids cannot collide, and ack handlers skip recovery sessions.

Menu actions capture the internal id rather than a shared_ptr to a
QObject-parented session, letting m_sessions return to unique_ptr and
removing a double-delete at shutdown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5DQbj1bGLZZLvDvbb9AsG
RemoteEditWidget becomes an embeddable page inside a new Remote Edits
Panel dock rather than a free-floating window per session, so editor
windows no longer pile up. Each page scopes its shortcuts to itself and
claims them ahead of the shortcut map while focused, so Ctrl+S submits
the edit in a tab but still saves the map elsewhere.

Unsent drafts are no longer auto-opened at launch. They are listed in
the panel (View / Discard), announced once on connect, and restored
where they can actually be sent: a new edit whose title matches a
pending draft offers to restore it, via an in-page banner for the
internal editor or a prompt before an external editor launches. A
dropped connection shows an in-page banner instead of a modal dialog.

Draft persistence moves behind RemoteEditDraftStore: files under the
editor directory natively, QSettings on WebAssembly so drafts survive a
page reload without Emscripten filesystem glue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5DQbj1bGLZZLvDvbb9AsG
@sourcery-ai

sourcery-ai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR moves remote-edit handling into a MainWindow-owned, GMCP-connected manager, adds cross-platform durable draft storage and autosave/recovery, and presents edits in a docked tab panel with terminal controls and page-scoped editor shortcuts.

Sequence diagram for remote edit draft recovery

sequenceDiagram
    participant MUME
    participant Proxy
    participant MainWindow
    participant RemoteEdit
    participant DraftStore
    participant Editor

    MUME->>Proxy: MUME.Client.Edit
    Proxy->>MainWindow: sig2_sentToUserGmcp
    MainWindow->>RemoteEdit: slot_parseGmcpInput
    RemoteEdit->>DraftStore: create(sessionId, title, body)
    RemoteEdit->>Editor: create edit page
    Editor->>RemoteEdit: sig_textModified(content)
    RemoteEdit->>DraftStore: save(key, content)
    MUME-->>Proxy: connection lost
    Proxy->>MainWindow: sig2_disconnected
    MainWindow->>RemoteEdit: onDisconnected
    RemoteEdit->>Editor: showDisconnected

    MUME->>Proxy: reconnect and MUME.Client.Edit
    Proxy->>MainWindow: sig2_sentToUserGmcp
    MainWindow->>RemoteEdit: slot_parseGmcpInput
    RemoteEdit->>DraftStore: findPendingDraft(title)
    RemoteEdit->>Editor: offerRecoveredDraft
    Editor->>RemoteEdit: Restore
    RemoteEdit->>DraftStore: read(key)
    RemoteEdit->>Editor: replaceText(content)
Loading

Entity relationship diagram for durable remote edit drafts

erDiagram
    REMOTE_EDIT_SESSION {
        int session_id
        string title
        string draft_key
        bool connected
    }
    REMOTE_EDIT_DRAFT {
        string key
        int session_id
        string title
        string content
        datetime last_modified
    }
    REMOTE_EDIT_SESSION ||--o| REMOTE_EDIT_DRAFT : persists
Loading

File-Level Changes

Change Details Files
Add durable draft persistence with native file and WebAssembly QSettings backends.
  • Create, atomically update, read, enumerate, and delete drafts with session/title metadata.
  • Configure a persistent editor directory for native external-editor files.
  • Use platform-specific storage selection and expose draft recovery/discard operations.
src/mpi/RemoteEditDraftStore.cpp
src/mpi/RemoteEditDraftStore.h
src/configuration/configuration.cpp
src/configuration/configuration.h
Refactor remote-edit lifecycle around MainWindow and GMCP signal wiring instead of Proxy/MpiFilter ownership.
  • Instantiate and own RemoteEdit from MainWindow, forwarding GMCP input/output and connection state through GameObserver and Proxy signals.
  • Remove MpiFilter and Proxy-held RemoteEdit plumbing.
  • Handle edit/view/write/cancel GMCP messages, disconnect recovery, shutdown preservation, and submission acknowledgements.
src/mainwindow/mainwindow.cpp
src/mainwindow/mainwindow.h
src/proxy/proxy.cpp
src/proxy/proxy.h
src/proxy/connectionlistener.cpp
src/proxy/connectionlistener.h
src/proxy/MudTelnet.cpp
src/proxy/MudTelnet.h
src/observer/gameobserver.cpp
src/observer/gameobserver.h
src/mpi/remoteedit.cpp
src/mpi/remoteedit.h
src/mpi/mpifilter.cpp
src/mpi/mpifilter.h
src/CMakeLists.txt
Replace standalone internal editor windows with a docked tabbed Remote Edits Panel.
  • Host internal editor/viewer widgets in tabs and list external sessions or pending drafts with View, Cancel, and Discard actions.
  • Show the dock on new pages, add compact-layout and menu integration, and track session/draft changes.
src/mpi/RemoteEditPanel.cpp
src/mpi/RemoteEditPanel.h
src/mainwindow/mainwindow.cpp
src/mainwindow/mainwindow-compact.cpp
src/mpi/remoteeditwidget.cpp
src/mpi/remoteeditwidget.h
Implement draft autosave and recovery flows for internal and external editors.
  • Autosave internal edits after a 2-second debounce with a 15-second maximum interval and flush on teardown.
  • Offer title-matched draft restoration in internal-editor banners or external-editor prompts, while preserving drafts on disconnect and deleting them only after successful submission or explicit discard.
  • Reuse persistent draft files as external-editor inputs and terminate external processes synchronously during shutdown.
src/mpi/remoteeditsession.cpp
src/mpi/remoteeditsession.h
src/mpi/remoteeditprocess.cpp
src/mpi/remoteeditprocess.h
src/mpi/remoteedit.cpp
src/mpi/remoteeditwidget.cpp
src/mpi/remoteeditwidget.h
Add terminal management for remote edit sessions and recovered drafts.
  • Introduce _edits list, status, cancel, and discard subcommands.
  • Expose session state and pending drafts through the RemoteEdit registry.
src/parser/AbstractParser-Commands.cpp
src/parser/abstractparser.h
src/mpi/remoteedit.cpp
src/mpi/remoteedit.h
Scope editor shortcuts to tab pages to avoid conflicts with MainWindow shortcuts.
  • Convert remote editor widgets to hosted QWidget pages and intercept page-local shortcuts such as Ctrl+S and Ctrl+Q.
  • Disable submission when disconnected and provide banners for disconnect and draft recovery states.
src/mpi/remoteeditwidget.cpp
src/mpi/remoteeditwidget.h

Possibly linked issues

  • #Remote edit external editor bug on MacOS: PR changes external editor process handling and removes sessions after successful GMCP write acknowledgements, directly addressing save-session closure.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

nschimme and others added 4 commits September 13, 2026 15:09
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5DQbj1bGLZZLvDvbb9AsG
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5DQbj1bGLZZLvDvbb9AsG
@codecov

codecov Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0.66890% with 891 lines in your changes missing coverage. Please review.
✅ Project coverage is 26.87%. Comparing base (1bf323f) to head (b28dd0d).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/mpi/remoteedit.cpp 0.00% 304 Missing ⚠️
src/mpi/RemoteEditDraftStore.cpp 0.00% 119 Missing ⚠️
src/mpi/RemoteEditPanel.cpp 0.00% 114 Missing ⚠️
src/mpi/remoteeditwidget.cpp 0.00% 106 Missing ⚠️
src/parser/AbstractParser-Commands.cpp 0.00% 76 Missing ⚠️
src/mpi/remoteeditsession.cpp 0.00% 59 Missing ⚠️
src/mainwindow/mainwindow.cpp 0.00% 35 Missing ⚠️
src/mpi/remoteeditprocess.cpp 0.00% 31 Missing ⚠️
src/mpi/remoteeditsession.h 0.00% 14 Missing ⚠️
src/proxy/proxy.cpp 0.00% 10 Missing ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #555      +/-   ##
==========================================
- Coverage   27.11%   26.87%   -0.25%     
==========================================
  Files         557      561       +4     
  Lines       45812    46559     +747     
  Branches     4876     4979     +103     
==========================================
+ Hits        12421    12511      +90     
- Misses      33391    34048     +657     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

1 participant