Skip to content

Commit befa722

Browse files
authored
Server-assigned session names + non-blocking MonitorView session chip (#22)
* feat: review error stats * feat(sessions,ui): auto-name unnamed sessions + collapse MonitorView session info * fix(ui): lint
1 parent cd8d922 commit befa722

File tree

14 files changed

+587
-159
lines changed

14 files changed

+587
-159
lines changed

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ Agent-assisted contributions are welcome, but should be **supervised** and **rev
2222
## Workflow expectations
2323

2424
- Keep PRs focused and minimal.
25-
- Run `just test` and `just lint` when making code changes (or explain why you couldnt).
25+
- Run `just test` and `just lint` when making code changes (or explain why you couldn't).
2626
- Follow `CONTRIBUTING.md` (DCO sign-off, Conventional Commits, SPDX headers where applicable).
27+
- **Linting discipline**: Do not blindly suppress lint warnings or errors with ignore/exception rules. Instead, consider refactoring or improving the code to address the underlying issue. If an exception is truly necessary, it **must** include a comment explaining the rationale.
2728

2829
## Docker notes
2930

apps/skit/src/session.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,50 @@ pub fn system_time_to_rfc3339(time: SystemTime) -> String {
2525
offset_datetime.format(&Rfc3339).unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string())
2626
}
2727

28+
fn normalize_optional_name(name: Option<String>) -> Option<String> {
29+
name.and_then(|name| {
30+
let trimmed = name.trim();
31+
(!trimmed.is_empty()).then(|| trimmed.to_string())
32+
})
33+
}
34+
35+
fn fnv1a_64(input: &str) -> u64 {
36+
const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
37+
const FNV_PRIME: u64 = 0x0100_0000_01b3;
38+
39+
let mut hash = FNV_OFFSET_BASIS;
40+
for byte in input.as_bytes() {
41+
hash ^= u64::from(*byte);
42+
hash = hash.wrapping_mul(FNV_PRIME);
43+
}
44+
hash
45+
}
46+
47+
fn generate_session_name(session_id: &str) -> String {
48+
// Deterministic "docker-style" name derived from the session id.
49+
// This keeps names consistent across clients without requiring additional storage or APIs.
50+
const ADJECTIVES: &[&str] = &[
51+
"ancient", "brave", "calm", "clever", "dapper", "eager", "fancy", "gentle", "happy",
52+
"jolly", "kind", "lively", "mighty", "noble", "proud", "quick", "quiet", "shiny", "silly",
53+
"swift", "wise",
54+
];
55+
56+
const NOUNS: &[&str] = &[
57+
"antelope", "badger", "beaver", "bison", "cougar", "dolphin", "dragon", "eagle", "falcon",
58+
"fox", "gecko", "heron", "ibis", "koala", "lemur", "lynx", "otter", "panther", "puma",
59+
"raven", "tiger", "yak",
60+
];
61+
62+
let hash = fnv1a_64(session_id);
63+
let adjective_idx = usize::try_from(hash % ADJECTIVES.len() as u64).unwrap_or(0);
64+
let noun_idx = usize::try_from((hash >> 8) % NOUNS.len() as u64).unwrap_or(0);
65+
let adjective = ADJECTIVES[adjective_idx];
66+
let noun = NOUNS[noun_idx];
67+
let suffix = (hash >> 16) & 0xffff;
68+
69+
format!("{adjective}-{noun}-{suffix:04x}")
70+
}
71+
2872
fn timestamp_us_to_rfc3339(timestamp_us: u64) -> String {
2973
system_time_to_rfc3339(UNIX_EPOCH + Duration::from_micros(timestamp_us))
3074
}
@@ -134,6 +178,8 @@ impl Session {
134178
created_by: Option<String>,
135179
) -> Result<Self, String> {
136180
let session_id = Uuid::new_v4().to_string();
181+
let name =
182+
normalize_optional_name(name).or_else(|| Some(generate_session_name(&session_id)));
137183
let display_name = name.as_deref().unwrap_or(&session_id);
138184
tracing::info!(session_id = %session_id, name = %display_name, "Creating new dynamic session");
139185

crates/core/src/stats.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,13 @@ impl NodeStatsTracker {
132132
// Many nodes only increment one side of the counters (e.g. pure sources only `sent`,
133133
// pure sinks only `received`). Use the max to keep the threshold behavior consistent
134134
// across node shapes and avoid the `0.is_multiple_of(..)` pitfall.
135-
let packet_count = self.stats.received.max(self.stats.sent).max(self.stats.discarded);
135+
// Include errored so error-only updates don't get stuck waiting for packet activity.
136+
let packet_count = self
137+
.stats
138+
.received
139+
.max(self.stats.sent)
140+
.max(self.stats.discarded)
141+
.max(self.stats.errored);
136142

137143
// Always emit the first non-empty snapshot promptly so monitoring can "lock on"
138144
// even if the node later blocks under backpressure.

crates/nodes/src/audio/filters/gain.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,13 @@ impl ProcessorNode for AudioGainNode {
161161
}
162162
Err(e) => {
163163
tracing::warn!("Rejected invalid gain parameter: {}", e);
164+
stats_tracker.errored();
164165
}
165166
}
166167
}
167168
Err(e) => {
168169
tracing::warn!("Failed to deserialize params for volume_adjust: {}", e);
170+
stats_tracker.errored();
169171
}
170172
}
171173
},

0 commit comments

Comments
 (0)