今日已更新 302 条资讯 | 累计 41076 条内容
关于我们

标签:#web

找到 2947 篇相关文章

开发者

A Pet Project: Zodiacl

It’s been awhile since I typed some words around here. Right around the World Cup was the last time, from what I can see. I really didn’t want to like the World Cup, but there’s something about watching it I family that makes it about more than just a game. But I digress - this post has nothing to do with football (fortunately). This is about a pet project I’ve been kicking around for some time, and finally got around to fleshing it out and making it live. A Chinese and Western Zodiac calculator. I’m not a hard-core zodiac horoscope person. I just find the topic interesting. From time to time people would say, “Hey what sign am I?”. I’d spin up the ol’ internet and look for a good Chinese Zodiac calculator (Western Zodiac is much simpler to calculate). But the calculators were usually either deeply embedded in larger sites that were not dedicated to the topic, or ugly as hell and ridden with ads (and who knows what else). I really wished to just find a nice-to-look-at calculator that worked. And one day I realized - hey, I could just make one. I make nice looking websites. I can code enough to set up a Chinese Zodiac calculator that can accurately calculate lunar cycles and your Chinese zodiac animal, without blindly saying, “Oh, you were born in 2026? You’re a horse, of course!” (Which would only be true if you were born on February 17th of 2026 or later). You know, a simple tool that does one thing, and one thing well - show you your sign! (So you can stop asking people in bars…) Zodiacl is Born If you want to know more about the technical side of the new site, you can check it out in my portfolio. So I was probably about halfway through coding the project when the name dawned on me. Unless a client or project needs otherwise, I code and publish for an English- and Spanish-speaking audience. I deeply favor names that work in both languages; the closer to a perfect cognate (word spelled exactly the same in both languages and with the same meaning) the better. I fou

2026-08-20 原文 →
开发者

I Built 132 Free Online Tools Because I Kept Searching for Them

As a developer, I constantly end up searching for small tools to do random things. Format JSON, decode JWTs, generate UUIDs, encode URLs, compare text, convert data, and so on. I got tired of opening a different website every time, so I started building my own collection. That's CtrlTool. It currently has 132 free tools for developers and everyday tasks, with a focus on keeping them fast, simple, and easy to use. A lot of the tools process data directly in the browser when possible. https://ctrltool.wtf It's still very new, so I'd love to hear what tools you think are missing.

2026-08-20 原文 →
AI 资讯

React State Management in 2026 — Context API vs Redux Toolkit vs Zustand vs Jotai (Same Cart, Real Code + Benchmarks)

The React state-management debate has produced more bad takes than any other frontend topic. "Just use Context." "Redux is dead." "Zustand for everything." "Jotai is the future." All four are partially right and partially dangerous, depending on what you're building. So instead of arguing, I built the same shopping cart — derived totals, async fetch, localStorage persistence, three subscribing components — in all four libraries , and benchmarked it. This is the condensed version; the full guide (all four implementations with real code, the complete matrix, and the decision flow) is on my site 👇 Full guide: https://prepstack.co.in/blog/react-state-management-context-redux-toolkit-zustand-jotai-comparison-guide The one benchmark that reframes everything 1,000 components subscribed to one store. Update one value. How many re-render? Library Components re-rendered Wall-clock Context (single value) 1,000 (all) 42 ms Context (split into 5) ~200 12 ms Redux Toolkit (selectors) 1 2.1 ms Zustand (selector) 1 1.8 ms Jotai (atom) 1 1.5 ms Context without splitting re-renders the world. The other three are within margin of each other — meaning the real differences are boilerplate and DX , not render speed. The four, in one line each Context API — built-in, 0 KB, but every consumer re-renders on any change. Right for theme/auth/locale; wrong for anything busy or with many subscribers. Redux Toolkit — ~22 KB, most boilerplate, but RTK Query (caching, dedupe, invalidation), middleware, and time-travel DevTools are best-in-class. Payoff scales with app complexity. Zustand — ~3 KB, no provider, selectors built in, a full store (state + async + persistence) in ~25 lines. The modern default for most 2026 apps. Jotai — state is many small atoms, each with its own subscriber list. Smallest blast radius per update; ideal for forms and derived graphs. Real production migration (same e-commerce app) Metric Context-everywhere Redux Toolkit Zustand Initial JS (gzipped) 412 KB 438 KB 390 KB A

2026-08-20 原文 →
AI 资讯

Foodwars: Battle of the Comfort Foods

What if deciding what to eat felt as exciting as winning a championship? It's 2 AM. You're hungry. You open your favorite food delivery app, convinced you'll order something in two minutes. Thirty minutes later, you're still scrolling. Pizza? Burger? Pasta? Fries? Momos? Ice cream? Suddenly, every option looks equally good, and now you're questioning your entire existence just because you wanted dinner. I have this problem almost every time I order food. So when I saw the DEV Challenge, I wanted to build something fun around this tiny but painfully relatable problem. Unfortunately, I couldn't finish it before the deadline, but I still wanted to share the idea because it's one of those projects that made me smile while building it. Meet Foodwars . Instead of endlessly scrolling through hundreds of dishes, why not let your favorite comfort foods battle each other until only one champion remains? What I Built We've all watched cooking shows like MasterChef and somehow turned into professional judges sitting comfortably on our sofas. "That steak is overcooked." "The sauce needed more balance." "I would've plated it differently." As if Gordon Ramsay personally asked for our opinion. Foodwars lets us finally put those imaginary judging skills to good use. Instead of comparing hundreds of dishes at once, the platform randomly pairs comfort foods against each other in head-to-head battles. You become the judge. Pick the winner, move on to the next matchup, and continue until one food survives the tournament. No endless scrolling. No decision fatigue. Just a series of fun, quick decisions that eventually crown your Ultimate Comfort Food . And once the champion is decided... Go order it. Or cook it. Either way, dinner has finally been decided. Demo comfort-foodwars.vercel.app Features of Foodwars Foodwars isn't just a random food picker. Every round is designed to make choosing food feel like a game instead of a chore. 1. Interactive Tournament Brackets Instead of presenting

2026-08-20 原文 →
AI 资讯

Single-database multi-tenancy in Symfony: a 31-line Doctrine filter, and the five places it never runs

Single-database multi-tenancy is the cheapest kind: one schema, one connection, an organization_id column on every tenant-owned table. The whole design rests on one promise, and it is a promise about forgetting : no developer on the team will ever have to remember to write WHERE organization_id = ? , because forgetting it once leaks another customer's data. Doctrine has had the tool for this for years. It is a SQLFilter , it is about thirty lines, and almost every article about it stops at the happy path. The interesting part is not the filter. It is the map of the places where it is simply not there, because that map is what you actually have to defend. Everything below is read from Doctrine ORM 3.6.7 and from a suite that runs on every commit. The filter final class OrganizationFilter extends SQLFilter { public const string NAME = 'organization' ; public const string PARAMETER = 'organization_id' ; public function addFilterConstraint ( ClassMetadata $targetEntity , string $targetTableAlias ): string { if ( ! $targetEntity -> getReflectionClass () -> implementsInterface ( OrganizationOwnedInterface :: class )) { return '' ; } return \sprintf ( '%s.organization_id = %s' , $targetTableAlias , $this -> getParameter ( self :: PARAMETER )); } } OrganizationOwnedInterface is a marker with one method, getOrganization() . An entity opts into tenancy by implementing it, and that is the entire public API of the mechanism. No attribute to remember, no base class to extend, no trait whose absence is invisible in a diff. The filter is declared in doctrine.yaml with enabled: false . That is deliberate, and it is the first design decision worth arguing about: a filter that is on by default in the container is on in your fixtures, in your migrations, in your data-repair scripts, and it will bite you at three in the morning. It gets turned on by the layer that knows who is asking. The layer that knows who is asking public static function getSubscribedEvents (): array { // Right aft

2026-08-19 原文 →
产品设计

S3 Egress Fees: Why Downloading Your Own Data Costs So Much

Cross-posted from the Runsite blog . You put a few hundred gigabytes of images on object storage, glance at the pricing page, and the numbers look friendly: storage is a couple of dollars a month, basically a rounding error. Then the first real invoice arrives and it's a hundred and something. Nothing about how much you're storing changed. The line that blew up isn't storage at all. It's egress — the charge for data leaving the bucket — and it's the part of the bill nobody shops on. Why the storage bill blows up after the first invoice The pricing page wasn't lying to you. Object storage genuinely is cheap to sit on. On AWS S3 , standard storage runs about $0.023 per GB per month at the time of writing, so a hundred gigabytes of assets costs you around two dollars and change to keep. That's the number you compare when you're choosing where to put your files. The number you don't compare is egress: the fee for moving data out of the provider's network. It doesn't show up when you upload, and it doesn't show up while the files just sit there. It shows up every time someone downloads something — roughly $0.09 per GB to the internet once you're past a small free allowance (about the first 100 GB a month on AWS). Individually those are tiny fractions of a cent. The trouble is you're not billed once. You're billed per download, and a popular file gets downloaded a lot. Where egress hides Egress is data transfer out: every byte that leaves the provider's network. The reason it surprises people is that it isn't a single line you can point at. It's a multiplier that quietly attaches itself to things you'd never think of as "downloading": Serving assets to users. Every image, video, PDF, or download your app hands to a visitor is egress. One 4 MB hero image on a page that gets a million views a month is four terabytes of transfer out, from a single file. CDN origin pulls. Putting a CDN in front of your bucket helps, but it isn't free. Every cache miss means the CDN fetches th

2026-08-19 原文 →
AI 资讯

I Built a Claude Code Skill That Reverse-Engineers Undocumented APIs

I Built a Claude Code Skill That Reverse-Engineers Undocumented APIs Because "the docs are in the code" is not a documentation strategy. The Week I Lost to Grepping I joined a new team last month. Day 1 task: add a feature to the billing service. Day 1 reality: I opened the API docs and realized they were from 2022. Half the routes had been rewritten. The other half never had docs to begin with. So I did what every backend dev does. I grepped. grep -r "app.get|app.post|router." src/ --include="*.js" Four hours later, I had a notebook full of endpoints, a headache, and zero confidence that I had found everything. I found routes that worked but were not documented. I found docs for routes that did not exist anymore. I found one GET /invoices/:id endpoint with zero auth checks that had been sitting there since 2022. This is normal. And it should not be. The Idea What if I could drop a single file into a repo and have Claude Code map the entire API layer for me? Not from annotations. Not from existing OpenAPI specs. From the actual code. So I built it. Meet API Archaeologist API Archaeologist is a Claude Code / Codex CLI skill that reads your source code and reverse-engineers your API layer. It finds: • Internal endpoints — REST, GraphQL, gRPC, WebSockets • External integrations — third-party APIs, webhooks, SDK clients • Auth flows — JWT, OAuth, API keys, session cookies, RBAC • Security gaps — unauthenticated routes, hardcoded secrets, missing rate limits • Dead code — auth middleware with no endpoints, orphaned routes And it generates two things: API_DISCOVERY.md — A complete catalog with Mermaid diagrams openapi-draft.yaml — A draft OpenAPI spec How It Works The skill is just a SKILL.md file. Claude Code reads it and follows the instructions. It: Discovers route definitions Traces handlers, DTOs, middleware, services, and database calls Maps authentication and authorization Finds external API calls and integrations Flags potential security and reliability risks Gene

2026-08-19 原文 →
AI 资讯

AI Incident Copilot Guide for GCC Operations

🚀 Technical Briefing: This tutorial is part of our deep-dive series on Agentic Workflows at Gate of AI . For the full technical breakdown, interactive code sandbox, and the native Arabic translation, visit the original article here . <p>Tutorial</p> <h1>Design a Safer AI Incident Copilot for GCC Operations</h1> <p>An AI incident copilot can help an operations team turn approved engineering facts into a clearer draft for stakeholders. It should not be treated as an autonomous incident commander, a source of truth, or an automatic publishing system. This tutorial explains how to define a safe operating model before choosing a framework, model provider, deployment platform, or integration.</p> <h2>Why incident copilots need a security-first design</h2> <p>During an incident, teams work under pressure. They need to communicate what is happening, who may be affected, what mitigation is under way, and when the next update will arrive. These messages must be accurate, calm, and consistent. An AI assistant may help prepare a first draft, but it can also amplify mistakes if it is allowed to infer missing facts, read untrusted material, or publish messages without review.</p> <p>The available security research on Copilot-style systems is a direct reason to design cautiously. Researchers have demonstrated ways AI systems can be manipulated to provide false references to files, extract some private data, and bypass security protections. The same research describes proof-of-concept abuse that can turn an AI assistant into an automated spear-phishing mechanism after an attacker gains the necessary access. These are not minor quality issues. They show that an AI feature connected to organizational information can become a security boundary.</p> <p>For an incident copilot, the safest initial scope is deliberately narrow: accept a small set of verified facts supplied by an authorized incident lead, create a draft in a fixed communication format, and require a human to review and pub

2026-08-19 原文 →
AI 资讯

Sandboxed Code Evaluation for AI-Generated Outputs — How I Built SafeCode Arena

The Problem: Candidate Code Without Trust You're using Cursor, Claude Code, or GitHub Copilot. The AI gives you three implementation options for the same feature. AI: "Here are three approaches: A) Quick but uses unsafe B) Slower but memory-safe C) Balanced tradeoffs" You: "Which one should I ship?" AI: "It depends..." That "it depends" is where responsibility falls through the cracks. Tests tell you if code compiles and passes specs. But they don't tell you about security, performance, maintainability, or resource limits — all at once. You end up making the call by gut feel. This essay is about building a system that doesn't let that happen. The Solution: Multi-Axis Scoring I built SafeCode Arena — an automated verifier that evaluates code candidates across five axes simultaneously, scores each, and surfaces the tradeoffs. The Five Axes Axis Weight Computation Correctness 50% compile (40%) + tests (40%) + property tests (20%) Security 20% unsafe heuristics (50%) + clippy warnings (50%) Performance 15% relative compile+test time across candidates Maintainability 10% function-length heuristics (60%) + clippy (40%) Resource Usage 5% pass/fail of sandboxed Wasm execution Why These Five? Correctness dominates — code that doesn't work is valueless, so it's 50% Security is explicit — unsafe compiles fine, but you need to detect it yourself Performance and maintainability matter equally — a fast mess vs. a slow masterpiece aren't comparable Resource limits are real — a 100-point algorithm that consumes 2GB is a fail in production Example Scorecard Candidate A: 85 points ├─ correctness: 100 (all tests pass) ├─ security: 60 (2 unsafe blocks flagged) ├─ performance: 70 (10% slower than B) ├─ maintainability: 85 (avg function 25 lines) └─ resource_usage: 80 (Wasm sandbox: 512MB, OK) Candidate B: 92 points ✓ Recommended ├─ correctness: 95 (1 edge case warning) ├─ security: 95 (no unsafe) ├─ performance: 95 (fastest) ├─ maintainability: 88 (avg function 20 lines) └─ resource_usa

2026-08-19 原文 →
AI 资讯

A practical guide to live streaming protocols, latency and scaling

Live video looks simple until you build it. Then you discover that "low latency" means five different things, that your CDN and your latency target are fighting each other, and that the box which handled ten viewers falls over at ten thousand for reasons nobody warned you about. This is the guide I wish existed when I started. No vendor talk, just how the pieces fit. 1. Ingest and delivery are separate decisions The single most common mistake is treating "streaming protocol" as one choice. It is two. Ingest is getting video from a camera, encoder or browser into your server. Delivery is getting it from your server to viewers. They have different constraints and you almost never use the same protocol for both. A typical stack ingests over RTMP or SRT and delivers over HLS. Another ingests WebRTC and delivers WebRTC. Mixing is normal and expected. Once you separate them, most of the confusion disappears. 2. The ingest protocols RTMP is old, TCP-based, and still everywhere. Every encoder speaks it, OBS defaults to it, and it just works. Latency is typically 2 to 5 seconds. Classic RTMP is limited to H.264 and AAC, though the Enhanced RTMP spec has added HEVC and AV1. Being TCP, it degrades badly on lossy networks: packet loss becomes head-of-line blocking, and your stream stalls instead of gracefully dropping quality. SRT is the answer to that. UDP-based with its own retransmission layer (ARQ), a configurable latency buffer, and built-in AES encryption. It is designed for pushing broadcast-quality video across the public internet, which is exactly where RTMP struggles. If your source is on a flaky connection, a 4G link, or a different continent, SRT is usually the right call. # Publishing over SRT with ffmpeg ffmpeg -re -i input.mp4 -c copy -f mpegts \ "srt://your-server:4200?streamid=live/stream1" RTSP is what IP cameras speak. If you are pulling from surveillance hardware, you are pulling RTSP whether you like it or not. WHIP (WebRTC-HTTP Ingestion Protocol) is the n

2026-08-19 原文 →
AI 资讯

Namecheap closes every auction at 11:00 AM ET. Last-second bidding is a myth.

If you have ever tried to win a domain at auction, you probably assumed the game works like eBay: watch the clock, wait for the last eight seconds, fire your bid, walk away with the name. On Namecheap, that does not work. Not "works badly". Does not work. Namecheap's expiring and marketplace auctions close in a daily batch at 11:00 AM ET. Every auction ending that day ends at roughly the same moment, which means there is no quiet corner of the day where you and one other bidder are paying attention. And if a bid lands in the closing window, the auction extends. So the buzzer-beater you were planning gets absorbed and the clock keeps running. The winner is not the fastest click. The winner is whoever set the smartest proxy maximum, on a name they found before anyone else was looking at it. I have been building PounceDomains around that one fact for months, and it is the reason the product looks the way it does. The edge moved from timing to discovery If speed is not the lever, the levers left are: find the good names earlier, and know what they are actually worth before you commit a number. So the engine scans the Namecheap aftermarket around the clock rather than at the bell. You describe the domains you want in plain English, something like "pronounceable 5-letter .com brandables under $50, no numbers or hyphens", and it builds a tuned config you can edit. If your config is too broad, it tells you and tightens it. There are seven scoring lenses you can stack: pronounceable, brandable, exact-match keyword, short premium, dictionary word, two-word combo, and free-text custom criteria. Fast programmatic filters run first, then AI scores what survives, and only domains that clear your threshold become matches. It has graded over 340,000 domains so far. The second lever is the one I care about more. Every match arrives with its receipts The failure mode in domain investing is not missing a name. It is paying $400 for something worth $80 because a free appraisal tool pri

2026-08-19 原文 →
AI 资讯

GitHub API Rate Limits: an Unauthenticated 304 Still Costs You a Request

No token. One IP. July 29, 2026: GET /repos/python/cpython 200 5996 B remaining 32 -> 31 + If-None-Match (no Authorization header) 304 0 B remaining 31 -> 30 + If-None-Match 304 0 B remaining 30 -> 29 + If-None-Match 304 0 B remaining 29 -> 28 Three conditional requests. Three 304 Not Modified . Zero bytes of body across all three. Three requests gone from a bucket of 60 per hour. I opened the terminal to write the opposite post. The short version: if you call the GitHub REST API without an Authorization header, an If-None-Match request that comes back 304 still decrements x-ratelimit-remaining . The ETag saves you bytes. It does not save you quota. GitHub's documentation states the claim five times on one page and attaches the condition to two of them, and that clause falls off easily when a sentence gets quoted on its own. The post I meant to write My working title was something like "poll GitHub for free with ETags". I believed it. I had read the sentence about 304 responses not using your rate limit, I had repeated it to other people, and the plan was a tidy little piece with a before-and-after budget chart. The first run killed it. remaining went down. My first reaction was that my counter reading was wrong, which is the normal reaction and usually the correct one. It was not wrong. So the post changed, and the finding turned out to be worth more than the one I went in with. Does a 304 count against the GitHub rate limit? What the docs actually say Here is the part that matters, and I want to be precise because it would be easy and dishonest to turn this into "GitHub's docs are wrong". They are not. On the page Best practices for using the REST API the claim shows up five times. Two of the five carry a condition; three do not. Here is the strict one, the only place on the page where the condition is spelled out as a header: "Making a conditional request does not count against your primary rate limit if a 304 response is returned and the request was made while c

2026-08-19 原文 →
AI 资讯

I'm building Guren, a fullstack TypeScript framework for the AI-agent era

Guren is a fullstack TypeScript framework for Bun. I started it because I wanted Laravel's shape in TypeScript, and I kept going for a different reason: once I was handing most of the code to agents, what I wanted from a framework was a way to check what came back. gurenjs / guren Guren is a Bun-native TypeScript MVC framework that unites Laravel-like ergonomics with Hono, Inertia.js, React, and Drizzle ORM, aiming to deliver a fast, elegant full-stack workflow that keeps frontend and backend work in sync. Guren The fullstack TypeScript framework for the AI-agent era. Laravel-style conventions, end-to-end type safety, and built-in agent introspection and verification — routing, controllers, ORM, authentication, and Inertia.js + React in one cohesive experience that humans and AI coding agents navigate from the same map. v2 — Stable. Breaking changes only in major releases, per the release policy . Quick Start # 1. Scaffold a new app with authentication (dependencies install automatically) bunx create-guren-app my-app --auth cd my-app # 2. Run migrations and seed the demo user (SQLite by default — no server needed) bun run db:migrate bun run db:seed # 3. Start the dev server bun run dev Open http://localhost:3333 and sign in at /login with demo@example.com / secret . Add features as you go bunx guren add auth # Authentication bunx guren add resource posts --fields " title:string,body:text " # CRUD resource bunx guren add queue # Background jobs … View on GitHub I like the way Laravel and Rails let you build. A feature is a route, a controller, a model and a view, and authentication, queues, mail and validation are already wired together before you start. TypeScript has the parts. Hono for HTTP, Drizzle for the ORM, Zod for validation, Inertia and React for rendering, all of them good. What's missing is an agreed way to connect them, so every project ends up wiring it slightly differently, and I've written that wiring more times than I want to count. The mistakes move

2026-08-19 原文 →
AI 资讯

What If the Blockchain Could Judge Your Bluff Without Seeing Your Dice?

Liar’s Dice sounds like a perfect game to put onchain. The rules are simple, every move can be verified, and you don’t need a centralized game server deciding who won. There is just one problem. Blockchains are public. Liar’s Dice only works if your dice are private. If I simply stored every roll inside a normal smart contract, anyone could inspect the state and know exactly what everyone was holding. At that point, there is no bluffing. You would basically be playing poker with everyone's cards face up. So I built FHE Liar’s Dice , a decentralized version of the game where your dice remain encrypted while the game is being played. Not hidden behind a backend. Not stored privately in some database. Encrypted onchain. And the interesting part is that the smart contract can still use those encrypted dice to determine whether you are lying. The problem with putting hidden-information games onchain Most blockchain games actually benefit from transparency. If you're building something like chess, every player is supposed to know the complete state of the board. Liar’s Dice is different. Each player starts with five dice that only they should be able to see. Players then make public claims about the combined dice across the entire table. You might say: There are six 4s on the table. The next player has two choices. Raise the bid. Or call your bluff. The entire game comes from the fact that nobody knows exactly what everyone else is holding. But a traditional smart contract has the opposite property. Its state is transparent. Even if the frontend refuses to display your dice, someone can simply inspect the contract, query the state, watch events, or build their own interface. Hiding something in the UI isn't privacy. I needed the actual game state itself to remain secret. FHE turned out to be a very good fit for the game I built the game using Fhenix CoFHE . Fully Homomorphic Encryption is interesting because it allows computation to happen directly over encrypted values.

2026-08-19 原文 →
AI 资讯

