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

2.4 KiB
Raw Blame History

status, priority, issue_id, tags, dependencies
status priority issue_id tags dependencies
pending p2 050
code-review
performance
analytical-frameworks

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

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

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