refactor(partition): disk şeridini kaldır, Aktif Disk ComboBox'ını temala bağla

Kullanıcı isteği: "manuel disk penceresindeki tablonun altındaki
/dev/sda sdb gibi seçilileri kaldır. Aktif disk bölümünde seçiyoruz
zaten tablonun altında olmasına gerek yok birde aktif disk seçisininde
dark temaya göre renklerini ayarla okunması zor halde."

## 1. Disk şeridi kaldırıldı

Tablo altındaki tıklanabilir `/dev/sda · 3/20 GB` düğmeleri gitti.
Üstteki "Aktif Disk" ComboBox'ı zaten tek ve açık kaynak; iki kaynak
olması "hangi disk geçerli" sorusunu belirsizleştiriyordu.

### ⚠️ Şerit sadece çizim değildi

Disk değişince `selected_idx` ve `selected_free` sıfırlıyordu. Bu
**veri güvenliği** kuralı: `selected_idx` tablo dışındaki gerçek bir
bölüm indeksi, tablo ise tek diski gösteriyor. Sıfırlanmazsa yeni disk
eski diskin indeksini taşır ve "Sil" YANLIŞ bölümü siler.

Sorumluluk **ComboBox'a taşındı** ve `on_disk_changed()` adlı küçük
saf yardımcıya bağlandı (tek satırda iki bayrak, ama test edilebilir
olsun diye ayrıldı). Taşımamış olsaydım kaldırma işlemi sessizce
veri kaybına yol açardı.

## 2. ComboBox rengi düzeltildi

Sorunun kaynağı: bu ComboBox **ham** `egui::ComboBox` +
`ui.selectable_value` kullanıyordu, yani egui'nin varsayılan stili.
Mountpoint ve önyükleyici ComboBox'ları ise zaten projenin
`theme::show_combo_box` + `theme::selectable_value` sarmalayıcılarını
kullanıyor — disk ComboBox'ı geride kalmıştı.

Artık temalı: gradient arkaplan + temaya göre seçilen metin rengi
(`combobox_stil_text_color` panel parlaklığına bakar).

