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

标签:#dev

找到 4361 篇相关文章

AI 资讯

🚀 From FlipaClip to SitePoint: The Full Story of Kehinde Owolabi

🚀 From FlipaClip to SitePoint: The Full Story of Kehinde Owolabi How a Nigerian teenager built a professional game engine with borrowed laptops, offline W3Schools, and pure determination. 🎮 Play the Game Try Limn Engine Live — Space Shooter Demo See what 4 years of determination built. This space shooter runs at 60 FPS on a Tecno Pop 4 with 1GB RAM. 📖 Introduction Every developer has an origin story. Some start with a fancy computer and a computer science degree. Others start with a flipbook app and a sister who trusted them with her phone. My name is Kehinde Owolabi . I'm 18 years old (born December 4, 2007), and I live in Lagos, Nigeria. I'm currently in PC103 at BYU Pathway, and I'm a member of The Church of Jesus Christ of Latter-day Saints. I built a 94/100 professional game engine called Limn Engine. It runs at 60 FPS on a Toshiba with 4GB RAM. It was published on SitePoint and ranked #3 among 2D JavaScript game engines. Nobody knew it was developed on a Chromebook, a borrowed Thinkpad (behind my sister's back), and a Toshiba that "hung like hell." That was the secret I kept for months. But that's only one part of this story. This is the full story of how I went from a button phone to a 94/100 game engine, from FlipaClip to SitePoint, from a boy who failed physics to a developer who built something that runs on a Tecno Pop 4. The one-line summary: "I'm Kehinde Owolabi, an 18-year-old developer from Lagos, Nigeria who went from FlipaClip to building a 94/100 game engine on borrowed laptops — and got published on SitePoint." 🎮🚀 🎨 The Beginning: FlipaClip and the Spark of Creativity Before I was a developer, I was an animator. I used FlipaClip — a simple animation app on mobile — to create flipbook-style animations. I loved bringing characters to life, frame by frame. I would spend hours drawing, tweaking, and watching my creations move. That creative spark stayed with me. I wanted to create interactive experiences. I wanted to build games. But I didn't know how.

2026-08-24 原文 →
AI 资讯

7 Signs You're Over-Engineering Your AI App (and How to Stop)

There's a very specific kind of AI project that looks incredibly impressive in the architecture diagram and does almost nothing a simple version couldn't do better. It has a vector database. It has a multi-agent orchestration graph. It has a fine-tuned model, a memory layer, custom tool wrappers, three retries with exponential backoff, and a couple of "future-proof" abstractions nobody's actually using yet. The agent at the center is simple. The scaffolding around it is a cathedral. Here's the uncomfortable truth most teams learn the hard way: AI apps rarely fail because someone picked the wrong model or framework. They fail because layers got added before anyone could name the problem each layer was supposed to solve. The biggest mistake in building AI apps isn't starting too small — it's starting too big. So here are 7 signs you've crossed into over-engineering, the simpler thing to do instead, and — at the end — a practical playbook for not falling into the trap in the first place. See how many feel a little too familiar. 1. You reached for a vector database before you needed one "First, set up your vector database" became the default opening line of every AI tutorial — so teams spin up Pinecone or Chroma reflexively, before they've confirmed they even have a retrieval problem that requires embeddings. The plot twist of the last year is how often that's overkill. Some of the most capable coding agents around quietly dropped vector search in favor of plain tool-driven search — grep, reading the file tree, asking for files by name. In one widely-cited case, ripping out the embedding pipeline and replacing it with grep reportedly outperformed the vector setup, by a lot. That doesn't mean vector DBs are dead — they're still a strong fit for large, stable knowledge bases (product docs, FAQs, glossaries) with a good reranker. But if your data is small enough to fit in context, or searchable with keywords and filters, you may be maintaining an entire embedding-and-migra

2026-08-24 原文 →
AI 资讯

Generating 50+ SEO Landing Pages from a Static Site Build Script

