Skip to content

Commit acf8c66

Browse files
franzwarningclaude
andcommitted
WVDSH-1280: add first-run splash with ASCII glyph
Render the Wavedash glyph (diamond + two chevrons) as bright-mono half-block ASCII art, sampled from the source SVG. Shown once the first time the CLI is run on an interactive terminal (gated by a marker in the config dir), and as the home screen when `wavedash` is run with no subcommand. Skipped for non-TTY output (pipes/CI) and the `update` command. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3c45455 commit acf8c66

4 files changed

Lines changed: 124 additions & 5 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wavedash"
3-
version = "0.1.88"
3+
version = "0.1.89"
44
edition = "2021"
55
authors = ["Wavedash Team"]
66
description = "Cross-platform CLI tool for uploading game projects to wavedash.com"

src/main.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod init;
88
mod publish;
99
mod stats;
1010
mod updater;
11+
mod welcome;
1112

1213
use achievements::{
1314
handle_achievement_create, handle_achievement_delete, handle_achievement_update,
@@ -52,7 +53,7 @@ struct Cli {
5253
#[arg(long, global = true, help = "Enable verbose output")]
5354
verbose: bool,
5455
#[command(subcommand)]
55-
command: Commands,
56+
command: Option<Commands>,
5657
}
5758

5859
#[derive(Subcommand)]
@@ -367,14 +368,27 @@ async fn main() {
367368
async fn run() -> Result<()> {
368369
let cli = Cli::parse();
369370

371+
// Bare `wavedash` (no subcommand) is the home screen: show the splash and
372+
// point at --help.
373+
let Some(command) = cli.command else {
374+
welcome::show_home();
375+
return Ok(());
376+
};
377+
378+
// Greet on the very first interactive run (skip for `update`, which has its
379+
// own focused output).
380+
if !matches!(command, Commands::Update) {
381+
welcome::show_first_run_if_needed();
382+
}
383+
370384
// Check for updates in background, but skip if the user is already running `update`
371-
let update_handle = if matches!(cli.command, Commands::Update) {
385+
let update_handle = if matches!(command, Commands::Update) {
372386
None
373387
} else {
374388
Some(updater::check_for_update())
375389
};
376390

377-
match cli.command {
391+
match command {
378392
Commands::Init => {
379393
handle_init().await?;
380394
}

src/welcome.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use crate::config;
2+
use colored::Colorize;
3+
use std::fs;
4+
use std::io::IsTerminal;
5+
use std::path::PathBuf;
6+
7+
/// The Wavedash glyph — a diamond chasing two chevrons — rendered with
8+
/// half-block characters (▀▄█) so the diagonals stay crisp at this size.
9+
/// Sampled from the source `glyph.svg`; 20 columns wide, 10 rows tall.
10+
const GLYPH: &str = " ▄██▄
11+
▀███▄
12+
▄█▄ ▀███▄
13+
▀██▄ ▀███▄
14+
▄█▄ ▀██▄ ▀███▄
15+
▀█▀ ▄██▀ ▄███▀
16+
▄██▀ ▄███▀
17+
▀█▀ ▄███▀
18+
▄███▀
19+
▀██▀";
20+
21+
/// Width (in columns) the glyph is padded to so the wordmark lines up.
22+
const GLYPH_WIDTH: usize = 20;
23+
24+
fn marker_path() -> Option<PathBuf> {
25+
config::wavedash_dir().ok().map(|d| d.join(".welcomed"))
26+
}
27+
28+
/// Print the first-run splash once, the very first time the CLI is run on an
29+
/// interactive terminal. A marker file under the wavedash config dir records
30+
/// that we've greeted, so it never shows again. No-ops when stdout isn't a TTY
31+
/// (piped output, CI) so it never pollutes machine-readable output.
32+
pub fn show_first_run_if_needed() {
33+
if !std::io::stdout().is_terminal() {
34+
return;
35+
}
36+
let Some(marker) = marker_path() else {
37+
return;
38+
};
39+
if marker.exists() {
40+
return;
41+
}
42+
43+
print_splash();
44+
mark_welcomed();
45+
}
46+
47+
/// The home screen shown when `wavedash` is run with no subcommand. Always
48+
/// prints the splash (this is an explicit "show me the overview" invocation)
49+
/// and points at `--help` for the full command list. Also records the greeting
50+
/// so a later first command doesn't greet again.
51+
pub fn show_home() {
52+
print_splash();
53+
println!(
54+
" Run {} to see every command.",
55+
"wavedash --help".bold().bright_white()
56+
);
57+
println!();
58+
mark_welcomed();
59+
}
60+
61+
/// Record that we've greeted so the first-run banner never repeats. Failures
62+
/// here must never block the CLI, so errors are intentionally ignored.
63+
fn mark_welcomed() {
64+
let Some(marker) = marker_path() else {
65+
return;
66+
};
67+
if let Some(parent) = marker.parent() {
68+
let _ = fs::create_dir_all(parent);
69+
}
70+
let _ = fs::write(&marker, b"1");
71+
}
72+
73+
fn print_splash() {
74+
let lines: Vec<&str> = GLYPH.lines().collect();
75+
let wordmark = "W A V E D A S H".bold().bright_white();
76+
let tagline = "Play anywhere instantly".dimmed();
77+
78+
println!();
79+
for (i, line) in lines.iter().enumerate() {
80+
// Pad the plain text first, then colorize — coloring before padding
81+
// would pad the ANSI escape codes and break alignment.
82+
let glyph = format!("{:<width$}", line, width = GLYPH_WIDTH)
83+
.bright_white()
84+
.bold();
85+
match i {
86+
4 => println!(" {} {}", glyph, wordmark),
87+
5 => println!(" {} {}", glyph, tagline),
88+
_ => println!(" {}", glyph),
89+
}
90+
}
91+
92+
println!();
93+
println!(" {}", "Get started".bold().bright_white());
94+
let steps = [
95+
("wavedash auth login", "Sign in to your account"),
96+
("wavedash init", "Set up this project"),
97+
("wavedash dev", "Preview your game locally"),
98+
("wavedash build push", "Upload a build"),
99+
];
100+
for (cmd, desc) in steps {
101+
let cmd = format!("{:<22}", cmd).bold();
102+
println!(" {} {} {}", "$".dimmed(), cmd, desc.dimmed());
103+
}
104+
println!();
105+
}

0 commit comments

Comments
 (0)