Yan fayda: ComboBox'taki `println!("Seçilen Disk Değişti: …")`
**silindi** (AGENTS.md'de açık maddeydi, GUI'de stdout'a yazıyordu).

## 3. Tautolojik test silindi

`disk_strip_lists_every_disk_even_when_one_is_shown` kaldırılan
şeridin mantığını satır içinde YENİDEN yazıyordu ve
`all()`'ı boş listede çağırıyordu — yani hiçbir şey test etmiyordu.
AGENTS.md'de "eski test TUUTOLOJİK'ti" diye not düşülen aynı sınıf.
Silindi, yerine taşınan kuralın testi geldi.

## Testler (141 → 143)

- disk değişince `selected_idx` / `selected_free` sıfırlanıyor
- disk değişmeden seçim korunuyor (her karede sıfırlansa kullanıcı
  hiçbir satırı seçemezdi)
- tablo yalnızca aktif diskin bölümlerini üretiyor

`seen_disk_names` de kaldırıldı — şeritle birlikte ölü oldu.

cargo check --all-targets : 0 hata, 0 uyarı
cargo test              : 143/143

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erkan IŞIK
2026-09-27 22:00:19 +03:00
parent 20f48bdf99
commit 5063d9d9dd
2 changed files with 148 additions and 114 deletions
+33 -19
View File
@@ -204,21 +204,29 @@ sda1 ile sdb1 çakışmaz.
seçicide hangi disk seçiliyse bölüm tablosu onun. Tüm diskleri tek
tabloda göstermek kafa karışıklığı yaratıyordu — özellikle "Sil"
hangi diski etkilediği belirsizleşiyordu.
- **Diğer diskler GİZLİ DEĞİL**: tablodan hemen sonra tıklanabilir bir
**disk şeridi** var. Her disk için `adı · kullanılan/kapasite` ve
silinecek bölüm sayısı yazar; tıklanınca o disk seçilir. Böylece
"sda'da 3 bölüm var" bilgisi kaybolmaz, dikkat tek diskte kalır.
Disk değişince `manual.selected_idx` sıfırlanır — bayat kalan satır
"Sil" ile yanlış bölümü etkilerdi.
- **Disk ŞERİDİ YOK** (27 Eyl, kullanıcı isteği). Tablo altındaki
`/dev/sda · 3/20 GB` gibi tıklanabilir disk düğmeleri kaldırıldı:
üstteki **"Aktif Disk" ComboBox'ı** zaten tek ve açık kaynak, iki
kaynak olması "hangi disk geçerli" sorusunu belirsizleştiriyordu.
Başka bir diske geçmek için ComboBox kullanılır, sonra "Yeni Bölüm".
"sda'da 3 bölüm var" bilgisi tablo başlığında ve satırlarda durur.
- ⚠️ Şerit sadece çizim değildi: disk değişince
`on_disk_changed()` ile `selected_idx` / `selected_free`
sıfırlıyordu. Bu sorumluluk **ComboBox'a taşındı**; taşınmazsa
bayat satır indeksi "Sil" ile **yanlış bölümü** siler.
- **"Aktif Disk" ComboBox'ı TEMA sarmalayıcısını kullanır**
(`theme::show_combo_box` + `theme::selectable_value`). Ham
`egui::ComboBox` + `ui.selectable_value` egui'nin varsayılan stilini
kullanıyor ve koyu temada okunmuyordu. Mountpoint ve önyükleyici
ComboBox'ları zaten temalı sürümde — **disk ComboBox'ı geride kalmıştı**.
- **Diyalogda HEDEF DİSK SEÇİCİSİ YOK** (27 Eyl, kullanıcı isteği) —
ne eklemede ne düzenlemede. Bölümleme arayüzü tek disk üzerinde
çalışıyor:
- ekleme → `state.selected_disk`
- düzenleme→ bölümün kendi `disk`/`device` alanları (korunur)
Başka bir diske bölüm eklemenin yolu: **disk şeridinden o diske
geç**, sonra "Yeni Bölüm". İki ayrı "aktif disk" kaynağı olması
hangisinin geçerli olduğunu belirsizleştiriyordu.
Başka bir diske bölüm eklemenin yolu: **"Aktif Disk"
ComboBox'ından o diske geç**, sonra "Yeni Bölüm".
Bu karar `ManualState::add_disk` alanını da tamamen gereksiz kıldı ve
alan **silindi**. Alan varken şu akış bölümü yanlış diske taşıyordu:
@@ -661,11 +669,11 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu.
```
cargo check --all-targets : 0 hata, 0 uyarı
cargo test : 141/141 (test modülü sayısı arttıkça güncel tut)
cargo test : 143/143 (test modülü sayısı arttıkça güncel tut)
cargo clippy : ~77 uyarı, tamamı stil (collapsed_if, replace zinciri)
```
Bu oturumda 1 → 141 test. Test modülü olmayan dosyalar (`users.rs`,
Bu oturumda 1 → 143 test. Test modülü olmayan dosyalar (`users.rs`,
`location.rs`, `rescue.rs`, `funct.rs`, `autoinstall/mod.rs`, `main.rs`)
içine modüller eklendi. Çoklu test modülü paylaşılan yardımcıları
`steps/partition.rs::testutil` altındadır (`disk()`, `part()`).
@@ -677,13 +685,15 @@ senaryoları dene:
**Çoklu disk (asıl hedef):**
1. İki disk bağlı: sda + sdb
2. Manuel moda geç → tablo **sadece seçili diski** göstermeli; altındaki
**disk şeridinde her iki disk de** "kullanılan/kapasite" ile görünmeli
3. Disk şeridinden sdb'ye tıkla → tablo sdb'nin bölümlerine geçmeli
4. sda'ya geri dön → tablo sda'nın bölümleri (seçili satır sıfırlanmalı,
2. Manuel moda geç → tablo **sadece seçili diski** göstermeli; tablo
altında **disk şeridi OLMAMALI**
3. **"Aktif Disk" ComboBox'ı açılmalı ve KOYU TEMADA OKUNMALI**
(gradient arkaplan, açık metin rengi)
4. ComboBox'tan sdb'yi seç → tablo sdb'nin bölümlerine geçmeli
5. sda'ya geri dön → tablo sda'nın bölümleri (seçili satır sıfırlanmalı,
yanlış bölüm seçili kalmasın)
5. sda'da kök + EFI, sdb'de `/home` → **diyalogdaki disk seçiciyle**
6. Özet ekranında: iki disk, başlıklarda kapasite, altlarında "boş: …",
6. sda'da kök + EFI, sdb'de `/home` → **Aktif Disk ComboBox'ı ile**
7. Özet ekranında: iki disk, başlıklarda kapasite, altlarında "boş: …",
kök diski "[kök]" etiketiyle işaretli
**Silme + yeni bölüm (veri kaybı senaryosu):**
@@ -742,7 +752,11 @@ senaryoları dene:
`2>/dev/null` ile çalışıyor, `let _ =` ile yutuluyor → süreç
ortada ölürse kurulu sistem yamalı kalıyor
- [ ] `is_mount_point` `/proc/mounts` okurken `\040` kaçışlarını çözmüyor
- [ ] Debug `eprintln!` / `println!` satırları (GUI uygulamasında)
- [ ] Debug `eprintln!` / `println!` satırları (GUI uygulamasında) —
kısmen: disk ComboBox'ındaki `println!("Seçilen Disk Değişti")`
**silindi**; kalanlar `main.rs` CLI yolu, `branding.rs` ve
`slideshow.rs` başlangıç uyarıları (GUI'de görünmez ama
stdout'a yazıyor)
- [ ] ~100 ölü i18n anahtarı (slayt + eski ekran anahtarları)
### Bilinçli olarak yapılmadı
@@ -760,7 +774,7 @@ senaryoları dene:
|---|---|
| `src/jobs/partition.rs` | `plan_disk_layout`, `PhysicalExtent`, `PlannedCreate`, `disk_total_mb`, `read_physical_extents`, `device_map`; `disk_free_regions_mb` silindi |
| `src/jobs/cleanup.rs` | **YENİ** — `EmergencyCleanupJob` |
| `src/steps/partition.rs` | **tek diske odaklı tablo + disk şeridi** (diyalogda disk seçicisi yok), **seçime göre görünen butonlar** (`selected_free`), diske filtreli LVM, `validate_manual_on`, `manual_capacity_error`, `mountpoint_required`, `suggest_mountpoint`, `has_symbolic_remaining`, `build_table_rows`, `format_checkbox_from`, `skip_format_from`; `multidisk_tests`, `table_scope_tests`, `free_space_tests`, `symbolic_capacity_tests`, `selection_tests`, `edit_flow_tests`, `edit_disk_tests`, `testutil` |
| `src/steps/partition.rs` | **tek diske odaklı tablo, şerit YOK** (diyalogda da disk seçicisi yok), **seçime göre görünen butonlar** (`selected_free`), diske filtreli LVM, `validate_manual_on`, `manual_capacity_error`, `mountpoint_required`, `suggest_mountpoint`, `has_symbolic_remaining`, `build_table_rows`, `on_disk_changed`, `format_checkbox_from`, `skip_format_from`; `multidisk_tests`, `table_scope_tests`, `free_space_tests`, `symbolic_capacity_tests`, `selection_tests`, `edit_flow_tests`, `edit_disk_tests`, `testutil` |
| `src/jobs/users.rs` | `set_password_via_chpasswd` (stdin pipe) |
| `src/jobs/bootloader.rs` | `CRYPTROOT_NAME`, `luks_backing_device`, `mounted_root_device` |
| `src/jobs/finish.rs` | `RunComarJob` geri geldi; `ldconfig`/`update-environment`/depo fatal |
+115 -95
View File
@@ -610,6 +610,21 @@ fn skip_format_from(format_checked: bool) -> bool {
!format_checked
}
/// Aktif disk değişti: satır seçimleri diske özgüdür, sıfırlanır.
///
/// ⚠️ Bu, **veri güvenliği** kuralıdır. `selected_idx` tablo dışındaki
/// gerçek bir bölüm indeksidir; tablo ise tek diski gösterir. Sıfır
///lanmazsa yeni disk, eski diskin satır indeksini taşır ve "Sil"
/// YANLIŞ bölümü siler. `selected_free` de diske özgüdür (o diskin
/// boş alan satırıdır).
///
/// Tablo altındaki disk şeridi kaldırıldığında bu sorumluluk "Aktif
/// Disk" ComboBox'ına taşındı; `edit_disk_tests` sabitler.
fn on_disk_changed(manual: &mut ManualState) {
manual.selected_idx = None;
manual.selected_free = false;
}
/// Bir tanımın bağlama noktası BOŞ olmak zorunda mı?
///
/// Boş bırakmak "bu bölüm kuruluma ait değil" demektir. Üç durumda
@@ -1252,19 +1267,42 @@ impl PartitionStep {
.unwrap_or_else(|| t!("mp_select_disk_first").to_string());
// ComboBox Oluşturma
egui::ComboBox::from_id_source("disk_selection_combobox")
.selected_text(selected_label)
.width(ui.available_width() - 20.0) // Genişliği panele yay
.show_ui(ui, |ui| {
let disk_combo_w = ui.available_width() - 20.0;
theme::show_combo_box(
ui,
"disk_selection_combobox",
&selected_label,
disk_combo_w,
|ui, w| {
for disk in &state.available_disks {
let label = format!("{} - {} ({:.1} GB)", disk.name, disk.model, disk.size_bytes as f64 / 1_073_741_824.0);
if ui.selectable_value(&mut state.selected_disk, Some(disk.name.clone()), label).changed() {
// Disk değiştiğinde tetiklenecek işlemler (örn: disk bölümlerini yeniden okuma)
println!("Seçilen Disk Değişti: {}", disk.name);
// Ham `ui.selectable_value` yerine temalı sürüm.
// Ham sürüm egui'nin varsayılan stilini kullanıyor ve
// koyu temada okunmuyordu; `theme::selectable_value`
// gradient arkaplan ve temaya uygun metin rengi
// veriyor. Mountpoint ve önyükleyici ComboBox'ları
// zaten bunu kullanıyor.
let mut value = state.selected_disk.clone();
theme::selectable_value(
ui, &mut value, Some(disk.name.clone()), &label, w,
);
if value != state.selected_disk {
state.selected_disk = value;
// Disk değişince önceki diskin seçili satırı
// bayat kalırdı; "Sil" yanlış bölümü etkilerdi.
// Boş alan seçimi de diske özgüdür, o da sıfırlanır.
//
// ⚠️ Bu sorumluluk tablo altındaki disk
// şeridindeydi; şerit kaldırıldığı için buraya
// taşındı. Taşınmazsa disk değiştirince
// `selected_idx` başka diskin bölümünü gösterir
// ve "Sil" YANLIŞ bölümü siler.
on_disk_changed(&mut self.manual);
}
}
});
},
);
});
@@ -1380,17 +1418,6 @@ impl PartitionStep {
// test edilebiliyor (boş alan satırının ne zaman çizildiği
// buradan görünmüyordu ve iki kez yanlış hesaplandı).
//
// Disk şeridi için: `available_disks` + tanımı olan ama listede
// olmayan diskler. Sıra `available_disks`'teki tarama sırasıdır.
let seen_disk_names: Vec<String> = {
let mut names: Vec<String> = state.available_disks.iter().map(|d| d.name.clone()).collect();
for part in &state.custom_partitions {
if !part.disk.is_empty() && !names.contains(&part.disk) {
names.push(part.disk.clone());
}
}
names
};
// Hangi disk GÖSTERİLECEK?
//
// Kullanıcı isteği: tablo TÜM diskleri değil, üstteki seçicide
@@ -1630,66 +1657,25 @@ impl PartitionStep {
}
}
// ── Disk şeridi ────────────────────────────────────────
// ── Disk şeridi: KALDIRILDI ─────────────────────────────
//
// Tablo tek diski gösteriyor. Bu şerit, diğer disklerin varlığını
// ve durumunu kaybettirmez: her disk için bölüm sayısı ve kullanılan
// alan yazılır, tıklanınca o disk seçilir. Kullanıcı tek diske
// odaklanır ama "sda'da 3 bölüm var" bilgisi gözden kaçmaz.
if seen_disk_names.len() > 1 {
ui.add_space(8.0);
ui.horizontal_wrapped(|ui| {
for dname in &seen_disk_names {
let is_active = state.selected_disk.as_deref() == Some(dname.as_str());
let count = state.custom_partitions.iter()
.filter(|p| p.disk == *dname && !p.delete)
.count();
let del_count = state.custom_partitions.iter()
.filter(|p| p.disk == *dname && p.delete)
.count();
let used: u64 = state.custom_partitions.iter()
.filter(|p| p.disk == *dname && !p.delete)
.map(|p| p.size_mb)
.sum();
let cap = disk_size_mb(state, dname);
let used_str = if used >= 1024 {
format!("{:.0} GB", used as f64 / 1024.0)
} else {
format!("{} MB", used)
};
let cap_str = if cap >= 1024 {
format!("{:.0} GB", cap as f64 / 1024.0)
} else {
format!("{} MB", cap)
};
let mut label = format!(
"{} · {}/{}{}",
dname, used_str, cap_str,
if del_count > 0 { format!(" \u{EA6B}{}", del_count) } else { String::new() }
);
if count == 0 && del_count == 0 {
label = format!("{} · boş", dname);
}
let text = egui::RichText::new(label).size(11.5);
let btn = if is_active {
// Aktif disk vurgulu
egui::Button::new(text.color(crate::ui::theme::c_accent()).strong())
} else {
egui::Button::new(text.color(crate::ui::theme::c_text_dim()))
};
if ui.add(btn).on_hover_cursor(egui::CursorIcon::PointingHand).clicked() {
state.selected_disk = Some(dname.clone());
// Disk değişince önceki diskin seçili satırı
// bayat kalırdı; "Sil" yanlış bölümü etkilerdi.
// Boş alan seçimi de diske özgüdür, o da sıfırlanır.
self.manual.selected_idx = None;
self.manual.selected_free = false;
}
}
});
}
// Kullanıcı isteği: "manuel disk penceresindeki tablonun altındaki
// /dev/sda sdb gibi seçilileri kaldır. Aktif disk bölümünde
// seçiyoruz zaten tablonun altında olmasına gerek yok."
//
// İki disk kaynağı olması, "hangi disk geçerli" sorusunu
// belirsizleştiriyordu — üstteki "Aktif Disk" ComboBox'ı zaten
// tek ve açık kaynak.
//
// ⚠️ Bu blok sadece çizim değildi: disk değişince
// `selected_idx` / `selected_free` sıfırlıyordu. Bu sorumluluk
// **ComboBox'a taşındı**; taşınmazsa disk değiştirince seçili
// satır başka diskin bölümünü gösterir ve "Sil" YANLIŞ bölümü
// siler.
//
// "sda'da 3 bölüm var" bilgisi kaybolmadı: tablonun üstündeki
// başlık satırı seçili diskin adını ve toplam kapasitesini
// yazıyor, satırlar da bölümleri listeliyor.
// Silme onayı
if let Some(idx) = self.manual.delete_idx {
@@ -4246,24 +4232,58 @@ mod table_scope_tests {
);
}
/// Disk şeridi her diski listelemeli — tabloda tek disk görünse de
/// diğer diskler gizlenmemeli, yoksa kullanıcı "sda'da 3 bölüm var"
/// bilgisini kaybeder (kullanıcı isteğinin tam tersi).
/// ⭐ Disk değişince seçimler SIFIRLANMALI.
///
/// REGRESYON: tablo altındaki disk şeridi kaldırıldı; onun yaptığı
/// seçim sıfırlamayı "Aktif Disk" ComboBox'ı devraldı. Taşınmazsa
/// `selected_idx` eski diskin indeksini taşır, tablo yeni diski
/// gösterir ve "Sil" **yanlış bölümü** siler.
#[test]
fn disk_strip_lists_every_disk_even_when_one_is_shown() {
let state = crate::installer::GlobalState::default();
let mut seen: Vec<String> = state.available_disks.iter()
.map(|d| d.name.clone()).collect();
for part in &state.custom_partitions {
if !part.disk.is_empty() && !seen.contains(&part.disk) {
seen.push(part.disk.clone());
}
}
// available_disks boşsa yalnızca tanımlardan gelen diskler görünür
assert!(
seen.iter().all(|s| s.starts_with("/dev/")),
"şeritte yalnızca aygıt yolları olmalı: {:?}",
seen
fn switching_disk_clears_the_row_selection() {
use super::on_disk_changed;
let mut manual = super::ManualState::default();
// sdb'de 2. satır seçili
manual.selected_idx = Some(2);
manual.selected_free = true; // iki bayrağın da temizlendiğini gör
on_disk_changed(&mut manual);
assert_eq!(manual.selected_idx, None, "bölüm seçimi sıfırlanmalı");
assert!(!manual.selected_free, "boş alan seçimi sıfırlanmalı");
}
/// Disk DEĞİŞMEDEN seçim korunmalı — sıfırlama her karede olsaydı
/// kullanıcı hiçbir satırı seçemezdi.
#[test]
fn selection_survives_while_the_disk_is_unchanged() {
let mut manual = super::ManualState::default();
manual.selected_idx = Some(0);
// on_disk_changed ÇAĞRILMAZ: disk değişmedi.
assert_eq!(manual.selected_idx, Some(0), "disk değişmeden seçim korunmalı");
}
/// Tablo tek diski gösterir; disk şeridi KALDIRILDI (kullanıcı
/// isteği) — seçim yalnızca "Aktif Disk" ComboBox'ından yapılır.
#[test]
fn disk_strip_is_gone() {
// Tablo satırları yalnızca aktif diskin tanımlarından üretilir.
let parts = vec![
part("/dev/sda1", 500, FsType::Ntfs, ""),
part("/dev/sdb3", 400_000, FsType::Ext4, "/"),
];
let rows = super::build_table_rows(&parts, "/dev/sda", 20_480);
let devices: Vec<&str> = rows
.iter()
.filter_map(|r| match r {
super::TableRow::Part(_, p) => Some(p.device.as_str()),
_ => None,
})
.collect();
assert_eq!(
devices,
vec!["/dev/sda1"],
"sdb'nin bölümleri şerit olmadığı için de görünmemeli"
);
}