From bdef828df3b7734bd48ae72b893077c25fe756c5 Mon Sep 17 00:00:00 2001
From: iris
Date: Mon, 26 Oct 2026 20:28:58 +0100
Subject: [PATCH] swarm-ui: the schedules are a folded card on the messages tab
The schedules tab is gone from the nav. Its page, unchanged inside, is a card
on /messages under the broadcast card that starts shut and reads nothing until
opened; its add button and refresh picker move into the card's body.
/schedules redirects to /messages. Docs follow.
---
docs/swarm/ui.md | 29 +++++-----
frontend/packages/swarm-ui/src/App.tsx | 5 +-
.../src/pages/messages/MessagesPage.tsx | 4 +-
.../src/pages/schedules/SchedulesPage.css | 10 ++++
.../src/pages/schedules/SchedulesPage.tsx | 58 +++++++++++--------
.../packages/swarm-ui/src/shell/Shell.tsx | 1 -
6 files changed, 65 insertions(+), 42 deletions(-)
create mode 100644 frontend/packages/swarm-ui/src/pages/schedules/SchedulesPage.css
diff --git a/docs/swarm/ui.md b/docs/swarm/ui.md
index 5b8b5222..dc0e0ce7 100644
--- a/docs/swarm/ui.md
+++ b/docs/swarm/ui.md
@@ -20,19 +20,18 @@ the highlighted one. **Escape**, **Tab** or a press elsewhere shut the list with
changing anything. Typing the start of an option's name picks it, also with the list
shut. A dropdown inside a dialog opens above the dialog and the dialog doesn't cut it off.
-| route | what |
-| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `/` | the start page: the swarm's name over a digital-rain backdrop, plus a paperclip offering to set up an agent while no `ruth` exists (it opens the create form with `ruth` filled in); the swarm name in the header links here |
-| `/agents` | every agent, under a folded **hives** card with the hive directory and each hive's last reported status: status, config PR, wanted state, inference settings, resource caps, permissions; create agents (with their permissions, optionally from an [archetype](archetypes.md)), link forge, matrix and GitHub accounts |
-| `/agents//terminal` | one agent's live terminal |
-| `/agents//subagents//terminal` | one of that agent's subagents' live terminal |
-| `/alerts` | the alerts the swarm's Grafana holds now: the firing ones, with a button for the pending, no-data and error ones and a link to Grafana's own alert list; read through the controller with a Viewer service-account token |
-| `/jobs` | the controller's job graph, where agent creation and credential mints show progress, and under it the swarm builder's recent builds of agent config PRs, with what nix printed when one failed |
-| `/schedules` | the scheduled prompts: add, edit, pause, resume and remove |
-| `/messages` | the swarm-wide message feed: every message between agents, and to them from the operator, reminders and compaction, with a history window and a filter on sender or recipient; a folded **broadcast** card above the feed sends one message to more than one agent at once |
-| `/notifications` | the notification router's flows: a switch for each built-in, every flow drawn as a graph, and an editor for custom flows |
-| `/issues` | a cross-repo issue report |
-| `/review` | the review classifier: its recent runs, a try-out on a real PR, and its settings |
+| route | what |
+| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `/` | the start page: the swarm's name over a digital-rain backdrop, plus a paperclip offering to set up an agent while no `ruth` exists (it opens the create form with `ruth` filled in); the swarm name in the header links here |
+| `/agents` | every agent, under a folded **hives** card with the hive directory and each hive's last reported status: status, config PR, wanted state, inference settings, resource caps, permissions; create agents (with their permissions, optionally from an [archetype](archetypes.md)), link forge, matrix and GitHub accounts |
+| `/agents//terminal` | one agent's live terminal |
+| `/agents//subagents//terminal` | one of that agent's subagents' live terminal |
+| `/alerts` | the alerts the swarm's Grafana holds now: the firing ones, with a button for the pending, no-data and error ones and a link to Grafana's own alert list; read through the controller with a Viewer service-account token |
+| `/jobs` | the controller's job graph, where agent creation and credential mints show progress, and under it the swarm builder's recent builds of agent config PRs, with what nix printed when one failed |
+| `/messages` | the swarm-wide message feed: every message between agents, and to them from the operator, reminders and compaction, with a history window and a filter on sender or recipient; a folded **broadcast** card above the feed sends one message to more than one agent at once, and a folded **schedules** card holds the scheduled prompts: add, edit, pause, resume and remove |
+| `/notifications` | the notification router's flows: a switch for each built-in, every flow drawn as a graph, and an editor for custom flows |
+| `/issues` | a cross-repo issue report |
+| `/review` | the review classifier: its recent runs, a try-out on a real PR, and its settings |
Everything it shows comes from [`swarm-controller`](../../swarm-controller/README.md).
Pages that refresh on a timer share one polling rule: the page sends no new
@@ -219,7 +218,9 @@ has no API key field: admins set each runtime's key in the bao UI.
### Scheduled prompts
-`/schedules` has one card per scheduled prompt. The **name** is the card's
+The **schedules** card on the **messages** tab holds one card per scheduled
+prompt. It starts shut and reads nothing until you open it; the old `/schedules` address
+redirects to the messages tab. The **name** is the card's
title, with the **agents** it fires for, the message, how often it repeats and
when it fires next. **edit** turns the card into its own form; **pause** and
**resume** stop and restart it; **remove** deletes it after a confirmation.
diff --git a/frontend/packages/swarm-ui/src/App.tsx b/frontend/packages/swarm-ui/src/App.tsx
index 8c6626b0..f524005f 100644
--- a/frontend/packages/swarm-ui/src/App.tsx
+++ b/frontend/packages/swarm-ui/src/App.tsx
@@ -15,7 +15,6 @@ import { ComponentsPage } from "./pages/ComponentsPage.js";
import { JobsPage } from "./pages/JobsPage.js";
import { IssueReportPage } from "./pages/IssueReportPage.js";
import { ReviewPage } from "./pages/review/ReviewPage.js";
-import { SchedulesPage } from "./pages/schedules/SchedulesPage.js";
import { StartPage } from "./pages/StartPage.js";
import { Panel } from "./ui/panel/Panel.js";
@@ -48,7 +47,9 @@ export function App() {
component={AgentTerminalPage}
/>
-
+
+
+
diff --git a/frontend/packages/swarm-ui/src/pages/messages/MessagesPage.tsx b/frontend/packages/swarm-ui/src/pages/messages/MessagesPage.tsx
index e8322c02..a8198a53 100644
--- a/frontend/packages/swarm-ui/src/pages/messages/MessagesPage.tsx
+++ b/frontend/packages/swarm-ui/src/pages/messages/MessagesPage.tsx
@@ -2,7 +2,7 @@
// operator, reminders and compaction to an agent, as the swarm's controller
// streams them (`GET /api/messages/stream`), across all hives. Above the
// list, a folded **broadcast** card holds the composer that sends one message
-// to several agents.
+// to several agents, and a folded **schedules** card holds the scheduled prompts.
//
// The stream sends the chosen window of history first, then the live tail. A
// message an agent drained with an in-turn `recv` is missing, because it starts
@@ -14,6 +14,7 @@ import { useLayoutEffect, useRef, useState } from "preact/hooks";
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
import { Row } from "@hive/shared/term-row.js";
import { BroadcastComposer } from "../broadcast/BroadcastComposer.js";
+import { SchedulesCard } from "../schedules/SchedulesPage.js";
import { Disclosure } from "../../ui/disclosure/Disclosure.js";
import { Panel } from "../../ui/panel/Panel.js";
import { SelectField } from "../../ui/select-field/SelectField.js";
@@ -96,6 +97,7 @@ export function MessagesPage() {
>
{composerBuilt ? : null}
+ — the swarm's scheduled prompts, one card per schedule.
+// — the swarm's scheduled prompts, one card per schedule, folded on the messages tab.
// Backed by swarm-controller's `GET/POST /api/schedules` and
// `PATCH/DELETE /api/schedules/{id}`; the controller is the only writer of
// the schedule store and computes each schedule's `next_fire_unix`, so
@@ -15,7 +15,8 @@ import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js";
import { Button } from "../../ui/button/Button.js";
import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js";
import { FilterableView } from "../../ui/filterable-view/FilterableView.js";
-import { Panel } from "../../ui/panel/Panel.js";
+import "./SchedulesPage.css";
+import { LazyDisclosure } from "../../ui/disclosure/LazyDisclosure.js";
import {
RefreshIntervalPicker,
type RefreshIntervalMs,
@@ -57,7 +58,7 @@ const COLUMNS: TableColumn[] = [
},
];
-export function SchedulesPage() {
+function SchedulesBody() {
const [intervalMs, setIntervalMs] =
useState(DEFAULT_INTERVAL_MS);
const {
@@ -145,26 +146,21 @@ export function SchedulesPage() {
}
return (
-
-
-
- >
- }
- >
+ <>
+