Skip to content

feat(dev-server): add log buffer and logs tab to DevTools#1455

Open
KanchanWaldia wants to merge 1 commit into
Karanjot786:mainfrom
KanchanWaldia:feat/devtools-log-panel
Open

feat(dev-server): add log buffer and logs tab to DevTools#1455
KanchanWaldia wants to merge 1 commit into
Karanjot786:mainfrom
KanchanWaldia:feat/devtools-log-panel

Conversation

@KanchanWaldia

@KanchanWaldia KanchanWaldia commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Description

Added appendLog, scrollLog, logs getter, and a logs tab to DevTools, following the existing _eventLog buffer pattern.

Related Issue

Closes #508

Which package(s)?

@termuijs/dev-server

Type of Change

  • 🐛 Bug fix (type:bug)
  • ✨ Feature (type:feature)
  • 📝 Docs (type:docs)
  • 🧪 Tests (type:testing)
  • ♻️ Refactor (type:refactor)
  • 🎨 Design / UX (type:design)
  • ♿ Accessibility (type:accessibility)
  • ⚡ Performance (type:performance)
  • 🔧 DevOps / CI (type:devops)
  • 🔒 Security (type:security)

Checklist

  • ⭐ You starred the repo. The needs-star check blocks your merge otherwise.
  • Tests pass locally: bun vitest run
  • Build passes: bun run build
  • Typecheck passes: bun run typecheck
  • You read CONTRIBUTING.md.
  • Your PR title follows type: short description.
  • Widget state mutators call markDirty() (if your change affects rendering).
  • No new any types without an inline comment explaining why.
  • No unrelated refactors bundled into this PR.

GSSoC 2026 Participation

  • You are a GSSoC 2026 contributor.
  • Your GSSoC profile: https://gssoc.girlscript.org/profile/9d129ba7-b2c3-46e7-aa8a-9557f15eef5f

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a Logs tab to DevTools with buffered log rendering and scroll control, plus adds ASCII-mode tests for the Gauge widget. DevTools now captures stdout/stderr output, maintains a 100-entry buffer, and displays logs in the panel with viewport scrolling. Gauge tests extend coverage for NO_UNICODE environment configuration.

Changes

DevTools Logs Tab Feature

Layer / File(s) Summary
Log entry type and DevTools API
packages/dev-server/src/devtools.ts
LogEntry type contract introduced ({ line: string; stream: 'stdout' | 'stderr' }). DevTools gains _logBuffer (capped at 100 entries), _logScrollOffset state, and new public methods: appendLog(line, stream), scrollLog(delta), and logs getter. Tab union expanded to include 'logs'.
Log panel rendering
packages/dev-server/src/devtools.ts
Tab bar updated to include Logs option in getPanel(). New 'logs' case in panel switch slices buffered logs by scroll offset and panel height, formats each line with [out]/[err] prefix, and displays "No logs yet" when empty.
Log buffer and panel tests
packages/dev-server/src/devtools.test.ts
New Vitest suite verifies appendLog buffers default stdout and explicit stderr, enforces 100-entry cap by dropping oldest lines, getPanel renders logs when tab is 'logs', scrollLog updates viewport, and scroll offset clamps to valid range.

Gauge ASCII Mode Tests

Layer / File(s) Summary
ASCII mode test cases
packages/widgets/src/data/Gauge.test.ts
Imports updated to include beforeEach/afterEach from vitest and caps from @termuijs/core. Two new tests exercise Gauge under NO_UNICODE=1: one verifies env var set/unset, the other confirms value round-trip (setValue(0.5)getValue() === 0.5).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #508: Both PRs implement the DevTools Logs tab feature with appendLog, scrollLog, logs API, and comprehensive devtools tests for log buffering and panel rendering.

Suggested labels

area:dev-server, type:feature, type:testing, area:widgets, level:intermediate, quality:clean

Suggested reviewers

  • Karanjot786

Poem

🐰 A logger hops through buffers bright,
Logs captured, scrolled with all its might,
Gauge glows in ASCII's humble glow,
Tests ensure both streams will flow! 📊✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR claims to close issue #36, which requests a TextArea widget in packages/ui/src/TextArea.ts. However, the PR only implements log buffer functionality in devtools.ts/devtools.test.ts and updates a Gauge test, with no TextArea implementation. Implement the TextArea widget as specified in issue #36 with multi-line text input, cursor movement, Enter/Ctrl+Enter handling, and required tests, or link to a different issue that matches the actual changes.
Out of Scope Changes check ⚠️ Warning The PR includes changes to packages/widgets/src/data/Gauge.test.ts (adding Unicode environment variable tests) which are unrelated to the devtools log buffer feature and issue #36's TextArea requirements. Remove the unrelated Gauge.test.ts changes or create a separate PR for those modifications to keep this PR focused on the devtools log buffer feature.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(dev-server): add log buffer and logs tab to DevTools' clearly and specifically describes the main changes in the PR, matching the actual code modifications to devtools.ts and devtools.test.ts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description is complete and follows the required template with all essential sections properly filled out.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions github-actions Bot added area:widgets @termuijs/widgets area:dev-server @termuijs/dev-server type:testing +10 pts. Tests. type:feature +10 pts. New feature. labels Jun 13, 2026

@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: 3

🧹 Nitpick comments (1)
packages/dev-server/src/devtools.test.ts (1)

53-53: 💤 Low value

Remove leftover development comment.

The // ← ADD THIS comment appears to be a development artifact that should be cleaned up.

Proposed fix
-        dt.setTab('logs');  // ← ADD THIS
+        dt.setTab('logs');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dev-server/src/devtools.test.ts` at line 53, Remove the leftover
development inline comment after the dt.setTab('logs') call: delete the " // ←
ADD THIS" text so the line reads simply dt.setTab('logs'); (no other behavior
changes required).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dev-server/src/devtools.test.ts`:
- Around line 40-48: The test in DevTools (methods appendLog, setTab, scrollLog,
getPanel) calls scrollLog(-5) which clamps at 0 and doesn't prove offset
behavior; change the test to move the offset away from 0 (e.g., call
dt.scrollLog(5) or another positive value after appending 20 lines) and then
call dt.getPanel(...) and assert the panel reflects that offset (for example
expect it to contain 'line 5' and/or not contain 'line 0') so the test actually
verifies that scroll offset affects visible content.

In `@packages/widgets/src/data/Gauge.test.ts`:
- Around line 37-43: Replace the direct mutation of process.env and the
redundant getValue assertion with a rendering test that spies on the capability
flag and uses a real Screen: use vi.spyOn(capabilitiesModule,
'unicode').mockReturnValue(false) (or vi.spyOn(caps, 'unicode') depending on the
import) before creating the Gauge('CPU'), create a Screen, call g.setValue(0.5)
and render the widget to the Screen, then assert the rendered output contains
ASCII characters like '#' or '-' and does not contain full-block chars like '█';
use beforeEach/afterEach to create/teardown the Screen and restore the spy
(spy.mockRestore()) to avoid side-effects and remove any process.env.NO_UNICODE
manipulation and the duplicate getValue() assertion.
- Around line 31-35: The test currently mutates process.env and only asserts
that mutation instead of verifying observable behavior; replace it by spying and
rendering: use vi.spyOn to mock the caps value exported from the env-caps module
(spy on the module's caps or the function that computes it so caps.unicode
returns false), render the Gauge with a real Screen instance, call updateRect()
and render(), and assert the Screen output contains ASCII-only characters (or
that the Gauge output matches an expected ASCII snapshot). Use beforeEach to set
up the spy and create the Screen/Gauge, and afterEach to restore spies and
destroy the Screen (vi.restoreAllMocks()/spy.mockRestore()) to avoid leaking
state; reference caps.unicode, vi.spyOn, Screen, updateRect(), render(),
beforeEach, and afterEach in your changes.

---

Nitpick comments:
In `@packages/dev-server/src/devtools.test.ts`:
- Line 53: Remove the leftover development inline comment after the
dt.setTab('logs') call: delete the " // ← ADD THIS" text so the line reads
simply dt.setTab('logs'); (no other behavior changes required).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 30823af8-be99-4f4d-9037-1c498fba0865

📥 Commits

Reviewing files that changed from the base of the PR and between 07f0dff and e502762.

📒 Files selected for processing (3)
  • packages/dev-server/src/devtools.test.ts
  • packages/dev-server/src/devtools.ts
  • packages/widgets/src/data/Gauge.test.ts

Comment on lines +40 to +48
it('getPanel respects scroll offset', () => {
const dt = new DevTools();
for (let i = 0; i < 20; i++) dt.appendLog(`line ${i}`);
dt.setTab('logs');
dt.scrollLog(-5);
const panel = dt.getPanel(40, 10);
const joined = panel.join('\n');
expect(joined).toContain('line 0');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test does not verify scroll offset behavior.

Calling scrollLog(-5) when the offset is already 0 clamps to 0, so the panel shows line 0 regardless of the scroll call. This test passes trivially without proving that scroll offset affects panel content.

To properly test that scroll offset changes visible output:

Proposed fix
     it('getPanel respects scroll offset', () => {
         const dt = new DevTools();
         for (let i = 0; i < 20; i++) dt.appendLog(`line ${i}`);
         dt.setTab('logs');
-        dt.scrollLog(-5);
+        dt.scrollLog(10);
         const panel = dt.getPanel(40, 10);
         const joined = panel.join('\n');
-        expect(joined).toContain('line 0');
+        expect(joined).not.toContain('line 0');
+        expect(joined).toContain('line 10');
     });
📝 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
it('getPanel respects scroll offset', () => {
const dt = new DevTools();
for (let i = 0; i < 20; i++) dt.appendLog(`line ${i}`);
dt.setTab('logs');
dt.scrollLog(-5);
const panel = dt.getPanel(40, 10);
const joined = panel.join('\n');
expect(joined).toContain('line 0');
});
it('getPanel respects scroll offset', () => {
const dt = new DevTools();
for (let i = 0; i < 20; i++) dt.appendLog(`line ${i}`);
dt.setTab('logs');
dt.scrollLog(10);
const panel = dt.getPanel(40, 10);
const joined = panel.join('\n');
expect(joined).not.toContain('line 0');
expect(joined).toContain('line 10');
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dev-server/src/devtools.test.ts` around lines 40 - 48, The test in
DevTools (methods appendLog, setTab, scrollLog, getPanel) calls scrollLog(-5)
which clamps at 0 and doesn't prove offset behavior; change the test to move the
offset away from 0 (e.g., call dt.scrollLog(5) or another positive value after
appending 20 lines) and then call dt.getPanel(...) and assert the panel reflects
that offset (for example expect it to contain 'line 5' and/or not contain 'line
0') so the test actually verifies that scroll offset affects visible content.

Comment thread packages/widgets/src/data/Gauge.test.ts Outdated
Comment on lines +31 to +35
it('caps.unicode is false when NO_UNICODE=1', () => {
process.env.NO_UNICODE = '1';
expect(process.env.NO_UNICODE).toBe('1');
delete process.env.NO_UNICODE;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Test violates guidelines and doesn't verify observable behavior.

This test has several critical issues:

  1. Violates coding guideline: The guideline explicitly states "To test caps.unicode or caps.motion behavior, use vi.spyOn, never direct mutation." Setting process.env.NO_UNICODE directly is forbidden.

  2. Doesn't test observable behavior: The test only asserts expect(process.env.NO_UNICODE).toBe('1'), which is a placeholder test. It doesn't verify that caps.unicode is actually false or that the Gauge renders ASCII characters.

  3. Missing Screen rendering: The guideline requires "Tests must use the real Screen... Render widgets in tests using Screen, updateRect(), and render()." To verify ASCII mode, you must render the widget and check the output.

  4. Incorrect approach: caps.unicode is determined at module load time (see packages/core/src/terminal/env-caps.ts). Setting process.env.NO_UNICODE after the module is imported won't change caps.unicode.

  5. Missing cleanup structure: beforeEach/afterEach were imported but not used. Inline cleanup with delete can leak state if the test throws.

✅ Correct approach using vi.spyOn and Screen
-    it('caps.unicode is false when NO_UNICODE=1', () => {
-        process.env.NO_UNICODE = '1';
-        expect(process.env.NO_UNICODE).toBe('1');
-        delete process.env.NO_UNICODE;
-    });
+    it('renders ASCII characters when caps.unicode is false', () => {
+        vi.spyOn(caps, 'unicode', 'get').mockReturnValue(false);
+        
+        const screen = new Screen();
+        screen.updateRect({ x: 0, y: 0, width: 30, height: 5 });
+        
+        const g = new Gauge('CPU');
+        g.setValue(0.5);
+        g.updateRect({ x: 0, y: 0, width: 30, height: 1 });
+        g.render(screen);
+        
+        // Verify ASCII chars '#' and '-' are used instead of '█' and '░'
+        const output = screen.toString();
+        expect(output).toContain('#');
+        expect(output).toContain('-');
+        expect(output).not.toContain('█');
+        expect(output).not.toContain('░');
+        
+        vi.restoreAllMocks();
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/data/Gauge.test.ts` around lines 31 - 35, The test
currently mutates process.env and only asserts that mutation instead of
verifying observable behavior; replace it by spying and rendering: use vi.spyOn
to mock the caps value exported from the env-caps module (spy on the module's
caps or the function that computes it so caps.unicode returns false), render the
Gauge with a real Screen instance, call updateRect() and render(), and assert
the Screen output contains ASCII-only characters (or that the Gauge output
matches an expected ASCII snapshot). Use beforeEach to set up the spy and create
the Screen/Gauge, and afterEach to restore spies and destroy the Screen
(vi.restoreAllMocks()/spy.mockRestore()) to avoid leaking state; reference
caps.unicode, vi.spyOn, Screen, updateRect(), render(), beforeEach, and
afterEach in your changes.

Source: Coding guidelines

Comment thread packages/widgets/src/data/Gauge.test.ts Outdated
Comment on lines +37 to +43
it('getValue returns 0.5 after setValue(0.5) in ASCII mode', () => {
process.env.NO_UNICODE = '1';
const g = new Gauge('CPU');
g.setValue(0.5);
expect(g.getValue()).toBe(0.5);
delete process.env.NO_UNICODE;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Test doesn't verify ASCII mode rendering and violates guidelines.

This test has critical issues:

  1. Violates coding guideline: "To test caps.unicode or caps.motion behavior, use vi.spyOn, never direct mutation." Direct mutation of process.env.NO_UNICODE is forbidden.

  2. Doesn't test ASCII mode: The test only checks getValue() returns 0.5, which is unaffected by unicode mode. getValue()/setValue() behavior is already tested in lines 15-23. ASCII mode affects rendering (whether the gauge uses #/- or /), not the internal value.

  3. Missing Screen usage: To verify ASCII mode, you must render the widget with a real Screen and check that ASCII characters appear in the output.

  4. Incorrect approach: Setting process.env.NO_UNICODE after module import won't change caps.unicode (it's determined at load time).

  5. Missing cleanup structure: beforeEach/afterEach were imported but not used.

✅ Correct approach to test ASCII mode rendering
-    it('getValue returns 0.5 after setValue(0.5) in ASCII mode', () => {
-        process.env.NO_UNICODE = '1';
-        const g = new Gauge('CPU');
-        g.setValue(0.5);
-        expect(g.getValue()).toBe(0.5);
-        delete process.env.NO_UNICODE;
-    });
+    it('renders filled ASCII bars with # when unicode is disabled', () => {
+        vi.spyOn(caps, 'unicode', 'get').mockReturnValue(false);
+        
+        const screen = new Screen();
+        screen.updateRect({ x: 0, y: 0, width: 30, height: 5 });
+        
+        const g = new Gauge('CPU', {}, {});
+        g.setValue(0.75);  // 75% filled
+        g.updateRect({ x: 0, y: 0, width: 30, height: 1 });
+        g.render(screen);
+        
+        const output = screen.toString();
+        // Verify filled portion uses '#' and empty portion uses '-'
+        expect(output).toMatch(/##+.*-+/);
+        
+        vi.restoreAllMocks();
+    });
📝 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
it('getValue returns 0.5 after setValue(0.5) in ASCII mode', () => {
process.env.NO_UNICODE = '1';
const g = new Gauge('CPU');
g.setValue(0.5);
expect(g.getValue()).toBe(0.5);
delete process.env.NO_UNICODE;
});
it('renders filled ASCII bars with # when unicode is disabled', () => {
vi.spyOn(caps, 'unicode', 'get').mockReturnValue(false);
const screen = new Screen();
screen.updateRect({ x: 0, y: 0, width: 30, height: 5 });
const g = new Gauge('CPU', {}, {});
g.setValue(0.75); // 75% filled
g.updateRect({ x: 0, y: 0, width: 30, height: 1 });
g.render(screen);
const output = screen.toString();
// Verify filled portion uses '#' and empty portion uses '-'
expect(output).toMatch(/##+.*-+/);
vi.restoreAllMocks();
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/data/Gauge.test.ts` around lines 37 - 43, Replace the
direct mutation of process.env and the redundant getValue assertion with a
rendering test that spies on the capability flag and uses a real Screen: use
vi.spyOn(capabilitiesModule, 'unicode').mockReturnValue(false) (or
vi.spyOn(caps, 'unicode') depending on the import) before creating the
Gauge('CPU'), create a Screen, call g.setValue(0.5) and render the widget to the
Screen, then assert the rendered output contains ASCII characters like '#' or
'-' and does not contain full-block chars like '█'; use beforeEach/afterEach to
create/teardown the Screen and restore the spy (spy.mockRestore()) to avoid
side-effects and remove any process.env.NO_UNICODE manipulation and the
duplicate getValue() assertion.

Source: Coding guidelines

@Karanjot786 Karanjot786 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

LGTM. CI is green.

@Karanjot786 Karanjot786 added gssoc:approved Approved PR. Earns +50 base points. quality:clean x 1.2 multiplier. Clean implementation. level:intermediate +35 pts. Moderate task. labels Jun 13, 2026
@KanchanWaldia KanchanWaldia force-pushed the feat/devtools-log-panel branch from e502762 to 07f0dff Compare June 13, 2026 19:15
@KanchanWaldia

Copy link
Copy Markdown
Contributor Author

Hi @Karanjot786, I accidentally closed the PR. Could you help reopen it? Sorry for the inconvenience.

@KanchanWaldia KanchanWaldia reopened this Jun 30, 2026
@Karanjot786

Copy link
Copy Markdown
Owner

Heads-up: this PR currently has an empty diff.

gh api repos/Karanjot786/TermUI/pulls/1455 reports additions: 0, deletions: 0, changed_files: 0 — the head commit (chore: trigger PR reopen) has zero diff against the base. The described feature (log buffer appendLog/scrollLog/logs getter + DevTools logs tab + tests) isn't present, so there's nothing to review (CI shows green only because there's no change).

Looks like the branch was reset/force-updated and lost its commits. Please push the actual feature commits, then re-request review. Thanks!

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

Labels

area:dev-server @termuijs/dev-server area:widgets @termuijs/widgets gssoc:approved Approved PR. Earns +50 base points. level:intermediate +35 pts. Moderate task. quality:clean x 1.2 multiplier. Clean implementation. type:feature +10 pts. New feature. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(dev-server): add log panel for child stdout/stderr

2 participants