I’ve Been Working on Something Big

EmailBuddy

I’ve been working on something really big, it’s been on my mind for maybe a year now and I’ve been working on it for 8 months already, so I want to talk about it. EmailBuddy is your help for your inbox.

What is EmailBuddy?

What is EmailBuddy? He’s supposed to be your one stop shop for cleaning up your email inbox. So for example, if you have like 100,000 emails and you don’t know where to even start, that’s the point of EmailBuddy. It’s supposed to help you in multiple different ways and try to help bring back some structure into your email.

Inbox Health & Score

One key feature we have is our inbox score. There is five main categories that comprise this score. Those are unread emails, spam emails, old newsletters, expired promotions, security emails. These are emails that may not be needed anymore like expired 2fa emails.

If we go a level deeper, we can interact with each score and see which emails are falling under these categories. You can get a summary from EmailBuddy saying what to do, then you can delete emails by cherry picking which ones you don’t need, or you can delete 100 at a time, or if you really don’t want them delete all of them at once.

Cleanup Feature

Another feature is the cleanup feature, this one is my personally favorite it pulls in your last 200 emails (it can get more emails if you click deeper scan) and it will sort it by senders who have sent you the most emails.

So for example, let’s say you have Amazon at the top of this list and it’s showing you have 150 emails. You can click the sender, then you can see all your emails from Amazon. You can get a summary from Emailbuddy and a recommendation on what to do with them.

Also, you will get four key actions: unsubscribe, mark as spam, mark as read, delete all. I like the control and transparency that comes from this feature. You see how much one sender is taking up your inbox and you get to take care of it in whatever way you feel like.

Automation

For the automation feature, there are toggles available that allow you to do delete certain emails such as delete old promos, old receipts, auto phishing emails. When you toggle these and then hit run it will remove all of these emails at once.

This one is a very strong feature, but it can be dangerous since it will or most of the work for the user but can be super effective.

Wrapping up

Those are some of the core features of the app to help people clean up and manage their emails. Currently, I have a roadmap setup for future updates.

If you are interested in the app I’ll leave a link right here if you want to give it a try. At the end of the day this project was for me to learn more about how app development is some and it’s been a blast learning.

Try it out: https://apps.apple.com/us/app/emailbuddy-clean-inbox/id6759539281

CFFBRW went from drag-and-drop to “hey AI, just deploy this for me”

Hey! So if you’ve been following CFFBRW since the Blockly days (DOUBT IT!), things have… changed a lot. Like, “I threw away the entire visual editor” level of change. 😅

Remember when I was all excited about snapping blocks together like LEGO? Turns out, when AI agents can write code better than I can drag blocks around, maybe the blocks aren’t the product anymore.

What happened

So I’ve been reading a lot about the whole “SaaS is dead” thing (I wrote some research notes about it). The basic idea: value is moving UP to the agent layer and DOWN to the data layer. The middle — dashboards, drag-and-drop UIs, per-seat pricing — is getting crushed.

And I looked at CFFBRW and thought… wait. My visual builder IS the middle layer. 🙈

So I rebuilt the whole thing around one idea: what if you just write what you want in plain English, and AI compiles it into a workflow?

Before:  Drag blocks → Configure each one → Deploy
Now:     Write markdown → AI compiles → Deploy

The markdown is the source of truth. Not a config file, not YAML, not blocks.

The AI compiler runs once per workflow version, caches the result, and then execution is fully deterministic. No AI involved at runtime = fast and cheap.

The part I’m most excited about: BYOM MCP Server

BYOM = Bring Your Own Model. I exposed the entire platform as an MCP server at /mcp. Any AI — Claude, Gemini, whatever — can compile, validate, deploy, and run workflows. Not through the dashboard. Through MCP tools.

This is… kind of the whole point? If agents are the future users, the MCP server IS the product. The dashboard is just nice-to-have.

Here’s what the MCP server gives you:

Resources (so the AI understands the system before acting):

  • cffbrw://schema/execution-plan — the JSON format it needs to generate
  • cffbrw://schema/step-types — all 9 step types (http_request, ai_call, transform, conditional, loop, etc.)
  • cffbrw://tools/available — what external MCP tools you’ve registered
  • cffbrw://examples/plans — examples from simple to complex

Tools (what the AI can actually do):

  • validate_plan — submit JSON, get errors or a validated plan
  • deploy_workflow / update_workflow — create or update workflows
  • run_workflow — execute, get back a run ID and WebSocket URL
  • get_run_status — poll until it’s done
  • Plus get_workflow, list_runs, list_workflows

There’s also a compile prompt that tells the AI “read the resources, take this markdown, give me valid JSON.” But honestly the resources contain everything — the prompt is optional.

The auth model (learned this the hard way)

Remember the voucher race condition? Yeah, I’ve gotten more careful about security since then.

Two access levels:

  • wfk_ API keys — scoped to ONE specific workflow. Can read, update, and run it (only if published). Cannot see other workflows or create new ones. If this key leaks, damage is contained.
  • Clerk JWT — full workspace access. Everything works. For the dashboard and admin stuff.

So a CI/CD key can trigger your workflow but can’t create arbitrary new ones. That felt important after the voucher incident taught me that “it’ll probably be fine” is not a security strategy 😄

Expression eval: because eval() doesn’t work on Cloudflare (not any more)

This was a fun rabbit hole. Workflows need to evaluate expressions — like referencing a previous step’s output or doing a conditional check. But Cloudflare Workers blocks eval() and new Function(). Just… doesn’t work. I learned this the hard way (as I learn most things).

So I built a three-tier system:

  • Tier A — Simple string templates like "Hello ${steps.step_0.name}". Parsed by an AST walker (jsep). Under 1ms. Handles most cases.
  • Tier B — More complex expressions. Tries the AST walker first, falls back to a Dynamic Worker sandbox if it’s too gnarly. ~10-30ms for the fallback. And I cannot stress this enough Dynamic Worker is a game-changer. So try it.
  • Tier C — Full JavaScript code blocks in a Dynamic Worker. Network blocked by default. The escape hatch.
    And I cannot stress this enough Dynamic Worker is a game-changer. So try it.

The AI compiler is told to prefer Tier A/B. Only use complex JS when you actually need multi-statement logic. Because honestly, most workflow expressions are just “get this value from the previous step.”

The stack (for the curious)

Everything runs on Cloudflare:

  • API: Hono
  • Validation: Zod everywhere — schemas are the single source of truth, TypeScript types derived from them.
  • AI: Claude (primary) + Gemini (fallback). Strategy pattern, so they’re swappable.
  • Storage: D1 for workflows/runs, KV for compilation cache, R2 for artifacts
  • Real-time: Cloudflare Agents SDK — NotebookAgent for WebSocket streaming, WorkflowRunner for durable execution. They talk via RPC.
  • Dashboard: TanStack Start + React Flow for DAG visualization
  • Auth: Clerk with organizations for multi-tenant

The Agents SDK was… an experience. The TypeScript types are incomplete, routeAgentRequest() doesn’t work with custom URL patterns, and you need a magic x-partykit-room header that isn’t documented anywhere. I ended up reading node_modules/agents/dist/index.js more than the actual docs. Classic Cloudflare beta experience 😅

So where’s the value now?

🤖 Agent Layer    → BYOM MCP server (any AI can use it)
🔌 Protocol Layer → 8 tools + 4 resources + compile prompt
🖥️ UI Layer       → Dashboard (nice but not the moat)
🗄️ Data Layer     → Run history, step logs, compilation cache (the actual moat)

The dashboard looks cool. But an AI agent talking to the MCP server can do literally everything the dashboard does. The data — workflow definitions, historical runs, step-level logs — is what compounds over time. That’s what no agent can replicate from the outside.

Still building. Still learning. If you want to try the MCP server or have thoughts on how agent-native products should work, I’d love to hear it.

And I believe any builder should use Gstack from Garry Tan. Old news I know, but for people who live under a rock like I do.

So cheers! ☕

Building a single-pass rich-text DSL parser (no regex)

I built this while trying to replace Markdown in my personal blog.
Markdown is convenient, but once you start adding custom syntax, parsing rules quickly become hard to reason about.

I wanted something with:

  • predictable parsing behavior
  • nested structures without ambiguity
  • extensible semantics
  • minimal syntax surface

So instead of a regex-driven approach, I implemented a single-pass recursive descent parser with no backtracking.
The parser handles three forms — inline, block, and raw — all using the same core state machine.

For example:

$$bold(hello)$$

$$info()*
This is a block tag
*end$$

Handlers are pluggable, so the grammar stays small while behavior is defined externally.
This keeps the parser focused on structure, not rendering.

The result is a lightweight DSL that works well for structured rich text, especially in static site generators or blog engines.

GitHub: https://github.com/chiba233/yumeDSL
npm: https://www.npmjs.com/package/yume-dsl-rich-text
demo: https://qwwq.org/blog/dsl-fallback-museum

The Venus Protocol Donation Attack: How a Dismissed Audit Finding Became a $2.15M Bad Debt — Twice

On March 15, 2026, Venus Protocol on BNB Chain was hit by an exploit that left it with $2.15 million in bad debt. The attack targeted the THENA (THE) token market using a donation attack — a vulnerability class so well-known it was literally flagged in Venus’s own Code4rena audit.

The protocol dismissed the finding. Then it got exploited. Again.

Here’s the anatomy of what went wrong, why three separate lines of defense failed simultaneously, and what every Compound-fork lending protocol needs to learn from this.

The Vulnerability: Donation Attacks in Compound Forks

In Compound-style lending protocols, supply caps limit how much of a given token can be deposited as collateral. But here’s the critical design flaw: most implementations only enforce this cap on the mint path — the standard deposit function that issues vTokens (or cTokens).

They don’t account for tokens transferred directly to the contract address.

// The mint path checks the supply cap
function mintInternal(uint mintAmount) internal {
    require(totalSupply + mintAmount <= supplyCap, "supply cap exceeded");
    // ... mint vTokens
}

// But a direct ERC-20 transfer bypasses everything:
// IERC20(THE).transfer(address(vTHE), amount);
// No cap check. No access control. Just... more tokens.

When tokens are sent directly to the vToken contract, the contract’s underlying balance increases without minting new vTokens. Since the exchangeRate is calculated as:

exchangeRate = (underlyingBalance + totalBorrows - reserves) / totalSupply

This inflates the exchange rate. Every existing vToken holder suddenly has more borrowing power — without any supply cap check firing.

The 9-Month Setup

This wasn’t a flash loan one-block wonder. The attacker started in June 2025:

  1. Funded via Tornado Cash: 7,447 ETH → deposited into Aave as collateral → borrowed $9.92M in stablecoins
  2. Gradual accumulation: Over 9 months, systematically bought THE tokens across multiple wallets
  3. Reached 84% of supply cap: By March 15, the attacker controlled ~12.2M THE out of the 14.5M cap

Every single one of these transactions was visible on-chain. The address had even been flagged by community members. Venus declined to act, citing decentralization.

The Execution

On March 15, 2026 at 11:00 UTC, the attacker deployed a malicious contract and executed the donation attack:

  1. Donated THE directly to the vTHE contract, bypassing the supply cap
  2. Exchange rate inflated 3.81x, massively increasing borrowing power
  3. Borrowed ~$14.9M in BTCB, CAKE, USDC, and BNB against the inflated collateral
  4. Repeated the cycle: borrowed → swapped for THE → donated → borrowed more
  5. THE price pushed from $0.263 to over $0.51 during the manipulation
  6. Total THE in Venus reached 53.23M — that’s 367% of the 14.5M supply cap

Three Lines of Defense, Three Failures

1. Supply Cap (Bypassed)

The supply cap only guarded the mint function. Direct transfers went unchecked. This is the core vulnerability.

Fix: Supply caps must account for the contract’s actual underlying balance, not just minted supply. The getCashPrior() function reads the raw token balance — any discrepancy between this and the tracked supply should trigger circuit breakers.

2. Oracle-Based Collateral Valuation (Manipulated)

The attacker’s buy-and-donate cycle artificially inflated THE’s market price alongside the exchange rate manipulation. The oracle faithfully reported the manipulated price.

Fix: TWAP oracles with longer windows, circuit breakers on rapid price movements, and collateral factor adjustments based on on-chain liquidity depth.

3. Liquidation Market (Overwhelmed)

254 liquidation bots competed across 8,048 transactions to unwind the position. Despite this aggressive competition, $2.15M in bad debt remained. The THE token’s price collapsed from $0.51 to $0.22 during the liquidation cascade — below its pre-attack level — making each successive liquidation less effective.

Fix: Gradual liquidation mechanisms (like Aave’s soft liquidation), protocol-owned liquidation reserves, and dynamic liquidation incentives that scale with position risk.

The Audit Finding That Was Dismissed

This is the most damning part. The donation attack vector was explicitly identified during Venus Protocol’s Code4rena security audit. The Venus team’s response:

“Donations are an intentional feature with no negative side effects.”

This wasn’t ignorance. It was a conscious decision to accept risk that the team didn’t fully understand.

And it wasn’t even the first time. In February 2025, Venus suffered the same class of attack on its zkSync deployment, resulting in $700K+ in bad debt. The protocol saw the same vulnerability exploited, patched the specific instance, and still didn’t address the systemic issue across all deployments.

Detection Opportunities That Were Missed

The 9-month preparation window provided multiple detection signals:

  • Single entity accumulating 84% of supply cap — visible from June 2025 onwards via position concentration monitoring
  • Tornado Cash-funded address active in protocol — visible from June 2025 via funding source analysis
  • Community flagging of suspicious address — months before exploit via governance alerts
  • Rapid position changes on attack day — March 15, 11:00 UTC via real-time transaction monitoring

None of these required sophisticated tooling. Basic position concentration alerts — “notify when any address holds >50% of a market’s supply” — would have surfaced this months in advance.

Takeaways for Builders and Auditors

If You’re Building a Compound Fork

  1. Validate supply against actual balance, not just mint accounting. Add invariant checks:
require(
    IERC20(underlying).balanceOf(address(this)) <= expectedBalance + DUST_THRESHOLD,
    "unexpected balance increase"
);
  1. Don’t dismiss audit findings because the behavior is “intentional.” Intent doesn’t equal safety.

  2. Monitor position concentration as a first-class risk metric, not an afterthought.

  3. Implement exchange rate change limits — if the rate jumps >X% in a single block, pause the market.

If You’re Auditing

  1. Test the full deposit surface, not just the intended entry points. Direct transfers, transferFrom with pre-approved amounts, and callback-based deposits should all be validated.

  2. Challenge “won’t fix” responses with concrete attack scenarios. “This is intended behavior” is not a security argument.

  3. Check for repeat vulnerabilities across different deployments of the same protocol. If it happened on zkSync, it can happen on BNB Chain.

If You’re Running a Lending Protocol

  1. Position concentration is a leading indicator. A single address at 84% of your supply cap is a red flag, not a feature.

  2. Historical exploits on your own protocol are the highest-signal intelligence you have. Venus ignored its own zkSync incident. Don’t repeat this pattern.

  3. Liquidation markets have limits. 254 competing bots still couldn’t prevent $2.15M in bad debt. Design for liquidation failure, not just liquidation success.

Timeline

  • June 2025 — Attacker begins accumulating THE via Tornado Cash funding
  • Feb 2025 — Venus zkSync deployment suffers similar donation attack ($700K+ bad debt)
  • Pre-attack — Community flags suspicious address; Venus declines action
  • Mar 15, 2026 11:00 UTC — Attacker deploys malicious contract, begins donation attack
  • Mar 15, 2026 ~12:00 UTC — 8,048 liquidation transactions begin unwinding the position
  • Post-attack — Venus suspends THE lending/borrowing, sets collateral factor to zero

The Venus incident is a masterclass in how DeFi protocols fail: not because the vulnerability was unknown, but because the warning was ignored. The audit found it. The community flagged it. A previous exploit demonstrated it. And still, three lines of defense crumbled against an attacker who took nine months to prepare.

The lesson isn’t “get better audits.” It’s listen to the audits you already have.

References: Venus Post-Mortem, BlockSec Analysis, Halborn Writeup

From Queries to Bytecode: The Final Pieces of SQLite’s Frontend

PS5 DualSense controller with haptic feedback — you already know what intentional haptic feedback feels like from your hands every day

Hello, I’m Maneshwar. I’m building git-lrc, an AI code reviewer that runs on every commit. It is free, unlimited, and source-available on Github. Star Us to help devs discover the project. Do give it a try and share your feedback for improving the product.

In the previous part, you saw how SQLite selects indexes and balances filtering with sorting.

Now we move into another important category of queries.

Aggregation and subqueries.

These introduce new challenges because SQLite is no longer just filtering rows.

It is grouping, transforming, and sometimes restructuring queries entirely.

How SQLite Executes GROUP BY

When you use a GROUP BY clause, SQLite introduces a special internal structure called an aggregator.

This is essentially a temporary table that stores:

  • A key → formed by GROUP BY columns
  • A value → aggregate data like COUNT, SUM, etc.

For example:

SELECT department, COUNT(*) FROM employees GROUP BY department;

SQLite processes this in two phases.

First, it scans rows and builds groups.

Then, it produces the final results.

The execution pattern looks like this:

where-begin  
    compute group-by key  
    focus on the group-by key  
    update aggregate terms  
where-end  
foreach group-by  
    compute result-set  
    send result to caller  
end-foreach

During the first phase, SQLite keeps updating aggregate values for each group.

During the second phase, it outputs the final computed results for each group

This approach ensures that rows are grouped efficiently without repeatedly scanning the same data.

The Problem with Subqueries in FROM

Now consider queries that use subqueries inside the FROM clause.

Example:

SELECT a FROM (
    SELECT x + y AS a FROM t1 WHERE z < 100
) WHERE a > 5;

The default way to execute this is:

  1. Run the inner query
  2. Store results in a temporary table
  3. Run the outer query on that table
  4. Delete the temporary table

This approach has a major drawback.

The temporary table has no indexes, so any filtering or joining done by the outer query becomes inefficient.

It also requires scanning data multiple times.

Subquery Flattening: A Smarter Approach

To avoid this overhead, SQLite uses an optimization called subquery flattening.

Instead of executing the subquery separately, SQLite merges it into the outer query.

The previous example becomes:

SELECT x + y AS a FROM t1 WHERE z < 100 AND a > 5;

Now the query can be executed in a single pass over the table.

This has two major benefits:

  • Eliminates temporary tables
  • Allows indexes on the base table to be used

This significantly improves performance

When Flattening Is Allowed

Flattening is not always possible.

SQLite applies this optimization only when a strict set of conditions is satisfied.

  1. The subquery and the outer query do not both use aggregates.
  2. The subquery is not an aggregate or the outer query is not a join.
  3. The subquery is not the right operand of a left outer join.
  4. The subquery is not DISTINCT or the outer query is not a join.
  5. The subquery is not DISTINCT or the outer query does not use aggregates.
  6. The subquery does not use aggregates or the outer query is not DISTINCT.
  7. The subquery has a FROM clause.
  8. The subquery does not use LIMIT or the outer query is not a join.
  9. The subquery does not use LIMIT or the outer query does not use aggregates.
  10. The subquery does not use aggregates or the outer query does not use LIMIT.
  11. The subquery and the outer query do not both have ORDER BY clauses.
  12. The subquery and outer query do not both use LIMIT.
  13. The subquery does not use OFFSET.
  14. The outer query is not part of a compound select or the subquery does not have both an ORDER BY and a LIMIT clause
  15. The outer query is not an aggregate or the subquery does not contain ORDER BY
  16. The sub-query is not a compound select, or it is a UNION ALL compound clause made up entirely of non-aggregate queries, and the parent query:
    • is not itself part of a compound select,
    • is not an aggregate or DISTINCT query, and
    • has no other tables or sub-selects in the FROM clause. The parent and sub-query may contain WHERE clauses.
    Subject to rules (11), (12) and (13), they may also contain ORDER BY, LIMIT and OFFSET clauses.
  17. If the sub-query is a compound select, then all terms of the ORDER by clause of the parent must be simple references to columns of the sub-query.
  18. The subquery does not use LIMIT or the outer query does not have a WHERE clause
  19. If the sub-query is a compound select, then it must not use an ORDER BY clause.

