DEV Community [Unofficial]

@dev.to.web.brid.gy

A space to discuss and keep up software development and manage your software career 🌉 bridged from 🌐 https://dev.to/: https://fed.brid.gy/web/dev.to

Best Tools for Managing Parallel AI Coding Agents in 2026

I’ve been looking at how AI coding agents are changing the way developers work, and one thing is becoming clear: the real opportunity isn’t just getting one agent to write code faster. It’s being able to run several agents at once without losing control of the development process. Imagine having one agent work on a new feature, another fix a bug, and a third write tests, all at the same time. That sounds great until they start making conflicting changes, duplicating work, or leaving you to figure out what each one has actually completed. This is where the right tools make a difference. Managing parallel AI coding agents requires more than launching multiple sessions. You need a way to organize tasks, keep work isolated, monitor progress, and review the results before anything gets merged into your codebase. I wanted to look at the tools that make this workflow easier, from platforms built specifically for coordinating AI agents to coding environments that support background execution and parallel sessions. Here are the best tools for managing parallel AI coding agents in 2026, along with what each one offers and where it fits best. ## What to Look for in a Parallel AI Coding Agent Tool Before choosing a tool, I think it’s worth looking at how it handles the practical challenges of running multiple agents. * **Task coordination:** Can you assign separate tasks to different agents and track their progress? * **Workspace isolation:** Can agents work independently without accidentally overwriting one another’s changes? * **Agent compatibility:** Does the tool work with the coding agents and models you already use? * **Review and integration:** Can you inspect changes, compare diffs, run tests, and decide what gets merged? * **Visibility:** Can you quickly identify which agents are running, blocked, or finished? * **Scalability:** Can you increase the number of concurrent agents without overwhelming your machine or development workflow? Not every tool handles all these responsibilities. Some focus on running coding agents, while others help coordinate the broader development process. ## 1. Sharkly: Best for Coordinating AI Agents Across Development Tasks When I think about managing parallel AI coding agents, I think beyond simply opening several coding sessions. I want a place where tasks are organized, agents can work independently, and I can see what is happening without constantly switching between terminals. That is the problem Sharkly is designed to address. Sharkly brings product and engineering work into a shared workspace. Teams can organize backlogs, manage tasks, assign work to AI agents, and track progress as the agents execute their assignments. Instead of treating each agent session as a separate activity, the workflow connects agent execution to the tasks the team is trying to complete. One useful part of this approach is workspace isolation. When tasks run in separate working directories or worktrees, agents can work on different assignments without automatically modifying the same working files. That makes it easier to run work in parallel while keeping the results organized for review. Sharkly also supports multiple coding agents, including Claude Code, Codex, Gemini CLI, and other supported agent environments. Teams can use their existing coding-agent setups rather than being forced into a single model or provider. Another practical consideration is that Sharkly supports Jira imports and two-way synchronization during migration, which can help teams that already manage their work in Jira. **What I like about the approach:** It treats parallel agent execution as part of the development workflow, rather than simply a way to open more coding sessions. **Best for:** Engineering teams that want task management, agent execution, and progress visibility in one place. **Considerations:** The value depends on how much coordination your workflow needs. Developers who only want to run a couple of isolated agents locally may prefer a lighter setup. Website: Sharkly.ai ## 2. OpenAI Codex: Best for Running Multiple Coding Tasks in Parallel OpenAI Codex is another option worth considering if you want AI agents to handle several development tasks concurrently. The Codex app supports working with multiple agents and threads, allowing developers to delegate different coding tasks instead of waiting for one task to finish before starting another. Its support for worktrees can also help keep changes isolated when agents are working on different parts of a codebase. For example, I could assign one task to investigate a failing test, another to implement a small feature, and a third to review an existing implementation. I would then inspect each result and decide which changes are ready to integrate. This workflow is particularly useful when tasks are reasonably independent. If two agents need to rewrite the same core component, parallel execution can create more review work than it saves. Codex is appealing because it brings agent execution and code review into a developer-focused workflow. However, teams that need a shared task-management layer across many projects may still want a separate coordination platform. **Best for:** Developers who want to delegate multiple coding tasks and review their results within the Codex ecosystem. **Considerations:** Parallel execution does not remove the need to define task boundaries, run tests, and review changes before merging. Website: OpenAI Codex ## 3. Cursor Background Agents: Best for Asynchronous Coding Tasks Cursor is well known as an AI-powered code editor, but its background-agent capabilities also make it relevant for parallel development. Background agents can work asynchronously in remote environments, allowing developers to delegate tasks without keeping every operation in the foreground. Depending on the workflow, agents can work on code, make changes on branches, and return results for review. I find this approach particularly useful for tasks that take time but do not require constant interaction. For example, I could ask an agent to investigate a bug or implement a self-contained improvement while continuing to work on another feature locally. The remote execution model also means that background work does not have to consume all the resources on your own development machine. That can be helpful when running several tasks, although concurrency, usage limits, and pricing depend on the applicable plan and product configuration. Cursor is a strong option for developers who already use its editor and want to extend their workflow with asynchronous agents. **Best for:** Developers who want background coding tasks integrated into their existing Cursor workflow. **Considerations:** Remote execution and branch-based changes are helpful, but developers still need to coordinate overlapping tasks and review the output. Website: Cursor ## 4. Conductor: Best for Managing Multiple Coding Agents in Separate Workspaces Conductor focuses on running multiple coding agents concurrently while keeping their work organized in separate environments. Rather than making every agent operate in the same working directory, Conductor gives parallel tasks their own workspaces. This helps developers run multiple sessions, inspect their changes independently, and manage the results without constantly switching between manually configured environments. It supports several coding-agent workflows, including Claude Code, Codex, Cursor, and OpenCode. This makes it worth considering if you prefer to choose an agent based on the task rather than standardizing on one tool for everything. For instance, I could use one agent to implement a feature and another to investigate a separate issue. Each can work in its own environment, reducing the chance that one task will interfere directly with another's uncommitted changes. The main advantage is organization. When several agents are active, having a clear view of their workspaces and changes can make the development process easier to manage. **Best for:** Developers who want a dedicated environment for running multiple coding agents side by side. **Considerations:** Separate workspaces help prevent direct file collisions, but changes can still conflict when they are eventually combined. Website: Conductor ## 5. Claude Code: Best for Developers Who Want Flexible Agent Workflows Claude Code offers several ways to work with multiple agents, making it useful for developers who want more control over how coding tasks are delegated. Its subagent capabilities allow a primary agent to delegate certain tasks to specialized agents. For example, one agent might investigate a problem while another explores the relevant code or performs a separate analysis. Developers can also use multiple Claude Code sessions and Git worktrees to keep independent coding tasks separated. This is useful when you want to work on different features or investigations without making every agent share the same files. The important distinction is that running multiple Claude Code sessions is not the same as having a complete team-wide task coordination system. Developers still need to decide how tasks are divided, how branches are managed, and how completed changes are reviewed. For people who enjoy customizing their development environment, that flexibility can be an advantage. **Best for:** Developers who want agent-assisted coding, delegated sub-tasks, and flexible terminal-based workflows. **Considerations:** More control can mean more setup and coordination, particularly when managing many independent tasks. Website: Claude Code ## 6. Git Worktrees: Best for Lightweight, DIY Parallel Development Git worktrees are not an AI coding agent manager, but I would still include them in this discussion because they solve one of the most important problems with parallel coding: workspace isolation. A worktree allows you to check out another branch of the same Git repository into a separate directory. Instead of repeatedly switching branches in one working directory, you can have multiple working directories available at the same time. That makes it possible to run separate coding agents against separate worktrees. One agent might implement a feature while another fixes a bug in a different worktree. The benefit is that the agents do not automatically edit the same working files. You can inspect each branch, run tests, and integrate the changes when they are ready. However, worktrees do not provide task assignment, agent monitoring, or automatic conflict resolution by themselves. You still need to manage the sessions, track progress, and decide when to merge changes. **Best for:** Developers who want a lightweight way to run parallel agents using their existing Git workflow. **Considerations:** Worktrees require some manual coordination, and separate branches can still produce merge conflicts. Documentation: Git Worktree ## 7. OpenAI Agents SDK: Best for Building Custom Multi-Agent Systems The OpenAI Agents SDK takes a different approach from the developer tools above. Rather than providing a ready-made interface for coordinating coding sessions, it gives developers building blocks for creating agent-powered applications and workflows. You can define agents with different instructions and tools, then configure how work is handed off or delegated. In suitable workflows, a coordinating agent can assign independent tasks to other agents and combine their results. For example, a custom development assistant could ask one agent to analyze a bug report, another to identify relevant code, and a third to propose tests. A coordinating workflow could then collect the findings and present them for review. This approach offers considerable flexibility, particularly if you want to build your own internal developer platform or automate a specialized engineering process. However, it also means that you are responsible for implementing the surrounding workflow. Workspace isolation, task tracking, Git operations, access controls, and code review may require additional components. **Best for:** Developers and engineering teams building custom multi-agent applications or orchestration systems. **Considerations:** It is a framework for building agent workflows, not an out-of-the-box project-management interface for parallel coding tasks. Documentation: OpenAI Agents SDK ## Quick Comparison These tools are not direct substitutes for one another. Some focus on executing coding tasks, some provide a way to isolate work, and others help coordinate the wider development process. ## How I Would Choose the Right Tool After comparing these options, I would start with the main problem I am trying to solve rather than the number of agents a tool claims to support. **If I want task management and agent execution together,** I would look at Sharkly. It connects agent assignments to a broader engineering workflow, which is useful when several people and agents are contributing to the same projects. **If I mainly want to delegate coding tasks,** I would consider Codex or Cursor Background Agents, depending on the environment I already use. **If workspace separation is my biggest concern,** Conductor or Git worktrees would be worth exploring. Both approaches help keep concurrent work organized, although the amount of manual setup differs. **If I want more control over how agents collaborate,** Claude Code or the OpenAI Agents SDK may be a better fit. The former supports flexible coding-agent workflows, while the latter is useful when building custom orchestration. I would also consider the cost of running agents concurrently. More agents can mean more compute usage, model consumption, and review overhead. Starting with two or three independent tasks is often more useful than immediately trying to run ten agents at once. ## Best Practices for Managing Parallel AI Coding Agents Choosing a tool is only part of the equation. How you structure the work matters just as much. ### 1. Give Each Agent a Clear, Independent Task Avoid assigning several agents vague instructions such as "improve the application." Instead, define the expected outcome, relevant files or components, constraints, and acceptance criteria. For example, an agent can update input validation while another adds tests for an unrelated service. This makes it easier to identify overlapping work before execution begins. ### 2. Isolate Workspaces Where Possible When agents share the same working directory, their changes can interfere with each other. Separate worktrees or isolated execution environments help reduce this risk. Isolation does not eliminate every problem, though. Two agents working on separate branches can still make incompatible changes to the same API or shared component. ### 3. Track Progress and Blockers When multiple agents are running, it is easy to lose track of which tasks are complete and which are waiting for input. Use a task board, agent dashboard, or another tracking system to record assignments, status, blockers, and results. This becomes increasingly important as the number of concurrent tasks grows. ### 4. Limit Concurrency to What You Can Review Running more agents does not automatically make development faster. If ten agents finish at the same time and you cannot review their work, you may simply move the bottleneck from coding to quality assurance. Start small, measure how much review each task requires, and increase concurrency only when the workflow can support it. ### 5. Review and Test Every Change Treat agent output as a proposed change, not a guarantee of correctness. Inspect the diff, run relevant tests, and check for regressions before merging. For important changes, consider security, performance, dependency updates, and compatibility with the rest of the application. ### 6. Keep Dependencies Between Tasks Explicit Some tasks cannot begin until others are finished. For example, a frontend agent may depend on an API contract that another agent is still implementing. Identify these dependencies in advance. Run independent work in parallel and sequence tasks that depend on one another. ## Frequently Asked Questions ### What does it mean to run AI coding agents in parallel? It means running multiple AI agents on separate coding tasks at the same time rather than completing every task sequentially. Depending on the tools, agents may run in separate sessions, worktrees, remote environments, or coordinated workflows. ### Can multiple AI coding agents work on the same repository? Yes. Multiple agents can work on the same repository, but they should generally use separate branches, worktrees, or isolated environments when changing code concurrently. Their changes still need to be integrated and tested. ### Is a parallel AI coding agent manager necessary? Not always. If you run only two agents occasionally, terminal sessions and Git worktrees may be sufficient. A dedicated coordination platform becomes more valuable when you need to assign tasks, monitor progress, manage several workspaces, and review results consistently. ### Which tool is best for managing parallel AI coding agents? There is no single best option for every developer. Sharkly is worth considering for task coordination and agent execution, Codex and Cursor for their respective coding workflows, Conductor for managing separate agent workspaces, and Git worktrees for a lightweight manual approach. ### Does running more agents make coding faster? Not necessarily. Parallel execution can reduce waiting time when tasks are independent, but it can also increase model usage, compute costs, review effort, and integration conflicts. The best results usually come from dividing work carefully and keeping tasks independent. ## Final Thoughts I think parallel AI coding agents are most useful when they help developers move several independent tasks forward without creating extra coordination work. The tools in this list approach the problem from different angles. Some focus on coding, others on workspace isolation, and others on coordinating agents across a broader development workflow. For a lightweight setup, Git worktrees can be enough. If you already work in Cursor, its background agents are a natural option to explore. Codex and Claude Code offer their own approaches to delegating and running coding tasks, while the OpenAI Agents SDK is useful for building custom multi-agent workflows. For teams that want to connect agent execution with task management and progress tracking, I would also take a look at Sharkly. Ultimately, the goal is not to run as many agents as possible. It is to get more useful development work done while keeping the codebase organized, the results reviewable, and the final decisions in the hands of the people responsible for the software.

dev.to

Stop Pasting API Keys Into Your Terminal: Use Windows Environment Variables Instead

> **TL;DR:** Windows System Environment Variables store API keys and secrets at the operating system level, eliminating terminal history leaks and accidental GitHub commits. Set them once through Windows settings, reference them in code via `process.env` or `os.getenv()`, and never paste another credential into a terminal again. Every developer has done it at least once. You copy an API key from OpenAI or Anthropic, paste it directly into a terminal command or test script, and run your code. It works. You move on. A few weeks later, you push that script to GitHub without thinking. Your key is now public. Forever. Terminal history leaks and accidental commits are not edge cases. They are the default outcome when you treat secrets like throwaway strings. Windows System Environment Variables exist to solve this exact problem, yet most developers skip them because they assume environment variables are "too advanced" or "only for production." That assumption is wrong. Environment variables are not advanced. They are foundational. Once you understand how they work, you will never paste another API key into a terminal again. ## What Windows System Environment Variables Actually Are Windows System Environment Variables are key-value pairs stored inside Windows itself. They live at the operating system level, not inside your code, not inside your terminal session, and not inside any file you will ever commit to version control. Each variable has a name and a value. The name is how your application references it. The value is what your application retrieves. You set them once through Windows settings, and every application running under your user account can read them. Two types exist: **User Environment Variables** and **System Environment Variables**. User variables apply only to your Windows user account. System variables apply to every user on the machine. As a developer, you will almost always use User Environment Variables because you want secrets scoped to you alone. When you store a variable like `OPENAI_API_KEY`, Windows makes it available to every process you launch. Node.js can read it. Python can read it. PowerShell can read it. Your text editor can read it. You set it once. It persists across reboots. It never touches your codebase. This is not new technology. Environment variables have been part of operating systems since Unix systems in the 1970s. They are battle-tested, widely supported, and built into every major programming language runtime. They are also the standard way production environments manage secrets in cloud platforms, CI/CD pipelines, and containerized deployments. > Environment variables have been part of operating systems since Unix systems in the 1970s, and they remain the industry standard for managing secrets in production environments today. ## Why Developers Rely on Environment Variables for Secrets The core reason is simple: **separation of configuration from code**. Your source code should describe logic, not contain live credentials. Hard-coding an API key into a script ties your code to one environment, one key, and one security failure waiting to happen. Environment variables decouple secrets from source files. Your code references `process.env.OPENAI_API_KEY` in Node.js or `os.getenv("OPENAI_API_KEY")` in Python. The actual key lives in Windows, completely separate from your repository. This separation unlocks three immediate benefits: * **Security** : Your API key never appears in a file you can commit. It never shows up in terminal history. It never gets logged by accident. Someone reading your GitHub repo will see `process.env.OPENAI_API_KEY` but not the actual key. * **Portability** : You can share your codebase with teammates or publish it publicly without sharing secrets. Each developer sets their own environment variables on their own machine. The code stays identical. * **Flexibility** : You can change an API key by updating one Windows setting, not by hunting through every script where you hard-coded it. You can use different keys for different projects without modifying code. Production deployments use environment variables for the same reasons. Heroku, Vercel, AWS Lambda, and every major cloud platform inject secrets as environment variables at runtime. Learning to use them locally means you are already practicing production patterns on your own machine, which directly ties into **AI tools impact your data privacy** in modern deployment workflows. ## The Risky Path: Pasting Keys Into Terminals and Scripts Let's walk through what actually happens when you paste an API key directly into PowerShell or a script file. You open PowerShell. You run a command like this: `powershell curl -H "Authorization: Bearer sk-proj-abc123yourkeyhere" https://api.openai.com/v1/models ` The command works. You get your response. What you don't see is PowerShell writing that entire command, including your API key, into its history file. > On Windows, PowerShell history is stored at `%APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt` where anyone with access to your user account can extract every command you've ever run. Anyone with access to your user account can open that file and read every command you have ever run. If you share your machine, if you back up that folder to cloud storage, or if malware scans common history paths, your key is exposed. The second failure mode happens when you paste a key into a script file. You create `test-openai.js` and write: `javascript const apiKey = "sk-proj-abc123yourkeyhere"; fetch("https://api.openai.com/v1/models", { headers: { Authorization: `Bearer ${apiKey}` } }); ` You test it. It works. You keep working. A week later, you commit that file to GitHub without realizing the key is still inside. GitHub scans for exposed secrets and may revoke the key automatically, but that depends on pattern matching. If you obfuscated it slightly or if it doesn't match known patterns, the key stays public. Even if you delete the file later, it remains permanently accessible in Git commit history. Anyone who clones your repository can run `git log` and extract the key from an earlier commit. Deletion doesn't remove history. It just hides it from the current branch view. ### What Goes Wrong in Real Scenarios Here's a real failure case. You initialize a new Node.js project and create `test.js`: `javascript const apiKey = "sk-proj-realkeyfromanthropichere"; console.log("Testing API key:", apiKey); ` You run it: `bash node test.js ` Output: `plaintext Testing API key: sk-proj-realkeyfromanthropichere ` You forget about the file. Two weeks later, you run `git add .` and `git commit -m "initial commit"` without checking what's staged. The key is now in your repository forever. You can remove the file in a later commit, but the original commit still contains it. Anyone who forks or clones your repo can retrieve it. The fix is simple: never put the key in the file in the first place. ## The Secure Path: Windows User Environment Variables They can be reviewed using a system configuration audit checker. Here's how to store an API key as a User Environment Variable on Windows, then reference it in your code. ### Step 1: Open Environment Variables Settings Press `Win + S` and search for "environment variables." Click **Edit the system environment variables**. In the System Properties window, click **Environment Variables** at the bottom. You will see two sections: **User variables** (top) and **System variables** (bottom). Click **New** under User variables. ### Step 2: Create Your Variable In the New User Variable dialog: * **Variable name** : `OPENAI_API_KEY` * **Variable value** : `sk-proj-yourrealkeyhere` Click **OK** , then **OK** again to close all dialogs. The variable is now stored in Windows. ### Step 3: Restart Any Open Terminals Environment variables are loaded when a process starts. If you already have PowerShell, Command Prompt, or VS Code open, close and reopen them. The new variable will be available in all new sessions. ### Step 4: Reference the Variable in Code In Node.js: `javascript const apiKey = process.env.OPENAI_API_KEY; console.log("API key loaded:", apiKey ? "Yes" : "No"); ` In Python: `python import os api_key = os.getenv("OPENAI_API_KEY") print(f"API key loaded: {'Yes' if api_key else 'No'}") ` In PowerShell: `powershell $apiKey = $env:OPENAI_API_KEY Write-Host "API key loaded: $($apiKey -ne $null)" ` Your code now reads the key from Windows at runtime. The key never appears in your source file. You can commit `test.js` or `test.py` to GitHub safely. Anyone who clones your repo will see `process.env.OPENAI_API_KEY` but not the actual key. ### Step 5: Verify It Works Run your script again: `bash node test.js ` Expected output: `plaintext API key loaded: Yes ` If you see "No", you either mistyped the variable name or forgot to restart your terminal. ## What You Gain as a Developer Using environment variables changes how you write, share, and deploy code. Here's what you gain immediately: **No more terminal history leaks.** You never paste a key into a command again. Your PowerShell history file contains no secrets. If you share your machine or back up that folder, no credentials are exposed. **No more accidental commits.** Your source files reference `process.env.OPENAI_API_KEY`, not the actual key. You can commit every script, push to GitHub, and share your code publicly without exposing credentials. **Portable code.** Your teammate clones your repo, sets their own `OPENAI_API_KEY` variable on their machine, and runs the same code without modification. Different developers, different keys, identical codebase. **Production-ready patterns.** You are already using the same secret management approach that Heroku, Vercel, AWS Lambda, and every major cloud platform uses. When you deploy, you set environment variables in the platform's dashboard. Your code stays identical. **Easier key rotation.** You can change an API key by updating one Windows setting. You don't hunt through every script where you hard-coded it. You update the variable once, and every script that references it picks up the new value. > Separation of configuration from code is not just a best practice. It is the foundational pattern that makes modern DevOps workflows possible. This approach scales beyond API keys. Database connection strings, OAuth client secrets, feature flags, deployment URLs, and any configuration that changes between environments should live in environment variables. Your code references the variable. The environment provides the value. This pattern is outlined in detail in our DevOps best practices guide. ## Common Mistakes to Avoid Even after setting environment variables correctly, developers make predictable mistakes. Here are the most common: **Logging the variable value during debugging.** You write `console.log(process.env.OPENAI_API_KEY)` to verify it loaded, then forget to remove the line. Now your key is in your application logs. If those logs go to a monitoring service or get committed to a repo, the key is exposed again. Log "loaded" or "not loaded", never the actual value. **Committing`.env` files.** Many Node.js projects use `.env` files with libraries like `dotenv`. These files store environment variables locally for development. They are not environment variables in the Windows sense. They are plain text files. If you commit `.env` to Git, you commit all the secrets inside. Always add `.env` to `.gitignore`. **Using System Environment Variables instead of User.** System variables apply to all users on the machine. If you store a personal API key as a System variable, every user account can read it. Use User variables for personal secrets, System variables only for shared configuration. **Forgetting to restart terminals.** Environment variables are loaded when a process starts. If you set a variable and immediately try to use it in an already-open terminal, it won't be there. Close and reopen the terminal first. **Hardcoding fallback values.** You write `const apiKey = process.env.OPENAI_API_KEY || "sk-proj-fallbackkey";` intending to make the code "more robust." Now your code works even when the variable isn't set, and the fallback key ends up in your repository. Never hardcode fallback secrets. If the variable is missing, fail loudly. ## Where to Go From Here You now know how to store secrets outside your codebase using Windows System Environment Variables. This is the single most effective way to prevent terminal history leaks and accidental commits. Start by identifying every API key, database password, or secret you currently have hardcoded in a script or pasted in a terminal. Move each one to a User Environment Variable. Update your code to reference `process.env.VARIABLE_NAME` or `os.getenv("VARIABLE_NAME")`. Restart your terminal. Run your code. Verify it works. Do this once for every secret you use. From that point forward, you will never paste a key into a terminal again. Your code will be shareable, portable, and production-ready. Your secrets will stay secret. This is not advanced. This is foundational. Every developer should know how to do this before they run their first API call. If you skipped it until now, you haven't lost anything. You just gained the most important security habit you will ever build. ## 📦 Publishing Kit — Dev.to ### Title Options (5) **Selected:** Stop Pasting API Keys Into Your Terminal: Use Windows Environment Variables Instead **Alternates:** 1. How to Permanently Stop Leaking API Keys Using Windows System Environment Variables 2. Windows Environment Variables: The Developer's Guide to Never Committing Secrets Again 3. Secure Your API Keys Forever: A Practical Guide to Windows Environment Variables 4. Why Every Developer Should Use Windows Environment Variables for API Keys and Secrets ### Slug stop-pasting-api-keys-use-windows-environment-variables ### Tags security, tutorial, beginners, envvars

dev.to

Firefox for Security Researchers: Burp Suite, XSS Testing, Certificate Analysis, Privacy Auditing

