# Events

> The station's record of what happened: durable alarm, occurrence and interlock timelines beside the live diagnostics console, with search, filters and CSV/JSON export.

Events is the page that answers "what happened?". It sits in the foot group of the navigation rail
(Validation, Events, Agent, Settings, Account) and holds four tabs: **Alarms**, **Occurrences** and
**Interlocks** are durable timelines read from the station's event journal, and **Console** is the
live diagnostic feed every subsystem writes into. The page header reads "Persistent alarm,
occurrence and interlock histories, and the live diagnostics console."

Seeing the page takes the **View events** permission (System area): both the rail entry and the
`/events` address gate on it. Every factory role holds it by default, "Not signed in" included, and
Admin always does; a role created blank starts without it, and an administrator can remove it from
any non-admin role. Exporting from any tab takes **Export/import histories** in addition.

## The tabs and their addresses {#the-tabs-and-their-addresses}

| Tab | What it lists | Address | Page |
| --- | --- | --- | --- |
| Alarms | The alarms waiting now, over the durable timeline of alarm state transitions | `/events?view=alarms` | [Alarms](events-alarms) |
| Occurrences | Every occurrence edge observed during a run or by the outside-run Always watcher | `/events?view=occurrences` | [Occurrences](events-occurrences) |
| Interlocks | Every transition of a unit's stop-interlock latch | `/events?view=interlocks` | [Interlocks](events-interlocks) |
| Console | The last 500 diagnostic entries of the running program | `/events?view=console` | [Console](events-console) |

The page opens on Alarms. Clicking a tab rewrites the address (replacing it, so the tab strip never
fills the browser's back stack), and a link that carries `view=` lands on that tab; a value the page
does not know leaves it opening on Alarms. This is how the status bar's alarm indicator (the bell
with "N active (M unacked)", drawn only while an alarm is active or waiting for an acknowledgement)
reaches the alarm list: it navigates to `/events?view=alarms`, the screen where what it is counting
is both read and answered, never the Logic page where alarms are configured.

Console stays mounted behind the other tabs, so its follow-tail state and scroll position survive a
switch; each of the three timelines keeps its own search, filter, rows and selection while you move
between them.

## Who can see and export {#who-can-see-and-export}

| Permission | What it opens on this page | Without it |
| --- | --- | --- |
| View events | The rail entry, the address and every tab | The entry is not drawn and the address is refused |
| Export/import histories | **Export** on every tab (CSV, JSON) and **Create support package** on Console | The Export button is not drawn; a press that reaches the handler anyway answers "Exporting events needs the Export/import histories permission." in the action feed |
| Operate logic | **Ack** and **Acknowledge all** on the Alarms tab | The verbs are not drawn; the rows stay readable |
| Change settings | **Start capture** and **Stop capture** on Console | The button is not drawn; the capture state is still announced to screen readers |

The permissions are decided per connection, so a browser circuit signed in as another user sees its
own set of buttons, and a sign-in, sign-out or role edit redraws them at once. The catalog is on
[Permissions](users-permissions).

## How the three timelines behave {#how-the-three-timelines-behave}

The Alarms, Occurrences and Interlocks tabs share one conduct, so switching tabs never changes the
rules:

| Behaviour | Detail |
| --- | --- |
| Window | Each tab lists 500 events per page, the ones matching its search, its filter and the period chosen, newest first (a tie on the timestamp is broken by insertion order). Under the list, **Newer** and **Older** walk the rest of the journal a page at a time, and the band is drawn only while there is another page to reach. The newest page follows the journal live; a page behind it stands still, so an event arriving now cannot push what you are reading down. |
| Date range | Two date boxes, **From** and **To**, each one end of the period read. An empty box is no bound at all, so leaving both empty reads the whole journal, and the day chosen as the closing bound is included whole, to its last instant. The cross beside them clears the pair and returns the tab to the first page. |
| Search | A text box whose placeholder names the columns it matches (name, message, source, model, unit, procedure, author, depending on the tab). The match is "contains", case-insensitive, applied 250 ms after you stop typing; leading and trailing spaces are ignored. |
| Level filter | One list per tab: **Priority** on Alarms, **Severity** on Occurrences, **Transition** on Interlocks. Changing it re-reads the journal at once. |
| Refresh | The circular-arrow button re-reads the journal on demand; its tooltip names the tab ("Refresh alarm events", "Refresh occurrences", "Refresh interlock events"). It is greyed while a read is running. |
| Live refresh | Every event committed to the journal re-queries the open tab on its own, coalesced so a burst costs one read (about 200 ms after the last commit). A tab you are not looking at is re-read when you return to it. |
| Selection | Clicking a row opens the detail panel on the right. The selection is kept across refreshes by its event id and dropped if the row is not on the page read; the cross in the panel head closes it. |
| Copy event | The button at the foot of the detail panel copies the event as one line of text: full timestamp, kind, level, name, the transition summary, the message and, for occurrences and interlocks, the identities. The action feed answers "Event copied to clipboard.", or "The clipboard refused the copy." when the browser withheld it. |
| Export | **Export** opens a menu with **Export as CSV…** and **Export as JSON…** for every event the search, the filter and the period match, page after page, not only the rows drawn. The button is greyed while the page is empty. |
| Unfiltered record | The journal is not filtered by who is signed in or by what that role may see elsewhere: it is the station's technical record, and a row silently missing would make the history lie. |

### The list and the detail panel {#the-list-and-the-detail-panel}

