Skip to content

TUI polish: paste debounce, status bar, crash logging, tool timers#17

Merged
Aaronontheweb merged 2 commits into
devfrom
feature/tui-polish-scrolling-status-paste
Feb 24, 2026
Merged

TUI polish: paste debounce, status bar, crash logging, tool timers#17
Aaronontheweb merged 2 commits into
devfrom
feature/tui-polish-scrolling-status-paste

Conversation

@Aaronontheweb
Copy link
Copy Markdown
Collaborator

Summary

  • Paste flood protection — Rx Buffer(100ms) coalesces rapid-fire submissions from pasted multi-line text into a single message, preventing the app from becoming unresponsive
  • Status bar overhaul — Shows model name (qwen3:30b), token usage with context window percentage (e.g., in=276 out=363 (1% ctx)), and status-based coloring (green=ready, yellow=generating, red=error)
  • Tool call elapsed timer — Live ElapsedTimeSegment ticks alongside spinner during tool execution; final elapsed time shown on completion (e.g., ✓ shell_execute → 8 (3.2s))
  • Crash logging — Top-level exception handler writes to ~/.netclaw/logs/crash-{timestamp}.log for diagnosable fatal errors in any mode
  • Port conflict fix — Kestrel bound to port 5199 to avoid conflicts with Aspire's dcpctrl on default port 5000
  • Noise reduction — Hid verbose thinking output from chat view, moved token usage from inline chat to status bar only, removed internal session ID from status bar

Test plan

  • dotnet build Netclaw.slnx — 0 errors, 0 warnings
  • dotnet test Netclaw.slnx — 110 tests pass
  • dotnet slopwatch analyze — 0 issues
  • Manual TUI test: status bar shows model + "Ready" in green
  • Manual TUI test: send message → usage with context % appears in status bar
  • Manual TUI test: tool calls show spinner + elapsed timer, completion shows final time
  • Manual TUI test: paste multi-line text → single submission
  • Manual TUI test: Ctrl+Q exits immediately
  • Crash logging: kill Kestrel port → crash log written to ~/.netclaw/logs/

- Add paste flood protection via Rx Buffer(100ms) to coalesce rapid-fire
  submissions from pasted multi-line text into a single message
- Add status bar with model name (qwen3:30b), context usage percentage,
  and status-based coloring (green=ready, yellow=generating, red=error)
- Hide verbose thinking output from chat view (too noisy for TUI)
- Move token usage from inline chat history to status bar only
- Remove session ID from status bar (internal noise)
- Add ElapsedTimeSegment for live tool call duration display with
  final elapsed time shown on completion (e.g., "✓ shell_execute → 8 (3.2s)")
- Add top-level crash logging to ~/.netclaw/logs/crash-{timestamp}.log
  for diagnosable fatal errors in any mode
- Bind Kestrel to port 5199 to avoid conflicts with Aspire's dcpctrl on 5000
- Inject SessionConfig into ChatViewModel for model name and context window
@Aaronontheweb Aaronontheweb enabled auto-merge (squash) February 24, 2026 00:04
Include supporting files required by the TUI and daemon mode changes:
- Termina package reference and CPM entry
- Daemon architecture with mode routing (chat/headless/run)
- SignalR gateway scaffold (SessionHub, output DTOs, security context)
- Config hot-reload watcher service
- Remove deprecated ConsoleChannel (replaced by TUI + headless)
- Updated specs, PRDs, and implementation plan
@Aaronontheweb Aaronontheweb merged commit 30a3f25 into dev Feb 24, 2026
3 checks passed
@Aaronontheweb Aaronontheweb deleted the feature/tui-polish-scrolling-status-paste branch February 24, 2026 00:10
Aaronontheweb added a commit that referenced this pull request Feb 24, 2026
Update PRDs (001, 004, 009), SPEC-011, and IMPLEMENTATION_PLAN to reflect
the OpenClaw-pattern architecture: persistent daemon (Netclaw.Daemon) with
thin CLI/TUI clients (Netclaw.Cli) connecting via SignalR.

