A portfolio you visit over SSH. Every connection gets its own ratatui terminal: the whole site — home, projects, timeline, blog, contact — is drawn inside the SSH session.
$ ssh lsnnt.dev # or: ssh -p 2222 localhost
# any password / any public key is accepted- Everything is a TUI. No browser, no HTML — just a palette, box drawing and a keyboard.
- The blog renders in the terminal. Posts are pulled from
blog.lsnnt.devand re-flowed as CommonMark: headings, lists, quotes, code blocks with a border and a language label, links, images. - Read it like a pager. Move through the post list with
j/k, press Enter to open a post, Backspace/Esc to walk back out. - Live data. Pinned repositories (GitHub GraphQL), Codeforces rating and LeetCode solve count are refreshed every 10 minutes.
- Per-client state. Selection, scroll position and the opened post are
private to each SSH connection; a shared
AppStateholds the fetched data.
| Key | Action |
|---|---|
h p a t b c |
go to home / projects / about / timeline / blog / contact |
j / ↓, k / ↑ |
move down / up (post list, reader, projects, timeline) |
g / Home, G / End |
jump to the top / the bottom |
space / PageDown, PageUp |
page through a post |
Enter / → |
open the selected blog post |
Backspace / Esc / ← |
close the post, back to the list |
q / Ctrl-C |
disconnect |
Inside the reader the same key switches back to the list: pressing b while
reading a post takes you up one level.
src/main.rs ssh server: sessions, key decoding, background refresh
src/libs/
tuis.rs every screen (nav bar, body, footer)
view.rs per-client view state + key handling
input.rs raw bytes -> keys (escape sequences, split reads)
markdown.rs CommonMark -> ratatui lines
mblog.rs blog.lsnnt.dev: sitemap.xml + <permalink>.md
theme.rs palette and small widget helpers
getpinnedrepo.rs GitHub GraphQL (pinned repositories)
rating.rs Codeforces user.info
lcapi.rs LeetCode GraphQL
src/models/ serde models for the APIs above
Each connection gets a Terminal<CrosstermBackend<TerminalHandle>> whose
Write sink forwards bytes into the SSH channel, and its own View (page,
selections, scroll offsets, the re-flowed post). The server keeps one
AppState behind an RwLock; a background task refreshes it and a 250 ms tick
repaints only the clients whose data actually changed.
blog.lsnnt.dev is a Hashnode site, which gives us two things:
https://blog.lsnnt.dev/sitemap.xml— one<loc>per page,<lastmod>for the date. Entries that are not posts (/,/archive,/recommendations,/tag/..., …) are skipped.https://blog.lsnnt.dev/<slug>.md— the post as CommonMark. The leading#heading is the title, the rest is the body.
The markdown is parsed into a small block model (markdown.rs) and re-flowed
on demand: terminal lines depend on the window width, so they are re-rendered
whenever a client resizes, instead of being baked in at fetch time.
GET sitemap.xml -> <loc> list -> GET <loc>.md -> parse -> BlogPost { title, date, blocks }
Hashnode exports images as , which is not valid
CommonMark, so that attribute is stripped before parsing.
cargo run # listens on 0.0.0.0:2222 and prints the ssh command
ssh -p 2222 localhostEnvironment:
| Variable | Purpose |
|---|---|
GT_TOKEN |
GitHub token with read:user; without it the projects page stays empty |
RUST_LOG |
info by default; RUST_LOG=russh=debug for protocol-level logs |
cargo test # unit tests: markdown, sitemap parsing, keys, view state, every screen
cargo test -- --ignored --nocapture # hits the real blog (network)
cargo fmt && cargo clippy --all-targetsThe screen tests render every page at 40x12, 80x24, 120x40 and 200x60 through
ratatui's TestBackend and assert on the resulting text, so layout regressions
and panics show up without an SSH client.
- The server binds
0.0.0.0:2222; put it behind a reverse proxy or aniptablesredirect from port 22. - Authentication accepts any password and any public key (it is a portfolio, not a shell).
- When running without a controlling tty (systemd, containers), ratatui's
Terminal::resizecannot query the local terminal size. That failure is logged and ignored — it must not tear down the client's connection.
Built on russh, ratatui, tui-big-text, pulldown-cmark and textwrap.