Commit 9688459
authored
🤖 Fix CostsTab re-render storm & consumer calculation issues (#282)
## Summary
Complete fix for consumer token breakdown feature with massive
performance improvements. Addresses console spam, missing data on
workspace switches, UI flash issues, and unnecessary re-renders.
## Issues Fixed
1. **Console spam** - "Cancelled by newer request" errors flooded
console during streaming
2. **Consumer breakdown never loads** - Showed "No consumer data
available" on workspace switch
3. **UI flash** - Brief flash of empty state before "Calculating..."
appeared
4. **Text alignment** - Consumer breakdown empty state wasn't aligned
correctly
5. **Excessive re-renders** - CostsTab re-rendered 50+ times during
streaming (now: 1 time)
## Architecture Improvements
### Created WorkspaceConsumerManager (182 → 208 lines)
- **Single responsibility**: consumer tokenization calculations
- Handles: debouncing (150ms), caching, lazy triggers, Web Worker,
cleanup
- Clean API: `getState()`, `scheduleCalculation()`, `removeWorkspace()`,
`dispose()`
- Separated `scheduledCalcs` (debounce window) from `pendingCalcs`
(executing)
### Created ConsumerBreakdown Component (186 lines)
- Extracted consumer breakdown UI from CostsTab
- Handles all three states: calculating, empty, data display
- Fixed text alignment issues
- Memoized to prevent unnecessary re-renders
### Simplified WorkspaceStore (-70 lines net)
- Removed calculation implementation details
- Delegates to WorkspaceConsumerManager
- Clear orchestration layer (decides when to calculate)
### Optimized CostsTab & ChatMetaSidebar
- Memoized all three components (CostsTab, ConsumerBreakdown,
ChatMetaSidebar)
- Prevents re-renders when parent (AIView) re-renders during streaming
- Still re-renders when data actually changes
## Key Technical Changes
### 1. Silent Cancellations
```typescript
catch (error) {
if (error instanceof Error && error.message === "Cancelled by newer request") {
return; // Don't cache, don't log - let lazy trigger retry
}
// Real errors still logged
}
```
### 2. Lazy Loading on Every Access
Moved lazy trigger **outside** MapStore.get() so it runs on every
access:
```typescript
getWorkspaceConsumers(workspaceId) {
const cached = this.consumerManager.getCachedState(workspaceId);
const isPending = this.consumerManager.isPending(workspaceId);
if (!cached && !isPending && isCaughtUp) {
this.consumerManager.scheduleCalculation(workspaceId, aggregator);
}
return this.consumersStore.get(workspaceId, () => {
return this.consumerManager.getStateSync(workspaceId);
});
}
```
### 3. Immediate Scheduling State
Mark as "calculating" immediately when scheduling (not when timer
fires):
```typescript
scheduleCalculation(workspaceId, aggregator) {
this.scheduledCalcs.add(workspaceId); // Immediate
this.onCalculationComplete(workspaceId); // Trigger UI update
setTimeout(() => {
this.scheduledCalcs.delete(workspaceId);
this.executeCalculation(workspaceId, aggregator);
}, 150);
}
```
### 4. React.memo Optimization
```typescript
const CostsTabComponent: React.FC<CostsTabProps> = ({ workspaceId }) => {
// ... component logic
};
export const CostsTab = React.memo(CostsTabComponent);
```
## Performance Gains
### Before
- **Console**: Flooded with cancellation errors during streaming
- **Workspace switch**: "No consumer data available" forever
- **UI flash**: 150ms empty state before "Calculating..."
- **Re-renders**: 50+ per streaming message (CostsTab × 50,
ConsumerBreakdown × 50, ChatMetaSidebar × 50)
### After
- **Console**: Clean ✅
- **Workspace switch**: Consumer breakdown loads automatically ✅
- **UI flash**: Eliminated - instant "Calculating..." state ✅
- **Re-renders**: ~98% reduction - 0 during streaming, 1 when data
changes ✅
## Dual-Cache Architecture
**WorkspaceConsumerManager.cache**:
- Source of truth for calculated consumer data
- Manages calculation lifecycle
**WorkspaceStore.consumersStore (MapStore)**:
- Handles subscription management (components subscribe to changes)
- Delegates actual state to manager
## Files Changed
### Created
- `src/stores/WorkspaceConsumerManager.ts` (208 lines)
- `src/components/ChatMetaSidebar/ConsumerBreakdown.tsx` (189 lines)
### Modified
- `src/stores/WorkspaceStore.ts` (-70 lines net)
- `src/components/ChatMetaSidebar/CostsTab.tsx` (simplified, memoized)
- `src/components/ChatMetaSidebar.tsx` (memoized)
**Net**: +475 lines (well-organized, well-documented code)
## Testing
- ✅ Typecheck passes
- ✅ Build succeeds
- ✅ No console spam during streaming
- ✅ Consumer breakdown loads on workspace switch
- ✅ No UI flash when switching workspaces
- ✅ Sidebar doesn't re-render during streaming
- ✅ Data updates correctly when calculations complete
## Commits
1. `1c08ec3b` - Fix consumer calculation spam and lazy loading
2. `c26ab425` - Extract consumer calculation logic and fix lazy loading
3. `6acd98d7` - Fix consumer calculation cancellations and lazy loading
4. `80809c2b` - Eliminate flash of 'No consumer data available'
5. `45f40efb` - Memoize CostsTab, ConsumerBreakdown, and ChatMetaSidebar
6. `d6b701e2` - Add missing React.memo export for ChatMetaSidebar
---
_Generated with `cmux`_1 parent f189e9a commit 9688459
File tree
11 files changed
+1018
-458
lines changed- src
- components
- ChatMetaSidebar
- contexts
- stores
- utils/tokens
11 files changed
+1018
-458
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
17 | 16 | | |
18 | 17 | | |
19 | 18 | | |
| |||
379 | 378 | | |
380 | 379 | | |
381 | 380 | | |
382 | | - | |
383 | | - | |
| 381 | + | |
384 | 382 | | |
385 | 383 | | |
386 | 384 | | |
| |||
426 | 424 | | |
427 | 425 | | |
428 | 426 | | |
429 | | - | |
430 | | - | |
431 | | - | |
432 | | - | |
433 | | - | |
434 | | - | |
435 | | - | |
436 | | - | |
437 | | - | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
438 | 536 | | |
439 | 537 | | |
440 | | - | |
441 | | - | |
442 | | - | |
443 | | - | |
444 | | - | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
452 | | - | |
453 | | - | |
454 | | - | |
455 | | - | |
456 | | - | |
457 | | - | |
458 | | - | |
459 | | - | |
460 | | - | |
461 | | - | |
462 | | - | |
463 | | - | |
464 | | - | |
465 | | - | |
466 | | - | |
467 | | - | |
468 | | - | |
469 | | - | |
470 | | - | |
471 | | - | |
472 | | - | |
473 | | - | |
474 | | - | |
475 | | - | |
476 | | - | |
477 | | - | |
478 | | - | |
479 | | - | |
480 | | - | |
481 | | - | |
482 | | - | |
483 | | - | |
484 | | - | |
485 | | - | |
486 | | - | |
487 | | - | |
488 | | - | |
489 | | - | |
490 | | - | |
491 | | - | |
492 | | - | |
493 | | - | |
494 | | - | |
495 | | - | |
496 | | - | |
497 | | - | |
498 | | - | |
499 | | - | |
500 | | - | |
501 | | - | |
502 | | - | |
503 | | - | |
504 | | - | |
505 | | - | |
506 | | - | |
507 | | - | |
508 | | - | |
509 | | - | |
510 | | - | |
511 | | - | |
512 | | - | |
513 | | - | |
514 | | - | |
515 | | - | |
516 | | - | |
517 | | - | |
518 | | - | |
519 | | - | |
520 | | - | |
521 | | - | |
522 | | - | |
523 | | - | |
524 | | - | |
525 | | - | |
526 | | - | |
527 | | - | |
528 | | - | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
533 | | - | |
534 | | - | |
535 | | - | |
536 | | - | |
537 | | - | |
538 | | - | |
539 | | - | |
540 | | - | |
541 | | - | |
542 | | - | |
543 | | - | |
544 | | - | |
545 | | - | |
546 | | - | |
547 | | - | |
548 | | - | |
549 | 538 | | |
550 | | - | |
551 | | - | |
552 | | - | |
553 | | - | |
554 | | - | |
555 | | - | |
556 | | - | |
557 | | - | |
558 | | - | |
559 | | - | |
560 | | - | |
561 | | - | |
562 | | - | |
563 | | - | |
564 | | - | |
565 | | - | |
566 | | - | |
567 | | - | |
568 | | - | |
569 | | - | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
570 | 564 | | |
571 | 565 | | |
572 | 566 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
90 | | - | |
| 90 | + | |
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
| |||
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
106 | | - | |
| 106 | + | |
107 | 107 | | |
108 | 108 | | |
109 | 109 | | |
110 | | - | |
111 | | - | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
112 | 114 | | |
113 | | - | |
| 115 | + | |
114 | 116 | | |
115 | 117 | | |
116 | 118 | | |
| |||
168 | 170 | | |
169 | 171 | | |
170 | 172 | | |
171 | | - | |
| 173 | + | |
172 | 174 | | |
173 | 175 | | |
174 | 176 | | |
| |||
184 | 186 | | |
185 | 187 | | |
186 | 188 | | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
0 commit comments