Firefox has become a go-to browser for security researchers, penetration testers, and privacy advocates. Its open architecture, powerful DevTools, extension ecosystem, and commitment to web standards make it well-suited for security work. Here's how security professionals use Firefox effectively. ## Why Security Researchers Prefer Firefox * **Open source** : mozilla-central is publicly auditable; security researchers find and report bugs * **Independent certificate store** : doesn't inherit corporate MITM certs unless explicitly configured, so you see what's actually there * **Strict CSP enforcement** : finds CSP misconfigurations that Chrome's more lenient behavior masks * **uBlock Origin at full capability** : MV2 extensions can intercept/block at the network level * **Proxy support** : native SOCKS5+DNS, per-browser proxy config (doesn't inherit system proxy) * **Private browsing** : no telemetry in private mode, no third-party cookies ## Configuring Firefox for Security Research **Essential`about:config` changes for research work:** security.OCSP.require = false (don't fail on OCSP timeouts during offline research) network.proxy.socks_remote_dns = true (DNS through SOCKS to prevent leaks) dom.security.https_only_mode = false (allow HTTP when testing HTTP-only apps) **Disable Safe Browsing for offline work** (Safe Browsing sends URLs to Google for checking): browser.safebrowsing.malware.enabled = false browser.safebrowsing.phishing.enabled = false Note: only disable safe browsing in isolated VMs used for malware analysis, not your daily driver. ## Burp Suite Integration Burp Suite is the standard proxy for web app security testing. Firefox integration: 1. Set Firefox proxy: Settings → Network Settings → Manual → HTTP Proxy: 127.0.0.1:8080 2. Navigate to `http://burp` in Firefox → download Burp's CA cert 3. Settings → View Certificates → Authorities → Import → select the cert → trust for websites 4. Burp now intercepts all Firefox HTTPS traffic For per-project configs, use FoxyProxy to switch between Burp proxy and direct connection per URL pattern. ## OWASP ZAP Integration OWASP ZAP is an open-source web app scanner: 1. ZAP → Tools → Options → Dynamic SSL Certificates → Generate → Save 2. Firefox → Import that cert as a trusted CA 3. Set Firefox proxy to ZAP's proxy address (default: 127.0.0.1:8080) ZAP's passive scanner analyzes traffic as you browse; active scanner auto-sends attack payloads. ## Cookie and Session Analysis Firefox's Storage Inspector (DevTools → Storage tab) shows: * All cookies for the current origin (session, persistent, httpOnly, Secure flags) * LocalStorage and SessionStorage * IndexedDB contents * Cache entries For cookie security testing, look for: * Missing `HttpOnly` flag → cookie accessible via JavaScript (XSS risk) * Missing `Secure` flag → cookie sent over HTTP * Missing `SameSite` attribute → CSRF risk on older endpoints * Overly broad domain scope (`Domain=.example.com`) ## XSS Testing in Firefox Firefox handles some XSS vectors differently from Chrome. Both should be tested: DevTools Console: test JavaScript injection directly // Test if inline event handlers work (CSP check) document.body.innerHTML = '<img src=x onerror=alert(1)>' // Test for DOM-based XSS via URL fragments location.hash = '#<script>alert(1)</script>' Firefox's stricter CSP enforcement means a CSP bypass that works in Chrome may not work in Firefox — and vice versa. ## Network Traffic Analysis Firefox DevTools Network tab for manual traffic inspection: * **Filter by type** : XHR/Fetch, JS, CSS, Doc, WS (WebSocket) * **Copy as cURL** : right-click any request → copy as cURL with all headers * **Edit and Resend** : right-click → Edit and Resend → modify request and replay * **HAR Export** : save entire session as HAR file for offline analysis For WebSocket analysis: filter by WS type → click the WebSocket connection → Messages tab shows all frames in both directions. ## TLS and Certificate Analysis Firefox's Security tab in DevTools (available per-request in Network tab → click request → Security tab) shows: * TLS version negotiated * Cipher suite * Certificate chain * Whether HSTS is active For deeper certificate analysis: address bar → padlock → Connection Secure → More Information → Security → View Certificate. Check for: * Unexpected certificate issuers (MITM detection) * Certificate transparency logs (crt.sh) * Expired or soon-to-expire certs * Weak cipher suites (RC4, DES — Firefox blocks these by default now) ## Extension Security Testing Firefox extensions are easier to inspect than Chrome extensions because AMO requires unobfuscated source code: 1. Firefox → Add-ons (about:addons) → extension page 2. "Source Code" link on the AMO listing → browse the source on AMO 3. Or: find the extension XPI file in `~/.mozilla/firefox/PROFILE/extensions/` and unzip it Look for: * Content scripts that run on all pages (`matches: ["<all_urls>"]`) * Broad permissions in `manifest.json` * External script loading * Data exfiltration via background script ## Privacy Analysis Firefox's Developer Tools + extensions make it excellent for privacy analysis: **Ghostery** or **uBlock Origin** logger shows all third-party connections a site makes. **Lightbeam for Firefox** : visualizes third-party tracking connections across sites you visit. **DevTools Network filter** for tracking: filter by `third-party` or search for known tracker domains (doubleclick.net, google-analytics.com). **WebRTC leak testing** : Firefox's WebRTC can expose local IP even through a VPN. Test at ipleak.net with and without VPN. In about:config, set `media.peerconnection.enabled = false` to disable WebRTC completely. ## Isolated Testing VMs For malware analysis or high-risk security testing, run Firefox in an isolated VM: * Snapshot the clean state * Perform analysis * Revert snapshot to clean state * Firefox profiles within the VM are disposable Firefox's portable mode (running from a USB drive or temp directory with a custom profile) also works for isolated testing without affecting your main installation.

dev.to

Leaving Netlify and Render without losing a user, a video or a link

_By Simon Frisk, Eyevinn Technology_ In the previous post I moved users from Supabase to Open Source Cloud without anyone resetting a password. This time it is the hosting itself: a full-stack app leaving Netlify and Render for Open Source Cloud (OSC), and bringing its database and media along. The app is Classync, a video review tool where teachers and students leave time-coded comments on videos. Three rules applied. Every user keeps their password, everything comes along (videos, comments, projects, all of it), and every old link keeps working. Everything on the OSC side is open source, so the same steps work in reverse. Each step ends with what moving again would take. ## What the move actually covers Classync is small: 24 users, 179 comments, 21 projects, about 0.1 MB of data and about 1.7 GB of media. Every problem below came from behavior, not scale. ## Getting it running on OSC OSC runs open-source services as managed instances, and runs your own code next to them as a My App. I did the work with a small team of Claude Code agents (plan, build, deploy, review), and I made the call at each step. The snippets below are illustrative, written for this post rather than copied from the repo. ## Step 1: Map what Netlify and Render did Before moving anything, list everything each platform did for you, not just the obvious parts. Job | Before | On OSC ---|---|--- Static frontend, SPA fallback | Netlify, with `_redirects` | Served by the backend My App Node backend | Render, with build, start and env vars in its dashboard | Same My App Config and secrets | Render's dashboard | One parameter store Database | MongoDB Atlas | FerretDB, on a PostgreSQL backend Media | Cloudinary | MinIO Backups | None of its own | Nightly dumps to a separate MinIO The quiet part on Netlify was this line, sending every unknown path to the app shell: /* /index.html 200 Its replacement must do the same, or deep links break. _Moving again:_ every row on the right is plain Node, environment variables, MongoDB-compatible or S3-compatible, so the map works backwards too. ## Step 2: Turn two services into one My App A My App is built from the repo with `build` and `start` scripts at the root, so one root `package.json` builds both halves. Folder names here are examples: { "scripts": { "build": "cd frontend && npm ci && npm run build && cd ../backend && npm ci", "start": "node backend/server.js" } } The frontend first went on My Pages, OSC's static hosting. It served `/` but returned 404 on reload of client routes like `/review/:id` or `/signUp?token=...`. So Express serves the built frontend instead, with health first and the browser fallback last. This is Express 4; the `*` route is written differently in Express 5: app.get('/health', (req, res) => // 1. health first, never the app shell res.sendStatus(mongoose.connection.readyState === 1 ? 200 : 503)); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(spec)); // Swagger moved off "/" app.use(apiRouter); // 2. API routes app.use(express.static(distDir)); // 3. built assets app.get('*', (req, res, next) => // 4. browser fallback, last req.accepts('html') ? res.sendFile(path.join(distDir, 'index.html')) : next()); // At boot: retry the database with backoff, then process.exit(1) // so the platform restarts the app. One origin means no CORS between frontend and API. The health check changed too. Before, a database outage at boot left the app up, broken, and reporting OK. _Moving again:_ plain Node and Express with standard root scripts. Nothing in the code is OSC-specific. ## Step 3: Move environment variables to a parameter store The backend's variables from Render, and the frontend's build-time variables, now live in one OSC parameter store attached to the app. Two things to know: * Vite inlines `VITE_` variables at build time, and the platform runs the build, so they go in the same store. * Keep one-off switches out of it. A flag that wipes or reseeds the database, such as `RESET_DB`, is read again on every restart. _Moving again:_ the app reads ordinary environment variables; the parameter store just supplies them. ## Step 4: Bring the data along without password resets FerretDB is the MongoDB-compatible database on OSC, so Mongoose only needs a new connection string. Rehearse first: `mongodump` from Atlas, `mongorestore` into a throwaway FerretDB, then compare counts, indexes and password hashes. The passwords are bcrypt hashes, so a straight copy keeps every password working. I checked the hashes, then logged in with my real account. Users still sign in once on the new address, because the login token in browser storage belongs to the old site. Two things behaved differently. FerretDB 2.7 has no multi-document transactions, and writes inside a transaction apply at once with nothing to roll back. And unsorted queries came back in a different order. MongoDB never promised an order there. Atlas was just consistent. // Ordered steps instead of a transaction. Reassign first, // so a failure never leaves orphaned comments. await Comment.updateMany({ author: user._id }, { author: placeholder._id }); await User.deleteOne({ _id: user._id }); // Every list a user sees gets an explicit sort. Comment.find({ project: projectId }).sort({ timestamp: 1, _id: 1 }); OSC's built-in backup tool does not cover the FerretDB backend, so the app writes its own nightly `mongodump`-format backup to MinIO. A restore drill passed. _Moving again:_ the code runs unchanged against Atlas or self-hosted MongoDB, and the nightly backups restore into any MongoDB with plain `mongorestore`. ## Step 5: Bring the media along Uploads used to pass through the backend, but OSC My Apps have a request size limit, so large files can no longer go that way. The browser now uploads straight to MinIO as S3 multipart uploads in 8 MiB parts, and the backend only signs: // Backend: start, sign each part, complete. const { UploadId } = await s3.send(new CreateMultipartUploadCommand({ Bucket, Key })); const partUrl = await getSignedUrl(s3, new UploadPartCommand({ Bucket, Key, UploadId, PartNumber })); await s3.send(new CompleteMultipartUploadCommand({ Bucket, Key, UploadId, MultipartUpload: { Parts } })); // Browser: PUT each slice, with no extra headers. const res = await fetch(partUrl, { method: 'PUT', body: file.slice(start, start + PART_SIZE) }); OSC's ingress allows only a few request headers on cross-origin calls, hence no extra headers. Videos are capped at 400 MiB, but that is a setting I chose (`MAX_VIDEO_MB`), not a technical limit. With 8 MiB parts, S3 multipart allows up to 10,000 parts, about 78 GiB per file, so the real limits are storage space and upload time. On Cloudinary's Free plan the ceiling was 100 MB. The browser now also makes thumbnails and refuses HEVC video and .mov files with uncompressed audio, asking for an MP4 export. [SCREENSHOT: the upload dialog that refuses HEVC video and .mov files with uncompressed audio, with the "export as MP4" message, or showing the 400 MiB setting] The copy covered only files the database references, out of about 1.7 GB in Cloudinary: 64 objects, about 780 MB, each hash-verified. The rest were files no record pointed to, such as Cloudinary's sample files and leftovers from deleted uploads, so they were left where they were. The stored URLs in 67 documents were rewritten only after a dry run showed every URL mapped, a backup was taken and a reverse run was ready as the undo. MinIO versioning, with a 90-day rule for old versions, keeps deleted files recoverable, and a nightly job mirrors the media into the backup MinIO. But on the MinIO release OSC runs, plain delete permission also covers old versions, so the app's MinIO credentials needed an explicit Deny on `s3:DeleteObjectVersion`. Only the MinIO owner account can permanently delete media. _Moving again:_ MinIO is S3-compatible and the uploads are standard S3 multipart with presigned URLs, so the same code works against AWS S3 or any S3-compatible storage. Leaving MinIO would mean a copy and a settings change, with no code to rewrite. ## Step 6: Cut over and keep old links working After one more rehearsal against the production database, the switch was a fresh read-only dump from Atlas, a restore into FerretDB, a check of counts and hashes, and a new database setting. It took about 3.5 minutes, mostly two app rebuilds. Then the old Netlify site became a forwarder. One line in its `_redirects` keeps path and query, so invitation links still work: /* https://<new-host>/:splat 301! _Moving again:_ Render, Atlas and Cloudinary stayed a working fallback throughout, so the way back was always open. ## Why open source matters here Open source means no black box: you can read the code and run the exact same software yourself. It also means you are not locked in: * **The software moves with you.** FerretDB and MinIO run the same on your own server or another cloud, so a price change or a shutdown does not mean rebuilding. * **Your data stays in open formats.** MongoDB dumps and S3 objects work anywhere. * **You set the limits and choose where data lives.** Upload caps are your own settings, not a pricing plan's. For a school app, it also means keeping student data where it has to be. This move showed the difference. Leaving Cloudinary meant a new storage driver, a copy job and 67 rewritten records. Leaving OSC would mean the same dump, copy and settings change, with no code to rewrite. ## Wrapping up The app will run on about 41 OSC tokens per day once the two rehearsal databases are removed: the app, the parameter store, FerretDB and its PostgreSQL backend, and the two MinIO instances for media and backups. [CHECK: add a currency equivalent only if there is a confirmed token price to cite] Render, Atlas and Cloudinary stay as a fallback for about four weeks. If you have an app split across Netlify and Render, try the same move on Open Source Cloud. If something behaves differently for you, let us know in the community Slack or on GitHub. ## What is Open Source Cloud? Managed open-source cloud for developers who want infrastructure, not DevOps. Run unmodified open-source services, deploy your own code alongside them as a My App, and spin up Postgres, Valkey and S3-compatible storage without touching Kubernetes or cloud config.

dev.to

Your Monitoring Reports Are Accurate. Are They Still Worth Sending?

Juan Camilo Auriti built a monitoring product that compared website properties across runs. The reports were correct. 78% of them showed a delta of exactly zero. His first response was technical: the change-detection thresholds must be too coarse. He spent about a month tuning them. That was the wrong layer. The product let someone audit a site, fix issues such as `robots.txt` or schema markup, and then watch those properties over time. Once the fixes were made, the site could remain stable in the dimensions being measured. That is a successful first task. It is not automatically a reason to send a new report every week. An empty recurring report can mean two different things: 1. The customer completed the job, and there was nothing new to act on. 2. The report did not carry a decision for the recipient in the first place. Those interpretations point to different engineering and product work. If the product promises assurance that a site remains stable, “no change” can be the result the customer wants. The question is whether the promise and cadence match that outcome. If the product promises to tell the customer what to do next, increasing sensitivity does not solve the problem by itself. A report can detect smaller changes and still change no decision. Juan Camilo Auriti described the turning point in his own monitoring product this way: > “The thresholds weren't the problem. The artifact was the problem.” One commenter gave the problem a useful name: **decisionless**. Before sending a recurring artifact, ask: what decision does this change? Auriti agreed that this is a property you can check before sending, rather than an interpretation you make afterward. This is why threshold work is so attractive. It produces code changes, diffs, and a number. The question “should this artifact exist at this frequency?” produces no diff and may force a change to the product promise. For a report, alert, digest, or recurring audit, separate these questions before adding another feature: * Did the first task succeed so completely that the original problem stopped occurring? * Or does the recurring artifact fail to connect to a decision the recipient still has to make? The first points to the promise and cadence. The second points to the artifact and its handoff into a decision. They are different failure layers, so they should not share the same next fix. The figure is self-reported and the underlying query is not independently audited. This is not a rule that monitoring products should stop sending weekly reports. Stability may be the product. The point is narrower: an empty report is not enough evidence to choose between threshold work, delivery work, and a promise change. When your recurring report comes back empty, what decision was the next issue supposed to change?

dev.to

How I Built a Domain Availability Checker with PHP and RDAP

Domain availability checking looks simple: type a domain, get a "yes" or "no." But when you need to check hundreds of domains across dozens of TLDs — reliably, quickly, and without paying for a commercial API — the problem gets interesting. I recently built Site Tools Box, a free online toolbox for webmasters that includes a bulk domain availability checker. Here's how the backend works, and why I chose RDAP over raw WHOIS. ## Why Raw WHOIS Doesn't Scale Traditional WHOIS is a plain-text protocol over port 43. It works, but it has real problems for a modern tool: * **No standard format.** Every registry returns text differently. Parsing "No match for" vs "Not found" vs "Status: available" becomes a fragile regex nightmare. * **Rate limiting.** Some WHOIS servers simply stop responding if you query too fast. There's no standard `Retry-After` header. * **No concurrency support.** You open a raw TCP socket for each query. That doesn't scale. For a bulk checker that handles up to 200 domains per request, this approach falls apart quickly. ## The Solution: RDAP with WHOIS Fallback RDAP (Registration Data Access Protocol) is the modern replacement for WHOIS, mandated by ICANN. It's JSON-based, supports HTTP status codes, and most major registries now support it. The idea is simple: use RDAP first, fall back to WHOIS only when RDAP isn't available. ### Step 1: Map TLDs to RDAP Endpoints Each registry has its own RDAP endpoint. I built a simple lookup table: function domainRdapEndpoint($tld) { $map = [ 'com' => 'https://rdap.verisign.com/com/v1/domain/', 'io' => 'https://rdap.identitydigital.services/rdap/domain/', 'ai' => 'https://rdap.identitydigital.services/rdap/domain/', 'xyz' => 'https://rdap.centralnic.com/xyz/domain/', // ... and more ]; return $map[$tld] ?? null; } If the TLD isn't in the map, the tool falls back to a WHOIS socket connection for that domain. ### Step 2: Parse the RDAP Response RDAP returns JSON. A `200` status means the domain is registered, and a `404` means it's available. The JSON body also contains registrar info, registration date, and expiration date: function parseRdapBody($domain, $body, $source = 'RDAP') { $d = json_decode($body, true); if (!$d) return null; $registrar = ''; if (!empty($d['entities'])) { foreach ($d['entities'] as $entity) { if (!empty($entity['roles']) && in_array('registrar', $entity['roles'])) { foreach ($entity['vcardArray'][1] as $v) { if ($v[0] === 'fn') { $registrar = $v[3]; break; } } } } } return [ 'domain' => $domain, 'registered' => true, 'registrar' => $registrar ?: '-', ]; } ### Step 3: Handle Bulk Checks with curl_multi For bulk requests, I use `curl_multi` to fire up to 30 concurrent RDAP queries. This is significantly faster than a sequential loop, and it respects the rate limits of most registries: function domainCheckBatch($domains, $concurrency = 30) { $rdapDomains = []; foreach ($domains as $d) { $tld = substr(strrchr($d, '.'), 1); if (domainRdapEndpoint($tld)) $rdapDomains[] = $d; } for ($i = 0; $i < count($rdapDomains); $i += $concurrency) { $chunk = array_slice($rdapDomains, $i, $concurrency); $mh = curl_multi_init(); // Add handles, execute, collect results } } Domains without an RDAP endpoint (like `.cn` or `.uk`) go through the WHOIS fallback, which uses a raw socket connection on port 43. ## Three Things I Learned **1. RDAP is far more reliable than WHOIS.** The JSON structure means no more parsing fragile text. HTTP status codes are unambiguous. **2. curl_multi is the right tool for bulk checks.** Sequential queries make the tool painfully slow. A concurrency of 30 is the sweet spot before registries start throttling. **3. Always have a fallback.** Not every TLD supports RDAP. The WHOIS fallback handles the rest, even if it's slower and messier. ## Bonus: Scoring Available Domains Since I was already building the checker, I added a small scoring algorithm for available domains. It looks at five dimensions: * **Extension** (.com scores highest) * **Length** (shorter is better) * **Meaning** (real English words or pinyin) * **Pronounceability** (vowel-consonant alternation) * **Character quality** (pure letters, no numbers) It's not a professional valuation — just a rough starting point. But it helps users quickly filter hundreds of available domains. ## Try It Out You can try the domain availability checker here: **Site Tools Box** It supports single and bulk checks, and gives available domains a rough value score. If you're building something similar and have questions about the RDAP implementation, drop a comment below. I'm happy to share more of the code.

dev.to

React Native Ecosystem Faces Fragmentation: Strategies for Compatibility Amid Rapid Technological Changes

