my prefect server setup prefect-metrics.waow.tech
python orchestration
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add visual hierarchy to briefing cards

accent colors, icons, and priority levels let the curator agent
control presentation. high-priority sections span full width,
highlighted items get accent bars, and each card gets a colored
left border + tint based on mood.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz 6a636106 1e96c804

+200 -8
+27
flows/curate.py
··· 23 23 24 24 each item note should be ~10 words of useful context. 25 25 the headline should be a single sentence summary. 26 + 27 + ## visual styling 28 + 29 + each section has accent, icon, and priority fields to control presentation. 30 + 31 + accent colors — pick the one that matches the section's mood: 32 + - red: urgent, blocked, overdue items 33 + - amber: warnings, going stale, needs attention soon 34 + - emerald: positive signals, quick wins, ready to merge 35 + - sky: informational, watching, low-urgency tracking 36 + - violet: features, enhancements, new ideas 37 + 38 + icons — pick one per section: 39 + - alert: urgent/blocked sections 40 + - clock: stale or time-sensitive sections 41 + - check: ready/completed/quick-win sections 42 + - eye: watching/tracking sections 43 + - bolt: quick wins, fast actions 44 + - bookmark: reference, docs, informational 45 + 46 + priority — controls layout size: 47 + - high: most important section, spans full width (use at most 1 per briefing) 48 + - normal: default column layout 49 + - low: compact, de-emphasized 50 + 51 + set highlight: true on at most ~3 items total across all sections. 52 + use it for the single most actionable or urgent item in a section. 26 53 """ 27 54 28 55 def make_agent(api_key: str) -> PrefectAgent[Briefing]:
+29
packages/mps/src/mps/briefing.py
··· 1 + from enum import StrEnum 2 + 1 3 from pydantic import BaseModel 2 4 3 5 6 + class SectionAccent(StrEnum): 7 + red = "red" # urgent, blocked, overdue 8 + amber = "amber" # warnings, going stale 9 + emerald = "emerald" # positive, quick wins, ready 10 + sky = "sky" # informational, watching 11 + violet = "violet" # features, enhancements 12 + 13 + 14 + class SectionIcon(StrEnum): 15 + alert = "alert" # exclamation triangle 16 + clock = "clock" # hourglass / stale 17 + check = "check" # checkmark / ready 18 + eye = "eye" # watching 19 + bolt = "bolt" # quick wins 20 + bookmark = "bookmark" # docs, reference 21 + 22 + 23 + class SectionPriority(StrEnum): 24 + high = "high" # full-width, larger text 25 + normal = "normal" # default 2-col grid 26 + low = "low" # compact, de-emphasized 27 + 28 + 4 29 class BriefingItem(BaseModel): 5 30 """A reference to a hub item with agent commentary.""" 6 31 7 32 item_id: str # matches Card.id: "github:prefecthq/prefect#1234" 8 33 note: str # 1-line context: "stale 2 weeks, might be blocked" 34 + highlight: bool = False 9 35 10 36 11 37 class BriefingSection(BaseModel): ··· 14 40 title: str # e.g. "needs review", "quick wins", "going stale" 15 41 summary: str # 1-2 sentence section summary 16 42 items: list[BriefingItem] 43 + accent: SectionAccent = SectionAccent.sky 44 + icon: SectionIcon = SectionIcon.eye 45 + priority: SectionPriority = SectionPriority.normal 17 46 18 47 19 48 class Briefing(BaseModel):
+93
web/src/lib/briefing-styles.ts
··· 1 + import type { SectionAccent, SectionIcon, SectionPriority } from '$lib/server/briefing'; 2 + 3 + /* 4 + * tailwind safelist (JIT needs to see these classes statically): 5 + * border-red-500 border-amber-500 border-emerald-500 border-sky-500 border-violet-500 6 + * text-red-400 text-amber-400 text-emerald-400 text-sky-400 text-violet-400 7 + * text-red-300 text-amber-300 text-emerald-300 text-sky-300 text-violet-300 8 + * bg-red-950/20 bg-amber-950/20 bg-emerald-950/20 bg-sky-950/20 bg-violet-950/20 9 + * border-l-2 border-l-3 border-l-4 10 + * sm:col-span-2 11 + */ 12 + 13 + export const ACCENT_STYLES: Record< 14 + SectionAccent, 15 + { 16 + border: string; 17 + headerText: string; 18 + summaryText: string; 19 + highlightBar: string; 20 + bgTint: string; 21 + } 22 + > = { 23 + red: { 24 + border: 'border-red-500', 25 + headerText: 'text-red-400', 26 + summaryText: 'text-red-300/70', 27 + highlightBar: 'border-red-500', 28 + bgTint: 'bg-red-950/20' 29 + }, 30 + amber: { 31 + border: 'border-amber-500', 32 + headerText: 'text-amber-400', 33 + summaryText: 'text-amber-300/70', 34 + highlightBar: 'border-amber-500', 35 + bgTint: 'bg-amber-950/20' 36 + }, 37 + emerald: { 38 + border: 'border-emerald-500', 39 + headerText: 'text-emerald-400', 40 + summaryText: 'text-emerald-300/70', 41 + highlightBar: 'border-emerald-500', 42 + bgTint: 'bg-emerald-950/20' 43 + }, 44 + sky: { 45 + border: 'border-sky-500', 46 + headerText: 'text-sky-400', 47 + summaryText: 'text-sky-300/70', 48 + highlightBar: 'border-sky-500', 49 + bgTint: 'bg-sky-950/20' 50 + }, 51 + violet: { 52 + border: 'border-violet-500', 53 + headerText: 'text-violet-400', 54 + summaryText: 'text-violet-300/70', 55 + highlightBar: 'border-violet-500', 56 + bgTint: 'bg-violet-950/20' 57 + } 58 + }; 59 + 60 + /** heroicons mini 20x20 SVG paths */ 61 + export const ICON_PATHS: Record<SectionIcon, string> = { 62 + alert: 'M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z', 63 + clock: 'M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm.75-13a.75.75 0 0 0-1.5 0v5c0 .414.336.75.75.75h4a.75.75 0 0 0 0-1.5h-3.25V5z', 64 + check: 'M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5z', 65 + eye: 'M10 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z M.664 10.59a1.651 1.651 0 0 1 0-1.186A10.004 10.004 0 0 1 10 3c4.257 0 7.893 2.66 9.336 6.41.147.381.146.804 0 1.186A10.004 10.004 0 0 1 10 17c-4.257 0-7.893-2.66-9.336-6.41z', 66 + bolt: 'M11.983 1.907a.75.75 0 0 0-1.292-.657l-8.5 9.5A.75.75 0 0 0 2.75 12h6.572l-1.305 6.093a.75.75 0 0 0 1.292.657l8.5-9.5A.75.75 0 0 0 17.25 8h-6.572l1.305-6.093z', 67 + bookmark: 68 + 'M10 2c-1.543 0-3.29.47-4.593 1.19C4.11 3.91 3 4.963 3 6.5c0 1.14.523 2.044 1.268 2.724.672.612 1.538 1.075 2.303 1.465A20.1 20.1 0 0 0 8 11.448V19.5a.75.75 0 0 0 1.272.537L10 19.31l.728.727A.75.75 0 0 0 12 19.5v-8.052c.56-.264 1.245-.622 1.929-1.06.765-.39 1.631-.853 2.303-1.465C16.977 8.544 17.5 7.64 17.5 6.5c0-1.537-1.11-2.59-2.407-3.31C13.79 2.47 12.043 2 10 2z' 69 + }; 70 + 71 + export const PRIORITY_LAYOUT: Record< 72 + SectionPriority, 73 + { colSpan: string; titleSize: string; padding: string; borderWidth: string } 74 + > = { 75 + high: { 76 + colSpan: 'sm:col-span-2', 77 + titleSize: 'text-base', 78 + padding: 'px-5 py-4', 79 + borderWidth: 'border-l-4' 80 + }, 81 + normal: { 82 + colSpan: '', 83 + titleSize: 'text-sm', 84 + padding: 'px-5 py-4', 85 + borderWidth: 'border-l-3' 86 + }, 87 + low: { 88 + colSpan: '', 89 + titleSize: 'text-xs', 90 + padding: 'px-4 py-3', 91 + borderWidth: 'border-l-2' 92 + } 93 + };
+43 -8
web/src/lib/components/Briefing.svelte
··· 2 2 import type { Briefing, BriefingItem } from '$lib/server/briefing'; 3 3 import type { Card } from '$lib/types'; 4 4 import { timeAgo } from '$lib/format'; 5 + import { ACCENT_STYLES, ICON_PATHS, PRIORITY_LAYOUT } from '$lib/briefing-styles'; 5 6 6 7 let { briefing, cards }: { briefing: Briefing | null; cards: Card[] } = $props(); 7 8 8 - let cardMap = $derived( 9 - new Map(cards.map((c) => [c.id, c])) 10 - ); 9 + let cardMap = $derived(new Map(cards.map((c) => [c.id, c]))); 11 10 12 11 function urlFor(item: BriefingItem): string | null { 13 12 return cardMap.get(item.item_id)?.url ?? null; 14 13 } 14 + 15 + /** "github:prefecthq/prefect#1234" -> "prefect#1234" */ 16 + function shortLabel(item_id: string): string { 17 + const after = item_id.includes(':') ? item_id.split(':')[1] : item_id; 18 + const match = after.match(/([^/]+)(#\d+)$/); 19 + return match ? `${match[1]}${match[2]}` : after; 20 + } 15 21 </script> 16 22 17 23 {#if briefing} ··· 20 26 21 27 <div class="grid gap-4 sm:grid-cols-2"> 22 28 {#each briefing.sections as section (section.title)} 23 - <div class="bg-gray-800 rounded-lg px-5 py-4 space-y-3"> 24 - <h3 class="text-sm font-medium text-gray-300 lowercase">{section.title}</h3> 25 - <p class="text-sm text-gray-400">{section.summary}</p> 29 + {@const accent = ACCENT_STYLES[section.accent ?? 'sky']} 30 + {@const layout = PRIORITY_LAYOUT[section.priority ?? 'normal']} 31 + {@const icon = section.icon ? ICON_PATHS[section.icon] : null} 32 + 33 + <div 34 + class="rounded-lg {accent.bgTint} {accent.border} {layout.borderWidth} {layout.padding} {layout.colSpan} space-y-3" 35 + > 36 + <div class="flex items-center gap-2"> 37 + {#if icon} 38 + <svg 39 + xmlns="http://www.w3.org/2000/svg" 40 + viewBox="0 0 20 20" 41 + fill="currentColor" 42 + class="h-5 w-5 shrink-0 {accent.headerText}" 43 + > 44 + <path d={icon} /> 45 + </svg> 46 + {/if} 47 + <h3 class="{layout.titleSize} font-medium {accent.headerText} lowercase"> 48 + {section.title} 49 + </h3> 50 + </div> 51 + 52 + <p class="text-sm {accent.summaryText}">{section.summary}</p> 26 53 27 54 {#if section.items.length > 0} 28 55 <ul class="space-y-1.5"> 29 56 {#each section.items as item (item.item_id)} 30 57 {@const url = urlFor(item)} 31 - <li class="text-sm text-gray-300"> 58 + {@const highlighted = item.highlight ?? false} 59 + <li 60 + class="text-sm flex gap-2 {highlighted 61 + ? `border-l-2 ${accent.highlightBar} pl-2 text-gray-100` 62 + : 'text-gray-300'}" 63 + > 64 + <span class="font-mono text-xs opacity-60 shrink-0 pt-0.5"> 65 + {shortLabel(item.item_id)} 66 + </span> 32 67 {#if url} 33 68 <a 34 69 href={url} ··· 39 74 {item.note} 40 75 </a> 41 76 {:else} 42 - {item.note} 77 + <span>{item.note}</span> 43 78 {/if} 44 79 </li> 45 80 {/each}
+8
web/src/lib/server/briefing.ts
··· 1 1 import { readFile } from 'fs/promises'; 2 2 3 + export type SectionAccent = 'red' | 'amber' | 'emerald' | 'sky' | 'violet'; 4 + export type SectionIcon = 'alert' | 'clock' | 'check' | 'eye' | 'bolt' | 'bookmark'; 5 + export type SectionPriority = 'high' | 'normal' | 'low'; 6 + 3 7 export interface BriefingItem { 4 8 item_id: string; 5 9 note: string; 10 + highlight?: boolean; 6 11 } 7 12 export interface BriefingSection { 8 13 title: string; 9 14 summary: string; 10 15 items: BriefingItem[]; 16 + accent?: SectionAccent; 17 + icon?: SectionIcon; 18 + priority?: SectionPriority; 11 19 } 12 20 export interface Briefing { 13 21 headline: string;