tmux-ide 2.9.0-beta.44 → 2.9.0-beta.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/bin/cli.js +2 -2
  2. package/package.json +2 -2
  3. package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +1 -1
  4. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +1 -1
  5. package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +53 -18
  6. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +33 -2
  7. package/packages/daemon/dist/tui/mirror/ui/agent-row.jsx +26 -6
  8. package/packages/daemon/dist/tui/mirror/ui/pane-interaction-presentation.js +72 -0
  9. package/packages/daemon/dist/tui/mirror/ui/pane-interaction.jsx +76 -0
  10. package/packages/daemon/dist/tui/mirror/ui/pane-mode-control.jsx +23 -0
  11. package/packages/daemon/dist/tui/mirror/ui/status-presentation.js +41 -0
  12. package/packages/daemon/dist/tui/mirror/workspace/terminal-pane-header.jsx +35 -54
  13. package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +4 -4
  14. package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +5 -0
  15. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +8 -0
  16. package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +89 -26
  17. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +32 -0
  18. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +56 -2
  19. package/packages/daemon/src/tui/mirror/ui/agent-row.tsx +39 -11
  20. package/packages/daemon/src/tui/mirror/ui/pane-interaction-presentation.ts +90 -0
  21. package/packages/daemon/src/tui/mirror/ui/pane-interaction.tsx +127 -0
  22. package/packages/daemon/src/tui/mirror/ui/pane-mode-control.tsx +47 -0
  23. package/packages/daemon/src/tui/mirror/ui/status-presentation.ts +54 -0
  24. package/packages/daemon/src/tui/mirror/workspace/terminal-pane-header.tsx +56 -68
  25. package/packages/daemon/dist/tui/mirror/ui/detail-row.jsx +0 -22
package/bin/cli.js CHANGED
@@ -11279,7 +11279,7 @@ var require_package = __commonJS({
11279
11279
  "package.json"(exports, module) {
11280
11280
  module.exports = {
11281
11281
  name: "tmux-ide",
11282
- version: "2.9.0-beta.44",
11282
+ version: "2.9.0-beta.45",
11283
11283
  description: "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
11284
11284
  type: "module",
11285
11285
  bin: {
@@ -11343,7 +11343,7 @@ var require_package = __commonJS({
11343
11343
  postinstall: "node scripts/postinstall.js",
11344
11344
  docs: "turbo run dev --filter=@tmux-ide/docs",
11345
11345
  "demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
11346
- "test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
11346
+ "test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/pane-interaction-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
11347
11347
  "test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
11348
11348
  "test:tui-live": "node scripts/tui-testdrive.mjs smoke",
11349
11349
  "test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-ide",
3
- "version": "2.9.0-beta.44",
3
+ "version": "2.9.0-beta.45",
4
4
  "description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,7 +64,7 @@
64
64
  "postinstall": "node scripts/postinstall.js",
65
65
  "docs": "turbo run dev --filter=@tmux-ide/docs",
66
66
  "demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
67
- "test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
67
+ "test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/pane-interaction-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
68
68
  "test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
69
69
  "test:tui-live": "node scripts/tui-testdrive.mjs smoke",
70
70
  "test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
@@ -191,7 +191,7 @@ export function HomeAgentRoster(props) {
191
191
  return (<box height={rowHeight()} width={width()} flexShrink={0} onMouseOver={() => setHovered(key)} onMouseMove={() => setHovered(key)}>
192
192
  <AgentRow surface="canvas" theme={props.theme} id={`home-agent:${key}`} name={row().name} context={[row().machineLabel, row().serverLabel, row().sessionName]
193
193
  .filter(Boolean)
194
- .join(" · ")} width={width()} compact={rowHeight() === 1} activity={row().activity} attention={row().attention} unavailable={row().paneId === null || stale(row())} selected={props.selection.selectedKey === key} focused={props.inputActive && !editing() && props.selection.selectedKey === key} hovered={hovered() === key} onOpen={(source) => {
194
+ .join(" · ")} width={width()} compact={rowHeight() === 1} interaction={props.interactionForAgent?.(row())} paneName={props.paneName} activity={row().activity} attention={row().attention} unavailable={row().paneId === null || stale(row())} selected={props.selection.selectedKey === key} focused={props.inputActive && !editing() && props.selection.selectedKey === key} hovered={hovered() === key} onOpen={(source) => {
195
195
  if (!props.inputActive || row().paneId === null || stale(row()))
196
196
  return;
197
197
  props.onSelect(key);
@@ -380,7 +380,7 @@ export function ApplicationMachineSidebar(props) {
380
380
  friendlySessionLabel(agent().sessionName),
381
381
  ]
382
382
  .filter(Boolean)
383
- .join(" · ")} width={Math.max(1, props.width - 1)} activity={agent().activity} attention={agent().attention} unavailable={row.group.state !== "ready" ||
383
+ .join(" · ")} width={Math.max(1, props.width - 1)} interaction={props.interactionForAgent?.(agent())} paneName={props.paneName} activity={agent().activity} attention={agent().attention} unavailable={row.group.state !== "ready" ||
384
384
  row.group.agentsAvailable === false ||
385
385
  !!agent().disabled ||
386
386
  !agent().paneId} selected={active(row)} focused={focused() && row.key === selectedKey()} onOpen={(source) => activate(row, source)}/>)}
@@ -1,10 +1,10 @@
1
- import { interactionReceiptTargetLabel } from "@tmux-ide/core";
2
- import { For, Show, createSignal } from "solid-js";
1
+ import { For, Show, createEffect, createSignal } from "solid-js";
3
2
  import { clipTerminal, terminalDisplayWidth } from "../terminal-text.js";
4
3
  import { SectionHeading } from "../ui/section-heading.jsx";
5
4
  import { KeyHint } from "../ui/key-hint.jsx";
6
5
  import { CHROME_ACTIONS, HOME_ACTIONS } from "../workspace/application-action-descriptions.js";
7
- import { DetailRow } from "../ui/detail-row.jsx";
6
+ import { PaneInteraction, PaneInteractionDetails } from "../ui/pane-interaction.jsx";
7
+ import { receiptPaneInteraction, } from "../ui/pane-interaction-presentation.js";
8
8
  import { TuiButton } from "../ui/button.jsx";
9
9
  import { HomeAgentRoster } from "./application-home-agent-roster.jsx";
10
10
  import { APPLICATION_HOME_WORDMARK, APPLICATION_HOME_WORDMARK_WIDTH } from "../ui/home-wordmark.js";
@@ -14,6 +14,7 @@ export function applicationHomeBrandVariant(width, height) {
14
14
  }
15
15
  /** Presentation only: session data, commands, and keyboard admission stay with the shell. */
16
16
  export function ApplicationHomeSurface(props) {
17
+ const [inspection, setInspection] = createSignal(null);
17
18
  const [tipHidden, setTipHidden] = createSignal(false);
18
19
  const width = () => Math.max(0, Math.floor(props.width));
19
20
  const height = () => Math.max(0, Math.floor(props.height));
@@ -75,14 +76,22 @@ export function ApplicationHomeSurface(props) {
75
76
  return [];
76
77
  return (props.recentPaneActivity ?? []).filter((receipt) => receipt.workspaceName === selected.sessionName &&
77
78
  receipt.target.kind === "pane" &&
78
- receipt.target.semanticPaneId === selected.paneId);
79
+ (receipt.target.semanticPaneId === selected.paneId ||
80
+ (receipt.phase === "observed" && receipt.sourceSemanticPaneId === selected.paneId)));
79
81
  };
80
82
  const recentActivity = () => selectedActivity().slice(0, spacious()
81
83
  ? Math.max(0, Math.min(1, Math.floor((height() - reservedRows() - (props.agentRoster ? 10 : 2) - 2) / activityRows())))
82
84
  : 0);
83
85
  const activityHeight = () => recentActivity().length > 0 ? recentActivity().length * activityRows() + 2 : 0;
86
+ createEffect(() => {
87
+ const value = inspection();
88
+ if (value && value.key !== `${props.activityDaemonId}:${selectedAgent()?.key}`)
89
+ setInspection(null);
90
+ });
84
91
  const paneLabel = (paneId) => {
85
- const matches = props.agentRoster?.rows.filter((row) => row.paneId === paneId) ?? [];
92
+ const matches = props.agentRoster?.rows.filter((row) => row.paneId === paneId &&
93
+ row.daemonInstanceId === props.activityDaemonId &&
94
+ row.sessionName === selectedAgent()?.sessionName) ?? [];
86
95
  return matches.length === 1 ? matches[0].name : paneId;
87
96
  };
88
97
  const activityTime = (receipt) => {
@@ -91,17 +100,17 @@ export function ApplicationHomeSurface(props) {
91
100
  ? `${new Date(at).toISOString().slice(5, 16).replace("T", " ")}Z`
92
101
  : "Time unknown";
93
102
  };
94
- const activityPhase = (receipt) => receipt.phase === "accepted"
95
- ? receipt.operationKind === "workspace.pane.read"
96
- ? "reading"
97
- : "sending"
98
- : receipt.phase === "observed"
99
- ? receipt.operationKind === "workspace.pane.read"
100
- ? "read"
101
- : "sent"
102
- : receipt.phase === "rejected"
103
- ? "failed"
104
- : "timed out";
103
+ const inspect = (event) => {
104
+ const names = new Map();
105
+ for (const id of [event.sourcePaneId, event.destinationPaneId])
106
+ if (id)
107
+ names.set(id, paneLabel(id));
108
+ setInspection({
109
+ event: { ...event },
110
+ names,
111
+ key: `${props.activityDaemonId}:${selectedAgent()?.key}`,
112
+ });
113
+ };
105
114
  const rosterHeight = () => Math.max(0, height() - reservedRows() - activityHeight());
106
115
  return (<box id="application-home" width={width()} height={height()} paddingLeft={inset()} paddingRight={inset()} paddingTop={spacious() ? 1 : 0} flexDirection="column" alignItems="flex-start" backgroundColor={props.theme.roles.surfaces.canvas} overflow="hidden">
107
116
  <box height={brandRows()} width={bodyWidth()} flexShrink={0} flexDirection="column">
@@ -134,7 +143,19 @@ export function ApplicationHomeSurface(props) {
134
143
  {clipTerminal(summary(), bodyWidth())}
135
144
  </text>
136
145
  </>}>
137
- {(snapshot) => (<HomeAgentRoster fitContent query={props.agentQuery} onQueryChange={props.onAgentQueryChange} filterLabel={props.agentFilterLabel} activityFilter={props.agentActivityFilter} onSetActivityFilter={props.onSetAgentActivityFilter} onCycleMachine={props.onCycleAgentMachine} onToggleAttention={props.onToggleAgentAttention} theme={props.theme} width={bodyWidth()} height={rosterHeight()} snapshot={snapshot()} selection={props.agentSelection ?? { selectedKey: null, scrollOffset: 0 }} inputActive={props.agentInputActive ?? false} onSelect={(key) => props.onSelectAgent?.(key)} onMove={(delta) => props.onMoveAgent?.(delta)} onViewport={(rows) => props.onAgentViewport?.(rows)} onOpen={(row, source) => props.onOpenAgent?.(row, source)} onRetry={props.onRetryAgents} onLoadMore={props.onLoadMoreAgents}/>)}
146
+ {(snapshot) => (<HomeAgentRoster fitContent query={props.agentQuery} onQueryChange={props.onAgentQueryChange} filterLabel={props.agentFilterLabel} activityFilter={props.agentActivityFilter} onSetActivityFilter={props.onSetAgentActivityFilter} onCycleMachine={props.onCycleAgentMachine} onToggleAttention={props.onToggleAgentAttention} theme={props.theme} width={bodyWidth()} height={rosterHeight()} paneName={paneLabel} interactionForAgent={(row) => {
147
+ if (!row.paneId || row.daemonInstanceId !== props.activityDaemonId)
148
+ return undefined;
149
+ const matches = props.agentRoster?.rows.filter((other) => other.paneId === row.paneId && other.sessionName === row.sessionName);
150
+ if (matches?.length !== 1)
151
+ return undefined;
152
+ const event = props.paneInteractions?.get(row.paneId);
153
+ return event &&
154
+ props.recentPaneActivity?.some((receipt) => receipt.operationId === event.operationId &&
155
+ receipt.workspaceName === row.sessionName)
156
+ ? event
157
+ : undefined;
158
+ }} snapshot={snapshot()} selection={props.agentSelection ?? { selectedKey: null, scrollOffset: 0 }} inputActive={(props.agentInputActive ?? false) && !inspection()} onSelect={(key) => props.onSelectAgent?.(key)} onMove={(delta) => props.onMoveAgent?.(delta)} onViewport={(rows) => props.onAgentViewport?.(rows)} onOpen={(row, source) => props.onOpenAgent?.(row, source)} onRetry={props.onRetryAgents} onLoadMore={props.onLoadMoreAgents}/>)}
138
159
  </Show>
139
160
  <Show when={activityHeight() > 0}>
140
161
  <box width={bodyWidth()} height={activityHeight()} flexShrink={0} flexDirection="column" overflow="hidden">
@@ -142,7 +163,16 @@ export function ApplicationHomeSurface(props) {
142
163
  {clipTerminal(`${selectedAgent()?.name ?? "Agent"} · latest activity`, bodyWidth())}
143
164
  </text>
144
165
  <For each={recentActivity()}>
145
- {(receipt) => (<DetailRow theme={props.theme} width={bodyWidth()} label={interactionReceiptTargetLabel(receipt, paneLabel)} detail={activityPhase(receipt)} description={activityRows() === 2 ? activityTime(receipt) : undefined} attention={receipt.phase === "rejected" || receipt.phase === "timed-out"}/>)}
166
+ {(receipt) => (<box height={activityRows()} width={bodyWidth()} flexDirection="column">
167
+ <Show when={receiptPaneInteraction(receipt, selectedAgent()?.paneId)} keyed>
168
+ {(event) => (<PaneInteraction theme={props.theme} event={event} paneName={paneLabel} width={bodyWidth()} onDetails={() => inspect(event)}/>)}
169
+ </Show>
170
+ <Show when={activityRows() === 2}>
171
+ <text height={1} fg={props.theme.roles.text.muted}>
172
+ {activityTime(receipt)}
173
+ </text>
174
+ </Show>
175
+ </box>)}
146
176
  </For>
147
177
  <text height={1} width={bodyWidth()} fg={props.theme.roles.text.muted}>
148
178
  {clipTerminal("Activity reported through tmux-ide", bodyWidth())}
@@ -179,5 +209,10 @@ export function ApplicationHomeSurface(props) {
179
209
  {clipTerminal(note(), bodyWidth())}
180
210
  </text>)}
181
211
  </Show>
212
+ <Show when={inspection()} keyed>
213
+ {(value) => (<Show when={value.key === `${props.activityDaemonId}:${selectedAgent()?.key}`}>
214
+ <PaneInteractionDetails theme={props.theme} event={value.event} paneName={(id) => value.names.get(id)} width={bodyWidth()} viewportWidth={props.width} viewportHeight={props.height} onDismiss={() => setInspection(null)}/>
215
+ </Show>)}
216
+ </Show>
182
217
  </box>);
183
218
  }
@@ -1,3 +1,4 @@
1
+ import { PaneInteractionDetails } from "../ui/pane-interaction.jsx";
1
2
  import { Menu } from "../ui/index.js";
2
3
  import { windowLinkTarget } from "./application-terminal-workspace-policy.js";
3
4
  /* @jsxImportSource @opentui/solid */
@@ -252,6 +253,27 @@ export function ApplicationTerminalWorkspace(props) {
252
253
  },
253
254
  });
254
255
  const paneContextMenu = paneMenu.state;
256
+ const [interactionDetails, setInteractionDetails] = createSignal(null);
257
+ const paneName = (id) => props.agentIndicators?.().get(id)?.name ?? id;
258
+ const inspectInteraction = (paneId) => {
259
+ const event = props.paneInteractions?.().get(paneId);
260
+ if (!event)
261
+ return;
262
+ paneMenu.dismiss();
263
+ const names = new Map();
264
+ for (const id of [event.sourcePaneId, event.destinationPaneId])
265
+ if (id)
266
+ names.set(id, paneName(id));
267
+ setInteractionDetails({ event: { ...event }, names, paneId, epoch: props.rendererEpoch });
268
+ };
269
+ createEffect(() => {
270
+ const details = interactionDetails();
271
+ if (details &&
272
+ (details.epoch !== props.rendererEpoch ||
273
+ props.interactive === false ||
274
+ !projectedFrames().some((frame) => frame.visible && frame.paneId === details.paneId)))
275
+ setInteractionDetails(null);
276
+ });
255
277
  let selecting = null;
256
278
  let linkPointer = false;
257
279
  let liveReturnPointer = false;
@@ -605,6 +627,11 @@ export function ApplicationTerminalWorkspace(props) {
605
627
  const handlePaneMenuKey = (name, event) => {
606
628
  if (props.interactive === false)
607
629
  return false;
630
+ if (interactionDetails()) {
631
+ if (name === "escape" && event?.eventType !== "release")
632
+ setInteractionDetails(null);
633
+ return true;
634
+ }
608
635
  if (windowLinkMenu()) {
609
636
  if (event?.eventType === "release")
610
637
  return true;
@@ -769,6 +796,7 @@ export function ApplicationTerminalWorkspace(props) {
769
796
  }, () => props.interactive !== false &&
770
797
  (drag !== null ||
771
798
  windowLinkMenu() !== null ||
799
+ interactionDetails() !== null ||
772
800
  paneMenu.ownsInput() ||
773
801
  keyboardCopy() !== null), () => {
774
802
  // Called only after global shortcuts, copy, and local navigation decline
@@ -789,7 +817,7 @@ export function ApplicationTerminalWorkspace(props) {
789
817
  const routePointer = (event) => {
790
818
  if (event.type === "down")
791
819
  wheelGesture.reset();
792
- if (windowLinkMenu() || paneMenu.ownsInput()) {
820
+ if (interactionDetails() || windowLinkMenu() || paneMenu.ownsInput()) {
793
821
  event.stopPropagation?.();
794
822
  return;
795
823
  }
@@ -1284,7 +1312,7 @@ export function ApplicationTerminalWorkspace(props) {
1284
1312
  ].join(":");
1285
1313
  });
1286
1314
  return (<box position="absolute" left={frame().left} top={frame().top + topOffset()} width={frame().width} height={frame().height} visible={frame().visible} backgroundColor={props.theme.roles.surfaces.canvas} onMouse={routePointer}>
1287
- <PaneTitleBar theme={props.theme} paneId={frame().paneId} title={`${frame().compactPosition ? `Compact ${frame().compactPosition} · Ctrl+O: next · ` : ""}${displayTitle()}${scrollback.offset(frame().paneId) > 0 ? ` ↑${scrollback.offset(frame().paneId)} · Esc: live` : ""}`} zoomed={layout().windows.some((window) => window.zoomed && window.panes.some((pane) => pane.pane === frame().paneId))} onRestoreIntent={() => props.onPaneContextAction?.(frame().paneId, "zoom-pane", displayTitle())} width={frame().width} selected={props.focusedPane === frame().paneId} terminalFocused={terminalSurfaceFocused(frame())} keyboardFocused={props.focusedPane === frame().paneId} menuOpen={paneContextMenu()?.paneId === frame().paneId} interaction={props.paneInteractions?.().get(frame().paneId)} activity={indicator()?.activity} attention={indicator()?.attention} menuAnchor={{
1315
+ <PaneTitleBar theme={props.theme} paneId={frame().paneId} title={`${frame().compactPosition ? `Compact ${frame().compactPosition} · Ctrl+O: next · ` : ""}${displayTitle()}`} connectionStatus={props.connectionStatus} scrollback={scrollback.offset(frame().paneId) > 0} linesAboveLive={scrollback.offset(frame().paneId)} onBackToLiveIntent={() => scrollback.live(frame().paneId)} zoomed={layout().windows.some((window) => window.zoomed && window.panes.some((pane) => pane.pane === frame().paneId))} onRestoreIntent={() => props.onPaneContextAction?.(frame().paneId, "zoom-pane", displayTitle())} width={frame().width} selected={props.focusedPane === frame().paneId} terminalFocused={terminalSurfaceFocused(frame())} keyboardFocused={props.focusedPane === frame().paneId} menuOpen={paneContextMenu()?.paneId === frame().paneId} interaction={props.paneInteractions?.().get(frame().paneId)} paneName={paneName} onInteractionDetails={() => inspectInteraction(frame().paneId)} activity={indicator()?.activity} attention={indicator()?.attention} menuAnchor={{
1288
1316
  x: (props.originX ?? 0) + frame().left + Math.max(0, frame().width - 1),
1289
1317
  y: (props.originY ?? 0) + frame().top + topOffset(),
1290
1318
  }} onSelectIntent={() => props.onSelectPane(frame().paneId)} onMenuIntent={(anchor) => openPaneContextMenu(frame().paneId, anchor)}/>
@@ -1325,6 +1353,9 @@ export function ApplicationTerminalWorkspace(props) {
1325
1353
  <Show when={windowLinkMenu()}>
1326
1354
  <Menu theme={props.theme} viewportOrigin={{ x: props.originX ?? 0, y: props.originY ?? 0 }} title="Window link" left={0} top={1} width={Math.min(36, props.width)} viewportWidth={props.width} viewportHeight={props.height} selectedId="unlink" items={[{ id: "unlink", label: "Unlink this tab", shortcut: "U" }]} footer="Other links keep their panes" onDismiss={() => setWindowLinkMenu(null)} onSelect={unlinkWindowTab}/>
1327
1355
  </Show>
1356
+ <Show when={interactionDetails()} keyed>
1357
+ {(details) => (<PaneInteractionDetails theme={props.theme} event={details.event} paneName={(id) => details.names.get(id)} width={props.width} viewportWidth={props.width} viewportHeight={props.height} viewportOrigin={{ x: props.originX ?? 0, y: props.originY ?? 0 }} onDismiss={() => setInteractionDetails(null)}/>)}
1358
+ </Show>
1328
1359
  <Show when={paneContextMenu()}>
1329
1360
  {(menu) => (<PaneActionMenu theme={props.theme} left={menu().left} top={menu().top} width={paneContextMenuWidth()} viewportWidth={props.width} viewportHeight={props.height + topOffset()} viewportOrigin={{ x: props.originX ?? 0, y: props.originY ?? 0 }} paneTitle={menu().displayName} active onDismiss={paneMenu.dismiss} selectedId={menu().selectedId} closeArmed={menu().closeArmed} onHighlight={paneMenu.highlight} onActionIntent={paneMenu.activate}/>)}
1330
1361
  </Show>
@@ -1,14 +1,27 @@
1
+ import { createPaneInteractionMarker } from "./pane-interaction.jsx";
2
+ import { paneInteractionPresentation, } from "./pane-interaction-presentation.js";
1
3
  import { Show } from "solid-js";
2
4
  import { clipTerminal } from "../terminal-text.js";
3
5
  import { NavigationRow } from "./navigation-row.jsx";
4
6
  import { createAgentStatusMarker } from "./agent-status-marker.js";
7
+ import { statusPresentation } from "./status-presentation.js";
5
8
  import { componentPalette } from "./state.js";
6
9
  /** One agent identity and one state marker, shared by Home and fleet navigation. */
7
10
  export function AgentRow(props) {
11
+ const status = () => statusPresentation(props);
12
+ const receipt = () => !props.unavailable &&
13
+ props.activity !== "disconnected" &&
14
+ !props.attention &&
15
+ props.activity !== "waiting" &&
16
+ props.activity !== "failed" &&
17
+ props.interaction
18
+ ? paneInteractionPresentation(props.interaction, props.paneName)
19
+ : undefined;
20
+ const receiptMarker = createPaneInteractionMarker(() => (receipt() ? props.interaction : undefined), () => props.theme);
8
21
  const marker = createAgentStatusMarker({
9
22
  theme: () => props.theme,
10
- status: () => props.activity,
11
- attention: () => !!props.attention,
23
+ status: () => (receipt() ? undefined : status()?.activity),
24
+ attention: () => status()?.activity === "waiting",
12
25
  unavailable: () => !!props.unavailable,
13
26
  });
14
27
  const palette = () => componentPalette(props.theme, {
@@ -20,7 +33,6 @@ export function AgentRow(props) {
20
33
  const background = () => palette().state === "base" && props.surface === "canvas"
21
34
  ? props.theme.roles.surfaces.canvas
22
35
  : palette().background;
23
- const exceptional = () => props.unavailable ? "unavailable" : props.activity === "disconnected" ? "unknown" : undefined;
24
36
  return (<box width={props.width} height={props.compact ? 1 : 2} flexShrink={0} flexDirection="column" backgroundColor={background()} onMouseDown={(event) => {
25
37
  if (event.button !== 0 || props.unavailable)
26
38
  return;
@@ -28,9 +40,17 @@ export function AgentRow(props) {
28
40
  event.stopPropagation();
29
41
  props.onOpen("mouse");
30
42
  }}>
31
- <NavigationRow surface={props.surface} theme={props.theme} id={props.id} width={props.width} label={props.name} marker={props.activity === "idle" && !props.attention && !props.unavailable ? " " : marker()} detail={exceptional()} selected={props.selected} focused={props.focused} hovered={props.hovered} disabled={props.unavailable} tone={props.attention || props.activity === "waiting" || props.activity === "failed"
32
- ? "warning"
33
- : "neutral"} onActivate={props.onOpen}/>
43
+ <NavigationRow surface={props.surface} theme={props.theme} id={props.id} width={props.width} label={props.name} marker={receipt()
44
+ ? receiptMarker()
45
+ : props.activity === "idle" && !props.attention && !props.unavailable
46
+ ? " "
47
+ : marker()} detail={props.width < 24
48
+ ? undefined
49
+ : receipt()
50
+ ? clipTerminal(receipt().compactLabel, Math.max(0, props.width - 14))
51
+ : status()?.label === "Idle"
52
+ ? undefined
53
+ : status()?.label} selected={props.selected} focused={props.focused} hovered={props.hovered} disabled={props.unavailable} tone={status()?.tone === "blocked" ? "warning" : "neutral"} onActivate={props.onOpen}/>
34
54
  <Show when={!props.compact}>
35
55
  <text height={1} width={props.width} fg={props.selected ? palette().foreground : props.theme.roles.text.muted}>
36
56
  {clipTerminal(` ${props.context}`, props.width)}
@@ -0,0 +1,72 @@
1
+ export function receiptPaneInteraction(receipt, viewingPaneId) {
2
+ if (receipt.target.kind !== "pane" ||
3
+ !["workspace.pane.read", "workspace.pane.send"].includes(receipt.operationKind))
4
+ return null;
5
+ return {
6
+ direction: viewingPaneId &&
7
+ receipt.sourceSemanticPaneId === viewingPaneId &&
8
+ receipt.target.semanticPaneId !== viewingPaneId
9
+ ? "outgoing"
10
+ : "incoming",
11
+ operationId: receipt.operationId,
12
+ operationKind: receipt.operationKind,
13
+ phase: receipt.phase,
14
+ origin: receipt.origin,
15
+ sourcePaneId: receipt.sourceSemanticPaneId,
16
+ destinationPaneId: receipt.target.semanticPaneId,
17
+ at: receipt.at,
18
+ };
19
+ }
20
+ export function paneInteractionPresentation(event, name = () => undefined) {
21
+ const read = event.operationKind === "workspace.pane.read";
22
+ // An accepted request is not an authenticated actor or proof of delivery.
23
+ const source = event.phase === "observed" && event.sourcePaneId ? name(event.sourcePaneId) : undefined;
24
+ const target = name(event.destinationPaneId) ?? "Pane";
25
+ const pending = event.phase === "accepted";
26
+ const failed = event.phase === "rejected" || event.phase === "timed-out";
27
+ const label = failed
28
+ ? `${read ? "Read" : "Send"} ${event.phase === "timed-out" ? "timed out" : "failed"}`
29
+ : pending
30
+ ? read
31
+ ? "Read requested"
32
+ : "Input pending"
33
+ : event.direction === "outgoing"
34
+ ? read
35
+ ? `Read ${target}`
36
+ : `Input sent to ${target}`
37
+ : read
38
+ ? source
39
+ ? `Read by ${source}`
40
+ : "Pane read · reader unknown"
41
+ : source
42
+ ? `Input from ${source}`
43
+ : "Input observed · sender unknown";
44
+ return {
45
+ label,
46
+ compactLabel: !pending && !failed && !source && event.direction !== "outgoing"
47
+ ? read
48
+ ? "Pane read"
49
+ : "Input observed"
50
+ : label,
51
+ source: source ?? (pending ? "Not yet verified" : "Unknown"),
52
+ target,
53
+ pending,
54
+ failed,
55
+ explanation: failed
56
+ ? "The operation did not complete successfully."
57
+ : pending
58
+ ? "Request accepted. Completion and source attribution are not yet confirmed."
59
+ : read
60
+ ? "Pane output was read. This does not prove comprehension or change your focus."
61
+ : "Input delivery was observed. This does not mean the application processed it.",
62
+ phase: pending
63
+ ? "Requested"
64
+ : failed
65
+ ? event.phase === "timed-out"
66
+ ? "Timed out"
67
+ : "Failed"
68
+ : read
69
+ ? "Read completed"
70
+ : "Input delivered",
71
+ };
72
+ }
@@ -0,0 +1,76 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import { createEffect, createSignal, onCleanup, Show } from "solid-js";
3
+ import { INTERACTION_PRESENCE_MS } from "@tmux-ide/core";
4
+ import { Badge } from "./badge.jsx";
5
+ import { TuiButton } from "./button.jsx";
6
+ import { createAgentStatusMarker } from "./agent-status-marker.js";
7
+ import { OverlayFrame } from "./overlay-frame.jsx";
8
+ import { useKeyboardRoute, usePasteRoute } from "./keyboard-router.jsx";
9
+ import { paneInteractionPresentation, } from "./pane-interaction-presentation.js";
10
+ export function createPaneInteractionMarker(event, theme) {
11
+ const [animate, setAnimate] = createSignal(false);
12
+ createEffect(() => {
13
+ const currentEvent = event();
14
+ setAnimate(false);
15
+ if (!currentEvent || currentEvent.phase !== "accepted" || theme().accessibility.reducedMotion)
16
+ return;
17
+ const age = Date.now() - Date.parse(currentEvent.at);
18
+ if (!Number.isFinite(age) || age < 0 || age >= INTERACTION_PRESENCE_MS)
19
+ return;
20
+ // Fast operations never acquire the spinner; old history never looks live.
21
+ const start = setTimeout(() => setAnimate(true), Math.max(0, 150 - age));
22
+ const end = setTimeout(() => setAnimate(false), INTERACTION_PRESENCE_MS - age);
23
+ onCleanup(() => {
24
+ clearTimeout(start);
25
+ clearTimeout(end);
26
+ });
27
+ });
28
+ const marker = createAgentStatusMarker({
29
+ theme: () => theme(),
30
+ status: () => (animate() ? "running" : undefined),
31
+ });
32
+ return () => {
33
+ const current = event();
34
+ if (!current)
35
+ return "";
36
+ const value = paneInteractionPresentation(current);
37
+ return value.failed ? "!" : value.pending ? (animate() ? marker() : "↳") : "✓";
38
+ };
39
+ }
40
+ /** The same receipt vocabulary and shared animation clock in chrome and Home. */
41
+ export function PaneInteraction(props) {
42
+ const value = () => paneInteractionPresentation(props.event, props.paneName);
43
+ const marker = createPaneInteractionMarker(() => props.event, () => props.theme);
44
+ const detailsWidth = () => (props.onDetails && props.width >= 32 ? 9 : 0);
45
+ return (<box width={props.width} height={1} flexShrink={0} flexDirection="row">
46
+ <Badge theme={props.theme} width={Math.max(1, props.width - detailsWidth())} label={value().label} marker={marker()} tone={value().failed ? "warning" : value().pending ? "accent" : "done"}/>
47
+ <Show when={detailsWidth()}>
48
+ <TuiButton theme={props.theme} label="Details" size="compact" width={9} onPress={props.onDetails}/>
49
+ </Show>
50
+ </box>);
51
+ }
52
+ export function PaneInteractionDetails(props) {
53
+ const value = () => paneInteractionPresentation(props.event, props.paneName);
54
+ useKeyboardRoute((event) => {
55
+ if (event.eventType === "press" && event.name === "escape")
56
+ props.onDismiss();
57
+ return true;
58
+ });
59
+ usePasteRoute(() => true);
60
+ return (<OverlayFrame theme={props.theme} viewportWidth={props.viewportWidth} viewportHeight={props.viewportHeight} viewportOrigin={props.viewportOrigin} width={68} height={19} title="Pane interaction" surface modal onDismiss={props.onDismiss}>
61
+ <scrollbox flexGrow={1}>
62
+ <box flexDirection="column" gap={1}>
63
+ <text fg={props.theme.roles.text.primary}>
64
+ <strong>{value().phase}</strong>
65
+ </text>
66
+ <text fg={props.theme.roles.text.secondary}>{value().explanation}</text>
67
+ <text fg={props.theme.roles.text.muted}>From</text>
68
+ <text fg={props.theme.roles.text.primary}>{value().source}</text>
69
+ <text fg={props.theme.roles.text.muted}>To</text>
70
+ <text fg={props.theme.roles.text.primary}>{value().target}</text>
71
+ <text fg={props.theme.roles.text.secondary}>{props.event.at}</text>
72
+ <TuiButton theme={props.theme} label="Close" shortcut="Esc" onPress={props.onDismiss}/>
73
+ </box>
74
+ </scrollbox>
75
+ </OverlayFrame>);
76
+ }
@@ -0,0 +1,23 @@
1
+ /* @jsxImportSource @opentui/solid */
2
+ import { Show } from "solid-js";
3
+ import { clipTerminal, terminalDisplayWidth } from "../terminal-text.js";
4
+ import { TuiButton } from "./button.jsx";
5
+ /** Fixed-budget mode description and an explicit action, never terminal geometry. */
6
+ export function PaneModeControl(props) {
7
+ const action = () => (props.scrollback ? "Back to live" : "Restore");
8
+ const actionWidth = () => Math.min(props.width, terminalDisplayWidth(action()) + 2);
9
+ const labelWidth = () => Math.max(0, props.width - actionWidth());
10
+ const label = () => props.scrollback
11
+ ? props.linesAboveLive && labelWidth() >= 28
12
+ ? ` ↑ Scrollback · ${props.linesAboveLive} lines up `
13
+ : " Scrollback "
14
+ : " Expanded ";
15
+ return (<box width={props.width} height={1} flexShrink={0} flexDirection="row" backgroundColor={props.theme.roles.surfaces.panel}>
16
+ <Show when={labelWidth() > 0}>
17
+ <text width={labelWidth()} fg={props.theme.roles.text.secondary}>
18
+ {clipTerminal(label(), labelWidth())}
19
+ </text>
20
+ </Show>
21
+ <TuiButton theme={props.theme} label={action()} size="compact" width={actionWidth()} onPress={() => (props.scrollback ? props.onBackToLive?.() : props.onRestore?.())}/>
22
+ </box>);
23
+ }
@@ -0,0 +1,41 @@
1
+ /** Availability > input capability > urgent activity > local mode > routine activity. No output inference. */
2
+ export function statusPresentation(facts) {
3
+ if (facts.unavailable)
4
+ return { label: "Unavailable", tone: "unknown" };
5
+ if (facts.connection && !["live", "read-only"].includes(facts.connection)) {
6
+ return {
7
+ label: facts.connection === "connecting"
8
+ ? "Connecting…"
9
+ : facts.connection === "recovering" ||
10
+ facts.connection === "reconnecting" ||
11
+ facts.connection === "rebinding"
12
+ ? "Reconnecting…"
13
+ : facts.connection === "unavailable" || facts.connection === "disposed"
14
+ ? "Unavailable"
15
+ : "Disconnected",
16
+ tone: "unknown",
17
+ };
18
+ }
19
+ if (facts.activity === "disconnected")
20
+ return { label: "Unknown", tone: "unknown" };
21
+ if (facts.connection === "read-only")
22
+ return { label: "Read-only", tone: "unknown" };
23
+ if (facts.activity === "failed")
24
+ return { label: "Failed", tone: "blocked", activity: "failed" };
25
+ if (facts.attention || facts.activity === "waiting")
26
+ return { label: "Needs input", tone: "blocked", activity: "waiting" };
27
+ if (facts.scrollback)
28
+ return { label: "Scrollback", tone: "idle", action: "back-to-live" };
29
+ if (facts.expanded)
30
+ return { label: "Expanded", tone: "idle", action: "restore" };
31
+ switch (facts.activity) {
32
+ case "running":
33
+ return { label: "Working", tone: "working", activity: "running" };
34
+ case "complete":
35
+ return { label: "Done", tone: "done", activity: "complete" };
36
+ case "idle":
37
+ return { label: "Idle", tone: "idle", activity: "idle" };
38
+ default:
39
+ return undefined;
40
+ }
41
+ }