From 2dafe938f41edbb8ceb7c6a3655c9533bb50e47d Mon Sep 17 00:00:00 2001 From: soryu Date: Thu, 30 Apr 2026 15:48:26 +0100 Subject: fix(doc-mode): autosave robustness, draft→active flip, save-now, sidebar context menus (#108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 1 of the planned doc-mode revamp — bug fixes + UX polish ahead of the larger contract-revisioning architecture work. ## Backend: 'draft' included in goal-update status flip repository::update_directive_goal previously flipped only idle/paused → active on a goal save, leaving 'draft' alone. That meant brand-new directives got their goal persisted on save but never spawned a planner — exactly the "orchestrator never runs" report. Extended the CASE clause so 'draft' also flips to 'active' on save. The status remains visible to users; this just makes the implicit "first goal save = start" behaviour work end-to-end. ## Autosave robustness (DocumentEditor.tsx) The synchronous-write fix from the previous PR was correct in principle but not visible enough for users to confirm it was working, and could still drop the very last edit on an abrupt tab close. This change: - Adds beforeunload / pagehide / visibilitychange handlers that synchronously flush pendingGoalRef → localStorage (skipping if it matches the persisted value). Backed by a persistedGoalRef that tracks directive.goal in real time so the handler doesn't capture a stale closure. - Tracks the timestamp of every successful draft write (draftSavedAt) and surfaces it as a "Draft saved Ns ago" stamp in the bar — refreshed on a 1Hz ticker so users can SEE the autosave is alive. - Logs a console.warn on localStorage write failure (was silently swallowed) so quota / storage-disabled environments are diagnosable. ## Always-visible save bar + Save now button The bar now renders in every state (was hiding when idle/pending-with-time- remaining). Idle shows a quiet "Up to date." Pending outside the last 10s shows "Unsaved changes — auto-save soon." Save now is always present; disabled only when truly idle. ## EXEC and CONTRACTS hidden in document mode NavStrip filters Contracts and Exec links when settings.documentModeEnabled is true. Those areas are subsumed by the directive-document interface; the nav strip stops surfacing them so document mode users have one canonical place to work. ## Right-click context menus on sidebar Right-clicking a directive folder header opens DirectiveContextMenu with start / pause / archive / delete / Go-to-PR — same component the legacy list page uses. Right-clicking a task row inside the tasks/ subfolder opens a smaller TaskContextMenu with Interrupt (for orchestrator/completion/ running steps) and Mark complete / failed / skipped (for step rows). Step lifecycle calls require the directive_step.id, so FolderTaskRow now carries stepId alongside taskId. Co-authored-by: Claude Opus 4.7 (1M context) --- makima/src/db/repository.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'makima/src/db') diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index ca07d92..cec9a82 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -5625,8 +5625,12 @@ pub async fn check_directive_idle( } /// Update a directive's goal and bump goal_updated_at. -/// Reactivates idle/paused directives and clears any stale orchestrator task -/// so that replanning triggers on the next tick. +/// Reactivates draft/idle/paused directives and clears any stale orchestrator +/// task so that planning/replanning triggers on the next reconciler tick. +/// +/// `draft` is included in the flip set because the document-mode UI treats +/// the first goal save as the implicit "start" — without this, a brand-new +/// directive's goal save would persist but never spawn a planner. pub async fn update_directive_goal( pool: &PgPool, owner_id: Uuid, @@ -5638,7 +5642,7 @@ pub async fn update_directive_goal( UPDATE directives SET goal = $3, goal_updated_at = NOW(), - status = CASE WHEN status IN ('idle', 'paused') THEN 'active' ELSE status END, + status = CASE WHEN status IN ('draft', 'idle', 'paused') THEN 'active' ELSE status END, orchestrator_task_id = NULL, updated_at = NOW(), version = version + 1 -- cgit v1.2.3