<feed xmlns='http://www.w3.org/2005/Atom'>
<title>soryu/makima/src/server/handlers/directives.rs, branch contract-drag-reorder</title>
<subtitle>soryu-co/soryu mirror</subtitle>
<id>http://src.eirin.xyz/soryu/atom?h=contract-drag-reorder</id>
<link rel='self' href='http://src.eirin.xyz/soryu/atom?h=contract-drag-reorder'/>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/'/>
<updated>2026-05-01T17:06:38+00:00</updated>
<entry>
<title>feat(doc-mode): unified surface — ephemeral tasks, tmp/, /exec redirect, palette, SWR (#117)</title>
<updated>2026-05-01T17:06:38+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-01T17:06:38+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=80085c7cfa9d679ed3e3fd54a7d55fa8ab1addef'/>
<id>urn:sha1:80085c7cfa9d679ed3e3fd54a7d55fa8ab1addef</id>
<content type='text'>
Phases 1-3 of the unified-surface plan, bundled per the user's request.
The directive document/folder UI is now the canonical place to interact
with makima; legacy /exec is subsumed by routing redirects, /contracts is
already hidden in nav, and orphan tasks surface under a top-level tmp/
pseudo-folder. Phase 4 (porting contract-only features) was confirmed
out of scope; Phase 5 (deleting the contracts code) is a follow-up.

## Backend

- New migration `20260501000000_archive_existing_contracts.sql` flips
  every legacy contract to `archived` so /contracts is read-only history.
- New endpoint `POST /api/v1/directives/{id}/tasks` creates an ephemeral
  task — `directive_id` set, `directive_step_id` NULL, repo/branch
  inherited from the directive. Reuses `create_task_for_owner`.
- New endpoint `GET /api/v1/directives/{id}/tasks` lists ephemeral tasks
  attached to a directive (drives the per-folder ephemeral group).
- `GET /api/v1/mesh/tasks?orphan=true` returns top-level tasks with no
  `directive_id` AND no `parent_task_id` — backs the sidebar's tmp/.
- New repo helpers `list_ephemeral_directive_tasks_for_owner` and
  `list_orphan_tasks_for_owner`.
- The existing `mesh_merge` endpoints are reused as-is for ephemeral
  task merge (no new merge logic needed).

## Frontend

### Sticky composer + auto-scroll fix (`DocumentTaskStream.tsx`)
- Sticky comment composer pinned to viewport bottom; padding compensates
  so the last entry isn't hidden behind it.
- `autoScroll` now resumes when the user scrolls back within 80px of
  the bottom (previously stuck off forever after a single scroll-up).
- Floating "↓ Jump to latest" chip when the user has scrolled away.
- Action header strip: explicit Stop / Send / Open-in-task-page +
  conditional "Merge to base ↗" button on ephemeral terminal tasks.
- Module-level cache of historical entries by taskId so re-selecting a
  task you've viewed renders instantly while a fresh fetch runs.

### Sidebar (`document-directives.tsx`)
- Top-level `tmp/` folder: orphan tasks, polled every 5s.
- Per-directive `tasks/` subfolder now also surfaces ephemeral tasks
  (lazily fetched on folder open) with a distinct asterisk-on-terminal
  icon (`EphemeralTaskIcon`).
- Inline hover-action chips on each directive folder header: Start /
  Pause / PR / +New task. Right-click menu still works as a power-user
  fallback.
- "Now executing" amber strip in the editor pane: surfaces the live
  orchestrator/completion/running-step task with a one-click jump.
- Inline `+ New task` modal (name + plan); on submit calls
  `createDirectiveTask` and navigates into the freshly-spawned task.
- New `EphemeralAwareTaskStream` wrapper passes `ephemeral` and
  `status` to `DocumentTaskStream` so the merge button only shows when
  the selected task is genuinely an ephemeral spinoff in a terminal
  state. Step-spawned tasks merge via the directive's PR completion.

### SWR cache (`useDirectives.ts`)
- Module-level `listCache` and per-id `detailCache` (mirrors the pattern
  in `useUserSettings.ts`). Mounting the hook renders the cache value
  immediately if present and kicks a background refresh; subscribers see
  the new value when it lands. Cuts perceived navigation latency to
  near-zero on warm cache hits.

### QuickSwitcher (`QuickSwitcher.tsx`, new)
- IntelliJ-style double-Shift command palette mounted at app root.
- Listens at the document level for two `Shift` keydowns within 300ms
  with no other key in between; ignores while focus is in an
  input/textarea so capitalising letters doesn't pop the palette.
- Searches across directives + their tasks (orchestrator/completion/
  steps/ephemerals) + orphan tmp tasks. Fuzzy matches on title.
- Eagerly loads task details for the first 20 directives on open so
  searches don't block on per-directive fetches.

### Routing (`main.tsx` + `exec-redirect.tsx` + `tmp.tsx`)
- New `ExecRedirect` wrapper at `/exec/:id`: when documentMode is on
  AND the task has a `directiveId`, replaces the URL with
  `/directives/&lt;directiveId&gt;?task=&lt;taskId&gt;`. Otherwise renders the
  legacy `MeshPage` as before.
- New `/tmp/:taskId` route renders `DocumentTaskStream` standalone for
  orphan tasks, with the masthead and a `tmp / &lt;slug&gt;` breadcrumb.

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(directives): per-PR revision snapshots + sidebar history (#112)</title>
<updated>2026-04-30T16:08:30+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-04-30T16:08:30+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=fe6b78fa59657449be2e888402e3a0197b5c0621'/>
<id>urn:sha1:fe6b78fa59657449be2e888402e3a0197b5c0621</id>
<content type='text'>
Stage 3 of the doc-mode revamp. Builds the foundation for treating contracts
as living specifications by freezing their content into a revision every
time a PR is raised.

## directive_revisions table (new migration)

(id, directive_id, content, pr_url, pr_branch, pr_state, version, frozen_at)
with UNIQUE(directive_id, version) and a partial index on pr_state='open'
so the next reconciler iteration can poll only what's still in flight.

pr_state is constrained to 'open' | 'merged' | 'closed' to mirror GitHub's
PR lifecycle. For Stage 3 we only freeze on PR creation; pr_state poll is
deferred to a follow-up.

## Repository helpers

- create_directive_revision: idempotent on (directive_id, pr_url) so a
  re-run of the orchestrator's completion task can't double-snapshot.
  Auto-assigns version = MAX(existing) + 1 per directive.
- list_directive_revisions_for_owner: scoped through the directive join
  so users can only read their own contract history.
- update_directive_revision_pr_state: stub for the upcoming poller.
- get_latest_merged_revision: returns the most recent merged revision —
  this is what Stage 4 will diff against on amendments.

## Snapshot trigger

update_directive handler now reads the BEFORE pr_url before the update.
If pr_url transitions None → Some, it snapshots the directive's current
goal as a revision tied to the new pr_url. Failures log and continue —
the directive update itself is unaffected.

## API + OpenAPI

GET /api/v1/directives/{id}/revisions returns DirectiveRevisionListResponse
(revisions newest-first). Schemas registered in OpenAPI.

## Frontend: revisions/ subfolder + read-only viewer

Each contract folder now has a third subfolder ("revisions/") that lazily
fetches and lists past revisions when the parent directive folder is open.
Empty contracts skip the subfolder entirely so brand-new ones aren't
cluttered. Each row shows v&lt;N&gt;.md plus a small pill ('open'/'merged'/
'closed').

Selecting a revision encodes itself into the existing ?task= param as
"revision:&lt;id&gt;", so EditorShell can route between the live task stream
(realTaskId), the read-only RevisionViewer (revisionId), or the editor
itself (neither). The viewer renders the frozen markdown verbatim with
a deep-link to the PR — these are immutable historical records, not edit
surfaces.

Co-authored-by: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;</content>
</entry>
<entry>
<title>fix(directive): cancel orphaned planner and kick reconciler on goal update (#104)</title>
<updated>2026-04-30T09:43:31+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-04-30T09:43:31+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=c3e97bbcc32bd18d9344dd44cc54dfcdce32100b'/>
<id>urn:sha1:c3e97bbcc32bd18d9344dd44cc54dfcdce32100b</id>
<content type='text'>
Resolves the user-visible bug where editing a directive's goal mid-flight
shows "saved" but does not actually replan: the running planner kept emitting
add-step calls based on the OLD goal while a fresh planner was supposed to
take over, and the user had to wait up to 15s for the next reconciler tick
before any replanning even started.

## What was happening

PUT /api/v1/directives/{id}/goal already had two paths:
  - Small change + planner running → SendMessage interrupt + KEEP orchestrator.
  - Everything else → clear orchestrator_task_id and let phase_replanning
    spawn a new planner on the next 15s tick.

The "everything else" path cleared the directive's pointer to the planner
task but never cancelled the task itself. The task kept executing and could
race the new planner by adding more steps from the stale plan. Worse, those
new steps could push MAX(steps.created_at) past the just-bumped
goal_updated_at, suppressing phase_replanning entirely.

## Fix

1. New helper `try_cancel_running_planner()` (orchestration/directive.rs):
   sends `InterruptTask { graceful: true }` to the daemon owning the
   orchestrator task and marks the task `interrupted` in the DB. All errors
   are logged and swallowed so the goal update still completes.

2. update_goal handler calls the helper whenever it is about to take the
   "clear orchestrator_task_id" branch, so the orphaned planner stops
   producing stale-plan steps before its DB linkage is cut.

3. New `AppState::directive_kick` (tokio::sync::Notify) lets the handler
   signal the reconciler to run a tick immediately. The reconciler loop in
   server/mod.rs now selects between its 15s interval and the notify, so the
   user no longer waits up to 15s after editing a goal before replanning
   actually starts. update_goal calls `kick_directive_reconciler()` after
   the goal is persisted (both paths).

## Why not also loosen `get_directives_needing_replanning`

The query already covers the common cases once the orphan-cancel lands —
without a still-running orphan adding fresh steps, goal_updated_at reliably
exceeds MAX(steps.created_at) after a goal edit. Loosening the predicate
risked spurious replans for directives that legitimately have no steps yet
(those are handled by `phase_planning`).

Co-authored-by: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;</content>
</entry>
<entry>
<title>feat: document-mode directive UI proof of concept (Lexical) (#101)</title>
<updated>2026-04-29T00:10:11+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-04-29T00:10:11+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=4b1d608b839769052634b4facc345b891d468926'/>
<id>urn:sha1:4b1d608b839769052634b4facc345b891d468926</id>
<content type='text'>
* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Backend: feature flag + goal-edit interrupt messaging

* WIP: heartbeat checkpoint

* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Frontend: Lexical document editor with step blocks, context menu, countdown</content>
</entry>
<entry>
<title>feat: soryu-co/soryu - makima: Add DOG database schema and backend CRUD</title>
<updated>2026-03-07T02:27:41+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-03-05T23:20:29+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=eed949c692cbce0229d07f49fe974aa57699f305'/>
<id>urn:sha1:eed949c692cbce0229d07f49fe974aa57699f305</id>
<content type='text'>
</content>
</entry>
<entry>
<title>WIP: heartbeat checkpoint</title>
<updated>2026-03-07T02:27:41+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-03-05T23:16:25+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=745b6f1b794e3d18f0ed42b1d261fc2bbcddb27e'/>
<id>urn:sha1:745b6f1b794e3d18f0ed42b1d261fc2bbcddb27e</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat: auto-remove merged steps, fix UI overflow, and improve worktree handling (#74)</title>
<updated>2026-02-20T19:07:23+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-02-20T19:07:23+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=f93489a52409af63cea69fd1ce8661f74d0361b8'/>
<id>urn:sha1:f93489a52409af63cea69fd1ce8661f74d0361b8</id>
<content type='text'>
* feat: soryu-co/soryu - makima: Fix contracts page overflow - constrain layout to viewport height

* feat: soryu-co/soryu - makima: Add git fetch to create_worktree and improve completion prompt merge conflict handling

* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Add pending question notification badge to directive sidebar and nav

* feat: soryu-co/soryu - makima: Fix reconcile:on blocking - make phaseguard poll indefinitely instead of returning immediately

* feat: soryu-co/soryu - makima: Auto-remove merged steps before planning runs</content>
</entry>
<entry>
<title>feat: smart cleanup, order linking, and improved PR titles (#69)</title>
<updated>2026-02-17T16:48:39+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-02-17T16:48:39+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=aee6cda5fc8c44ebc45b274d07a1ed64052e3699'/>
<id>urn:sha1:aee6cda5fc8c44ebc45b274d07a1ed64052e3699</id>
<content type='text'>
* feat: soryu-co/soryu: Reorder navigation: move Orders before Contracts

* feat: soryu-co/soryu: Generate PR titles from step content instead of directive title

* feat: soryu-co/soryu: Add orderId field to step creation and link orders to steps

* feat: soryu-co/soryu: Handle completed orders during plan-orders flow

* WIP: heartbeat checkpoint

* Merge origin/makima/soryu-co-soryu--handle-completed-orders-during-pla-5aa9a15b (resolved conflicts)</content>
</entry>
<entry>
<title>feat: reorder nav, link orders to steps, improve PR titles (#68)</title>
<updated>2026-02-17T13:04:18+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-02-17T13:04:18+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=049fd3e8a15952627954678838ca5382c11ecd04'/>
<id>urn:sha1:049fd3e8a15952627954678838ca5382c11ecd04</id>
<content type='text'>
* feat: soryu-co/soryu: Reorder navigation: move Orders before Contracts

* feat: soryu-co/soryu: Generate PR titles from step content instead of directive title

* feat: soryu-co/soryu: Add orderId field to step creation and link orders to steps

* feat: soryu-co/soryu: Handle completed orders during plan-orders flow</content>
</entry>
</feed>