## Introduction: The React Native Evolution The React Native ecosystem is at a crossroads, propelled by rapid technological advancements that, while groundbreaking, threaten to fracture its once-unified foundation. Recent innovations like **React Native Skia 3.0** and **babel-plugin-react-native-direct-imports** showcase the framework’s potential but also expose emerging compatibility risks. These developments, though transformative, are outpacing the ecosystem’s ability to adapt, creating a precarious balance between innovation and fragmentation. Consider **React Native Skia 3.0** : by replacing the Ganesh backend with **Graphite** running on **WebGPU** , it leverages **Metal on iOS** and **Vulkan on Android** for multithreaded recording and zero-copy textures. Mechanically, this shift reduces memory overhead by eliminating data duplication during texture transfers, improving performance. However, this change also introduces a dependency on WebGPU, a relatively new standard, which may not be uniformly supported across devices or platforms. The risk here is twofold: _older devices may struggle to render Skia-based components_ , and _developers could face compatibility issues when integrating Skia with legacy codebases._ Similarly, **babel-plugin-react-native-direct-imports** optimizes bundle size by rewriting barrel imports into direct requires. This reduces a test bundle from **711 KB to 10 KB** by eliminating unnecessary dependencies. However, this optimization relies on precise module resolution, which can break if the dependency tree is misconfigured or if third-party libraries are not designed for direct imports. The causal chain here is clear: _misaligned dependencies → unresolved imports → runtime failures._ These advancements, while impressive, highlight a critical tension. The ecosystem’s rapid evolution is creating silos of innovation, where cutting-edge tools like **Tamagui 3.0** (with its C++ native runtime) or **munim-maps** (with 3D car rendering on Apple Maps) risk becoming incompatible with older libraries or frameworks. The mechanism of risk formation is straightforward: _rapid adoption of new technologies → divergence in codebase standards → reduced interoperability._ To address this, the community must adopt a unified approach to integration and standardization. For instance, **React Native Skia 3.0** could provide fallback mechanisms for devices lacking WebGPU support, ensuring backward compatibility. Similarly, tools like **babel-plugin-react-native-direct-imports** should include validation checks to prevent misconfigurations. The optimal solution is a _layered standardization framework_ : core libraries maintain strict compatibility standards, while experimental features are sandboxed to prevent ecosystem-wide fragmentation. If left unchecked, the current trajectory will lead to increased development complexity, reduced interoperability, and a fragmented user experience. The rule here is clear: _if X (rapid technological advancements) → use Y (layered standardization framework)_. This approach ensures React Native remains a cohesive, cross-platform framework while fostering innovation. ## Scenario 1: The Great Graphite Migration The migration to the Graphite architecture in **React Native Skia 3.0** marks a significant leap in rendering performance, but it also introduces a critical juncture for the ecosystem. By replacing the Ganesh backend with Graphite running on **WebGPU** , the update leverages **Metal on iOS** and **Vulkan on Android** for multithreaded recording and zero-copy textures. This shift _mechanically reduces memory overhead_ by eliminating data duplication during texture transfers, as textures are shared directly with **React Native WebGPU** instead of being copied across memory spaces. However, this innovation _deforms compatibility_ with older devices that lack WebGPU support, as the dependency on this new standard _heats up_ the risk of rendering failures in legacy environments. The causal chain here is clear: **WebGPU dependency → older devices struggle with rendering → compatibility issues with legacy codebases.** For instance, a project relying on Skia 3.0 will _break_ on devices without WebGPU support, as the rendering pipeline fails to initialize. This risk is compounded by the ecosystem’s rapid adoption of such cutting-edge technologies, which _expands_ the divergence in codebase standards and _changes_ the interoperability landscape. To address this, a **layered standardization framework** emerges as the optimal solution. Here’s why: * **Fallback Mechanisms:** Implementing fallback options in Skia 3.0 for devices lacking WebGPU support ensures backward compatibility. This _mechanically prevents_ rendering failures by reverting to a compatible rendering path when WebGPU is unavailable. * **Validation Checks:** Incorporating validation in tools like **babel-plugin-react-native-direct-imports** prevents misconfigurations. This _mechanically stops_ runtime failures by ensuring precise module resolution before bundling. * **Sandboxed Experimentation:** Keeping experimental features like Graphite in a sandboxed layer _contains_ their risks, preventing ecosystem-wide fragmentation while fostering innovation. A typical choice error here is _overlooking fallback mechanisms_ in favor of pure performance gains. This error _forms_ when developers prioritize speed over compatibility, leading to a fragmented user experience. The rule for cohesion is clear: **If rapid technological advancements (X) → use a layered standardization framework (Y) to maintain a cohesive, cross-platform framework while fostering innovation.** Without this, the ecosystem risks _heating up_ into a state of irreversible fragmentation, where the appeal of React Native as a unified framework _breaks_ under the weight of incompatible innovations. ## Scenario 2: Derek Joins Fat Fighters The introduction of optimization tools like **Fat Fighters** and advancements such as **React Native Skia 3.0** and **babel-plugin-react-native-direct-imports** are reshaping the React Native ecosystem. While these tools promise significant performance gains, they also introduce risks of fragmentation and compatibility issues. This analysis dissects their impact on app performance, development workflows, and the broader community, backed by causal explanations and practical insights. ## Performance Leap: How Fat Fighters and Skia 3.0 Work **React Native Skia 3.0** replaces the Ganesh backend with **Graphite** , leveraging **WebGPU** via **Dawn**. On iOS, it uses **Metal** ; on Android, **Vulkan**. The key innovation is **multithreaded recording** and **zero-copy textures**. Mechanically, zero-copy textures eliminate memory duplication during data transfers between the CPU and GPU. This reduces memory overhead and heat generation in the device’s memory controller, as fewer bytes are written and read. The result is smoother rendering, especially in graphically intensive apps. **Fat Fighters** , exemplified by **babel-plugin-react-native-direct-imports** , rewrites barrel imports into direct requires. This reduces bundle size by eliminating unused dependencies. For instance, a test bundle shrinks from **711 KB to 10 KB**. The mechanism here is straightforward: fewer files are packed into the bundle, reducing the number of disk reads during app startup and lowering CPU load during parsing. ## Fragmentation Risks: Where Things Break The primary risk is **WebGPU dependency** in Skia 3.0. Older devices lacking WebGPU support fail to render graphics, as the GPU driver cannot interpret the new commands. This breaks legacy codebases that rely on the Ganesh backend. The causal chain is: **WebGPU dependency → unsupported hardware → rendering failure → app crash.** For Fat Fighters, the risk lies in **misconfigured dependencies**. If a module is directly imported but not present in the dependency tree, the app fails at runtime. The mechanism is: **missing module → unresolved import → JavaScript runtime error.** This is exacerbated by third-party libraries that assume barrel imports, leading to silent failures during builds. ## Impact on Development Workflows Adopting these tools requires developers to **validate configurations rigorously**. For Skia 3.0, teams must test on older devices to identify rendering failures. For Fat Fighters, dependency trees need auditing to ensure all direct imports are resolved. This adds overhead but is necessary to avoid runtime errors. The broader risk is **ecosystem divergence**. If teams adopt these tools at different paces, codebase standards fragment. For example, one team might use Skia 3.0 while another sticks to Ganesh, leading to incompatible UI components. The causal logic is: **rapid adoption → divergent standards → reduced interoperability.** ## Proposed Solutions: Layered Standardization The optimal solution is a **layered standardization framework** : * **Fallback Mechanisms** : Skia 3.0 should include fallbacks for devices without WebGPU. Mechanically, this involves detecting WebGPU support at runtime and reverting to Ganesh if unavailable. This prevents rendering failures on older devices. * **Validation Checks** : Tools like **babel-plugin-react-native-direct-imports** should incorporate validation to flag unresolved imports during builds. This blocks runtime errors by catching misconfigurations early. * **Sandboxed Experimentation** : Experimental features (e.g., Graphite) should be contained in separate modules. This prevents ecosystem-wide fragmentation while allowing innovation. Mechanically, this isolates unstable code from the core framework, ensuring stability for production apps. ## Rule for Cohesion **If rapid technological advancements (X) → use a layered standardization framework (Y) to maintain cross-platform cohesion while fostering innovation.** This rule ensures that performance gains do not come at the cost of compatibility. Without it, the React Native ecosystem risks becoming a patchwork of incompatible tools, undermining its appeal as a unified framework. ## Edge-Case Analysis Consider a scenario where a team adopts Skia 3.0 without fallbacks. On an older Android device with a Mali-T GPU, the app crashes during startup due to WebGPU incompatibility. The GPU driver, lacking WebGPU support, fails to initialize the rendering pipeline, causing a hard crash. This highlights the critical need for fallbacks. For Fat Fighters, imagine a misconfigured dependency tree where a critical module is missing. During runtime, the app throws an **“Unable to resolve module”** error, halting execution. This underscores the importance of validation checks to prevent such failures. ## Professional Judgment While tools like Skia 3.0 and Fat Fighters offer undeniable performance benefits, their adoption must be managed carefully. The optimal approach is to prioritize **backward compatibility** and **validation** over raw performance. Teams should adopt these tools incrementally, using sandboxing to contain risks. Without this, the ecosystem risks fragmentation, eroding React Native’s core value proposition as a unified cross-platform framework. ## Scenario 3: Dependency Tree Leaderboard In the rapidly evolving React Native ecosystem, managing dependencies has become a critical challenge. The introduction of tools like **React Native Skia 3.0** and **babel-plugin-react-native-direct-imports** brings performance leaps but also risks fragmentation. A _dependency tree leaderboard_ emerges as a strategic tool to visualize, prioritize, and manage these dependencies, ensuring compatibility and ecosystem cohesion. ### Mechanics of Dependency Fragmentation The causal chain of fragmentation begins with **rapid adoption of new technologies**. For instance, React Native Skia 3.0’s dependency on **WebGPU** introduces a compatibility risk. Here’s how it breaks: * **Impact:** Older devices without WebGPU support fail to render graphics. * **Internal Process:** WebGPU-dependent code attempts to initialize rendering pipelines that the hardware cannot support. * **Observable Effect:** App crashes or displays blank screens on legacy devices. Similarly, **babel-plugin-react-native-direct-imports** optimizes bundle size by rewriting imports, but misconfigurations lead to: * **Impact:** Unresolved module errors at runtime. * **Internal Process:** Direct imports bypass barrel files, exposing unresolved dependencies if third-party libraries assume barrel imports. * **Observable Effect:** JavaScript runtime errors halt app execution. ### Role of the Dependency Tree Leaderboard A dependency tree leaderboard addresses these risks by: 1. **Visualizing Dependency Chains:** Maps out all dependencies, highlighting critical paths and potential conflict points. 2. **Prioritizing Compatibility:** Ranks dependencies based on their compatibility impact, flagging those tied to new standards like WebGPU. 3. **Guiding Standardization:** Informs the _layered standardization framework_ , ensuring core libraries maintain backward compatibility while sandboxing experimental features. ### Edge-Case Analysis Consider two edge cases: #### 1. Skia 3.0 Without Fallbacks **Mechanism:** WebGPU-dependent rendering code fails on older Android devices due to unsupported hardware. **Effect:** App crashes during initialization. **Solution:** Implement runtime detection of WebGPU support and fall back to Ganesh rendering. #### 2. Misconfigured Direct Imports **Mechanism:** A missing module in the dependency tree causes an unresolved import. **Effect:** JavaScript runtime throws an error, halting execution. **Solution:** Integrate validation checks in the build process to flag unresolved imports. ### Optimal Solution: Layered Standardization with Leaderboard Integration The most effective solution combines a **dependency tree leaderboard** with a _layered standardization framework_. Here’s why: * **Effectiveness:** The leaderboard identifies high-risk dependencies, while layered standardization ensures core libraries remain compatible and experimental features are isolated. * **Conditions for Failure:** This solution fails if the leaderboard lacks real-time updates or if developers bypass sandboxed features, reintroducing fragmentation. * **Typical Choice Errors:** Prioritizing performance over compatibility (e.g., adopting Skia 3.0 without fallbacks) or neglecting validation checks for direct imports. ### Rule for Cohesion **If rapid technological advancements (X) → use a dependency tree leaderboard integrated with a layered standardization framework (Y) to maintain cross-platform cohesion while fostering innovation.** ### Professional Judgment The React Native ecosystem’s survival hinges on balancing innovation with compatibility. A dependency tree leaderboard is not just a tool but a necessity for visualizing and managing the complex web of dependencies. Without it, the ecosystem risks becoming a fragmented patchwork of incompatible libraries and tools. Prioritize backward compatibility, validate rigorously, and sandbox experimental features to ensure React Native remains a unified, cross-platform framework. ## Community Responses and Adaptations: Navigating the React Native Fragmentation Crisis The React Native ecosystem is at a crossroads, with innovations like **React Native Skia 3.0** and **babel-plugin-react-native-direct-imports** driving performance leaps but threatening compatibility. The community’s response is a mix of enthusiasm and caution, as developers grapple with the dual challenge of adopting new technologies while maintaining codebase cohesion. Here’s how the ecosystem is adapting—and where it’s falling short. ### 1. Skia 3.0: Performance Gains vs. Compatibility Breaks William Candillon’s **React Native Skia 3.0** replaces the _Ganesh backend_ with _Graphite_ , leveraging _WebGPU_ for multithreaded recording and _zero-copy textures_. This slashes memory overhead by eliminating data duplication during texture transfers between CPU and GPU. However, the _WebGPU dependency_ introduces a critical risk: **older devices without WebGPU support fail to render** , triggering app crashes. _Mechanism of Failure:_ WebGPU’s absence → GPU cannot process textures → rendering pipeline stalls → app crashes on unsupported hardware. _Community Response:_ Developers are advocating for **fallback mechanisms** —reverting to Ganesh when WebGPU is unavailable. This layered approach ensures backward compatibility while allowing innovation. However, implementation remains inconsistent, with some projects prioritizing performance over stability. ### 2. Direct Imports: Bundle Size Optimization with Hidden Risks **babel-plugin-react-native-direct-imports** rewrites barrel imports into direct requires, shrinking bundle sizes (e.g., _711 KB → 10 KB_). This reduces disk reads and CPU load during app startup. Yet, **misconfigured dependencies** lead to _unresolved module errors_ , halting execution. _Mechanism of Risk:_ Bypassed barrel files → missing module resolution → JavaScript runtime throws error. _Community Adaptation:_ Open-source tools are emerging to **validate dependency trees** at build time. However, adoption is slow, as developers underestimate the risk of third-party libraries assuming barrel imports. ### 3. Emerging Best Practices: Layered Standardization and Sandboxing The community is coalescing around a **layered standardization framework** : core libraries maintain strict compatibility, while experimental features (e.g., Graphite) are sandboxed. This isolates risks, preventing ecosystem-wide fragmentation. _Optimal Solution:_ Combine **dependency tree leaderboards** with layered standardization. Leaderboards visualize critical paths and conflicts, guiding prioritization. For example, a leaderboard flags Skia 3.0’s WebGPU dependency as high-risk, prompting fallback implementation. _Failure Conditions:_ The solution fails if **real-time updates are absent** or if developers bypass sandboxed features, reintroducing compatibility risks. ### 4. Edge-Case Analysis: Where Adaptations Fall Short * **Skia 3.0 Without Fallbacks:** Apps crash on _older Android devices_ due to WebGPU incompatibility. _Mechanism:_ WebGPU check fails → no fallback → rendering pipeline terminates. * **Direct Imports with Misconfigurations:** “ _Unable to resolve module_ ” errors halt execution. _Mechanism:_ Missing module → import resolver fails → runtime error propagates. ### 5. Professional Judgment: Prioritize Compatibility, Validate Rigorously The optimal strategy is clear: **if rapid technological advancements (X) → use layered standardization with dependency validation (Y)**. This maintains cross-platform cohesion while fostering innovation. Key actions include: * **Fallback Mechanisms:** Implement runtime checks for WebGPU support in Skia 3.0. * **Validation Checks:** Integrate build-time tools to flag unresolved imports. * **Sandboxed Experimentation:** Isolate features like Graphite to contain risks. Failure to adopt these measures risks React Native’s unified framework appeal, as fragmentation undermines developer confidence and user experience. ### 6. Critical Error: Performance Over Compatibility Leads to Fragmentation The ecosystem’s current trajectory prioritizes raw performance, neglecting backward compatibility. This trade-off is unsustainable. For instance, Skia 3.0’s zero-copy textures reduce heat generation but exclude older devices, fragmenting the user base. _Rule for Cohesion:_ **Rapid advancements (X) require layered standardization (Y)** to balance innovation and stability. Without this, React Native risks becoming a fragmented ecosystem of incompatible tools and libraries. ## Conclusion: Navigating the Future of React Native The React Native ecosystem stands at a crossroads, where rapid innovation threatens to outpace its ability to maintain cohesion. The release of **React Native Skia 3.0** , **babel-plugin-react-native-direct-imports** , and **Tamagui 3.0** exemplifies the double-edged sword of progress: while these advancements promise performance leaps, they introduce compatibility risks that could fragment the ecosystem. The core issue lies in the _mechanism of fragmentation_ : new technologies like WebGPU in Skia 3.0 create hardware dependencies, causing rendering failures on older devices. Similarly, direct imports bypass barrel files, leading to unresolved module errors if misconfigured. Without intervention, these risks will amplify, eroding React Native’s appeal as a unified cross-platform framework. ## Key Findings * **Performance vs. Compatibility Trade-off:** Innovations like zero-copy textures in Skia 3.0 reduce memory overhead by eliminating CPU-GPU duplication, but WebGPU dependencies break legacy device support. _Causal chain: WebGPU absence → GPU cannot process textures → rendering pipeline stalls → app crashes._ * **Bundle Size Optimization Risks:** Direct imports shrink bundles by removing unused dependencies, but misconfigurations lead to runtime errors. _Mechanism: bypassed barrel files → missing module resolution → JavaScript runtime throws error._ * **Ecosystem Divergence:** Rapid adoption of experimental features (e.g., Graphite) without standardization creates divergent standards, reducing interoperability. _Impact: fragmented libraries → increased development complexity → inconsistent user experiences._ ## Actionable Insights for Developers To navigate this landscape, developers must adopt a **layered standardization framework** that balances innovation with stability. Here’s how: * **Fallback Mechanisms:** Implement runtime checks in Skia 3.0 to detect WebGPU support and revert to Ganesh rendering on older devices. _Effectiveness: prevents app crashes by ensuring backward compatibility._ * **Validation Checks:** Integrate build-time tools like **babel-plugin-react-native-direct-imports** to flag unresolved imports. _Mechanism: blocks runtime errors by enforcing precise module resolution._ * **Sandboxed Experimentation:** Isolate experimental features (e.g., Graphite) in separate modules to contain risks. _Rule for cohesion: if introducing X (experimental feature), use Y (sandboxing) to prevent ecosystem-wide fragmentation._ ## Long-Term Implications The optimal solution combines a **dependency tree leaderboard** with layered standardization. This approach visualizes critical paths and conflicts, guiding prioritization and sandboxing. _Effectiveness: identifies high-risk dependencies and isolates experiments, ensuring backward compatibility._ However, this solution fails without **real-time updates** or if developers bypass sandboxed features. _Typical error: prioritizing performance over validation leads to unresolved module errors, halting execution._ The React Native community must act decisively. **Professional judgment:** adopt tools incrementally, prioritize backward compatibility, and rigorously validate dependencies. Without this, the ecosystem risks becoming a fragmented patchwork of incompatible tools, undermining its unified framework appeal. The rule is clear: _rapid technological advancements (X) require layered standardization (Y) to maintain cross-platform cohesion and innovation._

dev.to

An Evaluation Score Is Only as Useful as Its Target

My small document-evidence model agreed with its reference labels on 36 of 40 observations. The headline was 90%. Then I inspected the confusion matrix. It missed the only reference labeled as documented evidence. That error mattered more to the intended workflow than the headline suggested. I was building an assistant to help someone locate and check evidence in a document. Reproducing the dominant “not found” state while missing the sole positive example did not establish that the assistant was useful. This was a local prototype, evaluated on its own training observations against AI-origin references. It was not an independent test, a measure of candidate ability, or evidence of improved hiring outcomes. Those qualifications change what the score means. The exercise made me treat the target definition as part of the system design. ## What was the model being asked to predict? The prototype assigned a documentary state to each of four dimensions in a document. Its four possible states were documented evidence, not located, explicit absence, and ambiguous. Each state needs a precise interpretation. “Not located” describes what the available document and process revealed. It does not establish that a person lacks a skill. A document can omit something the person knows. Extraction can fail. A relevant passage can use unexpected language. “Documented” also requires care. A statement can mention a tool without describing completed work. An intention to learn is different from evidence of practice. Conflicting passages may require a reviewer rather than a forced positive or negative label. The references for this run came from AI, without a new independent human annotation. Agreement with them measured reproduction of those references. Calling that number “accuracy” without naming the target would invite a much broader interpretation than the experiment supported. ## Put class support beside the aggregate The forty observations came from ten documents, with four dimensions per document. Their reference distribution was highly uneven: Reference state | Observations ---|--- Documented evidence | 1 Not located | 33 Explicit absence | 0 Ambiguous | 6 The model reproduced all 33 “not located” references and three of the six ambiguous references. It predicted “not located” for the remaining three ambiguous observations and for the one documented-evidence observation. Those outcomes explain the 36/40 agreement. They also show why the score could conceal a failure in the capability I wanted to investigate. There were no explicit-absence references. A reporting convention can include that class in a four-class macro-F1 calculation, but its numerical contribution does not become measured performance on that state. The class had no evaluation support. I want a report to make unsupported capabilities visible. A reader should be able to distinguish “the method failed on these examples” from “this dataset did not test the capability.” ## Forty rows are not forty independent documents The four dimensions within one document share text, formatting, extraction quality, and source context. That creates correlation between observations. It would be misleading to describe this as forty independent cases. It would also be risky to split the rows randomly and allow dimensions from one document to appear in both training and evaluation. A model could encounter much of the same underlying input on both sides. For a future document-level evaluation, I would group observations by source document before assigning splits. Closely related versions and duplicated documents need the same treatment. The unit of separation should reflect how information can cross the boundary, rather than whichever unit makes the table easiest to partition. This particular run had an even simpler limitation: all forty observations were training observations. It demonstrated technical learning and exposed errors. It did not measure performance on unseen documents. ## Keep a baseline, then ask what it measures An earlier, separate benchmark compared a TF-IDF logistic-regression baseline with a small PyTorch model on a frozen document split. Its labels were derived from a lexical rubric, not from independently observed outcomes. Across five additional seeds on that same split, the baseline's test macro-F1 stayed at approximately 0.902. The neural model ranged from approximately 0.576 to 0.902. That was a useful diagnostic. The baseline was stable under that setup, while the neural model showed substantial sensitivity to initialization and optimization. Reporting only the best neural run would have hidden that sensitivity. However, the baseline also shared vocabulary with the labeling rubric. A high score could reflect successful reproduction of the proxy. It did not establish the quality of an operational decision. Repeated seeds examine one source of variability. They do not produce new independent documents, repair a weak target, or make an already inspected test set fresh again. I keep those historical results separate from the forty-observation prototype because they answer different questions. ## More examples can introduce a different task The next data problem was semantic compatibility. A larger synthetic corpus used eight graded criteria and levels from zero to four. The documentary prototype used four dimensions and four evidence states. These are different targets. Concatenating their rows would not make them equivalent. Consider this entirely invented passage: I have explored a video-editing tool through tutorials and would like to use it in a future team project. A generated grading rubric might award a low level for exposure to the tool. A documentary rule might classify the passage as insufficient evidence of completed editing work. Neither label automatically specifies an operational decision. The mapping needs an explicit definition, reviewed examples, and a record of the original target. Ambiguous or incompatible cases should remain visible. A score threshold is not a substitute for deciding what the label means. A later local experiment preserved the synthetic corpus's own eight-criterion target rather than silently converting it into the earlier four-state task. That preserves an important boundary: a result on the synthetic task remains a result on that task. ## An unseen identifier can still contain familiar language Synthetic data makes it easier to construct coverage and contrastive examples. It also makes shortcuts easy to repeat. Two documents can have different identifiers, names, section orders, and small wording changes while retaining the same underlying phrase template. An exact-duplicate check can pass even when the train and test examples remain linguistically very similar. In the corpus I inspected, variants reused phrase bases with added framing. That is a reason to investigate template overlap, not evidence that every result is invalid or that all leakage has been removed. For a stronger evaluation, I would track template families, inspect similarity across splits, and reserve genuinely different evidence formulations. I would also check whether length, section structure, or stylistic cues reveal the generated label. An unseen record is only useful to the extent that it tests the intended difficulty. ## Evaluate the workflow the model is meant to help The product question is whether the suggestion helps a reviewer find and assess evidence. The next useful comparison would be document plus search versus document plus search plus a model suggestion. It would need independently reviewed references, positive and ambiguous examples, missing-evidence cases, and explicit handling of extraction failures. I would measure missed evidence, misleading suggestions, corrections, abstentions, and review effort. The model suggestion and retrieved passage should remain separately inspectable: the search result is not automatically an explanation of the neural output. Reference review should also avoid showing the model's suggestion first. Otherwise, the evaluation can inherit the model's framing. Disagreements need resolution or a recorded pending state, rather than an invented consensus. That study has not been completed. It is the next evidence needed to judge usefulness, rather than another interpretation of the existing training score. ## Questions I now put before the score Before comparing methods, I write down: * What does the reference label mean, and who or what produced it? * Which classes and operational failures have actual support? * What is the independent unit: a row, a document, or a source family? * Has the evaluation set already influenced design decisions? * Are new labels compatible with the existing task? * Which workflow outcome would justify adding the model? The 90% headline was useful because inspecting it changed my next step. The system needed better reference coverage and an independent workflow comparison before a claim of utility. What does the target in your current evaluation actually represent?

dev.to

Design a food delivery app: the system design interview walkthrough

