summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/SupervisorQuestionNotification.tsx
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-04-30 23:26:10 +0100
committerGitHub <noreply@github.com>2026-04-30 23:26:10 +0100
commit6d922307223d12f436b229d4c4b29b8835b93b6c (patch)
treefa5bdfd0e812f95be38f3ef3bb25ca2ea4756a28 /makima/frontend/src/components/SupervisorQuestionNotification.tsx
parentc03e9a323e266c6a9a7ccb17bbbb7841296bbd5c (diff)
downloadsoryu-6d922307223d12f436b229d4c4b29b8835b93b6c.tar.gz
soryu-6d922307223d12f436b229d4c4b29b8835b93b6c.zip
fix(doc-mode): root-walk goal serializer + roundtrip-confirmed draft drop, plus richer context menus (#114)
## The data-loss bug User reported "even after clicking Save now I have lost my doc". Two causes: 1. **GoalChangePlugin only read children[1]** — it captured edits to the single goal paragraph but silently dropped any typing that landed in the trailing paragraph below the StepsBlock (or in extra paragraphs the user had inserted). pendingGoalRef stayed at the persisted value, Save now fired empty/stale content, the doc was overwritten. 2. **fireSave dropped localStorage immediately on save success.** If the save persisted the wrong/empty content, the draft (which had the real content) was already gone — no recovery path. ## Fixes ### Capture all body content New `serializeGoalFromRoot` walks the entire root, skips only the H1 title and the StepsBlock decorator, and emits multi-paragraph markdown joined by blank lines. `GoalChangePlugin` and `fireSave` both call it now. Seed code splits an existing multi-paragraph goal back into ParagraphNodes on load. ### Read directly from the editor at save time `fireSave` now reads pendingGoalRef AND consults the live editor state via `editor.getEditorState().read()`. If anything went wrong with OnChangePlugin (which is rare, but possible — and was eating typing for many users), the save still picks up the actual document body. ### Defensive pre-save flush Before talking to the backend, `fireSave` writes the value to localStorage. If the network fails, the page closes mid-flight, or anything else goes sideways, the draft survives. ### Roundtrip-confirmed draft cleanup We no longer drop the localStorage draft inside `fireSave`. Instead a new effect watches `directive.goal` and clears the draft only when: lastSavedValueRef === directive.goal === pendingGoalRef.current i.e. only when the polled state confirms our save persisted AND the user hasn't typed anything new in the meantime. ## Question notifications respect document mode `SupervisorQuestionNotification` and `PhaseConfirmationToast` now route to `/directives/<id>?task=<taskId>` (the doc-mode surface) when the user has documentMode on AND the question carries a directiveId. Falls back to the old `/exec/:taskId` route for non-doc-mode users. ## Richer directive folder context menu In addition to start/pause/archive/delete/go-to-PR/new-draft we now expose: - Advance DAG - Plan orders - Clean up - Create / Update PR All optional callbacks; the legacy tabular UI is unaffected. ## Richer task row context menu In addition to interrupt/complete/fail/skip we now expose: - Send message — browser prompt → sendTaskMessage (same wire as the inline comment box) - Open in task page — navigates to /exec/:taskId for the full task UI (worktree diff viewer, checkpoint controls, etc.) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'makima/frontend/src/components/SupervisorQuestionNotification.tsx')
-rw-r--r--makima/frontend/src/components/SupervisorQuestionNotification.tsx21
1 files changed, 17 insertions, 4 deletions
diff --git a/makima/frontend/src/components/SupervisorQuestionNotification.tsx b/makima/frontend/src/components/SupervisorQuestionNotification.tsx
index e62638c..9c56188 100644
--- a/makima/frontend/src/components/SupervisorQuestionNotification.tsx
+++ b/makima/frontend/src/components/SupervisorQuestionNotification.tsx
@@ -1,9 +1,13 @@
import { useNavigate } from "react-router";
import { useSupervisorQuestions } from "../contexts/SupervisorQuestionsContext";
+import { useUserSettings } from "../hooks/useUserSettings";
+import type { PendingQuestion } from "../lib/api";
export function SupervisorQuestionNotification() {
const navigate = useNavigate();
const { notificationQuestions, dismissNotification } = useSupervisorQuestions();
+ const { settings } = useUserSettings();
+ const documentMode = settings?.documentModeEnabled ?? false;
// Filter out contract_complete questions - they are displayed on the task page instead
const filteredQuestions = notificationQuestions.filter(
@@ -14,9 +18,18 @@ export function SupervisorQuestionNotification() {
return null;
}
- const handleGoToTask = (questionId: string, taskId: string) => {
- dismissNotification(questionId);
- navigate(`/exec/${taskId}`);
+ const handleGoToTask = (q: PendingQuestion) => {
+ dismissNotification(q.questionId);
+ // In document mode, route directly to the directive folder page with
+ // the task selected — that's the canonical surface where the question
+ // is answered (same comment/interrupt UI as the task stream). Fall
+ // back to /exec for non-doc-mode users or for tasks not tied to a
+ // directive.
+ if (documentMode && q.directiveId) {
+ navigate(`/directives/${q.directiveId}?task=${q.taskId}`);
+ } else {
+ navigate(`/exec/${q.taskId}`);
+ }
};
return (
@@ -35,7 +48,7 @@ export function SupervisorQuestionNotification() {
</span>
</div>
<button
- onClick={() => handleGoToTask(question.questionId, question.taskId)}
+ onClick={() => handleGoToTask(question)}
className="px-3 py-1 font-mono text-xs text-amber-400 border border-amber-500/30 hover:border-amber-400/50 hover:bg-amber-900/20 transition-colors uppercase"
>
View Task