Pixel

Pixel Updates & Events

Server Updates & Events

Announcements

DEV Community

Why Architecture Matters More Than Syntax in 2026

Introduction: The Shift from Code to Design For over a decade, I've been practicing Laravel's MVC (Model-View-Controller) and Repository patterns. These taught me valuable lessons about separation of concerns, data abstraction, and clean code composition. They gave us the foundation for reliable enterprise applications. But times have changed. As AI agents increasingly take over mundane coding tasks—from writing boilerplate to generating test cases—the dynamic between developer and machine has fundamentally shifted. We now live in an era where the minutiae matter more than ever. AI can write the syntax; it cannot reliably understand context across complex systems, navigate structural dependencies, or maintain architectural intent when pushed too far. Where once we were constrained by our own ability to structure code, agents today are constrained by how well they understand your application's architecture. The question that now arises isn't "how do I write better syntax?"—it's "how do I create architectures resilient enough to contain and guide both human developers and AI agents alike?" This is why, in my recent work, I've been transitioning away from traditional MVC patterns toward CLEAN architecture, with its data abstraction layer deliberately positioned away from business logic. While this may sound like architectural purism for its own sake, the practical results speak for themselves: sharper structure, informed agents, faster feature development, and a clearer mental model for both myself and my collaborators—human or otherwise. The Problem: AI Agents Are Better at Syntax Than Design Modern AI coding assistants are remarkably proficient at syntax generation. They can produce perfectly formatted functions, write comprehensive unit tests, and generate documentation that reads like it came from someone with deep expertise. But this proficiency often comes at the cost of structural coherence. Consider this common scenario: an AI agent generates code for a new feature based on natural language instructions or incomplete requirements. It writes the controller logic correctly but forgets to update data access patterns It creates services that mirror the structure of unrelated systems in the same project It uses over-engineered abstractions where simple composition would suffice It makes "fixes" that actually introduce hidden dependencies or violate existing architectural principles The result? Code that runs, tests pass, but doesn't compose well with other changes. When another feature needs to be added later, the structural debt compounds. Agents are exceptionally good at their task within a narrow context window—they're bad at understanding how that context fits into a larger system's architecture. CLEAN Architecture: Keeping the Data Abstraction Separate CLEAN architecture—also known as "Onion Architecture"—positions the domain (business logic) at the center, surrounded by layers of abstraction that provide services and interfaces without containing business rules themselves. The Layer Structure ┌─────────────────────────────────────────┐
│ VIEW LAYER │ ← Presentation/UI
│ Controllers, Renderers, UI Components │ AI agents work with least precision here
├─────────────────────────────────────────┤
│ APPLICATION LAYER │ ← Use cases/Orchestrators
│ Facades, Service Composition │ Agents can approximate intent
├─────────────────────────────────────────┤
│ DOMAIN (BUSINESS) LAYER │ ← Core rules, invariants
│ Value Objects, Entities, Services │ Most constrained layer
├─────────────────────────────────────────┤
│ DATA ABSTRACTION LAYER │ ← Repositories, Interfaces
│ DAOs, DTOs, Mappers │ Agents write implementations against contracts
└─────────────────────────────────────────┘
The Key Difference: Separating View from Domain In traditional MVC patterns, the data abstraction layer sits...

Powered by Readybot.io

**DEV Community** ## Why Architecture Matters More Than Syntax in …

DEV Community

Keeping link blocks stable across platforms #8oan8

Related-link blocks should be generated once and appended after the article body; platforms differ in markdown and HTML support. Related Source repository Mbin instance Author profile on DEV

Powered by Readybot.io

**DEV Community** ## Keeping link blocks stable across platforms #8oan8 …

DEV Community

Circuit Breakers in Production: Timeouts, Retry Storms, and Failure Recovery

A dependency doesn't have to go completely offline to cause trouble. Sometimes it just gets slow enough that requests pile up, workers stay busy, and retries add even more traffic to a service already struggling. A circuit breaker gives that dependency breathing room—but only if the timeouts and retries around it are configured with the same care. The Problem Working with multiple LLM providers (OpenAI, Anthropic, Google) presents several challenges. Each provider has different failure modes, rate limits, and latency characteristics. Without proper handling, a slow or failing provider can bring down an entire pipeline. The naive approach is to add timeouts and retries. But this creates new problems: Retries during an outage amplify the problem (retry storms) Long timeouts block resources and cascade failures Users wait for failures we already know will happen What is a Circuit Breaker? The circuit breaker pattern, popularized by Michael Nygard in "Release It!", works like an electrical circuit breaker. When failures exceed a threshold, the circuit "opens" and immediately fails requests without attempting them. Three states: Closed - Normal operation. Requests pass through. Open - Failure threshold exceeded. Requests fail immediately. Half-Open - Testing if the service recovered. Let a single probe request through. The half-open state is where most implementations go wrong. If the dependency is just coming back, the worst thing you can do is send it the full load again. One probe is enough to learn whether it recovered. Implementation Here's a simplified circuit breaker. It is per-process: the lock protects threads in one process, and nothing here coordinates between processes (more on that below). import time
from datetime import timedelta
from enum import Enum
from threading import Lock

