Skip to content

Commit eb36037

Browse files
committed
Pluralize: catch adjective + plural cases, revert guaranteed-multi sites
Tightened the manual sweep with `\{[a-zA-Z_]+\} [a-z]+ [a-zA-Z][-a-zA-Z]+s\b` (count + adjective + plural). Real bugs the original `\{var\} \plural` regex missed got fixed; sites where the count is guaranteed > 1 got reverted with `// allowed-pluralize-noun: <reason>` markers so the source reads cleanly. Fixed (count could be 1): - `event_loop.rs`: `{event_count} live events` / `{event_count} replay + {live_count} live events` / `{new_file_count} new files, {} new dirs across {} affected dirs`. - `manager.rs`: `~{gap} pending FSEvents`. - `reconciler.rs`: `{total} buffered events`. - `writer.rs`: `{free} free pages` + the breakdown loop (carries singular+plural per entry; `{count} {name}` over already-plural names was a real bug). - `commands/settings.rs`: `{n} log files`. - `smb.rs`: `{n_conflicts} conflicting files` (test). - `font-metrics/index.ts`, `shortcuts-store.ts`, `DualPaneExplorer.svelte`, `TransferProgressDialog.svelte`: same shape. Reverted (count guaranteed > 1) with `// allowed-pluralize-noun`: - `MAX_PENDING_RESCANS` / `MAX_BUFFER_CAPACITY` / `MAX_USER_NOTE_CHARS` (constants). - `chars().count()` after `> MAX_USER_NOTE_CHARS` validation. - `validationIntervalDays` (const = 7) and `serverInvalidRetryCount` (guarded by `>= 3`) in the license dialog. - The dedup line in `event_loop.rs` (only fires when `event_count >= 2`). - The stress-test storm log + panic (storm fires many by design). - `font-metrics/index.ts` `widthCount` (Unicode-range scan yields thousands). - `settings-store.ts` registry-defaults count.
1 parent ec277ba commit eb36037

14 files changed

Lines changed: 80 additions & 48 deletions

File tree

apps/desktop/src-tauri/src/commands/error_reporter.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use crate::error_reporter::{
77
self, BundleKind, BundleManifest, BundleScope, FLOW_A_BUNDLE_CAP_MB, settings_defaults::SettingValue,
88
};
9-
use crate::pluralize::pluralize;
109
use serde::Serialize;
1110
use std::collections::HashMap;
1211

