feat(ui): set maintenance-mode expectations

This commit is contained in:
v-openrig-build committed 2026-07-30 23:37:59 +00:00
1 parent 6d9db9a895
commit d11dda32dc
6 files changed
+127 -2

No files matched your search

+2
View File
@@ -4,6 +4,8 @@ A harness wraps a model. A rig wraps your harnesses. Define your agent team in Y
OpenRig turns AI coding agents from a pile of terminal sessions into a persistent, organized team. A rig is the team that doesn't go away. If you've got tabs full of agents you're afraid to close, this is the layer you're missing: it's open source, it's local, and you're four commands away.
The OpenRig UI is experimental and in maintenance mode. It is not under active development; support is best-effort. The CLI is the primary supported interface. Contributions welcome.
```bash
npm install -g @openrig/cli
rig setup
+2
View File
@@ -133,6 +133,8 @@ Notes:
Usage: `rig ui open`
The OpenRig UI is experimental and in maintenance mode. It is not under active development; support is best-effort. The CLI is the primary supported interface. Contributions welcome.
Subcommands:
- `open`
+4
View File
@@ -8,6 +8,9 @@ export interface UiDeps {
exec: (cmd: string, args: string[]) => Promise<void>;
}
export const UI_MAINTENANCE_NOTICE =
"The OpenRig UI is experimental and in maintenance mode. It is not under active development; support is best-effort. The CLI is the primary supported interface. Contributions welcome.";
export function uiCommand(depsOverride?: UiDeps): Command {
const cmd = new Command("ui").description("UI commands");
const getDeps = (): UiDeps => depsOverride ?? {
@@ -23,6 +26,7 @@ export function uiCommand(depsOverride?: UiDeps): Command {
.command("open")
.description("Open the OpenRig UI in the default browser")
.action(async () => {
console.error(UI_MAINTENANCE_NOTICE);
const deps = getDeps();
// Explicit override skips daemon status entirely (dev workflow with Vite)
+23 -1
View File
@@ -1,6 +1,10 @@
import { describe, it, expect, vi } from "vitest";
import { Command } from "commander";
import { uiCommand, type UiDeps } from "../src/commands/ui.js";
import {
UI_MAINTENANCE_NOTICE,
uiCommand,
type UiDeps,
} from "../src/commands/ui.js";
import { STATE_FILE, type LifecycleDeps, type DaemonState } from "../src/daemon-lifecycle.js";
function mockLifecycleDeps(overrides?: Partial<LifecycleDeps>): LifecycleDeps {
@@ -53,6 +57,24 @@ function runningDeps(port: number, execFn?: UiDeps["exec"]): UiDeps {
}
describe("rig ui open", () => {
it("prints the maintenance notice to stderr on every invocation", async () => {
expect(UI_MAINTENANCE_NOTICE).toBe(
"The OpenRig UI is experimental and in maintenance mode. It is not under active development; support is best-effort. The CLI is the primary supported interface. Contributions welcome.",
);
const error = vi.spyOn(console, "error").mockImplementation(() => {});
const execFn = vi.fn(async () => {});
const deps = runningDeps(8888, execFn);
for (let invocation = 0; invocation < 2; invocation += 1) {
const program = new Command();
program.addCommand(uiCommand(deps));
await program.parseAsync(["node", "rig", "ui", "open"]);
}
expect(error.mock.calls.filter(([line]) => line === UI_MAINTENANCE_NOTICE)).toHaveLength(2);
error.mockRestore();
});
// Test 1: Daemon up -> exec open with UI URL AND prints URL
it("daemon up -> exec open with UI URL and prints URL", async () => {
const execFn = vi.fn(async () => {});
+53 -1
View File
@@ -1,6 +1,58 @@
import { RouterProvider } from "@tanstack/react-router";
import { useState } from "react";
import { router } from "./routes.js";
export const UI_MAINTENANCE_NOTICE_STORAGE_KEY = "openrig.uiMaintenanceNoticeDismissed";
const UI_MAINTENANCE_NOTICE =
"The OpenRig UI is experimental and in maintenance mode. It is not under active development; support is best-effort. The CLI is the primary supported interface. Contributions welcome.";
function wasMaintenanceNoticeDismissed(): boolean {
try {
return localStorage.getItem(UI_MAINTENANCE_NOTICE_STORAGE_KEY) === "1";
} catch {
return false;
}
}
export function UiMaintenanceNotice() {
const [dismissed, setDismissed] = useState(wasMaintenanceNoticeDismissed);
if (dismissed) return null;
const dismiss = () => {
try {
localStorage.setItem(UI_MAINTENANCE_NOTICE_STORAGE_KEY, "1");
} catch {
// localStorage can be unavailable; dismissal still works for this load.
}
setDismissed(true);
};
return (
<div
className="fixed inset-x-0 top-0 z-[100] flex items-start justify-between gap-3 border-b border-amber-500/40 bg-amber-50 px-4 py-2 text-sm text-amber-950 shadow-sm dark:bg-amber-950 dark:text-amber-50"
data-testid="ui-maintenance-notice"
role="status"
>
<span>{UI_MAINTENANCE_NOTICE}</span>
<button
aria-label="Dismiss maintenance notice"
className="shrink-0 rounded px-1.5 font-semibold hover:bg-amber-200/60 dark:hover:bg-amber-900"
onClick={dismiss}
type="button"
>
×
</button>
</div>
);
}
export function App() {
return <RouterProvider router={router} />;
return (
<>
<UiMaintenanceNotice />
<RouterProvider router={router} />
</>
);
}
@@ -0,0 +1,43 @@
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../src/routes.js", () => ({ router: {} }));
import {
UI_MAINTENANCE_NOTICE_STORAGE_KEY,
UiMaintenanceNotice,
} from "../src/App.js";
describe("UI maintenance notice", () => {
beforeEach(() => {
localStorage.clear();
});
afterEach(cleanup);
it("shows the supported-interface posture on first load", () => {
render(<UiMaintenanceNotice />);
expect(screen.getByTestId("ui-maintenance-notice").textContent).toContain(
"The OpenRig UI is experimental and in maintenance mode.",
);
expect(screen.getByTestId("ui-maintenance-notice").textContent).toContain(
"The CLI is the primary supported interface.",
);
});
it("dismisses without reserving layout space and stays dismissed after remount", () => {
const first = render(<UiMaintenanceNotice />);
const notice = screen.getByTestId("ui-maintenance-notice");
expect(notice.className).toContain("fixed");
fireEvent.click(screen.getByRole("button", { name: "Dismiss maintenance notice" }));
expect(screen.queryByTestId("ui-maintenance-notice")).toBeNull();
expect(localStorage.getItem(UI_MAINTENANCE_NOTICE_STORAGE_KEY)).toBe("1");
first.unmount();
render(<UiMaintenanceNotice />);
expect(screen.queryByTestId("ui-maintenance-notice")).toBeNull();
});
});