diff --git a/home/doris-dashboard/bin/generate_dashboard.py b/home/doris-dashboard/bin/generate_dashboard.py
index f215c2b..d8ebc10 100755
--- a/home/doris-dashboard/bin/generate_dashboard.py
+++ b/home/doris-dashboard/bin/generate_dashboard.py
@@ -1534,10 +1534,11 @@ def focus_card(title:str,kicker:str,headline:str,body:str,tone:str='',href:str='
return f"{card_html}"
-def build_focus_cards(local_items:list[dict[str,Any]], todos:list[dict[str,Any]], pulse:list[dict[str,str]], paperless:dict[str,Any], hermes:dict[str,Any])->tuple[str,str]:
+def build_focus_cards(local_items:list[dict[str,Any]], todos:list[dict[str,Any]], pulse:list[dict[str,str]], paperless:dict[str,Any], hermes:dict[str,Any], excluded_story_keys:set[str] | None=None)->tuple[str,str]:
urgent_todos=[item for item in todos if (item.get('state') or '') in {'overdue','today'} and (item.get('source') or '') != 'Dashboard']
warn_items=[item for item in pulse if item.get('state')=='warn']
- local_story=local_items[0] if local_items else None
+ excluded_story_keys=excluded_story_keys or set()
+ local_story=next((item for item in local_items if story_key(item) not in excluded_story_keys), None)
cron_summary=hermes_cron_summary()
session_window=find_usage_window(hermes.get('account_windows',[]), 'session') if hermes.get('ok') else None
session_remaining=session_window.get('remaining_percent') if session_window else None
@@ -1564,6 +1565,12 @@ def build_focus_cards(local_items:list[dict[str,Any]], todos:list[dict[str,Any]]
hermes_headline='Hermes telemetry unavailable'
hermes_body='Could not read local Hermes usage stats right now.'
local_state='attention' if local_story else 'ok'
+ local_headline=str(local_story.get('title') or '') if local_story else ('Local lane already covered' if local_items else 'Local lane quiet')
+ local_body=(
+ f"{local_story.get('source','Local feed')} • {label(local_story.get('category','local'))}"
+ if local_story else
+ ('The strongest local item is already promoted above, so this slot stays out of the way.' if local_items else 'Nothing local survived filtering hard enough to earn the homepage.')
+ )
chores_state='warn' if urgent_todos else 'ok'
paperless_state='warn' if paperless.get('flagged_count',0) else ('ok' if not paperless.get('active_count',0) else 'attention')
homelab_state='warn' if warn_items else 'ok'
@@ -1573,8 +1580,8 @@ def build_focus_cards(local_items:list[dict[str,Any]], todos:list[dict[str,Any]]
focus_card(
'Local watch',
'Useful nearby',
- (local_story.get('title') if local_story else 'Local lane quiet'),
- (f"{local_story.get('source','Local feed')} • {label(local_story.get('category','local'))}" if local_story else 'Nothing local survived filtering hard enough to earn the homepage.'),
+ local_headline,
+ local_body,
f'local {local_state}'.strip(),
href=str(local_story.get('url') or '') if local_story else '' ,
target='_blank' if local_story and local_story.get('url') else ''
@@ -1671,6 +1678,39 @@ def choose_feature_story(preferred_items:list[dict[str,Any]], fallback_items:lis
return None
+def choose_secondary_feature_story(preferred_items:list[dict[str,Any]], fallback_items:list[dict[str,Any]], primary_story:dict[str,Any] | None, prefs:dict[str,Any])->dict[str,Any] | None:
+ primary_key=story_key(primary_story) if primary_story else ''
+ state=load_lead_warrant_state()
+ feedback=load_dashboard_feedback()
+ acknowledged=set(state.get('acknowledged',[]))
+ ignored_sources=set(state.get('ignored_sources',[]))
+ ignored_topics=set(state.get('ignored_topics',[]))
+ candidates=[]
+ fallback=[]
+ seen=set()
+ for pool_name,pool in (('preferred',preferred_items),('fallback',fallback_items)):
+ for item in pool:
+ key=story_key(item)
+ dedupe=key or f"{str(item.get('source') or '').strip().lower()}::{str(item.get('title') or '').strip().lower()}"
+ if dedupe in seen or (primary_key and key == primary_key):
+ continue
+ seen.add(dedupe)
+ fallback.append(item)
+ if good_lead_candidate(item,prefs,feedback,ignored_sources,ignored_topics):
+ candidates.append(item)
+ for item in candidates:
+ if story_key(item) not in acknowledged:
+ return item
+ if candidates:
+ return candidates[0]
+ for item in fallback:
+ if story_key(item) not in acknowledged:
+ return item
+ if fallback:
+ return fallback[0]
+ return None
+
+
def build_feature_story(feature_item:dict[str,Any] | None)->str:
if not feature_item:
return ''
@@ -1680,7 +1720,7 @@ def build_feature_story(feature_item:dict[str,Any] | None)->str:
when=(feature_item.get('published_at','')[:16].replace('T',' '))
category=str(feature_item.get('category') or 'other')
url=str(feature_item.get('url') or '')
- feature_label='Lead local item' if category=='local' else ('Lead homelab item' if category in {'homelab','smart_home','radio'} else 'Lead signal')
+ feature_label='Next local signal' if category=='local' else ('Next homelab signal' if category in {'homelab','smart_home','radio'} else 'Next signal')
why_text=why(feature_item)
return f"""
@@ -1695,7 +1735,7 @@ def build_feature_story(feature_item:dict[str,Any] | None)->str:
Open story
-
Promoted so you get one strong item instead of four equal tiles.
+
Secondary signal so the warrant and the next-click card are not the same story.
@@ -2105,15 +2145,17 @@ def render()->str:
pulse_metrics=''.join(metric_card(x['label'],x['value'],x['state'],x['state']) for x in pulse)
forecast_html=forecast_panel(weather)
hero_summary=build_hero_summary(groups['local'],todos,pulse,paperless,hermes)
- focus_html,focus_state=build_focus_cards(groups['local'],todos,pulse,paperless,hermes)
+ feature_item=choose_feature_story(top_briefing, selected, prefs)
+ secondary_feature_item=choose_secondary_feature_story(top_briefing, selected, feature_item, prefs)
+ excluded_focus_story_keys={key for key in [story_key(feature_item), story_key(secondary_feature_item)] if key}
+ focus_html,focus_state=build_focus_cards(groups['local'],todos,pulse,paperless,hermes,excluded_story_keys=excluded_focus_story_keys)
focus_signature=donetick_focus_signature(todos)
hermes_html=build_hermes_section(hermes)
system_html=build_system_section(system)
hero_tokens=compact_number(hermes.get('total_tokens',0)) if hermes.get('ok') else '—'
snapshot_html=build_operator_snapshot(todos,pulse,paperless,hermes)
- feature_item=choose_feature_story(top_briefing, selected, prefs)
hero_caseboard_html=build_hero_caseboard(feature_item,todos,paperless)
- feature_html=build_feature_story(feature_item)
+ feature_html=build_feature_story(secondary_feature_item)
session_window=find_usage_window(hermes.get('account_windows',[]),'session') if hermes.get('ok') else None
primary_html=''.join([
feature_html,