= {
+ 1: 'Program graphics',
+ 2: 'Preview graphics',
+ 3: 'DSK graphics',
+ }
+ const name = channelNames[channel] || `Channel ${channel}`
+ return `${name} failed on ${context.deviceName}`
+ },
+}
+
+export const manifest: StudioBlueprintManifest = {
+ blueprintType: 'studio',
+ // ... other required properties ...
+ deviceStatusMessages,
+}
+```
+
+## Tips
+
+- **Keep messages actionable** - Tell users what to do, not just what's wrong
+- **Use emoji sparingly** - They can help draw attention to critical errors
+- **Test with real devices** - Disconnect devices to verify your messages appear correctly
+- **Check TSR source** - Device status codes and their context values are defined in TSR integrations
+- **Use functions for complex cases** - Conditional logic, pluralization, severity-based filtering
+
+## See Also
+
+- [TSR Device Integrations](https://github.com/nrkno/sofie-timeline-state-resolver) - Device status codes
+- [Demo Blueprints](https://github.com/nrkno/sofie-demo-blueprints) - Working examples
diff --git a/packages/documentation/docs/for-developers/for-blueprint-developers/intro.md b/packages/documentation/docs/for-developers/for-blueprint-developers/intro.md
index 0dfe9486a1b..c2eed2ddd55 100644
--- a/packages/documentation/docs/for-developers/for-blueprint-developers/intro.md
+++ b/packages/documentation/docs/for-developers/for-blueprint-developers/intro.md
@@ -12,7 +12,7 @@ Documentation for this page is yet to be written.
Technically, a Blueprint is a JavaScript object, implementing one of the `BlueprintManifestBase` interfaces.
-Sofie doesn't have a built-in package manager or import, so all dependencies need to be bundled into a single `*.js` file bundle using a bundler such as [Rollup](https://rollupjs.org/) or [webpack](https://webpack.js.org/). The community has built a set of utilities called [SuperFlyTV/sofie-blueprint-tools](https://github.com/SuperFlyTV/sofie-blueprint-tools/) that acts as a nascent framework for building & bundling Blueprints written in TypeScript.
+Sofie doesn't have a built-in package manager or import, so all dependencies need to be bundled into a single `*.js` file bundle using a bundler such as [Rollup](https://rollupjs.org/) or [webpack](https://webpack.js.org/). The community has built a set of utilities called [SuperFlyTV/sofie-blueprint-tools](https://github.com/SuperFlyTV/sofie-blueprint-tools/) that acts as a nascent framework for building & bundling Blueprints written in TypeScript.
:::info
Note that the Runtime Environment for Blueprints in Sofie is plain JavaScript at [ES2015 level](https://en.wikipedia.org/wiki/ECMAScript_version_history#6th_edition_%E2%80%93_ECMAScript_2015), so other ways of building Blueprints are also possible.
@@ -28,10 +28,14 @@ Currently, there are three types of Blueprints:
These blueprints interpret the data coming from the [NRCS](../../user-guide/installation/installing-a-gateway/rundown-or-newsroom-system-connection/intro.md), meaning that they need to support the particular data structures that a given Ingest Gateway uses to store incoming data from the Rundown editor. They will need to convert Rundown Pages, Cues, Items, pieces of show script and other types of objects into [Sofie concepts](../../user-guide/concepts-and-architecture.md) such as Segments, Parts, Pieces and AdLibs.
+Optional event hooks include playlist snapshot callbacks — see [Snapshot hooks](./snapshot-hooks.md).
+
# Studio Blueprints
These blueprints provide a "baseline" Timeline that is being used by your Studio whenever there isn't a Rundown active. They also handle combining Rundowns into RundownPlaylists. Via the [`applyConfig`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie-automation_blueprints-integration.StudioBlueprintManifest.html#applyconfig) method, these Blueprints enable a _Configuration-as-Code_ approach to configuring connections to various elements of your Control Room and Studio.
+Optional event hooks include system snapshot callbacks (with TSR device access) — see [Snapshot hooks](./snapshot-hooks.md).
+
# System Blueprints
-These blueprints exist to allow a _Configuration-as-Code_ approach to an entire Sofie system. This is done via the [`applyConfig`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie-automation_blueprints-integration.SystemBlueprintManifest.html#applyconfig) providing personality information such as global system configuration or system-wide HotKeys via the Blueprints.
\ No newline at end of file
+These blueprints exist to allow a _Configuration-as-Code_ approach to an entire Sofie system. This is done via the [`applyConfig`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie-automation_blueprints-integration.SystemBlueprintManifest.html#applyconfig) providing personality information such as global system configuration or system-wide HotKeys via the Blueprints.
diff --git a/packages/documentation/docs/for-developers/for-blueprint-developers/snapshot-hooks.md b/packages/documentation/docs/for-developers/for-blueprint-developers/snapshot-hooks.md
new file mode 100644
index 00000000000..2cf6ec99bef
--- /dev/null
+++ b/packages/documentation/docs/for-developers/for-blueprint-developers/snapshot-hooks.md
@@ -0,0 +1,133 @@
+---
+title: Snapshot hooks
+---
+
+# Snapshot hooks
+
+Sofie can store **snapshots** of system configuration and rundown playlist state. Blueprints can run optional callbacks when those snapshots are **created**, which is useful for driving external systems—for example executing [TSR actions](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.IExecuteTSRActionsContext.html) on playout devices when a snapshot is stored.
+
+What actually triggers each hook depends on the snapshot type (see below). In short: **cron** only stores **playlist** snapshots (when enabled in system settings), not system snapshots. **Debug capture** is a separate user-triggered flow that runs both hooks.
+
+There are two hooks, on different blueprint types:
+
+| Snapshot type | Blueprint | Callback |
+| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| System (and debug capture) | [Studio](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.StudioBlueprintManifest.html) | [`onSystemSnapshotCreated`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.StudioBlueprintManifest.html#onsystemsnapshotcreated) |
+| Rundown playlist | [Show Style](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.ShowStyleBlueprintManifest.html) | [`onPlaylistSnapshotCreated`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.ShowStyleBlueprintManifest.html#onplaylistsnapshotcreated) |
+
+Restore operations do **not** invoke these hooks (creation only).
+
+## Why studio and show style (not system blueprint)
+
+Playout devices and TSR actions are **studio-scoped**. The studio blueprint hook runs in a studio worker job with access to `listPlayoutDevices()` and `executeTSRAction()`. The show style hook runs when playlist playout data is snapshotted and uses the same TSR APIs.
+
+The [system blueprint](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.SystemBlueprintManifest.html) has no device context and is not used for these callbacks.
+
+## `onSystemSnapshotCreated` (studio blueprint)
+
+Called **after** the system snapshot file has been stored.
+
+### It runs when:
+
+Typical triggers: **Settings → Snapshots** (system snapshot), **REST/API** (`POST /snapshots` with type `system`), and **debug capture** (see below). Automatic system snapshots taken before migration use the same path for full-system snapshots.
+
+- **Studio-scoped system snapshot** (`studioId` set in snapshot options): one invocation for that studio.
+- **Full-system snapshot** (no `studioId`, all studios in the file): one invocation **per studio** included in the snapshot.
+- **Debug snapshot** ([`storeDebugSnapshot`](https://github.com/Sofie-Automation/sofie-core/blob/main/meteor/server/api/snapshot.ts)): one invocation for the target studio (`info.type` is `'debug'`). This is available from the rundown UI / triggered actions (“create snapshot for debug”), not from cron. The embedded system data inside the debug file does not fire additional system hooks.
+
+If no studio is in scope (empty studio list), the hook is not called.
+
+### Context
+
+[`ISystemSnapshotCreatedContext`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.ISystemSnapshotCreatedContext.html) — studio config, mappings, and [`IExecuteTSRActionsContext`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.IExecuteTSRActionsContext.html).
+
+### Info payload
+
+[`IBlueprintSystemSnapshotInfo`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.IBlueprintSystemSnapshotInfo.html) — metadata only; the snapshot JSON is **not** passed into the blueprint VM.
+
+| Field | Description |
+| ----------------------------- | ------------------------------------------------------------ |
+| `snapshotId` | Id of the stored snapshot |
+| `reason` | Human-readable reason from the request (UI, API, cron, etc.) |
+| `type` | [`BlueprintSnapshotType`](https://sofie-automation.github.io/sofie-core/typedoc/types/_sofie_automation_blueprints_integration.BlueprintSnapshotType.html) (`system` or `debug`) |
+| `options.studioId` | Studio this invocation is for |
+| `options.withDeviceSnapshots` | Whether device state was included in the file |
+| `options.fullSystem` | `true` if the stored snapshot is a full-system snapshot |
+
+### Example
+
+```ts
+async onSystemSnapshotCreated(context, info) {
+ const devices = await context.listPlayoutDevices()
+ if (devices.length === 0) return
+
+ await context.executeTSRAction(devices[0].deviceId, 'mySnapshotAction', {
+ snapshotId: info.snapshotId,
+ reason: info.reason,
+ fullSystem: info.options.fullSystem ?? false,
+ })
+}
+```
+
+## `onPlaylistSnapshotCreated` (show style blueprint)
+
+Called **after** playlist snapshot data has been generated in the job-worker, **before** Meteor writes the snapshot file to disk.
+
+### It runs when:
+
+- User triggers “store snapshot” on a rundown playlist (rundown header, shelf, after-broadcast form, triggered actions, etc.)
+- **REST/API** playlist snapshots
+- **Cron** — when `coreSystem.settings.cron.storeRundownSnapshots.enabled` is true ([`meteor/server/cronjobs.ts`](https://github.com/Sofie-Automation/sofie-core/blob/main/meteor/server/cronjobs.ts)); optional filter by playlist name
+- **Debug capture** — for each **active** playlist in the studio, when the user runs debug snapshot capture (same UI/trigger path as above; one hook per active playlist)
+
+### Show style selection
+
+Playlists may contain multiple rundowns (and show styles). Only **one** show style blueprint is invoked per snapshot:
+
+1. Show style of the rundown for the **current** part instance, if set
+2. Otherwise show style of the **next** part instance
+3. Otherwise show style of the **first** rundown in the playlist (sorted by name)
+
+If the playlist has no rundowns, the hook is skipped.
+
+### Context
+
+[`IPlaylistSnapshotCreatedContext`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.IPlaylistSnapshotCreatedContext.html) — show style and studio config, and `IExecuteTSRActionsContext`.
+
+### Info payload
+
+[`IBlueprintPlaylistSnapshotInfo`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.IBlueprintPlaylistSnapshotInfo.html) — metadata only; not the full snapshot blob.
+
+| Field | Description |
+| ---------------------- | --------------------------------------------- |
+| `snapshotId` | Id assigned before generation |
+| `playlistId` | Rundown playlist id |
+| `reason` | Human-readable reason from the request |
+| `options.full` | All part/piece instances vs recent only |
+| `options.withTimeline` | Timeline included when playlist was activated |
+| `playlist.name` | Playlist name at snapshot time |
+| `playlist.active` | Whether the playlist had an activation |
+| `playlist.rehearsal` | Rehearsal mode flag |
+
+### Example
+
+```ts
+async onPlaylistSnapshotCreated(context, info) {
+ const devices = await context.listPlayoutDevices()
+
+ await context.executeTSRAction(devices[0].deviceId, 'playlistSnapshotAction', {
+ playlistId: info.playlistId,
+ active: info.playlist.active,
+ reason: info.reason,
+ })
+}
+```
+
+## Error handling
+
+If a hook throws or rejects, Core **logs the error** and continues. Snapshot generation and storage are not aborted. This matches other playout event hooks such as `onRundownActivate`.
+
+## Related API
+
+- Type definitions: [`snapshotContext.ts`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/blueprints-integration/src/context/snapshotContext.ts) in `@sofie-automation/blueprints-integration`
+- TSR methods: [`IExecuteTSRActionsContext`](https://sofie-automation.github.io/sofie-core/typedoc/interfaces/_sofie_automation_blueprints_integration.IExecuteTSRActionsContext.html) (also used by `onTake`, `onRundownActivate`, adlib actions, etc.)
diff --git a/packages/documentation/docs/for-developers/for-blueprint-developers/splits-box-previews.md b/packages/documentation/docs/for-developers/for-blueprint-developers/splits-box-previews.md
new file mode 100644
index 00000000000..5fac1b950fc
--- /dev/null
+++ b/packages/documentation/docs/for-developers/for-blueprint-developers/splits-box-previews.md
@@ -0,0 +1,169 @@
+---
+title: SPLITS box previews
+sidebar_position: 11
+---
+
+# SPLITS box previews
+
+Sofie can show **package-manager thumbnails and preview video inside each box** of a SPLITS (multi-box / DVE) piece.
+
+Blueprints define which sources sit in each box and which media files they use. Core resolves preview URLs at runtime and publishes them on the `uiPieceContentStatuses` publication. The WebUI draws them on the dashboard shelf buttons and in hover popups.
+
+| Part | Responsibility |
+| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Blueprint | [`SplitsContent`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/blueprints-integration/src/content.ts) (`boxSourceConfiguration`), optional [`expectedPackages`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/blueprints-integration/src/documents/pieceGeneric.ts) (same `MEDIA_FILE` pattern as VT), optional [`popUpPreview`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/blueprints-integration/src/previews.ts) |
+| Core | `status.boxPreviews[]` — one entry per box, same order as `boxSourceConfiguration` |
+| WebUI | Merges `boxPreviews` into split layouts; most inline surfaces stay colour-only |
+
+For SPLITS pieces, piece-level `thumbnailUrl` / `previewUrl` on content status are always empty. Use `boxPreviews` only.
+
+## What to put on the piece
+
+Each SPLITS piece that should show media previews needs layout on `content` and, for VT (or other file-based) boxes, package expectations on the same [`IBlueprintPieceGeneric`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/blueprints-integration/src/documents/pieceGeneric.ts).
+
+### `boxSourceConfiguration`
+
+[`SplitsContent`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/blueprints-integration/src/content.ts) stores boxes in `boxSourceConfiguration`. Index `0` is the rearmost layer.
+
+| Box | `SourceLayerType` | Blueprint must set |
+| --------------- | ----------------------------------- | ----------------------------------------------------------------------- |
+| VT clip | `VT` (or `LIVE_SPEAK` on your show) | `fileName`, `studioLabel`, `switcherInput`, `geometry`, usual VT fields |
+| Camera | `CAMERA` | `studioLabel`, `switcherInput`, `geometry` |
+| Remote | `REMOTE` | Same as camera |
+| Graphics / Nora | `GRAPHICS` (per show) | Your existing pattern; include `fileName` if the box uses a file |
+
+Any VT (or file-based) box that should show a thumbnail **must** have `fileName` on that box entry. Without it, Core cannot match media and the WebUI shows only a layer-colour block.
+
+**Geometry:** `geometry.x`, `geometry.y`, and `geometry.scale` are fractions of the layout (0–1). Optional `geometry.crop` (`left`, `top`, `right`, `bottom`, also 0–1) is applied in the WebUI with CSS `clip-path` — for example a 9:16 portrait window inside a square cell.
+
+Your ingest may still use a nested `{ geometry, source }[]` shape internally. Sofie does **not** store that on the piece; map it into `SplitsContent` when building pieces.
+
+### `expectedPackages`
+
+`expectedPackages` on pieces is **not new**. The `MEDIA_FILE` shape is the same one you already use on standalone VT pieces. Some shows may already attach packages to SPLITS pieces for Package Manager / playout.
+
+What **is** new is how Core uses them for the UI:
+
+| Before split box previews | After |
+| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
+| SPLITS + `expectedPackages` → one piece-level `thumbnailUrl` / `previewUrl` (first package only) | Per-box URLs in `status.boxPreviews[]`, matched to each VT box `fileName` |
+| SPLITS without `expectedPackages` → content status did not read VT media from `boxSourceConfiguration` | Core resolves previews from `fileName` via MediaObjects (and packages when present) |
+
+#### Rules
+
+- Add one [`ExpectedPackage.PackageType.MEDIA_FILE`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/shared-lib/src/package-manager/package.ts) per **distinct** file in the layout.
+- `content.filePath` must match the box `fileName` (same string as playout and Package Manager).
+- Reuse your existing VT package helpers (`sources`, `version`, accessors, containers).
+- Do **not** put `thumbnailUrl` or `previewUrl` on content or packages.
+- Two boxes sharing one file → one package entry; both boxes still need that `fileName`.
+
+If VT boxes have no `expectedPackages`, Core can still fill previews from **MediaObjects** only. Prefer `expectedPackages` whenever your VT pieces already use them — routing, status, and Package Manager thumbnails stay consistent.
+
+### Source layer
+
+- `sourceLayerId` must point to a layer with `type: SPLITS` in the show-style blueprint.
+- Timeline / playout for splits is unchanged; only content status and preview UI are affected.
+
+## Optional `popUpPreview`
+
+You may set `content.popUpPreview` with [`PreviewType.Split`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/blueprints-integration/src/previews.ts) (`boxes`, optional `background` asset).
+
+Preview **URLs still come from** `UIPieceContentStatus`, not from the blueprint preview object. If `popUpPreview` is omitted, SPLITS pieces still get a default hover popup built from `SplitsContent`.
+
+## Published status: `boxPreviews`
+
+The `uiPieceContentStatuses` publication includes `status.boxPreviews` on [`PieceContentStatusObj`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/corelib/src/dataModel/PieceContentStatus.ts).
+
+| Field | Description |
+| ------------------------------------------ | -------------------------------------------------------- |
+| `boxPreviews` | Array, same length and order as `boxSourceConfiguration` |
+| `boxPreviews[i]` | `{}` for camera / remote / other non-file boxes |
+| `boxPreviews[i].thumbnailUrl` | Thumbnail for box `i` when media is ready |
+| `boxPreviews[i].previewUrl` | Preview video for box `i` (hover scrub) |
+| `thumbnailUrl`, `previewUrl` (piece-level) | Always unset for SPLITS |
+
+## How Core resolves preview URLs
+
+Implementation: [`checkPieceContentStatus.ts`](https://github.com/Sofie-Automation/sofie-core/blob/main/meteor/server/publications/pieceContentStatusUI/checkPieceContentStatus.ts). Helpers: [`splitBoxMedia.ts`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/shared-lib/src/package-manager/splitBoxMedia.ts).
+
+### Via expected packages
+
+Runs when `piece.expectedPackages` is set (same entry point as other layers).
+
+1. For each `MEDIA_FILE` package, resolve thumbnail/preview URLs from Package Manager side effects on routed containers.
+2. Key URLs in memory by normalized `filePath`.
+3. Build `boxPreviews[]` with [`buildPublishedBoxPreviews`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/shared-lib/src/package-manager/splitBoxMedia.ts) (zip to box order).
+4. For any VT box still missing URLs, fill from MediaObjects by `fileName`.
+
+`contentDuration` on status comes from the package scan (same as VT pieces).
+
+### Via MediaObjects only
+
+Runs when the piece has **no** `expectedPackages` but the layer is SPLITS.
+
+1. Collect distinct media ids from VT / LIVE_SPEAK / GRAPHICS / TRANSITION boxes with `fileName`.
+2. Load each MediaObject and build `boxPreviews[]`.
+3. Set `contentDuration` from the longest stream duration found in mediainfo.
+
+Studio setting `mockPieceContentStatus` returns fake per-box URLs when `boxSourceConfiguration` is present (dev only).
+
+## Where previews appear in the WebUI
+
+| Surface | Component | Notes |
+| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
+| Dashboard / bucket / ad-lib buttons | `DashboardPieceButtonSplitPreview` (via `MediaBox`) | Inline box thumbnails when the layout enables thumbnails (buttons, or list with thumbnails enabled) |
+| Hover popup | [`BoxLayoutPreview`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/webui/src/client/ui/PreviewPopUp/Previews/BoxLayoutPreview.tsx) | Shows box thumbnails and preview video; supports scrub when `previewUrl` exists |
+
+These surfaces are **colour-only inline** (no visible box thumbnails), but will still show box media in the hover popup where supported:
+
+- Storyboard thumbnails and secondary pieces
+- Segment timeline SPLITS strip
+- OPL main-piece line
+- Clock view / camera screen indicators
+
+Shared helpers: [`getSplitPreview`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/webui/src/client/lib/ui/splitPreview.ts), [`RenderSplitPreview`](https://github.com/Sofie-Automation/sofie-core/blob/main/packages/webui/src/client/lib/SplitPreviewBox.tsx).
+
+## Example piece
+
+```typescript
+import { SourceLayerType, ExpectedPackage } from '@sofie-automation/blueprints-integration'
+
+const piece = {
+ sourceLayerId: '…', // SPLITS layer
+ content: {
+ boxSourceConfiguration: [
+ {
+ type: SourceLayerType.CAMERA,
+ studioLabel: 'Cam 1',
+ switcherInput: 1,
+ geometry: { x: 0.1, y: 0.2, scale: 0.4 },
+ },
+ {
+ type: SourceLayerType.VT,
+ studioLabel: 'Snow clip',
+ switcherInput: '',
+ fileName: 'clips/head3_Snow.mp4',
+ geometry: {
+ x: 0.35,
+ y: 0.7,
+ scale: 0.3,
+ crop: { left: 7 / 32, top: 0, right: 7 / 32, bottom: 0 },
+ },
+ },
+ ],
+ },
+ expectedPackages: [
+ {
+ _id: 'split_vt_clips_head3_snow',
+ type: ExpectedPackage.PackageType.MEDIA_FILE,
+ content: { filePath: 'clips/head3_Snow.mp4' },
+ version: {
+ /* same as standalone VT pieces */
+ },
+ sources: [
+ /* same as standalone VT pieces */
+ ],
+ },
+ ],
+}
+```
diff --git a/packages/documentation/docs/for-developers/url-query-parameters.md b/packages/documentation/docs/for-developers/url-query-parameters.md
index 3cc86e15a65..b30474946d8 100644
--- a/packages/documentation/docs/for-developers/url-query-parameters.md
+++ b/packages/documentation/docs/for-developers/url-query-parameters.md
@@ -21,5 +21,6 @@ Appending query parameter(s) to the URL will allow you to modify the behaviour o
| `show_hidden_source_layers=1` | _Default value is `0`._ |
| `speak=1` | Experimental feature that starts playing an audible countdown 10 seconds before each planned _Take_. _Default value is `0`._ |
| `vibrate=1` | Experimental feature that triggers the vibration API in the web browser 3 seconds before each planned _Take_. _Default value is `0`._ |
-| `zoom=1,...` | Sets the scaling of the entire GUI. _The unit is a percentage where `100` is the default scaling._ |
+| `zoom=100,...` | Sets the scaling of the entire GUI. _The unit is a percentage where `100` is the default scaling._ **Passing any `zoom` parameter will cause it to be stored in the browser's local storage as `uiZoomLevel` and will then be used for future sessions without notifying the user that they are using the Sofie GUI at a non-standard size!** |
| `hideRundownHeader=1` | Hides header on [Rundown view](../user-guide/features/sofie-views-and-screens#rundown-view) and [Active Rundown screen](../user-guide/features/sofie-views-and-screens#active-rundown-screen). _Default value is `0`._ |
+| `lockView=1` | Locks the [Active Rundown screen](../user-guide/features/sofie-views-and-screens#active-rundown-screen) for unattended or kiosk use: hides exit controls (header close button and context menu “Close Rundown”), disables the in-app navigation confirmation when a rundown is active, and shows the [Screensaver](../user-guide/features/sofie-views-and-screens#screensaver) when no rundown is active instead of a message with a link back to the lobby. Only applies on `/activeRundown/:studioId` routes (ignored on `/rundown/:playlistId`). _Default value is `0`._ |
diff --git a/packages/documentation/docs/user-guide/features/sofie-views-and-screens.mdx b/packages/documentation/docs/user-guide/features/sofie-views-and-screens.mdx
index 328d377d0b9..c5efcb82d3a 100644
--- a/packages/documentation/docs/user-guide/features/sofie-views-and-screens.mdx
+++ b/packages/documentation/docs/user-guide/features/sofie-views-and-screens.mdx
@@ -347,6 +347,20 @@ Example: [http://127.0.0.1/countdowns/studio0/camera?sourceLayerIds=camera0,dve0
A page which automatically displays the currently active rundown. Can be useful for the producer to have on a secondary screen.
+When no rundown is active, a message is shown with a link back to the rundown list.
+
+This screen can be configured using query parameters:
+
+| Query parameter | Type | Description | Default |
+| :-------------- | :---- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ |
+| `lockView` | 0 / 1 | Locks the view for unattended or kiosk displays. Hides the header close button and the context menu “Close Rundown” action, and disables the in-app navigation confirmation when a rundown is active. When no rundown is active, shows the [Screensaver](#screensaver) instead of the default idle message. Does not block browser back, manual URL changes, or closing the tab. Only applies on Active Rundown routes (not on a specific `/rundown/:playlistId` URL). | `0` |
+
+Example (locked Active Rundown for a secondary monitor):
+
+`http://localhost:3000/activeRundown/studio0?lockView=1`
+
+Other query parameters for the rundown view itself (such as `hideRundownHeader` and layout selection) can be combined with `lockView`. See [URL Query Parameters](../../for-developers/url-query-parameters.md).
+
### Active Rundown Shelf Screen
`/activeRundown/:studioId/shelf`
@@ -357,6 +371,10 @@ A screen which automatically displays the currently active rundown, and shows th
A shelf layout can be selected by modifying the query string, see [Shelf Layouts](#shelf-layouts).
+The `lockView` parameter described above also applies to this route. Example:
+
+`http://localhost:3000/activeRundown/studio0/shelf?lockView=1&layout=Stream`
+
### Specific Rundown Shelf Screen
`/rundown/:rundownId/shelf`
@@ -379,7 +397,7 @@ Each embedded screen shows a label to identify it. This screen is mostly intende
### Screensaver
-When big screen displays \(like Prompter Screen and the Presenter Screen\) do not have any meaningful content to show, an animated screensaver showing the current time and the next planned show will be displayed. If no Rundown is upcoming, the Studio name will be displayed.
+When big screen displays \(like Prompter Screen, the Presenter Screen, and the Active Rundown Screen with `lockView=1`\) do not have any meaningful content to show, an animated screensaver showing the current time and the next planned show will be displayed. If no Rundown is upcoming, the Studio name will be displayed.

diff --git a/packages/documentation/docs/user-guide/installation/quick-install.md b/packages/documentation/docs/user-guide/installation/quick-install.md
index d9fc1331d15..2f84c932a8c 100644
--- a/packages/documentation/docs/user-guide/installation/quick-install.md
+++ b/packages/documentation/docs/user-guide/installation/quick-install.md
@@ -48,7 +48,7 @@ services:
core:
hostname: core
- image: sofietv/tv-automation-server-core:release52
+ image: sofietv/tv-automation-server-core:v26.3.0-1
restart: always
ports:
- '3000:3000' # Same port as meteor uses by default
@@ -69,7 +69,7 @@ services:
condition: service_healthy
playout-gateway:
- image: sofietv/tv-automation-playout-gateway:release52
+ image: sofietv/tv-automation-playout-gateway:v26.3.0-1
restart: always
environment:
DEVICE_ID: playoutGateway0
@@ -96,7 +96,7 @@ services:
# - core
# mos-gateway:
- # image: sofietv/tv-automation-mos-gateway:release52
+ # image: sofietv/tv-automation-mos-gateway:v26.3.0-1
# restart: always
# ports:
# - "10540:10540" # MOS Lower port
diff --git a/packages/documentation/package.json b/packages/documentation/package.json
index 5bcc19685cd..c77926093c0 100644
--- a/packages/documentation/package.json
+++ b/packages/documentation/package.json
@@ -18,11 +18,11 @@
"node": ">=22.20.0"
},
"devDependencies": {
- "@docusaurus/core": "3.9.2",
- "@docusaurus/module-type-aliases": "3.9.2",
- "@docusaurus/preset-classic": "3.9.2",
- "@docusaurus/theme-mermaid": "^3.9.2",
- "@docusaurus/types": "3.9.2",
+ "@docusaurus/core": "3.10.0",
+ "@docusaurus/module-type-aliases": "3.10.0",
+ "@docusaurus/preset-classic": "3.10.0",
+ "@docusaurus/theme-mermaid": "^3.10.0",
+ "@docusaurus/types": "3.10.0",
"@mdx-js/react": "^3.1.1",
"@svgr/webpack": "^8.1.0",
"clsx": "^2.1.1",
diff --git a/packages/documentation/releases/releases.mdx b/packages/documentation/releases/releases.mdx
index 487525e51eb..410800c8e3d 100644
--- a/packages/documentation/releases/releases.mdx
+++ b/packages/documentation/releases/releases.mdx
@@ -4,10 +4,30 @@ hide_table_of_contents: true
slug: /
---
+import LatestVersionNumber from '../src/components/LatestVersionNumber.jsx'
+
-import GitHubReleases from '../src/components/GitHubReleases'
+Sofie project is working in a quarterly release cycle. The releases are done at the end of every March, June, September,
+and beginning of December. The main Sofie components do not follow semantic versioning and instead use a `year.month`
+system. For example, the June release of year 2026 will be versioned as `26.06.0`. Fixes for bugs found after a release
+is done are published as patch releases, with the patch number increased for every bugfix.
+
+:::info
+
+The latest version is: ****
+
+
+Use our **[Quick install](/docs/user-guide/installation/quick-install)** guide to install Sofie using **Docker**.
+
+Find Sofie release images on [DockerHub](https://hub.docker.com/u/sofietv) and [GitHub Container Registry](https://github.com/orgs/Sofie-Automation/packages).
+:::
+
+When a version is released, the `HEAD` of the Git repositories is branched off into a new branch called `releases/YY.mm`
+and a Git tag is created for it.
-Current, future, and past releases of _Sofie_ are all tracked on [**our GitHub repository**](https://github.com/Sofie-Automation/Sofie-TV-automation/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3ARelease).
+## Historic versioning
-
+The new versioning system was implemented in March 2026 by the Sofie Technical Steering Committee. All earlier versions follow a different versioning system,
+where `1.xx.xx` is used, with the major field fixed at `1`, the minor field indicating a development cycle of
+variable length, and the patch field used for bugfix releases, starting at `0`.
\ No newline at end of file
diff --git a/packages/documentation/src/components/HomepageFeatures.js b/packages/documentation/src/components/HomepageFeatures.jsx
similarity index 100%
rename from packages/documentation/src/components/HomepageFeatures.js
rename to packages/documentation/src/components/HomepageFeatures.jsx
diff --git a/packages/documentation/src/components/HomepagePRs.css b/packages/documentation/src/components/HomepagePRs.css
new file mode 100644
index 00000000000..5a0ae03a371
--- /dev/null
+++ b/packages/documentation/src/components/HomepagePRs.css
@@ -0,0 +1,109 @@
+.homepage-pr-gallery {
+ margin-top: 2em;
+ margin-bottom: 2em;
+}
+
+.homepage-prs-loading {
+ display: grid;
+ align-items: center;
+ justify-content: center;
+ min-height: 15em;
+ text-align: center;
+ font-size: 1.5em;
+ color: var(--ifm-color-gray-400);
+}
+
+.homepage-prs-error {
+ display: grid;
+ align-items: center;
+ justify-content: center;
+ min-height: 15em;
+ text-align: center;
+ font-size: 1.5em;
+ color: var(--ifm-color-danger);
+}
+
+.homepage-prs-title {
+ text-align: center;
+ font-size: 4em;
+ margin-top: 1em;
+ margin-bottom: 1em;
+ background-clip: text;
+ color: transparent;
+ background-image: linear-gradient(to bottom, var(--ifm-color-primary), var(--ifm-color-primary-darkest));
+}
+
+.homepage-pr-gallery-row {
+ margin-bottom: 1em;
+ overflow-x: hidden;
+}
+
+.homepage-pr-gallery-row-inner {
+ display: flex;
+ flex-direction: row;
+ gap: 1em;
+ opacity: 1;
+}
+
+.homepage-pr-gallery-pull {
+ display: flex;
+ flex-direction: column;
+ border: var(--ifm-global-border-width) solid var(--ifm-color-emphasis-300);
+ border-radius: var(--ifm-global-radius);
+ gap: 0.5em;
+ flex: 0 0 400px;
+ padding: 0.5em;
+}
+
+.homepage-pr-gallery-pull-header {
+ display: flex;
+ align-items: flex-start;
+ gap: 0.5em;
+ max-width: 100%;
+ min-width: 0;
+}
+
+.homepage-pr-gallery-pull-number {
+ color: var(--ifm-color-gray-600);
+}
+
+.homepage-pr-gallery-pull-title {
+ display: inline-block;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 40ch;
+}
+
+.homepage-pr-gallery-pull-title a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.homepage-pr-gallery-pull-title a:hover {
+ color: inherit;
+ text-decoration: underline;
+}
+
+.homepage-pr-gallery-pull-sponsor {
+ align-self: flex-start;
+ background-color: var(--label-color, var(--ifm-color-primary));
+ border-radius: 1em;
+ padding: 0 0.5em;
+ color: contrast-color(var(--label-color, var(--ifm-color-primary)));
+}
+
+.homepage-pr-gallery-pull-author {
+ display: flex;
+ flex-direction: row;
+ gap: 0.25em;
+}
+
+.homepage-pr-gallery-pull-author-avatar {
+ width: 1.5em;
+ height: 1.5em;
+ border-radius: 50%;
+ background-image: var(--user-avatar);
+ background-size: cover;
+ overflow: hidden;
+}
\ No newline at end of file
diff --git a/packages/documentation/src/components/HomepagePRs.jsx b/packages/documentation/src/components/HomepagePRs.jsx
new file mode 100644
index 00000000000..ab99dcf09d3
--- /dev/null
+++ b/packages/documentation/src/components/HomepagePRs.jsx
@@ -0,0 +1,174 @@
+import React, { useEffect, useLayoutEffect, useState, useRef } from "react";
+import './HomepagePRs.css';
+
+const GITHUB_API_URL = 'https://api.github.com'
+
+const GITHUB_ORGANIZATION = "Sofie-Automation"
+const GITHUB_REPO = "sofie-core"
+
+export function HomepagePRs() {
+ const [isReady, setIsReady] = useState("error")
+ const [pulls, setPulls] = useState([])
+
+ useEffect(() => {
+ let mounted = true
+ fetch(`${GITHUB_API_URL}/repos/${GITHUB_ORGANIZATION}/${GITHUB_REPO}/pulls?state=all&sort=updated&direction=desc&per_page=100`, {
+ headers: [['Accept', 'application/vnd.github.text+json']],
+ })
+ .then((value) => {
+ if (value.ok) {
+ return value.json()
+ } else {
+ throw new Error(value.status)
+ }
+ })
+ .then((data) => {
+ if (!mounted) return
+ setPulls(data.filter((pull) => !pull.draft && (pull.state === 'closed' && pull.merged_at || pull.state === 'open')))
+ setIsReady("done")
+ })
+ .catch((error) => {
+ if (!mounted) return
+ console.error(error)
+ setIsReady("error")
+ })
+
+ return () => {
+ mounted = false
+ }
+ }, [])
+
+ return (
+
+
+