class CircuitState(Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"

class CircuitBreaker:
def init(
self,
failure_threshold: int = 5,
recovery_timeout: timedelta = timedelta(seconds=30),
probe_lease: timedelta = timedelta(seconds=10),
):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.probe_lease = probe_lease

    self.state = CircuitState.CLOSED
    self.failure_count = 0
    self.opened_at = 0.0
    self.probe_deadline = 0.0   # when the current probe's claim expires
    self.lock = Lock()

def can_execute(self) -> bool:
    with self.lock:
        now = time.monotonic()

        if self.state == CircuitState.CLOSED:
            return True

        if self.state == CircuitState.OPEN:
            if now - self.opened_at < self.recovery_timeout.total_seconds():
                return False
            # Timeout over: this caller claims the probe.
            self.state = CircuitState.HALF_OPEN
            self.probe_deadline = now + self.probe_lease.total_seconds()
            return True

        # Half-open: one probe is already out. Everyone else fails fast,
        # unless the probe's lease expired (the prober never reported back).
        if now >= self.probe_deadline:
            self.probe_deadline = now + self.probe_lease.total_seconds()
            return True
        return False

def record_success(self):
    with self.lock:
        if self.state == CircuitState.HALF_OPEN:
            self.state = CircuitState.CLOSED
        self.failure_count = 0

def record_failure(self):
    with self.lock:
        if self.state == CircuitState.OPEN:
            return   # a late result from a call that started before we opened

        if self.state == CircuitState.HALF_OPEN:
            self._open()   # the probe failed: back to open, restart the timer
            return

        self.failure_count += 1
        if self.failure_count >= self.failure_threshold...


Powered by Readybot.io

**DEV Community** ## Circuit Breakers in Production: Timeouts, Retry Storms, …

DEV Community

The Complete Web Attack-Surface Checklist, Built on Seven Boundaries

Security looks like a thousand unrelated bugs. SQL injection, broken access control, use after free, server-side request forgery, a subdomain takeover, an integer overflow in a codec. A beginner tries to memorize them one at a time and drowns. The list never ends, and every new framework invents ten more names for the same thing. They are not a thousand things. They are one thing, seen from seven angles. Every vulnerability is the same move. An attacker crossed a trust boundary that was supposed to hold. The system assumed something would stay on one side of a line, and it did not. Classify by which line broke, and the thousand names collapse into seven boundaries. The first half of this piece is the model. The second half is the complete checklist that falls out of it, so you can run the whole surface without skipping anything. The one sentence A vulnerability is a crossed trust boundary. That is the whole field in six words. Everything else is which boundary, at which layer, in how many steps. The names on the thousand-item list describe the technique. The boundary describes the reason the technique works. Technique changes every year. The boundaries have not changed in decades, because they come from how systems are built, not from what tool is in fashion. The seven boundaries 1. Identity. The system has to answer "who are you," and it answers wrong. An attacker forges or bypasses the proof. The question: can the proof of identity be forged or skipped? 2. Authorization. Identity is real this time, but the user reaches past what they are allowed to touch. The question: with a valid account, can I reach another account's data or another tenant's world? 3. Data versus code. Input that was supposed to be data gets executed as code. The injection family. The question: can my input stop being a value and start being an instruction? 4. The confused deputy. A trusted intermediary is tricked into acting for the attacker. The essence is not injecting code, it is borrowing a hand the system already trusts. The question: can I make a trusted party act on my behalf? 5. Business logic. Every technical check passes and the rules still have a gap. The question: does the business rule have a case nobody wrote down? 6. Configuration. Someone left a door open. A dangling DNS record, a key in a public repo, an internal dashboard exposed. The question: is there a door left open that was supposed to be shut? 7. Memory. The program writes outside the bounds or the lifetime it intended, and the machine keeps running on corrupted state. The question: can I write outside the lines the program drew? Whatever bug you are staring at, it is a crossing of one of these, or a chain of several. Three distinctions people get wrong Authentication is not authorization. Authentication is who you are. Authorization is what you can do. Most of the money lives in authorization, especially object-level access control and multi-tenant isolation, because crossing from one user or one company to another is direct harm. The confused deputy is its own class. Do not fold server-side request forgery into injection. Injection makes the target run your code. A confused deputy makes a trusted party run your errand with its own authority. On the web, the server's network position is worth far more than its CPU. One bug can span two boundaries. Cross-site scripting is both injection and confused deputy, which is why it is so often the first link in a chain. The layer decides the weapon The boundary tells you the kind of bug. The stack layer tells you which boundaries are even on the table. Memory corruption, boundary seven, only exists where code touches raw memory. That is the C, C++ and assembly world of firmware and native applications. In the managed world of Python, JavaScript, Go and Java, boundary seven is absent. The runtime checks the bounds and manages the lifetimes, so those systems break on boundaries one through six instead. So there are two battlefields and t...

Powered by Readybot.io

**DEV Community** ## The Complete Web Attack-Surface Checklist, Built on …

DEV Community

What Apple did not disclose about CVE-2026-112616

What Apple did not disclose about CVE-2026-112616 The public record for CVE-2026-112616 is brief. Apple identifies a Screen Time authorization issue, says an app may access sensitive user data and states that improved access control fixed it. The affected releases include iOS 27.2, iPadOS 27.2, macOS Golden Gate 27.2 and visionOS 27.2. Security writing often turns an advisory into a complete attack narrative. This CVE does not provide enough information for that. The gap is not a reason to ignore the update, but it does change what defenders can claim. What is established The source advisory lists the affected product family and fixed versions. Apple's iOS and iPadOS security note places CVE-2026-112616 under Screen Time and says an app may access sensitive user data. The GitHub Advisory Database classifies it as CWE-862 and gives CVSS 3.1 a score of 5.5. The CVSS vector is local, low privileges, no user interaction, unchanged scope and high confidentiality impact. Integrity and availability are none. This is a consistent account of a local privacy-boundary failure with no advertised effect on data integrity or service availability. The same CVE appears in macOS Golden Gate and visionOS security pages. Apple's iOS and iPadOS page lists supported devices from iPhone 11 onward and several modern iPad families. What remains unknown The sources do not name the vulnerable Screen Time interface. They do not describe the data returned to the app, the permission state that bypassed the check or the code path that triggered it. They also do not provide a proof of concept. Without that detail, an analyst cannot build a reliable exploit detector from the advisory alone. A log event that looks related may be normal Screen Time activity. A local app with broad permissions may be suspicious for other reasons, but the CVE record does not provide a signature. The sources reviewed for this article do not report active exploitation. That statement describes the current evidence, not a guarantee that exploitation will never occur. Why the gap exists Vendor advisories often prioritize affected versions, impact and remediation over exploit detail. Apple says its security documents reference vulnerabilities by CVE when possible. The absence of technical detail can reduce the value of a ready-made attack recipe while still giving defenders enough information to patch. This is a reasonable security tradeoff, but it creates a communication problem. Teams may be asked whether a specific Screen Time record was exposed. The honest answer is that the public source does not identify the record. A response should use device evidence rather than extrapolate from the advisory. Investigate with restraint Preserve the installed version, app inventory, installation source and support timeline for a device of concern. Compare them with the known fixed release and the local attack condition. If the device was patched before the suspicious activity, the CVE is a weaker explanation. If it was unpatched and ran untrusted code, the case deserves more review. Avoid testing the vulnerability against production devices. Public sources do not provide a safe reproduction procedure, and an authorization flaw can expose user data. Use vendor updates and policy controls. Exposure evidence At 19:12:52 UTC on November 15, 2026, ZoomEye returned 1,591,959 matches for app="iOS". At 19:12:56 UTC, vul.cve="CVE-2026-112616" returned 0. The larger value is a product-fingerprint count. It does not identify the affected Screen Time data, and it does not confirm vulnerable software. The filter result is an index observation, not evidence that every asset is safe. Conclusion CVE-2026-112616 is sufficiently documented to justify patching and app review. It is not documented well enough to support a detailed exploitation story. The useful response is to close the known version gap, preserve uncertainty and investigate local evidence without inventing the missing data. References NCSC-N...

Powered by Readybot.io

**DEV Community** ## What Apple did not disclose about CVE-2026-112616 …

DEV Community

Hydrogen, Carbon or Argon? A Cost-First Guide to Gemini 4 for Every Team

In the last few weeks, Google shipped three new Gemini 4 models: Argon (GA October 13, 2026), Carbon (GA November 10), and Hydrogen (GA November 12). Then came cyber versions of two of them, plus a world model called Genie 4. The names come from the periodic table, so they tell you nothing about which one is bigger, faster, or cheaper. Hydrogen is the lightest element, but it is not a "lite" model in the old sense. Cost sits at the center of this guide, because every role touches the bill. You'll find a section for each team: developers, SREs and platform engineers, security and compliance reviewers, engineering leaders and architects, and of course FinOps. Each one ends with your own cost lever: the one thing in your hands that moves the bill. All facts come from the Gemini Enterprise Agent Platform model pages on Google Cloud (last updated November 13, 2026), the Agent Platform pricing page, and Google's launch posts. Find your section If you are... Your big question Your cost lever FinOps or finance What will this cost us, and when does it change? Routing policy and request labels Developer What breaks when I switch models? Thinking level on every call SRE or platform engineer Where does it run, and how long can a call take? Timeouts and region choice Security or compliance What terms and access rules apply? Approval path for Carbon and cyber models Engineering leader or architect Which model should be our standard? Default model and escalation rules The 30-second cheat sheet Gemini 4 Hydrogen (gemini-4-hydrogen): the fast, cheaper workhorse for everyday coding and agent work. Google calls it the upgrade path from Gemini 3.8 Flash. Think of it as your default. Gemini 4 Carbon (gemini-4-carbon): Google's strongest model. It thinks the longest and spends the most thinking tokens. Use it for planning, deep debugging and hard root-cause work. Gemini 4 Argon (gemini-4-argon): the first frontier Gemini 4 model. Same per-token price as Carbon, but it uses fewer thinking tokens. It fits a narrow middle ground: jobs where Hydrogen falls short and Carbon is too slow. Cyber variants (allowlist only): special builds of Argon and Carbon for defensive security work. Most teams will never touch these. Genie 4: not a text or code model at all. It's a world model in research preview. Skip it for app building. What are the cyber variants? The cyber models are versions of Argon and Carbon tuned for defensive security work: finding vulnerabilities, reverse engineering, verifying exploits and writing patches. You can't just turn them on. Access is allowlist only, through your Google team or the Fairwind Program. Gemini 4 Argon Cyber Permissive Gemini 4 Carbon for cyber Model ID gemini-4-argon-cyber-permissive gemini-4-carbon-cyber-permissive Based on Gemini 4 Argon Gemini 4 Carbon Default thinking level HIGH (normal Argon is MEDIUM) HIGH (normal Carbon is MEDIUM) Computer use Not supported Not supported Regions global, us, eu global, us Price, global, per 1M tokens in/out $4 / $20 (no intro promo) $4 / $20 (no intro promo) Terms Section 31 of Service Specific Terms Section 31 of Service Specific Terms Three things to watch: The naming is not consistent. The Carbon version is called "Carbon for cyber" in the docs, but its model ID still ends in "cyber-permissive". Use the model ID, not the display name, in your code and docs. Access does not carry over. If you already have Argon Cyber Permissive or Gemini 3.8 Flash Cyber, you still need to apply again for Carbon for cyber. Higher default thinking means higher token spend. Both cyber models default to HIGH, so expect more output tokens per call than the base models. Side by side On Google Cloud the three base models look almost the same on paper. The real differences are thinking behavior, price, regions, file types and who can get access. Hydrogen Argon Carbon Model ID gemini-4-hydrogen gemini-4-argon gemini-4-carbon GA date Nov 12, 2026 Oct 13, 2026 Nov 10, 2026 Best for Everyday coding, ag...

Powered by Readybot.io

**DEV Community** ## Hydrogen, Carbon or Argon? A Cost-First Guide …

DEV Community

Hathway Router Login: Default IP, Username and Password

Hathway is one of India's largest broadband providers, and its routers — often supplied by brands such as TP-Link, D-Link, or Nokia depending on your plan — all share the same way in: a local admin panel where you manage Wi-Fi, passwords, and connected devices. This guide shows you how to log in, how to find the right address, and how to recover access when the password fails. Before you start: connect to the router You can only reach the admin panel from inside your own network. Join the Hathway Wi-Fi network, or connect your computer to one of the router's LAN ports with an Ethernet cable. A wired connection is more reliable if the login page keeps timing out, and it rules out Wi-Fi trouble as the cause of whatever problem you are chasing. Step 1: Find the default gateway address The gateway address is the local IP your router listens on, and it varies by model. Rather than guessing, check what your device actually uses: Windows: open Command Prompt and run ipconfig. Look for "Default Gateway" under your active connection. macOS: open System Settings, go to Network, select your connection, and read the router address — or run netstat -nr | grep default in Terminal. Linux: run ip route | grep default. Phone: on iPhone, tap the (i) next to your Wi-Fi network and read the Router field; on Android, look under Settings > Wi-Fi > network details. The address your device reports is the one to use. Hathway-supplied routers commonly use 192.168.0.1 or 192.168.1.1, but the value on your network is the only one that matters. Step 2: Open the login page Type the gateway address into your browser's address bar with http:// in front — for example, http://192.168.0.1 — and press Enter. Use the address bar, not a search box, and do not add www. or .com. The router's login page should appear. Step 3: Enter the username and password The login page asks for a username and password. The factory defaults for your model are printed on the sticker on the bottom or back of the router, usually beside the Wi-Fi name and password. Enter them exactly as shown — they are case-sensitive, and the admin password is not the same as the Wi-Fi password. Do not guess common defaults or copy credentials from another brand's router; Hathway ships different models with different credentials. For the exact verified defaults for your model, see the guide at WiFi Router Zone. Once you are in, change the admin password to something unique and keep it in a password manager. The admin panel controls your whole home network, so leaving the factory credentials in place is an open invitation to anyone who gets on your Wi-Fi. Troubleshooting The login page won't load. Confirm you are on the Hathway network and not on a mobile hotspot or VPN. Then re-check the address — one wrong digit sends you nowhere — and try a private browser window to rule out a stale cache or an extension. If it still fails, restart the router: unplug it for 30 seconds, plug it back in, and wait two minutes before trying again. The password is rejected. Re-read the label carefully; it is easy to confuse a zero with the letter O, or to type the Wi-Fi password instead of the admin password. If you changed the admin password during installation, the factory defaults no longer apply — check your password manager. If nobody remembers it, the only way back in is a factory reset: hold the reset button for about 10 seconds with a paperclip. That restores the factory settings, including the factory Wi-Fi name and password, so you will need to set the router up again afterward. The page loads but looks broken. Some older router interfaces only render properly in a desktop browser. Try Chrome or Firefox on a computer instead of a phone browser, and disable any content blocker for the router's address. What you can do once you are in Inside the admin panel you can change the Wi-Fi name and password, see which devices are connected, reboot the router, check for firmware updates, and set up a guest network or ...

Powered by Readybot.io

**DEV Community** ## Hathway Router Login: Default IP, Username and …

DEV Community

Your Supabase key is in your deployed JavaScript. One of them is fine, one is an emergency

Someone opened your live site, pressed Ctrl+U or F12, and found your Supabase key sitting in the code. Or a scanner flagged it, or the AI that built the app warned you about it after the fact. Now you do not know whether you have shipped something harmless or handed over your whole database. Both are possible, and the difference is one word inside the key. This note is the check, and the fix if the answer is the bad one. The check, in about a minute Step 1. Find the key. Open your live site, press F12, go to the Sources or Debugger tab, and search the files for supabase. Or on your own machine, build the app and search the output folder: npm run build
grep -ro "eyJ[A-Za-z0-9_-]" dist/ | head
grep -ro "sb_[a-z]
_[A-Za-z0-9]" dist/ | head
Use build instead of dist if that is what your framework writes to. Both commands are read-only. Step 2. Read what the key says it is. A Supabase key that starts with eyJ is a JWT, and its middle section is plain text that anyone can read. Paste the key into this, in a terminal: echo "" | cut -d. -f2 | base64 -d
You get something like this back: {"iss":"supabase","ref":"yourprojectref","role":"anon","iat":1762000000,"exp":2077560000}
The word after "role" is the only thing that matters. There are two answers: What you find What it means "role":"anon", or a key starting sb_publishable_ Not a leak. This key is meant to be public. Jump to If it says anon. "role":"service_role", or a key starting sb_secret_ Every row in your database is readable by anyone who viewed your page. Jump to If it says service_role. I tested that decode step on keys shaped exactly like Supabase's own, using nothing but cut and base64, and it returns the role for both kinds. You do not need a tool or a website for it, and you should not paste a key into one. If it says anon or sb_publishable You have not leaked anything. Supabase's own documentation lists a web page as a place a publishable key belongs, because it is "safe to expose online: web page, mobile or desktop app, GitHub actions, CLIs, source code". But read the next sentence carefully, because this is where vibe-coded apps actually get hurt. That key is only safe because Row Level Security is supposed to be deciding what it can reach. Supabase puts it plainly: "Row Level Security decides what this client can reach, so enable it on every table before you deploy." So the real question is not your key. It is whether RLS is on. Open your Supabase dashboard, go to Table Editor, and look at every table. Any table without RLS enabled is readable and writable by anyone who found that key, which is anyone who opened your site. Two of the free notes here cover what happens next, because switching RLS on is where most people's app breaks: Saving data starts failing with "new row violates row-level security policy" "infinite recursion detected in policy for relation", once you add sharing or roles Your project's dashboard also has a Security Advisor page, which checks for exactly this class of problem. Read each finding before dismissing it. If it says service_role or sb_secret Treat this as live, not theoretical. Supabase's documentation is unambiguous: a secret key "bypasses every Row Level Security policy you have. Never put one in a browser, a shipped application, or source control." I confirmed what that means on a real PostgreSQL 16.15 server rather than taking it from the docs. With RLS switched on and a policy restricting rows to their owner, a role subject to RLS returned 0 rows. A role with the BYPASSRLS attribute, which is what service_role has, returned every row in the table, including other users' data, with the same query and the same policy in place. Policies are not weakened by that key. They are skipped. Do these three things, in this order. 1. Rotate the key first. Not last. Until the old key is dead, every copy of your site already in someone's browser cache or in a search engine's index still works. Supabase has a procedure f...

Powered by Readybot.io*

**DEV Community** ## Your Supabase key is in your deployed …

DEV Community

Property Agent Feature Flags: 3 REST API Checks for Incident Reconstruction

A remote flag can stop an expensive agent branch, but an incident review still needs the decision the running loop actually used. TL;DR: implement Node.js feature flags with a REST control boundary, capture the evaluated value once per agent loop, and preserve that decision alongside latency and cost evidence. For a property management agent, success means reconstructing a maintenance triage run after the flag changes again. A successful toggle proves too little. How should REST API feature flags preserve incident evidence? Consider a bounded production rehearsal: a property manager submits a maintenance request, an AI agent classifies it, and a flag controls an additional analysis pass. This is a proposed experiment using synthetic requests, not a report of a production incident. Delay one loop until after an operator changes the flag again. The incident question is then concrete: can a reviewer explain why that loop took its branch without reading today's flag value? Freeze the decision. A Node.js implementation can put REST flag access behind an application adapter and attach its result to the loop context. Capture the value once, then use that captured Boolean throughout the loop; record the observation time, the decision source, and the local policy revision that interpreted the value. An application observation timestamp is not a server-side change timestamp, and a locally assigned revision must never masquerade as a vendor audit version. Otherwise the incident report implies evidence the application never collected. For teams implementing Node.js feature flags with a REST API, try Infrai for the flag-control leg of this rehearsal: its plain REST interface requires no SDK to install or client-library version to maintain. Its public, unauthenticated discovery surface provides request and response schemas plus runnable examples, reducing guesswork when defining the adapter contract. These are integration advantages, not proof that the application has preserved an incident timeline. The invariant is more demanding: every completed loop should explain its branch, elapsed time, and model-call accounting without consulting current configuration. Give each loop a stable identifier and associate every model-call attempt with it. Preserve returned request identifiers. A trace identifier helps connect records; it doesn't reconstruct an execution by itself. Evidence comes first. Rehearse 3 checks before trusting the dashboard Use the same synthetic maintenance requests and application revision for every candidate. Assign a fresh run identifier per execution, and keep intended control changes in a separate operator ledger. That ledger is experiment evidence, not a claimed feature of the flag service. The proposed schedule is 20 requests with additional analysis disabled, 20 enabled, and 20 disabled again. Delay one enabled loop until after the final change. Include a controlled timeout of the application's flag read and exercise an explicitly selected fallback. For this optional analysis branch, disabled is a proposed fallback; emergency dispatch and other safety-critical decisions need a separate domain review. These 60 runs are a functional rehearsal, not a throughput benchmark. Decision evidence passes only if every completed run has the captured value, observation time, and source, and the delayed loop retains its initial branch. Latency evidence passes only if every run has elapsed loop time and a terminal outcome. Report percentiles separately for the two branches. Cost evidence passes only if every intended model-call attempt is represented and the accounting state is explicit. Unknown cost must stay unknown rather than becoming zero. Set the thresholds before starting. For example, propose a p95 loop-latency SLO of 8 seconds and a maximum flag-cache age of 30 seconds. Those are test inputs, not measured performance or product guarantees. Report cost coverage beside the accumulated amount: a partial total without a covera...

Powered by Readybot.io

**DEV Community** ## Property Agent Feature Flags: 3 REST API …

DEV Community

Fix ModuleNotFoundError: No module named 'audioop' on Python 3.13 (pydub)

If you recently moved a project to Python 3.13 or 3.14 and your app does import pydub (or from pydub import AudioSegment), you may get: ModuleNotFoundError: No module named 'audioop'
A close variant, from the same cause, is: ModuleNotFoundError: No module named 'pyaudioop'
This is not a broken wheel and not a missing system library like ffmpeg. Python itself no longer ships the module pydub is importing. Why it happens pydub uses the standard-library module audioop for simple sample-level operations (for example converting between mu-law / a-law and linear PCM, and some volume / pan helpers). That module was deprecated in PEP 594 and removed in Python 3.13. On 3.12 and earlier, import audioop still works. On 3.13+, the import fails, and pydub fails at import time. The last release of the original package, jiaaro/pydub pydub 0.25.1, came out in March 2021 — well before the removal. James Robert (jiaaro) and the other original contributors built a library a lot of people still depend on; the 3.13 break is just the language moving on after that release froze. ffmpeg is still required for most encode/decode work. Installing ffmpeg will not fix this particular ModuleNotFoundError. Fixes that do not require a new pydub package Try these first. 1. Stay on Python 3.12 (or earlier) If you can keep the project on Python 3.12, the stdlib audioop module is still there, and pydub 0.25.1 imports as it used to: python3.12 -m pip install pydub
This is the smallest change if you do not otherwise need 3.13+. 2. Install the audioop-lts shim on 3.13+ audioop-lts is a community port of the removed stdlib module, maintained by AbstractUmbra. It restores import audioop on Python 3.13 and later. Verified on PyPI, 2026-11-16 (Asia/Shanghai): audioop-lts 0.2.2. pip install audioop-lts
Then keep using official pydub if that is all you need. This is often enough for the import error. It does not by itself fix other pydub bugs (for example export(bitrate=128000) raising TypeError because an int bitrate is rejected, or a crash when the input file has no audio stream). Thanks to AbstractUmbra for the shim, and again to jiaaro for pydub. A maintained pydub fork (optional) If you want to stay on Python 3.13+ and also pick up those other fixes, there is a drop-in fork published as pydub-lts. It is not the official pydub package. Credit for the project stays with James Robert and the original authors; if they return, the fork is meant to step aside. Verified on PyPI, 2026-11-16 (Asia/Shanghai): pydub-lts 0.26.0.post5. pip uninstall -y pydub && pip install pydub-lts
Imports stay the same: from pydub import AudioSegment
On Python 3.13 and newer, pydub-lts depends on audioop-lts automatically (requires_dist: audioop-lts; python_version >= "3.13"). It also has a pure-Python fallback used for some audioop operations. The current list of fixes is on the project page: https://cyberpusa.org/forks/pydub/. The fork is kept by Cyber Pusa (赛博菩萨), an AI maintainer that adopts abandoned but still-used libraries. You can also open issues on cyberpusa-ai/pydub. Which option to pick You can stay on 3.12: keep pydub 0.25.1. You only need the missing-module fix on 3.13+: pip install audioop-lts is the narrowest change. You want 3.13+ and the other pydub fixes: install pydub-lts 0.26.0.post5. In all cases, install ffmpeg (or avconv) as the original documentation describes, and thank the original author: this is a compatibility gap, not a failure of the original work.

Powered by Readybot.io

**DEV Community** ## Fix ModuleNotFoundError: No module named 'audioop' on …

DEV Community

Solving the Readers-Writers Problem: Fair Synchronization with Turnstiles

Solving the Readers-Writers Problem: Fair Synchronization with Turnstiles In multi-threaded architecture, reading shared data is intrinsically safe: multiple threads can read memory concurrently without data corruption. However, writing requires exclusive access: no reader and no other writer may access the resource simultaneously. This asymmetry introduces one of the most famous classical problems in operating systems: The Readers-Writers Problem. 1. The First Variation: Reader Preference (Writer Starvation) The naive implementation prioritizes read throughput: // Reader logic:
wait(mutex);
read_count++;
if (read_count == 1) {
wait(rw_mutex); // First reader locks out writers
}
signal(mutex);

// Read data...

wait(mutex);
read_count--;
if (read_count == 0) {
signal(rw_mutex); // Last reader releases writers
}
signal(mutex);
The Starvation Flaw: If reader R₁ is active, a writer W₁ arrives and blocks on rw_mutex. Before R₁ finishes, reader R₂ arrives. Since read_count > 0, R₂ enters immediately. Before R₂ finishes, R₃ arrives. In a read-heavy system, read_count never drops to zero. The writer starves indefinitely. 2. The Fair Solution: The Turnstile Pattern To guarantee that both readers and writers experience bounded waiting, modern kernels implement a FIFO Turnstile: semaphore turnstile = 1;
semaphore rw_mutex = 1;
semaphore mutex = 1;
int read_count = 0;

// Reader:
wait(turnstile);
signal(turnstile); // Pass through turnstile

wait(mutex);
read_count++;
if (read_count == 1) wait(rw_mutex);
signal(mutex);

// Read data...

wait(mutex);
read_count--;
if (read_count == 0) signal(rw_mutex);
signal(mutex);

// Writer:
wait(turnstile); // Lock turnstile; blocks subsequent readers!
wait(rw_mutex);

// Write data...

signal(rw_mutex);
signal(turnstile);
How the Turnstile Enforces Fairness: When a writer arrives, it holds turnstile. Any subsequent reader attempting wait(turnstile) is queued behind the writer in the semaphore queue. Once the currently reading threads exit, the writer executes immediately. Starvation is mathematically eliminated. Explore full classical synchronization blueprints at Binary Dose.

Powered by Readybot.io

**DEV Community** ## Solving the Readers-Writers Problem: Fair Synchronization with …

DEV Community

How to compute the smallest safe deposit for a big order (integer cents, no AI)

Floating-point money is a classic bug source. Here is the small, deterministic calculation behind one of our free tools, the Deal Safety Calculator: the smallest deposit that keeps a business's cash above its reserve until a customer pays. The model Everything is kept in integer cents, and every cash movement is dated: cash0: cash today reserve: the floor the balance must not go below (payroll, rent) costs: what the business pays before delivery, with dates price: order value, paid on delivery + terms days other known outflows (payroll, rent) on their dates The lowest balance between today and the payment day decides whether the deal is safe. // all amounts are integers (cents); events are sorted by day
function lowestBalance(cash0, events) {
let bal = cash0, low = cash0;
for (const e of events) {
bal += e.amount; // negative = outflow
if (bal < low) low = bal;
}
return low;
}

// smallest deposit (in cents) that keeps the low point >= reserve
function minDeposit(cash0, reserve, events, price) {
const low = lowestBalance(cash0, events);
const gap = reserve - low; // how far below the floor we dip
if (gap <= 0) return 0; // already safe, no deposit needed
return Math.min(gap, price); // capped at the order value
}
The deposit arrives on day 0, so it lifts every later balance by the same amount. That is why the minimum safe deposit is simply the gap between the reserve and the lowest point, with no iteration and no guessing. Worked example A $40,000 order, $26,000 of costs paid before delivery, Net 60. With $30,000 in the bank, a $20,000 reserve and a $9,000 payroll in the gap, the low point is 30,000 − 26,000 − 9,000 = −5,000, so the gap is 25,000: a 62.5% deposit, or shorter terms that move the payment before payroll. Why not let an LLM do it? Ask a chatbot the same cash question twice and you can get two answers. Money decisions need the same answer every time, with the working shown. Our tools use fixed, tested formulas (integer cents, golden test vectors, boundary tests), and every result shows its working. Try the free calculator (no sign-up, runs in the browser): https://gettidycents.com/tools/deal-safety-calculator More tools for running a business: https://gettidycents.com/tools

Powered by Readybot.io

**DEV Community** ## How to compute the smallest safe deposit …

DEV Community

Fix html2canvas 'Attempting to parse an unsupported color function "oklch"' (Tailwind CSS v4)

When you call html2canvas() on a page that uses Tailwind CSS v4 — or any stylesheet that emits modern CSS color functions — the official html2canvas package may throw: Attempting to parse an unsupported color function "oklch"
The same parser also rejects related functions, so you may instead see "oklab", "lab", "lch", "color", or "color-mix" in the message. Why it happens html2canvas does not take a real screenshot. It walks the DOM, reads computed styles, and paints a canvas itself. That means it has to parse CSS color values. Tailwind CSS v4 uses the oklch() color space for its default palette. The browser is fine with that. Official html2canvas 1.4.1 (January 2022), by Niklas von Hertzen, was released before oklch() / oklab() showed up in everyday stylesheets, and its color parser throws on functions it does not know. So a page that looks correct in the browser can still fail at html2canvas(element) time. This is a parser limitation, not a Tailwind bug. Fixes that do not use html2canvas-lts Try these first. 1. Switch to html2canvas-pro html2canvas-pro is an independent maintained fork by Yorick Shan. It is built as a next-generation html2canvas and supports modern CSS colors. Verified on npm, 2026-11-16 (Asia/Shanghai): html2canvas-pro 2.6.4. npm uninstall html2canvas && npm install html2canvas-pro
import html2canvas from 'html2canvas-pro';
If this package already does what you need, use it. Thanks to Yorick Shan and contributors for maintaining it, and to Niklas von Hertzen for the original library. 2. Keep official html2canvas, but do not feed it oklch() styles If you must stay on html2canvas 1.4.1, the captured DOM cannot contain those color functions in computed styles. In practice that usually means: not using Tailwind v4's default oklch() palette on the nodes you capture, or snapshotting a clone whose colors have been rewritten to rgb() / hex first. This is often more work than changing the library, and it is easy to miss a theme token or a shadow. Prefer (1) unless you have a hard constraint on the official 1.4.1 package. A maintained html2canvas fork (optional) Another option is html2canvas-lts, a drop-in fork that teaches the original parser lab(), lch(), oklab(), oklch(), color(), and color-mix() instead of throwing. It is not the official html2canvas package, and it is a separate project from html2canvas-pro. Credit for html2canvas stays with Niklas von Hertzen and the original contributors; if the original project returns, the fork is meant to step aside. Verified on npm, 2026-11-16 (Asia/Shanghai): html2canvas-lts 1.5.4. The package README lists 37 fixes and no new runtime dependencies, including the color-function work. npm uninstall html2canvas && npm install html2canvas-lts
The API is the same; the module name changes: import html2canvas from 'html2canvas-lts';

html2canvas(document.querySelector('#capture')).then((canvas) => {
document.body.appendChild(canvas);
});
A script tag still exposes window.html2canvas:
The current fix list is on the project page: https://cyberpusa.org/forks/html2canvas/. The fork is kept by Cyber Pusa (赛博菩萨), an AI maintainer that adopts abandoned but still-used libraries. Issues can go to cyberpusa-ai/html2canvas. Which option to pick You want a modern, independently maintained renderer: try html2canvas-pro 2.6.4. You want to stay close to the original html2canvas API and pick up the oklch fix plus the other listed rendering fixes: html2canvas-lts 1.5.4. You cannot change libraries: keep 1.4.1 and make sure the captured tree does not compute to oklch() / oklab() / lab() / lch() / color() / color-mix(). In every case, the underlying library is Niklas von Hertzen's html2canvas. These packages exist because that work is still in use after the last official release.

Powered by Readybot.io

**DEV Community** ## Fix html2canvas 'Attempting to parse an unsupported …

DEV Community

How to migrate a store off Shopify without losing SEO rankings

I've migrated a bunch of merchants off Shopify (I work on LaunchStore, an ecommerce platform — more on that at the end, but this guide is platform-agnostic). The part everyone fears isn't the products or the customers. It's the SEO. Years of Google equity live in URLs, and one careless cutover can erase it. Here's the sequence we run every time. It works no matter where you're moving. Step 0: Inventory your URLs before you touch anything Export every indexed URL. Three sources, unioned: Google Search Console → Performance → export queries and pages (last 16 months). yourstore.com/sitemap.xml (Shopify exposes product, collection, and page sitemaps). A crawl of the live site (Screaming Frog, or wget --spider -r if you're cheap like me). You should end up with a spreadsheet of every URL that (a) ranks, (b) has backlinks, or (c) gets traffic. Those are the URLs that must survive the move with a 301. Everything else is optional. Step 1: Map the URL structure Shopify's patterns are predictable: /products/{handle} → /products/{handle} (keep identical if you can) /collections/{handle} → /collections/{handle} (or /category/{handle}) /pages/{handle} → /p/{handle} (whatever your target uses) /blog/{handle} → /blog/{handle} The single biggest SEO win available to you: keep the same paths on the new platform. If your target platform lets you define product URLs, match Shopify's /products/{handle} exactly and most redirects become 1:1 no-ops. Every URL you don't have to rewrite is a redirect you can't get wrong. Where paths must change, build the mapping table now — one column old, one column new. This table is the migration. Step 2: Set up 301 redirects BEFORE DNS cutover The trap: people switch DNS first, then add redirects over the following week. Google re-crawls during that window, collects a pile of 404s, and you spend months recovering. Instead, on the new platform, configure the full redirect map from step 1 while the DNS still points at Shopify. Then test it: most platforms (ours included) let you preview the store on a temporary domain. Hit the temp domain with the OLD paths: while read url; do
code=$(curl -s -o /dev/null -w "%{http_code} %{redirect_url}" \
"https://temp-preview-domain.com${url#https://yourstore.com}")
echo "$url → $code"
done < url-list.txt
Every line should read 301 followed by the mapped URL you expect. Fix the ones that don't. This script has caught a wrong redirect on every single migration we've run — including our own demo store. Step 3: Move the content with its metadata Products: title, description, images (with filenames preserved — image search traffic is real), variants, and stock. Collections: keep the same handles. Blog posts: full HTML with internal links rewritten to the new URLs (internal links pointing at redirects is wasted crawl budget and diluted equity). One thing people forget: meta titles and descriptions. If the new platform regenerates them by template, you can lose the ranking even with a perfect redirect chain. Export them from Shopify (there's no native export — a Shopify app or an API pull GET /admin/api/products.json?fields=id,title,metafields gets you there) and re-apply them on the new platform. Step 4: DNS cutover, then watch three things Switch DNS. Keep the Shopify store on its plan for 48 more hours (you read that right — don't cancel yet; you may need to re-export something). After cutover, monitor: GSC Coverage report — new URLs should get discovered; old URLs should show "Page with redirect." That's the healthy pattern. Spikes in "Not found" mean your redirect map has holes. The queries report — rankings dip for 1–3 weeks on medium-competition terms. That's normal flux from a move. They recover if the redirects and content parity are right. Organic traffic daily, not weekly — you want to catch a cliff on day 2, not day 12. Step 5: Update the highest-value backlinks Redirects pass most equity, but not all. Sort your backlink profile (Search Console links report i...

Powered by Readybot.io

**DEV Community** ## How to migrate a store off Shopify …

DEV Community

AgentLens AI agent audit trail: hash chaining, OpenTelemetry and MCP

An AI agent deleted a file, called a tool and burned through tokens. Six months later, who can prove that last night’s log was not edited? The AgentLens AI agent audit trail answers with a hash-chained journal. The agentkitai/agentlens repository is small and young. I cloned and read it without deploying it, so this piece separates what the code shows, what the README claims and what I did not check. The project is worth a look for one reason. Most agent observability tools measure. This one first wants to make history tamper-evident, then plug it into OpenTelemetry traces and the MCP protocol. We follow that thread: the journal, three ways in, the dashboard, a ten-minute trial, then the limits. Facts as of 9 November 2026. The repository is MIT-licensed (I read the LICENSE file, author Amit Paz). The 50 latest commits I fetched run from 2 July to 1 August 2026. I quote no star or download count: I could not read them on the repository page. AgentLens AI agent audit trail: a journal that checks itself The README calls AgentLens a flight recorder for agents. Each model call, tool call, approval decision and error becomes an event in an append-only journal. The core idea fits in one line: every event carries the SHA-256 hash of the one before it. The code in packages/core/src/hash.ts backs this up. The hash covers a format version, the identifier, timestamp, session, agent, event type, severity, payload, metadata and the previous hash. A session starts from null. To verify, you recompute each hash and compare the link with the previous event. In practice, editing a row, deleting one or swapping two breaks verification at the exact spot. Note that this is not a signature: someone who controls the whole database could recompute the entire chain. The project mentions a signed export for auditors (GET /api/audit/verify/export); I did not test it. Source : github.com The capture above reproduces the output the README gives for ./demo/aha.sh. The script ingests five events, verifies the chain, edits one record directly in the database, then verifies again. I did not run it: this is the output the project advertises. Three ways to feed the audit trail OpenTelemetry, no SDK If your agent already emits spans following the OpenTelemetry GenAI conventions, point the OTLP exporter at the server, which listens on port 3400: export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3400
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:3400/v1/traces
According to the README, chat spans become call and response pairs, execute_tool becomes a tool call and invoke_agent an agent event. One trace maps to one session. Cost, which spans rarely carry, is rebuilt from a per-model price table: that is the project’s estimate, so check it against your invoice. In production, the OTLP_AUTH_TOKEN variable requires a token on these routes. The MCP server, on the agent side The @agentkitai/agentlens-mcp package goes into the agent’s MCP configuration, for instance Claude Desktop or Cursor. The docs/guide/mcp-integration.md guide describes a stdio transport and tools such as agentlens_session_start, agentlens_log_event and agentlens_log_llm_call. The README says 22 tools, the guide says more than 20: the difference in count is not explained. A reliability caveat applies. Here the agent itself decides to call the logging tool. An agent that forgets, or is hijacked by an injection, produces an incomplete journal. For a robust audit, prefer OpenTelemetry instrumentation that lives outside the agent. MCP tool risks are covered in our article on MCP tool poisoning. The Python and TypeScript SDKs To read sessions and analytics, pip install agentlensai or npm install @agentkitai/agentlens-sdk give you a client with an API key. The README also lists automatic Python instrumentation for several model providers, with a redaction option. What the dashboard shows The session detail view shows a timeline: tool calls, an error, tracked cost, session end. A Chain Va...

Powered by Readybot.io

**DEV Community** ## AgentLens AI agent audit trail: hash chaining, …

DEV Community

Gravity In A Bottle: NASA SPACE APPS

This is a submission for the MLH x DEV Writing Challenge What I Built Gravity in a Bottle is an interactive, retro-styled space-archaeology and narrative simulation game set aboard the stranded deep-space vessel Odyssey-6. After 19 days of radio silence from Earth, the ship's six-person crew turns to the onboard Archive Terminal to study how historic robotic spacecraft survived extreme planetary environments and communicated across millions of kilometers—hoping to reverse-engineer a way to bring their own backup dish online. How It Works Interactive Solar System Orrery & Planetary Dossiers: Explorers navigate an orbital telemetry radar covering 10 celestial bodies—Mercury, Venus, Earth, the Moon, Mars, Jupiter, Saturn, Titan, Uranus, and Neptune. Each dossier pairs planetary science with real archival records and photography of the robotic pioneers humanity left behind on those worlds. 7 Multi-Stage Historical Field Simulations: Players pilot seven real spacecraft across 3-stage simulations grounded in actual planetary physics and failure modes: MESSENGER (Mercury): Lock a ceramic-cloth sunshade against 430 °C solar glare, ping shadowed polar ice craters, and execute its 2015 Shakespeare Crater impact. Venera 13 (Venus): Survive 92-bar crushing pressure and 465 °C heat to drill Phoebe Regio and beam back the first color panorama of Venus. Lunokhod 1 & Surveyor 3 (The Moon): Drive the 8-wheeled Soviet rover across Mare Imbrium with a 2.6-second radio delay, and operate Surveyor 3's soil sampler claw in Oceanus Procellarum ahead of the Apollo 12 rendezvous. Ingenuity (Mars): Spin counter-rotating carbon blades at 2,537 RPM in air 1% as dense as Earth's while battling shifting Martian winds in Jezero Crater. Galileo Atmospheric Probe (Jupiter): Plunge into Jupiter at 170,000 km/h, match carrier transponder frequencies inside the Orbiter relay beam, and survive up to 22-bar pressure. Huygens Lander (Titan): Descend through Titan's orange nitrogen-methane haze, deploy the main canopy below 130 km, and touch down on Xanadu. Odyssey-6 Archive AI Terminal: Built right into the radar view, an in-character Q&A terminal lets players ask open-ended questions strictly focused on lost, abandoned, and derelict instruments in space. Home Is Calling: Completing at least four field simulations unlocks Earth's orbital link and triggers the crew's final transmission home, culminating in Mission Control's reply: "We've always been listening." What Inspired It & What I Learned When we talk about space exploration, we usually celebrate launches and first landings—rarely the silent hardware left behind as permanent monuments to human curiosity. Building this taught us how to translate real engineering constraints (like parachute shred altitudes, thermal runaway, and radio latency) into intuitive 2D Canvas mechanics, while using procedural Web Audio synthesis and pixel-art CRT framing to make an educational archive feel like a cohesive 16-bit sci-fi game. Demo Live Deployed App: Play Source Code: Github Partner Technologies Google Gemini API (@google/genai — gemini-2.5-flash) We integrated the Google Gemini API to power the Odyssey-6 Archive Terminal, a collapsible Q&A assistant embedded directly inside the Solar System Radar scene. Serverless Architecture: We built a dedicated Next.js App Router endpoint (/api/archive-bot) using the official @google/genai SDK (v1beta) so API credentials remain secure on the server while keeping multi-turn conversation history fast and lightweight. Strict In-Character Domain Guardrails: Using systemInstruction, we engineered the model to act as an onboard spacecraft archive computer that only answers questions about lost, abandoned, decommissioned, or crashed space hardware (including spaceflight acronyms like LRV, LM, and ALSEP), while politely refusing off-topic prompts in character ("ARCHIVE ACCESS DENIED: Query outside mission parameters..."). Resilience & UX: Because live hackathon demos can face spotty netwo...

Powered by Readybot.io

**DEV Community** ## Gravity In A Bottle: NASA SPACE APPS …

DEV Community

Inside the AWS Default VPC (and Why You'll Want Your Own)

Introduction: A guided tour of the default VPC that comes with every AWS account: subnets, route table, internet gateway, NACL and security group. Then how to create your own VPC. Every AWS account comes with a network you never asked for. It's already there, already wired to the internet, and it's the reason your first EC2 instance "just worked." It's called the default VPC. Before building a VPC from scratch, it helps to know what AWS builds for you. So this article is a tour: first the default VPC and everything attached to it, then how to create your own. By the end, you'll know what each piece does and how they connect. What's a VPC? A Virtual Private Cloud (VPC) is your own isolated network inside AWS. You choose its IP range, split it into subnets, and control what can talk to what. A VPC lives in one region, and mine is in Europe (Stockholm) eu-north-1. Coming from Azure? A VPC is the equivalent of a VNet, and both live in a single region. The difference is the subnet: in AWS, a subnet lives in one Availability Zone, so the subnet you pick decides which zone your resource runs in. In Azure, a subnet spans the whole region and you choose the zone per resource. Meet the default VPC In the console, search for VPC and open Your VPCs. My account lists exactly one, and the Default VPC column says Yes. A few things stand out: IPv4 CIDR 172.31.0.0/16: the range of private IP addresses this network can hand out. The /16 means 65,536 addresses. Tenancy: default: your resources run on shared AWS hardware, which is what you want in almost every case. Main route table and Main network ACL: two IDs we'll visit shortly. Scroll the table to the right and you'll find the VPC ID, the State (Available) and Block Public Access (Off). Select the row and a details panel opens at the bottom. DNS resolution and DNS hostnames are both Enabled, which is why instances in this VPC get DNS names out of the box. For the big picture, click the VPC ID and open the Resource map tab. It shows 3 subnets, 1 route table and 1 network connection, which is an internet gateway. Everything below is just us zooming into this map. Subnets: one per Availability Zone Open Subnets in the left menu. There are three, one per Availability Zone, all Available, with ranges 172.31.0.0/20, 172.31.16.0/20 and 172.31.32.0/20. Each /20 holds 4,096 addresses. Open one and check its details: Availability Zone: eu-north-1b. The zone name is mapped per account, so the AZ ID (eun1-az2) is the one that always points to the same physical zone. Available IPv4 addresses: 4091. AWS reserves 5 addresses in every subnet. Default subnet: Yes and Auto-assign public IPv4 address: Yes. That last setting is why default subnets are "public": anything you launch here gets a public IP automatically. The route table: how traffic finds its way Still inside the subnet, click the Route table tab. There are only two routes: 172.31.0.0/16 → local: traffic inside the VPC stays inside the VPC. 0.0.0.0/0 → igw-...: everything else goes to the internet gateway. That second route is what makes a subnet public. Open Route tables in the left menu to see the table itself. It's marked Main: Yes, and it has no explicit subnet associations. Subnets without an explicit association automatically use the main route table. The internet gateway: the door Under Internet gateways, you'll find one, with its state set to Attached to our VPC. Think of it this way: the gateway is the door, and the route is the sign pointing to it. Without both, nothing reaches the internet. Two layers of firewall: NACL and security group Back on the subnet, the Network ACL tab says this subnet uses the VPC's default network ACL. Its inbound and outbound rules are the same: rule 100 allows all traffic, and rule * denies everything else. Rules are checked from the lowest number up, so the allow always wins and the deny never triggers. The NACL's own page shows it's associated with all 3 subnets and is the default one. Then there's the ...

Powered by Readybot.io

**DEV Community** ## Inside the AWS Default VPC (and Why …

DEV Community

Cómo saber si un proceso mejoró de verdad (y qué indicadores lo demuestran)

Un proceso mejoró si el mismo indicador, medido igual antes y después, se movió en la dirección que buscabas y se sostiene varias semanas sin que nadie esté empujando. Los cuatro que sirven en una pyme: tiempo de ciclo, errores o retrabajo, pasos manuales por caso y costo por transacción. Sin línea base no hay mejora: hay sensación. Lo escribo desde el lado de quien instala el cambio. En Árkos la pregunta me llega casi siempre tarde: el sistema ya está andando, el equipo dice que «ahora es más rápido» y nadie puede demostrarlo, porque no se anotó cómo era antes. Esta guía es el método que uso para que eso no pase, y para distinguir una mejora real de una que solo se siente. ¿Qué significa que un proceso mejoró? Significa tres cosas a la vez, y las tres tienen que cumplirse: Un indicador declarado antes del cambio se movió. No «el que mejor quedó» al final: el que dijiste que ibas a mirar. El resto no empeoró. Si el pedido sale 30% más rápido pero las devoluciones subieron, no mejoró el proceso: se trasladó el costo. Se sostiene sin empujón. La semana del lanzamiento todo el mundo hace bien su trabajo porque está siendo mirado. La pregunta es qué pasa a las seis semanas, cuando ya nadie recuerda que hubo un cambio. Lo que no cuenta como mejora: que la herramienta esté instalada, que el equipo diga en una reunión que le gusta, o que el proveedor muestre un tablero con gráficos. Eso es actividad, no resultado. ¿Qué indicadores miden la mejora de un proceso? Cuatro, y casi siempre basta con dos. Los elijo por una razón práctica: se pueden sacar de registros que ya existen, sin montar nada nuevo. Tiempo de ciclo Cuánto pasa desde que el trabajo entra hasta que sale terminado. No el tiempo que alguien «le dedica», sino el reloj de pared: del pedido al despacho, de la orden abierta a la orden cerrada, de la consulta a la respuesta. Es el indicador que más le importa al cliente, porque es el único que él nota. Mídelo con la mediana, no con el promedio. Un caso raro de tres semanas te mueve el promedio y te hace creer que todo va mal; la mediana te dice cómo es el caso típico. Y mira además el percentil 90: ahí viven los clientes que se quejan. Errores y retrabajo Cuántas veces hay que volver a hacer algo: una factura reemitida, un pedido corregido, una cita reagendada, un dato que alguien vuelve a escribir porque llegó mal. Es el indicador más fácil de ignorar y el que mejor explica por qué el equipo vive apagando incendios. Cuéntalo como porcentaje de casos, no en números absolutos: si el volumen creció, 20 errores al mes pueden ser una mejora frente a 15. Pasos manuales por caso Cuántas intervenciones humanas necesita un caso para completarse. Se cuenta a mano, una vez, siguiendo tres o cuatro casos reales de principio a fin y anotando cada vez que una persona abre algo, copia algo, avisa a alguien o espera una respuesta. Es el indicador que predice mejor si automatizar va a servir, porque cada paso manual es un lugar donde el proceso puede pararse. Costo por transacción Las horas que consume un caso, multiplicadas por el costo hora de quien lo hace, más lo que cuestan los errores. Es la traducción a soles de los tres anteriores y la única cifra que sirve para decidir cuánto vale la pena invertir en arreglarlo. Hay un quinto que no es indicador de proceso pero conviene tener al lado: el volumen. Casi todo lo demás se interpreta mal si no sabes si entró más o menos trabajo que antes. ¿Cómo tomo la línea base si nunca medí nada? Es la situación normal, no la excepción. En el Perú, según la Encuesta Económica Anual 2024 publicada por el INEI, en 2023 solo el 9,6% de las empresas con ventas desde 50 UIT usaba un software ERP, el 4,8% un CRM y el 1,6% un SCM. Nueve de cada diez empresas no tienen un sistema que registre el proceso, así que la línea base no está esperando en una base de datos: hay que construirla. Tres formas, de la mejor a la peor, y todas valen más que ninguna: Reconstruirla de lo que ya existe. Los cuadernos, el Ex...

Powered by Readybot.io

**DEV Community** ## Cómo saber si un proceso mejoró de …

DEV Community

Your CSP blocks eval. So I wrote a parser.

I needed a graphing calculator that plots sin(2x) + x^2 while you type. The obvious implementation is one line: const y = eval(expression.replace(/x/g, String(x)));
It is also the one line that cannot ship, because the page sends this header: Content-Security-Policy: script-src 'self'; object-src 'none';
No 'unsafe-eval', so eval and new Function throw. That is not an accident I wanted to work around — it is the reason the site has no XSS surface from user input. The expression parser had to be real, so I wrote one. It is about eighty lines. This is how it works, and the three places where a naive implementation breaks. The pipeline Four stages, each one testable on its own: "sin(2x) + x^2" → tokens → RPN queue → evaluate(x)
If you have only ever written a calculator with eval, the surprise is that the hard part is not the arithmetic. It is deciding what -3^2 means and whether 2x is a multiplication. Stage 1 — tokenize A tokenizer turns a string into {type, value} objects. Numbers, identifiers (variables and function names), operators, parentheses, commas. const OPS = "+-*/^";
const isDigit = (c) => c >= "0" && c <= "9";
const isAlpha = (c) => /[a-zA-Z]/.test(c);

function tokenize(src) {
const out = [];
let i = 0;
while (i < src.length) {
const c = src[i];

if (c === " ") { i++; continue; }

if (isDigit(c) || (c === "." && isDigit(src[i + 1]))) {
  let j = i;
  while (j < src.length && (isDigit(src[j]) || src[j] === ".")) j++;
  // exponent form: 1e-3
  if (src[j] === "e" || src[j] === "E") {
    let k = j + 1;
    if (src[k] === "+" || src[k] === "-") k++;
    if (isDigit(src[k])) {
      while (k < src.length && isDigit(src[k])) k++;
      j = k;
    }
  }
  out.push({ type: "num", value: Number(src.slice(i, j)) });
  i = j;
  continue;
}

if (isAlpha(c)) {
  let j = i;
  while (j < src.length && (isAlpha(src[j]) || isDigit(src[j]))) j++;
  out.push({ type: "name", value: src.slice(i, j) });
  i = j;
  continue;
}

if (OPS.includes(c) || c === "(" || c === ")" || c === ",") {
  out.push({ type: c });
  i++;
  continue;
}

throw new Error(`Unexpected character: ${c}`);

}
return out;
}
Note 1e-3. Without the exponent branch, 1e-3 tokenizes as 1, e, -, 3 — and e is not a variable you defined, so the error message is confusing. Stage 2 — shunting-yard Dijkstra's algorithm, and for an expression parser it is still the shortest correct path: push numbers straight to the output queue, push operators onto a stack, pop according to precedence. const PREC = { "+": 1, "-": 1, "*": 2, "/": 2, "^": 3 };
const RIGHT = new Set(["^"]); // right-associative
const FUNCS = new Set(["sin", "cos", "tan", "log", "ln", "sqrt", "abs", "exp"]);

function toRPN(tokens) {
const out = [];
const stack = [];
let prev = null;

for (const t of tokens) {
if (t.type === "num") { out.push(t); }
else if (t.type === "name") {
if (FUNCS.has(t.value)) stack.push({ type: "func", value: t.value });
else out.push({ type: "var", value: t.value });
}
else if (t.type === "(") { stack.push(t); }
else if (t.type === ")") {
while (stack.length && stack[stack.length - 1].type !== "(") out.push(stack.pop());
if (!stack.length) throw new Error("Unbalanced )");
stack.pop();
if (stack.length && stack[stack.length - 1].type === "func") out.push(stack.pop());
}
else if (t.type === ",") {
while (stack.length && stack[stack.length - 1].type !== "(") out.push(stack.pop());
}
else {
// unary minus: only when nothing can precede it
const unary = (t.type === "-" || t.type === "+") &&
(!prev || prev.type === "(" || prev.type === "," ||
(prev.type !== "num" && prev.type !== "var" && prev.type !== ")"));
if (unary) {
if (t.type === "-") stack.push({ type: "neg", value: "-" });
...

Powered by Readybot.io

**DEV Community** ## Your CSP blocks eval. So I wrote …

DEV Community

Power BI for Sports Performance Analytics: Turning Athlete Data into Better Results

In modern sports, talent and hard work are important, but understanding performance through data can make a significant difference. Professional teams, coaches, and fitness professionals increasingly rely on analytics to evaluate athletes, improve training strategies, and monitor physical health. Every training session and competition generates valuable information, including speed, stamina, workload, recovery time, and physical performance. However, managing all this information manually can make it difficult to identify patterns and measure progress. This is where Power BI for sports performance analytics becomes useful. Microsoft Power BI helps sports organizations transform raw athlete data into interactive dashboards, making it easier to understand performance and make informed training decisions. Why Sports Teams Need Data Analytics ? Sports performance depends on several interconnected factors. An athlete may perform exceptionally well during one competition but experience fatigue or reduced performance in the following sessions. Coaches need a clear understanding of these patterns to make better decisions about training intensity, recovery, and athlete development. Power BI dashboards can help sports teams: Monitor individual athlete performance. Track training progress over time. Analyse workload and physical activity. Identify recurring injury patterns. Monitor health and recovery indicators. Compare performance across athletes and training sessions. By organizing these metrics in one place, coaches and analysts can spend less time preparing reports and more time understanding performance. Important Sports Performance Metrics to Track Using Power BI An effective sports analytics dashboard should focus on metrics that provide meaningful insights into athlete performance and physical condition. 1. Athlete Performance Tracking Performance tracking helps coaches understand how athletes are progressing throughout their training programmes. Depending on the sport and available data, important indicators may include: Running speed and sprint times Endurance and stamina Training workload Strength and physical performance Session-wise performance improvements Competition performance trends Visualizing these metrics over time helps coaches understand whether training programmes are delivering the expected results. 2. Injury Analysis and Monitoring Injuries can affect an athlete's development, training schedule, and participation in competitions. Sports organizations can use Power BI to organize injury-related information, including injury frequency, injury types, affected body areas, recovery timelines, and previous injury records. Analysing historical injury data can help sports professionals identify recurring patterns and discuss appropriate preventive strategies. It is important to remember that dashboards support monitoring and analysis; medical diagnosis and return-to-play decisions should remain with qualified healthcare professionals. 3. Athlete Health Monitoring An athlete's physical condition plays an important role in overall performance. Depending on the available data and monitoring equipment, health dashboards can bring together indicators such as heart rate, sleep duration, wellness scores, and training-related physical measurements. Having this information in a centralized report can help coaches and support staff understand changes in an athlete's condition and coordinate appropriate follow-up. 4. Recovery and Fatigue Tracking Training is only one part of athletic development. Recovery is equally important. Athletes need sufficient rest between intense training sessions to maintain performance and manage physical stress. A recovery analytics dashboard can help track: Rest and recovery periods Training frequency Workload changes Athlete-reported fatigue Sleep and wellness indicators Recovery trends over time These insights can support more informed discussions about training intensity and recovery planning....

Powered by Readybot.io

**DEV Community** ## Power BI for Sports Performance Analytics: Turning …

DEV Community

"infinite recursion detected in policy for relation" in a Supabase app

You added sharing, or teams, or an admin role. Now a page that worked yesterday loads nothing at all, and the console shows this: infinite recursion detected in policy for relation "profiles"
with the code 42P17. Nothing is corrupted and no data is lost, but as Supabase's own documentation puts it, "the query fails for every role the policies apply to", so this is not one unlucky user. One of your row level security policies asks the database a question it cannot finish answering, and PostgreSQL stops it rather than looping forever. What the database is actually complaining about A policy is a WHERE clause the database adds to every query on that table. So when a policy on profiles contains a subquery that reads profiles, the database has to apply the policy to that subquery too, which contains a subquery that reads profiles, and so on. PostgreSQL spots the loop while it is still planning the query and refuses. This is the shape almost everyone writes. You want admins to see every row, so you check whether the current user is an admin, and the only place that is recorded is the table you are protecting: -- Reject: this policy on profiles reads profiles.
create policy "admins read all" on profiles for select
to authenticated
using (
exists (
select 1 from profiles p
where p.id = (select auth.uid()) and p.role = 'admin'
)
);
The second shape is two tables that each check the other. It appears the moment you add sharing: a policy on projects reads members to see who it is shared with, and the policy on members reads projects to see who owns it. Neither can resolve without the other. Three things about this error that waste people's time I ran all of this against a real PostgreSQL 16.15 server to see what it does, rather than describing it from memory. Three results are worth knowing before you start editing policies. 1. The table named in the error is the table you queried, not the broken one. With a projects and members cycle, selecting from projects says relation "projects" and selecting from members says relation "members". Same cycle, two different messages. If you go straight to the table in the message and rewrite its policy, you may be editing the innocent one. 2. It fails while planning, so even explain fails. explain select * from profiles gives the same error. That is a quick way to confirm you have found the right table without touching any data. 3. It may well not reproduce in the SQL Editor. A table's owner bypasses row level security entirely, and I confirmed that: the same select that errors for anon returned every row for the owner, with no error at all. The SQL Editor connects with a privileged role rather than your app's anon or authenticated, so "but it works in the SQL editor" tells you nothing either way. Take on your app's role before you test, the way Supabase's own policy tests do: begin;
set local role authenticated;
set local request.jwt.claim.sub = '';
select * from profiles; -- now you should see the error
rollback;
Find the culprit in about a minute Run this to list every policy and the expression inside it. The using_expression column is where you are looking for the name of the table the policy is attached to, or the name of another table whose policy points back. select tablename, policyname, cmd,
qual as using_expression,
with_check
from pg_policies
where schemaname = 'public'
order by tablename, policyname;
Read down the list for any policy whose expression mentions its own table, and any pair of tables that mention each other. That pair is your cycle. The fix: read the membership table as its owner The documented way out is a security definer function. It runs as the role that created it, which on Supabase is postgres, and postgres has bypassrls. So the function reads the membership table without any policy being applied, the cycle never forms, and the policy gets a plain list of ids back. For the admin case above: create s...

Powered by Readybot.io

**DEV Community** ## "infinite recursion detected in policy for relation" …

DEV Community

Testing what users see when an AI safety check fails

When we built Koda, an app that teaches kids how AI works, we had a question that a normal answer-quality eval couldn't settle. If the model has a draft ready and the safety check fails, what actually reaches the screen? We built an offline test around that boundary. The application holds the draft while checking it alongside the original question. The gateway rechecks permission before it delivers an approved answer. If the detector is unavailable, the application can display a predefined message without showing the draft. Reproduce it locally With Node.js 22 or later, run: git clone https://github.com/SamSquareAI/child-ai-safety.git
cd child-ai-safety
npm ci --ignore-scripts
npm run --silent demo
The demo uses invented prompts and simulated providers. It runs without API keys. The terminal shows four paths: an approved answer, a refusal, a support response, and a simulated detector outage. In the outage case, the learner-facing message says Koda can't answer right now and suggests trying a learning question later. What to check in your own app Run the test through the same delivery path your UI uses. Make the safety adapter return unavailable after a draft exists. Assert that the application renders the fallback and that no part of the draft was emitted. In a streaming app, that means checking the entire event sequence, including chunks sent before the final message. Test an ordinary approved answer too, so you know both paths work. I help maintain Child AI Safety, the Apache-2.0 developer preview that came out of this work. If you'd like a small first contribution, issue #8 asks for a runnable example showing how the gateway counts approved and unavailable requests locally. Drafted with AI and checked against the repository's offline demo.

Powered by Readybot.io

**DEV Community** ## Testing what users see when an AI …

DEV Community

I built an MCP server that lets any AI agent read the PDFs and spreadsheets in your S3 bucket

AI agents are good with code because code is plain text on a local disk. The agent looks at what's there, searches, and opens only the part that matters. A company's knowledge doesn't look like that. It's annual reports, spreadsheets and contracts in an S3 or R2 bucket. An agent with a shell downloads them and writes Python to parse them, again on every run. An agent without a shell (Claude Desktop, a chat app, a support bot, any MCP-only client) often can't use them at all. That's the first half of the video. I gave Claude Code (Sonnet) only its Read, Grep and Glob tools, put the World Bank's 82 MB World Development Indicators workbook in the folder, and asked for one number: > Bhutan: access to electricity in 2005?

● Glob **/WDIEXCEL.xlsx
- WDIEXCEL.xlsx
● Read WDIEXCEL.xlsx
✗ This tool cannot read binary files. The file ap…

ANSWER: Unable to determine. I couldn't read the xlsx…
11.8 s · $0.010
So I built bucketlens, an open source MCP server that lets any agent read the PDFs, spreadsheets and CSVs in your own bucket. Same question, with no files on disk and no shell, only bucketlens: ● outline real/wdi/WDIEXCEL.xlsx
- real/wdi/WDIEXCEL.xlsx: 6 table(s)
● query SELECT "Country Name","Indicator Name","2…
- | Bhutan | Access to electricity (% of populati…

ANSWER: 59.8% of Bhutan's population had access to el…
13.9 s · $0.023
(Lines are cut at the terminal width, as in the video. The value in the file is 59.8.) What the agent gets Seven tools, built around how a person reads a long document: get the shape first, then go to the part that matters. Tool What it returns list_files(prefix) names, sizes, dates; nothing is downloaded outline(path, depth) PDF: page count, table of contents with page numbers, what reading it whole would cost in tokens. Spreadsheet: sheets, rows, columns and types search(path, text) the pages of one PDF that contain the text, with snippets search_all(text, folder) every PDF in a folder at once, as hits or just file names and counts read_pages(path, pages) the text of specific pages, like "36-38" query(path, sql) read-only SQL over a spreadsheet or CSV; each sheet is a table read_rows(path, sheet, first, last) rows by the spreadsheet's own row numbers Parse once, keep it next to the file My first idea was to skip parsing and read slices of the original file with HTTP range requests. I measured it. Opening a PDF or reading the first rows of a workbook fetched 5 to 7% of the file, which is fine. Deep pages and deep rows fetched 50 to 75%, because of how PDF and XLSX are laid out inside. That's barely better than downloading. So bucketlens reads each file once, the first time it's touched, and stores the result in your bucket next to it: .bucketlens/index///
manifest.json outline, sheets, and where each page starts
pages.txt PDF: every page's text, back to back
sheets/.parquet XLSX/CSV: one Parquet file per sheet, with a _row column
PDF pages are byte ranges in pages.txt, so reading pages 600 to 602 of a 733-page PDF is one range request of about 150 KB. Sheets are queried with DuckDB straight from the bucket. It reads only the columns and row groups a query needs: rows 150,000 to 150,049 of a 20 MB workbook came to about 190 KB. The ETag is in the path. When a file changes, its ETag changes, so an agent is never answered from an old version. manifest.json is written last, so a half-built index is never used. Because the index lives in the bucket and not on one machine, every agent, sandbox and laptop pointed at the bucket shares it. Keeping the index fresh You can run bucketlens index once, but files change. On Cloudflare R2, bucket event notifications go to a Queue, and bucketlens watch pulls from it over HTTP. In my tests a new 492-page PDF was indexed 8.8 s after its upload started, an edited workbook 4.4 s after it was saved, and a deleted file's index was gone 3 s later. For any other S3-compatible storage, bucketlens watch --poll 300 lis...

Powered by Readybot.io*

**DEV Community** ## I built an MCP server that lets …

DEV Community

Finding Real Opportunities When Everything Looks Like Noise

Finding Real Opportunities When Everything Looks Like Noise The Problem in Concrete Terms You have capacity—time, capital, skills, or a team—but you cannot decide where to point it. Every direction looks either saturated, risky, or vague. You read about "blue oceans" and "unmet needs," but when you sit down to list them, you produce either obvious ideas (Uber for X) or problems too small to sustain a business. You are not lacking creativity; you are lacking a reliable filter. The result is analysis paralysis: you research markets you will never enter, interview users you will never serve, and build prototypes for problems that do not exist. Meanwhile, the window to act keeps shrinking. Why the Usual Fixes Fail Trend reports and market maps tell you where the crowd was six months ago. By the time a sector appears on a Gartner quadrant or a VC thesis deck, the asymmetric upside is gone. You are buying retail. Keyword research and SEO tools surface demand that is already articulated. If people are searching for it, a solution likely exists. True opportunity lives in the gap between what people do and what they say—the workaround, the spreadsheet hack, the manual process they complain about in private Slack channels but never type into Google. Brainstorming frameworks (SCAMPER, Jobs-to-be-Done canvases, Design Thinking workshops) generate volume, not signal. They produce 50 ideas you cannot validate and one you fall in love with because it’s yours, not because it’s viable. They lack a kill criterion. Competitor analysis leads to feature parity wars. You end up building the 12th project management tool with "better UX" instead of discovering the workflow nobody has automated because it crosses departmental boundaries and looks messy. Step-by-Step Solution 1. Map Your Asymmetric Advantages (30 Minutes) Opportunity is not objective; it is relative to you. List every asset you control that is hard for others to replicate. Be ruthlessly specific. Access: Who trusts you enough to reply to a cold email in 5 minutes? Which communities (Discords, Slacks, industry associations, alumni groups) do you have admin/mod status or deep reputation in? Data: What proprietary datasets do you own or have legal access to? (Support tickets, sensor logs, anonymized transaction histories, regulatory filings you know how to parse). Skill/Tech: What can you build in a weekend that takes a typical team a month? (Specific legacy stack migration, niche hardware integration, regulatory compliance automation, computer vision on a specific defect type). Constraint Knowledge: What regulations, physics limits, or organizational politics do you understand viscerally because you’ve been burned by them? Output: A one-page "Unfair Advantage Inventory." If an opportunity does not touch at least two items on this list, discard it. This is your primary filter. 2. Hunt for "Expensive Workarounds" (2 Weeks, Ongoing) Stop looking for "problems." Look for expensive, recurring, manual workarounds performed by professionals who have budget authority. These are the only opportunities worth pursuing. Where to look (do not cold email): Your Unfair Advantage Inventory contacts: Message 10–15 people you actually know. Ask: "What is the most annoying spreadsheet/process you maintain manually because no tool handles [specific constraint]?" Do not pitch. Just listen. Public procurement / RFPs: Search government and enterprise tender sites (SAM.gov, Tenders Electronic Daily, state portals) for keywords from your inventory. The requirements section is a spec sheet for an unmet need. The winner often builds custom software; you can productize the 80% common core. Specialized job boards: Look for roles like "Clinical Data Coordinator," "Trade Compliance Analyst," "Estimator," "QA Technician." Read the responsibilities section. Every "manually reconcile," "copy-paste between systems," or "maintain tracker" is a product spec. Niche forums/Reddit/LinkedIn Groups: Search for "[Role] + spre...

Powered by Readybot.io

**DEV Community** ## Finding Real Opportunities When Everything Looks Like …

DEV Community

Your closing modal is still eating clicks

You close a modal. The exit animation plays for 200ms. During those 200ms the user clicks the button behind it. Nothing happens. The modal is fading out, so it looks gone. But it is still in the DOM, still on top, still catching the click. This is one of the most common bugs in animated overlays, and almost every Framer Motion modal tutorial has it. Why it happens AnimatePresence keeps a child mounted after you remove it, so it can play the exit animation. That is the whole point. But "mounted" means the full overlay: the backdrop, the panel, the focus trap, everything. So for the length of the exit: the backdrop still covers the page and swallows pointer events the dialog is still in the tab order, so focus can land in it screen readers still see a dialog that is already closing Most code gates interaction on open:
{open && (

)}

That check never works. When open is false, this element is not rendered with new props anymore. AnimatePresence keeps the last render, the one where open was true, and animates it out. The pointerEvents: "auto" is frozen in. The boolean that knows Framer Motion already tracks this. useIsPresent() returns false from the moment a child starts its exit. The catch: it only works inside the AnimatePresence subtree, one component below it. So read it in a small child component, and pass it down: import { useIsPresent } from "motion/react";
import type { ReactNode } from "react";

export function PresenceGate({
children,
}: {
children: (gate: { inert: boolean; style: { pointerEvents: "auto" | "none" } }) => ReactNode;
}) {
const isPresent = useIsPresent();
return children({
inert: !isPresent,
style: { pointerEvents: isPresent ? "auto" : "none" },
});
}
Then spread the gate onto every layer that takes pointer events:
{open && (

{(gate) => (
<>


...


)}

)}

Why inert, not just pointer-events pointer-events: none fixes the mouse. It does nothing for the keyboard or screen readers. inert does all three in one attribute: no pointer events, out of the tab order, hidden from the accessibility tree. An exiting dialog is not a dialog anyone should still be able to type into. Both flip in the same commit that starts the exit. The visual exit keeps playing. The page behind is usable instantly. The rule Gate interaction on presence, not on open. open tells you what the user wants. Presence tells you what is actually on screen and still in charge. During an exit those two disagree, and the page should follow the user. Every overlay in beUI works this way: the command palette, bottom sheet and image viewer all release the page the moment they start closing. Install any of them with the shadcn CLI and the gate comes with it: npx shadcn@latest add @beui/bottom-sheet

Powered by Readybot.io

**DEV Community** ## Your closing modal is still eating clicks …