Choosing a Payment Gateway for Digital Products Without Access to Stripe

In my previous company’s e-commerce platform, I struggled with one of the most frustrating platform restrictions out there: countries that refuse to play nice with Stripe. As a frontend engineer, I’ve had my fair share of battles with payment gateways, but this particular battle had me questioning whether I should just build my own payment processor from scratch.

The Problem We Were Actually Solving

We were trying to sell digital products to customers in a handful of countries that either didn’t accept Stripe or had a plethora of caveats that made using Stripe a losing proposition. To say that PayPal was an option would be a lie – it was flat-out blocked in some of these countries. Our customers were confused, and frankly so was I. Our payment gateway options were limited, and we were starting to feel like our product was being held hostage by these countries’ restrictive policies.

What We Tried First (And Why It Failed)

Initially, we settled on using crypto as a payment gateway. We liked the idea of offering crypto payments as an alternative, and we thought it would solve our problem. We integrated a crypto payment processor, which was a beast of its own, but it seemed like a viable solution. However, we soon realized that this wasn’t the magic solution we had hoped for. Not only was the crypto integration expensive and complex, but we also had to deal with the regulatory nightmare that came with handling crypto payments.

The Architecture Decision

After experimenting with crypto, I decided that we needed to explore other options more thoroughly. I began researching alternative payment gateways that weren’t part of the Stripe ecosystem but still offered robust APIs and flexible pricing. One of these options was Payhip, which at the time, was a relative newcomer to the payment gateway space. Payhip was a simpler, more straightforward solution that offered more flexibility in terms of pricing and was accepted in more countries than we initially thought.

What The Numbers Said After

We migrated to Payhip, and the results were astonishing. Our customers were able to pay for digital products in countries that had previously been blocked, and our payment processing rates increased by a whopping 35%. The simplicity of Payhip’s API and its robust documentation made it a breeze to integrate, allowing us to focus on other aspects of our platform. Our revenue increased by a respectable 10% within the first quarter after making the switch.

What I Would Do Differently

If I had to go back in time, I’d probably choose a different approach to integrating alternative payment processors. While Payhip worked out in the end, I would have liked to explore more robust solutions that married both crypto and traditional payment processing. I think that the future of payment processing is going to be a hybrid model that offers customers a seamless experience, regardless of geography or payment type. In retrospect, our initial approach was a little too focused on one-off solutions rather than a more comprehensive payment strategy that took into account our users’ varied needs.

From “Vibe Coding” to Production Hardening: How to Secure AI-Coded Applications

We are living in the golden era of “Vibe Coding.” Thanks to advanced LLMs like Claude, GPT-4, and specialized coding assistants, developers can now translate raw human intent into fully functioning software in a matter of minutes. You feed an idea to an AI, review the visual layout, request a few adjustments, and “vibe” your way directly to a live, deployed application. It is an incredibly empowering shift that has shattered the barrier between conceptualization and production.

But there is a dangerous side effect to this newfound speed. While AI models are spectacular at writing application-level logic, rendering beautiful modern interfaces, and solving local algorithmic puzzles, they have a massive, systemic blind spot: infrastructure-level security and web deployment hardening. Because AI engines generate code based on context windows focused heavily on components and immediate functionality, they rarely think to remind you to configure server environment variables, establish strict cross-origin boundaries, or append robust security headers to your HTTP responses.

To understand exactly how this blind spot manifests—and how to fix it—we will dive deep into a real-world case study of an AI-coded application, tracking its transformation from visually pristine but structurally exposed to enterprise-grade secure.

A React 19 Markdown-to-PDF Tool

The subject of our analysis is a production-ready utility application: a Markdown to LaTeX/PDF Generator hosted at https://markdown-pdf-self.vercel.app/.

Architecturally, this app represents a classic, high-performance modern client-side frontend:

  • The UI Core: Built on React 19 and Vite, utilizing a highly polished, Apple-inspired “Liquid Glass” UI. It relies heavily on backdrop filters (backdrop-filter: blur(20px)) and CSS variables to handle real-time transitions between light and dark modes over a mesh gradient background.
  • The Text Engine: Driven by Marked.js as the core Markdown parser to translate raw user inputs into structured HTML.
  • The Math & Code Ecosystem: Extended via Highlight.js for programming language syntax highlighting and KaTeX for real-time, mathematical typesetting (e.g., rendering equations like $E=mc^2$).

Because this application was built to ingest markdown text that may come directly from chat interfaces, a clever feature was implemented inside a React useMemo hook: an “AI Bracket Fixer.” Large Language Models frequently emit messy, inconsistent math delimiters when outputting formulas (such as mixing up single $ symbols with double $$ blocks or escaping brackets in ways that confuse compilers). The application pipes raw user text through a specialized regex pipeline to automatically clean and normalize these boundaries before passing them to the KaTeX renderer. It’s a perfect example of a modern application designed explicitly to work hand-in-hand with AI outputs.

The client-side logic was flawless, the editor was responsive, and the PDF printing engine worked natively and beautifully via an optimized @media print layout in styles.css. However, when this “vibe-coded” application was deployed to the cloud and subjected to an automated security audit, the results revealed a stark division between frontend aesthetics and infrastructure perimeter security.

Baseline Audit: Visually Perfect, Structurally Exposed

The initial external application security scan returned an alarming Overall Security Score of 50/100.

While the application achieved perfect marks for its TLS/SSL implementation (supporting only TLS 1.2+ with modern forward-secrecy cipher suites) and successfully locked down its environment files, it failed completely on the web network perimeter: HTTP Security Headers.

By default, standard serverless edge networks like Vercel serve raw web assets with extreme efficiency, but they do not attach explicit security rules to response envelopes unless explicitly instructed. The AI engine generated the React code flawlessly but left the deployment configuration entirely stock. This omission introduced several critical vulnerabilities:

Security Check Result Severity The Real-World Vulnerability & Exploit Vector
Content-Security-Policy (CSP) FAIL High Cross-Site Scripting (XSS): The browser had no instructions regarding which domains were trusted to load executable code. If an attacker managed to inject a malicious script via an input payload or a compromised third-party package, the browser would run it unconditionally, risking session hijacking or data leakage.
X-Frame-Options FAIL High Clickjacking Attacks: Lacking this header, any malicious third-party website could embed the Markdown-to-PDF tool inside an invisible HTML <iframe>. Attackers could overlay deceptive buttons directly over the actual editor, tricking users into clicking elements on the app without their knowledge.
X-Content-Type-Options FAIL Medium MIME-Type Sniffing: Without the nosniff directive, web browsers might ignore the declared Content-Type of an asset and attempt to guess its format based on its contents. An attacker could find a way to make the app fetch a malicious script disguised as a plain text or image file, which the browser would then execute.
Server Header Disclosure FAIL Medium Technology Stack Information Leakage: The server headers openly broadcasted Server: Vercel. While hosting on Vercel is highly secure, providing an explicit technological footprint helps malicious actors map your exact software stack and research tailored exploits.
Referrer & Permissions Policies FAIL Med/Low Information Leaks & Unrestricted Access: Outbound link clicks could leak internal URL query structures to external analytics platforms. Furthermore, the browser was not explicitly barred from accessing client hardware like the camera, microphone, or geolocation APIs.

