mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
feat(ui): set maintenance-mode expectations
This commit is contained in:
1 parent
6d9db9a895
commit
d11dda32dc
6 files changed
+127
-2
No files matched your search
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user