Skip to content

Tui#518

Merged
tcsenpai merged 23 commits intotestnetfrom
tui
Dec 8, 2025
Merged

Tui#518
tcsenpai merged 23 commits intotestnetfrom
tui

Conversation

@tcsenpai
Copy link
Contributor

@tcsenpai tcsenpai commented Dec 4, 2025

User description

Added a tui as default visualization


PR Type

Enhancement


Description

  • Implemented comprehensive TUI (Terminal User Interface) system as default visualization for the node

  • Created TUIManager orchestrator managing layout, keyboard input, and panel coordination with 12 tabbed categories (All, Core, Net, Peer, Chain, Sync, Cons, ID, MCP, XM, DAHR, CMD)

  • Developed CategorizedLogger with 10 log categories using ring buffers for fixed-size circular storage and event emission for real-time TUI display

  • Implemented LegacyLoggerAdapter maintaining backward compatibility with existing Logger API while routing through new categorized logging system

  • Added command terminal (CMD tab) with history and built-in commands (help, quit, clear, status, peers, sync)

  • Integrated node info header displaying version, status, identity, port, peers, block number, and sync status

  • Implemented scrollable log display with auto-scroll toggle, per-tab scroll offsets, and real-time filtering

  • Added console interception to route all output through TUI logger, preventing external library corruption

  • Integrated TUI initialization in main node startup with --no-tui flag for fallback to standard output

  • Refactored logger system to use new TUI-integrated architecture while preserving all existing import paths

  • Added ASCII logo assets for TUI header display in multiple sizes


Diagram Walkthrough

flowchart LR
  A["Legacy Logger API"] -->|"Adapter"| B["LegacyLoggerAdapter"]
  B -->|"Routes to"| C["CategorizedLogger"]
  C -->|"Emits events"| D["TUIManager"]
  D -->|"Displays"| E["12 Tabbed Interface"]
  E -->|"Includes"| F["Logs, Info Panel, CMD Terminal"]
  G["Node Startup"] -->|"Initializes"| D
  H["Console Output"] -->|"Intercepted"| C
Loading

File Walkthrough

Relevant files
Enhancement
6 files
TUIManager.ts
Main TUI orchestrator with panels and controls                     

src/utilities/tui/TUIManager.ts

  • Implemented comprehensive TUI orchestrator managing layout, keyboard
    input, and panel coordination
  • Created tabbed interface with 12 categories (All, Core, Net, Peer,
    Chain, Sync, Cons, ID, MCP, XM, DAHR, CMD)
  • Implemented scrollable log display with auto-scroll toggle, per-tab
    scroll offsets, and real-time filtering
  • Added command terminal (CMD tab) with history, built-in commands
    (help, quit, clear, status, peers, sync)
  • Integrated node info header showing version, status, identity, port,
    peers, block number, sync status
  • Implemented console interception to route all output through TUI
    logger, preventing external library corruption
+1435/-0
CategorizedLogger.ts
TUI-ready categorized logging system with ring buffers     

src/utilities/tui/CategorizedLogger.ts

  • Created new categorized logging system with 10 log categories (CORE,
    NETWORK, PEER, CHAIN, SYNC, CONSENSUS, IDENTITY, MCP, MULTICHAIN,
    DAHR)
  • Implemented ring buffer for fixed-size circular log storage per
    category to prevent memory bloat
  • Added event emission for TUI integration and real-time log display
  • Implemented file logging with per-level and per-category log files for
    backward compatibility
  • Added TUI mode flag to suppress direct terminal output when TUI is
    active
+600/-0 
LegacyLoggerAdapter.ts
Backward compatibility layer for legacy Logger API             

src/utilities/tui/LegacyLoggerAdapter.ts

  • Created backward compatibility adapter maintaining old Logger API
    while routing through new CategorizedLogger
  • Implemented tag-to-category mapping for automatic category inference
    from existing log messages like [PEER], [MAIN]
  • Preserved all legacy methods (info, error, debug, warning, critical,
    custom, only, cleanLogs, getPublicLogs, getDiagnostics)
  • Maintained file logging paths and custom log file support for existing
    code
+410/-0 
index.ts
TUI module exports and type definitions                                   

src/utilities/tui/index.ts

  • Created TUI module barrel export consolidating CategorizedLogger,
    LegacyLoggerAdapter, and TUIManager
  • Exported types (LogLevel, LogCategory, LogEntry, LoggerConfig,
    NodeInfo, TUIConfig) for type safety
  • Set up proper module structure for gradual migration from legacy to
    new logging API
+31/-0   
index.ts
Integrated TUI into main node startup and logging               

src/index.ts

  • Added TUI initialization in main() with fallback to standard output on
    failure
  • Replaced console.log and term.* calls with categorized logger calls
    throughout warmup and preMainLoop
  • Added --no-tui command-line flag to disable TUI and use scrolling log
    output
  • Integrated TUIManager updates at key lifecycle points (after warmup,
    after preMainLoop, when running)
  • Updated node info display with real-time peer count, block number, and
    sync status
+107/-43
mainLoop.ts
Updated mainLoop diagnostics logging                                         

src/utilities/mainLoop.ts

  • Replaced console.log call in logCurrentDiagnostics() with categorized
    logger debug call
  • Maintained diagnostic output routing to custom log file
+2/-2     
Refactoring
1 files
logger.ts
Logger refactored to use new TUI-integrated system             

src/utilities/logger.ts

  • Replaced entire old Logger implementation with re-export of
    LegacyLoggerAdapter
  • Maintained backward compatibility by exporting both default and named
    Logger exports
  • Added exports for new CategorizedLogger and TUIManager for gradual
    migration
  • Preserved all existing import paths so no code changes needed
+29/-204
Documentation
1 files
issues.jsonl
TUI implementation issue tracking and project management 

.beads/issues.jsonl

  • Added 7 issue tracking entries documenting TUI implementation epic and
    6 phases
  • Tracked completion of phases 1-4 (Categorized Logger, TUI Framework,
    Log Display, Info Panel)
  • Marked phase 5 (Migrate Existing Logging) as in_progress with core
    migration complete
  • Documented phase 6 (Testing and Polish) as open for future work
+7/-0     
Miscellaneous
5 files
demos_logo_ascii_bn
Full-size DEMOS ASCII logo asset                                                 

res/demos_logo_ascii_bn

  • Added full-size DEMOS node ASCII logo (59 lines) for TUI header
    display
+59/-0   
demos_logo_ascii_bn_small
Small DEMOS ASCII logo asset                                                         

res/demos_logo_ascii_bn_small

  • Added small DEMOS node ASCII logo (28 lines) for compact TUI display
+28/-0   
demos_banner_ascii
DEMOS banner ASCII art asset                                                         

res/demos_banner_ascii

  • Added DEMOS banner ASCII art (14 lines) for alternative TUI branding
+14/-0   
demos_logo_ascii
Add large ASCII art logo resource file                                     

res/demos_logo_ascii

  • Added a new ASCII art logo file with a large decorative design
  • File contains 59 lines of ASCII characters forming a visual pattern
  • Uses @ symbols and special characters to create the logo artwork
+59/-0   
demos_logo_ascii_bn_xsmall
Add small ASCII art logo resource file                                     

res/demos_logo_ascii_bn_xsmall

  • Added a new compact ASCII art logo file with smaller dimensions
  • File contains 11 lines of ASCII characters using block characters
  • Uses (full block) characters to create a minimalist logo design
+11/-0   

Summary by CodeRabbit

  • New Features

    • Full Terminal UI: real-time node status, tabs, live log views, command panel, and graceful quit controls.
    • CLI option to run without TUI (--no-tui) for legacy linear console output.
  • Improvements

    • Categorized, buffered logging with per-category/level views, persisted files, runtime filtering, and backward-compatible adapter.
  • Chores

    • Bulk-added phased issue records and deletions, added ASCII-art branding assets, and CI workflows to manage repository-specific metadata.
  • Documentation

    • README updated with TUI usage, keybindings, and legacy-mode instructions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

A Terminal User Interface (TUI) system is introduced, replacing direct terminal-kit calls and console logging with a categorized logging infrastructure and a full-screen interactive UI. The Logger class is refactored to delegate to a new CategorizedLogger with event-driven updates. TUIManager orchestrates the TUI rendering, state management, and command processing. The main index.ts is updated to initialize and lifecycle-manage the TUI. Shell scripts and GitHub Actions workflows are added for legacy mode support and .beads file management. ASCII art assets and documentation complete the feature.

Changes

Cohort / File(s) Summary
TUI Core Infrastructure
src/utilities/tui/CategorizedLogger.ts, src/utilities/tui/LegacyLoggerAdapter.ts, src/utilities/tui/TUIManager.ts, src/utilities/tui/index.ts
Adds CategorizedLogger (singleton, ring buffers, file I/O, event emission, category/level filtering), LegacyLoggerAdapter (backwards-compat wrapper mapping old Logger API to CategorizedLogger), TUIManager (singleton full-screen TUI with tabs, command mode, node info, input handling), and central TUI exports.
Logger Surface/Integration
src/utilities/logger.ts, src/index.ts, src/utilities/mainLoop.ts
Replaces old Logger class with re-exports/adapters to the new TUI logging infra; integrates TUI initialization/lifecycle and TUI-enabled logging in src/index.ts; routes main-loop diagnostics through logger.debug instead of console.
Tag Mapping
src/utilities/tui/tagCategories.ts
Adds TAG_TO_CATEGORY mapping to translate legacy log tags to new LogCategory values for compatibility and category inference.
TUI Assets
res/demos_banner_ascii, res/demos_logo_ascii, res/demos_logo_ascii_bn, res/demos_logo_ascii_bn_small, res/demos_logo_ascii_bn_xsmall
Adds five static ASCII art resource files used as header/logo assets in the TUI.
CLI / Run Script
run
Adds --no-tui / -t handling, pre-parse for NO_TUI, builds FINAL_COMMAND including --no-tui when requested, and updates startup messaging to reflect legacy vs TUI mode.
Issue & Deletion Records
.beads/issues.jsonl, .beads/deletions.jsonl
Appends multiple new TUI-related issue entries (Phases 1–6 and an Epic) with dependencies and acceptance criteria; appends six deletion records (batch delete entries).
Workflows
.github/workflows/fix-beads-conflicts.yml, .github/workflows/notify-beads-merging.yml
Adds workflows to preserve branch-specific .beads files during merges and warn on PRs that touch .beads files.
Documentation
README.md
Adds a TUI section describing features, controls table, and a Legacy Mode subsection explaining --no-tui usage.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Main as Main Process
    participant TUI as TUIManager
    participant Log as CategorizedLogger
    participant Term as Terminal (terminal-kit)
    participant Node as Node App Logic

    Main->>Log: CategorizedLogger.getInstance(config)
    Main->>TUI: TUIManager.getInstance(config)
    Main->>TUI: start()
    TUI->>Term: initialize screen & render header/tab layout
    TUI->>Log: enableTuiMode()

    Node->>Log: log(level, category?, message)
    Log->>Log: filter & write to files, buffer
    Log->>Log: emit "log" event
    Log->>TUI: log event received
    TUI->>TUI: buffer & filter by active tab
    TUI->>Term: render log area

    Main->>TUI: updateNodeInfo({port, peers, block, status})
    TUI->>Term: update header / status display

    Term->>TUI: user key input
    alt CMD mode
        TUI->>TUI: parse command (help|quit|clear|peers|status)
        TUI->>Log: query entries
        TUI->>Term: display command result
    else Nav / Tab switch
        TUI->>TUI: change active tab / scroll
        TUI->>Log: fetch entries
        TUI->>Term: re-render log view
    end

    Term->>TUI: quit (Ctrl+C / quit command)
    TUI->>Log: disableTuiMode()
    TUI->>Term: restore terminal state
    TUI->>Main: emit quit -> Main triggers graceful shutdown
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay extra attention to:
    • src/utilities/tui/TUIManager.ts — terminal lifecycle, input handling, restore-on-exit, and event wiring.
    • src/utilities/tui/CategorizedLogger.ts — concurrent file writes, buffer limits, event emission consistency.
    • src/utilities/tui/LegacyLoggerAdapter.ts — tag extraction, category inference, and compatibility of legacy APIs.
    • src/index.ts integration — startup ordering, NO_TUI handling, and graceful shutdown interaction with TUIManager.

Possibly related PRs

  • Tui #518 — Implements essentially the same TUI subsystem (CategorizedLogger, LegacyLoggerAdapter, TUIManager) and touches overlapping files in src/utilities/tui, src/index.ts, and resources.

Poem

A rabbit hops through terminal-kit dreams, 🐰
I stitch tabs, logs, and colored streams,
Old Logger whispers, new bells chime—
Keys and commands keep perfect time.
Flag --no-tui lets the logs unwind.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Tui' is overly vague and generic; it refers to the feature being implemented but lacks specificity about the scope, impact, or primary change. Consider a more descriptive title like 'Add Terminal User Interface (TUI) for real-time node monitoring' to clearly convey the main change and its purpose.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 288cfa9 and 92ffda8.