## Design a food delivery app: the system design interview walkthrough The request to **design a food delivery app** sounds like one line — you order food, a driver brings it — and that one line hides the whole interview. Picture it for a single restaurant and you reach for a table of orders; picture it for a whole city, matching every new order to a nearby available courier in seconds and streaming that courier's moving dot to the customer's phone, and it becomes a three-sided real-time problem with a dozen sharp edges. This is one of the most revealing **food delivery system design** prompts — the DoorDash-style question — because it forces every distributed-systems muscle at once: an order state machine, geospatial matching, event-driven services, and a high-write location firehose. The round turns on one structural insight. "Order food, a driver brings it" hides a system with **three** live participants — the customer, the restaurant, and the courier — and the hard parts are not the menu or the payment. The hard parts are **matching** an order to the right nearby courier and **streaming** that courier's live position back to the customer. Seen clearly, this is a real-time matching plus tracking problem sitting on top of an order state machine: dispatch is a geospatial nearest-available search, and **real-time tracking** is a high-write stream you must deliberately keep off your main database. Get that reframe and the rest of the **system design interview** is deciding how the order moves through its states, how you find the nearest free courier among tens of thousands, and where you put the location firehose so it never threatens order correctness. This walkthrough follows the path a strong candidate draws on the whiteboard — requirements, estimate, API and data model, high-level design, then the deep dives — and at each step it names the follow-up the interviewer uses to tell a memorised answer from an engineer who understands the tradeoff. ## How the food delivery round is actually run A food delivery round runs about 45 minutes on a shared canvas — you draw the boxes and arrows while you talk, and the interviewer interrupts. It is a favourite because the one-line problem opens onto deep water fast: the moment you draw "customer orders, courier delivers," the question becomes _how do you find the right courier_ and _where does the stream of GPS pings go_ , and every answer after that compounds from those two. The clock is a touch tighter than the hour-long rounds, so you have to spend your minutes on matching and tracking, not on the menu schema. > The interviewer rarely lets "a courier delivers it" stand. "An order comes in at the dinner rush — how do you find the right courier among tens of thousands moving around the city, and what happens when the first one you offer it to says no?" That question — nearest-available matching under load — is the one a copied answer can never rehearse, and the one this round is really testing. The rounds here are **modelled on how the loop runs** , not scraped from any candidate's recall. What matters for prep is the shape: a blank canvas, a tighter clock, and a follow-up after every decision you make. ### The five-step framework for any HLD round Every high-level design answer fits the same spine. Say it out loud at the start so the interviewer knows you have a plan, then walk it: 1. **Clarify requirements** — functional and non-functional, and what you are explicitly _not_ building. 2. **Estimate** — orders per second, active couriers and ping rate, and live-tracking connections, to justify every later choice. 3. **High-level design** — the boxes and arrows: customer, restaurant and courier apps, the order, dispatch and location services, and the event stream between them. 4. **Deep dive** — the two or three decisions that actually decide this system. 5. **Follow-ups** — bottlenecks, failure modes, and what you would change at ten times the scale. ### What the debrief measures The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: **problem solving** (did you reach an order state machine with geospatial dispatch and a location stream kept off the main database), **communication** (did you say why the location firehose must not share the order database in a single sentence), **technical execution** (order states and events, a geohash/quadtree index over courier locations, pub-sub plus websockets for the live dot), and **time management** (did you spend your 45 minutes on orders, matching and tracking, not on the restaurant menu model). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back. ## Step 1 — Requirements: what a food delivery platform must do Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system. Type | Requirement ---|--- **Functional** | A customer browses restaurants and places an order; the restaurant accepts and prepares it; the platform matches the order to a nearby available courier; the courier picks up and delivers; the customer tracks the courier live on a map with an ETA; all three sides get status updates. **Functional (optional)** | Batching multiple orders onto one courier, scheduled/ahead-of-time orders, promotions and surge pricing — call these out, then scope most of them for later. **Non-functional** | Low-latency matching (assign a courier in seconds); real-time location updates to the customer; high availability at dinner-rush peaks; order state must be consistent and never lost; location updates can tolerate some loss; scale across many cities. The sentence that earns points: _"The hard part isn't the menu or checkout — it's a three-sided real-time system, so the core is an order state machine, a geospatial search to match a nearby courier, and a live-location stream I keep off the order database."_ Say that and you have framed the entire design before drawing a box. ## Step 2 — Capacity estimation (back of the envelope) Estimation here is about proving one thing: there are **two very different write patterns** — low-rate order state and a high-rate location firehose — and the design must keep them apart. Quantity | Assumption | Result ---|---|--- Order throughput | high but modest — thousands of orders/sec at peak | ordinary rows in a normal transactional database; this is not the scaling problem Active couriers + pings | hundreds of thousands of couriers, a GPS ping every ~2–4 seconds | ~millions of location writes/sec — a streaming firehose, never the order DB Geospatial queries | one nearest-courier lookup per new order (plus re-tries) | frequent index lookups over moving courier locations — the matching hot path Live-tracking connections | one long-lived connection per customer watching an active order | hundreds of thousands of concurrent websockets, fan-out of courier positions Two conclusions fall out, and you should say both out loud. First, **order state is the easy axis** — the write rate is modest and fits a normal database, so order data stays consistent and durable without heroics. Second, **location is the hard axis** — millions of pings per second is a firehose that would crush a transactional database, so it belongs in an in-memory/streaming layer, and the matching lookups belong in a geospatial index, not in `SELECT ... WHERE distance < x`. You have now pointed the interview at the two real deep dives — dispatch and location — before anyone asked. ## Step 3 — API and data model The API is deliberately small: a customer places and tracks an order, couriers stream their location, and the platform assigns a courier. Operation | Shape ---|--- `place_order(customer, restaurant, items)` | creates an order in state `placed`; returns the `order_id`. `get_order_status(order)` | returns the current state of the order (placed, preparing, picked-up, …). `update_location(courier, lat, lng)` | a courier's app streams its GPS position every few seconds; goes to the location service, not the order DB. `assign_courier(order)` | the dispatch service finds and offers the order to a nearby available courier. `track(order)` | returns the live courier location and current ETA over a long-lived connection. The data model is thin. The order record is the source of truth for state; location pings live elsewhere and are mostly disposable: Entity | Notes ---|--- `order` | `order_id`, `state`, `customer_id`, `restaurant_id`, `courier_id` (once assigned), `items`, timestamps — the durable source of truth for state. `courier` | `courier_id`, `status` (available / on-delivery / offline), latest `location` — the dispatch and location services read this. `restaurant` | `restaurant_id`, menu, location, prep-time estimate. `location_ping` | `(courier_id, lat, lng, ts)` — high-volume, low-value; keep only the latest per courier (and maybe a sampled trail), never every ping forever. ## Step 4 — High-level design: orders, dispatch and location Now the boxes. The three **apps** — customer, restaurant and courier — talk through an **API gateway** to a small set of services, tied together by an **event stream** : * **Order service:** owns the **order state machine** and the order database — the durable record of every order and its current state. * **Restaurant service:** owns menus and restaurant-side actions (accept the order, mark it preparing, mark it ready). * **Dispatch / matching service:** holds a **geospatial index** of courier locations and runs the nearest-available search to assign a courier to an order. * **Location service:** ingests the courier GPS **ping** firehose into an in-memory / streaming layer and **pub-sub** , keeping it entirely off the order database. * **Notification service:** turns order-state events into push updates to the right side (restaurant ticket, courier offer, customer "your order is on its way"). * **Event stream:** every order-state transition is an event, so each service reacts without the order service calling them all directly — the restaurant gets the ticket, dispatch is triggered, the customer is notified. * **Customer live map:** reads the courier's moving position over a **websocket** fed by the location service, and shows the dot and ETA. Say explicitly that there are **two very different write patterns** here, and that the design keeps them in separate systems: low-rate, must-be-consistent **order state** in the order database, and high-rate, can-be-lossy **location pings** in the streaming layer. Naming that split unprompted — so the firehose never threatens order correctness — is a signal the interviewer is listening for. ## Deep dive: the order lifecycle and the services This is the first decision that actually decides the round. What _is_ an order, and how do three independent sides stay in step without one giant service calling everyone? Model the order as a **state machine**. An order moves through a defined set of states, and each transition is triggered by a specific actor; the order record is the **source of truth** for which state it is in. The services are split by who owns which actions, and they coordinate through **events** rather than direct calls, so each side reacts to what it cares about: Order state | Transition / trigger ---|--- `placed` | customer submits the order; order service creates the record and emits an event. `restaurant-accepted` | restaurant service: the restaurant accepts the ticket. `preparing` | restaurant marks the food as being prepared. `courier-assigned` | dispatch service matches and a courier accepts the offer. `picked-up` | courier confirms collection at the restaurant. `delivered` | courier confirms hand-off to the customer — terminal. `cancelled` | customer, restaurant or system cancels — terminal; compensating actions run. Each service owns a slice: the **order service** holds the state machine and the durable order record; the **restaurant service** owns accept/prepare/ready; the **courier service** owns a courier's status and the pickup/delivery confirmations; the **notification service** owns turning events into pushes. Because transitions are published to an **event stream** , the restaurant gets its ticket, dispatch is kicked off, and the customer is notified — all without the order service orchestrating every step synchronously. Keeping the order record as the single source of truth for state is what makes the whole thing debuggable: there is always one answer to "what state is this order in?" > "The restaurant marks an order ready, but the courier's app still shows 'preparing' — how do the two stay in sync?" Both read from the same order state, updated by events: the restaurant's "ready" is a transition the order service records and republishes, and the courier app subscribes to that order's events (or polls its status), so it converges to the new state. The order record — not either app's local copy — is the source of truth, so a stale screen is a refresh away from correct, never a second conflicting truth. System Design HLD · Messaging & realtime · 45 min **Design a food delivery app out loud, with follow-ups** Practise on Zynter → ## Deep dive: courier matching and dispatch This is the decision the whole round is really about. The write path for orders is easy; the follow-up is always "an order comes in — how do you find the right courier among tens of thousands moving around the city?" You cannot scan every courier and compute a distance per order — at city scale that is far too slow. Instead you keep courier locations in a **geospatial index** (a **geohash** , **quadtree** , or S2 cell scheme over the lat/lng of every available courier) so "who is near this restaurant?" is a cheap lookup rather than a full scan. Then you **score** the small candidate set and **offer** the order, handling rejection by moving to the next candidate. It is the same shape as ride-sharing dispatch: Step | What it optimises ---|--- **Index lookup** | query the geospatial index for available couriers within a radius of the restaurant — turns a city-wide scan into a cheap neighbourhood lookup. **Candidate set** | take the nearby available couriers as a short list — small enough to score precisely. **Score** | rank candidates by ETA to the restaurant, rating, current load, and whether the order can be **batched** with one they already carry. **Offer** | send the order to the top candidate and wait for accept within a timeout. **Accept / retry** | on accept, move the order to `courier-assigned`; on reject or timeout, offer the next candidate — so one slow courier never stalls the order. The geospatial index is what makes this real-time: because couriers are constantly moving, their positions update in the index (fed from the same location stream in the next deep dive), and each new order runs a fresh neighbourhood lookup rather than a brute-force distance sort over the whole fleet. Scoring, not just raw distance, is what separates a toy answer from a real one — the nearest courier is not always the best if they are about to finish another drop two streets from your restaurant. > "An order comes in — how do you find the right courier among tens of thousands moving around the city?" Not by scanning everyone: courier locations live in a geospatial index (geohash or quadtree), so I look up only the couriers near the restaurant, score that short list by ETA, rating, load and batching, and offer to the best. If they reject or time out, I offer the next — the index keeps the search local, and scoring keeps the choice sensible under load. System Design HLD · Messaging & realtime · 45 min **Defend courier matching and dispatch out loud** Practise on Zynter → ## Deep dive: real-time location tracking at scale Matching decides who delivers; this decides how the customer watches the dot move — and it is where candidates most often put the firehose in the wrong place. The follow-up is always about volume. Every active courier's app emits a **GPS ping** every few seconds. At hundreds of thousands of couriers that is **millions of writes per second** — and the first rule is that **you do not write every ping to the main transactional database**. That firehose would overwhelm the order DB and threaten the one thing that must stay consistent: order state. Instead, the pings flow through a dedicated path: * **Ping → location service:** the courier app sends `update_location(courier, lat, lng)` into a **location service** that holds the latest position per courier in an **in-memory** store (fast key-value, overwrite-in-place) — not a durable row per ping. * **Location service → pub-sub:** the service publishes position updates to a **pub-sub** layer, so anything interested (the customer watching this order, the dispatch index) subscribes without the location service knowing who is listening. * **Pub-sub → customer websocket:** the customer's live map holds a **websocket** / long-lived connection and receives the courier's moving position pushed to it, so the dot moves smoothly without the app polling. * **ETA:** the location service (or a small compute on top of it) turns the courier's position, the route and traffic into a live **ETA** that rides along with the position updates. * **Store only what's needed:** keep the **latest location per courier** and maybe a **sampled trail** for the active order; do not persist every ping forever — most of the firehose is disposable the moment the next ping arrives. The point to state plainly: this write-heavy stream is **isolated from order data**. Pings are high-rate and can tolerate loss (miss one and the next arrives in two seconds); orders are low-rate and must never be lost. Putting the firehose in an in-memory streaming layer with pub-sub fan-out — rather than the order database — is exactly what keeps a surge of location updates from ever threatening order correctness. > "Hundreds of thousands of couriers ping their location every 2 seconds — where does that firehose go, and how does the customer see the dot move?" Not into the order database — it goes to a location service that keeps the latest position per courier in memory and publishes updates to a pub-sub layer; the customer's map holds a websocket and gets the position pushed to it, with a live ETA. I keep only the latest location (and maybe a sampled trail), because a dropped ping is harmless but a dropped order is not — so the stream stays off the transactional store entirely. ## The follow-ups that decide the food delivery round Scan the probes from each deep dive and they rhyme. Master these five and you are ready for a version of the question that is not drawn exactly like this one. * **"Why an order state machine with events, not one big service?"** → The order record is the single source of truth for state, and publishing each transition as an event lets the restaurant, courier and notification sides react independently — so three sides stay in sync without synchronous orchestration. (communication) * **"How do you find the right courier among tens of thousands?"** → A geospatial index (geohash/quadtree) makes "who is near this restaurant?" a cheap neighbourhood lookup, then score the short list by ETA, rating, load and batching and offer, retrying the next on rejection. (problem solving) * **"Where does the location firehose go, and why not the order DB?"** → Into a location service holding the latest position in memory plus a pub-sub layer pushed to the customer over a websocket; pings are high-rate and lossy, orders are low-rate and must be consistent, so they stay in separate systems. (technical execution) * **"Restaurant says ready but the courier app shows preparing — how do they sync?"** → Both derive from the order's state, updated by events; the order record is the source of truth, so a stale screen converges on the next event or refresh, never a second conflicting truth. (technical execution) * **"A courier rejects or times out on an offer — what happens to the order?"** → The dispatch loop offers to the next candidate from the scored set, so one slow courier never stalls an order; the order simply stays in `placed`/pending-assignment until someone accepts. (problem solving) Each maps to a debrief dimension. Reading this list teaches you the answers; it does not teach you to deliver them while someone interrupts — that only comes from saying them out loud. ## How to prepare: a one-week plan You do not need fifty systems. You need this one cold and the framework automatic, so the next "design X" is just new nouns in the same five steps. * **Days 1–2:** Learn the five-step framework until it is reflex. Do the food delivery app end to end on paper — requirements, estimate, API, data model, the orders/dispatch/location boxes. * **Days 3–4:** Drill the matching deep dive: the geospatial index, neighbourhood lookup, scoring by ETA/rating/load/batching, and the offer-and-retry loop. Say each tradeoff in one sentence. * **Days 5–6:** Own the order state machine and location tracking — events between services, the ping firehose into an in-memory + pub-sub layer, websockets to the customer, and why the firehose stays off the order database. Answer each of the five follow-ups out loud. * **Day 7:** A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts. This post is the deep walkthrough behind one row of the broader Top 25 system design interview questions — once the food delivery app is automatic, the same spine carries the rest of the list. ## Frequently asked questions ### How do I design a food delivery app step by step? Follow the five-step HLD framework. Clarify requirements (place an order, restaurant accepts and prepares, match a nearby courier, deliver, track live with an ETA); estimate to show the two write patterns — modest order throughput that fits a normal database, and a location-ping firehose of millions of writes per second that must not; define the small API (place_order, get_order_status, update_location, assign_courier, track) with a thin order/courier/restaurant/ping model; draw the design (customer, restaurant and courier apps through a gateway to order, dispatch, location and notification services tied by an event stream, with the customer map over a websocket); then deep-dive the decisions that matter — the order state machine and services, courier matching and dispatch, and real-time location tracking. Finish on the follow-ups about rejection and sync. ### Why model the order as a state machine? Because an order is not one event — it moves through a defined sequence (placed, restaurant-accepted, preparing, courier-assigned, picked-up, delivered, plus cancelled), and each transition is triggered by a specific actor. Modelling it as a state machine with the order record as the single source of truth makes the whole system debuggable: there is always one answer to "what state is this order in?" Publishing each transition as an event lets the restaurant, courier and notification services react independently, so three sides stay in sync without one service orchestrating every step synchronously. ### How does courier matching find the nearest available driver? Not by scanning the whole fleet and computing a distance each time — that is far too slow at city scale. Courier locations are kept in a geospatial index (a geohash, quadtree, or S2 scheme over lat/lng), so "who is available near this restaurant?" is a cheap neighbourhood lookup that returns a small candidate set. You then score that short list by ETA to the restaurant, rating, current load, and whether the order can be batched with one the courier already carries, offer it to the best candidate, and on rejection or timeout offer the next. The index keeps the search local; the scoring keeps the choice sensible under load. ### Where should real-time location tracking live — not the main database? Correct — not the main transactional database. Hundreds of thousands of couriers pinging every few seconds is millions of writes per second, a firehose that would overwhelm the order database and threaten order consistency. Instead the pings go to a location service that holds the latest position per courier in memory and publishes updates to a pub-sub layer; the customer's live map holds a websocket and receives the moving position pushed to it, with a live ETA. You keep only the latest location (and maybe a sampled trail), because a dropped ping is harmless but a dropped order is not — so the high-write stream stays isolated from order data. ### How does the customer see the courier move in real time? The customer's app opens a long-lived connection — a websocket — when they start tracking an active order. The courier's app streams GPS pings to the location service, which publishes position updates to a pub-sub layer; the customer's websocket subscribes to that courier's updates and receives each new position pushed to it, so the dot moves smoothly on the map without the app repeatedly polling. A live ETA, computed from the courier's position and route, rides along with the position updates. The whole path runs through the streaming layer, never the order database. ### How do I practise a system design interview out loud? Reading a walkthrough is not the same as defending a design while someone interrupts. On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — you talk through a shared design on the canvas, and a voice interviewer pushes with the follow-ups above one at a time, then gives you a debrief on problem solving, communication, technical execution, and time management. The score is a practice signal, not a hiring decision. ## Practise it on Zynter ### You can read every state transition. The round is won on matching and live tracking. Pick the Food Delivery round, draw the orders-dispatch-location design on the canvas, and defend it out loud to a voice interviewer that keeps asking "how do you find the right courier among tens of thousands?" and "where does the location firehose go?" — then read the debrief. Zynter.ai is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026). Practise the Food Delivery round → Browse all interview tracks

dev.to

LocalLedger AI: Open-Source Bookkeeping with Human-Verified AI

_This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle._ ## What I Built LocalLedger AI is a bookkeeping assistant for freelancers and small businesses. It imports receipts, reads them locally on the computer where it runs, and requires a person to confirm every amount before it becomes part of the ledger. It also creates invoices, records payments that have already been received, and exports CSV files. The target user is a freelancer who keeps receipt photos in a folder and tracks expenses in a spreadsheet but does not want those documents sent to a hosted model. A bookkeeper managing a client's monthly records would benefit for the same reason. Names, addresses, and prices stay on the local machine. The application does not connect to a bank or collect card payments. It maintains records; it does not file taxes or decide which expenses are tax-deductible. ## Demo There is no public website because local operation is a core part of the product. The screenshots below, when added, will use a fictional Northwind Supplies receipt rather than real customer documents. The demonstration covers the following workflow: 1. **Import a receipt:** Upload a text-based PDF. The local reader extracts the vendor, date, currency, subtotal, tax, total, and receipt number. 2. **Review inconsistencies:** Change the total and observe how the application flags a mismatch between the subtotal, tax, and total without silently changing any values. 3. **Approve an expense:** Confirm the expense. Only then does it appear on the dashboard and in the default CSV export. 4. **Prevent duplicates:** Upload the same file again. The application identifies the matching file hash and explains why a second expense is not created. 5. **Create an invoice:** Issue an invoice with two line items and two tax rates. The server calculates the total in minor currency units and generates a downloadable PDF. 6. **View category reports:** Open the category report, where amounts in different currencies are displayed separately rather than incorrectly combined. The same workflow is available in `scripts/run_demo.py`. It uses a temporary data directory and does not call a commercial AI API. On my development machine, the script completed with the local model disabled. PDF text extraction and manual review were sufficient for the demonstration. ## Code **Repository:** LocalLedger AI on GitHub **License:** MIT The README covers setup, Tesseract OCR, and Ollama. Original documents and the SQLite database remain outside the source tree. ## How I Built It The backend uses FastAPI and SQLite, while the frontend uses React. Monetary values are stored as integer minor units, and invoice totals are calculated on the server from the line items. Text-based PDFs are processed using PyMuPDF. Photos and scanned documents can be processed using Tesseract when it is installed. A deterministic parser then looks for the vendor, date, currency, amounts, and reference number. Missing or ambiguous fields remain empty. A missing date is not replaced with today's date, and a currency is not inferred from the computer's location. Ollama provides optional support for running an open-weight language model locally. It communicates only through the loopback interface unless that restriction is explicitly disabled, and there is no hosted-model fallback. A model-generated suggestion is accepted only when it quotes text that actually appears on the receipt and fills a field that the deterministic parser left blank. The suggestion is still marked for review. Human approval is required before an expense becomes a ledger record. The core test suite completed with **71 tests passed and 1 skipped** on my development machine. The skipped test covers image OCR because Tesseract was not installed in that environment. The demonstration used Python 3.12. ## Why Does Open Innovation Matter? A receipt is a poor candidate for unnecessary third-party data processing. It can contain a person's name, address, tax details, and even fragments of account information. Sending every receipt to a hosted API can introduce recurring costs and expose sensitive business information to an external service. LocalLedger AI avoids per-document API fees and can still function on a laptop without a language model installed. When a model is installed, it can be changed through Settings without replacing the ledger. Changes in model pricing or the retirement of a hosted service do not take the user's bookkeeping records with them. The most important design decision is limiting what the model is allowed to do. If a receipt does not state the date, the date remains blank. If the subtotal, tax, and total disagree, the discrepancy is shown rather than silently corrected. The model is not asked to invent missing values. This makes local AI useful without treating its output as automatically trustworthy. ## My Agent Session The project was built using Cursor. The DevRelay session will be added once the session has been saved and made public.

dev.to

How to Find and Replace Data in Excel with Python and Highlight Matches

Reports exported from several systems rarely use the same wording. One file says "North America", another says "America"; a department was renamed last quarter; a column header oscillates between "Total" and "Sum". Fixing this by hand has a second, quieter cost: after the edits nobody knows which cells were touched, so verification becomes a line-by-line comparison. A more practical approach is to let a script perform the replacement and color every changed cell at the same time, so the affected area is visible at a glance. In Python this can be done with Spire.XLS for Python. Beyond plain string replacement, the library supports regular expression matching, numeric and formula lookup, and replacement that applies a new style in the same call, which covers most report-cleaning scenarios. ## Why Do Find and Replace in a Script * **Auditable** : the result is written to a new file while the original stays untouched, so you can always compare before and after. * **Annotated** : replacing and highlighting happen in the same loop, so there is no separate pass to work out what changed. * **Repeatable** : the same rules apply to dozens of worksheets or files by adding one more loop. ## Setting Up the Environment Install the Spire.XLS library: pip install Spire.XLS Then import the required modules in your script: from spire.xls import * from spire.xls.common import * ## Finding and Replacing Text While Highlighting It The worksheet-level `FindAllString()` returns a collection of every matching cell, and iterating that collection is enough to change the text and style one cell at a time. The example below replaces every "Total" with "Sum" and fills the affected cells in yellow: from spire.xls import * from spire.xls.common import * workbook = Workbook() workbook.LoadFromFile("Report.xlsx") worksheet = workbook.Worksheets[0] # Find all matching cells ranges = worksheet.FindAllString("Total", True, True) for range in ranges: # Assigning the text performs the replacement range.Text = "Sum" # Fill the cell yellow to mark the change range.Style.Color = Color.get_Yellow() workbook.SaveToFile("ReplaceAndHighlight.xlsx", ExcelVersion.Version2010) workbook.Dispose() The three arguments of `FindAllString()` are the text to look for, whether the search is case-sensitive, and whether it must match a whole word. Passing `True, True` here means only a standalone "Total" with exactly that casing is replaced, so `total` and `Subtotal` are left alone. `range.Text` is both a read and a write property — assigning to it performs the replacement — while `range.Style.Color` controls the cell fill and `Color.get_Yellow()` is a predefined color from the enum. ## Marking Matches Without Changing the Content When reviewing data you often want to flag suspicious cells rather than modify them. In that case, skip the assignment inside the loop: from spire.xls import * from spire.xls.common import * workbook = Workbook() workbook.LoadFromFile("Data.xlsx") worksheet = workbook.Worksheets[0] ranges = worksheet.FindAllString("Area", False, False) for range in ranges: # Keep the original text, mark it with color only range.Style.Color = Color.get_Yellow() workbook.SaveToFile("Marked.xlsx", ExcelVersion.Version2013) workbook.Dispose() Both flags are `False` here, so the search ignores case and does not require a whole-word match. "Area", "area", and "Area Code" are all highlighted. ## Matching Complex Patterns with Regular Expressions Regular expressions help when the data has no fixed literal form. `FindAllString()` has an overload with a fourth parameter; set it to `True` to enable regex matching: from spire.xls import * from spire.xls.common import * workbook = Workbook() workbook.LoadFromFile("FindTextByRegex.xlsx") worksheet = workbook.Worksheets[0] # Match every text that starts with North ranges = worksheet.FindAllString(".*North.", False, False, True) for range in ranges: range.Style.Color = Color.get_Yellow() workbook.SaveToFile("RegexHighlighted.xlsx", ExcelVersion.Version2013) workbook.Dispose() The pattern follows .NET regular expression syntax, so `\d{4}` locates four-digit years and `^A.*` locates any text beginning with A. Combining a regex with highlighting is a quick way to circle the cells that break a naming convention in a sheet that mixes several formats. ## Finding Numbers and Formulas Non-text data has its own lookup methods: # Find the number 100; the second argument means "match the exact value" numberRanges = sheet.FindAllNumber(100, True) for range in numberRanges: print(range.RangeAddress) # Find cells that use a specific formula formulaRanges = sheet.FindAll("=SUM(A11,A12)", FindType.Formula, ExcelFindOptions.none) for range in formulaRanges: print(range.RangeAddress) When the second argument of `FindAllNumber()` is `True`, only cells equal to that number match; with `False`, cells containing the number also match. `FindAll()` takes a `FindType` value — besides `Formula` it supports `Text`, `Number`, and `DateTime` — while `ExcelFindOptions.none` means no extra match options are applied. Unlike the earlier examples, these results are usually used for locating rather than editing: read `range.RangeAddress` inside the loop to get the cell reference, then act on it. A common follow-up is deleting the whole row: cr = sheet.FindString("Address", False, False) sheet.DeleteRow(cr.Row) `FindString()` returns only the first match, which suits "handle it once" cases. Read the `Row` property from the result and pass it to `DeleteRow()` to remove the row that carries the keyword. ## Applying a New Style During Replacement When a replacement also has to update appearance, `ReplaceAll()` performs the content and style swap in a single call. It takes four arguments: the old text, the old style, the new text, and the new style. newStyle = workbook.Styles.Add("newStyle") newStyle.Font.FontName = "Arial Black" newStyle.Font.Size = 14 cellRange = sheet.Range["D9"] oldStyle = cellRange.Style sheet.ReplaceAll("North America", oldStyle, "America", newStyle) This replaces "North America" with "America" only in cells whose style matches cell `D9`, and applies `newStyle` (Arial Black, size 14) at the same time. Styles created with `workbook.Styles.Add()` can be reused, so for consistently formatted reports you can take the style from one sample cell and let `ReplaceAll()` filter by it, which avoids rewriting identical text elsewhere in the sheet. ## Practical Tips * **Choose the match strength deliberately** : start with `False, False` to see the full set of matches, then tighten to a whole-word match once the data is confirmed clean. * **Log the addresses** : collect `range.RangeAddress` into a list or a log file during the loop to produce a change list after the replacement finishes. * **Keep a backup** : even though the script writes a new file, retain the original so the diff between the two versions stays available. * **Release resources** : call `workbook.Dispose()` after each workbook, and in batch jobs load, process, and release one file at a time. * **Work across worksheets** : `FindAllString()` operates on one worksheet, so for multi-sheet workbooks loop with `Worksheets.Count` and `get_Item(i)`. ## Conclusion This article covered several ways to find and replace data in Excel with Python: replacing text through `FindAllString()` and marking the results with `Style.Color`, matching complex patterns through the regex overload, locating numbers and formulas with `FindAllNumber()` and `FindAll()`, and applying a new style during replacement with `ReplaceAll()`. Combined, these operations form an auditable report-cleaning routine in which replacement and annotation happen in one pass.