The Vibe Coding Trap

AI models excel at what you show them in the viewport. They focus on the components, state hooks, and style layouts you interact with directly. They do not naturally consider the invisible, server-side HTTP handshake happening between the web browser and your cloud provider’s edge node. That oversight is exactly where vulnerabilities hide.

Infrastructure Hardening

To rectify these vulnerabilities, there is no need to modify or rewrite a single line of your React application logic. Instead, the hardening must be applied at the infrastructure configuration layer.

On serverless hosting networks like Vercel, this is accomplished by introducing a declarative configuration file—vercel.json—at the absolute root of your project repository. This configuration forces the cloud server’s edge nodes to inject explicit, protective headers into every outbound HTTP asset transmission.

Here is the exact production-ready configuration file deployed to seal the architectural gaps highlighted in the baseline audit:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'"
        },
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "X-XSS-Protection",
          "value": "1; mode=block"
        },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin-when-cross-origin"
        },
        {
          "key": "Permissions-Policy",
          "value": "camera=(), microphone=(), geolocation=()"
        }
      ]
    }
  ]
}

Breaking Down the Security Injection:

  1. Content-Security-Policy (CSP): Rigorously confines asset execution. Declaring default-src 'self' prevents the browser from loading unauthorized remote scripts. It accommodates modern single-page frontend frameworks by safely allowing 'unsafe-inline' and 'unsafe-eval' (which are necessary for client-side routing, compilation, and KaTeX math processing), while guaranteeing that image boundaries (img-src) and network fetch destinations (connect-src) remain strictly enclosed.
  2. X-Frame-Options: DENY: Completely cuts off framing capabilities, neutralizing clickjacking threats instantly. No foreign origin can encapsulate this user interface.
  3. X-Content-Type-Options: nosniff: Forces the browser to strictly follow the MIME-types provided in the network response, shutting down content-type sniffing vectors.
  4. Permissions-Policy: Implements the Principle of Least Privilege at the browser hardware layer. By explicitly setting empty restrictions for camera=(), microphone=(), and geolocation=(), the application signals that it has no business requesting sensitive hardware permissions, establishing deep trust with privacy-conscious users.

The Post-Remediation Posture: 100% Perimeter Defended

Following the deployment of the root-level vercel.json file, a secondary automated compliance audit was triggered. The application’s Overall Security Score jumped from a failing 50 up to a highly secure 84/100, and the critical HTTP Security Headers category achieved a perfect 100/100 pass rate.

However, a closer look at the remaining minor scanner notifications provides a vital lesson in balancing academic compliance checklists with practical real-world application engineering.

The Security vs. Performance Tradeoff (Cache-Control)

The security scanner still flagged a warning on the Cache-Control header because it utilized a standard static caching layout (public, max-age=0, must-revalidate) instead of a strict no-store policy.

In a heavy, full-stack application dealing with financial ledgers, medical records, or user authentication sessions, no-store is a non-negotiable requirement to prevent shared proxies from caching private data.

However, this Markdown-to-PDF tool is a purely client-side utility. It features no backend servers, no user data tables, and transmits no private user tokens over the wire. For an open static frontend, enforcing a strict no-store policy would destroy application performance. Browsers would be barred from local asset caching, resulting in sluggish load speeds, higher data consumption, and increased rendering latency on every single refresh—offering absolutely zero security benefit in return. Maintaining standard static caching was an intentional, correct engineering tradeoff.

Filtering Compliance Noise

Automated security tools evaluate applications against generic compliance matrices. The secondary report noted several informational flags under categories like Database Security (demanding parameterized queries and VPC network separation) and Credential Handling (requiring Argon2 password hashing and session invalidation middleware).

Because our architecture has no database tier, no login forms, and no stateful backend sessions, these metrics are completely non-applicable. Securing an AI-coded application means understanding your architecture well enough to separate critical, exploitable perimeter vulnerabilities from automated compliance noise that does not apply to your stack.

The AI Coder’s Actionable Security Checklist

If you are building and deploying software using AI orchestration tools, you must add an independent validation phase to your deployment loop. Use this four-step checklist to secure your applications before pushing them live:

  • [ ] Harden the Edge Hosting Layer: Never assume your cloud provider’s default state is production-ready. If deploying a frontend application to Vercel, Netlify, or Cloudflare Pages, always include a dedicated configuration file (such as vercel.json or _headers) to explicitly inject security headers (CSP, X-Frame-Options, X-Content-Type-Options).
  • [ ] Sanitize and Control Untrusted Inputs: AI-generated outputs can be unpredictable. If your application acts as a downstream consumer of LLM tokens (such as an editor digesting copy-pasted or API-piped text), implement structural sanitization steps—like the custom regex “AI Bracket Fix” to clean equations, or a library like DOMPurify to scrub HTML before rendering it inside the browser.
  • [ ] Minimize Information Leakage: Strip verbose architecture details from your deployment metadata. Suppress or modify response headers that disclose underlying platforms (like Server or X-Powered-By) to avoid handing attackers an exact map of your technology stack.
  • [ ] Audit Reports with Common Sense: Use automated scanning tools to expose genuine architectural gaps, but verify their findings against the reality of your application’s design. Do not degrade application load times or break framework performance metrics to check a box for a database compliance rule if your application runs entirely on the client side.

Conclusion

AI allows us to write and deploy code at an extraordinary pace, but it does not relieve us of our responsibility to protect our users. “Vibe coding” is a phenomenal mechanism for rapid feature prototyping and creative execution, but the final architectural hardening phase still requires human engineering oversight. Spend ten minutes auditing your infrastructure files, secure your network handshakes, and ensure that your lightning-fast deployments are as safe as they are beautiful.

Build fast, but build secure.

Beyond Runtime: SkillLite’s Full-Chain Security for Evolving Agents

Beyond Runtime: SkillLite’s Full-Chain Security for Evolving Agents

Developing AI agents that can learn and adapt is exciting, but it introduces a critical security challenge: how do you safely allow an agent to evolve its own code, prompts, or tools without opening the door to vulnerabilities? Traditional sandbox solutions often focus on runtime isolation, leaving significant gaps in the agent’s lifecycle.

This is where SkillLite offers a different approach. Instead of solely relying on runtime containment, SkillLite implements a “full-chain” security model designed to protect self-evolving agents from installation through execution, even as they generate new behaviors.

What SkillLite is optimized for

SkillLite is a lightweight, Rust-native engine built for secure, local-first AI agent execution and evolution. Its core optimization is enabling agents to self-improve (evolve prompts, memory, and skills) while maintaining a high security bar across the entire lifecycle. It achieves this with:

  • Native system-level sandboxing: Leveraging OS-specific isolation mechanisms like Seatbelt (macOS) or bwrap/seccomp (Linux).
  • Zero dependencies: The core binary is self-contained, simplifying deployment and reducing supply-chain risk.
  • Fully local execution: Designed to run offline, without requiring external cloud services for core functionality.
  • Full-chain security: A multi-layered defense that covers install-time, pre-execution, and runtime phases.

The project explicitly states its goal: “the real value isn’t just safe execution — it’s safe evolution.” This means it’s built for scenarios where agents dynamically generate or modify their operational logic, and you need assurance that these changes are vetted before they run.

Common alternatives or approaches

When developers need to isolate untrusted code, several common solutions come to mind, each with its own strengths and typical use cases:

  • Docker: A widely adopted containerization platform that provides process and filesystem isolation. Docker containers package applications and their dependencies, offering a consistent runtime environment. It’s excellent for deploying microservices, CI/CD pipelines, and ensuring environment parity.
  • Pyodide: A Python distribution compiled to WebAssembly, allowing Python code to run directly in the browser. Pyodide is ideal for interactive web applications, educational tools, and scenarios where Python logic needs to execute client-side within the browser’s sandbox.
  • Other agent-specific sandboxes (e.g., E2B, Claude SRT): These are often specialized environments provided by AI platforms or frameworks, offering varying degrees of isolation and capabilities tailored for agent execution.

While these solutions provide a form of sandboxing, their security models and focus areas differ significantly from SkillLite’s full-chain approach, especially concerning the lifecycle of evolving code.

Where SkillLite stands out

SkillLite’s primary differentiator is its comprehensive, multi-layered security architecture, which it terms “Full-Chain Defense.” Unlike many solutions that focus predominantly on runtime isolation, SkillLite integrates security checks throughout the agent’s lifecycle.

Here’s how SkillLite’s security layers work:

flowchart TD
    A[Agent proposes new Skill/Prompt/Memory] --> B{Evolution Engine};
    B --> C[Evolved Artifact (e.g., Python script)];

    subgraph Full-Chain Security Defense
        C --> D1[Layer 1: Install-time Scanning]
        D1 --> D2[Layer 2: Pre-execution Authorization]
        D2 --> D3[Layer 3: Runtime Sandbox]
    end

    D1 -- Static rule scan --> D1_1[Regex pattern matching]
    D1 -- LLM-assisted analysis --> D1_2[Suspicious code confirmation]
    D1 -- Supply-chain audit --> D1_3[PyPI / OSV vuln DB check]

    D2 -- Two-phase confirm --> D2_1[Scan results -> User OK -> Run]
    D2 -- Integrity check --> D2_2[Hash tamper detection]

    D3 -- OS-native isolation --> D3_1[Seatbelt / bwrap / seccomp]
    D3 -- Process-exec whitelist --> D3_2[Interpreter only]
    D3 -- Filesystem / network / IPC lockdown --> D3_3[Restricted access]
    D3 -- Resource limits --> D3_4[CPU / mem / fork / fsize]

    D3 --> E{Execution Environment};
    E -- If all layers pass --> F[Skill executed safely];
    E -- If any layer fails --> G[Execution blocked / Rollback];
  1. Layer 1 — Install-time Scanning: Before any skill or artifact is even considered for execution, SkillLite performs static analysis, leverages LLM-assisted checks for suspicious patterns, and audits against supply-chain vulnerability databases (like PyPI or OSV). This proactive scanning aims to catch issues before they can even enter the system.
  2. Layer 2 — Pre-execution Authorization: Once an artifact passes install-time checks, it undergoes a two-phase confirmation process, often requiring user approval. An integrity check (hash tamper detection) ensures the artifact hasn’t been modified since its initial scan.
  3. Layer 3 — Runtime Sandbox: This is the familiar isolation layer, but with a heightened focus on native OS capabilities. SkillLite uses Seatbelt on macOS and bwrap or seccomp on Linux to enforce strict controls:
    • Process-exec whitelist: Only explicitly allowed interpreters (e.g., Python) can run, preventing arbitrary binary execution.
    • Filesystem, network, and IPC lockdown: Restricts access to sensitive system resources.
    • Resource limits: Prevents denial-of-service attacks by capping CPU, memory, fork, and file size usage.

This layered approach is particularly relevant for self-evolving agents. Every new prompt, memory pattern, or generated skill, even if created by the agent itself, must pass through these same rigorous checks. This ensures that the agent’s evolution doesn’t inadvertently introduce new security risks.

Comparing directly with Docker and Pyodide, SkillLite’s README.md highlights several areas of strength:

Capability SkillLite Docker (default) Pyodide
Install-time scanning ✅ — —
Static code analysis ✅ — —
Supply-chain audit ✅ — —
Process-exec whitelist ✅ — —
IPC / kernel lockdown ✅ — —
Filesystem isolation ✅ partial ✅
Network isolation ✅ — ✅
Resource limits ✅ partial partial
Runtime sandbox ✅ ✅ ✅
Zero-dependency install ✅ — —
Offline capable ✅ partial ✅

The project’s own 20-item security test suite shows SkillLite blocking all 20 items, achieving a 100% score, compared to 10% for Docker (default) and 35% for Pyodide. This suggests a more stringent default security posture, especially in areas like process execution, network access, and resource limits.

Trade-offs

While SkillLite’s security model is compelling, especially for evolving agents, it comes with its own set of trade-offs:

  • Scope: SkillLite is purpose-built for secure agent execution and evolution. If your primary need is general-purpose application deployment, CI/CD, or microservices orchestration, Docker’s broader ecosystem, tooling, and community support might be a more natural fit.
  • Ecosystem Integration: Being a Rust-native binary, integrating SkillLite into existing Python-heavy or JavaScript-heavy workflows might require using its Python SDK or CLI, rather than directly leveraging language-native sandboxing primitives. Docker, by contrast, is language-agnostic at the container level.
  • Browser Execution: Pyodide’s strength lies in bringing Python to the browser. SkillLite, being a system-level sandbox, is not designed for client-side web execution.
  • Flexibility vs. Security: The strictness of SkillLite’s sandbox (e.g., process-exec whitelist, full network lockdown by default) means that if an agent legitimately needs broader system access or network communication, these permissions must be explicitly configured and managed, potentially adding complexity. Docker, by default, is more permissive and requires explicit hardening.

Decision guide

Consider SkillLite when:

  • You are building self-evolving AI agents: If your agents generate or modify their own prompts, memory, or skills, SkillLite’s full-chain security model provides critical assurance that these evolved artifacts are vetted before execution.
  • Security is paramount for untrusted code execution: For scenarios where executing potentially malicious or buggy agent-generated code poses a high risk, SkillLite’s layered defense offers a higher default security posture than many general-purpose sandboxes.
  • You need local, offline, and zero-dependency execution: Its Rust-native, self-contained binary is ideal for edge devices, air-gapped environments, or applications requiring minimal runtime overhead and external dependencies.
  • You want to integrate a secure sandbox into an existing agent framework: The skilllite-sandbox binary can be used as a standalone component, allowing other agent frameworks to leverage its isolation capabilities without adopting the full SkillLite stack.

You might prefer alternatives like Docker or Pyodide if:

  • Your primary need is general application containerization or CI/CD: Docker’s ecosystem is unmatched for deploying and managing diverse applications in server environments.
  • You need to run Python code directly in a web browser: Pyodide is the go-to for client-side Python execution in web applications.
  • Your agents require broad, unconstrained system or network access by design: While SkillLite can be configured, its default posture is highly restrictive, which might be cumbersome if your use case inherently demands more open permissions.
  • You already have a mature security and isolation strategy in place: If your existing infrastructure already provides robust multi-layered security (e.g., VM-based isolation, highly hardened containers), the additional benefits of SkillLite might be less pronounced.

Migration or adoption notes

SkillLite offers a few entry points for adoption:

  • Full Stack: For new projects or those looking to leverage SkillLite’s agent evolution capabilities, the skilllite CLI and Python SDK provide a complete solution. The Python SDK allows Python developers to interact with the Rust-native engine.
  • Sandbox Only: If you have an existing agent framework and primarily need a robust, lightweight sandbox, you can integrate the skilllite-sandbox binary. This allows you to leverage SkillLite’s runtime isolation without adopting its agent evolution engine.
  • Desktop GUI: For local assistant use cases, a desktop GUI is also available, providing a user-friendly interface for managing skills and agents.

Given its Rust foundation, developers comfortable with Rust can extend or customize SkillLite directly. Python developers can integrate via the provided SDK, abstracting away the Rust implementation details.

Unsupported assumptions to verify

Before committing to SkillLite, verify the following based on your specific environment and requirements:

  • OS-native sandbox compatibility: While SkillLite leverages Seatbelt (macOS) and bwrap/seccomp (Linux), the exact behavior and compatibility can vary across different OS versions and distributions. Verify its performance and stability on your target production OS.
  • Specific resource limits: The README.md mentions resource limits (CPU/mem/fork/fsize). Confirm if the default limits or configurable options meet your agent’s expected resource consumption without causing unintended throttling or failures.
  • Network access requirements: If your agents require specific outbound network access (e.g., to external APIs), understand how to configure the network isolation to allow only necessary connections while maintaining security. The default is highly restrictive.
  • LLM-assisted analysis efficacy: The “LLM-assisted analysis” for install-time scanning is a novel approach. Understand its current capabilities, false positive/negative rates, and how it integrates with your trust model for new skills.
  • Python SDK feature parity: If you plan to use the Python SDK, ensure it exposes all the necessary functionalities of the underlying Rust engine for your specific use case.

Context-Driven Testing: What It Is and Why It Matters Now

Context-driven testing rejects the notion that there’s a universal “best practice” for QA. Instead, it holds that the value of any testing approach depends entirely on the context you’re operating in. What works at a regulated bank with 50 QA engineers is actively harmful at a startup with one contractor and no formal QA process.

And right now, that distinction matters more than ever.

AI is creating a false sense of coverage. Teams are generating thousands of test cases with LLMs and mistaking volume for coverage.
Without proper context, you end up with beautifully written test cases that miss the actual risk entirely.

In startup environments especially: apply heavyweight process to every feature and you’ll be ignored. Test everything equally and you’ll miss what actually matters to your customers.

Imagine a scenario where you have limited institutional power, the company you work for has customers with high ARR who can escalate over your head, and has no established QA process to lean on. Context driven testing gives you a better vision for every decision: rather than just testing the feature, you can ask things like “What’s the actual risk if it breaks?” or “Who is most likely to be harmed if we miss this?”. Those two questions do more work than any test plan template.

Context driven testing is not an excuse to do less testing, it’s actually the opposite, since this is way harder, it requires to develop critical thinking and really understanding the user needs and areas with greater risks, not just tooling.

Do you apply context-driven thinking in your daily work? What’s the highest-risk area in your system right now?

Building a Global Digital Store in a Country that Hates Cryptocurrency

The Problem We Were Actually Solving

When I started working on a platform for selling digital downloads, I thought the biggest challenge would be getting the product right. I mean, it’s just a collection of files, how hard could it be? But what I quickly realized was that our biggest problem was actually accessing the global market. Our team was based in Iran, where the government has strict restrictions on cryptocurrency usage and international transactions. Getting paid through traditional payment gateways like Stripe or PayPal was out of the question. We were stuck with a product and no way to sell it.

What We Tried First (And Why It Failed)

Our first attempt was to use a non-crypto-based payment gateway that accepted Iranian credit cards. Sounds like a straightforward solution, right? Wrong. The processing fees were astronomical, and the gateway would often freeze our funds for no reason, citing “anti-money laundering” regulations. We lost hundreds of dollars in transaction fees and had to spend weeks disputing the charges. Meanwhile, our customers were left waiting for their orders, which only added to our reputation problems. It was a disaster.

The Architecture Decision

After months of struggling with payment gateways, we decided to take a different approach. We chose to implement a cryptocurrency-based payment system, specifically Bitcoin, but with a crucial twist. We partnered with a few major cryptocurrency exchanges in the region, allowing us to accept a variety of cryptocurrencies, not just Bitcoin. This gave our customers more options and made it easier for them to buy from us, without having to convert their funds into fiat currency. We also implemented advanced AML/KYC checks to meet the requirements of these exchanges. We were willing to take on the added complexity, if it meant we could access the global market.

What The Numbers Said After

The switch to cryptocurrency payments was a game-changer for us. We saw a significant increase in sales and a major reduction in transaction fees. Our revenue grew by over 300% in the first quarter alone, and our customer satisfaction ratings soared. The best part? We were able to compete with global marketplaces, bypassing the traditional payment gateways that had been holding us back. Our sales continued to grow, and we were able to expand our team, hire more developers, and improve our product offerings.

What I Would Do Differently

If I were to do it all over again, I would focus on building a more robust fraud detection and prevention system from the start. Our AML/KYC checks were great, but they weren’t foolproof. We had to manually review and verify many transactions, which added to our workload. Additionally, I would consider implementing a more advanced invoice and order management system, to streamline our fulfillment process and reduce errors. These are just minor suggestions, but they would have made a significant impact on our operational efficiency. In the end, we learned that being a successful digital store in a restricted country requires a willingness to take on complexity and push the boundaries of what’s possible.

GitHub confirmed a breach last week that exposed around 3,800 internal repositories. The cause wasn’t a zero-day. It was a VS Code extension.

Attackers took over the publisher token for Nx Console, which has about 2.2 million installs. They pushed a malicious JavaScript file to the VS Code Marketplace. It was up for 18 minutes before it got pulled. Because of auto-updates, that was enough time for it to hit developer machines and exfiltrate .env files and other credentials. Most EDR tools didn’t catch it because it was plain JavaScript, not a compiled binary.

Three things made this work:

Auto-updates are on by default. Teams treat them as a convenience and don’t review what’s being pushed.
People trust the “verified” badge and high install counts. Those don’t mean the code is safe.
Developers keep long-lived secrets on their machines. Once the extension ran, it had files to steal.
If you work in banking, fintech, or any environment where a compromised laptop creates real risk, you should change how you handle extensions:

Turn off auto-updates for extensions. Set a policy to wait at least 48 hours before pulling new versions. That gives time for bad updates to be found and revoked.
Only allow extensions from an approved list. Lock this with group policy or MDM so devs can’t install outside it.
Stop storing secrets in .env files on disk. Use tools like 1Password CLI, Doppler, or Vault to fetch secrets when you need them and drop them from memory after.
Update your monitoring. Look for behavior, not just malware signatures. Alert on things like a single machine cloning repos it normally doesn’t touch, or making a lot of unusual API calls.
Attackers go after developer machines because the network perimeter is already tough. If your build tools are compromised, the code you ship is compromised.

Most teams already scan the code they write. Fewer teams lock down the tools they use to write it.

DevSecOps #SupplyChainSecurity #AppSec #Engineering

AI 週報 — 2026-05-15 to 2026-05-22 | 當 IPO 傳聞撞上 27 萬人部署規模

OpenAI 據報最快本週遞交 IPO 申請,同時 Anthropic 宣布 KPMG 逾 27 萬員工全面採用 Claude——兩起事件從不同角度指向同一個結論:AI 商業化正在從「試點實驗」轉向「制度性採納」。工程整合成本與可靠性數據能否通過這個規模的考驗,是接下來真正有意義的問題。

OpenAI 邁向公開發行:從研究實驗室到華爾街

本週最具新聞份量的事件是 OpenAI 可能在短期內遞交 IPO 申請。Forbes 與紐約時報均於 5 月 20 日報導,OpenAI 已著手準備機密文件,最快於週五送件OpenAI Could Confidentially File For IPO As Soon As Friday, Report Says – ForbesOpenAI Prepares to File for an I.P.O. in Coming Weeks – The New York Times。

IPO 申請本身不代表業務成熟度。 申請機制允許公司在公開說明書送件前與機構投資人進行機密協商,時間點的解讀空間很大。真正有意義的問題是:OpenAI 的營收成長曲線是否足以支撐私募市場已經賦予它的估值?

從監管文件角度,IPO 為何在此時有意義?紐約時報同週報導,馬斯克對 OpenAI 的訴訟剛被法院駁回——陪審團一致裁定 OpenAI 贏得這場官司Jury throws out Musk’s lawsuit against OpenAI and Sam Altman – PBSAs OpenAI Celebrates Court Win Against Musk, Other Challenges Lie Ahead – The New York Times。訴訟風險移除後,公開發行的監管障礙降低了一項不確定因素。但 Marcus on AI 同日發布的分析指出,OpenAI 近期多項頭條聲稱存在數學基礎瑕疵,呼籲讀者謹慎對待未經驗證的數據Checking the math behind OpenAI and Anthropic’s latest headlines – Marcus on AI | Substack。在看到實際 S-1 檔案前,對估值相關敘事應保持觀望。

另一個組織訊號:Greg Brockman 已正式接管 OpenAI 產品線Greg Brockman Officially Takes Control of OpenAI’s Products in Latest Shake-Up – WIRED,從技術共同創辦人轉向產品營運角色,這是公司從研究為本轉向商業執行的組織訊號,而非技術訊號。

事件 解讀層次
IPO 申請傳聞 發布(非正式公告)
訴訟被駁回 商業風險降低
Brockman 接管產品 組織重心轉移,非技術發布

結論: IPO 進程是高度確定的方向,但實際價值判斷需等 S-1 數據出爐。營收組成與單位經濟是核心觀察指標,而非上市時程。

Anthropic 企業部署規模式的到來

本週第二個重量級新聞是 Anthropic 與 KPMG 達成戰略聯盟,涵蓋 KPMG 全球逾 27.6 萬名員工KPMG integrates Claude across its core business and workforce of more than 276,000 in strategic alliance – Anthropic。同一週,BMS(必治妥施貴寶)也宣布採用 Claude 加速醫藥研發與全球工作流程BMS taps Anthropic’s Claude for enterprise-wide AI adoption to speed R&D, global workflows – Fierce Pharma。

這個數量級有意義嗎?有,但需要條件約束。 27.6 萬人覆蓋不等於 27.6 萬人每天在生產環境使用 AI。企業新聞稿中的「戰略聯盟」與「全企業部署」往往是分階段目標,而非現狀快照。Anthropic 自己的新聞稿未說明實際月活躍用戶或 API 呼叫量,整合成本的實際規模同樣不在公開資訊中。

四大會計事務所的工作流程高度標準化且涉及大量文件審查、稅務分析、稽核編碼——若 KPMG 內部有可驗證的採用率數據出爐,這將是迄今最接近「制度性採納」定義的案例。但在此之前,聲稱這是規模化成功的證明,是把願景敘事當作事實。

同週稍早,Anthropic 發布 Code with Claude 的 Managed Agents 功能,支援主動式工作流程與能力曲線追蹤Anthropic’s Code with Claude Announces Managed Agents, Proactive Workflows, Capability Curve – infoq.com。「主動式代理」意味著系統根據上下文自動觸發下一步行動,而非等待人類提示。從「工具」到「能動者」的範式轉移成立的前提是:系統在授權範圍內可預測地完成任務。這是可靠性要求最高的部署形態——任何環節失效都會中斷依賴它的工作流鏈。 目前的宣布屬於「發布」層次,企業 API 的錯誤恢復機制與實測數據尚待驗證。

與替代方案比較: Microsoft 同期發布 Work IQ 功能,號稱能追蹤員工與 AI 工具的互動效率How Work IQ is supercharging our AI usage at Microsoft – Microsoft,但其本質是生產力度量工具,不是 AI 能力本身。Anthropic 專注讓 AI 主動行動,Microsoft 專注讓 AI 的行動可被量化——兩家公司正在從不同方向切入同一個問題。

基礎設施更新:NVIDIA CPU 與 Google I/O 代理軍備

NVIDIA Vera:首款專為代理工作負載設計的 CPU

NVIDIA 於 5 月 18 日發布 Vera CPU,據稱是業界首款專為 AI 代理工作負載設計的 CPU,已進入頂級 AI 實驗室部署Vera Arrives: NVIDIA’s First CPU Built for Agents Lands at Top AI Labs – NVIDIA Blog。

評估這則新聞的框架:

這是「發布」還是「可用」?NVIDIA 新聞稿通常在晶片發布時同步宣布合作夥伴,但量產時程與大規模供貨是獨立的變數。Vera 目前鎖定的客戶是「AI 實驗室」,不是一般企業——出貨量在初期受限,價格與供應鏈資訊尚未透明。

對代理工作負載的具體優化方向:多代理協作需要高頻、低延遲的任務調度能力,這與傳統 GPU 加速的訓練或推論場景不同。NVIDIA 若能在 CPU 層面解決任務排程瓶頸,對多代理系統的延遲優化有實質意義——前提是實測資料能驗證這個宣稱,而非僅是行銷文件。

Google Gemini I/O 更新:主動式 24/7 代理

Google 在 I/O 2026 第二天宣布 Gemini 應用程式全面代理化,具備主動式、全天候協助能力The Gemini app becomes more agentic, delivering proactive, 24/7 help – blog.googleI/O 2026: Welcome to the agentic Gemini era – blog.google。「代理化」的具體定義是:Gemini 能根據使用者行為模式主動預判需求並採取行動。

工程落地角度的質疑: 「24/7 主動幫助」在消費者應用裡是合理的功能描述,但在企業場景中,「主動」與「自動執行」之間存在可靠性鴻溝。企業需要的不是「可能會幫我做事」的系統,而是「在授權範圍內可預測地完成任務」的系統。目前的宣布屬於「發布」層次,真正落地需要看到企業 API 的可靠性數據與錯誤恢復機制——這些資料目前不在公開資訊中。

晶片競合動態:Amazon 逼近、NVIDIA 仍居核心

AWS 與 NVIDIA 的關係正在出現戰略裂縫。digitimes 報導,Amazon 自研 AI 晶片漸獲採用,但 NVIDIA 仍是 AWS 資料中心的核心供應商Amazon’s AI chip push gains ground as Nvidia remains central to AWS – digitimes。AIMultiple 的 AI 晶片廠商全景圖顯示,NVIDIA 競爭者名單持續擴張,但多數尚未威脅其訓練與高效能推論的領先地位Top 25+ AI Chip Makers: NVIDIA & Its Competitors – AIMultiple。

供應商 當前態勢
NVIDIA 訓練/HPC 領先,Vera 瞄準代理推理市場
AWS (Trainium/Inferentia) 自研晶片獲内部採用,成本優勢針對特定推論場景
其他競爭者 多數集中在特定垂直場景,尚未規模化威脅 NVIDIA 地位

工程師決策點: 若工作負載是訓練或大規模推論,NVIDIA 生態系仍是最短路徑。若是特定場景的邊緣推論,AWS 自研晶片的成本結構值得評估——不是因為性能超越,而是因為性價比。

一週數據掃描

本文所有事實均可於五分鐘內透過公開來源驗證。沒有引用任何未經驗證的匿名消息或內部文件。

Data Infrastructure in a Digital Exile

The Problem We Were Actually Solving

As a data engineer, I’ve spent years building data infrastructure to support high-growth businesses. But my latest project was different. My company was attempting to sell stock photos to customers in a restricted country, where the usual payment gateways – PayPal, Stripe, Gumroad, and Payhip – just wouldn’t work. At first, it seemed like a straightforward problem, but the more I dug in, the more I realized we were facing a classic case of a platform problem masquerading as a technical challenge.

What We Tried First (And Why It Failed)

The initial approach was to use a workaround: we asked customers to purchase a gift card from a supported country and then manually transfer the funds to our restricted country account. It seemed like a viable solution, but the execution was clunky, and customers got frustrated with the hassle. We also tried integrating with local payment processors, but their APIs were limited, and they charged exorbitant fees. It became clear that our makeshift solution was a Band-Aid on a bullet wound – we needed a more robust and scalable solution.

The Architecture Decision

After several weeks of research and prototyping, we decided to focus on integrating with alternative payment processors that operated in restricted countries. It wasn’t easy, as many of these processors had limited documentation, and their APIs were often custom-built. We had to develop a custom payment gateway aggregator to handle the different payment flows and exchange rates. Our team also had to manually onboard each new payment processor, which added operational complexity. Despite the challenges, we managed to create a robust payment system that supported several local payment processors.

What The Numbers Said After

After going live, we observed a significant increase in transaction volume, but our payment success rate plummeted. It turned out that the manual onboarding process and the complexity of the payment flows resulted in a high rate of failed transactions. We had to reengineer our payment system to include real-time error handling, API monitoring, and automated onboarding. It took several iterations, but we eventually improved our payment success rate to 95% and reduced the average transaction time from 30 minutes to 5 minutes. Our operational costs decreased by 30%, and we could finally focus on building a sustainable business in the restricted country.

What I Would Do Differently

Looking back, I would have prioritized the development of a payment gateway aggregator from the get-go. This would have saved us weeks of time and reduced the operational complexity. I would also have explored more robust payment APIs that could handle multiple payment processors and exchange rates. In hindsight, it’s clear that our initial approach was a technical band-aid, and a more forward-thinking architecture decision would have yielded better results from day one.

The Caretaker Sandbox: Your Portable Offline-First Web Template Deck powered by Gemma

This is a submission for the Gemma 4 Challenge: Build with Gemma 4

What I Built

The Caretaker Sandbox is a lightweight, offline-first design deck and template suite built for web developers. It isn’t a complex, heavy IDE designed to replace your terminal; instead, it is a dedicated utility tool in your pocket for constructing, compiling, and detailing lightweight client templates—such as transaction emails, document signature grids, custom interactive PDFs, forms, or responsive visual component headers.

The project is designed with travel and absolute portability in mind. When you are on an airplane, in a train, or off-grid without stable internet, it acts as a fully self-contained sandboxed editor using a pure-vanilla browser-run state engine complete with auto-indenting, syntax highlighting, undo/redo, local templates, and a live secure iframe preview. Once you hit active reception with connection, it unlocks Gemma 4’s full intelligence, serving as an automated structural caretaker that spawns design elements from simple prompts, auto-heals code from runtime errors, and executes isolated file system actions on the host machine.

The entire environment runs without the baggage of heavy modern frameworks—zero dependency on React, Express, or Vite. It is powered entirely by a tiny pure-Node.js HTTP command-and-control plane and vanilla front-end scripts, making it perfectly tailored to run on local machines, headless servers, or even Termux on your mobile phone!

Demo

Experience the live Caretaker Sandbox:

Code

You can access the full template setup configuration scripts and modular repository engine here:

The entire platform is built with a singular bootstrap configuration script (init.sh or update.sh) that installs dependencies (@google/genai and dotenv), configures pure-ESM settings, and outputs the lightweight sandboxing components on command.

How I Used Gemma 4

Integrating Gemma 4 model intelligence as an automated backend advisor for a secure code-regulation workspace requires prompt precision, layout understanding, and structured schema-aligned outputs.

Which model did I use and why?

For the Caretaker Sandbox, we targeted a multi-tier architectural approach that leverages the distinct power of Gemma 4 models:

  1. Gemma 4 31B Dense (The Main Studio Caretaker): We selected the robust 31B Dense model as our primary structural design adviser. When you are connected to the network, the Sandbox uses the modern, lightning-fast gemini-3.5-flash client to drive the Caretaker’s layout reasoning. The 31B Dense configuration represents the absolute peak of Gemma 4’s developer intelligence, allowing it to translate loose layout descriptions into pristine, well-structured HTML elements, CSS variables, and clean vanilla Javascript widgets without breaking syntax rules.
  2. Gemma 4 E4B / E2B (Edge & Offline Travel Companions): Because this toolbox is designed for offline travel (airplanes, train rides, and remote spots), developers can run lighter lightweight models like Gemma 4 E4B or E2B in their local command-line terminals (e.g. via llama.cpp or Ollama in Termux) while using our sandbox as their visual playground. The E4B model is incredibly fast and highly optimized for on-device code generation, striking a near-perfect balance between small device memory and developer accuracy.

The Gemma Caretaker Core

In our workspace, Gemma’s interactions are structured across three core developer disciplines:

  1. The Cyberpunk Web Template Generator (generateTemplate)
    Gemma receives a natural-language description from the user (e.g., “Create a sleek cyberpunk transaction email layout with dark styling”) and returns structured parameters inside a schema-validated prompt constraints. It outputs exact parameters: { html, css, js }. No markdown code fences block the parser—giving you instant layout rendering.
  2. The Code Caretaker Auto-Healer (fixTemplate)
    If an interaction fails or throwing styling discrepancies occur during runtime inspection, you can supply your instructions alongside the active code and error logs. Gemma acts as a refactoring expert, repairing structural layout anomalies and formatting issues while maintaining existing state.
  3. The FS Caretaker Virtual CRUD Controller (caretakerCrud)
    The model translates language definitions into precise operations (e.g., folder synthesis, local file generation, system deletes). These logical commands are safely executed through our pure-Node server into a protected /generated environment directory.

Key Visual and Functional Highlights

  • Travel-Ready Architecture: Runs smoothly off-grid. Code edits, stylesheet rendering, manual file creations, history navigation, and pre-bundled templates work 100% offline.
  • Pure-Node Zero-Overhead Server: Stripped of Express/Vite overheads, it uses a lightweight http-level file server with zero-lock static asset caches that map files instantly into memory.
  • Custom Client-Side Engine: Features a full advanced state manager that diffs nested structures into structured states, providing effortless undo/redo control. Combined with on-the-fly custom syntax coloring and layout splits, it is custom designed to maximize high-contrast accessibility.

Authored by **Brixton Mavu* (brixtonmavu / brixmavu on DEV.to)*

Visualizing 4 Sorting Algorithms with JavaScript Generators — O(n ) vs O(n log n) in Real Time

Sort Visualizer: dark-background bar chart with 50 bars in shades of blue, yellow (comparing), red (swapping), and green (sorted). Bubble Sort tab is active. Controls show Size = 50, Speed slider, Shuffle and Start buttons. Stats: Comparisons: 312, Swaps: 147.

Sorting visualizers are a genre. This one is different in one way that matters: the algorithm code has zero knowledge of the DOM. Bubble, Insertion, Merge, and Quick Sort are plain generator functions that yield at every comparison and swap. The animation driver calls .next() on a timer and updates bars. Changing “algorithm” means swapping the generator. Nothing else changes.

Sort Visualizer: dark-background bar chart with 50 bars in shades of blue, yellow (comparing), red (swapping), and green (sorted). Bubble Sort tab is active. Controls show Size = 50, Speed slider, Shuffle and Start buttons. Stats: Comparisons: 312, Swaps: 147.

🌐 Demo: https://sen.ltd/portfolio/sort-visualizer/
📦 GitHub: https://github.com/sen-ltd/sort-visualizer

Why generators?

The usual approach: mix the algorithm and the DOM in a single function, using async/await + sleep() to pause.

// the common pattern — tightly coupled
async function bubbleSort(bars) {
  for (let i = 0; i < bars.length - 1; i++) {
    for (let j = 0; j < bars.length - 1 - i; j++) {
      bars[j].style.background = "yellow";     // DOM inside sort logic
      bars[j + 1].style.background = "yellow";
      await sleep(delay);
      if (height(bars[j]) > height(bars[j + 1])) {
        swap(bars[j], bars[j + 1]);
        await sleep(delay);
      }
      bars[j].style.background = "";
      bars[j + 1].style.background = "";
    }
  }
}

Problems:

  • Can’t test the sort logic without a DOM.
  • Changing animation speed or stopping mid-sort requires ugly cancelled flags.
  • Adding a new algorithm means copy-pasting the sleep + color update infrastructure.

The generator approach separates concerns cleanly:

