Track how many seconds a user actively reads each story, using a 1-second
coroutine timer with a 2-minute idle threshold (matching iOS/web). Read
times piggyback on mark-as-read API calls and flush standalone when
leaving story view or backgrounding the app. Queued times are restored
on API failure to prevent data loss.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* main: (214 commits)
Show "Untitled" for stories with no title, content, or permalink
Bump daily briefing email font size from 16px to 18px for better readability
Fix briefing crash by calling secure_image_urls on Feed instead of MStory
Fix three Daily Briefing bugs: nested folder matching, disabled sections, and custom prompts
Add clickable story links, favicon tooltips, and fix briefing email icon
Add descriptive labels to LLM Costs stat panels in Grafana
Move 3 misplaced table panels into Trending Feeds section in Grafana dashboard
Fix LLM costs Prometheus scrape timeout by eliminating scan_iter on 150K keys
Fix LLM Costs panels overlapping Discover Usage in Grafana dashboard
Fix daily briefing JS errors from missing secure_image_urls and uninitialized views
Replace abstract thinking icon with recognizable Lucide brain SVG
Fix Discover panel overlap with LLM Costs and refactor taskbar info to flexbox
Fix bottom toolbar layout by converting taskbar-info to flex child
Add discover usage tracking to Prometheus/Grafana
Redesign daily briefing with inline styles for email-compatible rendering
Move try feed Subscribe/Follow/Sign up buttons to story titles list
Fix model dropdown scroll and undefined model on re-ask click
Add Ask AI thinking mode toggle and fix briefing lock TTL
Fix triple daily briefing by adding Redis distributed locks
Fix Manage Training crash when folder-scoped classifiers exist
...
When a story has no title, the backend already falls back to stripped
content then permalink. This adds a final fallback to display "Untitled"
so the story row renders with proper height instead of collapsing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bug #1: Nested folders returned 0 stories because the frontend sends the
leaf folder name (e.g. "Child") but flatten_folders() produces full-path
keys like "Parent - Child". Added suffix matching to resolve nested folders.
Bug #2: Disabled sections still appeared because the LLM saw CATEGORY
annotations for disabled sections and created them anyway. Two-layer fix:
remap disabled categories to trending_global before sending to LLM, and
post-process output HTML to strip any hallucinated disabled sections.
Bug #3: Custom section prompts generated irrelevant content when switching
folders because prompts were sent to the LLM even when no stories matched
the keywords. Now only include custom prompts that have matching stories.
Fixes#2049
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Daily briefing stories now have real href links (/briefing?story=HASH) that
work in both email notifications and the web reader. Favicon images show the
feed title on hover. Bumped briefing font size from 15px to 16px. Fixed the
broken Daily Briefing header icon in email by serving briefing.svg for
briefing feeds. On web, navigating to /briefing?story=HASH auto-selects
the referenced story after loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each panel had a single query returning ~12 series (total + 4 providers +
7 features) all with the same generic legend like "Today". Split into 3
targets per panel with meaningful labels: Total, provider name, feature name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Top Stories by Read Time, Long Reads, and Top Trending Feeds by Subscriptions
were positioned at incorrect y-coordinates causing them to overlap with the
Discover Usage and LLM Costs sections. Repositioned all three tables into the
Trending Feeds section and shifted all subsequent sections down accordingly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redis db=3 has ~150K keys (mostly from IPRateTracker/ScannerTracker).
scan_iter with default COUNT=10 required ~15K SCAN round-trips (~13s),
causing Prometheus to timeout with "context deadline exceeded".
Fix: replace scan_iter with a known_models SET populated during record(),
combine 6 separate Redis calls into a single pipeline with 2 round-trips,
and partition 30-day MGET results into daily/weekly/monthly buckets locally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shift all LLM Costs panel y-positions down by 18 to sit below the
Discover Usage section. The row header was moved but its child panels
were left at old positions, causing them to interleave with Discover
panels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add secure_image_urls to briefing story serialization so HTTPS image
proxying works, guard image_url() against missing secure_image_urls,
protect toggle_classes from firing before render, and check
story_title_view exists before calling found_largest_image.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move LLM Costs row y-position down in Grafana dashboard so Discover
Usage unique users panels no longer overlap it. Replace absolute
positioning with flexbox for taskbar info centering, simplifying the
layout and overflow logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The removal of .NB-modal-submit (which had flex-grow:1) in b05d7302f
caused all toolbar buttons to bunch up on the left. Convert
.NB-taskbar-info from absolute positioning to a flex-grow:1 child
inside .NB-taskbar-flex, so it acts as the center spacer pushing
Style and layout buttons to the right.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Track discover feeds and discover stories usage with Redis-based
counters exposed via a new /monitor/discover Prometheus endpoint.
Metrics include request counts and unique users across daily, weekly,
monthly, and all-time periods. Adds a "Discover Usage" section to the
Grafana dashboard with four panels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add inline CSS to briefing HTML so it renders well in both web and email
clients. Favicons now act as bullet points in a table layout with proper
text alignment, classifier pills render as styled green rounded pills
with thumbs-up icons, and section headers get typography styling. Also
refresh the summary story gradient to a cleaner blue tone and hide the
redundant generic globe image preview.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the small taskbar buttons in the bottom toolbar with prominent
banners at the top of the story titles list, matching the existing
sign-up banner pattern used for unauthenticated users. Each banner
shows a feed icon, title, descriptive subtitle, and action button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove max-height/overflow-y from model dropdowns so thinking toggle
doesn't cause scrolling. Fix model selection handlers to use
closest('.NB-model-option') so clicking on provider pill child elements
correctly resolves the data-model attribute.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ask AI: Add a Fast/Thinking segmented toggle to the model dropdown.
When Thinking is selected, providers use extended reasoning (Anthropic
thinking, OpenAI reasoning_effort, Gemini thinking_budget, xAI reasoning
model). Preference is persisted and switching modes re-asks automatically.
Briefing: Fix distributed locks to not delete on completion — the
GenerateBriefings task completes in ~0.1s, so deleting the lock let
later beat tasks from other servers acquire it. Now uses 14-min TTL
that persists for the full scheduling interval. Per-user lock also
persists for scheduled runs; only on-demand regeneration deletes it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3 htask-work servers each run celery with -B (beat scheduler), causing
GenerateBriefings to fire 3x concurrently every 15 minutes. All 3
pass the exists_for_period() dedup check (race condition), dispatch 3
GenerateUserBriefing tasks, and send 3 notifications.
Add Redis SET NX locks at two levels:
- GenerateBriefings: only one instance runs across all servers
- GenerateUserBriefing: per-user lock as safety net against races
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The callback parameter `_` in _.each(folder_names, ...) shadowed the
Underscore.js global, causing _.each inside the callback to fail with
"_.each is not a function" and leaving the tab stuck on Loading Classifiers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Top-level briefing clicks and same-section re-clicks now trigger a full
server reload instead of using cached data. Only switching to a different
sub-section uses the fast cached re-render path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch sub-features from float to flexbox to prevent items from appearing on their own line due to varying heights. Add viewport meta tag so mobile Safari renders at device width instead of desktop width.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change sub-feature images to object-fit: cover with left-top anchoring so all thumbnails are uniform size
- Remove Native iOS/macOS and Android app sub-features (already shown in platforms section)
- Swap YouTube and Dark Mode positions
- Add new sub-feature images (11-16)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Manage Training tab's folder dropdown only showed folders containing
feeds with feed-scoped classifiers. Folders that only had folder-scoped
classifiers (but no feed-specific ones) were missing from the dropdown,
even though their classifiers rendered correctly in the list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reword features section heading to "Full-featured news reader" and subheading
to "Keep the signal, lose the noise." Add Web Feeds, Ask AI, Daily Briefing,
Notifications, Keyboard Shortcuts, and River of News as new sub-features.
Also adjust DB log timing color from dim to yellow for 1-5s queries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The folder source filter in briefing scoring only matched feeds directly in the
selected folder, missing all subfolder feeds. Since flatten_folders() stores
nested folders as separate keys (e.g. "Tech - AI"), a prefix match is needed
to collect feeds from the entire folder tree. Also removed the silent skip
when no feeds matched — the filter now always applies, and missing
UserSubscriptionFolders logs a debug message instead of silently passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show query counts per database (e.g. sql: 3/0.002s), cap time display
at millisecond resolution, and gray out zero-count entries for readability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch from URL-based <img> tags to inline base64 data URIs for both
feed favicons (from MFeedIcon) and section header SVGs (from disk),
eliminating network requests and icon pop-in on the web
- Fix regex capture group bug where group(1) captured the full HTML tag
instead of the extracted hash/key value
- Append italic debug footer showing model name, story count, token
usage (in/out), and generation time
- Return metadata tuple from generate_briefing_summary for stats
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Favicons and section header icons were injected via JavaScript after render,
causing them to pop in on the web and be completely missing from email
notifications. Now embeds them as <img> tags with absolute URLs and inline
styles directly in the briefing HTML during generation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Center hero branding (logo, title, tagline, try-out button), add cream
text with golden glow shadow for section headings on dark backgrounds,
tighten platform descriptions to two lines each, add "Full-featured"
heading with subtitle to features section, add "synced" to platforms
subheading, normalize heading punctuation (no trailing periods on
titles, periods on subtitles), restyle pricing/stats headings, and
add dark mode overrides for new elements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OpenAI reasoning models (gpt-5-*) include internal reasoning tokens in
max_completion_tokens, causing empty content when the budget is too low.
Use max_completion_tokens with 5x multiplier (min 16384) for OpenAI/XAI
providers. Add separate gray SVG icons for briefing context so original
colored icons (green rocket, gradient unread) remain unchanged for other
uses. Increase frontend briefing generation timeout from 90s to 120s.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restore growth-rocket.svg (green) and indicator-unread.svg (gradient)
to their original colors since they're used elsewhere. Apply a simple
grayscale(1) CSS filter in the briefing context instead of modifying
the source SVGs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
growth-rocket.svg was green (#5cb85c), indicator-unread.svg used a
brownish gradient, and discover/pulse/stack had a one-digit hex
variation (#95958E vs #95968E). All icons now use the same gray.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix source feed folder not persisting: make_folders() compares against
raw folder titles but we were passing river:-prefixed values that never
matched. Strip the folder: prefix to get just the folder name instead.
Remove Pro gating from the AI model selector since the entire briefing
feature is already gated under Premium Archive. All briefing users can
now choose their preferred model.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Briefing was hardcoded to Anthropic Haiku, blocking self-hosters who
only have OpenAI/Gemini keys (fixes#2048). Now uses the provider system
from Ask AI with a separate cheap-model registry (Haiku, GPT 5 Mini,
Gemini Flash Lite, Grok 4.1 Fast). Default is Haiku via BRIEFING_MODEL
setting. Pro users can pick their model in the briefing preferences
popover; non-pro users see a locked selector with an upgrade link.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>