dev.to

The Silent Killers of AI Code: How "Vibe Coding" is Rotting Codebases from the Inside

We have officially entered the era of "vibe coding". You type a prompt into Cursor or Claude Code, watch hundreds of lines of polished TypeScript or Python scroll past, eye the green test suite, and hit merge. Engineering velocity has never looked higher on paper. Features that once took weeks now take hours, and product managers are celebrating unprecedented delivery metrics. But behind the shiny dashboard numbers, senior architects and core maintainers are starting to sound an alarm. A silent, structural decay is creeping into modern software repositories: Synthetic Code Rot. It isn't obvious at first. The tests pass, the linter doesn't complain, and the code compiles without warnings. Yet under the hood, system intuition is evaporating, micro-abstractions are multiplying exponentially, and production debugging is transforming into a high-stakes guessing game. Here is a look at how AI-first generation is quietly degrading long-term codebases—and the concrete engineering guardrails you can deploy today to stop it. **1. The Velocity Illusion vs. The Mental Model Deficit** Traditionally, writing code was the slowest part of software engineering. The real work happened prior: mapping out state transitions, considering failure modes, and mental-modeling how a new feature interacted with legacy modules. Writing the actual lines was simply translating an already-formed mental model into syntax. When you outsource the syntax generation entirely to Large Language Models (LLMs), you skip the mental modeling phase altogether. **[ Traditional Flow ] **Problem -> Mental Model -> Architectural Trade-offs -> Code -> Verification **[ Vibe Coding Flow ]** Problem -> LLM Prompt -> Eyeball Code -> Pass Tests -> Merge The consequence? Zero institutional memory. When an outage strikes at 2:00 AM, the engineer on call is no longer stepping through code they once conceptualized. They are staring at hundreds of lines of synthetic logic that looks right, but that no living person on the team truly understands. **2. The "Synthetic Pattern" Problem: Over-Defensive Bloat** LLMs are probabilistic engines trained on vast, heterogeneous open-source datasets. To minimize error rates across diverse contexts, AI code assistants exhibit distinct default behaviors that compound over time: Micro-Abstraction Sprawl: LLMs love wrappers, helper utilities, and single-use factory functions. Instead of writing a straightforward 5-line inline function, an AI assistant will often introduce an abstract handler class, an interface, and a dependency-injected builder pattern. Duplicated Logical Fragments: Because the LLM’s context window lacks full global awareness of your repository’s existing utility modules, it frequently re-invents minor utility logic (e.g., date formatting, string normalization, custom retry handlers) using slightly different syntax across multiple files. Over-Defensive Null Checks & Try-Catch Bloat: LLMs frequently wrap code in excessive, generic try-catch blocks that swallow specific exceptions or return broad null values, obscuring true stack traces when silent failures occur downstream. Individually, these patterns appear harmless. At scale, they cause code inflation—increasing the surface area of your codebase without adding real business value. **3. The Test Suite False Sense of Security** "If the unit tests pass, we're safe to deploy." This classic engineering rule breaks down when AI is used to write both the implementation logic and the test suite. LLMs naturally mirror the assumptions made in the code they generate. If an AI assistant implements a buggy state transition, it will almost certainly write a unit test that validates that specific flawed assumption. The result is a green pipeline masking fundamental edge-case vulnerabilities, race conditions, and memory leaks that only reveal themselves under real-world production load. **4. Engineering Guardrails: How to Reclaim Control** You don't need to ban AI coding assistants; doing so would be a massive step backward for productivity. Instead, engineering teams must transition from AI-first writing to strict AI-first auditing. Here are four concrete strategies top engineering organizations are deploying: A. Implement Strict AST-Based Linters & .cursorrules / Custom System Prompts Don't rely on human reviewers to catch synthetic boilerplate. Configure AST-based linters (like ESLint rules or custom static analysis) and repo-level AI rules (.cursorrules or .claudecode guidelines) to explicitly restrict unnecessary pattern expansion: Enforce explicit limits on file length and cyclomatic complexity. Ban single-use factory functions and premature interface abstractions. Mandate the reuse of verified internal utility libraries over inline generation. B. Enforce the "Explain-the-State-Machine" PR Rule In pull requests containing significant AI-generated logic, require the author to include a concise description of: The underlying state transitions being modified. Potential failure modes and how they are handled. Why existing codebase utilities were not reused. If the author cannot explain the exact state flow without re-prompting the AI, the PR should not pass review. C. Separate Code Generation from Test Generation Never let the same LLM context generate both the feature implementation and its test coverage in a single prompt. Have one engineer (or prompt run) write the implementation, and a separate test run—or ideally, a human developer—write boundary-condition tests against the spec before looking at the synthetic code. Conclusion: Velocity is Vanity, Maintainability is Scale AI assistants are extraordinary force multipliers for developers who already possess strong system design fundamentals. But when velocity completely untethers itself from comprehension, technical debt accumulates at compound interest. The teams that win in the AI era won't be the ones shipping the highest volume of raw syntax—they will be the ones maintaining the crispest mental models, tightest guardrails, and cleanest architectural boundaries.

dev.to

Best Firefox Extensions for Developers: What I Actually Use

After years of web development with Firefox, these are the extensions that actually improve my workflow — not the ones that just look good in a list. ## Privacy + Ad Blocking ### uBlock Origin (Essential) The only ad blocker that works exactly as advertised. Open source, maintained by Raymond Hill (gorhill), does what it says without any adware-style behavior. Key feature for developers: **Medium Mode** (dynamic filtering). Right-click the extension popup → Dashboard → "I am an advanced user." This lets you block entire categories of third-party scripts — useful for checking what a site loads without your usual content block. Install: addons.mozilla.org/firefox/addon/ublock-origin ### Multi-Account Containers (Mozilla) First-party isolation done right. Separate sessions for work Gmail, personal Gmail, different AWS accounts — all in one browser window. For developers: use a container for each project's staging environment. No cross-cookie leakage between environments. Install: addons.mozilla.org/firefox/addon/multi-account-containers ## Session and Tab Management ### Tab Manager & Session Saver Named sessions for context switching. Save your "Code Review" tabs separately from "Documentation" and "Project X" tabs. One-click to switch between sets. Key feature: all local, no cloud sync, MIT licensed. For developers who context-switch between multiple projects throughout the day. Install: addons.mozilla.org/firefox/addon/tab-manager-session-saver ## Developer Tools Additions ### Wappalyzer Identifies technologies used by any website. Stack fingerprinting — see which framework, CDN, analytics, etc. a site uses. Useful for competitive research and inspiration. Install: addons.mozilla.org/firefox/addon/wappalyzer ### JSON Formatter Firefox displays raw JSON in the browser with no formatting. This extension formats it with collapsible trees, links, and color coding. Essential when working with APIs. Search "JSON Formatter" on AMO — several good ones available. ### CSS Peeper A simplified CSS viewer for design inspection. Shows colors, fonts, and spacing of selected elements in a clean sidebar. Useful when you want quick visual inspection without opening DevTools. ## Productivity ### Vimium Keyboard-driven browser navigation. `f` to show link hints, `H`/`L` for back/forward, `j`/`k` for scrolling, `T` to search open tabs, `b` for bookmarks. Takes a few days to build muscle memory but significantly reduces mouse usage. Install: addons.mozilla.org/firefox/addon/vimium-ff ### Dark Reader (vs NightOwl) **Dark Reader** : Transforms any site to dark mode via DOM manipulation. Works everywhere. Higher CPU. Occasional visual glitches. **NightOwl** : Uses CSS `prefers-color-scheme` only. Works on sites with built-in dark mode (GitHub, HN, Notion, etc.). Lighter, less likely to break things. If you primarily use modern developer sites: NightOwl. If you need dark mode everywhere: Dark Reader. ### OneTab (or Tab Manager alternatives) OneTab collapses all tabs into a list. Tab Manager & Session Saver is a more capable alternative (named sessions, restore specific sessions, not just "collapse everything"). ## Code-Related ### Stylus Inject custom CSS into any site. Useful for fixing a site's layout that bothers you, creating a custom reading mode for specific documentation sites, or applying company style guide tweaks. ### Redirector Redirect URLs automatically. Useful for always accessing a specific environment (dev vs staging vs production) or using alternative frontends for privacy. ### PrivacyBadger (EFF) Behavioral tracking protection. Unlike uBlock which uses lists, Privacy Badger learns over time what behaves like a tracker. Complementary to uBlock, not a replacement. ## What I Don't Use **Grammarly** : Sends your text to external servers. Too much privacy risk for code review comments. Use the VS Code extension instead. **LastPass/other cloud password managers with unknown server-side access** : Bitwarden is the exception — open source, self-hostable, well-audited. **Tab suspenders** : Modern Firefox with `browser.tabs.unloadOnLowMemory = true` handles this natively. Start with uBlock Origin + Multi-Account Containers — these two change your browsing fundamentally. Add Tab Manager & Session Saver if you context-switch between multiple projects. Vimium if you want to go keyboard-first. Everything else is preference.

dev.to

Exploring End-to-End Test Automation: More Than Just Execution

When we talk about test automation, the first thing that often comes to mind is writing scripts and running tests. But is test automation really just about that? As I've been exploring this field, I've started looking at testing from a broader perspective. Writing a test is one part of the process. Understanding what to test, making sure the test is reliable, investigating failures, and keeping tests useful as an application changes are equally important. In this post, I want to share some of my thoughts on end-to-end test automation, how AI is becoming part of it, and what I'm interested in learning more about. What Is End-to-End Test Automation? Imagine you're using an online shopping application. You log in, search for a product, add it to your cart, complete the payment, and receive an order confirmation. For everything to work correctly, several parts of the application need to work together. End-to-end (E2E) testing checks whether this complete user journey works as expected. Instead of checking just one button or one function, we test how different parts of an application work together from the user's point of view. As I've been exploring this field, I came across X360 AI Tech, a platform focused on AI-powered test automation. What I found was its broader approach to the testing workflow. The platform's publicly described capabilities cover areas such as requirement ingestion, test scenario generation, coverage intelligence, automated execution, self-healing, and failure analysis. Exploring platforms like this gives me more questions to investigate. I find this direction interesting because it shifts the discussion from simply automating test execution to considering how the overall testing process might be improved. For me, exploring these questions is a useful way to understand where modern test automation is heading. I don't expect one tool or technique to solve every testing challenge. Different applications have different requirements, risks, and constraints. Understanding those differences is part of what makes exploring this field interesting. I plan to continue exploring these ideas and sharing what I learn along the way. I'm interested in learning from your experiences and perspectives. This is part of my ongoing exploration of modern test automation. More learning and questions to come!

dev.to

Firefox Passkeys: How They Work and How to Use Them

Passkeys are a replacement for passwords using public-key cryptography. Firefox 122+ has built-in passkey support. Here's how it works and how to use it. ## What Passkeys Are A passkey is a pair of cryptographic keys: * **Private key** : Stored on your device (in a secure enclave or hardware security key), never leaves the device * **Public key** : Stored on the website When you sign in, the website sends a challenge. Your device proves it has the private key by signing the challenge, without ever sending the key itself. No password is transmitted. **Benefits:** * Phishing-resistant: The key is bound to the website's domain — a fake `g00gle.com` site can't steal your passkey for `google.com` * No password reuse: Each passkey is unique per website * No credential stuffing: Your credentials can't be leaked if the website is breached (only the public key is stored) * Biometric unlock: You authenticate with TouchID, Windows Hello, or a PIN — not a password ## How Firefox Handles Passkeys Firefox 122+ implements the WebAuthn API with passkey support. When you sign in to a site that supports passkeys: 1. Site sends a WebAuthn authentication request 2. Firefox shows a passkey dialog 3. You authenticate with your device's biometric (TouchID on Mac, Windows Hello on Windows, screen lock on Linux) 4. Firefox signs the challenge with your private key 5. Site verifies the signature Firefox stores passkeys in: * **macOS** : System Keychain (via macOS's credential storage) * **Windows** : Windows Hello credential store * **Linux** : Firefox's own storage (more limited) * **Hardware keys** : YubiKey, Titan Key, etc. (via FIDO2/CTAP) ## Sync Across Devices This is the current limitation: **passkeys don't sync via Firefox Sync yet**. If you create a passkey on your desktop, your phone doesn't have it (unless you use a hardware key that works on both). Options: * **Hardware key (YubiKey)** : Works on all devices; carries your passkeys physically * **Cross-device authentication** : Use your phone to authenticate on desktop (via QR code + Bluetooth proximity) — standard passkey flow * **Third-party password managers** : Bitwarden, 1Password, and Dashlane can sync passkeys across browsers and devices Mozilla has announced passkey sync is coming to Firefox Sync, but as of late 2024 it's not yet available. ## Creating a Passkey in Firefox On any site that supports passkeys (GitHub, Google, Apple ID, Microsoft, PayPal, Adobe, etc.): 1. Go to your account security settings 2. Look for "Passkeys" or "Security keys" or "Passwordless sign-in" 3. Click "Add a passkey" or "Create passkey" 4. Firefox shows the passkey creation dialog 5. Authenticate with your device's biometric or PIN 6. The passkey is created and stored Firefox's passkey dialog shows: * Which device will store the passkey * An option to use a hardware security key instead * An option to use another device (QR code flow) ## Using Passkeys to Sign In When you sign in to a site with a passkey: 1. Enter your username (or email) on the login page 2. Firefox detects a stored passkey and prompts you to use it 3. Authenticate with TouchID/Windows Hello 4. Signed in — no password entry Some sites support passkey auto-fill (similar to password auto-fill) where Firefox offers the passkey before you even start typing. ## Managing Passkeys in Firefox `about:logins` — Firefox's password manager. Passkeys created in Firefox appear here, labeled as passkeys (distinct from passwords). You can delete passkeys from `about:logins`. This removes them from Firefox's storage. Note: the server still has the public key registered — you may need to delete it from your account settings on the site as well. ## Hardware Security Keys Firefox has excellent hardware security key support (FIDO2/WebAuthn via CTAP). YubiKeys, Titan Keys, and any FIDO2 key work. Hardware keys are the most portable passkey solution — you plug in the key, tap it to authenticate, and it works on any device regardless of OS. On Linux: hardware key USB access may require adding udev rules: # For YubiKey on Linux wget https://raw.githubusercontent.com/Yubico/libu2f-host/master/70-u2f.rules sudo cp 70-u2f.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules ## Passkey Support vs Password Support Passkeys supplement, not replace, passwords in most site implementations currently. Sites usually let you sign in with either a passkey OR your password. The goal is to eventually make passkeys the primary method and deprecate passwords. For now: if a site offers passkeys, setting one up is worth it. You can still use your password as a fallback. Passkeys are the most significant improvement to web authentication in years. Firefox's support is solid for desktop use. The main gap — sync across devices — will improve as Mozilla adds passkey sync to Firefox Sync. For cross-device use now: a hardware key or a password manager that handles passkeys (Bitwarden supports them) is the practical path.

dev.to

I Built a Free Cargo Line Planner for Transport Fever 3

Transport Fever 3 changed the one thing every transport sim player takes for granted: cargo no longer has a destination. In Transport Fever 2, an industry produced goods "for" a town and your job was connecting the dots. In TF3, cargo just sits at the industry until one of your lines collects it. Nothing moves unless you move it. That single design change turns fleet sizing from arithmetic into guesswork — how many vehicles does a line actually need when there's no demand signal telling you? I kept getting it wrong in my own games. So I built a small free tool to do the math: the Transport Fever 3 Cargo Line Planner (https://gametinkerer.com/transport-fever-3/cargo-line-planner/). ## What it does (three things, nothing more) **1. Size a line.** You enter a cargo target per period, what one vehicle carries per trip, and how many round trips it completes. The planner divides target by per-vehicle monthly capacity and rounds up. That's the whole formula — deliberately boring. **2. Compare two setups.** Two fleet configurations side by side: capacity per vehicle, vehicles needed, current fleet capacity, cargo still uncovered, extra vehicles needed. This is the one I use most — "should I buy 6 fast trucks or 10 slow ones" stops being a vibe decision. **3. Find the bottleneck.** Enter each stage of a chain (production → first leg → transfer → final leg) and it tells you which stage caps the whole chain. In TF3's model this matters more than in TF2, because a warehouse transfer stage silently throttling your line to 90 units while everything else does 110 is exactly the kind of thing you don't notice until you're wondering why the industry is drowning in unsold goods. ## What it deliberately doesn't do No game recipes. No profit estimates. No delivery deadlines, no spoilage modeling, no loading-queue simulation. The official cargo dev blog confirms the systems changed but doesn't publish a universal profit formula, and I'd rather show an honest estimate than a confident fiction. Every number on the site is labeled: announced systems vs. review-reported details vs. our editorial suggestions. The cargo type table (37 types across liquid/bulk/flatbed/goods — the category decides which vehicle body can carry what) only lists what Urban Games has officially named; the rest gets added when it can be checked in the released game, not scraped from game files that might change with the next patch. ## Three things I learned building it **Scope is a feature.** The first version tried to be a full logistics simulator. It was unusable. Cutting it to "three questions, one page" made it something I'd actually open mid-game. **Label your uncertainty.** Transport sim players are spreadsheet people — they'll trust a tool that says "unverified" over one that fakes precision. Marking every figure's source turned out to be the most-complimented part of the whole site. **Build the tool you'd tab to.** If I wouldn't alt-tab to it during a session, nobody else will either. The planner lives or dies on whether it answers in under 30 seconds. The planner is free, no account, runs in the browser: https://gametinkerer.com/transport-fever-3/cargo-line-planner/. If you play TF3, try breaking it — I'd genuinely like to know where the estimates fall apart against real saves. — Luke (https://gametinkerer.com)

dev.to

Encrypted Client Hello (ECH) in Firefox: What It Does and Why It Matters

Encrypted Client Hello (ECH) is one of the most significant recent privacy improvements to how HTTPS works. Firefox 118+ supports it. Here's what it does and why it matters. ## The Problem ECH Solves When you connect to a website over HTTPS, the TLS handshake reveals more than you might think. Specifically, the **Server Name Indication (SNI)** — the hostname you're connecting to — is sent in plaintext during the initial handshake, before encryption kicks in. This means: * Your ISP can see which websites you're visiting (even with HTTPS), not just the IP addresses * Network monitors and firewalls can see and potentially block specific domains * This happens even when using DNS over HTTPS (which only encrypts the DNS lookup, not the SNI) ## What ECH Does ECH encrypts the Client Hello message, which contains SNI and other information, using a public key obtained from the DNS server (stored in an HTTPS DNS record). Result: * The hostname you're connecting to is encrypted from passive network observers * ISPs, network monitors, and other middleboxes see only that you're connecting to an IP * The actual hostname is only visible to the authoritative server (and anyone with access to its private key) ## Browser + Server + DNS: All Must Support ECH ECH requires coordination between: 1. **Browser** (Firefox 118+ supports it) 2. **Server** (must publish an ECH key in DNS and handle encrypted Client Hellos) 3. **DNS resolver** (must support HTTPS records / `SVCB` DNS resource records) Currently (as of late 2024), Cloudflare-hosted sites are the primary ECH supporters. If you visit `cloudflare.com` or sites using Cloudflare's CDN, you're likely getting ECH. ## Enabling ECH in Firefox ECH is enabled by default in Firefox 118+ when using a compatible DoH resolver. Check if ECH is working: 1. Visit `cloudflare.com/cdn-cgi/trace/` 2. Look for `sni=encrypted` If it shows `sni=plaintext`, ECH is either not working or not supported for that connection. **about:config check** : * `network.dns.echconfig.enabled` — should be true * `network.dns.http3_echconfig.enabled` — ECH for HTTP/3 connections ## ECH + DNS over HTTPS ECH works best with DoH. If you're using your ISP's DNS resolver: 1. The DNS lookup for ECH keys goes through the ISP's resolver, which sees your query 2. The ISP still knows which domain you're looking up (before ECH encryption starts) With DoH + ECH: * DNS query for ECH keys is encrypted (ISP can't see domain) * TLS Client Hello is encrypted (ISP can't see SNI) * Combined: ISP sees only the IP address This is why the combination of DoH + ECH is significant — they address two different observation points. ## Who Benefits Most from ECH **VPN users** : Even with a VPN, if the VPN logs traffic, they can see SNI. ECH means even VPN providers see only IPs. **Users in censored networks** : If a firewall blocks sites by SNI, ECH prevents that blocking mechanism. **Privacy-focused users** : Reduces passive traffic analysis by ISPs, employers, or anyone monitoring network traffic. ## ECH Limitations **Not universal yet** : Most of the web doesn't support ECH. Cloudflare sites do; other CDNs are in various stages of adoption. **GREASE** : Firefox sends fake ECH (GREASE — Generate Random Extensions And Sustain Extensibility) even to servers that don't support it. This ensures middleboxes that block ECH don't break the non-ECH future internet. **Outer vs Inner Hello** : Even with ECH, a "shared" outer SNI is sent. Cloudflare uses `cloudflare-ech.com` as the shared outer SNI for all ECH connections — it reveals you're connecting to a Cloudflare site but not which specific site. ## How to Check If a Site Supports ECH # Check if a domain has ECH config in DNS dig +short @1.1.1.1 cloudflare.com HTTPS # Look for 'ech=' in the output Or use: `https://www.cloudflare.com/ssl/encrypted-sni/` (Cloudflare's checker tool). ## Relationship to HTTP/3 ECH is independent of HTTP/3 but both have overlapping privacy benefits. HTTP/3 (QUIC) encrypts more of the handshake by default, and QUIC + ECH together provide strong metadata privacy. Firefox supports HTTP/3 with QUIC and ECH. Modern connections to Cloudflare sites from Firefox should use all three. ECH is one of those "the infrastructure is building toward this" features where the benefit will increase as more servers adopt it. For now, Cloudflare coverage is substantial (a large fraction of major websites use Cloudflare). Firefox's support means you're getting ECH when it's available without any configuration.

dev.to

Firefox Developer Edition: What Is It and When Should You Use It?

Firefox Developer Edition is a separate browser aimed at developers. Here's what makes it different from regular Firefox and when it's worth using. ## What Firefox Developer Edition Is Firefox Developer Edition is the Beta release channel with a developer-focused skin and some extra features pre-enabled. It's not a separate browser engine — it uses the same Gecko engine as standard Firefox, just tracks Beta instead of Stable. Key characteristics: * **Tracks Beta** — you're running the next Firefox release (approximately 4-8 weeks ahead of Stable) * **Separate profile** — doesn't share cookies, history, or settings with your regular Firefox install * **Dark DevTools theme** — the main visual difference * **Some developer defaults** pre-configured ## Features Pre-Enabled in Developer Edition Firefox Developer Edition enables a few things that aren't on by default in Stable: **Remote debugging** : Enabled by default. You can debug Firefox on your Android device or other Firefox instances via USB without changing settings. **Developer-focused about:config settings** : Some experimental web platform features may be on. Check `about:config` → search "DevEdition" for specific flags. **CSS Grid/Flexbox inspector** : Available in both editions now, but Developer Edition often gets these earlier. ## The Separate Profile Advantage Because Developer Edition has its own profile, you can: * Keep production browser state clean (no testing artifacts) * Test your site as a "real user" without all your normal extensions and logged-in states * Install experimental extensions without risking your main browser * Run Developer Edition alongside stable Firefox simultaneously This is the main reason to use Developer Edition. You don't have to choose between a clean test environment and your daily driver. ## Running Both Simultaneously Firefox Stable and Developer Edition use separate profiles and can run at the same time on the same machine. No special flags needed — they're essentially different programs: * `/Applications/Firefox.app` (stable) * `/Applications/Firefox Developer Edition.app` (dev edition) On Windows: separate install directories. On Linux: separate install directories, or install from different package sources. ## Developer Edition vs Firefox Nightly Feature | Developer Edition | Nightly ---|---|--- Channel | Beta | Nightly (cutting edge) Stability | Like Beta (mostly stable) | Can break Updates | Every 4 weeks (new version) + patches | Twice daily Features | Beta features | Latest, some experimental Use case | Development testing | Maximum early access Developer Edition is the practical choice for most devs. Nightly is for Firefox contributors, extension developers who need maximum lead time, and people who want bleeding edge features. ## Should You Use Developer Edition as Your Daily Driver? Yes, if: * You want to stay ahead of upcoming Firefox changes * You actively develop for Firefox and need to test against Beta * You want the separate profile for clean testing * You're OK with occasional Beta-era bugs No, if: * You need maximum stability * You don't want to run two browser versions * The visual difference (dark toolbar) bothers you Many developers who choose Developer Edition do so primarily for the separate profile and the slightly-ahead-of-stable position. You can achieve most of this by using Firefox Profiles manually (`about:profiles`) with the stable version. ## Developer Edition DevTools Specifics The built-in DevTools are identical to standard Firefox DevTools. The "extra" DevTools features in Developer Edition: **Eyedropper** : Pick colors from anywhere on screen (not just the page). Accessible from the color picker in DevTools. **Screenshot toolbar button** : Takes screenshots of the current viewport or the full page. Available in standard Firefox too, but sometimes pre-pinned in Developer Edition. **Web Developer toolbar** : A command line for DevTools (`Shift+F2`) — mostly superseded by modern DevTools. ## Getting Developer Edition Download from `www.mozilla.org/firefox/developer/` Available for Windows, macOS, and Linux. On Linux, it's typically an archive you extract rather than a package you install (to avoid conflicts with system-installed Firefox). ## Firefox Developer Edition on Linux On most Linux distributions, the recommended approach: # Extract to opt directory tar xjf firefox-developer-edition.tar.bz2 -C /opt/ # Create symlink or launcher /opt/firefox/firefox --version Or use flatpak: `flatpak install flathub org.mozilla.firefox` — though this is the stable version. The Developer Edition flatpak is available at `flathub.org` as well. For developers who want a second Firefox that tracks upcoming changes and has an isolated profile: Developer Edition is the right choice. For most everyday development work, standard Firefox with DevTools is equally capable.

dev.to

GitHub Actions Workflow Lockfiles: How Pinned Action Versions Prevent Supply-Chain Attacks in CI/CD Pipelines

GitHub Actions workflow lockfiles bring package-manager-style dependency pinning to CI/CD pipelines. Instead of trusting mutable tags like `v1` or `main`, workflows now lock actions to specific commit SHAs. This creates an immutable execution graph that prevents compromised action updates from poisoning every dependent workflow. The feature entered public preview in October 2026 for Enterprise Cloud, Team, and Free plans. It addresses a critical attack vector: if an action maintainer's credentials are stolen or a tag is force-pushed, every workflow using that action immediately runs the malicious code. Lockfiles break that transitive trust chain. ## The Problem with Mutable References Most GitHub Actions workflows reference dependencies by tag: - uses: actions/checkout@v4 - uses: docker/build-push-action@v5 Tags are Git references that can move. If `v4` points to commit `abc123` today and `def456` tomorrow, your workflow executes different code without any change to your repository. Semantic versioning helps with accidental breakage, but it does nothing against intentional compromise. The attack surface is broad: * Maintainer account takeover * Force-pushed tags after code review * Compromised release automation * Transitive dependencies in composite actions Dependabot can pin actions to full SHAs in workflow files, but that creates a different problem: every SHA update requires a pull request and manual review. Lockfiles separate the pinning mechanism from the workflow definition. ## How Workflow Lockfiles Work A lockfile lives at `.github/workflows/actions.lock` and maps each action reference to a commit SHA: { "actions/checkout@v4": { "resolved": "a1b2c3d4e5f6...", "integrity": "sha256-..." }, "docker/build-push-action@v5": { "resolved": "f6e5d4c3b2a1...", "integrity": "sha256-..." } } When a workflow runs, GitHub Actions reads the lockfile and executes the recorded commits, ignoring any tag movements. The workflow file still uses human-readable tags, but the runtime uses immutable SHAs. The `gh actions-lock` CLI extension manages the lockfile: # Generate lockfile for all workflows gh actions-lock generate # Update a single action gh actions-lock update actions/checkout # Verify lockfile matches workflow definitions gh actions-lock verify Workflows onboard individually. Once a workflow is locked, GitHub Actions requires lockfile entries for all its dependencies. Missing entries cause the workflow to fail. ## Transitive Dependencies and Composite Actions Composite actions can call other actions. If `my-org/deploy-action@v1` internally uses `actions/checkout@v4` and `aws-actions/configure-aws-credentials@v2`, the lockfile must pin all three. The `generate` command walks the dependency graph: 1. Parse workflow YAML for `uses:` statements 2. Fetch each action's `action.yml` 3. Recursively resolve composite action dependencies 4. Record commit SHAs for the entire tree Reusable workflows (workflows that call other workflows) are also locked. The lockfile pins both the reusable workflow's commit and any actions it uses. This creates a complete dependency graph. If any transitive dependency is compromised, the lockfile prevents execution until you explicitly update and review the change. ## Failure Modes and Cache Behavior Lockfiles do not create local caches. If a pinned commit is deleted or force-pushed, the workflow fails. GitHub Actions fetches actions from the repository at runtime, so a missing SHA is a hard error. This is intentional. A deleted commit signals either repository corruption or an attempt to hide malicious code. Failing loudly is safer than falling back to a mutable reference. If an action repository is deleted entirely, workflows fail until you remove the dependency or point to a fork. There is no automatic fallback. ## Lockfile Policy Enforcement The "Require lockfile" policy lets administrators mandate lockfiles at the enterprise, organization, or repository level. When enabled: * Workflows without lockfiles fail * Workflows with stale lockfiles (missing entries for new actions) fail * Manual workflow runs cannot bypass the requirement This creates a security boundary. Developers cannot opt out by removing the lockfile or skipping `gh actions-lock verify` in CI. Policy enforcement works at the workflow level, not the repository level. You can onboard high-risk workflows (those with write permissions or access to secrets) first, then expand coverage. ## Comparison with Other Pinning Strategies Approach | Immutability | Review Overhead | Transitive Deps | Policy Enforcement ---|---|---|---|--- Mutable tags (`@v4`) | None | Low | Implicit | No SHA pinning in YAML | Full | High (PR per update) | Manual | No Dependabot SHA pins | Full | Medium (automated PRs) | Manual | No Workflow lockfiles | Full | Low (batch updates) | Automatic | Yes Lockfiles reduce review overhead by batching updates. Instead of 10 pull requests for 10 action updates, you get one lockfile update with a diff showing all changes. ## Integration with Dependabot Dependabot can update lockfiles automatically. When a new action version is released, Dependabot: 1. Runs `gh actions-lock update <action>` 2. Commits the updated lockfile 3. Opens a pull request with the SHA diff You review the lockfile change, not the workflow YAML. The workflow file stays readable with semantic tags, while the lockfile enforces immutability. Dependabot respects lockfile policies. If a workflow is locked, Dependabot will not propose updates that bypass the lockfile. ## Observability and Audit Trail Lockfile changes are Git commits, so you get a full audit trail. Each update shows: * Which action was updated * Old and new commit SHAs * Who approved the change * When the change was deployed This is critical for incident response. If a compromised action reaches production, you can trace which workflows were affected and when they started using the malicious commit. GitHub Actions logs include the resolved SHA for each action, so you can correlate runtime behavior with lockfile state. ## Deployment Shape Lockfiles are repository-scoped. Each repository maintains its own `.github/workflows/actions.lock`. There is no centralized lockfile for an organization or enterprise. This matches the trust boundary: each repository controls its own execution graph. Shared actions (like organization-wide deployment scripts) are pinned per-repository, so a compromise in one repository does not cascade. For monorepos with multiple workflow directories, you can maintain separate lockfiles per directory by running `gh actions-lock generate --workflow-dir <path>`. ## Security Boundaries Lockfiles protect against: * Tag hijacking (attacker moves `v1` to malicious commit) * Maintainer account compromise (attacker pushes malicious release) * Transitive supply-chain attacks (compromised composite action) Lockfiles do not protect against: * Malicious code in the initial locked commit * Vulnerabilities in the action's dependencies (npm packages, Docker images) * Secrets exfiltration if the action already has access You still need to review actions before locking them. The lockfile ensures you run the code you reviewed, not whatever the maintainer pushes later. ## Technical Verdict Use workflow lockfiles if: * Your workflows have write permissions or access to production secrets * You use third-party actions from outside your organization * You need audit trails for compliance or incident response * You want to enforce supply-chain security policies at scale Avoid lockfiles if: * Your workflows are read-only and run in isolated environments * You only use first-party actions from your own organization * You prefer manual SHA pinning and can tolerate high review overhead Lockfiles are most valuable for high-risk workflows. Start with deployment pipelines, release automation, and anything that touches production infrastructure. Expand to lower-risk workflows as you build confidence with the tooling. The `gh actions-lock verify` command belongs in CI. Fail the build if the lockfile is stale, forcing developers to update it before merging. ## Source Links * GitHub Actions Workflow Lockfiles Announcement * Lobsters Discussion

dev.to

OpenViking's Self-Evolving Context Database: How Agent Memory, RAG, and Skills Unify Under One Storage Layer