React useScrollLock Hook: Lock Body Scroll for Modals (2026)

Your modal is open, centered, perfect. Then someone flicks the overlay and the page behind it scrolls away underneath. Everyone's first fix is the same three lines: useEffect (() => { document . body . style . overflow = open ? " hidden " : "" ; }, [ open ]); It works on your laptop. Then the bug reports arrive: On iPhone the page still moves. iOS Safari rubber-band scrolls the document by touch even with overflow: hidden on <body> . Something else got wiped. "" isn't necessarily what was there before — you just erased whatever your design system or CSS-in-JS had set inline. Two overlays, one frozen page. A drawer and a lightbox both own body.style.overflow ; close them in the wrong order and the page never scrolls again. The layout jumps the instant the desktop scrollbar disappears. useScrollLock from @reactuses/core is those three lines with the hard parts handled: it restores the exact inline overflow it replaced, adds a touchmove guard on iOS that still lets your modal's own content scroll, exposes the lock as React state you can render off, and works on any element — not just <body> . This post covers what it actually does line by line, why overflow: hidden is not enough on iOS, how it compares to the position: fixed and body:has(dialog[open]) approaches, and the six gotchas that show up in real apps. Quick Start npm install @reactuses/core import { useScrollLock } from " @reactuses/core " ; import { useEffect } from " react " ; function Modal ({ open , onClose , children }: ModalProps ) { // a getter, not `document.body` — see the SSR gotcha below const [, setLocked ] = useScrollLock (() => document . body ); useEffect (() => { setLocked ( open ); return () => setLocked ( false ); // release even if we unmount while open }, [ open , setLocked ]); if ( ! open ) return null ; return ( < div className = "overlay" onClick = { onClose } > < div className = "sheet" onClick = { e => e . stopPropagation () } > { children } </ div > </ div > ); } The signature: const [

2026-08-19 原文 →
AI 资讯

Why pasted text keeps breaking search and formatting (and the regexes I ended up using to clean it)

I kept running into a boring problem that was harder to debug than it should have been: text that looked normal, but behaved wrong the moment I pasted it into a CMS, a spreadsheet, or a code comment. Search would fail. Line breaks would get weird. A heading copied from ChatGPT would drag Markdown markers along with it. Sometimes the only visible clue was that the punctuation felt slightly "off." What finally made this manageable wasn't some big NLP trick. It was going back to the dumb, reliable layer: exact character matching. The tool I built for this is basically a pile of small, deterministic cleanups for the specific junk that copied text tends to accumulate — full-width punctuation mixed into ASCII, invisible Unicode code points, curly quotes, em dashes, leftover Markdown, and whitespace noise. The most useful part is the invisible-character scan, not the cleaning The piece I trust most in the whole component is the part that explicitly names which invisible characters it cares about, then counts them by code point. It's not doing a vague "this text seems suspicious" pass. It has a hard-coded inventory: const invisibleDefs = [ { key : " zwsp " , codes : [ 0x200b ] }, { key : " zwnj " , codes : [ 0x200c ] }, { key : " zwj " , codes : [ 0x200d ] }, { key : " bomZwnbsp " , codes : [ 0xfeff ] }, { key : " wordJoiner " , codes : [ 0x2060 ] }, { key : " softHyphen " , codes : [ 0x00ad ] }, { key : " bidiMarks " , codes : [ 0x200e , 0x200f , 0x202a , 0x202b , 0x202c , 0x202d , 0x202e ] }, ]; const codesToRegex = ( codes ) => new RegExp ( `[ ${ codes . map (( c ) => " \\ u " + c . toString ( 16 ). padStart ( 4 , " 0 " )). join ( "" )} ]` , " g " ); const analyzeInvisible = ( str ) => { const breakdown = invisibleDefs . map (( def ) => ({ key : def . key , count : ( str . match ( codesToRegex ( def . codes )) || []). length , })); const total = breakdown . reduce (( sum , row ) => sum + row . count , 0 ); return { breakdown , total }; }; I like this because it's brutall

2026-08-19 原文 →