I run TextTimeTools , a free site with speaking-time and reading-time calculators. It's a pure static site deployed to Cloudflare Pages — no backend, no database, no CMS. The calculators themselves are one page. But the site has 50+ pages , each targeting a different keyword like "how many words is a 5 minute speech" or "how long to read 1000 words". Every single one of those pages is generated by a build script. I've never written one by hand. Here's the pattern, and why it's the highest-leverage thing I've done for this site's organic traffic. The problem with a single calculator page A speaking-time calculator answers one query well: "how many words is my speech". But people don't search for tools — they search for answers : "how many words is a 5 minute speech" "how many words for a 3 minute speech" "how long to read 1000 words" "how long to read 5000 words" Each of those is a separate keyword with its own search intent and its own competition. One calculator page can't rank for all of them — a page titled "Speaking Time Calculator" has no reason to show up for "how long to read 1000 words". The classic fix is to write a page per keyword. That works, but it doesn't scale — every new keyword means hand-writing another page, and keeping them consistent is a nightmare. The fix: generate pages at build time The build script ( gen-longtail.cjs ) takes a list of keyword targets and emits a complete, keyword-specific HTML page for each one. The word count pages and reading time pages are both generated this way. const PAGES = [ { minutes : 1 , slug : ' how-many-words-is-a-1-minute-speech ' , variant : ' is-a ' }, { minutes : 2 , slug : ' how-many-words-is-a-2-minute-speech ' , variant : ' is-a ' }, { minutes : 5 , slug : ' how-many-words-is-a-5-minute-speech ' , variant : ' is-a ' }, // ... up to 15 minutes { minutes : 2 , slug : ' how-many-words-for-a-2-minute-speech ' , variant : ' for-a ' }, { minutes : 5 , slug : ' how-many-words-for-a-5-minute-speech ' , variant :

2026-08-24 原文 →
开发者

How to Extract Colors From an Image Using JavaScript and Canvas?

How to Extract Colors From an Image Using JavaScript and Canvas Have you ever looked at an image and wanted to know the exact HEX color of a particular pixel? Designers often need to extract colors from photographs, screenshots, logos, UI designs, and illustrations. You can do this directly in the browser without uploading the image to a server. The browser Canvas API gives us everything we need. Reading pixels with Canvas The basic process is: Load an image. Draw it onto a canvas. Read the pixel data. Convert the RGBA values into a color format such as HEX or RGB. The important API is getImageData() . javascript const imageData = ctx.getImageData(x, y, 1, 1); const pixel = imageData.data; const r = pixel[0]; const g = pixel[1]; const b = pixel[2]; const a = pixel[3];

2026-08-24 原文 →
AI 资讯

Our AI reviewer invented a request. Our producer retried 245 times.

We run ~100 LLM agents unattended on local models. Last week we found one document that had been rewritten 245 times in 5 days — every attempt rejected. A sibling document: 225 times. Combined, about 470 wasted generations, all burned on the same two files. Here is the autopsy, with the actual numbers. The loop Our pipeline is simple: a producer agent writes a document, a reviewer agent checks it against a contract (minimum length, required sections, no placeholder junk), and rejected work goes back with fix instructions. The rejected document was a key-management (KMS) implementation spec — 4,452 characters, perfectly on-topic. The reviewer's verdict: "The request was a 3-line email triage response (LOCK / VERDICT / REASON), but the answer is a long KMS spec. Rewrite as 3 lines only ." One problem. We grepped the document: the words "LOCK", "VERDICT", and the name of the triage service appear zero times in it. The reviewer had invented the request. Why the loop never ended Two contracts collided: The reviewer's fix instruction: output 3 lines only The producer's output contract: minimum 600 characters No output can satisfy both. So the producer failed the contract, got re-queued, produced again, failed again — 245 times. Our retry cap counted reviews , but a contract-failed output never reaches review. The give-up mechanism existed; it just watched the wrong counter. Root cause: the reviewer never saw the request Our review prompt contained the artifact body (first 4,000 chars) and the output format. It never contained the original request. We asked a model "does this match the request?" without telling it what the request was. A model asked to judge against information it doesn't have will hallucinate that information. Ours did, confidently, 245 times' worth. Bonus failure: we truncated long documents to 4,000 characters before review without saying so, and reviewers marked them "thin — cut off mid-sentence." The cut was ours, not the producer's. How common was it

2026-08-24 原文 →
AI 资讯

Architectural Breakdown: We fixed the eval platform we're competing on: a TypeError that crashed thr

We Fixed the Eval Platform: The TypeError That Took Down Three Benchmark Pipelines At 3 AM, Sentry lit up with TypeError: Cannot read property 'map' of undefined . Three benchmark pipelines crashed. Not a memory leak, not a segfault, but a race condition hiding behind a TypeError, turning a high-stakes eval run into chaos. Here is how we resolved it, with no fluff. The Root Cause: Async Data Meets Blind Faith in .map() The error trace pointed to evaluator.ts:42 , where .map() assumed inputData.metrics would always exist. The junior dev tested with clean data, but in production, fetchBenchmarkData() (async) and evaluatePipeline() (sync) were racing . At 100+ RPS, metrics was often undefined . The Offending Code: const results = inputData . metrics . map ( metric => computeScore ( metric )); Why It Failed: Race Condition : inputData was fetched asynchronously, but evaluatePipeline() treated it as synchronous. OOM Risk : Unbounded .map() on 10K+ metrics could exhaust 8GB RAM. Worker Starvation : No concurrency limits led to thread pool exhaustion. The Fix: Guard Clauses, Bounded Queues, and Pragmatism Step 1: Fail Fast, Fail Loud Added zero-overhead runtime checks to reject bad data early: // eval-platform/core/evaluator.ts import { isNullOrUndefined } from ' ../utils/guards ' ; async function evaluatePipeline ( inputData : BenchmarkInput ): Promise < EvaluationResult > { if ( isNullOrUndefined ( inputData ?. metrics )) { throw new Error ( ' EVAL_400: metrics missing ' ); } // Proceed only if data is valid } Why? Stops TypeError crashes immediately. Cost: 1-2 CPU cycles. Negligible. Step 2: Chunked Processing for 8GB RAM Original code processed all metrics at once, causing OOM crashes. Fixed with 100-item chunks: const CHUNK_SIZE = 100 ; // 100 items ≈ 10MB peak memory const results : number [] = []; for ( let i = 0 ; i < inputData . metrics . length ; i += CHUNK_SIZE ) { const chunk = inputData . metrics . slice ( i , i + CHUNK_SIZE ); results . push (... chunk . map

2026-08-24 原文 →
AI 资讯

Architecting a background-service-based sound manager that survives Android's Doze mode

It was the final ten minutes of a high-stakes client presentation. I was mid-sentence, explaining a complex system migration, when my phone erupted with a loud, aggressive ringtone. The room went silent, but my phone did not. I scrambled to silence it, accidentally hitting the volume buttons while fumbling with the screen. That moment of pure, unadulterated embarrassment followed me for days. It was not the first time this had happened, but it was the time I decided I had finally had enough of relying on my own memory to toggle sound profiles before entering sensitive environments. Most of us live in a state of perpetual concern regarding our devices. We walk into movie theaters, attend religious services, or sit through medical consultations, constantly checking our pockets to ensure we have toggled the mute switch. If we forget, we face the social friction of a disruption. The existing solutions were either too manual—requiring a conscious effort I rarely possessed in the moment—or too intrusive, demanding constant location permissions and draining the battery to perform simple state changes. I wanted something that functioned as a set-and-forget background utility. I needed a system that understood the context of my environment without requiring me to interact with an interface every time my routine shifted. To build this, I had to architect a background service that could survive the aggressive power-management constraints of modern Android, specifically Doze mode. The primary challenge was ensuring that my sound-toggling logic fired precisely when a rule was triggered, even if the device had been sitting idle for hours. I initially experimented with a standard Service , but Android’s lifecycle management quickly killed it to save resources. I shifted to using a ForegroundService with a persistent notification, which is the standard approach for long-running tasks, but that only solved the visibility part. The real hurdle was the timing accuracy required for eve

