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
21 changes: 21 additions & 0 deletions .cursor/rules/root-cause-first.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
description: Require source-level fixes for malformed or polluted domain data
alwaysApply: true
---

# Root-Cause-First Bug Fixing

When malformed, stale, duplicated, or unexpected data appears in the UI:

1. Do not start with a UI filter, hidden row, fallback label, or string-pattern special case.
2. Determine whether the value is persisted/remote domain data or only a presentation defect.
3. Inspect the authoritative source and trace all transformations and writers to the earliest boundary that created the invalid state.
4. Fix the invariant at that boundary: input parsing, API/RPC ingestion, persistence, sync reconciliation, or canonical state projection.
5. Add a regression test at the producing boundary. A render/selector test alone is insufficient.
6. Handle historical pollution separately: inventory dependencies, confirm destructive cleanup, clean narrowly, and read back the authoritative source.
7. UI filtering is allowed only for an explicit product requirement or as defense-in-depth after the source fix. Never use it as the sole fix for invalid upstream data.
8. Do not change adjacent valid behavior without an explicit request.

Before declaring success, report: authoritative source, root cause, producing write path, source-level invariant, historical remediation, and verification evidence.

Any UI predicate that hides malformed data must cite an explicit product requirement. If the value violates the domain model, reject the UI-only patch until the producing path is fixed and regression-tested.
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ If the skill block isn't already prefetched in your context, read its `SKILL.md`

---

## Root-Cause-First Bug Fixing

When malformed, stale, duplicated, or unexpected data appears in the UI:

1. **Do not start with a UI filter, hidden row, fallback label, or string-pattern special case.** First determine whether the value is real persisted/remote domain data or only a presentation defect.
2. Identify the authoritative source, inspect the actual stored payload, and trace every transformation and writer back to the earliest boundary that created the invalid state.
3. Fix the invariant at that authoritative boundary: user-input parsing, API/RPC ingestion, persistence write, sync reconciliation, or canonical state projection.
4. Add a regression test at the producing boundary proving the invalid state can no longer be created. A selector/render test alone is not sufficient.
5. Treat historical pollution separately: inventory dependent data, get confirmation before destructive cleanup, perform the narrowest cleanup, then read back the authoritative source.
6. UI filtering is allowed only when exclusion is an explicit product requirement or defense-in-depth **after** the source fix. It must never be the sole fix for invalid upstream data.
7. Do not change adjacent valid behavior unless the user explicitly requests it.

Before declaring the issue fixed, report the authoritative source, root cause, producing write path, source-level invariant, historical remediation, and verification evidence.

Review gate: any UI predicate introduced to hide malformed data must cite an explicit product requirement. If the value violates the domain model, reject the UI-only patch until the producing path is fixed and covered by a regression test.

---

## Default Delivery Flow

### Touching `*.tsx` files (UI work)
Expand Down
18 changes: 18 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ If the skill block isn't already prefetched in your context, read its `SKILL.md`

---

## Root-Cause-First Bug Fixing

When malformed, stale, duplicated, or unexpected data appears in the UI:

1. **Do not start with a UI filter, hidden row, fallback label, or string-pattern special case.** First determine whether the value is real persisted/remote domain data or only a presentation defect.
2. Identify the authoritative source, inspect the actual stored payload, and trace every transformation and writer back to the earliest boundary that created the invalid state.
3. Fix the invariant at that authoritative boundary: user-input parsing, API/RPC ingestion, persistence write, sync reconciliation, or canonical state projection.
4. Add a regression test at the producing boundary proving the invalid state can no longer be created. A selector/render test alone is not sufficient.
5. Treat historical pollution separately: inventory dependent data, get confirmation before destructive cleanup, perform the narrowest cleanup, then read back the authoritative source.
6. UI filtering is allowed only when exclusion is an explicit product requirement or defense-in-depth **after** the source fix. It must never be the sole fix for invalid upstream data.
7. Do not change adjacent valid behavior unless the user explicitly requests it.

Before declaring the issue fixed, report the authoritative source, root cause, producing write path, source-level invariant, historical remediation, and verification evidence.

Review gate: any UI predicate introduced to hide malformed data must cite an explicit product requirement. If the value violates the domain model, reject the UI-only patch until the producing path is fixed and covered by a regression test.

---

## Default Delivery Flow

### Touching `*.tsx` files (UI work)
Expand Down
24 changes: 24 additions & 0 deletions src-tauri/crates/project-management/src/projects/io/orgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub fn create_project_org(request: &CreateProjectOrgRequest) -> Result<ProjectOr
if name.is_empty() {
return Err("Org name is required".to_string());
}
if name.contains("://") {
return Err("Org name must be a name, not a URL".to_string());
}

let slug = normalize_slug(name);
if slug.is_empty() {
Expand Down Expand Up @@ -392,6 +395,27 @@ mod tests {
assert!(orgs.iter().any(|entry| entry.id == org.id));
}

#[test]
fn create_project_org_rejects_urls_as_names() {
let _sandbox = test_env::sandbox();

for name in [
"orgii://cloud/join?invite=abc",
"https://example.com/team",
"ssh://git@example.com/team",
] {
let error = create_project_org(&CreateProjectOrgRequest {
name: name.to_string(),
id: None,
})
.expect_err("URL must not be persisted as an org name");

assert_eq!(error, "Org name must be a name, not a URL");
}

assert_eq!(read_project_orgs().expect("read orgs").len(), 1);
}

#[test]
fn create_project_org_uniquifies_colliding_slug_and_key() {
let _sandbox = test_env::sandbox();
Expand Down
Loading