Pixel

Pixel Updates & Events

Server Updates & Events

Announcements

GitHub Actions always() After Cancellation Explained

DEV Community

GitHub Actions if: always() runs a step after cancellation

GitHub Actions if: always() is a function that returns true and checks nothing, so a step guarded with it starts after somebody presses cancel. The companion belief, that adding any condition removes the built-in success check, is the opposite of what the parser does, and it is worth unlearning before you add success() to conditions that never needed it. The grid on the left is the table reproduced in the body of this page. The rewrite on the right is quoted from ConvertToIfCondition in actions/runner. What this error means Nothing here is an error message. The symptom is a step that ran when you did not expect it to, and the bill or the side effect that followed. Somebody cancels a run halfway through and a deployment still goes out, or a cleanup job tears down an environment that the next run needed, or a notification announces a failure for a run a person deliberately stopped. In the log the step is green and the run conclusion is cancelled, which is an unusual combination and the clearest sign that this is what you are looking at. The second half of the problem produces the mirror image: a step that never runs after a failure, because somebody added success() to a condition that already had it. .github/workflows/ci.yml (illustrative) name: Upload logs if: always() # true in every state, cancellation included run: ./upload-logs.sh name: Notify if: ${{ !cancelled() }} # every state except cancellation run: ./notify.sh Common causes always() was used to mean success or failure The function name reads like natural language and the intention is almost always narrower than what it does. Teams reach for it on artifact uploads, notifications and teardown steps, and it works until the first time somebody cancels a run, at which point the teardown runs against a half-finished environment. A status function was added that was already there The mirror mistake, and it is caused by the belief that any condition drops the gate. Writing success() next to a branch test changes nothing, because the parser would have added it. Writing failure() next to one changes everything, because now the step runs only after a failure. The negated form was written without braces A leading exclamation mark is YAML tag syntax, so the bare form is rejected at parse time rather than misbehaving at run time. It looks like a problem with the function when it is a problem with the quoting. A job condition was copied from a step condition The two positions allow different contexts. A job-level condition accepts the four status functions but not steps, env, job, runner or the file-hashing function, so a condition that works on a step can be rejected outright when it is moved up to the job. How to fix it Say which states you want, then pick the expression Write down whether the step should run after success, after failure, and after cancellation. Success and failure but not cancellation is the negated cancellation check, in braces. Failure only is failure(). Success only is nothing at all. All three, including cancellation, is always(), and it should be rare. Replace always() on anything with an external effect Deployments, teardown, cloud resources and anything that posts to a third party are the steps where cancellation should mean stop. Swap the function and leave the rest of the step alone..github/workflows/ci.yml (illustrative) name: Tear down preview environment if: ${{ !cancelled() }} run: ./teardown.sh Stop adding success() to ordinary conditions A condition with no status function in it is already gated. Removing the redundant call makes the expression shorter and makes the ones that really do override the gate visible in a diff..github/workflows/ci.yml (illustrative) name: Deploy to production if: github.ref == 'refs/heads/main' run: ./deploy.sh ``` Combine a status function with your own test explicitly When you do want a step to run after a failure and only in some cases, name both halves. The status function is what turns the gate...

Powered by Readybot.io

GitHub Actions always() After Cancellation Explained

ProjektSUBRO: Automating Insurance Claim Recovery

DEV Community

ProjektSUBRO

This is a submission for the MLH x DEV Writing Challenge. What I Built ProjektSUBRO connects upfront funding with automated recovery of denied insurance claims. Healthcare providers need cash for staff, supplies, and patient care—even while insurers review payments for care already delivered. Why this matters Nearly $18 billion spent by U.S. hospitals fighting claim denials in 2025. AHA 41.2% of rural hospitals operated at a loss, and 417 were vulnerable to closure, according to Chartis’ 2026 analysis. Chartis 68.6% of denied claims were eventually paid in Premier’s survey of 2023 claims—but providers averaged three review rounds, each taking 45–60 days. Premier SUBRO prices denied claims, simulates upfront funding, and runs recovery workflows. Evidence, agent actions, and financial outcomes stay visible in one workspace. $$ \text{Expected profit} = (\text{Collectible} \times P(\text{recovery})) - \text{Advance} - \text{Costs} $$ The demo uses 120 synthetic claims. No real patient data or money. Recovery probabilities are assumptions. Demo Try SUBRO live Open Full recovery, inspect the claim, and approve the simulated advance. Follow the recovery run, then check Ledger to compare the forecast with the recorded outcome. Other scenarios demonstrate missing evidence, expired deadlines, repeated rejection, conflicting records, and partial payment. Partner Technologies The current demo does not integrate MLH partner APIs. I built it with Next.js, React, TypeScript, Tailwind CSS, Recharts, and Vercel. Deterministic functions handle pricing; a bounded recovery agent supports optional AI-assisted appeal drafting. Browser localStorage preserves the sandbox. Hackathon Experience I built SUBRO for SanD Hacks Fall 2026. The hardest parts were preventing duplicate transactions, separating forecasts from outcomes, and deciding when recovery should stop. My biggest takeaway: trustworthy automation depends on structured records, evidence, and guardrails. That shaped the audit trail and ledger—each action needed enough context to explain what happened and how it affected the money.

Powered by Readybot.io

ProjektSUBRO: Automating Insurance Claim Recovery

Why AI Designs Keep Using Purple: Color Trends Explained

DEV Community

AI Loves Purple: Most and Least Used AI Colors

Ask any AI coding tool for a landing page and watch what happens. A dark hero section. A glowing card or two. A button that fades from indigo into purple. Ask again with different words and you often get the same thing. If you have noticed this, you are not imagining it. Developers have been joking about "the purple problem" for more than a year, and the creator of Tailwind CSS publicly apologized for his part in it. But the jokes hide a more useful question: which colors does AI actually use the most, which does it use the least, and what can you do about it when you ship real products? This guide breaks down the colors most used by AI, where they come from, and the exact steps to replace them in your own code. We answer that with a clear line between three kinds of evidence: what was actually measured, what is well documented history, and what is designer observation. Then we give you code you can use today to find and replace AI default colors in your own projects. The Short Answer Most used by AI: Blue, indigo, violet and purple, often as gradients. Stock Tailwind swatches. White or near white backgrounds with slate grays. Dark backgrounds with glowing purple or blue accents. Least used by AI: In the one academic study we found that counted colors, pink and black were the least frequent. Designers also point to warm and earthy colors as rare in AI output, such as deep greens, ochre, amber, terracotta and warm off whites. That second group comes from observation, not a published count. NOTE — How we graded the evidence We could not find a large, independent census of every color AI tools produce. Anyone who gives you exact percentages for "AI's favorite color" is guessing. What exists is a mix of a small study, a well documented origin story and a lot of consistent practitioner experience. Each claim below is labelled. Colors Most Used by AI: The Defaults You See Everywhere Color pattern Where it shows up Evidence strength Indigo and violet buttons Primary buttons, links, focus rings Strong: documented origin, widely reported Blue to purple or pink to purple gradients Hero sections, headline text, buttons Strong: widely reported by designers Blue as the default scheme Generic sites when no scheme is given Measured in a 2026 study Dark background with neon purple glow SaaS and AI product landing pages Moderate: designer observation Pure white backgrounds and slate grays Cards, sections, body text Moderate: appears in AI slop checklists Pure black body text Paragraphs and headings Moderate: appears in AI slop checklists 1. Indigo and violet: the signature The most famous AI color is the indigo to violet range. The origin story is unusually well documented. In August 2025, Tailwind CSS creator Adam Wathan posted on X: "I'd like to formally apologize for making every button in Tailwind UI bg-indigo-500 five years ago." He acknowledged that this default had led to AI generated interfaces coming out indigo. The mechanism people describe is simple. Tailwind's examples and templates used indigo as the primary color, and that convention is plain to see in the Tailwind color palette documentation. Developers copied those examples into tutorials and open source projects. Models trained on that code learned that "modern web design" and "indigo button" go together. A word of caution: that causal chain is plausible but untested. Nobody has published an analysis of the training data showing how often indigo appears. The Tailwind history is fact. The idea that it is the main cause is a well supported hypothesis. 2. Gradients in the blue to purple range Several design write-ups call the indigo to purple gradient the loudest tell of AI generated design. Gradient headline text, gradient "Get started" buttons and soft aurora backgrounds behind a dark hero are the usual forms. Pink to purple gradients that trended around 2020 and 2021, and blue to indigo hero looks associated with the Linear aesthetic, also feed into this pool of common p...

Powered by Readybot.io

Why AI Designs Keep Using Purple: Color Trends Explained

Free Sitemap & Indexing Tool for Developers

DEV Community

Build a Free Sitemaps & Indexing Utility: Streamline Your Website Structure for Developers

Hi everyone! 👋 As developers and site administrators, we often spend a lot of time structuring our web properties, handling configurations, and making sure search engines can easily crawl and index our pages. Having clean, fast, and accessible tools right in the browser can save hours of repetitive work. Recently, I built and published a handy utility page designed specifically to help streamline sitemap management and quick generation tasks: Sitemap & Indexing Tool. Why I Built This Utility When managing static sites, blogs, or custom web projects, generating a quick structural template or mapping out URL directories shouldn't require complex CLI setups every single time. I wanted something lightweight, client-side, and instantly accessible. Key highlights of the tool include: Clean Structure: Generates well-formatted XML/HTML sitemap blocks instantly. Client-Side Efficiency: Runs directly in your browser with zero bloat or complex dependencies. Copy-to-Clipboard Functionality: Easily copy generated snippets with a single click to plug right into your project files. A Quick Look at the Code Logic If you're curious about how it handles the dynamic clipboard copying on the frontend, it uses a straightforward, modern JavaScript Clipboard API implementation:
javascript
function copySnippet() {
const codeElement = document.getElementById('sitemapCodeText');
const textToCopy = codeElement.innerText;

navigator.clipboard.writeText(textToCopy).then(() => {
    console.log('Successfully copied to clipboard!');
}).catch(err => {
    console.error('Failed to copy text: ', err);
});

}

