From 88ea84b89a1e6b1a1ad4b818cfd1a6f896ec1d3c Mon Sep 17 00:00:00 2001 From: Fizzlepoof Date: Thu, 14 May 2026 19:35:00 +0000 Subject: [PATCH] fix(dashboard): scope thumbs-up to a story and restore refreshes --- .../doris-dashboard/bin/generate_dashboard.py | 115 ++++++++++++++---- home/doris-dashboard/public/style.css | 4 +- 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/home/doris-dashboard/bin/generate_dashboard.py b/home/doris-dashboard/bin/generate_dashboard.py index ff784de..8110ddc 100755 --- a/home/doris-dashboard/bin/generate_dashboard.py +++ b/home/doris-dashboard/bin/generate_dashboard.py @@ -277,7 +277,12 @@ def card(i:dict[str,Any], compact:bool=False)->str: topic=(i.get('category') or 'other').strip() or 'other' source=(i.get('source') or '').strip() domain=(urlparse(i.get('url') or '').netloc or '').lower().removeprefix('www.') - feedback_html='' if not compact else f"
" + feedback_html='' if not compact else ( + f"
" + f"" + f"" + f"
" + ) return f"""
{esc(label(i.get('category','')))}{esc(i.get('source'))}{esc(when)} UTC

{esc(i.get('title'))}

@@ -335,23 +340,34 @@ def paperless_review_section()->str: continue (acknowledged if status=='acknowledged' else active).append(item) def render_card(item:dict[str,Any], acknowledged_card:bool=False)->str: + triage=item.get('triage',{}) or {} + current=item.get('current',{}) or {} + urgency=triage.get('urgency','none') + summary=strip_markup(triage.get('summary','')) + reason=strip_markup(triage.get('reason','')) + meta_parts=[x for x in [triage.get('document_type',''), current.get('correspondent','')] if x] + meta=' โ€ข '.join(str(x) for x in meta_parts) status_class=' acknowledged' if acknowledged_card else '' - return f"""
-
{esc(item.get('triage',{}).get('urgency','none').upper())}{esc(item.get('original_filename') or 'Document #'+str(item.get('id','')))}
-

{esc(item.get('title') or item.get('triage',{}).get('suggested_title','Untitled'))}

-

{esc(item.get('triage',{}).get('summary',''))}

-

Type: {esc(item.get('triage',{}).get('document_type',''))} | Correspondent: {esc(item.get('current',{}).get('correspondent',''))} | {esc(item.get('triage',{}).get('reason',''))}

-
{f"View in Paperless" if item.get('archive_url') else ''}
+ return f"""
+
{esc(str(urgency).upper())}{esc(item.get('original_filename') or 'Document #'+str(item.get('id','')))}
{f"View in Paperless" if item.get('archive_url') else ''}
+

{esc(item.get('title') or triage.get('suggested_title','Untitled'))}

