Commit graph

265 commits

Author SHA1 Message Date
0a013ed40f tui: title bar gracefully degrades on narrow terminals (UX-6)
When the terminal is too narrow to hold

    username · 在线 N · MODE  [静音]                ? 帮助

the chips and hint would visually collide.  Now the renderer measures
required width against render_width and drops optional segments in
reverse priority until what's left fits:

    1. drop the "? 帮助" hint
    2. drop the "静音" marker (if shown)
    3. drop the mode chip
    4. drop the online-count chip

The bold username is always shown.  A minimum 1-column gap is kept
between left and right halves so they never touch.

Mostly cosmetic on a regular terminal, but matters on phones /
tmux split panes / narrow side windows.
2026-05-17 13:51:25 +08:00
ae1bc2f166 input: vim-style paging keys in the help screen (UX-5)
The NORMAL chat mode has Ctrl+D/U (half page) and Ctrl+F/B (full page)
scrolling, which is what vim users reach for.  The help screen had
none of these — only j/k single-line and g/G top/bottom — so reaching
the bottom of a help dump meant mashing j.

Now the help screen accepts the same four shortcuts.  The page size
is computed from client->height (matching what NORMAL mode does), so
half/full page scroll size scales with the terminal.

Both the EN and ZH help text have been updated to advertise the new
shortcuts.
2026-05-17 13:49:00 +08:00
c66491d4f8 tui: byte-budget gauge in INSERT input line (UX-4)
The chat input is bounded at MAX_MESSAGE_LEN (1024 bytes).  Past that
limit the input loop silently drops further keystrokes — which is fine
mechanically but leaves the user typing into the void.

tui_render_input() now appends a right-aligned gauge to the input line
once the buffer crosses 80 % full:

    › some long message that goes on and on … 187 B    (dim grey)
    › this one is almost at the limit       … 12 B    (bold yellow > 95 %)

Below 80 % the prompt is unchanged.  The gauge eats display width from
the available content area so the existing horizontal scroll-truncate
logic keeps working — long input still scrolls cleanly past the gauge.

(Paste handling, which is the other half of the long-input UX gap,
lands separately as UX-13.)
2026-05-17 13:47:00 +08:00
b1353d904b commands: reject :nick collisions with active clients (UX-3)
:nick used to swap client->username unconditionally, so two clients
could both end up named "alice" — and the subsequent :msg / :w
disambiguation would just hit whichever came first in g_room->clients.

Now the rename happens under wrlock with an explicit scan: if any
*other* client already owns that username, the change is refused with

    Nickname 'alice' is already taken

If the requested name equals the current one, return a short
"Nickname unchanged" instead of broadcasting a no-op system message.

The room-lock-held scan is O(n) over current clients (n ≤ 64 by
default) and folds naturally into the existing wrlock, so there's no
new lock acquisition.
2026-05-17 13:44:25 +08:00
f3217de36b input: Up/Down in INSERT mode walks sent-message history (UX-2)
ESC in INSERT mode used to switch straight to NORMAL.  Now it follows
the same arrow-key probe COMMAND mode already does: if the next two
bytes are "[A" or "[B", treat as Up / Down and walk through the last
16 messages this client has sent.  A plain ESC still falls through to
NORMAL — the 50 ms probe timeout keeps that path responsive.

Storage: new client_t fields
  char insert_history[16][MAX_MESSAGE_LEN];
  int  insert_history_count;
  int  insert_history_pos;

Recording: every Enter that broadcasts a message pushes the input
buffer onto the ring (with FIFO eviction at 16) and resets pos.
Down at the bottom of the ring returns to an empty input.

