Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions greenfield/docs/architecture/greenfield-rewrite/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
This matrix is the living phase status. Update it in the same change that materially advances or
closes a phase; dated entries below provide the evidence, not a second status source.

| Phase | Status | Current evidence and remaining gate |
| ----------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 0 — Evidence and qualification | Complete | All eight mandatory spikes pass on exact Bun revision `17d6843606d76620cb55d31424d7fb0aed51c367`: build, transport, cross-process SQLite/outbox, Drizzle/Bun SQLite, browser data, chat batching, shutdown, and capped resources. Source-derived parity and the OpenClaw source audit pass as additional evidence. |
| 1 — Foundation | Complete | The self-contained future root builds immutable browser/web/worker artifacts, protects project-local production state, installs exact Bun and systemd artifacts, migrates a database copy, atomically promotes the release/database pair, serves readiness/browser assets, writes project-local logs, and proves crash-safe rollback and shutdown in a disposable lifecycle. |
| 2 — Trust and transport | Complete for the stated server scope | Authentication, MFA, WebAuthn, automation credentials, audit, authenticated renewable SSE, one-shot native Gateway bootstrap verification, and the consolidated [threat model](../../security/greenfield-phase-two-threat-model.md) have executable evidence. Browser UI and production cutover remain later gates. |
| 3 — Core operator domains | Started | Task and agent-directory parity are implemented with durable history, realtime invalidation, and browser workflows. Monitoring ingestion plus report, incident, and notification server parity are implemented; report, incident, and global notification browser state are also complete. Dashboard-local durable schedules/jobs, real worker execution, their `/jobs` operator UI, and the first claim-fenced `system.host` cache provider are implemented. OpenClaw cron, overview, cache browser consumption, and metrics remain open. |
| 4 — Gateway and chat | Not started | The Phase 2 verifier is one-shot only. Persistent native Gateway lifecycle, current-protocol re-audit, sessions, chat journal/recovery, attachments, and frontend remain open. |
| 5 — Privileged and external domains | Not started | Worker-owned file/media, Docker, database, OpenClaw, GitHub, deployment, backup, and other privileged adapters remain open. |
| 6 — Parity, hardening, and cutover | Not started | Full UI parity, generated `/docs`, load/resource/restore evidence, cutover rehearsal, fresh production database, and legacy removal remain open. |
| Phase | Status | Current evidence and remaining gate |
| ----------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 0 — Evidence and qualification | Complete | All eight mandatory spikes pass on exact Bun revision `17d6843606d76620cb55d31424d7fb0aed51c367`: build, transport, cross-process SQLite/outbox, Drizzle/Bun SQLite, browser data, chat batching, shutdown, and capped resources. Source-derived parity and the OpenClaw source audit pass as additional evidence. |
| 1 — Foundation | Complete | The self-contained future root builds immutable browser/web/worker artifacts, protects project-local production state, installs exact Bun and systemd artifacts, migrates a database copy, atomically promotes the release/database pair, serves readiness/browser assets, writes project-local logs, and proves crash-safe rollback and shutdown in a disposable lifecycle. |
| 2 — Trust and transport | Complete for the stated server scope | Authentication, MFA, WebAuthn, automation credentials, audit, authenticated renewable SSE, one-shot native Gateway bootstrap verification, and the consolidated [threat model](../../security/greenfield-phase-two-threat-model.md) have executable evidence. Browser UI and production cutover remain later gates. |
| 3 — Core operator domains | Started | Task and agent-directory parity are implemented with durable history, realtime invalidation, and browser workflows. Monitoring ingestion plus report, incident, and notification server parity are implemented; report, incident, and global notification browser state are also complete. Dashboard-local durable schedules/jobs, real worker execution, their `/jobs` operator UI, the first claim-fenced `system.host` cache provider, and its cache-browser/overview foundation are implemented. Metrics and the remaining overview composition stay open; OpenClaw cron belongs to Phase 4. |
| 4 — Gateway and chat | Not started | The Phase 2 verifier is one-shot only. Persistent native Gateway lifecycle, current-protocol re-audit, sessions, chat journal/recovery, attachments, and frontend remain open. |
| 5 — Privileged and external domains | Not started | Worker-owned file/media, Docker, database, OpenClaw, GitHub, deployment, backup, and other privileged adapters remain open. |
| 6 — Parity, hardening, and cutover | Not started | Full UI parity, generated `/docs`, load/resource/restore evidence, cutover rehearsal, fresh production database, and legacy removal remain open. |

### 2026-08-03 — Phase 0 started

Expand Down Expand Up @@ -997,3 +997,25 @@ full-browser parity, production rehearsal, cutover, and legacy deletion remain o
pump locally; worker-originated cache outbox rows are discovered through its bounded adaptive
cross-process polling. Cache browser consumption, metrics, overview, persistent OpenClaw cron,
and the remaining Phase 3 exit gates stay open.

### 2026-08-08 — Phase 3 cache browser and overview foundation

