Skip to content
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ A 2026-07-17 whole-repo audit removed exactly this kind of code; don't reintrodu
- **No single-line forwarding wrappers.** A function whose body is one call to another function with no added logic gets deleted — make the target `pub` and call it directly (e.g. the removed `persist_layout_on_quit`). Exception: thin accessors that are the only route to a private field (`QrateTableDelegate::cell`/`row_image`) are encapsulation, keep those.
- **No `new()`/`Default` for fieldless unit structs.** Construct `StatusBar`-style structs directly: `cx.new(|_| StatusBar)`.
- **One builder function per UI component, conditions inline.** Don't split a gpui component into a helper fn per visual part — use closures, `FluentBuilder::map`/`when`, and `match` inside a single function (see `render_image_frame` in `crates/workspace/src/panels/details.rs`). If a builder is genuinely shared across multiple `Render` impls, return `AnyElement` — propagating gpui's nested builder generics into several callers overflows rustc's stack at type-check time.
- **No speculative scaffolding.** Code "kept for later" outside the module tree, provider stubs full of `todo!()`, and builder methods only tests call are deletions, not investments — git history is the archive.
- **No speculative scaffolding.** Code "kept for later" outside the module tree, provider stubs full of `todo!()`, and builder methods only tests call are deletions, not investments — git history is the archive. **Two deliberate exceptions, do not flag or delete them:** the `ai` crate (`crates/ai/` — traits + Cohere/mock providers) is the planned home for AI review/embedding and stays despite its `todo!()` bodies; `crates/workspace/src/panels/log_viewer.rs` is the reusable line-coloring viewer that becomes the diagnostics content for `ProblemsPanel`, kept out of `mod.rs` until then.
- **Explanation comments.** If you have to write a comment of more than 1 line to justify a decision, that is the wrong decision. Go back and rethink the implimentation from the start in a different manner.

