Files
worldmonitor/todos/050-pending-p2-country-brief-framework-change-no-debounce.md
Elie Habib 110ab402c4 feat(intelligence): analytical framework selector for AI panels (#2380)
* feat(frameworks): add settings section and import modal

- Add Analysis Frameworks group to preferences-content.ts between Intelligence and Media sections
- Per-panel active framework display (read-only, 4 panels)
- Skill library list with built-in badge, Rename and Delete actions for imported frameworks
- Import modal with two tabs: From agentskills.io (fetch + preview) and Paste JSON
- All error cases handled inline: network, domain validation, missing instructions, invalid JSON, duplicate name, instructions too long, rate limit
- Add api/skills/fetch-agentskills.ts edge function (proxy to agentskills.io)
- Add analysis-framework-store.ts (loadFrameworkLibrary, saveImportedFramework, deleteImportedFramework, renameImportedFramework, getActiveFrameworkForPanel)
- Add fw-* CSS classes to main.css matching dark panel aesthetic

* feat(panels): wire analytical framework store into InsightsPanel, CountryDeepDive, DailyMarketBrief, DeductionPanel

- InsightsPanel: append active framework to geoContext in updateFromClient(); subscribe in constructor, unsubscribe in destroy()
- CountryIntelManager: pass framework as query param to fetchCountryIntelBrief(); subscribe to re-open brief on framework change; unsubscribe in destroy()
- DataLoaderManager: add dailyBriefGeneration counter for stale-result guard; pass frameworkAppend to buildDailyMarketBrief(); subscribe to framework changes to force refresh; unsubscribe in destroy()
- daily-market-brief service: add frameworkAppend? field to BuildDailyMarketBriefOptions; append to extendedContext before summarize call
- DeductionPanel: append active framework to geoContext in handleSubmit() before RPC call

* feat(frameworks): add FrameworkSelector UI component

- Create FrameworkSelector component with premium/locked states
- Premium: select dropdown with all framework options, change triggers setActiveFrameworkForPanel
- Locked: disabled select + PRO badge, click calls showGatedCta(FREE_TIER)
- InsightsPanel: adds asterisk note (client-generated analysis hint)
- Wire into InsightsPanel, DailyMarketBriefPanel, DeductionPanel (via this.header)
- Wire into CountryDeepDivePanel header right-side (no Panel base, panel=null)
- Add framework-selector CSS to main.css

* fix(frameworks): make new proto fields optional in generated types

* fix(frameworks): extract firstMsg to satisfy strict null checks in tsconfig.api.json

* fix(docs): add blank lines around lists/headings to pass markdownlint

* fix(frameworks): add required proto string fields to call sites after make generate

* chore(review): add code review todos 041-057 for PR #2380

7 review agents (TypeScript, Security, Architecture, Performance,
Simplicity, Agent-Native, Learnings) identified 17 findings across
5 P1, 8 P2, and 4 P3 categories.
2026-03-27 23:36:44 +04:00

52 lines
2.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: pending
priority: p2
issue_id: "050"
tags: [code-review, performance, analytical-frameworks]
dependencies: []
---
# `country-intel.ts` framework change fires full RPC immediately — no debounce, extra LLM cost
## Problem Statement
`country-intel.ts` subscribes to framework changes for the `'country-brief'` panel and immediately calls `openCountryBriefByCode()` on every change. `openCountryBriefByCode()` initiates a full LLM-backed country brief RPC. There is no debounce. If a user rapidly switches frameworks (common when exploring options), multiple RPC calls fire to the server. The `briefRequestToken` cancels stale renders but the first LLM call runs to completion and bills tokens even though its result is discarded.
## Findings
- **`src/app/country-intel.ts:72-78`** — subscription fires `openCountryBriefByCode()` immediately
- No debounce wrapper on the callback
- Under rapid switching: N framework changes = N LLM API calls = N × token cost
- Flagged by: performance-oracle
## Proposed Solutions
### Option A: 400ms debounce on the subscription callback (Recommended)
```ts
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
this.frameworkUnsubscribe = subscribeFrameworkChange('country-brief', () => {
const page = this.ctx.countryBriefPage;
if (!page?.isVisible()) return;
const code = page.getCode();
const name = page.getName() ?? code;
if (!code || !name) return;
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => void this.openCountryBriefByCode(code, name), 400);
});
```
**Pros:** Coalesces rapid switches into one call, 400ms is imperceptible to deliberate selection | **Effort:** Small | **Risk:** Low
### Option B: Cancel the pending LLM call server-side (not just the render)
Pass an `AbortSignal` to the RPC and cancel it when a new framework change fires.
**Pros:** Saves server compute too | **Cons:** RPC client may not support AbortSignal in current implementation | **Effort:** Medium | **Risk:** Medium
## Technical Details
- File: `src/app/country-intel.ts`
- PR: koala73/worldmonitor#2380
## Acceptance Criteria
- [ ] Rapid framework switching triggers only one RPC call (after debounce settles)
- [ ] Single deliberate selection still triggers the RPC within ~400ms
- [ ] Debounce timer is cleared on panel destroy / unsubscribe
## Work Log
- 2026-03-27: Identified during PR #2380 review by performance-oracle