Skip to content

fix(live-monitor): borders are misaligned in some terminal environments - #366

Merged
ryoppippi merged 12 commits into
ccusage:mainfrom
AsPulse:fix-issue-172-emoji-misalignment
Aug 3, 2025
Merged

ryoppippi merged 12 commits into
ccusage:mainfrom
AsPulse:fix-issue-172-emoji-misalignment

Conversation

@AsPulse

@AsPulse AsPulse commented Jul 27, 2025 •

Copy link
Copy Markdown
Contributor

This PR implemented the drawEmoji function, which draws all emojis using fixed width characters,
and used it for all emoji drawing parts in the code.

Screenshots

This PR bdc7980b (terminal: Zellij over Ghostty)
image

main 786ddba0 (terminal: Zellij over Ghostty)
Screenshot 2025-07-27 at 15 18 10

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Emojis in live rendering displays are now consistently aligned and spaced for improved readability.
  • Bug Fixes
    • Resolved inconsistent emoji widths in terminal output.
  • Tests
    • Added tests to ensure emojis are always rendered with the correct width in the terminal.

@coderabbitai

coderabbitai Bot commented Jul 27, 2025 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

"""

Walkthrough

The changes introduce a new drawEmoji utility in _terminal-utils.ts to standardize emoji rendering to a consistent width in terminal output. All hardcoded emoji characters in _live-rendering.ts are replaced with calls to drawEmoji, ensuring layout alignment. No control flow or logic is altered beyond this rendering update.

Changes

File(s) Change Summary
Emoji rendering utility
src/_terminal-utils.ts
Added drawEmoji function to wrap emojis with ANSI escape sequences for cursor save/restore and cursor forward movement to maintain consistent emoji width; includes inline tests verifying width correctness for various emojis.
Live display rendering update
src/_live-rendering.ts
Replaced all hardcoded emoji literals with calls to drawEmoji to ensure consistent emoji width rendering in live monitoring output.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Assessment against linked issues

Objective Addressed Explanation
Ensure consistent emoji width rendering to prevent terminal box layout misalignment (#172) ✅
Replace hardcoded emojis with a utility to handle emoji width and cursor positioning (#172) ✅
No changes to control flow or other rendering logic unrelated to emoji rendering (#172) ✅

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes detected.

Possibly related PRs

Poem

🐰✨
Emojis now stand side by side,
In terminal boxes, they no longer hide.
With drawEmoji they keep their place,
No more borders out of space!
A dashboard neat, a rabbit’s delight—
Aligned and tidy, everything’s right.
🖥️🌈
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b7ccd4 and bda6410.

📒 Files selected for processing (1)
  • src/_terminal-utils.ts (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary of Changes

Hello @AsPulse, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where borders in the live monitor display were misaligned in certain terminal environments. The core solution involves introducing a new utility function to ensure consistent rendering of emojis, which often cause these alignment problems due to varying character widths across terminals.

Highlights

  • New drawEmoji Utility: Implemented a drawEmoji function in src/_terminal-utils.ts that standardizes emoji rendering to a consistent two-character width, regardless of the terminal's interpretation of the emoji's actual width.
  • Consistent Emoji Rendering: All existing emoji usages within src/_live-rendering.ts have been updated to utilize the new drawEmoji function, ensuring uniform display and preventing border misalignment.
  • Robustness with ANSI Escapes: The drawEmoji function leverages specific ANSI escape sequences (\u001B7, \u001B8, ansiEscapes.cursorForward) to precisely control cursor positioning and ensure correct rendering, even in challenging terminal environments like NeoVim.
  • In-source Testing: Added Vitest tests directly within src/_terminal-utils.ts to validate that the drawEmoji function consistently produces output with a string width of 2 for both single and double-width emojis.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a drawEmoji function to standardize emoji widths in the terminal, which is a great approach to fixing the alignment issues. My review focuses on improving the implementation of this new function for better consistency and maintainability.

I've identified a logic issue in drawEmoji that causes inconsistent spacing, a typo in a constant name, and a minor performance improvement. Addressing these points will make the new utility more robust and easier to use correctly throughout the codebase.

Comment thread src/_terminal-utils.ts Outdated
Comment thread src/_live-rendering.ts
Comment thread src/_terminal-utils.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/_terminal-utils.ts (1)

335-337: Consider updating the test comment for clarity.

The comment mentions "1-width emojis (should be padded to 2)" but the function doesn't pad - it moves the cursor by the actual emoji width. Consider updating the comment to reflect the actual behavior: "1-width emojis (cursor moves by their actual width)".

This would align the comment with both the implementation and test expectations.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af8c51e and bdc7980.

📒 Files selected for processing (2)
  • src/_live-rendering.ts (7 hunks)
  • src/_terminal-utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Lint code using ESLint MCP server (available via Claude Code tools)
Format code with ESLint (writes changes) using bun run format
No console.log allowed except where explicitly disabled with eslint-disable
Do not use console.log. Use logger.ts instead.

Files:

  • src/_live-rendering.ts
  • src/_terminal-utils.ts
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (CLAUDE.md)

Type check with TypeScript using bun typecheck

Files:

  • src/_live-rendering.ts
  • src/_terminal-utils.ts
**/*.ts

📄 CodeRabbit Inference Engine (CLAUDE.md)

**/*.ts: File paths always use Node.js path utilities for cross-platform compatibility
Use .ts extensions for local file imports (e.g., import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
Use Result.try() for wrapping operations that may throw (JSON parsing, etc.)
Use Result.isFailure() for checking errors (more readable than !Result.isSuccess())
Use early return pattern (if (Result.isFailure(result)) continue;) instead of ternary operators
For async operations: create wrapper function with Result.try() then call it
Keep traditional try-catch only for: file I/O with complex error handling, legacy code that's hard to refactor
Always use Result.isFailure() and Result.isSuccess() type guards for better code clarity
Variables: start with lowercase (camelCase) - e.g., usageDataSchema, modelBreakdownSchema
Types: start with uppercase (PascalCase) - e.g., UsageData, ModelBreakdown
Constants: can use UPPER_SNAKE_CASE - e.g., DEFAULT_CLAUDE_CODE_PATH
Only export constants, functions, and types that are actually used by other modules
Internal/private constants that are only used within the same file should NOT be exported
Always check if a constant is used elsewhere before making it export const vs just const
All test files must use current Claude 4 models, not outdated Claude 3 models
Test coverage should include both Sonnet and Opus models for comprehensive validation
Model names in tests must exactly match LiteLLM's pricing database entries
When adding new model tests, verify the model exists in LiteLLM before implementation
Tests depend on real pricing data from LiteLLM - failures may indicate model availability issues
Dynamic imports using await import() should only be used within test blocks to avoid tree-shaking issues
Mock data is created using fs-fixture with createFixture() for Claude data directory simulation
In-source testing pattern: Tests are written...

Files:

  • src/_live-rendering.ts
  • src/_terminal-utils.ts
**/_*.ts

📄 CodeRabbit Inference Engine (CLAUDE.md)

Internal files: use underscore prefix - e.g., _types.ts, _utils.ts, _consts.ts

Files:

  • src/_live-rendering.ts
  • src/_terminal-utils.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: AsPulse
PR: ryoppippi/ccusage#328
File: src/_live-rendering.ts:133-167
Timestamp: 2025-07-20T23:46:21.740Z
Learning: When AsPulse points out that variables are clearly type-inferred as strings from their construction (using template literals, string methods like .toFixed(), .padStart(), etc.), avoid suggesting unnecessary type assertions that would reduce code readability. ESLint type safety errors about "error type" often indicate type definition issues with imported packages rather than actual type problems in the user's code.
src/_live-rendering.ts (1)

Learnt from: CR
PR: ryoppippi/ccusage#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-19T10:58:04.397Z
Learning: Applies to **/*.ts : Variables: start with lowercase (camelCase) - e.g., usageDataSchema, modelBreakdownSchema

🪛 ESLint
src/_live-rendering.ts

[error] 38-38: Unsafe return of a value of type error.

(ts/no-unsafe-return)


[error] 38-38: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 38-38: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 197-197: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 197-197: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 224-224: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 224-224: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 224-224: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 276-276: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 276-276: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 382-382: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 382-382: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 384-384: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 384-384: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 385-385: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 385-385: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 386-386: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 386-386: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 389-389: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 389-389: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 439-439: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)

src/_terminal-utils.ts

[error] 320-320: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 320-320: Unsafe member access .cursorForward on an error typed value.

(ts/no-unsafe-member-access)


[error] 320-320: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 325-325: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 326-326: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 328-328: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 328-328: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 328-328: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 328-328: Unsafe member access .toBe on an error typed value.

(ts/no-unsafe-member-access)


[error] 329-329: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 329-329: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 329-329: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 329-329: Unsafe member access .toBe on an error typed value.

(ts/no-unsafe-member-access)


[error] 330-330: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 330-330: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 330-330: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 330-330: Unsafe member access .toBe on an error typed value.

(ts/no-unsafe-member-access)


[error] 331-331: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 331-331: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 331-331: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 331-331: Unsafe member access .toBe on an error typed value.

(ts/no-unsafe-member-access)


[error] 332-332: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 332-332: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 332-332: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 332-332: Unsafe member access .toBe on an error typed value.

(ts/no-unsafe-member-access)


[error] 333-333: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 333-333: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 333-333: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 333-333: Unsafe member access .toBe on an error typed value.

(ts/no-unsafe-member-access)


[error] 336-336: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 336-336: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 336-336: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 336-336: Unsafe member access .toBe on an error typed value.

(ts/no-unsafe-member-access)


[error] 337-337: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 337-337: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 337-337: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 337-337: Unsafe member access .toBe on an error typed value.

(ts/no-unsafe-member-access)

🔇 Additional comments (6)
src/_terminal-utils.ts (1)

303-321: Well-implemented emoji rendering solution!

The drawEmoji function properly addresses the terminal alignment issues by:

  • Using compatible ANSI sequences (\u001B7/\u001B8) instead of problematic ones
  • Moving cursor forward by the actual emoji width using stringWidth(emoji)
  • Module-level constants for better performance

The detailed comments explaining the sequence choice are valuable for maintainability.

src/_live-rendering.ts (5)

19-19: Proper import of the new utility function.

The drawEmoji import is correctly added to the existing import statement from _terminal-utils.ts.


38-38: Consistent emoji rendering in rate indicator.

Good application of drawEmoji to ensure the checkmark displays consistently in the "NORMAL" rate status.


197-197: Consistent emoji rendering across all section labels.

All main section labels (SESSION, USAGE, PROJECTION) now use drawEmoji for consistent terminal rendering while preserving the existing formatting and spacing.

Also applies to: 276-276, 389-389


224-224: Improved reliability of critical status indicators.

Status messages now use drawEmoji for consistent rendering of warning (⚠️), error (❌), and success (✓) indicators. This ensures users can reliably see important status information regardless of terminal environment.

Also applies to: 382-386


438-438: Consistent emoji rendering in footer elements.

Good application of drawEmoji to the models display and refresh indicator. The spacing appears consistent with other UI elements.

Also applies to: 445-445

@ryoppippi ryoppippi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

HUGE
LGTM

@pkg-pr-new

pkg-pr-new Bot commented Aug 3, 2025 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/ryoppippi/ccusage@366

commit: bda6410

@ryoppippi
ryoppippi merged commit 62af70f into ccusage:main Aug 3, 2025
@AsPulse
AsPulse deleted the fix-issue-172-emoji-misalignment branch August 3, 2025 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live monitoring dashboard box layout misalignment in various terminal widths

2 participants