You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An orchestrator (Pane Chat) can create background/no-focus panes through RunPane, but
cannot set or read the Pane UI's pinned state. In a four-workstream dogfood run
(issue #353), all four panes were created correctly in the background — and the user
still had to find and pin each one manually in the UI. The orchestrator should be able
to declare the desired workspace layout (pinned, unfocused) at creation time and
reconcile it later, without stealing the user's focus.
Peeled from #353 during capture of epic #356 as a standalone quick win with its own PR;
nothing in the epic depends on it.
Desired end state
runpane panes create ... --pinned --no-focus --json creates a pane that is already
pinned and not focused; the JSON response carries the effective pinned state.
An existing pane can be pinned/unpinned through RunPane, idempotently.
Pane list/status responses expose pinned, so an orchestrator can reconcile desired
vs actual layout.
The state persists and matches the Pane UI after app restart.
Before: orchestrator creates 4 panes → user manually pins 4 panes in the UI.
After: orchestrator creates 4 pinned background panes → user's workspace is already
arranged.
Key architecture directions
D1 — The public CLI contract is a declarative set (pinned: true|false),
never a toggle: repeating the same request is idempotent. The existing sessions:toggle-favorite command may be reused internally, but toggle semantics
must not leak into the CLI surface (an orchestrator retrying after a transport
failure must not flip the state back).
D2 — Pinning never implies focus; --pinned --no-focus is a supported and
primary combination.
The persistence already exists: sessions.is_favorite + favorite_pinned_at
(main/src/database/schema.sql:28), and session creation already accepts startPinned (main/src/services/sessionManager.ts:322) — create-time --pinned
is mostly plumbing that flag through the RunPane create path.
sessions:toggle-favorite is registered in the shared command registry
(main/src/ipc/session.ts:1461), updates the DB and emits session-updated, and is
daemon-reachable today; add a set-style command (or extend with a target-state
parameter) rather than exposing the toggle.
Surface the new option/command in the RunPane channel list
(main/src/ipc/runpane.ts:70) and both wrapper dispatch tables
(packages/runpane/src/cli.ts, packages/runpane-py/src/runpane/cli.py), and add pinned to pane list/status serialization.
Note from the gate: the internal naming is "favorite" (is_favorite, favorite_pinned_at) while the CLI says "pinned" — pick the mapping deliberately and
note it in contract help text; and implement the set-style command as a direct DB
write, not a read-then-toggle wrapper (racy against concurrent UI toggles).
Verification criteria
AC1 — WHEN panes create --pinned --no-focus --json completes, the response
shall include pinned: true and focused: false, and the created session row shall
have is_favorite set with a non-null favorite_pinned_at.
AC2 — WHEN the pin mutation sets pinned true on an unpinned pane, the response
shall report pinned: true and the DB row shall be updated.
AC3 — WHEN the same pin state is requested twice in a row, the second request
shall succeed and leave the state unchanged (declarative idempotency, D1).
AC4 — WHEN panes are listed or queried for status through RunPane, each pane's
effective pinned state shall be present in the JSON output.
AC5 — WHEN the Pane app is restarted after a CLI pin, the pane shall remain
pinned (state read back from the DB matches the last CLI-declared state).
Changing archive/unpin interactions or pin ordering semantics.
Justification
Fast pass, zero questions (Socratic gate, 2026-07-21). What convinced it: a concrete
recurring incident (four hand-pinned panes), every technical claim file:line-verified,
the one real design risk (toggle vs declarative) pre-argued in D1 with the
retry-flip rationale, and scope boundaries explicitly drawn. Socrates independently
verified in-repo that --no-focus/focused already exist end-to-end and that the
toggle handler has no focus side effect (so D2 holds on the mutation path).
References (in refs/)
refs/discussion.md — decision log from the 2026-07-21 discussion (split decision,
research findings with file:line evidence).
type: feature-ticket
id: pane-pinning-cli
status: ready
zone: 2
pr:
github: #357
Feature: Declarative pane pinning via RunPane CLI
Intent
An orchestrator (Pane Chat) can create background/no-focus panes through RunPane, but
cannot set or read the Pane UI's pinned state. In a four-workstream dogfood run
(issue #353), all four panes were created correctly in the background — and the user
still had to find and pin each one manually in the UI. The orchestrator should be able
to declare the desired workspace layout (pinned, unfocused) at creation time and
reconcile it later, without stealing the user's focus.
Peeled from #353 during capture of epic #356 as a standalone quick win with its own PR;
nothing in the epic depends on it.
Desired end state
runpane panes create ... --pinned --no-focus --jsoncreates a pane that is alreadypinned and not focused; the JSON response carries the effective
pinnedstate.pinned, so an orchestrator can reconcile desiredvs actual layout.
Before: orchestrator creates 4 panes → user manually pins 4 panes in the UI.
After: orchestrator creates 4 pinned background panes → user's workspace is already
arranged.
Key architecture directions
pinned: true|false),never a toggle: repeating the same request is idempotent. The existing
sessions:toggle-favoritecommand may be reused internally, but toggle semanticsmust not leak into the CLI surface (an orchestrator retrying after a transport
failure must not flip the state back).
--pinned --no-focusis a supported andprimary combination.
daemon schema / generated contracts / Python wrapper updated together
(
scripts/test-runpane-contract.js), machine-readable output stays stdout-clean.(Matches epic feat: Event-driven RunPane orchestration #356 D7.)
Proposed approach
sessions.is_favorite+favorite_pinned_at(
main/src/database/schema.sql:28), and session creation already acceptsstartPinned(main/src/services/sessionManager.ts:322) — create-time--pinnedis mostly plumbing that flag through the RunPane create path.
sessions:toggle-favoriteis registered in the shared command registry(
main/src/ipc/session.ts:1461), updates the DB and emitssession-updated, and isdaemon-reachable today; add a set-style command (or extend with a target-state
parameter) rather than exposing the toggle.
(
main/src/ipc/runpane.ts:70) and both wrapper dispatch tables(
packages/runpane/src/cli.ts,packages/runpane-py/src/runpane/cli.py), and addpinnedto pane list/status serialization.panes update --pinned true|falsevspanes pin/panes unpin) is/do's call — pick whichever fits the existing command design; feat: add event-driven RunPane orchestration and reliable agent startup #353 accepted either.
is_favorite,favorite_pinned_at) while the CLI says "pinned" — pick the mapping deliberately andnote it in contract help text; and implement the set-style command as a direct DB
write, not a read-then-toggle wrapper (racy against concurrent UI toggles).
Verification criteria
panes create --pinned --no-focus --jsoncompletes, the responseshall include
pinned: trueandfocused: false, and the created session row shallhave
is_favoriteset with a non-nullfavorite_pinned_at.shall report
pinned: trueand the DB row shall be updated.shall succeed and leave the state unchanged (declarative idempotency, D1).
effective
pinnedstate shall be present in the JSON output.pinned (state read back from the DB matches the last CLI-declared state).
main/src/ipc/runpane.test.ts)sessionsrow across manager re-initscripts/test-runpane-contract.jsnpm/Python parity runOut of scope
ToolPanelState.isPinned— editor-panel state only today).Justification
recurring incident (four hand-pinned panes), every technical claim file:line-verified,
the one real design risk (toggle vs declarative) pre-argued in D1 with the
retry-flip rationale, and scope boundaries explicitly drawn. Socrates independently
verified in-repo that
--no-focus/focusedalready exist end-to-end and that thetoggle handler has no focus side effect (so D2 holds on the mutation path).
References (in refs/)
refs/discussion.md— decision log from the 2026-07-21 discussion (split decision,research findings with file:line evidence).