<feed xmlns='http://www.w3.org/2005/Atom'>
<title>soryu/makima/src/server/mod.rs, branch remove-llm</title>
<subtitle>soryu-co/soryu mirror</subtitle>
<id>http://src.eirin.xyz/soryu/atom?h=remove-llm</id>
<link rel='self' href='http://src.eirin.xyz/soryu/atom?h=remove-llm'/>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/'/>
<updated>2026-05-17T20:22:34+00:00</updated>
<entry>
<title>chore: remove LLM module + all dependent surfaces</title>
<updated>2026-05-17T20:22:34+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-17T20:22:34+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=857e717e6343fa5c2ae96664bdc64741d5ba6830'/>
<id>urn:sha1:857e717e6343fa5c2ae96664bdc64741d5ba6830</id>
<content type='text'>
Wholesale removal of the LLM integration layer. ~14,200 LOC deleted
across backend and frontend. All chat-driven UIs go with it.

## Backend
- Delete `src/llm/` (7,400 LOC): claude/groq clients, contract_tools,
  contract_evaluator, discuss_tools, mesh_tools, phase_guidance,
  task_output, templates, markdown round-trip, tools, transcript_analyzer.
- Delete handlers wholly dependent on LLM:
  - `chat.rs` (file-level LLM chat at /files/{id}/chat)
  - `mesh_chat.rs` (mesh &amp; task LLM chat + history)
  - `templates.rs` (/contract-types listing)
- Strip LLM uses from `mesh_daemon.rs`:
  - `compute_action_directive` (used phase_guidance::check_deliverables_met
    to nudge supervisors with "all tasks done" messages). The auto-PR
    path below still fires when all tasks finish, so no behaviour lost.
  - `crate::llm::markdown_to_body` → inline 1-line replacement that
    wraps markdown content in a single BodyElement::Markdown. The
    editor re-parses on display, so round-trip is preserved.
- Drop routes: /files/{id}/chat, /mesh/chat, /mesh/chat/history,
  /mesh/tasks/{id}/chat, /contract-types.
- Drop the matching openapi registrations.

## Frontend
- Delete components that were LLM-only:
  - `mesh/UnifiedMeshChatInput.tsx`
  - `listen/DiscussContractModal.tsx`
  - `listen/TranscriptAnalysisPanel.tsx`
  - `listen/ContractPickerModal.tsx`
  - `files/CliInput.tsx`
- Delete the entire /listen page (its primary value-add was
  voice → LLM analysis → contract creation; without LLM the page is
  just a transcript display with no obvious user purpose).
- Delete `hooks/useMeshChatHistory.ts` and `lib/listenApi.ts`
  (transcript-analysis API client to the already-Phase-5-removed
  listen handlers).
- Strip api.ts of LLM exports: LlmModel, ChatMessage/Request/Response,
  UserQuestion/Answer, chatWithFile, MeshChat* types &amp; functions,
  getMeshChatHistory, clearMeshChatHistory, chatWithMeshContext,
  ContractTypeTemplate, listContractTypes, chatWithContract,
  getContractChatHistory, clearContractChatHistory, discussContract,
  PhaseDefinition, DeliverableDefinition.
- mesh.tsx: drop UnifiedMeshChatInput render + the chatContext memo +
  handleTaskUpdatedFromCli (only consumer was the input).
- files.tsx: drop CliInput render + handleGenerateFromElement +
  handleBodyUpdate + handleClearFocus + suggestedPrompt state (all
  CliInput-only).
- NavStrip: drop the /listen link.
- main.tsx: drop the /listen route.