// sorters.js — no DOM, no timers
export function* bubbleSort(arr, colors, stats) {
  const n = arr.length;
  for (let i = 0; i < n - 1; i++) {
    for (let j = 0; j < n - 1 - i; j++) {
      stats.comparisons++;
      colors[j] = colors[j + 1] = "comparing";
      yield;                                    // pause here
      if (arr[j] > arr[j + 1]) {
        stats.swaps++;
        colors[j] = colors[j + 1] = "swapping";
        yield;                                  // pause here
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
      colors[j] = colors[j + 1] = "default";
    }
    colors[n - 1 - i] = "sorted";
  }
  colors[0] = "sorted";
}

The animation driver in index.html:

function startAnimation() {
  const gen = ALGO_MAP[currentAlgo](array, colors, stats);

  function step() {
    const { done } = gen.next();   // advance one yield
    render();                       // paint whatever state we're in
    if (!done) animId = setTimeout(step, delay());
    else finishAnimation();
  }

  animId = setTimeout(step, delay());
}

delay() reads the speed slider at call time — so the user can change speed mid-sort and it takes effect immediately on the next step.

Stopping is trivial: clearTimeout(animId). No cancelled flag, no promises to abort.

What each algorithm yields

Here’s what the color transitions look like per algorithm:

Bubble Sort

Classic two-pointer walk. The n - 1 - i position is guaranteed sorted after pass i.

for each pass i:
  for j from 0 to n-2-i:
    mark [j, j+1] as comparing → yield
    if out of order:
      mark [j, j+1] as swapping → yield → swap
    reset [j, j+1] to default
  mark [n-1-i] as sorted

O(n²) comparisons. O(n²) swaps worst case. Already-sorted input: 0 swaps, but still O(n²) comparisons.

Insertion Sort

Pick each element, shift right to find its place.

export function* insertionSort(arr, colors, stats) {
  const n = arr.length;
  for (let i = 1; i < n; i++) {
    const key = arr[i];
    colors[i] = "comparing";
    yield;
    let j = i - 1;
    while (j >= 0) {
      stats.comparisons++;
      colors[j] = "comparing";
      yield;
      if (arr[j] > key) {
        stats.swaps++;
        colors[j + 1] = "swapping";
        arr[j + 1] = arr[j];      // shift right
        yield;
        colors[j + 1] = "default";
        colors[j] = "default";
        j--;
      } else {
        colors[j] = "sorted";
        break;
      }
    }
    arr[j + 1] = key;
    for (let k = 0; k <= i; k++) colors[k] = "sorted";  // ← important
    yield;
  }
}

Color tracking gotcha: during the shift phase, multiple positions change from their original values. If you mark individual positions “sorted” during the shift (rather than after placing the key), you get positions that are colored “sorted” but don’t yet hold their final value. The fix: re-mark the full sorted prefix [0..i] after each outer iteration.

O(n²) worst case, but O(n) on nearly-sorted input — good for “almost sorted” real-world data.

Merge Sort

Divide-and-conquer with a temp buffer.

function* _merge(arr, colors, stats, l, m, r) {
  const left  = arr.slice(l, m + 1);  // temp copy
  const right = arr.slice(m + 1, r + 1);
  let i = 0, j = 0, k = l;

  while (i < left.length && j < right.length) {
    stats.comparisons++;
    colors[k] = "comparing";
    yield;
    if (left[i] <= right[j]) { arr[k] = left[i++]; }
    else { stats.swaps++; arr[k] = right[j++]; }
    colors[k] = "swapping";
    yield;
    colors[k++] = "default";
  }
  // drain remainder ...
}

Delegating recursion through generators requires yield*:

function* _mergeHelper(arr, colors, stats, l, r) {
  if (l >= r) return;
  const m = (l + r) >> 1;
  yield* _mergeHelper(arr, colors, stats, l, m);      // delegate
  yield* _mergeHelper(arr, colors, stats, m + 1, r);  // delegate
  yield* _merge(arr, colors, stats, l, m, r);         // delegate
}

yield* propagates each yield up the call chain — the outer animation driver receives them all as if they came from one flat generator.

O(n log n) always. Extra O(n) space for the temp arrays.

Quick Sort

Lomuto partition scheme: pick the last element as pivot.

function* _partition(arr, colors, stats, low, high) {
  const pivot = arr[high];
  colors[high] = "pivot";   // orange color signals the pivot
  let i = low - 1;

  for (let j = low; j < high; j++) {
    stats.comparisons++;
    colors[j] = "comparing";
    yield;
    if (arr[j] <= pivot) {
      i++;
      if (i !== j) {
        stats.swaps++;
        colors[i] = colors[j] = "swapping";
        [arr[i], arr[j]] = [arr[j], arr[i]];
        yield;
        colors[i] = "default";
      }
    }
    colors[j] = "default";
  }
  // place pivot at i+1
  // ...
  return i + 1;
}

The pivot color (orange) makes it visually obvious which element is partitioning the array.

O(n log n) average, O(n²) worst case (already-sorted or all-equal input with Lomuto). Each iteration n - 1 - i elements are guaranteed sorted, so we mark colors[pivotPos] = "sorted" immediately after partition.

Testing without a DOM

Because generators are pure functions over plain arrays, the test suite is straightforward:

import { bubbleSort, insertionSort, mergeSort, quickSort, runSort } from "../sorters.js";

function run(algo, input) {
  const arr    = [...input];
  const colors = new Array(arr.length).fill("default");
  const stats  = { comparisons: 0, swaps: 0 };
  runSort(algo(arr, colors, stats));
  return { arr, colors, stats };
}

test("bubbleSort sorts a random array", () => {
  const { arr } = run(bubbleSort, [5, 3, 8, 1, 9, 2, 7, 4, 6]);
  assert.deepEqual(arr, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
});

test("all bars end as sorted", () => {
  for (const algo of [bubbleSort, insertionSort, mergeSort, quickSort]) {
    const { colors } = run(algo, [3, 1, 4, 1, 5, 9, 2, 6]);
    assert.ok(colors.every(c => c === "sorted"));
  }
});

runSort just drains the generator:

export function runSort(gen) {
  while (!gen.next().done) {}
}

46 tests, zero mocking, no jsdom.

What you can see in the visualizer

At speed 1 (slowest), watch:

  • Bubble: the sorted green grows from the right one element per pass.
  • Insertion: the sorted green grows from the left as each key finds its place.
  • Merge: wide orange bands appear at the merge boundaries; yellow comparisons happen within small segments.
  • Quick: an orange pivot jumps around; after partition, its final position lights up green and never moves again.

At speed 5 (fastest), Bubble on 120 elements becomes a blur while Merge and Quick finish in a fraction of the time — a direct visual comparison of O(n²) vs O(n log n).

TL;DR

  • Generator + yield separates algorithm logic from animation — no DOM in the sort code, no sort code in the animation driver.
  • yield* delegates yields through recursive calls, keeping the driver simple.
  • Speed changes take effect immediately because delay() reads the slider on each setTimeout callback.
  • Stop is clearTimeout — no cancellation flags needed.
  • Color tracking in insertion sort requires re-marking the full sorted prefix after each placement, not individual positions during the shift.
  • Tests run in Node without any browser or mock DOM.

Source: https://github.com/sen-ltd/sort-visualizer — MIT, ~200 lines of JS (sorters) + ~150 lines of UI, 46 unit tests, zero dependencies, no build step.

🛠 Built by SEN LLC as part of an ongoing series of small, focused developer tools. Browse the full portfolio for more.