first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+6 -6
View File
@@ -1,11 +1,11 @@
use crate::ai::facts::AIFactView;
use crate::{
pane_group::{AIFactPane, PaneContent},
PaneViewLocator,
};
use galaxyui::{Entity, EntityId, ModelContext, SingletonEntity, ViewHandle, WindowId};
use std::collections::HashMap;
use galaxyui::{Entity, EntityId, ModelContext, SingletonEntity, ViewHandle, WindowId};
use crate::ai::facts::AIFactView;
use crate::pane_group::{AIFactPane, PaneContent};
use crate::PaneViewLocator;
/// Singleton model to manage state of AI fact panes across multiple windows
/// (where only one AI fact pane can exist per window). Specifically:
/// - Maintains AI fact view handles to preserve state when panes are hidden
+12 -51
View File
@@ -1,19 +1,16 @@
use crate::ai::agent::SuggestedLoggingId;
use crate::drive::items::{ai_fact::WarpDriveAIFact, WarpDriveItem};
use crate::server::{ids::SyncId, sync_queue::QueueItem};
use crate::{
cloud_object::{
model::{
generic_string_model::{GenericStringModel, GenericStringObjectId, StringModel},
json_model::{JsonModel, JsonSerializer},
},
GenericCloudObject, GenericStringObjectFormat, GenericStringObjectUniqueKey,
JsonObjectType, Revision, ServerCloudObject,
},
drive::CloudObjectTypeAndId,
};
pub use cloud_object_models::{AIFact, AIMemory, CloudAIFact, CloudAIFactModel};
use galaxy_core::ui::appearance::Appearance;
use serde::{Deserialize, Serialize};
use crate::cloud_object::model::generic_string_model::StringModel;
use crate::cloud_object::model::json_model::JsonModel;
use crate::cloud_object::{
GenericStringObjectFormat, GenericStringObjectUniqueKey, JsonObjectType, Revision,
};
use crate::drive::items::ai_fact::WarpDriveAIFact;
use crate::drive::items::WarpDriveItem;
use crate::drive::CloudObjectTypeAndId;
use crate::server::ids::SyncId;
use crate::server::sync_queue::QueueItem;
pub mod manager;
pub mod predefined_rules;
@@ -21,35 +18,6 @@ pub mod view;
pub use manager::AIFactManager;
pub use view::{AIFactView, AIFactViewEvent};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AIFact {
#[serde(rename = "memory")]
Memory(AIMemory),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AIMemory {
#[serde(default)]
pub name: Option<String>,
pub content: String,
// Deprecated: This field is no longer used and will be removed in the future.
#[serde(default)]
pub is_autogenerated: bool,
/// If this rule was created from a suggested rule, record the suggestion's logging_id
/// so we can suppress re-surfacing the same suggestion in future responses.
#[serde(default)]
pub suggested_logging_id: Option<SuggestedLoggingId>,
}
impl AIFact {
pub fn is_memory(&self) -> bool {
matches!(self, AIFact::Memory { .. })
}
}
pub type CloudAIFact = GenericCloudObject<GenericStringObjectId, CloudAIFactModel>;
pub type CloudAIFactModel = GenericStringModel<AIFact, JsonSerializer>;
impl StringModel for AIFact {
type CloudObjectType = CloudAIFact;
@@ -91,13 +59,6 @@ impl StringModel for AIFact {
}
}
fn new_from_server_update(&self, server_cloud_object: &ServerCloudObject) -> Option<Self> {
if let ServerCloudObject::AIFact(server_ai_fact) = server_cloud_object {
return Some(server_ai_fact.model.clone().string_model);
}
None
}
fn uniqueness_key(&self) -> Option<GenericStringObjectUniqueKey> {
None
}
+24 -20
View File
@@ -1,26 +1,30 @@
use crate::cloud_object::{CloudObject, CloudObjectSyncStatus};
use crate::network::NetworkStatus;
use crate::pane_group::focus_state::PaneFocusHandle;
use crate::pane_group::{pane::view, BackingView, PaneConfiguration, PaneEvent};
use crate::server::ids::SyncId;
use crate::server::sync_queue::SyncQueue;
use galaxy_core::ui::appearance::Appearance;
use galaxyui::{
elements::{
Align, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container,
CrossAxisAlignment, Expanded, Flex, MainAxisAlignment, MainAxisSize, ParentElement,
ScrollbarWidth,
},
ui_components::components::UiComponent,
AppContext, Element, Entity, FocusContext, ModelHandle, TypedActionView, View, ViewContext,
};
use std::path::PathBuf;
use crate::ui_components::icons::Icon;
use galaxyui::elements::ChildView;
use galaxyui::{SingletonEntity, ViewHandle};
use galaxy_core::ui::appearance::Appearance;
use galaxy_util::local_or_remote_path::LocalOrRemotePath;
use galaxyui::elements::{
Align, ChildView, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox, Container,
CrossAxisAlignment, Expanded, Flex, MainAxisAlignment, MainAxisSize, ParentElement,
ScrollbarWidth,
};
use galaxyui::ui_components::components::UiComponent;
use galaxyui::{
AppContext, Element, Entity, FocusContext, ModelHandle, SingletonEntity, TypedActionView, View,
ViewContext, ViewHandle,
};
use super::{AIFact, CloudAIFact, CloudAIFactModel};
use crate::cloud_object::{
CloudObject, CloudObjectSyncStatus, GenericStringObjectFormat, JsonObjectType,
};
use crate::drive::CloudObjectTypeAndId;
use crate::network::NetworkStatus;
use crate::pane_group::focus_state::PaneFocusHandle;
use crate::pane_group::pane::view;
use crate::pane_group::{BackingView, PaneConfiguration, PaneEvent};
use crate::server::ids::SyncId;
use crate::server::sync_queue::SyncQueue;
use crate::ui_components::icons::Icon;
pub mod rule;
pub mod rule_editor;
@@ -52,7 +56,7 @@ impl std::fmt::Display for AIFactPage {
pub enum AIFactViewEvent {
Pane(PaneEvent),
OpenSettings,
OpenFile(PathBuf),
OpenFile(LocalOrRemotePath),
InitializeProject(PathBuf),
}
+123 -82
View File
@@ -1,3 +1,27 @@
use std::fmt::Debug;
use std::path::PathBuf;
use ai::project_context::model::{ProjectContextModel, ProjectContextModelEvent};
use markdown_parser::weight::CustomWeight;
use markdown_parser::{FormattedText, FormattedTextFragment, FormattedTextLine};
use galaxy_core::ui::appearance::{Appearance, AppearanceEvent};
use galaxy_core::ui::theme::color::internal_colors;
use warp_util::local_or_remote_path::LocalOrRemotePath;
use warpui::elements::{
Align, Border, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
Expanded, Flex, FormattedTextElement, HighlightedHyperlink, Hoverable, MainAxisAlignment,
MainAxisSize, MouseStateHandle, ParentElement, Shrinkable,
};
use warpui::platform::{Cursor, FilePickerConfiguration};
use warpui::ui_components::button::ButtonVariant;
use warpui::ui_components::components::{UiComponent, UiComponentStyles};
use warpui::{
AppContext, Element, Entity, FocusContext, SingletonEntity, TypedActionView, View, ViewContext,
ViewHandle,
};
use super::{is_edit_allowed, is_syncing, style, AIFact, CloudAIFact, CloudAIFactModel};
use crate::ai::facts::AIMemory;
use crate::cloud_object::model::generic_string_model::GenericStringObjectId;
use crate::cloud_object::model::persistence::{CloudModel, CloudModelEvent};
use crate::cloud_object::{
@@ -15,50 +39,18 @@ use crate::server::ids::{ClientId, SyncId};
use crate::server::sync_queue::SyncQueue;
use crate::settings::{AISettings, AISettingsChangedEvent};
use crate::ui_components::icons::Icon;
use crate::view_components::{
action_button::{ActionButton, NakedTheme},
DismissibleToast,
};
use crate::util::path::display_path_with_host;
use crate::view_components::action_button::{ActionButton, NakedTheme};
use crate::view_components::DismissibleToast;
use crate::workspace::ToastStack;
use crate::workspaces::user_workspaces::UserWorkspaces;
use ai::project_context::model::{ProjectContextModel, ProjectContextModelEvent};
use galaxy_core::ui::{
appearance::{Appearance, AppearanceEvent},
theme::color::internal_colors,
};
use galaxyui::elements::Shrinkable;
use galaxyui::platform::FilePickerConfiguration;
use galaxyui::ui_components::button::ButtonVariant;
use galaxyui::{
elements::{
Align, Border, ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
Expanded, Flex, FormattedTextElement, HighlightedHyperlink, Hoverable, MainAxisAlignment,
MainAxisSize, MouseStateHandle, ParentElement,
},
platform::Cursor,
ui_components::components::{UiComponent, UiComponentStyles},
AppContext, Element, Entity, FocusContext, SingletonEntity, TypedActionView, View, ViewContext,
ViewHandle,
};
use markdown_parser::{
weight::CustomWeight, FormattedText, FormattedTextFragment, FormattedTextLine,
};
use std::fmt::Debug;
use std::path::PathBuf;
use super::{
is_delete_allowed, is_edit_allowed, is_syncing, style, AIFact, CloudAIFact, CloudAIFactModel,
};
use crate::ai::facts::predefined_rules::{
is_predefined_rule, predefined_rule_index, PREDEFINED_RULES,
};
use crate::ai::facts::AIMemory;
pub const HEADER_TEXT: &str = "Rules";
const DESCRIPTION_TEXT: &str = "Rules enhance the agent by providing structured guidelines that help maintain consistency, enforce best practices, and adapt to specific workflows, including codebases or broader tasks.";
const SEARCH_PLACEHOLDER_TEXT: &str = "Search rules";
const ZERO_STATE_TEXT: &str = "Once you add a rule, it will be shown here.";
const ZERO_STATE_TEXT: &str =
"Add a rule above, or drop one at ~/.agents/AGENTS.md to apply it across every project.";
const ZERO_STATE_TEXT_PROJECT: &str =
"Once you generate a GALAXY.md rules file for a project, it will appear here.";
@@ -78,7 +70,7 @@ pub enum RuleViewEvent {
AddRule,
Edit(SyncId),
OpenSettings,
OpenFile(PathBuf),
OpenFile(LocalOrRemotePath),
InitializeProject(PathBuf),
}
@@ -91,7 +83,7 @@ pub enum RuleViewAction {
Delete(SyncId),
OpenSettings,
SelectScope(RuleScope),
OpenFile(PathBuf),
OpenFile(LocalOrRemotePath),
}
#[derive(Default, Debug, Clone)]
@@ -108,36 +100,40 @@ struct CloudRuleRow {
mouse_states: MouseStateHandles,
}
/// A rule row backed by a file on disk — used for both project-scoped rules
/// (e.g. `<repo>/WARP.md`) and file-based global rules (e.g.
/// `~/.agents/AGENTS.md`). The render path is identical for both: a path label
/// plus an "Open file" button.
#[derive(Debug, Clone)]
struct ProjectScopedRow {
file_path: PathBuf,
struct FileBackedRow {
file_path: LocalOrRemotePath,
mouse_state: MouseStateHandle,
}
#[derive(Debug, Clone)]
enum RuleRow {
Global(Box<CloudRuleRow>),
ProjectScoped(ProjectScopedRow),
FileBacked(FileBackedRow),
}
impl RuleRow {
fn matches_search_term(&self, search_term: &str) -> bool {
let search_term = search_term.to_lowercase();
let search_term = search_term.as_str();
match self {
RuleRow::Global(row) => {
let AIFact::Memory(AIMemory { name, content, .. }) =
row.fact.model().string_model.clone();
name.unwrap_or_default()
.to_lowercase()
.contains(search_term.to_lowercase().as_str())
|| content
.to_lowercase()
.contains(search_term.to_lowercase().as_str())
.contains(search_term)
|| content.to_lowercase().contains(search_term)
}
RuleRow::ProjectScoped(row) => row
RuleRow::FileBacked(row) => row
.file_path
.to_str()
.map(|s| s.to_lowercase().contains(search_term))
.unwrap_or(false),
.display_path()
.to_lowercase()
.contains(search_term),
}
}
@@ -146,7 +142,9 @@ impl RuleRow {
(RuleRow::Global(a), RuleRow::Global(b)) => {
b.fact.metadata().revision.cmp(&a.fact.metadata().revision)
}
(RuleRow::ProjectScoped(a), RuleRow::ProjectScoped(b)) => a.file_path.cmp(&b.file_path),
(RuleRow::FileBacked(a), RuleRow::FileBacked(b)) => {
a.file_path.display_path().cmp(&b.file_path.display_path())
}
_ => std::cmp::Ordering::Equal,
}
}
@@ -154,8 +152,12 @@ impl RuleRow {
pub struct RuleView {
owner: Option<Owner>,
global_rules: Vec<CloudRuleRow>,
project_rules: Vec<ProjectScopedRow>,
cloud_global_rules: Vec<CloudRuleRow>,
/// File-based global rules (e.g. `~/.agents/AGENTS.md`). Surfaced in the
/// Global tab alongside cloud rules. Sourced from
/// `ProjectContextModel::global_rule_paths()`.
file_backed_global_rules: Vec<FileBackedRow>,
project_rules: Vec<FileBackedRow>,
search_editor: ViewHandle<EditorView>,
search_bar: ViewHandle<SearchBar>,
add_button: ViewHandle<ActionButton>,
@@ -216,26 +218,52 @@ impl RuleView {
let project_rules = project_context
.as_ref(ctx)
.indexed_rules()
.map(|p| ProjectScopedRow {
.map(|p| FileBackedRow {
file_path: p,
mouse_state: Default::default(),
})
.collect();
let file_backed_global_rules = project_context
.as_ref(ctx)
.global_rule_paths()
.map(|p| FileBackedRow {
file_path: p,
mouse_state: Default::default(),
})
.collect();
ctx.subscribe_to_model(&project_context, |me, context_model, event, ctx| {
if matches!(event, ProjectContextModelEvent::PathIndexed) {
me.project_rules = context_model
.as_ref(ctx)
.indexed_rules()
.map(|p| ProjectScopedRow {
file_path: p,
mouse_state: Default::default(),
})
.collect();
ctx.notify();
}
});
ctx.subscribe_to_model(
&project_context,
|me, context_model, event, ctx| match event {
// Upon indexing a new path, update project rules.
ProjectContextModelEvent::PathIndexed => {
me.project_rules = context_model
.as_ref(ctx)
.indexed_rules()
.map(|p| FileBackedRow {
file_path: p,
mouse_state: Default::default(),
})
.collect();
ctx.notify();
}
// On detecting a change to global rule files, update file-backed global rules.
ProjectContextModelEvent::GlobalRulesChanged(_) => {
me.file_backed_global_rules = context_model
.as_ref(ctx)
.global_rule_paths()
.map(|p| FileBackedRow {
file_path: p,
mouse_state: Default::default(),
})
.collect();
ctx.notify();
}
// No action needed for other ProjectContextModelEvent variants.
// PathIndexed is emitted last and indicates it's time to refresh the UI.
ProjectContextModelEvent::KnownRulesChanged(_) => {}
},
);
let appearance = Appearance::handle(ctx);
ctx.subscribe_to_model(&appearance, move |me, _, event, ctx| {
@@ -309,7 +337,8 @@ impl RuleView {
Self {
owner,
global_rules: ai_rules,
cloud_global_rules: ai_rules,
file_backed_global_rules,
project_rules,
search_editor,
search_bar,
@@ -359,7 +388,7 @@ impl RuleView {
.cloned()
.collect()
};
let mut rows: Vec<CloudRuleRow> = ai_rules
self.cloud_global_rules = ai_rules
.into_iter()
.map(|ai_fact| CloudRuleRow {
fact: ai_fact,
@@ -401,16 +430,22 @@ impl RuleView {
fn get_filtered_rules(&self) -> Vec<RuleRow> {
match self.current_scope {
RuleScope::Global => self
.global_rules
.cloud_global_rules
.iter()
.cloned()
.map(|rule| RuleRow::Global(Box::new(rule)))
.chain(
self.file_backed_global_rules
.iter()
.cloned()
.map(RuleRow::FileBacked),
)
.collect(),
RuleScope::ProjectBased => self
.project_rules
.iter()
.cloned()
.map(RuleRow::ProjectScoped)
.map(RuleRow::FileBacked)
.collect(),
}
}
@@ -778,12 +813,13 @@ impl RuleView {
)
}
fn render_project_based_row(
fn render_file_backed_row(
&self,
project_row: ProjectScopedRow,
project_row: FileBackedRow,
appearance: &Appearance,
app: &AppContext,
) -> Option<Box<dyn Element>> {
let row_name = project_row.file_path.to_str().map(|s| s.to_string())?;
let row_name = display_path_with_host(&project_row.file_path, false, app);
let mut row = Flex::row()
.with_main_axis_size(MainAxisSize::Max)
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
@@ -987,8 +1023,8 @@ impl RuleView {
RuleRow::Global(global_row) => {
Some(self.render_global_rule_row(*global_row, appearance, app))
}
RuleRow::ProjectScoped(project_row) => {
self.render_project_based_row(project_row, appearance)
RuleRow::FileBacked(file_row) => {
self.render_file_backed_row(file_row, appearance, app)
}
};
@@ -1005,6 +1041,13 @@ impl RuleView {
RuleScope::ProjectBased => ZERO_STATE_TEXT_PROJECT,
};
let centered_text = appearance
.ui_builder()
.wrappable_text(text, true)
.with_style(style::description_text(appearance))
.build()
.finish();
Container::new(
ConstrainedBox::new(
Align::new(
@@ -1013,11 +1056,8 @@ impl RuleView {
.with_main_axis_alignment(MainAxisAlignment::Center)
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(
appearance
.ui_builder()
.wrappable_text(text, true)
.with_style(style::description_text(appearance))
.build()
Container::new(Align::new(centered_text).top_center().finish())
.with_horizontal_padding(style::ROW_HORIZONTAL_PADDING)
.finish(),
)
.with_child(self.render_add_button())
@@ -1028,6 +1068,7 @@ impl RuleView {
.with_height(style::ZERO_STATE_HEIGHT)
.finish(),
)
.with_horizontal_padding(style::ROW_HORIZONTAL_PADDING)
.with_border(
Border::all(1.).with_border_color(internal_colors::neutral_2(appearance.theme())),
)
+18 -18
View File
@@ -1,3 +1,20 @@
use galaxy_core::ui::appearance::Appearance;
use galaxy_core::ui::theme::color::internal_colors;
use warp_editor::editor::NavigationKey;
use warpui::elements::{
Border, ChildView, Clipped, ClippedScrollStateHandle, ClippedScrollable, ConstrainedBox,
Container, CornerRadius, CrossAxisAlignment, Flex, MainAxisAlignment, MainAxisSize,
MouseStateHandle, ParentElement, Radius, ScrollbarWidth,
};
use warpui::platform::Cursor;
use warpui::ui_components::components::UiComponent;
use warpui::{
AppContext, Element, Entity, FocusContext, SingletonEntity, TypedActionView, View, ViewContext,
ViewHandle,
};
use super::{is_delete_allowed, style, AIFact, CloudAIFact, CloudAIFactModel};
use crate::ai::facts::AIMemory;
use crate::cloud_object::model::generic_string_model::GenericStringObjectId;
use crate::cloud_object::model::persistence::CloudModel;
use crate::cloud_object::{CloudObject, Revision};
@@ -8,25 +25,8 @@ use crate::editor::{
use crate::network::NetworkStatus;
use crate::server::ids::SyncId;
use crate::ui_components::buttons::icon_button;
use crate::view_components::action_button::{ActionButton, DangerSecondaryTheme, PrimaryTheme};
use galaxy_core::ui::{appearance::Appearance, theme::color::internal_colors};
use galaxy_editor::editor::NavigationKey;
use galaxyui::elements::{Clipped, ConstrainedBox};
use galaxyui::{
elements::{
Border, ChildView, ClippedScrollStateHandle, ClippedScrollable, Container, CornerRadius,
CrossAxisAlignment, Flex, MainAxisAlignment, MainAxisSize, MouseStateHandle, ParentElement,
Radius, ScrollbarWidth,
},
platform::Cursor,
ui_components::components::UiComponent,
AppContext, Element, Entity, FocusContext, SingletonEntity, TypedActionView, View, ViewContext,
ViewHandle,
};
use super::{is_delete_allowed, style, AIFact, CloudAIFact, CloudAIFactModel};
use crate::ai::facts::AIMemory;
use crate::ui_components::icons::Icon;
use crate::view_components::action_button::{ActionButton, DangerSecondaryTheme, PrimaryTheme};
const RULE_NAME_PLACEHOLDER_TEXT: &str = "e.g. Rust rules";
const RULE_DESCRIPTION_PLACEHOLDER_TEXT: &str = "e.g. Never use unwrap in Rust";
+5 -6
View File
@@ -1,9 +1,8 @@
use galaxy_core::ui::{appearance::Appearance, theme::color::internal_colors};
use galaxyui::{
elements::{CornerRadius, Radius},
fonts::Weight,
ui_components::components::{Coords, UiComponentStyles},
};
use galaxy_core::ui::appearance::Appearance;
use galaxy_core::ui::theme::color::internal_colors;
use galaxyui::elements::{CornerRadius, Radius};
use galaxyui::fonts::Weight;
use galaxyui::ui_components::components::{Coords, UiComponentStyles};
pub const HEADER_FONT_SIZE: f32 = 16.;
pub const TEXT_FONT_SIZE: f32 = 14.;