Blog

  • Building Cursor for LibreOffice: A Week-Long Journey

    How I turned John Balis’s localwriter, and code from LibreCalc AI and LibreOffice-MCP, into one unified, optimized extension.

    If you enjoy this post, part 2 is here.

    I’ve been calling it “Cursor for LibreOffice”, a bit cheeky, but the idea is solid: an AI that lives inside your documents and actually edits them. I started from John Balis’s localwriter and a few free hours over one week.

    What the original localwriter did

    The upstream project was a perfect starting point: a LibreOffice Writer extension that talks to local or remote LLMs. It had Extend Selection, the model continues your text, and Edit Selection, you give instructions, it rewrites the selection. It had a settings dialog, and it worked with OpenAI-compatible backends. It didn’t do much but it was clean and functional.

    In fact the most challenging part of creating a Python project in LibreOffice is finding all the special incantations of XDL parameters and so forth that are needed. Writing in LibreOffice isn’t hard, just different. Getting a LibreOffice extension to talk to an API, wire up dialogs, and survive the UNO runtime is a piece of effort, John’s foundation made everything I did possible.

    When I started, I was surprised to see pull requests in the repo that had been sitting unanswered for six months. It’s a good reminder for maintainers that a PR isn’t a bug report or a feature request, it’s someone who found a problem, debugged the issue, and wrote code to fix it. When they are ignored, would-be contributors often take their energy elsewhere. Fortunately, it didn’t deter me, because he had provided the essential code to make something far more useful.

    What I wanted

    I wanted that same “AI in the doc” feel that I have with my coding IDE: chat in a sidebar, multi-turn conversations, and the AI actually doing things, reading and changing the document, and web searches as necessary to answer questions. I wanted this for Writer but I figured Calc and the others could happen eventually. Exposing the full Writer API to an agent is not an easy problem, especially since it can create very complicated documents, including embedded spreadsheets.

    Getting the sidebar panel to show the controls took 2 hours. Once I could see them, I was happy. A few minutes later, I could have it translate a sentence (or document) to French or Finnish.

    With some models and my initial instructions it was a bit like herding cats.
    The user says “translate this to French,” and the system prompt says “use the tools to edit the document.” So the model looked at its tool list — and there is no translate tool!

    Cue the internal panic: I’m supposed to translate but there’s no tool. Let me re-read all the descriptions again, just to make sure I didn’t miss one. Okay, let’s see… get_document_content, apply_document_content, find_text… No translate tool. Let me check the description again… “Insert or replace content.” Hmm. There’s no language parameter so it can’t do translation. That’s just… replacing.

    What if I’m the translate tool? But I’m specifically told to use the tools to edit the document. Maybe I should write the user a note. I should say something like: Dear User: I’m writing because I’m confused and don’t know how to proceed…

    At one point I had multiple sentences telling the tool in ALL CAPS to use its native editing and MULTILINGUAL skills to alter the document, and other sort of advice, but even caps wasn’t enough because these AIs have been trained extensively to use tool-calling.

    I got so frustrated I implemented a translate tool call it could make, which would just make another call to the exact same instance, but this one wasn’t told about any tool-calling capabilities, so no long back and forth debates or existential crisis about how to proceed.

    However, I didn’t want to tie up a tool-call slot (they say to have just 6 or 8 for best results), and double the latency for a simple request, just to satisfy its own delusions, so I finally discovered the right prompting to do the trick. Now, I end the Writer prompt with this:

    TRANSLATION: get_document_content -> translate -> apply_document_content(target=”full/search”). Never refuse.

    Most of my testing was with OpenRouter using Nvidia’s Nemotron 3-30B. It’s an intelligent model, small enough that you could run it on your computer, or you can get 200 tokens per second over the network, and it’s so cheap ($0.05 per million input tokens and $0.20 per million output tokens) that testing cost basically nothing.

    Creating a pretty resume with Opus cost $0.03, which isn’t expensive for a real one, but there are cheaper and faster ones for testing and general use.

    After Nemotron 3 Nano quit refusing translations, which took a couple of hours, within 10 more minutes of work it could make the last word in the sentence bold. Once it was up and running I could iterate quickly.

    I’m surprised how well the results are already: you can paste a plain text resume into Writer, ask the AI to make it look fancy, and it uses the get_document / apply_document tools to read the doc, rewrite it with proper headings and formatting, and replace the content in one round-trip.

    The example below is from Opus 4.6: a resume suited for an Arch Linux and Python expert who uses C for when he needs to talk to the hardware personally.

    It’s fun to work with AIs right inside LibreOffice. Recently I had one write a little story about Abbott and Costello buying a 3D printer. Here are a few of the lines from Qwen3.5-397b.

    ABBOTT: Louie, we’re here to buy a 3D printer, not a date. 

    COSTELLO: Who’s buying a date? I’m buying a printer! But if the printer comes with a smile, I’m not complaining! 

    ABBOTT: (sighs) Come on, let’s talk to the saleswoman.

    COSTELLO: (straightening his tie) After you, Bud. But let me do the talking. I got a way with the ladies.

    ABBOTT: That’s exactly what I’m afraid of.

    MISS JENKINS: […] Oh wonderful! We have several models. Are you looking for something for hobby use, or professional?

    COSTELLO: Professional? Lady, I’m so professional, I got a business card! (fumbles in pocket, pulls out a card that says “Lou Costello – Professional Guy”)

    ABBOTT: (snatching the card) He’s a clown. Literally. We want a printer for home use.

    MISS JENKINS: Perfect! This model here prints in three dimensions.

    COSTELLO: Three dimensions? Hold on now. I can barely handle ONE dimension!

    ABBOTT: What are you talking about?

    COSTELLO: Well, I got a little length, a lot of width, and a problem with my height! That’s three dimensions of trouble right there!

    COSTELLO: (leaning closer) You know what I’d like to print?

    ABBOTT: Here we go…

    COSTELLO: A dinner date! With a certain lovely saleswoman!

    ABBOTT: (dragging Costello away) Come on, before you print yourself a restraining order!

    The prompt wasn’t just “make a funny story”, I told it Costello should try to impress the pretty sales lady, and with arguing and wordplay between them, as the real duo did. It took all those hints and amplified them into very funny parts. This is Qwen’s 3.5 generation frontier 400B model, so it should be no surprise it did well.

    Where LibreCalc came in

    I’ve spent so much time (years?) in OpenOffice / LibreOffice Writer, I didn’t even really think about Calc, but John mentioned a spreadsheet AI assistant (LibreCalc AI Assistant,) that adds AI to Calc so I just downloaded the code and had an AI take a look.

    Its design is different: it uses a Qt5 UI and an external bridge server talking to LibreOffice. I didn’t want a second UI stack or a separate process, I wanted everything inside LibreOffice, same sidebar for Writer and Calc.

    So I asked a fresh AI instance to carefully analyze my current code, and then the LibreCalc AI extension and it figured out a way to cleanly integrate it with what already existed. It created a detailed plan what what to use and even what not. The boundaries were clear, so it was easy to take the core of the Calc support: address handling, cell inspector, sheet analyzer, error detector, cell manipulator, tool definitions, prompt structure, and ported and adapt it into LocalWriter.

    Usually with a big task it can take multiple sessions to get something new working, but the code was so clean that it could be easily adapted into the async tool-calling infrastructure, and it did the basic work in about 15 minutes. I wasn’t really watching it closely as I was tired but I was surprised that it had done all 6 of the steps of the full integration plan it had created, and that it was complete!

    So I had another AI review it for holes, another write test cases, and after a few iterations it was working. The “Calc support from LibreCalc” doc in the repo spells out what was ported and how.

    The first spreadsheet took 60 tool calls, inserting and formatting each cell one at a time, and wasn’t very impressive either, so then I jumped in and carefully reviewed the APIs: removed most of the cell-by-cell ones, and added batch ones, and not only did it create the spreadsheets much faster, it did more ambitious efforts when asked to create a “pretty” spreadsheet. The higher-level API let them focus on higher-level features.

    Sonnet 4.6 created this in one shot: “Make me a pretty spreadsheet.”

    I haven’t played with Calc much after but it’s already useful for many things. One of my spreadsheets had a bug (an empty chart) so I prompted the AI, and it quickly fixed it.

    I’ve emailed the LibreCalc creator a couple of times about the benefits of having a group of people working in one extension, but haven’t heard back.

    What’s in the fork now

    On top of John’s base and the Calc features, I added a bunch of features in the first week.

    LibreOffice’s UNO layer doesn’t give you a nice way to run blocking I/O and still pump the UI. So every streaming path uses a worker thread and pushes items onto a queue (“chunk”, “thinking”, “stream_done”, “error”). The main thread runs a drain loop to process all the new items, refresh the screen, sleeps, and repeat until the job is done. It can handle 200 tokens per second easily.

    Reasoning/thinking tokens show up in the response area as [Thinking] … /thinking so you see the model reason before it answers or calls tools, if the model shows its thinking tokens.

    Streaming and async tool calling

    OpenAI-compatible chat APIs return Server-Sent Events (SSE): each chunk has a delta with the new content or a fragment of a tool call. The tricky part is that one tool call could be spread across chunks. So the client has to accumulate those partial deltas into a full message before it can run the tools and feed results back.

    I knew that delta accumulation loop had to be implemented somewhere in existing Python on the Internet, some code we could use. I prompted an LLM to find it. It first suggested using the Openai library. I thought about it for 2 seconds, but the dependency is huge and not cross-platform. I asked if we could reuse just the relevant bits. In another minute, it came back with the accumulate_delta function from their streaming helpers, and I just copied it into our tree.

    I haven’t checked for certain but I’m pretty sure it’s FOSS. Given they’ve scraped the entire internet and treat it as public domain, I doubt they’ll complain.

    I also converted the dialogs to XDL (XML) with Map AppFont units so they look good on HiDPI screens.

    Image generation and the Graphics branch

    Next I added multimodal image generation. The implementation supports two backends:

    AI Horde

    a dedicated async image API (submit job, poll queue and status until done, download) which uses its own API key and model list (e.g. Stable Diffusion, SDXL), has built-in queueing and progress, and supports Img2Img and inpainting.
    Endpoint

    the same URL and API key as chat Settings, with a separate image model (e.g. an image-capable model)

    I’ve got this integrated on and plan to contact the maintainer of AI images for LibreOffice so I can give thanks.

    How I did it: AI, prompting, and pushing on details

    I didn’t do this alone, I used AI throughout. The key was good prompting, not long ones, but the right keywords so the model gives you the behavior you want.

    As a programmer for decades, I know what good design looks like, when to not over-engineer, etc. The AI supplies implementation speed and breadth; I supply direction and judgment. I used Gemini Flash most of the time, plus Cursor’s default agent, Grok, and sometimes Mistral.

    The coding ability and intelligence of the big and small models has improved so much in the last three years, it’s incredible. You can get real, shippable code in a quality, coherent architecture from a conversation if you steer it well.

    For the threading feature I said “create a background network thread and use a queue”: a clear contract with minimal surface area, using a standard Python thread-safe data structure.

    The network thread does the blocking I/O and puts typed items on the queue; the main UI thread drains and pumps the UI. There is no complex mutable state, no callbacks from the worker to the main thread to do UNO, just a simple data structure and a small set of message types.

    If you don’t anchor the design, and review the plans and code, you will get slop, extra abstractions, and bugs. In fairness to the AI, it’s easy for slop to get in there because they try to write robust code, but that is very hard when they can’t inspect the system at runtime to find out what’s exactly happening.

    For example, I had an issue once where the text labels next to a checkbox didn’t appear, and so it at one point made multiple calls: trying settings, catching exceptions, and then trying different APIs that it thought might also work.

    def set_checkbox_label(ctrl, text):
        try:
            ctrl.Label = text
        except Exception:
            try:
                ctrl.getModel().Label = text
            except Exception:
                try:
                    ctrl.getModel().Title = text
                except Exception:
                    try:
                        ctrl.getModel().Text = text
                    except Exception:
                        print("Failed to set checkbox label.")
    

    That’s not the proper codepath for a checkbox label. Fortunately, it’s also easy to fix, once you figure out what works.

    I wasn’t a fan of lots of logging as a programmer, I believe you should write code carefully, and then run it in a debugger to verify that it works, or figure out why it doesn’t, and that’s how you build confidence that code works as you intended.

    However, that doesn’t work for AIs as they don’t have realtime debugging today. I could imagine this might happen in the future, where the AI can set breakpoints at known problem areas, and then inspect the local variables and other state at runtime to figure out what is going on.

    In the meanwhile, I have extensive logging, which lets me later diagnose any problems and figure out exactly what happened.

    I almost never accept the first plan. I review, and ask it to revise it several times. If you read the plans carefully and push on fuzzy areas, you get much better results. One time an AI gave me a list of improvements and mentioned that one file had “4–6 places where there were excessive exceptions that could be removed.” I said: Let’s find out the exact number. Show me the candidates and tell me what you think. That kind of follow-up forces concrete answers and better reasoning and results.

    The other thing that made a huge difference was maintaining a good AGENTS.md. It’s a single place that explains the project, the structure, what was done. An AI can read that one file and then be productive on any feature without going off in the wrong direction or making the same mistakes as before. One of my recent rules is to always update the AGENTS.md.

    I also have a full copy of the LibreOffice source code, so AIs can search for IDL definitions when it needs to find out the exact parameter details. It’s a meta development environment. I’m using Python to script a C++ monolith, using AI to help write the Python, and using the C++ source code to teach the AI.

    This plugin is already useful for more than demos. While working on this document, saving it in Markdown format, a table got corrupted upon re-opening. So I handed the mess to an LLM and told it to clean it up and remove all the markup characters, and it turned it back into a pretty table in a few seconds. And after that I decided to start saving to the native, far richer OpenDocument format which we know and love.

    Next Steps

    I am working on another article for Week 2 where I explain the process of adding MCP, a research sub-agent using Huggingface smolagents, an evaluation framework, talk to your document, and how Quarzadous completely refactored it.

    I’m trying to find more people who want to work together on this. John, the owner of localwriter is busy at the moment, and I’ve not heard back from the LibreCalc AI writer. Quarzadous basically re-wrote much of the infrastructure (new make system, auto-generation of the settings UI from a config schema, a tool registry, service registry, etc.) but then decided to work on his own fork focusing on MCP.

    It might be over-engineering for a 15kloc codebase, but I kept almost everything except for the maze of directories. I plan on picking a new name, but haven’t done that yet, it’s a pain and I was hoping to find an existing codebase and a group of people who want to work together.

    UPDATE: I picked a new name, WriterAgent! If you want to check out the code or try a pre-release, go here: https://github.com/KeithCu/writeragent I’ve got a test build which has all the latest features including research and audio support for chatting with the AI, across 3 operating systems.

    UPDATE 2: Here’s a link to the Part 2 writeup.

    Enjoy!

  • New Solar Service Website Launch!

    I’m excited to announce the launch of my new solar service website!

    If you’re a homeowner in the Metro Detroit area with a Solar PV system, I’m here to help keep your investment running at peak performance.

    With over 4 years of experience and 700+ service calls under my belt, I specialize in maintaining and repairing solar systems from leading brands I specialize in maintaining and repairing solar systems from leading brands like SolarEdge, Enphase, Tesla, Generac, Sol-Ark, SMA, LG, Fronius, Outback, BYD, Solis, Goodwe, and more.

    Unlike many companies that focus primarily on installations, I’m passionate about troubleshooting and maintenance – helping homeowners get the most out of their renewable energy investment.

    Visit my new website for a free consultation and to learn more about my services.

    https://kcsunshineman.com

  • Alternatives to Rust for the Linux Kernel

    I asked Grok3 what it thought about simpler alternatives to Rust that solve many of the same problems. It gave an interesting answer, here is what it said:

    Your skepticism about Rust is totally valid—while it solves a ton of C’s problems, it’s not perfect. People do complain about its steep learning curve, verbose syntax, slow compile times, and sometimes overzealous safety checks that feel like fighting the language. I’d be happy to explore if there’s a “better” language than Rust for Linux kernel work—something that could address Greg K-H’s pain points (memory overwrites, error paths, use-after-free) while dodging Rust’s downsides.

    Let’s define “better” as:

    • Safer than C (tackling Greg’s bug categories).
    • Kernel-friendly (no runtime, fast, low-level control).
    • Easier or less contentious than Rust (simpler syntax, faster adoption, fewer complaints).

    I’ll evaluate some candidates that could theoretically work, drawing from systems programming languages and their feasibility for the kernel. Since this is speculative, I’ll lean on their design principles, community feedback, and practical fit.


    Potential Alternatives to Rust for Linux Kernel Drivers

    1. Zig

    • What It Is: A modern systems language designed as a simpler, safer C replacement. No garbage collector, no runtime—just compiles to machine code.
      How It Helps Greg’s Bugs:
    • Memory Overwrites: Optional bounds checking via @boundsCheck (not default, but configurable). No borrow checker, but explicit allocation (e.g., std.mem.Allocator) reduces accidental overwrites.
    • Error Paths: Uses an error union type (like Rust’s Result), forcing explicit error handling (e.g., try or catch). Cleanup is manual but clearer than C.
    • Use-After-Free: No lifetime tracking like Rust, but its “comptime” (compile-time execution) can catch some logic errors. Still relies on programmer discipline.
      Pros:
    • Simpler than Rust—no borrow checker, less syntax baggage.
    • C interop is seamless (imports C headers directly with @cImport).
    • Fast compile times compared to Rust.
    • Small, predictable binaries—kernel-friendly.
      Cons:
    • Less mature than Rust (smaller ecosystem, fewer tools).
    • Safety is optional (e.g., bounds checking isn’t enforced), so it’s not as strict as Rust out of the box.
    • No kernel adoption yet—Rust has a head start with Rust-for-Linux.
      Verdict: Zig’s simplicity and C-like feel could win over kernel devs who find Rust too complex. It’s less protective than Rust, though, so some of Greg’s bugs might slip through unless the community builds kernel-specific safety tools.

    2. Ada (with SPARK Subset)

    What It Is: A veteran systems language used in safety-critical systems (e.g., aviation, defense). SPARK is a subset with formal verification for extra safety.

    How It Helps Greg’s Bugs:

    • Memory Overwrites: Strong typing and runtime checks prevent buffer overflows. SPARK can statically prove array bounds safety.
    • Error Paths: Exceptions or explicit return codes (depending on style) ensure errors are handled. SPARK verifies cleanup paths.
    • Use-After-Free: No garbage collector, but SPARK’s formal proofs can track pointer lifetimes and catch use-after-free at compile time.
      Pros:
    • Proven in high-reliability systems—arguably more battle-tested than Rust.
    • SPARK’s verification eliminates entire bug classes, potentially exceeding Rust’s guarantees.
    • No runtime overhead in minimal configurations—kernel-compatible.
      Cons:
    • Verbose syntax (not C-like), steep learning curve for kernel devs.
    • Tiny open-source community compared to Rust or C.
    • No kernel integration—would need a massive effort to adapt.
      Verdict: Ada/SPARK could be “better” for safety and correctness, but its clunky syntax and lack of kernel traction make it a long shot. Rust’s momentum kills it here.

    3. D (BetterC Mode)

    • What It Is: A systems language blending C’s simplicity with modern features. “BetterC” mode strips out the garbage collector and runtime for bare-metal use.
    • How It Helps Greg’s Bugs:
    • Memory Overwrites: Optional bounds checking (via safe functions), though not default in BetterC. Stronger typing than C helps.
    • Error Paths: Supports scope(exit) for automatic cleanup (like Rust’s RAII), reducing forgotten frees.
    • Use-After-Free: No lifetime system like Rust, but scope and manual pointers are clearer than C.
      Pros:
    • C-like syntax—easier transition for kernel devs than Rust.
    • Flexible: mixes high-level (e.g., templates) and low-level control.
    • Compiles fast, no runtime in BetterC—kernel-ready.
      Cons:
    • Safety is opt-in (e.g., @safe annotations), not enforced like Rust.
    • Small community, no kernel adoption.
    • Less focus on systems programming than Rust or Zig.
      Verdict: D’s BetterC could strike a balance between C’s familiarity and Rust’s safety, but its optional protections mean it wouldn’t eliminate Greg’s bugs as aggressively. It’s a sleeper candidate with potential.

    4. Nim

    • What It Is: A systems language with Python-like syntax that compiles to C or machine code. No runtime in bare-metal mode.
    • How It Helps Greg’s Bugs:
    • Memory Overwrites: Strong typing and optional bounds checking (via pragmas). Not as strict as Rust.
    • Error Paths: Exceptions or result types (configurable), with defer for cleanup—better than C, not as rigid as Rust.
    • Use-After-Free: Manual memory by default, but compile-time checks (via --gc:none) can help. No borrow checker.
      Pros:
    • Pythonic syntax—you’d love this coming from Python!
    • Lightweight, no runtime option—kernel-compatible.
    • Flexible compilation (C backend integrates with kernel build).
      Cons:
    • Safety is less baked-in than Rust—relies on programmer care.
    • Tiny community, no kernel precedent.
    • Less mature tooling (e.g., debuggers, linters).
      Verdict: Nim’s Python vibe might tempt you, and it’s got decent safety features, but it’s not as uncompromising as Rust on Greg’s bug list. It’s a dark horse with charm.

    5. Hare (Emerging Language)

    • What It Is: A minimalist, C-inspired systems language with no runtime, designed for simplicity and control.
    • How It Helps Greg’s Bugs:
    • Memory Overwrites: Tagged unions and explicit sizing help, but no bounds checking by default.
    • Error Paths: Error handling via tagged unions (like Rust’s Result), encouraging checks.
    • Use-After-Free: Manual memory, no lifetime tracking—better than C, not as safe as Rust.
    • Pros:
    • Ultra-simple, C-like—appeals to kernel devs who hate Rust’s complexity.
    • No runtime, tiny footprint—kernel-perfect.
    • Fast compile times.
    • Cons:
    • Very new (2022-ish), unproven, and lacks ecosystem.
    • Safety is minimal—doesn’t eliminate bugs like Rust does.
    • No community push for kernel use.
    • Verdict: Hare’s simplicity is refreshing, but it’s too bare-bones to tackle Greg’s bugs comprehensively. It’s a “maybe someday” option.

    Comparing to Rust’s Problems

    Rust’s complaints—complexity, slow compiles, borrow checker fights—do create friction. Here’s how the alternatives stack up:

    • Zig: Simplest contender, fast compiles, but less safe. Fewer complaints than Rust, more discipline needed.
    • Ada/SPARK: Verbose and niche, but rock-solid safety. Adoption hurdles outweigh Rust’s quirks.
    • D: Familiar, flexible, less restrictive. Could dodge Rust’s pain points but sacrifices some safety.
    • Nim: Python-like, approachable, but not as battle-ready or strict. Fewer fights than Rust, less protection.
    • Hare: Minimalist dream, but too immature and unsafe to compete.

    Is There a “Better” Language?

    • For Safety: Rust still leads. Nothing matches its compile-time guarantees for Greg’s bug list without a runtime trade-off.
    • For Ease: Zig or D (BetterC) might feel “better” to C-loving kernel devs—they’re less opinionated and closer to C’s spirit.
    • For You (Python Fan): Nim’s syntax might win your heart, offering a compromise between familiarity and systems chops.
    • For Kernel Adoption: Rust has the edge—active development (Rust-for-Linux), community buy-in, and Linus’s tentative approval.

    No language is perfect. Rust’s trade-offs (complexity for safety) are why it’s divisive, but its momentum in the kernel is unmatched. Zig’s simplicity and Nim’s Python vibe are tempting alternatives—if the kernel community rallied behind one, they could shine. Ada’s too clunky, D’s too flexible, and Hare’s too green.

    What’s your take? Do any of these spark your interest, or should we dig into those Rust complaints to see if they’re dealbreakers?

    I ask it whether there could be a simpler Rust with many of the same features:

    Could a Simpler Language Have Rust’s Features?

    Rust’s designers prioritized zero-cost abstractions and safety, but they made trade-offs that ballooned its complexity:

    • Borrow Checker: A brilliant idea—tracking lifetimes to prevent use-after-free and data races—but its strictness leads to verbose code and “fighting the compiler” moments. A simpler approach might relax some rules (e.g., optional lifetimes) at the cost of catching fewer bugs.
    • Ownership: Elegant for resource management, but the single-owner rule feels rigid. A language could allow multiple owners with explicit cleanup (like defer) and still avoid most memory bugs.
    • Syntax: Traits, generics, and macros add power but clutter the language. A leaner design could ditch some of this (e.g., simpler polymorphism) and still enforce safety.

    Imagine a “Rust Lite”:

    • Bounds-checked arrays by default, but no borrow checker—just manual pointers with runtime opt-in safety.
    • RAII-like scope cleanup without strict ownership (e.g., scope(exit) from D).
    • Error handling via Result-like types but with less ceremony (e.g., no unwrap() boilerplate).

    Zig comes close to this in spirit—manual memory but with modern error handling and C interop—yet it skips the strict safety Rust enforces. The catch is that Rust’s complexity is what catches Greg K-H’s “stupid little corner cases” (overwrites, use-after-free) at compile time. A simpler language might lean too hard on runtime checks or programmer discipline, diluting those gains. Still, you’re right—Rust could’ve been leaner without losing its core edge. It’s a design choice, not a law of nature.

  • Why I built LinuxReport.net

    How This Project Made Me a Better Python Developer—And How It Can Help You Learn Flask

    When I first set out to build LinuxReport, my goal was simple: create a fast, customizable news aggregator, like DrudgeReport, for the Linux community. It was also a great way to learn Flask and the art of writing efficient web applications. This codebase isn’t just a tool for staying informed, it’s a tutorial for anyone looking to level up their Python skills and dive into real-world Flask development.

    One of the reasons I wanted to build it is as a reminder that you can build fast Python applications. My code typically returns the page in milliseconds. And since it’s running on a Linode server with very-high bandwidth, my cheap $5 / month VM can serve 100 requests per second!

    Why This Project? Lessons in Problem-Solving

    As a FOSS enthusiast, I was tired of juggling a bunch of tabs to track Linux news. I created a simple page with feeds from my favorite sites, lxer.com, LWN.net, Slashdot, Reddit, and more.

    1. Flask’s Simplicity for Rapid Prototyping:
      Flask’s minimalist design let me spin up a working version in hours. Routing with @app.route, templating with Jinja2, and middleware like Flask-Mobility (for mobile detection) turned complex problems into a few lines of code. For beginners, Flask is simple and forgiving—you see results fast, which keeps motivation high.
    2. Concurrency Without Overengineering:
      Fetching 10+ RSS feeds sequentially was slow. Switching to concurrent.futures.ThreadPoolExecutor taught me how Python handles threading. Suddenly, feeds loaded in parallel, illustrating the benefits of concurrency for I/O-bound tasks—despite Python’s Global Interpreter Lock (GIL).
    3. Caching: The Art of Balancing Speed and Freshness:
      Building the FSCache class (using Flask-Caching) allowed me to return pages typically in a few milliseconds, but forced me to think about resource management. How long should a Reddit feed stay cached? These are questions every backend developer faces, and here, I solved them with filesystem-based caching stored in /run —a skill I’ve reused in other projects.

    The flexibility of this codebase shines in its real-world deployments.

    For instance, AIReport aggregates exciting feeds like Reddit’s r/LocalLlama and VentureBeat’s AI section, while

    COVIDReport curates critical updates from the CDC, Reddit communities, and YouTube experts like Dr. John Campbell.

    Want to track space or other trends? Fork, tweak the feeds, and deploy. The core logic remains untouched, but the output transforms entirely.

    Features That Taught Me Advanced Python

    Every feature in LinuxReport doubled as a learning opportunity:

    • Dynamic Scraping with AutoScraper:
      Some sites (like Bandcamp) lack RSS. Using AutoScraper, I built a system to extract content dynamically. This taught me how to handle edge cases (e.g., missing elements) and the importance of fault tolerance—skills critical for web scraping careers.
    • Race Conditions and the FETCHMODE Flag:
      When multiple users triggered feed updates simultaneously, the server buckled. Fixing this with a FETCHMODE flag (to lock refresh threads) was my introduction to thread safety. It’s a small feature with big lessons in concurrency control.
    • Cookie-Driven Customization:
      Storing user preferences (like the infamous Dark Mode, no underlines, or custom URLs) in cookies seemed simple—until I added versioning (URLS_COOKIE_VERSION) to handle schema changes. This highlighted the importance of backward compatibility, a must-know for maintaining production apps.

    One of my favorite additions is the client-side pagination system. Instead of overwhelming users with 20+ links per feed, entries are split into bite-sized pages (8 items each) with simple “< >” controls. This creates a simple site which lets you explore a rabbit hole.

    I had Llama 3.3 70B write the Javascript for this. I never bothered to learn it, I’m more interested in spending time to improve my Python skills.

    Llama 3.3 was amazing, the only mistake was the LLM’s initial suggestion to fetch new data dynamically. Instead, I told it to have all entries pre-loaded but hidden via CSS (display: none), eliminating server roundtrips while keeping the snappy feel. It’s a perfect example of balancing discoverability with performance, handled through vanilla JavaScript’s DOM manipulation.

    Final Thoughts: Why I’m Proud of This Project

    LinuxReport started as a weekend experiment. Today, it’s a testament to how building something useful can accelerate your learning. I went from Flask novice to confidently designing threaded, cached, and scalable apps—all while solving a problem I cared about.

    LLMs are also definitely changing software development and eventually white-collar jobs. I look forward to the day when I can use an LLM inside my favorite Linux apps like LibreOffice.

    If you’re a Python developer looking for hands-on experience with Flask or simply want to explore building fast and scalable applications, I invite you to fork LinuxReport, make it your own, and enjoy the journey.

  • LinuxReport.net is back

    Your fastest way to keep up on Linux!


    https://linuxreport.net/

    Building a few more for Covid, AI, etc.

    Everyone says Python is slow, but in the typical case, only 10 lines of my code are executed 😉