Skip to content

refactor(server): move the plan route and types into their own slice - #1694

Merged
devswha merged 2 commits into
mainfrom
refactor/slice-plans
Oct 23, 2026
Merged

devswha merged 2 commits into
mainfrom
refactor/slice-plans

Conversation

@devswha

@devswha devswha commented Oct 23, 2026

Copy link
Copy Markdown
Owner

The next feature slice out of server/index.ts and shared/protocol.ts, after integrations (#1686), plugins (#1687), background tasks (#1688) and the agent catalog (#1690): the plan route and its types move into their own files. Behaviour, path, status codes and messages stay the same. The plan is .omo/plans/feature-slices-plan.md in the maintainer's workspace.

This PR moves responsibility, so it is not move-only.

Two commits: the check first, then the move

  1. test(server): pin GET /api/pane/plan before it moves adds server/plan-routes.test.ts.

    • Why it is needed: the existing route coverage is live only: api.contract.test.ts (missing pane, wrong method) and claude-session.contract.test.ts (a Claude Code pane's plan). The plan readers have unit tests (session-plan.test.ts, session-plan-readers.test.ts).
    • How it runs without herdr, in the unit lane: a real createServer({ port: 0, hostname: "127.0.0.1", stateDir: mkdtempSync(…), … }), with sessionSnapshot stubbed through spyOn(herdr, "sessionSnapshot").
    • What it pins:
      • a POST answers 400 method_not_allowed, and a request without pane_id answers 400 missing_pane_id;
      • a pane herdr does not have, and a pane that keeps no plan, both answer 200 { plan: null } with a valid server_time;
      • when herdr does not answer the snapshot, the route still answers 200 { plan: null } once its 1-second bound (within(1000, …)) runs out. That makes this test take about a second.

    On unchanged code it gives 3 pass, 0 fail.

  2. refactor(server): move the plan route and types into their own slice

    • Moved: the branch, unchanged, into handlePlanRequest(request, url, pathname, { omo, claudeAgents, plans }): Promise<Response | null> in the new server/plan-routes.ts. This is the nullable route pattern of the earlier slices.
    • The trackers stay the core's: index.ts passes the core's own omo (OmoStatus), claudeAgents (ClaudeSubagentStatus) and plans (SessionPlans). The route's type allows only the methods it calls: apply; ensure; and known, ensure, planOf. It constructs nothing, so there is one tracker each as before and startup order does not change.
    • Moved: PlanStep and PanePlan, verbatim with their doc comments, into the new shared/plans.ts (types only).
    • Stays in protocol.ts: PlanSummary, because the session snapshot's pane enrichment and the pane-status WebSocket message both carry it; a re-export would add nothing there. Also a type re-export of PlanStep and PanePlan, so server/session-plan.ts, server/session-plan-reader.ts, server/claude-session.contract.test.ts, src/lib/api.ts, src/lib/plan.ts and its test, BackgroundTasks.tsx and site/demo/transport.ts compile unchanged.
    • Stays in index.ts: one call at the same place in the dispatch order, after the background tasks route and before session history. The now-unused within, sessionSnapshot, HerdrPane and PanePlan imports leave.

Checks

  • After the move: HERDR_TEST_MODE=unit bun test server/plan-routes.test.ts server/tasks-routes.test.ts server/session-plan.test.ts server/session-plan-readers.test.ts src/lib/plan.test.ts server/startup-order.test.ts gives 41 pass, 0 fail. The pinning test is unchanged between the two commits.
  • --color-moved on the move commit: git diff --color-moved=plain --color-moved-ws=allow-indentation-change HEAD~1 HEAD -- server shared shows 102 lines as moved: the branch (one indent level shallower in its new file) and both types. The 23 other lines are:
    • in index.ts:
      • the shortened protocol import;
      • the dropped within and sessionSnapshot imports;
      • the new plan-routes.ts import;
      • the 2-line call.
    • in the new route file:
      • its 3-line header;
      • 7 imports;
      • the PlanTrackers type with its comment;
      • the function's doc comment and signature;
      • return null.
    • in protocol.ts: the re-export line.
  • Client imports: bun run typecheck passes on both tsconfigs. No file in src/ or site/ imports shared/plans.ts. shared/plans.ts has no import and no value export.
  • Sizes: server/index.ts goes from 724 to 704 lines and shared/protocol.ts from 1,404 to 1,381. The new modules are 41 and 24 lines.
  • Risks checked:
    • Import cycles: none new. The route imports claude-subagents.ts, herdr/client.ts and http.ts, plus types only from omo-status.ts and session-plan.ts. None of them reaches index.ts, and nothing comes from core/.
    • Initialization order: unchanged.
  • bun run check fast: passes (unit 6156 pass, 0 fail, 12 skip in 439 files, 8 at a time, 57.9s).
  • CI's integration lane runs the api.contract.test.ts and claude-session.contract.test.ts plan cases against an isolated live herdr.

Overlapping open PRs

Open PRs that touch these files: #1652, #1598, #1442 and #1427. I checked each diff for the moved lines and for PlanStep and PanePlan:

No changelog fragment: users see no change.

A unit-lane test through a real createServer, with sessionSnapshot stubbed, so
it runs without herdr: the route wants GET and a pane_id, tells a pane herdr
does not have, or one that keeps no plan, that it has none on this PC's clock,
and does the same when herdr does not answer within its second.
The next feature slice out of server/index.ts and shared/protocol.ts (plan:
.omo/plans/feature-slices-plan.md). GET /api/pane/plan moves, unchanged, into
handlePlanRequest in server/plan-routes.ts, called at the same place in the
dispatch order and given the core's own OmO, Claude subagent and plan trackers
(it makes none of its own). PlanStep and PanePlan move verbatim into
shared/plans.ts (types only), and protocol.ts re-exports them. PlanSummary stays
in protocol.ts: the session snapshot and the pane-status message carry it.
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 952b9fc and 3f16ac6.

📒 Files selected for processing: 5
  • server/index.ts
  • server/plan-routes.test.ts
  • server/plan-routes.ts
  • shared/plans.ts
  • shared/protocol.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

📝 Summary

Summary by CodeRabbit

  • New Features
    • Added access to plan details for individual panes, including step labels, progress status, dependencies, owners, and start and completion times.
    • Plan information now includes a server timestamp. When no plan is available or the pane cannot be found, the response indicates that no plan is available.

The /api/pane/plan handler now resides in server/plan-routes.ts. The server delegates requests to it, shared plan types are defined in shared/plans.ts, and route tests cover validation and null-plan responses.

Changes: Pane plan route

Layer / File(s) Summary
Plan contract and request handling
shared/plans.ts, shared/protocol.ts, server/plan-routes.ts
The shared interfaces define plan-step fields and the pane-plan response. The route handler validates requests, checks the session snapshot, ensures tracker state, and returns the pane plan or null with server time.
Server wiring and route validation
server/index.ts, server/plan-routes.test.ts
The server delegates plan requests to the route handler with the OmO, Claude agent, and plan trackers. Tests cover method and parameter errors, null plans, and a session snapshot timeout.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Refactor

Possibly related issues

  • devswha/herdr-web-ui#1053 requests showing the plan's current step in the chat chip. This change provides the plan endpoint and response contract, but does not implement the chip behavior.

Suggested reviewers: haeminway1

Merge Risk: ⚪ Minimal · up to 3f16a

The route extraction has no identified merge-blocking risk and is ready for normal merge checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary change: moving the server plan route and related types into dedicated modules.
Description check Passed The description includes the required Change and Validation sections. It explains the refactor, preserved behavior, test coverage, validation results, risks, and changelog decision.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devswha
devswha merged commit 06a3083 into main Oct 23, 2026
17 checks passed
@devswha
devswha deleted the refactor/slice-plans branch October 23, 2026 02:40
devswha added a commit that referenced this pull request Oct 23, 2026
…#1696)

The last inline feature slice out of `server/index.ts` and
`shared/protocol.ts`, after integrations (#1686), plugins (#1687),
background tasks (#1688), the agent catalog (#1690) and plans (#1694):
the file routes and their types move into their own files. Behaviour,
paths, status codes, headers and messages stay the same. The plan is
`.omo/plans/feature-slices-plan.md` in the maintainer's workspace.

This PR moves responsibility, so it is **not move-only**.

## Two commits: the check first, then the move

1. **`test(server): pin the file routes before they move`** adds
`server/file-routes.test.ts`.
- **Why it is needed:** the existing route coverage is mostly live:
`page-media.contract.test.ts`, `route-envelope.contract.test.ts` and
`device-access.contract.test.ts`. The helpers have unit tests
(`file-view.test.ts`, `project-favicon.test.ts`, `files.test.ts`). In
the unit lane, only `open-access.test.ts` reaches a file route, and only
for the watching-device refusal.
- **How it runs without herdr, in the unit lane:** a real
`createServer({ port: 0, hostname: "127.0.0.1", stateDir:
mkdtempSync(…), … })` reading a temp project folder, with `paneGet`
stubbed through `spyOn(herdr, "paneGet")` where a pane is involved.
   - **What it pins:**
     - **Methods and other sites:**
       - a POST answers `400 method_not_allowed`, and HEAD is allowed;
- another site's `no-cors` read answers `403 cross_site`, while a
top-level navigation is served.
     - **A file by its absolute path:**
       - `/api/fs/stat` answers its name, kind and size;
- `/api/fs/file` answers its bytes, `inline` by default and `attachment`
with `download=1`;
       - a missing file answers `404 not_found`.
- **A relative path in a pane's folder:** with `pane_id`, a relative
path is found in the folder herdr reports for that pane. When herdr
cannot say that folder, the relative path is `404 not_found`, but an
absolute path is still served.
- **The project icon:** `/api/fs/favicon` without `root` answers `400
missing_root`. A folder with `public/favicon.ico` answers that icon, and
a folder without one answers `{ icon: null }`.
- **Page media:** an address never given out answers `404 not_found`,
and a POST answers `400 method_not_allowed`.

   On unchanged code it gives **5 pass, 0 fail**.
2. **`refactor(server): move the file routes and types into their own
slice`**
- **Moved:** both branches, unchanged, into `handleFileRequest(request,
url, pathname, requester): Promise<Response | null>` in the new
`server/file-routes.ts`:
     - `/api/fs/stat`, `/api/fs/file` and `/api/fs/favicon`;
     - `/api/fs/page-media/:media_id`.
- **Moved with them:** `claudeTempFolder`, which only the file read
used.
- **Who asks:** the access gate in `index.ts` already worked out who
asks: `bridgeAuthorized`, `requestHost`, `forwarded`, `client` and
`access`. The route gets those values as they are (`FileRequester`),
because a page's media plays only for the client and device that
rendered it. Nothing is computed again.
- **Shared state stays where it is:** the page-media registry stays in
`file-view.ts`. Sign-out and device revocation still call
`forgetPageMedia` from `index.ts`.
- **Moved:** `FileKind`, `FileInfo` and `ProjectFavicon`, verbatim with
their doc comments, into the new `shared/files.ts` (types only).
- **Stays in `index.ts`:** one call at the same place in the dispatch
order, after the agent catalog and before the workspace routes. Every
gate before it is untouched, including the watching-device refusal of
file reads. The imports only these routes used leave `index.ts`:
`paneContext`, `resolveAgent`, `claudePaneSession`,
`claudeSessionTempDir`, `findProjectFavicon`, `paneGet`,
`isSecureRequest`, the file-view helpers except `forgetPageMedia`, and
`ProjectFavicon`.
- **Stays in `protocol.ts`:** a type re-export of the three names, so
every importer compiles unchanged.

## Checks

- **After the move:** `HERDR_TEST_MODE=unit bun test
server/file-routes.test.ts server/file-view.test.ts
server/project-favicon.test.ts server/files.test.ts
server/open-access.test.ts server/startup-order.test.ts` gives ****71
pass, 0 fail****. The pinning test is unchanged between the two commits.
- **`--color-moved` on the move commit:** `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change HEAD~1 HEAD -- server shared`
shows 168 lines as moved: both branches (one indent level shallower in
their new file), `claudeTempFolder` and the three types. The 26 other
lines are:
  - in `index.ts`:
- the shortened protocol, `auth.ts` and `file-view.ts` imports (each
before and after);
    - the new `file-routes.ts` import;
    - the dropped `paneGet` import line;
    - the 2-line call.
  - in the new route file:
    - its 4-line header;
    - the 6 imports that did not move with the code;
    - the `FileRequester` type with its comment;
    - the function's doc comment and signature;
    - `return null`.
  - in `protocol.ts`: the re-export line.

The imports only the moved code used (`paneContext`/`resolveAgent`,
`claudePaneSession`, `claudeSessionTempDir`, `findProjectFavicon`) show
as moved from `index.ts` into the route file.
- **Client imports:** `bun run typecheck` passes on both tsconfigs. No
file in `src/` or `site/` imports `shared/files.ts`. `shared/files.ts`
has no import and no value export.
- **Sizes:** `server/index.ts` goes from 704 to 657 lines and
`shared/protocol.ts` from 1,381 to 1,353. The new modules are 69 and 29
lines.
- **Risks checked:**
- Import cycles: none new. The route imports `access.ts` (type only),
`auth.ts`, `claude-store.ts`, `conversation.ts`, `file-view.ts`,
`herdr/client.ts`, `http.ts`, `pane-context.ts` and
`project-favicon.ts`. These were all already imported by `index.ts`, and
none of them imports `index.ts`.
  - Initialization order: unchanged. No module state moves.
- `bun run check fast`: passes (unit 6161 pass, 0 fail, 12 skip in 440
files, 8 at a time, 57.4s).
- CI's integration lane runs `page-media.contract.test.ts`,
`route-envelope.contract.test.ts` and `device-access.contract.test.ts`
against an isolated live herdr.

## Overlapping open PRs

Open PRs that touch these files: #1652, #1598, #1442 and #1427. I
checked each diff for the moved lines, the page-media names and the
three file types:
- #1652, #1442 and #1427 change none of them.
- #1598 changes the `bridgePath` list in `index.ts`, which names
`/api/fs/`. That list stays in `index.ts`, and this PR does not touch
it.

No changelog fragment: users see no change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant