AI 资讯
How to structure a Chrome Extension with Manifest V3 (the right way)
If you've tried building a Chrome extension recently, you've probably hit Manifest V3 and spent an hour just figuring out why your background page stopped working. MV3 replaced background pages with service workers, changed how content scripts communicate, and made permissions stricter. The official docs are... not great. So here's the structure that actually works. The folder structure chrome-extension/ ├── manifest.json ├── popup/ │ ├── popup.html │ ├── popup.css │ └── popup.js ├── options/ │ ├── options.html │ └── options.js ├── content/ │ └── content.js ├── background/ │ └── service-worker.js ├── utils/ │ └── storage.js └── icons/ The manifest.json (MV3) The biggest MV3 gotcha: background scripts are now service workers. { "manifest_version": 3, "name": "Your Extension", "version": "1.0.0", "permissions": ["storage", "activeTab", "scripting"], "action": { "default_popup": "popup/popup.html" }, "background": { "service_worker": "background/service-worker.js" }, "content_scripts": [ { "matches": [""], "js": ["content/content.js"] } ] } Communicating between popup and content script This trips up almost everyone. The popup can't directly access the page DOM — it has to message the content script. // popup.js const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); await chrome.tabs.sendMessage(tab.id, { type: 'RUN_ACTION' }); // content.js chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'RUN_ACTION') { // do something on the page sendResponse({ success: true }); } return true; // keeps the channel open for async response }); The return true at the end is critical — without it, async responses silently fail. Storage that syncs across devices Use chrome.storage.sync instead of localStorage. Here's a utility wrapper that makes it clean to use anywhere: const Storage = { async get(key) { return new Promise((resolve) => { chrome.storage.sync.get([key], (result) => resolve(result[key])); }); }, async set
AI 资讯
Upgrade .NET 8 to .NET 10 Without Breaking Your API Contract
If I need to upgrade .NET 8 to .NET 10 , I treat the work as an API contract migration, not a project-file edit. A service can compile, pass unit tests, and still surprise consumers with a changed JSON shape, status code, authentication response, or OpenAPI document. That risk matters now because Microsoft has confirmed that .NET 8 and .NET 9 reach end of support on November 10, 2026 . .NET 10 and C# 14 are the current stable releases, and .NET 10 is the supported LTS destination. Why the deadline changes my upgrade order My first step is inventory, not retargeting. I list every deployable project, test project, global.json , container base image, CI SDK pin, and Microsoft package reference. dotnet --list-sdks shows what a machine can build; dotnet --info shows what the current environment actually resolves. If that inventory needs more detail, my older guide to dotnet sdk check is a useful starting point. For APIs still on .NET 8, the broader Web API setup and security checklist can help identify behavior worth protecting before the move. I then separate the migration into three changes: SDK and target framework, NuGet dependencies, and runtime infrastructure. Keeping those changes visible makes a failure easier to locate. A giant dependency-refresh commit may be quick to create, but it is hard to diagnose. Upgrade .NET 8 to .NET 10 behind contract tests Before changing net8.0 , I add a small set of tests around the endpoints consumers cannot tolerate changing. I care about observable behavior: status codes, content types, required JSON names, and authentication boundaries. I avoid asserting an entire serialized string because harmless property ordering can make that test noisy. Here is a focused xUnit test for a Minimal API: using System.Net ; using System.Text.Json ; using Microsoft.AspNetCore.Mvc.Testing ; using Xunit ; public sealed class ProductContractTests ( WebApplicationFactory < Program > factory ) : IClassFixture < WebApplicationFactory < Program >> { [
AI 资讯
Building Real-Time AI Translation Assistance with FastAPI, Claude, and Server-Sent Events
How we added an on-demand translation help feature to our book translation platform, streaming LLM suggestions for tricky passages. At LectuLibre, our AI-powered book translation service allows users to upload EPUB or PDF files and get translations generated by large language models like Claude and DeepSeek. But we quickly noticed a pain point: automated translations, while fast, sometimes produced awkward or ambiguous results for culturally specific phrases, idioms, or technical jargon. Users wanted a way to get instant, contextual help for these tricky passages without leaving the platform. That’s when we set out to build the 翻译与转录求助 (Translation Assistance) feature — an interactive side panel where users can select any sentence or paragraph and receive alternative translations, explanations, and stylistic suggestions from an LLM in real time. In this article, I’ll walk you through the engineering challenge, the architecture we chose, and the specific code and trade-offs that made it work smoothly under production constraints. The Problem: Real-Time, Context-Aware Translation Help The core requirement was simple: a user highlights a piece of text in the translated book and clicks “Get Assistance”. Immediately, the system should stream back multiple translation options, a brief explanation of differences, and stylistic notes — all aware of the surrounding context, the author’s style, and the target language. Under the hood, this meant: Low latency : Users expect a response in under 2 seconds. Streaming : The LLM output can be long, so we needed to stream tokens as they are generated. Context awareness : We must include enough surrounding text from the book to ground the model’s response. No blocking : The main translation pipeline shouldn’t be affected; the assistance feature should exist as an independent async service. Cost efficiency : Avoid re-processing the entire book each time a user asks for help. Our Approach: Async FastAPI + SSE + Rate Limiting We run a P
AI 资讯
Part 4: When It Breaks, Just Fix the 'Raw Parts'. The Self-Reliance to Maintain Tools Yourself by Commanding AI
This article was originally published on e-shikumi-labo . Hello, I'm Shin from e-Shikumi-Labo. This is the final installment (Part 4) of "Systematized Thinking," where we use AI to build our own tools and independently maintain them. So far, we have discussed creating a prototype that automatically saves Gemini chat logs, converting them to Markdown for Obsidian integration, and elevating it to a safe, fully automated system. In this final installment, we will cover the "countermeasures for downtime due to screen specification changes," an unavoidable issue when operating tools that handle web data, and the core of the "self-reliance" humans should possess in the AI era. 1. The Web Data Extraction Compromise: "You Can't Extract What Isn't on the Screen" During development, there was a time when I thought, "I also want to record the exact date and time (timestamp) when the chat was sent." However, no matter how much I analyzed Gemini's screen structure, the exact timestamp of each utterance did not exist in the HTML. The fundamental rule of web data extraction is: "You cannot extract data that does not exist on the browser screen." As long as you are extracting data from the screen (DOM) rather than via an API, forcing the extraction of something that isn't there will require complex guesswork processes and will instead become a cause of trouble. Understanding this "technical limit," gracefully giving up on what cannot be done, and judging to maintain simplicity is also an important element of tool building. 2. Specification Changes Are Not Defects, But "Fate" As long as you deal with tools that extract data from other people's websites, the time will inevitably come when the tool suddenly stops working one day due to design changes or updates on Google's side. "It was working fine until yesterday, but suddenly it stopped saving." This is not a defect in the tool, but an unavoidable "fate" as long as you depend on someone else's platform. The important thing is not t
AI 资讯
Restoring Codebase Harmony
The Chaotic Bug: The Infinite State Loop & Memory Leak In a real-time clinical AI health suite, high-frequency telemetry streaming (such as 60Hz ECG canvas updates) demands surgical precision. During heavy load testing, our frontend performance suddenly degraded: CPU thread usage hit 98%, heap memory ballooned to over 1.4 GB, and DOM frame rendering dropped to single digits. The Root Cause A subtle React useEffect hook listening to the incoming WebSocket data stream contained the state setter inside its dependency array: // ❌ THE CHAOTIC BUG (Caused infinite state sync re-renders) useEffect(() => { const sub = ecgDataStream.subscribe((point) => { setEcgPoints((prev) => [...prev, point]); // Triggered full tree re-render on every frame! }); return () => sub.unsubscribe(); }, [ecgPoints]); // Including state array in deps created recursive re-subscription storm! Every incoming telemetry frame pushed new state, triggering an immediate top-level component re-render, which re-subscribed to the stream and accumulated thousands of orphaned event listeners. Best Use of Sentry: Pinpointing & Clearing the Lineup Sentry Performance Tracing and Sentry Error Tracking proved invaluable in isolating this silent killer: Transaction Waterfalls: Sentry flagged transaction spans render_ecg_canvas exceeding the 500ms threshold (averaging 842ms). Breadcrumb Trail: Sentry logged a rapid succession of CanvasRenderer memory allocation warnings (>64MB/sec). Issue Grouping: Sentry grouped 14,000 React Maximum update depth exceeded exceptions into a single actionable alert. The Fix & Restored Harmony We refactored the streaming engine to bypass React state re-renders entirely for frame accumulation, employing a zero-allocation useRef buffer paired with a requestAnimationFrame render cycle, and instrumented Sentry Breadcrumbs: // ✅ THE RESILIENT FIX (Zero-allocation ref buffer + Sentry Breadcrumb) import * as Sentry from '@sentry/react'; const bufferRef = useRef([]); useEffect(() => { Sentry.a
AI 资讯
The gate for an agent belongs in the environment, not in the agent
There's a discussion running on Product Hunt right now about where an AI agent should stop and hand a decision back to a person. The framing that stuck with me: "can this be undone?" is the wrong gate. It over-fires on things nobody cares about, like writing a log line, and under-fires on the ones that actually hurt. A mass email. A migration you can only roll back with downtime. The proposed replacement is reach. Not "is this safe" but "how far does this go if I'm wrong." And the sharpest point in that thread is that the agent is the worst possible judge of its own reach, because it doesn't know there are 50,000 people on the list. I've been building a browser with an agent in it, and the browser case makes that concrete in a way the terminal case doesn't. A coding agent's blast radius is usually one person: you, reading a diff. A browser agent doesn't start there. It inherits every session you're already signed into. The reach of a click isn't a property of the agent's plan, it's a property of the cookie jar it's holding. The same "click the blue button" step is harmless on a docs site and irreversible on a payments dashboard, and nothing in the agent's own summary distinguishes them. So I stopped trying to make the agent classify risk. Here's what I do instead, with the caveat that this is alpha and I've been wrong about it before. Some things never reach the model. Credential fields are filtered out of the page snapshot before the agent sees it. That means password inputs, and anything with cc-number , cc-csc , cc-exp , or one-time-code autocomplete semantics. A fill targeting a password field refuses outright. The agent cannot misjudge the reach of a control it was never shown, and that's a stronger guarantee than any classifier, because it doesn't depend on getting a judgment right. The model's judgment is a floor, never a ceiling. For everything that does reach it, the agent can say "this needs approval" and be believed. It cannot say "this is routine" and be
开发者
What "18 years in web dev" actually means when your clients are small businesses, not startups
Most dev-to content about longevity comes from people who scaled one product for a decade. My version of 18 years is different: 235+ separate small projects, each with a different client, budget, and expectation. That produces a completely different set of lessons. Every project restarts the trust clock. In a startup, trust compounds — the team, the codebase, the client relationship all carry forward. In agency work for small businesses, you start from zero credibility on every single engagement. The client has no idea if you're competent until you prove it, usually within the first draft. That reality shaped how we scope: front-load a visible win early, even a small one, rather than saving the "impressive part" for the end. Most client requests aren't really about the website. "Can we change the homepage headline" is often actually "I'm nervous this won't generate leads" or "my business partner didn't like it." Treating every request as a literal design brief instead of what it's actually about leads to a lot of pointless revision cycles. Asking one clarifying question — "what's this in response to?" — before touching the page has cut our revision count more than any process change. Consistency beats innovation for this client base. A small business owner doesn't want a novel UX pattern. They want their site to look like the successful competitor's site, load fast, and not embarrass them. Chasing design trends for this audience is optimizing for the wrong judge — they're not evaluating craft, they're evaluating "does this look like it'll work." The real skill is saying no to the wrong project, not saying yes to more of them. Early on I took every lead. Now the highest-leverage thing I do is a 10-minute pre-call that filters out projects where the client's expectations and budget don't match — before either of us spends real time on it. That single filter has done more for margin than any pricing change. None of this shows up in a portfolio. But if you're a develope
AI 资讯
100 Days of Solana: What I Actually Learned (Not What I Expected to Learn)
One hundred days ago, I had no idea this challenge would become one of the most rewarding technical journeys I've taken. There were days when everything clicked, and there were days when nothing compiled. I celebrated successful deployments, stared at cryptic errors for hours, rewrote programs that weren't good enough, and learned that understanding Solana has far less to do with memorizing APIs than changing how you think about state, ownership, and security. Looking back now, I realize I didn't just complete 100 challenges; I built a completely different mental model of software. Where I started I came in with a biochemistry degree I never used, a self-taught engineering career I built in public, and a GitHub full of Rust and Python work. I co-maintain statix, a Nix linter that ended up in the canonical NixOS/nixpkgs upstream. I had shipped real software before. What I did not have was any intuition for how Solana actually works. I knew the buzzwords. I had the mental model of "it's fast and cheap." I did not know what an account was. I did not know what a program was. I did not know why those two things were different. That gap, between knowing the words and understanding the model, is what these 100 days actually closed. What I expected I expected Solana to feel like a database with extra steps. In Web2, you have a server that holds state. You call an API. The server reads from the database, does something, writes back. I expected a blockchain to be the same thing, just slower and decentralized. The first thing that broke that model: accounts. On Solana, an account is not a row in your database. It is the database. Every piece of state, your wallet balance, a token you hold, the program you deployed, is an account. Programs are accounts. Data is accounts. Everything is an account. That sounds obvious written down. It took me until around Day 15 to actually feel it, when I was staring at a getAccountInfo call wondering why the program I deployed was also an accou
AI 资讯
**Soul & Spoon — Comfort Food Landing Page**
--- title : " Perfect Landing — Soul & Spoon (Comfort Food Edition) — Research Summary" published : false tags : [ " frontend" , " html" , " css" , " javascript" , " accessibility" , " performance" , " react" , " svelte" ] cover_image : " https://images.unsplash.com/photo-1543353071-087092ec393a?q=80&w=1600&auto=format&fit=crop&ixlib=rb-4.0.3&s=3" canonical_url : " " series : " " --- Perfect Landing — Soul & Spoon Research Summary A concise, ready-to-paste DEV post that summarizes the research, design decisions, technical choices, and next steps for the Soul & Spoon landing-page project — a warm, accessible, performance-minded single-page site celebrating soul food. What I Built Soul & Spoon — Comfort Food Landing Page is a single-page landing site that showcases soul-food plates with a polished, modern frontend. The static prototype includes: Hero with a full-bleed background and clear CTAs. Featured dishes section with responsive cards and descriptive copy. Gallery of soul-food plates with a keyboard-accessible lightbox. Contact form with client-side validation and toast feedback. Responsive, accessible, and performant implementation using semantic HTML, picture / srcset /WebP, lazy loading, and minimal JavaScript. Research Summary and Rationale Design goals Evoke warmth and comfort through color, rounded shapes, and soft shadows. Prioritize readability and hierarchy for quick scanning on mobile and desktop. Keep interactions simple and predictable: smooth scroll, accessible modal, and unobtrusive toast notifications. Accessibility findings Semantic elements ( header , main , section , figure , figcaption , footer ) improve screen-reader navigation and SEO. Keyboard operability is essential: gallery images must be focusable and open via Enter/Space; Escape should close the lightbox. ARIA attributes ( aria-hidden , aria-expanded , role="dialog" ) plus focus management significantly improve modal usability. Performance findings Images dominate page weight; srcset an
AI 资讯
My Similarity Check Let the Same Story Through 3 Times. Here's How I Killed It.
I run a content pipeline that picks trending topics and publishes articles automatically. Last week I found out it had published the same story three times. Not the same title — the same exact topic, reworded each time. My dedup check was supposed to stop that. It didn't. Here's why, and how I killed the check. The Bug My pipeline had a similarity gate. Every candidate title got compared against the last 30 published titles, and anything scoring 0.58 or higher was rejected. Straightforward, right? from difflib import SequenceMatcher def jaccard_bigram ( a : str , b : str ) -> float : def bigrams ( s : str ) -> set [ str ]: return { s [ i : i + 2 ] for i in range ( len ( s ) - 1 )} x , y = bigrams ( a ), bigrams ( b ) return len ( x & y ) / len ( x | y ) if ( x | y ) else 1.0 def similarity ( a : str , b : str ) -> float : return max ( SequenceMatcher ( None , a , b ). ratio (), jaccard_bigram ( a , b )) THRESHOLD = 0.58 Here's the pair that slipped through. The candidate: 中国军队国际形象网宣片《当红》 And a title I had already published: 《当红》网宣片刷屏,普通人看到的中国军人是什么样 Same film. Same topic. Third time it was being covered. Watch what the algorithm did: candidate = " 中国军队国际形象网宣片《当红》 " published = " 《当红》网宣片刷屏,普通人看到的中国军人是什么样 " print ( similarity ( candidate , published )) # SequenceMatcher: 0.187 # jaccard bigram: 0.185 # max: 0.187 < 0.58 -> PASSED 0.187. The gate let it through with a five-fold margin to spare. Why It Failed The name 当红 is the same in both titles. That is the whole topic. But the algorithm does not care about that. SequenceMatcher matches in order. In the published title, 当红 sits at position zero. In the candidate, it is at the end. Reordered tokens break the match, so the ratio collapses to the shared fragments — 网宣片 plus the generic words around it. The bigram fallback does not save you either. Jaccard over character bigrams measures surface overlap, not meaning. Five shared bigrams out of twenty-seven total. 0.185. It "proves" the titles are unrelated because most of
开发者
8 Open Source Gems To Become The Ultimate Developer 🔥
TL;DR Hello everyone! It's been a while since I've posted a list of interesting projects,...
AI 资讯
JavaScript vs React: What's the Difference?
JavaScript vs React: Understanding How They Work Together If you're starting web development, you've probably heard about JavaScript and React. Many beginners think they are competitors, but they actually work together. Let's understand them in simple terms. What is JavaScript? JavaScript is a programming language used to make websites interactive. Without JavaScript, a website would mostly be static. JavaScript allows you to: Handle button clicks Validate forms Create animations Fetch data from APIs Update content without refreshing the page Example: document . getElementById ( " btn " ). addEventListener ( " click " , () => { alert ( " Hello World! " ); }); JavaScript is the foundation of modern web development. What is React? React is a JavaScript library created by Meta Platforms for building user interfaces. Instead of manipulating the webpage manually, React helps developers create reusable UI components. Example: function Welcome () { return < h1 > Hello World! </ h1 >; } React uses JavaScript to create dynamic and interactive user interfaces more efficiently. Simple Analogy Think of building a house: JavaScript = The tools and materials (bricks, cement, wood) React = A construction framework that helps you build the house faster and more efficiently You need JavaScript to use React. Key Differences Feature JavaScript React Type Programming Language JavaScript Library Purpose Adds logic and interactivity Builds UI components Learning Curve Easier to start Requires JavaScript knowledge Usage Works everywhere Used mainly for frontend applications Created By Netscape Meta (Facebook) DOM Updates Manual Virtual DOM for optimized updates Why React Became Popular As applications grew larger, managing UI with plain JavaScript became difficult. React solves this by providing: Component-based architecture Reusable code Better state management Faster UI updates with Virtual DOM Large ecosystem and community support This makes React ideal for building modern applications
AI 资讯
How BrowserAct Fixed the Stale-Selector Failures Breaking My Browser Tasks
Disclosure: BrowserAct sponsored this piece. The BrowserAct links below are affiliate-tracked — I get...
AI 资讯
Analyze a Travel Dataset with Claude and Flexmonster Pivot Table
I recently discovered you can build a fully working Flexmonster pivot table right inside a Claude...
AI 资讯
Your link checker thinks deleted Telegram bots are alive
Delete a Telegram bot and https://t.me/your_deleted_bot keeps returning HTTP 200 with a page that looks completely normal. Every link checker I know of — CI actions, directory scripts, monitoring cron jobs — reports it as healthy forever. If you maintain anything that lists Telegram bots, some fraction of your list is already dead and your checks are telling you it is fine. Reproducing it Pick a username that has never existed: curl -s -o /dev/null -w "%{http_code} \n " https://t.me/nonexistent_test_bot_77712 # 200 Two hundred. No redirect, no 404, no soft-404 marker in the body that a status check would catch. Where the truth is The status code is useless here, but the Open Graph title is not. I measured four usernames — two live bots, two that do not exist: URL og:title t.me/BookClassBot (live) BookClass t.me/instanavy_bot (live) StoryViewer - anonymous instagram story viewer tool t.me/nonexistent_test_bot_77712 Telegram: Contact @nonexistent_test_bot_77712 t.me/zzz_definitely_not_a_real_bot_9182 Telegram – a new era of messaging A live bot puts its own display name in og:title . A dead one gets one of two Telegram placeholders: Telegram: Contact @<username> , or — if the username is not even syntactically valid — the generic Telegram – a new era of messaging . That is the whole signal. curl -s https://t.me/some_bot | grep -o '<meta property="og:title" content="[^"]*"' The check Standard library only, no dependencies: import re import urllib.request UA = " Mozilla/5.0 (compatible; linkcheck/1.0) " DEAD_EXACT = { " Telegram – a new era of messaging " , " Telegram " } DEAD_PREFIX = " Telegram: Contact @ " def telegram_bot_exists ( url : str ) -> bool : """ True if the bot behind a t.me URL still exists. HTTP status is not usable here: Telegram serves 200 with a placeholder page for usernames that were deleted or never existed. The Open Graph title is what actually differs. """ req = urllib . request . Request ( url , headers = { " User-Agent " : UA }) with urllib .
AI 资讯
Laravel Packages Every Developer Should Know (After Building a Real-World Product)
Laravel is one of my favorite frameworks because it allows you to move from idea to production incredibly fast. But after spending months building CelebrateMe a platform that helps people celebrate life's special moments through virtual gifts, wishlists, messages, and verified vendors—I realized something. I wasn't just using Laravel. I was relying heavily on the incredible ecosystem around it. Some packages solved problems that would have taken days (or weeks) to build myself. Others helped me monitor, debug, and secure the application as it grew. Here are the Laravel packages I now consider essential for almost every project. 1. Laravel Sanctum Use it for: API Authentication CelebrateMe has a Laravel API with a React frontend, so authentication needed to be secure without adding unnecessary complexity. Laravel Sanctum was the perfect choice. It provides: Personal access tokens SPA authentication Mobile API authentication Lightweight implementation For most APIs, Sanctum is more than enough. 2. Laravel Horizon Use it for: Queue Monitoring As CelebrateMe grew, background jobs became increasingly important. Things like: Sending emails Processing uploads Notifications Payment-related jobs Instead of wondering whether jobs were running correctly, Horizon gave me a beautiful dashboard to monitor everything in real time. If you're using queues and not using Horizon, you're missing out. 3. Laravel Telescope Use it for: Debugging Telescope quickly became one of my favorite development tools. Instead of scattering dd() statements throughout my code, I could inspect: Requests SQL queries Jobs Exceptions Cache operations Notifications It made debugging significantly easier. 4. Spatie Laravel Permission Use it for: Roles & Permissions CelebrateMe has multiple user types, each requiring different permissions. Managing authorization manually would have become difficult very quickly. Spatie's Permission package made it straightforward to assign roles and permissions while integra
AI 资讯
🛡️ Building AbilityGuard: Monitoring the WordPress Abilities API in Production
A few weeks ago I wrote about the WordPress Abilities API — what it is, why WordPress 6.9 shipped it, and what it means for how plugins will talk to each other and to AI agents going forward. That post was theory. This one is the part where theory meets a composer.json file and a stubborn bug at 1 AM. This is the story of building AbilityGuard — a plugin that monitors Abilities API usage in production, so you actually know what's happening when abilities get registered, called, and (occasionally) abused. 👀 Why monitoring, and why now Here's the thing about the Abilities API that got me nervous the first time I really understood it: it's a capability surface . Any plugin can register an ability. Any authorized caller — a human-triggered action, an automation, or increasingly, an AI agent — can invoke one. That's the whole point of the API, and it's genuinely exciting. But it also means your site now has a growing list of "things that can be done to it programmatically," and most WordPress admins have zero visibility into that list. I've spent enough years debugging WordPress sites in production to know what happens when you can't see something: you find out about it during an incident, not before. Slow queries, rogue cron jobs, plugin conflicts — they all follow the same pattern. Nobody notices the small thing until the small thing becomes the outage. So the idea for AbilityGuard was simple: give site owners a dashboard and a log for every ability registered on their site, every time one gets called, and by whom. Not another abstract "security scanner" — just honest, readable visibility into a part of WordPress that's brand new and mostly invisible right now. 🔌 Where I started: hooking into the registry, not fighting it The Abilities API exposes a central registry ( wp_get_ability_registry() under the hood, with helper functions layered on top). My first instinct was to intercept ability calls by wrapping core functions — and I killed that idea within the hour. Wrapp
AI 资讯
The 300px Canvas Bug That Shrunk My React Image Editor
This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry . Project Overview I am building a browser-based text removal workspace where a user uploads an image, paints over unwanted text or objects, and sends the resulting mask to an image-editing pipeline. The mask editor uses three stacked <canvas> elements: a base canvas for the uploaded image; an overlay canvas for the painted mask; a cursor canvas for the brush preview and pointer events. All three canvases must have identical dimensions. The pointer coordinates must also map back to the same bitmap coordinate system, or the generated mask will not match the part of the image the user selected. Bug Fix On desktop, the editor had plenty of horizontal space but the uploaded image appeared inside a narrow strip surrounded by a large empty area. The result preview used the available width correctly, so the two sides of the same workspace looked unrelated. The visible symptom was a tiny image editor. The actual failure started before the image was drawn. The initialization code measured the width of the canvas wrapper: const container = canvas . parentElement if ( ! container ) return const containerWidth = container . clientWidth || 1 const containerHeight = 600 It then calculated the largest canvas size that would preserve the uploaded image's aspect ratio: const imgAspectRatio = img . width / img . height const containerAspectRatio = containerWidth / containerHeight let canvasWidth : number let canvasHeight : number if ( imgAspectRatio > containerAspectRatio ) { canvasWidth = containerWidth canvasHeight = containerWidth / imgAspectRatio } else { canvasHeight = containerHeight canvasWidth = containerHeight * imgAspectRatio } The aspect-ratio calculation was correct. The measurement it received was not. Root Cause: The Canvas Measured Itself The wrapper was a relatively positioned element with no declared width: < div className = "relative transition-all duration-500 ease-out" style = { {
AI 资讯
Every claim on my site carries its sources. Here is the schema that forces it.
I run a fact-check site for an unreleased game. That genre is a swamp: half the pages you find are somebody's guess reprinted six times until it reads like news. I wanted the opposite, so I made provenance a schema requirement instead of an editorial habit. If a claim has no source, the build fails. Here is how that works in Astro, and what it cost me. Sources live in the content schema, not in the prose Every entity on the site is a YAML file validated by a Zod schema. The interesting part is that sources is not optional: const sourceSchema = z . object ({ url : z . string (). url (), date : z . string (), // when the source said it, not when I read it }); const base = { status : z . enum ([ ' confirmed ' , ' trailer-spotted ' , ' rumor ' , ' debunked ' ]), updated : z . string (), sources : z . array ( sourceSchema ). min ( 1 ), }; export const entitySchema = z . object ({ name : z . string (), description : z . string (), sections : z . array ( z . object ({ heading : z . string (), text : z . string (), status : z . enum ([ ' confirmed ' , ' trailer-spotted ' , ' rumor ' , ' debunked ' ]), sources : z . array ( sourceSchema ). min ( 1 ), // per section, not per page })). optional (), ... base , }); Two decisions in there matter more than they look. Sources are per section, not per page. A page usually mixes a confirmed fact with a plausible reading of a trailer. One source list at the bottom lets those blur together. Per-section sources force me to say which sentence rests on what. Status is a required enum, not a boolean. rumor and debunked are first-class. The page renders a badge from the same field, so the reader sees the confidence level next to the claim instead of a disclaimer nobody scrolls to. The cost is real: adding a paragraph means finding a citable source for it. Several times I have deleted a nice sentence because I could not back it. That is the feature working. Seven locales, and the empty ones stay invisible The site ships in seven languages, a
AI 资讯
Rino.js 3, Building Modern Websites Without a Frontend Framework
Modern web development has become incredibly powerful. But also increasingly complicated. Many projects begin by installing hundreds of megabytes of dependencies before writing a single page. Frameworks, bundlers, routers, templating systems, CSS tooling, and runtime libraries all solve important problems, but they also introduce additional complexity. I wanted something different. I wanted to build websites that start with plain HTML, while still providing the features developers expect today: Reusable components Markdown support TypeScript CSS and JavaScript bundling Internationalization (i18n) Content collections RSS/Atom feeds Sitemap generation Fast development builds That idea became Rino.js. What is Rino.js? Rino.js is an HTML-first website compiler for building static websites, documentation, blogs, portfolios, company websites, and other content driven projects. Instead of introducing a custom templating language or requiring a frontend framework, Rino.js treats HTML as the primary language. Pages remain valid HTML while additional functionality is added through a small set of build-time conventions. The goal is simple: Write HTML. Generate optimized static websites. Why HTML First? HTML has existed for decades, yet modern web development often treats it as something generated by another language. Rino.js takes the opposite approach. Instead of writing components in JSX or another template language, components are simply HTML files. <component rino-import= "header" ></component> That's all it takes. The compiler replaces the component during the build, producing plain static HTML with no runtime dependency. Starting Rino.js Rino.js has a command that is designed to provide default project. npm create rino@latest Project Shape A Rino.js project usually looks like this: my-site/ rino-config.js dev.js generate.js feed.js sitemap.js backoffice.js pages/ index.html about.html components/ header.html footer.html public/ images/ photo.webp scripts/ export/ app.js