This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-names/src/lib.rs
T
Miguel PalhasandClaude Opus 5 c9cfdb5668 feat(maestro): one-word handles, quieter project boxes
Session handles are one word from a single 218-entry list — instruments,
forms, notation and theory, tempo and dynamics — replacing the
adjective-noun pair. A batch is shuffled, so it repeats only once the
wordlist runs out.

Project boxes show the parent directory instead of the full path: the
last segment is already the name line above it. The last command is gone
from the box, and picking a project no longer pre-selects it — the
directory and what runs in it are independent choices. Both changes land
in the TUI picker too, which mirrors the web form's rules.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 22:40:49 +01:00

301 lines
5.8 KiB
Rust

//! The one home of the session-handle wordlist (#53).
//!
//! Previously duplicated in `web/src/names.ts`; every client now gets handles
//! from here, served by `GET /api/names`.
/// Every word a handle can be: instruments, forms, and the vocabulary of
/// tempo, dynamics, notation and theory. One word per handle — the list is
/// wide enough that a single word still reads as distinct.
pub const WORDS: &[&str] = &[
// Instruments
"cello",
"violin",
"viola",
"oboe",
"flute",
"piccolo",
"clarinet",
"bassoon",
"trumpet",
"trombone",
"tuba",
"horn",
"cornet",
"bugle",
"flugelhorn",
"euphonium",
"saxophone",
"recorder",
"timpani",
"harp",
"piano",
"organ",
"lyre",
"lute",
"mandolin",
"banjo",
"ukulele",
"guitar",
"sitar",
"koto",
"shamisen",
"balalaika",
"zither",
"dulcimer",
"kantele",
"erhu",
"guzheng",
"pipa",
"duduk",
"ocarina",
"kalimba",
"harmonica",
"accordion",
"bandoneon",
"bagpipe",
"harpsichord",
"clavichord",
"celesta",
"theremin",
"marimba",
"xylophone",
"vibraphone",
"glockenspiel",
"snare",
"cymbal",
"tambourine",
"castanet",
"djembe",
"tabla",
"bodhran",
"gong",
"chimes",
"contrabass",
"shawm",
"crumhorn",
"sackbut",
"didgeridoo",
// Forms and pieces
"sonata",
"fugue",
"canon",
"prelude",
"overture",
"rondo",
"nocturne",
"scherzo",
"etude",
"ballade",
"barcarolle",
"bagatelle",
"caprice",
"chaconne",
"passacaglia",
"toccata",
"intermezzo",
"interlude",
"minuet",
"gavotte",
"sarabande",
"gigue",
"allemande",
"courante",
"mazurka",
"polonaise",
"waltz",
"bolero",
"habanera",
"tarantella",
"fandango",
"serenade",
"aubade",
"requiem",
"cantata",
"oratorio",
"madrigal",
"motet",
"chorale",
"hymn",
"anthem",
"lullaby",
"elegy",
"rhapsody",
"symphony",
"concerto",
"fantasia",
"divertimento",
"quartet",
"quintet",
"sextet",
"septet",
"octet",
"trio",
// Structure, notation and theory
"octave",
"chord",
"cadence",
"cadenza",
"coda",
"motif",
"refrain",
"verse",
"chorus",
"bridge",
"finale",
"arpeggio",
"glissando",
"ostinato",
"counterpoint",
"descant",
"harmony",
"melody",
"rhythm",
"tempo",
"timbre",
"treble",
"clef",
"staff",
"quaver",
"crotchet",
"semibreve",
"fermata",
"trill",
"mordent",
"appoggiatura",
"portamento",
"vibrato",
"triplet",
"tuplet",
"downbeat",
"upbeat",
"anacrusis",
"syncopation",
"modulation",
"transposition",
"unison",
"semitone",
"tritone",
"diapason",
"tonic",
"dominant",
"mediant",
"ionian",
"dorian",
"phrygian",
"lydian",
"mixolydian",
"aeolian",
"locrian",
"pentatonic",
"chromatic",
"diatonic",
// Tempo, dynamics and articulation
"allegro",
"andante",
"adagio",
"presto",
"vivace",
"largo",
"grave",
"lento",
"moderato",
"crescendo",
"diminuendo",
"staccato",
"legato",
"forte",
"dolce",
"agitato",
"cantabile",
"sostenuto",
"tremolo",
"rubato",
"marcato",
"brillante",
"maestoso",
"grazioso",
"espressivo",
"tranquillo",
"animato",
"spirito",
"sotto",
"vivo",
"feroce",
"giocoso",
"affettuoso",
"risoluto",
"leggiero",
"pesante",
"morendo",
"accelerando",
"ritardando",
];
/// Upper bound for a `GET /api/names?count=N` batch.
pub const MAX_BATCH: usize = 100;
/// A fresh one-word handle, e.g. `nocturne`.
pub fn generate() -> String {
use rand::seq::SliceRandom;
let mut rng = rand::thread_rng();
(*WORDS.choose(&mut rng).unwrap()).to_string()
}
/// `count` fresh handles, distinct while the wordlist lasts (the caller
/// clamps; this only trusts its input).
pub fn generate_batch(count: usize) -> Vec<String> {
use rand::seq::SliceRandom;
let mut rng = rand::thread_rng();
let mut pool: Vec<&str> = WORDS.to_vec();
pool.shuffle(&mut rng);
(0..count)
.map(|i| match pool.get(i) {
// Past the wordlist a batch has to repeat itself.
Some(word) => (*word).to_string(),
None => generate(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn generated_name_is_a_single_known_word() {
let name = generate();
assert!(!name.contains('-'), "{name} is not one word");
assert!(WORDS.contains(&name.as_str()), "{name} not in WORDS");
}
#[test]
fn the_wordlist_has_no_duplicates_and_covers_a_full_batch() {
let unique: HashSet<&&str> = WORDS.iter().collect();
assert_eq!(unique.len(), WORDS.len(), "WORDS has duplicates");
assert!(
WORDS.len() >= MAX_BATCH,
"a full batch would have to repeat"
);
}
#[test]
fn batch_returns_the_requested_count_without_repeats() {
assert_eq!(generate_batch(1).len(), 1);
let batch = generate_batch(MAX_BATCH);
assert_eq!(batch.len(), MAX_BATCH);
for name in &batch {
assert!(WORDS.contains(&name.as_str()), "{name} not in WORDS");
}
let unique: HashSet<&String> = batch.iter().collect();
assert_eq!(unique.len(), batch.len(), "batch repeated a handle");
}
#[test]
fn rerolls_can_differ() {
let first = generate();
let differs = (0..20).any(|_| generate() != first);
assert!(differs);
}
}