2026-08-24 原文 →
AI 资讯

Building PickTool with Next.js and Laravel: Lessons from Creating a Software Discovery Platform

Finding software is easy. Finding the right software is not. Search for almost any category—email marketing, CRM, productivity, design, or AI—and you will find hundreds of options. Every product presents itself as the best choice, while many comparison articles repeat the same features without explaining which users each tool actually suits. That problem inspired me to build PickTool , a platform for discovering and comparing AI and SaaS tools. PickTool is still evolving. I am currently improving its content quality, tool coverage, comparison experience, performance, and SEO structure. This is not a polished launch announcement. It is an honest look at the architecture behind the project and some of the lessons I have learned while building it. What Is PickTool? The goal of PickTool is simple: Help people find the right software in minutes, not hours. Instead of creating a basic directory filled with product names and affiliate links, I want each important tool to include useful and structured information, such as: Core features Pricing model Best use cases Strengths and limitations Ratings and evaluation criteria Alternatives Direct comparisons Related guides and category pages The challenge is that this creates several interconnected types of content. A single product can appear on its own tool page, inside a category, in multiple comparisons, and in articles about the best software for a particular use case. Keeping all of this consistent requires more than publishing isolated blog posts. Why I Chose Next.js and Laravel PickTool uses a decoupled architecture: Next.js powers the public-facing website. Laravel powers the backend, API, database logic, and administration system. MySQL stores tools, categories, ratings, pricing information, and editorial content. I chose this combination because I wanted the frontend and content-management logic to evolve independently. Laravel provides a structured backend for managing relationships between tools and content. Next.js

2026-08-24 原文 →
AI 资讯

Cloudflare OS: Cloudflare's Open-Source Corporate AI Platform Built on a Capability-Based Model

Cloudflare recently open-sourced Cloudflare OS. It allows enterprise teams to output work artifacts grounded in enterprise knowledge, know-how, and provisioned connectors, automate repetitive workflows with optimized token cost (with AI assistance only where needed), and build personal, shareable, customizable work software that caters to specific, complex use cases within a secure sandboxed model By Bruno Couriol

2026-08-24 原文 →
AI 资讯

Why I Built an Ad-Free Alternative to Untappd

I've used Untappd for years to log the beers I drink. It works. It also drives me a little crazy every time I open it. Between the ads wedged into my feed, the check-in pressure that makes logging a beer feel like a social performance, and an interface that's accumulated more features than I've ever asked for, opening the app to do one simple thing — "I liked this beer, I want to remember it" — started to feel like more work than it should be. So a few weeks ago, I decided to build my own. The idea: Letterboxd, but for beer If you haven't used Letterboxd, it's a film-logging app that took a genre Untappd basically also occupies — "social logging app for a hobby" — and did it with a fraction of the clutter. Clean, fast, personal-journal-first, social-second. That's the model I wanted for beer. I called it HopLog. The pitch, in one sentence: log what you drink, remember what you liked, discover something new — without ads, without check-in pressure, without a hundred features you'll never touch. Building it like an actual product, not just a weekend hack I didn't want to just start writing code and see what happened. Before a single line was written, I worked through the process a real product team would use: A product requirements doc — what's actually in scope for a first version, and just as importantly, what's not User personas — who is this actually for? (Turns out: the casual drinker who wants a nice photo journal, the homebrewer who wants precise tasting notes, and the traveler hunting for good local breweries — three genuinely different people with different needs) User stories, wireframes, a database schema, an API design, and a milestone-by-milestone roadmap Only after all of that did I start building — six milestones, one at a time, each one tested and verified before moving to the next: authentication, a real beer/brewery database, the actual tasting-logging flow, profiles with stats and badges, a social layer with feeds and follows, and finally search pol

