10 KiB
Source Diversity via Recent History — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Inject recently-used domains into the LLM search prompt to encourage source diversity across syntheses.
Architecture: New source_diversity_window setting (default 3, 0=disabled). At generation time, load recent syntheses, extract domains from JSONB sections, pass to prompt builder which appends a soft avoidance instruction.
Tech Stack: Rust (sqlx, serde_json, url crate), SolidJS, PostgreSQL
Spec: docs/superpowers/specs/2026-03-23-source-diversity-history-design.md
Task 1: Migration + backend model
Files:
-
Create:
backend/migrations/20260323000013_add_source_diversity_window.sql -
Modify:
backend/src/models/settings.rs -
Modify:
backend/src/db/settings.rs -
Modify:
CLAUDE.md -
Step 1: Create migration
ALTER TABLE settings ADD COLUMN source_diversity_window INTEGER NOT NULL DEFAULT 3;
- Step 2: Add field to all structs in
models/settings.rs
Add pub source_diversity_window: i32 to UserSettings, SettingsResponse, UpdateSettingsRequest (after max_articles_per_source).
Add to From<UserSettings> for SettingsResponse:
source_diversity_window: s.source_diversity_window,
Add validation in UpdateSettingsRequest::validate():
if !(0..=10).contains(&self.source_diversity_window) {
return Err("source_diversity_window must be between 0 and 10".into());
}
Add to impl Default for UserSettings:
source_diversity_window: 3,
- Step 3: Add column to DB queries in
db/settings.rs
Add source_diversity_window: i32 to SettingsRow. Add to TryFrom<SettingsRow>:
source_diversity_window: row.source_diversity_window,
Add to both SQL queries (get_or_create_default and upsert): INSERT column list, VALUES placeholder, RETURNING clause, .bind() call, and ON CONFLICT SET (upsert only). The new column goes after max_articles_per_source.
-
Step 4: Update CLAUDE.md migration count to 13
-
Step 5: Add validation tests in
models/settings.rs
Add source_diversity_window: 3 to the valid_request() test helper. Then add tests:
#[test]
fn test_source_diversity_window_zero_is_valid() {
let mut req = valid_request();
req.source_diversity_window = 0;
assert!(req.validate().is_ok());
}
#[test]
fn test_source_diversity_window_ten_is_valid() {
let mut req = valid_request();
req.source_diversity_window = 10;
assert!(req.validate().is_ok());
}
#[test]
fn test_source_diversity_window_below_range() {
let mut req = valid_request();
req.source_diversity_window = -1;
assert!(req.validate().is_err());
}
#[test]
fn test_source_diversity_window_above_range() {
let mut req = valid_request();
req.source_diversity_window = 11;
assert!(req.validate().is_err());
}
- Step 6: Run tests
Run: cd backend && cargo test --lib
Expected: all tests pass
- Step 7: Commit
git add backend/migrations/20260323000013_add_source_diversity_window.sql backend/src/models/settings.rs backend/src/db/settings.rs CLAUDE.md
git commit -m "feat: add source_diversity_window setting (migration + model + DB)"
Task 2: Prompt modification + tests
Files:
-
Modify:
backend/src/services/prompts.rs -
Step 1: Add
recent_domainsparameter tobuild_search_prompt
Change signature from:
pub fn build_search_prompt(
settings: &UserSettings,
sources: &[Source],
current_date: &str,
) -> (String, String) {
To:
pub fn build_search_prompt(
settings: &UserSettings,
sources: &[Source],
current_date: &str,
recent_domains: &[String],
) -> (String, String) {
- Step 2: Append avoidance instruction when domains are non-empty
At the end of the user_prompt format string (after the JSON instruction line, before the closing "), add a conditional block. After the format!() call that builds user_prompt, append:
let user_prompt = if recent_domains.is_empty() {
user_prompt
} else {
let domains_list = recent_domains.join(", ");
format!(
"{}\n\nEvite si possible les sources deja utilisees dans les syntheses precedentes : {}.",
user_prompt, domains_list
)
};
- Step 3: Update test fixture
In the test_settings() function (~line 137), add:
source_diversity_window: 3,
- Step 4: Update existing test calls
All existing tests that call build_search_prompt need the 4th argument. Add &[] (empty slice) to each existing call. Search for build_search_prompt( in the test module and add , &[] before the closing ).
- Step 5: Add new tests
#[test]
fn search_prompt_includes_recent_domains_avoidance() {
let settings = test_settings();
let sources = vec![];
let date = "lundi 17 mars 2026";
let domains = vec!["techcrunch.com".to_string(), "theverge.com".to_string()];
let (_, user_prompt) = build_search_prompt(&settings, &sources, date, &domains);
assert!(user_prompt.contains("Evite si possible"));
assert!(user_prompt.contains("techcrunch.com"));
assert!(user_prompt.contains("theverge.com"));
}
#[test]
fn search_prompt_no_avoidance_when_domains_empty() {
let settings = test_settings();
let sources = vec![];
let date = "lundi 17 mars 2026";
let (_, user_prompt) = build_search_prompt(&settings, &sources, date, &[]);
assert!(!user_prompt.contains("Evite si possible"));
}
- Step 6: Run tests
Run: cd backend && cargo test --lib
Expected: all tests pass
- Step 7: Commit
git add backend/src/services/prompts.rs
git commit -m "feat: build_search_prompt accepts recent_domains for source diversity"
Task 3: Pipeline integration — extract domains + wire prompt
Files:
-
Modify:
backend/src/services/synthesis.rs -
Step 1: Add domain extraction from recent syntheses
Before the build_search_prompt call (~line 303), add a new step that loads recent syntheses and extracts domains. Insert between the rate limit check (step 5) and the search pass (step 6):
// Step 5b: Load recently-used domains for source diversity
let recent_domains = if settings.source_diversity_window > 0 {
let recent = db::syntheses::list_for_user(
&state.pool,
user_id,
settings.source_diversity_window as i64,
0,
)
.await
.unwrap_or_default();
let mut domains: Vec<String> = recent
.iter()
.filter_map(|s| {
serde_json::from_value::<Vec<crate::models::synthesis::NewsSection>>(
s.sections.clone(),
)
.ok()
})
.flat_map(|sections| {
sections
.into_iter()
.flat_map(|sec| sec.items.into_iter())
.filter_map(|item| extract_domain(&item.url))
})
.collect();
domains.sort();
domains.dedup();
domains
} else {
Vec::new()
};
- Step 2: Update the
build_search_promptcall
Change line ~304 from:
let (system_prompt, user_prompt) =
prompts::build_search_prompt(&settings, &sources, ¤t_date);
To:
let (system_prompt, user_prompt) =
prompts::build_search_prompt(&settings, &sources, ¤t_date, &recent_domains);
- Step 3: Run tests
Run: cd backend && cargo test --lib
Expected: all tests pass
- Step 4: Commit
git add backend/src/services/synthesis.rs
git commit -m "feat: extract recent domains and pass to search prompt for diversity"
Task 4: Frontend setting
Files:
-
Modify:
frontend/src/types.ts -
Modify:
frontend/src/i18n/fr.ts -
Modify:
frontend/src/pages/Settings.tsx -
Step 1: Add field to frontend types
In frontend/src/types.ts, add to UserSettings interface (after max_articles_per_source):
source_diversity_window: number;
Add to DEFAULT_SETTINGS:
source_diversity_window: 3,
- Step 2: Add i18n label
In frontend/src/i18n/fr.ts, add after settings.maxArticlesPerSource:
'settings.diversityWindow': 'Syntheses a examiner pour diversite',
- Step 3: Add number input to Settings page
In frontend/src/pages/Settings.tsx, inside the generation settings grid (after maxArticlesPerSource), add:
<div>
<label
for="diversityWindow"
class="block text-sm font-medium text-gray-700"
>
{t('settings.diversityWindow')}
</label>
<div class="mt-1">
<input
type="number"
id="diversityWindow"
min="0"
max="10"
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md py-2 px-3 border"
value={settings().source_diversity_window}
onInput={(e) =>
setSettings((prev) => ({
...prev,
source_diversity_window:
parseInt(e.currentTarget.value) || 3,
}))
}
/>
</div>
</div>
- Step 4: Run frontend tests
Run: cd frontend && npx tsc --noEmit && npx vitest run
Expected: type check passes, all tests pass
- Step 5: Commit
git add frontend/src/types.ts frontend/src/i18n/fr.ts frontend/src/pages/Settings.tsx
git commit -m "feat: add source_diversity_window setting to frontend"