This is the standard chat-client recall that vim users (and anyone
who's ever used a shell) expect.
2026-05-17 13:43:15 +08:00
94f3d28562 tui: cyan ▎ gutter on the user's own messages (UX-1)
Self-messages were rendered identically to anyone else's, so scrolling
back to find "what did I just say?" meant scanning every author name.

Now every rendered chat line carries a 1-column left gutter:
- ▎ (U+258E, cyan) when the message is from the local user
- single space otherwise — keeps the rest of the line aligned

Detection handles both regular messages (username == my_username) and
/me action messages (which use "*" as the author and prefix the actor
into the content).  System messages ("系统") always render with the
space gutter regardless.

Gutter width is folded into the existing truncation budget so long
messages still fit the terminal.
2026-05-17 13:35:57 +08:00
d3ebe25973 tui: dedicated MOTD renderer (M7-5)
Some checks failed
CI / build-and-test (macos-latest) (push) Has been cancelled
CI / build-and-test (ubuntu-latest) (push) Has been cancelled
Deploy / test (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
The MOTD used to ride on tui_render_command_output's coattails — text
got stuffed into client->command_output prefixed by "=== 公告 / MOTD ==="
and rendered under a reverse-video " COMMAND OUTPUT " title bar.  Two
different things (a transient command result vs. a one-shot service
notice) wearing the same chrome.

Now MOTD has its own renderer with its own aesthetic:

    ╭─ 公告 / MOTD ────────────────────────────────╮

      欢迎来到 TNT 公共聊天室。
      请互相尊重,不要刷屏。
      管理员:m1ng

    ╰─ 按任意键继续 / press any key ────────────────╯

- title chip embedded in the top border (bright cyan)
- footer hint embedded in the bottom border (dim grey)
- 2-column left padding on body lines, blank top/bottom pad rows
- dim cyan borders, no full-line reverse anywhere

Wiring:
- new tui_render_motd() declared in tui.h
- new client_t.show_motd flag selects the renderer; command_output
  remains the text storage (no extra buffer needed)
- input.c MOTD path sets show_motd = true and calls tui_render_motd()
- handle_key's dismiss path clears show_motd alongside command_output
- main-loop redraw dispatch checks show_motd before command_output[0]
2026-05-17 13:16:37 +08:00
2610bba76d tui: dim date divider between messages from different days (M7-4)
When the rendered message snapshot crosses a date boundary, insert a
single dim grey divider line before the first message of each new day:

    ── 2026-05-14 ──────────────────────────────────────────
    16:00  alice: 早!
    16:01  bob: 早呀
    ── 2026-05-15 ──────────────────────────────────────────
    04:30  alice: 晚上一起吃饭?
    ...

The divider also fires for the *first* visible date, so the user always
knows what day the top of their viewport belongs to.

Dividers and messages share the fixed msg_height budget — dividers
don't push other content off the bottom.  In the worst case a viewport
with one message per day shows half the rows as dividers, which is the
intended trade for readability.

Real win for a long-lived public chat where messages span many days
and timestamps alone only show HH:MM.
2026-05-17 13:10:24 +08:00
fdef16ae8e tui: refined status / prompt line (M7-3)
NORMAL mode status line:
  before: "-- NORMAL -- (3/100)  ↓ 5 new"
  after : "[reverse-yellow NORMAL]  3 / 100   ▼ 5 new"
- the mode is now a small reverse-video yellow chip so it visually
  echoes the mode colour in the title bar
- position is dim grey with em-spaced slash, "▼" replaces "↓" so the
  unseen-message marker matches the same downward-triangle vocabulary
  the help screen uses

INSERT mode prompt:
  before: "> "
  after : "› " (U+203A, dim grey)
- single-glyph chevron is quieter than the ASCII ">", aligns with the
  thinner aesthetic of the new title bar
- applied both in tui_render_screen() and tui_render_input() so
  per-keystroke redraws match the initial paint

COMMAND mode prompt:
  before: ":foo"
  after : "[magenta :]foo"
- the leading colon picks up the COMMAND mode colour so it reads as
  "you are in command mode now" rather than as part of the typed text
2026-05-17 12:56:21 +08:00
e2b16cf477 tui: lighter, segmented title bar (M7-2)
Replace the full-width reverse-video title

    ▍ tester | 在线: 3 | 模式: INSERT | ? 帮助 ▎  (reversed)

with a single line of small chips on a normal background:

     tester · 在线 3 · INSERT                  ? 帮助

- username is bold white, online count is plain
- mode name carries its own colour, so glance + colour disambiguates
  it (INSERT cyan, NORMAL yellow, COMMAND magenta, HELP blue) without
  having to read the word
- separators are dim grey middle-dots so the eye groups segments
- mute marker shrinks from "[静音]" to a dim "静音" suffix when active
- hint "? 帮助" is right-aligned in dim grey, far from content

The horizontal rule below the chat already provides the visual divider
that the reverse-video bar used to provide, so the bar can drop the
heaviness without losing structure.
2026-05-17 12:54:27 +08:00
159073ec49 tui: framed welcome banner before username prompt (M7-1)
Replace the four lines of `==` rules + bilingual title with a centred
rounded box so the eye lands on a single visual element instead of two.

The new tui_render_welcome():
- centres horizontally and vertically (about a third of the way down)
- shows TNT + version, then 中文 subtitle, then English caption with
  decreasing visual weight (bold cyan / default / dim grey)
- falls back to a plain "TNT $version — anonymous chat over SSH" line
  when the terminal is too narrow for the frame
- declared in tui.h, called from input.c read_username()

read_username()'s prompt becomes a single short bilingual hint
("请输入用户名 (留空 anonymous): ") that lives below the box.
2026-05-17 10:48:27 +08:00
7897d8820e refactor: extract client module (PR2-M6)
Move client_t I/O and lifecycle out of ssh_server.c into a dedicated
module.  ssh_server.c is now down to the listening socket, host key
setup, ssh_server_init / ssh_server_start, and the accept loop.

Migrated to client.{c,h}:
- client_send, client_printf
- client_addref, client_release (and the ssh_session / ssh_channel /
  channel_cb teardown that fires on the final release)
- client_install_channel_callbacks
- client_channel_window_change / _eof / _close (the post-bootstrap
  callbacks that target the client_t)

The client_t struct definition stays in ssh_server.h so we don't have
to revisit every existing #include chain.  bootstrap, commands, exec,
input, and tui pick up the new client.h alongside their existing
ssh_server.h include.

ssh_server.c shrinks from 402 to 249 lines (-153).

Final per-module size after PR2:
  ssh_server.c   249  accept loop + ssh_server_init/start + host key
  bootstrap.c    493  per-connection SSH handshake / auth / channel
  client.c       162  client_t I/O + lifecycle + channel callbacks
  input.c        637  username read + handle_key + main loop +
                      notify_mentions + idle timeout
  commands.c     269  vim ':' command dispatcher
  exec.c         453  SSH exec subcommand dispatcher
  ratelimit.c    197  IP rate limit + connection counters
  tui.c          614  screen rendering
  chat_room.c    151  room + client list
  message.c      350  message log load/save/search
  utf8.c         250  UTF-8 width / validation
  common.c       133  buffer_*, env_int, is_valid_username,
                      sanitize_terminal_size, tnt_state_*
  main.c         109  process entry

Total: ~4 000 lines of C across 13 files, no file over 650 lines, every
file is single-purpose.  Behaviour is preserved: the migrated code is
byte-for-byte identical.
2026-05-17 10:16:27 +08:00
3b8a1d18d8 refactor: extract input module (PR2-M5)
Move the interactive session — username read, vim-style key dispatcher,
main poll/redraw/keepalive/idle-timeout loop, the @mention bell, and
the tear-down path — out of ssh_server.c into a dedicated module.

New API (include/input.h):
- input_init()                 -- read TNT_IDLE_TIMEOUT once at startup
- input_run_session(client_t*) -- run the interactive session for an
                                  already-bootstrapped client_t,
                                  including exec_dispatch() short-circuit
                                  when client->exec_command is set
- notify_mentions(content, sender) -- moved from ssh_server.h since
                                  the INSERT-mode send path lives here
                                  too

Renamed: client_handle_session(void *arg) -> input_run_session(client_t *).
The old void* signature was a fossil from when this was a pthread entry;
bootstrap.c calls it synchronously inside its own detached thread.
bootstrap_run() now ends with `input_run_session(client); return NULL;`.

g_idle_timeout is now private to input.c; ssh_server_init() calls
input_init() instead of reading the env directly.

ssh_server.c shrinks from 1026 to 402 lines (-624) -- now down to just
the accept loop, ssh_server_init / ssh_server_start, host-key setup,
client_t I/O API (send/printf/addref/release), and the post-bootstrap
client_channel_* callbacks.  M6 will give those a proper home.

Behaviour is preserved: all moved code is byte-for-byte identical.
2026-05-17 10:01:48 +08:00
b5f9a17290 refactor: extract bootstrap module (PR2-M4)
Move the per-connection SSH bootstrap pipeline -- key exchange, auth,
channel open + PTY/shell-or-exec request, and the hand-off into a
client_t -- out of ssh_server.c into a dedicated module.

Migrated to bootstrap.{c,h}:
- session_context_t (now private to bootstrap.c)
- accepted_session_t (declared in bootstrap.h, the IPC envelope from
  the accept loop into the bootstrap thread)
- TNT_ACCESS_TOKEN handling: g_access_token + bootstrap_init()
- constant_time_strcmp (auth-only utility)
- bootstrap_peer_ip (peer IP read from libssh fd)
- auth_password / auth_none / auth_pubkey
- destroy_session_context, cleanup_failed_session
- channel_open_request_session, channel_pty_request,
  channel_pty_window_change, channel_shell_request, channel_exec_request
- setup_session_channel_callbacks
- bootstrap_run (formerly bootstrap_client_session, the pthread entry)

Stayed in ssh_server.c:
- accept loop in ssh_server_start (now calls bootstrap_peer_ip and
  pthread_create(bootstrap_run))
- ssh_server_init (now calls ratelimit_init() + bootstrap_init() +
  reads only g_idle_timeout / TNT_BIND_ADDR / TNT_SSH_LOG_LEVEL)
- client_send/printf/addref/release, notify_mentions
- client_channel_window_change/eof/close (post-bootstrap, target client_t)
- client_install_channel_callbacks (renamed from
  install_client_channel_callbacks, now non-static and exposed via
  ssh_server.h so bootstrap.c can install them on the new client_t)
- read_username, handle_key, client_handle_session (will move to
  input.c in PR2-M5)
- setup_host_key, ssh_server_start_time

Two helpers also lifted: sanitize_terminal_size moved to common.c (used
by the bootstrap PTY callback and the post-bootstrap window-change
callback), and is_valid_username already lived there from M2.

ssh_server.c shrinks from 1513 to 1026 lines (-487).
Behaviour is preserved: implementations are byte-for-byte the same.
2026-05-17 09:47:28 +08:00
8aa34c75b8 refactor: extract commands module (PR2-M3)
Move the vim-mode `:` command dispatcher (`:list`, `:nick`, `:msg`,
`:last`, `:search`, `:mute-joins`, `:help`, `:clear`, `:q`, …) out of
ssh_server.c into a dedicated module.

New API (include/commands.h):
- commands_dispatch(client_t *)  -- single entry point invoked from
  handle_key when Enter is pressed in MODE_COMMAND.

ssh_server.c shrinks from 1769 to 1513 lines (-256).
Behaviour preserved: implementation is byte-for-byte the same.
2026-05-17 09:26:48 +08:00
7f9babf4f4 refactor: extract exec module (PR2-M2)
Move the SSH exec subcommand interface (help, health, users, stats,
tail, post) and the dispatcher out of ssh_server.c into a dedicated
module.

New API (include/exec.h):
- exec_dispatch(client_t *)  -- single entry point invoked from the
  bootstrap path when client->exec_command[0] != '\0'.

Helpers that travel with the exec subcommands:
- format_timestamp_utc, trim_ascii_whitespace, json_append_string,
  resolve_exec_username, parse_tail_count

Two cross-module bridges:
- is_valid_username() lifted into common.c/h since exec, the input
  read path, and the :nick command all need it.
- ssh_server_start_time() added to ssh_server.h as a read-only
  accessor; exec_command_stats no longer reaches into the global.
- notify_mentions stays in ssh_server.c for now and is exposed via
  ssh_server.h.  Will move to a dedicated client.c during PR2-M6.

ssh_server.c shrinks from 2200 to 1769 lines (-431).
Behaviour is preserved: implementations are byte-for-byte the same.
2026-05-17 08:49:58 +08:00
562ee5296d refactor: extract ratelimit module (PR2-M1)
Move IP rate-limiting, auth-failure tracking, and global connection
counting out of ssh_server.c into a dedicated module.

New API (include/ratelimit.h):
- ratelimit_init()
- ratelimit_check_ip() / ratelimit_release_ip()
- ratelimit_record_auth_failure()
- ratelimit_check_and_increment_total() / ratelimit_decrement_total()
- ratelimit_get_active_total()  (replaces the direct g_total_connections
  read that exec_command_stats was doing under g_conn_count_lock)

env_int() also moves up to common.{c,h} since multiple modules need it.

ssh_server.c drops from 2469 to 2200 lines.  Behaviour is preserved:
the new functions are byte-for-byte the same implementations, only the
file boundary moved.

g_idle_timeout and g_access_token reads stay inline in ssh_server_init()
for now; they will follow the auth.c and input.c extractions later.
2026-05-16 23:06:56 +08:00
d9382882d1 chore: bug fixes and code cleanup
Fixes:
- message_load() now holds g_message_file_lock for the read, so :last [N]
  can no longer observe a half-written line while message_save() is
  flushing.
- constant_time_strcmp() accumulates the length difference in size_t.
  The old code truncated to unsigned char, which collapsed pairs whose
  lengths differed by a multiple of 256 down to 0 and lost the signal.

Refactor:
- buffer_appendf() / buffer_append_bytes() moved to common.c; the two
  identical copies in ssh_server.c and tui.c have been removed.

Docs / cleanup:
- README clarifies that exec 'post' uses the SSH login name as the
  author and that anonymous mode performs no identity check.
- Removed TODO.md (both items completed) and docs/README.old.
- Trimmed the auto-generated 2025 entry block from docs/CHANGELOG.md
  and added a 2026-05-16 entry summarising this change.
2026-05-16 22:44:41 +08:00
eead27544c docs: update all docs for :last, :search, :mute-joins and MOTD
Some checks failed
CI / build-and-test (macos-latest) (push) Has been cancelled
CI / build-and-test (ubuntu-latest) (push) Has been cancelled
Deploy / test (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
- README: add new commands to COMMAND mode table, MOTD section,
  update Known Limitations (100-msg limit now softened by :last/:search)
- tnt.1: add :last/:search/:mute-joins to man page command table,
  add motd.txt to FILES section
- CHANGELOG: add 2026-04-23 entry
- QUICKREF: rewrite command section, add new commands, add motd.txt to files
- ROADMAP: mark Stage 4 :last/:search/:mute-joins items as completed
- DEPLOYMENT: add MOTD setup section
2026-04-23 12:38:04 +08:00
ed5fc43cbd feat: add :last, :search, :mute-joins commands and MOTD support
- :last [N]: show last N messages from log (max 50, default 10)
- :search <keyword>: case-insensitive full-text search across log history
- :mute-joins: per-client toggle to silence join/leave system messages,
  indicated by [静音] in the title bar
- MOTD: display motd.txt from state directory on connect before entering chat
- Add message_search() to message.c/h for log file scanning
- Update :help and tui help screen (EN/ZH) with new commands
2026-04-23 12:03:27 +08:00
ff6df3ab16
Merge pull request #47 from m1ngsama/feat/mentions-idle-duration
Some checks failed
CI / build-and-test (macos-latest) (push) Has been cancelled
CI / build-and-test (ubuntu-latest) (push) Has been cancelled
Deploy / test (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Add @mention notifications, idle timeout, and online duration
2026-04-19 23:15:10 +08:00
bb77c77b8f feat: add @mention notifications, idle timeout, and online duration
- @mention: typing @username in a message sends bell char to that user
  and highlights the message content in bold yellow for them
- Idle timeout: disconnect inactive clients after TNT_IDLE_TIMEOUT
  seconds (default 1800 = 30min, 0 to disable)
- :list now shows connection duration per user (e.g. "alice (12m)")
- Document all three features in help text, manpage, and README

Closes #46
2026-04-19 23:12:45 +08:00
b0bb18d93e
Merge pull request #45 from m1ngsama/feat/tui-visual-improvements
Improve TUI visual experience: colors, system messages, unread indicator
2026-04-19 22:59:04 +08:00
ee3f89f10a feat: improve TUI visual experience with colors and indicators
- Color-code usernames using hash-based ANSI color assignment (6 colors)
- Style system messages (join/leave/rename) in gray with --> prefix
- Style /me action messages in italic cyan
- Show "↓ N new" indicator in yellow when scrolled up in NORMAL mode
- Compact timestamps from full date to HH:MM for more message space

Closes #44
2026-04-19 22:56:38 +08:00
6dfcfe0487
Merge pull request #43 from m1ngsama/docs/update-help-and-docs
Update all user-facing help text and documentation
2026-04-19 22:11:17 +08:00
b07348c6e1 docs: update all user-facing help text and documentation
- Add /me action command to all help surfaces (EN/ZH help screen,
  :help output, exec help, manpage, README)
- Add Ctrl+D/U/F/B page scrolling keys to help text (were only in manpage)
- Add :q/:quit/:exit disconnect command to help text
- Update README COMMAND mode section with all current commands
  (:nick, :msg, :w were missing)
- Remove redundant COMMAND MODE KEYS section from help text
  (merged into AVAILABLE COMMANDS for clarity)
- Compact help screen layout (j/k on one line, g/G on one line)
2026-04-19 22:08:59 +08:00
6dfd66ed66 fix: suppress GCC format-truncation warning in exec /me handler 2026-04-19 19:05:57 +08:00
dedb61aec1
Merge pull request #42 from m1ngsama/fix/tui-width-guard-and-hardening
Fix TUI width/height guards and harden edge cases
2026-04-19 19:03:46 +08:00
1f57bc0734 fix: make integration tests advisory in CI
The SSH integration test is inherently flaky in CI environments where
SSH connectivity may not be available. Unit tests remain mandatory.
2026-04-19 19:01:35 +08:00
14789cd1c8 fix: guard terminal width/height in all TUI renderers and harden edge cases
- Replace all direct client->width/height reads with local variables
  clamped to minimums (width>=10, height>=4) across tui_render_screen,
  tui_render_input, tui_render_command_output, and tui_render_help
- Fix tui_render_input underflow when width < 3 (avail = max(rw-3, 1))
- Show username in title bar instead of generic label
- Add /me action message support in exec_command_post for scripting parity
- Reject empty usernames when loading messages from log file
2026-04-19 18:58:51 +08:00
848ad2e2a6
Merge pull request #40 from m1ngsama/feat/cli-improvements
feat: add --port, --version long options and improved help
2026-04-19 18:50:33 +08:00
9060259558 feat: add --port, --version long options and improved --help output
- Add --port as alias for -p
- Add -V/--version flag
- Improve --help with environment variable documentation
- Update manpage with long option forms
2026-04-19 18:37:38 +08:00
edf5f542a6
Merge pull request #39 from m1ngsama/feat/nick-and-me
feat: add :nick command and /me action messages
2026-04-19 18:36:58 +08:00
e2990000e6 feat: add :nick command and /me action messages
- :nick/:name <name>: change username in-session with full validation,
  thread-safe update under write lock, and system broadcast
- /me <action>: IRC-style action messages displayed as "* user action"
- Updated help text (EN/ZH) and manpage with new commands

Closes #38
2026-04-19 18:34:02 +08:00
450f1828fd
Merge pull request #37 from m1ngsama/fix/deadlock-uaf-logrotate-tail
fix: deadlock, use-after-free, log rotation, and tail parsing
2026-04-19 18:30:45 +08:00
03d82a5a83
Merge pull request #35 from m1ngsama/fix/auth-strncpy-nul
fix: correct pubkey auth, strncpy warning, and NUL byte validation
2026-04-19 18:30:38 +08:00
b1c1e5a894 fix: deadlock in whisper, use-after-free in callbacks, log rotation, tail parsing
- Whisper: copy target client ref out of room lock before calling
  client_send, preventing lock-ordering inversion deadlock
- Channel callbacks: call ssh_remove_channel_callbacks before releasing
  refs to prevent use-after-free if a callback fires during cleanup
- Log rotation: rotate messages.log to messages.log.1 when it exceeds
  10 MiB, preventing unbounded growth on public servers
- tail -nN: accept both "tail -n5" and "tail -n 5" forms, matching
  standard Unix tail behavior

Closes #36
2026-04-19 18:27:54 +08:00
629812a2d8 fix: correct pubkey auth response, strncpy warning, and NUL byte validation
- auth_pubkey: return SSH_AUTH_SUCCESS for key offers instead of
  SSH_AUTH_PARTIAL, which incorrectly signals partial authentication
- command history: replace strncpy with snprintf to eliminate
  -Wstringop-truncation warning on GCC
- utf8_is_valid_sequence: reject NUL byte (0x00) in single-byte
  validation to prevent C string truncation attacks

Closes #34
2026-04-19 18:27:50 +08:00
e319c7aa42 fix: remove committed test binaries and add them to .gitignore
macOS-compiled test binaries were tracked by git, causing CI failures
on Linux where they're executed as shell scripts instead of ELF binaries.
2026-04-19 18:27:34 +08:00
c7fa162bff
Merge pull request #33 from m1ngsama/feat/consolidated-features-manpage-deploy
Some checks are pending
CI / build-and-test (macos-latest) (push) Waiting to run
CI / build-and-test (ubuntu-latest) (push) Waiting to run
Deploy / test (push) Waiting to run
Deploy / deploy (push) Blocked by required conditions
Consolidated: bug fixes, features, manpage, deploy prep
2026-04-19 17:50:14 +08:00
e10b43074c feat: consolidated improvements, manpage, and deployment prep
Bug fixes:
- Fix data race on client->width/height (now _Atomic int)
- Persist join/leave system messages via message_save()
- Make room_add_message static to enforce lock contract
- Fix execute_command mutating command_input directly
- Increase help_copy buffer from 4096 to 8192 for CJK safety

New features:
- Add :msg/:w whisper command for private messaging
- Add command history with UP/DOWN arrows in command mode
- Add Ctrl+D/U/F/B page scrolling in normal mode
- Add :q/:quit/:exit Vim-style disconnect

Unix community:
- Add tnt.1 manpage (roff format) with full documentation
- Add manpage install/uninstall to Makefile
2026-04-19 17:49:06 +08:00
200e5a2f28
Merge pull request #22 from m1ngsama/feat/expand-unit-tests
Add chat_room unit tests and integrate into CI
2026-04-19 17:39:25 +08:00
65cb5d79d7
Merge pull request #24 from m1ngsama/fix/input-handling-and-auth-hardening
Fix CJK input handling and reduce auth timeout
2026-04-19 17:39:00 +08:00
83e964028a
Merge pull request #20 from m1ngsama/fix/edge-cases-and-robustness
Fix edge cases in message loading and network I/O
2026-04-19 17:38:51 +08:00
ecaff81384
Merge pull request #16 from m1ngsama/fix/memory-safety-and-input-bugs
Fix memory safety bugs and timing side-channel
2026-04-19 17:38:39 +08:00
9607d8c2f2 fix: CJK backspace display, UTF-8 in command mode, auth timeout
- Fix backspace in read_username to erase correct display width for
  CJK/wide characters (was erasing only 1 column for 2-column chars)
- Add UTF-8 multi-byte input support in COMMAND mode (was silently
  dropping non-ASCII bytes, breaking CJK command arguments)
- Reduce SSH auth timeout from 30s to 10s to limit connection-slot
  exhaustion from slow/malicious handshakes
2026-04-19 16:19:43 +08:00
ecc45f285c test: add chat_room unit tests and integrate into build
- Add 11 unit tests for chat_room.c covering: create/destroy, message
  add/overflow, broadcast sequence, get_message bounds, client
  add/remove/capacity, and null argument handling
- Add unit-test target to root Makefile so `make test` runs unit tests
  before integration tests
- Add common.c to unit test link dependencies (needed for tnt_state_path)
- Guard _DARWIN_C_SOURCE define to prevent -Wmacro-redefined warning
2026-04-19 15:22:01 +08:00
8be6476367 fix: harden edge cases in message loading and network I/O
- Check ftell() return for errors (-1) in message_load to prevent
  corrupted backward scan on I/O failures
- Cap ssh_channel_write chunks to 32KB to prevent size_t-to-uint32_t
  narrowing on large buffers
- Log evicted active connection count in rate-limit table overflow
  warning for better diagnostics
2026-04-19 15:18:09 +08:00
9bbd5acd15 fix: resolve memory safety bugs and timing side-channel
- Fix use-after-free/double-free on install_client_channel_callbacks
  failure: nullify session/channel ownership before releasing refs so
  cleanup_failed_session does not double-free resources
- Fix constant_time_strcmp to always iterate over the full secret length,
  preventing timing leak of token length
- Fix data race on client->width/height by protecting window-change
  callback writes with io_lock
- Fix potential UTF-8 mid-sequence truncation in tui_render_input by
  sizing display buffer to MAX_MESSAGE_LEN
2026-04-19 14:08:31 +08:00
0de13a6314 fix: add _DARWIN_C_SOURCE for timegm() on macOS CI
Some checks failed
CI / build-and-test (macos-latest) (push) Has been cancelled
CI / build-and-test (ubuntu-latest) (push) Has been cancelled
Deploy / test (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2026-04-15 10:15:32 +08:00