Try It Out & Contribute Ideas
You can test the live tool directly here: 👉 Sitemap & Indexing Tool.

I built this as part of a broader set of web utilities on my site seobasil. If you have any feature requests, optimizations, or suggestions on how to make developer utility scripts faster and more responsive, drop a comment below. I’d love to hear your feedback!

Happy coding! 🚀

Powered by Readybot.io

Free Sitemap & Indexing Tool for Developers

AWS Lambda MicroVMs for Secure AI Code Sandboxes

DEV Community

Lambda MicroVMs: run AI code safely in a sandbox

Lambda MicroVMs are the building block I had been waiting for to solve one very specific problem: running code that I did not write myself. An AI assistant suggests a solution, an agent wants to test it, a learning platform lets beginners write their first loops. Simply running that code inside your own Lambda function would be reckless. Operating a container cluster just for sandboxes, on the other hand, feels a lot like “servers are back”. On November 11, 2026, AWS added x86 support to MicroVMs, and that was my reason to take a thorough look. In this article we build a small code sandbox together: one MicroVM per learning session, controlled by a Lambda function that you deploy with the Serverless Framework. On top of that you get my honest take on where the model shines and where you are better off sticking with regular functions. In short Lambda MicroVMs start an isolated VM with its own HTTPS endpoint from a snapshot. It lives for up to eight hours and is put to sleep when idle. This lets you build sandboxes for AI-generated or third-party code without running servers. The biggest pitfall: outbound internet access is open by default, and for untrusted code you have to close it deliberately. What you will learn in this article What Lambda MicroVMs are and how they differ from Lambda functions How the snapshot, the /ready hook and the /run hook work together How to build a code sandbox step by step with TypeScript and the Serverless Framework Which security settings are mandatory for untrusted code What a session costs What are Lambda MicroVMs? A MicroVM is a very lightweight virtual machine. AWS builds it on Firecracker, the same virtualization technology that regular Lambda functions run on. What is new is that you now control the VM yourself: you start it, it gets its own HTTPS endpoint, it keeps its memory and disk state, and when it is idle it is put to sleep instead of being thrown away. The trick behind this is the snapshot. When you create a MicroVM image, Lambda runs your Dockerfile, starts your application and waits until it reports that it is ready. Then Lambda freezes the complete state of memory and disk. Every new MicroVM starts from this frozen state, so your application does not have to boot again. If you know SnapStart, you will recognize the principle. Two more terms come up all the time. Suspend means the VM is paused, memory and disk are kept, and you only pay for storing the state. Resume wakes it up again, automatically on the next request if you want. In addition, there are hooks: HTTP endpoints in your application that Lambda calls at fixed points in the lifecycle, for example /ready during the build or /run when a new VM starts. Lambda function or MicroVM: the difference Lambda function Lambda MicroVM Invocation event or invoke, Lambda distributes the load own HTTPS endpoint per VM, no load balancer Duration at most 15 minutes per invocation up to 8 hours including suspend State ephemeral, no guarantee between invocations memory and disk survive a suspend Isolation execution environment is reused for invocations of the same function one VM per session, job or tenant, full operating system Billing per request and duration baseline size per second while RUNNING, storage cost while suspended Bottom line: functions are built for many short, similar invocations. MicroVMs are meant for longer, stateful sessions in which someone (or something) brings code you do not trust. When are Lambda MicroVMs worth it? Among other use cases, AWS lists sandboxes for AI code, interactive development environments, notebooks, security scans and multi-tenant CI/CD runners. I would make the decision with three questions. First: are you running code you do not control? If yes, a dedicated VM per session is the cleanest boundary you can get with serverless. Second: does the session need state for minutes or hours, for example installed packages, an open database connection or a conversation with an agent? That is where su...

Powered by Readybot.io

AWS Lambda MicroVMs for Secure AI Code Sandboxes

uv 0.13 Installs 6,000-File Packages 32% Faster

DEV Community

uv 0.13's tar-codec backend installs a 6,000-file sdist in 2.5 seconds instead of 3.8

I built a source distribution with 6,000 small files in it and installed it with uv pip install. With uv 0.13.6, the median time across five runs was 2.53 seconds. With the same binary told to use the old extraction code, the median was 3.55 seconds. With the actual pre-change release, 0.12.8, it was 3.76 seconds. That's the headline, and it's a real number from a real install, not a microbenchmark of tar extraction in isolation. The part I didn't expect going in was what happened when I fed the new backend a source distribution with a character device node baked into it. The old backend installed it anyway and silently threw the device file away. The new one refuses the whole install and tells you exactly why. uv is Astral's Rust-based replacement for pip, venv and friends. Version 0.13.0, released on 9 October 2026, swapped the library it uses to extract .tar.gz archives from astral-tokio-tar to a newer one called tar-codec. The release notes say the new backend "applies stricter validation when reading archives" and warns that archives with hard links or unsupported tar extensions that older versions accepted may now be rejected. There's an escape hatch: setting UV_LEGACY_TAR_BACKEND=1 restores the old code path. That one environment variable made this easy to test properly, because I could hold the binary constant and flip only the backend. The install-time number I generated a package called bigpkg with 6,000 text files totalling 48MB uncompressed (36MB as a gzipped tarball), gave it a trivial pyproject.toml using the setuptools backend, and installed it with --no-deps into a fresh virtual environment each time, with a fresh cache directory each time. Five runs per configuration: Configuration Run times (s) Median Fastest uv 0.13.6, default (tar-codec) 2.50, 2.52, 2.53, 2.61, 2.75 2.53s 2.50s uv 0.13.6, UV_LEGACY_TAR_BACKEND=1 3.07, 3.46, 3.55, 3.76, 4.84 3.55s 3.07s uv 0.12.8 (pre-change release) 3.21, 3.42, 3.76, 3.93, 5.12 3.76s 3.21s That's a 29% drop in median time against the same binary with the old flag set, and 33% against the actual old release. The fastest-run comparison is less dramatic — 2.50s against 3.07s and 3.21s, around 18-22% — because the slowest outliers in the legacy runs inflate the median more than the minimum. Either way the direction is consistent across all fifteen runs: tar-codec never lost to the legacy path on this workload. I deliberately used a package with many small files rather than one large file, because that's where tar's per-entry overhead shows up. When I first tried this with a package containing 400 files and 8MB of data, the three configurations landed within noise of each other — 0.96 to 2.6 seconds with no clear ordering. The fixed cost of spinning up a Python subprocess to run the setuptools build backend was swamping the extraction time. Scaling up to 6,000 files is what made the effect visible. If you're testing this yourself and see "no difference," that's very likely your workload being too small, not the feature being inert. What it refuses, and what it used to do silently The release notes mention hard links and "unsupported tar extensions." I built four adversarial archives with Python's tarfile module: one with a hard link pointing at ../../../../../../etc/passwd, one with an absolute symlink to /etc/passwd, one with a GNU sparse-file entry missing its required header, and one with a character device node. Archive uv 0.13.6 default uv 0.13.6 legacy flag uv 0.12.8 Hard link escaping the extraction root rejected rejected rejected Absolute symlink rejected rejected rejected Malformed sparse entry rejected rejected rejected Character device node rejected installed installed Three of the four are a wash. This is the finding that argues against my own headline: the path-escape protections for hard links and symlinks aren't new in 0.13. The old backend already refused them, with almost the same wording: # uv 0.12.8 (old backend)
error: Failed to extract archive: file:///....

Powered by Readybot.io

uv 0.13 Installs 6,000-File Packages 32% Faster

Actions Runner Controller Alternatives: Move Off Kubernetes

DEV Community

Actions Runner Controller alternative: moving ARC off Kubernetes

Teams shopping for an Actions Runner Controller alternative are rarely unhappy with ARC itself, which does exactly what it says: a Kubernetes operator that scales ephemeral runner pods against your job queue, with no per-minute fee from GitHub or from anyone. They are unhappy with what it sits on, because the cluster, the images, the listener and the pager are all yours, and none of that appears on a rate card. Published rates for a 10,000-minute month against one labeled assumption about ARC upkeep. The assumption is yours to replace; the meters are not. The workflow change is small. ARC selects runners with runs-on set to the Helm installation name of a runner scale set, a managed vendor selects runners with a label, and everything above that line in the workflow file stays as it is. The work is in the eight things below that your cluster was quietly providing, and in deciding which of them you actually still need. It is worth being precise about what ARC costs, because the honest answer is not "more than a managed runner". GitHub documents that Actions usage is free for self-hosted runners, and ARC itself is free software. What you pay is the cluster, the node capacity that sits idle so a queue does not form, the image rebuilds, and the hours. The last of those is the one nobody bills you for and everybody spends. This page is the ARC-specific version of the general move, which is worked through in migrate from self-hosted runners. Vendor rates and shapes are in GitHub Actions runner alternatives. What ARC is doing for you right now ARC installs as two Helm charts: a controller manager, by convention in an arc-systems namespace, and one runner scale set per pool, by convention in arc-runners. The scale set opens a long-polling HTTPS connection to the Actions service, waits for a job-available message, and patches an ephemeral runner set to the desired replica count. Each runner pod registers with a just-in-time token, takes exactly one job, and is deleted. Two documented behaviors are worth carrying in your head before you change anything. If a runner pod fails to start, the controller retries it up to five times. If no runner accepts the job, the Actions service unassigns it after 24 hours, which is why a broken listener looks like a job that queues all afternoon rather than a job that fails. Managed runners remove both by provisioning per job instead of matching against a pool you own. The pre-flight checklist Nine items. The first six are the standard runner-migration list, and the last three exist only because the runners were pods in your cluster. Labels. Grep for the Helm installation name of every scale set, not for self-hosted: in scale-set mode runs-on matches the installation name. Include reusable workflows and composite actions. Runner groups. A scale set placed in a group through runnerGroup inherits that group's repository access. On the other side that becomes plain label access, so write down which repositories were allowed to use which pool. Secrets. Anything mounted into the runner pod, a registry credential, a cloud key file, a .npmrc baked into your runner image, becomes a repository or environment secret. This is the single largest source of first-run failures. OIDC. This one carries over unchanged, because the token is minted by GitHub rather than by the runner. What does not carry over is pod identity: a job that authenticated as the service account of its pod, through IRSA or Workload Identity, has no equivalent and needs an OIDC trust policy instead. Cache keys. They carry over as they are. What changes is the hit rate, if your runner image pre-baked dependencies that the cache now has to restore. Matrix and concurrency. Your ceiling was maxRunners plus whatever the cluster could schedule. On the other side it is the vendor's: GitHub allows 20 to 500 concurrent jobs by plan, Namespace publishes 32 to 160 Linux vCPUs, Depot and Blacksmith publish no ceiling at all. Container mode. containerMode: ...

