Skip to content

Events and the view route

An element never changes what is shown on its own. It sends an event saying what the reader asked for; its host decides. The monitor changes its view route; another page might open its own view, or ignore it. Each event crosses shadow roots and all but ca-rounds bubble, so you can listen on any ancestor, and each is typed in HTMLElementEventMap:

src/core/events.ts
export interface OpenTaskDetail {
/** A task key. */
key: string;
/** What to show of it; the host's choice when absent. */
view?: TaskView;
}
export interface SelectRunDetail {
run: string;
}
export interface FilterDetail {
kind?: string;
/** A journal stop, or 'bad' for every attempt that did not complete. */
stop?: string;
q?: string;
}
/** Where to go; `replace`: in place of where the reader is (a link that named nothing, set right), not a step back can return to. */
export type NavigateDetail = ViewRoutePatch & { replace?: boolean };
export interface ChooseThemeDetail {
/** The reader's choice: a theme, or `system`. */
theme: ThemeChoice;
}
/** What a journal row says about a try (the fields this needs). */
export interface TryResult {
attempt?: number | null;
ok: boolean;
stop: string;
reason?: string | null;
/** The models this try went through, one per hop of the failover chain. */
hops?: readonly { model?: string | null; route?: string | null; stop?: string | null }[] | null;
}
export interface RoundInfo {
kind: 'try' | 'fixup' | 'switch';
/** 1-based try number. */
tryNo: number;
/** 0 for a try's first answer, else the fix-up's number within its try. */
fixupNo: number;
/** 0 for the try's first model, else the model switch's number within the try. */
hopNo: number;
/** The model of this round's hop, when the journal names it. */
model: string;
/**
* `ok`: this round's output passed (the try's last round, the try succeeded);
* `rejected`: a later fix-up in the same try followed it (its output failed its checks);
* `failed`: the try's last round, and the try failed;
* `switched`: a model switch followed it (its model stopped, e.g. rate limited);
* null: not known yet (running).
*/
result: 'ok' | 'rejected' | 'failed' | 'switched' | null;
/** For a switched round: how its model's session ended (a stop value). */
switchedStop: string;
/** For a switched round: the model the next session ran on ('' when the journal names none). */
switchedTo: string;
/** For a rejected round: the errors the next round was sent back with. */
rejectedWith: string[];
/** For a failed round: the journal row of its try. */
row: TryResult | null;
}
export interface RoundsChangedDetail {
/** Each round's launch event (null for events before any launch). */
launches: (LaunchEvent | null)[];
/** The round shown, 0-based; -1 while there is none. */
shown: number;
/** Each round as a try or a fix-up, with its result when known. */
rounds: RoundInfo[];
}
/** Every event an element sends, by name. */
export interface CaEventMap {
/** Open a task: its transcript, or `view` of it. */
'ca-open-task': OpenTaskDetail;
/** Switch to another run. */
'ca-select-run': SelectRunDetail;
/** Change a list filter. */
'ca-filter': FilterDetail;
/** Go elsewhere in the monitor (its own views use this). */
'ca-navigate': NavigateDetail;
/** Show another theme (the theme control). */
'ca-choose-theme': ChooseThemeDetail;
/** Close the element (the run picker). */
'ca-close': Record<string, never>;
/** A transcript's rounds or its shown round changed; does not bubble (`ca-transcript`). */
'ca-rounds': RoundsChangedDetail;
}
document.addEventListener('ca-open-task', (e) => {
e.detail.key; // string: typed, no cast
});

<ca-monitor> keeps one typed value for where the reader is, and mirrors it in the URL (or in memory, with history="memory"):

src/core/view-route.ts
export interface ViewRoute {
/** '' for a run's views; `hosts` for the hosts (on a hub), with no run. */
page: Page;
/** The run; '' until one is picked. */
run: string;
tab: Tab;
/** stream tab: the task key whose transcript is open (null: the latest, `latestTask`). */
key: string | null;
/** stream tab: what the task view shows. */
view: TaskView;
/** stream tab: the round shown, 1-based (null: follow the latest). */
round: number | null;
/** definitions tab: the selected kind (null: the default one). */
def: string | null;
/** tasks and journal tabs: only this kind (kept, and out of the URL, while another tab is shown). */
kind: string;
/** journal tab: only this stop ('bad': every attempt that did not complete). */
stop: string;
/** tasks and journal tabs: a text filter. */
q: string;
/** overview tab: the pipeline graph's layer, its segments joined by `/` ('': the layer the graph opens on). */
layer: string;
/** overview tab: the unit highlighted in the pipeline graph. */
unit: string;
/** overview tab: what is selected in the pipeline graph's layer (a node, a loop box or a mark; '' for nothing). */
sel: string;
/** trends page: the period, the last `days` days (0: the days `from` to `to`, YYYY-MM-DD). */
days: number;
from: string;
to: string;
/** trends page: the time zone its days are counted in ('': the browser's). */
tz: string;
/** trends page: only the runs of this run group. */
group: string;
/** trends page: the open tab, a trends panel's `tab` ('': the first, the trends). */
trendsTab: string;
}

Moving to another place (a tab, a task, a view, a round, a definition, a run) pushes a history entry, so Back returns. Changing a filter replaces the entry, so typing does not fill the history.

What the URL does not say is kept in the history entry itself (history.state, under one key, merged with whatever the host keeps there): how far each list is scrolled and which cards are open. Back, Forward and a reload return to it; a new place starts at the top. Restoring waits until the content, which arrives over the network, is tall enough to scroll to the remembered position. Tool cards opened in a transcript are kept for the browser tab, since one transcript is shown on several entries.