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

标签:#JavaScript

找到 548 篇相关文章

AI 资讯

Stop Declaring Tools Dead — lucide-react is Still Fine

Every few months, a post goes viral: "Please stop using [perfectly good tool]." This time it's lucide-react. And honestly? The take is lazy What's the actual problem? Nothing. The library is actively maintained, tree-shakable, TypeScript-friendly, and has 1000+ consistent icons. A new icon library dropped? Cool. That doesn't make this one broken. Tools don't expire — context does Before switching anything in production, ask: Is it maintained? ✅ Does it solve my problem? ✅ Is my team comfortable with it? ✅ Then keep using it. Real usage — still clean in 2026 import { Search , Bell , User } from ' lucide-react ' ; export default function Navbar () { return ( < nav > < Search size = { 20 } /> < Bell size = { 20 } strokeWidth = { 1.5 } /> < User size = { 20 } color = "#6366f1" /> </ nav > ); } Tree-shaking works perfectly — only Search, Bell, and User are bundled. Not the entire library. When you should switch Unpatched security vulnerability Repo abandoned for 2+ years Bundle size issue you've actually measured If none of these apply, you're switching for hype, not logic. The real issue "Please stop using X" posts get engagement. Developers see them, second-guess stable choices, and waste hours migrating things that weren't broken. Don't let LinkedIn trends drive your architecture. Build products. Not migrations.

2026-06-12 原文 →
AI 资讯

Parallel AI Coding with Git Worktrees: Run Multiple Agents Without Conflicts

Parallel AI Coding with Git Worktrees: Run Multiple Agents Without Conflicts Most parallel AI development problems stem from a single architectural mistake: multiple agents sharing the same working directory. Teams spin up three Claude Code instances, point them at the same project folder, and watch as file writes collide, branch checkouts interrupt each other, and lock files corrupt. The symptom looks like a race condition. The root cause is filesystem design. Git worktrees solve this by giving each agent its own isolated working directory while sharing a single .git repository. This distinction is critical. Developers get parallel execution without the storage overhead of full clones, and agents operate on separate branches without stepping on each other's file handles. The pattern has existed since Git 2.5, but AI coding workflows finally make it essential infrastructure. The Collision Problem: Why Multiple AI Agents Can't Share a Working Directory When you run git checkout feature-A in a directory where another process is reading files, the filesystem state changes underneath that reader. The other process doesn't see atomic transitions—it sees partial writes, missing files, and inconsistent dependency graphs. TypeScript compilers fail with "Cannot find module" errors. Dev servers crash because watched files disappeared mid-read. Lock files from package managers become corrupted when two agents run npm install simultaneously on different branches with different dependency trees. The obvious solution—staggering agent execution so only one runs at a time—defeats the purpose of parallel development. Teams that try this pattern end up with AI agents waiting in queue, each one blocking the next until it finishes. The bottleneck shifts from human typing speed to serial execution, and the productivity gains evaporate. Full repository clones work but waste disk space. A 2GB monorepo cloned five times for five agents consumes 10GB of redundant Git objects. Sparse checkou

2026-06-12 原文 →
AI 资讯

How to Convert JSON to XML Without Breaking Your Integration

Working with modern APIs means living in JSON. But the moment your project touches a legacy enterprise system - a bank, a government service, or a SOAP endpoint that hasn't changed in a decade - you're suddenly dealing with XML. The challenge isn't just swapping syntax; it's understanding where the two formats are structurally incompatible, and what breaks silently when you ignore that. Why JSON and XML Don't Simply Map to Each Other JSON is compact and type-aware - it distinguishes between numbers, booleans, strings, and arrays natively. XML is verbose, treats all content as text, and has no concept of arrays. It only has repeated sibling elements. This gap is where most conversion bugs are born. A JSON array with just one item can silently become a plain object if your converter doesn't handle the edge case explicitly. The Three Biggest Conversion Pitfalls First is array ambiguity - XML has no array type, so a JSON array becomes repeated sibling elements. A single-item array is indistinguishable from a plain object unless your converter explicitly preserves the list context. Second is type erasure - XML flattens numbers, booleans, and strings into plain text, destroying the type information that many downstream systems depend on. Third is the single root element rule - JSON can have multiple top-level keys, but every valid XML document must have exactly one root element wrapping everything else. Handling Arrays the Right Way Always nest array items inside a named parent element. A JSON users array should produce a parent element containing individual child elements. This structure makes the list unambiguous to any downstream XML parser and prevents silent data loss during round-trips. Escaping Special Characters Characters that are perfectly valid inside a JSON string will break an XML parser immediately. Your conversion logic must escape these four: less-than becomes <, greater-than becomes >, ampersand becomes &, and double-quote becomes ". Skipping even one of

2026-06-12 原文 →
AI 资讯

Validate and Mask Environment Variables in Node.js and TypeScript

Configuring environment variables seems simple, but it frequently leads to two common issues in production-grade applications: Missing Variables: A developer adds a new variable locally but forgets to update the .env.example file. Other team members pull the changes, and their local environments crash. Leaked Credentials: A debug log like console.log(process.env) prints database connection strings or API tokens to the terminal, leaving them visible in plaintext logs. To solve this, we created @novaedgedigitallabs/envkit . It is a zero-dependency (other than Zod) utility that validates, loads, and masks environment variables, and keeps your example configurations updated automatically. Key Features Schema Validation: Define your environment variables with Zod to enforce types and formats. Log Masking: Automatically redact sensitive credentials in console output. Example Syncing: Automatically append missing variables to your .env.example file without overwriting comments or values. Module Support: Runs in ESM and CommonJS. Getting Started Install the package and Zod: npm install @novaedgedigitallabs/envkit zod Initialize your configuration in a file like env.ts : import { createEnv } from ' @novaedgedigitallabs/envkit ' ; import { z } from ' zod ' ; export const env = createEnv ({ schema : { DATABASE_URL : z . string (). url (), JWT_SECRET : z . string (). min ( 32 ), PORT : z . coerce . number (). default ( 3000 ), NODE_ENV : z . enum ([ ' development ' , ' production ' , ' test ' ]). default ( ' development ' ), }, secrets : [ ' JWT_SECRET ' , ' DATABASE_URL ' ], generateExample : true , // Appends new keys to .env.example at runtime }); Because the variables are validated using Zod, your editor will provide full TypeScript autocomplete and type safety: env . PORT ; // Resolved as a number env . JWT_SECRET ; // Resolved as a string Preventing Secret Leaks in Logs Logging environment configs is a common debugging habit. With envkit, any variables listed in the secre

2026-06-12 原文 →
开发者

Announcing Limn Engine — A Lightweight 2D Game Framework for the Browser

Announcing Limn Engine I'm excited to launch Limn Engine — a lightweight, zero-dependency HTML5 Canvas game framework for the browser. No npm install. No build step. No bloated dependency tree. Drop in a single script and start making 2D games. The Core Idea: Display + Component Limn Engine is built around two classes that cover 90% of what you need in a 2D game: Display — The Game Shell A singleton Display class that manages the canvas, runs the game loop, handles keyboard and mouse input, controls the camera, and manages scenes. Setting up a game is a one-liner: const display = new Display (); display . start ( 800 , 600 ); Let me try creating it step by step . Component — Every Object in Your Game A unified Component class that combines position, size, color/image, velocity, physics, and collision detection. No separate "Sprite" and "Body" classes — one object does it all. const player = new Component ( 40 , 40 , " blue " , 100 , 100 ); Components support three modes: Rectangle — solid color shapes for rapid prototyping Image — loaded from spritesheets or single image files Text — the Tctxt subclass for text elements with backgrounds, padding, and alignment Key Features Dual-Canvas High-Performance Rendering Call display.perform() to activate dual-canvas mode. Static backgrounds and tilemaps are drawn once to an offscreen buffer, then composited as a single drawImage() call per frame. This dramatically reduces draw calls and improves frame rates for complex scenes. display . perform (); display . start ( 800 , 600 ); Tilemap Levels Define game worlds as 2D arrays and initialize the tilemap engine with one call. Supports dynamic tile placement during gameplay — great for destructible environments and breakable blocks. display . map = [ [ 1 , 1 , 1 , 1 , 1 ], [ 1 , 0 , 0 , 0 , 1 ], [ 1 , 0 , 9 , 0 , 1 ], [ 1 , 0 , 0 , 0 , 1 ], [ 1 , 1 , 1 , 1 , 1 ] ]; display . tileMap (); Sprite & AnimatedSprite Load horizontal spritesheets and define named animation clips (idle,

2026-06-11 原文 →
AI 资讯

🚀 New React Challenge: Build a Spreadsheet with Formula Evaluation

You've built todo apps, counters, and forms. But can you handle a grid of 50 cells that reference each other through formulas? This challenge pushes your state management skills into real spreadsheet territory — formula evaluation, two-way cell bindings, and an interface that juggles editing, selection, and keyboard shortcuts all at once. 🔥 Start the Challenge Now 🧩 Overview Build a spreadsheet with real-time formula evaluation. You'll wire up a 10-row × 5-column grid where cells support basic values and Excel-style formulas (like =A1+B2), column and row selection, and a formula bar that mirrors what you're typing. ✅ Requirements Render a spreadsheet with column headers A through E and rows 1 through 10 Each cell uses an <output> element for the computed value and an <input> overlaid for editing Click a cell to edit; press Enter or blur to commit the change Formulas starting with = must be evaluated: Arithmetic: =1+1 → 2 Cell references: A1= 5 and B1= =A1+3 → 8 Click a column header to select/deselect that column Click a row number to select/deselect that row Selecting a column deselects any row and vice-versa Backspace clears the selected column or row Click outside the table deselects everything A formula bar ( fx ) mirrors the editing cell's value Cells must start empty — no default values 💡 Notes Use useState for cells, selected column, and selected row. No need for useReducer here. Each cell uses two overlapping layers: a visible <output> for the computed value and an invisible <input> for the raw formula. Toggle with opacity-0 / opacity-100 so the input stays mounted. Evaluate formulas with eval : generate JS const declarations from all cell values, wrap them in an IIFE, and evaluate. Recompute every cell on any change — cells can reference each other. 🧪 Tests renders the app title renders the spreadsheet with column headers A-E and rows 1-10 renders cells with initial empty values allows editing a cell and displays the new computed value evaluates a simple fo

2026-06-11 原文 →
AI 资讯

I built an AI chat over my CV on a zero-pound inference budget

My CV is a PDF, and PDFs do not answer questions. So I built ask.hiten.dev : a streaming chat grounded in my actual career history, where a recruiter can ask "why should I hire you over another senior frontend engineer?" and get a real answer. The constraint that made it interesting: the total inference budget is zero. No OpenAI bill, no hosted vector DB, nothing. Here is what that actually took. Four free providers and a failover chain No single free tier is reliable enough to put in front of strangers. Groq's free tier caps at 100k tokens/day, and I hit that cap on day one. OpenRouter's free models come and go. Cerebras occasionally queues you out at busy times. The fix is boring and effective: an ordered provider chain, all OpenAI-compatible, walked per-request until one answers. Groq (llama-3.3-70b) -> OpenRouter (gpt-oss-120b:free) -> NVIDIA (llama-3.3-70b) -> Cerebras (gpt-oss-120b) Each provider is just a base URL, a key and a model name. The API route tries each in order; the first 2xx with a body wins, and the response streams straight through. The client gets an X-Provider header so I can see who served what in the logs. Two details that mattered: Empty env vars are not unset. Docker Compose's ${VAR:-} yields an empty string, which defeats ?? defaults in Node. Every key goes through a helper that coerces "" to undefined , otherwise a provider with no key "exists" and fails every request. You cannot cheaply probe a token-per-day cap. My health check hits GET /models on each provider (auth check, 60s cache). It tells you "key works, service up", not "you have tokens left". The failover chain covers the gap: a TPD-capped provider fails fast and the next one picks up. If every provider is down, the page itself says so. The health check runs server-side at render time, and instead of a broken chat you get a short maintenance note. Never ship a chat UI that can fail after the user has typed. Open-weight models do not follow formatting orders My site's voice avoi

2026-06-11 原文 →
AI 资讯

I built a a 3KB alternative to replace zxcvbn (389KB) without detection loss

zxcvbn is the most widely used password strength estimator with 1M npm downloads a week. It's also 389KB gzipped and hasn't shipped a commit since 2017. Most sign-up forms are hauling that around just to block password123 . Poor password UX is a real conversion problem. A strength meter that adds 389KB to your bundle delays page load — on mobile, measurably so. Users who hit a slow registration page don't wait. They leave. The irony is that most of that weight goes toward catching passwords nobody is actually using to register on your site. So I built passcore - 3.0KB gzipped and 98.4% detection rate on real breach data - same as zxcvbn, benchmarked against a deduped list of passwords pulled live from RockYou, Adobe, HIBP, and other major leak lists. zxcvbn takes ~9.7ms to load — it's parsing 389KB of dictionary into memory on every cold start. passcore loads in ~0.2ms. It evaluates a password in ~2,600 nanoseconds. For a registration form, it's effectively invisible — no jank, no layout shift, no contribution to your Core Web Vitals score. The strength meter shows up before the user finishes typing their first character. How it works: passcore runs five detection layers on every password: Dictionary - All entries sourced directly from breach data, not a generic word list Keyboard patterns - qwerty , asdf , 1234 , numpad walks Repeats - aaaa , ababab Sequences - abcdef , 123456 L33t speak - decodes p@ssw0rd → password , m0nk3y → monkey , then dictionary lookup The dictionary is small by design. Every entry was chosen because it appears in real breach data - not because it's a common English word. Password1! is caught not by a 40k word list but by stripping the suffix and checking if the core word is in the breach list. It is. The scoring model: passcore returns a score from 0 to 4 - same scale as zxcvbn. The detection layers run first. A dictionary match, keyboard pattern, repeat, sequence, or l33t substitution scores 0 or 1 immediately - no further calculation. If

2026-06-11 原文 →
AI 资讯

I automated my Gumroad product screenshots with Playwright

I automated my Gumroad product screenshots with Playwright I recently started packaging a few small frontend projects as digital products, and one surprisingly annoying part was preparing product screenshots. Manual screenshots quickly became messy: different browser sizes inconsistent cropping blurry images mobile screenshots were easy to get wrong Gumroad needed a square thumbnail every update meant taking screenshots again So I built a small local screenshot workflow with Next.js and Playwright. The workflow captures: desktop screenshots mobile screenshots square thumbnail images consistent PNG outputs route status checks basic console error reporting basic horizontal overflow checks The basic command flow is: npm run build npm run start npm run screenshots The script reads a simple config file, opens the configured local routes, captures each screenshot with consistent viewport settings, and exports the images into a predictable folder. For example: screenshots/gumroad/ landing.png dashboard.png template-preview.png mobile-preview.png thumbnail.png I found this especially useful when preparing Gumroad product galleries, because I could regenerate all product images after every UI change instead of taking screenshots manually. This is not a hosted screenshot service. It is just a local source-code workflow for people who want to generate product screenshots from their own Next.js pages. I packaged the workflow as a small Gumroad product here: https://remix410.gumroad.com/l/screenshot-automation-kit Curious how other developers handle product screenshots. Do you take them manually, use Playwright/Puppeteer, or use a design tool workflow?

2026-06-11 原文 →
AI 资讯

How to Use Primitive Types in TypeScript: string, number, and boolean

TLDR TypeScript has 7 primitive types: string , number , boolean , null , undefined , bigint , and symbol . You use them to tell TypeScript what kind of value a variable holds. You write them in lowercase. TypeScript can often figure out the type for you. But knowing how each one works is key to writing safe and clear code. What Are Primitive Types? Primitive types are the simplest building blocks in TypeScript. Every piece of data in your program starts with one. They hold a single value. They are not objects. You cannot add methods or properties to them directly. TypeScript has 7 primitive types in total: Type What It Holds string Text like names, messages, or IDs number Any number: integers, decimals, negatives boolean Only true or false null An intentional empty value undefined A value that was never assigned bigint Very large whole numbers symbol A unique identifier value This article covers all 7. You will use string , number , and boolean the most in everyday TypeScript code. How to Use the string Type A string holds text. Use it for names, messages, emails, URLs, and any other text data. Basic string annotation let firstName : string = " Alice " ; let greeting : string = " Hello, world! " ; let empty : string = "" ; Three ways to write strings TypeScript supports the same three string styles as JavaScript: let single : string = ' Single quotes work fine ' ; let double : string = " Double quotes work too " ; let template : string = `Template literals with ${ firstName } ` ; Template literals (backticks) let you insert values inside a string with ${} . TypeScript checks the types of those inserted values too. let age : number = 30 ; let message : string = `I am ${ age } years old` ; // TypeScript checks that 'age' is compatible here What TypeScript catches with strings let name : string = " Alice " ; name = 42 ; // Error: Type 'number' is not assignable to type 'string'. name = true ; // Error: Type 'boolean' is not assignable to type 'string'. Once a variable

2026-06-11 原文 →
AI 资讯

Diagnose Node.js CommonJS vs ESM Errors with Claude: A Copy-Paste Prompt Kit (ERR_REQUIRE_ESM, ERR_MODULE_NOT_FOUND)

By the end of this article you'll have a small Node.js script that pipes a module-resolution error ( ERR_REQUIRE_ESM , ERR_MODULE_NOT_FOUND , Cannot use import statement outside a module ) plus the surrounding config into Claude and gets back a specific fix — not a Stack Overflow lecture. You'll also have four hardened prompts you can paste straight into claude.ai, and a script that auto-detects whether your project is CJS or ESM before you even ask. Everything below runs on Node 18+. Why "just use ESM" doesn't fix the CommonJS/ESM ERR_REQUIRE_ESM error The reason these errors waste so much time is that the failing line is almost never where the problem lives. You see this: Error [ERR_REQUIRE_ESM]: require() of ES Module /app/node_modules/node-fetch/src/index.js from /app/server.js not supported. and your instinct is to edit server.js . But the actual decision is made by four things you can't see from the traceback: the "type" field in your package.json , the "type" (or "exports" map) in the dependency's package.json , your file extension ( .js vs .mjs vs .cjs ), and — if you use TypeScript — the module and moduleResolution fields in tsconfig.json . node-fetch v3 went ESM-only; that's why require('node-fetch') blows up while v2 was fine. The traceback tells you none of that. This is exactly the shape of problem an LLM is good at: lots of small context scattered across files, one correct answer, and a human who keeps pattern-matching on the wrong line. The trick is to feed Claude the config alongside the error, not the error alone. A prompt that only gets the stack trace will confidently tell you to "convert your project to ESM," which is often the most destructive possible fix. Prompt 1 for Claude: force a root-cause classification before any code The failure mode of asking an AI to "fix my module error" is that it jumps to a rewrite. The fix is to make it classify first. Paste this into claude.ai, filling the three blocks: You are debugging a Node.js module resolut

2026-06-11 原文 →
AI 资讯

From Keypoints to Measurements: Why Landmarks Alone Are Useless

Every hand-tracking demo shows you 21 dots. The interesting part is what nobody shows: turning dots into numbers someone can act on. Dots are a capability, not a product Run any modern hand-tracking model and you get 21 beautifully stable landmarks per hand at 30 FPS. Impressive — and by itself, worthless. No client has ever paid for dots. They pay for measurements : is this clearance compliant, is this part aligned, did this patient's range of motion improve. I learned this on utility infrastructure work, where the deliverable was never "we detected the wire" — it was the attachment height of that wire, and whether it violates clearance rules . Keypoints were step one of three. The demo: live metrics, not just a skeleton My portfolio's keypoint demo derives three measurements per hand, every frame: const wrist = lm [ 0 ]; const palm = distance ( wrist , lm [ 9 ]); // scale reference const pinch = distance ( lm [ 4 ], lm [ 8 ]) / palm ; // thumb tip ↔ index tip The crucial line is the scale reference . Pixel distances are meaningless — they change as you move toward the camera. Dividing by palm length (wrist to middle knuckle) gives a relative measurement that's stable under distance, and multiplying by the average adult palm length (~8.5 cm) converts it into an approximate real-world gap — the demo shows "≈ 3.2 cm" floating on the pinch line. In infrastructure work the same role is played by a known object dimension — a standard crossarm, a pole class height. Every measurement-from-pixels system needs its ruler. Finger counting is a geometric test (is each fingertip farther from the wrist than its middle joint?), and "hand openness" averages fingertip extension — three lines of geometry each, but they convert a model output into a readout a human understands instantly. Honest layering The landmarks come from MediaPipe's pretrained pipeline (palm detector → landmark regressor → gesture classifier, float16, WASM + GPU delegate) — Google's models, credited on the page

2026-06-11 原文 →
AI 资讯

I Put a Neural Network Inside My Portfolio — No TensorFlow, No Server, 145 KB

Training a network from scratch in raw NumPy, quantizing it to int8, and running it as ~80 lines of dependency-free JavaScript — with a parity test proving the browser matches Python to 1e-6. Why bother? MNIST is a solved problem Digit recognition is the "hello world" of ML — that's exactly why I used it. The model isn't the point. The point is everything around the model, which happens to be the part that matters in production work too: training without a framework, compressing for deployment, running inference in a constrained environment, and proving the deployed system matches the trained one. Training: just NumPy and math The network is a 784→128→64→10 MLP — hand-written forward pass, backpropagation, and Adam optimizer. No autograd, no framework: # backward pass, by hand dz3 = ( probs - y_batch ) / batch_size grads_w [ 2 ] = a2 . T @ dz3 da2 = dz3 @ weights [ 2 ]. T dz2 = da2 * ( z2 > 0 ) # ReLU mask grads_w [ 1 ] = a1 . T @ dz2 ... One trick that matters for a drawing demo specifically: shift augmentation . MNIST digits are centered; humans draw wherever they like. Training on randomly translated copies makes the model tolerant of sloppy placement. Combined with MNIST-style preprocessing at inference (crop to bounding box, scale into a 20×20 box, center by center-of-mass), real-world doodles classify reliably. Final test accuracy: 98.2% . Compression: int8 in 15 lines A float32 weight file would be ~430 KB. Symmetric int8 quantization cuts it ~4×: scale = np . abs ( w ). max () / 127.0 q = np . clip ( np . round ( w / scale ), - 127 , 127 ). astype ( np . int8 ) One scale factor per layer, weights stored as base64 in JSON: 145 KB total , and quantized test accuracy is identical to float — 98.2%. Inference: ~80 lines of plain JavaScript In the browser, the weights are dequantized once on load, and inference is three matrix-vector products with ReLU and a softmax. ~109K multiply-adds — about a microsecond-scale problem for any modern device. No TensorFlow.js (t

2026-06-11 原文 →
AI 资讯

Testing Camouflage Against the Real Adversary: an AI

Camouflage has always been graded by human eyes. But the thing hunting for you in 2026 is increasingly a detection model — so test against that. The premise Surveillance is automated now: drones, trail cameras, perimeter systems — most of what "sees" runs an object-detection network. Which makes traditional camouflage evaluation (a person squinting at a photo) the wrong test. The right test is adversarial: run the actual detector against your concealment and measure what it finds. That's the whole demo: upload a photo, and an object-detection model hunts for people in it — at four simulated distances — producing a detection-range profile and a stealth score. Simulating distance with pixels You can't move the camera after the photo is taken, but you can simulate the dominant factor in long-range detection: pixels on target . A person at 50 m simply occupies far fewer pixels than at 5 m. So each analysis run downscales the image progressively and re-runs detection: const DISTANCE_LEVELS = [ { label : ' Close (~5m) ' , scale : 1 }, { label : ' Mid (~15m) ' , scale : 0.45 }, { label : ' Far (~30m) ' , scale : 0.22 }, { label : ' Very far (~50m) ' , scale : 0.12 }, ]; for ( const level of DISTANCE_LEVELS ) { const scaled = drawScaled ( image , level . scale ); const detections = await model . detect ( scaled , 10 , 0.15 ); // best 'person' confidence at this simulated range } The output reads like a range card: detected at 5 m with 96% confidence, 41% at 15 m, invisible beyond 30 m. A stealth score aggregates it: how poorly did the adversary see you, averaged across ranges? Honest about the model The detector is COCO-SSD (a pretrained MobileNet-based model from the TensorFlow.js team) running entirely on-device — I didn't train it, and the demo says so on the page. The contribution here is the evaluation framework : using detectors as adversaries, simulating range, and turning subjective "good camo" into a measurable profile. The full version of this concept goes further

2026-06-11 原文 →
AI 资讯

Mirror Therapy Without the Mirror Box: Treating Phantom Limbs in a Browser Tab

A 1990s Nobel-adjacent therapy, a webcam, and 21 hand keypoints — recreating the mirror-box illusion for phantom limb pain, no hardware required. A therapy built on an illusion In the 1990s, neuroscientist V.S. Ramachandran discovered something remarkable: amputees suffering phantom limb pain often felt relief just by seeing their missing limb move again. His apparatus was almost comically simple — a box with a mirror. Put your intact hand in, look at its reflection where the missing hand would be, and move. The brain, watching the "missing" hand obey commands again, often dials the pain down. The limitation was never the science. It was the box: a physical apparatus, used in clinics, hard to scale, impossible to measure. Replacing glass with keypoints A webcam plus real-time hand tracking can produce the same illusion with better properties: webcam frame → hand landmark model (21 keypoints, on-device) → reflect: phantom[i] = { x: 1 − x, y, z } → render real hand (solid) + phantom twin (ghost) on canvas The reflection is one line of math. Everything around it is what makes the illusion land: const phantom = real . map ( p => ({ x : 1 - p . x , y : p . y , z : p . z })); The visual treatment matters more than I expected. The phantom hand is rendered as a ghostly cyan skeleton with a translucent palm fill, a "breathing" glow that pulses on a ~3 second cycle, and a fading afterimage trail of its last few frames — it reads as present but ethereal , which is exactly the perceptual story mirror therapy needs to tell. A dashed mirror plane down the center of the frame makes the reflection relationship legible at a glance. The engineering details that matter Tracking : MediaPipe HandLandmarker (Google's pretrained model — credit where due), running via WebAssembly with GPU delegate. ~30 FPS on a laptop. Privacy by architecture : every frame is processed on-device. For a medical-adjacent application, "video never leaves your browser" isn't a feature, it's a requirement. Lazy

2026-06-11 原文 →
AI 资讯

Debugging the Google Maps Duplicate Loading Bug in React

Originally published on clintech.me If you've integrated Google Maps into a React app and seen Autocomplete randomly stop working, Directions silently fail, or the API throw google is not defined on second render — you've hit the duplicate loading bug. Here's exactly what caused it in my case and how I fixed it. The setup that broke things While building delivery address flows at POLOM — a production e-commerce platform — I integrated Google Places Autocomplete across 20+ screens. I had the Maps JavaScript API loading in two places: A provider.tsx for global script loading across the app A useLoadGoogleMaps hook inside a shared component This caused race conditions. The Autocomplete and Directions APIs were initialising before the script fully resolved in some renders, silently failing in others. The failure wasn't consistent, which made it harder to catch. The fix Step 1 — Remove the global load Delete the script tag or next/script call in provider.tsx . There should be exactly one place the Maps API loads. Step 2 — Centralise in a hook Move all loading logic into a single useLoadGoogleMaps hook using dynamic loading. If you're on Next.js, next/script with strategy="afterInteractive" inside the hook is the right approach. Step 3 — Guard before initialising if ( ! window . google ?. maps ) return ; Check that the API is fully available before attempting to attach Autocomplete or Directions . Don't assume the script load event means every namespace is ready. Step 4 — Scope your ref correctly Bind the autocomplete instance to inputRef.current explicitly. If the component remounts, re-initialise the binding — don't assume the previous instance is still attached. The result One load, one source of truth, no race conditions. Autocomplete and Directions worked consistently across all 20+ screens without reinitialising on every render. Security — the step most developers skip Restrict your API key at the Google Cloud Console level: HTTP referrers: whitelist your domain onl

2026-06-11 原文 →
AI 资讯

Developer Tools That Actually Save You Time in 2026

As developers, we often lose hours to repetitive tasks - formatting data, debugging auth tokens, or wrestling with encoding issues. The right set of utility tools can cut that overhead significantly. Here is a curated breakdown of the categories and tools worth keeping in your daily workflow in 2026. JSON and Data Formatting Tools Working with raw, minified API responses is a real productivity killer. A browser-based JSON formatter with real-time validation and proper indentation helps you parse and debug responses in seconds - without sending your data to third-party servers. Similarly, XML is far from dead; SOAP APIs and enterprise integrations still rely on it, so a solid XML formatter with XPath support is worth having around. On the frontend, a CSS minifier that handles dead code removal and selector optimization can meaningfully reduce your page load times. Data Conversion Utilities Modern stacks often have to bridge the gap between formats. A JSON-to-XML converter is critical when integrating with legacy systems, but look for one that handles arrays and special characters correctly. CSV-to-JSON converters help structure flat export data from spreadsheets or databases into something your application can consume. And if your deployment pipeline involves YAML config files, a formatter that catches indentation errors before they break your CI/CD run is essential. Encoding and Security Helpers Base64 encoding comes up constantly - from embedding images in CSS to building HTTP Basic Auth headers. A reliable encoder/decoder handles all these cases without fuss. URL encoding matters too; unencoded special characters in query strings cause subtle API bugs that are surprisingly hard to track down. For authentication debugging, a JWT decoder lets you inspect token payloads without needing to verify the signature - ideal for local development and troubleshooting. Code Generation and Color Utilities Distributed systems need collision-resistant unique IDs - a UUID generato

2026-06-11 原文 →