Powered by Readybot.io

Actions Runner Controller Alternatives: Move Off Kubernetes

Cómo validar un CSV financiero con Python

DEV Community

Validar un CSV antes del reporting financiero: Decimal, duplicados y datos pendientes

Nota de edición: artículo y código generados con IA para el equipo de Numio. El ejemplo se ha ejecutado localmente con datos ficticios. El recurso enlazado al final pertenece al blog de Numio. Un informe puede estar bien diseñado y sumar documentos duplicados. También puede mostrar cero donde en realidad falta un dato. Antes de construir un gráfico, conviene establecer qué archivo se admite, qué controles se aplican y qué pasa si alguno falla. Este ejemplo en Python valida una exportación pequeña antes de calcular un resumen mensual por empresa. No se conecta a un ERP, no modifica la contabilidad y no envía información a servicios externos. El contrato del archivo Trabajaremos con cinco columnas, separadas por punto y coma, en este orden: empresa;documento;fecha;moneda;importe. El archivo es UTF-8. Cada fila representa un documento ya agregado, no una línea de asiento ni una partida de factura. La clave (empresa, documento) debe ser única dentro de esta exportación. Las fechas tienen formato YYYY-MM-DD. Los importes usan punto decimal, exactamente dos decimales y ningún separador de miles. Una exportación con coma decimal necesita una conversión explícita y revisada antes de este paso; el script la rechaza en vez de adivinar su significado. empresa;documento;fecha;moneda;importe
cliente_demo_A;F001;2026-10-01;EUR;0.10
cliente_demo_A;F002;2026-10-02;EUR;0.20
cliente_demo_A;F003;2026-10-03;EUR;0.00
cliente_demo_B;F001;2026-10-01;EUR;5.00
F001 puede existir en dos empresas diferentes. Un cero explícito es válido. Un importe vacío bloquea la entrega: no significa cero. Los identificadores son ficticios y sirven únicamente para explicar el formato. Validar antes de devolver un total """Ejemplo educativo: validar un CSV de documentos antes de resumirlo."""
import csv
import re
from collections import defaultdict
from datetime import date
from decimal import Decimal, localcontext

FIELDS = ["empresa", "documento", "fecha", "moneda", "importe"]

def resumir(stream):
reader = csv.DictReader(stream, delimiter=";", strict=True)
try:
fieldnames = reader.fieldnames
except csv.Error as exc:
raise ValueError("Cabecera CSV no valida") from exc
if fieldnames != FIELDS:
raise ValueError("Cabecera distinta del contrato esperado")

seen = set()
totals = defaultdict(lambda: Decimal("0.00"))
errors = []
rows = 0
# Limites del ejemplo: 10 000 documentos, 18 digitos enteros por importe.
with localcontext() as ctx:
    ctx.prec = 28
    try:
        for row in reader:
            rows += 1
            line = reader.line_num
            if rows > 10_000:
                raise ValueError("El ejemplo admite hasta 10 000 documentos")
            try:
                if None in row or any(v is None for v in row.values()):
                    raise ValueError("Numero de columnas incorrecto")
                row = {k: v.strip() for k, v in row.items()}
                if not all(row.values()):
                    raise ValueError("Dato pendiente o identificador vacio")
                if not re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", row["fecha"]):
                    raise ValueError("Fecha fuera del formato YYYY-MM-DD")
                day = date.fromisoformat(row["fecha"])
                if row["moneda"] != "EUR":
                    raise ValueError("Este ejemplo solo agrega EUR")
                if not re.fullmatch(r"-?[0-9]{1,18}\.[0-9]{2}", row["importe"]):
                    raise ValueError("Importe: punto decimal y dos decimales")
                key = (row["empresa"], row["documento"])
                if key in seen:
                    raise ValueError("Documento repetido en la misma empresa")
                seen.add(key)
                totals[(row["empresa"], day.strftime("%Y-%m"))] += Decimal(row["importe"])
            except ValueError as exc:
                err...


Powered by Readybot.io

Cómo validar un CSV financiero con Python

SEO Lessons from 88 Free Browser Tools

DEV Community

I Built 88 Free Browser Tools and Got 8 Organic Visits. Here’s What the Community Taught Me About SEO.

A few weeks ago, I shared a vulnerable post on Indie Hackers: I built 88 browser-based tools, and my traffic was 281 direct visits and exactly 8 organic search visits. As a developer, my instinct was always: “If you build it, write clean code, and run everything locally in the browser for privacy, people will find it.” I spent weeks optimizing WebAssembly, tweaking client-side canvas APIs, and making sure files were processed 100% locally without server uploads. The tech was solid. The utility was real. The SEO performance was an absolute flatline. The Indie Hackers community gave me a masterclass in SEO reality. If you are a solo developer building side projects, micro-SaaS, or utility sites, here are the 5 big mistakes I made—and what you should do instead. 1. The "Thin Content" Mistake (Google Hates Empty Utility Pages) My tool pages looked great to a human: A drag-and-drop zone An action button (e.g., "Compress" or "Strip EXIF") An instant download link To Googlebot, that page had roughly 40 words of text. Search engines don't run your WebAssembly code to see how clever it is. They read text. If your page doesn't explain: What problem this tool solves How it works step-by-step Why it’s safe (e.g., client-side vs. server-side) Frequently Asked Questions (FAQs) ...then crawlers classify the page as "Thin Content" and refuse to index or rank it. The Fix: Every tool needs at least 300–500 words of genuine, helpful context: instructions, supported formats, limitations, and an FAQ section backed by FAQPage schema markup. 2. Competing for Impossible Keywords When I launched an image compressor, my target was basically "compress image." Do you know who ranks on page 1 for "compress image"? Adobe TinyPNG Cloudflare 10-year-old domains with tens of thousands of backlinks A brand-new domain with zero authority has a 0% chance of ranking for broad head terms. The Fix: You must target long-tail, low-competition keywords where search intent matches your unique edge. Instead of: ❌ "PDF Splitter" ❌ "Image Compressor" Target: ✅ "How to remove GPS metadata from photos without uploading" ✅ "Compress PNG client side WebAssembly" ✅ "Free private alternative to Smallpdf" These have much smaller search volumes, but their keyword difficulty (KD) is low enough that a new site can actually hit page 1. 3. The 88 vs. 3 Trap (Spreading Authority Too Thin) Building 88 tools felt like having 88 lottery tickets. In reality, it meant having 88 orphan pages with zero backlink equity. Google evaluates site authority hierarchically. If no one links to any of your pages, Google doesn't know which one matters. The Fix: Pick 2 or 3 flagship tools that solve a specific, high-demand problem really well. Build content, tutorials, and external links pointing specifically to those 2–3 tools. Build clear internal navigation (pillar pages like /image-tools or /pdf-tools) to distribute page rank to the rest of the catalog. 4. Technical SEO: Is Google Actually Seeing Your Tools? A classic developer mistake is building an entire tool suite as a Single Page Application (SPA) using React, Vue, or Svelte, relying completely on client-side rendering. While Googlebot can execute JavaScript, it often delays JS rendering or times out on complex bundles. If your , meta tags, and H1 tags aren't present in the initial HTML response, your indexing will suffer. The Checklist: [ ] Use SSR (Next.js, Nuxt, Astro, SvelteKit) or Static Site Generation (SSG). [ ] View page source (Ctrl+U) and verify that all headings, descriptions, and FAQs exist in raw HTML. [ ] Implement canonical tags, OpenGraph tags, and structured JSON-LD data. [ ] Check Google Search Console for "Discovered – currently not indexed" errors. 5. SEO Is a Trailing Metric—You Need Initial Distribution You cannot launch a website and wait for Google to discover you. Crawlers look for signals: direct traffic spikes, mentions on social networks, and incoming links. Before organic search kicks in (which takes 3–6 ...

Powered by Readybot.io

SEO Lessons from 88 Free Browser Tools

How Fancy Text Generators Work: Unicode Explained

DEV Community

How Fancy Text Generators Actually Work (It’s Just Unicode)

You've seen it in Instagram bios, Twitter names and Discord servers: text like 𝐁𝐨𝐥𝐝, 𝓢𝓬𝓻𝓲𝓹𝓽 or 𝔾𝕠𝕥𝕙𝕚𝕔 in apps that don't let you pick a font at all. So how does it work? Short answer: it isn't a font. Fancy text generators swap each normal letter for a different Unicode character that happens to look styled. In this post I'll show you exactly which characters they use, the few lines of JavaScript that do the conversion, and the edge cases that broke my own generator when I built one. It's Not a Font, It's a Different Character A fancy text generator doesn't change how a letter looks. It replaces the letter with a completely separate character. A font is a visual skin applied to the same character, but 𝐀 and A are two different characters with two different code points. You can prove it in your browser console: console.log("𝐀" === "A"); // false
console.log("𝐀".codePointAt(0).toString(16)); // "1d400"
U+1D400 is officially named MATHEMATICAL BOLD CAPITAL A. Because it's a real character and not styling, it survives copy and paste. That's why it works in places with no formatting options: an Instagram bio, a username field, a plain . Where These Characters Come From: The Mathematical Alphanumeric Symbols Block Most "fonts" in a fancy text generator come from one Unicode block called Mathematical Alphanumeric Symbols, which runs from U+1D400 to U+1D7FF. It was added in Unicode 3.1 (2001) for mathematicians, not for social media. In math notation, a bold x, an italic x and a script 𝓍 can mean three different things, so each one needed its own character to keep that meaning in plain text. The useful part for developers is how the block is laid out. Each style is a neat run of 26 capitals followed by 26 lowercase letters: Style Example Capital A Small a Bold 𝐀𝐚 U+1D400 U+1D41A Italic 𝐴𝑎 U+1D434 U+1D44E Script 𝒜𝒶 U+1D49C U+1D4B6 Bold Script 𝓐𝓪 U+1D4D0 U+1D4EA Fraktur 𝔄𝔞 U+1D504 U+1D51E Double-struck 𝔸𝕒 U+1D538 U+1D552 Sans-serif 𝖠𝖺 U+1D5A0 U+1D5BA Monospace 𝙰𝚊 U+1D670 U+1D68A The block also holds bold, double-struck, sans-serif and monospace digits. Bold 𝟎 starts at U+1D7CE. Because the letters sit in alphabetical order, converting text is just arithmetic. The Character Offset Trick in JavaScript To convert a letter, find its position in the alphabet (A = 0, B = 1 and so on) and add that number to the style's starting code point. That's the whole trick. Here's the simplest version, a bold converter: function toBold(text) {
return [...text].map((ch) => {
const code = ch.codePointAt(0);
if (code >= 65 && code <= 90) return String.fromCodePoint(0x1d400 + (code - 65)); // A-Z
if (code >= 97 && code <= 122) return String.fromCodePoint(0x1d41a + (code - 97)); // a-z
return ch; // leave spaces, numbers and punctuation alone
}).join("");
}

