Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions app/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,11 @@ pub enum LeafContents {
/// The in-app network log pane. Not persisted across restarts because the
/// backing log is an in-memory ring buffer that starts empty on launch.
NetworkLog,
/// twarp 07 (7b): a Claude Code pane. Not persisted across restarts — a
/// restored pane can't replay a live `claude` process and twarp keeps no
/// transcript store; sessions are reopened via `claude --resume` from the
/// 7h session list.
ClaudeCode,
/// An entrypoint pane type to launch other pane types from a search palette. The default view
/// when creating a tab.
Welcome {
Expand All @@ -759,6 +764,8 @@ impl LeafContents {
// starts empty on launch; persisting would also regress back to
// an on-disk log via the app-state database.
LeafContents::NetworkLog => false,
// twarp 07 (7b): see the variant doc — never persisted.
LeafContents::ClaudeCode => false,
LeafContents::Terminal(_)
| LeafContents::Notebook(_)
| LeafContents::AIDocument(_)
Expand Down Expand Up @@ -891,9 +898,8 @@ pub enum LeftPanelDisplayedTab {
GlobalSearch,
WarpDrive,
Shortcuts,
// twarp 07: persisted identity of the Claude Code tab so it can be the
// restored active view across restarts.
ClaudeCode,
// twarp 07 (7b): no Claude Code sidebar tab to persist — it's a
// main-content pane opened by typing `claude` (re-spec #70).
ConversationListView,
}

Expand All @@ -904,7 +910,6 @@ impl From<ToolPanelView> for LeftPanelDisplayedTab {
ToolPanelView::GlobalSearch { .. } => LeftPanelDisplayedTab::GlobalSearch,
ToolPanelView::WarpDrive => LeftPanelDisplayedTab::WarpDrive,
ToolPanelView::Shortcuts => LeftPanelDisplayedTab::Shortcuts,
ToolPanelView::ClaudeCode => LeftPanelDisplayedTab::ClaudeCode,
ToolPanelView::ConversationListView => LeftPanelDisplayedTab::ConversationListView,
}
}
Expand Down
395 changes: 242 additions & 153 deletions app/src/claude_code_panel/mod.rs → app/src/claude_code_view.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions app/src/launch_configs/launch_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ impl TryFrom<PaneNodeSnapshot> for PaneTemplateType {
| LeafContents::ExecutionProfileEditor
| LeafContents::GetStarted
| LeafContents::NetworkLog
// twarp 07 (7b): Claude Code panes aren't saved in launch configs.
| LeafContents::ClaudeCode
| LeafContents::Welcome { .. }
| LeafContents::AIDocument(_)
| LeafContents::AmbientAgent(_) => {
Expand Down
10 changes: 6 additions & 4 deletions app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ mod banner;
mod billing;
mod changelog_model;
mod chip_configurator;
// twarp 07: Claude Code left-panel. Always-on (no feature flag — acceptable on
// a personal fork; degrades to the unavailable state when `claude` is off PATH,
// PRODUCT §6). See TECH.md §Feature flag & rollout.
mod claude_code_panel;
// twarp 07 (7b): the Claude Code main-content pane view. Opened by typing
// `claude` in a terminal (re-spec #70 moved it here from the #69 sidebar).
// Always-on (no feature flag — acceptable on a personal fork; degrades to the
// unavailable state when `claude` is off PATH, PRODUCT §4). See TECH.md
// §Feature flag & rollout.
mod claude_code_view;
mod cloud_object;
mod code;
mod code_review;
Expand Down
16 changes: 16 additions & 0 deletions app/src/pane_group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ mod tests;

pub use crate::code_review::CodeReviewPanelArg;
// twarp: 2c-d — CodeDiffPane / ExecutionProfileEditorPane / AIFactPane removed (AI panes deleted)
pub use pane::claude_code_pane::ClaudeCodePane; // twarp 07 (7b)
pub use pane::code_pane::CodePane;
pub use pane::env_var_collection_pane::EnvVarCollectionPane;
pub use pane::file_pane::FilePane;
Expand Down Expand Up @@ -546,6 +547,13 @@ pub enum Event {
/// The session that the path was opened from.
session: Arc<Session>,
},
/// twarp 07 (7b): tell the workspace to open a Claude Code pane (PRODUCT §1).
OpenClaudeCodePane {
/// The args after `claude` (`claude <prompt>` seeds the first turn).
args: String,
/// The originating terminal's working directory (PRODUCT §4).
cwd: Option<PathBuf>,
},
OpenWarpDriveLink {
open_warp_drive_args: OpenWarpDriveObjectArgs,
},
Expand Down Expand Up @@ -1778,6 +1786,14 @@ impl PaneGroup {
"Network log pane should not have been persisted, as it cannot be restored"
))
}
// twarp 07 (7b): Claude Code panes are not persisted (see
// `LeafContents::is_persisted`) — a restored pane can't replay a
// live `claude` process. `save_pane_state` skips them, so reaching
// this arm is a persistence-side programmer error. Sessions reopen
// via `claude --resume` from the 7h session list.
LeafContents::ClaudeCode => Err(anyhow::anyhow!(
"Claude Code pane should not have been persisted, as it cannot be restored"
)),
LeafContents::GetStarted => {
if !FeatureFlag::GetStartedTab.is_enabled() {
Err(anyhow::anyhow!("GetStarted pane not supported"))
Expand Down
138 changes: 138 additions & 0 deletions app/src/pane_group/pane/claude_code_pane.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! [`PaneContent`] wrapper that hosts [`ClaudeCodeView`] as a first-class
//! main-content pane (roadmap feature 07, sub-phase 7b).
//!
//! Modeled on [`NetworkLogPane`](super::network_log_pane) — the simplest
//! non-persisted pane: it wraps the view in a [`PaneView`], forwards the view's
//! [`PaneEvent`]s to the [`PaneGroup`], and snapshots to a non-persisted
//! [`LeafContents::ClaudeCode`] (twarp keeps no transcript store; live history
//! comes from `claude --resume`, wired in 7h). Unlike `NetworkLogPane` there is
//! no pane manager: a Claude Code pane is opened on demand by the `claude`
//! terminal trigger and has no global registry.

use std::path::PathBuf;

use warpui::{AppContext, ModelHandle, SingletonEntity, View, ViewContext, ViewHandle};

use crate::app_state::LeafContents;
use crate::claude_code_view::{ClaudeCodeView, ClaudeCodeViewEvent};

use super::{
view::PaneView, DetachType, PaneConfiguration, PaneContent, PaneGroup, PaneId, ShareableLink,
ShareableLinkError,
};

pub struct ClaudeCodePane {
view: ViewHandle<PaneView<ClaudeCodeView>>,
pane_configuration: ModelHandle<PaneConfiguration>,
}

impl ClaudeCodePane {
pub fn from_view(claude_code_view: ViewHandle<ClaudeCodeView>, ctx: &mut AppContext) -> Self {
let pane_configuration = claude_code_view.as_ref(ctx).pane_configuration();

let view = ctx.add_typed_action_view(claude_code_view.window_id(ctx), |ctx| {
let pane_id = PaneId::from_claude_code_pane_ctx(ctx);
PaneView::new(
pane_id,
claude_code_view,
(),
pane_configuration.clone(),
ctx,
)
});

Self {
view,
pane_configuration,
}
}

/// Open a fresh Claude Code pane. `initial_prompt` is the `claude <prompt>`
/// positional (PRODUCT §2); `cwd` is the originating terminal's directory
/// (PRODUCT §4).
pub fn new<V: View>(
initial_prompt: Option<String>,
cwd: Option<PathBuf>,
ctx: &mut ViewContext<V>,
) -> Self {
let view =
ctx.add_typed_action_view(move |ctx| ClaudeCodeView::new(initial_prompt, cwd, ctx));
Self::from_view(view, ctx)
}

pub fn claude_code_view(&self, ctx: &AppContext) -> ViewHandle<ClaudeCodeView> {
self.view.as_ref(ctx).child(ctx)
}
}

impl PaneContent for ClaudeCodePane {
fn id(&self) -> PaneId {
PaneId::from_claude_code_pane_view(&self.view)
}

fn attach(
&self,
_group: &PaneGroup,
focus_handle: crate::pane_group::focus_state::PaneFocusHandle,
ctx: &mut ViewContext<PaneGroup>,
) {
self.view
.update(ctx, |view, ctx| view.set_focus_handle(focus_handle, ctx));

let claude_code_view = self.claude_code_view(ctx);
let pane_id = self.id();

ctx.subscribe_to_view(&claude_code_view, move |pane_group, _, event, ctx| {
let ClaudeCodeViewEvent::Pane(pane_event) = event;
pane_group.handle_pane_event(pane_id, pane_event, ctx)
});
ctx.subscribe_to_view(&self.view, move |group, _, event, ctx| {
group.handle_pane_view_event(pane_id, event, ctx);
});
}

fn detach(
&self,
_group: &PaneGroup,
_detach_type: DetachType,
ctx: &mut ViewContext<PaneGroup>,
) {
// No manager to deregister from; just drop the subscriptions. Closing
// the pane drops `ClaudeCodeView`, which (in 7c) kills the live
// `claude` process via `kill_on_drop`.
let claude_code_view = self.claude_code_view(ctx);
ctx.unsubscribe_to_view(&claude_code_view);
ctx.unsubscribe_to_view(&self.view);
}

fn snapshot(&self, _app: &AppContext) -> LeafContents {
// Non-persisted (see `LeafContents::is_persisted`): a restored pane
// can't replay a live `claude` process, and twarp keeps no transcript
// store. Session restore is `claude --resume` from the 7h session list.
LeafContents::ClaudeCode
}

fn has_application_focus(&self, ctx: &mut ViewContext<PaneGroup>) -> bool {
self.view.is_self_or_child_focused(ctx)
}

fn focus(&self, ctx: &mut ViewContext<PaneGroup>) {
self.claude_code_view(ctx)
.update(ctx, |view, ctx| view.focus(ctx));
}

fn shareable_link(
&self,
_ctx: &mut ViewContext<PaneGroup>,
) -> Result<ShareableLink, ShareableLinkError> {
Ok(ShareableLink::Base)
}

fn pane_configuration(&self) -> ModelHandle<PaneConfiguration> {
self.pane_configuration.clone()
}

fn is_pane_being_dragged(&self, ctx: &AppContext) -> bool {
self.view.as_ref(ctx).is_being_dragged()
}
}
21 changes: 21 additions & 0 deletions app/src/pane_group/pane/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
//! The [`PaneId`] must be created via a [`PaneView<BackingView>`]. The [`PaneId`] is consequently
//! used to render a [`PaneView`] which internally renders the pane, including the [`BackingView`].
// twarp: 2c-d — code_diff_pane / code_diff_pane_model / execution_profile_editor_pane removed (AI panes)
// twarp 07 (7b): the Claude Code main-content pane host.
pub(super) mod claude_code_pane;
pub(super) mod code_pane;
pub(super) mod env_var_collection_pane;
pub(super) mod file_pane;
Expand All @@ -31,6 +33,7 @@ use crate::pane_group::pane::get_started_view::GetStartedView;
use crate::view_components::action_button::ActionButton;
use crate::{
// twarp: 2c-d — ExecutionProfileEditorView/AIDocumentView/CodeDiffView/AIFactView imports removed (AI panes)
claude_code_view::ClaudeCodeView, // twarp 07 (7b)
code::view::CodeView,
drive::sharing::ShareableObject,
env_vars::view::env_var_collection::EnvVarCollectionView,
Expand Down Expand Up @@ -130,6 +133,8 @@ pub(crate) enum IPaneType {
Notebook,
File,
Code,
/// twarp 07 (7b): the Claude Code main-content pane.
ClaudeCode,
CodeDiff,
EnvVarCollection,
Workflow,
Expand All @@ -153,6 +158,7 @@ impl Display for IPaneType {
IPaneType::Notebook => write!(f, "Notebook"),
IPaneType::File => write!(f, "File"),
IPaneType::Code => write!(f, "Code"),
IPaneType::ClaudeCode => write!(f, "Claude Code"),
IPaneType::CodeDiff => write!(f, "Code Diff"),
IPaneType::EnvVarCollection => write!(f, "Environment Variable Collection"),
IPaneType::Workflow => write!(f, "Workflow"),
Expand Down Expand Up @@ -217,6 +223,11 @@ impl PaneId {
Self::new_from_ctx(IPaneType::Code, ctx)
}

/// twarp 07 (7b): creates a [`PaneId`] from a [`ViewContext<PaneView<ClaudeCodeView>>`].
pub fn from_claude_code_pane_ctx(ctx: &ViewContext<PaneView<ClaudeCodeView>>) -> Self {
Self::new_from_ctx(IPaneType::ClaudeCode, ctx)
}

// twarp: 2c-d — from_code_diff_pane_ctx / from_ai_fact_pane_ctx / from_ai_document_pane_ctx /
// from_execution_profile_editor_pane_ctx removed (AI panes deleted)

Expand Down Expand Up @@ -262,6 +273,13 @@ impl PaneId {
Self::new(IPaneType::Code, code_pane_view)
}

/// twarp 07 (7b): creates a [`PaneId`] from a [`PaneView<ClaudeCodeView>`] entity ID.
pub fn from_claude_code_pane_view(
claude_code_pane_view: &ViewHandle<PaneView<ClaudeCodeView>>,
) -> Self {
Self::new(IPaneType::ClaudeCode, claude_code_pane_view)
}

// twarp: 2c-d — from_code_diff_pane_view removed (AI pane deleted)

/// Creates a [`PaneId`] from a [`PaneView<EnvVarCollection>`] entity ID.
Expand Down Expand Up @@ -385,6 +403,9 @@ impl PaneId {
IPaneType::Code => {
ChildView::<PaneView<CodeView>>::with_id(self.0.pane_view_id).finish()
}
IPaneType::ClaudeCode => {
ChildView::<PaneView<ClaudeCodeView>>::with_id(self.0.pane_view_id).finish()
}
IPaneType::CodeDiff => {
// twarp: 2c-d — CodeDiffView removed (AI)
warpui::elements::Empty::new().finish()
Expand Down
7 changes: 7 additions & 0 deletions app/src/pane_group/pane/terminal_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,13 @@ fn handle_terminal_view_event(
session: session.clone(),
});
}
// twarp 07 (7b): forward the `claude`-trigger event to the workspace.
Event::OpenClaudeCodePane { args, cwd } => {
ctx.emit(pane_group::Event::OpenClaudeCodePane {
args: args.clone(),
cwd: cwd.clone(),
});
}
#[cfg(feature = "local_fs")]
Event::PreviewCodeInWarp { source } => {
ctx.emit(pane_group::Event::PreviewCodeInWarp {
Expand Down
6 changes: 4 additions & 2 deletions app/src/persistence/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,8 @@ fn save_pane_state(
LeafContents::GetStarted => GET_STARTED_PANE_KIND,
LeafContents::Welcome { .. } => WELCOME_PANE_KIND,
LeafContents::AIDocument(_) => AI_DOCUMENT_PANE_KIND,
LeafContents::NetworkLog => {
// twarp 07 (7b): ClaudeCode joins NetworkLog as a non-persisted pane.
LeafContents::NetworkLog | LeafContents::ClaudeCode => {
// These pane types are filtered out before this function is
// called; see `LeafContents::is_persisted` and the skip in
// `save_app_state`. Reaching this arm would mean a `pane_nodes`
Expand Down Expand Up @@ -1266,7 +1267,8 @@ fn save_pane_state(
.values(ambient_agent_pane)
.execute(conn)?;
}
LeafContents::NetworkLog => {
// twarp 07 (7b): ClaudeCode joins NetworkLog as a non-persisted pane.
LeafContents::NetworkLog | LeafContents::ClaudeCode => {
// Unreachable: filtered by `is_persisted` in `save_app_state`.
}
}
Expand Down
Loading
Loading