Skip to content
Merged
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
2 changes: 2 additions & 0 deletions doc/rfc/web-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ Summary is the default request view, and `?view=history` makes History shareable

**Theme.** Views use BaseUI components with host-supplied `BaseProvider` and Styletron `Provider`. BaseUI owns layout, responsive grids, colors and typography; the stylesheet only enables wrapping long identifiers. There is no independent library theme or styling engine. Hosts choose light, dark, custom, or system-driven themes and own SSR extraction and client hydration. The reference Next.js hosts share a request-scoped Styletron registry that streams new styles and hydrates them in the browser. BaseUI and Styletron React are peers alongside React and React DOM; React 18 and 19 are both supported.

**Packaging.** Hosts process the ESM UI entries with their bundler for SSR and the browser. BaseUI imports use package entry points, such as `baseui/block`, so the host can supply its own BaseUI package layout. The separate `/server` entries run directly in Node and do not load BaseUI. Package consumer tests exercise direct Node page loading and UI rendering and hydration through Vite.

## Observability and identity

Metrics and tracing use injected OpenTelemetry meters and tracers, defaulting to the API's no-op providers. Logging uses a synchronous `onLog` callback passed with each page request: severity, message, and structured metadata, including the request ID and the original error under `error`. The host binds the callback to its request-scoped logger; the libraries have no logging-library dependency. Omitting the callback is silent, and callback exceptions cannot change page outcomes. Every gateway read and page load is timed and traced. The reference hosts write Pino JSON logs, serializing errors under `err`, and SubmitQueue exposes Prometheus metrics only when a port is configured. The reference host registers no tracer provider, so its spans are no-ops; a host that registers OpenTelemetry tracing can also add a gateway interceptor that propagates trace context.
Expand Down
2 changes: 2 additions & 0 deletions web/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ test_suite(
"//web/test/e2e/submitqueue:typecheck_typecheck_test",
"//web/test/package:consumer_node_typecheck_typecheck_test",
"//web/test/package:consumer_runtime_test",
"//web/test/package:consumer_server_runtime_test",
"//web/test/package:consumer_typecheck_typecheck_test",
"//web/test/package:pack_contents_test",
"//web/test/package:platform_pack_contents_test",
"//web/test/package:stovepipe_pack_contents_test",
"//web/test/package/react18:react18_runtime_test",
"//web/test/testutil:compose_test",
Expand Down
2 changes: 2 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ submitqueue the SubmitQueue module: routes, controllers, pages, styles

Dependencies only point down. The module never imports generated protos, a transport, or a web framework; only the host chooses implementations. A package test checks this against every file in the built package.

UI entry points are ESM for the host's bundler, including server-side rendering. They import BaseUI through package entry points such as `baseui/block`, allowing hosts to supply BaseUI with their own package layout. The separate `/server` entry points run directly in Node without loading the UI or BaseUI.

## How a page is served

1. Next.js routes every path to one catch-all page in the host.
Expand Down
6 changes: 3 additions & 3 deletions web/platform/auto-refresh.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { Block } from "baseui/block/index.js";
import { Button, SIZE } from "baseui/button/index.js";
import { LabelXSmall } from "baseui/typography/index.js";
import { Block } from "baseui/block";
import { Button, SIZE } from "baseui/button";
import { LabelXSmall } from "baseui/typography";

export interface AutoRefreshProps {
terminal?: boolean;
Expand Down
4 changes: 2 additions & 2 deletions web/platform/facts.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import type { ReactNode } from "react";
import { styled } from "baseui/index.js";
import { LabelXSmall, ParagraphSmall } from "baseui/typography/index.js";
import { styled } from "baseui";
import { LabelXSmall, ParagraphSmall } from "baseui/typography";

const FactRow = styled("div", ({ $theme }) => ({
paddingTop: $theme.sizing.scale500,
Expand Down
2 changes: 1 addition & 1 deletion web/platform/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
StyledTableHead,
StyledTableHeadCell,
StyledTableHeadRow,
} from "baseui/table-semantic/index.js";
} from "baseui/table-semantic";
import type { ReactNode } from "react";

export function Table({ label, headers, children }: {
Expand Down
2 changes: 2 additions & 0 deletions web/stovepipe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Logging uses a synchronous `onLog` callback passed on each `handle` request. Bin

## Integration

Process the UI entry with the host's bundler for both SSR and the browser. The `/server` entry runs directly in Node and does not load BaseUI. See the shared [package contract](../README.md#layers).

Instrumentation belongs in the host: wrap `handle` to observe page results and instrument the concrete service client for RPC traces. A `render` result can contain a failed section, so check `requests.ok`, or both `history.ok` and `projectStatus.ok`, before reporting success. The reference host uses native OpenTelemetry spans, a page-load counter and a duration metric; other hosts can use their own telemetry without an adapter. The reusable module has no telemetry dependency or instrumentation API.

Use the server entry's `createStovepipeWeb` to load a page and render its model with `StovepipeApp`. Mount beneath `WebNavigationProvider` when the host supplies client navigation and refresh. Without it, links and refresh use ordinary document navigation. Import `@submitqueue/web-stovepipe/styles.css` for long-identifier wrapping, and supply BaseUI's `BaseProvider` and Styletron's `Provider`. The host owns the theme and styling engine, including SSR stylesheet extraction and hydration; the library creates neither. BaseUI, React, React DOM, and Styletron React are peer dependencies.
Expand Down
6 changes: 3 additions & 3 deletions web/stovepipe/view/app.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client";

import { Fact, Timestamp } from "@submitqueue/web-platform";
import { Block } from "baseui/block/index.js";
import { Tag } from "baseui/tag/index.js";
import { HeadingLarge, HeadingSmall, LabelMedium, ParagraphSmall } from "baseui/typography/index.js";
import { Block } from "baseui/block";
import { Tag } from "baseui/tag";
import { HeadingLarge, HeadingSmall, LabelMedium, ParagraphSmall } from "baseui/typography";
import { RefreshControls } from "./refresh.js";
import type { StovepipePageModel } from "../entity/index.js";
import { Link, Section } from "./components.js";
Expand Down
6 changes: 3 additions & 3 deletions web/stovepipe/view/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import { useState, type ReactNode } from "react";
import { useWebNavigation, type WebLinkProps } from "@submitqueue/web-platform";
import type { ReadResult } from "../entity/index.js";
import { Notification, KIND } from "baseui/notification/index.js";
import { StyledLink } from "baseui/link/index.js";
import { ParagraphSmall } from "baseui/typography/index.js";
import { Notification, KIND } from "baseui/notification";
import { StyledLink } from "baseui/link";
import { ParagraphSmall } from "baseui/typography";

export { Table } from "@submitqueue/web-platform";

Expand Down
8 changes: 4 additions & 4 deletions web/stovepipe/view/pages.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import { Fact, Timestamp } from "@submitqueue/web-platform";
import { Block } from "baseui/block/index.js";
import { StyledTableBodyCell, StyledTableBodyRow } from "baseui/table-semantic/index.js";
import { Tag } from "baseui/tag/index.js";
import { ParagraphSmall } from "baseui/typography/index.js";
import { Block } from "baseui/block";
import { StyledTableBodyCell, StyledTableBodyRow } from "baseui/table-semantic";
import { Tag } from "baseui/tag";
import { ParagraphSmall } from "baseui/typography";
import type {
RequestSummaryModel,
HistoryEventModel,
Expand Down
8 changes: 4 additions & 4 deletions web/stovepipe/view/projects.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import { Timestamp } from "@submitqueue/web-platform";
import { Block } from "baseui/block/index.js";
import { StyledTableBodyCell, StyledTableBodyRow } from "baseui/table-semantic/index.js";
import { Tag } from "baseui/tag/index.js";
import { ParagraphSmall } from "baseui/typography/index.js";
import { Block } from "baseui/block";
import { StyledTableBodyCell, StyledTableBodyRow } from "baseui/table-semantic";
import { Tag } from "baseui/tag";
import { ParagraphSmall } from "baseui/typography";
import type { ProjectStatusModel } from "../entity/index.js";
import { Link, Table } from "./components.js";

Expand Down
4 changes: 2 additions & 2 deletions web/stovepipe/view/refresh.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import { useCallback, useState } from "react";
import { AutoRefresh, useWebNavigation } from "@submitqueue/web-platform";
import { Checkbox } from "baseui/checkbox/index.js";
import { ParagraphXSmall } from "baseui/typography/index.js";
import { Checkbox } from "baseui/checkbox";
import { ParagraphXSmall } from "baseui/typography";

export function RefreshControls() {
const navigation = useWebNavigation();
Expand Down
5 changes: 5 additions & 0 deletions web/stovepipe/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,10 @@ export default defineConfig({
environment: "jsdom",
globals: true,
setupFiles: ["./test-setup.ts"],
server: {
deps: {
inline: ["@submitqueue/web-platform"],
},
},
},
});
2 changes: 2 additions & 0 deletions web/submitqueue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ The complete read-only SubmitQueue UI as a mountable module. Folders follow the

Entry points: the root is client-safe (`SubmitQueueApp`, page frames, `WebNavigationProvider`, model types); `./server` has `createSubmitQueueWeb`, the gateway contract, and `WebPaths`; `./extension/gateway/mock` has test fakes; `./styles.css` is the full stylesheet. Controllers and helpers stay internal.

Process the UI entry with the host's bundler for both SSR and the browser. The `/server` entry runs directly in Node and does not load BaseUI. See the shared [package contract](../README.md#layers).

**Hosting it.** Give `createSubmitQueueWeb` a gateway client (any client matching the structural contract, so the module ships no generated code) and optionally a mount path, authorization check, error classifier, or cached queue catalog. Route every page path to `handle`, render `SubmitQueueApp` with each page model, and wrap it in a `WebNavigationProvider` for client-side navigation. Load `./styles.css` for long-identifier wrapping and supply BaseUI's `BaseProvider` and Styletron's `Provider`. The host owns the theme and styling engine, including SSR stylesheet extraction and hydration; BaseUI, React, React DOM, and Styletron React are peers. `WebShell` follows the provider's theme rather than taking a separate theme prop.

Pass `onLog` on each `handle` request to deliver severity, message, and structured metadata to the host's request-scoped logger. Errors retain their original value under `error`, and logs include the correlation `requestId` and page `path`. The callback must be synchronous; omitting it is silent, and callback exceptions do not change page results. The module has no logging-library dependency.
Expand Down
18 changes: 9 additions & 9 deletions web/submitqueue/view/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import { ErrorState } from "./error-state.js";
import { useLastSuccessfulData } from "./last-successful-data.js";
import { Fact, Table, Timestamp } from "@submitqueue/web-platform";
import { styled } from "baseui/index.js";
import { Block } from "baseui/block/index.js";
import { Button, SIZE } from "baseui/button/index.js";
import { Input } from "baseui/input/index.js";
import { KIND, Notification } from "baseui/notification/index.js";
import { Select, StyledInput } from "baseui/select/index.js";
import { Tag } from "baseui/tag/index.js";
import { StyledTableBodyCell, StyledTableBodyRow } from "baseui/table-semantic/index.js";
import { HeadingLarge, HeadingSmall, LabelXSmall, ParagraphSmall, ParagraphXSmall } from "baseui/typography/index.js";
import { styled } from "baseui";
import { Block } from "baseui/block";
import { Button, SIZE } from "baseui/button";
import { Input } from "baseui/input";
import { KIND, Notification } from "baseui/notification";
import { Select, StyledInput } from "baseui/select";
import { Tag } from "baseui/tag";
import { StyledTableBodyCell, StyledTableBodyRow } from "baseui/table-semantic";
import { HeadingLarge, HeadingSmall, LabelXSmall, ParagraphSmall, ParagraphXSmall } from "baseui/typography";
import { useWebApp, WebLink } from "./web-app.js";
import { forwardRef, useState, type ComponentPropsWithoutRef, type ReactNode } from "react";
import type {
Expand Down
4 changes: 2 additions & 2 deletions web/submitqueue/view/error-state.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import type { WebError } from "../entity/index.js";
import { KIND, Notification } from "baseui/notification/index.js";
import { HeadingSmall, ParagraphSmall } from "baseui/typography/index.js";
import { KIND, Notification } from "baseui/notification";
import { HeadingSmall, ParagraphSmall } from "baseui/typography";

export function ErrorState({ error, compact = false }: { error: WebError; compact?: boolean }) {
return <Notification kind={KIND.negative} overrides={{ Body: {
Expand Down
2 changes: 1 addition & 1 deletion web/submitqueue/view/frame.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render, screen } from "../../platform/test/rtl.js";
import { render as renderUnwrapped } from "@testing-library/react";
import { BaseProvider, DarkTheme, LightTheme } from "baseui";
import { Button } from "baseui/button/index.js";
import { Button } from "baseui/button";
import { renderToString } from "react-dom/server";
import { Server } from "styletron-engine-atomic";
import { Provider } from "styletron-react";
Expand Down
4 changes: 2 additions & 2 deletions web/submitqueue/view/pages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { WebRefreshControl } from "./refresh-control.js";
import { WebShell, type WebShellProps } from "./shell.js";
import { Timestamp } from "@submitqueue/web-platform";
import { Block } from "baseui/block/index.js";
import { HeadingLarge, ParagraphSmall, ParagraphXSmall } from "baseui/typography/index.js";
import { Block } from "baseui/block";
import { HeadingLarge, ParagraphSmall, ParagraphXSmall } from "baseui/typography";
import { WebLink } from "./web-app.js";
import { ChangeSubmissions, LastSuccessfulData, QueueDirectory, RequestDetailView, RequestListView } from "./components.js";
import type { SubmitQueuePageModel } from "../entity/index.js";
Expand Down
8 changes: 4 additions & 4 deletions web/submitqueue/view/shell.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import type { ReactNode } from "react";
import { Block } from "baseui/block/index.js";
import { StyledLink } from "baseui/link/index.js";
import { Tag } from "baseui/tag/index.js";
import { LabelMedium, LabelXSmall } from "baseui/typography/index.js";
import { Block } from "baseui/block";
import { StyledLink } from "baseui/link";
import { Tag } from "baseui/tag";
import { LabelMedium, LabelXSmall } from "baseui/typography";

export interface WebShellProps {
children: ReactNode;
Expand Down
2 changes: 1 addition & 1 deletion web/submitqueue/view/web-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { createContext, useContext, useMemo, useRef, type ComponentType } from "react";
import type { WebPageModel } from "../entity/index.js";
import { useWebNavigation, type WebLinkProps } from "@submitqueue/web-platform";
import { StyledLink } from "baseui/link/index.js";
import { StyledLink } from "baseui/link";

/** Client-side controls of the enclosing {@link WebApp}. */
export interface WebAppControls {
Expand Down
5 changes: 5 additions & 0 deletions web/submitqueue/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,10 @@ export default defineConfig({
environment: "jsdom",
globals: true,
setupFiles: ["./test-setup.ts"],
server: {
deps: {
inline: ["@submitqueue/web-platform"],
},
},
},
});
2 changes: 1 addition & 1 deletion web/test/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Web tests

- [`e2e/`](e2e/): the real stack (Bazel-built Go services, web image, MySQL) in Docker, driven by Playwright with axe checks. `make web-e2e-test`; set `SKIP_CLEANUP=true` to keep the stack for debugging.
- [`package/`](package/): checks the built packages as a consumer sees them: tarball contents and import boundaries, a typecheck and a plain-Node render through `createSubmitQueueWeb`, and React 18 render and hydration in `react18/`.
- [`package/`](package/): checks the built packages as a consumer sees them: tarball contents, portable BaseUI imports, import boundaries, consumer typechecks, and the server entries running directly in Node. UI rendering runs through Vite, including React 18 SSR and hydration with light, dark, and custom host themes in `react18/`.

Unit tests live next to the code they cover.
39 changes: 36 additions & 3 deletions web/test/package/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
load("@aspect_rules_js//js:defs.bzl", "js_test")
load("@aspect_rules_js//js:defs.bzl", "js_library", "js_test")
load("@aspect_rules_js//npm:defs.bzl", "npm_link_package")
load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project")
load("@npm//web:vitest/package_json.bzl", vitest = "bin")
load("@rules_python//python:defs.bzl", "py_test")

package(default_visibility = ["//visibility:public"])
Expand Down Expand Up @@ -76,9 +77,13 @@ py_test(
main = "pack_contents_test.py",
)

js_test(
vitest.vitest_test(
name = "consumer_runtime_test",
args = ["run", "--config", "vitest.config.mjs"],
chdir = "web/test/package",
data = [
"consumer-runtime.test.mjs",
"vitest.config.mjs",
":node_modules/@submitqueue/web-platform",
":node_modules/@submitqueue/web-submitqueue",
"//web:node_modules/@bufbuild/protobuf",
Expand All @@ -88,9 +93,37 @@ js_test(
"//web:node_modules/react-dom",
"//web:node_modules/styletron-engine-atomic",
"//web:node_modules/styletron-react",
"//web:node_modules/vitest",
"//web/submitqueue:node_modules",
],
entry_point = "consumer-runtime.mjs",
)

js_test(
name = "consumer_server_runtime_test",
data = [
":node_modules/@submitqueue/web-platform",
":node_modules/@submitqueue/web-stovepipe",
":node_modules/@submitqueue/web-submitqueue",
],
entry_point = "consumer-server-runtime.mjs",
)

js_library(
name = "vitest_config",
srcs = ["vitest.config.mjs"],
visibility = ["//web/test/package/react18:__pkg__"],
)

py_test(
name = "platform_pack_contents_test",
srcs = ["pack_contents_test.py"],
args = [
"$(rootpath //web/platform:pkg.pack)",
"platform",
],
data = ["//web/platform:pkg.pack"],
legacy_create_init = 0,
main = "pack_contents_test.py",
)

py_test(
Expand Down
32 changes: 0 additions & 32 deletions web/test/package/consumer-runtime.mjs

This file was deleted.

Loading
Loading