<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged sideprojects</title>
<link>https://coderlegion.com/tag/sideprojects</link>
<description>Powered by Question2Answer</description>
<item>
<title>Guaranteed JSON from Claude, and the five parsers I deleted to get it.</title>
<link>https://coderlegion.com/32439/guaranteed-json-from-claude-and-the-five-parsers-i-deleted-to-get-it</link>
<description>&lt;p&gt;My job-search tool had five functions whose only purpose was fixing JSON that Claude had just written. &lt;code&gt;_clean_json_text&lt;/code&gt;, &lt;code&gt;_fix_unescaped_newlines&lt;/code&gt;, &lt;code&gt;_fix_single_quotes&lt;/code&gt;, &lt;code&gt;_strip_markdown_wrapper&lt;/code&gt;, &lt;code&gt;_extract_and_parse_json&lt;/code&gt;. There was also a sixth, &lt;code&gt;_retry_json_fix&lt;/code&gt;, which took the broken JSON and sent it back to the model with a polite request to fix its own mess. I wrote every one of them, one bug at a time, over weeks. I was a little proud of them.&lt;/p&gt;
&lt;p&gt;That was the problem.&lt;/p&gt;
&lt;h3 id=&quot;how-you-end-up-with-five-parsers&quot;&gt;How you end up with five parsers&lt;/h3&gt;
&lt;p&gt;JobSearch is my personal tool, in production, single user: me. It ingests job offers from nine boards, and when I press &quot;Analyze&quot;, Claude reads the offer against my CV and returns a structured verdict: score, recommendation, career track, the English level the ad actually requires. That verdict has to be JSON, because everything downstream is a database row, not prose.&lt;/p&gt;
&lt;p&gt;The first version did what every tutorial does. Ask the model for JSON in the prompt, take &lt;code&gt;response.content[0].text&lt;/code&gt;, run &lt;code&gt;json.loads&lt;/code&gt; on it. It worked in the demo and then production started teaching me things.&lt;/p&gt;
&lt;p&gt;The model wrapped the JSON in markdown fences, so I wrote a function to strip them. Sometimes it used single quotes, so I wrote a function to fix those. Then a description with a line break inside a string, so I wrote &lt;code&gt;_fix_unescaped_newlines&lt;/code&gt;. Then a &lt;code&gt;NaN&lt;/code&gt; where a number should be. Every fix was five lines, obviously correct, and came with its own tests. I still have the test names in the git history and they read like a confession: &lt;code&gt;test_removes_trailing_commas&lt;/code&gt;, &lt;code&gt;test_replaces_nan_with_null&lt;/code&gt;, &lt;code&gt;test_replaces_infinity&lt;/code&gt;, &lt;code&gt;test_unclosed_fence_still_strips_opening&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By April the parsing layer was around 250 lines with seven strategies, chained, each catching what the previous one let through. The last resort was the AI self-repair call: if nothing parsed, send the broken output back and ask the model to repair it. A second API call, with real latency and real cost, to fix a formatting problem the first call should never have had.&lt;/p&gt;
&lt;p&gt;I had a test suite asserting that my code could survive output nobody should ever have produced. That is not robustness. That is a bug report addressed to the wrong recipient.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-get-guaranteed-json-from-claude-with-tool-choice-instead-of-parsing-text-&quot;&gt;How do you get guaranteed JSON from Claude with tool_choice instead of parsing text?&lt;/h3&gt;
&lt;p&gt;You define exactly one tool whose input schema is the shape of the answer you want, and you force it with &lt;code&gt;tool_choice&lt;/code&gt;. The model cannot answer any other way, and the JSON arrives already parsed and validated against the schema by the API, not by your code.&lt;/p&gt;
&lt;p&gt;Anthropic's API has tool use, and you normally reach for it to let the model call your functions. This is the stricter reading: one tool, forced, and the answer comes back as a Python dict on the response object.&lt;/p&gt;
&lt;p&gt;I already had a Pydantic model for the analysis, because the DB row needed one. So the schema was free:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def _schema_from_model(model_cls: type[BaseModel]) -&amp;gt; dict[str, Any]:
    &quot;&quot;&quot;Produce a JSON Schema from a Pydantic model, suitable for input_schema.&quot;&quot;&quot;
    ...