- The authenticated root route now consumes the bounded cache status projection and loads exact
entry detail only after selection. Its inventory preserves `totalCount` and `truncated`, keeps
freshness separate from the last attempt result, virtualizes larger snapshots, and renders only
the reviewed `system.host` payload rather than exposing generic cache payloads or metadata.
- TanStack Query owns the remote state. Status polling advances derived freshness every 30 seconds,
while `cache.entries` events coalesce precise status and selected-entry invalidation through the
shared realtime fallback and terminal-resync behavior. Transient refresh failures retain the
last validated snapshot and expose only fixed, non-sensitive warnings.
- Manual refresh is available only for providers that advertise it. Lost-response retries retain
one caller/auth-generation-scoped idempotency key per cache key, and confirmed enqueue results
repair job projections before best-effort cache, job, and schedule invalidation. The UI presents
the validated run state and links to the exact run; exact cache state remains authoritative for
clearing accepted-run feedback.
- Route and component coverage locks auth gating, lazy exact reads, truncation disclosure,
accessibility, safe payload validation, separate freshness/attempt presentation, realtime
invalidation, and retry-safe refresh. No contract, server, database, or migration surface changes
in this slice. Root-route parity remains `planned`: metrics and the remaining Phase 3 overview
composition are still open. Parity bookkeeping assigns OpenClaw cron and `cache.getHeartbeat` to
Phase 4 with their persistent authenticated Gateway dependency.
18 changes: 8 additions & 10 deletions greenfield/src/browser/api/useRealtimeQueryInvalidation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type QueryClient, useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
import { useEffect, useRef } from "react";

import { useDashboardRealtimeHub } from "./realtimeContextValue.ts";
import type { DashboardRealtimeTopic } from "./realtimeHub.ts";
Expand All @@ -23,6 +23,11 @@ export function useRealtimeQueryInvalidation({
}: RealtimeQueryInvalidationOptions): void {
const hub = useDashboardRealtimeHub();
const queryClient = useQueryClient();
const refreshQueriesReference = useRef(refreshQueries);

useEffect(() => {
refreshQueriesReference.current = refreshQueries;
}, [refreshQueries]);

useEffect(() => {
let disposed = false;
Expand All @@ -35,7 +40,7 @@ export function useRealtimeQueryInvalidation({
if (disposed) return;
refreshInFlight = true;
try {
await refreshQueries(queryClient);
await refreshQueriesReference.current(queryClient);
} catch {
// Realtime refresh is best-effort; cached data remains available.
} finally {
Expand Down Expand Up @@ -76,12 +81,5 @@ export function useRealtimeQueryInvalidation({
if (refreshTimer !== undefined) clearTimeout(refreshTimer);
if (fallbackTimer !== undefined) clearInterval(fallbackTimer);
};
}, [
fallbackRefreshIntervalMs,
hub,
queryClient,
refreshDelayMs,
refreshQueries,
topic,
]);
}, [fallbackRefreshIntervalMs, hub, queryClient, refreshDelayMs, topic]);
}
20 changes: 17 additions & 3 deletions greenfield/src/browser/application.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ const unexpectedWebAuthnClient: DashboardWebAuthnClient = Object.freeze({
});

describe("Dashboard browser application", () => {
test("renders the overview and owns throttled authenticated activity", async () => {
test("renders the overview cache foundation and owns authenticated activity", async () => {
const timestampMs = Date.now();
const queryClient = createDashboardQueryClient();
const router = createDashboardRouter(
createMemoryHistory({ initialEntries: ["/"] })
);
let touchCalls = 0;
let notificationCalls = 0;
let cacheStatusCalls = 0;
const trpcClient = createDashboardTrpcClient({
mutation(path, input) {
expect(path).toBe("auth.touch");
Expand All @@ -35,6 +36,16 @@ describe("Dashboard browser application", () => {
return Promise.resolve({ lastSeenAtMs: timestampMs });
},
query(path, input) {
if (path === "cache.getStatus") {
expect(input).toEqual({});
cacheStatusCalls += 1;
return Promise.resolve({
entries: [],
generatedAtMs: timestampMs,
totalCount: 0,
truncated: false,
});
}
if (path === "notifications.list") {
expect(input).toEqual({ limit: 100 });
notificationCalls += 1;
Expand Down Expand Up @@ -89,11 +100,14 @@ describe("Dashboard browser application", () => {
expect(
screen.getByRole("link", { name: "Skip to content" }).getAttribute("href")
).toBe("#dashboard-content");
expect(screen.getByRole("heading", { level: 2, name: "Cache" })).toBeTruthy();
expect(
screen.getByRole("status", { name: "Application status" }).textContent
).toContain("Application shell ready");
screen.getByRole("heading", { level: 3, name: "No cache attempts yet" })
).toBeTruthy();
expect(screen.queryByText("Select a cache entry")).toBeNull();
await waitFor(() => expect(touchCalls).toBe(1));
await waitFor(() => expect(notificationCalls).toBe(1));
await waitFor(() => expect(cacheStatusCalls).toBe(1));
expect(queryClient.getQueryData(authStatusQueryKey)).toMatchObject({
session: { lastSeenAtMs: timestampMs },
state: "authenticated",
Expand Down
Loading