feat(editor): enable mobile compose and env editing (#1371)

* feat(editor): enable mobile compose and env editing

The mobile stack-detail Compose segment was read-only and told users to
edit on desktop. Operators need to make small emergency edits from a
phone, so the Compose segment now opens a full-screen editor for small,
safe compose and .env changes.

The editor is a lightweight monospace textarea rather than Monaco, sized
for small corrections at common phone widths. It reuses the existing
desktop save path from useStackActions and the global overlays, so every
protection behaves the same: ETag conflict handling, diff preview when
enabled, save-only, save-and-deploy, and the unsaved-changes guard. A
compose/.env toggle appears when the stack has an env file, and the
env-file picker is locked while edits are unsaved so switching files
cannot drop them. Editing is gated by the same stack:edit permission as
desktop.

A footer note reminds users that mobile editing is for small changes and
points large rewrites to desktop. The desktop Monaco editor is unchanged.

* fix(editor): keep the mobile editor save target in sync with the shown buffer

Two edge cases in the mobile compose/.env editor could silently drop an edit:

- When the desktop editor was on the Files tab (or an env tab with no env file)
  and the viewport crossed into the mobile breakpoint, the editor showed the
  compose buffer while the shared active tab stayed on files, so a save quietly
  no-opped. Normalize the active tab to compose on the mobile surface so the
  visible edit always saves to the visible file.
- The textarea stayed writable while an env-file switch was loading, so edits
  typed during the fetch were overwritten when it resolved. Make the textarea
  read-only while a file load is in flight.

Adds unit tests for both normalizations and the read-only-during-load guard.
This commit is contained in:
Anso
2026-06-14 20:18:48 -04:00
committed by GitHub
parent 49f1b49ac6
commit a5109e7916
8 changed files with 608 additions and 34 deletions
@@ -199,6 +199,16 @@ export interface EditorViewProps {
// Mobile-only: back affordance in the detail header returns to the stack list.
onMobileBack?: () => void;
// Mobile-only (always supplied by renderEditor): close the full-screen
// compose/.env editor, routed through the unsaved-changes guard so a dirty
// close prompts before discarding. Required, not optional, because the
// fallback would silently discard edits; the desktop EditorView ignores it.
onCloseEditor: () => void;
// Mobile-only (always supplied by renderEditor): true when the compose or env
// buffer differs from disk; gates the env-file selector so switching files
// cannot drop unsaved edits. Required so a wiring gap is a compile error
// rather than a silently-disabled data-loss guard.
hasUnsavedChanges: () => boolean;
// Mobile-only: notifications + more-menu cluster for the detail header right
// slot (the global TopBar is dropped on the full-screen detail surface).
headerActions?: React.ReactNode;