2026-08-24 原文 →
AI 资讯

Enforcing a style rule with a linter that actually fails the build

Background I run a fleet of static sites that publish new content every day, mostly unattended. One of the house style rules is simple: no emoji anywhere in our own copy. That rule is impossible to hold by hand. A single site builds a few hundred HTML files, and emoji can slip into nav icons, button labels, <title> , the RSS feed, or JSON-LD (the JSON-formatted metadata embedded in a page to describe its structure to search engines). Nobody is going to review all of that before every deploy. So I wrote emoji-lint , a check that exits 1 the moment it finds a single emoji . It sits in the pre-deploy gate, which means a failure stops that day's publish. This post is not about the regex. It's about what happens when you put a failing check into real operation: you immediately discover the places where the rule must not apply. How it works The core is unremarkable. A regex holds the emoji code point ranges, the scanner walks each file line by line, and matching lines are reported as JSON. const EMOJI_RE = / [\u {1F000}- \u {1FAFF} \u {2600}- \u {27BF} \u {2B00}- \u {2BFF} \u {1F1E6}- \u {1F1FF} \u {FE0F} \u {200D} \u {2049} \u {203C} \u {2122} \u {2139} ] /u ; \u{FE0F} (variation selector) and \u{200D} (ZWJ) are in there because emoji are not always a single code point. Arrows and similar symbols used in ordinary technical writing are deliberately left out. Catch everything and the check drowns in false positives, at which point people stop reading it. The interesting part came later. Three categories of content look exactly like a violation but must not be treated as one: Verbatim quotes from other people Real proper nouns whose official spelling contains a symbol Passages where the emoji itself is the subject being explained Delete the emoji in any of those and you break something more important than the style rule. One term up front: "masking" here means replacing a range with spaces so the scanner cannot see it. Nothing is deleted from the file. Implementation Scope

2026-08-24 原文 →
AI 资讯

Don't validate the output. Validate that you were allowed to generate it.

