March 31, 2026
9 pull requests merged across 4 repos
bahdotsh/blogr
Adds support for external posts — posts that appear in all blog listings but link to an external URL instead of having local content. This lets blog authors curate articles they've published on other sites.
Closes #35
- Adds optional
external_urlfield toPostMetadata(backward-compatible via#[serde(default)]) - External posts appear in index, archive, tag pages, RSS/Atom feeds, search index, and JSON API — but no local
.htmlpage is generated - All 4 blog themes (brutja, terminal_candy, minimal_retro, obsidian) and their simple variants updated with
↗indicator andtarget="_blank" - CLI:
blogr new "Title" --external-url "https://..."creates an external post
Example frontmatter
---
title: "My Article on Another Blog"
date: "2026-03-31"
author: "Author"
description: "An article published elsewhere"
tags: ["rust"]
status: "published"
slug: "my-article-on-another-blog"
external_url: "https://example.com/article"
---
Test plan
-
cargo build— compiles cleanly -
cargo test— all 123 tests pass (including 2 new external post tests) -
cargo clippy --all-targets --all-features -- -D warnings— zero warnings -
cargo fmt --all -- --check— formatting clean - Manual:
blogr new "External" --external-url "https://example.com"creates external post with empty content - Manual:
blogr buildgenerates site with external post in listings, noposts/external.htmlgenerated - Manual: RSS/Atom feeds contain external URL for external posts
- Fixes search result excerpts showing sentences mashed together without spaces where paragraph/block boundaries existed in the original markdown
- Adds space insertion for
End(Paragraph),End(Heading),End(Item), andEnd(BlockQuote)events inmarkdown_to_text() - Downstream
extract_excerpt()already collapses whitespace viasplit_whitespace().join(), so extra spaces are harmless
Fixes #25
Test plan
-
cargo test -p blogr-cli— all 113 tests pass -
cargo clippy --all-targets --all-features -- -D warnings— clean - Build a site with multi-paragraph posts and verify
search_index.jsonexcerpts have proper spacing
bahdotsh/feedr
- Module split: Decomposed
ui.rs(2872 lines) into 10 modules undersrc/ui/and extractedevents.rsfromtui.rsfor maintainability - 8 new features spanning high-impact usability and quality-of-life improvements:
?Help overlay: scrollable keybinding cheatsheet, context-aware per viewmMark all as read: bulk mark scoped to dashboard, feed, category, or starred- Feed tree view: FeedList is now a collapsible category tree with folder nodes
- Mouse support: scroll wheel for lists/content, click to dismiss overlays
lLink extraction: parse article HTML for links/images, open from overlay- Global search:
/now available from all views (was missing in Starred, Detail) - Per-feed refresh:
refresh_intervalper feed in config.toml - Configurable keybindings:
[keybindings]config section withKeyActionenum,key_matches()helper, and 11 unit tests
Details
Module refactor
| Before | After |
|---|---|
ui.rs (2872 lines) | src/ui/ with 10 modules (mod, dashboard, feed_list, feed_items, detail, starred, summary, categories, modals, utils) |
tui.rs (1226 lines) | tui.rs (190 lines) + events.rs (1100+ lines) |
Configurable keybindings example
[keybindings]
quit = "x"
move_up = ["w", "Up"]
toggle_star = "Ctrl+s"
open_search = "Ctrl+f"
Feed tree view
▾ Tech (3 feeds)
◆ Hacker News (25)
◆ TechCrunch (12)
▸ News (2 feeds)
◆ Reddit /r/rust (15)
Test plan
-
cargo fmt --all -- --checkpasses -
cargo clippy --all-targets --all-features -- -D warningspasses -
cargo test --all-features --verbose— 45 tests pass (41 unit + 4 integration) - Manual smoke test: press
?from each view, scroll, dismiss - Manual: press
min Dashboard/FeedItems/FeedList, verify items marked read - Manual: press
lin article detail, verify links listed, open with Enter - Manual: scroll wheel in lists, verify navigation
- Manual: assign feeds to categories, verify tree rendering, expand/collapse
- Manual: add
[keybindings]overrides to config.toml, verify remapped keys work - Manual: add
refresh_interval = 60to a feed, verify independent refresh
When a user adds a regular web page URL instead of a direct feed URL, feedr now auto-discovers RSS/Atom feed links from <link rel="alternate"> elements in the HTML:
- Single feed found: automatically adds it
- Multiple feeds found: shows a selection modal for the user to pick
- No feeds found: shows a clear error message instead of the old cryptic "received HTML" error
Changes
feed.rs: AddedDiscoveredFeedstruct,HtmlWithFeedsErrorcustom error type, anddiscover_feeds_from_html()function using thescrapercrate. Modified HTML detection to return discovered feeds instead of a generic error.app.rs: AddedSelectDiscoveredFeedinput mode and discovery state fields.tui.rs: ModifiedInsertUrlhandler to downcastHtmlWithFeedsErrorand branch on feed count. AddedSelectDiscoveredFeedinput handler (j/k nav, Enter select, Esc cancel).ui.rs: Added themedrender_feed_selection_modal()with[RSS]/[Atom]badges, titles, URLs, and highlighted selection.Cargo.toml: Addedscraperandurldependencies.
Test plan
-
cargo clippy --all-targets --all-features -- -D warningspasses -
cargo test --verbosepasses (8 new unit tests for discovery logic) - Manual test: run feedr, press
a, enter a blog URL (e.g.https://blog.rust-lang.org/), verify feed auto-discovery works - Manual test: enter a URL with multiple feeds, verify selection modal appears
- Manual test: enter a URL with no feeds, verify clear error message
— category switching on the dashboard was broken because event handlers and rendering used different item lists.
- Rendering correctly used
filtered_dashboard_itemswhen a category filter was active, but all interactions (navigation, Enter, Star, Space) indexed into the unfiltereddashboard_items— so pressing Enter on item 3 in a filtered view opened a completely different article selected_itemwas never clamped afterapply_filters()shrank the visible list, so the cursor could point past the end of filtered results- Added
active_dashboard_items()as a single source of truth for the currently visible item list, used consistently in bothui.rs(rendering) andtui.rs(event handling) - Added
clamp_dashboard_selection()called at the end ofapply_filters()to keep selection valid
Files changed
| File | What |
|---|---|
src/app.rs | Added active_dashboard_items(), active_dashboard_item(), clamp_dashboard_selection() |
src/tui.rs | Updated Star, Up, Down, Enter, Space handlers to use active_dashboard_items() |
src/ui.rs | Replaced inline list selection and dashboard_item(idx) with shared helpers |
Test plan
-
cargo clippy --all-targets --all-features -- -D warnings— clean -
cargo test --all-features --verbose— all 22 tests pass - Manual: run app, add feeds to multiple categories, cycle
cthrough categories — verify correct items shown, Enter opens the right article, star/space work on the visible item, selection doesn't jump out of bounds - Manual: verify refresh (
r) with an active category filter doesn't cause items to flash and disappear
bahdotsh/indxr
find(mode: "callers")andsummarize(glob)compound tools now return compact columnar{columns, rows}format by default, matching how symbol/signature/relevant modes already worked- Adds
compactboolean param to granularget_callersandbatch_file_summariestool schemas for direct usage - Normalizes import refs in
get_callerscompact output (setsnamefrom import text,kindto"import") for uniform column layout
Test plan
-
cargo build— compiles cleanly -
cargo test— all 354 tests pass - Manual MCP test: call
find(query, mode: "callers")and verify compact columnar response - Manual MCP test: call
summarize("src/**/*.rs")and verify compact columnar response
- Replace 12 granular default MCP tools with 3 compound tools (
find,summarize,read) that dispatch to existing implementations — reduces per-round schema overhead from ~1,100 to ~422 tokens - Inject codebase tree context into benchmark agent prompt, simulating real INDEX.md usage
- Rewrite benchmark questions as realistic developer tasks (43 questions across navigate/understand/design)
- Track schema vs content token overhead separately
Compound tools
| Tool | Schema | Replaces |
|---|---|---|
find(query, mode?) | ~122 tok | search_relevant, lookup_symbol, get_callers, search_signatures, explain_symbol |
summarize(path, scope?) | ~135 tok | get_file_summary, get_public_api, batch_file_summaries, explain_symbol, list_declarations, get_file_context |
read(path, symbol?, ...) | ~164 tok | read_source |
All 23 granular tools remain callable via --all-tools (26 total). No breaking changes — granular tools still work, just not listed by default.
Why this matters
Tool definitions are re-sent by the API on every round. This is a universal tax paid by every MCP client (Claude Code, Cursor, Windsurf). 3 tools instead of 12+ means ~750 fewer tokens per round across every conversation for every user. Combined with context injection and simpler tool selection (fewer rounds), projected ~5x total token reduction.
Test plan
-
cargo test— 329 tests pass -
cargo clippy— no new warnings -
python -m bench --dry-run— 3 tools loaded, tree context injected -
python -m bench --runs 1— full benchmark run to validate token reduction
bahdotsh/wrkflw
- Parse and support the GitHub Actions
containerdirective at the job level, handling both string (container: node:18) and object (container: { image: ..., env: ..., volumes: ... }) formats - Use the container image as the runner image when specified, with
runs-onas fallback - Propagate container env vars (lowest precedence), mount container-defined volumes, and remap GitHub env file paths inside container runtimes
- Extract shared volume/env-remapping logic into
prepare_container_mounts()helper to avoid duplication across Docker-action and run-step branches - Handle single-path volume specs (
/data) by mounting at the same path inside the container - Warn when unsupported container fields (
options,credentials,ports) are specified
Closes #58
Test plan
- Parser tests for
deserialize_container: string format, full object format, absent container, registry image with colon in tag - All existing tests pass (114 total)
-
cargo clippyclean -
cargo fmtclean
- New
action_resolvermodule fetchesaction.yml/action.yamlfromraw.githubusercontent.comfor third-party actions and parsesruns.usingto determine the action type (Node/Docker/Composite) prepare_action()now tries remote resolution first, selecting the correct Docker image based on the action's metadata, and falls back to the existing hardcoded mapping on any failure (zero regressions)- Remote composite actions are now supported — cloned into a tempdir and executed via the existing
execute_composite_action()path ActionInfo.versionfield added so the@refportion of action references is preserved for fetching the correct version- Supports
GITHUB_TOKENenv var for private repos; results are cached in memory with negative caching
Test plan
- All 83 existing tests pass
- 8 new unit tests for action.yml parsing (Node, Docker, Composite, missing runs, image mapping)
-
cargo clippy— no new warnings -
cargo fmt— clean - Manual test with a workflow using
EmbarkStudios/cargo-deny-actionor similar third-party action - Manual test with a well-known action (e.g.,
actions/checkout) to confirm no regression