Watch
0
0
Fork
You've already forked hyperhive
0

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.
This commit is contained in:
iris 2026-10-26 20:28:58 +01:00
commit bdef828df3
5 changed files with 65 additions and 42 deletions

View file

@ -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}
/>
<Route path="/jobs" component={JobsPage} />
<Route path="/schedules" component={SchedulesPage} />
<Route path="/schedules">
<Redirect to="/messages" replace />
</Route>
<Route path="/builds" component={BuildsMoved} />
<Route path="/messages" component={MessagesPage} />
<Route path="/notifications" component={NotificationsPage} />

View file

@ -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 ? <BroadcastComposer /> : null}
</Disclosure>
<SchedulesCard />
<Panel
title="messages"
icon="💬"

View file

@ -0,0 +1,10 @@
/* The bar at the top of the schedules card: the add button, then the refresh
picker, to the right. */
.ui-schedules-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 0.5em 0.75em;
margin-block-end: 0.5em;
}

View file

@ -1,4 +1,4 @@
// <SchedulesPage> — the swarm's scheduled prompts, one card per schedule.
// <SchedulesCard> — 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<Schedule>[] = [
},
];
export function SchedulesPage() {
function SchedulesBody() {
const [intervalMs, setIntervalMs] =
useState<RefreshIntervalMs>(DEFAULT_INTERVAL_MS);
const {
@ -145,26 +146,21 @@ export function SchedulesPage() {
}
return (
<Panel
title="schedules"
icon="⏰"
actions={
<>
<Button
variant="primary"
onClick={() => setCreating(true)}
disabled={creating}
>
+ schedule
</Button>
<RefreshIntervalPicker
id="schedules-refresh"
value={intervalMs}
onChange={setIntervalMs}
/>
</>
}
>
<>
<div class="ui-schedules-bar">
<Button
variant="primary"
onClick={() => setCreating(true)}
disabled={creating}
>
+ schedule
</Button>
<RefreshIntervalPicker
id="schedules-refresh"
value={intervalMs}
onChange={setIntervalMs}
/>
</div>
{error ? (
<ApiErrorPanel context="failed to load the schedules" problem={error} />
) : null}
@ -226,6 +222,20 @@ export function SchedulesPage() {
</p>
) : null}
</ConfirmDialog>
</Panel>
</>
);
}
// A folded card on the messages tab: the scheduled prompts, read and polled
// only once it is opened.
export function SchedulesCard() {
return (
<LazyDisclosure
title="schedules"
icon="⏰"
hint="prompts sent to agents at set times: add, edit, pause, remove"
>
<SchedulesBody />
</LazyDisclosure>
);
}

View file

@ -53,7 +53,6 @@ const NAV_ITEMS: { href: string; label: string; accent: string }[] = [
{ href: "/agents", label: "agents", accent: "var(--green)" },
{ href: "/alerts", label: "alerts", accent: "var(--red)" },
{ href: "/jobs", label: "jobs", accent: "var(--pink)" },
{ href: "/schedules", label: "schedules", accent: "var(--cyan)" },
{ href: "/messages", label: "messages", accent: "var(--purple)" },
{ href: "/notifications", label: "notifications", accent: "var(--yellow)" },
{ href: "/issues", label: "issues", accent: "var(--yellow)" },