## gpui test modules
Expand Down
4 changes: 1 addition & 3 deletions crates/app/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ pub fn key_bindings() -> Vec<KeyBinding> {
// Settings. Declared in `app_menus` (it's a menu action first); the handler is already
// registered globally in `main.rs`, so this only adds the key.
KeyBinding::new("ctrl-,", OpenSettings, None),
// Find in the table. The action is declared in `crate::table` (app → table is one-way, so
// an app-declared action would be invisible to `TablePanel`). Scoped to the `TablePanel`
// context, not global, so it doesn't shadow Ctrl+F inside the cell editor's `Input`.
// `Search` is declared in `crate::table` (app→table is one-way); scoped so it spares the cell editor's Ctrl+F.
KeyBinding::new("ctrl-f", table::Search, Some("TablePanel")),
]
}
Expand Down
4 changes: 1 addition & 3 deletions crates/app/src/app_settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ fn columns_page(cx: &App) -> SettingPage {
let (get_key, set_key, remove_key) = (key.clone(), key.clone(), key.clone());
page = page.group(
SettingGroup::new()
// The column name is the group *title* so it also lists as a sub-entry under
// "Columns" in the sidebar, which only picks up titled groups. That makes it
// text-only, so Remove gets its own right-aligned row directly beneath it.
// Column name is the group title so it lists under "Columns" in the sidebar (titled groups only).
.title(name)
.item(SettingItem::render(move |_opts, _window, _cx| {
let remove_key = remove_key.clone();
Expand Down
23 changes: 5 additions & 18 deletions crates/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ use gpui_component::dock::DockPlacement;
use workspace::Workspace;

/// Opens the Settings window, focusing the existing one if it's already open.
// `gpui::App`, spelled out: a bare `App` here would resolve to this file's own
// `pub struct App` below, not the GPUI context type — local items always win
// over glob imports regardless of source order.
// `gpui::App` spelled out: a bare `App` binds to this file's own `struct App`, not the context type.
pub(crate) fn open_settings_window(cx: &mut gpui::App) {
if WindowRegistry::focus_or_clear(SETTINGS_WINDOW_KIND, cx) {
return;
Expand Down Expand Up @@ -99,9 +97,7 @@ fn set_main_window_title(window: &mut Window, cx: &gpui::App) {

pub(crate) fn open_main_window(cx: &mut gpui::App) {
if WindowRegistry::focus_or_clear(MAIN_WINDOW_KIND, cx) {
// The window already exists (opening a project just switched `CurrentProject`
// to a different one) — reload its layout instead of leaving the previous
// project's panels in place.
// Window already exists (a project switch); reload its layout instead of keeping the old one's.
if let Some(handle) = WindowRegistry::get(MAIN_WINDOW_KIND, cx) {
handle
.update(cx, |_, window, cx| {
Expand Down Expand Up @@ -183,10 +179,7 @@ impl App {
}
});

// Block the OS close button while a background task is running, and
// otherwise run the same final flush that `on_app_quit` does — the
// native X skips the app-quit path on Windows (zed#40385/#40290), so
// relying on `on_app_quit` alone loses state on OS-native close.
// Native X skips `on_app_quit` on Windows (zed#40385/#40290), so flush here too — unless locked.
window.on_window_should_close(cx, |_, cx| {
if WindowLock::is_locked(cx) {
return false;
Expand Down Expand Up @@ -324,20 +317,14 @@ fn main() {
cx.quit();
});

// Guaranteed flush before exit (gpui gives `on_app_quit` handlers 100ms).
// Two gaps close here: (1) both writers debounce at 450ms, so a write
// enqueued right before quit would otherwise be lost; (2) dock
// open/close toggles and edge resizes never emit `LayoutChanged`, so
// the final layout may never have been captured at all.
// Flush before exit: writers debounce 450ms, and dock toggles/resizes never emit `LayoutChanged`.
cx.on_app_quit(|cx| {
flush_all_state(cx);
async {}
})
.detach();

// The launcher (Recent Projects + Create New) is the real startup window — it opens
// the main window itself for a recent project, or a project-creation wizard window for
// "Create New".
// The launcher is the real startup window; it opens the main window or the wizard itself.
project_wizard::open_launcher_window(cx);
});
}
8 changes: 4 additions & 4 deletions crates/app/src/status_items/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub fn build_status_bar_registry(cx: &mut App, dock: WeakEntity<DockArea>) -> St
IconName::Info,
)
});
registry.add_left(left_panel);
registry.items_mut().add_left(left_panel);

// ponytail: diagnostics don't exist yet — count is a placeholder "0". Wire the real
// error/warning totals when ProblemsPanel grows content.
Expand All @@ -35,12 +35,12 @@ pub fn build_status_bar_registry(cx: &mut App, dock: WeakEntity<DockArea>) -> St
)
.label("0")
});
registry.add_left(problems);
registry.items_mut().add_left(problems);

// Text readout of the table's selected cell. Added to the right *before* the agent star so it
// lands leftmost in the right group (text items go right-leftmost, star stays rightmost).
let cell_location = cx.new(CellLocation::new);
registry.add_right(cell_location);
registry.items_mut().add_right(cell_location);

let agent = cx.new(|_| {
DockToggleButton::new(
Expand All @@ -50,7 +50,7 @@ pub fn build_status_bar_registry(cx: &mut App, dock: WeakEntity<DockArea>) -> St
IconName::Star,
)
});
registry.add_right(agent);
registry.items_mut().add_right(agent);

registry
}
4 changes: 2 additions & 2 deletions crates/app/src/title_items/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn build_title_bar_registry(cx: &mut App, dock: WeakEntity<DockArea>) -> Tit
let mut registry = TitleBarRegistry::new();

let menus = cx.new(|_| TitleMenus);
registry.add_left(menus);
registry.items_mut().add_left(menus);

for (id, placement, icon) in [
("title-panel-left", DockPlacement::Left, IconName::PanelLeft),
Expand All @@ -31,7 +31,7 @@ pub fn build_title_bar_registry(cx: &mut App, dock: WeakEntity<DockArea>) -> Tit
),
] {
let btn = cx.new(|_| DockToggleButton::new(id, dock.clone(), placement, icon));
registry.add_right(btn);
registry.items_mut().add_right(btn);
}

registry
Expand Down
4 changes: 1 addition & 3 deletions crates/cloud-sync/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,7 @@ pub struct SheetData {
/// off the UI thread.
pub fn fetch_sheet(link: &str) -> Result<SheetData, SheetSyncError> {
let SheetRef { id, gid } = parse_sheet_ref(link)?;
// ponytail: gid is forwarded so a link to a specific tab exports that tab
// when Google honors it; if Google ignores it for xlsx we still fall back
// to the first tab, same as before. Real multi-tab selection needs API v4.
// ponytail: gid forwarded as a hint, falls back to first tab; real multi-tab selection needs API v4.
let gid_param = gid.map(|g| format!("&gid={g}")).unwrap_or_default();
let url = format!("https://docs.google.com/spreadsheets/d/{id}/export?format=xlsx{gid_param}");
// Explicit timeout so a stalled connection returns an error instead of
Expand Down
4 changes: 1 addition & 3 deletions crates/project-wizard/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ impl SpreadsheetPreview {
}

impl From<cloud_sync::SheetData> for SpreadsheetPreview {
// ponytail: notes are dropped here — the wizard preview has no field for
// them yet. Carry `sheet.notes` through when the Problems/column-notes
// panels land (see the ".qrate project file" + "Problems panel" tasks).
// ponytail: `sheet.notes` dropped — no preview field yet; carry them through when the Problems panel lands.
fn from(sheet: cloud_sync::SheetData) -> Self {
Self {
headers: sheet.headers,
Expand Down
5 changes: 1 addition & 4 deletions crates/project-wizard/src/launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,7 @@ impl Render for Launcher {
.child(
v_flex()
.flex_1()
// min_h(0) lets this column be bounded to the row
// height so the inner list — not the column — scrolls,
// keeping the heading pinned. Without it the flex
// item's default min-height:auto grows to fit content.
// min_h(0) overrides the flex default min-height:auto so the inner list scrolls, not the column.
.min_h(px(0.))
.min_w(px(0.))
.gap_2()
Expand Down
4 changes: 1 addition & 3 deletions crates/project-wizard/src/steps/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ impl ProjectWizard {
)
.content(
v_flex().gap_2().mt_2().child(
// The pattern input lives *inside* the option card
// (appended to `option_card`'s own v_flex), revealed
// once the card is selected.
// Pattern input lives inside the option card, revealed once it's selected.
option_card(
"link-pattern",
"Use a custom pattern",
Expand Down
4 changes: 1 addition & 3 deletions crates/project-wizard/src/steps/review.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ impl ProjectWizard {
LinkMethod::ExactFilename => "exact filename",
LinkMethod::CustomPattern => "custom pattern",
});
// Blank projects go through the Columns step too, so honor any config
// loaded there; otherwise fall back to spreadsheet headers (empty for
// Blank), every column defaulting to Text.
// Prefer config from the Columns step; else fall back to spreadsheet headers, all Text.
let columns: Vec<project::ProjectColumn> = self
.config_preview
.as_ref()
Expand Down
17 changes: 4 additions & 13 deletions crates/project-wizard/src/wizard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,7 @@ impl ProjectWizard {
Ok(())
}
WizardStep::Files => match self.entry_kind {
// `skip_files` only waives the *folder* requirement below — it
// means "skip linking a files folder", not "skip checking the
// spreadsheet/sheet exists" (see its label in steps/files.rs).
// `skip_files` waives only the folder requirement, not the spreadsheet/sheet check.
EntryKind::Csv => {
if self.csv_preview.is_none() {
return Err("Choose a valid CSV spreadsheet".into());
Expand Down Expand Up @@ -354,9 +352,7 @@ impl ProjectWizard {
} else {
"Next →"
};
// When the sheet still needs checking, Next doubles as "Check" and is
// live regardless of the blocker. Otherwise a blocker greys out Next and
// its reason is shown beside it.
// While the sheet needs checking, Next acts as "Check" (no blocker); otherwise a blocker greys it out.
let blocker = if self.needs_sheet_check(cx) {
None
} else {
Expand Down Expand Up @@ -411,9 +407,7 @@ impl Render for ProjectWizard {
WizardStep::Review => self.render_review_step(window, cx).into_any_element(),
};

// Shared scaffold for every step: pinned breadcrumb on top, scrolling
// body in the middle, pinned Back/Next footer at the bottom. Each step
// only supplies `body`; navigation and validation live in the footer.
// Shared scaffold: pinned breadcrumb, scrolling body, pinned footer; each step supplies only `body`.
let breadcrumb = self.render_breadcrumb(cx).into_any_element();
let footer = self.render_footer(cx).into_any_element();

Expand All @@ -425,10 +419,7 @@ impl Render for ProjectWizard {
v_flex()
.id("wizard-body")
.flex_1()
// min_h(0) bounds this to the window height (below the title
// bar) so the scroll region — not the whole page — grows.
// Without it the flex item's default min-height:auto expands
// to fit all steps' content, pushing the footer off-screen.
// min_h(0) overrides flex min-height:auto so the scroll region grows, not the page.
.min_h(px(0.))
.p_5()
.gap_3()
Expand Down
4 changes: 1 addition & 3 deletions crates/table/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,7 @@ fn row(
// key would leave hover/focus on the wrong row.
Checkbox::new(SharedString::from(format!("filter-value-{value}")))
.checked(!is_excluded)
// The value goes in as a *child*, not `.label()`: the label path forces
// `line_height(1.0)` under an `overflow_hidden` parent, which clips descenders —
// underscores in ids vanished entirely.
// Value as a child, not `.label()`: the label's `line_height(1.0)` clips descenders here.
.child(
div()
.w_full()
Expand Down
Loading
Loading