Introduction I run a site that collects overseas viewer comments about individual anime episodes, translates them, and publishes them. It updates automatically every day. There is one failure mode that matters more than all the others: creating a page for an episode that has not aired yet. If the page exists before the broadcast, there are no comments to put on it. But the heading "Episode 8 — overseas reactions" is already live. An empty page is recoverable. What is not recoverable is a pipeline that decides an empty page looks bad and fills it with something plausible. At that point invented sentences are wearing the face of real people. This post is about the check that prevents that, and about the day it actually fired. The overall shape The obvious implementation is arithmetic on dates: Take the air date of episode 1 Assume weekly broadcast Count the weeks elapsed until today Treat every episode up to that number as aired That works for producing candidates, but it is not evidence that anything aired . A skipped week makes the real count lower. So does a recap episode. Calendar arithmetic never observes the broadcast; it only restates an assumption. So I split the pipeline in two: Candidate generation — weekly arithmetic, guessing which episodes are missing. Guessing is fine here. Existence check — does an observation from outside my system exist for this episode? No guessing allowed here. For the existence check I use the per-episode discussion threads on MyAnimeList (a large anime database; MAL from here on). One thread is created per episode after it airs, and the timestamp of the first post in that thread is readable. Viewers post after watching. So that timestamp is external evidence that the broadcast happened. Better still, it lives in exactly the same place I fetch the comments from, so verification costs no additional data source. The core of the implementation The check is a subtraction between what I claim and what the outside world recorded. /** * @

2026-08-24 原文 →
AI 资讯

The Counter That Counted a Call the Preflight Never Reached

This is a submission for DEV's Summer Bug Smash: Clear the Lineup , powered by Sentry . Project Overview I was working on a small Python component that performs a preflight check and then, if the check succeeds, invokes one synchronous operation callback. A counter records whether that callback invocation returned normally. The counter is used for diagnostics, so it must follow the control flow rather than the expected happy path. Bug Fix or Performance Improvement When a handled failure occurred, the old implementation still returned one: return 1 That value was hard-coded because the successful path was expected to invoke exactly one operation. If the preflight check failed, however, the operation was never entered and the function still returned one. An offline reproduction produced: operation_entries=0 old_count=1 The failure was handled, but the counter contradicted the actual control flow. Code Reduced to the relevant lines, the old behavior was: # Simplified pre-fix behavior def buggy_completed_calls ( * , preflight , operation ): try : preflight () operation () except Exception : pass return 1 Here is the complete fixed function from the standalone reproducer: from collections.abc import Callable Callback = Callable [[], None ] def completed_calls ( * , preflight : Callback , operation : Callback ) -> int : """ Return one only when the cooperative operation returned normally. """ try : preflight () operation () except Exception : return 0 return 1 The essential regression assertion is shown below. Both callbacks are local, so the test performs no network request: # Abbreviated test excerpt def test_preflight_failure_does_not_count_an_unentered_operation (): operation_entries = 0 def refuse_preflight (): raise RuntimeError ( " controlled preflight refusal " ) def operation (): nonlocal operation_entries operation_entries += 1 result = completed_calls ( preflight = refuse_preflight , operation = operation , ) assert operation_entries == 0 assert result == 0 My

2026-08-24 原文 →
AI 资讯

.NET 10 NU1015: Fix PackageReference Without Version Restore Failures

.NET 10 NU1015 turns a PackageReference without a version into a restore error. I like the stricter default because an unbounded direct dependency can quietly resolve the lowest package version. The catch is that versionless XML is also the correct shape for NuGet Central Package Management (CPM). A mechanical “add Version everywhere” repair can undo the policy your repository intended to enforce. I use a simple split: first decide who owns the version, then make restore prove the answer. Why .NET 10 NU1015 stops the build Before .NET 10, NuGet reported NU1604 when a direct reference had no inclusive lower bound. Restore could continue and select the lowest version available from the configured sources. Starting with .NET 10, the same mistake produces NU1015 and restore fails. Microsoft documents this as a stable behavioral change in the .NET 10 compatibility guidance . Here is the ambiguous project entry: <ItemGroup> <PackageReference Include= "Demo.Greeting" /> </ItemGroup> If this is a normal direct reference, the project is missing its version. If CPM is active, the project is correct and the version should live elsewhere. The NU1015 diagnostic reference calls out a common failure mode: a project that expected CPM was copied into a location where CPM is disabled or its props file is no longer discovered. That distinction matters more than silencing the error. It tells me whether the project file or the repository-level package policy is broken. The timing can be misleading. An SDK upgrade may expose an old direct reference that had always relied on lowest-version resolution, while a repository move may break a previously valid CPM import. I inspect the failing project's evaluated inputs, nearby props files, and recent path changes before editing package metadata. That keeps a restore migration from turning into an accidental package-management migration. Fix the owner, not only the XML For a direct reference, I add an explicit version: <PackageReference Include=

2026-08-24 原文 →
AI 资讯

How I Built Smart Scraper M2M: A Fast ~30ms Scraper API for AI Agents

Building AI Agents with frameworks like CrewAI or LangChain often hits a bottleneck: heavy, slow web scraping that bloats context windows and increases LLM token costs. To solve this, I built Smart Scraper M2M — a lightweight, high-performance web scraper API designed specifically for machine-to-machine (M2M) communication. 🌟 Key Features ⚡ Ultra-fast: Returns clean structured JSON in ~30ms . 🧠 Context-optimized: Strips out useless HTML/CSS junk so your LLMs process only relevant data. 🤖 Agent-friendly: Built to integrate seamlessly into CrewAI, LangChain, or custom Node.js agents. 🚀 Quick Start You can test the API or check the full source code directly on GitHub: 🔗 GitHub Repository: https://github.com/MRIGL/smart-scraper-m2m 💬 Feedback & Community I’m actively improving the API and would love to hear your thoughts, feature requests, or contributions! Feel free to star the repo or leave a comment below.

2026-08-24 原文 →
AI 资讯

Too Many Req: A Bucket List Guide to Building a Rate Limiter

Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github. Star git-lrc to help devs discover the project. Do give it a try and share your feedback. Every serious API will eventually tell you to sit down and be quiet. Hammer GitHub, Stripe, or AWS a little too eagerly and your requests start bouncing back with a polite but firm 429 . I always found that fascinating, so let's build the thing that says no. By the end of this post we'll have designed a rate limiter that actually holds up when you put it in front of real traffic, and I promise to only make a reasonable number of bucket puns along the way. A rate limiter does one job: it decides how many requests a client is allowed to make in a given window of time. It protects your system from getting flattened, and it keeps one greedy user from eating everyone else's lunch. Simple idea. Surprisingly spicy implementation. Let's build it up piece by piece, the way you'd actually reason through it in an interview or a design doc. First, what are we even building? Before writing a single line, let's agree on what "good" looks like. Here's my wishlist: Configurable limits. Something like "100 requests per minute per user." The rules should not be hardcoded, because free users and premium users deserve different amounts of pain. Honest rejections. When someone goes over, we return HTTP 429 Too Many Requests and include helpful headers telling them how many requests they have left and when the window resets. No mystery. Barely-there latency. This check runs on every single request , so it has to be fast. Let's aim for under 3ms at P95. If your rate limiter is slow, congratulations, you built a second bottleneck. Highly available and shared. Multiple servers need to agree on the same counts. More on why that word "shared" is doing a lot of heavy lifting later. Cool. Now let's start naive and let reality punch us in the face a few times. Attempt 1:

2026-08-24 原文 →
开发者

thumb: popup images and render LaTeX directly in Vim

I made a small Vim 9.2+ plugin called thumb . Put the cursor on an image path → :Thumb → popup the image. Select LaTeX in Visual mode → :Thumb → render it as an image popup. For example: ![diagram](images/diagram.png) Put the cursor on diagram.png and run: : Thumb Or select: \frac { a }{ b } = \sqrt { x ^ 2 + y ^ 2 } and run: : Thumb It uses Vim's native popup image support, with Python/Pillow for image conversion and matplotlib for LaTeX rendering. No mappings are installed, so you can add your own: nnoremap < leader > t < Cmd > Thumb < CR > xnoremap < leader > t < Cmd > Thumb < CR > GitHub: https://github.com/JosefAlbers/thumb Requires Vim 9.2+, Python 3, Pillow, and matplotlib. Feedback welcome, particularly around the popup positioning/rendering.

2026-08-24 原文 →
AI 资讯

Keeping Mac work alive without pretending awake means safe

A developer usually meets Mac power management through a simple need. A build, local server, download, or agent is still running, and idle sleep would interrupt it. The caffeinate command can be enough for that open lid case. Lid close is a different boundary. An idle sleep assertion does not mean the same thing as a closed display session, and a product should not blur the distinction. I built Afterlid around three explicit states. Sleepy follows normal sleep. Awake prevents idle system and display sleep while the lid is open. Always On is the lid closed mode, with the display off. The important engineering work begins after activation. What happens if the app crashes? What happens when the battery is falling or the machine is under thermal pressure? What state is restored after a helper failure? For Afterlid, Always On ends at 30 percent battery while unplugged, under serious or critical thermal pressure, when the app heartbeat disappears, or after eight hours. When a limit fires, the app drops its wake assertion and returns the Mac to normal sleep behaviour. The current implementation uses a small privileged helper and an undocumented macOS sleep setting for the lid closed path. That makes broad hardware testing and honest release notes essential. It is not something I want to hide behind a friendly menu bar character. A useful principle emerged from the work: activation is a feature, but recovery is the product. If you are building a system utility, test the path back to the operating system defaults with the same seriousness as the path into your special mode. Founder disclosure: I built Afterlid. The full product and current boundaries are here: AfterLid

2026-08-24 原文 →