Key changes:
- Two binaries: daemon owns Akka/persistence/tools, CLI is lightweight
- TUI chat becomes pure thin client rendering SessionOutput over SignalR
- CLI commands categorized as offline vs daemon-required
- Daemon management commands: start/stop/status/install/uninstall
- systemd user service registration (no sudo)
- New implementation tasks 1.26-1.31 for the split
- Tasks 1.10-1.12 marked as done (PRs #14, #16, #17)
Aaronontheweb added a commit that referenced this pull request Feb 24, 2026
* Revise architecture to daemon + thin client split

Update PRDs (001, 004, 009), SPEC-011, and IMPLEMENTATION_PLAN to reflect
the OpenClaw-pattern architecture: persistent daemon (Netclaw.Daemon) with
thin CLI/TUI clients (Netclaw.Cli) connecting via SignalR.

Key changes:
- Two binaries: daemon owns Akka/persistence/tools, CLI is lightweight
- TUI chat becomes pure thin client rendering SessionOutput over SignalR
- CLI commands categorized as offline vs daemon-required
- Daemon management commands: start/stop/status/install/uninstall
- systemd user service registration (no sudo)
- New implementation tasks 1.26-1.31 for the split
- Tasks 1.10-1.12 marked as done (PRs #14, #16, #17)

* Split Netclaw.App into Netclaw.Daemon and Netclaw.Cli

Structural split following the daemon + thin client pattern:

- Netclaw.Daemon (netclawd): Web SDK, actor system, SignalR hub,
  config watcher, health endpoint
- Netclaw.Cli (netclaw): CLI routing (chat, -p, init, doctor,
  daemon stub, config stub), TUI, headless channel

Shared types moved to library projects:
- Config POCOs (ProviderEntry, ModelSelection, ModelReference) →
  Netclaw.Configuration
- SessionOutputDto → Netclaw.Actors.Protocol

CLI temporarily keeps full Akka stack in-process with transitional
copies of ChatClientFactory/NetclawChatClientProvider. Task 1.28
refactors to SignalR client and removes Akka dependencies.

* Wire SessionHub to SessionPipeline via SessionRegistry

Replace the stub SessionHub with a functional implementation that
creates sessions, accepts messages, and streams output back to
SignalR callers. SessionRegistry (singleton) owns session state and
uses IHubContext to push output from Akka.Streams callbacks without
requiring a hub instance.

* Add daemon management commands (start, stop, status, install, uninstall)

DaemonManager handles the full lifecycle: binary discovery, detached
process spawning with PID file tracking, graceful SIGTERM shutdown on
Linux/macOS, and systemd user service registration. Windows service
support tracked in #21.

* Fix review issues and update README for daemon architecture

Review fixes:
- CLI port conflict: use port 0 (random) for transitional in-process mode
- SessionHub: call base.OnDisconnectedAsync for framework cleanup
- SessionRegistry: log output delivery errors instead of swallowing,
  guard against duplicate CreateSession per connection, check OfferAsync
  result for queue failures
- DaemonManager: don't redirect stdio (prevents SIGPIPE / pipe deadlock),
  validate process name before SIGTERM to prevent PID recycling kills,
  check kill() return value, proper DateTimeOffset for uptime calc
- Eliminate duplicate NetclawPaths instances in both Program.cs files

README updated to reflect daemon + thin client architecture with CLI
reference documentation.

* Harden daemon session ownership and PID handling

Enforce connection-scoped SignalR sessions with typed IDs, preserve sessions for reconnect, and tighten daemon PID/process validation for safer lifecycle commands. Add targeted tests and daemon PID file ownership to keep status/stop behavior reliable.
Aaronontheweb added a commit that referenced this pull request May 22, 2026
* fix(security): address open CodeQL alerts

- workflows: add `permissions: contents: read` to website-rebuild.yml
  so the rebuild job no longer inherits the repo's default GITHUB_TOKEN
  scope (cs-actions/missing-workflow-permissions, alert #21).
- webhooks: sanitize the raw route value via SanitizeWebhookId before
  logging the route_not_found case — ASP.NET URL-decodes path segments,
  so a caller could otherwise inject CR/LF into log lines
  (cs/log-forging, alert #22).
- lifecycle: add a SanitizeReason helper to DaemonLifecycleNotifier that
  strips control chars and caps length at 200, and apply it in
  NotifyShutdown (HTTP-tainted via ShutdownDaemonRequest.Reason) and
  NotifyCrashing (cs/log-forging, alert #18).
- reminders: harden ReminderDefinitionStore.GetPath with an explicit
  base-directory containment check on top of the existing
  Uri.EscapeDataString encoding, and add a regression test covering
  traversal, backslash, and absolute-path ids (cs/path-injection,
  alert #17).

* fix(security): tighten log/telemetry sanitization from review

Follow-ups from the post-CodeQL code review:

- webhooks: sanitize `route` once and pass the safe value to both
  WebhookTelemetry.RecordRouteNotFound and the log line, not just the log.
  The metric tag was the same log-forging surface and additionally an
  unbounded high-cardinality vector against the `route` dimension.
- lifecycle: widen the SanitizeReason predicate from `char.IsControl` to
  also strip U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR);
  these are categories Zl/Zp (not Cc) so IsControl misses them, but JSON-
  line readers and many log shippers still split on them.
- lifecycle: strip sanitized chars rather than replacing them with space,
  so an attacker-controlled `reason=ok\nlevel=critical` collapses to
  `reason=oklevel=critical` instead of `reason=ok level=critical` — a
  space would have been a plausible field separator to key=value log
  parsers.
- lifecycle: when truncating reason at 200 chars, back off one position
  if the cut would orphan a high surrogate, so downstream UTF-8 encoders
  don't emit U+FFFD or throw.
- tests: cover the four behaviors above with a Theory across CR/LF, NUL,
  U+2028, U+2029, and a surrogate-pair-boundary truncation case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant