← All Articles

Building The Daily Feed News Reader with Claude Code

The macOS News app has always felt like too much. The moment you open it there are headlines engineered to alarm you, a layout designed to keep you scrolling, and no real way to control what comes through. The problem is I still want to read news, I just want to read it on my own terms: specific sources, reasonable volume, and without the anxiety spiral.

I built something to replace it. The application is called The Daily Feed, and it’s a native macOS RSS reader with a handful of features I could not find anywhere else in quite the combination I wanted. The catch, same as the last app I built this way, is that I do not know Swift. What I have is Claude Code and a clear enough idea of what the app should do.

The project GitHub repo can be found here: https://github.com/Blake-C/the-daily-feed

What the App Does

The main panel shows articles pulled from whatever RSS sources you have configured in the sidebar. You can filter down to a single source, browse everything in All Articles, or use the tag bar at the top of the feed to filter by topic.

The Daily Feed source feed view

Along the top you get a refresh button, a toggle to hide articles you’ve already read, a Dim button that drops thumbnail brightness when it’s late at night and you don’t want to stare at something bright, a time filter for the past hour, four hours, six hours, and so on, and a search field that runs full-text search across title, author, and body text. Search is backed by SQLite FTS5, which means it’s fast regardless of how many articles are cached.

On each article card you can bookmark it or click the X to hide it without reading. Hidden articles live in their own section in case you want to unhide them later.

The Library section on the right side of the sidebar holds your bookmarks and your quiz stats, both of which I will get to in a moment.

The Daily Feed bookmarks screen

The Quiz

This is the part that took the most iteration to get right, and the part I use to test my knowledge for what I just read.

When you open an article it renders in a modal using Mozilla Readability.js, which strips out all the navigation, ads, and surrounding noise and gives you just the text. In the top-right corner of that modal there’s a button to generate comprehension questions about what you just read.

The Daily Feed quiz screen with question generation from Ollama

The questions are generated by Ollama running locally on your machine. The app sends the article title and body to Ollama and asks it to produce five questions. Three are multiple choice, two are true/false. The reason for the quiz is simple: I kept reading article after article and retaining very little. Having to answer questions about something you just read forces you to slow down.

The generation prompt is fairly specific. Rather than asking Ollama to produce all five questions at once and then parse the response, the app fires five separate requests, one per question, and displays each as it arrives. That approach came out of a lot of trial and error. Single-batch generation produced inconsistent JSON that would fail parsing in unpredictable ways. Per-question requests are slower overall but far more reliable. My “aha” moment when building this feature was to make sure that when we passed a question for generation by Ollama to remove the paragraphs that was used in the previous question so that we didn’t get duplicate questions on subsequent generations.

let prompt = """
    Generate ONE \(typeRule) comprehension question about this news article.
    This is question \(number) of 5.

    Rules:
    - correctIndex: 0-based index of the correct answer
    - explanation: one sentence explaining why the answer is correct
    - sourceExcerpt: first 12-15 words verbatim from the paragraph the question \
      is based on (omit if not tied to a specific paragraph)

    Respond with ONLY this JSON object and nothing else:
    \(example)

    Article title: \(safeTitle)
    Article content: \(safeContent)
    """

The paragraph the question came from gets highlighted in amber in the article text with a numbered badge, so you can go back and find exactly where in the article the question originated.

The Daily Feed quiz screen where an answer is correct

The Daily Feed quiz screen where an answer was incorrect

Disputing Answers

LLMs hallucinate. Ollama is not immune to this. Sometimes it generates a question that is wrong, or marks a correct answer as incorrect. I built in a dispute system to handle this.

When you get an answer marked wrong, a button appears to dispute the question. The app sends the question, all answer options, the correct answer as marked, your answer, and the relevant article excerpt back to Ollama for a second look. Ollama re-examines the question against the source material and either rules in your favor or confirms the original answer. If it voids the question entirely because it recognizes the question was a hallucination, the question does not count against your score.

onDispute: { questionIndex, userChosenIndex in
    let content = readabilityResult?.textContent ?? article.summary ?? article.title
    await detailVM.disputeAnswer(
        questionIndex: questionIndex,
        question: detailVM.quizQuestions[questionIndex],
        userChosenIndex: userChosenIndex,
        content: content,
        endpoint: appState.ollamaEndpoint,
        model: appState.ollamaModel
    )
}

The Daily Feed quiz screen where a question was disputed and confirmed correct

The Daily Feed quiz screen where a question was voided

The quiz stats screen shows your scores broken out by day, month, and year.

The Daily Feed quiz results screen with scores for day, month, and year

The Other Ollama Features

The quiz is the most elaborate, but there are three other places the app talks to Ollama.

Article summary. There is a button at the top of the article modal to generate a summary of what you’re reading. Useful before you decide whether the article is worth reading at all.

Suggested sources. The Library sidebar has a Suggested Sources section that periodically asks Ollama to recommend reputable RSS feeds based on what you’re already subscribed to. Take these with skepticism. LLMs can hallucinate URLs and publication names that don’t exist. It’s one discovery mechanism, not a definitive directory.

The Daily Feed suggested sources view

Daily summary. As you read articles throughout the day, the app tracks them. The Daily Summary view in the Library takes everything you’ve read and generates a summary of your reading day, all in one place.

The Daily Feed daily summary view

All of these are configurable in the AI settings tab. You can point the app at a different Ollama endpoint, choose the model, and write your own prompt template for article summaries using {title} and {content} as placeholders.

The Daily Feed AI features settings screen

Security

Passing article content to a local language model sounds benign until you think about what happens if the endpoint is not actually local. The app enforces HTTPS for any Ollama endpoint that is not localhost or 127.0.0.1. If you point it at a remote server over plain HTTP, it refuses to send anything.

let isLocal = host == "localhost" || host == "127.0.0.1"
    || host == "::1" || host == "[::1]"
guard isLocal || scheme == "https" else {
    throw NewsError.invalidURL(
        "Remote Ollama endpoints must use HTTPS to protect article content in transit."
    )
}

Article rendering uses Readability.js inside a WKWebView. The extracted content gets a Content Security Policy injected both during extraction and again in the render template, since cached content bypasses the extraction step entirely.

// ReadabilityService already injects a CSP during extraction, but the
// render-side template had no CSP of its own. DB-cached content bypasses
// ReadabilityService entirely, so this ensures consistent protection
// regardless of content source.

API keys for OpenWeatherMap live in the macOS Keychain, not UserDefaults.

Building with Claude Code

The initial commit landed on April 14 with the core MVVM architecture, SQLite persistence via GRDB, feed parsing via FeedKit, and the Readability.js extraction pipeline all in place at once. The next three days were 56, 50, and 36 commits respectively. That is a lot of ground covered fast for someone who does not write Swift.

I wrote a CLAUDE.md file early in the project. Not a spec, just a plain-language description of what the app was supposed to do and why, along with constraints and decisions as they accumulated. Claude reads it at the start of each session and does not lose track of what we already decided.

The architecture decisions came in early and stayed stable: MVVM, repositories for each data type, async/await concurrency, @MainActor on views. Claude suggested those patterns on its own.

What Claude does not do is tell you what to build. Every feature in this app started with me deciding I wanted it. The quiz, the dispute mechanism, the dim button, the daily summary. I had to know what I wanted before Claude could build it.

Some things took multiple rounds. The quiz in particular went through significant iteration on the generation prompt, the JSON parsing strategy, the duplicate-question prevention, and the paragraph highlight behavior. The pattern that worked was: describe exactly what was wrong with the current behavior, point at the specific code path, and if the first fix doesn’t land, explain the symptoms more precisely rather than accepting “I can’t reproduce that.” It finds the issue eventually.

What’s Next

A few things on my list:

  • Weather widget improvements (current implementation requires an OpenWeatherMap key, fairly minimal)
  • Better source discovery that doesn’t rely entirely on an LLM
  • Read-later improvements
  • Adding additional LLM options such as Claude or ChatGPT.

The app is open source. If you want to run it, clone the repo, run ./build_app.sh, and copy the .app bundle to /Applications. Ollama is required for the AI features. OpenWeatherMap is optional and only needed for the weather widget.

Looking for a senior developer? I'm open to new opportunities (opens in a new tab) or send an email .