AI 资讯
The Rusty Hobbit: Ownership System Explained for JavaScript Developers
The Quest Begins (The "Why") Hey friend, picture this: you’re happily writing a Node.js service, passing objects around like they’re candy at a parade. Everything works until one day you mutate a shared object in a helper function and suddenly your UI shows stale data, or worse, you get a mysterious Cannot read property 'map' of undefined that only appears in production. You spend hours tracing the flow, adding console.log s everywhere, and you start to wonder if there’s a hidden contract you missed. I’ve been there. I spent an entire afternoon debugging a race condition that only showed up when two async requests touched the same user profile. The fix felt like a band‑aid, and I kept thinking, “There has to be a better way to reason about who owns what.” That curiosity led me to Rust, and more specifically, to its ownership system—a set of rules that, at first glance, feels like a strict teacher with a red pen, but ends up being the most reliable compass I’ve ever had for writing safe, concurrent code. The Revelation (The Insight) Rust’s ownership model isn’t just another syntax quirk; it’s a philosophy that answers three simple questions for every piece of data: Who owns it? How long can it live? Who can read or change it while it’s alive? If you can answer those, the compiler guarantees you won’t have dangling pointers, use‑after‑free, or data races— without a garbage collector pausing your thread. For a JavaScript developer, that sounds like magic, but the rules are surprisingly concrete once you see them in action. Surprising Feature #1: Move Semantics (The “Give Away” Rule) In JavaScript, when you do let b = a; you’re copying a reference. Both a and b point to the same object, and mutating one affects the other unless you clone. Rust treats assignment differently for types that own resources (like String , Vec<T> , or custom structs). Assigning b = a moves the ownership; after that, a is considered uninitialized and you can’t use it again. let s1 = String .fro
AI 资讯
I built a local LLM that runs entirely in your browser. No install, no GPU, no server
A few months ago I got obsessed with a question: can you run a real LLM entirely inside a browser tab, with zero backend, zero GPU, and zero install? The answer is yes. Here's what I built. ghost is a single HTML file that downloads a quantized language model into your browser's cache on first visit, then runs inference locally in WebAssembly forever after. Fully offline after that first download. No API key. No npm. No build step. Open the file, pick a model, chat. How it works The inference engine is wllama — a WebAssembly binding for llama.cpp. It runs GGUF quantized models directly in the browser using WASM SIMD. I pin it to a specific version so the JS and WASM files always match (learned this the hard way after a fun debugging session involving mismatched memory imports). Models are downloaded from HuggingFace on first load and cached via the browser's Cache API. On every subsequent visit they load instantly from cache, no network needed. Features Three models: Qwen2.5 1.5B (smart), Qwen2 0.5B (fast), TinyLlama (lightweight) Markdown rendering from scratch — no library, just regex transforms RAG: drag a .txt or .pdf onto the chat window. It chunks the text, embeds each chunk using wllama's embedding API, stores vectors in memory, and retrieves the top-3 relevant chunks on each message. Fully local, fully offline Voice input via the Web Speech API — mic button auto-sends on silence Multi-turn conversation memory capped at 10 turns PWA installable — works on mobile home screen too The hard parts Getting wllama to load from a cached model was genuinely tricky. Blob URLs created in the main thread aren't accessible from wllama's internal Web Worker. IndexedDB chunk reconstruction hit a 2GB ArrayBuffer limit on Windows Chrome. The final solution was using wllama's built-in loadModelFromHF with useCache: true which handles everything internally. The embeddings API requires toggling a flag (embeddings: true) that conflicts with normal chat completion — so I toggle it
AI 资讯
The Blinking Toilet Light and My `isProcessing` Flag Were Doing the Same Job
Introduction Hello from Japan! 🇯🇵 I am a professional truck driver teaching myself Python and web development while working toward a career transition into web engineering. This article records what I learned after approximately 122 hours of programming study , starting on May 12, 2026. Recently, I added a ripple animation effect to the answer buttons in my self-developed application: 🚛 DPT — Driver Personality Test https://qiita.com/tosane932/items/220d0f7d36bd79b2aa81 At first, I thought it would be a small visual improvement. However, while implementing it, I realized that the blinking light on my toilet control panel and a JavaScript flag named isProcessing were performing exactly the same role. This article explains that connection. It Started as Protection Against Repeated Clicks In DPT, clicking an answer button moves the user to the next question. In the original version, the next question appeared immediately after the button was clicked. However, this created a problem. If a user repeatedly clicked the button, the application continued advancing through the questions at the same speed. In an extreme case, someone could finish all 50 questions in only a few seconds. That would reduce the reliability of the personality test and could also create invalid answer records. To prevent this, I introduced a processing-state flag . let isProcessing = false ; testContainer . addEventListener ( " click " , ( event ) => { if ( isProcessing ) return ; const button = event . target . closest ( " .option-btn " ); if ( ! button ) return ; isProcessing = true ; createRipple ({ currentTarget : button , clientX : event . clientX , clientY : event . clientY }); const qIdx = Number ( button . dataset . qIndex ); const oIdx = Number ( button . dataset . oIndex ); setTimeout (() => { if ( oIdx === - 1 ) { handleAnswer ( qIdx , - 1 , " No answer " , 0 ); } else { const option = shuffledQuestions [ qIdx ]. shuffledOptions [ oIdx ]; handleAnswer ( qIdx , oIdx , option . text , optio
AI 资讯
Accessibility Semantics: The UI Tree You Cannot See
Accessibility has become personal for me. I am getting older, and large type is no longer an abstract preference somebody else needs. It is how I read a phone comfortably. What is Codename One? Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at codenameone.com . I worked with accessibility experts at Sun Microsystems and learned how deep the problem goes. A label is the easy part. Real accessibility needs roles, values, ranges, actions, traversal order, live announcements, collections, focus, platform conventions, and a way to test all of it. That complexity is why full Codename One accessibility support sat dormant for a decade. We eventually added setAccessibilityText() . It was useful, but it was the poor man's version. PR #5363 replaces that single-label model with a portable semantics tree we can be proud of. Lightweight UI needs a second tree Codename One paints lightweight components into its own native surface. VoiceOver cannot inspect a Button as a UIKit button because there is no UIKit button there. TalkBack cannot walk an Android View hierarchy because most of the painted controls are not Android views. The new accessibility manager builds an immutable virtual tree beside the visual component tree. Standard controls infer their semantics. Custom controls can replace or extend them. Each port exposes that virtual tree through the platform accessibility API. The visual and semantic hierarchies can differ. A card made from five labels might need to read as one item. A chart may paint 200 points from one component, but expose each meaningful point as a virtual child. A renderer-backed list can expose stable rows even though those rows are not component instances. Standard components work without annotations Buttons, checkboxes, radio buttons, sliders, text fields, lists, tables, tabs, labels, dialogs, and containers infer their normal roles, values, states, and
开源项目
🔥 calesthio / Crucix - Your personal intelligence agent. Watches the world from mul
GitHub热门项目 | Your personal intelligence agent. Watches the world from multiple data sources and pings you when something changes. | Stars: 10,756 | 257 stars this week | 语言: JavaScript
开源项目
🔥 faroukbmiled / RyukGram - RyukGram, The Instagram tweak for iOS power users.
GitHub热门项目 | RyukGram, The Instagram tweak for iOS power users. | Stars: 367 | 11 stars today | 语言: JavaScript
开源项目
🔥 jenkinsci / jenkins - Jenkins automation server
GitHub热门项目 | Jenkins automation server | Stars: 25,805 | 179 stars today | 语言: Java
开发者
Java News Roundup: Simple JSON API, JEPs for JDK 28, Oracle CPUs, Embabel 1.0, Azul Payara, Helidon
This week's Java roundup for July 20th, 2026, features news highlighting: two JEPs proposed to target for JDK 28; new JEPs 540 and 541, Simple JSON API (Incubator) and Deprecate the macOS/x64 Port for Removal, respectively; the July 2026 Critical Patch Update; the GA release of Embable 1.0; Azul Payara 7.2.0; and Helidon 4.5.1. By Michael Redlich
AI 资讯
Claude Code, Bun and TypeScript
Why Claude Code runs on Bun: runtime tradeoffs in TypeScript CLI tooling Anthropic recently shipped Claude Code — their agentic CLI coding assistant — on Bun instead of Node.js. For most product announcements, the runtime choice would be a footnote. Here it's worth unpacking, because the tradeoffs Anthropic navigated are exactly the ones you hit when building or evaluating TypeScript-heavy developer tooling: startup latency, bundling strategy, native module compatibility, and what "good enough" dependency management actually looks like in 2025. This isn't a Bun vs. Node benchmarking post. It's an examination of why the decision makes sense for a CLI tool specifically, what it signals about the broader ecosystem, and where the tradeoffs still bite you. Why runtime choice matters more for CLIs than for servers For a long-running server process, Node.js startup cost of 50–150ms is irrelevant — you pay it once. For a CLI invoked dozens of times per development session, cold-start latency is a first-class UX concern. Bun's startup time is consistently in the 5–15ms range for a simple script. Node.js lands closer to 50–80ms before your first line of application code runs. That delta is imperceptible in a single invocation. Run a CLI 30 times in a session and you've saved a couple of seconds — more importantly, you've removed the subjective sense of lag that makes a tool feel heavy. This is the same reason Deno has gained traction in scripting contexts despite losing the server-side battle to Node. Fast startup is a feature, and for AI-assisted tooling where the human is waiting in a tight feedback loop, it matters. Bun's bundler as a distribution primitive Bun ships a first-party bundler. For a CLI, this is significant. The standard Node.js distribution story for a TypeScript CLI involves: Compile TypeScript with tsc or esbuild Bundle with esbuild or rollup to collapse the dependency graph Either ship node_modules (large, fragile) or use a tool like pkg or nexe to produce
AI 资讯
Migrating a Rich Text Editor : CKEditor 5 to SynapEditor (with code)
Disclosure: I work on the team behind SynapEditor. 🧩 TL;DR: Moving from CKEditor 5 to SynapEditor is a one-to-one swap in three steps: installation, toolbar config, and content/event APIs. The main reason to consider it is Office document fidelity (Word, PowerPoint, Excel import/export). Full runnable example at the end. Switching rich text editors sounds like a big job, but most of the work is a straightforward, one-to-one swap. This guide walks through moving an existing CKEditor 5 integration over to SynapEditor: loading the library, wiring up the toolbar and content APIs, and a complete working example you can copy and run. ⚖️ Which is better: CKEditor or SynapEditor? Both CKEditor and SynapEditor are mature, capable editors. If you already have CKEditor running, it clearly does a lot right. So the question isn't really "which is better" in the abstract, it's which one fits where your product is heading. Two things tend to drive the decision: 📜 Licensing and support. CKEditor 4 reached end of life in 2023, and security fixes now sit behind a paid Extended Support agreement. If you're revisiting the integration anyway, it's a natural moment to reconsider the editor itself. 📄 Office documents. This is where SynapEditor differs most. It imports a broad range of office formats: MS Word (.doc, .docx), PowerPoint (.ppt, .pptx), Excel (.xls, .xlsx, ODT, and HTML, and exports back to Word (.docx) with formatting preserved. If your users upload real documents and expect the layout to survive, that's worth weighing. CKEditor 5 SynapEditor Core editing ✅ ✅ CKEditor 4 still supported Paid ESM only n/a Word / PPT / Excel import-export Limited ✅ Native With that out of the way, let's migrate. 📋 What you'll need [ ] An existing CKEditor 5 integration [ ] A SynapEditor license and API key (free at Get Started ) [ ] About 15 minutes for a basic swap ⚙️ 1. Installation CKEditor 5 loads from a single script. SynapEditor loads from a script and a stylesheet: the UI is styled by tha
AI 资讯
I built a guard that refused to read the user's tab. Then my own cleanup code closed it.
Three days ago my browser automation tool closed one of my own tabs. Not a tab it had opened — a dashboard I had open in another window, with a page I hadn't finished reading. What makes it worth writing up isn't the bug. It's that the guard designed to prevent exactly this had already fired, correctly, ninety seconds earlier. The guard worked Safari MCP lets an AI agent drive your real, logged-in Safari. That premise means the single worst thing it can do is act on a tab you're using. So there's an identity system: every tab the tool opens gets a marker stamped into window.name , which survives navigation, redirects, and cross-origin loads. Before running anything in a tab, the tool checks the marker. I was filling in a form. The URL was a forms.gle shortlink, which 302s to docs.google.com — a cross-origin redirect that, it turns out, drops window.name . My next read came back refused: Tab tracking lost — refusing to target the user's current tab. Correct. Exactly the intended behaviour. The tool no longer knew which tab was its own, so it declined to guess. So I did the tidy thing and cleaned up my orphaned tab: safari_close_tab It closed a different tab. One of mine. The tool went from "I can't prove which tab is mine, so I won't read" to "let me close a tab" in one step, and nobody stopped it. The shape of the hole Here is the close path as it existed: if ( _st (). activeTabIndex ) { await osascript ( `... close tab ${ _st (). activeTabIndex } of ${ window } ` ); } else { await osascript ( `... close current tab of ${ window } ` ); // ← the user's tab } current tab of window is whatever the user is looking at. So the fallback for "I don't know which tab is mine" was "close theirs." That branch is only reachable when the index is unknown — which is precisely the state the guard had just announced. The two pieces of code were describing the same condition and disagreeing about what it meant. Three layers, one mistake When I went looking, the same fail-open was in
AI 资讯
I Built 47 Free Dev Tools That Run Entirely in Your Browser
Every developer has done it — copy-pasted a JWT, a private key, or a JSON blob with sensitive data into some random website and held their breath. Wondering if it was being logged, tracked, or worse. Every developer has done it — copy-pasted a JWT, a private key, or a JSON blob with sensitive data into some random website and held their breath. Wondering if it was being logged, tracked, or worse. I built KRUMB.DEV because I wanted tools that didn't make me feel dirty after using them. What Is It? 46 developer tools, all in one place. No signup. No uploads. No tracking. Open source. The terminal-inspired interface isn't just aesthetic — it's a constraint. Every tool fits in a single column, zero sidebar, zero popups. Just you and the tool. What's Inside Formatters — JSON, SQL (17 dialects), HTML, JavaScript, CSS Encoders — Base64, URL, JWT decoder, YAML↔JSON, JSON↔CSV Generators — Passwords, UUIDs (v1/v3/v4/v5), hashes (MD5/SHA/HMAC), QR codes, Lorem Ipsum, color palettes, CSS gradients/shadows/grids, meta tags, robots.txt, .gitignore Testing & Debugging — Regex tester, diff checker, webhook tester, cURL→code, HTTP status reference, cron expression builder Converters — Unix timestamps, hex↔RGB, binary, SVG→JSX, JSON→TypeScript, HTML playground, markdown editor Network — DNS lookup, SSL checker, IP lookup, QR code decoder, IBAN validator Why I Built It This Way Most "free" dev tools follow the same pattern: create an account, hit a rate limit, and wonder if your data is being stored somewhere. KRUMB.DEV flips that: Everything runs in your browser — JSON, JWT, source code, passwords never touch a network request Zero accounts — open the page, use the tool, leave. No signup wall between you and the output Clean interface — ⌘K opens a command palette to jump to any tool in seconds Open source — MIT license, deploy your own if you want The Tech Next.js, TypeScript, and Tailwind. Static-first, client-side execution for all core tools. Server routes exist only for DNS/SSL l
AI 资讯
Title: How to Automate A4 Batch ID Card Printing in React (Without a Backend)
The Nightmare of HTML-to-PDF in React If you’ve ever built a School ERP, HR portal, or Event Management system, you’ve probably hit this exact wall: Your client needs to print 5,000 ID cards or badges. Usually, this forces frontend teams to do one of two terrible things: Pay for an expensive backend PDF generation API (which raises huge GDPR/privacy concerns because you have to send sensitive employee photos to a 3rd-party server). Force the non-technical HR team to manually type names into Canva, crop photos, and manually drag them onto an A4 grid (an 80-hour manual data entry nightmare). I got tired of rebuilding complex html2canvas and jsPDF calculators from scratch for every project. So, I decided to automate the entire pipeline natively in the browser. Enter @stratametriq/id-card-designer — an open-source, turnkey drag-and-drop ID card studio and A4 mathematical rendering engine for React. What it does out of the box: Instead of building a canvas from scratch, you install this NPM package in one line of code. It gives your end-users a complete visual dashboard directly inside your own application. Here is a 60-second video of how it looks running in a live production environment: 👉 https://youtu.be/l9aXWqRSFCM?si=nEIaaqsxypmzCflm The Core Features: Dynamic Handlebars Data Binding Your users can design a visual template and drop in tags like {{studentName}} or {{employeeId}}. Our engine automatically binds these variables to your live database array. No manual typing required. Scannable Barcodes & QR Codes We built native QR and Barcode generators directly into the canvas. You just pass the ID string, and the engine renders a scannable vector code instantly. The Magic Moment: Precision A4 Batch Matrix When your HR admin selects 500 employees and hits "Batch Print", the real magic happens. Our client-side mathematical matrix calculates exact millimeter dimensions—arranging exactly nine PVC cards perfectly on standard A4 cut-sheets, complete with professional 0.35
AI 资讯
React useDeepCompareEffect: Fix useEffect Object Dependencies (2026)
React useDeepCompareEffect: Fix useEffect Object Dependencies (2026) You wire up a fetch. The endpoint takes a query object, so you pass it in the dependency array. The effect fires, sets state, the component re-renders, the query object is rebuilt — a brand-new object with identical contents — and the effect fires again. You have written an infinite loop, and React thinks it did exactly what you asked. function Results ({ term , page }: Props ) { const [ rows , setRows ] = useState ([]); const query = { term , page , sort : ' desc ' }; // new object, every render useEffect (() => { fetchRows ( query ). then ( setRows ); // setRows → re-render → new query → 🔁 }, [ query ]); } useDeepCompareEffect from @reactuses/core is a drop-in replacement for useEffect that compares dependencies by value instead of by reference. Same signature, same cleanup semantics — the effect just stops firing when nothing actually changed. Everything below is the real implementation, TypeScript-first, including the parts that cost you something. Why useEffect Can't See It React compares dependency arrays with Object.is , element by element. For primitives that's exactly what you want: 5 is 5 , 'desc' is 'desc' . For anything with an identity — objects, arrays, Date s, Map s, functions — it compares the reference , and a literal written inside a component body produces a fresh reference on every single render: Object . is ({ term : ' react ' }, { term : ' react ' }); // false — different objects So the dependency "changed" on every render, by React's definition. This isn't a bug in useEffect ; reference equality is the only comparison that's O(1), and React runs it on every render of every component. The cost of value comparison is real, and React declines to pay it on your behalf. Which leaves you paying it — one way or another. The Usual Workarounds, and Where They Fray Memoize the object. Correct, and the right answer when there's one dependency: const query = useMemo (() => ({ term , page
AI 资讯
Stop Using `useEffect` for Data Fetching—Please, I Beg You
The Scene It's 2 AM. You're staring at your screen, debugging why your dashboard keeps showing yesterday's data even after you've changed the filter. Your useEffect dependency array looks like a crime scene. You've got three useState hooks just to manage loading, error, and data. You added a cleanup function, but somehow the component still throws that dreaded warning: "Can't perform a React state update on an unmounted component." You take a sip of cold coffee. You wonder where it all went wrong. The Problem with useEffect for Data Fetching Let's be honest with ourselves. useEffect was never designed for data fetching. The React team gave us this hook to synchronize with external systems, DOM events, subscriptions, and timers. But somewhere along the line, we collectively decided to use it as our go-to tool for API calls. And look, I get it. When you're learning React, the pattern is simple: useEffect (() => { const fetchData = async () => { setLoading ( true ); const response = await fetch ( ' /api/users ' ); const data = await response . json (); setUsers ( data ); setLoading ( false ); }; fetchData (); }, []); It works. Until it doesn't. Here's what happens when your application grows: Race Conditions — When your user clicks filters too quickly, old requests return after newer ones and override your state. The UI shows mismatched data, and you waste hours adding request cancellation logic that nobody on your team fully understands. Unnecessary Re-renders — Every state update triggers a re-render. With useEffect , you're juggling at least three states: data , loading , and error . Three states, three renders, even before React mounts your actual content. Poor Caching — If a user visits a page, leaves, and comes back, your useEffect fires again. Same data, same API call, same network cost. Multiply this by a thousand users, and you're burning your backend for no good reason. Manual Cleanup Headaches — Need to cancel pending requests? Need to prevent state updates
AI 资讯
Widgets, Live Activities, and Dynamic Island From One Java API
Widget support was one of the earliest Codename One requests. We dismissed it for years because a widget must render while the application UI is not running. A normal Codename One Component needs the application renderer, event dispatch thread, and live object graph. A home-screen widget gets none of those. What is Codename One? Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at codenameone.com . The missing piece had been under our nose for a decade. Steve added background processes so an app could refresh data without showing its UI. That solves the update side. The rendering side becomes possible once the widget is data rather than a live component. PR #5365 turns that observation into com.codename1.surfaces , one API for home-screen widgets, Live Activities, Dynamic Island, Android ongoing notifications, and desktop floating widgets. The dead-process rule An external surface is a piece of application state that the operating system can render outside the app. The app publishes a serializable layout and a timeline of state maps. The platform persists that data, then renders it with its own surface technology. You cannot attach a Java listener to a widget. There may be no Java process to invoke. You assign a string action ID instead. A tap launches the app and delivers that action after startup. The simulator implements the same model. Open Widgets > Widgets Preview to inspect every registered kind, move through its timeline, change size and appearance, and click actions without creating a device build. Widget kinds exist at build time iOS and Android compile widget galleries into the native application. The kinds must therefore be known during the build. Add a surfaces.json resource: { "liveActivities" : true , "kinds" : [ { "id" : "delivery_status" , "name" : "Delivery" , "description" : "Track your order" , "iosFamilies" : [ "systemSmall" , "systemMedium" ] } ] }
AI 资讯
The 50KB Problem: Why Government Forms Keep Rejecting Your Photo
There's a deceptively simple bug hiding in plain sight on almost every government form, university portal, and job application site: "Upload a photo under 50KB." No API, no error message explaining why, no tolerance — just silent rejection if you're 2KB over. It sounds like a trivial constraint until you actually try to satisfy it programmatically. File size in bytes isn't a variable you can set directly; it's a derived value — a function of pixel dimensions, image entropy, and compression quality — which makes "resize this to exactly 51,200 bytes" a surprisingly nontrivial optimization problem, not a one-line canvas.toBlob() call. A few months ago, my cousin ran into this on a state exam portal that capped passport photos at 50KB. She spent two hours bouncing between random "photo compressor" sites, most of which just apply a fixed compression ratio and let you deal with whatever number comes out. None of them actually solve for a target size. By the time she landed on something that worked, the registration window had closed for the day. So here's the actual technical problem underneath this UX annoyance — and how to solve it properly instead of guessing quality percentages by hand. It's Not You. File Size Is Genuinely Unpredictable. Here's the thing nobody tells you: file size in kilobytes isn't something you can just "set." It's the result of several things happening at once — how detailed the image is, what dimensions it's saved at, and how aggressively it's compressed. Change any one of those, and the final number shifts unpredictably. A plain white background compresses down to almost nothing. A busy, detailed photo — a face with visible texture, a signature with lots of fine ink strokes — resists compression much harder, because there's more actual information in the pixels. Two photos that look similarly sized on your screen can land at wildly different file sizes once compressed, simply because of what's in them. Then there's the format problem, which trip
开源项目
🔥 gchq / CyberChef - The Cyber Swiss Army Knife - a web app for encryption, encod
GitHub热门项目 | The Cyber Swiss Army Knife - a web app for encryption, encoding, compression and data analysis | Stars: 35,443 | 77 stars this week | 语言: JavaScript
开源项目
🔥 OtterMind / Chat2DB - 🔥🔥🔥 AI-driven database tool and SQL client, The hottest GUI
GitHub热门项目 | 🔥🔥🔥 AI-driven database tool and SQL client, The hottest GUI client, supporting MySQL, Oracle, PostgreSQL, DB2, SQL Server, DB2, SQLite, H2, ClickHouse, and more. | Stars: 26,906 | 399 stars today | 语言: Java
AI 资讯
PhilBuilder vs voltbuilder
The problem Every time I needed to hand someone a quick installable build — a client, a tester, myself on a different machine — I had to either keep a full local toolchain ready (Android Studio, Flutter SDK, Visual Studio...) or spend 20 minutes reinstalling one just for a single build. So I built PhilBuilder : upload a zipped source project, pick a platform, get back an installable app. No local setup required. 🔗 Try it: https://philbuilder.netlify.app What it does You upload a .zip of your project. The tool: Auto-detects the project type (React, Vue, Flutter, React Native, Kotlin, .NET MAUI, Python, Go, Godot, and 14 others — 22 combinations total) Builds it on remote CI Gives you a download link for an APK, AAB, or Windows .exe No account needed for occasional use (3 builds/day). A free account bumps that to 10/day. How it's built The stack is intentionally simple: Frontend : a single static HTML file, no framework, no build step Backend : a Cloudflare Worker handling auth, rate limiting, and dispatching builds Build execution : GitHub Actions — one big workflow with per-language jobs (Cordova for web frameworks, Capacitor for modern web, native Gradle for Kotlin/Java, dotnet publish for MAUI, flutter build for Flutter, briefcase for Python, gomobile for Go, etc.) Storage : Cloudflare R2 for source zips and build artifacts The auto-detection logic walks the extracted zip looking for telltale files — pubspec.yaml → Flutter, *.csproj → .NET MAUI, capacitor.config.* or a @capacitor/core dependency → Capacitor, build.gradle without package.json → native Kotlin/Java, and so on — with fallbacks down to plain HTML. Some technical details Signing : for Android release builds, it can auto-generate a keystore (and let you download it afterward — losing it means you can never update your app on Play Store again, so this is clearly flagged) or accept an uploaded one. Windows builds : this is the newest addition. Flutter and .NET MAUI need windows-latest runners; Go cross-com