📒 Files selected for processing (4)
  • .beads/deletions.jsonl (1 hunks)
  • .github/workflows/fix-beads-conflicts.yml (1 hunks)
  • .github/workflows/notify-beads-merging.yml (1 hunks)
  • run (6 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Dec 4, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Unsafe process exit

Description: The quit command and Ctrl+C handling call process.exit(0) directly from UI input without
cleanup, allowing any user with terminal access to force an immediate process termination
and potentially bypass graceful shutdown or resource cleanup; this can be abused in
multi-tenant or service contexts and should emit a shutdown signal and let the main
process exit safely instead.
TUIManager.ts [724-733]

Referred Code
private handleQuit(): void {
    this.emit("quit")
    this.stop()
    process.exit(0)
}

/**
 * Setup terminal resize handler
 */
private setupResizeHandler(): void {
Global console hook risk

Description: Global interception and replacement of console methods affects the entire process and all
modules, which can hide critical logs or break libraries relying on console behavior; if
an attacker can inject input that is only visible via console (e.g., stack traces), it may
be suppressed or re-categorized, reducing observability and complicating incident
response—consider scoping or opt-in interception and preserving original behavior with a
safe passthrough.
TUIManager.ts [410-450]

Referred Code
private interceptConsole(): void {
    // Store original methods
    this.originalConsole = {
        log: console.log,
        error: console.error,
        warn: console.warn,
        info: console.info,
        debug: console.debug,
    }

    // Replace with TUI-safe versions that route to the logger with category detection
    console.log = (...args: unknown[]) => {
        const message = args.map(a => String(a)).join(" ")
        const { category, cleanMessage } = this.extractCategoryFromMessage(message)
        this.logger.debug(category, `[console.log] ${cleanMessage}`)
    }

    console.error = (...args: unknown[]) => {
        const message = args.map(a => String(a)).join(" ")
        const { category, cleanMessage } = this.extractCategoryFromMessage(message)
        this.logger.error(category, `[console.error] ${cleanMessage}`)


 ... (clipped 20 lines)
Blocking file I/O DoS

Description: File logging uses synchronous fs.appendFileSync on every log write which blocks the event
loop; under high load this may cause backpressure leading to dropped network handshakes or
timeouts and can be exploited for DoS by generating many logs—use buffered async writes or
a dedicated writer.
CategorizedLogger.ts [429-438]

Referred Code
private appendToFile(filename: string, content: string): void {
    const filepath = path.join(this.config.logsDir, filename)

    try {
        fs.appendFileSync(filepath, content)
    } catch (err) {
        // Silently fail file writes to avoid recursion
        console.error(`Failed to write to log file: ${filepath}`, err)
    }
}
Identity exposure

Description: The header prints the node public key (or a truncated form) in the terminal UI by default,
potentially exposing sensitive identity information to any user with terminal access or
logs captured from the TUI; consider masking by default or gating behind a configuration
flag.
TUIManager.ts [1008-1046]

Referred Code
term.moveTo(infoStartX, 2)
switch (this.nodeInfo.status) {
    case "running":
        term.bgGreen.black(` ${statusIcon} RUNNING `)
        break
    case "syncing":
        term.bgYellow.black(` ${statusIcon} SYNCING `)
        break
    case "starting":
        term.bgCyan.black(` ${statusIcon} STARTING `)
        break
    case "stopped":
        term.bgGray.white(` ${statusIcon} STOPPED `)
        break
    case "error":
        term.bgRed.white(` ${statusIcon} ERROR `)
        break
}

// Line 3: Separator
term.moveTo(infoStartX, 3)


 ... (clipped 18 lines)
Misleading sync state

Description: Sync status logic derives "standalone" based on peer connection strings containing
'localhost' or '127.0.0.1', which is an unreliable heuristic that can be spoofed;
displaying SYNCED/STANDALONE based on this may mislead operators and impact security
decisions—ensure sync state comes from verified protocol state.
TUIManager.ts [1056-1088]

Referred Code
// Line 7: Peers (read live from PeerManager)
term.moveTo(infoStartX, 7)
term.yellow("👥 ")
term.gray("Peers: ")
let livePeersCount = 0
try {
    livePeersCount = PeerManager.getInstance().getPeers().length
} catch {
    livePeersCount = this.nodeInfo.peersCount
}
term.brightWhite(String(livePeersCount))

// Line 8: Block (read live from sharedState)
term.moveTo(infoStartX, 8)
term.yellow("📦 ")
term.gray("Block: ")
const liveBlockNumber = getSharedState.lastBlockNumber ?? this.nodeInfo.blockNumber
term.brightWhite("#" + String(liveBlockNumber))

// Line 9: Sync status (read live from sharedState)
term.moveTo(infoStartX, 9)


 ... (clipped 12 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Unstructured Logs: The new logging system writes plain text lines to files without enforced structure or
required audit fields (e.g., user ID), so it is unclear if critical actions are captured
with all mandated context.

Referred Code
private formatLogLine(entry: LogEntry): string {
    const timestamp = entry.timestamp.toISOString()
    const level = entry.level.toUpperCase().padEnd(8)
    const category = entry.category.padEnd(10)
    return `[${timestamp}] [${level}] [${category}] ${entry.message}\n`
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Process Exit: The TUI commands 'quit' and error paths call process.exit(0/1) directly without
cleanup or contextual error logging, which may bypass graceful shutdown and hinder
diagnostics.

Referred Code
server.once("error", err => {
    server.close()
    if (err["code"] == "EADDRINUSE") {
        resolve(false)
    } else {
        resolve(false) // or throw error!!
        // reject(err);
    }

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Console Fallback: On file write failures the logger prints filesystem errors to console which, in non-TUI
contexts, could expose internal paths and details to end users.

Referred Code
    fs.appendFileSync(filepath, content)
} catch (err) {
    // Silently fail file writes to avoid recursion
    console.error(`Failed to write to log file: ${filepath}`, err)
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Sensitive Output: The TUI header and CMD 'status' command display the node public key and ports
which may be sensitive operational details without explicit redaction policy.

Referred Code
term.yellow("🔑 ")
term.gray("Identity: ")
const availableWidth = this.width - infoStartX - 15 // Account for emoji + "Identity: "
let keyDisplay = "Loading..."
if (this.nodeInfo.publicKey) {
    if (this.nodeInfo.publicKey.length <= availableWidth) {
        keyDisplay = this.nodeInfo.publicKey
    } else {
        // Show first 4 and last 4 characters
        keyDisplay = `${this.nodeInfo.publicKey.slice(0, 4)}...${this.nodeInfo.publicKey.slice(-4)}`
    }
}
term.brightWhite(keyDisplay)

// Line 5: Empty separator
term.moveTo(infoStartX, 5)

// Line 6: Port
term.moveTo(infoStartX, 6)
term.yellow("📡 ")
term.gray("Port: ")


 ... (clipped 2 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Arg Parsing: Command-line argument parsing accepts arbitrary values (e.g., 'param=...')
without validation or sanitization and logs them, which may introduce security and
injection risks.

Referred Code
async function digestArguments() {
    const args = process.argv
    if (args.length > 3) {
        log.debug("[MAIN] Digesting arguments")
        for (let i = 3; i < args.length; i++) {
            // Handle simple commands
            if (!args[i].includes("=")) {
                log.info("[MAIN] cmd: " + args[i])
                process.exit(0)
            }
            // Handle configurations
            const param = args[i].split("=")
            // NOTE These are all the parameters supported
            switch (param[0]) {
                case "port":
                    log.info("[MAIN] Overriding port")
                    indexState.OVERRIDE_PORT = parseInt(param[1])
                    break
                case "peerfile":
                    log.warning(
                        "[PEER] Overriding peer list file is not supported anymore (see PeerManager)",


 ... (clipped 17 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Dec 4, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Adopt a component-based TUI library

Refactor the monolithic TUIManager by adopting a component-based TUI library
like Ink or Blessed. This will break the UI into smaller, manageable components
and simplify state management and rendering.

Examples:

src/utilities/tui/TUIManager.ts [178-1431]
export class TUIManager extends EventEmitter {
    private static instance: TUIManager | null = null

    private logger: CategorizedLogger
    private config: Required<TUIConfig>
    private nodeInfo: NodeInfo
    private activeTabIndex = 0
    private scrollOffsets: Map<string, number> = new Map() // Per-tab scroll positions
    private autoScroll = true
    private isRunning = false

 ... (clipped 1244 lines)

Solution Walkthrough:

Before:

class TUIManager {
    // ... many state properties ...
    private activeTabIndex = 0;
    private scrollOffsets = new Map();
    private cmdInput = "";

    render() {
        if (!this.isRunning) return;
        this.renderHeader();
        this.renderTabs();
        if (this.isCmdMode) {
            this.renderCmdArea();
        } else {
            this.renderLogArea();
        }
        this.renderFooter();
    }

    private renderHeader() {
        // Manual cursor positioning and drawing
        term.moveTo(x, y);
        term.cyan("...");
        // ... more manual drawing ...
    }

    private handleKeyPress(key) {
        // Large switch statement for all keys
        switch (key) {
            // ...
        }
    }
}

After:

// Using a component-based library like Ink (React for CLIs)

const App = () => {
    const [activeTab, setActiveTab] = useState('ALL');
    const { logs } = useLogs(activeTab);
    const { nodeInfo } = useNodeInfo();

    return (
        <Box flexDirection="column">
            <Header info={nodeInfo} />
            <Tabs activeTab={activeTab} onTabChange={setActiveTab} />
            <LogPanel logs={logs} />
            <Footer />
        </Box>
    );
};

// Each component (Header, Tabs, etc.) would be a separate,
// manageable file responsible for its own rendering and logic.
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies the monolithic TUIManager as a major architectural flaw and proposes a component-based approach that would significantly improve maintainability and scalability.

High
Possible issue
Map console.log to info, not debug
Suggestion Impact:The commit changed the console.log interception to use this.logger.info instead of this.logger.debug.

code diff:

         console.log = (...args: unknown[]) => {
             const message = args.map(a => String(a)).join(" ")
             const { category, cleanMessage } = this.extractCategoryFromMessage(message)
-            this.logger.debug(category, `[console.log] ${cleanMessage}`)
+            this.logger.info(category, `[console.log] ${cleanMessage}`)

Change the intercepted console.log calls to be routed to this.logger.info
instead of this.logger.debug to prevent unintentionally hiding logs.

src/utilities/tui/TUIManager.ts [421-425]

 console.log = (...args: unknown[]) => {
     const message = args.map(a => String(a)).join(" ")
     const { category, cleanMessage } = this.extractCategoryFromMessage(message)
-    this.logger.debug(category, `[console.log] ${cleanMessage}`)
+    this.logger.info(category, `[console.log] ${cleanMessage}`)
 }

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that mapping console.log to debug is unconventional and could hide important information, and changing it to info is a more robust choice.

Medium
Prevent memory leak in command history
Suggestion Impact:The commit wraps history push with a trim check and caps cmdHistory at 100 entries by shifting when exceeded, exactly as suggested.

code diff:

+                if (this.cmdInput.trim()) {
+                    this.cmdHistory.push(this.cmdInput)
+                    if (this.cmdHistory.length > 100) { // Limit history size
+                        this.cmdHistory.shift()
+                    }
+                }

Prevent a memory leak by limiting the size of the command history array
(cmdHistory) to a fixed number of entries.

src/utilities/tui/TUIManager.ts [622-629]

 case "ENTER":
     // Execute command
     this.executeCommand(this.cmdInput)
-    this.cmdHistory.push(this.cmdInput)
+    if (this.cmdInput.trim()) {
+        this.cmdHistory.push(this.cmdInput)
+        if (this.cmdHistory.length > 100) { // Limit history size
+            this.cmdHistory.shift()
+        }
+    }
     this.cmdHistoryIndex = this.cmdHistory.length
     this.cmdInput = ""
     this.render()
     break

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a memory leak where the command history array cmdHistory grows indefinitely and proposes a sensible fix by capping its size.

Low
General
Use asynchronous file writes for performance
Suggestion Impact:The commit replaced fs.appendFileSync within a try/catch with fs.promises.appendFile using a .catch handler, matching the suggested asynchronous approach and comments.

code diff:

-        try {
-            fs.appendFileSync(filepath, content)
-        } catch (err) {
-            // Silently fail file writes to avoid recursion
+        fs.promises.appendFile(filepath, content).catch(err => {
+            // Silently fail file writes to avoid recursion.
+            // Using the original console.error to bypass TUI interception.
             console.error(`Failed to write to log file: ${filepath}`, err)
-        }
+        })

Improve performance by replacing the synchronous fs.appendFileSync with the
asynchronous fs.promises.appendFile to avoid blocking the event loop.

src/utilities/tui/CategorizedLogger.ts [429-438]

 private appendToFile(filename: string, content: string): void {
     const filepath = path.join(this.config.logsDir, filename)
 
-    try {
-        fs.appendFileSync(filepath, content)
-    } catch (err) {
-        // Silently fail file writes to avoid recursion
+    fs.promises.appendFile(filepath, content).catch(err => {
+        // Silently fail file writes to avoid recursion.
+        // Using the original console.error to bypass TUI interception.
         console.error(`Failed to write to log file: ${filepath}`, err)
-    }
+    })
 }

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a performance issue with synchronous file I/O and proposes a valid asynchronous alternative, which improves application responsiveness under heavy logging.

Medium
  • Update

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (12)
.beads/issues.jsonl (1)

1-7: Consider sorting phases sequentially for readability.

While the JSONL format doesn't enforce ordering, the entries are currently in the order: 1, 2, 5, 4, 3, 6, epic. Reordering them sequentially (1→2→3→4→5→6→epic) would improve human readability and make the progression clearer at a glance.

This is a minor organizational improvement and non-urgent—consider applying if you're already editing this file.

src/utilities/tui/CategorizedLogger.ts (3)

224-229: Config parameter is silently ignored after first instantiation.

When getInstance(config) is called after the singleton already exists, the config parameter is ignored without warning. This could lead to subtle bugs if callers expect their config to be applied.

Consider logging a warning or throwing when config is passed to an already-initialized instance:

 static getInstance(config?: LoggerConfig): CategorizedLogger {
     if (!CategorizedLogger.instance) {
         CategorizedLogger.instance = new CategorizedLogger(config)
+    } else if (config) {
+        // Optionally warn that config is ignored
+        console.warn("CategorizedLogger already initialized, config ignored")
     }
     return CategorizedLogger.instance
 }

200-200: Unused fileHandles map and synchronous file writes.

  1. The fileHandles Map (line 200) is declared but never populated - appendToFile uses synchronous fs.appendFileSync instead of streams.
  2. closeFileHandles() iterates over an always-empty map, making it a no-op.
  3. Synchronous file I/O can block the event loop under heavy logging load.

Either remove the unused fileHandles declaration or implement async file streams:

-    private fileHandles: Map<string, fs.WriteStream> = new Map()

Or use async writes with buffering for production workloads:

private appendToFile(filename: string, content: string): void {
    const filepath = path.join(this.config.logsDir, filename)
    fs.appendFile(filepath, content, (err) => {
        if (err) console.error(`Failed to write to log file: ${filepath}`, err)
    })
}

Also applies to: 429-438


570-583: Duplicate category list violates DRY principle.

getCategories() returns a hardcoded array identical to ALL_CATEGORIES (lines 177-188). These could get out of sync.

Reuse the existing constant:

 static getCategories(): LogCategory[] {
-    return [
-        "CORE",
-        "NETWORK",
-        "PEER",
-        "CHAIN",
-        "SYNC",
-        "CONSENSUS",
-        "IDENTITY",
-        "MCP",
-        "MULTICHAIN",
-        "DAHR",
-    ]
+    return [...ALL_CATEGORIES]
 }
src/index.ts (2)

307-310: Duplicate --no-tui flag parsing.

The --no-tui flag is checked here (lines 307-310) and also in digestArguments() (lines 111-114). The early check here uses includes("no-tui") which matches both no-tui and --no-tui, but digestArguments uses split("=") logic expecting a different format.

Consider consolidating the flag handling:

-    // Check for --no-tui flag early (before warmup processes args fully)
-    if (process.argv.includes("no-tui") || process.argv.includes("--no-tui")) {
-        indexState.TUI_ENABLED = false
-    }
+    // Check for --no-tui flag early (before warmup processes args fully)
+    if (process.argv.includes("--no-tui")) {
+        indexState.TUI_ENABLED = false
+    }

And update digestArguments to match:

-                case "no-tui":
+                // no-tui is handled early in main(), but keep for completeness
+                case "--no-tui":
                     log.info("[MAIN] TUI disabled, using scrolling log output")
                     indexState.TUI_ENABLED = false
                     break

321-329: Hardcoded version string.

The version "1.0.0" is hardcoded. This will become stale and inconsistent with the actual package version.

Consider reading from package.json:

import { version } from "../package.json"
// or
const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"))

indexState.tuiManager.updateNodeInfo({
    version: packageJson.version,
    // ...
})
src/utilities/tui/TUIManager.ts (3)

124-133: Consider graceful shutdown instead of immediate process.exit.

The quit command handler calls process.exit(0) after only 500ms. This may not allow sufficient time for cleanup operations (closing connections, flushing logs, etc.) to complete, potentially causing data loss or resource leaks.

Consider emitting a shutdown event and letting the main application orchestrate the graceful shutdown:

     {
         name: "quit",
         description: "Exit the node",
         handler: (_args, tui) => {
             tui.addCmdOutput("Shutting down...")
-            setTimeout(() => {
-                tui.emit("quit")
-                tui.stop()
-                process.exit(0)
-            }, 500)
+            tui.emit("quit")
+            // Let the main application handle graceful shutdown
+            // It should call tui.stop() and then process.exit() after cleanup
         },
     },

724-728: Consider delegating exit to main application.

Similar to the quit command handler, handleQuit() immediately calls process.exit(0) without allowing time for graceful shutdown. Consider emitting the event and letting the main application orchestrate the shutdown sequence.

 private handleQuit(): void {
     this.emit("quit")
     this.stop()
-    process.exit(0)
+    // Let main application handle process.exit after cleanup
 }

947-963: Make localhost detection more robust.

The checkIfStandalone() method uses case-sensitive string matching to detect localhost peers (lines 954-955). This may not catch all localhost variations:

  • IPv6 localhost (::1)
  • Case variations (LOCALHOST, LocalHost)
  • IP variations (127.*)
 const nonLocalPeers = peers.filter(peer => {
     const connStr = peer.connection?.string?.toLowerCase() || ""
-    return !connStr.includes("localhost") && !connStr.includes("127.0.0.1")
+    return !connStr.includes("localhost") && 
+           !connStr.includes("127.0.0.1") && 
+           !connStr.includes("::1") &&
+           !/^127\./.test(connStr)
 })
src/utilities/tui/LegacyLoggerAdapter.ts (3)

197-213: Remove incomplete terminal suppression logic.

Lines 204-210 contain incomplete logic to suppress terminal output. The prevTerminal variable is declared but never used, and the comment indicates the implementation is incomplete. This dead code should be removed or completed.

 static info(message: string, logToTerminal = true): void {
     if (this.LOG_ONLY_ENABLED) return

     const { tag, cleanMessage } = extractTag(message)
     const category = inferCategory(tag)

-    // Temporarily adjust terminal output based on parameter
-    const config = this.logger.getConfig()
-    const prevTerminal = config.terminalOutput
-
-    if (!logToTerminal && !this.logger.isTuiMode()) {
-        // In non-TUI mode, we need to suppress terminal for this call
-        // We'll emit the event but not print
-    }
-
     this.logger.info(category, cleanMessage)
 }

Note: The logToTerminal parameter is effectively ignored. If this behavior is intentional, document it. Otherwise, implement proper suppression or remove the parameter.


258-291: Mixing of sync and async file operations.

The custom() method is async but uses synchronous file operations (fs.rmSync, fs.appendFileSync), which can block the event loop. Additionally, the fs.promises.writeFile call at line 274 is not awaited.

Either make the method fully synchronous or fully asynchronous:

 static async custom(
     logfile: string,
     message: string,
     logToTerminal = true,
     cleanFile = false,
 ): Promise<void> {
     if (this.LOG_ONLY_ENABLED) return

     const customPath = `${this.LOG_CUSTOM_PREFIX}${logfile}.log`
     const timestamp = new Date().toISOString()
     const logEntry = `[INFO] [${timestamp}] ${message}\n`

     // Clean file if requested
     if (cleanFile) {
         try {
-            fs.rmSync(customPath, { force: true })
-            await fs.promises.writeFile(customPath, "")
+            await fs.promises.rm(customPath, { force: true })
+            await fs.promises.writeFile(customPath, "")
         } catch {
             // Ignore errors
         }
     }

     // Write to custom file
     try {
-        fs.appendFileSync(customPath, logEntry)
+        await fs.promises.appendFile(customPath, logEntry)
     } catch {
         // Ignore errors
     }

325-385: Consider async alternatives for file operations.

Methods cleanLogs(), getPublicLogs(), and getDiagnostics() use synchronous file operations that can block the event loop. While these are likely called infrequently for diagnostic purposes, providing async alternatives would improve performance in high-throughput scenarios.

Consider adding async versions:

static async cleanLogsAsync(withCustom = false): Promise<void>
static async getPublicLogsAsync(): Promise<string>
static async getDiagnosticsAsync(): Promise<string>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f35c3b4 and cfb45ee.

📒 Files selected for processing (13)
  • .beads/issues.jsonl (1 hunks)
  • res/demos_banner_ascii (1 hunks)
  • res/demos_logo_ascii (1 hunks)
  • res/demos_logo_ascii_bn (1 hunks)
  • res/demos_logo_ascii_bn_small (1 hunks)
  • res/demos_logo_ascii_bn_xsmall (1 hunks)
  • src/index.ts (11 hunks)
  • src/utilities/logger.ts (1 hunks)
  • src/utilities/mainLoop.ts (1 hunks)
  • src/utilities/tui/CategorizedLogger.ts (1 hunks)
  • src/utilities/tui/LegacyLoggerAdapter.ts (1 hunks)
  • src/utilities/tui/TUIManager.ts (1 hunks)
  • src/utilities/tui/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/utilities/mainLoop.ts (1)
src/utilities/tui/CategorizedLogger.ts (1)
  • log (313-344)
src/index.ts (5)
src/utilities/logger.ts (2)
  • TUIManager (28-28)
  • CategorizedLogger (26-26)
src/utilities/tui/index.ts (2)
  • TUIManager (25-25)
  • CategorizedLogger (11-11)
src/utilities/tui/CategorizedLogger.ts (3)
  • log (313-344)
  • CategorizedLogger (193-591)
  • error (393-396)
src/utilities/sharedState.ts (2)
  • getSharedState (266-268)
  • publicKeyHex (94-107)
src/libs/blockchain/routines/findGenesisBlock.ts (1)
  • findGenesisBlock (39-86)
src/utilities/tui/CategorizedLogger.ts (2)
src/utilities/logger.ts (4)
  • LogLevel (27-27)
  • LogCategory (27-27)
  • LogEntry (27-27)
  • CategorizedLogger (26-26)
src/utilities/tui/index.ts (5)
  • LogLevel (15-15)
  • LogCategory (16-16)
  • LogEntry (17-17)
  • LoggerConfig (18-18)
  • CategorizedLogger (11-11)
🔇 Additional comments (24)
.beads/issues.jsonl (2)

1-7: Approve: Comprehensive TUI tracking structure.

The issue tracking data is well-structured with detailed design documentation, clear acceptance criteria, and properly defined dependencies. Phase statuses appropriately reflect work progression (completed phases closed, Phase 5 actively in progress, future phases open). The embedded design specs provide good context for implementation phases.


1-7: JSONL format and data integrity validated successfully.

All 7 issue entries are valid JSON with complete required fields (id, title, description, design, acceptance_criteria, status, priority, issue_type, timestamps, labels, and dependencies). Dependency graph is coherent—all 24 dependency references resolve to existing issue IDs. The structure properly tracks the TUI implementation workflow across 6 phases plus epic, with appropriate status progression (5 closed, 1 in_progress, 2 open).

res/demos_logo_ascii_bn (1)

1-59: LGTM!

Static ASCII art asset for the TUI branding. No code concerns.

res/demos_logo_ascii_bn_xsmall (1)

1-11: LGTM!

Compact ASCII art variant for smaller terminal displays.

res/demos_logo_ascii_bn_small (1)

1-28: LGTM!

Medium-sized ASCII art variant complements the other logo sizes.

res/demos_banner_ascii (1)

1-14: LGTM!

ASCII text banner asset for TUI headers or startup display.

res/demos_logo_ascii (1)

1-59: LGTM!

Full-size ASCII art logo using @ characters—good fallback for terminals with limited Unicode support.

src/utilities/tui/CategorizedLogger.ts (6)

14-59: LGTM! Well-structured type definitions.

The types and interfaces are well-designed with clear documentation. The LogLevel, LogCategory, LogEntry, and LoggerConfig provide a solid foundation for the categorized logging system.


66-139: LGTM! Correct ring buffer implementation.

The circular buffer correctly handles wrap-around with head/tail pointers. The push, getAll, getLast, and filter methods are implemented correctly.


460-473: LGTM! Terminal output with color coding.

The timestamp extraction and color-coded output implementation is correct and provides good readability for terminal logs.


476-487: LGTM! Chronological ordering via entry IDs.

Using id for sorting ensures correct chronological order when merging entries from multiple category buffers. The implementation is correct.


349-364: Filtered logs are not stored in buffers.

The shouldLog check happens before creating and storing the LogEntry. This means logs filtered by level or category are permanently lost, not just hidden from display. If the user changes filters, they won't see previously-filtered logs.

Is this the intended behavior? If users should be able to see previously-filtered logs after changing filters, consider always storing entries and filtering only on retrieval:

 debug(category: LogCategory, message: string): LogEntry | null {
-    if (!this.shouldLog("debug", category)) return null
-    return this.log("debug", category, message)
+    const entry = this.log("debug", category, message)
+    // Filtering happens in display/retrieval, not storage
+    return entry
 }

Also applies to: 369-404


593-600: LGTM! Convenient singleton default export.

The default export provides easy access to the logger instance while the class export allows for testing and direct instantiation control.

src/utilities/mainLoop.ts (1)

234-235: LGTM! Diagnostic output migrated to categorized logger.

The change correctly routes diagnostic output through the new logging system with appropriate debug level and [MAIN LOOP] prefix for category detection.

src/utilities/tui/index.ts (1)

1-31: LGTM! Clean barrel file with proper type exports.

The module provides a well-organized public API surface. Using export type for interfaces ensures proper tree-shaking and avoids runtime overhead.

src/index.ts (3)

312-334: LGTM! TUI initialization with proper fallback.

Good defensive coding with try/catch that falls back to standard output if TUI fails to start. The initial node info with placeholder values provides immediate visual feedback.


76-78: LGTM! Logging migration maintains consistency.

The log calls correctly use the category tags (e.g., [SYNC], [MAIN]) that the LegacyLoggerAdapter will auto-detect for category mapping.


352-363: LGTM! Progressive TUI updates with node lifecycle.

The node info is updated incrementally as information becomes available. Truncating the public key to 16 characters is a sensible UI choice.

src/utilities/logger.ts (1)

1-29: LGTM! Clean backward-compatibility wrapper with clear documentation.

The module serves as an excellent migration bridge:

  • Existing import log from "src/utilities/logger" continues to work via LegacyLoggerAdapter
  • New code can use CategorizedLogger directly
  • Type exports enable proper TypeScript usage
  • Documentation provides clear guidance for gradual migration
src/utilities/tui/TUIManager.ts (3)

681-701: LGTM - Command execution is safe.

The command execution logic properly validates commands against a predefined whitelist (COMMANDS array) and doesn't execute arbitrary code. Unknown commands are safely rejected.


289-293: LGTM - Proper interval cleanup.

The refresh interval is properly cleaned up in the stop() method with appropriate null checks.

Also applies to: 305-309


706-712: LGTM - Bounded buffer prevents memory leak.

The CMD output buffer is properly bounded to 500 lines, preventing unbounded memory growth.

src/utilities/tui/LegacyLoggerAdapter.ts (2)

165-192: LGTM - Proper error handling and fallback.

The setLogsDir() method has appropriate error handling with fallback to a default directory.


387-409: LGTM - Clean API delegation.

The new API access methods provide clean delegation to the underlying CategorizedLogger instance, supporting the migration path mentioned in the file header.

tcsenpai and others added 5 commits December 4, 2025 18:44
Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com>
Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com>
Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@tcsenpai
Copy link
Contributor Author

tcsenpai commented Dec 5, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

1 similar comment
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (2)
src/utilities/tui/TUIManager.ts (2)

262-295: Event listeners are not explicitly removed in stop().

The setupInputHandlers() and setupResizeHandler() methods register event listeners via term.on("key", ...) and term.on("resize", ...) without storing references. The stop() method calls term.grabInput(false) but does not explicitly remove these listeners. If the TUI is stopped and restarted, listeners will accumulate.

This was flagged in a previous review. Store listener references and call term.off() in stop() to prevent listener accumulation:

+    private keyHandler: ((key: string) => void) | null = null
+    private resizeHandler: ((width: number, height: number) => void) | null = null

     private setupInputHandlers(): void {
-        term.on("key", (key: string) => {
-            this.handleKeyPress(key)
-        })
+        this.keyHandler = (key: string) => this.handleKeyPress(key)
+        term.on("key", this.keyHandler)
     }

     private setupResizeHandler(): void {
-        term.on("resize", (width: number, height: number) => {
+        this.resizeHandler = (width: number, height: number) => {
             this.width = width
             this.height = height
             this.logAreaHeight = this.height - HEADER_HEIGHT - TAB_HEIGHT - FOOTER_HEIGHT
             this.render()
-        })
+        }
+        term.on("resize", this.resizeHandler)
     }

     stop(): void {
         // ... existing code ...
+        if (this.keyHandler) term.off("key", this.keyHandler)
+        if (this.resizeHandler) term.off("resize", this.resizeHandler)
+        this.keyHandler = null
+        this.resizeHandler = null
         // ... rest of stop ...
     }

330-404: TAG_TO_CATEGORY mapping is duplicated from LegacyLoggerAdapter.ts.

This mapping (lines 332-392) duplicates the one in LegacyLoggerAdapter.ts (lines 20-117). This violates DRY and creates maintenance burden when adding new tags.

This was flagged in a previous review. Extract the mapping and helper functions to a shared module:

// src/utilities/tui/tagMapping.ts
export const TAG_TO_CATEGORY: Record<string, LogCategory> = { /* ... */ }

export function extractTag(message: string): { tag: string | null; cleanMessage: string } { /* ... */ }

export function inferCategory(tag: string | null): LogCategory { /* ... */ }
🧹 Nitpick comments (6)
src/utilities/tui/TUIManager.ts (2)

58-79: Unused COLORS constant.

The COLORS object is defined but never referenced in the code. The rendering methods use inline terminal-kit color methods (e.g., term.cyan(), term.green()) instead of this constant.

Either remove the unused constant or refactor the rendering methods to use it for consistency:

-const COLORS = {
-    // Status colors
-    statusRunning: "green",
-    statusSyncing: "yellow",
-    statusStopped: "red",
-    statusError: "brightRed",
-
-    // Log level colors
-    logDebug: "magenta",
-    logInfo: "white",
-    logWarning: "yellow",
-    logError: "red",
-    logCritical: "brightRed",
-
-    // UI colors
-    border: "cyan",
-    header: "brightCyan",
-    tabActive: "brightWhite",
-    tabInactive: "gray",
-    footer: "gray",
-    footerKey: "brightYellow",
-}

1423-1438: getStatusColor() method is defined but never used.

The helper method getStatusColor() is implemented but not called anywhere in the codebase. Either remove it or use it in the status rendering logic.

-    /**
-     * Get status color based on node status
-     */
-    private getStatusColor(): string {
-        switch (this.nodeInfo.status) {
-            case "running":
-                return "green"
-            case "syncing":
-                return "yellow"
-            case "starting":
-                return "cyan"
-            case "stopped":
-                return "gray"
-            case "error":
-                return "red"
-            default:
-                return "white"
-        }
-    }
src/utilities/tui/CategorizedLogger.ts (4)

200-201: fileHandles map is populated but never used.

The fileHandles property is declared and cleared in closeFileHandles(), but appendToFile() uses fs.promises.appendFile() directly without creating or storing write streams. This is dead code.

Remove the unused property and method, or implement proper stream-based file writing for better performance:

-    private fileHandles: Map<string, fs.WriteStream> = new Map()

-    /**
-     * Close all file handles
-     */
-    private closeFileHandles(): void {
-        for (const stream of this.fileHandles.values()) {
-            stream.close()
-        }
-        this.fileHandles.clear()
-    }

Also update resetInstance():

     static resetInstance(): void {
         if (CategorizedLogger.instance) {
-            CategorizedLogger.instance.closeFileHandles()
             CategorizedLogger.instance = null
         }
     }

479-486: getAllEntries() performance concern with large buffers.

Each call to getAllEntries() merges all category buffers and sorts by ID. With 10 categories × 500 entries = 5000 entries, this is O(n log n) per call. The render loop calls this frequently via updateFilteredLogs().

Consider caching the merged result and invalidating on new log entry:

private mergedEntriesCache: LogEntry[] | null = null

private log(...): LogEntry {
    // ... existing code ...
    this.mergedEntriesCache = null // Invalidate cache
    return entry
}

getAllEntries(): LogEntry[] {
    if (this.mergedEntriesCache) return this.mergedEntriesCache
    
    const allEntries: LogEntry[] = []
    for (const buffer of this.categoryBuffers.values()) {
        allEntries.push(...buffer.getAll())
    }
    this.mergedEntriesCache = allEntries.sort((a, b) => a.id - b.id)
    return this.mergedEntriesCache
}

569-582: getCategories() duplicates ALL_CATEGORIES constant.

The static method returns a hardcoded array that duplicates the ALL_CATEGORIES constant defined at line 177-188.

Reuse the existing constant:

     static getCategories(): LogCategory[] {
-        return [
-            "CORE",
-            "NETWORK",
-            "PEER",
-            "CHAIN",
-            "SYNC",
-            "CONSENSUS",
-            "IDENTITY",
-            "MCP",
-            "MULTICHAIN",
-            "DAHR",
-        ]
+        return [...ALL_CATEGORIES]
     }

594-599: Default singleton export may cause issues with testing.

Exporting a pre-instantiated singleton as the default export makes it harder to test code that imports this module, as the singleton is created at import time.

Consider documenting that CategorizedLogger.getInstance() should be preferred over the default export for testability, or remove the default instance export:

-// SECTION Default Export - Singleton Instance
-
-/**
- * Default logger instance
- */
-const logger = CategorizedLogger.getInstance()
-
-export default logger
+// Use CategorizedLogger.getInstance() to obtain the singleton instance
+export default CategorizedLogger
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cfb45ee and ab74c37.

📒 Files selected for processing (3)
  • src/utilities/tui/CategorizedLogger.ts (1 hunks)
  • src/utilities/tui/LegacyLoggerAdapter.ts (1 hunks)
  • src/utilities/tui/TUIManager.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/utilities/tui/CategorizedLogger.ts (2)
src/utilities/logger.ts (4)
  • LogLevel (27-27)
  • LogCategory (27-27)
  • LogEntry (27-27)
  • CategorizedLogger (26-26)
src/utilities/tui/index.ts (5)
  • LogLevel (15-15)
  • LogCategory (16-16)
  • LogEntry (17-17)
  • LoggerConfig (18-18)
  • CategorizedLogger (11-11)
🔇 Additional comments (3)
src/utilities/tui/CategorizedLogger.ts (2)

66-139: Well-implemented ring buffer with clear semantics.

The RingBuffer<T> class is correctly implemented with proper circular buffer logic, O(1) push operations, and efficient retrieval methods.


193-219: Per-category buffers prevent log starvation - good design.

Using separate RingBuffer instances per category ensures that a noisy category (e.g., PEER) won't evict logs from quieter categories. This is a thoughtful design choice.

src/utilities/tui/TUIManager.ts (1)

1080-1081: No issue found—the code is safe.

getSharedState is a getter that always returns a valid SharedState singleton instance via getInstance(). The lastBlockNumber property is always defined (initialized to 0) and never null or undefined. The nullish coalescing operator on line 1080 is unnecessary but harmless, as it will never fall back to this.nodeInfo.blockNumber.

tcsenpai and others added 4 commits December 5, 2025 12:41
Capture console.error at module initialization to prevent TUI interception
when logging file write failures. This avoids infinite recursion if the TUI
patches console methods and routes them back through the logger.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Create src/utilities/tui/tagCategories.ts as single source of truth
- Update LegacyLoggerAdapter.ts to import from shared module
- Update TUIManager.ts to import from shared module (also fixes missing tags)

The TUIManager had a partial copy missing ~25 tag mappings. Now both files
use the complete authoritative mapping from one location.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…exit

- Remove process.exit(0) from TUIManager.handleQuit()
- Add "quit" event listener in index.ts for application-level shutdown
- Implement 5-second timeout fallback for forced termination
- Add placeholder cleanup for peerManager and mcpServer

The TUI now signals intent via event, letting the application perform
cleanup (flush writes, close connections) before exiting.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…rence

The only() method was saving console.log to originalLog then replacing it
with a no-op, but then calling console.log (the no-op) to print the styled
"ONLY" message. Now uses this.originalLog to ensure messages actually appear.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/utilities/tui/TUIManager.ts (1)

853-861: Verify control flow prevents CMD category in getEntriesByCategory.

The control flow in setActiveTab() (lines 716-732) sets isCmdMode = true when the CMD tab is active and only calls updateFilteredLogs() for non-CMD tabs (line 732). However, updateFilteredLogs() is also called from handleLogEntry() (line 841) without checking isCmdMode.

If handleLogEntry() is triggered while the CMD tab is active, updateFilteredLogs() will attempt to call getEntriesByCategory(activeTab.category) with "CMD", which is not a valid LogCategory and will cause a TypeScript error or runtime issue.

Apply this guard:

 private updateFilteredLogs(): void {
     const activeTab = TABS[this.activeTabIndex]

     if (activeTab.category === "ALL") {
         this.filteredLogs = this.logger.getAllEntries()
+    } else if (activeTab.category === "CMD") {
+        // CMD tab doesn't use filtered logs
+        this.filteredLogs = []
     } else {
         this.filteredLogs = this.logger.getEntriesByCategory(activeTab.category)
     }
 }
🧹 Nitpick comments (2)
src/utilities/tui/TUIManager.ts (2)

894-910: Consider more robust standalone detection.

The checkIfStandalone() method checks for localhost/127.0.0.1 peers by examining the connection string with includes(). This could produce false positives if a peer has "localhost" or "127.0.0.1" elsewhere in the connection string (e.g., in a query parameter or path).

Consider using URL parsing for more accurate detection:

 private checkIfStandalone(): boolean {
     try {
         const peers = PeerManager.getInstance().getPeers()
         if (peers.length === 0) return true

         // Check if all peers are localhost
         const nonLocalPeers = peers.filter(peer => {
             const connStr = peer.connection?.string?.toLowerCase() || ""
-            return !connStr.includes("localhost") && !connStr.includes("127.0.0.1")
+            try {
+                const url = new URL(connStr)
+                const hostname = url.hostname
+                return hostname !== "localhost" && hostname !== "127.0.0.1"
+            } catch {
+                // If URL parsing fails, use includes as fallback
+                return !connStr.includes("localhost") && !connStr.includes("127.0.0.1")
+            }
         })

         return nonLocalPeers.length === 0
     } catch {
         // If we can't get peers, assume standalone
         return true
     }
 }

1362-1377: Potentially unused helper method.

The getStatusColor() method is defined but doesn't appear to be used anywhere in the file. The rendering methods use direct color calls (e.g., term.bgGreen) rather than this helper.

Consider removing this unused method or documenting its intended future use.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ab74c37 and 6737118.

📒 Files selected for processing (5)
  • src/index.ts (11 hunks)
  • src/utilities/tui/CategorizedLogger.ts (1 hunks)
  • src/utilities/tui/LegacyLoggerAdapter.ts (1 hunks)
  • src/utilities/tui/TUIManager.ts (1 hunks)
  • src/utilities/tui/tagCategories.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utilities/tui/LegacyLoggerAdapter.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/utilities/tui/TUIManager.ts (3)
src/utilities/tui/tagCategories.ts (2)
  • LogCategory (114-114)
  • TAG_TO_CATEGORY (14-111)
src/utilities/tui/CategorizedLogger.ts (4)
  • LogCategory (25-35)
  • info (380-383)
  • CategorizedLogger (196-593)
  • LogEntry (40-46)
src/utilities/sharedState.ts (1)
  • getSharedState (266-268)
🔇 Additional comments (21)
src/utilities/tui/tagCategories.ts (1)

1-114: LGTM! Clean single source of truth for tag categorization.

This module successfully extracts the tag-to-category mapping into a shared constant, addressing the DRY concern raised in previous reviews. The mapping is comprehensive, well-organized by category, and easy to maintain.

src/index.ts (8)

19-19: LGTM! Clean TUI state integration.

The addition of TUI_ENABLED and tuiManager state fields is well-structured, with appropriate defaults (TUI enabled by default, manager initially null) and proper typing (TUIManager | null).

Also applies to: 39-40, 51-51, 56-56, 68-68


76-78: LGTM! Comprehensive logging migration in warmup flow.

The migration from console/terminal-kit to the new logger API is thorough and consistent. The addition of the --no-tui flag handling at line 111-114 provides a clean fallback mechanism.

Also applies to: 83-120, 158-226


307-369: TUI initialization looks solid with proper error handling.

The TUI initialization sequence is well-structured:

  • Early --no-tui detection prevents unnecessary setup
  • Graceful fallback to standard output on TUI startup failure
  • The quit event handler implements proper cleanup with a 5-second timeout fallback

The cleanup sequence correctly calls process.exit(0) in the finally block rather than directly, which aligns with the graceful shutdown pattern recommended in previous reviews.


375-380: LGTM! Clean TUI state update after warmup.

The pattern of conditionally updating TUI node info after warmup is correct, with proper null checks and partial updates.


229-303: LGTM! Consistent logging throughout preMainLoop.

The logging migration in preMainLoop is thorough and uses appropriate log levels (info for normal flow, warning for potential issues, debug for detailed information).


387-398: LGTM! Comprehensive TUI state update after preMainLoop.

The node info update captures key runtime state (identity, peers, block number) from appropriate sources and sets a reasonable initial status of "syncing". The public key truncation (first 16 chars) is suitable for header display.


402-460: LGTM! Clear logging for server startup sequence.

The signaling and MCP server initialization flows use appropriate log levels and maintain the failsafe pattern for MCP startup failures.


461-469: LGTM! Final TUI state update before main loop.

The final node info update correctly transitions to "running" status and reflects the current sync state from shared state. Well-placed just before entering the main loop.

src/utilities/tui/TUIManager.ts (6)

1-103: LGTM! Well-structured types and configuration.

The type definitions (NodeInfo, TUIConfig, Tab) and constants (layout, colors, tabs) provide a clean foundation for the TUI. The 12-tab configuration covers all log categories plus special ALL and CMD tabs.


105-175: LGTM! Comprehensive command system.

The command definitions provide a useful set of built-in commands (help, quit, clear, status, peers, sync) with clear handlers. The command system architecture allows easy extension.


177-248: LGTM! Solid singleton initialization with comprehensive state management.

The TUIManager constructor properly initializes all state including per-tab scroll offsets, CMD terminal state, and logger event subscriptions. The singleton pattern with resetInstance() for testing is a good practice.


328-405: LGTM! Console interception with proper safeguards.

The console interception implementation correctly:

  • Uses the shared TAG_TO_CATEGORY mapping imported from tagCategories.ts (addressing the DRY concern from previous reviews)
  • Guards against double-interception at lines 349-350 (addressing the previous concern)
  • Routes console output through the categorized logger with tag detection
  • Properly restores original console methods in stop()

425-622: LGTM! Comprehensive input handling with proper history management.

The input handling system correctly:

  • Manages both normal and CMD modes
  • Implements comprehensive keyboard shortcuts (navigation, tabs, scrolling)
  • Includes command history with a 100-entry limit (lines 568-570, addressing the previous memory leak concern)
  • Handles edge cases like empty input and special keys

624-675: LGTM! Command execution and graceful quit handling.

The command execution and quit handling are well-implemented:

  • Commands are parsed and dispatched correctly
  • CMD output buffer is managed with a 500-line limit
  • handleQuit() correctly emits the "quit" event and calls stop() without directly calling process.exit(), following the graceful shutdown pattern confirmed in previous reviews
src/utilities/tui/CategorizedLogger.ts (6)

1-62: LGTM! Clean type definitions with recursion prevention.

The module correctly captures originalConsoleError at initialization (line 13) to prevent TUI interception recursion, addressing the concern from previous reviews. The type definitions for LogLevel, LogCategory, LogEntry, and LoggerConfig are comprehensive and well-documented.


64-142: LGTM! Solid RingBuffer implementation.

The circular buffer implementation correctly manages fixed-size storage with proper head/tail pointer updates, overflow handling, and utility methods (getAll, getLast, filter, clear).


144-310: LGTM! Well-designed logger configuration with per-category buffers.

The logger correctly initializes per-category ring buffers (line 218-221), preventing log loss when one category is highly active. The TUI mode toggle and configuration methods are clean and thread-safe.


312-407: LGTM! Comprehensive logging methods with proper filtering.

The core logging flow correctly:

  • Filters by level and category via shouldLog()
  • Stores entries in per-category ring buffers
  • Emits events for TUI integration
  • Writes to files asynchronously
  • Conditionally outputs to terminal based on TUI mode

409-475: LGTM! File logging with proper recursion prevention.

The file logging implementation correctly:

  • Writes to multiple log files (all.log, level-specific, category-specific)
  • Uses asynchronous file writes with fs.promises.appendFile() for non-blocking I/O
  • Uses originalConsoleError in the catch handler (line 438) to prevent TUI interception recursion, addressing the concern from previous reviews
  • Provides proper cleanup via closeFileHandles()

477-602: LGTM! Comprehensive buffer access and utility methods.

The logger provides extensive buffer access methods:

  • Merging entries from all categories with chronological sorting by ID
  • Filtering by category, level, or both
  • Buffer management (clear, size)
  • File cleanup utilities
  • Static helpers for introspection

The default export of a singleton instance provides a convenient ready-to-use logger.

tcsenpai and others added 2 commits December 5, 2025 12:59
Store listener callbacks as class properties and remove them in stop()
to prevent memory leaks and multiple handler executions when TUI is
restarted. Null out references after removal to avoid retained closures.

- Add keyListener and resizeListener properties
- Store callbacks before attaching with term.on()
- Remove listeners with term.off() in stop()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Change unbounded quantifier [A-Za-z0-9_ ]+ to bounded {1,50} to prevent
super-linear backtracking on malicious input. Add .trim() to handle any
trailing spaces in captured tags.

Affects extractTag() in LegacyLoggerAdapter.ts and
extractCategoryFromMessage() in TUIManager.ts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/utilities/tui/TUIManager.ts (1)

869-877: Guard updateFilteredLogs() against "CMD" tab to avoid invalid category usage.

Tab.category is LogCategory | "ALL" | "CMD", but logger.getEntriesByCategory expects a LogCategory. When the active tab is "CMD", updateFilteredLogs() can end up calling getEntriesByCategory("CMD") (e.g., via handleLogEntry), which is outside the declared log categories and may break CategorizedLogger depending on its implementation.

Add an explicit guard for "CMD" so the logger never sees that pseudo-category:

     private updateFilteredLogs(): void {
         const activeTab = TABS[this.activeTabIndex]

-        if (activeTab.category === "ALL") {
-            this.filteredLogs = this.logger.getAllEntries()
-        } else {
-            this.filteredLogs = this.logger.getEntriesByCategory(activeTab.category)
-        }
+        if (activeTab.category === "ALL") {
+            this.filteredLogs = this.logger.getAllEntries()
+        } else if (activeTab.category === "CMD") {
+            // CMD tab doesn't show log entries
+            this.filteredLogs = []
+        } else {
+            this.filteredLogs = this.logger.getEntriesByCategory(activeTab.category)
+        }
     }
🧹 Nitpick comments (4)
src/utilities/tui/TUIManager.ts (4)

217-231: Unsubscribe the logger "log" listener on stop()/resetInstance() to avoid leaks across instances.

The constructor registers a "log" listener with:

this.logger.on("log", this.handleLogEntry.bind(this))

but stop() / resetInstance() never remove it. Since resetInstance() can create a new TUIManager while the old instance’s listener remains attached, logger events will keep references to the old instance and call handleLogEntry on it indefinitely.

Store the bound listener and remove it during stop():

 export class TUIManager extends EventEmitter {
@@
-    private logger: CategorizedLogger
+    private readonly logger: CategorizedLogger
+    private logListener: ((entry: LogEntry) => void) | null = null
@@
-        this.logger = CategorizedLogger.getInstance()
-
-        this.nodeInfo = {
+        this.logger = CategorizedLogger.getInstance()
+
+        this.logListener = this.handleLogEntry.bind(this)
+        this.logger.on("log", this.logListener)
+
+        this.nodeInfo = {
@@
     stop(): void {
         if (!this.isRunning) return
@@
-        // Disable TUI mode in logger
-        this.logger.disableTuiMode()
+        // Disable TUI mode in logger and detach listener
+        this.logger.disableTuiMode()
+        if (this.logListener) {
+            this.logger.off("log", this.logListener)
+            this.logListener = null
+        }

resetInstance() already calls stop(), so this will clean up listeners across start/stop cycles and tests.

Also applies to: 246-251, 305-340


569-637: Consider splitting handleCmdInput to reduce complexity and match Sonar’s threshold.

handleCmdInput has a fairly large switch on key and Sonar flags its cognitive complexity (17 vs allowed 15). Functionally it looks correct, but you can reduce complexity and improve readability by extracting small helpers, e.g.:

  • navigateCmdHistory(direction: "up" | "down")
  • executeCurrentCmd()
  • clearOrQuitOnCtrlC()

Example sketch:

private executeCurrentCmdInput(): void {
    this.executeCommand(this.cmdInput)
    if (this.cmdInput.trim()) {
        this.cmdHistory.push(this.cmdInput)
        if (this.cmdHistory.length > 100) this.cmdHistory.shift()
    }
    this.cmdHistoryIndex = this.cmdHistory.length
    this.cmdInput = ""
    this.render()
}

and call this.executeCurrentCmdInput() from the "ENTER" case. Similar extraction for history navigation would satisfy Sonar without changing behavior.


57-80: Tighten up unused config/color helpers (optional cleanup).

COLORS, TUIConfig.debugMode, and getStatusColor() are currently unused in this file; colors and status rendering are all hard-coded via term.* calls.

To avoid dead code and keep the API surface minimal:

  • Either wire these through (e.g., drive header/status colors off COLORS / getStatusColor() and use debugMode to toggle footer hints), or
  • Remove them from this first version to reduce noise and add them back when they’re actually needed.

Also applies to: 183-215, 1378-1393


362-405: Consider using util.inspect for object rendering and mapping console.log to info level.

The current interception uses String(a) which yields "[object Object]" for non-string arguments, making structured logs harder to debug. Similarly, routing console.log to logger.debug() hides third-party library output when running at info level.

Both refinements align with Node.js logging best practices:

  1. Object rendering: Use util.inspect() with controlled depth to preserve object structure:

    import util from "util"
    
    const message = args
      .map(arg => (typeof arg === "string" ? arg : util.inspect(arg, { depth: 3, colors: false })))
      .join(" ")
  2. Severity mapping: Map console.log to info level since it represents user-visible output:

    - this.logger.debug(category, `[console.log] ${cleanMessage}`)
    + this.logger.info(category, `[console.log] ${cleanMessage}`)

These changes improve interoperability with third-party libraries and make console output more useful in production logs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6737118 and de2ceb3.

📒 Files selected for processing (1)
  • src/utilities/tui/TUIManager.ts (1 hunks)
🧰 Additional context used
🪛 GitHub Check: SonarCloud Code Analysis
src/utilities/tui/TUIManager.ts

[warning] 383-383: 'a' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ul&open=AZruYCpjduZKUq2z37ul&pullRequest=518


[warning] 401-401: arrow function is equivalent to String. Use String directly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uq&open=AZruYCpjduZKUq2z37uq&pullRequest=518


[warning] 347-347: Use the "RegExp.exec()" method instead.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uf&open=AZruYCpjduZKUq2z37uf&pullRequest=518


[warning] 186-186: Member 'scrollOffsets' is never reassigned; mark it as readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ud&open=AZruYCpjduZKUq2z37ud&pullRequest=518


[failure] 569-569: Refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ut&open=AZruYCpjduZKUq2z37ut&pullRequest=518


[warning] 377-377: 'a' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uj&open=AZruYCpjduZKUq2z37uj&pullRequest=518


[warning] 401-401: 'a' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ur&open=AZruYCpjduZKUq2z37ur&pullRequest=518


[warning] 183-183: Member 'config' is never reassigned; mark it as readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uc&open=AZruYCpjduZKUq2z37uc&pullRequest=518


[warning] 395-395: 'a' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37up&open=AZruYCpjduZKUq2z37up&pullRequest=518


[warning] 389-389: arrow function is equivalent to String. Use String directly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37um&open=AZruYCpjduZKUq2z37um&pullRequest=518


[warning] 395-395: arrow function is equivalent to String. Use String directly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uo&open=AZruYCpjduZKUq2z37uo&pullRequest=518


[warning] 383-383: arrow function is equivalent to String. Use String directly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uk&open=AZruYCpjduZKUq2z37uk&pullRequest=518


[warning] 480-480: Prefer Number.parseInt over parseInt.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37us&open=AZruYCpjduZKUq2z37us&pullRequest=518


[warning] 182-182: Member 'logger' is never reassigned; mark it as readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ub&open=AZruYCpjduZKUq2z37ub&pullRequest=518


[warning] 389-389: 'a' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37un&open=AZruYCpjduZKUq2z37un&pullRequest=518

Extract switch case handlers into dedicated methods:
- handleCmdEscape, handleCmdEnter, handleCmdBackspace
- handleCmdHistoryUp, handleCmdHistoryDown
- handleCmdCtrlC, handleCmdCharInput
- addToHistory (extracted nested logic)

Use early returns and null-safe operators (??) to simplify control flow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace parseInt(key) with Number.parseInt(key, 10) for consistency
and explicit radix parameter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
src/utilities/tui/TUIManager.ts (3)

24-39: Unify tag parsing/category inference between TUIManager and LegacyLoggerAdapter.

Both TUIManager.extractCategoryFromMessage() and LegacyLoggerAdapter’s extractTag/inferCategory use effectively the same regex and mapping logic, now backed by a shared TAG_TO_CATEGORY table. This duplication makes it easy for the two paths to drift.

Consider moving the tag‑parsing helpers into the shared tagCategories.ts (or a small sibling like tagParsing.ts) and reusing them in both places, e.g.:

  • In tagCategories.ts export:
    • extractTag(message): { tag: string | null; cleanMessage: string }
    • inferCategory(tag: string | null): LogCategory
  • In TUIManager, replace extractCategoryFromMessage with a call to these helpers.
  • In LegacyLoggerAdapter, reuse the same helpers instead of its local implementations.

That way, changes to tag syntax or categorization are centralized in one place.

Also applies to: 346-358


125-135: CMD quit command reintroduces direct process.exit(0), bypassing graceful shutdown.

The quit command directly calls process.exit(0) after emitting "quit" and tui.stop(), while handleQuit() is explicitly designed to let the main app handle shutdown via the "quit" event. This duplicates logic, bypasses any async cleanup the app may perform, and partially undoes the earlier refactor that removed direct exits from handleQuit().

Consider delegating the command to handleQuit() and making that method callable from the command layer:

@@
     {
         name: "quit",
         description: "Exit the node",
         handler: (_args, tui) => {
             tui.addCmdOutput("Shutting down...")
             setTimeout(() => {
-                tui.emit("quit")
-                tui.stop()
-                process.exit(0)
+                // Reuse the same graceful shutdown path as keyboard-based quit
+                tui.handleQuit()
             }, 500)
         },
     },
@@
-    private handleQuit(): void {
+    handleQuit(): void {
         this.stop()
         this.emit("quit")
     }

This keeps all quit flows going through a single, graceful path and leaves responsibility for actually exiting the process to the main application.

Also applies to: 689-692


871-878: Guard updateFilteredLogs() against "CMD" tab category to avoid invalid logger calls.

activeTab.category is typed as LogCategory | "ALL" | "CMD", but updateFilteredLogs() treats everything except "ALL" as a valid LogCategory and passes it to getEntriesByCategory. When the active tab is "CMD", this will pass "CMD" into the logger, which is neither a valid LogCategory nor something the logger expects.

Add an explicit guard for the "CMD" tab so the logger only receives real categories:

     private updateFilteredLogs(): void {
         const activeTab = TABS[this.activeTabIndex]

         if (activeTab.category === "ALL") {
             this.filteredLogs = this.logger.getAllEntries()
+        } else if (activeTab.category === "CMD") {
+            // CMD tab does not use the log area
+            this.filteredLogs = []
         } else {
             this.filteredLogs = this.logger.getEntriesByCategory(activeTab.category)
         }
     }

This matches the intent that CMD mode uses its own output buffer and avoids passing unsupported categories into the logger.

src/utilities/tui/LegacyLoggerAdapter.ts (2)

100-116: info() ignores logToTerminal and contains dead config code.

The info() method currently:

  • Computes config and prevTerminal from this.logger.getConfig()
  • Checks !logToTerminal && !this.logger.isTuiMode()
  • But never actually changes logger behavior based on logToTerminal

So the parameter is effectively ignored while leaving confusing, unused code around.

Either fully implement the “no terminal output for this call” behavior, or simplify by explicitly ignoring the parameter. If you don’t need per-call suppression, a minimal cleanup would be:

-    static info(message: string, logToTerminal = true): void {
+    static info(message: string, _logToTerminal = true): void {
         if (this.LOG_ONLY_ENABLED) return

         const { tag, cleanMessage } = extractTag(message)
         const category = inferCategory(tag)
-
-        // Temporarily adjust terminal output based on parameter
-        const config = this.logger.getConfig()
-        const prevTerminal = config.terminalOutput
-
-        if (!logToTerminal && !this.logger.isTuiMode()) {
-            // In non-TUI mode, we need to suppress terminal for this call
-            // We'll emit the event but not print
-        }
 
         this.logger.info(category, cleanMessage)
     }

If legacy callers rely on logToTerminal, consider wiring it through the underlying logger’s config in a try/finally instead.


240-257: cleanLogs() duplicates cleanup already handled by CategorizedLogger.

cleanLogs() delegates to this.logger.cleanLogs(withCustom) and then immediately performs its own directory scan and rmSync loop under this.LOGS_DIR. This duplicates responsibility, increases I/O surface, and can race with the logger’s own file operations.

Given that CategorizedLogger.cleanLogs() is the new source of truth, consider dropping the legacy manual removal logic:

     static cleanLogs(withCustom = false): void {
         this.logger.cleanLogs(withCustom)
-
-        // Also clean using legacy paths for compatibility
-        if (fs.existsSync(this.LOGS_DIR)) {
-            const files = fs.readdirSync(this.LOGS_DIR)
-            for (const file of files) {
-                if (file.startsWith("custom_") && !withCustom) {
-                    continue
-                }
-                try {
-                    fs.rmSync(`${this.LOGS_DIR}/${file}`, { force: true })
-                } catch {
-                    // Ignore errors
-                }
-            }
-        }
     }

If some deployments still depend on the legacy behavior, gate the extra cleanup behind an explicit flag rather than running it unconditionally.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between de2ceb3 and 576ef5a.

📒 Files selected for processing (2)
  • src/utilities/tui/LegacyLoggerAdapter.ts (1 hunks)
  • src/utilities/tui/TUIManager.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/utilities/tui/TUIManager.ts (4)
src/utilities/tui/tagCategories.ts (2)
  • LogCategory (114-114)
  • TAG_TO_CATEGORY (14-111)
src/utilities/tui/CategorizedLogger.ts (3)
  • LogCategory (25-35)
  • CategorizedLogger (196-593)
  • LogEntry (40-46)
src/libs/peer/PeerManager.ts (1)
  • PeerManager (20-423)
src/utilities/sharedState.ts (1)
  • getSharedState (266-268)
src/utilities/tui/LegacyLoggerAdapter.ts (3)
src/utilities/tui/tagCategories.ts (2)
  • LogCategory (114-114)
  • TAG_TO_CATEGORY (14-111)
src/utilities/tui/CategorizedLogger.ts (2)
  • LogCategory (25-35)
  • CategorizedLogger (196-593)
src/utilities/sharedState.ts (1)
  • getSharedState (266-268)
🪛 GitHub Check: SonarCloud Code Analysis
src/utilities/tui/TUIManager.ts

[warning] 349-349: Remove duplicates in this character class.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruZYIPhc2agEIOiHLl&open=AZruZYIPhc2agEIOiHLl&pullRequest=518


[warning] 182-182: Member 'logger' is never reassigned; mark it as readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ub&open=AZruYCpjduZKUq2z37ub&pullRequest=518


[warning] 385-385: arrow function is equivalent to String. Use String directly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uk&open=AZruYCpjduZKUq2z37uk&pullRequest=518


[warning] 403-403: 'a' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ur&open=AZruYCpjduZKUq2z37ur&pullRequest=518


[warning] 349-349: Use the "RegExp.exec()" method instead.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruZYIPhc2agEIOiHLj&open=AZruZYIPhc2agEIOiHLj&pullRequest=518


[warning] 633-633: Prefer String#codePointAt() over String#charCodeAt().

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uu&open=AZruYCpjduZKUq2z37uu&pullRequest=518


[warning] 9-9: Prefer node:events over events.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ua&open=AZruYCpjduZKUq2z37ua&pullRequest=518


[warning] 183-183: Member 'config' is never reassigned; mark it as readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uc&open=AZruYCpjduZKUq2z37uc&pullRequest=518


[warning] 379-379: 'a' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37uj&open=AZruYCpjduZKUq2z37uj&pullRequest=518


[warning] 385-385: 'a' will use Object's default stringification format ('[object Object]') when stringified.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37ul&open=AZruYCpjduZKUq2z37ul&pullRequest=518


[warning] 391-391: arrow function is equivalent to String. Use String directly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCpjduZKUq2z37um&open=AZruYCpjduZKUq2z37um&pullRequest=518

src/utilities/tui/LegacyLoggerAdapter.ts

[warning] 26-26: Use the "RegExp.exec()" method instead.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruZYFJhc2agEIOiHLg&open=AZruZYFJhc2agEIOiHLg&pullRequest=518


[warning] 54-54: Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uN&open=AZruYCnTduZKUq2z37uN&pullRequest=518


[warning] 57-57: Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uQ&open=AZruYCnTduZKUq2z37uQ&pullRequest=518


[warning] 52-52: Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uL&open=AZruYCnTduZKUq2z37uL&pullRequest=518


[warning] 60-60: Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uR&open=AZruYCnTduZKUq2z37uR&pullRequest=518


[warning] 26-26: Remove duplicates in this character class.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruZYFJhc2agEIOiHLi&open=AZruZYFJhc2agEIOiHLi&pullRequest=518


[warning] 55-55: Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uO&open=AZruYCnTduZKUq2z37uO&pullRequest=518


[warning] 50-50: Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uJ&open=AZruYCnTduZKUq2z37uJ&pullRequest=518


[warning] 53-53: Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uM&open=AZruYCnTduZKUq2z37uM&pullRequest=518


[warning] 108-108: Remove this useless assignment to variable "prevTerminal".

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uS&open=AZruYCnTduZKUq2z37uS&pullRequest=518


[warning] 47-47: Member 'logger' is never reassigned; mark it as readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uI&open=AZruYCnTduZKUq2z37uI&pullRequest=518


[warning] 51-51: Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=kynesyslabs_node&issues=AZruYCnTduZKUq2z37uK&open=AZruYCnTduZKUq2z37uK&pullRequest=518

Add LogCategory to import from tagCategories.ts for explicit type
resolution. The code worked before due to TypeScript inference, but
explicit imports are clearer and safer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@tcsenpai
Copy link
Contributor Author

tcsenpai commented Dec 6, 2025

Your trial has ended! 😢

To keep getting reviews, activate your plan here.

Got questions about plans or want to see if we can extend your trial? Talk to our founders here.😎

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
src/utilities/tui/TUIManager.ts (2)

125-135: quit command still calls process.exit(0) directly, bypassing graceful shutdown.

This was flagged in a previous review. The quit command handler (line 132) calls process.exit(0) directly after tui.stop(), which bypasses any asynchronous cleanup the main application might perform in response to the "quit" event. This is inconsistent with handleQuit() (lines 714-717) which was fixed to delegate shutdown to the application.

Consider reusing the graceful shutdown path:

     {
         name: "quit",
         description: "Exit the node",
         handler: (_args, tui) => {
             tui.addCmdOutput("Shutting down...")
             setTimeout(() => {
-                tui.emit("quit")
-                tui.stop()
-                process.exit(0)
+                // Reuse the same graceful shutdown path
+                ;(tui as any).handleQuit()
             }, 500)
         },
     },

Alternatively, make handleQuit() public and call it directly.


896-904: getEntriesByCategory may receive invalid "CMD" category.

When the CMD tab is active, activeTab.category is "CMD", which is not a valid LogCategory type. While this code path may not execute in practice (CMD tab uses renderCmdArea instead of log filtering), the type system doesn't enforce this, and the call at line 902 could fail or produce unexpected results.

Add an explicit guard for the CMD category:

 private updateFilteredLogs(): void {
     const activeTab = TABS[this.activeTabIndex]

     if (activeTab.category === "ALL") {
         this.filteredLogs = this.logger.getAllEntries()
+    } else if (activeTab.category === "CMD") {
+        // CMD tab doesn't use filtered logs
+        this.filteredLogs = []
     } else {
         this.filteredLogs = this.logger.getEntriesByCategory(activeTab.category)
     }
 }
src/utilities/tui/LegacyLoggerAdapter.ts (2)

100-116: Unused variables config and prevTerminal with incomplete implementation.

The info() method retrieves config and stores prevTerminal (lines 107-108) but never uses them. The comment at lines 110-113 indicates an intent to conditionally suppress terminal output, but the implementation is incomplete—the if-block is empty.

Either complete the implementation or remove the dead code:

 static info(message: string, logToTerminal = true): void {
     if (this.LOG_ONLY_ENABLED) return

     const { tag, cleanMessage } = extractTag(message)
     const category = inferCategory(tag)

-    // Temporarily adjust terminal output based on parameter
-    const config = this.logger.getConfig()
-    const prevTerminal = config.terminalOutput
-
-    if (!logToTerminal && !this.logger.isTuiMode()) {
-        // In non-TUI mode, we need to suppress terminal for this call
-        // We'll emit the event but not print
-    }
-
     this.logger.info(category, cleanMessage)
 }

240-257: Double file cleanup may cause race conditions or redundant I/O.

This was flagged in a previous review. The method calls this.logger.cleanLogs(withCustom) then immediately performs manual file deletion in the same directory. This could cause issues if the logger's cleanup is async or if files are being written concurrently.

Consider removing the legacy cleanup if CategorizedLogger.cleanLogs() already handles this:

 static cleanLogs(withCustom = false): void {
     this.logger.cleanLogs(withCustom)
-
-    // Also clean using legacy paths for compatibility
-    if (fs.existsSync(this.LOGS_DIR)) {
-        const files = fs.readdirSync(this.LOGS_DIR)
-        for (const file of files) {
-            if (file.startsWith("custom_") && !withCustom) {
-                continue
-            }
-            try {
-                fs.rmSync(`${this.LOGS_DIR}/${file}`, { force: true })
-            } catch {
-                // Ignore errors
-            }
-        }
-    }
 }
🧹 Nitpick comments (3)
.beads/issues.jsonl (1)

1-13: Optional: Consider reorganizing issues by topic or sequence.

The TUI phases appear non-sequentially (1, 2, 5, 4, 3, 6) with ZK Identity phases interleaved throughout. While JSONL files are often append-only, reorganizing by topic (TUI phases together, then ZK phases) or by phase sequence would improve readability and maintainability.

</comment_end>

src/utilities/tui/TUIManager.ts (1)

1405-1420: Unused method getStatusColor() and COLORS constant.

The getStatusColor() method is defined but never called. Similarly, the COLORS constant (lines 59-80) is never referenced. These appear to be dead code, possibly from an earlier design iteration.

Consider removing unused code to improve maintainability:

-// SECTION Color Schemes
-
-const COLORS = {
-    // Status colors
-    statusRunning: "green",
-    ...
-}

And remove the getStatusColor() method, or document its intended future use with a TODO comment.

src/utilities/tui/LegacyLoggerAdapter.ts (1)

161-194: Inconsistent sync/async file operations in custom() method.

The method is declared async but mixes sync and async file operations:

  • Line 176: fs.rmSync (sync)
  • Line 177: await fs.promises.writeFile (async)
  • Line 185: fs.appendFileSync (sync)

This inconsistency could cause confusion and doesn't leverage the async nature of the method.

Consider using consistent async operations:

-        // Write to custom file
-        try {
-            fs.appendFileSync(customPath, logEntry)
-        } catch {
-            // Ignore errors
-        }
+        // Write to custom file
+        try {
+            await fs.promises.appendFile(customPath, logEntry)
+        } catch {
+            // Ignore errors
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 576ef5a and 8ed5487.

📒 Files selected for processing (4)
  • .beads/.local_version (1 hunks)
  • .beads/issues.jsonl (1 hunks)
  • src/utilities/tui/LegacyLoggerAdapter.ts (1 hunks)
  • src/utilities/tui/TUIManager.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .beads/.local_version
🔇 Additional comments (12)
.beads/issues.jsonl (1)

1-13: Data structure is well-formed and comprehensive.

The issue entries are properly structured with all required fields, dependencies, timestamps, and rich metadata. The design sections contain detailed technical specifications, acceptance criteria are clearly defined with checklists, and status tracking includes proper timestamps. The JSONL format is correctly applied with valid JSON on each line.

</comment_end>

src/utilities/tui/TUIManager.ts (7)

937-953: LGTM!

The checkIfStandalone() method is well-implemented with proper optional chaining, defensive error handling, and a safe fallback to standalone mode when peers cannot be determined.


1168-1200: LGTM!

The log entry rendering with timestamp extraction, level icons, and category display is well-structured. The timestamp parsing is safe given LogEntry.timestamp is typed as Date.


236-251: LGTM!

The singleton pattern implementation is correct with proper instance management and cleanup in resetInstance().


614-620: LGTM!

Command history properly limits size to 100 entries, preventing memory accumulation. This addresses the previous concern about unbounded command history.


364-407: LGTM!

Console interception is well-implemented with:

  • Guard against double-interception (line 366)
  • Category extraction from messages
  • Appropriate routing to logger methods

The choice to map console.log to debug level (rather than info) is reasonable for captured external library output.


267-340: LGTM!

The TUI lifecycle management is well-implemented:

  • Idempotent start() with early return
  • Proper listener cleanup in stop() using stored references
  • Correct ordering: remove listeners → restore console → restore terminal
  • TUI mode toggling on the logger

1058-1078: No issues found. The getSharedState usage is correct. It's a getter property (not a function call) that returns the SharedState singleton instance via SharedState.getInstance(). The property access pattern getSharedState.lastBlockNumber and getSharedState.syncStatus is appropriate and matches the actual export mechanism defined in src/utilities/sharedState.ts (lines 269-273). The naming convention with "get" is accurate—it reflects that this is a getter, not a regular constant.

src/utilities/tui/LegacyLoggerAdapter.ts (4)

24-31: LGTM!

The extractTag function has a well-designed regex with proper ReDoS mitigation using bounded quantifier {1,50} instead of unbounded +. Good defensive coding.


199-235: LGTM!

The only() and disableOnlyMode() methods are now properly implemented:

  • Original console.log is stored before overwriting
  • disableOnlyMode() correctly restores the original
  • ONLY messages use originalLog to bypass the no-op override

This addresses previous concerns about permanent console mutation and silent ONLY messages.


13-39: LGTM!

The imports and helper functions are well-structured:

  • TAG_TO_CATEGORY and LogCategory properly imported from shared tagCategories module
  • inferCategory has correct return type
  • Both helpers have clear, focused responsibilities

This addresses the previous concerns about duplicated mapping and missing type imports.


68-95: replace(".", "") only removes the first dot.

Line 74 uses identityFile?.replace(".", "") which only removes the first occurrence of .. If the identity file name contains multiple dots (e.g., "node.identity.json"), only the first dot is removed, resulting in "nodeidentity.json".

If the intent is to remove all dots or just the leading dot, consider:

-const identityFile = getSharedState.identityFile?.replace(".", "") ?? ""
+// Remove only leading dot (if present)
+const identityFile = getSharedState.identityFile?.replace(/^\./, "") ?? ""
+// OR remove all dots
+const identityFile = getSharedState.identityFile?.replaceAll(".", "") ?? ""

Verify the expected identity file naming convention to determine the correct approach.

Comment on lines +1 to +2
{"id":"node-1q8","title":"Phase 1: Categorized Logger Utility","description":"Create a new categorized Logger utility that serves as a drop-in replacement for the current logger. Must support categories and be TUI-ready.","design":"## Logger Categories\n\n- **CORE** - Main bootstrap, warmup, general operations\n- **NETWORK** - RPC server, connections, HTTP endpoints\n- **PEER** - Peer management, peer gossip, peer bootstrap\n- **CHAIN** - Blockchain, blocks, mempool\n- **SYNC** - Synchronization operations\n- **CONSENSUS** - PoR BFT consensus operations\n- **IDENTITY** - GCR, identity management\n- **MCP** - MCP server operations\n- **MULTICHAIN** - Cross-chain/XM operations\n- **DAHR** - DAHR-specific operations\n\n## API Design\n\n```typescript\n// New logger interface\ninterface LogEntry {\n level: LogLevel;\n category: LogCategory;\n message: string;\n timestamp: Date;\n}\n\ntype LogLevel = 'debug' | 'info' | 'warning' | 'error' | 'critical';\ntype LogCategory = 'CORE' | 'NETWORK' | 'PEER' | 'CHAIN' | 'SYNC' | 'CONSENSUS' | 'IDENTITY' | 'MCP' | 'MULTICHAIN' | 'DAHR';\n\n// Usage:\nlogger.info('CORE', 'Starting the node');\nlogger.error('NETWORK', 'Connection failed');\nlogger.debug('CHAIN', 'Block validated #45679');\n```\n\n## Features\n\n1. Emit events for TUI to subscribe to\n2. Maintain backward compatibility with file logging\n3. Ring buffer for in-memory log storage (TUI display)\n4. Category-based filtering\n5. Log level filtering","acceptance_criteria":"- [ ] LogCategory type with all 10 categories defined\n- [ ] New Logger class with category-aware methods\n- [ ] Event emitter for TUI integration\n- [ ] Ring buffer for last N log entries (configurable, default 1000)\n- [ ] File logging preserved (backward compatible)\n- [ ] Unit tests for logger functionality","status":"closed","priority":1,"issue_type":"feature","assignee":"claude","created_at":"2025-12-04T15:45:22.238751684+01:00","updated_at":"2025-12-04T15:57:01.3507118+01:00","closed_at":"2025-12-04T15:57:01.3507118+01:00","labels":["logger","phase-1","tui"],"dependencies":[{"issue_id":"node-1q8","depends_on_id":"node-wrd","type":"parent-child","created_at":"2025-12-04T15:46:41.663898616+01:00","created_by":"daemon"}]}
{"id":"node-66u","title":"Phase 2: TUI Framework Setup","description":"Set up the TUI framework using terminal-kit (already installed). Create the basic layout structure with panels.","design":"## Layout Structure\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ HEADER: Node info, status, version │\n├─────────────────────────────────────────────────────────────────┤\n│ TABS: Category selection │\n├─────────────────────────────────────────────────────────────────┤\n│ │\n│ LOG AREA: Scrollable log display │\n│ │\n├─────────────────────────────────────────────────────────────────┤\n│ FOOTER: Controls and status │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n## Components\n\n1. **TUIManager** - Main orchestrator\n2. **HeaderPanel** - Node info display\n3. **TabBar** - Category tabs\n4. **LogPanel** - Scrollable log view\n5. **FooterPanel** - Controls and input\n\n## terminal-kit Features to Use\n\n- ScreenBuffer for double-buffering\n- Input handling (keyboard shortcuts)\n- Color support\n- Box drawing characters","acceptance_criteria":"- [ ] TUIManager class created\n- [ ] Basic layout with 4 panels renders correctly\n- [ ] Terminal resize handling\n- [ ] Keyboard input capture working\n- [ ] Clean exit handling (restore terminal state)","status":"closed","priority":1,"issue_type":"feature","assignee":"claude","created_at":"2025-12-04T15:45:22.405530697+01:00","updated_at":"2025-12-04T16:03:17.66943608+01:00","closed_at":"2025-12-04T16:03:17.66943608+01:00","labels":["phase-2","tui","ui"],"dependencies":[{"issue_id":"node-66u","depends_on_id":"node-1q8","type":"blocks","created_at":"2025-12-04T15:46:29.51715706+01:00","created_by":"daemon"},{"issue_id":"node-66u","depends_on_id":"node-wrd","type":"parent-child","created_at":"2025-12-04T15:46:41.730819864+01:00","created_by":"daemon"}]}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider updating the TUI Epic status to reflect progress.

The TUI Epic (node-wrd, line 13) is marked as "open", but 4 of its 6 child phases (Phases 1, 2, 3, 4) are already "closed", Phase 5 is "in_progress", and only Phase 6 remains "open". Updating the epic's status to "in_progress" would better reflect the actual project state and completion progress.

</comment_end>

Also applies to: 9-9, 11-13

🤖 Prompt for AI Agents
In .beads/issues.jsonl around lines 1-2, the TUI Epic (node-wrd, currently
"open") should be updated to reflect that most child phases are closed or in
progress; change its "status" field to "in_progress", update the "updated_at"
timestamp to now (ISO8601), and ensure its dependency/children metadata still
references the correct phase issues without changing their closed_at fields; do
not mark the epic closed.

@tcsenpai
Copy link
Contributor Author

tcsenpai commented Dec 6, 2025

Your trial has ended! 😢

To keep getting reviews, activate your plan here.

Got questions about plans or want to see if we can extend your trial? Talk to our founders here.😎

@tcsenpai
Copy link
Contributor Author

tcsenpai commented Dec 8, 2025

Your trial has ended! 😢

To keep getting reviews, activate your plan here.

Got questions about plans or want to see if we can extend your trial? Talk to our founders here.😎

@tcsenpai
Copy link
Contributor Author

tcsenpai commented Dec 8, 2025

Your trial has ended! 😢

To keep getting reviews, activate your plan here.

Got questions about plans or want to see if we can extend your trial? Talk to our founders here.😎

1 similar comment
@tcsenpai
Copy link
Contributor Author

tcsenpai commented Dec 8, 2025

Your trial has ended! 😢

To keep getting reviews, activate your plan here.

Got questions about plans or want to see if we can extend your trial? Talk to our founders here.😎

@github-actions
Copy link

github-actions bot commented Dec 8, 2025

⚠️ Beads Issue Tracking Files Detected

This PR modifies .beads/ issue tracking files. After merge, these changes will be automatically reverted to preserve branch-specific issue tracking.

Files that will be reverted:

  • .beads/deletions.jsonl
  • .beads/issues.jsonl

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 8, 2025

@tcsenpai tcsenpai merged commit f5cd9cb into testnet Dec 8, 2025
7 checks passed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
.beads/issues.jsonl (1)

7-7: Epic status still needs updating (duplicate concern).

As previously noted, the TUI Epic (node-wrd) remains marked as "open" despite 4 of 6 phases being closed and Phase 5 in progress. Update the status to "in_progress" and refresh the "updated_at" timestamp to accurately reflect project completion state.

src/utilities/tui/TUIManager.ts (2)

892-900: Guard updateFilteredLogs against "CMD" category.

Tab.category includes "CMD", but the logger’s getEntriesByCategory API is intended for real log categories. When the CMD tab is active (and when new log entries arrive), updateFilteredLogs() can be invoked with activeTab.category === "CMD" and will call getEntriesByCategory("CMD").

Even though the CMD tab doesn't render log entries, making the intent explicit keeps both runtime behavior and typing cleaner:

     private updateFilteredLogs(): void {
         const activeTab = TABS[this.activeTabIndex]

-        if (activeTab.category === "ALL") {
-            this.filteredLogs = this.logger.getAllEntries()
-        } else {
-            this.filteredLogs = this.logger.getEntriesByCategory(activeTab.category)
-        }
+        if (activeTab.category === "ALL") {
+            this.filteredLogs = this.logger.getAllEntries()
+        } else if (activeTab.category === "CMD") {
+            // CMD tab does not display categorized logs
+            this.filteredLogs = []
+        } else {
+            this.filteredLogs = this.logger.getEntriesByCategory(activeTab.category)
+        }
     }

This avoids ever passing "CMD" into the logger.


125-135: Route quit command through handleQuit() instead of calling process.exit(0) directly.

The quit command currently emits "quit", stops the TUI, and then calls process.exit(0), which bypasses any asynchronous cleanup the main app might do in response to the "quit" event. You already have a graceful path in handleQuit() that stops the TUI and emits "quit" without forcing an immediate exit.

Reusing handleQuit() here avoids duplicating shutdown logic and keeps the shutdown strategy consistent:

     {
         name: "quit",
         description: "Exit the node",
         handler: (_args, tui) => {
             tui.addCmdOutput("Shutting down...")
             setTimeout(() => {
-                tui.emit("quit")
-                tui.stop()
-                process.exit(0)
+                // Reuse the same graceful shutdown path as keyboard-based quit
+                tui.handleQuit()
             }, 500)
         },
     },

And make handleQuit callable from the command handler:

-    private handleQuit(): void {
+    handleQuit(): void {
         this.stop()
         this.emit("quit")
     }

This aligns the CMD quit behavior with the keyboard Q/CTRL_C path and with previous feedback about avoiding direct process.exit.

Also applies to: 698-701

🧹 Nitpick comments (1)
src/utilities/tui/TUIManager.ts (1)

229-231: Optionally detach the logger log listener on stop/reset to avoid accumulation.

The ctor registers a bound handler on the shared CategorizedLogger:

this.logger.on("log", this.handleLogEntry.bind(this))

but stop() / resetInstance() never remove that listener. Across multiple resetInstance() calls (e.g., in tests), this can accumulate listeners and retain old TUIManager instances.

Consider storing the bound listener and removing it on stop/reset:

 export class TUIManager extends EventEmitter {
+    private logListener: ((entry: LogEntry) => void) | null = null;
@@
-        this.logger = CategorizedLogger.getInstance()
-
-        this.nodeInfo = {
+        this.logger = CategorizedLogger.getInstance()
+
+        this.nodeInfo = {
@@
-        // Subscribe to log events
-        this.logger.on("log", this.handleLogEntry.bind(this))
+        // Subscribe to log events
+        this.logListener = this.handleLogEntry.bind(this)
+        this.logger.on("log", this.logListener)
@@
     stop(): void {
         if (!this.isRunning) return
@@
-        // Disable TUI mode in logger
+        // Disable TUI mode in logger
         this.logger.disableTuiMode()
@@
         this.emit("stopped")
     }
@@
     static resetInstance(): void {
         if (TUIManager.instance) {
-            TUIManager.instance.stop()
+            TUIManager.instance.stop()
+            if (TUIManager.instance.logListener) {
+                TUIManager.instance.logger.off("log", TUIManager.instance.logListener)
+                TUIManager.instance.logListener = null
+            }
             TUIManager.instance = null
         }
     }

This keeps the singleton clean across start/stop/reset cycles, especially in test environments.

Also applies to: 246-251, 305-340

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fb58e79 and 288cfa9.

📒 Files selected for processing (4)
  • .beads/issues.jsonl (1 hunks)
  • README.md (1 hunks)
  • run (6 hunks)
  • src/utilities/tui/TUIManager.ts (1 hunks)
🔇 Additional comments (2)
README.md (1)

48-79: TUI documentation matches implementation and CLI flags.

The new TUI section, controls table, and -t / --no-tui legacy mode docs are consistent with the run script and TUIManager key handling. No changes needed.

run (1)

55-59: TUI on/off UX and startup command wiring look good.

The -t / --no-tui help text, examples, display summary, and the FINAL_COMMAND augmentation with -- --no-tui cleanly expose legacy (non‑TUI) mode without affecting default behavior. This matches the README and keeps the startup flow straightforward.

Also applies to: 66-68, 403-407, 615-619, 623-624

run
Comment on lines +309 to +318
# Handle long options (--no-tui) before getopts
for arg in "$@"; do
case $arg in
--no-tui)
NO_TUI=true
# Remove --no-tui from arguments so getopts doesn't choke on it
set -- "${@/--no-tui/}"
;;
esac
done
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the file
fd -t f "^run$" | head -5

Repository: kynesyslabs/node

Length of output: 63


🏁 Script executed:

# Read the run file around lines 309-318
head -320 run | tail -20

Repository: kynesyslabs/node

Length of output: 798


🏁 Script executed:

#!/bin/bash
# Test the current implementation behavior vs. proposed fix

# Test 1: Current implementation with ${@/--no-tui/}
echo "=== TEST 1: Current implementation ==="
test_current() {
    set -- "-p" "53551" "--no-tui" "-x" "value"
    echo "Before processing: \$@ = $*"
    echo "Number of args before: $#"
    
    for arg in "$@"; do
        case $arg in
            --no-tui)
                # Current approach
                set -- "${@/--no-tui/}"
                ;;
        esac
    done
    
    echo "After processing: \$@ = $*"
    echo "Number of args after: $#"
    echo "Arg list:"
    for i in "${!@}"; do
        echo "  [$((i+1))] = '${!i}'"
    done
}

test_current

# Test 2: Proposed fix with FILTERED_ARGS
echo ""
echo "=== TEST 2: Proposed fix ==="
test_proposed() {
    set -- "-p" "53551" "--no-tui" "-x" "value"
    echo "Before processing: \$@ = $*"
    echo "Number of args before: $#"
    
    FILTERED_ARGS=()
    for arg in "$@"; do
        case "$arg" in
            --no-tui)
                NO_TUI=true
                ;;
            *)
                FILTERED_ARGS+=("$arg")
                ;;
        esac
    done
    set -- "${FILTERED_ARGS[@]}"
    
    echo "After processing: \$@ = $*"
    echo "Number of args after: $#"
    echo "Arg list:"
    for i in "${!@}"; do
        echo "  [$((i+1))] = '${!i}'"
    done
}

test_proposed

Repository: kynesyslabs/node

Length of output: 576


🏁 Script executed:

# Read the run file to see the actual implementation and how getopts is used
sed -n '309,330p' run

Repository: kynesyslabs/node

Length of output: 779


🏁 Script executed:

# Also check for getopts usage to understand the impact
rg -n "getopts" run -A 3 -B 1

Repository: kynesyslabs/node

Length of output: 524


Fix --no-tui preprocessing: ${@/--no-tui/} leaves empty argument slots that break getopts.

Using ${@/--no-tui/} removes the text but leaves the positional argument empty. This creates a gap in $@ that getopts will see as a blank argument, breaking option parsing for commands like ./run -p 53551 --no-tui. The argument count remains unchanged but with a missing value, confusing the parser.

Build a filtered array instead:

-# Handle long options (--no-tui) before getopts
+# Handle long option (--no-tui) before getopts without breaking other args
+FILTERED_ARGS=()
 for arg in "$@"; do
-    case $arg in
+    case "$arg" in
         --no-tui)
             NO_TUI=true
-            # Remove --no-tui from arguments so getopts doesn't choke on it
-            set -- "${@/--no-tui/}"
             ;;
+        *)
+            FILTERED_ARGS+=("$arg")
+            ;;
     esac
 done
+set -- "${FILTERED_ARGS[@]}"

This removes --no-tui entirely while preserving argument boundaries for getopts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Handle long options (--no-tui) before getopts
for arg in "$@"; do
case $arg in
--no-tui)
NO_TUI=true
# Remove --no-tui from arguments so getopts doesn't choke on it
set -- "${@/--no-tui/}"
;;
esac
done
# Handle long option (--no-tui) before getopts without breaking other args
FILTERED_ARGS=()
for arg in "$@"; do
case "$arg" in
--no-tui)
NO_TUI=true
;;
*)
FILTERED_ARGS+=("$arg")
;;
esac
done
set -- "${FILTERED_ARGS[@]}"
🤖 Prompt for AI Agents
In run around lines 309 to 318, the preprocessing for --no-tui uses
"${@/--no-tui/}" which leaves empty positional slots and breaks getopts; instead
iterate over "$@" and build a new array of args that excludes any exact
"--no-tui" entries, set NO_TUI=true when seen, then replace the positional
parameters with the filtered array (set -- "${filtered[@]}") so argument
boundaries are preserved and getopts works correctly.

Comment on lines +463 to +549
switch (key) {
// Quit
case "q":
case "Q":
case "CTRL_C":
this.handleQuit()
break

// Tab switching with number keys
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
this.setActiveTab(Number.parseInt(key, 10))
break

case "-":
this.setActiveTab(10) // DAHR tab
break

case "=":
this.setActiveTab(11) // CMD tab
break

// Tab navigation
case "TAB":
case "RIGHT":
this.nextTab()
break

case "SHIFT_TAB":
case "LEFT":
this.previousTab()
break

// Scrolling
case "UP":
case "k":
this.scrollUp()
break

case "DOWN":
case "j":
this.scrollDown()
break

case "PAGE_UP":
this.scrollPageUp()
break

case "PAGE_DOWN":
this.scrollPageDown()
break

case "HOME":
this.scrollToTop()
break

case "END":
this.scrollToBottom()
break

// Toggle auto-scroll
case "a":
case "A":
this.toggleAutoScroll()
break

// Clear logs
case "c":
case "C":
this.clearLogs()
break

// Help
case "h":
case "H":
case "?":
this.showHelp()
break
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align help text with actual key bindings (S/P/R are not implemented).

showHelp() advertises S=start, P=pause, R=restart:

this.logger.info("CORE", "Controls: S=start, P=pause, R=restart, Q=quit")

but handleKeyPress() does not handle S, P, or R at all—only Q, navigation keys, scroll keys, A, C, H, and ?. This can confuse users when they try keys that do nothing.

Either wire up those keys, or (simpler) adjust the help text to only list the implemented controls, e.g.:

-        this.logger.info("CORE", "Controls: S=start, P=pause, R=restart, Q=quit")
-        this.logger.info("CORE", "Other: A=auto-scroll, C=clear, H=help")
+        this.logger.info("CORE", "Controls: Q=quit")
+        this.logger.info("CORE", "Other: A=auto-scroll, C=clear, H=help")

This keeps in-TUI help accurate for current behavior.

Also applies to: 1365-1372

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant