test: add renderer stress explorer#172
Conversation
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
|
Warning Review limit reached
More reviews will be available in 52 minutes and 46 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR adds comprehensive stress testing infrastructure to the renderer. It introduces an abstracted and mockable IPC layer, a full in-memory PearAPI mock with test harness controls, web build and Playwright configuration, and two Playwright stress test suites that measure renderer performance under synthetic 1000-agent broker load with variable event rates and chat patterns. ChangesRenderer Stress Testing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a Playwright-based stress testing harness for the renderer, including configuration files, mock IPC implementations (ipc-mock.ts and ipc-electron.ts), and stress test specs to simulate high agent and chat loads. It also includes minor bug fixes, type safety improvements, and DOM test attributes. The review feedback highlights a rate-generation error in the stress explorer's fractional chat distribution, a potential runtime TypeError in AccountSettings.tsx when checking properties on a potentially null event, and an anti-pattern of casting types to never in syntax-highlighter.tsx.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const chatCountForAgent = (tick: number, agentIndex: number): number => { | ||
| if (useLegacyChatHeavyCadence) return tick % 6 === 0 ? 1 : 0 | ||
| const expectedChatEvents = logicalEventsPerAgentPerTick * explicitChatRatio | ||
| const baseChatEvents = Math.floor(expectedChatEvents) | ||
| const fractional = expectedChatEvents - baseChatEvents | ||
| if (fractional <= 0) return baseChatEvents | ||
| const denominator = Math.max(1, Math.round(1 / fractional)) | ||
| return baseChatEvents + ((tick + agentIndex) % denominator === 0 ? 1 : 0) | ||
| } |
There was a problem hiding this comment.
The current implementation of chatCountForAgent uses a rounding-based denominator approach to distribute fractional chat events over ticks. However, this introduces significant rate-generation errors for certain fractional values.
For example, if expectedChatEvents is 0.7 (e.g., 10 events/sec/agent with a 7% chat ratio):
fractionalis0.7denominatorisMath.round(1 / 0.7) = Math.round(1.428) = 1(tick + agentIndex) % 1 === 0is alwaystrue- This results in returning
1event every single tick, leading to an actual rate of1.0instead of0.7(a 42.8% over-generation error).
Similarly, if expectedChatEvents is 0.6, it returns 1 every 2 ticks, leading to a rate of 0.5 instead of 0.6 (a 16.7% under-generation error).
An elegant, stateless, and mathematically perfect way to distribute fractional events over ticks is to use the difference of the cumulative floors:
Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)
This formula guarantees that over any window of
const chatCountForAgent = (tick: number, agentIndex: number): number => {
if (useLegacyChatHeavyCadence) return tick % 6 === 0 ? 1 : 0
const expectedChatEvents = logicalEventsPerAgentPerTick * explicitChatRatio
return Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)
}| } | ||
|
|
||
| if (event.projectId === activeProjectId) { | ||
| if ('projectId' in event && event.projectId === activeProjectId) { |
There was a problem hiding this comment.
Using the 'projectId' in event check directly can throw a runtime TypeError if event is null or undefined (since typeof null === 'object' but using the in operator on it throws an error). To ensure robust defensive programming, verify that event is a truthy object before using the in operator.
| if ('projectId' in event && event.projectId === activeProjectId) { | |
| if (event && typeof event === 'object' && 'projectId' in event && event.projectId === activeProjectId) { |
| lang: lang as never, | ||
| theme: themeMap[theme] as never |
There was a problem hiding this comment.
Casting types to never (e.g., as never) is an anti-pattern that completely disables TypeScript's type checking and can hide potential bugs or breakages during future library updates. It is safer to cast to any if the library types are too complex or not easily importable, or ideally cast to the correct union types imported from the syntax highlighter library (such as BundledLanguage or BundledTheme).
| lang: lang as never, | |
| theme: themeMap[theme] as never | |
| lang: lang as any, | |
| theme: themeMap[theme] as any |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
tests/playwright/stress-1000-agents.spec.ts (1)
94-145: ⚡ Quick winAdd a duplicate/replay event assertion to this stress path.
This test currently validates happy-path load and DOM/state consistency, but it does not inject duplicated
event_idevents to verify dedupe/replay behavior under pressure. Add a small replay batch and assert message count/id-set stability.
Based on learnings: event-streaming test changes should include duplicate/replay cases, not only happy path.Also applies to: 187-213
🤖 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 `@tests/playwright/stress-1000-agents.spec.ts` around lines 94 - 145, Add a small duplicate/replay batch after the final broker injection (inside the chatEveryTicks === null branch after mock.injectBrokerEvents and requestAnimationFrame) that re-sends a subset of events with identical event_id values to simulate replay; use the same event_id format used earlier (e.g., `stress:final:${index}:chat`) and call mock.injectBrokerEvents with that replay array, then assert deduplication by checking chatEvents (or the DOM/message id set) did not increase unexpectedly and that the id set remains stable; ensure the replay targets the same agent names and includes both a few duplicates and a couple of unique events to validate stability under pressure.Source: Learnings
🤖 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 `@src/renderer/src/components/common/StatusBar.tsx`:
- Line 106: The StatusBar effect that polls PRs is only keyed on
projectRootPathKey, so branch switches won't re-run the effect until the next
poll; update the dependency array in the StatusBar useEffect (the effect that
triggers git:active-pull-requests / getActivePullRequestsForRoot and ultimately
uses getCurrentBranch) to include the current branch (e.g.,
projectSummary?.branch) so the effect re-initializes immediately on branch
changes, or alternatively add a clear comment documenting the “stale up to
PULL_REQUEST_REFRESH_MS” behavior if you intend to keep the existing timing.
In `@src/renderer/src/components/diff/DiffPane.tsx`:
- Line 1755: The code unsafely casts the result of useProjectStore((s) =>
s.getActiveRoot()) to ProjectRoot; remove the `as ProjectRoot` so TypeScript
infers ProjectRoot | undefined and rely on the existing runtime guards (e.g.,
the `if (!root || !root.pathExists ...)` checks) or, if the component truly
requires a non-null root invariant, replace the cast with an explicit runtime
assertion immediately after retrieving root (throw or assert if root is
undefined). Update any places that assumed the casted non-null type to handle
root as possibly undefined or after the assertion treat it as non-null.
In `@src/renderer/src/lib/syntax-highlighter.tsx`:
- Around line 110-111: The call in highlightCode that passes lang: lang as never
and theme: themeMap[theme] as never circumvents type safety; instead narrow
values to the exact types expected by codeToTokensBase by introducing a small
typed helper (e.g., a function or typed assertion) that maps/validates the
normalized lang against Shiki’s bundledLanguages/bundledLanguagesAlias (falling
back to 'text') and maps Theme to the actual theme type used in themeMap, then
pass the helper's return values to codeToTokensBase without using as never;
update highlightCode to call this helper and pass lang and theme with correct
types so invalid values are rejected by the compiler.
In `@tests/playwright/stress-explorer.spec.ts`:
- Around line 1-2: Wrap every use of appendFileSync in a defensive try/catch so
file-write failures cannot propagate and fail the test; before writing, ensure
the target directory exists (e.g., mkdirSync(dir, { recursive: true })) and on
failure swallow or non-fatal-log the error (console.debug/processLogger.debug)
rather than rethrowing; update all appendFileSync occurrences (including the
ones used in both the normal path and the catch path) so they follow this
pattern to harden result-file writes.
---
Nitpick comments:
In `@tests/playwright/stress-1000-agents.spec.ts`:
- Around line 94-145: Add a small duplicate/replay batch after the final broker
injection (inside the chatEveryTicks === null branch after
mock.injectBrokerEvents and requestAnimationFrame) that re-sends a subset of
events with identical event_id values to simulate replay; use the same event_id
format used earlier (e.g., `stress:final:${index}:chat`) and call
mock.injectBrokerEvents with that replay array, then assert deduplication by
checking chatEvents (or the DOM/message id set) did not increase unexpectedly
and that the id set remains stable; ensure the replay targets the same agent
names and includes both a few duplicates and a couple of unique events to
validate stability under pressure.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 731e496b-32d4-44d0-b881-473d0d16865c
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (20)
.gitignorepackage.jsonplaywright.stress.config.tssrc/renderer/src/components/chat/ChatMessage.tsxsrc/renderer/src/components/common/StatusBar.tsxsrc/renderer/src/components/diff/DiffPane.tsxsrc/renderer/src/components/graph/GraphView.tsxsrc/renderer/src/components/settings/AccountSettings.tsxsrc/renderer/src/components/terminal/TerminalInstance.tsxsrc/renderer/src/hooks/use-message-reconciliation.tssrc/renderer/src/lib/ipc-electron.tssrc/renderer/src/lib/ipc-mock.tssrc/renderer/src/lib/ipc.tssrc/renderer/src/lib/syntax-highlighter.tsxsrc/renderer/src/vite-env.d.tstests/playwright/STRESS-EXPLORATION-REPORT.mdtests/playwright/stress-1000-agents.spec.tstests/playwright/stress-explorer.spec.tstsconfig.web.jsonvite.web.config.ts
| window.clearInterval(interval) | ||
| } | ||
| }, [projectRootPathKey, projectSummary?.branch]) | ||
| }, [projectRootPathKey]) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check if activePullRequests API or PR display logic references branch state
# Check if activePullRequests filters by branch
rg -nP -C3 'activePullRequests' --type=ts
# Check if PRs are displayed conditionally based on current branch
rg -nP -C3 'pullRequest.*branch|branch.*pullRequest' --type=tsxRepository: AgentWorkforce/pear
Length of output: 2871
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect the effect in StatusBar.tsx around the dependency array
sed -n '70,140p' src/renderer/src/components/common/StatusBar.tsx
# 2) Re-run PR/branch searches with safe TSX matching (no --type=tsx)
rg -nP -C3 'activePullRequests' src --glob '*.ts' --glob '*.tsx'
rg -nP -C3 'pullRequest.*branch|branch.*pullRequest' src --glob '*.ts' --glob '*.tsx'Repository: AgentWorkforce/pear
Length of output: 5995
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find any branch -> pull request filtering/usages in the renderer
rg -n --hidden --glob '*.{ts,tsx}' 'projectSummary\?\.(branch|headBranch|currentBranch)|currentBranch|activePullRequests' src/renderer
# Find any pullRequests filtering by pullRequest.branch
rg -n --hidden --glob '*.{ts,tsx}' 'pullRequests.*filter|filter.*pullRequests|pullRequests.*includes|includes.*pullRequests' src/renderer
rg -n --hidden --glob '*.{ts,tsx}' 'pullRequest\.branch' src/renderer
# Also scan the IPC API usage sites (renderer+preload) for branch-based params
rg -n --hidden --glob '*.{ts,tsx}' 'activePullRequests\(|git:active-pull-requests|pull-requests|pullRequests' src/preload src/renderer src/shared/typesRepository: AgentWorkforce/pear
Length of output: 788
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect StatusBar for any usage of projectSummary?.branch/currentBranch when displaying PRs
sed -n '1,220p' src/renderer/src/components/common/StatusBar.tsx
# Find the IPC handler implementation for 'git:active-pull-requests'
rg -n --hidden --glob '*.{ts,tsx}' "git:active-pull-requests|active-pull-requests|activePullRequests" srcRepository: AgentWorkforce/pear
Length of output: 9592
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '430,560p' src/main/ipc-handlers.tsRepository: AgentWorkforce/pear
Length of output: 5144
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the implementation of getActivePullRequests in the main git layer
rg -n --hidden --glob '*.{ts,js}' 'getActivePullRequests' src/main src/shared
# Print the implementation region (first match) with a bit of surrounding context
file=$(rg -l --hidden --glob '*.{ts,js}' 'getActivePullRequests' src/main src/shared | head -n 1)
echo "Using file: $file"
sed -n '1,240p' "$file" | head -n 240Repository: AgentWorkforce/pear
Length of output: 9087
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1280,1420p' src/main/git.tsRepository: AgentWorkforce/pear
Length of output: 4779
PR list refresh is branch-sensitive but the StatusBar effect won’t re-run until the next tick/root change.
git:active-pull-requests ultimately calls getActivePullRequestsForRoot, which derives the lookup from getCurrentBranch(path) and queries PRs for that branch. Since StatusBar only re-initializes its polling effect on [projectRootPathKey], switching branches won’t refresh the PR indicator until the next PULL_REQUEST_REFRESH_MS (60s) interval tick (or a root/project change). If intentional, document this “stale up to 60s” behavior; otherwise include the current branch (e.g., projectSummary?.branch) in the dependency array to refresh immediately on branch switches.
🤖 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 `@src/renderer/src/components/common/StatusBar.tsx` at line 106, The StatusBar
effect that polls PRs is only keyed on projectRootPathKey, so branch switches
won't re-run the effect until the next poll; update the dependency array in the
StatusBar useEffect (the effect that triggers git:active-pull-requests /
getActivePullRequestsForRoot and ultimately uses getCurrentBranch) to include
the current branch (e.g., projectSummary?.branch) so the effect re-initializes
immediately on branch changes, or alternatively add a clear comment documenting
the “stale up to PULL_REQUEST_REFRESH_MS” behavior if you intend to keep the
existing timing.
| const windowFocused = useWindowFocused() | ||
| const activeProject = useProjectStore((s) => s.getActiveProject()) | ||
| const root = useProjectStore((s) => s.getActiveRoot()) | ||
| const root = useProjectStore((s) => s.getActiveRoot()) as ProjectRoot |
There was a problem hiding this comment.
Unsafe type assertion bypasses null safety.
The cast as ProjectRoot assumes getActiveRoot() never returns undefined, but the component has guards like if (!root || !root.pathExists ...) at line 2210 and elsewhere, indicating root can be nullable. This type assertion removes TypeScript's null-checking protection.
Instead, let TypeScript infer the type as ProjectRoot | undefined and rely on existing guards, or add a runtime assertion immediately after if this assumption is a component invariant.
🛡️ Recommended fix to preserve type safety
- const root = useProjectStore((s) => s.getActiveRoot()) as ProjectRoot
+ const root = useProjectStore((s) => s.getActiveRoot())The existing guards throughout the component already handle undefined correctly.
📝 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.
| const root = useProjectStore((s) => s.getActiveRoot()) as ProjectRoot | |
| const root = useProjectStore((s) => s.getActiveRoot()) |
🤖 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 `@src/renderer/src/components/diff/DiffPane.tsx` at line 1755, The code
unsafely casts the result of useProjectStore((s) => s.getActiveRoot()) to
ProjectRoot; remove the `as ProjectRoot` so TypeScript infers ProjectRoot |
undefined and rely on the existing runtime guards (e.g., the `if (!root ||
!root.pathExists ...)` checks) or, if the component truly requires a non-null
root invariant, replace the cast with an explicit runtime assertion immediately
after retrieving root (throw or assert if root is undefined). Update any places
that assumed the casted non-null type to handle root as possibly undefined or
after the assertion treat it as non-null.
| lang: lang as never, | ||
| theme: themeMap[theme] as never |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify shiki version evidence and current callsite typing.
rg -n '"shiki"|shiki' package.json
fd -i 'pnpm-lock.yaml|package-lock.json|yarn.lock' -x rg -n 'shiki' {}
rg -n 'codeToTokensBase\(|as never' src/renderer/src/lib/syntax-highlighter.tsxRepository: AgentWorkforce/pear
Length of output: 2350
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
# Show the relevant section around the flagged lines
sed -n '70,150p' "$FILE" | cat -n
# Find how lang/theme/langMap/themeMap are defined and typed in this file
rg -n "langMap|themeMap|\blang\b|\btheme\b" "$FILE"
# Locate codeToTokensBase import and its type usage
rg -n "codeToTokensBase|codeToTokens" "$FILE"Repository: AgentWorkforce/pear
Length of output: 2791
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
sed -n '70,150p' "$FILE" | cat -n
rg -n "langMap|themeMap|\blang\b|\btheme\b" "$FILE"
rg -n "codeToTokensBase|codeToTokens" "$FILE"Repository: AgentWorkforce/pear
Length of output: 2791
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
sed -n '70,150p' "$FILE" | cat -n
rg -n "langMap|themeMap|\blang\b|\btheme\b" "$FILE"
rg -n "codeToTokensBase|codeToTokens" "$FILE"Repository: AgentWorkforce/pear
Length of output: 2791
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
sed -n '70,150p' "$FILE" | cat -n
rg -n "langMap|themeMap|\blang\b|\btheme\b" "$FILE"
rg -n "codeToTokensBase|codeToTokens" "$FILE"Repository: AgentWorkforce/pear
Length of output: 2791
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
sed -n '90,130p' "$FILE" | cat -nRepository: AgentWorkforce/pear
Length of output: 1404
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
sed -n '90,140p' "$FILE" | cat -n
rg -n "langMap|themeMap" "$FILE"
rg -n "codeToTokensBase|createHighlighter|codeToTokens" "$FILE"
rg -n "as never" "$FILE"Repository: AgentWorkforce/pear
Length of output: 1794
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
sed -n '90,140p' "$FILE" | cat -n
rg -n "langMap|themeMap" "$FILE"
rg -n "codeToTokensBase|createHighlighter|codeToTokens" "$FILE"
rg -n "as never" "$FILE"Repository: AgentWorkforce/pear
Length of output: 1794
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
wc -l "$FILE"Repository: AgentWorkforce/pear
Length of output: 111
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/lib/syntax-highlighter.tsx"
sed -n '1,220p' "$FILE" | head -n 160Repository: AgentWorkforce/pear
Length of output: 3691
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate Shiki type definitions in this repo sandbox
# (prefer .d.ts so we can see the exact option types)
fd -a "codeToTokensBase" node_modules/shiki -t f -e d.ts -e ts | head -n 50
# Search for the codeToTokensBase declaration and print a small surrounding window
rg -n "codeToTokensBase" node_modules/shiki -S --glob "*.d.ts" | head -n 50
# Print the declaration around the best match
FILE="$(rg -l "codeToTokensBase" node_modules/shiki -S --glob "*.d.ts" | head -n 1)"
echo "DECL_FILE=$FILE"
if [ -n "${FILE:-}" ]; then
rg -n "codeToTokensBase" "$FILE" -n
# Show the block around the first match
LNUM="$(rg -n "codeToTokensBase" "$FILE" | head -n 1 | cut -d: -f1)"
START=$((LNUM-20))
END=$((LNUM+80))
sed -n "${START},${END}p" "$FILE" | cat -n
fiRepository: AgentWorkforce/pear
Length of output: 169
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -ლა . | head
ls -la node_modules 2>/dev/null || echo "no node_modules/"
fd shiki -t d -a . 2>/dev/null | head -n 50
rg -n "codeToTokensBase" -S --glob "*.d.ts" . | head -n 50Repository: AgentWorkforce/pear
Length of output: 127
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -la . | head -n 50
if [ -d node_modules ]; then
echo "node_modules exists"
else
echo "node_modules missing"
fi
# Search for any local Shiki type declarations in the repo (not node_modules)
fd -a "shiki.*d.ts" -t f . 2>/dev/null | head -n 50
rg -n "codeToTokensBase" -S --glob "*.d.ts" . | head -n 50Repository: AgentWorkforce/pear
Length of output: 2082
🌐 Web query:
Shiki 4.0.2 codeToTokensBase type options lang theme
💡 Result:
In Shiki 4.0.2, codeToTokensBase is a low-level function used for syntax highlighting that returns raw token data [1]. It serves as the replacement for the deprecated codeToThemedTokens API [2][1]. The function signature for codeToTokensBase accepts the highlighter instance, the code string, and an options object [1]. The options object (CodeToTokensBaseOptions) extends TokenizeWithThemeOptions and typically includes the following [1]: lang: Specifies the language (grammar) to use for tokenization. If omitted, it defaults to 'text' [1]. theme: Specifies the theme to use. If omitted, it defaults to the first theme loaded in the highlighter instance [1]. Additional options available via TokenizeWithThemeOptions include [3][1]: - colorReplacements: A map for replacing specific theme colors. - tokenizeMaxLineLength: Limits the line length for tokenization. - tokenizeTimeLimit: Sets a time limit for the tokenization process. - grammarState: Provides an initial grammar state. - grammarContextCode: Contextual code for grammar parsing. - includeExplanation: A boolean that, when set to true, includes scope explanation data for the tokens [1]. Because codeToTokensBase is a lower-level API, it is often recommended to use the higher-level codeToTokens function when possible, which provides a more streamlined interface for most use cases [2][1].
Citations:
- 1: shikijs/shiki@d123b4bc
- 2: https://shiki.style/guide/migrate
- 3: https://github.com/shikijs/shiki/blob/main/packages/types/src/options.ts
🌐 Web query:
Shiki 4.0.2 CodeToTokensBaseOptions TokenizeWithThemeOptions lang theme TypeScript
💡 Result:
In Shiki 4.0.2, the tokenization process leverages codeToTokensBase (which replaced the older codeToThemedTokens API) [1][2]. The configuration types relevant to this process are CodeToTokensBaseOptions and TokenizeWithThemeOptions [3][2]. CodeToTokensBaseOptions This interface is used to configure the behavior when calling codeToTokensBase. It extends TokenizeWithThemeOptions and includes the following primary properties [3][2]: - lang: Specifies the language (grammar) for the code snippet [2]. - theme: Specifies the theme to be used for tokenization [2]. TokenizeWithThemeOptions This interface defines granular options for the tokenization engine itself [2][4]. Key properties include: - colorReplacements: A map of color names to new color values, allowing you to override specific theme colors [2][5]. - includeExplanation: A boolean or the string literal 'scopeName' that determines whether to include detailed information about why a token received a specific color. Setting it to 'scopeName' is a performance-optimized option that includes only the scope names [5][4]. - tokenizeMaxLineLength and tokenizeTimeLimit: Constraints for the tokenization engine [4]. - grammarState and grammarContextCode: Advanced options for controlling the tokenization grammar context [4]. TypeScript Usage To tokenize code in TypeScript, you typically use codeToTokensBase (for direct base tokenization) or codeToTokens (a higher-level API that handles theme resolution automatically) [1][2][6]. Example: import { codeToTokensBase } from 'shiki'; // Basic usage with TypeScript const tokens = await codeToTokensBase(code, { lang: 'typescript', theme: 'vitesse-dark', includeExplanation: 'scopeName' // Example of using TokenizeWithThemeOptions }); In Shiki 4.x, ensure you are using the correct asynchronous shorthands or the createHighlighter factory, as Shiki 4.0.0+ requires Node.js 20+ and has standardized the API surface [7][8][9].
Citations:
- 1: https://shiki.style/guide/migrate
- 2: shikijs/shiki@d123b4bc
- 3: shikijs/shiki@e0fcf9f5
- 4: https://github.com/shikijs/shiki/blob/main/packages/types/src/options.ts
- 5: shikijs/shiki@efbb7fb
- 6: https://shiki.style/guide/install.html
- 7: https://shiki.style/guide/shorthands
- 8: https://shiki.style/blog/v4
- 9: https://registry.npmjs.org/shiki
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "export (type|interface) Theme|enum Theme|type Theme" src -S
rg -n "`@/stores/ui-store`|ui-store" -S src/renderer/src/lib/syntax-highlighter.tsxRepository: AgentWorkforce/pear
Length of output: 199
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/renderer/src/stores/ui-store.ts"
sed -n '1,120p' "$FILE" | cat -n
# Also locate ThemeSchema definition if it's elsewhere
rg -n "ThemeSchema|themeSchema|Theme =" "$FILE"Repository: AgentWorkforce/pear
Length of output: 5536
🌐 Web query:
github shikijs shiki packages/types src options.ts CodeToTokensBaseOptions lang theme types
💡 Result:
In the Shiki library, CodeToTokensBaseOptions is a TypeScript interface defined in @shikijs/types (and historically located in packages/core/src/types/tokens.ts or similar paths in earlier versions) that configures the low-level codeToTokensBase function [1][2]. Key aspects of CodeToTokensBaseOptions include: Languages and Themes: It is a generic interface, defined as CodeToTokensBaseOptions, which allows for type-safe specification of language and theme identifiers [1][2]. Base Configuration: The interface extends TokenizeWithThemeOptions, which provides foundational properties for tokenization, such as handling color replacements [1][2]. Core Options: - lang: Specifies the language of the code to be tokenized. It accepts string literals for supported languages or special identifiers like 'text' or 'ansi' [1][2]. - theme: Specifies the theme to be used for tokenization [2]. If not provided, the function typically defaults to the first loaded theme [2]. Evolution: - The interface was previously associated with the now-deprecated codeToThemedTokens function [2][3]. It was renamed to codeToTokensBase as part of a significant API modernization that introduced the high-level codeToTokens and codeToHast APIs [2][3]. - The options object continues to evolve with the library, recently adding support for advanced features like colorsRendering to manage how multiple themes are rendered via CSS variables [4][5]. For most use cases, users are encouraged to use the high-level shorthands like codeToHtml or codeToTokens, which provide a more convenient API than directly using codeToTokensBase [2][6][3].
Citations:
- 1: shikijs/shiki@e0fcf9f5
- 2: shikijs/shiki@d123b4bc
- 3: https://shiki.style/guide/migrate
- 4: shikijs/shiki@0f850d1c
- 5: https://github.com/shikijs/shiki/blob/main/packages/types/src/options.ts
- 6: https://shiki.style/guide/shorthands
Remove as never in Shiki codeToTokensBase options.
In src/renderer/src/lib/syntax-highlighter.tsx (highlightCode), the call to codeToTokensBase uses lang: lang as never and theme: themeMap[theme] as never. Since lang is normalized against Shiki’s bundledLanguages/bundledLanguagesAlias (fallback 'text') and theme is already limited by Theme to 'github-dark-default' | 'github-light-default', replace the as never casts with proper narrowing to codeToTokensBase’s lang/theme option types (e.g., a small typed helper) so invalid values can’t slip past type checking.
🤖 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 `@src/renderer/src/lib/syntax-highlighter.tsx` around lines 110 - 111, The call
in highlightCode that passes lang: lang as never and theme: themeMap[theme] as
never circumvents type safety; instead narrow values to the exact types expected
by codeToTokensBase by introducing a small typed helper (e.g., a function or
typed assertion) that maps/validates the normalized lang against Shiki’s
bundledLanguages/bundledLanguagesAlias (falling back to 'text') and maps Theme
to the actual theme type used in themeMap, then pass the helper's return values
to codeToTokensBase without using as never; update highlightCode to call this
helper and pass lang and theme with correct types so invalid values are rejected
by the compiler.
| import { appendFileSync } from 'node:fs' | ||
| import { expect, test } from '@playwright/test' |
There was a problem hiding this comment.
Harden result-file writes so logging cannot fail the stress explorer run.
appendFileSync can throw (missing parent dir, permissions). Because this helper is called in both try and catch, a write failure can bubble out and fail the test unexpectedly.
Proposed fix
-import { appendFileSync } from 'node:fs'
+import { appendFileSync, mkdirSync } from 'node:fs'
+import { dirname } from 'node:path'
@@
const appendResult = (record: RunRecord): void => {
const resultPath = process.env.STRESS_RESULT_PATH
if (!resultPath) return
- appendFileSync(resultPath, `${JSON.stringify({ ...record, recordedAt: new Date().toISOString() })}\n`)
+ try {
+ mkdirSync(dirname(resultPath), { recursive: true })
+ appendFileSync(resultPath, `${JSON.stringify({ ...record, recordedAt: new Date().toISOString() })}\n`)
+ } catch (error) {
+ console.warn('[stress-explorer] failed to append result', error)
+ }
}Also applies to: 103-107, 336-348
🤖 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 `@tests/playwright/stress-explorer.spec.ts` around lines 1 - 2, Wrap every use
of appendFileSync in a defensive try/catch so file-write failures cannot
propagate and fail the test; before writing, ensure the target directory exists
(e.g., mkdirSync(dir, { recursive: true })) and on failure swallow or
non-fatal-log the error (console.debug/processLogger.debug) rather than
rethrowing; update all appendFileSync occurrences (including the ones used in
both the normal path and the catch path) so they follow this pattern to harden
result-file writes.
There was a problem hiding this comment.
3 issues found across 21 files
You’re at about 99% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="playwright.stress.config.ts">
<violation number="1" location="playwright.stress.config.ts:11">
P2: The preview server command is not strict about port binding, which can make Playwright target the wrong URL when 4174 is occupied.</violation>
</file>
<file name="src/renderer/src/components/common/StatusBar.tsx">
<violation number="1" location="src/renderer/src/components/common/StatusBar.tsx:106">
P2: Removing `projectSummary?.branch` from this effect dependencies delays PR refresh on branch switch, causing stale PR status for up to the 60s poll interval.</violation>
</file>
<file name="tests/playwright/stress-explorer.spec.ts">
<violation number="1" location="tests/playwright/stress-explorer.spec.ts:179">
P2: The fractional event distribution logic has significant rate-generation errors. When `Math.round(1 / fractional)` rounds to 1 (e.g., for expectedChatEvents=0.7), the modulo condition is always true, generating 1 event per tick instead of 0.7 (42.8% over-generation). Use a cumulative-floor approach for mathematically correct distribution:
return Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| timeout: 15_000 | ||
| }, | ||
| webServer: { | ||
| command: 'npx vite preview --config vite.web.config.ts --host 127.0.0.1 --port 4174', |
There was a problem hiding this comment.
P2: The preview server command is not strict about port binding, which can make Playwright target the wrong URL when 4174 is occupied.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At playwright.stress.config.ts, line 11:
<comment>The preview server command is not strict about port binding, which can make Playwright target the wrong URL when 4174 is occupied.</comment>
<file context>
@@ -0,0 +1,23 @@
+ timeout: 15_000
+ },
+ webServer: {
+ command: 'npx vite preview --config vite.web.config.ts --host 127.0.0.1 --port 4174',
+ url: 'http://127.0.0.1:4174',
+ reuseExistingServer: !process.env.CI,
</file context>
| window.clearInterval(interval) | ||
| } | ||
| }, [projectRootPathKey, projectSummary?.branch]) | ||
| }, [projectRootPathKey]) |
There was a problem hiding this comment.
P2: Removing projectSummary?.branch from this effect dependencies delays PR refresh on branch switch, causing stale PR status for up to the 60s poll interval.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/renderer/src/components/common/StatusBar.tsx, line 106:
<comment>Removing `projectSummary?.branch` from this effect dependencies delays PR refresh on branch switch, causing stale PR status for up to the 60s poll interval.</comment>
<file context>
@@ -103,7 +103,7 @@ export function StatusBar(): React.ReactNode {
window.clearInterval(interval)
}
- }, [projectRootPathKey, projectSummary?.branch])
+ }, [projectRootPathKey])
const statusColor =
</file context>
| }, [projectRootPathKey]) | |
| }, [projectRootPathKey, projectSummary?.branch]) |
| const baseChatEvents = Math.floor(expectedChatEvents) | ||
| const fractional = expectedChatEvents - baseChatEvents | ||
| if (fractional <= 0) return baseChatEvents | ||
| const denominator = Math.max(1, Math.round(1 / fractional)) |
There was a problem hiding this comment.
P2: The fractional event distribution logic has significant rate-generation errors. When Math.round(1 / fractional) rounds to 1 (e.g., for expectedChatEvents=0.7), the modulo condition is always true, generating 1 event per tick instead of 0.7 (42.8% over-generation). Use a cumulative-floor approach for mathematically correct distribution:
return Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/playwright/stress-explorer.spec.ts, line 179:
<comment>The fractional event distribution logic has significant rate-generation errors. When `Math.round(1 / fractional)` rounds to 1 (e.g., for expectedChatEvents=0.7), the modulo condition is always true, generating 1 event per tick instead of 0.7 (42.8% over-generation). Use a cumulative-floor approach for mathematically correct distribution:
return Math.floor((tick + agentIndex) * expectedChatEvents) - Math.floor((tick + agentIndex - 1) * expectedChatEvents)
<file context>
@@ -0,0 +1,350 @@
+ const baseChatEvents = Math.floor(expectedChatEvents)
+ const fractional = expectedChatEvents - baseChatEvents
+ if (fractional <= 0) return baseChatEvents
+ const denominator = Math.max(1, Math.round(1 / fractional))
+ return baseChatEvents + ((tick + agentIndex) % denominator === 0 ? 1 : 0)
+ }
</file context>
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
1 similar comment
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
tests/playwright/stress-explorer.spec.ts (2)
235-247:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftMake the scheduler honor the configured per-second rate.
Line 235 rounds the per-tick budget once and then clamps it to at least
1, so runs drift badly as soon astickMsis not an exact 1-second bucket. For example,STRESS_EVENTS_PER_SEC=1withSTRESS_TICK_MS=100injects 10 events/sec per agent. That means the explorer can report thresholds for a load shape different from the one requested. Track cumulative/fractional budget across ticks instead of rounding each tick independently.Also applies to: 311-316
🤖 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 `@tests/playwright/stress-explorer.spec.ts` around lines 235 - 247, The scheduler currently computes logicalEventsPerAgentPerTick with Math.max(1, Math.round(...)) which forces at least one event per tick and re-rounds every tick, causing rate drift; change to compute a per-tick fractional budget (logicalEventsPerAgentPerSecond * (tickMs / 1000)) and track a cumulative accumulator (per-agent or shared) that adds this fractional budget each tick, consume Math.floor(accumulator) events and subtract that integer from the accumulator so fractional remainder carries forward; update chatCountForAgent and the effectiveChatRatio/legacy chat-heavy branches to use the accumulated integer budget when deciding chat vs non-chat events (and apply the same accumulator approach for the logic referenced around lines 311-316) so the configured per-second rate is honored over time.
280-318:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFlush the last partial PTY aggregate window.
Lines 282-318 only emit PTY traffic on exact
defaultPtyAggregateTicksboundaries. A run with 35 ticks and the default aggregate of 10 never flushes ticks 31-35, so the renderer sees less PTY load than configured andptyEventsis undercounted. Emit once more on the final tick and clamp the aggregation start to the first real tick.💡 Localized fix
- const emitPty = effectiveChatRatio < 1 && tick % defaultPtyAggregateTicks === 0 + const emitPty = effectiveChatRatio < 1 + && (tick % defaultPtyAggregateTicks === 0 || tick === ticks) @@ - for (let aggregateTick = tick - defaultPtyAggregateTicks + 1; aggregateTick <= tick; aggregateTick += 1) { + const aggregateStart = Math.max(1, tick - defaultPtyAggregateTicks + 1) + for (let aggregateTick = aggregateStart; aggregateTick <= tick; aggregateTick += 1) { aggregateChatCount += chatCountForAgent(aggregateTick, index) }🤖 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 `@tests/playwright/stress-explorer.spec.ts` around lines 280 - 318, The PTY aggregation currently only emits on exact defaultPtyAggregateTicks boundaries, so partial final windows are dropped; fix by clamping the aggregation start when computing aggregateChatCount (use Math.max(1, tick - defaultPtyAggregateTicks + 1) instead of tick - defaultPtyAggregateTicks + 1) and add a final emit after the main while loop: if effectiveChatRatio < 1 and (tick % defaultPtyAggregateTicks) !== 0 then compute the remaining aggregateChatCount for the final tick window (using the same chatCountForAgent logic and useLegacyChatHeavyCadence branch), update ptyEvents, build the chunk and call mock.injectPtyChunk('mock-project', agentName, chunk) for that final partial window so leftover ticks are flushed.
🤖 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 `@tests/playwright/stress-explorer.spec.ts`:
- Around line 193-207: The heap sampler loop (heapSampler) currently uses a
non-abortable setTimeout(10_000) between sampleHeap() calls which can delay
shutdown; change it so the sleep is abortable or the final sample is triggered
immediately when stopHeapSampling is flipped: either (A) replace the setTimeout
sleep with a Promise that races the timeout against a "stop" resolver (e.g.
create let resolveStop; const stopPromise = new Promise(r => resolveStop = r)
and await Promise.race([timeout, stopPromise]) and call resolveStop() when
setting stopHeapSampling), or (B) keep the loop but, wherever stopHeapSampling
is set to true, call sampleHeap() once (or resolve the stop promise) before
awaiting heapSampler; update references to heapSampler, stopHeapSampling and
sampleHeap accordingly so tests don't wait the full 10s.
---
Outside diff comments:
In `@tests/playwright/stress-explorer.spec.ts`:
- Around line 235-247: The scheduler currently computes
logicalEventsPerAgentPerTick with Math.max(1, Math.round(...)) which forces at
least one event per tick and re-rounds every tick, causing rate drift; change to
compute a per-tick fractional budget (logicalEventsPerAgentPerSecond * (tickMs /
1000)) and track a cumulative accumulator (per-agent or shared) that adds this
fractional budget each tick, consume Math.floor(accumulator) events and subtract
that integer from the accumulator so fractional remainder carries forward;
update chatCountForAgent and the effectiveChatRatio/legacy chat-heavy branches
to use the accumulated integer budget when deciding chat vs non-chat events (and
apply the same accumulator approach for the logic referenced around lines
311-316) so the configured per-second rate is honored over time.
- Around line 280-318: The PTY aggregation currently only emits on exact
defaultPtyAggregateTicks boundaries, so partial final windows are dropped; fix
by clamping the aggregation start when computing aggregateChatCount (use
Math.max(1, tick - defaultPtyAggregateTicks + 1) instead of tick -
defaultPtyAggregateTicks + 1) and add a final emit after the main while loop: if
effectiveChatRatio < 1 and (tick % defaultPtyAggregateTicks) !== 0 then compute
the remaining aggregateChatCount for the final tick window (using the same
chatCountForAgent logic and useLegacyChatHeavyCadence branch), update ptyEvents,
build the chunk and call mock.injectPtyChunk('mock-project', agentName, chunk)
for that final partial window so leftover ticks are flushed.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fcf66611-9772-4d0b-b401-c4179aa47d31
📒 Files selected for processing (2)
tests/playwright/STRESS-EXPLORATION-REPORT.mdtests/playwright/stress-explorer.spec.ts
✅ Files skipped from review due to trivial changes (1)
- tests/playwright/STRESS-EXPLORATION-REPORT.md
| heapSampler = (async () => { | ||
| while (!stopHeapSampling) { | ||
| try { | ||
| await sampleHeap() | ||
| } catch { | ||
| return | ||
| } | ||
| await new Promise((resolve) => setTimeout(resolve, 10_000)) | ||
| } | ||
| try { | ||
| await sampleHeap() | ||
| } catch { | ||
| // Ignore a final sampling race if the page closed after the run. | ||
| } | ||
| })() |
There was a problem hiding this comment.
Stop the heap sampler without waiting out the full 10s sleep.
Lines 193-200 sleep for 10 seconds between samples, and Lines 419-423 / 440-443 await the sampler after flipping stopHeapSampling. If the run finishes just after a sample, the test can sit idle for almost a full extra interval before writing the record, which both slows parameter sweeps and inflates wallClockMs with sampler wait time. Make the sleep abortable, or trigger the final sample directly on shutdown instead of awaiting the existing timeout.
Also applies to: 419-423, 440-443
🤖 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 `@tests/playwright/stress-explorer.spec.ts` around lines 193 - 207, The heap
sampler loop (heapSampler) currently uses a non-abortable setTimeout(10_000)
between sampleHeap() calls which can delay shutdown; change it so the sleep is
abortable or the final sample is triggered immediately when stopHeapSampling is
flipped: either (A) replace the setTimeout sleep with a Promise that races the
timeout against a "stop" resolver (e.g. create let resolveStop; const
stopPromise = new Promise(r => resolveStop = r) and await Promise.race([timeout,
stopPromise]) and call resolveStop() when setting stopHeapSampling), or (B) keep
the loop but, wherever stopHeapSampling is set to true, call sampleHeap() once
(or resolve the stop promise) before awaiting heapSampler; update references to
heapSampler, stopHeapSampling and sampleHeap accordingly so tests don't wait the
full 10s.
54fb4bb to
16cd553
Compare
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
Summary
Key findings so far
Testing
Note: this branch is stacked on origin/claude/web-stress-harness because main does not yet contain the calibrated stress harness baseline.