From 56edcbcba83b8ff7f6f69ee4231f163e97df3346 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Mon, 6 Jul 2026 23:06:00 +0300 Subject: feat: expose pane scroll state --- docs/next/CHANGELOG.md | 1 + docs/next/api/herdr-api.schema.json | 136 ++++++++++++++++++++- .../website/src/content/docs/cli-reference.mdx | 2 + docs/next/website/src/content/docs/socket-api.mdx | 17 ++- 4 files changed, 154 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 6d2348ae..8701dd24 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -8,6 +8,7 @@ - Added `session.snapshot` to bootstrap client runtime state in one socket API response before subscribing to events. - Added `herdr api schema` to inspect the bundled socket API schema, with `--json` for the full JSON Schema document and `--output PATH` for file output. - Added `layout.updated` socket events so protocol clients can keep tab layout snapshots current after pane split, resize, swap, move, zoom, and layout mutations. +- Added pane scroll metrics to pane socket API responses and `pane.scroll_changed` subscriptions for clients that need to show when a pane is scrolled back. - Added `herdr terminal session observe` for read-only live ANSI terminal streams that bridge processes can consume as newline-delimited JSON. - Added `herdr terminal session control` for bridge processes that need live ANSI frames plus input, resize, scroll, release, and takeover authority. - Added `ui.hide_tab_bar_when_single_tab` to hide the tab row when a workspace has one tab. (#448) diff --git a/docs/next/api/herdr-api.schema.json b/docs/next/api/herdr-api.schema.json index 4b33e750..ef6a6fdb 100644 --- a/docs/next/api/herdr-api.schema.json +++ b/docs/next/api/herdr-api.schema.json @@ -745,6 +745,16 @@ "minimum": 0, "type": "integer" }, + "scroll": { + "anyOf": [ + { + "$ref": "#/schemas/event/$defs/PaneScrollInfo" + }, + { + "type": "null" + } + ] + }, "state_labels": { "additionalProperties": { "type": "string" @@ -897,6 +907,31 @@ ], "type": "object" }, + "PaneScrollInfo": { + "properties": { + "max_offset_from_bottom": { + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "offset_from_bottom": { + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "viewport_rows": { + "format": "uint64", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "offset_from_bottom", + "max_offset_from_bottom", + "viewport_rows" + ], + "type": "object" + }, "SplitDirection": { "enum": [ "right", @@ -3354,6 +3389,22 @@ ], "type": "object" }, + { + "properties": { + "pane_id": { + "type": "string" + }, + "type": { + "const": "pane.scroll_changed", + "type": "string" + } + }, + "required": [ + "type", + "pane_id" + ], + "type": "object" + }, { "properties": { "type": { @@ -5093,6 +5144,50 @@ ], "type": "object" }, + "PaneScrollChangedEvent": { + "properties": { + "pane_id": { + "type": "string" + }, + "scroll": { + "$ref": "#/schemas/subscription_event/$defs/PaneScrollInfo" + }, + "workspace_id": { + "type": "string" + } + }, + "required": [ + "pane_id", + "workspace_id", + "scroll" + ], + "type": "object" + }, + "PaneScrollInfo": { + "properties": { + "max_offset_from_bottom": { + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "offset_from_bottom": { + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "viewport_rows": { + "format": "uint64", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "offset_from_bottom", + "max_offset_from_bottom", + "viewport_rows" + ], + "type": "object" + }, "ReadFormat": { "enum": [ "text", @@ -5116,13 +5211,17 @@ }, { "$ref": "#/schemas/subscription_event/$defs/PaneAgentStatusChangedEvent" + }, + { + "$ref": "#/schemas/subscription_event/$defs/PaneScrollChangedEvent" } ] }, "SubscriptionEventKind": { "enum": [ "pane.output_matched", - "pane.agent_status_changed" + "pane.agent_status_changed", + "pane.scroll_changed" ], "type": "string" } @@ -6377,6 +6476,16 @@ "minimum": 0, "type": "integer" }, + "scroll": { + "anyOf": [ + { + "$ref": "#/schemas/success_response/$defs/PaneScrollInfo" + }, + { + "type": "null" + } + ] + }, "state_labels": { "additionalProperties": { "type": "string" @@ -6811,6 +6920,31 @@ ], "type": "object" }, + "PaneScrollInfo": { + "properties": { + "max_offset_from_bottom": { + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "offset_from_bottom": { + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "viewport_rows": { + "format": "uint64", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "offset_from_bottom", + "max_offset_from_bottom", + "viewport_rows" + ], + "type": "object" + }, "PaneSwapReason": { "enum": [ "no_neighbor", diff --git a/docs/next/website/src/content/docs/cli-reference.mdx b/docs/next/website/src/content/docs/cli-reference.mdx index a6870fff..81fe4dce 100644 --- a/docs/next/website/src/content/docs/cli-reference.mdx +++ b/docs/next/website/src/content/docs/cli-reference.mdx @@ -224,6 +224,8 @@ herdr pane report-agent \ Those commands include `foreground_cwd` when Herdr can resolve the cwd of the foreground process controlling the pane. The existing `cwd` field remains the pane/workspace cwd used for labels and follow-cwd behavior. +`pane get` and `pane list` include `scroll` when terminal scroll metrics are available. `scroll.offset_from_bottom == 0` means the pane is at the bottom of its scrollback. + Report display-only pane metadata without taking over semantic state: ```bash diff --git a/docs/next/website/src/content/docs/socket-api.mdx b/docs/next/website/src/content/docs/socket-api.mdx index 64c5ac4e..600238b9 100644 --- a/docs/next/website/src/content/docs/socket-api.mdx +++ b/docs/next/website/src/content/docs/socket-api.mdx @@ -150,6 +150,18 @@ like `ctrl+h`, `control+j`, `alt+x`, and `shift+tab`, function keys like Herdr returns that pane. When it is omitted, Herdr returns the active focused pane. +`PaneInfo` includes `scroll` when terminal scroll metrics are available: + +```json +{ + "offset_from_bottom": 12, + "max_offset_from_bottom": 240, + "viewport_rows": 30 +} +``` + +Clients can treat `offset_from_bottom == 0` as at-bottom state. + `pane.layout` returns the tab layout snapshot with `workspace_id`, `tab_id`, `zoomed`, outer `area`, `focused_pane_id`, pane rects, and split rects/ratios. `pane.neighbor` and `pane.edges` include that same layout snapshot so clients @@ -630,7 +642,10 @@ Tab event subscriptions include `tab.created`, `tab.closed`, `tab.focused`, that workspace. Pane event subscriptions include `pane.created`, `pane.closed`, `pane.focused`, `pane.moved`, `pane.exited`, `pane.agent_detected`, -`pane.output_matched`, and `pane.agent_status_changed`. +`pane.output_matched`, `pane.agent_status_changed`, and `pane.scroll_changed`. +`pane.scroll_changed` is scoped to one `pane_id` and emits `pane_id`, +`workspace_id`, and the current `scroll` metrics whenever Herdr observes a +changed scroll snapshot. Layout event subscriptions include `layout.updated`. The event carries the updated `PaneLayoutSnapshot` for one tab. Clients that bootstrap with `session.snapshot` should replace the cached layout with the same -- cgit v1.2.3