console.log(toBold("Hello, World!")); // 𝐇𝐞𝐥𝐥𝐨, 𝐖𝐨𝐫𝐥𝐝!
Two details matter here: String.fromCodePoint(), not fromCharCode(). These characters are above U+FFFF, so fromCharCode() can't create them in one call. [...text] instead of text.split(""). Spreading a string iterates by code point, so it won't cut a fancy character in half. More on that later. To support every style, store the starting points in a map and reuse one function: const STYLES = {
bold: { upper: 0x1d400, lower: 0x1d41a },
italic: { upper: 0x1d434, lower: 0x1d44e },
script: { upper: 0x1d49c, lower: 0x1d4b6 },
boldScript: { upper: 0x1d4d0, lower: 0x1d4ea },
fraktur: { upper: 0x1d504, lower: 0x1d51e },
doubleStruck: { upper: 0x1d538, lower: 0x1d552 },
sans: { upper: 0x1d5a0, lower: 0x1d5ba },
monospace: { upper: 0x1d670, lower: 0x1d68a },
};

function stylize(text, style) {
const { upper, lower } = STYLES[style];
return [...text].map((ch) => {
const code = ch.codePointAt(0);
if (code >= 65 && code <= 90) return String.fromCodePoint(upper + code - 65);
if (code >= 97 &...

Powered by Readybot.io

How Fancy Text Generators Work: Unicode Explained

GitHub Actions vs Drone CI: Where Drone Went

DEV Community

GitHub Actions vs Drone CI, and where Drone went

GitHub Actions vs Drone CI is no longer only a comparison of two pipeline engines, because github.com/drone/drone now returns a 301 to github.com/harness/harness and Drone survives there as a feature branch whose last commit was 20 April 2026. Before you weigh YAML syntax, read the license: commercial use of the Enterprise Edition is limited to a trial period of 32 consecutive calendar days unless you qualify for one of two waivers. Where Drone is now: the canonical repository path redirects to Harness Open Source, and Drone continues as a branch whose newest commit is 20 April 2026. Drone is a continuous delivery system built on container technology that uses a simple YAML build file to define and execute build pipelines inside Docker containers. That description is from the project's own readme, and it is still an accurate description of what the software does. What changed is where it lives. Harness Open Source now occupies the repository, and its readme has a section headed "Where is Drone?" that says Harness Open Source represents a massive investment in the next generation of Drone, that a snapshot of Drone was taken as a feature branch so it can continue development, and that the goal is for Harness to eventually be at full parity with Drone in terms of pipeline capabilities, while expecting this to take some time. The facts below were read from the redirect itself, from the readme and LICENSE on that drone branch, and from the drone-runners organization on 21 September 2026. They are checkable in a browser in under two minutes, and they matter more to this decision than any syntax difference. Comparison GitHub Actions Drone CI Where the code lives A first-party GitHub product github.com/drone/drone 301s to harness/harness. Drone is a drone feature branch of that repository Most recent movement Continuous Latest release v2.28.2 on 20 April 2026, cut from a branch whose newest commit is the same day. The main branch of the same repository is pushed daily License A hosted service under GitHub's terms Community Edition under Apache 2.0. Enterprise Edition under the Drone Non-Commercial License, which caps commercial use at 32 consecutive calendar days Who escapes that cap Not applicable Companies under $5 million annual gross revenue and under $5 million all-time debt and equity financing, or anyone running fewer than 5,000 pipelines in the preceding year What you operate Nothing, or self-hosted runners if you choose A server plus one or more runners. The readme describes a runner as a standalone daemon that polls the server for pending pipelines to execute Runner options GitHub-hosted, a managed vendor's fleet, or self-hosted Docker, Kubernetes, AWS and SSH runners are still published. The exec, DigitalOcean, MacStadium and Nomad runners are archived Config .github/workflows/.yml, discovered from the branch .drone.yml, with every step running in a container What you pay per minute $0.006 at 2 vCPU on Linux, or $0.0025 on a managed runner at the same size Nothing to the software. Your server and runner hosts bill whether or not a pipeline is running Self-healing or retries None on GitHub-hosted runners; transient failures diagnosed and retried inside the run on Latchkey None. A failed pipeline is a failed pipeline Setup change required A file in the repository A server, at least one runner, an OAuth application on your forge, and a .drone.yml per repository The license, which is the part most comparisons skip The LICENSE file on the drone branch splits the project in two. The Community Edition is licensed under the Apache License, Version 2.0, and the source files carry headers saying which license each is under. The Enterprise Edition is under the Drone Non-Commercial License, and its first rule is the one to read: you must limit use of this software in any manner primarily intended for or directed toward commercial advantage or private monetary compensation to a trial period of 32 consecutive calendar days. Two waiv...

Powered by Readybot.io*

GitHub Actions vs Drone CI: Where Drone Went

How reelay Turns Goals Into AI-Powered Workflows

DEV Community

How We Built reelay: Turning Goals Into Workflows

Less back-and-forth. More moving forward. What if you could explain what you want to achieve, and an app could create a plan using the tools and services needed to make it happen? That was the idea behind reelay, the project our team created at the hackathon. reelay is built as a general-purpose tool for managing plans. A person shares a goal, and the system creates a series of connected steps that can involve different tools and businesses. For our demo, we picked one scenario: organizing an accessible day out, including a place to eat, an activity and a way to move between them. The approach can also be applied to many other kinds of tasks. Here’s how we made it. 01 / We made the graph the foundation A plan becomes a graph of tasks. Each node represents a step, and the links show how those steps are connected. This helps the user and the system see what needs to be done and in what order. The same structure can represent different types of work, whether the request involves finding information, bringing services together or completing steps that depend on each other. 02 / We used Gemini to create plans A person describes a goal in their own words. Gemini turns the request into a graph, which the system checks and saves. This gives users a plan they can inspect without building every step themselves. 03 / We made businesses easier to find We built a search tool using Qdrant and embeddings. It finds businesses based on a natural-language request and can filter results by location, type and cost. We made it available through MCP, so agents and the graph system can use it as part of a plan. 04 / We created services for the demo To show how different services can work together, we made MCP providers for dining, activities and transport. They can search, create an intent and fulfill it. Our Timișoara example brings them together in an accessible day out. We also added a transport disruption to explore how a plan can change when things go wrong. The providers use fictional demo data. 05 / We added voice with ElevenLabs We wanted users to be able to speak their goals, so we added voice-to-text, editable transcripts and audio playback for results. Users can check what the system heard before submitting their request. 06 / We built the workspace and login system We used Next.js for the frontend, Django for the backend and PostgreSQL for storage. Auth0 handles login, giving users a place to create, inspect and return to their graphs. 07 / We built a Solana escrow prototype We wrote a program in Rust with Anchor to explore how a person could fund a plan and have money released step by step. The program supports deposits, individual releases, top-ups and refunds, with a record for each payment. We deployed it to Solana devnet and ran a test that locked 52 tokens, sent payments to two recipients and refunded the rest. Connecting the escrow prototype to the app is the next step. The biggest challenge: making all the APIs work together Gemini, ElevenLabs, Auth0, MCP providers, business search and Solana each had their own formats, assumptions and ways things could go wrong. Getting each part to work on its own was only part of the task. The harder part was making the output of one part useful to the next while keeping the process clear for the user. We worked through it by giving each part a clear role: understand the request, build the plan, find the right services, carry out steps and manage payments. The main thing we learned: a good integration is more than a set of APIs. It makes them feel like one coherent product. The trip example is one use case. The bigger idea behind reelay is a system that can turn a wide range of goals into steps and bring the right services together to help make them happen. Explore reelay Check out the code: reelay on GitHub

Powered by Readybot.io

How reelay Turns Goals Into AI-Powered Workflows

Fix NU1301: Diagnose NuGet Service Index Errors

DEV Community

nu1301 unable to load service index, and which source failed

A nu1301 unable to load service index error means dotnet restore asked a package source for its V3 index and did not get a usable answer, so it never got as far as looking for your packages. The message always names the source, and the second line, which people skip, carries the reason. Read those two lines together and the error resolves into one of three ordinary situations. NU1301 is raised before any package lookup happens, which is why it names a source and never a package. The second line is the one that says what to fix. What this error means Restore fails early, before any package is mentioned, with a line that names your project file, the code NU1301, and a source URL. A second NU1301 line follows, indented, carrying the underlying failure. NuGet documents NU1301 as "Restore could not be completed because the listed source is unavailable", which covers a remote feed that will not answer and a local folder that is not there. The block below is a container running the .NET SDK 10.0.401 with a single package source pointed at a server that answers 503 to everything. This page carries no reproduction on a Latchkey runner, so the host in the message is ours; the two line structure is the part that matches yours. ```.NET SDK 10.0.401, package source answering 503 /w/app.csproj : error NU1301: Unable to load the service index for source http://mirror503:8899/v3/index.json. /w/app.csproj : error NU1301: Response status code does not indicate success: 503 (Service Unavailable). Failed to restore /w/app.csproj (in 5.44 sec).

Common causes

A private feed is down, throttled or slow

Azure Artifacts, a GitHub Packages feed or a self hosted Nexus answering 503 or 429 is the everyday version. Restore contacts the index before it knows what it needs, so the failure arrives before any package name appears and looks unrelated to the change that triggered the build.

The restore is authenticating to a feed it was never given credentials for

A private feed answers 401 to an anonymous index request, and NuGet reports that as NU1301 rather than as an authentication error, which sends people looking for an outage. On GitHub Actions this usually means the credential provider was not installed, or the source was added without a token in the step that added it.

A source from a developer machine is in scope on the runner

A nuget.config checked into the repository, or one in a parent directory of the checkout, can carry a local folder source or an internal host that does not exist in CI. NuGet merges configuration files up the directory tree, so a source nobody remembers adding is still contacted on every restore.

The runner cannot reach the host at all

A self hosted runner behind a proxy, a network policy that blocks egress to a new domain, or DNS that does not resolve an internal name. The second line says so plainly in this case, naming a connection or name resolution failure rather than a status code, which is the fastest way to tell this cause apart from the others.

How to fix it

Find out which source failed, and whether you need it

  1. Print the sources the runner will actually use, which is the merged result of every nuget.config in scope, not just the one in the repository.
  2. Remove sources the build does not need. Every extra feed is another chance for restore to fail on something irrelevant.
  3. Start the source list from a clean slate in the repository so a machine level configuration cannot add to it.

```Terminal
dotnet nuget list source
Pin the source list in the repository with a clear element A nuget.config at the repository root that begins by clearing inherited sources makes the restore reproducible: the runner uses exactly the feeds listed and nothing a machine level file added. This alone removes the third cause above permanently. <?xml version="1.0" encoding="utf-8"?>



<add key="nuget.org" value="https://api.nuget.org/v3/index...

Powered by Readybot.io

Fix NU1301: Diagnose NuGet Service Index Errors

AI Agents on BNB Chain: Why On-Chain Trust Matters

DEV Community

My AI Agent Moves Real Money On-Chain — Here's Why 'Just Trust Me' Isn't Enough

My AI Agent Moves Real Money On-Chain — Here's Why "Just Trust Me" Isn't Enough Creator Track entry for Agent Games Season 01 — #AgentGames I let an AI agent run my crypto wallet. Not a demo wallet. My real one. It mints NFTs, claims airdrops, runs miners, and signs transactions on BNB Chain while I sleep — under an Agent ID called mujib.ai. Every morning I wake up to a report of what it did. It works. It has also taught me the one problem nobody in the agent hype talks about honestly: every time my agent acts, I am taking its word for it. What my agent actually does My agent — I call her cintaku — does the kind of on-chain chores that eat a whole evening if you do them by hand: Free mints and claims across a dozen small projects (Nomio, QAGENT, ORBIT, DYOR, CLAF, Pacture, Agent Harvest…). Daily farming steps: claiming seeds, checking plots, watching prices hourly. Proof-of-work mining sessions that run for two hours a night. Price watches on everything I hold, so I only hear about it when something actually moves. None of this is science fiction. It's scripts, RPC calls, and a wallet that signs. The agent reads the chain, decides, and acts. And that is exactly where the trust problem lives. The signature is a leap of faith A wallet signature is not a chat reply. Once my agent signs, value moves. There is no undo button on-chain, no customer support to call, no "are you sure?" from the blockchain. So before every signature, my agent and I built guardrails the hard way: Preview before signing. Every transaction is simulated first. If the simulation reverts, shows unexpected token movement, or touches an approval I didn't ask for, nothing gets broadcast. The preview is the whole decision. Exact-amount approvals, never unlimited. An unlimited approval is a blank cheque with my wallet as the account. My agent defaults to the exact amount, every time. Fail closed on ambiguity. If the recipient, chain, token, or amount is unclear, the agent stops and asks. Guessing with money is not a feature. No paid moves without a fresh yes. Free lanes only, unless I explicitly approve a specific paid action. "Stop all paid mints" was one of my earliest instructions — and it has stayed in force for weeks. These rules exist because I have seen what happens without them. Twice now, something tried to get past: A forwarded post promised a 50,000-token reward pool for a waitlist I was joining. The official page promised only early access. My agent checked the official source instead of the forwarded claim — and registered me for early access only, with no reward counted. A farming transaction kept getting rejected by the wallet's own risk engine (code 351803: "transaction risk is too high"). The agent didn't route around it. It stopped, logged it, and the land stayed unplanted. Annoying — but that is the system working. The lesson is simple: an agent that never says "I won't do that" is not helpful. It is dangerous. A claim is not a proof Here is the gap my guardrails can't close on their own: everything my agent tells me is still just a claim. "Mint succeeded." "Price is 0.00132 BNB." "The pool hasn't opened yet." I can verify each one myself — check the transaction hash, read the contract, open the marketplace. And I do, when the stakes are high. But the whole point of having an agent is that I don't check everything. The moment I stop checking, I am back to trusting a black box that prints confident sentences. This is the exact problem zkVerify's Agent Games is built around: if an AI agent says it did the job, how would you know? A zero-knowledge proof changes the shape of the answer. Instead of "trust me, I followed the rules," the agent hands over a receipt that anyone can verify: this decision followed these rules, on this input, and the math checks out — without revealing anything else. The proof doesn't ask you to trust the agent's character. It asks you to trust arithmetic. That's a different relationship between a human and their agent....

Powered by Readybot.io

AI Agents on BNB Chain: Why On-Chain Trust Matters

Celesto CI vs GitHub Actions and Blacksmith Benchmarks

DEV Community

Blacksmith vs GitHub Actions vs Celesto CI: what the benchmarks say

I build Celesto CI. Celesto has a lower paid per-minute rate than both Blacksmith and GitHub's 4-core Linux larger runner. In the snapshot below, Celesto finished the cold builds faster than GitHub's benchmark runner. That's the useful comparison: how long you wait, what a running minute costs, and what those numbers actually measure. What we compared The Celesto CI benchmark repository publishes workflows, results and source run links. This article uses its November 14 comparison, including the November 15 Celesto Docker rerun after a runner disk change. All three rows use Linux x64 runners with 4 vCPU and 16 GB of listed RAM. That is the same listed configuration, not identical hardware. CPU models, disks, regions and host load differ. The workloads are real public projects pinned to commits: Rust: spotify-player, with formatting checks, tests and two Clippy passes. Node/TypeScript: opencode, with a cold Bun install and typecheck. Turbo's local and remote caches are disabled. Docker: PostHog's production image, built with no build cache. Cache: save and restore a 4 GiB file using actions/cache. Queue: start a burst of 15 jobs and measure the median wait from job creation to runner start. Celesto's results come from our repository. GitHub and Blacksmith results come from the RunsOn public benchmark data, using the workloads described in its methodology. RunsOn also sells CI infrastructure, so this is vendor-maintained evidence, not an independent lab test. Build times: Celesto beats GitHub; Blacksmith trades a 6x paid rate for speed Lower is better. Workload GitHub Blacksmith Celesto CI Rust 3m 35s 2m 02s 2m 54s Node/TypeScript 1m 38s 50s 1m 19s Docker 12m 59s 5m 52s 9m 14s Cache save + restore 1m 20s 17s 41s Burst queue, p50 3s 11s 15s Compared with GitHub in this snapshot, Celesto reduced Rust workload time by about 19%, Node/TypeScript by 19%, and Docker by 29%. Blacksmith completed all three builds and the cache test sooner, at 6x Celesto's paid per-minute rate for this listed configuration ($0.008 versus $0.00133333). That is a rate comparison, not a claim that each build costs 6x as much: Blacksmith's shorter runtime offsets part of the rate difference. GitHub had the shortest burst queue wait. These are workload-phase times, not the full time from pushing a commit to getting a green check. Setup, collection and other steps outside the measured phases are excluded. Queue wait is shown separately. Paid rates: Celesto costs less per running minute Here are the published Linux x64 rates for the same listed 4-vCPU / 16-GB configuration, checked November 15, 2026: Provider Paid rate per minute Source GitHub, Linux 4-core larger runner $0.012 GitHub runner pricing Blacksmith, 4 vCPU $0.008 Blacksmith pricing Celesto CI, Large $0.00133333 Celesto CI pricing Celesto's $0.08/hour rate works out to about 89% less per minute than GitHub's 4-core larger runner and 83% less than Blacksmith's 4-vCPU runner. There is an important distinction: the GitHub timing row uses its public-repository standard runner. The $0.012 rate belongs to GitHub's paid 4-core larger-runner SKU. The listed CPU and RAM align, but these results do not establish the performance of that paid SKU. This is a specification-level price comparison alongside a benchmark, not an invoice comparison for the measured GitHub jobs. Also, free usage matters: GitHub's standard runners are free for public repositories, and eligible private-repository usage has an included allowance. Celesto does not beat a $0 bill on price. Blacksmith advertises 3,000 free minutes per month. Celesto offers a one-time $5 trial, but the current trial is Micro-only. It does not cover the Large runner used here. A lower rate is not the whole bill A runner that costs more per minute can finish sooner. A fair cost-per-job comparison needs each provider's full billable runtime, its billing rules, allowances and any extra charges. Multiplying only the build times above by the rates would leave th...

Powered by Readybot.io

Celesto CI vs GitHub Actions and Blacksmith Benchmarks

Test AI Preference Pairs for Shortcut Bias

DEV Community

Shortcut judges: a cheap test your preference pairs should pass before you train on them

I'm building evaluation adapters (small reward models) for Telegraph, a network that routes AI requests to miners and scores their answers. Disclosure: I'm competing in Telegraph Hackathon Season II, which has prizes. A preference pair is a prompt, a better answer and a worse one. A reward model trained on pairs learns whatever separates the two answers. If something cheaper than the skill you want separates them, it learns that instead, and your accuracy number measures the shortcut. The test Before training, run rules that pick an answer without checking anything: pick the longer answer pick the answer with more words in common with the prompt (TF-IDF cosine) pick the answer with more of an obvious feature (alarm words, a verdict word) If any of them is far above 50%, fix the pairs first. Two cases from this week Another builder (AdversarialEval, in the Telegraph Discord) found a keyword-count judge got every one of their severity pairs right, because all the worse answers were downgrades. After adding inflations, the TF-IDF judge still won ~95%. They rebuilt the pairs so the two answers are identical except for the severity claim, drew wrong severities from the same distribution as true ones (either direction), and balanced which answer is longer. On 84 new pairs: length 50%, TF-IDF 50%, keywords 55%. My own 300 weather-verification pairs: length was 49.7% overall but 17–88% within error families, because the verdict phrases differ in length and some families lean one way. TF-IDF was 78% overall. Part of that is legitimate (in a wrong-station pair, not matching the question is the error), but my digit-swap and fill-value pairs leak too. Those rows are already in the pool, so I'm reporting this as a limit rather than quietly fixing it. What I'd do differently write the worse answer by copying the better one and changing only the claim balance verdict direction and length within every error family, not just overall report the shortcut judges next to model accuracy in every run Script (standard library only) and the per-family table: https://github.com/fexx301/oathcast-telegraph-s2/tree/main/shortcut-judges-v1 dev.to allows up to four tags. I'd use machinelearning, ai, llm, datascience.

Powered by Readybot.io

Test AI Preference Pairs for Shortcut Bias

Type Faster by Improving Accuracy and Consistency

DEV Community

Typing Faster Isn't Enough: Why Accuracy and Consistency Matter More

Most people think improving their typing speed means learning to move their fingers faster across the keyboard. That sounds reasonable, but it misses an important part of the equation. Typing speed is useful only when you can maintain accuracy. If you type quickly but constantly make mistakes, you may spend more time correcting errors than you save by typing faster. Whether you're a software developer, student, content writer, or someone who works with computers every day, improving your typing skills can make everyday tasks more efficient. Let's look at what actually makes someone a better typist. Understand the Difference Between Speed and Accuracy Typing performance is usually measured using two main metrics: Words Per Minute (WPM): How many standardized words you can type in one minute. Accuracy: The percentage of characters typed correctly. For example, consider two people: Metric Person A Person B Typing speed 75 WPM 60 WPM Accuracy 88% 99% Person A types faster, but makes considerably more mistakes. In real-world situations, Person B might actually finish a document or email sooner because fewer corrections are needed. This is why accuracy should be a priority when developing typing skills. Learn Touch Typing Instead of Looking at the Keyboard One common habit among beginners is constantly looking down at the keyboard to find individual keys. This interrupts the natural flow of typing. Touch typing is the ability to type without looking at the keyboard. Instead of visually searching for each key, your fingers gradually learn their positions through repetition. Start by learning the home row: A S D F J K L ; Your left-hand fingers rest on A, S, D, and F, while your right-hand fingers rest on J, K, L, and the semicolon key. These positions provide a starting point for reaching other keys. Once you're comfortable with the home row, gradually practise the top row, bottom row, and number row. Don't worry about speed initially. Focus on pressing the correct keys without looking down. Build Muscle Memory Through Consistent Practice Typing is a physical skill that improves through repetition. Occasional long practice sessions are generally less effective than short, consistent sessions. For example, practising for 10–15 minutes every day gives you regular opportunities to reinforce correct finger movements. A simple routine could look like this: 3 minutes practising home-row keys. 3 minutes practising difficult letter combinations. 3 minutes typing complete sentences. 3 minutes taking a timed typing test. The goal isn't to achieve an impressive score every day. It's to make correct finger movements feel increasingly natural. Over time, you'll spend less attention finding keys and more attention thinking about what you're writing. Measure Your Progress Instead of Guessing One problem with learning typing is that improvement can be difficult to notice. You might feel faster without actually knowing whether your speed or accuracy has improved. This is where regular typing tests become useful. A typing test gives you measurable results, including: Words per minute Characters per minute Typing accuracy Number of mistakes You can use these results to identify weaknesses and adjust your practice routine. For example, if your speed increases but your accuracy decreases, you may be rushing too much. If your accuracy is consistently high but your speed remains low, you might need more practice with finger movement and common letter combinations. A browser-based typing test such as RapidFinger can help you measure your performance across different test durations and track your progress. Try both short and longer tests. A one-minute test measures your burst speed, while a longer session gives you a better idea of how consistently you can type over time. Practise Difficult Words and Letter Combinations Not all words are equally easy to type. Some combinations require more complicated finger movements, especially when several letters are...

Powered by Readybot.io

Type Faster by Improving Accuracy and Consistency

PHP Security Audit: Spot Hidden Authentication Flaws

DEV Community

Auditing PHP Code for Hidden Security Flaws

Imagine you are reviewing an API endpoint that checks a shared secret: $payload = json_decode(file_get_contents('php://input'));

if ($payload->token == 'SECRET_API_TOKEN') {
$session->grantAdmin();
}
At first glance, this looks like normal authentication code. But because it uses the loose == operator, an attacker who sends {"token": true} will pass the check. In PHP, any non-empty string other than '0' loosely evaluates to boolean true Zend Engine type rules. The caller gains administrative access without ever knowing the secret. Spotting vulnerabilities in a PHP codebase requires understanding how the engine handles data types, superglobals, and input streams. Type Juggling Across PHP Versions Loose comparisons have caused authentication bypasses for years. Developers often assume that upgrading to PHP 8 eliminates every comparison flaw, but that is only partly true. In PHP 7.x, comparing a non-numeric string to zero evaluated to true type juggling notes. That meant "admin" == 0 succeeded. PHP 8.0 changed this behaviour by casting the number to a string instead PHP 8.0 migration guide. Under PHP 8, "admin" == 0 evaluates to false. Boolean type juggling remains unchanged across PHP versions Laravel comparison notes. If your code checks a string against user input with ==, a boolean value in JSON will still match. Magic hashes also persist in modern runtimes. When two strings formatted in scientific notation are compared loosely, the engine treats both as float 0.0 and returns true magic hash behavior. However, this PHP 8 behaviour strictly requires both strings to consist exclusively of 0e followed solely by digits. If a digest contains letters other than the exponent e, PHP 8 classifies it as a non-numeric string and the comparison fails. To prevent comparison bypasses, always verify that security checks use strict comparison (===) or constant-time comparison: if (!is_string($payload->token) || !hash_equals('SECRET_API_TOKEN', $payload->token)) {
throw new \InvalidArgumentException('Unauthorized');
}
You can study more comparison traps and input validation flaws in the CodeReviewLab learning hub. Where Untrusted Input Starts Every audit requires tracing data from input boundaries to dangerous sinks. In PHP, inputs arrive through superglobals such as $_GET, $_POST, and $_COOKIE. A frequent review finding is the use of $_REQUEST. The superglobal $_REQUEST combines GET, POST, and COOKIE data based on the engine's request_order setting PHP superglobals. If a handler expects a token in a POST body but inspects $_REQUEST, an attacker can often supply that value in a GET query string instead. File uploads present a similar pitfall. The values in $_FILES['upload']['name'] and $_FILES['upload']['type'] come directly from client HTTP request headers Safeguard upload notes. They do not reflect server-side MIME detection. Treat both values as untrusted strings until verified by an extension allowlist and a server-side inspection tool such as finfo. Command Execution and Argument Injection When code runs operating system commands via exec(), shell_exec(), or system(), developers often try to sanitise parameters using escapeshellcmd(). The escapeshellcmd() function escapes shell metacharacters like pipes and semicolons, but it leaves spaces and command switches untouched PHP escapeshellcmd manual. If untrusted input represents command flags rather than a fixed argument, an attacker can pass arbitrary switches. // Insecure: escapeshellcmd allows space-separated parameter switches
$extraOptions = escapeshellcmd($_GET['options']);
system("tar -czf /backups/archive.tar.gz {$extraOptions} /var/data");
In this example, an attacker can supply --checkpoint=1 --checkpoint-action=exec=sh to achieve arbitrary code execution. Passing an array of arguments to proc_open() bypasses the system shell, which prevents shell metacharacter injection proc_open documentation. However, proc_open() does not prevent argument injection if the binary...

Powered by Readybot.io

PHP Security Audit: Spot Hidden Authentication Flaws

Fix Docker x509 Errors in GitHub Actions CI

DEV Community

Docker x509 certificate signed by unknown authority in CI

Docker x509 certificate signed by unknown authority in GitHub Actions means the daemon reached your registry, read the certificate it presented, and found no trusted root behind it, so to fix registry TLS you install that CA where Docker looks for it. Marking the registry insecure also silences the error, and it turns off the check that was protecting every layer the job pulls. Three places hold a CA, and they serve different clients. Installing one and testing with another is why this error survives the first fix. What this error means Public images pull normally and one host fails, every time, at the first request rather than part way through a download. Two wordings are in circulation: the older one ends the Get line with "x509: certificate signed by unknown authority", and newer clients put "tls: failed to verify certificate" in front of it, which is the shape cnoe-io/idpbuilder#520 reports. The failing URL is usually the registry /v2/ endpoint, or something under it, because verification happens during the handshake and the first request is the one that trips it. We did not reproduce this on a runner: it needs a registry holding a certificate from a private CA, which we are not going to stand up on the public internet, so the log below is the shape a private registry prints rather than a recorded run. ```The shape a private registry prints (illustrative, not a recorded run) Error response from daemon: Get "https://registry.internal.example.com:5000/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority

Common causes

The registry certificate comes from a private CA

The ordinary case. An internal registry, a corporate proxy that re-signs TLS, or a self-signed certificate on a test registry all present a chain that ends in a root the runner has never seen. Nothing is wrong with the certificate; the runner simply has no reason to trust it.

The CA was installed in the wrong trust store

The wasted fix on this page. A certs.d entry does nothing for a build running inside BuildKit or for a curl in the next step, and the system store does not cover a daemon lookup that has its own directory. In our experience the second attempt at this error is the same certificate in a different place.

The directory name does not match the registry reference

The daemon looks up the directory by the host and port exactly as they appear in the image reference. A registry pulled as registry.internal:5000 needs the directory with the port; the same registry behind a proxy on 443 needs the directory without one. A mismatch leaves the file on disk and the daemon unaware of it.

The chain is incomplete rather than untrusted

A server configured to send only its leaf certificate leaves the client unable to reach the root it does trust, and the error reads the same way. Check what the server actually presents before assuming the CA is missing locally, because the fix is on the registry rather than on the runner.

How to fix it

Install the CA where the daemon looks

  1. Create the certs.d directory for that registry, with the host and port spelled as your references spell them.
  2. Write the CA certificate into ca.crt in that directory.
  3. Pull one image from that registry in the same job to confirm the trust took effect.

```Terminal
sudo mkdir -p /etc/docker/certs.d/registry.internal.example.com:5000
sudo cp internal-ca.crt /etc/docker/certs.d/registry.internal.example.com:5000/ca.crt
docker pull registry.internal.example.com:5000/acme/api:1.4.2
Add it to the system store for everything else Anything that is not the daemon reads the system bundle: curl, Helm, a language HTTP client, a scanner. Copy the CA into the system directory and refresh the bundle, in the same step that wrote the daemon copy, so the two can never drift apart. sudo cp internal-ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates
curl -sSf https://registry.internal.example....

Powered by Readybot.io

Fix Docker x509 Errors in GitHub Actions CI

DNS over HTTPS: Resolver Policy and Security

DEV Community

DNS over HTTPS changes resolver policy, not just transport

DNS over HTTPS changes resolver policy, not just transport DNS over HTTPS sends DNS queries and responses through HTTPS. RFC 8484 maps each query and response pair to an HTTP exchange and defines the application/dns-message media type for the DNS wire format. The encryption protects the path between the client and the selected DoH endpoint. It does not make the resolver disappear, and it does not make every DNS answer globally consistent. That distinction matters in a security review. An endpoint can be encrypted, authenticated, and still return policy-dependent answers. Split DNS, local filtering, enterprise domains, and resolver-side logging all remain part of the system. Moving clients to a public DoH service can change both the answer and the evidence available to the network owner. Choose the resolver before the protocol Record which resolver the client is configured to use, how it discovered the endpoint, and how it validates the TLS connection. RFC 9462 describes discovery of designated resolvers through SVCB records. RFC 9463 describes network-designated resolver options for DHCP and router advertisements. These mechanisms solve configuration distribution. They do not remove the need to define which resolver is authorized for which network. An enterprise laptop on a corporate network may need the internal resolver for private names. The same laptop on a hotel network may need a public resolver only for public names. A DoH client that sends every query to one public endpoint can break split-horizon resolution or send internal names to a service the organization did not intend to trust. Create a resolver policy by environment, device class, and name scope. Pin the endpoint and its certificate expectations. Document the fallback path when the DoH endpoint is unavailable. A fallback to cleartext DNS may be acceptable for some networks and unacceptable for others. Account for the HTTP surface DoH uses a URL template and the DNS message media type. The client must validate the HTTPS certificate and process the DNS response format, including truncation and error conditions. A response can arrive over HTTP status codes that do not map directly to DNS errors. Logging should distinguish a transport failure, a TLS failure, a malformed DNS message, and a valid negative answer. The resolver can still see the query name and client address unless a privacy design such as Oblivious DoH is used. RFC 9230 describes one such relay design. It does not turn every DoH deployment into an oblivious one. Explain what the chosen service can observe and retain. HTTP intermediaries also matter. A proxy that inspects DoH traffic may defeat the privacy goal, while a cache or logging layer may retain URLs and request bodies. Apply the same data-minimization rules to HTTP access logs as to DNS logs. Do not log full query strings from sensitive applications unless the investigation requires it and the retention policy permits it. Keep DNSSEC and DoH separate DoH protects transport to the resolver. DNSSEC authenticates signed DNS data through a chain of trust. A client can use DoH and still receive a forged or unsigned answer from a resolver that does not validate DNSSEC. Conversely, a DNSSEC-validating client can use an unencrypted transport and still verify signed answers. Document which layer supplies confidentiality, which layer supplies data authentication, and which resolver performs validation. Test a signed name with a valid chain, a bogus signature, and an unsigned name that policy should reject. Check the result at the application, not only the resolver log. Roll out with a name map Build a list of internal zones, partner domains, break-glass names, and public domains that applications need. Test each class through the new endpoint. Include a service that resolves through the corporate proxy and a service that performs direct DNS. Confirm that the application receives the expected answer, not just that port 443 is reachable. Record the re...

Powered by Readybot.io

DNS over HTTPS: Resolver Policy and Security

How to Quote a Small Business Website Project

DEV Community

How I Quote a Website Project: What a Small Business Should Expect

When a small business asks me "how much for a website?", the honest answer is always the same: it depends on what the site has to do. A quote is not a price pulled from thin air. It is a short list of decisions, and the business owner should be able to see every one of them. This is how I build a quote, and what a business should expect from any developer who quotes one. 1. Start with the job, not the price The first question is never about money. It is about what the website has to do: show services and prices, take bookings, sell products, or just be findable on Google. A simple brochure site with five or six pages is one job. A site with online booking or an online shop is a different job, with more moving parts and more testing. I ask the owner to describe the site in one sentence: "a website where customers can see my services and book an appointment." That sentence decides almost everything else. 2. Count the pages and the features separately A quote has two parts. The pages — Home, Services, About, Contact, and any others — are one part. The features are the other: a booking form, a WhatsApp button, a photo gallery, a map, a contact form that actually reaches the owner. Listing them separately matters, because it lets the owner change the quote. If a feature is not needed now, it can wait. A quote that hides everything in one number gives the owner nothing to decide about. 3. Say what is included — and what is not A good quote names what the price covers: the design and build, the basic SEO setup (titles and meta descriptions on every page), the site going live on the owner's domain, and a short handover showing the owner how to change text and photos. It should also name what it does not cover, so there are no surprises: the domain and hosting costs (paid to the hosting company, not to me), any paid plugin the site needs, and new pages or features added after launch. Those are separate, and the owner should know that before work starts. 4. Give one number, in writing I give a single fixed number for the agreed pages and features, in writing — usually by message or email — before any work starts. A simple small-business website typically falls in a range I can state upfront once I know the pages and features; a booking or shop feature moves it up, because it adds real work and testing. A range is honest at the quoting stage. A fixed number is what the owner gets once the scope is agreed. What a quote should never be is a number that grows during the work without anything being added. 5. Ask for a deposit, and say when the rest is due For a small project, I ask for a deposit before starting and the rest when the site is live and the owner is happy with it. The deposit protects the developer's time; paying the rest on delivery protects the owner. Both sides should know the schedule before the first page is built. 6. Show the work before it goes live Before a site goes live, the owner should see it — a preview link or a staging site — and be able to ask for changes. The pages, the text, the photos, the phone number: the owner checks all of it, because it is their business and their customers who will read it. A developer who launches a site without the owner seeing it first is skipping the most important step of the whole job. That is the whole process: understand the job, list the pages and features, say what is included, give one number in writing, agree the payment schedule, and show the work before launch. A business that gets a quote like that knows exactly what it is paying for. And a developer who quotes like that has nothing to hide. Author Bio Muhammad Asad Bukhari is a freelance WordPress developer based in Lahore, Pakistan. He builds WordPress websites for small and service businesses, including salons and spas, sets up AI automation with n8n workflows, and works on SEO for WordPress sites. His portfolio is at https://asadbukhari.site.

Powered by Readybot.io

How to Quote a Small Business Website Project

Build a Verifiable Product Management Portfolio

DEV Community

Building a Product Management Portfolio That Survives Review (Without Inventing a Single User)

Most early-career product management candidates face the same trap: job posts ask for evidence of shipped work, and the honest answer is that you have not shipped anything yet. The tempting shortcut is to inflate — invent a user base, claim revenue, dress up a class project as a launch. It works right up until someone asks a follow-up question in an interview. There is a better path. A portfolio can be built entirely from work that is real, small, and verifiable, as long as you structure it around evidence rather than storytelling. Here is how to do that. 1. Separate evidence from narrative Every portfolio claim falls into one of two buckets: something a reviewer can inspect, or something they have to take on faith. Faith-based claims are cheap and therefore discounted. Reviewable evidence — a written PRD, a defined metric with a calculation, a SQL query that returns a specific number, an evaluation plan with labeled criteria — costs you effort and therefore signals something. A simple test before you publish any project page: underline every sentence and ask "what could a reviewer click, read, or recompute to confirm this?" Sentences with no answer get cut or rewritten. 2. Label simulated work as simulated If you work through a project brief rather than a live product, say so in the first line of the page, not in a footnote. "Simulated project brief: I defined the problem, drafted the PRD, and built an evaluation plan; no real users or revenue are represented." This is not a weakness. Reviewers who have read a hundred inflated portfolios find clear labeling refreshing, and it protects you from the follow-up question you cannot answer. 3. Show the AI decisions, not just the AI feature If your project involves AI, the interesting part for a reviewer is your judgment, not the model. Document: what you chose to automate and what you kept human, what you refused to build, how you planned to catch failures, and what would make you reverse the decision. A one-page decision log beats a polished mockup. 4. Write the evaluation plan before the feature spec An evaluation plan is the cheapest high-signal artifact you can produce. Define the task, the inputs, the output you will grade, and the rubric — then build a small evaluation set as a CSV with a rubric definition alongside it. Even twenty rows, honestly graded, demonstrate more rigor than a diagram of an architecture you never tested. 5. Make metrics auditable For each metric you cite, include the definition, the numerator and denominator, the time window, and where the number came from. If it came from a simulated dataset, say that. An auditable small number is worth more than an impressive vague one. 6. Fix the three biggest problems, not all twelve Portfolio revision goes wrong when candidates try to rewrite everything at once. A more effective loop is: answer, score, revise, retry — and keep before-and-after versions so you can see what actually changed. Prioritize ruthlessly. Three fixes to the weakest dimensions will move a portfolio further than twelve cosmetic edits. This is the loop the free 12-point rubric at OfferKu is built around: twelve transparent, self-reported criteria spanning evidence, AI decisions, evaluation, metrics, project integrity, and presentation. It returns dimension scores and up to three highest-priority revisions in about five minutes, with no name and no material upload required. Treat the score as a diagnostic, not a verdict. 7. Optimize the first thirty seconds Presentation is its own dimension. Assume a reviewer gives your page half a minute. Lead with the problem, what you produced, and what evidence exists. Put artifacts near the top, links working, file names descriptive, and remove anything you cannot explain out loud. A weekly cadence that actually compounds Pick one brief per week. Produce one artifact set: a PRD, a metrics definition, a SQL query, and an evaluation plan. Label it simulated. Score it against the rubric. Make the three hi...

Powered by Readybot.io

Build a Verifiable Product Management Portfolio

Fix GitHub Actions Steps with Both Uses and Run

DEV Community

GitHub Actions step cannot have both uses and run, and what you get

A GitHub Actions step cannot have both uses and run, and that sentence was once the message you got, but the parser that ships today does not contain it anywhere. What it produces instead names one of the two keys as unexpected, which is a different sentence and a more precise one once you know how the reader arrives at it. The reader narrows as it reads. The first of the two keys chooses a shape, and the second has nowhere to go inside it. What this error means The workflow stops running and the commit carries an invalid file annotation with a line, a column and a quoted key. The quoted key is one of the two you wrote, and which one it is depends on the order you wrote them in rather than on which one is wrong. If you came here from an older answer expecting a sentence about both keys, the thing to check is whether your annotation says that at all, because a message that quotes a single key is telling you something the older sentence did not. ```Reconstructed from the key branch of TemplateReader and the UnexpectedValue format string in actions/runner Invalid workflow file: .github/workflows/ci.yml#L9 (Line: 9, Col: 9): Unexpected value 'run'

Common causes

Indentation merged two steps into one item

The commonest cause and the hardest to see. A missing dash turns what should be a new sequence item into another key of the previous one, and the file stays valid YAML throughout.

A command was replaced by an action in place

Somebody swapped the middle of a step, putting an action reference where a command was, and left the other key behind. The diff touches one line, which is why review passes over it.

An example was pasted inside an existing step

Documentation examples begin with a dash and assume they are being added as a new item. Pasted into the body of an existing step they become extra keys of it.

The step was meant to do two things

Occasionally the intent really was to run an action and then a command. In our experience this is rarer than the accidental cases, and the answer is the same: two steps.

How to fix it

Read which key the annotation names

  1. Open the annotation and note the quoted key and the column.
  2. The quoted key is the second one in your file, not necessarily the wrong one.
  3. Go to that line and look at whether it should have started a new sequence item.

Split into two steps

Give each of the two a dash of its own at the same indentation as the other items in the sequence. Name them if the job has several steps, since the default name of a run step is the command text.

```.github/workflows/ci.yml
steps:
- name: Check out
uses: actions/checkout@v5
- name: Install
run: npm ci
Check the whole sequence, not just the reported line An indentation slip that merged one pair often merged another further down, and the reader stops recording after it has enough to reject the file. Read every item in the sequence once before pushing again. Validate before pushing The same schema is available to editor tooling, so a file that will be rejected can be rejected locally. This is the only one of these fixes that prevents the next occurrence rather than resolving this one. How to prevent it Keep one action or one command per step, and treat that as a review rule. Watch for a missing dash whenever a step gains a key it did not have. Paste documentation examples after a step, never inside one. Validate workflow files in the editor against the published schema. What the reader does with the second key In the published schema an item in a step sequence is a choice between two mapping shapes. One requires the run key and additionally allows a working directory and a shell. The other requires the uses key and additionally allows with. Everything else the shapes accept, env among it, is on both. Neither shape lists the other required key, and neither permits loose keys. The reader does not collect your keys and judge them at the end. It narrows while it reads. Each key ...

Powered by Readybot.io

Fix GitHub Actions Steps with Both Uses and Run

How to Receive and Spend USDC in Indonesia with Raenest

DEV Community

Paid in USDC, Living in Indonesia: How I Use Raenest to Receive, Hold and Spend Stablecoins

I work in web3 from Indonesia, and a growing share of my work — bounties, contests, research and security gigs — pays in USDC on Solana. Getting paid was never the hard part. A client or a platform sends stablecoins to my wallet, and they arrive in seconds. The hard part starts after the money lands. A wallet full of USDC is not the same thing as money I can use. I still need dollars I can hold safely, a way for them to not just sit idle, and a practical way to spend or move them when real life asks. For a long time my answer was a patchwork: keep everything in a self-custody wallet, swap through an exchange when I needed local currency, and accept the friction at each step. That is the gap I opened a Raenest account to close. This article walks through what Raenest does for someone paid in stablecoins from an emerging market — without pretending any of it is magic. What Raenest is Raenest describes itself as "your global money partner to receive, send and spend." It is a financial technology platform, not a bank — banking services are provided by its partners. Inside one app you can open global accounts in USD, GBP and EUR, alongside dedicated wallets for USDT and USDC, plus invoicing and payment links for getting paid by clients, money transfers, and virtual and physical cards. For a freelancer or remote worker, the pitch is simple: one place where money earned in different currencies — including stablecoins — can be received, held, converted, spent or sent on, instead of being scattered across wallets and exchanges. Receiving USDC and USDT This is the feature that matters most to me. On Raenest, USDC and USDT each have their own dedicated wallet, with its own balance, wallet address and transaction history. Setting one up takes less than a minute: open the app, go to Accounts, switch to the Stablecoin tab, and create your wallet. To get paid, you share your wallet address with the sender. Stablecoin deposits are supported across several networks, including Ethereum, Tron, Binance, Polygon and Solana. Because most of my payouts move on Solana, that support is the deciding detail: a sponsor or client can send USDC straight from their Solana wallet to my Raenest address, and it lands in my USDC wallet. One rule matters more than everything else here: the sender must use a network your wallet supports. Sending on the wrong network can keep funds from arriving correctly, so I always state the network explicitly when I share an address — "USDC, Solana network" — rather than assuming the sender will guess. A second detail is worth knowing. Incoming stablecoins are no longer automatically converted to USD on arrival. They stay in their original form until you decide what to do with them. That control is the whole point: the same balance can be held, converted, spent or sent, on your schedule rather than the platform's. Adding money with SOL Not everything I earn arrives as a stablecoin. Sometimes the payout is SOL itself. Raenest covers that case too: through the Add Money flow you can deposit SOL, BTC or ETH, and Raenest converts the deposit into USDC in your stablecoin wallet. A small feature, but it removes a step — no detour through an exchange just to turn a SOL payout into dollars. Holding dollars — and putting idle ones to work Once stablecoins are in Raenest, there are two sensible things to do with them: hold them as dollar exposure, or put part of them to work. For the second option, Raenest offers the Stablecoin Vault: move USDC or USDT in and earn variable returns, advertised at up to 7% APY on eligible balances. Per Raenest's help centre, the minimum deposit is $100, earnings begin 24 hours after a deposit is processed, and they are added daily, so they compound over time. Now the honest part, because this is where marketing usually gets loud. "Up to 7%" is a ceiling, not a promise. The rate is variable and moves with market conditions; the rate in the app on a given day may be lower. The Vault is a yield product, ...

Powered by Readybot.io

How to Receive and Spend USDC in Indonesia with Raenest

Warako PDF Scanner Seeks 12 Android Testers

DEV Community

Closed test swap: Warako (PDF scanner) needs 12 testers, I'll test yours too

Hi all, I'm an indie developer and my app Warako is in a Google Play closed test. I need testers to stay opted in for 14 days, and I'm happy to test yours in return. About Warako: a PDF scanner for Android. It scans documents, receipts and ID cards into clean PDFs, reads Arabic and Latin text on the phone (OCR), and lets you sign and fill forms. Documents stay on the phone: no account, no upload to my servers. The interface is in English, Arabic, French, Spanish and German. What I ask: Join the test with your Gmail: https://play.google.com/apps/testing/com.amrani.docscan Install it and keep it installed for 14 days Open it a couple of times and tell me anything that is confusing or broken What I offer: I will install and keep your app for 14 days too I will send you written feedback (what worked, what didn't) Comment with your opt-in link and I'll join today.

Powered by Readybot.io

Warako PDF Scanner Seeks 12 Android Testers