response = client.messages.create(
    model=model_id,
    system=system,
    messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: user}],
    tools=[{
        &quot;name&quot;: &quot;submit_analysis&quot;,
        &quot;description&quot;: &quot;Return the structured job analysis.&quot;,
        &quot;input_schema&quot;: _schema_from_model(JobAnalysis),
    }],
    tool_choice={&quot;type&quot;: &quot;tool&quot;, &quot;name&quot;: &quot;submit_analysis&quot;},
)
block = next(b for b in response.content if b.type == &quot;tool_use&quot;)
data = block.input  # a dict, parsed by the SDK, no text in sight
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The refactor landed on April 14 as one commit: every AI call in the codebase migrated, all five parsers deleted, the self-repair fallback deleted, the garbage-JSON test file deleted with them. The commit message says minus 200 lines and it undersells it, because the lines that left were the ones I had to re-read every time something broke.&lt;/p&gt;
&lt;p&gt;It was the scary PR of the batch. I shipped it in the middle of an afternoon where Claude and I pushed thirteen PRs to production, and I wrote about that day &lt;a rel=&quot;nofollow&quot; href=&quot;https://marcobellingeri.dev/en/writing/thirteen-prs-one-afternoon&quot;&gt;separately&lt;/a&gt;. Twelve of those PRs were routine. This one deleted a safety net and replaced it with a promise from an API, in the same diff.&lt;/p&gt;
&lt;p&gt;One thing I kept: the Pydantic validation after the call. The schema guarantees shape, not sense. A score of 950 on a 0-100 field is schema-valid JSON and still garbage, and model output stays untrusted input no matter how it is delivered. The contract moved into the API; the checking stayed on my side of it.&lt;/p&gt;
&lt;h3 id=&quot;the-surprise-came-after-launch&quot;&gt;The surprise came after launch&lt;/h3&gt;
&lt;p&gt;Here is the part I did not expect, and the reason this piece is not just &quot;use tool use, delete your parsers&quot;.&lt;/p&gt;
&lt;p&gt;With text output, the model treated my prompt rules a bit loosely, and the parsing chaos hid it. With a forced schema, it obeys much more literally. I had a fallback rule for freelance positions: Italian job ads sometimes want a P.IVA, a VAT number, which changes whether the offer makes sense for me at all. The rule said, roughly, &quot;if freelance status is ambiguous, flag it&quot;. Under tool use the model started flagging offers that mentioned freelancing in passing, a line about contractors in another team, anything. Ambiguous had quietly meant &quot;mentioned anywhere&quot;.&lt;/p&gt;
&lt;p&gt;The schema made the model more obedient, and the obedience exposed how sloppy my instructions had been. The fix was not code. It was rewriting the prompt with explicit precedence: the contract type stated in the offer wins, the fallback fires only when the offer itself is about the contract and does not settle it.&lt;/p&gt;
&lt;p&gt;So the lesson I actually paid for: when you migrate from text parsing to tool use, your interpretive prompt rules need to be tightened, not loosened. The model stops improvising on format and starts taking your words seriously. If your rules were vague, you find out now.&lt;/p&gt;
&lt;h3 id=&quot;what-i-would-tell-past-me&quot;&gt;What I would tell past me&lt;/h3&gt;
&lt;p&gt;The parsers were never defensive programming. They were a symptom that the contract lived on the wrong side of the API call, and every new repair function was me renegotiating that contract in the worst possible place, after the response, one edge case at a time.&lt;/p&gt;
&lt;p&gt;If your pipeline has a function called &lt;code&gt;_fix_single_quotes&lt;/code&gt;, you do not need a better parser. Move the shape into &lt;code&gt;input_schema&lt;/code&gt;, force the tool, keep your validation, and delete the museum. Then go re-read your prompt, because the model is about to start believing every word of it.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32439/guaranteed-json-from-claude-and-the-five-parsers-i-deleted-to-get-it</guid>
<pubDate>Mon, 26 Oct 2026 01:28:37 +0000</pubDate>
</item>
<item>
<title>using unity access to create a basic seen. What works and what still needs developing</title>
<link>https://coderlegion.com/27467/using-unity-access-to-create-a-basic-seen-what-works-and-what-still-needs-developing</link>
<description>&lt;p&gt;Since development of unity access has gone so well, I have been able to have most of the basic features built. This means that I was able to begin testing it to see if it would be viable to use in the long run. &lt;br&gt;
I did this by building a basic first person seen. &lt;/p&gt;
&lt;h3 id=&quot;building-the-scene&quot;&gt;Building The Scene&lt;/h3&gt;
&lt;p&gt;I started with a blank unity seen and created a capsule, using the game objects menu in the menu bar. &lt;br&gt;
I then used the accessible hierarchy to find the capsule. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/q8yh7zqi002wxgn9cxur.png&quot; alt=&quot;shows the new accessible hierarchy with objects inside it&quot;&gt;&lt;br&gt;
I then renamed it to player. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/z1godb9ja3j8ta4civd7.png&quot; alt=&quot;the image is of the object inspector where the name of the capsule is changed to player&quot;&gt;&lt;/p&gt;
&lt;p&gt;Next, I added the components to the player object that will be needed for the game. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/atb97xnzukfmvzj0c0o9.png&quot; alt=&quot;the image is of the component selection screen.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ppty1y6pa708p8qh4tbm.png&quot; alt=&quot;the image is of all the components added to the player in the inspector.&quot;&gt;&lt;br&gt;
I then used the same process to add a ground and 2 walls. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/oszkxpdl0upm8bjuzsh8.png&quot; alt=&quot;the image is of the entire scene. It is seen from a first person perspective and features 2 walls on a ground.&quot;&gt;&lt;br&gt;
overall, I found the new plugin to be much easier than using unity's editor or writing the code to create each object. However, there is still room for improvement. &lt;/p&gt;
&lt;h3 id=&quot;what-to-work-on-next&quot;&gt;what to work on next&lt;/h3&gt;
&lt;p&gt;Now that I have the basic features in place I need to work on a way for the unity access plugin to be integrated into the editor asset system. At the moment I have to move and rename assets in file explorer. This could cause reference errors if I forget to move a .meta file with the asset it is attached to. &lt;br&gt;
I also want to build a material system. The alternative would be to write the code for the objects materials. However, a material system would give me added convenience when adding shaders or effects. &lt;br&gt;
{% card %}&lt;br&gt;
If you want to follow along with the development of Unity Access or check out the code, you can find the repository over on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/retro-coder64/unity_access&quot;&gt;GitHub!&lt;/a&gt;&lt;br&gt;
{% endcard %}&lt;/p&gt;
&lt;h3 id=&quot;join-the-conversation&quot;&gt;join the conversation&lt;/h3&gt;
&lt;p&gt;what problems have you faced when developing plugins for unity? &lt;br&gt;
Have you done anything around accessibility? &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27467/using-unity-access-to-create-a-basic-seen-what-works-and-what-still-needs-developing</guid>
<pubDate>Thu, 17 Sep 2026 06:11:07 +0000</pubDate>
</item>
<item>
<title># I Built an AI-Powered Python-to-C++ Transpiler Entirely From My Phone. Here is How (and Why).</title>
<link>https://coderlegion.com/20284/i-built-an-ai-powered-python-to-c-transpiler-entirely-from-my-phone-here-is-how-and-why</link>
<description>&lt;p&gt;Every developer knows the drill: you love &lt;strong&gt;Python&lt;/strong&gt; for its speed of development, but when it comes to raw execution power, heavy data processing, or low-latency systems, &lt;strong&gt;C++&lt;/strong&gt; is king. &lt;/p&gt;
&lt;p&gt;But what if you could write clean, expressive Python code and instantly transform it into highly optimized, production-ready C++? Even better, what if you could orchestrate this entire infrastructure from a device that fits in your pocket?&lt;/p&gt;
&lt;p&gt;Meet &lt;strong&gt;Astmize&lt;/strong&gt; ⚡ — an open-source, AI-powered Python-to-C++ transpiler with an active execution sandbox. And yes, I coded the entire ecosystem—both the frontend and backend architecture—directly from a &lt;strong&gt;6.67&quot; mobile phone screen&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here is a deep engineering breakdown of how it works under the hood.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-the-architecture-behind-the-scenes-of-astmize&quot;&gt;🛠️ The Architecture: Behind the Scenes of Astmize&lt;/h3&gt;
&lt;p&gt;Building a complex developer tool on a mobile device requires discarding heavy frameworks and relying on pure, asynchronous engineering. Here is how the orchestration layers interact:&lt;/p&gt;
&lt;h4 id=&quot;1-multi-model-ai-fallback-chain&quot;&gt;1. Multi-Model AI Fallback Chain&lt;/h4&gt;
&lt;p&gt;Static AST (Abstract Syntax Tree) parsing can only get you so far with highly dynamic Python code. To ensure flawless code conversion, Astmize utilizes an &lt;strong&gt;AI Orchestrator via OpenRouter&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;To counter server loads, API limits, or downtime, I built a sequential &lt;strong&gt;Fallback Chain&lt;/strong&gt;:&lt;br&gt;
(Qwen3 Coder -&amp;gt; DeepSeek -&amp;gt; Nemotron -&amp;gt; GPT-OSS ...)&lt;/p&gt;
&lt;p&gt;If the primary model hits a rate limit or experiences high latency, the backend seamlessly routes the payload to the next model in the chain asynchronously, ensuring zero interruption for the user.&lt;/p&gt;
&lt;h4 id=&quot;2-live-cloud-execution-sandboxing&quot;&gt;2. Live Cloud Execution Sandboxing&lt;/h4&gt;
&lt;p&gt;A transpiler is useless if you can’t verify the output. To solve this, I integrated the &lt;strong&gt;Wandbox API&lt;/strong&gt;. Once the code is compiled into C++, the frontend triggers a remote compilation sequence running native GCC. The user receives real-time console feedback, compilation errors, and program outputs directly inside a dark, responsive cyber UI.&lt;/p&gt;
&lt;h4 id=&quot;3-lightweight-mobile-first-stack&quot;&gt;3. Lightweight Mobile-First Stack&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Backend:&lt;/strong&gt; A robust Python/Flask API hosted on a containerized infrastructure, fortified with strict rate-limiting (60 requests/minute) to handle high-traffic spikes securely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; A single-file, zero-build vanilla JavaScript frontend designed to render beautifully across desktop and mobile browsers alike, complete with user-customizable editor configurations (font mapping, dynamic scaling, and custom tab sizing).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-live-demo-amp-source-code&quot;&gt;🚀 Live Demo &amp;amp; Source Code&lt;/h3&gt;
&lt;p&gt;The entire project is completely free, lightweight, and open-source. You can check it out right now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Live Web Application:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://thespacetimedebugger.github.io/Astmize/&quot;&gt;https://thespacetimedebugger.github.io/Astmize/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/TheSpacetimeDebugger/Astmize.git&quot;&gt;https://github.com/TheSpacetimeDebugger/Astmize.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-a-personal-note-from-the-developer-behind-the-glass-&quot;&gt;📝 A Personal Note from the Developer (Behind the Glass)&lt;/h3&gt;
&lt;p&gt;If you’ve made it this far, thank you. I know most readers skip the closing thoughts, but I wanted to share something real with you. &lt;/p&gt;
&lt;p&gt;I don't own a computer. Every single line of code in &lt;strong&gt;Astmize&lt;/strong&gt;—from handling the asynchronous orchestration tokens down to wrestling with responsive CSS and debugging raw JSON strings—was typed with my thumbs using tools like Pydroid 3 on a small mobile device. &lt;/p&gt;
&lt;p&gt;It was an exhausting, screen-switching nightmare at times, but watching hundreds of developers clone the repo over the last few days has made every thumb cramp worth it. &lt;/p&gt;
&lt;p&gt;If this mobile-built project brings a smile to your face, inspires you, or adds any value to your workflow, &lt;strong&gt;please consider dropping a ⭐ Star on our GitHub Repository&lt;/strong&gt;. It takes you exactly two seconds, but for a solo developer fighting limitations from a phone screen, it is the validation and fuel that keeps this project—and my future startup dreams—alive. &lt;/p&gt;
&lt;p&gt;Thank you for riding along! ⚡!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20284/i-built-an-ai-powered-python-to-c-transpiler-entirely-from-my-phone-here-is-how-and-why</guid>
<pubDate>Thu, 11 Jun 2026 10:49:30 +0000</pubDate>
</item>
<item>
<title>How I saved my Python-to-C++ Transpiler from failing by pivoting to Gemini AI (Built 100% on Mobile) </title>
<link>https://coderlegion.com/20037/how-saved-my-python-to-transpiler-from-failing-by-pivoting-to-gemini-ai-built-100-on-mobile</link>
<description>&lt;p&gt;Hey devs!&lt;br&gt;
​A while ago, I started building Astmize, a web tool to transpile Python code into optimized C++. In the first version, I tried building a strict AST parser. Long story short: handling type inference across languages entirely from a mobile IDE was an absolute nightmare, and I almost gave up on the project.&lt;br&gt;
​Instead of quitting, I decided to pivot. I completely rewrote the backend to leverage Gemini AI as the core translation engine. Now, instead of breaking on complex structures, the AI understands the logic and generates clean, optimized C++ code dynamically.&lt;br&gt;
​And yes, the entire stack (Frontend on GitHub Pages, Backend API on Render) was built, debugged, and deployed 100% using my phone.&lt;br&gt;
​Version 2.0 is officially LIVE! I'd love for you guys to break it, test it with some complex Python logic, and give me your brutal feedback.&lt;br&gt;
​ Live Demo: [https://thespacetimedebugger.github.io/Astmize/]&lt;br&gt;
 GitHub Repo: [https://github.com/TheSpacetimeDebugger/Astmize.git]&lt;br&gt;
​Let's hear your thoughts!&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/inikvo3bpni8l9ug1dz5.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20037/how-saved-my-python-to-transpiler-from-failing-by-pivoting-to-gemini-ai-built-100-on-mobile</guid>
<pubDate>Tue, 09 Jun 2026 09:24:42 +0000</pubDate>
</item>
<item>
<title>I Built PentestScan: A Simple Web &amp; API Security Scanner for Developers and Small Teams</title>
<link>https://coderlegion.com/19562/i-built-pentestscan-a-simple-web-api-security-scanner-for-developers-and-small-teams</link>
<description>&lt;p&gt;Building security tools always sounds more impressive than it really is at the beginning.&lt;/p&gt;
&lt;p&gt;In my case, PentestScan started as a practical idea:&lt;br&gt;
I wanted to build a simple Web &amp;amp; API security scanner that could help developers, small teams, and DevOps engineers catch common security issues earlier - before an application reaches production.&lt;/p&gt;
&lt;p&gt;Not as a replacement for professional penetration testing.&lt;br&gt;
Not as a magic “find everything” scanner.&lt;br&gt;
But it is a practical DevSecOps tool that gives fast, understandable feedback.&lt;/p&gt;
&lt;p&gt;The problem I wanted to solve was simple:&lt;/p&gt;
&lt;p&gt;A lot of small teams do not have a dedicated AppSec engineer.&lt;br&gt;
Security reviews often happen too late.&lt;br&gt;
Reports from bigger tools can be too complex, too noisy, or too expensive for early-stage projects.&lt;br&gt;
And developers usually need something direct:&lt;/p&gt;
&lt;p&gt;What is wrong?&lt;br&gt;
Why does it matter?&lt;br&gt;
How can I fix it?&lt;/p&gt;
&lt;p&gt;That is the direction I took with PentestScan.&lt;/p&gt;
&lt;p&gt;What PentestScan does&lt;/p&gt;
&lt;p&gt;PentestScan is a Web &amp;amp; API security scanner focused on practical checks around common security weaknesses.&lt;/p&gt;
&lt;p&gt;The current version is built around:&lt;/p&gt;
&lt;p&gt;Web application scanning&lt;br&gt;
API security testing&lt;br&gt;
OWASP Top 10 related checks&lt;br&gt;
Security headers analysis&lt;br&gt;
JWT and session-related checks&lt;br&gt;
Basic exposure detection&lt;br&gt;
Report generation&lt;br&gt;
DevSecOps-friendly workflow&lt;/p&gt;
&lt;p&gt;The goal is not to overload the user with hundreds of unclear findings.&lt;/p&gt;
&lt;p&gt;The goal is to provide a clean report that helps someone understand the risk and take action.&lt;/p&gt;
&lt;p&gt;Tech stack&lt;/p&gt;
&lt;p&gt;The project is built with a simple and practical stack:&lt;/p&gt;
&lt;p&gt;Python&lt;br&gt;
FastAPI&lt;br&gt;
Docker&lt;br&gt;
Nginx&lt;br&gt;
HTML reporting&lt;br&gt;
API-first backend structure&lt;br&gt;
Security-focused scanning modules&lt;/p&gt;
&lt;p&gt;I wanted the architecture to stay modular because security tooling can become messy very quickly if everything is placed into one large script.&lt;/p&gt;
&lt;p&gt;So the scanner is organized around separate modules and checks, with the idea that new functionality can be added gradually without breaking the existing structure.&lt;/p&gt;
&lt;p&gt;Why I built it&lt;/p&gt;
&lt;p&gt;I built PentestScan mainly as a hands-on DevSecOps project.&lt;/p&gt;
&lt;p&gt;I wanted to combine several areas that I work with and care about:&lt;/p&gt;
&lt;p&gt;application security&lt;br&gt;
backend development&lt;br&gt;
automation&lt;br&gt;
Linux deployment&lt;br&gt;
Docker-based services&lt;br&gt;
CI/CD security thinking&lt;br&gt;
security reporting&lt;/p&gt;
&lt;p&gt;One thing I learned while building it is that detection is only one part of the problem.&lt;/p&gt;
&lt;p&gt;The harder part is explaining the finding in a useful way.&lt;/p&gt;
&lt;p&gt;A security report should not only say:&lt;/p&gt;
&lt;p&gt;Missing security header detected.&lt;/p&gt;
&lt;p&gt;It should explain:&lt;/p&gt;
&lt;p&gt;what was detected,&lt;br&gt;
why it matters,&lt;br&gt;
how it could be abused,&lt;br&gt;
how serious it is,&lt;br&gt;
and what the developer can do next.&lt;/p&gt;
&lt;p&gt;That became one of the most important ideas behind the project.&lt;/p&gt;
&lt;p&gt;What I am trying to improve&lt;/p&gt;
&lt;p&gt;PentestScan is still evolving.&lt;/p&gt;
&lt;p&gt;Some of the things I am working on or planning to improve are:&lt;/p&gt;
&lt;p&gt;better API scanning&lt;br&gt;
cleaner report structure&lt;br&gt;
more contextual findings&lt;br&gt;
CI/CD integration&lt;br&gt;
better severity scoring&lt;br&gt;
improved attack-path style explanations&lt;br&gt;
more useful remediation guidance&lt;br&gt;
public sample reports&lt;/p&gt;
&lt;p&gt;I am also trying to keep the tool realistic.&lt;/p&gt;
&lt;p&gt;Security scanners can easily create false confidence.&lt;br&gt;
Just because a scanner does not find something does not mean the application is secure.&lt;/p&gt;
&lt;p&gt;So I see PentestScan as a first security layer - something that can help teams catch obvious and common issues earlier, not as a full replacement for manual testing.&lt;/p&gt;
&lt;p&gt;Current version&lt;/p&gt;
&lt;p&gt;The project is currently available for free here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pentestscan.app/&quot;&gt;https://pentestscan.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Before trying the scanner, you can also check a public sample report:&lt;/p&gt;
&lt;p&gt;Sample vulnerability report:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://pentestscan.app/api/v1/public/marketing/vulnerability_report.html&quot;&gt;https://pentestscan.app/api/v1/public/marketing/vulnerability_report.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I am sharing PentestScan publicly to get feedback from developers, DevOps engineers, and security people.&lt;/p&gt;
&lt;p&gt;I would especially appreciate feedback on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;report structure and clarity,&lt;/li&gt;
&lt;li&gt;usefulness of the findings,&lt;/li&gt;
&lt;li&gt;scanning flow,&lt;/li&gt;
&lt;li&gt;missing checks,&lt;/li&gt;
&lt;li&gt;and whether the tool feels useful for small teams or solo developers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This project is still a work in progress, but it has already been a valuable learning experience in building security tooling that is not only technical but also understandable and useful.&lt;/p&gt;
&lt;p&gt;That, for me, is the main point of PentestScan:&lt;/p&gt;
&lt;p&gt;help developers see security issues earlier, understand them faster, and fix them with less friction.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19562/i-built-pentestscan-a-simple-web-api-security-scanner-for-developers-and-small-teams</guid>
<pubDate>Wed, 03 Jun 2026 11:06:15 +0000</pubDate>
</item>
<item>
<title>Flow of Autonomous Brain and its Engine</title>
<link>https://coderlegion.com/19138/flow-of-autonomous-brain-and-its-engine</link>
<description>&lt;p&gt;On the last post, I mentioned usuage of multiple github models, but made some more modifications, and now 2 new providers are also added to the boardroom. &lt;/p&gt;
&lt;p&gt;STAGE 1  ARCHITECT CONFERENCE&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     Architect A (Mixtral/Groq) + Architect B (Llama/Groq) propose plans in parallel
     Judge (GPT-4o) synthesises or proposes a more unpredictable plan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;STAGE 2  IMPLEMENT&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     Engineer (GPT-4o) writes each file with full sibling context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;STAGE 3  QUALITY LOOP (up to 8 rounds)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     Reviewer A (Mixtral/Groq) + Reviewer B (Gemini) in parallel
     Fixer applies merged feedback + Playwright interaction test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;STAGE 4  QA REVIEW&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     Playwright clicks every button, tests every slider
     QA Tester (GPT-4o) issues verdict: shippable / partially_usable / non_functional
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;STAGE 5  PUBLISH&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     New public GitHub repo created via API
     GitHub Pages enabled -&amp;gt; live URL in under 60 minutes from cold start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;STAGE 6  SELF-IMPROVE&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     CTO (Gemini) reads last 30 failure logs, proposes a surgical source patch
     Validates Python syntax, commits - next build runs improved code
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19138/flow-of-autonomous-brain-and-its-engine</guid>
<pubDate>Thu, 28 May 2026 07:23:50 +0000</pubDate>
</item>
<item>
<title>What have you been working on lately?</title>
<link>https://coderlegion.com/17961/what-have-you-been-working-on-lately</link>
<description>&lt;p&gt;Hi community! Would love to talk and get to know what you are working on right now&lt;/p&gt;
&lt;p&gt;Could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a startup or a side project&lt;/li&gt;
&lt;li&gt;open-source tool&lt;/li&gt;
&lt;li&gt;article&lt;/li&gt;
&lt;li&gt;random idea that accidentally became serious&lt;/li&gt;
&lt;li&gt;or maybe you started new course or learned something new &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’ll go first.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/17961/what-have-you-been-working-on-lately</guid>
<pubDate>Fri, 15 May 2026 12:37:01 +0000</pubDate>
</item>
<item>
<title> Building an Email System with Aliases, Routing, and AI Assistance (Wizard Mail)</title>
<link>https://coderlegion.com/17310/building-an-email-system-with-aliases-routing-and-ai-assistance-wizard-mail</link>
<description>&lt;p&gt;When I started building Wizard Ecosystem, I didn’t plan to build an email system.&lt;/p&gt;
&lt;p&gt;It started as a side idea:&lt;/p&gt;
&lt;p&gt;“What if users could have AI-managed email inside the same ecosystem as their assistant, files, and agents?”&lt;/p&gt;
&lt;p&gt;That idea turned into Wizard Mail — a full email client with alias support, IMAP integration, and AI-assisted features.&lt;/p&gt;
&lt;p&gt;This post is about how it actually works, and what I learned building it.&lt;/p&gt;
&lt;p&gt;⚙️ What Wizard Mail is&lt;/p&gt;
&lt;p&gt;Wizard Mail is an email module inside Wizard Ecosystem that:&lt;/p&gt;
&lt;p&gt;connects to external email providers (IMAP-based)&lt;br&gt;
supports sending and receiving emails&lt;br&gt;
organizes inbox, sent, spam, and folders&lt;br&gt;
adds AI features on top of traditional email&lt;/p&gt;
&lt;p&gt;But the most interesting part is not the email client itself.&lt;/p&gt;
&lt;p&gt;It’s email aliases and routing.&lt;/p&gt;
&lt;p&gt; The idea of email aliases&lt;/p&gt;
&lt;p&gt;Instead of just using a normal email like:&lt;/p&gt;
&lt;p&gt;user@gmail.com&lt;/p&gt;
&lt;p&gt;Wizard Mail introduces:&lt;/p&gt;
&lt;p&gt;@wizardecosystem.dpdns.org aliases&lt;/p&gt;
&lt;p&gt;These are virtual addresses that:&lt;/p&gt;
&lt;p&gt;forward messages to a real inbox&lt;br&gt;
can be created dynamically per user&lt;br&gt;
can be used for separation (projects, signups, services)&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;arnav@wizardecosystem.dpdns.org&lt;br&gt;
projects@wizardecosystem.dpdns.org&lt;br&gt;
testing@wizardecosystem.dpdns.org&lt;/p&gt;
&lt;p&gt;All of them map back to a real mailbox internally.&lt;/p&gt;
&lt;p&gt;⚙️ How alias routing works (simplified)&lt;/p&gt;
&lt;p&gt;The system works in layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Incoming email hits alias domain&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Mail is received for:&lt;/p&gt;
&lt;p&gt;alias@wizardecosystem.dpdns.org&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Alias resolution layer&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Backend checks:&lt;/p&gt;
&lt;p&gt;user mapping table&lt;br&gt;
alias → real inbox mapping&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Routing engine&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The system forwards or stores:&lt;/p&gt;
&lt;p&gt;original message metadata&lt;br&gt;
sender + headers&lt;br&gt;
mapped user inbox&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Storage layer&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Emails are then:&lt;/p&gt;
&lt;p&gt;stored in database&lt;br&gt;
categorized into folders&lt;br&gt;
made accessible via API&lt;br&gt;
 Why I built it this way&lt;/p&gt;
&lt;p&gt;Most email systems are:&lt;/p&gt;
&lt;p&gt;provider → inbox → UI&lt;/p&gt;
&lt;p&gt;But I wanted:&lt;/p&gt;
&lt;p&gt;provider → abstraction layer → AI system → user experience&lt;/p&gt;
&lt;p&gt;Because Wizard Ecosystem already has:&lt;/p&gt;
&lt;p&gt;memory systems&lt;br&gt;
AI assistants&lt;br&gt;
multi-agent workflows&lt;/p&gt;
&lt;p&gt;So email becomes:&lt;/p&gt;
&lt;p&gt;not just communication, but structured data inside an AI system&lt;/p&gt;
&lt;p&gt;⚠️ Problems I ran into&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;IMAP inconsistency&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Different providers behave differently:&lt;/p&gt;
&lt;p&gt;Gmail vs Outlook vs Yahoo&lt;br&gt;
folder structures not standardized&lt;br&gt;
delayed sync behavior&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Alias complexity&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Aliases sound simple, but:&lt;/p&gt;
&lt;p&gt;forwarding loops can happen&lt;br&gt;
mapping conflicts need strict validation&lt;br&gt;
spam handling becomes harder&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Email parsing edge cases&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Emails are messy:&lt;/p&gt;
&lt;p&gt;broken headers&lt;br&gt;
weird encodings&lt;br&gt;
inconsistent formatting&lt;/p&gt;
&lt;p&gt;So a lot of time went into:&lt;/p&gt;
&lt;p&gt;cleaning and normalizing email data&lt;/p&gt;
&lt;p&gt; Where AI actually helps&lt;/p&gt;
&lt;p&gt;This is where Wizard Ecosystem becomes interesting.&lt;/p&gt;
&lt;p&gt;On top of email, I added AI features:&lt;/p&gt;
&lt;p&gt;summarizing long email threads&lt;br&gt;
extracting key actions&lt;br&gt;
categorizing messages automatically&lt;br&gt;
helping draft replies&lt;/p&gt;
&lt;p&gt;So instead of:&lt;/p&gt;
&lt;p&gt;“read inbox manually”&lt;/p&gt;
&lt;p&gt;it becomes:&lt;/p&gt;
&lt;p&gt;“AI-assisted inbox processing”&lt;/p&gt;
&lt;p&gt;⚙️ Key insight&lt;/p&gt;
&lt;p&gt;The most important realization was:&lt;/p&gt;
&lt;p&gt;Email is not a messaging problem — it’s a data routing problem.&lt;/p&gt;
&lt;p&gt;Once you think of it that way, everything changes:&lt;/p&gt;
&lt;p&gt;aliases become routing rules&lt;br&gt;
inbox becomes structured storage&lt;br&gt;
threads become data graphs&lt;br&gt;
 What I learned&lt;/p&gt;
&lt;p&gt;Building Wizard Mail taught me:&lt;/p&gt;
&lt;p&gt;simple features hide complex backend systems&lt;br&gt;
alias systems require careful routing logic&lt;br&gt;
email is much more “messy data” than expected&lt;br&gt;
AI adds value only after structure is clean&lt;br&gt;
abstraction layers matter more than UI&lt;br&gt;
 Final thought&lt;/p&gt;
&lt;p&gt;Wizard Mail started as a small feature idea.&lt;/p&gt;
&lt;p&gt;It became:&lt;/p&gt;
&lt;p&gt;a routing + abstraction layer between external communication and internal AI systems&lt;/p&gt;
&lt;p&gt;And it changed how I think about system design in general.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17310/building-an-email-system-with-aliases-routing-and-ai-assistance-wizard-mail</guid>
<pubDate>Sun, 10 May 2026 17:42:50 +0000</pubDate>
</item>
<item>
<title>I Built a Free Online Charades Game With 500+ Words, a Live Timer, and Team Scoring No App, No Sig</title>
<link>https://coderlegion.com/15810/i-built-a-free-online-charades-game-with-500-words-a-live-timer-and-team-scoring-no-app-no-sig</link>
<description>&lt;p&gt;I play charades at family gatherings. Every time, someone spends 10 minutes writing words on paper slips, someone loses the slips, someone argues about the score, and the phone timer app keeps locking the screen mid-countdown.&lt;br&gt;
So I built a browser-based charades game that handles all of it. One link, open it, start playing in 10 seconds.&lt;br&gt;
Here is what it does and how I built it.&lt;/p&gt;
&lt;p&gt;What It Is&lt;br&gt;
The &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/charades-generator/&quot;&gt;Charades Generator&lt;/a&gt; is a complete game system that runs entirely in your browser. No download, no account, works on any phone.&lt;br&gt;
Features:&lt;/p&gt;
&lt;p&gt;500+ words and phrases across 10 categories&lt;br&gt;
Built-in countdown timer with visual pressure cues&lt;br&gt;
Live team score tracker with round summaries&lt;br&gt;
Difficulty levels: Easy, Medium, Hard&lt;br&gt;
Adult 18+ mode, Christmas pack, Movie charades, Animal charades&lt;br&gt;
Fully customizable: rounds, cards per round, timer length, team names&lt;/p&gt;
&lt;p&gt;The Word Bank Architecture&lt;br&gt;
The words are organized into category objects. Each word has three properties: the phrase, a difficulty level, and an optional hint.&lt;br&gt;
javascriptconst categories = {&lt;br&gt;
  general: [&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ word: &quot;Skydiving&quot;, difficulty: &quot;medium&quot;, hint: &quot;extreme sport&quot; },
{ word: &quot;Invisible Man&quot;, difficulty: &quot;hard&quot;, hint: &quot;classic concept&quot; },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  ],&lt;br&gt;
  movies: [&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ word: &quot;Inception&quot;, difficulty: &quot;medium&quot;, hint: &quot;dreams&quot; },
{ word: &quot;Titanic&quot;, difficulty: &quot;easy&quot;, hint: &quot;ship&quot; },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  ],&lt;br&gt;
  animals: [&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ word: &quot;Platypus&quot;, difficulty: &quot;hard&quot;, hint: &quot;unusual mammal&quot; },
{ word: &quot;Dog&quot;, difficulty: &quot;easy&quot;, hint: null },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  ]&lt;br&gt;
};&lt;br&gt;
When the user picks categories and a difficulty level, the system filters across all selected categories and builds a flat deck. The deck gets shuffled with a Fisher-Yates shuffle before the game starts.&lt;br&gt;
javascriptfunction buildDeck(selectedCategories, difficulty) {&lt;br&gt;
  let deck = [];&lt;br&gt;
  selectedCategories.forEach(cat =&amp;gt; {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const words = categories[cat].filter(w =&amp;gt;
  difficulty === &quot;all&quot; || w.difficulty === difficulty
);
deck = deck.concat(words);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  });&lt;br&gt;
  return shuffleDeck(deck);&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;function shuffleDeck(deck) {&lt;br&gt;
  for (let i = deck.length - 1; i &amp;gt; 0; i--) {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const j = Math.floor(Math.random() * (i + 1));
[deck[i], deck[j]] = [deck[j], deck[i]];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  }&lt;br&gt;
  return deck;&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;The Timer With Visual Pressure&lt;br&gt;
A plain countdown is boring. I wanted players to feel the pressure build as time runs out.&lt;br&gt;
The timer ring changes color based on time remaining. It turns orange at 15 seconds and red at 5. The CSS transition handles the color shift smoothly.&lt;br&gt;
javascriptfunction updateTimerDisplay(secondsLeft, totalSeconds) {&lt;br&gt;
  const ring = document.getElementById('timerRing');&lt;/p&gt;
&lt;p&gt;  if (secondsLeft &amp;lt;= 5) {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ring.style.borderColor = '#ef4444'; // red
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  } else if (secondsLeft &amp;lt;= 15) {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ring.style.borderColor = '#f97316'; // orange
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  } else {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ring.style.borderColor = '#8b5cf6'; // default purple
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  }&lt;/p&gt;
&lt;p&gt;  document.getElementById('timerDisplay').textContent = secondsLeft;&lt;br&gt;
}&lt;br&gt;
Players notice the color change before they check the number. It creates urgency without them thinking about it.&lt;/p&gt;
&lt;p&gt;Score Tracking Across Rounds&lt;br&gt;
Each team has a running total plus a per-round breakdown. At the end of each round, a summary screen shows what each team scored before switching turns.&lt;br&gt;
javascriptconst gameState = {&lt;br&gt;
  teams: {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;team1: { name: &quot;Team 1&quot;, total: 0, rounds: [] },
team2: { name: &quot;Team 2&quot;, total: 0, rounds: [] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  },&lt;br&gt;
  currentRound: 1,&lt;br&gt;
  currentTeam: &quot;team1&quot;&lt;br&gt;
};&lt;/p&gt;
&lt;p&gt;function recordScore(teamId, points) {&lt;br&gt;
  gameState.teams[teamId].total += points;&lt;br&gt;
  gameState.teams[teamId].rounds.push(points);&lt;br&gt;
}&lt;br&gt;
No paper. No arguments. The scoreboard stays visible throughout the entire game.&lt;/p&gt;
&lt;p&gt;What Broke During Testing&lt;/p&gt;
&lt;p&gt;The Adult 18+ category needed to be completely isolated from family categories. Early builds let filters bleed across category selections when users switched between Quick Bundle presets rapidly.&lt;br&gt;
Fix: reset the entire category selection state whenever a Quick Bundle button is pressed, then apply only that bundle's categories fresh. No carry-over from previous selections.&lt;br&gt;
The second issue was the timer continuing to tick after a player pressed &quot;Got It&quot; or &quot;Skip&quot; on the last card. The interval kept running in the background. Fix was simple: always call clearInterval(timerInterval) before any card transition, not just on timeout.&lt;/p&gt;
&lt;p&gt;Why Client-Side Architecture Matters Here&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/charades-generator/&quot;&gt;Charades Generator&lt;/a&gt; runs zero server calls during gameplay. The entire word bank loads once with the page. The timer, scoring, and card logic all run in the browser tab.&lt;br&gt;
This is the same approach I use across all tools at &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/&quot;&gt;stackflowtools.com&lt;/a&gt;. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/background-remover/&quot;&gt;background remover&lt;/a&gt; processes images without uploading them. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/image-size-compressor/&quot;&gt;image compressor&lt;/a&gt; runs entirely on-device. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/avif-to-jpg-converter/&quot;&gt;AVIF to JPG converter&lt;/a&gt; uses the Canvas API to handle conversion in the browser tab. No file ever leaves the user's device.&lt;br&gt;
For a game tool, the benefit is different. No server means no latency between cards. The game feels instant because it is. There is no network request between pressing &quot;Next Card&quot; and seeing the next word.&lt;/p&gt;
&lt;p&gt;By the Numbers&lt;br&gt;
FeatureDetailTotal words500+Categories10Timer options30s, 60s, 90s, 2 minRound options3, 5, 7, 10Cards per round3, 5, 7, 10Server requests during game0Signup requiredNo&lt;/p&gt;
&lt;p&gt;Try It&lt;br&gt;
Open &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/charades-generator/&quot;&gt;stackflowtools.com/charades-generator&lt;/a&gt; on your phone, pick your categories, and start playing. Setup takes under 30 seconds.&lt;br&gt;
If you are building other party game tools, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/random-animal-generator/&quot;&gt;Random Animal Generator&lt;/a&gt; on the same site follows a similar word-bank approach and works well as a kids game companion. For game nights that need a name generator, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/free-fire-stylish-name-generator/&quot;&gt;Free Fire Stylish Name Generator&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/pubg-name-generator/&quot;&gt;PUBG Name Generator&lt;/a&gt; are also part of the same tool suite at &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/&quot;&gt;stackflowtools.com&lt;/a&gt;.&lt;br&gt;
If you have questions about the deck logic, timer behavior, category isolation, or state management across rounds, drop a comment below.&lt;/p&gt;
&lt;p&gt;Sohaib Khan builds free browser-based tools at &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackflowtools.com/&quot;&gt;stackflowtools.com&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15810/i-built-a-free-online-charades-game-with-500-words-a-live-timer-and-team-scoring-no-app-no-sig</guid>
<pubDate>Sat, 25 Apr 2026 14:15:02 +0000</pubDate>
</item>
<item>
<title> The Indie Hacker’s Manifesto: Stop Coding, Start Shipping</title>
<link>https://coderlegion.com/15805/the-indie-hackers-manifesto-stop-coding-start-shipping</link>
<description>&lt;h3 id=&quot;i-strategy-finding-the-arbitrage-of-mediocrity-&quot;&gt;I. Strategy: Finding the &quot;Arbitrage of Mediocrity&quot;&lt;/h3&gt;
&lt;h4 id=&quot;1-the-differentiation-framework&quot;&gt;1. The Differentiation Framework&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The &quot;Goldilocks&quot; Niche:&lt;/strong&gt; Don’t go so niche that the market doesn’t exist. If there’s zero competition, there’s likely zero money. Aim for &quot;Niche but Scalable.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dimensional Attacks:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Attack Open Source:&lt;/strong&gt; Most OSS tools are powerful but &quot;ugly,&quot; hard to configure, and lack support. Your opportunity? Build a &lt;strong&gt;Better UI/UX Wrapper&lt;/strong&gt; that provides a &quot;plug-and-play&quot; experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unbundle the Giants:&lt;/strong&gt; SaaS giants (Salesforce, Adobe, Jira) are bloated, expensive, and forced subscriptions. Your opportunity? Build a &lt;strong&gt;Lightweight, Privacy-First, One-Time Payment&lt;/strong&gt; alternative for just &lt;em&gt;one&lt;/em&gt; specific feature.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The 3-Star Arbitrage:&lt;/strong&gt; Scour Product Hunt or the App Store for famous products with a &lt;strong&gt;3.2-star rating&lt;/strong&gt;. Read the complaints. Fix that &lt;em&gt;one&lt;/em&gt; specific pain point and release it as a standalone micro-tool.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-geopolitical-arbitrage&quot;&gt;2. Geopolitical Arbitrage&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Willingness to Pay (WTP):&lt;/strong&gt; Target markets with high disposable income and a culture of paying for software (North America, DACH region, UK). &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ignore Low-Value Noise:&lt;/strong&gt; Don't waste your support time on markets that demand &quot;free&quot; unless you are building a non-profit.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ii-development-escaping-the-productivity-porn-trap&quot;&gt;II. Development: Escaping the &quot;Productivity Porn&quot; Trap&lt;/h3&gt;
&lt;h4 id=&quot;3-the-embarrassment-principle&quot;&gt;3. The &quot;Embarrassment&quot; Principle&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Core Logic Only:&lt;/strong&gt; If a feature doesn't solve the primary pain point, it’s a distraction.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Reid Hoffman Rule:&lt;/strong&gt; &quot;If you are not embarrassed by the first version of your product, you’ve launched too late.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;4-the-developer-s-comfort-zone&quot;&gt;4. The Developer’s Comfort Zone&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stop Building Tools for Yourself:&lt;/strong&gt; Spending three weeks writing a &quot;custom Twitter bot&quot; instead of actually talking to users is just &lt;strong&gt;procrastination disguised as work.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The 70/30 Rule:&lt;/strong&gt; Spend &lt;strong&gt;30%&lt;/strong&gt; of your time coding and &lt;strong&gt;70%&lt;/strong&gt; marketing. Great code that nobody finds is just a graveyard of wasted hours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code Freeze:&lt;/strong&gt; Once you launch, stop touching the CSS. Unless it’s a critical bug, put the keyboard away and pick up the megaphone.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;iii-marketing-weaponizing-distribution&quot;&gt;III. Marketing: Weaponizing Distribution&lt;/h3&gt;
&lt;h4 id=&quot;5-high-roi-channels&quot;&gt;5. High-ROI Channels&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Channel &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Why it Works &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;The Catch&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Reddit&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;High intent, niche subreddits. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;They hate &quot;Salesmen.&quot; Must provide value.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;X / Twitter&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;The home of &quot;Building in Public.&quot; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Requires consistent personality.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Hacker News&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Can trigger massive viral traffic. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Highly critical audience. No marketing fluff allowed.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Product Hunt&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;The &quot;Grand Opening&quot; of the tech world. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Only lasts 24 hours. Needs a launch strategy.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;6-fighting-the-algorithm&quot;&gt;6. Fighting the Algorithm&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Human-to-Human (H2H):&lt;/strong&gt; Platforms hate &quot;Corporate&quot; ads. Use &lt;strong&gt;Storytelling&lt;/strong&gt;. Talk about your failures, your tech stack, or &quot;The one thing I learned building X.&quot; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Incognito Test:&lt;/strong&gt; Always check your posts in &lt;strong&gt;Incognito/Private mode&lt;/strong&gt;. If you don't see it, you've been shadowbanned or filtered. Don't wait for stats—verify visibility immediately.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;iv-the-moat-trust-amp-passive-traffic&quot;&gt;IV. The Moat: Trust &amp;amp; Passive Traffic&lt;/h3&gt;
&lt;h4 id=&quot;7-trust-engineering&quot;&gt;7. Trust Engineering&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Show, Don't Tell:&lt;/strong&gt; In an era of AI scams, trust is the only currency. Offer &quot;Local-First&quot; processing, &quot;No-Tracking&quot; guarantees, or high-quality interactive Demos that don't require an email signup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero-Friction Entry:&lt;/strong&gt; Reduce the &quot;Aha! Moment&quot; time. If I have to confirm my email and set up a profile just to see how your tool works, I'm already gone.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;8-aieo-ai-engine-optimization-&quot;&gt;8. AIEO (AI Engine Optimization)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Programmatic SEO:&lt;/strong&gt; Create landing pages for &quot;Long-tail&quot; keywords (e.g., &quot;Best lightweight Alternative to X for Mac&quot;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The llms.txt Standard:&lt;/strong&gt; Deploy an &lt;code&gt;llms.txt&lt;/code&gt; file in your root directory. This provides a clean, markdown-based summary for LLMs (ChatGPT, Perplexity, Claude) to read. When a user asks an AI for a &quot;tool that does X,&quot; you want the AI to recommend &lt;em&gt;you&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;v-post-launch-surviving-the-trough-of-sorrow-&quot;&gt;V. Post-Launch: Surviving the &quot;Trough of Sorrow&quot;&lt;/h3&gt;
&lt;h4 id=&quot;9-the-72-hour-rule&quot;&gt;9. The 72-Hour Rule&lt;/h4&gt;
&lt;p&gt;After the initial launch buzz, your traffic will drop to near-zero. &lt;strong&gt;This is normal.&lt;/strong&gt; Do not panic. Do not rewrite the codebase. Stick to your marketing plan for at least three days before making a pivot.&lt;/p&gt;
&lt;h4 id=&quot;10-the-asset-mindset&quot;&gt;10. The Asset Mindset&lt;/h4&gt;
&lt;p&gt;Everything you build—the domain, the code snippets, the backlinked landing pages—is &lt;strong&gt;Digital Real Estate.&lt;/strong&gt; Even if the product fails to generate revenue today, the &quot;Domain Authority&quot; and the &quot;Skill&quot; you’ve built are assets for your next ship.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;The Bottom Line:&lt;/strong&gt; Be a &lt;strong&gt;Marketer who knows how to Code&lt;/strong&gt;, not a Programmer who &quot;tries&quot; to Market.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://we1.top&quot;&gt;author&lt;/a&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://airouter.me&quot;&gt;web&lt;/a&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://nestifyapp.org&quot;&gt;Organization&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;buildinpublic-indiehackers-solopreneur&quot;&gt;BuildInPublic #IndieHackers #Solopreneur&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15805/the-indie-hackers-manifesto-stop-coding-start-shipping</guid>
<pubDate>Sat, 25 Apr 2026 11:39:31 +0000</pubDate>
</item>
<item>
<title>How I Built LoopSignal: Public Feedback Boards, GitHub Sync, and Changelogs for Indie Developers</title>
<link>https://coderlegion.com/14675/how-built-loopsignal-public-feedback-boards-github-sync-and-changelogs-indie-developers</link>
<description>&lt;p&gt;If you are building a SaaS, user feedback usually ends up everywhere except the one place where it is actually useful.&lt;/p&gt;
&lt;p&gt;Some requests arrive through email. Some show up in support chats. A few land in GitHub issues. One power user sends a long DM. Then roadmap time comes around and you realize you are mostly guessing.&lt;/p&gt;
&lt;p&gt;That frustration is what led me to build &lt;a rel=&quot;nofollow&quot; href=&quot;https://loopsignal.dev/&quot;&gt;&lt;strong&gt;LoopSignal&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;LoopSignal is a lightweight feedback platform for indie developers and small teams. It gives every product a public board where users can submit ideas and vote on requests, a public changelog to show what shipped, a widget you can embed inside your app, and a GitHub integration that connects feedback directly to development work.&lt;/p&gt;
&lt;p&gt;The goal was simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make feedback easy to collect.&lt;/li&gt;
&lt;li&gt;Make prioritization obvious.&lt;/li&gt;
&lt;li&gt;Make it easy to show users that their feedback mattered.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That last part is the reason for the name. I did not just want a feature request board. I wanted a tool that &lt;strong&gt;closed the loop&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-i-wanted-to-solve&quot;&gt;The problem I wanted to solve&lt;/h3&gt;
&lt;p&gt;Most feedback tools I looked at fell into one of two buckets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They were powerful, polished, and priced for larger product teams.&lt;/li&gt;
&lt;li&gt;They were simple, but too limited or too disconnected from a developer-led workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For indie hackers and small SaaS teams, that leaves an awkward gap.&lt;/p&gt;
&lt;p&gt;You do not need an enterprise product suite just to answer questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What are users asking for most often?&lt;/li&gt;
&lt;li&gt;Which requests are repeated by multiple customers?&lt;/li&gt;
&lt;li&gt;What should I build next?&lt;/li&gt;
&lt;li&gt;How do I tell users their request is now shipped?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You need a system that is small, fast, and opinionated in the right ways.&lt;/p&gt;
&lt;p&gt;I wanted something that let me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;collect feedback without forcing end users to create accounts&lt;/li&gt;
&lt;li&gt;let people vote so demand becomes visible&lt;/li&gt;
&lt;li&gt;moderate low-quality or spammy posts&lt;/li&gt;
&lt;li&gt;push approved requests into GitHub automatically&lt;/li&gt;
&lt;li&gt;publish a changelog from shipped work&lt;/li&gt;
&lt;li&gt;drop a widget into any app with one script tag&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That became the product spec for LoopSignal.&lt;/p&gt;
&lt;h3 id=&quot;what-loopsignal-does&quot;&gt;What LoopSignal does&lt;/h3&gt;
&lt;p&gt;At a high level, each project in LoopSignal gets its own public feedback workflow.&lt;/p&gt;
&lt;p&gt;Here is what that includes today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Public feedback boards&lt;/strong&gt; where users can submit feature requests and vote on existing ones&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Anonymous participation&lt;/strong&gt; so end users do not need accounts just to leave feedback&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflow states&lt;/strong&gt; like Open, Planned, In Progress, Completed, and Closed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Moderation and spam checks&lt;/strong&gt; to keep the board usable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub integration&lt;/strong&gt; that creates issues from approved feedback&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub webhook sync&lt;/strong&gt; so closing an issue can mark the related request as completed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Public changelog pages&lt;/strong&gt; generated from shipped work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embeddable widget&lt;/strong&gt; that works with a single script tag&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Email notifications&lt;/strong&gt; when a request changes status&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team roles&lt;/strong&gt; for owner, admin, and member access&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analytics and CSV export&lt;/strong&gt; so teams can understand what is happening on the board&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API access&lt;/strong&gt; for teams that want to script or automate around it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The workflow I cared about most looked like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user submits feedback from a public board or an in-app widget.&lt;/li&gt;
&lt;li&gt;Other users vote on it.&lt;/li&gt;
&lt;li&gt;The team reviews and approves it.&lt;/li&gt;
&lt;li&gt;LoopSignal creates a GitHub issue automatically.&lt;/li&gt;
&lt;li&gt;The issue gets worked on in the normal development flow.&lt;/li&gt;
&lt;li&gt;When the issue is closed, the feedback item becomes completed and shows up in the public changelog.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That is the whole product in one loop.&lt;/p&gt;
&lt;h3 id=&quot;why-i-built-it-for-developers-first&quot;&gt;Why I built it for developers first&lt;/h3&gt;
&lt;p&gt;A lot of feedback tools are built like product management software. That makes sense for larger organizations, but it also creates friction for small teams.&lt;/p&gt;
&lt;p&gt;Small teams do not want another heavyweight system to maintain. They want something that fits the tools they already use.&lt;/p&gt;
&lt;p&gt;That is why LoopSignal is intentionally developer-friendly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub is a first-class part of the workflow&lt;/li&gt;
&lt;li&gt;the widget is just a script tag&lt;/li&gt;
&lt;li&gt;the product exposes basic API endpoints&lt;/li&gt;
&lt;li&gt;the public board is simple enough that users can start contributing immediately&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wanted the setup to feel closer to &quot;connect repo, share link, start collecting signal&quot; than &quot;spend a week configuring a process.&quot;&lt;/p&gt;
&lt;h3 id=&quot;the-stack-behind-it&quot;&gt;The stack behind it&lt;/h3&gt;
&lt;p&gt;I built LoopSignal with a stack that is well suited to founder-led SaaS products:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Next.js 15&lt;/strong&gt; for the app, routing, server rendering, and server actions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React 19&lt;/strong&gt; for the UI layer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supabase&lt;/strong&gt; for Postgres, auth, and database access&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Workers&lt;/strong&gt; for deployment via OpenNext&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paddle&lt;/strong&gt; for billing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resend&lt;/strong&gt; for transactional emails&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tailwind CSS + shadcn/ui&lt;/strong&gt; for the UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This stack gave me a nice balance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fast iteration&lt;/li&gt;
&lt;li&gt;a single TypeScript codebase&lt;/li&gt;
&lt;li&gt;no separate backend service to maintain&lt;/li&gt;
&lt;li&gt;enough structure to support auth, billing, and multi-project access cleanly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;how-the-core-product-flow-works&quot;&gt;How the core product flow works&lt;/h3&gt;
&lt;h4 id=&quot;1-public-submission-without-account-friction&quot;&gt;1. Public submission without account friction&lt;/h4&gt;
&lt;p&gt;One of the easiest ways to kill feedback volume is to ask users to create an account before they can submit an idea.&lt;/p&gt;
&lt;p&gt;LoopSignal avoids that. Users can submit feedback from the public board or embedded widget with an optional email if they want updates later.&lt;/p&gt;
&lt;p&gt;The submission flow does a few things before a post is created:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;validates the title and description&lt;/li&gt;
&lt;li&gt;rate-limits submissions&lt;/li&gt;
&lt;li&gt;runs a simple spam scoring pass&lt;/li&gt;
&lt;li&gt;stores the post as &lt;code&gt;pending&lt;/code&gt; for moderation&lt;/li&gt;
&lt;li&gt;auto-subscribes the submitter for future status emails&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the heart of that server action:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const spam = checkSpam({ title, description, email, ip });
if (spam.isSpam) {
  await logSpam(ip, &quot;spam_detected&quot;, spam.signals, spam.score, {
    title,
    description,
    email,
  });
  return { error: &quot;Your submission was flagged.&quot; };
}

const { data: newPost } = await admin
  .from(&quot;posts&quot;)
  .insert({
    project_id: projectId,
    title,
    description,
    category,
    email: email || user?.email || null,
    ip,
    spam_score: spam.score,
    moderation_status: &quot;pending&quot;,
    workflow_status: &quot;open&quot;,
  })
  .select(&quot;id&quot;)
  .single();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is intentionally simple. The product is optimized for useful signal, not for forcing people through a heavy workflow.&lt;/p&gt;
&lt;h4 id=&quot;2-voting-as-lightweight-prioritization&quot;&gt;2. Voting as lightweight prioritization&lt;/h4&gt;
&lt;p&gt;Once a post is approved, it appears on the public board where users can vote on it.&lt;/p&gt;
&lt;p&gt;That matters because raw feedback is noisy. Voting turns isolated requests into visible demand. It gives teams a better signal than the loudest customer email.&lt;/p&gt;
&lt;p&gt;On the public board, users can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sort by most voted or newest&lt;/li&gt;
&lt;li&gt;filter by status&lt;/li&gt;
&lt;li&gt;filter by category&lt;/li&gt;
&lt;li&gt;open individual request pages&lt;/li&gt;
&lt;li&gt;see admin replies directly under posts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That keeps the board useful as it grows instead of turning into a long unstructured list.&lt;/p&gt;
&lt;h4 id=&quot;3-github-issue-creation-on-approval&quot;&gt;3. GitHub issue creation on approval&lt;/h4&gt;
&lt;p&gt;The most important product decision I made was to connect approved feedback directly to GitHub.&lt;/p&gt;
&lt;p&gt;Once a request is approved, LoopSignal can create a GitHub issue automatically for the selected repository. The issue body includes the feedback description and gets labeled as &lt;code&gt;feedback&lt;/code&gt;, plus the category if one exists.&lt;/p&gt;
&lt;p&gt;This is the core issue creation logic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const body = [
  description || &quot;&quot;,
  &quot;&quot;,
  &quot;---&quot;,
  &quot;*Created from LoopSignal feedback*&quot;,
].join(&quot;\n&quot;);

await fetch(`https://api.github.com/repos/${owner}/${repo}/issues`, {
  method: &quot;POST&quot;,
  headers: {
    Authorization: `Bearer ${githubToken}`,
    Accept: &quot;application/vnd.github+json&quot;,
    &quot;Content-Type&quot;: &quot;application/json&quot;,
    &quot;User-Agent&quot;: &quot;LoopSignal&quot;,
  },
  body: JSON.stringify({ title, body, labels }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This sounds small, but it changes the product from &quot;a place where ideas go to sit&quot; into &quot;a place where ideas can enter the actual engineering workflow.&quot;&lt;/p&gt;
&lt;h4 id=&quot;4-closing-the-loop-with-github-webhooks&quot;&gt;4. Closing the loop with GitHub webhooks&lt;/h4&gt;
&lt;p&gt;Creating an issue is only half the story.&lt;/p&gt;
&lt;p&gt;The more interesting part is the reverse sync. When a GitHub issue is closed, LoopSignal looks up the linked post and updates the workflow status to &lt;code&gt;completed&lt;/code&gt;. If the issue is reopened, the post moves back to &lt;code&gt;open&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That logic is intentionally tiny:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;if (action === &quot;closed&quot;) {
  workflowStatus = &quot;completed&quot;;
} else if (action === &quot;reopened&quot;) {
  workflowStatus = &quot;open&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is one of my favorite parts of the app because it removes a manual step. Teams do not have to remember to update the feedback board after shipping. Their existing development workflow updates it for them.&lt;/p&gt;
&lt;h4 id=&quot;5-public-changelog-generation&quot;&gt;5. Public changelog generation&lt;/h4&gt;
&lt;p&gt;Once a post reaches a shipped state, it can appear on the project's public changelog page.&lt;/p&gt;
&lt;p&gt;That matters because collecting feedback is only half the job. Users need to see that something happened because they spoke up.&lt;/p&gt;
&lt;p&gt;A good changelog does a few things at once:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;shows active development&lt;/li&gt;
&lt;li&gt;builds trust with current users&lt;/li&gt;
&lt;li&gt;gives potential customers confidence that the product is alive&lt;/li&gt;
&lt;li&gt;makes submitters more likely to contribute again&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In LoopSignal, the changelog is just another view of the same underlying feedback data. That keeps the system simple and makes the &quot;close the loop&quot; story visible.&lt;/p&gt;
&lt;h4 id=&quot;6-embeddable-widget-with-one-script-tag&quot;&gt;6. Embeddable widget with one script tag&lt;/h4&gt;
&lt;p&gt;I also wanted the product to work inside existing apps without requiring a deep integration.&lt;/p&gt;
&lt;p&gt;That led to a simple embed script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script
  src=&quot;https://loopsignal.dev/embed.js&quot;
  data-slug=&quot;your-project&quot;
  defer
&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script renders an iframe-based widget. It can run in a floating mode with a feedback button or in an inline mode inside a chosen container.&lt;/p&gt;
&lt;p&gt;The important part is not technical complexity. It is setup speed. If someone can paste one script and start collecting feedback in two minutes, they are much more likely to actually use the feature.&lt;/p&gt;
&lt;h3 id=&quot;a-few-implementation-details-i-cared-about&quot;&gt;A few implementation details I cared about&lt;/h3&gt;
&lt;h4 id=&quot;server-actions-for-mutations&quot;&gt;Server actions for mutations&lt;/h4&gt;
&lt;p&gt;I used server actions for most write operations. For a product like this, they are a good fit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fewer separate mutation endpoints to manage&lt;/li&gt;
&lt;li&gt;direct access to auth context and headers&lt;/li&gt;
&lt;li&gt;straightforward cache revalidation with &lt;code&gt;revalidatePath&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That made actions like submitting feedback, voting, updating statuses, and managing settings feel compact and easy to reason about.&lt;/p&gt;
&lt;h4 id=&quot;supabase-for-auth-and-data-but-with-careful-access-checks&quot;&gt;Supabase for auth and data, but with careful access checks&lt;/h4&gt;
&lt;p&gt;Supabase gave me a lot quickly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Postgres&lt;/li&gt;
&lt;li&gt;auth flows&lt;/li&gt;
&lt;li&gt;admin access for privileged operations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But once team-based access enters the picture, things get more complicated. Ownership, admin permissions, member permissions, public access, and API access all intersect in subtle ways.&lt;/p&gt;
&lt;p&gt;I ended up centralizing access checks in auth helpers so permission logic was not duplicated all over the app. That was much easier to maintain than sprinkling project ownership checks throughout every action and page.&lt;/p&gt;
&lt;h4 id=&quot;simple-anti-spam-instead-of-overengineering&quot;&gt;Simple anti-spam instead of overengineering&lt;/h4&gt;
&lt;p&gt;For public boards, spam is not optional. If anyone can submit, someone eventually will submit garbage.&lt;/p&gt;
&lt;p&gt;Instead of starting with a complicated external moderation service, I began with a simple scoring system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;all-caps titles&lt;/li&gt;
&lt;li&gt;too many URLs&lt;/li&gt;
&lt;li&gt;repetitive characters&lt;/li&gt;
&lt;li&gt;suspicious email patterns&lt;/li&gt;
&lt;li&gt;known spam phrases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is enough to block obvious junk while keeping the product lightweight.&lt;/p&gt;
&lt;h4 id=&quot;basic-api-surface-for-power-users&quot;&gt;Basic API surface for power users&lt;/h4&gt;
&lt;p&gt;LoopSignal also exposes a small API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /api/v1/[slug]/posts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/v1/[slug]/posts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /api/v1/[slug]/stats&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The write endpoint uses an &lt;code&gt;x-api-key&lt;/code&gt; header and is gated to the Pro plan. I wanted the API to be useful for simple automation without expanding the scope into a huge platform surface area too early.&lt;/p&gt;
&lt;h3 id=&quot;the-hardest-parts-of-building-it&quot;&gt;The hardest parts of building it&lt;/h3&gt;
&lt;p&gt;No SaaS build is complete without a few annoying edges.&lt;/p&gt;
&lt;p&gt;Here are the biggest ones I ran into:&lt;/p&gt;
&lt;h4 id=&quot;1-deployment-on-cloudflare-workers&quot;&gt;1. Deployment on Cloudflare Workers&lt;/h4&gt;
&lt;p&gt;Running Next.js 15 on Cloudflare Workers is possible, but not frictionless. OpenNext helps a lot, but you still have to think about environment assumptions, missing Node APIs, and deployment quirks.&lt;/p&gt;
&lt;p&gt;I like the performance and cost profile, but it definitely required more care than a default Vercel deployment.&lt;/p&gt;
&lt;h4 id=&quot;2-permissions-get-complex-fast&quot;&gt;2. Permissions get complex fast&lt;/h4&gt;
&lt;p&gt;The moment a product supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;project owners&lt;/li&gt;
&lt;li&gt;admins&lt;/li&gt;
&lt;li&gt;members&lt;/li&gt;
&lt;li&gt;public users&lt;/li&gt;
&lt;li&gt;API clients&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;...your permission model gets real very quickly.&lt;/p&gt;
&lt;p&gt;What looks simple at the UI level can turn messy in the backend if you do not centralize access control early.&lt;/p&gt;
&lt;h4 id=&quot;3-billing-logic-touches-more-of-the-app-than-you-expect&quot;&gt;3. Billing logic touches more of the app than you expect&lt;/h4&gt;
&lt;p&gt;Billing was one of those features that looked isolated at first and then ended up touching a lot of product behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;project limits&lt;/li&gt;
&lt;li&gt;team member limits&lt;/li&gt;
&lt;li&gt;Pro-only analytics&lt;/li&gt;
&lt;li&gt;Pro-only API access&lt;/li&gt;
&lt;li&gt;expired trial handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is a good reminder that billing is rarely &quot;just a checkout page.&quot;&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned-from-building-loopsignal&quot;&gt;What I learned from building LoopSignal&lt;/h3&gt;
&lt;p&gt;The biggest lesson was that product scope matters more than feature count.&lt;/p&gt;
&lt;p&gt;It would have been easy to keep adding &quot;nice to have&quot; ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;more integrations&lt;/li&gt;
&lt;li&gt;custom branding controls everywhere&lt;/li&gt;
&lt;li&gt;more advanced automation&lt;/li&gt;
&lt;li&gt;internal product planning features&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the product gets stronger when the core loop stays obvious:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;collect feedback -&amp;gt; validate demand -&amp;gt; sync to development -&amp;gt; show what shipped&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Everything in LoopSignal is there to support that loop.&lt;/p&gt;
&lt;p&gt;The second big lesson is that feedback products live or die on friction.&lt;/p&gt;
&lt;p&gt;If users need too many steps to submit feedback, they will not do it.&lt;br&gt;
If teams need too many steps to act on feedback, they will ignore it.&lt;br&gt;
If nobody sees what shipped, the system does not build trust.&lt;/p&gt;
&lt;p&gt;Reducing friction at each of those points mattered more than adding complexity.&lt;/p&gt;
&lt;h3 id=&quot;who-i-built-it-for&quot;&gt;Who I built it for&lt;/h3&gt;
&lt;p&gt;LoopSignal is aimed at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;solo founders&lt;/li&gt;
&lt;li&gt;indie hackers&lt;/li&gt;
&lt;li&gt;small SaaS teams&lt;/li&gt;
&lt;li&gt;developer-led products that already live in GitHub&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is not trying to be an all-in-one enterprise product management platform.&lt;/p&gt;
&lt;p&gt;It is trying to be the fastest way for a small team to go from scattered user requests to a visible, structured product feedback workflow.&lt;/p&gt;
&lt;p&gt;Pricing reflects that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Starter: $19/month&lt;/li&gt;
&lt;li&gt;Pro: $49/month&lt;/li&gt;
&lt;li&gt;14-day free trial with full Pro access&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Starter plan covers the public board, changelog, widget, GitHub integration, notifications, and CSV export. Pro adds things like API access and analytics, plus unlimited projects and team members.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;I built LoopSignal because I wanted a feedback tool that fit the way small teams actually work.&lt;/p&gt;
&lt;p&gt;Not every product needs a giant roadmap suite. Sometimes you just need one clear place to collect requests, let users vote, connect the important ones to GitHub, and show people that you shipped what mattered.&lt;/p&gt;
&lt;p&gt;That is what LoopSignal is for.&lt;/p&gt;
&lt;p&gt;If you want to check it out, the product is live at &lt;a rel=&quot;nofollow&quot; href=&quot;https://loopsignal.dev&quot;&gt;loopsignal.dev&lt;/a&gt;. I am also dogfooding it by collecting feedback about LoopSignal on LoopSignal itself, which felt like the right way to launch a product like this.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14675/how-built-loopsignal-public-feedback-boards-github-sync-and-changelogs-indie-developers</guid>
<pubDate>Mon, 13 Apr 2026 12:47:57 +0000</pubDate>
</item>
<item>
<title>Building PrudentSpend: Lessons From Shipping a Real-World Personal Finance Web App</title>
<link>https://coderlegion.com/14185/building-prudentspend-lessons-from-shipping-a-real-world-personal-finance-web-app</link>
<description>&lt;p&gt;Building side projects is easy.&lt;br&gt;
Shipping something people actually use — and maintaining it — is the hard part.&lt;/p&gt;
&lt;p&gt;In this article, I want to share the story behind PrudentSpend, a personal finance web application I built to help users track spending, manage budgets, and make better financial decisions. I’ll cover why I built it, the technical choices I made, and the lessons I learned from taking it live.&lt;/p&gt;
&lt;p&gt;Why I Built PrudentSpend&lt;/p&gt;
&lt;p&gt;Like many people, I struggled to clearly understand where my money was going each month. Existing budgeting tools either felt too complex, too restrictive, or overly automated.&lt;/p&gt;
&lt;p&gt;I wanted to build something that:&lt;/p&gt;
&lt;p&gt;Was simple and intentional&lt;/p&gt;
&lt;p&gt;Focused on clarity over automation&lt;/p&gt;
&lt;p&gt;Let users stay in control of their financial data&lt;/p&gt;
&lt;p&gt;That idea became PrudentSpend — a lightweight budgeting and expense-tracking platform designed around usability and transparency.&lt;/p&gt;
&lt;p&gt;Tech Stack &amp;amp; Architecture&lt;/p&gt;
&lt;p&gt;PrudentSpend is a full-stack web application built with scalability and maintainability in mind.&lt;/p&gt;
&lt;p&gt;Core technologies include:&lt;/p&gt;
&lt;p&gt;Frontend: Modern JavaScript framework with component-based UI&lt;/p&gt;
&lt;p&gt;Backend: RESTful API architecture&lt;/p&gt;
&lt;p&gt;Authentication: Secure user authentication and session management&lt;/p&gt;
&lt;p&gt;Database: Relational data modelling for users, budgets, and transactions&lt;/p&gt;
&lt;p&gt;Deployment: Cloud-based hosting with environment separation&lt;/p&gt;
&lt;p&gt;From the start, I treated the project like a production system — version control, clear commit history, and structured APIs were non-negotiable.&lt;/p&gt;
&lt;p&gt;Key Features&lt;/p&gt;
&lt;p&gt;Some of the core features include:&lt;/p&gt;
&lt;p&gt;User authentication and protected routes&lt;/p&gt;
&lt;p&gt;Expense and income tracking&lt;/p&gt;
&lt;p&gt;Budget creation and monitoring&lt;/p&gt;
&lt;p&gt;Monthly summaries and usage insights&lt;/p&gt;
&lt;p&gt;Responsive UI for desktop and mobile&lt;/p&gt;
&lt;p&gt;Each feature was built iteratively, based on real usage and feedback rather than assumptions.&lt;/p&gt;
&lt;p&gt;Adoption &amp;amp; Real-World Usage&lt;/p&gt;
&lt;p&gt;One of the most rewarding parts of this project has been seeing real user adoption.&lt;/p&gt;
&lt;p&gt;Over time:&lt;/p&gt;
&lt;p&gt;Monthly active users grew steadily&lt;/p&gt;
&lt;p&gt;Users consistently recorded transactions&lt;/p&gt;
&lt;p&gt;Engagement remained stable as the user base scaled&lt;/p&gt;
&lt;p&gt;This validated an important lesson: a simple product that solves a real problem often outperforms complex solutions.&lt;/p&gt;
&lt;p&gt;What I Learned&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Shipping beats perfection&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Waiting for the “perfect” version delays learning. Releasing early helped me identify what actually mattered to users.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Metrics matter&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tracking adoption and engagement changed how I prioritized features. Data &amp;gt; assumptions.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Maintainability is a feature&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Clean code, clear APIs, and documentation saved me time when adding new features later.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Building for users builds better engineers&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Working on PrudentSpend sharpened my skills in:&lt;/p&gt;
&lt;p&gt;System design&lt;/p&gt;
&lt;p&gt;API development&lt;/p&gt;
&lt;p&gt;Debugging real-world issues&lt;/p&gt;
&lt;p&gt;Communicating technical decisions clearly&lt;/p&gt;
&lt;p&gt;What’s Next&lt;/p&gt;
&lt;p&gt;PrudentSpend is still evolving. Planned improvements include:&lt;/p&gt;
&lt;p&gt;Advanced analytics and insights&lt;/p&gt;
&lt;p&gt;Exportable financial reports&lt;/p&gt;
&lt;p&gt;Performance optimizations&lt;/p&gt;
&lt;p&gt;Accessibility enhancements&lt;/p&gt;
&lt;p&gt;You can explore the live app here:&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://prudentspend.com&quot;&gt;https://prudentspend.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And I’m always open to feedback from other developers and users.&lt;/p&gt;
&lt;p&gt;Final Thoughts&lt;/p&gt;
&lt;p&gt;If you’re a developer thinking about building a side project, my advice is simple:&lt;/p&gt;
&lt;p&gt;Build something useful. Ship it. Let real users guide you.&lt;/p&gt;
&lt;p&gt;That’s where the real learning begins.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14185/building-prudentspend-lessons-from-shipping-a-real-world-personal-finance-web-app</guid>
<pubDate>Tue, 07 Apr 2026 21:55:40 +0000</pubDate>
</item>
<item>
<title>I built a free alternative to iLoveIMG — no login, no ads, no paywalls</title>
<link>https://coderlegion.com/14173/i-built-a-free-alternative-to-iloveimg-no-login-no-ads-no-paywalls</link>
<description>&lt;p&gt;We've all been there.&lt;/p&gt;
&lt;p&gt;You need to quickly compress an image. You Google &quot;free image tool&quot;, land on iLoveIMG, upload your file — and boom.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Sign up to continue.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You just wanted to resize a photo. Not create another account you'll forget about in 3 days.&lt;/p&gt;
&lt;p&gt;So after hitting this wall one too many times during a side project, I did what any developer does.&lt;/p&gt;
&lt;p&gt;I built my own. ️&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;introducing-ihateimg-in&quot;&gt;Introducing ihateimg.in&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://ihateimg.in&quot;&gt;ihateimg.in&lt;/a&gt;&lt;/strong&gt; — a completely free image toolkit. No account. No ads. No &quot;upgrade to pro&quot; popup appearing right when you need it most.&lt;/p&gt;
&lt;p&gt;Just open the site and get things done.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-it-does&quot;&gt;What it does&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Tool &lt;/th&gt;&lt;th&gt;What it's for&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;️ Crop &lt;/td&gt;&lt;td&gt;Trim your image exactly how you want&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Resize &lt;/td&gt;&lt;td&gt;Custom dimensions in pixels or percentage&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;️ Compress &lt;/td&gt;&lt;td&gt;Reduce file size without killing quality&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Watermark &lt;/td&gt;&lt;td&gt;Add text or image watermarks in seconds&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Upscale &lt;/td&gt;&lt;td&gt;Increase resolution without heavy blur&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-part-i-m-most-proud-of-pipelines-&quot;&gt;The part I'm most proud of — Pipelines ⚡&lt;/h3&gt;
&lt;p&gt;Most tools make you do this:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Upload → Edit → Download → Re-upload → Edit again → Download → Repeat &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With &lt;strong&gt;ihateimg.in&lt;/strong&gt;, you can chain multiple edits into a single pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compress + Resize + Watermark&lt;/strong&gt; — all in one go. Upload once, get your final result. Done.&lt;/p&gt;
&lt;p&gt;This alone saves me several minutes every single time I work with images.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-it-stacks-up-against-iloveimg&quot;&gt;How it stacks up against iLoveIMG&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature &lt;/th&gt;&lt;th&gt;iLoveIMG &lt;/th&gt;&lt;th&gt;ihateimg.in&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Login required &lt;/td&gt;&lt;td&gt;✅ Yes &lt;/td&gt;&lt;td&gt;❌ Never&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Ads &lt;/td&gt;&lt;td&gt;✅ Yes &lt;/td&gt;&lt;td&gt;❌ Never&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Paid plan &lt;/td&gt;&lt;td&gt;$60/year &lt;/td&gt;&lt;td&gt;Free forever&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Pipeline / batch edits &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;✅ Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Compress, Resize, Crop &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Watermark &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Upscale &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;iLoveIMG is a solid product — but it's built around converting free users into paying ones. Nothing wrong with that as a business, but it's not what I wanted as a user.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ihateimg.in is built around getting out of your way.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-honest-backstory&quot;&gt;The honest backstory&lt;/h3&gt;
&lt;p&gt;I needed a quick image tool for a side project. Every popular option either had ads, required signup, or locked the exact feature I needed behind a paywall.&lt;/p&gt;
&lt;p&gt;So I spent a weekend building something I actually wanted to use.&lt;/p&gt;
&lt;p&gt;It's been live for a while now. I've been quietly adding features and fixing bugs. Today felt like the right time to finally share it with the dev community and get real feedback.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;try-it-out&quot;&gt;Try it out&lt;/h3&gt;
&lt;p&gt; &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://ihateimg.in&quot;&gt;ihateimg.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'd genuinely love to know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What's missing?&lt;/li&gt;
&lt;li&gt;What would make this a tool you actually reach for?&lt;/li&gt;
&lt;li&gt;Any bugs or rough edges you hit?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Drop a comment — I read every single one. &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14173/i-built-a-free-alternative-to-iloveimg-no-login-no-ads-no-paywalls</guid>
<pubDate>Tue, 07 Apr 2026 16:18:46 +0000</pubDate>
</item>
<item>
<title>I added a flyable spaceship to my GitHub visualizer | Phase 6 of Stack Universe is live</title>
<link>https://coderlegion.com/14018/i-added-a-flyable-spaceship-to-my-github-visualizer-phase-6-of-stack-universe-is-live</link>
<description>&lt;h3 id=&quot;i-made-your-github-profile-explorable-you-can-now-fly-a-spaceship-through-other-developers-universes&quot;&gt;I made your GitHub profile explorable, you can now fly a spaceship through other developers' universes&lt;/h3&gt;
&lt;p&gt;Phase 6 of Stack Universe just shipped and it changes what the product fundamentally is. It started as a visualizer. It is now an explorable multiverse.&lt;/p&gt;
&lt;p&gt;→ &lt;a rel=&quot;nofollow&quot; href=&quot;https://stack-universe.vercel.app&quot;&gt;https://stack-universe.vercel.app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rh1ypuz2sfrjvbzedw8.jpeg&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick context if you are new&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Stack Universe turns any GitHub profile into a unique 3D solar system. Your repos are planets sized by star count, orbiting at speeds based on how recently you pushed. Your commit messages float on shooting stars. Your CI/CD runs are space weather, failing builds crack your planets and shatter your deploy streak aura. Every developer gets a permanent URL that is their coordinate in the multiverse.&lt;/p&gt;
&lt;p&gt;Previous phases added repo health scores, GitHub Actions weather, OAuth to claim your universe, a README embed widget, and a full AI layer with roast mode, narrator, horoscope, and developer identity panel.&lt;/p&gt;
&lt;p&gt;Phase 6 connects every universe together into a living community.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What shipped in Phase 6:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xiafddoxvwd10fc5oriy.PNG&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Constellation Multiverse Map&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Route: &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackuniverse.vercel.app/multiverse&quot;&gt;https://stackuniverse.vercel.app/multiverse&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Every universe stored in the database now appears as a glowing dot on a top down 2D star map. Position is determined by score — high scoring developers sit near the center, new developers are toward the edges. Constellation lines are drawn between developers who share three or more languages. Named regions appear as faint labels across the map — The JavaScript Nebula, The Rust Cluster, The Python Belt, The TypeScript Corridor.&lt;/p&gt;
&lt;p&gt;Hover any dot and it shows the username, score, and distance. Click it and the full cinematic warp entry plays, you fly into that developer's universe from the map. The multiverse is no longer a concept. It is a navigable space.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Universe Discovery Feed&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The landing page now has a live ticker powered by Supabase Realtime. Every time someone searches a username it appears in the feed. Two minutes ago someone explored JakeWharton's universe. Just now torvalds was visited. The landing page feels alive instead of static. It is a pure FOMO engine — you see other people exploring and immediately want to explore yours.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tech Stack Fingerprint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Button in the HUD: MY DNA &lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Groq and your language data combine to generate a unique radial mandala chart from your exact language percentages. A polyglot developer with eight languages gets a complex multi-pointed flower. A specialist who codes exclusively in one language gets a strong single directional spike. No two fingerprints look the same because no two developers have the same stack.&lt;/p&gt;
&lt;p&gt;Downloadable as a PNG. Shareable as your developer DNA. This is the kind of thing developers post on Twitter without being asked to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multiplayer Presence&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When two or more people view the same universe simultaneously&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Button on the landing page: EXPLORE MULTIVERSE &lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The camera switches to a first person spaceship view. WASD or arrow keys control movement. Mouse controls direction. You fly through infinite space and real developer universes are visible as glowing solar systems in the distance. Fly toward one and it grows larger as you approach. A label appears showing the username, score, and distance. Press E to trigger the full cinematic warp entry and fly inside.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where the product stands after six phases&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Phase 1 gave every developer a beautiful universe. Phase 2 made every planet informative. Phase 3 made it live with CI/CD weather. Phase 4 gave developers ownership. Phase 5 gave it a voice with AI. Phase 6 connected every universe into a community.&lt;/p&gt;
&lt;p&gt;The comment that became the product philosophy came from a user early on: same data, but way more engaging when it talks back. That is still the north star. Every phase asks what else this data is trying to say.&lt;/p&gt;
&lt;p&gt;Phase 7 is next — a full analytics dashboard with graphs and charts for the professional use case. Same data from the solar system but presented as clean readable charts for teams, managers, and recruiters. After that the combat system — universe battles where your GitHub stats become weapons.&lt;/p&gt;
&lt;h3 id=&quot;an-honest-note&quot;&gt;An honest note&lt;/h3&gt;
&lt;p&gt;I am still learning and improving with every phase I ship. This is a side project and every piece of feedback has directly shaped what gets built next. If you are an experienced developer and see something I should be doing differently in the Three.js architecture, the Supabase Realtime implementation, the Tone.js audio generation, or anything else — I genuinely want to hear it.&lt;/p&gt;
&lt;p&gt;Try the spaceship mode. Fly around. Find someone's universe. Drop your universe URL in the comments.&lt;/p&gt;
&lt;p&gt;→ &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackuniverse.vercel.app&quot;&gt;https://stackuniverse.vercel.app&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14018/i-added-a-flyable-spaceship-to-my-github-visualizer-phase-6-of-stack-universe-is-live</guid>
<pubDate>Sun, 05 Apr 2026 18:43:39 +0000</pubDate>
</item>
<item>
<title>What Android OEMs do to background apps, and the 11 layers I built to survive it</title>
<link>https://coderlegion.com/13762/what-android-oems-do-to-background-apps-and-the-11-layers-i-built-to-survive-it</link>
<description>&lt;p&gt;I spent over a year building a safety monitoring app that runs 24/7 on elderly parents' phones. If it gets killed, nobody gets alerted when something goes wrong. That constraint forced me into the deepest, most frustrating corners of Android background execution.&lt;/p&gt;
&lt;p&gt;This article covers what I learned about how Samsung, Xiaomi, Honor, OPPO, and Vivo actively kill background apps, why the standard Android approach is nowhere near sufficient, and the 11-layer recovery architecture I ended up building. I will also cover two related problems that surprised me: GPS hardware that silently stops working, and accelerometer data that lies about its age.&lt;/p&gt;
&lt;p&gt;126,000 lines of Kotlin, 125+ versions, solo developer. The app is called &lt;a rel=&quot;nofollow&quot; href=&quot;https://howareu.app&quot;&gt;How Are You?!&lt;/a&gt; — it learns an elderly person's daily routine over 7 days, then monitors around the clock and emails the family if something seems wrong. But this article is about the engineering, not the product.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-android-wants-your-app-dead&quot;&gt;The problem: Android wants your app dead&lt;/h3&gt;
&lt;p&gt;Stock Android already makes continuous background work difficult. Doze mode, App Standby, background execution limits — Google has been tightening the screws since Android 6. A foreground service with &lt;code&gt;REQUEST_IGNORE_BATTERY_OPTIMIZATIONS&lt;/code&gt; is the standard answer.&lt;/p&gt;
&lt;p&gt;That is necessary. It is nowhere near sufficient.&lt;/p&gt;
&lt;p&gt;OEMs add their own proprietary battery management on top of stock Android, and they are far more aggressive. Here is what I encountered on the devices I tested:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Samsung&lt;/strong&gt; maintains a &quot;Sleeping Apps&quot; list. If your app has no foreground activity for 3 days, Samsung kills it. OTA updates silently reset your battery optimization exemption. The user opted you out of optimization? Samsung un-opted you after the update.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Xiaomi (MIUI/HyperOS)&lt;/strong&gt; kills background services aggressively and resets autostart permissions after OTA updates. Your app was whitelisted? Not anymore.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Honor and Huawei&lt;/strong&gt; have PowerGenie, which monitors how often your app wakes the system. Call &lt;code&gt;setAlarmClock()&lt;/code&gt; more than about 3 times per day and you get flagged as &quot;frequently wakes your system.&quot; They also have HwPFWService, which kills apps holding wakelocks longer than 60 minutes with non-whitelisted tags.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OPPO (ColorOS)&lt;/strong&gt; has &quot;Sleep standby optimization&quot; that freezes apps during the hours the phone detects the user is sleeping. A safety monitoring app for elderly people needs to run &lt;em&gt;especially&lt;/em&gt; during sleep hours — that is when falls and medical events go unnoticed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vivo (Funtouch OS)&lt;/strong&gt; has &quot;AI sleep mode&quot; that does the same thing.&lt;/p&gt;
&lt;p&gt;Each manufacturer found a different way to kill you. No single workaround survives all of them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-answer-11-layers-of-recovery&quot;&gt;The answer: 11 layers of recovery&lt;/h3&gt;
&lt;p&gt;The core insight is that no single mechanism is reliable across all OEMs and all device states. The answer is redundancy — each layer catches the failures of the layers above it.&lt;/p&gt;
&lt;h4 id=&quot;layer-1-foreground-service-with-start-sticky&quot;&gt;Layer 1: Foreground service with START_STICKY&lt;/h4&gt;
&lt;p&gt;The foundation. &lt;code&gt;startForeground()&lt;/code&gt; with a persistent notification. The notification channel must use &lt;code&gt;IMPORTANCE_MIN&lt;/code&gt; — not &lt;code&gt;IMPORTANCE_DEFAULT&lt;/code&gt; or higher. Why? OEMs auto-grant &lt;code&gt;POST_NOTIFICATIONS&lt;/code&gt; on higher importance channels, bypassing the user's notification settings and making your persistent notification visible. &lt;code&gt;IMPORTANCE_MIN&lt;/code&gt; keeps it silent while &lt;code&gt;startForeground()&lt;/code&gt; still gives your process elevated priority.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;START_STICKY&lt;/code&gt; tells the system to restart the service after a kill. But &quot;restart&quot; can take minutes or never happen on aggressive OEMs.&lt;/p&gt;
&lt;h4 id=&quot;layer-2-ondestroy-recovery-scheduling&quot;&gt;Layer 2: onDestroy recovery scheduling&lt;/h4&gt;
&lt;p&gt;When the system kills your service, &lt;code&gt;onDestroy()&lt;/code&gt; fires (most of the time). Use this 50ms window to schedule everything that will bring you back:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;override fun onDestroy() {
    super.onDestroy()
    ServiceWatchdogReceiver.scheduleWithBackup(this)
    MotionSnapshotReceiver.schedule(this)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This fires both the AlarmManager chain and the motion snapshot chain. If &lt;code&gt;onDestroy()&lt;/code&gt; does not fire (force-stop, OEM kill without callback), the other layers cover it.&lt;/p&gt;
&lt;h4 id=&quot;layer-3-alarmmanager-watchdog-chain&quot;&gt;Layer 3: AlarmManager watchdog chain&lt;/h4&gt;
&lt;p&gt;A self-chaining &lt;code&gt;setExactAndAllowWhileIdle()&lt;/code&gt; alarm at 15-minute intervals during active use. When it fires, it checks whether the service is alive and restarts it if not.&lt;/p&gt;
&lt;p&gt;The interval adapts to power state: 15 minutes when active, 30 minutes when idle, 60 minutes during deep sleep. This matters for OEM battery scoring — more frequent alarms get flagged.&lt;/p&gt;
&lt;p&gt;Important: never use &lt;code&gt;Handler.postDelayed()&lt;/code&gt; as a replacement for AlarmManager. Handlers do not fire during CPU deep sleep. I learned this the hard way.&lt;/p&gt;
&lt;h4 id=&quot;layer-4-workmanager-periodic-watchdog&quot;&gt;Layer 4: WorkManager periodic watchdog&lt;/h4&gt;
&lt;p&gt;A &lt;code&gt;PeriodicWorkRequest&lt;/code&gt; at 15-minute intervals that does the same thing — checks the service and restarts if needed. WorkManager survives service kills and uses JobScheduler under the hood, which OEMs are more reluctant to interfere with.&lt;/p&gt;
&lt;p&gt;But there is a subtle trap: &lt;code&gt;ExistingPeriodicWorkPolicy.KEEP&lt;/code&gt; silently discards new requests if a worker is already enqueued, even if the existing one has a stale timer from hours ago. And &lt;code&gt;REPLACE&lt;/code&gt; resets the countdown every time you call &lt;code&gt;schedule()&lt;/code&gt;. The solution: query &lt;code&gt;getWorkInfosForUniqueWork()&lt;/code&gt; first and only schedule when the worker is not already enqueued.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;val workInfos = workManager
    .getWorkInfosForUniqueWork(WATCHDOG_WORK_NAME).await()
val isEnqueued = workInfos.any {
    it.state == WorkInfo.State.ENQUEUED || it.state == WorkInfo.State.RUNNING
}
if (!isEnqueued) {
    workManager.enqueueUniquePeriodicWork(
        WATCHDOG_WORK_NAME,
        ExistingPeriodicWorkPolicy.KEEP,
        watchdogRequest
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;layer-5-boot-recovery&quot;&gt;Layer 5: Boot recovery&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;BOOT_COMPLETED&lt;/code&gt;, &lt;code&gt;LOCKED_BOOT_COMPLETED&lt;/code&gt;, &lt;code&gt;QUICKBOOT_POWERON&lt;/code&gt;, and &lt;code&gt;MY_PACKAGE_REPLACED&lt;/code&gt; receivers that re-establish the service and all alarm chains after reboot or app update.&lt;/p&gt;
&lt;p&gt;Some OEMs reset permissions after OTA updates. OnePlus, Samsung, Xiaomi, Redmi, and POCO all do this. You need to detect the OTA and re-prompt the user for battery optimization exemption.&lt;/p&gt;
&lt;h4 id=&quot;layer-6-syncadapter-for-process-priority&quot;&gt;Layer 6: SyncAdapter for process priority&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;ContentResolver.addPeriodicSync()&lt;/code&gt; gives your process elevated priority through the sync framework. OEMs are reluctant to kill sync adapter processes because the sync framework is a system concept — killing it could break contacts, calendar, and email sync.&lt;/p&gt;
&lt;p&gt;This is a ~1-hour periodic callback that checks service health. It will not bring you back fast, but it is extremely hard for OEMs to suppress.&lt;/p&gt;
&lt;h4 id=&quot;layer-7-alarmclock-safety-net&quot;&gt;Layer 7: AlarmClock safety net&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;setAlarmClock()&lt;/code&gt; at 8-hour intervals — approximately 3 calls per day. This is the nuclear option. AlarmClock alarms get the highest delivery priority on Android because they are designed to wake users up.&lt;/p&gt;
&lt;p&gt;Why 8 hours and not shorter? Honor's PowerGenie specifically tracks AlarmClock frequency. At 15-minute intervals, it flags you as &quot;frequently wakes your system&quot; and kills you. At 8-hour intervals (~3/day), you fly under the radar.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;fun scheduleSafetyNet(context: Context) {
    val intent = PendingIntent.getBroadcast(
        context, REQUEST_CODE_ALARMCLOCK, intent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
    val triggerAt = System.currentTimeMillis() + SAFETY_NET_INTERVAL_MS // 8 hours
    alarmManager.setAlarmClock(
        AlarmManager.AlarmClockInfo(triggerAt, null),
        intent
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;layer-8-exact-alarm-permission-recovery&quot;&gt;Layer 8: Exact alarm permission recovery&lt;/h4&gt;
&lt;p&gt;When the user revokes &lt;code&gt;SCHEDULE_EXACT_ALARM&lt;/code&gt;, &lt;strong&gt;all&lt;/strong&gt; pending AlarmManager chains die silently. No callback, no exception. Your watchdog, your snapshot receiver, your safety net — all gone.&lt;/p&gt;
&lt;p&gt;Listen for &lt;code&gt;ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED&lt;/code&gt; and re-establish everything on re-grant:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class ExactAlarmPermissionReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (canScheduleExactAlarms()) {
            ServiceWatchdogReceiver.scheduleWithBackup(context)
            MotionSnapshotReceiver.schedule(context)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;layer-9-batched-accelerometer-sensing&quot;&gt;Layer 9: Batched accelerometer sensing&lt;/h4&gt;
&lt;p&gt;This is the layer that surprised me most. Keep the accelerometer registered with &lt;code&gt;maxReportLatencyUs&lt;/code&gt; during idle and deep sleep. The sensor HAL continuously samples into a hardware FIFO buffer and delivers readings via a sensor interrupt — this is completely invisible to OEM battery managers because it does not use AlarmManager, WorkManager, or any schedulable mechanism.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;sensorManager.registerListener(
    batchedMotionListener,
    accelerometer,
    SensorManager.SENSOR_DELAY_NORMAL,
    maxReportLatencyUs  // 10 min in deep sleep
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The HAL batches readings and delivers them all at once when the buffer fills or the latency expires. You get continuous motion awareness with zero wakes visible to the OEM.&lt;/p&gt;
&lt;p&gt;One gotcha: a single SLIGHT_MOVEMENT reading (1.0-3.0 m/s^2) should not exit batched mode. Table vibrations and building micro-movements produce transient spikes. I require 3 consecutive SLIGHT_MOVEMENT readings (~15 seconds) before exiting. Anything above 3.0 m/s^2 (MODERATE_MOVEMENT) exits immediately.&lt;/p&gt;
&lt;h4 id=&quot;layer-10-network-restoration-and-app-foreground-triggers&quot;&gt;Layer 10: Network restoration and app foreground triggers&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;CONNECTIVITY_ACTION&lt;/code&gt; receiver triggers a service health check when the network comes back. &lt;code&gt;ProcessLifecycleOwner&lt;/code&gt; fires when the user opens the app. These are opportunistic — they catch edge cases where the service died during airplane mode or extended offline periods.&lt;/p&gt;
&lt;h4 id=&quot;layer-11-user-facing-gap-detection&quot;&gt;Layer 11: User-facing gap detection&lt;/h4&gt;
&lt;p&gt;When all 10 layers fail (and on some devices, they do), the app detects the gap and shows the user device-specific instructions: &quot;Your [Manufacturer] phone is stopping background apps. Open Settings &amp;gt; Battery &amp;gt; [OEM-specific path] and disable optimization for How Are You?!&quot;&lt;/p&gt;
&lt;p&gt;This is the least satisfying layer because it requires user action. But on a few particularly aggressive OEM configurations, it is the only thing that works.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-wakelock-tag-problem-on-honor&quot;&gt;The wakelock tag problem on Honor&lt;/h3&gt;
&lt;p&gt;HwPFWService on Honor and Huawei devices maintains a whitelist of allowed wakelock tags. If your app holds a wakelock for more than 60 minutes with a tag that is not on the whitelist, HwPFWService kills your app.&lt;/p&gt;
&lt;p&gt;The solution is embarrassingly simple: use a whitelisted tag on Honor/Huawei, your real tag everywhere else.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private val WAKELOCK_TAG: String = run {
    val manufacturer = Build.MANUFACTURER?.lowercase().orEmpty()
    if (manufacturer == &quot;huawei&quot; || manufacturer == &quot;honor&quot;) {
        &quot;LocationManagerService&quot;  // Whitelisted by HwPFWService
    } else {
        &quot;HowAreYou:PulseBurst&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;LocationManagerService&lt;/code&gt; is whitelisted because it is a system service tag. I am not proud of this, but it works.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;getcurrentlocation-hangs-forever&quot;&gt;getCurrentLocation() hangs forever&lt;/h3&gt;
&lt;p&gt;Once I had the service staying alive, I discovered a second problem: GPS does not work when you need it.&lt;/p&gt;
&lt;p&gt;At approximately 12% battery on my Honor test device, the OEM battery saver silently killed GPS hardware access. No exception, no error callback, no log entry. The foreground service was alive, the accelerometer worked. But &lt;code&gt;getCurrentLocation(PRIORITY_HIGH_ACCURACY)&lt;/code&gt; simply never completed. The Task from Play Services hung indefinitely — neither &lt;code&gt;onSuccessListener&lt;/code&gt; nor &lt;code&gt;onFailureListener&lt;/code&gt; ever fired.&lt;/p&gt;
&lt;p&gt;The code fell back to &lt;code&gt;getLastLocation()&lt;/code&gt;, which returned a 5-hour-old cached position from a completely different city.&lt;/p&gt;
&lt;h4 id=&quot;fix-1-always-timeout&quot;&gt;Fix 1: Always timeout&lt;/h4&gt;
&lt;p&gt;Every &lt;code&gt;getCurrentLocation()&lt;/code&gt; call must be wrapped in a coroutine timeout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;suspend fun getLocation(priority: Int): Location? {
    return withTimeoutOrNull(30_000L) {
        suspendCancellableCoroutine { cont -&amp;gt;
            fusedClient.getCurrentLocation(priority, token)
                .addOnSuccessListener { cont.resume(it) }
                .addOnFailureListener { cont.resume(null) }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;fix-2-priority-fallback-chain&quot;&gt;Fix 2: Priority fallback chain&lt;/h4&gt;
&lt;p&gt;GPS hardware being dead does not mean all location sources are dead. Cell towers and Wi-Fi still work. I built a sequential fallback:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;PRIORITY_HIGH_ACCURACY (GPS, ~10m)
    | timeout or null
PRIORITY_BALANCED_POWER_ACCURACY (Wi-Fi + cell, ~40-300m)
    | timeout or null
PRIORITY_LOW_POWER (cell only, ~300m-3km)
    | timeout or null
getLastLocation() (cached, any age)
    | null
TotalFailure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each step gets its own 30-second timeout. In practice, when GPS is killed, BALANCED_POWER_ACCURACY returns in 2-3 seconds because Wi-Fi scanning still works.&lt;/p&gt;
&lt;h4 id=&quot;fix-3-gps-wake-probe&quot;&gt;Fix 3: GPS wake probe&lt;/h4&gt;
&lt;p&gt;Sometimes the GPS hardware is not permanently dead — it has been suspended by the battery manager. A brief &lt;code&gt;requestLocationUpdates&lt;/code&gt; call can wake it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;if (hoursSinceLastFreshGps &amp;gt; 4) {
    val probeRequest = LocationRequest.Builder(
        Priority.PRIORITY_HIGH_ACCURACY, 1000L
    )
        .setDurationMillis(5_000L)
        .setMaxUpdates(5)
        .build()

    withTimeoutOrNull(6_000L) {
        fusedClient.requestLocationUpdates(probeRequest, callback, looper)
    }
    fusedClient.removeLocationUpdates(callback)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Five seconds, maximum once every 4 hours. On Honor, this recovers the GPS roughly 40% of the time.&lt;/p&gt;
&lt;h4 id=&quot;fix-4-explicit-outcome-types&quot;&gt;Fix 4: Explicit outcome types&lt;/h4&gt;
&lt;p&gt;The original code returned &lt;code&gt;Location?&lt;/code&gt;. The caller had no way to distinguish a fresh 10-meter GPS fix from a 5-hour-old cached position. I changed the return type to make the quality of data explicit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;sealed interface GpsLocationOutcome {
    data class FreshGps(val accuracy: Float) : GpsLocationOutcome
    data class WakeProbeSuccess(val accuracy: Float) : GpsLocationOutcome
    data class CellFallback(val accuracy: Float) : GpsLocationOutcome
    data class StaleLastLocation(val ageMs: Long) : GpsLocationOutcome
    data object TotalFailure : GpsLocationOutcome
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the consumer can make informed decisions. A 3km cell tower reading is low precision, but it answers &quot;is this person in the expected city or 200km away?&quot; For a safety app, that distinction matters.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-sensor-hal-lies-about-timestamps&quot;&gt;The sensor HAL lies about timestamps&lt;/h3&gt;
&lt;p&gt;At 3 AM, your app wakes up to check the accelerometer. You call &lt;code&gt;registerListener()&lt;/code&gt;, and the sensor HAL returns data. You check &lt;code&gt;event.timestamp&lt;/code&gt; against &lt;code&gt;SystemClock.elapsedRealtimeNanos()&lt;/code&gt;. The delta is small. The data looks fresh.&lt;/p&gt;
&lt;p&gt;It is not. It is 22-minute-old data sitting in the hardware FIFO buffer since the last time anyone read the sensor.&lt;/p&gt;
&lt;p&gt;This is the normal behavior of hardware sensor FIFOs. When the CPU sleeps, the sensor continues sampling into its buffer. When you register a listener after wakeup, the HAL dumps the entire buffer contents at you. The timestamps are real (the readings were taken at those times), but the data is stale — it describes what happened 22 minutes ago, not what is happening now.&lt;/p&gt;
&lt;p&gt;On most devices, you can catch this by comparing &lt;code&gt;event.timestamp&lt;/code&gt; (CLOCK_BOOTTIME nanoseconds) against &lt;code&gt;SystemClock.elapsedRealtimeNanos()&lt;/code&gt;. If the delta is large, the reading is stale.&lt;/p&gt;
&lt;p&gt;Honor broke this assumption. On Honor devices, the HAL rebases &lt;code&gt;event.timestamp&lt;/code&gt; on FIFO flush, so the delta check shows the data as fresh even when it is not.&lt;/p&gt;
&lt;h4 id=&quot;the-fix-flush-wait-for-callback-then-collect&quot;&gt;The fix: flush, wait for callback, then collect&lt;/h4&gt;
&lt;p&gt;Do not trust the first readings after &lt;code&gt;registerListener()&lt;/code&gt;. Instead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Call &lt;code&gt;sensorManager.flush(this)&lt;/code&gt; to drain the stale FIFO data&lt;/li&gt;
&lt;li&gt;Wait for the &lt;code&gt;onFlushCompleted()&lt;/code&gt; callback from &lt;code&gt;SensorEventListener2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Only start collecting readings after the flush completes&lt;/li&gt;
&lt;li&gt;Set a 1000ms fallback timer in case the HAL never fires the callback&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class MotionSnapshotReceiver : BroadcastReceiver(), SensorEventListener2 {
    private var isFlushPhase = true

    override fun onSensorChanged(event: SensorEvent) {
        if (isFlushPhase) return  // Discard stale FIFO data
        collectReading(event)
    }

    override fun onFlushCompleted(sensor: Sensor?) {
        endFlushPhase(byHal = true)
    }

    private fun endFlushPhase(byHal: Boolean) {
        if (!isFlushPhase) return  // Guard against double-trigger
        isFlushPhase = false
        handler.removeCallbacks(flushFallbackRunnable)
        // Now start collecting real readings
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fallback timer at 1000ms is important. I originally used 200ms, which was insufficient for Honor devices — their deep FIFO drains at approximately 16Hz, and a full buffer can take over 200ms to flush.&lt;/p&gt;
&lt;p&gt;As a secondary safety net, I use dual-clock comparison: both &lt;code&gt;CLOCK_BOOTTIME&lt;/code&gt; and &lt;code&gt;CLOCK_MONOTONIC&lt;/code&gt; deltas must agree that the reading is fresh. If either delta exceeds 500ms of staleness, the reading is discarded.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-race-condition-in-gps-processing&quot;&gt;A race condition in GPS processing&lt;/h3&gt;
&lt;p&gt;I had multiple independent trigger paths (stillness detector, smart GPS scheduler, area stability detector) that could request GPS concurrently. Two of them fired within 33 milliseconds of each other. Both read the same &lt;code&gt;getLastLocation()&lt;/code&gt;, both passed the stationarity filter, and both inserted a GPS reading.&lt;/p&gt;
&lt;p&gt;My code uses a minimum-readings-per-cluster filter to discard drive-through locations — a place needs at least 2 GPS readings to count as a real visit. The duplicate entry from the race condition defeated this filter. A single drive-by at 60km/h became a &quot;cluster of 2.&quot;&lt;/p&gt;
&lt;p&gt;The fix is a Mutex around the entire location processing path:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private val processLocationMutex = Mutex()

suspend fun processLocation(location: Location) {
    processLocationMutex.withLock {
        val lastLocation = getLastLocation()
        // The second concurrent caller now sees the just-inserted
        // location and correctly skips as duplicate
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;battery-result&quot;&gt;Battery result&lt;/h3&gt;
&lt;p&gt;After all 11 layers and three tiers of power state, the battery impact is under 1% per day. The key numbers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Before optimization:&lt;/strong&gt; ~4,300 AlarmManager wakes per day. Every active-mode pulse (15s/30s) used AlarmManager. Every watchdog check (every 5 minutes) used AlarmManager. Honor flagged the app within hours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;After optimization:&lt;/strong&gt; ~240 wakes per day. Active-mode pulses use &lt;code&gt;Handler.postDelayed()&lt;/code&gt; (zero AlarmManager wakes). Watchdog intervals extended from 5 to 15 minutes. AlarmClock safety net reduced from every 15 minutes to every 8 hours.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is a 94% reduction in system wakes while maintaining the same monitoring reliability.&lt;/p&gt;
&lt;p&gt;The insight: aggressive scheduling wastes more battery than it saves in reliability. A three-tier power state that backs off when the device is still (active at 15-second pulses, idle at 5-minute pulses, deep sleep at 30-minute pulses with batched accelerometer as safety net) achieves both low battery impact and high reliability.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-would-do-differently&quot;&gt;What I would do differently&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Build the OEM compatibility layer first.&lt;/strong&gt; I treated background reliability as something I would fix later. It took 40+ versions across several months to get right. It should have been the architectural foundation from Day 1.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test on real OEM devices from the start.&lt;/strong&gt; The Android emulator and Pixel devices tell you nothing about OEM battery management. I did not discover the Honor wakelock whitelist problem, the GPS hardware suspension, or the sensor FIFO timestamp rebasing until I tested on actual devices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Never trust a single mechanism.&lt;/strong&gt; Every Android background API has an OEM that breaks it. AlarmManager gets suppressed. WorkManager gets deferred. Foreground services get killed. The only reliable approach is layered redundancy where each mechanism independently tries to recover.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The app is called &lt;a rel=&quot;nofollow&quot; href=&quot;https://howareu.app&quot;&gt;How Are You?!&lt;/a&gt; and is available on Google Play. It is still in early testing — if you have an elderly parent on Android and want to try it, I would appreciate feedback, especially from OEM devices I have not tested yet. Email: developer@howareu.app&lt;/p&gt;
&lt;p&gt;I am happy to answer questions about any of these techniques. The OEM compatibility rabbit hole goes much deeper than what I have covered here.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13762/what-android-oems-do-to-background-apps-and-the-11-layers-i-built-to-survive-it</guid>
<pubDate>Tue, 31 Mar 2026 10:50:49 +0000</pubDate>
</item>
<item>
<title>Vector Similarity, Zero Client JS: Decoupled Analytics on a Side Project Budget</title>
<link>https://coderlegion.com/13615/vector-similarity-zero-client-js-decoupled-analytics-on-a-side-project-budget</link>
<description>&lt;p&gt;A leaderboard for &lt;a rel=&quot;nofollow&quot; href=&quot;https://dumbquestion.ai/?utm_source=legion&quot;&gt;DumbQuestion.ai&lt;/a&gt; sounds simple. Track the most asked questions, display them. Done. Except people never ask the same question the same way twice.&lt;/p&gt;
&lt;p&gt;I was curious about how creative users of DumbQuestion.ai got with their questions, and I thought others might be as well. So I built a leaderboard of the most frequently asked dumb questions.&lt;/p&gt;
&lt;p&gt;The Overqualified persona calls it &lt;strong&gt;THE ARCHIVE OF INCOMPETENCE.&lt;/strong&gt;&lt;br&gt;
The Weary persona calls it &lt;strong&gt;THE WALL OF REGRET.&lt;/strong&gt;&lt;br&gt;
[REDACTED] calls it &lt;strong&gt;THE WATCHLIST.&lt;/strong&gt;&lt;br&gt;
The Compliant calls it &lt;strong&gt;THE WALL OF EXCELLENCE&lt;/strong&gt; (bless its reprogrammed heart).&lt;/p&gt;
&lt;p&gt;Building it turned out more interesting than it sounds.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-product-challenge&quot;&gt;The Product Challenge&lt;/h3&gt;
&lt;p&gt;People ask the same dumb question in a hundred different ways. &quot;What is 2+2?&quot; and &quot;can you add two plus two for me?&quot; are functionally identical. A simple string counter would give you noise, not signal. I needed semantic matching, not string matching.&lt;/p&gt;
&lt;p&gt;This is a solved problem in the ML world, but the typical solutions come with tradeoffs: heavyweight models, expensive APIs, or significant latency added to the critical path. None of those fit a &quot;brutally efficient&quot; side project.&lt;/p&gt;
&lt;h3 id=&quot;the-solution-vector-similarity-on-a-budget&quot;&gt;The Solution: Vector Similarity on a Budget&lt;/h3&gt;
&lt;p&gt;Each question gets run through an embedding model and compared against a &lt;a rel=&quot;nofollow&quot; href=&quot;https://qdrant.tech/&quot;&gt;Qdrant&lt;/a&gt; vector database. Qdrant's &lt;a rel=&quot;nofollow&quot; href=&quot;https://qdrant.tech/pricing/&quot;&gt;free tier&lt;/a&gt; is remarkably generous for a side project workload, but self-hosting is trivially easy if you need it.&lt;/p&gt;
&lt;p&gt;The matching logic is straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate an embedding for the incoming question&lt;/li&gt;
&lt;li&gt;Compare against existing embeddings using cosine similarity&lt;/li&gt;
&lt;li&gt;If similarity exceeds a threshold, increment that question's counter&lt;/li&gt;
&lt;li&gt;If it's new, add it to the database&lt;/li&gt;
&lt;li&gt;The first instance of a question becomes the official display version&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The embedding call costs fractions of a cent. The similarity comparison is fast. The result is a leaderboard that actually understands context rather than just matching strings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The key architectural decision:&lt;/strong&gt; None of this runs in the main app.&lt;/p&gt;
&lt;p&gt;Adding vector similarity matching to every request would add latency, bloat the container, and burn more compute. Anti-pattern to the &quot;brutally efficient&quot; principle I've been following throughout. Instead, every question flows through the console output, gets picked up by a &lt;a rel=&quot;nofollow&quot; href=&quot;https://vector.dev/&quot;&gt;Vector&lt;/a&gt; sidecar container, routed through GCP Pub/Sub, and processed asynchronously on my Mac Mini home server (more later).&lt;/p&gt;
&lt;p&gt;The Mac Mini handles the Qdrant comparisons and updates a JSON file in Cloudflare R2 storage. When a user hits the leaderboard page it loads directly from R2. No live database queries. No per-request costs. Essentially free page loads at any scale.&lt;/p&gt;
&lt;h3 id=&quot;what-ended-up-on-the-leaderboard-&quot;&gt;What Ended Up on the Leaderboard?&lt;/h3&gt;
&lt;p&gt;As early users started using the app, the leaderboard filled up with exactly what you'd expect: actual dumb questions, a handful of self-awareness probes, and more than a few prompt injection attempts.&lt;/p&gt;
&lt;p&gt;Apparently people &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jagostoni/dumbquestionai-self-awareness-prompt-injection-search-intent-and-darkness-3pd&quot;&gt;read this series&lt;/a&gt; and went straight for the easter eggs. &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The leaderboard was just one piece of a larger analytics picture. Building it taught me something useful: the most interesting features don't always belong in your main app. That same principle shaped the entire analytics stack.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-observability-problem&quot;&gt;The Observability Problem&lt;/h3&gt;
&lt;p&gt;Running a side project means making real product decisions with limited data. Are people actually asking questions or just bouncing off the homepage? Which sites are driving traffic? Are ads being seen, clicked, ignored?&lt;/p&gt;
&lt;p&gt;Two constraints shaped the solution: no client-side JavaScript (page bloat is the enemy of brutal efficiency) and no SaaS analytics bill that spikes with usage.&lt;/p&gt;
&lt;p&gt;So I built (assembled, really) my own stack from open source tools. On a Mac Mini sitting at home.&lt;/p&gt;
&lt;h3 id=&quot;the-full-pipeline&quot;&gt;The Full Pipeline&lt;/h3&gt;
&lt;p&gt;Every event in &lt;a rel=&quot;nofollow&quot; href=&quot;https://dumbquestion.ai/?utm_source=legion&quot;&gt;DumbQuestion.ai&lt;/a&gt; emits structured telemetry to standard console output:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP requests (method, path, status, duration)&lt;/li&gt;
&lt;li&gt;Questions asked (anonymized)&lt;/li&gt;
&lt;li&gt;Searches performed&lt;/li&gt;
&lt;li&gt;LLM operations (model, token counts, duration, cost)&lt;/li&gt;
&lt;li&gt;Prompt injection attempts&lt;/li&gt;
&lt;li&gt;Custom product events (Question Asked, Shared, Ad Shown, Ad Clicked)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://gin-gonic.com/&quot;&gt;Go/GIN&lt;/a&gt; framework handles much of the HTTP telemetry automatically. The rest is custom instrumentation added deliberately at key points in the application.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Vector sidecar container&lt;/strong&gt; picks up the console output and routes it to &lt;strong&gt;GCP Pub/Sub&lt;/strong&gt;. This is the critical architectural decision: Pub/Sub acts as a resilient buffer between the main app and everything downstream. The Mac Mini can go down, lose power, or restart. Once it comes back up, the stack picks up exactly where it left off. No data loss, no backfill scripts, no drama.&lt;/p&gt;
&lt;p&gt;From Pub/Sub, a second Vector instance on the Mac Mini routes to two primary targets:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/plausible/analytics&quot;&gt;Plausible&lt;/a&gt;&lt;/strong&gt; handles user behavior and product analytics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Page views and session depth&lt;/li&gt;
&lt;li&gt;UTM tag tracking (know exactly which article drove which visit)&lt;/li&gt;
&lt;li&gt;User journey depth (did they just hit the root page or actually ask a question?)&lt;/li&gt;
&lt;li&gt;Browser, device type, country of origin&lt;/li&gt;
&lt;li&gt;Custom events: Question Asked, Shared, Ad Shown, Ad Clicked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this without a single line of client-side JavaScript. No tracking scripts, no page weight, no GDPR cookie banners for analytics. Pure server-side telemetry piped through the same pipeline as everything else.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/parseablehq&quot;&gt;Parseable&lt;/a&gt;&lt;/strong&gt; handles the operational side:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LLM performance metrics and cost tracking by day&lt;/li&gt;
&lt;li&gt;Ad CTR dashboards&lt;/li&gt;
&lt;li&gt;Log aggregation for debugging and incident investigation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Think of it as Plausible for the product lens, Parseable for the business and ops lens.&lt;/p&gt;
&lt;h3 id=&quot;the-resilience-payoff&quot;&gt;The Resilience Payoff&lt;/h3&gt;
&lt;p&gt;I've had power outages. Slowdowns. The occasional restart. Every time, the stack catches up from where Pub/Sub left off without any manual intervention.&lt;/p&gt;
&lt;p&gt;This isn't accidental. Designing around failure rather than pretending it won't happen is the difference between a toy and a production system. The GCP Pub/Sub buffer was a deliberate choice specifically because I knew the downstream consumers (Mac Mini, Qdrant, Plausible, Parseable) were running on non-guaranteed infrastructure.&lt;/p&gt;
&lt;p&gt;Even on a Mac Mini, you can build something production-grade. You just have to design for it.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;Two things surprised me building this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First:&lt;/strong&gt; How much you can accomplish by treating console output as a first-class telemetry stream. No SDKs, no agents baked into the app, no client-side scripts. Just structured logging and a pipeline that knows what to do with it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second:&lt;/strong&gt; How much the &quot;keep it off the critical path&quot; principle scales. It started as a constraint (keep the main container lean) and became a design philosophy. The leaderboard, the analytics - none of it runs in the main app. All of it works reliably because the main app doesn't have to care about it.&lt;/p&gt;
&lt;p&gt;AI helped build all of it. But knowing what to measure, where to put the seams, and how to design for failure? Still the interesting (and super fun) part.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dumbquestion.ai/?utm_source=legion&quot;&gt;dumbquestion.ai&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13615/vector-similarity-zero-client-js-decoupled-analytics-on-a-side-project-budget</guid>
<pubDate>Sat, 28 Mar 2026 19:53:21 +0000</pubDate>
</item>
<item>
<title>ShipWithAI #11 — Turn Repetitive Client Work Into a Productized Feature in One Day</title>
<link>https://coderlegion.com/7242/shipwithai-11-turn-repetitive-client-work-into-a-productized-feature-in-one-day</link>
<description>&lt;p&gt;Turn your manual consulting tasks into a repeatable, sellable AI-powered workflow. Perfect for indie devs, PMs, and engineers building solo SaaS tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;br&gt;
• Review 5–10 past client jobs and list the inputs, decisions, and outputs.&lt;br&gt;
• Find the 80% of steps that repeat across projects — that’s your core feature.&lt;br&gt;
• Sketch a simple pipeline: Input → Validation → AI Judgment → Output.&lt;br&gt;
• Separate rule-based logic from AI steps, and wrap AI calls with guardrails.&lt;br&gt;
• Expose one clean contract — an API route or upload UI with job tracking.&lt;br&gt;
• Add basic metering and pricing (e.g., per file or per job) to start charging.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gotchas:&lt;/strong&gt;&lt;br&gt;
• Don’t chase edge cases — they kill your launch speed.&lt;br&gt;
• Don’t skip validation or monitoring — AI without guardrails breaks trust fast.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do this today:&lt;/strong&gt;&lt;br&gt;
• Pick one deliverable you’ve repeated at least twice.&lt;br&gt;
• Write its input → steps → output on a page.&lt;br&gt;
• Draft your first pipeline and test it with one real user.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://shipwithai.substack.com/p/turn-manual-workflows-into-productized?utm_source=linkedin&amp;amp;utm_medium=legion_newsletter&amp;amp;utm_campaign=20251112&quot;&gt;Full guide →&lt;/a&gt;&lt;/p&gt;
</description>
<category>AI</category>
<guid isPermaLink="true">https://coderlegion.com/7242/shipwithai-11-turn-repetitive-client-work-into-a-productized-feature-in-one-day</guid>
<pubDate>Wed, 12 Nov 2025 10:58:35 +0000</pubDate>
</item>
<item>
<title>ShipWithAI #10 — Accept Stripe Payments in 4 Steps (MVP-Ready)</title>
<link>https://coderlegion.com/7081/shipwithai-10-accept-stripe-payments-in-4-steps-mvp-ready</link>
<description>&lt;p&gt;Get subscription payments working in your side project without building custom checkout flows.&lt;/p&gt;
&lt;p&gt;Steps:&lt;/p&gt;
&lt;p&gt;• Embed Stripe's pricing table component with your user's ID as client_reference_id and their email&lt;/p&gt;
&lt;p&gt;• Set up checkout.session.completed webhook to capture subscription_id and link it to your user in your database&lt;/p&gt;
&lt;p&gt;• Listen to customer.subscription.created, updated, and deleted webhooks to keep local subscription status in sync&lt;/p&gt;
&lt;p&gt;• Add Stripe's customer portal link so users can manage their own subscriptions, upgrades, and cancellations&lt;/p&gt;
&lt;p&gt;Gotchas:&lt;/p&gt;
&lt;p&gt;• Skipping client_reference_id breaks everything—you won't know which user owns which subscription&lt;/p&gt;
&lt;p&gt;• Don't query Stripe's API on every request to check subscription status—maintain a local copy instead&lt;/p&gt;
&lt;p&gt;Do this today:&lt;/p&gt;
&lt;p&gt;• Copy Stripe's pricing table script into your app and pass user ID + email&lt;/p&gt;
&lt;p&gt;• Create one webhook endpoint for checkout.session.completed&lt;/p&gt;
&lt;p&gt;• Store subscription_id with your user record when checkout completes&lt;/p&gt;
&lt;p&gt;Full guide → &lt;a rel=&quot;nofollow&quot; href=&quot;https://shipwithai.substack.com/p/add-stripe-payments-in-4-steps-mvp?utm_source=linkedin&amp;amp;utm_medium=legion_newsletter&amp;amp;utm_campaign=20251108&quot;&gt;https://shipwithai.substack.com/p/add-stripe-payments-in-4-steps-mvp?utm_source=linkedin&amp;amp;utm_medium=legion_newsletter&amp;amp;utm_campaign=20251108&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/7081/shipwithai-10-accept-stripe-payments-in-4-steps-mvp-ready</guid>
<pubDate>Sat, 08 Nov 2025 09:40:24 +0000</pubDate>
</item>
<item>
<title>ShipWithAI #8 — Ship a One-Feature MVP in 1 Week</title>
<link>https://coderlegion.com/5541/shipwithai-8-ship-a-one-feature-mvp-in-1-week</link>
<description>&lt;p&gt;Build and validate a one-feature MVP fast. This is for solo devs using AI to move from idea to proof without overbuilding.&lt;/p&gt;
&lt;p&gt;Steps:&lt;br&gt;
• Pick one job story with a clear success metric&lt;br&gt;
• Use AI to draft a one-page spec (journey, UI, API, tests, not-doing list)&lt;br&gt;
• Slice to essentials: one screen, two endpoints, three tests, one metric&lt;br&gt;
• Test with real users or fake doors for quick signal&lt;br&gt;
• Assess the metric: pass → add one adjacent feature; fail → tighten flow and retry&lt;br&gt;
• Run the loop again until you hit the metric&lt;/p&gt;
&lt;p&gt;Gotchas:&lt;br&gt;
• Don’t waste time on auth, dashboards, or settings before value is proven&lt;br&gt;
• Don’t validate based on vibes—only on your metric&lt;/p&gt;
&lt;p&gt;Do this today:&lt;br&gt;
• Write a job story using the “When / I want to / so I can” format&lt;br&gt;
• Define one numeric success metric&lt;br&gt;
• Run the AI one-pager prompt and trim it to one screen&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://shipwithai.substack.com/p/pasta-al-dente-ship-a-one-feature?utm_source=linkedin&amp;amp;utm_medium=li_newsletter&amp;amp;utm_campaign=20250924&quot;&gt;Full guide →&lt;/a&gt;&lt;/p&gt;
</description>
<category>AI</category>
<guid isPermaLink="true">https://coderlegion.com/5541/shipwithai-8-ship-a-one-feature-mvp-in-1-week</guid>
<pubDate>Wed, 24 Sep 2025 17:40:21 +0000</pubDate>
</item>
</channel>
</rss>