+ {f"

{esc(summary)}

" if summary else ''} + {f"

{esc(meta)}

" if meta else ''} + {f"

{esc(reason)}

" if reason else ''}
""" - active_cards=''.join(render_card(item,False) for item in active[:8]) + visible_active=active[:3] + overflow_active=active[3:] + active_cards=''.join(render_card(item,False) for item in visible_active) acked_cards=''.join(render_card(item,True) for item in acknowledged[:8]) count=len(active) ack_count=len(acknowledged) body=f"
{active_cards or '

No active documents waiting.

'}
" + if overflow_active: + body += f"
Show {esc(str(len(overflow_active)))} more active items
{''.join(render_card(item,False) for item in overflow_active[:8])}
" if ack_count: body += f"
Acknowledged {esc(str(ack_count))}
{acked_cards}
" - if count+ack_count>8: - body += f"

Showing {esc(str(min(count,8)))} active and {esc(str(min(ack_count,8)))} acknowledged items from the local queue snapshot.

" + if count+ack_count>3: + body += f"

Showing {esc(str(min(count,3)))} active up front and keeping the rest tucked away to cut page bloat.

" return collapsible_section(f'๐Ÿ“„ Paperless Review ({count} active)',f'{ack_count} acknowledged ยท review queue written by n8n paperless-intake-triage workflow.',body,'paperless-review',open_by_default=count>0,extra_class='panel') def list_panel(title:str,items:list[dict[str,Any]],empty:str)->str: @@ -374,34 +390,66 @@ def donetick_panel(items:list[dict[str,Any]],empty:str)->str: rows=[] for x in items[:6]: chips=''.join(f"{esc(c.get('name',''))}" for c in (x.get('chips') or [])) - subtitle=' โ€ข '.join([p for p in [x.get('subtitle') or '', x.get('assignee') or ''] if p]) + subtitle_text=(x.get('subtitle') or '').strip() + chip_names={str(c.get('name','')).strip().lower() for c in (x.get('chips') or []) if c.get('name')} + subtitle_parts=[] + for part in [p.strip() for p in subtitle_text.split('โ€ข') if p.strip()]: + if part.lower() in chip_names: + continue + subtitle_parts.append(part) + subtitle=' โ€ข '.join(subtitle_parts) state=esc(x.get('state') or 'upcoming') due_style='' if state=='overdue': due_style=' style="color:#ff6b6b;"' elif x.get('priority') in PRIORITY_COLORS: due_style=f' style="color:{esc(PRIORITY_COLORS[x.get("priority")])};"' - rows.append(f"
{esc(x.get('due_title') or x.get('time') or 'No due date')}
{f'{esc(x.get("assignee") or "")}' if x.get('assignee') and x.get('assignee')!='Unassigned' else ''}
{esc(x.get('title') or 'Untitled task')}
{f'
{chips}
' if chips else ''}{f'
{esc(x.get("subtitle") or "")}
' if x.get('subtitle') else ''}
") + rows.append(f"
{esc(x.get('due_title') or x.get('time') or 'No due date')}
{f'{esc(x.get("assignee") or "")}' if x.get('assignee') and x.get('assignee')!='Unassigned' else ''}
{esc(x.get('title') or 'Untitled task')}
{f'
{chips}
' if chips else ''}{f'
{esc(subtitle)}
' if subtitle else ''}
") return f"

Donetick

{''.join(rows)}
" def feedback_bootstrap()->str: return """""" @@ -512,16 +575,16 @@ 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) return f"""Doris Dashboard
-

PC Doris Thatcher

Doris Dashboard

Morning briefing, sky watch, homelab pulse, and local signal โ€” refreshed on NOMAD.

Hidden topics: 0
{esc(now.strftime('%a %b %-d, %-I:%M %p'))}Last generated Central
{len(candidates)}Candidates
{len(set(i.get('source','') for i in candidates))}Sources
-{collapsible_section('At a glance','Weather and sky cards.',f"
{weather_metrics}{moon_metrics}
",'at-a-glance',open_by_default=True)} +

PC Doris Thatcher

Doris Dashboard

Morning briefing, sky watch, homelab pulse, and local signal โ€” refreshed on NOMAD.

Hidden topics: 0
{esc(now.strftime('%a %b %-d, %-I:%M %p'))}Last generated Central ยท auto-refreshes at :05 / :35
{len(candidates)}Candidates
{len(set(i.get('source','') for i in candidates))}Sources
+{collapsible_section('At a glance','Weather and sky cards.',f"
{weather_metrics}{moon_metrics}
",'at-a-glance',open_by_default=False)} {collapsible_section('Today','Calendar, Donetick, and notes.',f"
{list_panel('Today',calendar,'Calendar integration not wired yet.')}{donetick_panel(todos,'No open Donetick tasks found; JSON placeholder active.')}{list_panel('Notes',notes,'No notes.')}
",'today-overview',open_by_default=True)} {forecast_html} {paperless_review_section()} {collapsible_section('Homelab pulse','Safe local checks on NOMAD only.',f"
{pulse_metrics}
",'homelab-pulse',open_by_default=False)} {section('Top briefing','Compact scan of what is worth your time.',top_briefing,'Nothing worth bothering you with.',compact=True)} -{section('Sky / Space','SpaceX, NASA, planetary science, and astronomy.',groups['space'][:6],'No sky signal right now.',compact=True)} +{section('Sky / Space','SpaceX, NASA, planetary science, and astronomy.',groups['space'][:5],'No sky signal right now.',compact=True)} {section('Homelab / Smart Home / Radio','Infrastructure, self-hosting, smart home, and ham radio.',groups['homelab'][:4],'No homelab signal right now.',compact=True)} -{section('Local','Clarksville / Middle Tennessee items that seem useful.',groups['local'][:4],'No local signal right now.',compact=True)} +{section('Local','Clarksville / Middle Tennessee items that seem useful.',groups['local'][:3],'No local signal right now.',compact=True)} {section('Other signals','Everything else that survived filtering.',groups['signals'][:3],'No extra signals.',compact=True)}
{feedback_bootstrap()}""" diff --git a/home/doris-dashboard/public/style.css b/home/doris-dashboard/public/style.css index 5018ee9..d1cc4d9 100644 --- a/home/doris-dashboard/public/style.css +++ b/home/doris-dashboard/public/style.css @@ -2,10 +2,12 @@ /* Compact article layout: keep the number of stories, reduce scroll debt. */ .news-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.compact-card{background:rgba(21,27,39,.9);border:1px solid var(--line);border-left:5px solid var(--accent);border-radius:16px;padding:10px 12px;box-shadow:0 6px 18px rgba(0,0,0,.14)}.compact-card.astronomy{border-left-color:var(--space)}.compact-card.homelab,.compact-card.smart_home,.compact-card.radio{border-left-color:var(--home)}.compact-card.local{border-left-color:var(--local)}.compact-card.security{border-left-color:var(--warn)}.compact-card .meta{margin-bottom:4px}.compact-card h3{font-size:.98rem;line-height:1.18;margin:0 0 3px}.compact-card p{margin:0;color:#c8d5ea;font-size:.88rem;line-height:1.25;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.compact-card .meta span{padding:2px 7px}.compact-card:nth-child(n+7){display:none}section header{display:flex;justify-content:space-between;gap:12px;align-items:end}section header p{margin:0}.feedback{display:flex;gap:6px;justify-content:flex-end;margin-top:8px}.feedback-btn{background:transparent;border:1px solid var(--line);border-radius:999px;color:var(--text);padding:2px 8px;font:inherit;cursor:pointer}.feedback-btn:hover{border-color:var(--accent);transform:translateY(-1px)}.topic-liked{box-shadow:0 0 0 1px rgba(94,231,135,.45),0 6px 18px rgba(0,0,0,.14)}@media(max-width:650px){.compact-card p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}section header{display:block}} +.feedback-btn[aria-pressed='true']{font-weight:700}.feedback-btn.active-up{border-color:rgba(94,231,135,.65);background:rgba(94,231,135,.12);color:#c8ffd9}.feedback-btn.active-down{border-color:rgba(255,107,107,.6);background:rgba(255,107,107,.12);color:#ffd9d9}.dashboard-toast{position:fixed;right:16px;bottom:16px;z-index:1000;max-width:min(420px,calc(100vw - 24px));background:rgba(12,17,27,.96);border:1px solid var(--line);border-left:4px solid var(--accent);border-radius:16px;padding:12px 14px;color:var(--text);box-shadow:0 14px 34px rgba(0,0,0,.35)}.dashboard-toast[hidden]{display:none}.toast-action{margin-left:10px;background:transparent;border:0;color:var(--accent);font:inherit;font-weight:700;cursor:pointer;padding:0} .muted{color:var(--muted);font-size:.86rem;margin:0 .25rem}.panel-item{background:rgba(32,41,57,.55);border:1px solid var(--line);border-radius:14px;padding:10px 12px}.panel-item-top{display:flex;justify-content:space-between;gap:10px;align-items:baseline}.panel-item-title{margin-top:2px;font-weight:600;color:#dbe7f7}.panel-item-body{margin-top:4px;color:#c8d5ea;font-size:.9rem;line-height:1.25;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.panel .empty{padding:12px 0}.donetick-panel{padding:12px 14px}.task-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.task-card{background:rgba(32,41,57,.58);border:1px solid var(--line);border-radius:16px;padding:10px 12px;min-width:0}.task-top{display:flex;justify-content:space-between;gap:8px;align-items:center;margin-bottom:4px}.task-due{font-size:.8rem;font-weight:800;letter-spacing:.02em;text-transform:none;color:#dbe7f7}.task-assignee{font-size:.72rem;padding:2px 8px;border-radius:999px;background:var(--panel2);color:var(--muted)}.task-name{font-size:1rem;font-weight:700;line-height:1.08;color:var(--text);margin-bottom:6px}.task-subtitle{font-size:.78rem;line-height:1.16;color:var(--muted)}.task-chips{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:5px}.task-chip{display:inline-block;padding:3px 8px;border-radius:999px;color:#fff;font-size:.72rem;font-weight:700;line-height:1.05}.task-card.overdue{border-color:rgba(255,107,107,.55)}.task-card.today{border-color:rgba(255,209,102,.5)}.task-card.tomorrow{border-color:rgba(142,202,230,.45)}.task-card.nodue{opacity:.92} +.donetick-panel .task-grid{gap:8px}.task-card{padding:9px 10px;border-radius:14px}.task-top{gap:6px;margin-bottom:3px}.task-state-dot{width:9px;height:9px;border-radius:999px;background:var(--muted);flex:0 0 auto;box-shadow:0 0 0 2px rgba(255,255,255,.03)}.task-card.overdue .task-state-dot{background:var(--warn)}.task-card.today .task-state-dot{background:var(--local)}.task-card.tomorrow .task-state-dot,.task-card.upcoming .task-state-dot{background:#8ecae6}.task-card.nodue .task-state-dot{background:#6c7a90}.task-due{font-size:.76rem;line-height:1.1}.task-name{font-size:.95rem;line-height:1.15;margin-bottom:4px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.task-subtitle{font-size:.74rem;line-height:1.1;opacity:.9;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.task-chips{gap:4px;margin-bottom:4px}.task-chip{padding:2px 7px;font-size:.68rem} /* Forecast */ .forecast-panel{padding:14px 16px}.forecast-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.forecast-card{background:rgba(32,41,57,.65);border:1px solid var(--line);border-radius:16px;padding:12px 14px;display:flex;flex-direction:column;gap:6px}.forecast-day{color:var(--accent);font-size:.82rem;text-transform:uppercase;letter-spacing:.08em}.forecast-card strong{font-size:1rem}.forecast-card em{font-style:normal;color:var(--muted);font-size:.88rem} /* Paperless review queue */ -.badge{display:inline-block;margin-left:.35rem;padding:2px 8px;border-radius:999px;background:var(--panel2);color:var(--accent);font-size:.85rem}.paperless-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.paperless-card{background:rgba(21,27,39,.9);border:1px solid var(--line);border-left:5px solid var(--accent);border-radius:16px;padding:12px 14px}.paperless-card.warn{border-left-color:var(--warn)}.paperless-card.info{border-left-color:var(--local)}.paperless-card.ok{border-left-color:var(--ok)}.paperless-card.acknowledged{opacity:.7}.paperless-card h4{margin:.4rem 0;font-size:1rem}.paperless-card .summary{margin:.35rem 0;color:#d7e3f5}.paperless-card .link{display:inline-block;margin-top:.25rem;color:var(--accent);text-decoration:none}.paperless-card .link:hover{text-decoration:underline}.paperless-actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:10px}.paperless-btn{background:var(--panel2);border:1px solid var(--line);border-radius:999px;color:var(--text);padding:6px 10px;font:inherit;cursor:pointer}.paperless-btn.danger{border-color:rgba(255,107,107,.4);color:#ffd9d9}.paperless-btn:hover{border-color:var(--accent)}.paperless-acked{margin-top:12px;border-top:1px solid var(--line);padding-top:12px}.paperless-acked summary{cursor:pointer;color:var(--muted)}.urgency{font-weight:700}.urgency.warn{color:var(--warn)}.urgency.info{color:var(--local)}.urgency.ok{color:var(--ok)}@media(max-width:650px){.paperless-grid{grid-template-columns:1fr}} +.badge{display:inline-block;margin-left:.35rem;padding:2px 8px;border-radius:999px;background:var(--panel2);color:var(--accent);font-size:.85rem}.paperless-grid{display:grid;grid-template-columns:1fr;gap:10px}.paperless-card{background:rgba(21,27,39,.9);border:1px solid var(--line);border-left:5px solid var(--accent);border-radius:16px;padding:10px 12px}.paperless-card.warn{border-left-color:var(--warn)}.paperless-card.info{border-left-color:var(--local)}.paperless-card.ok{border-left-color:var(--ok)}.paperless-card.acknowledged{opacity:.7}.paperless-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.paperless-card h4{margin:.35rem 0;font-size:.96rem;line-height:1.2}.paperless-card .summary{margin:.2rem 0;color:#d7e3f5;font-size:.9rem;line-height:1.25;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.paperless-meta,.paperless-reason{margin:.2rem 0;color:var(--muted);font-size:.8rem;line-height:1.2}.paperless-card .link{display:inline-block;color:var(--accent);text-decoration:none}.paperless-card .link:hover{text-decoration:underline}.paperless-actions{display:flex;gap:6px;flex-wrap:wrap;align-items:center;justify-content:flex-end}.paperless-btn{background:var(--panel2);border:1px solid var(--line);border-radius:999px;color:var(--text);padding:5px 9px;font:inherit;cursor:pointer}.paperless-btn.danger{border-color:rgba(255,107,107,.4);color:#ffd9d9}.paperless-btn:hover{border-color:var(--accent)}.paperless-acked,.paperless-more{margin-top:12px;border-top:1px solid var(--line);padding-top:12px}.paperless-acked summary,.paperless-more summary{cursor:pointer;color:var(--muted)}.urgency{font-weight:700}.urgency.warn{color:var(--warn)}.urgency.info{color:var(--local)}.urgency.ok{color:var(--ok)}@media(max-width:720px){.paperless-head{display:block}.paperless-actions{justify-content:flex-start;margin-top:8px}}