Query flattening is an important optimization when views are used because each use of a view is translated into a su bquery.

Fast MIN and MAX Queries

Aggregation is not always expensive.

SQLite has a very efficient optimization for queries like:

SELECT MIN(age) FROM users;

or

SELECT MAX(age) FROM users;

If there is no index on the column, SQLite must scan the entire table.

But if an index exists, SQLite can do something much faster.

It directly navigates to:

  • The first entry in the index for MIN
  • The last entry in the index for MAX

Since indexes are stored as B-trees, this operation takes logarithmic time, not linear time.

If the column is an INTEGER PRIMARY KEY, SQLite can even use the table’s primary B+ tree directly.

This makes MIN and MAX queries extremely efficient when proper indexing is in place

Bringing It All Together

At this point, you have seen how SQLite handles:

  • Filtering using WHERE
  • Choosing indexes
  • Ordering joins
  • Grouping results
  • Flattening subqueries
  • Optimizing aggregations

All of this happens before execution, inside the frontend.

By the time the Virtual Machine runs, everything has already been carefully planned and optimized.

Final Thoughts on the Frontend

The SQLite frontend is a complete pipeline:

  • The tokenizer breaks SQL into tokens
  • The parser builds structured representations
  • The optimizer reshapes queries for efficiency
  • The code generator produces executable bytecode

All of this work is triggered by a single function call:

sqlite3_prepare()

Query optimization remains one of the most complex and delicate parts of any database system.

SQLite keeps things relatively simple by using heuristics instead of heavy statistical models, but still manages to achieve strong performance in most real-world scenarios

What’s Next

We have now covered the entire frontend pipeline of SQLite, from raw SQL to optimized bytecode.

In the next series, we will move beyond compilation and explore how SQLite interacts with the outside world.

We will start with the SQLite Interface Handler, where queries enter the system and results are returned.

git-lrc
*AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.

git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.*

Any feedback or contributors are welcome! It’s online, source-available, and ready for anyone to use.

⭐ Star it on GitHub:

GitHub logo

HexmosTech
/
git-lrc

Free, Unlimited AI Code Reviews That Run on Commit

AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs — without telling you. You often find out in production.

git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.

See It In Action

See git-lrc catch serious security issues such as leaked credentials, expensive cloud
operations, and sensitive material in log statements

git-lrc-intro-60s.mp4

Why

  • 🤖 AI agents silently break things. Code removed. Logic changed. Edge cases gone. You won’t notice until production.
  • 🔍 Catch it before it ships. AI-powered inline comments show you exactly what changed and what looks wrong.
  • 🔁 Build a…

Meet Good Design

In plain terms: software that’s easy to change without breaking what’s already there.

I hope you are starting to see the enemy. Complexity makes our systems hard to understand and hard to change — and left unchecked, it only compounds. But it helps to know not just what we’re fighting against, but what we’re fighting for. What does a system look like when complexity isn’t winning? What does it feel like to work in one?

Good design has a few common characteristics. They’re worth knowing, because they’re not just abstract ideals — they’re the goals that every tool and principle we’ll cover in this series is working toward.

The first thing you notice in a well-designed system is that it doesn’t ask too much of you. The information you need is clear and present. What a component does is apparent. You can reason about a part of the system without needing to hold the entire thing in your head — remember Ousterhout’s definition: anything that makes a system hard to understand and modify. A system that overwhelms you with things to know is a complex system, almost by definition.

Closely related is consistency. It operates at two levels. Within a component, consistency means the thing behaves predictably according to its own rules — you can learn how it works once and trust that it holds. Across the system, consistency means shared standards — naming conventions, patterns, error handling approaches — that make the codebase feel like it was written by one coherent mind rather than assembled from unrelated pieces. Independence gives different parts of a system the freedom to make their own decisions, but that freedom shouldn’t extend to arbitrariness. When different parts behave differently for no good reason, every part becomes its own puzzle. Inconsistency is a form of accidental complexity — it makes things harder to understand without making them any more capable. A consistent system respects the developer’s time and reduces the number of surprises.