Every row carries the date and time of the event in the format configured under
[Localization](settings-localization), a dot in the tone of its level, the event's name, a one-line
summary of the transition and, at the right edge, the level's own word (the priority, the severity
or the transition). The tones follow one scale on every surface: the critical tone for a Critical
alarm, a Critical occurrence or an interlock trip; the caution tone for High, Warning and "Source
safe"; the attention tone for Medium; neutral for the rest.

The detail panel opens with the level badge and the name, then the full timestamp with
milliseconds, the transition summary, the message (or "No message captured." when the definition had
none), an **EVENT** card with **Level** and **Event ID**, and one or more snapshot cards whose
fields each tab's page lists.

### The status line {#the-status-line}

Under every timeline: "N shown · page 1", where N counts the rows on the page open, followed by
" · filtered" while a search, a level filter or a date range is in force; "N shown ·
refreshing…" during a re-read; on the right, "1 selected" while a row is selected and "Updated
hh:mm:ss", the time of the last successful read.

### Loading, empty and failed states {#loading-empty-and-failed-states}

| State | What the tab shows |
| --- | --- |
| First read | "Loading alarm events…" with "Reading the newest matching alarm events from the persistent journal." (the noun follows the tab). |
| Nothing recorded | "No alarm events yet" with a line saying what will appear there; each tab's page quotes its own. |
| Nothing matches | "No alarm events match these filters" with "Adjust the priority or search terms to widen this history." (priority, severity or transition, by tab). |
| The journal cannot be read | "Alarms could not be loaded" with "The persistent journal is temporarily unavailable. Console logs are unaffected." and a **Try again** button; the status line reads "Alarms unavailable". |
| A later refresh fails | The last good rows stay on screen and the action feed reports "Alarms refresh failed." |

## Exporting a timeline {#exporting-a-timeline}

Both formats are browser downloads, so they work on the station's own window and on a remote
browser alike; the action feed answers "Exported N event(s) to" followed by the file name, or
"Export failed.". The
file name carries the tab and a sortable stamp: `alarm-history-20260902-143000.csv`,
`occurrence-history-<stamp>.json`, `interlock-history-<stamp>.csv`.

Every instant is written in the configured display format, and the file declares the time reference
once: the CSV names it in the header of its first column, "Timestamp (UTC-03:00)" (or "Timestamp
(UTC)"), and the JSON is an envelope with a top-level `TimeZone` field beside an `Events` array.
Beyond what the row shows, the columns carry the full frozen snapshot, identities included (the
event id, the alarm, occurrence or observation ids, the model and unit ids), so an exported timeline
can be correlated with run files and reports. A CSV cell that begins with `=`, `+`, `-` or `@` is
prefixed with an apostrophe so a spreadsheet opens it as text rather than as a formula. Each tab's
page lists its columns.

## What the journal keeps {#what-the-journal-keeps}

The three timelines live in `events.db`, a database of its own in the station's data folder beside
`config.db` and `history-index.db`, with one append-only table per timeline. What is written is a
snapshot taken at the instant of the event (names, priority, message, model, unit, source, the
observed value), so renaming, editing or deleting the definition afterwards never rewrites what the
station told you at the time. Nothing on this page, or anywhere else in the app, deletes a row:
retention keeps the newest **100,000 rows per timeline**, pruning the oldest by insertion order with
each append, so a corrected clock cannot evict a fresh event.

The journal survives a restart and a configuration restore: a [backup](settings-backups) copies the
configuration database and the asset folder only, so restoring one neither carries the journal
along nor rolls it back. It is a record of transitions, not of state: after a restart the alarm
registry re-derives every alarm from the first sweep, acknowledgements that were pending are lost,
and the timeline simply shows what happened before and after.

Events reach the journal through an ordered queue that never blocks the runtime: the producer takes
the snapshot immediately, a single writer commits in arrival order, and a commit that keeps failing
is retried up to ten times before the event is dropped with an Error line in Console. An occurrence
observed **during a run** is written to that run's own file first (the source Histories and reports
read) and then copied into the journal with the same event id and the run context; an observation
made **outside a run** by an Always occurrence goes to the journal only. Interlock and alarm
transitions have no record other than the journal. An agent with the Logs area open reads the same
journal through `event_history_read`, read-only (see [Allowed areas](agent-areas)).

## The console beside the record {#the-console-beside-the-record}

Console is a different thing from the three timelines: a rolling 500-entry view of the program's
diagnostic stream, in memory, lost at restart and emptied by **Clear view**. The two overlap where
the runtime narrates what it is doing. An occurrence edge leaves a Console line under the Process
area (an activation at the level of its severity: Critical as an error, Warning as a warning, Info
as information), and so does an interlock transition (a trip as a warning, a safe source and a
rearm as information). Alarm transitions do **not** appear in Console: the evaluator's trace is
diagnostic detail that only a [detailed capture](events-console#detailed-capture) records. The
timelines are the typed record; Console explains the runtime around it.

## What Events does not do {#what-events-does-not-do}

- It configures nothing. Alarms are defined on [Logic > Alarms](logic-alarms), occurrences under
  [Process > Setup > Occurrences](process-occurrences) and the stop interlock under
  [Process > Definition > Automation](process-automation).
- It rearms no interlock: that is the Interlock component on a dashboard or the unit's own view.
- It has no unit or run filter: search by name, or open the run in
  [Histories](histories) for its own occurrence record.
- It deletes nothing and edits nothing; retention alone removes rows.
- While the runtime is stopped this page is not drawn at all: every screen except
  [Account](account) is replaced by the paused notice, the timelines included. The journal itself
  is untouched and reads exactly as it did once the runtime is restarted.