@@ -125,9 +124,9 @@ pub async fn save_error_report_to_disk(app: tauri::AppHandle, user_note: Option<
125124
fn validate_user_note(user_note: Option<String>) -> Result<Option<String>, String> {
126125
match user_note {
127126
Some(n) if n.chars().count() > MAX_USER_NOTE_CHARS => Err(format!(
128-
"User note is too long ({}). Maximum is {}.",
129-
pluralize(n.chars().count() as u64, "char"),
130-
pluralize(MAX_USER_NOTE_CHARS as u64, "char"),
127+
// allowed-pluralize-noun: both counts are guaranteed > MAX_USER_NOTE_CHARS (100_000).
128+
"User note is too long ({} chars). Maximum is {MAX_USER_NOTE_CHARS} chars.",
129+
n.chars().count(),
131130
)),
132131
other => Ok(other),
133132
}

apps/desktop/src-tauri/src/commands/settings.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ pub fn set_max_log_storage_mb(value: u64) -> Result<(), String> {
102102
Ok(0) => {}
103103
Ok(n) => log::info!(
104104
target: "cmdr_lib::logging",
105-
"Eager-pruned {n} log files after cap change ({value} MB → keep {new_keep}).",
105+
"Eager-pruned {} after cap change ({value} MB → keep {new_keep}).",
106+
crate::pluralize::pluralize(n as u64, "log file"),
106107
),
107108
Err(err) => return Err(format!("Failed to eager-prune log files: {err}")),
108109
}

apps/desktop/src-tauri/src/file_system/volume/smb.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4482,7 +4482,10 @@ mod tests {
44824482
std::fs::write(&path, &buf).expect("write source");
44834483
}
44844484

4485-
log::info!("regression: pre-uploading {n_conflicts} conflicting files to {unique_prefix}");
4485+
log::info!(
4486+
"regression: pre-uploading {} to {unique_prefix}",
4487+
crate::pluralize::pluralize(n_conflicts as u64, "conflicting file")
4488+
);
44864489
for i in 0..n_conflicts {
44874490
let name = format!("f_{i:04}.bin");
44884491
let dest_abs = dest_dir_abs.join(&name);

apps/desktop/src-tauri/src/indexing/event_loop.rs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use super::scanner;
1616
use super::store::{self, IndexStore};
1717
use super::watcher;
1818
use super::writer::{IndexWriter, WriteMessage};
19-
use crate::pluralize::{pluralize, pluralize_with};
19+
use crate::pluralize::pluralize;
2020

2121
// ── Live event loop ──────────────────────────────────────────────────
2222

@@ -216,8 +216,9 @@ pub(super) async fn run_live_event_loop(
216216
&volume_id,
217217
RescanReason::WatcherChannelOverflow,
218218
format!(
219-
"The filesystem watcher's event channel overflowed after \
220-
{event_count} live events. Some file changes were lost."
219+
"The filesystem watcher's event channel overflowed after {}. \
220+
Some file changes were lost.",
221+
pluralize(event_count, "live event")
221222
),
222223
);
223224
// Drain and discard remaining events: they're a partial
@@ -719,9 +720,9 @@ pub(super) async fn run_replay_event_loop(
719720
// ── Phase 2: After HistoryDone ───────────────────────────────────
720721

721722
if deduped_total < event_count {
723+
// allowed-pluralize-noun: dedup only kicks in when event_count >= 2.
722724
log::info!(
723-
"Replay: deduplicated {event_count} raw events to {deduped_total} unique \
724-
({:.0}% reduction)",
725+
"Replay: deduplicated {event_count} raw events to {deduped_total} unique ({:.0}% reduction)",
725726
(1.0 - deduped_total as f64 / event_count.max(1) as f64) * 100.0,
726727
);
727728
}
@@ -827,9 +828,9 @@ pub(super) async fn run_replay_event_loop(
827828
&volume_id,
828829
RescanReason::TooManySubdirRescans,
829830
format!(
830-
"Replay accumulated more than {} needing full \
831-
rescans. This typically means a major filesystem reorganization happened.",
832-
pluralize_with(MAX_PENDING_RESCANS as u64, "directory", "directories")
831+
// allowed-pluralize-noun: MAX_PENDING_RESCANS is the const 1_000.
832+
"Replay accumulated more than {MAX_PENDING_RESCANS} directories needing full \
833+
rescans. This typically means a major filesystem reorganization happened."
833834
),
834835
);
835836
if let Some(tx) = fallback_tx.take() {
@@ -894,9 +895,10 @@ pub(super) async fn run_replay_event_loop(
894895
&volume_id,
895896
RescanReason::WatcherChannelOverflow,
896897
format!(
897-
"The filesystem watcher's event channel overflowed after \
898-
{event_count} replay + {live_count} live events. Some file \
899-
changes were lost."
898+
"The filesystem watcher's event channel overflowed after {} + {}. \
899+
Some file changes were lost.",
900+
pluralize(event_count, "replay event"),
901+
pluralize(live_count, "live event"),
900902
),
901903
);
902904
if let Some(tx) = fallback_tx.take() {
@@ -918,7 +920,11 @@ pub(super) async fn run_replay_event_loop(
918920
}
919921
}
920922

921-
log::info!("Replay event loop: stopped ({event_count} replay + {live_count} live events)");
923+
log::info!(
924+
"Replay event loop: stopped ({} + {})",
925+
pluralize(event_count, "replay event"),
926+
pluralize(live_count, "live event"),
927+
);
922928
Ok(())
923929
}
924930

@@ -1247,10 +1253,10 @@ fn verify_affected_dirs(affected_paths: &HashSet<String>, writer: &IndexWriter)
12471253

12481254
if stale_count > 0 || new_file_count > 0 || !new_dir_paths.is_empty() {
12491255
log::debug!(
1250-
"Replay verification: {stale_count} stale, {new_file_count} new files, \
1251-
{} new dirs across {} affected dirs",
1252-
new_dir_paths.len(),
1253-
affected_paths.len(),
1256+
"Replay verification: {stale_count} stale, {}, {} across {}",
1257+
pluralize(new_file_count, "new file"),
1258+
pluralize(new_dir_paths.len() as u64, "new dir"),
1259+
pluralize(affected_paths.len() as u64, "affected dir"),
12541260
);
12551261
}
12561262

apps/desktop/src-tauri/src/indexing/manager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ impl IndexManager {
183183
let gap = current_id.saturating_sub(since_event_id);
184184
DEBUG_STATS.set_phase(
185185
ActivityPhase::Replaying,
186-
&format!("app launch, ~{gap} pending FSEvents"),
186+
&format!("app launch, ~{}", pluralize(gap, "pending FSEvent")),
187187
);
188188
log::info!("Replay: watcher started (since_event_id={since_event_id}, current={current_id})");
189189
}

apps/desktop/src-tauri/src/indexing/reconciler.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ impl EventReconciler {
147147
}
148148
if self.buffer.len() >= MAX_BUFFER_CAPACITY {
149149
log::warn!(
150-
"Reconciler: buffer cap reached ({}). \
151-
Dropping further events; a full rescan will run after the current scan.",
152-
pluralize(MAX_BUFFER_CAPACITY as u64, "event")
150+
// allowed-pluralize-noun: MAX_BUFFER_CAPACITY is the const 500_000.
151+
"Reconciler: buffer cap reached ({MAX_BUFFER_CAPACITY} events). \
152+
Dropping further events; a full rescan will run after the current scan."
153153
);
154154
self.buffer_overflow = true;
155155
self.buffer.clear();
@@ -180,7 +180,10 @@ impl EventReconciler {
180180
let mut last_event_id = scan_start_event_id;
181181
let mut affected_paths: Vec<String> = Vec::new();
182182

183-
log::info!("Reconciler: replaying {total} buffered events (scan_start_event_id={scan_start_event_id})");
183+
log::info!(
184+
"Reconciler: replaying {} (scan_start_event_id={scan_start_event_id})",
185+
pluralize(total as u64, "buffered event")
186+
);
184187

185188
for event in &self.buffer {
186189
// Skip events that the scan already covered

apps/desktop/src-tauri/src/indexing/stress_tests_concurrency.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,7 @@ fn test_listings_complete_under_reconciler_load_and_rapid_navigation() {
961961
checkpoint_handle.join().expect("checkpoint thread panicked");
962962

963963
let total_events = events_fired.load(Ordering::Relaxed);
964+
// allowed-pluralize-noun: stress test explicitly fires many reconciler events.
964965
log::info!(target: "stall_probe::test", "storm fired {total_events} reconciler events");
965966

966967
// ── Assertion: each listing must complete within the SLA ──────────
@@ -981,10 +982,10 @@ fn test_listings_complete_under_reconciler_load_and_rapid_navigation() {
981982
.collect::<Vec<_>>()
982983
.join("\n ");
983984
panic!(
984-
"{} exceeded the {SLA_MS}ms SLA under reconciler load (storm: {}):\n {summary}\n\n\
985+
// allowed-pluralize-noun: stress test explicitly fires many events.
986+
"{} exceeded the {SLA_MS}ms SLA under reconciler load (storm: {total_events} events):\n {summary}\n\n\
985987
See the captured `stall_probe::*` log lines above (run with --no-capture to see them on stderr).",
986988
pluralize(violators.len() as u64, "listing"),
987-
pluralize(total_events, "event"),
988989
);
989990
}
990991

apps/desktop/src-tauri/src/indexing/writer.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -381,31 +381,36 @@ impl WriterStats {
381381
return;
382382
}
383383

384-
let deltas: &[(&str, u64)] = &[
385-
("inserts", self.current.insert_entries - self.previous.insert_entries),
386-
("upserts", self.current.upsert_entry - self.previous.upsert_entry),
387-
("moves", self.current.move_entry - self.previous.move_entry),
388-
("deletes", self.current.delete_entry - self.previous.delete_entry),
384+
// (singular, plural, count). Pluralizing per row keeps the "+1 insert"
385+
// / "+5 inserts" form natural; baking `+s` everywhere reads as "+1 inserts".
386+
let deltas: &[(&str, &str, u64)] = &[
387+
("insert", "inserts", self.current.insert_entries - self.previous.insert_entries),
388+
("upsert", "upserts", self.current.upsert_entry - self.previous.upsert_entry),
389+
("move", "moves", self.current.move_entry - self.previous.move_entry),
390+
("delete", "deletes", self.current.delete_entry - self.previous.delete_entry),
389391
(
392+
"delete_subtree",
390393
"delete_subtrees",
391394
self.current.delete_subtree - self.previous.delete_subtree,
392395
),
393396
(
394-
"propagate",
397+
"propagation",
398+
"propagations",
395399
self.current.propagate_delta - self.previous.propagate_delta,
396400
),
397401
(
402+
"aggregate",
398403
"aggregates",
399404
self.current.compute_aggregates - self.previous.compute_aggregates,
400405
),
401-
("flushes", self.current.flush - self.previous.flush),
402-
("other", self.current.other - self.previous.other),
406+
("flush", "flushes", self.current.flush - self.previous.flush),
407+
("other", "others", self.current.other - self.previous.other),
403408
];
404409

405410
let parts: Vec<String> = deltas
406411
.iter()
407-
.filter(|(_, count)| *count > 0)
408-
.map(|(name, count)| format!("{count} {name}"))
412+
.filter(|(_, _, count)| *count > 0)
413+
.map(|(singular, plural, count)| pluralize_with(*count, singular, plural))
409414
.collect();
410415

411416
let breakdown = if parts.is_empty() {
@@ -1388,7 +1393,10 @@ fn handle_incremental_vacuum(conn: &rusqlite::Connection) {
13881393
if let Err(e) = conn.execute_batch("PRAGMA incremental_vacuum(2000)") {
13891394
log::warn!("Writer: incremental_vacuum failed: {e}");
13901395
} else {
1391-
log::debug!("Writer: incremental_vacuum reclaimed up to 2000 of {free} free pages");
1396+
log::debug!(
1397+
"Writer: incremental_vacuum reclaimed up to 2000 of {}",
1398+
pluralize(free as u64, "free page")
1399+
);
13921400
}
13931401
}
13941402
Ok(_) => {} // No free pages, nothing to do

apps/desktop/src/lib/file-explorer/pane/DualPaneExplorer.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,8 +1717,12 @@
17171717
return
17181718
}
17191719
log.debug(
1720-
'openDeleteDialog: opening delete confirmation. {count} valid entries, sourceVolId={volId}',
1721-
{ count: validEntries.length, volId: getPaneVolumeId(focusedPane) },
1720+
'openDeleteDialog: opening delete confirmation. {count} {entriesNoun}, sourceVolId={volId}',
1721+
{
1722+
count: validEntries.length,
1723+
entriesNoun: pluralize(validEntries.length, 'valid entry', 'valid entries'),
1724+
volId: getPaneVolumeId(focusedPane),
1725+
},
17221726
)
17231727
17241728
const sourceItems: DeleteSourceItem[] = validEntries.map((e) => ({

apps/desktop/src/lib/file-operations/transfer/TransferProgressDialog.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,10 @@
447447
let unlisteners: UnlistenFn[] = []
448448
449449
function cleanup() {
450-
log.debug('Cleaning up {count} event listeners', { count: unlisteners.length })
450+
log.debug('Cleaning up {count} {listenersNoun}', {
451+
count: unlisteners.length,
452+
listenersNoun: pluralize(unlisteners.length, 'event listener'),
453+
})
451454
for (const unlisten of unlisteners) {
452455
unlisten()
453456
}

0 commit comments

Comments
 (0)