AI 资讯
Why Software Can't Tell You It's Wrong
Software architecture debates have a problem that most other engineering disciplines don't: the alternative was never built. When a bridge fails, the failure is physical, attributable, and measurable against every other bridge that didn't. The engineering decisions that caused it can be isolated, traced, and corrected — not just in theory, but in the next bridge, because the material itself produces feedback that no amount of professional opinion can override. Steel deflects. Concrete cracks. Physics doesn't care what the architect believed. Software produces no equivalent feedback. A system built around the wrong abstractions compiles, runs, ships, and passes its tests just as readily as one built around the right ones. A bug introduced by a misaligned domain model looks identical, from the outside, to a bug introduced by a typo. A feature that took three times longer than it should have, because the structure made it harder than the business logic warranted, produces no artifact that distinguishes it from a feature that was simply difficult. The cost is real. The cause is invisible. This is the unfalsifiability problem, and it runs deeper than "we can't measure everything." It means that when a system becomes expensive to change, the diagnosis almost always lands on the wrong variable. The domain is complex. The requirements changed. The previous team was careless. Almost never: the structure was wrong, and the structure was wrong because nobody ever built the other version of it to compare against. That version doesn't exist, it never will, and every architectural argument in the industry is conducted in its absence. This would be a purely philosophical problem if there were nothing to do about it. There is something to do about it — but it requires accepting that the standard metric for software quality, whether it works, is measuring the wrong thing entirely. The Metric That Hides the Problem The natural substitute for "is this good engineering" is "does it wor
AI 资讯
I added nested CSV to JSON support to a free browser-based converter
I built JSON Utility Kit as a small browser-based toolkit for everyday JSON tasks. The CSV to JSON converter recently got an update for nested JSON structures. For example, headers like user.name, user.email, order.id can be converted into nested objects instead of flat keys. What it supports: CSV to JSON conversion Nested object output from dot notation headers Browser-side processing No signup JSON formatting and validation tools nearby Tool: https://jsonutilitykit.com/tools/csv-to-json/ GitHub: https://github.com/kejie1/json_utility_kit
开发者
React Doctor marcó 1,249 problemas en una SPA de React. Cinco valían la pena arreglar
React Doctor es un linter de cero instalación que escanea un codebase de React buscando problemas de correctitud, seguridad, accesibilidad, rendimiento y mantenibilidad, luego los rankea y te entrega una lista de arreglos con forma de agente. Este post es un reporte de campo: lo corrí sobre una SPA real de React 18 + Vite + TypeScript (~50 rutas, TanStack Query, react-hook-form) y separé lo que de verdad me atrapó. El resultado honesto: 1,249 hallazgos, cinco que valía la pena arreglar, incluyendo dos bugs de seguridad reales. Los otros 1,244 fueron una mezcla de ruido, decisiones de criterio, y falsos positivos. TL;DR Categoría Reportados Vale la pena actuar Por qué la brecha Seguridad 18 warnings 2 13 eran sinks saneados en el servidor (falsos positivos); 1 mina de código muerto + 1 XSS real fueron el oro Bugs 24 errores, 487 warnings 2 1 fuga de timer, 1 key/spread; ~27 exhaustive-deps piden criterio humano Accesibilidad 434 warnings, 1 error 1 el 1 error ( aria-selected faltante) era real; los 434 son ruido de <Label> / <button type> Rendimiento 110 warnings 0 nada caliente; candidatos pero sin impacto medido Mantenibilidad 176 warnings 0 opiniones de estilo tipo "componente grande" Total 1,249 5 señal-a-ruido ≈ 1 en 250 El puntaje que imprimió: 39 / 100 . Dos cosas que ese encuadre esconde: los cinco que sacó a la superficie eran de alto valor (una fuga de token en localStorage y un sink de XSS almacenado), y el conteo no es determinista : una segunda corrida del mismo commit reportó 1,254, no 1,249. El veredicto en una línea: excelente generador de hipótesis, pésima compuerta. Úsalo para encontrar candidatos, verifica cada uno contra el código, y nunca conectes el conteo al CI. Qué es React Doctor Un solo binario que corres por npx / pnpm dlx , sin agregar dependencia a tu proyecto, sin archivo de configuración obligatorio. Parsea tu src/ , hace match contra un conjunto de reglas (sus familias: Seguridad, Bugs, Accesibilidad, Rendimiento, Mantenibilidad), e im
产品设计
My Next.js 16 Optimistic UI Looked Perfect. Then Someone Clicked It Five Times Fast
Everything worked. I'd wired up useOptimistic on a task list, the checkbox flipped the instant you...
AI 资讯
Aesecnryption demo site
I rebuilt aesencryption.net so text AES (128/192/256) runs fully in the browser - the key and plaintext never leave the page. The hard part is staying byte-compatible with common server-side AES libraries (mode, IV, padding, base64 output), so I ship copy-paste equivalents in PHP, Java, Python, Go, Rust, Kotlin and JS. Live tool (mine, free): https://aesencryption.net - feedback on the crypto choices welcome. My own site.
开源项目
🔥 Piebald-AI / claude-code-system-prompts - All parts of Claude Code's system prompt, 27 builtin tool de
GitHub热门项目 | All parts of Claude Code's system prompt, 27 builtin tool descriptions, sub agent prompts (Plan/Explore/Task), utility prompts (CLAUDE.md, compact, statusline, magic docs, WebFetch, Bash cmd, security review, agent creation). Updated for each Claude Code version. | Stars: 11,638 | 28 stars today | 语言: JavaScript
开源项目
🔥 jnMetaCode / superpowers-zh - 🦸 AI 编程超能力 · 中文增强版 — superpowers(116k+ ⭐)完整汉化 + 6 个中国原创 skil
GitHub热门项目 | 🦸 AI 编程超能力 · 中文增强版 — superpowers(116k+ ⭐)完整汉化 + 6 个中国原创 skills,让 Claude Code / Copilot CLI / Hermes Agent / Cursor / Windsurf / Kiro / Gemini CLI 等 16 款 AI 编程工具真正会干活 | Stars: 6,504 | 66 stars today | 语言: JavaScript
AI 资讯
15 browser-based dev tools I use daily — no login, nothing uploaded
Like most developers, I have a handful of small utilities I reach for every day — formatting JSON, decoding a JWT, generating a UUID, testing a regex. For years I just googled "json formatter" and pasted my data into whatever site came up first. Then one day I caught myself pasting a production JWT into a random online parser that POSTs everything to its server. That felt bad. So I built my own toolbox that never sends data anywhere. It's called WeTool — free, no login, and every tool runs 100% in your browser . You can open DevTools → Network and confirm there are zero requests while you use it. Here are the 15 I use most: Everyday JSON formatter / validator URL encode / decode Base64 encode / decode Timestamp ↔ date converter Security & encoding Hash calculator (MD5 / SHA) JWT parser UUID generator QR code generator Text & format Regex tester Text diff Markdown preview SQL formatter Debugging Cron expression parser Color converter User-agent parser Two things that matter to me and might to you: Nothing is uploaded. No backend, no login, no tracking of what you type. Local-only. 15 languages. Most tool boxes are English-only; this one isn't. It's free and I'm actively adding tools — if something you use daily is missing, tell me in the comments and I'll add it. 👉 wetool.site
开发者
State colocation is not a preference, it is an architecture
The first question I ask when reviewing a frontend architecture is: where does the state live relative to where it is used? In most codebases I have reviewed, the answer is "in a global store, regardless of scope." This is the wrong default. The rule State should live as close to its consumers as possible. If only one component needs it, it is component state. If a subtree needs it, it is a context or service scoped to that subtree. Global state is for truly global concerns: authentication, locale, theme.
AI 资讯
AbortController: The Async Cleanup Pattern You Keep Skipping
Most async code in frontend apps has a hidden bug: it doesn't stop when it should. A user navigates away mid-request. A component unmounts. A newer search query supersedes the previous one. The old network call keeps running, eventually resolves, and tries to update state that no longer exists. In React, that's the infamous warning: "Can't perform a React state update on an unmounted component." In vanilla JS, it silently delivers stale data. AbortController is the browser's built-in solution. It's been in every major browser since 2018 — old enough that there's no excuse not to use it. But most tutorials skip it, most codebases use it inconsistently, and most devs reach for it only after they've debugged a flicker one too many times. Here's the pattern, end to end. The race condition you already have function SearchResults ({ query }: { query : string }) { const [ results , setResults ] = useState < Result [] > ([]); useEffect (() => { fetch ( `/api/search?q= ${ query } ` ) . then ( r => r . json ()) . then ( data => setResults ( data )); // runs even if query changed }, [ query ]); return < ul > { results . map ( r => < li key = { r . id } > { r . name } </ li >) } </ ul >; } When the user types "re" and then "rea" before the first request finishes, two fetches are in flight simultaneously. The request for "re" might complete after the request for "rea" — and when it does, setResults silently overwrites the correct result with the stale one. The component shows the wrong data. No error, no warning, no clue. This is a race condition, not a hypothetical. It happens on slow networks, during fast typing, on underpowered devices, and in staging environments right before a demo. AbortController: the three-line fix An AbortController is a pair: a controller object and a signal. You pass the signal into any abort-aware API; you call abort() to cancel it. useEffect (() => { const controller = new AbortController (); fetch ( `/api/search?q= ${ query } ` , { signal : control
开发者
Node.js 26: Temporal API Enabled by Default, V8 14.6, and a Round of Deprecations
Node.js 26 has been released, featuring the Temporal API enabled by default, an updated V8 engine to version 14.6, and the Undici HTTP client upgraded to 8.0. The release also removes deprecated legacy APIs. Developers should note migration points related to NODE_MODULE_VERSION changes. Node.js 26 is current for six months before entering long-term support. By Daniel Curtis
AI 资讯
Java News Roundup: Strict Field Initialization, GlassFish, GraalVM, JReleaser, RefactorFirst
This week's Java roundup for June 29th, 2026, features news highlighting: a new JEP candidate, Strict Field Initialization; point releases of GraalVM, JReleaser, RefactorFirst and Java Operator SDK; maintenance releases of GlassFish and Micronaut; the second milestone release of Grails 8.0; and the beta release of Open Liberty 26.0.0.7. By Michael Redlich
AI 资讯
Hard Object References: Stable Object References for Mutable Application State
In JavaScript and TypeScript, object references are often treated as disposable. An object is created, assigned to a variable, passed around, replaced, copied, spread, cloned, and eventually discarded. That is normal language behavior, but in larger mutable systems it creates a specific class of bugs: stale aliases. A stale alias appears when one part of the program still holds a reference to an old object while another part has already replaced that object with a new one. The old reference is still valid JavaScript, but it no longer points to current data. Hard Object References is a discipline for avoiding that class of bugs. The idea is simple: Object and array references should be stable. Do not replace them as a normal update mechanism. Copy data into existing objects instead. This rule is useful for application state, but it is not limited to global stores. It applies to ordinary variables, local component state, nested fields, arrays, drafts, snapshots, runtime models, and temporary objects. The broader principle is: Replace primitive values. Do not replace object and array references. The First Rule: const for Objects and Arrays The first level is variable bindings. If a variable holds an object or array, it should normally be declared with const : const user = { /* ... */ }; const items = [ /* ... */ ]; not: let user = { /* ... */ }; let items = [ /* ... */ ]; The point is not that the object becomes immutable. It does not. This is still possible: user . name = ' Alex ' ; items . push ( nextItem ); The point is that the variable should not be rebound to a different object: user = nextUser ; items = nextItems ; That replacement changes which object the variable points to. Any other code that still holds the old reference now points to obsolete data. So the first rule is: Use const for object and array references. Mutate or copy data into the object. Do not rebind the reference. This rule also applies to temporary objects. A temporary object may be short-live
AI 资讯
Decoupling Async State from UI Lifecycles
In my previous articles, I’ve consistently emphasized a core architectural principle: once the render layer no longer dictates the entire data flow, the boundaries between State, Derived State, and Effects become critical. When we fall into the habit of stuffing every UI-affecting variable into generic "state," the system quickly loses its semantic structure. In modern frontend applications, this architectural gap becomes most glaring when dealing with asynchronous work. Async data is never merely "a value that will appear in the future." It carries complex semantics regarding its source, temporal validity, cancellation, error recovery, and invalidation. If these semantics aren't modeled explicitly, they inevitably get pushed down into the UI framework’s lifecycle—indirectly patched together through component mounts, effect dependencies, and callback guards. This brings us to the core question of this article: What does a system lose when the correctness of async work is forced to depend on the UI lifecycle? We are all incredibly familiar with this pattern: const data = await fetchSomething () setState ( data ) Or, using a standard UI framework hook: useEffect (() => { let cancelled = false fetchSomething (). then ( result => { if ( ! cancelled ) { setData ( result ) } }) return () => { cancelled = true } }, []) There is nothing inherently wrong with this code for simple use cases. It’s intuitive and perfectly aligns with how Promises are designed to work: trigger the operation, wait for the resolution, and write the result back into state. However, this mental model has a subtle downside. It encourages us to think of async work as simply calling setState after a Promise resolves. That may hold up for simple screens, but as an application grows, the model starts to expose structural problems. Promise Only Describes Completion, Not Ownership A Promise solves a very specific problem: A piece of work will complete in the future, and it will either succeed or fail. It c
开源项目
Ship multi-language audio in HLS: author the manifest, wire the hls.js switcher
📦 Code: github.com/USER/hls-multi-audio - replace before publishing TL;DR We'll add a working language picker to an HLS player. The hard part isn't the dropdown, it's the manifest. We'll author alternate audio with EXT-X-MEDIA audio groups, package it correctly, debug the classic "zero audio tracks" bug, and wire a switcher on hls.js v1.7 . Adaptive video, captions, the whole pipeline already works. Now someone wants an English/Spanish audio toggle. In HLS, "which audio can the viewer pick" is decided at packaging time and written into the master playlist. The player just displays it. Let's build it in that order. 1. Understand the structure (audio groups) HLS decouples video variants from audio renditions: Each audio rendition is an #EXT-X-MEDIA:TYPE=AUDIO entry pointing at its own media playlist. Renditions are bundled into a named audio group via GROUP-ID . Each video variant ( #EXT-X-STREAM-INF ) references a group with AUDIO="..." . A correct master playlist: #EXTM3U #EXT-X-VERSION:6 #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud",NAME="English",LANGUAGE="en",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="2",URI="audio/en.m3u8" #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud",NAME="Espanol",LANGUAGE="es",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2",URI="audio/es.m3u8" #EXT-X-STREAM-INF:BANDWIDTH=2128000,CODECS="avc1.640028,mp4a.40.2",AUDIO="aud" video/720p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1128000,CODECS="avc1.640020,mp4a.40.2",AUDIO="aud" video/480p.m3u8 Every attribute earns its place: LANGUAGE - BCP-47 code, used for the label. DEFAULT - plays when the viewer has no preference. AUTOSELECT - may be auto-picked from the OS language. CHANNELS - needed so the player can reason about stereo vs surround. BANDWIDTH on each video variant must include the audio group's bitrate , or your ABR logic works from a wrong total. 2. Author the renditions with FFmpeg Extract/encode each language's audio, then package. First, encode video-only and audio-only renditions: # video only (no audio), two ladder rungs
AI 资讯
From Angular.js to Fine-Grained Reactivity: Part 2 — The JS Proxy Runtime
In the first article of this series, we saw how a custom build-time compiler can transform a legacy Angular.js template into raw, optimized JavaScript. To recap, starting from this template: <!-- simple.html --> <p> Hello {{ name }}! </p> Our Go compiler generates the following JavaScript module: // simple.js export function template () { const p_0 = document . createElement ( " p " ); const text_1 = document . createTextNode ( "" ); p_0 . append ( text_1 ); return { mount ( container ) { container . append ( p_0 ); }, update ( change ) { if ( " name " in change ) { text_1 . data = " Hello " + change . name + " ! " ; } } } } This is incredibly clean. By running template() , we get an object with mount and update methods. Using mount is fully intuitive: we pass a reference to a DOM element, and it injects our empty paragraph ( p_0 ) into it: import { template } from ' ./simple.js ' ; const { mount , update } = template (); const container = document . getElementById ( ' view-container ' ); mount ( container ); // The DOM now contains: <p></p> (waiting for data) However, the paragraph remains empty until we call update with a change object like this: let changes = { name : " Mario " , }; update ( changes ); // The DOM surgically updates to: <p>Hello Mario!</p> But who is responsible for tracking changes in our application state, building this changes object, and calling update ? The answer lies in marrying the legacy Angular.js $scope with the modern JavaScript Proxy API . The Legacy State Pattern In a traditional Angular.js application, developers mutate the state directly inside a controller by assigning properties to the $scope object: // simple-controller.js export function SimpleController ( $scope ) { $scope . name = " Mario " ; } To bridge the gap between this legacy controller and our new build-time template, we need a way to automatically capture the assignment $scope.name = "Mario" and translate it into a structured update: let changes = { name : " Mario " }
AI 资讯
Guardrails for LLM Apps in Java
Introduction Every post in this series has quietly touched a piece of the same problem. Building Agentic Workflows in Java said toolUse.input() is untrusted and must be validated before it reaches your code. Building Reliable LLM Applications in Java said the model will confidently invent facts, so ground it and get typed output instead of parsing prose. Neither post named the thing underneath both statements: anything that crosses from outside your code into the model, or from the model back into your code, is untrusted input — a request body from the network, not a trusted internal value. This post names that boundary directly and gathers the defenses in one place — prompt injection (direct and indirect), input validation, output validation, and PII redaction — with the SAFE pattern shown beside every unsafe one it replaces, since this is the security-forward capstone of the series. The Trust Boundary: Three Kinds of Untrusted Input An LLM application has three places where untrusted text enters: User input — anything a person types, uploads, or submits through an API. Retrieved content — Making RAG Accurate in Java built a pipeline that ranks and returns chunks from a document store; those chunks were written by whoever authored the source document, not by you, and a malicious or compromised document can carry text aimed at the model reading it, not at a human reader. Model output — untrusted the moment it's about to be used rather than displayed : passed to a tool, interpolated into a query, or fed into another LLM call as context. A model that just read attacker-controlled retrieved text can be manipulated into producing attacker-controlled output. The single rule under all three: text is data until your code has explicitly decided it's safe to use for anything more than display. Nothing below is executed against a live API — every snippet is illustrative, and none of it uses a real key or a real record. Direct Prompt Injection: Defending the System Prompt A di
AI 资讯
Prompt Caching and Cost Control in Java
Introduction We already covered picking the right model tier for the task and caching a large shared prefix in https://pg-blogs.netlify.app/posts/11-building-reliable-llm-apps-in-java/ . Those two lines were the tip of a bigger discipline: LLM cost is not a fixed line item, it's an engineering variable — one you can measure and shrink with the same rigor you'd apply to database query time or container memory. This post goes deeper: how input/output pricing actually works, the exact cache_control shape and how to prove a cache hit rather than assume one, the Batches API for work that isn't latency-sensitive, and model routing — using a cheap model to triage, escalating only the hard cases to a stronger one. The honest framing throughout: measure before you optimize. Every technique here has a cost of its own; applied to the wrong workload, "optimization" makes things slower or more expensive. Token Economics: Why the Prefix Is the Bill Anthropic (like every hosted LLM provider) prices input and output tokens separately, and output is always pricier — the model has to generate output autoregressively, one token informed by all the ones before it, while input can be processed in parallel. Representative pricing from the current model catalog: Model Input Output Claude Opus 4.8 $5.00 / MTok $25.00 / MTok Claude Sonnet 5 $3.00 / MTok $15.00 / MTok Claude Haiku 4.5 $1.00 / MTok $5.00 / MTok Two consequences follow directly: Long system prompts, tool definitions, and RAG context are read on every request , not written once. A 20K-token system prompt sent on every one of 10,000 requests is 200M input tokens — at Opus 4.8 rates, $1,000 before a single output token is generated. The shared prefix , not the user's question, is usually where the money goes. A verbose model wastes money twice — once on the extra output tokens themselves, and again because the next turn's messages history now carries that verbosity forward as input on every subsequent call. Trimming max_tokens an
AI 资讯
Evaluating LLM Apps in Java
Introduction Building Reliable LLM Applications in Java put it plainly: treat model output as a hypothesis to verify, not a fact to trust. Testing Best Practices in Java put the same discipline in JUnit terms: a suite only earns trust by asserting the right things at the right level, unhappy paths included. This post is where those two ideas meet — a JUnit test either passes or fails against a fixed expected value; an LLM's output is a paragraph of prose that might be right in spirit while differing token-for-token from anything you wrote down in advance. Evaluating it takes a harness, not an assertEquals . That harness has three parts: a golden dataset of representative cases with known-good expected behavior, scoring that turns each case into a pass/fail or a number, and regression testing that runs the harness on every change and fails the build when the score drops. Making RAG Accurate in Java already gave you half of this story — recall@k, precision@k, MRR, nDCG measure whether retrieval found the right chunks. This post measures the other half: whether the generated answer built from those chunks is actually good, which is a genuinely different question a retrieval metric can't answer on its own. Everything below is illustrative, non-executed Java, grounded in the same Anthropic Java SDK shapes as posts 10/11. The Golden Dataset: Curating Cases, Not Just Inputs A golden dataset is a small, hand-curated set of (input, expected behavior) pairs that represents the ways your application is actually used — not a random sample, and not just the cases that already work. Each case needs enough structure to be scored automatically later: public record EvalCase ( String id , String category , // "extraction", "qa", "summarization", ... String input , // the prompt/question sent to the system under test String expectedExact , // non-null only for cases scorable by exact/programmatic match List < String > mustContain , // key facts a correct answer must mention (programma
AI 资讯
The Model Context Protocol in Java
Introduction Every agent needs tools, and every tool needs a way to reach the model. Building Agentic Workflows in Java built that connection by hand — a hand-written Tool schema, a loop that dispatches on toolUse.name() . LLM Frameworks vs. the Raw SDK in Java showed LangChain4j and Spring AI turning an annotated Java method into that same schema via reflection. Both are still bespoke : the tool lives inside one process, wired to one agent, in one language. The Model Context Protocol (MCP) solves a different problem: it standardizes the wire format between an AI application and a tool server, so the server doesn't have to be rewritten per agent, per framework, or per language. This post covers what that buys you, builds a minimal MCP server and a client that consumes it — both on the official Java SDK — and gives an honest answer to when reaching for a protocol is worth it over a direct tool call. The Problem MCP Solves Without a shared protocol, every pairing of agent framework and tool needs its own glue code: a LangChain4j tool wrapper, a Spring AI @Tool method, a hand-rolled schema for the raw SDK — three integrations for one capability, repeated for every tool and every framework you add. That's an M×N integration problem. MCP flattens it to M+N. A server exposes tools, resources, and prompts once, over a standard JSON-RPC protocol. Any host application — Claude Code, Claude Desktop, VS Code, or your own agent — creates an MCP client that speaks that same protocol, regardless of which framework built the host. Write the server once; every MCP-aware host can use it without new integration code. The protocol itself is intentionally boring: JSON-RPC 2.0 messages for lifecycle negotiation, tool discovery, and tool execution. Discovery ( tools/list ) and execution ( tools/call ) are the two calls that matter for this post: // tools/list response (abbreviated) { "jsonrpc" : "2.0" , "id" : 2 , "result" : { "tools" : [ { "name" : "get_account_balance" , "description"