Most agent frameworks bolt memory onto RAG, then bolt skills onto both. OpenViking flips the problem: it treats agent context (memory, knowledge, and skills) as a unified database that evolves its own schema based on agent query patterns. Instead of three separate systems, you get one storage layer with self-evolution primitives. The project, backed by Volcengine (ByteDance's cloud arm), has 40K+ stars and positions itself as a "Context Database for AI Agents." The DSH-plugin architecture and self-evolving indexing suggest production-scale deployment patterns, not a wrapper around existing vector stores. ## What OpenViking Actually Does OpenViking provides a single storage abstraction for three agent context types: * **Agent Memory** : Episodic and semantic memory of past interactions, decisions, and outcomes. * **Knowledge RAG** : Document embeddings, retrieval strategies, and reranking logic. * **Skills** : Callable tools, plugins, and their execution history. The "self-evolving" mechanism monitors query patterns, retrieval latency, and agent feedback to adjust indexing strategies, schema fields, and embedding models without manual intervention. When an agent repeatedly queries a specific knowledge domain, OpenViking can promote those embeddings to a hot tier or refine chunking boundaries. ## DSH-Plugin Architecture The DSH-plugin model separates three concerns: * **Data layer (D)** : Stores raw context, embeddings, and metadata in a versioned schema. * **Schema layer (S)** : Defines how context is indexed, chunked, and retrieved. This layer can mutate based on usage patterns. * **Handler layer (H)** : Exposes retrieval, insertion, and evolution APIs to agent runtimes. Plugins extend the handler layer with domain-specific logic (e.g., code search, customer support memory, financial document RAG). Each plugin can register its own schema evolution rules. ### Why This Matters Traditional agent stacks treat memory and RAG as separate databases. You end up with: * Duplicate embeddings (one for memory, one for RAG). * Inconsistent versioning when an agent's behavior changes. * No shared indexing strategy across context types. OpenViking's unified layer means a single embedding model, one versioning strategy, and shared evolution logic. When an agent's retrieval patterns shift, the entire context database adapts. ## Self-Evolution Primitives The self-evolution mechanism tracks four signals: 1. **Query latency** : Slow retrievals trigger index restructuring or hot-tier promotion. 2. **Retrieval accuracy** : Low precision/recall scores prompt embedding model swaps or chunking adjustments. 3. **Agent feedback** : Explicit thumbs-up/down or implicit task success rates. 4. **Schema drift** : New context types (e.g., a new skill plugin) trigger schema expansion. Evolution decisions are logged as versioned migrations. You can roll back to a previous schema if agent performance degrades. ### Example Evolution Flow # Agent repeatedly queries customer support tickets from Q4 2025 # OpenViking detects high query frequency for date range [2025-10-01, 2025-12-31] evolution_event = { "trigger": "query_frequency", "context_type": "knowledge_rag", "pattern": "date_range_filter", "action": "create_temporal_index", "params": { "field": "ticket_date", "range": ["2025-10-01", "2025-12-31"], "index_type": "btree" }, "version": "schema_v47" } # OpenViking applies migration, logs rollback path # Next queries hit the new index, latency drops 60% ## Storage Layer Trade-Offs Aspect | OpenViking Approach | Traditional Vector Store ---|---|--- **Schema flexibility** | Self-evolving, versioned migrations | Static schema, manual updates **Context unification** | Memory, RAG, skills in one layer | Separate databases per type **Indexing strategy** | Adaptive based on query patterns | Pre-configured, rarely changed **Rollback support** | Built-in version history | Manual backups, risky restores **Embedding duplication** | Single model across context types | Often duplicated per use case **Observability** | Evolution events logged as first-class | Metrics bolted on after the fact The downside: complexity. Self-evolution adds a control plane that must be monitored. If evolution logic misfires, you can degrade retrieval quality across all agents. Rollback paths are critical. ## Deployment Shape OpenViking runs as a standalone service with three components: 1. **Context API** : REST/gRPC endpoints for agent reads/writes. 2. **Evolution Engine** : Background process that analyzes query logs and applies schema migrations. 3. **Plugin Registry** : Manages DSH-plugin lifecycle (install, update, rollback). Agents connect via a thin client library. The context database handles all storage, indexing, and evolution logic. ### Failure Modes * **Evolution thrashing** : Rapid schema changes if query patterns are noisy. Mitigation: rate-limit migrations, require minimum sample size. * **Rollback cascades** : Rolling back one schema version may invalidate dependent plugins. Mitigation: dependency graph for migrations. * **Hot-tier exhaustion** : Promoting too many embeddings to fast storage. Mitigation: LRU eviction with agent priority hints. * **Plugin conflicts** : Two plugins evolve the same schema field in incompatible ways. Mitigation: schema namespacing per plugin. ## Observability Hooks OpenViking exposes evolution events as structured logs: { "event_type": "schema_migration", "version": "schema_v48", "trigger": "retrieval_accuracy_drop", "context_type": "agent_memory", "action": "reembed_with_new_model", "model": "text-embedding-3-large", "affected_records": 142000, "rollback_path": "schema_v47", "timestamp": "2026-10-16T14:22:00Z" } You can pipe these to your existing observability stack (Prometheus, Grafana, Datadog). Key metrics: * Evolution frequency per context type. * Rollback rate (high rollback rate signals unstable evolution logic). * Query latency before/after migration. * Embedding model swap frequency. ## Security Boundaries Because OpenViking unifies memory, RAG, and skills, access control becomes critical. A compromised agent could leak memory from other agents or inject malicious skills. Recommended boundaries: * **Namespace isolation** : Each agent gets a dedicated context namespace. Cross-namespace reads require explicit grants. * **Plugin sandboxing** : Skills run in isolated containers with limited syscall access. * **Audit logs** : All context writes and evolution events are immutable, signed logs. * **Encryption at rest** : Embeddings and raw context are encrypted. Evolution engine operates on encrypted data where possible. ## When to Use OpenViking **Good fit:** * Multi-agent systems where memory, RAG, and skills overlap (e.g., customer support bots that learn from ticket history and call external APIs). * Long-running agents that need adaptive indexing without manual tuning. * Teams that want a single source of truth for agent context instead of stitching together vector stores, SQL databases, and plugin registries. **Avoid if:** * You have a single-purpose agent with static retrieval patterns. The evolution overhead is wasted. * Your compliance regime forbids automated schema changes. OpenViking's self-evolution is its core value. * You need sub-millisecond retrieval latency. The evolution engine adds a control plane layer that introduces some overhead. ## Technical Verdict OpenViking solves a real problem: agent context fragmentation. By treating memory, RAG, and skills as a unified database with self-evolution primitives, it reduces duplication and manual tuning. The DSH-plugin architecture provides clean separation between storage, schema, and handlers. The trade-off is operational complexity. Self-evolution requires monitoring, rollback strategies, and careful plugin management. If your agents have stable retrieval patterns, a simpler vector store may suffice. But for multi-agent systems with shifting context needs, OpenViking's unified layer is worth the overhead. The Volcengine backing suggests production-scale deployment patterns are already in play. If you're building agent infrastructure that will evolve over months or years, OpenViking's self-adapting storage layer is a strong foundation. ## Source Links * OpenViking GitHub Repository * OpenViking Documentation * OpenViking Live Demo

dev.to

AVIF took 3 to 17 times longer to encode than WebP, and what I got back depended on the image

Say your export menu has WebP and you add AVIF under it. Click WebP and the file is saved before you have moved the mouse. Click AVIF and there is a pause you can feel. The AVIF is supposed to be smaller, but by how much, and is that the same for every image? I build small internal tools for a content team and wanted numbers for that trade, so I put together a test page. Some plain words first. AVIF and WebP are both image formats built to come out smaller than JPEG, AVIF being the newer one. An encoder is the code that turns raw pixels into one of those files. I ran both encoders as WASM, which means a C library compiled so it can run inside a web page: `@jsquash/avif` 2.1.1 (libavif v1.0.1 underneath) and `@jsquash/webp` 1.5.0. Everything here is Chromium 149 on one Apple M4 Mac, with each image scaled down to about 2 megapixels. AVIF was set to quality 50 and speed 6, using the multi-threaded build. Speed is the encoder's effort dial: a bigger number means less effort and a faster result, and 6 is the library default. The awkward part is "same quality". I had no reason to think quality 50 in AVIF and some quality number in WebP mean the same thing, so I matched on SSIM instead. It is a score you get by decoding the output and comparing it with the pixels you started from, where 1 means identical. I computed it on luma only, the brightness part of the picture. Then I encoded each image as WebP at ten quality settings and worked out, by interpolating between them, how big a WebP would be at the exact SSIM the AVIF reached. Hence the ≈ on the WebP figures. Think of it as a kitchen scale: it can tell you two bags weigh the same, and it has no opinion on how they taste. Your eyes may still prefer one of two files with equal SSIM. Read the coloured column first, then the last one. The two photos are from Wikimedia Commons, a lake by Jakub Fryš and a fruit stall by Josep M. Gracia, both CC BY-SA 4.0. The two screenshots are mock pages I rendered myself, a docs page and an admin table, so no real product is shown. On the photos AVIF came out at 69% and 68% of the WebP size. The lake was 43,764 bytes against roughly 63,610, in 324 ms where WebP took about 113. On these two, three to four times the wait bought a file about 30% smaller. The text screenshot barely shrank: 172,174 bytes against roughly 180,295, which is 95%, for about three times the encode time. I had assumed screenshots would behave as one group, and the fourth row broke that. The table UI, big flat areas with thin lines, came out at 46% of the WebP size, and it took 1,513 ms where WebP needed about 85, around 17 times as long. I have not looked into why that one is so slow. At AVIF quality 60, 70 and 80 the ratios stayed close: 73%, 73%, 71% for the lake and 96%, 98%, 97% for the text screenshot. To see how a finished tool handles this, I used ImgIng (https://imging.ai/), an online image tool with AVIF in its converter, and gave it the lake photo as a PNG. What follows is what I saw on its Chinese-language site. With AVIF selected its quality slider settles on 52, next to four content presets: photo 52, smooth 48, texture 56, graphics and text 60. It tagged the lake as a photo. The 2 MP lake took 1,678 ms from click to "done" on the first run, which included a one-off start-up step, and the 12 MP version took 1,579 ms. That was Chromium 149 only, one run per size, and I did not look at how it works inside. Those are whole waits measured from outside the page, a different kind of number from the encode times in my table, so I am not setting them side by side. This is its English interface after the same 2 MP conversion. The badge says Saved 98%. That compares the AVIF with the PNG I gave it, which says nothing about AVIF against WebP. Four images at one size on one machine is a small sample, and I would not stretch "30% smaller" to photos in general. There is also a hole in my measuring. The AVIF files here are 4:2:0, meaning colour is stored at half the resolution of brightness in each direction, and a luma-only SSIM cannot see anything that happens to colour. Whether thin coloured lines or small coloured text go soft is something I did not test. What I do now, before adding AVIF to any export, is take one image typical of what that tool handles, encode it both ways, match the SSIM, and write down two numbers: the percentage and the milliseconds. Then I open both files and look.

dev.to

The AI Velocity Paradox: How Fast Code Generation Causes Silent Spec Drift

> **TL;DR:** AI coding agents write code 10x faster than humans can verify it. Passing tests and green PRs often hide silent specification drift. Here is how to build a deterministic, Git-native **Software Intent Graph** using StrictDoc and Tracey to mechanically govern coding agents. Software engineering has always been a discipline defined by an ongoing tension between two competing forces: the desire to produce functionality quickly, and the need to ensure that the functionality produced actually matches what was originally intended. For most of the history of the field, these two forces existed in a manageable equilibrium. Human developers, constrained by typing speed, cognitive load, and the necessity of context-switching, naturally produced code at a rate that approximately matched the rate at which others could review, test, and verify that code. The equilibrium was imperfect—bugs shipped, requirements drifted, and documentation went stale—but it was _stable_. Systems could tolerate the drift because the drift accumulated slowly. Autonomous AI coding agents fundamentally break this equilibrium. When an agent can generate a thousand lines of plausible, syntactically valid, often surprisingly correct code in the time it takes a human reviewer to read a single paragraph of a specification, the _ratio_ between generation velocity and verification velocity becomes catastrophically skewed. This is the AI velocity paradox: the very tool that promises to accelerate delivery also accelerates the decoupling of what the system _is_ from what the system was _supposed to be_. And crucially, that decoupling is almost always invisible at the moment it occurs. The code compiles. The tests pass. The pull request is green. Nothing screams. And yet, silently, the intent that justified the code has been left behind. The concepts and code demonstrated here are drawn directly from the comprehensive roadmap laid out in the ebook **Governing AI Coding Agents with StrictDoc & Tracey Stack: Controlling Spec Drift with Living Intent Graphs** here. Check also the 9 volumes TypeScript AI & Agentic Engineer Masterclass that includes also a volume on Jev. ## The Physics of Intent: Why Velocity Without Governance Breaks Systems Let us begin with a fundamental theoretical claim: **specification drift is not a bug, it is a thermodynamic default**. In the absence of an active, enforced mechanism that ties intent to implementation, the two will diverge. Always. Given enough time, enough contributors, and enough change pressure, every system drifts away from the intentions that originally justified it. This is not a moral failing of developers; it is a structural property of any knowledge system in which different representations of the same idea are maintained in different places, by different tools, for different audiences. We can think of this as the _second law of software thermodynamics_ : the entropy of alignment between intent and implementation tends to increase unless external work is applied to reverse it. That external work—reviews, tests, traceability exercises, verification gates—is finite. And that finitude is precisely where the breakdown occurs. The classic analogy here is **bit rot** in physical storage media. Magnetic disks gradually lose their alignment of particles; CDs develop microscopic scratches; magnetic tape degrades. The data does not disappear all at once. It fades—a flipped bit here, a misread sector there. Systems that depend on that data either notice through active checksum verification or they silently corrupt. Silent spec drift is the _semantic equivalent of bit rot_ , but operating in the higher-level representation we call "system intent." Where bit rot corrupts _bytes_ , spec drift corrupts _meaning_. And it does so without any checksum mechanism to detect it unless we build one. ## The Verification Bandwidth Constraint Every engineering organization has a finite _verification bandwidth_ : the rate at which it can genuinely verify that code does what it is supposed to do. In classical software development, this bandwidth was largely consumed by human code review. A reviewer reads a diff, holds the relevant requirements in working memory, evaluates the change against those requirements, and either approves or rejects. This works—barely—because the diff is small enough that the reviewer's working memory can hold both the change and the intent simultaneously. AI agents destroy this process in two distinct ways. First, they inflate the _volume_ of changes. A single prompt can produce a pull request touching twenty files. The reviewer's working memory cannot hold twenty files' worth of context, so they begin to _skim_. Skimming is the death of verification. A skimmed review is a review in name only—a ritual that provides the _feeling_ of verification without the substance. Second, and more insidiously, they inflate the _plausibility density_ of changes. AI-generated code often looks correct in isolation. It uses idiomatic patterns, reasonable variable names, and appropriate library calls. The reviewer's "spidey sense" for wrong code is calibrated for human-written mistakes. It fires less often when reading AI output because AI output is superficially more consistent with the training distribution of "correct-looking code." This means that plausibility, which was historically a weak signal of correctness, has become an outright _misleading_ signal in the AI era. ## Intent Graphs vs. Documents: A Structural Distinction To make this intent graph operational without relying on heavy enterprise tools or closed commercial platforms, we combine two lightweight, open-source engines: * **StrictDoc** (Python): A Git-native _Requirements-as-Code_ platform. It replaces passive markdown files and Jira tickets with structured specifications (`.sdoc`), enforcing validation rules, formal EARS syntax, and persistent Machine Identifiers (MIDs). It defines **what the system must do**. * **Tracey** (Rust): A high-performance specification-coverage and verification engine. It uses Tree-sitter to parse code comments across 30+ languages, mechanically verifying that requirements are implemented (`// r[impl]`) and proven by tests (`// r[verify]`). It tracks **what the code actually does** , flags stale references when specs change, and exposes the intent graph to AI agents via the Model Context Protocol (MCP). Together, they form a symbiotic, zero-lock-in loop: **StrictDoc models the intent; Tracey verifies the reality.** Traditional specifications live in documents. A document is a _linear, prose-shaped, human-optimized artifact_. It has a beginning, a middle, and an end. It is read sequentially. It is authored in natural language, which is expressive but ambiguous. And, critically, it has no internal _state_. A document does not know whether it is being followed or violated. It cannot answer questions. It cannot report on its own consistency. It is passive data. The theoretical move that StrictDoc and Tracey together enable is to shift from documents to **graphs**. A graph consists of _nodes_ (representing discrete, identifiable requirements) and _edges_ (representing relationships—parent-child, sibling, refinement, verification, implementation). The graph has _structure_ that can be queried. It has _topology_ that can be analyzed. It can be traversed, checked for cycles, examined for orphans, and—most importantly— _linked to external entities_ such as source code files, test functions, and verification annotations. Once intent exists as a graph rather than a document, **bi-directional referential integrity** becomes possible. Every code artifact can carry a reference to the requirement it implements. Every test artifact can carry a reference to the requirement it verifies. Every requirement can, in turn, be queried for its upstream justifications and its downstream implementations. The graph becomes the _connective tissue_ that binds intent, code, and tests into a coherent whole. ## The Living Property: Why "Graph" Alone Is Not Enough A graph is a necessary condition for governing spec drift, but it is not sufficient. A graph that is built once and never updated is exactly as stale as a document that is written once and never updated. The property that distinguishes a _governance system_ from a _diagramming tool_ is that the graph must be **living** : it must be continuously rebuilt from authoritative sources, continuously validated against those sources, and continuously _queried_ by the actors—human and machine—who are making changes. There are three properties that make an intent graph "living": 1. **Continuity.** The graph is never allowed to fall out of sync with its source documents. Whenever a spec file changes on disk, the graph is rebuilt incrementally. 2. **Queryability.** The graph is exposed as a set of _queries_ that can be asked at any moment. "Which requirements have no implementation?" "Which implementations reference a stale version?" 3. **Enforcement.** The graph is not merely informative; it is _prescriptive_. Broken references, stale references, and orphaned requirements are defined as pipeline failures. ## Practical Implementation: Building an Intent-Anchored TypeScript Service Picture a mid-sized SaaS team shipping a multi-tenant billing service. Their coding agents can produce a working `stripe-webhook` handler, a JWT refresh endpoint, and its test suite in under ninety seconds. That velocity is the entire reason the team adopted agents. It is also precisely the reason they started shipping silent regressions. Here is how we wire StrictDoc and Tracey into a real TypeScript project to enforce a living intent graph. ### Step 1: Define the Requirement in StrictDoc We start with the spec. StrictDoc's `.sdoc` grammar parses requirements into first-class objects with stable UIDs. [DOCUMENT] TITLE: Northwind Authentication Intent OPTIONS: AUTO_UIDS: True MARKUP: Markdown [[SECTION]] TITLE: Session Lifecycle [REQUIREMENT] UID: auth.login.token-ttl STATUS: Approved TITLE: Login token time-to-live STATEMENT: >>> The login endpoint shall issue a JSON Web Token whose `exp` claim is set to exactly 86,400 seconds (24 hours) after the token's `iat` claim. Any token whose `exp - iat` differs from 86,400 seconds shall be rejected during verification, regardless of signature validity. <<< RATIONALE: >>> A fixed, auditable TTL is required by SOC 2 control CC6.1 ("logical access security"). Agents generating code must not drift toward shorter "safer-looking" TTLs without an explicit, versioned spec change. <<< RELATIONS: - TYPE: Parent VALUE: auth.session.root [/SECTION] ### Step 2: Wire Tracey via the Styx Configuration Tracey uses a declarative configuration file (`.config/tracey/config.styx`) to link documentation files to source code and test directories. @schema {id crate:tracey-config@1, cli tracey} specs ( { name northwind-auth source_url https://github.com/northwind/api/blob/main/docs/spec/auth.sdoc include (docs/spec/**/*.sdoc) impls ( { name typescript-backend include (src/**/*.ts) exclude (src/**/*.generated.ts dist/** node_modules/**) test_include (tests/**/*.test.ts) } ) } ) ### Step 3: Implement with Explicit Tracing Annotations In our TypeScript backend source code, we use JSDoc-style `// r[impl ...]` annotations to bind code blocks directly to the upstream StrictDoc requirement UID. // src/auth/login.ts import jwt, { JwtPayload, SignOptions } from "jsonwebtoken"; import { z } from "zod"; export const TOKEN_TTL_SECONDS = 86_400; export const LoginRequestSchema = z.object({ email: z.string().email(), password: z.string().min(1), }); export type LoginRequest = z.infer<typeof LoginRequestSchema>; export interface LoginResult { token: string; expiresAt: number; } /** * Issues a signed JWT for a user, with an `exp` exactly * `TOKEN_TTL_SECONDS` after the current time. * * // r[impl auth.login.token-ttl] */ export async function login(request: LoginRequest): Promise<LoginResult> { const parsed = LoginRequestSchema.parse(request); const user = await verifyCredentials(parsed.email, parsed.password); if (!user) { throw new Error("INVALID_CREDENTIALS"); } const issuedAt = Math.floor(Date.now() / 1000); const signOptions: SignOptions = { expiresIn: TOKEN_TTL_SECONDS, algorithm: "RS256", }; const token = jwt.sign( { sub: user.id, email: user.email }, getPrivateKey(), signOptions, ); const decoded = jwt.decode(token) as JwtPayload; if (!decoded.exp || !decoded.iat) { throw new Error("TOKEN_MISSING_TIMECLAIMS"); } if (decoded.exp - decoded.iat !== TOKEN_TTL_SECONDS) { throw new Error("TOKEN_TTL_DRIFT"); } return { token, expiresAt: decoded.exp }; } async function verifyCredentials( email: string, password: string, ): Promise<{ id: string; email: string } | null> { return { id: "u_123", email }; } function getPrivateKey(): string { return process.env.JWT_PRIVATE_KEY ?? ""; } ### Step 4: Verify with Test Annotations Tests are similarly bound to the exact same UID using `// r[verify ...]`. This establishes a complete validation loop. // tests/auth/login.test.ts import { describe, it, expect, vi, beforeEach } from "vitest"; import { login, TOKEN_TTL_SECONDS, LoginRequestSchema, } from "../../src/auth/login"; import jwt from "jsonwebtoken"; describe("POST /auth/login — token lifetime", () => { beforeEach(() => { vi.stubEnv("JWT_PRIVATE_KEY", process.env.__TEST_KEY ?? "test-key"); }); /** * The canonical happy-path test for the TTL requirement. * * // r[verify auth.login.token-ttl] */ it("issues a token whose exp - iat is exactly 86,400 seconds", async () => { const { token } = await login({ email: "alice@northwind.example", password: "correct-horse-battery-staple", }); const decoded = jwt.decode(token) as { exp: number; iat: number }; expect(decoded.exp - decoded.iat).toBe(TOKEN_TTL_SECONDS); }); }); ### Step 5: Enforce in CI Pipelines To prevent unverified changes from reaching production, we enforce the intent graph inside our CI pipeline. If an AI agent attempts to ship code without valid, up-to-date mappings, the build fails instantly. name: Specification Assurance Gate on: [push, pull_request] jobs: verify-intent: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Tracey run: | curl --proto '=https' --tlsv1.2 -LsSf \ https://github.com/bearcove/tracey/releases/latest/download/tracey-installer.sh | sh echo "{% katex inline %}HOME/.cargo/bin" >> {% endkatex %}GITHUB_PATH - name: Install StrictDoc run: pip install strictdoc - name: Validate StrictDoc Document Tree run: strictdoc export --formats=html . - name: Enforce Zero-Warning Spec Coverage & Traceability run: tracey query validate --deny warnings - name: Refuse untested requirements run: | if tracey query untested | grep -qv '^$'; then echo "::error::Unverified requirements detected." tracey query untested exit 1 fi ## Common Pitfalls When Adopting Intent Graphs When teams first implement automated traceability tooling alongside autonomous coding agents, they frequently encounter several predictable hurdles: 1. **Hallucinated Requirement IDs:** Unprompted agents may invent plausible UIDs like `auth.login.token-expiry` instead of `auth.login.token-ttl`. StrictDoc and Tracey catch these as broken references during validation, but only if the CI pipeline actively blocks on warnings. 2. **`impl` Annotations in Test Files:** Placing implementation annotations inside test files violates structural separation. Tests must use `verify` channels exclusively. 3. **Bumping Versions Without Fixing Code:** When a requirement changes, agents will sometimes simply bump the version suffix on the annotation to clear CI warnings without updating the actual business logic. The rule must be strictly enforced: update the code first, verify the tests, and _only then_ bump the annotation version. ## Conclusion Autonomous AI coding agents represent a quantum leap in engineering productivity, but they also supercharge the silent drift between what software is designed to do and what it actually executes. Traditional human reviews, unmoored documentation, and isolated tests are no longer sufficient to maintain system integrity at machine speed. By replacing passive documentation with a **living intent graph** —anchored via StrictDoc, mapped through Tracey, exposed via Model Context Protocol (MCP) to AI agents, and enforced strictly in CI—we reclaim control over our codebases. Spec drift ceases to be an inevitable thermodynamic decay and becomes a visible, measurable, and blockable condition. Speed without a graph is unmitigated chaos; speed anchored to a living intent graph is the future of resilient software engineering.

dev.to

FetchUpdate – AI Pet Sitter Assistant

# FetchUpdate – The AI Assistant for Local Pet Sitters 🐾 _This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle_ ## What I Built **FetchUpdate** is an AI tool designed for independent dog walkers and local pet sitters. Pet sitters often spend 1–2 hours every evening manually texting individual updates to anxious owners. FetchUpdate automates this chore: the sitter takes a daily photo of the pet, selects their profile, and the tool uses a vision model paired with stored pet care details to instantly draft a warm, highly personalized SMS update ready to copy and send. ## Demo 👉 **Live App:** FetchUpdate on Streamlit Cloud 📁 **GitHub Repository:** github.com/2AJ0/fetchupdate ## How I Built It * **Database (MongoDB Atlas):** Stores structured pet profiles, including names, breeds, special instructions (e.g., _"Loves walks on paved trails"_), owner details, and tone preferences. * **Multimodal AI Pipeline (OpenRouter / Gemini / Open Models):** Accepts the uploaded image along with the retrieved MongoDB profile and prompts an open-weight vision model to describe the activity and generate a tailored SMS update. * **Frontend & Deployment (Streamlit):** Built using Python and Streamlit, deployed seamlessly on Streamlit Community Cloud. ## Why Does Open Innovation Matter? For local side hustles and micro-businesses, open innovation makes software viable in two key ways: 1. **Privacy First:** Sitter notes contain sensitive client information (home locations, daily schedules, dog routines). Running open-weight vision models ensures client data stays secure without being harvested by closed corporate APIs. 2. **Near-Zero Cost Structure:** Closed API pricing quickly kills margins for small solo sitters charging $15–$30 per walk. Leveraging open models keeps operational overhead low enough to run a profitable local SaaS product. ## My First User I tested **FetchUpdate** with a local test profile for **Merku** , a Golden Retriever who loves long walks on paved trails. Uploading a photo of Merku generated the following update in seconds: > _"Hey Athul! Merku is sitting patiently, looking like the goodest boy. He's probably dreaming of soft treats and a nice long walk on the paved trails! 🐶"_ The prompt correctly synthesized both the visual context from the photo and the background care instructions from MongoDB. ## Prize Categories * **Best Use of MongoDB Atlas:** Used MongoDB Atlas to store long-term pet care instructions, owner metadata, and personality traits. * **Best Use of Open Models / Gemma:** Used open-weight vision models to generate contextual text updates from images.

dev.to

A CRM where the AI never leaves your laptop

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle ## What I Built **Private AI Lead Assistant** — a small CRM where you paste a client conversation and a local model turns it into a structured lead. A freelancer's WhatsApp or email thread with a prospect is already a CRM record in prose: who they are, what they want built, the budget, the deadline. Extracting it is tedious, and every cloud "AI CRM" solves it by uploading the transcript. This solves it on your own machine: 1. Paste the conversation → **Analyze** with a local model 2. Model returns a validated JSON lead: client, company, project, budget, priority, timeline, status, tags 3. **Review** step — nothing is saved until you approve it (a 1.5B model is confidently wrong sometimes, so a human is the last gate) 4. Open the lead → draft a **follow-up message** or a full **project proposal** , then copy it out **Who would pay for it:** freelancers and 2–10 person agencies. Client conversations contain budgets, deadlines and NDA'd product plans; per-seat AI CRM pricing is hard to justify when you invoice by the project. Running extraction locally makes the marginal cost $0 — which is the difference between a side hustle's tooling and a SaaS bill. Stack: React 19 + Tailwind, Express, Postgres, TypeScript, and **Qwen 2.5 (1.5B, open weights) served by Ollama** — no external AI API anywhere in the codebase. ## Demo The app runs on localhost by design — the point is that the conversation never leaves the laptop, so there is no hosted demo to link. <!-- TODO: drop a Loom/YouTube walkthrough URL in here before publishing --> **Run it:** `npm install && npm run db:migrate && npm run dev` (with Ollama up and `ollama pull qwen2.5:1.5b`) → client on `:5173`, API on `:3001`. What a full session looks like (real run against `qwen2.5:1.5b`): * Register → land on the dashboard * Paste a chat where a restaurant owner in Kathmandu asks for an online ordering system * `POST /api/ai/analyze` → ~36s cold → extraction: **Sita Gurung · ABC Restaurant · "online ordering system" · 40,000–60,000 NPR · high priority** * Review, approve → lead appears in the list, searchable by `q=ABC` * Open lead → **Generate follow-up** (~26s) → **Generate proposal** (~26s) * `GET /api/health` → `{"status":"ok","db":"up","ai":"up","model":"qwen2.5:1.5b","provider":"ollama"}` ## Code Three pieces that make a small local model trustworthy: **1. Constrained decoding + determinism.** Extraction is a structured prediction, so the request carries a JSON Schema (`format`) and fixed sampling — same conversation, same lead: /** Deterministic extraction: temperature 0 + fixed seed + explicit context budget. */ export const EXTRACTION_OPTIONS = { temperature: 0, seed: 42, numCtx: 8192, numPredict: 512, } as const; export async function analyzeConversation(ai: AiProvider, conversation: string) { const raw = await callAi(ai, buildLeadExtractionPrompt(conversation), { system: LEAD_EXTRACTION_SYSTEM, format: leadExtractionJsonSchema, // Ollama constrained decoding ...EXTRACTION_OPTIONS, }); return parseLeadExtraction(raw); } **2. Never trust the model's JSON.** LLM output crosses a Zod boundary, or the request fails with a 502 the UI can show: /** LLM -> JSON -> Zod. Rejects with a useful 502 instead of trusting the model. */ const result = leadExtractionSchema.safeParse(parsed); if (!result.success) { const details = result.error.issues .map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`) .join('; '); throw new HttpError(502, `AI returned data that failed validation: ${details}`); } **3. Prompt injection is a real risk with paste-anything input.** I measured the 1.5B model failing to resist injections in the eval harness, so protection is layered: the system prompt treats conversation text as untrusted data, a detector flags instruction-shaped text _before_ it reaches the model, and the review UI warns you: const INJECTION_PATTERNS: RegExp[] = [ /ignore\s+(?:all\s+|any\s+)?(?:previous|prior|earlier|above|preceding)\s+instructions?/i, /\[system\]/i, /<\s*system\s*>/i, /pretend\s+(?:to\s+be|you\s+are)\s+(?:an?\s+)?(?:system|developer|assistant)/i, ]; export function detectPromptInjection(text: string): string | null { for (const pattern of INJECTION_PATTERNS) if (pattern.test(text)) return PROMPT_INJECTION_WARNING; return null; } ## How I Built It * **Open-weight model, local inference:** Qwen 2.5 1.5B through **Ollama** — pulled once, works offline, zero API keys. The 1.5B size is deliberate: it runs on a laptop CPU and keeps the whole thing free, which is what makes it a _side hustle_ tool rather than another subscription. * **Provider abstraction:** `AiProvider` (`health()` / `generate(prompt, options)`) with `format: 'text' | 'json' | object` for constrained decoding. Swapping to llama.cpp or a bigger Qwen is one class. * **Different sampling per task:** extraction and proposals run at `temperature: 0, seed: 42`; follow-ups run at `0.7` so regenerating gives you a different email instead of the same one. * **A 29-conversation eval harness** (`npm run ai:eval -w server`) that prints Expected / Actual / Pass-Fail per case — this is how I found the model's habits, e.g. defaulting `priority: "medium"` when the chat never mentions urgency, which is exactly why the review step exists. * **Around it:** React 19 + Tailwind (dark mode, toasts, skeletons, keyboard shortcuts), Express with Zod validation, bcrypt auth, rate limits, allow-list CORS, Drizzle on Postgres with `ilike` search, 109 tests, and a Docker Compose dev stack (React + Express + Postgres, Ollama as an optional profile). Getting the containers talking took the usual compose lesson — `localhost` inside a container is the container, so the Vite dev proxy switches to the compose service name: proxy: { '/api': { target: process.env.VITE_API_PROXY ?? 'http://localhost:3001', changeOrigin: true } } (I got this from The 'Localhost' Trap: Networking Microservices in Docker Compose by Omar Lashin, and the whole local-first framing echoes How I Built a Zero-API-Cost AI Agent That Runs 100% Locally — No Keys, No Cloud, No Bills by scriptgather.) ## Why Does Open Innovation Matter? For this project it isn't a preference — it's the product: * **The data can't leave.** Client budgets and NDA'd plans are the input. A closed API means every paste is a disclosure; local inference means the transcript stays on disk. * **$0 marginal cost.** The business case for a freelancer is "extract leads for free forever," not "another $20/token-metered seat." * **No lock-in or pricing risk.** The model is a file you `ollama pull`. If Qwen's license, pricing, or availability changed tomorrow, the pipeline wouldn't notice — and the `AiProvider` interface means the vendor layer was never load-bearing. * **Open weights make the limitations debuggable.** I could evaluate the 1.5B model against 29 realistic conversations and _see_ where it's weak (prompt injection, unstated priority), then engineer around it with a review gate and a detector. With a closed API you only get vibes — and when their model version changes under you, your prompts silently break. * **Room to fine-tune for the niche.** The extraction schema is domain-specific (budget ranges in local currency, project tags). With open weights, a few hundred in-domain examples can specialize it — something a general API handles poorly and you can't tune at all. ## My Agent Session The full build session — verifying Phases 17–24 of the PRD, writing the Docker stack, the dark-mode sweep, the live end-to-end run against the local model, and drafting this post: Finishing the Private AI Lead Assistant (PRD Phases 17–24) + Hacktoberfest submission post You Continue the PRD for private-ai-lead-assistant — Phases 17–24: search/filter, error handling, testing, security, Docker dev environment, UI polish, documentation, and the final MVP demo. Agent Verifying Phases 17–20 first — the search/filter code, error middleware, test suites, and security measures looked already implemented. Confirming before writing anything new. ▸ grep_search listLeads q status priority followUp Input listLeads q status priority followUp Output lead.service.ts:40 — plain ilike search over client/company/project; LeadsList.tsx has selects + chips ▸ bash npm test (server + client) Input $ npm test (server + client) Output 92 server tests + 17 client tests pass Agent Phases 17–20 confirmed complete. Building Phase 21: Docker dev environment — root multi-stage Dockerfile, compose stack (db/server/client + optional ollama profile), and a VITE_API_PROXY so the Vite dev proxy targets the compose service name instead of localhost. ▸ write Dockerfile, .dockerignore, docker-compose.yml Input Dockerfile, .dockerignore, docker-compose.yml Output node:22-alpine multi-stage (base/server/client), npm ci, migrate-then-dev on server, profiles: ["ollama"] with host.docker.internal:host-gateway ▸ edit client/vite.config.ts — proxy target Input client/vite.config.ts — proxy target Output process.env.VITE_API_PROXY ?? 'http://localhost:3001', host: true Agent Phase 22 (UI polish): dark mode with a pre-paint inline script in index.html to avoid a flash of wrong theme, Toast/Skeleton/ConfirmDialog/Shortcuts components, centralized light/dark class sets in lib/ui.ts, then a full sweep adding dark: variants across all 15 UI files (grep-verified zero missing pairings). ▸ grep_search dark: pairing sweep across client/src Input dark: pairing sweep across client/src Output 0 missing dark: variants Agent Phase 23 (docs): README rewritten with all 14 required sections + a Browser → Express → AI Service → Ollama → Qwen diagram and an API appendix; AGENTS.md phase list corrected. Phase 24 (demo): running the full flow live against the real local model. ▸ bash register → /api/ai/analyze → create lead → search → follow-up → proposal (cur... Input $ register → /api/ai/analyze → create lead → search → follow-up → proposal (curl, cookie jar) Output qwen2.5:1.5b extracted Sita Gurung / ABC Restaurant / online ordering system / 40,000–60,000 NPR; analyze 36s cold, follow-up 26s, proposal 26s; health: provider=ollama Agent Final verification: typecheck clean, 17 client + 92 server tests pass, production build succeeds (355 kB js / 29 kB css). Docker stack authored and YAML-validated but not executed — no Docker daemon in this WSL distro. You What did we do so far? Agent Delivered a per-phase status report (17–24, with which were pre-existing vs newly built), verification results, caveats, and closed with the Community Wisdom citations that shaped the Docker networking and dark-mode work. You Write the Hacktoberfest Open-Source AI Challenge Week 2 (Side Hustle) submission post using the challenge template. Agent Grounded the draft with a semantic search on DEV for local/zero-API-cost AI builds, checked the challenge's full_details (deadline Oct 18 11:59 PM PDT, required tag #hf26challenge), verified tag existence, and staged an unpublished draft: "A CRM where the AI never leaves your laptop" with the template sections filled, two honest TODO markers (video link, repo embed), and the My First User / Prize Categories sections removed per template instructions. ▸ create_article title: A CRM where the AI never leaves your laptop; tags: hf26challenge, ai, ... Input title: A CRM where the AI never leaves your laptop; tags: hf26challenge, ai, opensource, privacy; published: false Output draft id 4860930, edit_url returned You yes Agent Saving this session transcript, embedding it in the draft's My Agent Session section, then publishing. 12 of 10 messages

dev.to

How to track your brand in ChatGPT

Someone types "what is the best project management tool for a small agency" into ChatGPT. It names three products. Yours is either one of them or it is not. You cannot see that in Google Analytics. You cannot see it in Search Console. If you want to track brand mentions in ChatGPT, you have to ask ChatGPT, and you have to do it in a way that produces numbers you can trust. This guide walks through how. Start by hand. Then decide when hand-work stops being worth it. ## Why ChatGPT answers are hard to track A search ranking is stable enough to check once a day. A ChatGPT answer is not. Three things get in the way. **Answers vary.** Ask the same question twice and you may get a different list of brands, in a different order. One run tells you almost nothing. You need repeated runs to see how often you appear. **Prompts vary.** Your buyers do not all phrase the question the same way. "Best CRM for startups" and "which CRM should a five-person startup use" can return different brands. **Context varies.** Whether ChatGPT searches the web for an answer changes what it can cite. Answers built from search can link to sources. Answers built from the model alone may only name brands. So the job is not "check if we show up." The job is "measure how often we show up across a fair set of prompts, over time." ## Step 1: Build a prompt set Write down the questions your buyers actually ask. Aim for 20 to 50 to start. Cover four types: * **Category prompts.** "Best [category] tools." These decide who gets shortlisted. * **Use-case prompts.** "[Category] for [audience or job]." These are narrower and often easier to win. * **Comparison prompts.** "[Your brand] vs [competitor]." These show how you are framed. * **Problem prompts.** "How do I [solve the problem you solve]?" These catch buyers before they know your category name. Pull the wording from sales calls, support tickets, search queries and community threads. Real phrasing beats invented phrasing. If you are short on ideas, the prompt fan-out generator takes one seed prompt and expands it into the related sub-questions AI platforms tend to run behind the scenes. It is a fast way to widen a prompt set beyond the obvious ten. ## Step 2: Run each prompt more than once Open a fresh chat for every run. Old conversation context bleeds into later answers, and you want each test clean. Run each prompt at least five times. Ten is better for the prompts that matter most. Log every run. You are not looking for one answer. You are looking for a rate. Two practical notes: * Test with and without web search when the option is available to you. They are different behaviours and may produce different brands. * Log the date. Answers change as models and sources change, so a result from March says little about September. ## Step 3: Record the right fields For every run, capture: Field | What it tells you ---|--- Prompt | Which question was asked Brand mentioned (yes/no) | Whether you are in the answer at all Position | First, second, third, or later in the list Sentiment | How the answer describes you Competitors named | Who you are sharing the answer with Sources cited | Which pages the answer linked to A spreadsheet is enough at this stage. One row per run. The sources column is the one people skip and the one that pays off. If ChatGPT keeps citing the same review site or the same competitor comparison page, that page is shaping your category. It is where your work should go. ## Step 4: Turn rows into metrics Raw rows are noise. Turn them into three numbers. **Visibility rate.** Runs where your brand appeared, divided by total runs. If you appeared in 12 of 40 runs, your visibility rate is 30%. **Share of voice.** Your mentions divided by all brand mentions across the same runs. This tells you how you compare with competitors, not just how you do alone. **Average position.** Where you land when you do appear. Being named first is worth more than being named fifth. Track each by prompt group, not only overall. A strong overall number can hide a category prompt where you never appear. ## Step 5: Read the sources, not just the mentions A mention is the outcome. A citation is the cause. When ChatGPT answers with web search, it often links the pages it used. Collect those links. Sort them into types: your own site, competitor sites, review platforms, forums, news, listicles. Then ask two questions. 1. **Which sources show up for prompts where we win?** Protect and extend those. 2. **Which sources show up where we lose?** These are your targets. Get listed, get reviewed, or publish something better. This is where tracking turns into a plan. You are no longer guessing why you are missing. You are looking at the pages the answer was built from. ## Step 6: Set a cadence Run the full prompt set on a fixed schedule. Weekly works for most teams. Monthly is the floor. Anything less and you will miss a change until it has already cost you. Keep the prompt set stable between runs. If you change the prompts every time, you cannot tell whether the answers moved or you did. Add new prompts, but keep a core set fixed for trend lines. ## Where manual tracking breaks down Manual tracking works. It also gets expensive fast. Do the arithmetic. 40 prompts, 5 runs each, is 200 chats per pass. Weekly, that is 800 a month, for one platform. Add a second platform and you have doubled it. Add logging, sorting sources and building charts, and it becomes somebody's job. It also stays stuck in ChatGPT. Your buyers use several assistants. Gemini, Perplexity, Claude, Google AI Overviews, AI Mode, Grok and Copilot each pull from their own mix of sources, and a brand that wins on one can be invisible on another. Tracking one engine gives you a partial picture. A rule of thumb: do it by hand until you have a prompt set you believe in and you know which metrics you care about. Then automate it. ## What to do with the results Tracking is only useful if it changes what you do. A few common moves: * **Fix missing basics.** If you never appear for a use case, check that you have a page that answers it plainly. * **Get into the cited sources.** If a comparison article dominates citations, work out how to be included in it. * **Correct wrong descriptions.** If ChatGPT gets your pricing or features wrong, find the page it learned that from and fix or outrank it. * **Watch competitors.** Sudden jumps in a rival's visibility usually trace back to a specific piece of content or coverage. Retest after each change. That is the loop: measure, change one thing, measure again. ## Track it automatically If you would rather not run 800 chats a month yourself, Pineprompt does it for you. It runs your prompts on a schedule across ChatGPT and seven other AI engines, then reports visibility, share of voice, position, sentiment and the sources behind each answer. There is a page on ChatGPT visibility tracking that shows what that looks like. Plans start at $99 a month with every engine included, and the details are on the pricing page. Either way, start this week. Write 20 prompts, run each five times, and see where you stand. You will learn more from that one spreadsheet than from any amount of guessing. Originally published on pineprompt.com. Read the original.

dev.to

What is prompt fan-out?

Prompt fan-out is what happens when an AI search engine takes one question and turns it into several smaller searches. You ask one thing. The system searches for many related things, then combines what it finds into one answer. For anyone trying to be visible in AI answers, this matters. Your page may never match the question the buyer typed. It only has to match one of the sub-queries. The glossary entry on prompt fan-out has the short definition. This guide goes further. ## How fan-out works Take a buyer who types: "best accounting software for a small design studio." A person would search that phrase and scan the results. An AI system often does something different. It may break the question into parts such as: * accounting software for freelancers * accounting software with invoicing * pricing of popular accounting tools * reviews of accounting software from small businesses * accounting software integrations with payment tools It runs those searches, reads what comes back, and writes one answer. The sources cited in that answer may come from any of the sub-queries, not from the original question. The exact sub-queries are chosen by the system, and they can change from run to run. You usually cannot see all of them. ## Who does it Google has described this technique for AI Mode. Its documentation says AI Mode uses a "query fan-out" approach, issuing multiple related searches across subtopics and data sources. Google says AI Overviews can use the same technique. Other assistants also search the web when a question needs fresh information. Microsoft's documentation for Copilot, for example, says it identifies terms where a search would improve the response and generates a search query. Whether a given assistant issues one query or many differs by product and by question, and it changes over time. Treat fan-out as a common pattern, not a fixed rule for every engine. ## Why it matters for visibility ### You compete on more than one query A single question can lead to five or ten searches. Each is a chance to be found or missed. A page that ranks for a narrow sub-query can be pulled into an answer for a broad question. ### Broad pages can lose to specific ones A general page about "accounting software" may not match "invoicing features for freelancers" as well as a page written on exactly that. Specific, well-structured pages give the system something precise to pick up. ### Your brand can be missing from part of the picture If your product is strong on invoicing but no page or third-party source says so, the invoicing sub-query will not find you. You may be absent from the answer without knowing why. ### Cited sources become clearer The sources in an answer often reflect the sub-queries. Reading them tells you which angles the system explored, and which sites it trusted for each. ## How to use fan-out to pick prompts to track You cannot track every possible question. Fan-out gives you a way to choose a set that covers real ground. ### Step 1: Start with the buyer questions List the questions buyers ask before they pick a product like yours. Include category questions, comparisons and problem questions. Aim for 10 to 15 to begin with. ### Step 2: Expand each one into sub-queries For each question, ask what a system would need to know to answer it. Look for: * **Attributes:** price, features, size of company it suits, integrations. * **Comparisons:** you against named alternatives. * **Proof:** reviews, case studies, independent write-ups. * **Use cases:** the jobs people hire the product for. * **Constraints:** country, language, budget, compliance. You can do this by hand. Or use the free Prompt fan-out generator: enter a question and it suggests related sub-queries you can test. ### Step 3: Turn the good ones into prompts Not every sub-query makes a good tracking prompt. Keep the ones a real buyer might ask an assistant in natural language. Drop duplicates and anything too vague to give a stable answer. ### Step 4: Group them Keep prompts in groups, for example category, comparison, use case and pricing. When results come in, you can see which group is weak instead of staring at one blended number. ### Step 5: Fix the list and track it Keep the set stable so you can compare over time. Add new prompts as a separate group. Each prompt should be run repeatedly, since answers vary. ## What to measure once you are tracking For each prompt, record: * **Mention:** is your brand named? * **Position:** where in the answer does it appear? * **Sentiment:** how are you described? * **Cited sources:** which of your pages and which other domains are linked? * **Competitors:** who else is named, and how often? Share of voice ties these together: your mentions as a share of all brand mentions across the set. Read the results by group. If you are named on category prompts but not on pricing prompts, the gap is in pricing content or in what third parties say about your pricing. That is a concrete task, which a blended score would hide. ## A worked example Suppose you sell scheduling software for clinics. Your seed question is "best appointment scheduling software for a small clinic." Expanded, it might give these prompts: Group | Prompt ---|--- Category | best appointment scheduling software for small clinics Attribute | scheduling software with automatic patient reminders Comparison | your product vs a named competitor for clinics Constraint | scheduling software that meets health data rules in Germany Use case | how to reduce missed appointments at a clinic Five prompts from one question. Each can trigger its own sub-queries. Track all five and you will see whether you show up across the range, or only on the obvious one. ## What to do with the findings * **Missing on an attribute:** publish a clear page that covers it, in plain language. * **Missing on comparisons:** write an honest comparison page. Check what third-party comparisons say about you. * **Cited sources are all third parties:** work on being present and accurately described there. * **Wrong or dated descriptions:** fix your own pages first, then the sources the answers cite. None of this is a trick. It is ordinary content work, aimed by better information. ## Limits to keep in mind **You cannot see every sub-query.** Generators and manual expansion give you likely ones, not the exact list a system ran. **It changes.** Systems are updated. A pattern that held last quarter may not hold now. **It is not a guarantee.** Covering a sub-query well raises your chance of being used. It does not force a mention. ## Tracking across engines Fan-out affects AI Mode, AI Overviews and other assistants that search the web, so track across them. See our pages on Google AI Mode tracking and Google AI Overview tracking for the Google side. Pineprompt tracks prompts daily across ChatGPT, Gemini, Perplexity, Claude, Google AI Overviews, Google AI Mode, Grok and Copilot, with country and language targeting. It reports mentions, position, sentiment, cited sources, competitors and share of voice. The methodology page explains how runs are recorded. Plans start at $99 a month with no free trial; see pricing. ## The short version One question becomes many searches. Your visibility depends on the sub-queries, not only the headline question. Use fan-out to widen your prompt list, group the prompts, keep the list fixed, and read results by group. Start with one buyer question and the generator today. Originally published on pineprompt.com. Read the original.

dev.to

Your Client’s GST Bills Never Leave the Laptop: BillBook

_This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle_ ## What I Built BillBook turns GST bill photos into verified, import-ready rows for Tally or Excel. It runs entirely in your browser, so client data never leaves the laptop. Picture a small accounting practice in the last week of the month. A pile of supplier bills, some photographed at an angle, some in Hindi or Gujarati, all waiting to be typed into Tally by hand. It’s slow, error-prone work, and it’s billed by the hour. The obvious fix is to upload every bill to a cloud AI. For an accountant that’s a non-starter. Those bills carry a client’s vendors, GSTINs and margins. And a per-bill API cost eats the thin margin on exactly the small clients who need the help most. BillBook takes the other route: Drop a bill (photo or PDF) and a Gemma model of your choice reads it on your own device. Pick a smaller one for older laptops or a larger one for harder bills. Get a structured row: vendor, GSTIN, invoice number, date, taxable value, CGST, SGST, IGST and total. See a status on every row. Only rows marked OK are safe to import without a look. Edit any cell and the checks re-run instantly. Export to CSV for Tally or Excel. Works for English, English + Hindi, and English + Gujarati bills. Who would pay: GST practitioners, bookkeepers and small CA offices handling bills for several small-business clients. They bill for data entry today. BillBook cuts the time per bill and costs ₹0 per bill to run. The side hustle: a freelancer or student bookkeeper can offer “bill digitisation” to local businesses at a flat monthly fee. There’s no API bill and no server to maintain, so the margin is the whole fee. [Add your pricing idea or target client type.] ## Demo 🔗 Live app: https://hiyalukka.github.io/Bill-Book/ Needs a WebGPU browser (Chrome or Edge). The first model load takes a few minutes, then it’s cached locally. ## Code ## hiyalukka / Bill-Book ### # 🧾 BillBook: bills in, books out **Turn a photo or PDF of an Indian GST bill into a clean, verified row ready for Excel or Tally. An open-weight AI model reads the bill inside your browser, so the bill itself is never uploaded anywhere.** Live demo → · Built for the DEV × Hacktoberfest Week 3 challenge, _Side Hustle_ : open-source AI that someone would actually pay for. ## The problem A small accountant or shopkeeper receives bills as WhatsApp photos, crumpled paper, and inconsistent PDFs. Someone then retypes vendor, GSTIN, date, taxable amount, CGST/SGST/IGST, and total into a sheet by hand. It is slow, boring, and one wrong digit can cause a GST mismatch. Sending client financial documents to a cloud AI is often not an option, and per-bill API fees eat into a small tool's margin. ## What BillBook does 1. **Cleans the image** (grayscale, contrast, sensible resizing). 2. **Reads the text** … View on GitHub ## How I Built It BillBook is a static site hosted on GitHub Pages, with no backend. All inference happens in the user’s browser. Open-weight stack Models: a picker offering [list the Gemma variants from your dropdown], running on-device via WebGPU. Text from the bill: OCR engine, or "Gemma reads the image directly", with English, Hindi and Gujarati options Frontend: html, hosted on GitHub Pages Why a model picker instead of one fixed model: Users have very different hardware. A small Gemma loads fast on a modest laptop but misses more fields. A larger one is more accurate but needs more memory and a longer first download. Rather than guess for everyone, BillBook lets the user choose. Gemma is a small model running in a browser tab, and small models make mistakes. In accounting, a confidently wrong ₹11,800 is worse than no answer. So extraction is only half the pipeline. Every row passes three independent checks before it earns an OK: GSTIN checksum. A GSTIN carries a check character, so a hallucinated or misread number fails validation. Tax arithmetic. Taxable value plus CGST, SGST and IGST must reconcile to the total. Grounding. Every number Gemma outputs must appear in the scanned text. If the model invents a figure that isn’t on the bill, it’s caught. The checks are plain deterministic code, so Gemma can be wrong without the user being misled. Failed rows are flagged for review instead of silently imported. Editing a cell re-runs all three checks, so the human fix and the machine verification live in one loop. It also makes the model picker safe: switching to a smaller model is fine because its mistakes get flagged. Limits I’m upfront about. PDFs process page 1 only. Handwritten or badly skewed bills fail more often, which is what the status column is for. [Add anything else you found.] ## Why Does Open Innovation Matter? Four things BillBook does only because Gemma’s weights are open: Privacy by architecture, not by policy. A closed API means sending financial documents to someone else’s servers and trusting their terms. With Gemma running in the browser, the data physically can’t leave the device. For accountants, that turns “can I use AI on client data?” into a yes. ₹0 per bill is a real business model. Per-token pricing punishes high-volume, low-margin work like bill entry. Local inference makes the marginal cost zero, which is what lets a student or freelancer run this as a side hustle. Open weights let me design around the model’s weaknesses. Because Gemma runs on the user’s own machine, I could watch how it fails on real bills and build the verification layer around those failures. With a hosted API I’d be hoping the vendor’s next update doesn’t change its behaviour on my bills. Users choose the trade-off, not a vendor. With a closed API you get whichever model the provider serves, at whatever price they set. With open Gemma weights, BillBook offers a size choice and each user picks speed versus accuracy for their own hardware. ## My First User A small grocery shop owner in my neighbourhood. His shop handles supplier bills every week, and he keeps the records by hand. I showed him BillBook on my laptop and walked him through a sample bill. His reaction was about three problems he already lives with: Time: Writing up bills by hand takes a long stretch of his day, and the work is tedious and complicated. Fragile records: Paper records aren’t permanent. A bill can be lost, damaged or misplaced, and then the record is gone. Where the data goes: He liked that the bills are read on the device and don’t go to anyone else’s server. For a shop owner, supplier prices and purchase volumes are business information he wants to keep private. What struck me is that he didn’t ask about the AI. He cared about the three things above. Honest status: he hasn’t paid for it, and this was a first demo, not a long-term trial. ## Prize Categories Build with gemma category

dev.to

Taking a Copilot Studio agent to production: the 6 steps I use after 200+ agents

**Short answer:** a Copilot Studio agent is ready for production when six things are in place: a clear scope, testable instructions, a clean knowledge base, readable scanned documents, real connections to your systems, and security and lifecycle management agreed with IT. I'm a freelance Data & AI Tech Lead in Paris. At SUEZ I worked on 20 generative AI use cases and put more than 200 AI agents into production. This is the method I apply. ## 1. Scope the agent Define its role, its users and what it must refuse. Then choose between generative orchestration (you describe the agent and give it sources, tools and memory) and hand-designed topics. Generative orchestration is faster to build and less predictable, so keep designed topics for the paths that must never vary. ## 2. Write instructions you can test Instructions are the agent's contract. I write them in five blocks: who the agent is, what it does, what it refuses, how it answers, and what it does when it doesn't know. Each block comes with test questions, so a change to the instructions can be checked instead of guessed. ## 3. Treat the knowledge base like a database Most bad answers blamed on the model come from the documents: outdated versions, duplicates, conflicting files. Keep one up-to-date version of each document in SharePoint or your file sources before you tune anything else. ## 4. Make scanned documents readable For an agent, a PDF that is only an image is a blank page. Contracts and technical sheets often are. Add OCR and controlled preprocessing before indexing. ## 5. Connect the agent to your systems An agent that explains how to open a ticket helps. An agent that opens the ticket changes how people work. Use connectors, APIs, MCP servers and agent flows, and let flows handle what must be guaranteed: the model reasons, the flow executes. ## 6. Secure and industrialize Agree with IT and security on access rights, traceability, environments and ALM (dev, test, production). Then monitor answer quality with the same test set as in step 2, after every change. ## Limits These steps don't make an agent infallible. They make its failures visible and fixable. When you need fine-grained model choice or cost control at scale, Azure AI Foundry is often a better fit than Copilot Studio, and in large groups the two coexist. ## Going further * Detailed method (French), with FAQ: https://zakariakhchiche.github.io/copilot-studio/ * Answering only when a source supports it: Copilot Studio over thousands of documents _Zakaria Khchiche, freelance Data & AI Tech Lead (Paris). Contributor to Microsoft Agent Framework. LinkedIn · Website_ Want your team to build agents like these? I run a hands-on Copilot Studio training (in French, with Spar-x, Qualiopi-certified, eligible for OPCO funding in France): https://zakariakhchiche.github.io/formation-copilot-studio/ and a free AI Act article 4 kit: https://zakariakhchiche.github.io/kit-ai-act/

dev.to

Multi-threaded AVIF encoding in the browser was 2-3x faster, until I raised the speed setting

`@jsquash/avif` 2.1.1 ships two copies of the same AVIF encoder (libavif v1.0.1), and on disk they are about 50 KB apart. `avif_enc.wasm` works on one thread, and `avif_enc_mt.wasm` spreads the work over several. Some background first. AVIF is the image format built on the AV1 video codec, and most browsers I tried will decode it but will not write it. The writing is done by an encoder, the program that turns raw pixels into the compressed file, shipped to the page as WASM, which is compiled code the browser runs next to JavaScript. With the one-thread file, a 12-megapixel photo took close to four seconds to encode on my test page. I wanted to know how much of that the second file would give back. All of this was timed on one Apple M4 Mac running macOS, in Chromium 149, Chrome 155 and WebKit 26.5. No phones, no Windows, no release Safari. The number I report is the `encode()` call alone, inside a Web Worker (a background script, so the page stays responsive), three runs per cell, at the package defaults of quality 50 and speed 6. Decoding and resizing the source are outside the timer. The photo is a lake scene by Jakub Fryš from Wikimedia Commons (CC BY-SA 4.0), scaled on a canvas to each size. const glueFile = wantThreads ? 'avif_enc_mt.js' : 'avif_enc.js'; const makeCodec = (await import(`./codec/enc/${glueFile}`)).default; const codec = await makeCodec({ noInitialRun: true }); const startedAt = performance.now(); const avif = codec.encode(rgbaPixels, width, height, { ...avifDefaults, speed }); const encodeMs = performance.now() - startedAt; ## At the default speed the threaded build was 2 to 3 times faster At 12 MP (4256×2820) the single-threaded build took 3802 / 3754 / 3747 ms in Chromium and the threaded one took 1441 / 1410 / 1468 ms, so about 3.8 s became about 1.4 s. WebKit went from 4022 / 3963 / 3930 ms to 1279 / 1260 / 1251 ms, and Chrome sat within a few dozen milliseconds of Chromium. The gap held at 2 MP, where Chromium went from 696 / 696 / 673 ms to 327 / 324 / 323 ms. It also held on the slowest picture I fed it, an 8.3 MP screenshot of an admin-style table I rendered myself: 13662 / 13635 / 13613 ms against 5470 / 5426 / 5374 ms. Taking the middle run at each size of the lake photo, the threaded build came out 1.9 to 2.7 times faster in Chromium and Chrome and 2.0 to 3.2 times faster in WebKit. Picking the threaded file cost almost nothing up front. Gzipped on my machine the two WASM files are 1,115,948 and 1,130,150 bytes, and going from `import()` to a ready encoder took 14 / 13 / 12 ms and 35 / 33 / 30 ms in Chromium, with no download in that figure. ## At the two fastest speeds I tried the threads did not help `speed` is libavif's effort setting, and a higher number means a quicker encode. I ran the 2 MP lake photo through five values in Chromium. At speed 4 the threaded build still led clearly, 2746 / 2591 / 2625 ms against 7682 / 7563 / 7510 ms. Then the order flipped. At speed 8 the single-threaded build took 230 / 163 / 161 ms and the threaded one 239 / 174 / 173 ms. At speed 10 it was 156 / 103 / 100 ms against 204 / 149 / 146 ms, so the build with more threads was the slower one in all three runs. I only have the timings and did not investigate why. The chart shows the single-threaded middle run at each speed. Look at the bottom two bars: 163 ms and 103 ms are the numbers the threaded build failed to beat. Those files were smaller too, but I did not measure their image quality. ## The threaded build needs two response headers The threaded file needs a page that is cross-origin isolated, a state the browser grants when the document is served with these two headers. My test server sent them, and `crossOriginIsolated` read `true` in every browser I used. Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp What happens when the threaded build is loaded without them is something I did not test, so I have no failure mode to describe. That matters to me more than the milliseconds, because headers on the document are a change to the whole page and not to my export component. To see how a shipping product handles the loading, the tool I used was ImgIng (https://imging.ai/), a browser-based image converter, in Chromium 149 with one run per image size. The network observations come from its Chinese-language site and the screenshot from the English one. It fetched `avif_enc_mt.wasm`, the threaded build, at 1,135,337 bytes over gzip, and the request went out the moment I selected AVIF, before I pressed convert. I did not read its code, and its on-screen wait includes reading the image and updating the page, a different clock from my `encode()` timer. In the screenshot the AVIF button is tagged "On-demand WASM" and the main button reads "Downloading module…". For my own page the outcome is narrower than I expected: at speed 6 the threaded build is the one I would load, and if I moved to speed 8 or above I would time both files again before asking for the headers.

dev.to

JustNote8.com: The Free Online Notepad That Turns Your Ideas into Visual Flow Notes

Most note-taking apps give you a blank page and a blinking cursor. That works for a grocery list, but not for real thinking. Ideas don’t arrive in straight lines. They branch, connect, and loop back. JustNote8 is a free online notepad built for exactly that: it lets you organize your thoughts visually, as connected flow nodes on an open canvas. What Is JustNote8.com? JustNote8 is a visual notepad that runs in your browser. Instead of stacking text line after line, you place your ideas as nodes on a canvas and connect them with links. Each note opens into its own canvas, so every project, plan, or brainstorm gets a dedicated space. There is nothing to install, and you can start without creating an account. How Does a Flow-Based Notepad Work? Create a note. Click “Add note” and give it a name. Open its canvas. Click any note to enter a visual workspace. Add nodes. Each node holds one idea, task, or piece of information. Connect the dots. Link nodes to show order, dependencies, or relationships. The result is a map of your thinking, not just a list of it. Why Visual Notes Beat Plain Text You see relationships at a glance. A link between two nodes shows a connection that paragraphs hide. Planning gets easier. Flowcharts, roadmaps, and decision trees are natural on a canvas. Brainstorming feels freer. You can drop ideas anywhere first and organize them later. Memory improves. Spatial layouts are easier to recall than a long scroll of text. Who Is JustNote8 For? Students mapping out essays, study topics, and exam revision. Developers sketching app architecture, user flows, and task dependencies. Writers and creators outlining articles, stories, and content calendars. Founders and teams planning launches, processes, and strategies. Anyone who thinks better in diagrams than in bullet points. Use Cases to Try Today Plan a project from the first idea to the final deliverable. Map a user journey or a software workflow. Build a study plan that connects topics and deadlines. Outline a blog post, video script, or presentation. Turn a messy brain dump into a clear, linked structure. Free, Fast, and Private by Design JustNote8 is free to use and quick to open. You don’t need to sign up to start, and you can log in when you want your notes tied to an account. Details on how data is handled are in the site’s Privacy Policy and Cookie Policy. Frequently Asked Questions What is JustNote8.com? JustNote8 is a free online notepad that organizes ideas as connected flow nodes on a visual canvas. Do I need an account to use JustNote8? No. You can start taking notes without signing up. Is JustNote8 free? Yes, you can use JustNote8 for free. What is a flow node note? A flow node is a single idea or task placed on a canvas. Linking nodes together forms a visual flow that shows how your ideas relate. Can I use JustNote8 for brainstorming and project planning? Yes. The canvas layout suits brainstorming, workflows, outlines, and project plans. Start Organizing Your Ideas Visually Stop forcing your thoughts into straight lines. Open JustNote8, create your first note, and see your ideas take shape.

dev.to