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

标签:#web

找到 2947 篇相关文章

AI 资讯

Enterprise MCP Gateway Solutions: Providers, Alternatives, and Cost 💎

Your company uses six different AI providers. OpenAI for ChatGPT, Anthropic for Claude and Groq for speed critical inference. Each one has different API formats. Different authentication models. Different rate limits and costs. Different failure modes. Your application code has to know about all of them. Your security team has to audit requests across all of them. Your finance team has to track costs across all of them. Your compliance team has to ensure governance across all of them. Bifrost Gateway solves this by doing what HTTP gateways have done for decades: centralizing control . But for AI. 👀 What is an MCP gateway? Model Context Protocol (MCP) is an open standard that lets AI models discover and execute external tools at runtime filesystems, web search, databases, ticketing systems, and custom business logic instead of being limited to text generation. An MCP gateway sits between your applications (or external MCP clients like Claude Desktop and Cursor) and the upstream MCP servers. Instead of each client maintaining its own connections, credentials, and tool lists, the gateway: Aggregates tools from multiple MCP servers into one registry Applies governance : authentication, tool filtering, budgets, and rate limits Exposes a single endpoint that external MCP clients can connect to In Bifrost, this pattern is implemented in two complementary roles: Role What it does MCP Client Connects to external MCP servers via STDIO, HTTP, or SSE MCP Server (Gateway) Exposes aggregated tools at /mcp for Claude Desktop, Cursor, and other MCP-compatible clients Bifrost is both an AI gateway (routing LLM traffic to 20+ providers) and an MCP gateway (connecting to and exposing tool servers). The open-source gateway covers virtual keys, budgets, rate limits, routing, and MCP tool filtering. Bifrost Enterprise adds RBAC, SSO, audit logs, MCP Tool Groups, guardrails, clustering, and in-VPC deployment options. ⚙️ How does an MCP gateway work? Connection layer Each upstream MCP serv

2026-08-20 原文 →
AI 资讯

How to pull every open job from Greenhouse, Lever, Ashby and SmartRecruiters with public APIs (and monitor changes)

Job postings are one of the most underrated public data sources on the internet. Recruiters use them to spot placement opportunities, B2B teams read them as buying signals (a new Head of Data means data-tooling budget), and job seekers want to apply on day one — not when a posting finally reaches the aggregators. The usual instinct is to scrape career pages. Don't. Most tech companies host their careers page on one of a handful of Applicant Tracking Systems (ATS), and the four biggest ones — Greenhouse, Lever, Ashby and SmartRecruiters — all expose public, documented JSON APIs . No auth. No proxies. No brittle HTML selectors. The career page itself loads the same JSON you're about to fetch. In this tutorial we'll build a single-file Python tool that: fetches every open job for a company from any of the four ATS, auto-detects which ATS a company uses, normalizes everything into one clean schema, monitors changes — run it on a schedule and get only new / removed / changed postings. The four endpoints ATS Endpoint Greenhouse GET https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true Lever GET https://api.lever.co/v0/postings/{slug}?mode=json Ashby GET https://api.ashbyhq.com/posting-api/job-board/{slug} SmartRecruiters GET https://api.smartrecruiters.com/v1/companies/{slug}/postings (paginated) The {slug} is the company identifier you see in career-page URLs: boards.greenhouse.io/stripe → stripe , jobs.lever.co/spotify → spotify , jobs.ashbyhq.com/linear → linear , careers.smartrecruiters.com/Visa → Visa . Try one right now — no API key needed: curl -s "https://api.ashbyhq.com/posting-api/job-board/linear" | head -c 400 Step 1 — fetchers, one per ATS Each API returns a different shape, so we normalize as we fetch. Here are all four (Python 3, only requests ): import requests UA = { " User-Agent " : " ats-jobs-tutorial/1.0 " } def get_json ( url , params = None ): r = requests . get ( url , params = params , headers = UA , timeout = 30 ) r . raise_for_statu

2026-08-20 原文 →
AI 资讯

🎬 Reel Quick now has a live animated demo in the GitHub README

The demo gives a quick look at the workflow for creating short-form videos with trimming, stitching, text overlays, voice tools, themes, and transitions. Built with FastAPI, Next.js, Redis/ARQ, and FFmpeg. Repo: https://github.com/ronin1770/reel-quick OpenSource #Python #FastAPI #NextJS #FFmpeg #VideoAutomation #DeveloperTools #AI

2026-08-20 原文 →
AI 资讯

The Midnight wallet SDK changed its npm scope. Here is what to update.

If you installed the Midnight wallet SDK a while back and pinned the package names, your imports are now pointing at a deprecated scope. Nothing is broken yet. But the packages you depend on moved, and the old names are living on borrowed time. Here is what changed, why it matters, and the one gotcha that trips people up. The short version The wallet SDK packages moved from the @midnight-ntwrk scope (with a dash) to @midnightntwrk (no dash). @midnight-ntwrk/wallet-sdk-facade -> @midnightntwrk/wallet-sdk-facade The old dashed packages still install, so your build keeps working for now. They are published as a transitional alias. But the dashed scope is deprecated, and the newest releases only show up on the new no-dash scope. So you want to move over. There is one exception. @midnight-ntwrk/ledger-v8 stays on the dashed scope. Do not rename that one. More on that below. What actually changed Straight from the wallet SDK v1.2.0 release notes: the npm scope has changed from @midnight-ntwrk to @midnightntwrk (no dash). New installs should depend on @midnightntwrk/* . The old @midnight-ntwrk/* packages continue to be published as a transitional alias during the migration window, so existing consumers keep working, but the dashed scope is deprecated. So both scopes exist on npm right now. That is why nothing breaks. But they are not equal. The no-dash scope is where the active releases land, and the dashed scope lags behind. You can see it yourself. Here are the current latest versions, dashed vs no-dash: Package Dashed (old) No-dash (new) wallet-sdk-facade 4.0.1 4.1.0 wallet-sdk-hd 3.0.2 3.0.3 wallet-sdk-shielded 3.0.1 3.0.2 wallet-sdk-dust-wallet 4.1.0 4.2.0 If you stay on the dashed names, you quietly get the older packages. The version fixes and new features go to the no-dash scope first. The gotcha: ledger-v8 does not move This is the part that catches people. When you do a find and replace across your project, it is tempting to swap every @midnight-ntwrk for @midnig

2026-08-20 原文 →
AI 资讯

How to Create Your Own Claude Code Skill With SKILL.md

If you use Claude Code for frontend development, you may have noticed something. Claude can write code very fast. But sometimes the UI it creates looks too similar to other AI-generated websites. You get the same rounded cards, large headings, soft shadows, gradients, and simple layouts. The code works. But the design does not always feel like your own. Hi everyone, I am Henry. In this article, I want to show you a simple way to fix that. We are going to create our own Claude Code Skill using a SKILL.md file. You do not need to build a complicated tool. You just need a clear set of instructions that Claude can follow when working on your frontend. What Is a Claude Code Skill? A Claude Code Skill is a reusable set of instructions for a specific type of work. For example, you can create a skill for: Frontend design Testing Documentation Code review Database work DevOps UI accessibility For this tutorial, we will create a frontend design skill . Our goal is simple: Help Claude create clean frontend UI without falling back to the same generic design patterns. Instead of writing the same design rules in every prompt, we can keep them inside a skill. Step 1: Create the Skill Folder Open your project in the terminal. Create a .claude folder if you do not already have one. Then create a skills folder: mkdir -p .claude/skills/frontend-design Now create the skill file: touch .claude/skills/frontend-design/SKILL.md Your project should now look something like this: your-project/ ├── .claude/ │ └── skills/ │ └── frontend-design/ │ └── SKILL.md ├── src/ ├── package.json └── README.md The important file here is: SKILL.md This is where we will put our instructions. Step 2: Write Your SKILL.md Open the file: code .claude/skills/frontend-design/SKILL.md Now add the following: --- name : frontend-design description : Build clean, responsive frontend UI with simple and consistent design rules. --- # Frontend Design Rules Before writing UI code: 1. Understand the purpose of the page. 2.

2026-08-20 原文 →
AI 资讯

Connect a Carrd Landing Page to Payhip Without Building a Backend

Affiliate disclosure: I’m an independent Payhip Partner. The optional signup link at the end is my partner link; I may receive a commission from Payhip if a referred seller generates eligible revenue. I am not a Payhip employee or official representative. A creator selling one template or downloadable guide does not need to write a payment backend. The safer architecture is usually: Carrd or static page ↓ Payhip product page or direct checkout ↓ Hosted payment and product delivery Your public page explains the offer. The hosted commerce platform owns the payment flow. No card data, secret keys, or payment logic belongs in Carrd. This tutorial shows two link-based integrations and one optional embed route. Before you start You need: A published product in Payhip Its public product URL A button on your Carrd or static page A clear product description, support contact, and terms In Payhip, the product URL is available from the product’s Share / Embed controls. A typical product URL has this shape: https://payhip.com/b/PRODUCT_KEY Use your real product key in every example below. Option 1: Send visitors to the product page This is the safest default when the buyer still needs details before purchasing. In Carrd: Select the call-to-action button. Set its URL to your full Payhip product URL. Use a descriptive label such as View template details or See what’s included . Preview the page on desktop and mobile. On a conventional static site, the equivalent HTML is just an anchor: <a class= "product-button" href= "https://payhip.com/b/PRODUCT_KEY" > View product details </a> No JavaScript is required. Use this route when the Payhip product page contains important previews, license terms, compatibility notes, or variations that do not fit on your landing page. Option 2: Link directly to checkout If your landing page already gives the buyer everything needed to decide, a direct checkout removes an intermediate page. Payhip documents this URL format: https://payhip.com/buy?link=

2026-08-20 原文 →
开发者

Making a screenshot PDF searchable — no OCR, because we rendered the page

We archive whole web pages as PDFs. Under the hood each page is a full-height screenshot dropped onto a PDF page — which looks perfect and is completely useless the moment you want to use the text. Ctrl+F finds nothing. You can't copy a sentence. A screen reader opens the document and sees… an empty page with one big image. The fix is the same trick a "searchable scan" uses: draw the real text invisibly , on top of the image, at the exact coordinates where each word appears. The difference is that a scanner needs OCR to guess the text — we rendered the page ourselves , so we already have the ground truth. No OCR, no guessing. Here's how we built it with pdf-lib and @pdf-lib/fontkit , and the one part that turned out to be genuinely hard. The shape of it While the page is still open in the headless browser, ask the DOM where every word is. Assemble the PDF: embed the screenshot as the page background. For each word, drawText it at its coordinates with opacity: 0 . Steps 1 and 3 are easy. The trap is in which words you're allowed to draw. Step 1 — ask the browser where the words are Running inside the page (Puppeteer's page.evaluate ), we walk every text node and measure each word with a Range : const walker = document . createTreeWalker ( document . body , NodeFilter . SHOW_TEXT ); // ...for each word in each text node: const range = document . createRange (); range . setStart ( node , start ); range . setEnd ( node , end ); const rects = range . getClientRects (); if ( ! rects . length ) continue ; // display:none or empty line box const b = rects [ 0 ]; // first rect = where the word starts out . push ({ t : word , x : b . left + window . scrollX , // document coordinates, not viewport y : b . top + window . scrollY , w : b . width , h : b . height , fs : parseFloat ( getComputedStyle ( el ). fontSize ) || 12 , }); getClientRects() gives viewport coordinates, so we add scrollX/scrollY to get document coordinates — the ones that line up with a full-page screenshot.

2026-08-20 原文 →
AI 资讯

WebMCP Agentic Web: Debugging 2‑Second Latency Spikes

webmcp agentic web: Why Backend Engineers Must Rethink Their Architecture Quick Answer webmcp agentic web: Agentic web workloads over MCP require stateless gateways, distributed context stores, prompt caching, and fine‑grained telemetry to keep latency below 350 ms and cost under control. Latency and State in Multi‑Agent LLMs When a Multi‑Agent System talks to an LLM over the Model Context Protocol (MCP) , the assumptions that hold for CRUD REST APIs break apart. A 200‑ms timeout that covers a simple GET request now collapses into a 2‑second latency spike because each tool call injects a new sub‑prompt, inflates the token budget, and forces the backend to stitch together dozens of partial contexts. In the field, the LLM behaves like a stateful, high‑throughput service that must be orchestrated, not a stateless function. Real‑World Example Consider a U.S. e‑commerce platform that needs to serve 12 k concurrent shopping sessions. Each session spawns up to five agents (pricing, inventory, recommendation, fraud, checkout). The platform’s existing micro‑service stack was built for single‑shot CRUD calls; when the agentic layer was added, the following issues surfaced: Context drift: stale prompts silently degraded recommendation quality. Token explosion: every tool call added 200–300 tokens, pushing the total payload past 8 k tokens. Throughput hit: the MCP service was throttled by Azure OpenAI’s per‑deployment request rate limits. After re‑architecting to a stateless MCP gateway backed by a distributed context store, the platform maintained 99th‑percentile latency under 350 ms even during a Black Friday surge. Trade‑Offs Aspect Option A Option B When to choose Context Storage Redis Cluster (in‑memory, low latency) Cosmos DB (strong consistency, global replication) Redis for ultra‑low latency, Cosmos for compliance or multi‑region writes Prompt Caching Enable KV‑cache on Azure OpenAI Re‑send system prompt on every request Enable when prompt size >20% of total token budge

2026-08-20 原文 →
AI 资讯

How to Convert PDF to Word in the Browser with Vue 3 and pdf-lib

Converting PDF to Word seems straightforward, but the reality is more complex. PDF stores text as character coordinates, while Word uses structured paragraphs. Bridging this gap requires careful text extraction and order reconstruction. Here's how to build a browser-based PDF to Word converter with Vue 3 and pdf-lib . The challenge: PDF vs Word PDF is a presentation format — text is positioned precisely on the page. Word is an editing format — text flows in paragraphs with styles. Converting between them means: Extracting text from PDF coordinates Reconstructing reading order Generating structured DOCX output The stack Vue 3 with Composition API pdf-lib for PDF parsing docx for Word document generation Vite for bundling The core implementation < script setup lang= "ts" > import { ref } from ' vue ' import { PDFDocument } from ' pdf-lib ' import { Document , Paragraph , TextRun } from ' docx ' const file = ref < File | null > ( null ) const processing = ref ( false ) const result = ref < Blob | null > ( null ) async function convertPdfToWord () { if ( ! file . value ) return processing . value = true const arrayBuffer = await file . value . arrayBuffer () const pdf = await PDFDocument . load ( arrayBuffer ) const pages = pdf . getPages () const allChunks : TextChunk [] = [] for ( const page of pages ) { const textContent = await page . getTextContent () for ( const item of textContent . items ) { allChunks . push ({ text : item . text , x : item . transform [ 4 ], y : item . transform [ 5 ], size : item . size }) } } // Sort by reading order const sorted = sortByReadingOrder ( allChunks ) // Generate DOCX const doc = new Document ({ sections : [{ properties : {}, children : sorted . map ( chunk => new Paragraph ({ children : [ new TextRun ( chunk . text )] }) ) }] }) const blob = await doc . pack () result . value = blob processing . value = false } interface TextChunk { text : string x : number y : number size : number } function sortByReadingOrder ( chunks : TextCh

2026-08-20 原文 →
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

2026-08-20 原文 →
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

2026-08-20 原文 →
开发者

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

2026-08-20 原文 →
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

2026-08-20 原文 →
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

2026-08-20 原文 →
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

2026-08-20 原文 →
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

2026-08-20 原文 →
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

2026-08-20 原文 →
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

2026-08-20 原文 →
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

2026-08-20 原文 →
AI 资讯

Three of the First Four Alerts Were the Question's Fault

Last week I turned my data audit into a build step : a check that runs before anything else and fails the build when the database and any static copy of my travel site's legal-status data disagree. It ended the era of the site contradicting itself. It did nothing about the site agreeing with itself on something false. That's not a hypothetical. The most expensive error the whole project found was a country whose law changed in January while every copy on my site — database, data files, search index — kept saying the old thing in perfect unison. Internal consistency was the camouflage . No diff between my own sources could ever have caught it, because every internal source was equally behind the world. A build gate proves agreement. Agreement is not truth. Something has to look outside. You can't diff against the world, but you can sample it The naive version of "look outside" is another audit — a human session checking primary sources jurisdiction by jurisdiction. I've done three of those now, and I know exactly what they're worth: they're correct the day they ship and they decay from that morning on. Laws don't change on my audit schedule. So the outside check became what the inside check became: a scheduled job. Once a week, a script asks a web-connected model — one that searches and cites, not one answering from training memory — for the current legal status of about fourteen jurisdictions, and compares each answer to the corresponding database row. Fourteen, not all 271, because the selection is doing the real work: A hot list is checked every single run: the highest-traffic pages plus the jurisdictions with active legislative motion — the places where being a month stale costs the most. Everything else sits on a rotating cursor : eight per run, round-robin, so every row on the site gets sampled roughly twice a year without any run costing more than a few cents. The whole thing runs on about seven cents a week. Two rules were non-negotiable, both inherited from

2026-08-20 原文 →