<feed xmlns='http://www.w3.org/2005/Atom'>
<title>soryu/makima/frontend/src/components/directives/DirectiveContextMenu.tsx, branch strict-orchestration</title>
<subtitle>soryu-co/soryu mirror</subtitle>
<id>http://src.eirin.xyz/soryu/atom?h=strict-orchestration</id>
<link rel='self' href='http://src.eirin.xyz/soryu/atom?h=strict-orchestration'/>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/'/>
<updated>2026-05-16T18:55:34+00:00</updated>
<entry>
<title>feat(directives): strict orchestration flow + sidebar overhaul + task page rewrite</title>
<updated>2026-05-16T18:55:34+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-16T18:55:34+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=8cd7b40ace4e5e2b22ad89aafec74c7655def19b'/>
<id>urn:sha1:8cd7b40ace4e5e2b22ad89aafec74c7655def19b</id>
<content type='text'>
End-to-end rewrite addressing the issues from the user's UX review.
The system now feels like a daemon-orchestration tool: lock a contract
and the orchestrator just goes; PR raised → auto-ship → reopen for
amendments. The sidebar tree shows real entities only (no duplicates,
no inline action buttons polluting the file list), and every entity
gets a right-click context menu. Task page matches the old /exec
layout (diff on the left, feed + composer on the right).

## Backend — strict lifecycle (the orchestrator-never-spawned bug)

Root cause: `phase_planning()` gates on `directive.status='active'`, but
`start_contract()` only flipped the contract row — the parent directive
stayed in whatever state it was. So locking a contract did nothing
visible.

Fix: contract lifecycle now drives directive status in the same
transaction.

  start_contract   → if contract becomes active, flip directive
                     draft|paused|idle|inactive → active
  pause_contract   → after promote, if no active contract left,
                     directive → paused
  complete_contract→ after promote, if no active left, directive →
                     inactive (also fires on auto-ship from PR detect)
  unlock_contract  → if was active and no active left, directive →
                     paused
  reopen_contract  → NEW. shipped → active. Directive → active,
                     orchestrator_task_id/pr_url/pr_branch cleared so
                     the reconciler spawns a fresh planner. The
                     planner reads get_latest_merged_revision and
                     frames the new plan as an amendment.

handlers::directive_documents lifts state.kick_directive_reconciler()
into run_contract_transition so every successful transition wakes the
reconciler immediately (no 15s wait).

handlers::directives `update_directive` (PR-detection branch) calls
`complete_contract(active_contract_id, pr_url, pr_branch)` instead of
`set_directive_inactive`. The contract auto-ships; the directive
follows via the sync above. No more manual "Mark complete" click.

POST /api/v1/contracts/{id}/reopen added + wired through openapi.

Spawn task names dropped the directive-title prefix that looked
redundant in the sidebar:
  "Plan: &lt;title&gt;"      → "orchestrator"
  "Re-plan: &lt;title&gt;"   → "orchestrator (re-plan)"
  "PR: &lt;title&gt;"        → "completion"
  "Update PR: &lt;title&gt;" → "completion (update)"

## Frontend — sidebar

* De-dupe: DocumentTasksFolder filters tasks[] to exclude any task
  whose id already appears in steps[].taskId. Single row per task,
  single highlight on click.
* Generic SidebarContextMenu (new) replaces the directive-only
  DirectiveContextMenu (deleted). Per-entity item arrays built at the
  page level — directive, contract, step, task each have their own
  contextual actions.
* Right-click works on every sidebar entity now (was directive-only).
* `+ New document` / `+ New ephemeral task` inline buttons removed.
  Reachable via the directive folder right-click OR the hover-only
  `+` button on the directive folder row.
* ContractHeader: dropped "Mark complete" button (auto-fires on PR).
  Added "Reopen for amendment" button when contract is shipped.

## Frontend — task page rewrite

TaskPage.tsx replaces DocumentTaskStream.tsx (deleted). Two-column
layout matches the old /exec page that the user preferred:

  ┌────────────────────────┬──────────────────────────────────┐
  │  Changed files (~30%)  │  Transcript feed (scrollable)    │
  │  ──────────────────    │  ──────────────────────          │
  │  src/foo.rs            │  [user] do thing                 │
  │  src/bar.rs            │  [tool] Read foo.rs              │
  │                        │                                  │
  │  Diff (selected file)  │                                  │
  │                        ├──────────────────────────────────┤
  │                        │  Composer (sticky bottom)        │
  └────────────────────────┴──────────────────────────────────┘