## Net diff: 37 files changed, 58 insertions, 14,281 deletions.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(directives): strict orchestration flow + sidebar overhaul + task page rewrite (#134)</title>
<updated>2026-05-16T18:56:21+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-16T18:56:21+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=ce29ae801bcc5a0ba76d5a8d1565242ab267a47d'/>
<id>urn:sha1:ce29ae801bcc5a0ba76d5a8d1565242ab267a47d</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>feat(directives): drop directives.goal — orchestration reads contract body (#132)</title>
<updated>2026-05-08T15:34:11+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-08T15:34:11+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=dce7f50e503dc374aaf879df33e725af16c4cc78'/>
<id>urn:sha1:dce7f50e503dc374aaf879df33e725af16c4cc78</id>
<content type='text'>
Hard cut. The unified contracts surface owns spec text now; the
directive itself is just a folder. The orchestrator daemon reads the
active contract's body when it spawns, replans, or runs completion.

Schema (migration 20260510000000):
  - DROP TABLE directive_goal_history
  - ALTER TABLE directives DROP COLUMN goal
  - ALTER TABLE directives DROP COLUMN goal_updated_at

New repo helper:
  - get_active_contract_body(directive_id) — picks the
    active|queued|draft contract (in that order), most-recent first.

Backend cuts:
  - Directive / DirectiveSummary / CreateDirectiveRequest /
    UpdateDirectiveRequest lose goal &amp; goalUpdatedAt.
  - CreateDirectiveRequest gains optional `contractBody` — when
    provided, create_directive_for_owner auto-creates a first contract
    with that body in the same transaction.
  - Removed: update_directive_goal, update_directive_goal_keep_orchestrator,
    save_directive_goal_history, get_directive_goal_history,
    DirectiveGoalHistory model, UpdateGoalRequest.
  - Removed handlers::directives::update_goal + the
    /directives/{id}/goal route.
  - orchestration::directive::build_planning_prompt /
    build_completion_prompt / build_order_pickup_prompt now take a
    `contract_body: &amp;str` instead of `goal_history`. classify_goal_change
    + try_interrupt_planner_with_goal_edit + GoalChangeKind +
    GoalEditInterruptResult removed (they were only useful for the
    small-vs-large goal-edit interrupt cycle).

CLI:
  - `makima directive update-goal` removed (UpdateGoalArgs deleted,
    Commands enum trimmed, ApiClient::directive_update_goal +
    UpdateGoalRequest deleted).

Frontend:
  - Directive / DirectiveSummary / CreateDirectiveRequest types lose
    goal &amp; goalUpdatedAt; CreateDirectiveRequest gains `contractBody`.
  - useDirective drops updateGoal helper.
  - api.ts updateDirectiveGoal removed.
  - Legacy DirectiveList + DirectiveDetail components deleted; the
    /directives route now always renders the document-mode page.
    The user-settings documentModeEnabled flag is no longer
    consulted at the route level.
  - NewContractModal passes body via contractBody.

Co-authored-by: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;</content>
</entry>
<entry>
<title>feat(contracts): lifecycle — Lock/Start/Pause/Complete/Unlock + queue scheduler (#129)</title>
<updated>2026-05-08T11:12:51+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-08T11:12:51+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=6690b714c64aaef5781bc0aac41b777ab72e9070'/>
<id>urn:sha1:6690b714c64aaef5781bc0aac41b777ab72e9070</id>
<content type='text'>
Adds the contract lifecycle layer on top of the unified-contracts
backbone (#128). State machine:

    draft → queued → active → shipped → archived

At most one contract per directive sits in `active` at any time —
the queue is serialised because each directive owns a single shared
worktree. Repository helpers handle the transition checks AND
auto-promote the next-up `queued` contract whenever the active slot
frees (pause / complete / unlock-from-active / archive-from-active).

Endpoints (all under /api/v1/contracts/{id}):
  POST /start    draft → queued | active (depending on slot)
  POST /pause    active → queued; promotes next queued
  POST /complete active → shipped; optional pr_url + pr_branch
  POST /unlock   queued | active → draft; promotes if was active

Frontend wiring:
  * `DirectiveContractStatus` now includes `queued`.
  * Migration adds `queued` to the CHECK constraint on
    directive_documents.status.
  * `ContractHeader` component renders breadcrumb + status pill +
    status-driven action buttons + a merge-mode (shared / own_pr)
    radio. Merge mode is editable only while draft / queued so a
    running flow's branch target can't change mid-stream.
  * RepositoryError gains a `Validation(String)` arm; the three
    existing exhaustive matches (files, mesh, versions) get a
    400 BAD_REQUEST response for it.

Drag-to-reorder UI deferred to a small follow-up — the backend
endpoint already exists from the backbone PR.

Co-authored-by: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;</content>
</entry>
<entry>
<title>feat(directives): unified contracts surface — backbone (#128)</title>
<updated>2026-05-08T10:29:56+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-08T10:29:56+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=e00be74c8b575c725829677aadeb755ee81454d0'/>
<id>urn:sha1:e00be74c8b575c725829677aadeb755ee81454d0</id>
<content type='text'>
This is the backbone PR for the unified directive workflow. A directive
holds a sequence of contracts; each contract is a spec body whose
execution drives tasks in the directive's shared worktree. Lifecycle
(Lock &amp; Start, queue scheduler, drag-reorder) lands in follow-ups.

What's in this PR:
- Migration adds `position` (queue order) and `merge_mode`
  (shared|own_pr) columns to directive_documents. The actual table
  rename is deferred — the legacy `contracts` table from the old
  contracts system still exists, and the rename collision waits for
  Phase 5 to drop legacy contracts.
- Repository: list orders by position; create assigns next-position;
  update accepts merge_mode; new reorder_directive_document_position
  shifts siblings inside a transaction.
- HTTP: endpoints aliased under /api/v1/directives/{id}/contracts and
  /api/v1/contracts/{id}/... with a new /contracts/{id}/reorder.
- Frontend: api types renamed `DirectiveContract*` (avoiding the
  legacy `Contract` type collision); document-directives.tsx imports
  via aliases so the rest of the file is untouched.

Internal struct + table names stay `DirectiveDocument` /
`directive_documents` until the legacy contracts cleanup.

Co-authored-by: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;</content>
</entry>
<entry>
<title>feat: multi-document directives with ephemeral task lifecycle (#119)</title>
<updated>2026-05-02T14:07:33+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-02T14:07:33+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=760516b2e7b97fa389fb3902e8d2314eea052ff0'/>
<id>urn:sha1:760516b2e7b97fa389fb3902e8d2314eea052ff0</id>
<content type='text'>
* feat: soryu-co/soryu - makima: Fix folder/file naming and breadcrumb hash bugs

* WIP: heartbeat checkpoint

* WIP: heartbeat checkpoint

* WIP: heartbeat checkpoint

* feat: soryu-co/soryu - makima: Frontend: render multiple documents per directive folder

* WIP: heartbeat checkpoint

* WIP: heartbeat checkpoint

* WIP: heartbeat checkpoint

* Fix DirectiveRevision import in openapi.rs after merge

* Fix document-directives.tsx merge artifacts and add inactive status</content>
</entry>
<entry>
<title>chore(cleanup): Phase 5 contracts removal + tmp directive + 30-day expiry + scroll fix (#118)</title>
<updated>2026-05-01T22:56:51+00:00</updated>
<author>
<name>soryu</name>
<email>soryu@soryu.co</email>
</author>
<published>2026-05-01T22:56:51+00:00</published>
<link rel='alternate' type='text/html' href='http://src.eirin.xyz/soryu/commit/?id=e11759447b1ac00becfb1e979e488f7f9c9cf478'/>
<id>urn:sha1:e11759447b1ac00becfb1e979e488f7f9c9cf478</id>
<content type='text'>
Sweeping cleanup across the surface and the wire. Net: -14k LOC of legacy
contracts code, plus the tmp/scroll/UX fixes the user asked for.

## Sidebar/editor independent scroll
Replace `height: calc(100vh - 80px)` (which assumed an 80px masthead and
quietly clipped or pushed the whole page below the fold when the masthead
was taller) with `h-screen + overflow-hidden` on the page root and proper
`flex-1 min-h-0` sizing on `&lt;main&gt;`. Sidebar and editor pane now manage
their own scroll independently; the page itself never scrolls.

Same fix in /tmp/:taskId.

## tmp directive — real backing for orphans/ephemerals

New migration `20260501100000_tmp_directive_and_clear_orphans.sql`:
  * Adds `directives.is_tmp` BOOLEAN NOT NULL DEFAULT false.
  * Partial unique index `(owner_id) WHERE is_tmp` — at most ONE tmp
    directive per owner.
  * Hard-deletes every existing orphan task (`directive_id IS NULL`).
    Per the user spec: "ALSO there are TOO MANY old tasks in tmp, we
    need to remove all of them as well."

New repository helpers:
  * `get_or_create_tmp_directive(pool, owner_id) -&gt; Directive`
    INSERT ON CONFLICT DO NOTHING + fallback SELECT, race-safe.
  * `list_all_tmp_directives` — drives the expiry sweep.
  * `delete_expired_tmp_tasks(tmp_directive_id) -&gt; u64`.
  * `list_tmp_tasks_for_owner` (replaces `list_orphan_tasks_for_owner`).

`mesh::create_task`: every top-level task must have a directive. If a
caller doesn't supply `directive_id` and isn't a subtask, attach to the
caller's tmp directive (auto-creating it on first use).

`list_directives_for_owner` filters out `is_tmp=true` so the scratchpad
directive doesn't pollute the contract list — surfaced via the sidebar's
`tmp/` folder instead.

## 30-day expiry on tmp tasks

New `phase_tmp_expiry` in the directive reconciler. Throttled to once per
hour: enumerates every tmp directive, calls `delete_expired_tmp_tasks`,
logs the count. The actual delete is `WHERE created_at &lt; NOW() - INTERVAL
'30 days'` and is fast on the existing index. Subtasks die via FK cascade.

## Phase 5 — contracts removed

### Frontend
Deleted entire `/contracts` surface:
  * routes: `contracts.tsx`, `contract-file.tsx`
  * components/contracts: ContractList, ContractDetail, ContractCliInput,
    ContractContextMenu, CommandModePanel, PhaseBadge, PhaseHint,
    PhaseDeliverablesPanel, PhaseProgressBar, QuickActionButtons,
    RepositoryPanel, TaskDerivationPreview
  * (Kept `PhaseConfirmationModal` — used outside the contracts surface
    by `TaskOutput` and `PhaseConfirmationNotification`.)
  * Routes deregistered from `main.tsx`; nav entry removed from
    `NavStrip`.

### Backend handlers
Deleted: `contracts.rs` (2.4k LOC), `contract_chat.rs` (3.2k LOC),
`contract_daemon.rs` (~940 LOC), `contract_discuss.rs` (~590 LOC),
`transcript_analysis.rs` (~690 LOC). All `/api/v1/contracts/*` routes
deregistered. OpenAPI entries dropped. Module declarations removed from
`server/handlers/mod.rs`.

### CLI
Removed `makima contract` and `makima supervisor` subcommands. Deleted
`daemon/cli/contract.rs` and `daemon/cli/supervisor.rs`. Bin dispatch
trimmed (~377 LOC).

### Orchestrator
Removed the contract-spawn path from `phase_execution`
(`spawn_step_contract` and its caller). `directive_steps.contract_type`
now logs a warning and falls through to standalone-task spawn. Column
itself stays — old data still reads, just no longer triggers a
contract+supervisor spawn.

### TUI
`Action::PerformCreateContract` is now a no-op that surfaces a status
message: "Contracts have been removed. Use directives instead." The TUI
form is dead code pending a wider refresh.

## Out of scope (deliberately left)

* Contracts DB tables (`contracts`, `contract_repositories`,
  `contract_chat_history`, `contract_events`, `contract_templates`) are
  retained for historical data + because some peripheral code still
  joins to them in TaskSummary queries.
* `mesh_supervisor` handlers are retained — they aren't only used by
  contracts (some mesh-level supervisor behaviour persists), and the
  cross-cutting cleanup is bigger than this PR.
* `directive_steps.contract_type` column itself isn't dropped; just no
  longer functional.

Co-authored-by: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt;</content>
</entry>
<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>
</feed>
