AI 资讯
Next.js 16.3: Instant Navigations, Up to 90% Less Dev Memory and Faster Builds
Vercel has released Next.js 16.3, featuring significant updates since version 16.0. Enhancements include reduced memory usage during development, accelerated build times, and improved type checking. Instant Navigations introduces faster, client-like responses while maintaining server-rendered architecture. Developers are advised to gradually adopt new features due to noted caveats. By Daniel Curtis
开发者
One rented /24 could eclipse a Kademlia node. Now it takes ten.
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry. The...
AI 资讯
The test was green. Every real connection would have failed.
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry. The...
AI 资讯
Calling a TypeScript Backend Without Integration Code - A Simple Task Tracker with Graftcode
Most developers building frontend applications spend a lot of time writing code that communicates with their backend due to the traditional approach (using APIs). This is not because the logic is hard to implement, but because the communication itself is complex. When using standard APIs, we build routes, define request and response models, generate clients, and keep multiple layers on track with application updates. Instead of exposing backend functionality through REST endpoints and consuming it through HTTP clients, Graftcode exposes backend methods directly and generates packages that applications can install and use as dependencies. The result is a communication model that is like you are calling a library rather than consuming an API with strongly typed clients. Working with Graftcode is very simple: install your library and call its functions. In this article, we'll be building a simple task tracker or to-do list application using React and a TypeScript backend to see what working with Graftcode looks like. In this blog post, we will learn the following: Why API layers require you to maintain APIs manually How Graftcode exposes backend functionality through Graftcode Gateway How Graftcode Vision helps discover backend capabilities Familiarity with APIs and fetch() requests How React applications can use TypeScript backend logic without building API routes Why strongly-typed backend packages can improve developer experience Prerequisites Let’s get our hands a bit dirty, but before we do, there are some need-to-haves to get you started. Let’s have a look at that in this section: Latest Node version installed on your machine Basic knowledge of React and TypeScript Familiarity with how APIs and fetch requests work (for understanding how easy Graftcode’s approach is) A Graftcode account Graftcode gateway installed on your local machine With these prerequisites, you’ll first understand why most to-do list applications rely heavily on APIs for their logic and what c
开发者
React useEventListener Hook: Type-Safe DOM Events (2026)
Here's a modal close-on-Escape that quietly does the wrong thing: function Modal ({ onClose }: { onClose : () => void }) { useEffect (() => { const onKey = ( e : KeyboardEvent ) => { if ( e . key === " Escape " ) onClose (); }; window . addEventListener ( " keydown " , onKey ); return () => window . removeEventListener ( " keydown " , onKey ); }, [ onClose ]); return < div role = "dialog" > … </ div >; } If the parent passes an inline onClose={() => setOpen(false)} — and it almost always does — onClose is a new function on every render, so this effect tears the listener down and adds a fresh one on every single render of the parent. Drop onClose from the deps to stop the churn and you get the other bug: the listener now holds the first render's onClose forever, and closing the modal calls a stale closure. You can't win this with a dependency array, because the two things you want are in direct conflict: subscribe once , but always run the newest handler . The fix is to separate them — register the listener on a stable identity, and call through a ref that's kept current. useEventListener from @reactuses/core is that split, packaged. This post covers what it actually does under the hood, the four ways to name a target, exactly what TypeScript infers for each one (this part surprises people), the options that don't retrigger, and the two gotchas worth knowing before you ship it. Quick Start npm install @reactuses/core import { useEventListener } from " @reactuses/core " ; function Modal ({ onClose }: { onClose : () => void }) { useEventListener ( " keydown " , ( e ) => { if ( e . key === " Escape " ) onClose (); }); return < div role = "dialog" > … </ div >; } That's the whole fix. No dependency array, no useCallback on the parent, no cleanup to remember. The listener is added to window once when the component mounts and removed when it unmounts; the arrow function you passed is re-created on every render and it doesn't matter, because the listener never re-registers
AI 资讯
The Smallest Fix With The Biggest Impact [Skips VS Technology Edition]
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry . Remember that one Regular Show episode where Skips tried to destroy the park's computer because it caught the Error 220 bug? He took one look at it, picked up a sledgehammer and said the line we’ve all felt as devs: “ There’s something evil in that computer. We gotta smash it ”. In the cartoon, they literally smash the computer and this works to fix the bug. In real life? We don’t get sledgehammers. We get Github PRs. Last week, I almost felt like Skips. I found a one-line bug in an open source repo that could’ve broken Instagram webhook security. No hammer, no explosion, just one misindented ‘if’ statement and a missing test. This is the story of how the smallest fix had the biggest impact. -The Challenge So what was my Error 220 ? While contributing to the corsair open-source repo, I found a security breach in the Instagram webhook handler. Something about the verification flow felt off, so I started tracing it line by line. The code called timingSafeEqual but the result was indecisive. I took an extensive look at it and that's when I saw it- The if statement meant to guard the check was there, but timingSafeEqual was indented wrong. It was meant to return the result of timingSafeEqual to accept or reject the request, but it fell through instead. Although it was running, its return value wasn’t being used to control the flow. This bug was tiny-one mis-indented line- but it had a great impact. In JS, it is not considered an error and so it’s easy to miss. Webhook security relies on a signature check to prove a request. If timingSafeEqual isn’t actually enforcing it, an attacker could forge a webhook and it would be accepted. The entire protection could fall apart over one tab. View PR #759 -The Fix In fixing it, I opened PR#759 to correct the indentation so crypto.timingSafeEqual would be inside the if block and its boolean result would decide whether to return true or false . Prior
AI 资讯
I built an MCP memory server for one user (me, for six weeks)
Building in public You explain your deploy setup to your assistant. It helps. Tomorrow you explain the same setup again. And the day after. You are not training it. You are re-typing. The tool nobody asked for I did not set out to build a product. I set out to stop repeating myself. My setup is four servers with names that mean nothing to anyone else, a tunnel with a numbering scheme I keep getting wrong, and a dozen small traps that only exist because of decisions I made two years ago. Every new session started from zero. So I gave the assistant a place to write things down, and a way to read them back before it started working. Two calls: one to save what was learned, one to recall it. That was the whole idea. For six weeks it had exactly one user. Nobody else could have used it, because I had not written a single line of documentation. Six weeks of being my own only customer That stretch turned out to be the most valuable part, and not because of what got built. Because of what got measured. When you are the only user, every rough edge lands on you within a day. A recall that returns the wrong thing costs you the next hour. A save that silently drops a field costs you the next week, when you go looking for it. I kept a count of the times the memory actually prevented a mistake. Not a feeling, a count. After six weeks it was high enough that I stopped arguing with myself about whether the thing was worth the effort. The uncomfortable part: several of those saved lessons were about mistakes I had already made twice. The tool did not make me smarter. It made me stop paying for the same lesson. The moment it stopped being a personal tool The thought that changed it was not a market analysis. It was smaller and more honest: if I find this useful, and my setup is not special, then somebody else is retyping their own servers right now. That is a weak argument on its own. Plenty of internal tools are useful precisely because they fit one person. So I looked for the part
AI 资讯
Microsoft Releases Aspire 13.5 With a Refreshed Dashboard and Workflow Improvements
Last week, Microsoft released Aspire 13.5, an update that refreshes the dashboard and the aspire.dev homepage and adds several quality-of-life features. The Interaction Service gains file imports and progress dialogs; resources can host an interactive terminal in the dashboard, and deployment adds Kubernetes persistent volumes and cross-scope Azure references. By Almir Vuk
AI 资讯
Flux Mirror Uses Gitless GitOps to Keep Software Supply Chain Under Control
Flux has introduced Flux Mirror, a CLI plugin that mirrors container images, Helm charts and OCI artifacts between registries from a declarative configuration. The plugin is part of the Flux v2.9 CLI plugin system and is presented as a way to keep Kubernetes clusters reconciling only from registries that teams operate themselves. By Matt Saunders
AI 资讯
Stop Writing Regex to Match URLs — The Browser Already Can
Priya was three paragraphs into rewriting a support ticket when the page flashed and her draft reverted to what it had looked like an hour earlier. She hadn't refreshed. Nobody had. The service worker had. It was running a cache-first strategy for ticket pages — fetch once, serve from cache after that, so the dashboard felt instant on a flaky connection. The intent was to cache /tickets/482 , the read-only view, and leave /tickets/482/edit alone, since an edit form is exactly the page you never want served stale. Here's the line that decided which was which: const isTicketView = /^ \/ tickets \/\d +/ . test ( pathname ); Spot it yet? Read it once more before you scroll. The missing character was $ /^\/tickets\/\d+/ anchors the start of the string — ^ — but never anchors the end. So it matches /tickets/482 . It also matches /tickets/482/edit , /tickets/482/history , and /tickets/482-anything-at-all , because "one or more digits after /tickets/ " is true of all of them. The regex was never wrong about what it checked. It just never checked enough. The one-character fix is obvious once you see it: const isTicketView = /^ \/ tickets \/\d +$/ . test ( pathname ); Ship that and you'll hit the next edge case within a week: a trailing slash ( /tickets/482/ ) now fails to match, because $ demands nothing comes after the digits — not even a slash. Add \/? before the $ and you've fixed that one. Then someone deep-links to /tickets/482?tab=history and the query string breaks the anchor again, because pathname on some code paths actually holds the full URL. Each fix is a patch on the last, and every patch is a chance to reintroduce the first bug in a new shape. This is the part nobody tells you about hand-rolled URL matching: it isn't hard because regex is hard. It's hard because "does this path match this shape" has a dozen boundary conditions, and a hand-written pattern only encodes the ones you happened to think of on the day you wrote it. The API built for exactly this job T
AI 资讯
Dashforge: an application orchestrator for React
React solved rendering. Dashforge tries to solve orchestration — theming, forms, permissions, and visibility moved out of your components, declaratively, predictably, reusably. Two skins (MUI and Tailwind), one contract. Building complex applications isn't about building components. Inside a single module you're juggling forms, permissions, roles, visibility conditions, fields that depend on other fields, business logic. And all that logic ends up scattered across the app : a <Controller> here, an if (user.role === …) there, a useEffect watching one field to update another, a context for theming. If React solves the rendering problem, Dashforge tries to solve the orchestration problem. Dashforge moves that complexity out of the components and makes it declarative, predictable, and reusable . At its core it uses react-hook-form ; on top of it, a stable contract — identical across the MUI and Tailwind editions. Let's go through it piece by piece. 1. Theming — token-first, build-time and run-time Components don't hard-code colors or spacing: they consume typed design tokens ( @dashforge/tw-tokens , a pure TypeScript package, zero runtime). From there, the tokens travel on two rails. Build-time — the utilities. A Tailwind preset emits the usual utilities ( bg-primary-600 , text-neutral-900 ): // tailwind.config.ts import { dashforgePreset } from ' @dashforge/tw-theme ' ; export default { presets : [ dashforgePreset ()], content : [ ' ./src/**/*.{ts,tsx} ' ] }; Run-time — the CSS variables. The provider republishes those same tokens as CSS variables on <html> : < DashforgeTailwindProvider > < App /> </ DashforgeTailwindProvider > Here's the trick: bg-primary-600 doesn't resolve to a fixed color — it resolves to var(--tw-color-primary-500) . The provider sets that variable; change the variable, the color changes — no re-render, no Tailwind rebuild. The store is reactive (Valtio) with cross-tab sync, so dark mode or a live theme change is just a variable flip. In the MUI e
AI 资讯
1a vez trabalhando com git com time: tudo que você precisa saber
Faz mais de 5 anos que eu não abria um PR ou issue técnica no Github, mas essa semana tenho aprendido algumas boas práticas e termos que reuni neste artigo. Introdução Essa semana eu fiz uma coisa simples: atualizei o README de um projeto open source, o 4noobs , da comunidade He4rt. Troquei um badge, ajustei o contraste de um logo, organizei umas pastas e adicionei um índice pra facilitar a navegação. Nada muito complexo no fim das contas. Só que antes de chegar no "nada muito complexo", eu passei um tempo enrolada com uma pergunta boba: "E se eu mandar isso direto pra branch principal e bagunçar tudo?" Se tu já sentiu esse friozinho na barriga antes de mexer num repositório que não é só teu, esse artigo é pra ti. Não importa se tu é dev há anos ou se nunca abriu um terminal na vida... A lógica por trás de "como contribuir sem quebrar nada" é a mesma e bem mais simples do que parece. Definição de Git Colaborativo Quando eu aprendi git há uns anos, aprendi somente o versionamento e a enviar os arquivos pra dentro do Github, mas ele é bem mais que isso, né? É através dele que times enormes interagem a respeito de um mesmo projeto de forma organizada, comentando, gerenciando tarefas, sugerindo melhorias e conhecendo o que os outros envolvidos estão fazendo. Isso é a parte do Git Colaborativo . O Git resolve isso com um conceito central: branches (ou "ramificações"). Cada branch é tipo uma cópia paralela do projeto, onde tu pode mexer à vontade sem afetar a versão "oficial" (geralmente chamada de main ou master ). Quando tu termina sua parte, tu propõe que essas mudanças sejam incorporadas de volta pelo Pull Request (PR) . Ou seja, o fluxo básico é: Tu cria uma branch nova a partir do projeto principal Faz as alterações lá, no seu espaço isolado Envia ( push ) essa branch pro repositório remoto Abre um Pull Request pedindo pra essas mudanças serem revisadas e, se aprovadas, unidas ( merge ) à branch principal Ninguém mexe direto na versão "de produção" do projeto. Isso
AI 资讯
HTTP Caching Explained: max-age, ETag and Why Your Users Still See Last Week's CSS
📺 Prefer to watch? 90-second YouTube Short · 💬 Telegram Originally published on software-engineer-blog.com . You fixed the CSS. You deployed. You opened the site and checked it yourself — perfect. Then a customer sends a screenshot of last week's layout. Nothing is broken. No deploy failed, no CDN is lying to you, no file is corrupt. The browser is doing exactly what you told it to do, several days ago, in a header you probably never wrote by hand. This is the part of web performance that gets skipped, because caching looks like a setting rather than a contract. It is a contract. And like any contract, the interesting part is not what it gives you — it is what you can no longer do once you have signed it. Throughout this post I will use one running example: PlantPal , a small plant shop. One stylesheet ( app.css ), one logo ( logo.png ), one API endpoint ( /api/products ). The floor: a page load is not one thing Before caching means anything, you have to see what it is acting on. Loading PlantPal's homepage is not a request. It is roughly 40 separate requests — the HTML, the stylesheet, a few fonts, the logo, a dozen product images, the JavaScript bundle, the product API. Each one is a full round trip: DNS is probably warm, but you still pay connection setup, the request, the server's think time, and the bytes coming back. The numbers for a first visit: ~40 requests 1.2 MB transferred 2.1 s to a usable page Which gives us the only sentence in this post that you actually need to remember: The fastest request is the one the browser never sends. Not a faster server. Not a closer edge node. Not a smaller file. No request at all. Everything below is a way of getting closer to that. max-age: buying silence The blunt instrument is Cache-Control : HTTP / 1.1 200 OK Content-Type : text/css Cache-Control : max-age=31536000 31536000 is one year in seconds. You are telling every browser that receives this response: keep this copy and use it for a year without asking me again. O
AI 资讯
Physical Server vs Cloud Server: Which Infrastructure Makes More Sense?
When building an application, we usually focus on the frontend, backend, APIs, and database. But there is another important question: Where should the application actually run? Two common approaches are physical servers and cloud/virtual servers. Understanding the difference is important because infrastructure decisions affect scalability, availability, security, maintenance, and cost. What Is a Server? A server is a computer system that runs applications, processes requests, communicates with databases, and provides information to users. A typical request might look like: User → Internet → Application Server → Backend → Database → Response Depending on the application, the server may handle authentication, APIs, user data, file processing, notifications, and other backend operations. In simple terms, the server provides the execution environment behind the application. Physical Server: More Control, Less Flexibility A physical server is a dedicated machine used to run applications. For example: 16 CPU cores + 64 GB RAM + 2 TB SSD Advantages: • Dedicated hardware • Predictable performance • Greater hardware-level control • Suitable for stable workloads Limitations: • Higher initial investment • Hardware maintenance • Hardware failures can cause downtime • Scaling requires additional or upgraded hardware If an application suddenly grows beyond the capacity of the machine, increasing capacity may require purchasing and configuring new hardware. Cloud / Virtual Server: Infrastructure That Can Adapt A cloud server is a virtual server running on physical infrastructure inside a cloud data center. For example: 4 vCPU + 16 GB RAM + SSD Instead of purchasing the entire physical machine, resources can be provisioned according to the application's requirements. Cloud environments also provide different scaling approaches. Scale Up: Increase the resources of an existing server. 4 vCPU → 8 vCPU → 16 vCPU Scale Out: Add additional application instances. Application Server 1 + Ap
AI 资讯
Switch Icons v0.2.0: A React Icon Library Built for the Icons Developers Actually Need
Modern web applications rarely need only arrows, menus, and generic interface icons. A fintech dashboard needs payment and banking icons. A logistics platform needs waybills, packages, warehouses, and delivery trucks. An AI application needs model, prompt, and AI-related visual language. An African commerce platform may need icons that represent local payment methods such as Naira, USSD, POS, and bank transfers. That is the idea behind Switch Icons. Switch Icons is a modern, developer-focused React icon library designed around practical icons for real-world applications—not simply another collection of unrelated SVGs. Why Switch Icons? There are already plenty of excellent icon libraries available. But while building modern applications, there is often a gap between the generic icons most libraries provide and the domain-specific icons developers actually need. Switch Icons is being built around that gap. Instead of focusing exclusively on generic UI elements, the library combines familiar interface icons with categories such as: Fintech and payment rails Logistics AI Commerce Technology Security Social Business and CRM Communication Media The goal is simple: make it easier for developers to find the right icon without having to create or hunt down an SVG every time they build a feature. What's New in v0.2.0? Switch Icons has now reached its first public npm release. Version 0.2.0 includes 93 icons across 9 major categories, along with 14 solid variants for icons where a filled visual style makes more sense. The current collection includes: Navigation & UI Essential icons for navigation, actions, and common interface patterns. People & Communication Icons for users, teams, messaging, communication, and related functionality. Business & CRM Icons designed for business applications and customer-management interfaces. Fintech & Payment Rails This is one of the areas that makes Switch Icons particularly different. The library currently includes icons such as: Naira Bank
AI 资讯
Fix Next.js "params should be awaited" Error in Next.js 15+
Fix Next.js "params should be awaited" Error in Next.js 15+ If you are seeing the params should be awaited Next.js error after upgrading to Next.js 15 or following an older App Router tutorial, you are not alone. The error usually looks something like this: Route "/blog/[slug]" used params.slug. params should be awaited before using its properties. Sometimes it appears with searchParams . Sometimes it appears with cookies() or headers() . And sometimes the page still seems to work, but your terminal keeps shouting at you. This article will slow it down and explain the fix in a beginner-friendly way. No deep framework lecture first. Just the actual problem, the broken code, the fixed code, and the reason it works. What This Error Means in Plain English In older Next.js code, you may have treated params like a normal JavaScript object. Something like this: const slug = params . slug ; That used to feel natural. If your route was: /blog/[slug] and the user opened: /blog/my-first-post you expected: params . slug ; // "my-first-post" In newer Next.js versions, especially Next.js 15+, some request-based values became asynchronous. That means you should treat them like values that need to be waited for before you read from them. So instead of reading params.slug directly, you do this: const { slug } = await params ; That is the heart of the fix. The error is not saying your route is missing. It is not saying your [slug] folder is wrong. It is saying: You are trying to read route data before awaiting it. The common flow: the page loads, the code reads params.slug directly, Next.js expects params to be awaited, and the error appears. Why This Changed Next.js has a group of features called Dynamic APIs . That sounds more complicated than it is. In simple terms, Dynamic APIs are values that depend on the current request. For example: What route did the user open? What query string is in the URL? What cookies came with this request? What headers came with this request? Is draft
AI 资讯
Self-Hosted Chatwoot: 5 Failures the Docs Don't Warn You About
I run self-hosted Chatwoot as the WhatsApp inbox for a dozen or so small Israeli businesses. Two servers, a few thousand conversations a week, a drip-sequence engine bolted on the side. Chatwoot is good software. The self-hosting docs will get you to a running container. What they will not tell you is which failures actually happen at month six, when you have real customers and real volume. These five all bit me in production, and none of them looked like what they were. 1. Your disk fills from somewhere Postgres never sees I got a disk alert at 86 percent and immediately went looking at the database. That was the wrong place. DB (postgres): 680 MB chatwoot_storage_data: 17 GB Attachments live in ActiveStorage, on a Docker volume, not in Postgres. Every image, voice note, and PDF a customer sends is a file on disk, and none of it shows up when you check database size. If your monitoring watches the DB, it will report everything is fine right up until the container cannot write. The growth curve is a function of how many accounts you host, not how busy any one of them is. Mine sat at roughly 0.05 GB a month until I onboarded seven new businesses over two months, and then it hit 16 GB a month. Check the right volume: docker system df -v | grep chatwoot_storage_data 2. Forty-four percent of my outbound storage was duplicate files This is the part that surprised me. When I actually measured what was on that volume, almost half the outbound media was byte-identical copies of the same file. One 14.5 MB video was stored 48 separate times. One image was stored 325 times. Chatwoot creates a new blob and a new file on disk on every send, even when the bytes are identical. That is correct behavior for a chat app where every message owns its attachment. It becomes expensive the moment you have anything that fans one file out to many conversations. In my case it was not campaigns at all, it was the drip engine sending the same media to 48 separate conversations as ordinary outbo
AI 资讯
How I Built a Color Picker That Actually Converts Colors Correctly (HEX/RGB/HSL)
While working on a design system recently, I kept running into the same frustrating problem: I'd grab a color from Figma in HEX format, need it in HSL for a CSS variable, and end up bouncing between three different websites just to convert one value. Each site had its own UI quirks, some required JavaScript to be enabled, and none of them gave me a proper color scheme alongside the conversion. So I did what any reasonable developer would do — I built my own. Because apparently I enjoy reinventing wheels. The Problem With Existing Solutions The existing color converter tools online weren't bad, but they had a few issues that bugged me: They were slow — many loaded heavy JavaScript libraries just to do simple math They lacked context — I wanted to see complementary colors and schemes alongside the conversion They were ad-heavy — I don't want to dodge pop-ups while trying to match a shade of blue I wanted something that felt like a native tool: instant, offline-capable, and comprehensive. A single HTML file that I could open, use, and close without ceremony. The Architecture Decision The first decision was whether to use a library or write the conversion logic myself. Libraries like color (npm) are battle-tested, but they add weight. Since this is a browser-only tool with no build step, I decided to write the conversions in vanilla JavaScript. Here's the core conversion logic that handles the heavy lifting: function hslToRgb ( h , s , l ) { s /= 100 ; l /= 100 ; const k = n => ( n + h / 30 ) % 12 ; const a = s * Math . min ( l , 1 - l ); const f = n => l - a * Math . max ( - 1 , Math . min ( k ( n ) - 3 , Math . min ( 9 - k ( n ), 1 ))); return [ Math . round ( f ( 0 ) * 255 ), Math . round ( f ( 8 ) * 255 ), Math . round ( f ( 4 ) * 255 )]; } This is the most concise HSL-to-RGB conversion I know. It's a compact version of the standard formula that avoids the typical case-based approach. The math checks out for all edge cases, including grayscale (when s = 0 ). AI-Assi
AI 资讯
Deploying Multiple Python Bots to a Single Railway Container
A tutorial for running two or more python bots on Railway inside one container and one service, with independent crash recovery for each. Deploying Multiple Python Bots to a Single Railway Container If you're running more than one Python bot — say, a Telegram ingestion bot and a Discord notification bot that share a database — deploying each as its own Railway service means double the hosting cost and double the configuration for something that's logically one unit. This tutorial covers deploying both bots inside a single Railway container, with each one still getting fully independent crash recovery. Table of Contents Why Two Services Is Usually Overkill The Naive Fix and Why It Falls Short Step 1: Install StayPresent Step 2: Structure Your Project Step 3: Configure Multiple Bots in One Entry Point Step 4: Read Railway's Assigned Port Step 5: Deploy as a Single Railway Service Verifying Both Bots Are Running FAQs Conclusion Why Two Services Is Usually Overkill Railway (like most PaaS platforms) charges per service, and each service needs its own configuration, environment variables, and deployment pipeline. If two bots are closely related — sharing a database, a queue, or just conceptually belonging to the same project — running them as two separate Railway services duplicates all of that for no real benefit. The Naive Fix and Why It Falls Short A common first instinct is a shell script: python telegram_bot.py & python discord_bot.py & wait This runs both, but there's no real process supervision here — if telegram_bot.py crashes, nothing restarts it, and you still haven't solved Railway's HTTP port requirement, since neither script opens one. Step 1: Install StayPresent pip install staypresent[prod] # requirements.txt staypresent[prod] Step 2: Structure Your Project project/ ├── main.py ├── telegram_bot.py ├── discord_bot.py ├── requirements.txt Both bot scripts stay exactly as they are — nothing about their internal logic needs to change. Step 3: Configure Multipl
AI 资讯
Perry Mason in: The Case of the Drifting Timer
Perry Mason in: The Case of the Drifting Timer Opening Statement You need a reactive "current time" in your Vue 3 app. A schedule grid with a red line showing "now." A live clock. A dashboard that updates every minute. Every Vue developer reaches for setInterval first. It works. But "works" and "works well" are different things. This is the story of taking a naive timer from "it ticks" to production-grade — and the four iterations it took to get there. The prosecution calls four exhibits. Let's begin. Exhibit A: The Memory Leak const currentTime = ref ( new Date ()) onMounted (() => { setInterval (() => { currentTime . value = new Date () }, 60000 ) }) It works. Sort of. The defense rests — but the prosecution is just getting started. Exhibits of negligence: The interval is never cleared. When the component unmounts, the timer keeps firing every 60 seconds forever — updating a ref nothing reads anymore, and holding its closure (and everything the ref references) in memory for the lifetime of the page. Silent. Invisible. The kind of leak that shows up in production after a user navigates around your app for 20 minutes. Exhibit B: Component-Only Cleanup const currentTime = ref ( new Date ()) let timeInterval = null onMounted (() => { currentTime . value = new Date () timeInterval = setInterval (() => { currentTime . value = new Date () }, 60000 ) }) onUnmounted (() => { if ( timeInterval ) clearInterval ( timeInterval ) }) Now we clean up. The interval is stored in a variable, cleared on unmount. A step forward. But onUnmounted has a scope limitation worth understanding: The limitation: onUnmounted only works inside components. If someone calls this logic from a Pinia store or outside a component's setup() context, onUnmounted never fires. The timer leaks silently. (Composables called synchronously during setup() are fine — Vue's docs recommend exactly that. The problem is when there's no component instance at all.) The timer fires 60 seconds after load , not at the t