Diff comes from getTaskDiff(); parseDiff + DiffFileView exported from
OverlayDiffViewer for reuse (no duplication). Diff auto-refreshes
when the task transitions to a terminal state. Transcript styling +
sticky composer keep the parts the user liked. "Open in task page"
button removed — the right pane IS the task page.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(doc-mode): root-walk goal serializer + roundtrip-confirmed draft drop, plus richer context menus (#114)</title>
<updated>2026-04-30T22:26:10+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-04-30T22:26:10+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=6d922307223d12f436b229d4c4b29b8835b93b6c'/>
<id>urn:sha1:6d922307223d12f436b229d4c4b29b8835b93b6c</id>
<content type='text'>
## 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/&lt;id&gt;?task=&lt;taskId&gt;` (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) &lt;noreply@anthropic.com&gt;</content>
</entry>
<entry>
<title>feat(directives): amendment lifecycle — inactive status, new draft, before/after diff (#113)</title>
<updated>2026-04-30T16:09:45+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-04-30T16:09:45+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=c03e9a323e266c6a9a7ccb17bbbb7841296bbd5c'/>
<id>urn:sha1:c03e9a323e266c6a9a7ccb17bbbb7841296bbd5c</id>
<content type='text'>
Stage 4 of the doc-mode revamp. Closes the loop on living-spec contracts:
once a contract ships (PR raised) it becomes 'inactive', editing it kicks
off an amendment cycle, the planner sees the previously-merged content as
context, and "New draft" lets users abandon amendment and start the next
contract on a clean slate.

## inactive lifecycle

- New status `'inactive'`. Set automatically when `update_directive` detects
  a `pr_url` transition None → Some, alongside the revision snapshot
  (set_directive_inactive: idempotent, only flips active/idle/paused).
- `update_directive_goal` extends its CASE flip to include 'inactive', so
  editing a shipped contract's goal reactivates it for the planner.
- Frontend: `DirectiveStatus` gains 'inactive'; STATUS_DOT and the legacy
  STATUS_BADGEs (DirectiveDetail, DirectiveList) get color/label entries.
  Sidebar sort puts inactive after draft / before archived.

## Amendment diff to the orchestrator

`build_planning_prompt` takes a new `previous_merged_revision` parameter.
When set, it prepends an "AMENDMENT TO A PREVIOUSLY-MERGED CONTRACT" header
that shows the merged content and the amended content explicitly, with
guidance to plan a delta rather than a from-scratch rebuild. Both the
planning and replanning phases call `get_latest_merged_revision` and pass
it through.

## "New draft" affordance

- New `repository::reset_directive_for_new_draft`: clears goal to '',
  status → 'draft', detaches pr_url / pr_branch / orchestrator linkage.
  Past revisions stay in directive_revisions as history.
- New `POST /api/v1/directives/{id}/new-draft` handler.
- DirectiveContextMenu surfaces "New draft" only when status === 'inactive',
  via an optional onNewDraft callback (legacy tabular UI doesn't have to
  wire it up). After reset, the page navigates to the contract so the user
  starts typing the next iteration immediately.

## PR-state-aware updates

The user's spec — "open ⇒ update, merged ⇒ new PR, closed ⇒ new PR" — is
already implemented in `build_completion_prompt`'s `gh pr view` runtime
check, so no code change was needed here. The amendment cycle naturally
flows through it: inactive → goal save → status flips to active →
phase_replanning spawns a planner → completion task picks up the existing
pr_url, sees the GitHub state, and decides update vs new PR accordingly.

Co-authored-by: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;</content>
</entry>
<entry>
<title>feat: soryu-co/soryu - makima: Add right-click context menu to directives page</title>
<updated>2026-03-07T20:16:54+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-03-07T20:16:54+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=00b02e12a4ffe40c60f180fe742f090873a2f698'/>
<id>urn:sha1:00b02e12a4ffe40c60f180fe742f090873a2f698</id>
<content type='text'>
</content>
</entry>
</feed>