The next cluster of qualities all relate to change — which makes sense, because software that can’t change gracefully is software that will become a burden. A well-designed system is flexible: you can change one part without disrupting the whole. The parts are independent enough to be worked on separately, to grow at their own pace, to be improved without requiring a coordinated effort across the entire codebase.

This flexibility is what makes a system stable. Stability here doesn’t mean frozen — it means that existing functionality is protected. When you add something new, you shouldn’t have to worry about what you might be breaking. When you fix a bug, the fix should stay contained. The risk of every change should be proportional to its scope, not amplified by hidden dependencies and tangled responsibilities.

Good design is also extendable. When a new requirement comes in, you shouldn’t have to rewrite what already exists — you should be able to build on it. This is one of the most practical tests of a design: can I add to this without dismantling it? Extendability is closely tied to reusability. Components that are focused and independent can be used in multiple places and contexts without being tailored to any specific one. You write something once, and it earns its keep many times over. Every time you reuse a well-designed component, you’re getting more value out of the original investment — and you’re not introducing new places for things to go wrong.

And then there’s testability. It might seem like a separate concern — a QA thing, not a design thing — but it’s actually one of the best signals of good design you have. It turns out that code that is clear, consistent, focused, and independent is also easy to test. You can test a component in isolation, without needing to set up the entire system around it. You can verify its behavior precisely and thoroughly. And when you have that kind of confidence in the individual parts, you have confidence in the whole. Testability isn’t just a nice-to-have — it’s a consequence of doing everything else right.

All of these qualities point toward the same thing. When your design is working, making a change means going to one place. Not one function or one line of code — that’s too literal. But one isolated, independent part of the system where the concern lives, where the change can be made, where it can be understood and tested without pulling the rest of the system into view. The change — whether it’s a new feature, a bug fix, a performance improvement, or a third-party integration — stays contained. The rest of the system doesn’t even notice.

That’s the goal. It’s optimistic, and real systems are messier than any ideal. But it’s a useful test: when you sit down to make a change, how many places do you have to touch? How much do you have to understand before you feel safe? The answer tells you a lot about the health of your design.

Now that we know what we’re fighting for, we can start talking about how to get there. The tools and principles we’ll cover from here aren’t abstract exercises — they’re the specific things that make these qualities possible. Each one is a way of pushing your design closer to the system you just read about.

I Built an Open-Source Alternative to $500/Year Trading Signal Tools

I was spending $47/month on TradingView Pro + 3Commas to get decent trading signals for XAUUSD, BTCUSD, and EURUSD. After a year, I cancelled both and built my own.

TradeClaw is now open source: https://github.com/naimkatiman/tradeclaw

What it does

  • Real-time buy/sell signals for 12 assets (XAUUSD, BTCUSD, ETHUSD, EURUSD, GBPUSD, and more)
  • Signal engine: weighted RSI (28%), MACD (20%), EMA crossovers (20%), Support/Resistance (20%), Bollinger Bands (6%), Stochastic (6%)
  • Backtesting against historical data
  • Telegram alerts when signals fire
  • Beautiful dark dashboard with candlestick charts

The cost comparison

Tool Monthly Annual
TradingView Pro $15 $180
3Commas Starter $29 $348
TradeClaw (self-hosted) $5 VPS $60

Deploy in 60 seconds

git clone https://github.com/naimkatiman/tradeclaw
cd tradeclaw
cp .env.example .env
docker compose up -d

Open http://localhost:3000. That’s it.

Why open source?

These tools shouldn’t cost $500/year. The technical analysis is well-understood math. The value isn’t in the algorithm — it’s in the UX, the integrations, and reliability.

TradeClaw is MIT licensed. Self-host it, modify it, contribute back.

Live demo: https://tradeclaw.win/demo
GitHub: https://github.com/naimkatiman/tradeclaw

⭐ Star it if you find it useful — it helps others discover the project.