AI 资讯
Buying a phone number is a distributed transaction
The API makes it look trivial. const number = await carrier . numbers . buy ({ phone_number : " +1... " }); await db . insert ( " rented_numbers " , { user_id , e164 : number . phone_number }); await stripe . subscriptions . create ({ customer , price }); Three lines, one number, done. Ship it. What you actually wrote is a distributed transaction across three systems. They share no transaction log, they have no two-phase commit, and none of them can roll back the others. The carrier will keep charging you for a number your database has never heard of. Stripe will stop charging for a number your database still thinks is paid up. Neither one is going to mention it. I run a virtual phone number product. Below are the failure modes that actually cost us money, roughly in order of how much. The orphan taxonomy Write down the states first, because the interesting ones are the states nobody designs for. Three systems, each holding an opinion about a single number: Your DB Carrier Stripe What is actually happening active owns it active The happy path. Rare in the tail. no row owns it nothing You pay monthly rent on a number nobody can see or use. active released active You bill a customer for a number you no longer own. pending_cancellation owns it canceled Customer stopped paying. You are still paying the carrier. active owns it canceled You provide service for free, indefinitely. cancelled owns it canceled Release failed at teardown. Silent monthly bleed. Every row under the first one is reachable from a plain network timeout at a bad moment. The first orphan class is the worst, because you cannot see it from inside your own product. No row, no user, no support ticket. The number sits in the carrier's inventory producing an invoice line every month until somebody actually reads the invoice. The second class is the one that generates a complaint. The rest leak money in one direction or the other, quietly. Reconcile, don't prevent The instinct is to armour the write path. S
AI 资讯
Clean code isn't what I thought it was
What working on real systems taught me about maintainable code. My second job was the first time I worked with an international team where everyone had ten or more years of experience. I had maybe two. It was also the first time I was part of proper code reviews, branching strategies, and pull request workflows. Everything felt new and slightly intimidating. One of my first tasks was adding spacing between two elements. It should have been a simple margin or padding change, but I added a <br> tag instead. The feedback on that PR was polite but clear, and it made me a little embarrassed. That moment, along with dozens of similar ones, made me want to get better. I started reading about clean code and caring deeply about how my code looked. Small functions, no repetition, everything abstracted and organized. For a while, that served me well. It helped me grow from a junior developer into someone who could write code that passed review without a wall of comments. But over time, as I worked on larger systems with real users and real constraints, I started noticing that the rules I had learned didn't always hold up. Sometimes the "clean" approach made things worse, and sometimes messy-looking code worked better than the elegant version I would have written. This post is about how my definition of clean code expanded. I still believe in the principles I learned early on. I'd just add a few things to them now. What I thought clean code meant When I first started paying attention to code quality, my idea of clean code was mostly about appearances. If the code looked organized and followed certain patterns, it was clean. If it didn't, it wasn't. I believed in small functions for everything. If a function was longer than fifteen or twenty lines, something was wrong. I would extract pieces into helpers even when they were only used once, just because the parent function felt "too long." I was strict about DRY. Any time I saw similar logic in two places, I would immediately pul
AI 资讯
I Gave Five Graph Databases 256MB of RAM Each. Here's What Broke.
I Gave Five Graph Databases 256MB of RAM Each. Here's What Broke. CognoDB Cloud's free tier gives you a graph database instance with half a CPU core and 256MB of RAM. That's not a lot. It's also, honestly, a pretty realistic starting point a lot of real side projects and early-stage products live exactly there, on whatever the free tier happens to give them, and find out the hard way what their database does under pressure. So I decided to actually find out. I took CognoDB and lined it up against four other graph databases Neo4j AuraDB, FalkorDB, and ArangoDB gave every single one of them the same tiny resource budget, threw the same 198,050-edge dataset at all of them, and ran the same queries. No cherry-picking, no "best case" numbers. Just: here's a small VM's worth of resources, go. One of the databases I originally planned to include never even made it into the results. It crashed on startup. Not "slow to start" a full segfault, reproducibly, across two different versions, with nothing I threw at it fixing it. More on that below, because it's honestly one of the more interesting parts of this whole thing. The setup, quickly Five candidates going in: CognoDB (mandatory, since that's the actual point of this), Neo4j AuraDB Free, Memgraph, FalkorDB, and ArangoDB. Same dataset for all of them a real social-graph-shaped dataset from Stanford's SNAP collection, ~18.7k nodes and ~198k edges, sized specifically to fit inside every platform's free tier without anyone getting an unfair advantage. Same queries too: I wrote every single query 1-hop, 2-hop, 3-hop traversals, point lookups, filtered lookups, aggregations exactly once, then translated each one into whatever query language a given platform actually speaks. No platform ever got a "friendlier" version of a query than another. And everyone ran under the same 0.5 vCPU / 256MB RAM ceiling, whether that was their real cloud free tier or a Docker container I capped by hand to match. The one that didn't survive Memgra
AI 资讯
Building File Utilities That Run 100% in the Browser
I recently built filetools, a suite of file utilities that run entirely in the browser. No server backend, no file uploads, no data collection. The Problem Existing tools for CSV extraction, PDF manipulation, and table conversion often require uploading files or creating accounts. That creates friction and privacy concerns. But these tasks are fundamentally simple: extracting text from a PDF or parsing a CSV can happen entirely in JavaScript. The Solution filetools is a collection of single-purpose utilities: PDF Tools: Merge, split, rotate PDFs Extract tables from PDFs to CSV Convert bank statements to CSV Data Tools: Extract tables from HTML to CSV or JSON Convert between XLSX, JSON, YAML, and CSV Remove duplicate lines, sort CSV files, merge/compare data files Each tool is its own page, targeting one specific task without bloat. Architecture Why static hosting? Keeps infrastructure simple and costs near zero. Files are built once, served from GitHub Pages. Why client-side only? User files never leave their machine. Processing is fast (no network round-trip). Privacy is the default. Tech stack: vanilla JavaScript using npm libraries (pdfjs-dist, exceljs, js-yaml, pdf-lib) - no framework, no server. Each page is roughly 5-15KB gzipped. Design: Started with demand mining, looking at actual Google search queries and autocomplete suggestions to pick which tools to build first. What's Next Live site: https://usefiletools.com/?utm_source=dev.to&utm_medium=article&utm_campaign=filetools-launch I'm building more tools based on real search demand. If there's a file utility you've always wished existed, especially for data professionals, I'd love to hear about it.
AI 资讯
Cleaning Up Feature Flags: The Art of Not Leaving a Mess
You said you'd remove that flag after launch. You lied. It's been six months and the flag is still in appsettings.json , the if statement is still in your controller, and nobody remembers which state is "on." This is how codebases turn into haunted houses. Why Cleanup Matters Dead feature flags are technical debt with teeth . They add branches to your code that nobody tests. They confuse new developers who don't know the history. They inflate configuration files and make deployments harder to reason about. And they compound. Every flag you don't clean up makes the next cleanup harder because the cognitive load of understanding the system keeps increasing. The cost of removing a flag is lowest immediately after the feature ships, while everyone still remembers what the thing does. Six months later? Good luck. Track Every Flag You can't clean up what you can't find. Maintain a registry of every active feature flag with: Name Purpose Owner Date created Expected removal date This can be a spreadsheet, an issue tracker, internal documentation, or a dedicated feature flag management system. The format doesn't matter nearly as much as the habit. When you add a flag, add it to the registry. When you remove a flag, remove it from the registry. If your registry contains flags with no owner or no removal date, congratulations: you've found your next cleanup project. Set Expiry Dates Every flag should have a planned removal date when it's created. For example: Release toggles: Remove shortly after the feature ships. Two weeks is a reasonable default. Experiment toggles: Remove when the experiment concludes. Ops toggles: May be permanent by design. Permission toggles: May also be permanent, but document that explicitly. If a flag has been alive longer than its planned expiry and nobody deliberately extended it, it's already a zombie. Treat it accordingly. Make Cleanup Part of the Process Flag cleanup doesn't happen unless someone owns it. Add a cleanup step to your feature compl
AI 资讯
Your agent isn't reckless. It just can't see the blast radius.
I've been running Claude Code as a daily driver for about three months now. It writes Ansible I'd have taken a week to write. It reads a codebase faster than I do. It is, genuinely, very good. It also once wanted to force-push to main , and it wanted to for an extremely good reason. Sit with that for a second, because it's the whole post. The rebase was stuck. Force-pushing would have unstuck it. Every link in that chain of reasoning is sound. The agent wasn't being careless, wasn't hallucinating, wasn't "drifting" or whatever we're calling it this month. It made a locally correct decision with a non-local consequence, which is the exact category of mistake that human code review is worst at catching — because the diff looks fine . It could see the command. It could not see the crater. The thing I stopped doing For a while my answer was to read everything. Every diff, every command, eyes on the screen, hand hovering over Ctrl-C like a man watching a toddler near a staircase. This does not scale, and the reason it doesn't is embarrassing when you say it out loud: reviewing output scales with how much the agent writes. That number is going exactly one direction, and it isn't down. So I flipped it. Instead of reviewing what it produces, I started writing down what it must never do. And here's the good news that took me way too long to notice: that list is short . Not "short for a security policy" short. Short like you can fit it on a napkin. Here's mine: A credential it read an hour ago gets inlined into a source file. A rebase gets stuck, and the fastest route to a green terminal is git push --force origin main . rm -rf "$BUILD_DIR/" runs on the one machine where BUILD_DIR never got set. A version bump gets typed straight into package-lock.json , because that's the file the version number is visibly in. A failing test quietly grows a .skip and CI goes green. Someone runs cat .env "just to see which variables exist." That last one is my favourite, and I'll come back to
AI 资讯
LAB now ships a free Idea Feed: rule-shaped trading ideas, deliberately untested
A small release, not a launch. The LAB tab on gex.live has a new rightmost rail called IDEA FEED . It is a stream of short, rule-shaped trading ideas about SPX dealer positioning — "fade the first touch of the call wall after a gap up", that kind of thing — collected daily by a scanner from what people actually discuss, rewritten into something the Lab compiler can parse, and published untested . That last word is the point. Why untested is the feature Every feed of trading ideas on the internet comes with a verdict attached: "this works", "78% win rate", a screenshot of a good month. The feed here refuses to do that. Each card says exactly two things about its idea: compiles clean (our compiler turned the text into a runnable rule without complaint) and untested (nobody has run it against the archive yet). The honest test is yours to run. One click drops the idea into the Lab conveyor. The compiler has already done the translation, so the first message in your session is the rule itself, stamped ↳ from IDEA FEED · compiles, untested . Running the backtest costs one Lab credit; a failed job refunds itself. If your balance is zero the button does not go dead — it turns into 0 CREDITS · BUY → , remembers the idea you picked, and comes back to it after. What you will not find No source attribution on the cards. The idea is the unit, not the poster. No win rates, no "rated", no thumbs. The archive is 1,000+ finished SPX sessions; the Lab tests against all of it with an out-of-sample split and tells you what survived, which so far is: very little. That verdict is worth more than a badge on a card. No approval gate. The scanner's finds ship directly every day, so the feed stays fresh by itself. "NEW" is personal — it means new since you last opened the rail, not new for everyone. Why build a feed that mostly produces "no" Because the alternative is pretending. The whole site is built on measuring dealer positioning from the tape instead of assuming it from yesterday's ope
AI 资讯
The Lab: a backtester that is allowed to say "no"
gex.live has two halves. The terminal measures where SPX options dealers are positioned, every second, from the tape. The Lab is the half that asks the uncomfortable question: does any of that predict anything? What it is A browser-side conveyor with three stages and a credit meter. Compile. You describe a rule in plain text — "short the first touch of the put wall when net gamma is below the 20th percentile" — and the compiler turns it into a deterministic rule over the archive's fields: flip, walls, hold band, gamma percentile, DEX/VEX/vanna/charm per strike, time of day. Compiling is free. If the text is ambiguous the compiler says which part, instead of guessing. Backtest. The rule runs against the full session archive — 1,000+ finished SPX days, every one of them public at gex.live/sessions — with a fixed out-of-sample split. One credit per job; a job that fails refunds itself. Quant optimize. Optional. A LightGBM pass over the same feature store to see whether there is structure the hand-written rule missed, reported as out-of-sample AUC plus feature importance, not as a new "signal". The heavy part (DuckDB + LightGBM) runs in a scale-to-zero container that reads snapshots over HTTPS from the public archive. It depends on no machine and on no private data, which is the point: you are testing against the same files anyone can download. The honest-stats rule Every verdict comes with its baseline. "Your rule made 3% in-sample" means nothing next to "the unconditional drift over the same days was 2.8%". The report shows both, shows the out-of-sample half separately, and refuses to produce a headline number from the in-sample half. Most rules do not survive this. That includes our own: the site's own directional levels were tested three separate ways across the whole archive and none held out of sample — which is why the terminal sells measurement and not signals, and why the Lab exists at all. The free Idea Feed Next to the conveyor sits a rail of rule-shaped idea
AI 资讯
Backtest SPX dealer-gamma rules from your AI assistant
gex.live has an MCP server. Add it to Claude, Cursor, ChatGPT or any MCP client and the assistant can read the dealer-positioning archive and drive the backtesting Lab on your behalf. The one-line version is the title. Here is the rest. Two tiers, one rule: free data stays free Free, no key — the same finished-session data that is free on the website: list_sessions — finished SPX sessions in the archive, newest first, paged (max 50 a call). get_session(day) — one session's dealer-positioning summary: OHLC, the zero-gamma flip and how often price crossed it, call/put walls, the hold band, net-gamma percentile, ATM IV at the open. Measurements only. get_levels(day) — just the level set for one session — flip, call resistance, put support, hold band — plus where the session closed relative to them. Keyed — the Lab, metered in credits exactly as on the site. These tools only appear in the tool list once the client sends a Lab token: lab_compile(message) — turn a plain-words idea ("fade a +3 sigma stretch above vwap on top-decile volume") into a testable rule. Free of credits, needs a positive balance. Returns the compiled spec, a clarifying question, or compile errors — never a guess. lab_run(id, kind) — one conveyor step: backtest first (rule → tested), then quant (the LightGBM optimize, tested → ready). One credit, refunded on failure. The result is the engine's honest verdict: per-leg era tables — all / this year / holdout. lab_state — your whole Lab in one call: ideas with stages and results, which idea holds the conveyor, your credit balance. lab_thread(id) — the compile-chat thread for one idea. lab_idea(id, action) — desk actions: put a ready idea on the desk, drop it back to the start, delete, or set its desk display/alert options. When a keyed tool is called without a token, the error is the instruction: what it does, where to get a key (gex.live/account → LAB & API, shown once, scoped to the Lab only, revocable), what it costs. The assistant relays it verbatim
AI 资讯
AI Killed Git Commits: So I Stopped Publishing Them
Today I shipped contenox 1.0.0. Not by pushing a tag on top of a thousand commits, but as a single commit into an empty repository: the whole tree, one signed tag, binaries built from that tag by CI. The 957 commits that got me there are still public, in the old repository, as history. They are no longer how the project is published. This post is about why, and about what went wrong before I had finished reading the result back. What a commit used to mean GitHub's workflow rests on four assumptions so old that nobody states them any more: A commit is a unit of human intent. Someone decided something and typed it. A pull request is a unit of review. A human reads the diff, because a human wrote it. History is provenance. Who changed what, when, and — through the message — why. Timestamps are labor. The contribution graph on your profile is a diary. All four were true in 2008. For a tree that agents write, none of them survive contact. What my repository actually looked like Some numbers from a tree you can inspect yourself: 957 commits in just over a year, most of them named Checkpoint , Fix tests , Snapshot WiP . Dozens on a busy day. The production Go grew from 17,267 hand-written lines to 134,040 agent-assisted ones. Measured, not estimated. The median file stayed the same size; the number of files and packages did not. At one point 530 uncommitted paths sat in a single working tree. Inside that blob, the file that carried the repository's own conventions had been deleted. Nobody noticed for days, because nobody reviews a 530-file diff. A commit stream like that is not history. It is a log. Reading it tells you nothing about what a human decided — the decisions happened in prompts, in agent declarations, in a policy file — and it tells you one thing with great precision: when the work happened. If you also do client work, a public commit stream is a timesheet you never agreed to publish. Review had quietly inverted, too. I was no longer reviewing commits. I was re
AI 资讯
I Ran 157 Agent Plans Against a Real LLM. The Problem Wasn't Execution. It Was Planning.
I thought I was building a better planning engine. What I actually built was a machine for showing...
AI 资讯
Enterprise MCP Gateway Solutions: Providers, Alternatives, and Cost 💎
Your company uses six different AI providers. OpenAI for ChatGPT, Anthropic for Claude and Groq for speed critical inference. Each one has different API formats. Different authentication models. Different rate limits and costs. Different failure modes. Your application code has to know about all of them. Your security team has to audit requests across all of them. Your finance team has to track costs across all of them. Your compliance team has to ensure governance across all of them. Bifrost Gateway solves this by doing what HTTP gateways have done for decades: centralizing control . But for AI. 👀 What is an MCP gateway? Model Context Protocol (MCP) is an open standard that lets AI models discover and execute external tools at runtime filesystems, web search, databases, ticketing systems, and custom business logic instead of being limited to text generation. An MCP gateway sits between your applications (or external MCP clients like Claude Desktop and Cursor) and the upstream MCP servers. Instead of each client maintaining its own connections, credentials, and tool lists, the gateway: Aggregates tools from multiple MCP servers into one registry Applies governance : authentication, tool filtering, budgets, and rate limits Exposes a single endpoint that external MCP clients can connect to In Bifrost, this pattern is implemented in two complementary roles: Role What it does MCP Client Connects to external MCP servers via STDIO, HTTP, or SSE MCP Server (Gateway) Exposes aggregated tools at /mcp for Claude Desktop, Cursor, and other MCP-compatible clients Bifrost is both an AI gateway (routing LLM traffic to 20+ providers) and an MCP gateway (connecting to and exposing tool servers). The open-source gateway covers virtual keys, budgets, rate limits, routing, and MCP tool filtering. Bifrost Enterprise adds RBAC, SSO, audit logs, MCP Tool Groups, guardrails, clustering, and in-VPC deployment options. ⚙️ How does an MCP gateway work? Connection layer Each upstream MCP serv
AI 资讯
🎬 Reel Quick now has a live animated demo in the GitHub README
The demo gives a quick look at the workflow for creating short-form videos with trimming, stitching, text overlays, voice tools, themes, and transitions. Built with FastAPI, Next.js, Redis/ARQ, and FFmpeg. Repo: https://github.com/ronin1770/reel-quick OpenSource #Python #FastAPI #NextJS #FFmpeg #VideoAutomation #DeveloperTools #AI
AI 资讯
How to Create Your Own Claude Code Skill With SKILL.md
If you use Claude Code for frontend development, you may have noticed something. Claude can write code very fast. But sometimes the UI it creates looks too similar to other AI-generated websites. You get the same rounded cards, large headings, soft shadows, gradients, and simple layouts. The code works. But the design does not always feel like your own. Hi everyone, I am Henry. In this article, I want to show you a simple way to fix that. We are going to create our own Claude Code Skill using a SKILL.md file. You do not need to build a complicated tool. You just need a clear set of instructions that Claude can follow when working on your frontend. What Is a Claude Code Skill? A Claude Code Skill is a reusable set of instructions for a specific type of work. For example, you can create a skill for: Frontend design Testing Documentation Code review Database work DevOps UI accessibility For this tutorial, we will create a frontend design skill . Our goal is simple: Help Claude create clean frontend UI without falling back to the same generic design patterns. Instead of writing the same design rules in every prompt, we can keep them inside a skill. Step 1: Create the Skill Folder Open your project in the terminal. Create a .claude folder if you do not already have one. Then create a skills folder: mkdir -p .claude/skills/frontend-design Now create the skill file: touch .claude/skills/frontend-design/SKILL.md Your project should now look something like this: your-project/ ├── .claude/ │ └── skills/ │ └── frontend-design/ │ └── SKILL.md ├── src/ ├── package.json └── README.md The important file here is: SKILL.md This is where we will put our instructions. Step 2: Write Your SKILL.md Open the file: code .claude/skills/frontend-design/SKILL.md Now add the following: --- name : frontend-design description : Build clean, responsive frontend UI with simple and consistent design rules. --- # Frontend Design Rules Before writing UI code: 1. Understand the purpose of the page. 2.
AI 资讯
Puppet Core 9.0 and 8.21 Released: Ruby 4.0, OpenSSL 3.5, Platform Changes, and Security Hardening
Did you know there's a new major version in town for Puppet Core? You might have heard about it through the grapevine or in the Are You Ready for Puppet 9? webinar that @gpatton and I recently hosted. The wait is over and Puppet Core 9.0.0 is now available alongside Puppet Core 8.21.0. Puppet Core 9 introduces significant runtime and platform changes, moving to Ruby 4.0, OpenSSL 3.5, and other changes, but the essential Puppet under the hood is largely unchanged from Puppet 8. The majority of upgrade effort will center on Ruby 4 compatibility and runtime dependency changes rather than Puppet language changes. If you are staying on the Puppet Core 8.x release track, the latest Puppet Core 8.21 delivers the basic support fixes and security improvements you might need without the major dependency changes found in Puppet Core 9. What matters most for the admins Before upgrading to Puppet Core 9: Test custom facts, functions, types, and providers against Ruby 4.0. Validate any Forge modules you use for Ruby 4 compatibility. Review integrations that depend on OpenSSL behavior. Verify any workflows that still rely on SHA-1. Confirm managed nodes are running supported operating systems. Review any custom code that depends on PSON or multi_json . Check deferred function behavior if you have custom types or providers. Perforce will be rolling out updates to Puppetlabs modules on the Forge based on their priority tier and dependencies. The first batch of these should be rolling out soon. Puppet Core 9.0 highlights These are a few highlights I pulled from the release notes. Make sure to reference the full 9.0 release notes to get all the details about what has changed! Ruby updated to 4.0.5: With a new Ruby baseline some deprecated syntax from older Ruby versions will no longer be compatible. This is the primary focus area for upgrades as you will want to validate your custom code and modules. The latest PDK 3.8.0 introduced some Ruby 4 validators to help you update your syntax
AI 资讯
Connect a Carrd Landing Page to Payhip Without Building a Backend
Affiliate disclosure: I’m an independent Payhip Partner. The optional signup link at the end is my partner link; I may receive a commission from Payhip if a referred seller generates eligible revenue. I am not a Payhip employee or official representative. A creator selling one template or downloadable guide does not need to write a payment backend. The safer architecture is usually: Carrd or static page ↓ Payhip product page or direct checkout ↓ Hosted payment and product delivery Your public page explains the offer. The hosted commerce platform owns the payment flow. No card data, secret keys, or payment logic belongs in Carrd. This tutorial shows two link-based integrations and one optional embed route. Before you start You need: A published product in Payhip Its public product URL A button on your Carrd or static page A clear product description, support contact, and terms In Payhip, the product URL is available from the product’s Share / Embed controls. A typical product URL has this shape: https://payhip.com/b/PRODUCT_KEY Use your real product key in every example below. Option 1: Send visitors to the product page This is the safest default when the buyer still needs details before purchasing. In Carrd: Select the call-to-action button. Set its URL to your full Payhip product URL. Use a descriptive label such as View template details or See what’s included . Preview the page on desktop and mobile. On a conventional static site, the equivalent HTML is just an anchor: <a class= "product-button" href= "https://payhip.com/b/PRODUCT_KEY" > View product details </a> No JavaScript is required. Use this route when the Payhip product page contains important previews, license terms, compatibility notes, or variations that do not fit on your landing page. Option 2: Link directly to checkout If your landing page already gives the buyer everything needed to decide, a direct checkout removes an intermediate page. Payhip documents this URL format: https://payhip.com/buy?link=
AI 资讯
Presentation: Why Fetch When You Can Sync? Building Local-First Apps on a Sync Engine Architecture
James Arthur shares why sync is the next frontier in frontend architecture. He explains how extending reactivity to the server with Electric and TanStack DB replaces imperative fetching with declarative data bindings. Learn how query-driven sync and local optimistic updates enable engineering leaders to build insanely fast, collaborative, and agentic applications using their existing stack. By James Arthur
AI 资讯
PRINCÍPIO DA SUBSTITUIÇÃO DE LISKOV
Uma classe mãe deve ser capaz de ser substituída pelas suas classes filhas sem que a aplicação quebre. Isso na prática ajuda a organizar a ideia de herança, já que nos faz evitar estender uma classe mãe, apenas para depois remover um método já implementado ou fazer um “throw new Error(‘Not implemented’)”. Fazendo com que tenhamos mais cuidado no planejamento. O MAIOR SINTOMA DE ERRO Infelizmente é um sintoma que aparece de forma tardia, mas é justamente quando vamos fazer uma nova implementação. Você percebe que feriu o Liskov quando você vai construir uma classe ou subclasse e precisa lançar um erro proposital na implementação de um método. Justamente porque aquele método não deveria estar ali, mas está. UM EXEMPLO RUIM Por exemplo em um sistema de entregas. Nesse caso a classe “Delivery” deveria ser a mãe/base para as demais implementações. Mas a classe ‘MotoboyDelivery’ quebra isso. Exemplo de Código: // RUIM: A subclasse quebra o contrato da classe mãe. class Delivery { public calculateShipping (): number { return 15.0 ; } public getTrackingCode (): string { return " TRK123456789 " ; } } class MotoboyDelivery extends Delivery { public calculateShipping (): number { return 8.0 ; } // ERRO! Não tem código de rastreio. public getTrackingCode (): string { throw new Error ( " Motoboys não possuem código. " ); } } A SOLUÇÃO Para quem ainda não conhece o 'Liskov Substitution Principle', pode parecer que encaixar uma sequência de ifs é a solução. Mas na verdade o caminho ideal é repensar como essa abstração é construída. Um bom norte é pensar que uma classe filha sempre deve ser capaz de substituir o lugar da mãe, sem quebrar a aplicação. UM EXEMPLO BOM Ainda no sistema de entregas. ‘Delivery’ agora tem no meio do caminho ‘TrackableDelivery’. Com isso, cada “folha”/ponta da aplicação herda quem faz mais sentido e nada é quebrado. Exemplo de Código: interface Delivery { calculateShipping (): number ; } interface TrackableDelivery extends Delivery { getTrackingCode (): st
开发者
Making a screenshot PDF searchable — no OCR, because we rendered the page
We archive whole web pages as PDFs. Under the hood each page is a full-height screenshot dropped onto a PDF page — which looks perfect and is completely useless the moment you want to use the text. Ctrl+F finds nothing. You can't copy a sentence. A screen reader opens the document and sees… an empty page with one big image. The fix is the same trick a "searchable scan" uses: draw the real text invisibly , on top of the image, at the exact coordinates where each word appears. The difference is that a scanner needs OCR to guess the text — we rendered the page ourselves , so we already have the ground truth. No OCR, no guessing. Here's how we built it with pdf-lib and @pdf-lib/fontkit , and the one part that turned out to be genuinely hard. The shape of it While the page is still open in the headless browser, ask the DOM where every word is. Assemble the PDF: embed the screenshot as the page background. For each word, drawText it at its coordinates with opacity: 0 . Steps 1 and 3 are easy. The trap is in which words you're allowed to draw. Step 1 — ask the browser where the words are Running inside the page (Puppeteer's page.evaluate ), we walk every text node and measure each word with a Range : const walker = document . createTreeWalker ( document . body , NodeFilter . SHOW_TEXT ); // ...for each word in each text node: const range = document . createRange (); range . setStart ( node , start ); range . setEnd ( node , end ); const rects = range . getClientRects (); if ( ! rects . length ) continue ; // display:none or empty line box const b = rects [ 0 ]; // first rect = where the word starts out . push ({ t : word , x : b . left + window . scrollX , // document coordinates, not viewport y : b . top + window . scrollY , w : b . width , h : b . height , fs : parseFloat ( getComputedStyle ( el ). fontSize ) || 12 , }); getClientRects() gives viewport coordinates, so we add scrollX/scrollY to get document coordinates — the ones that line up with a full-page screenshot.
AI 资讯
How to Convert PDF to Word in the Browser with Vue 3 and pdf-lib
Converting PDF to Word seems straightforward, but the reality is more complex. PDF stores text as character coordinates, while Word uses structured paragraphs. Bridging this gap requires careful text extraction and order reconstruction. Here's how to build a browser-based PDF to Word converter with Vue 3 and pdf-lib . The challenge: PDF vs Word PDF is a presentation format — text is positioned precisely on the page. Word is an editing format — text flows in paragraphs with styles. Converting between them means: Extracting text from PDF coordinates Reconstructing reading order Generating structured DOCX output The stack Vue 3 with Composition API pdf-lib for PDF parsing docx for Word document generation Vite for bundling The core implementation < script setup lang= "ts" > import { ref } from ' vue ' import { PDFDocument } from ' pdf-lib ' import { Document , Paragraph , TextRun } from ' docx ' const file = ref < File | null > ( null ) const processing = ref ( false ) const result = ref < Blob | null > ( null ) async function convertPdfToWord () { if ( ! file . value ) return processing . value = true const arrayBuffer = await file . value . arrayBuffer () const pdf = await PDFDocument . load ( arrayBuffer ) const pages = pdf . getPages () const allChunks : TextChunk [] = [] for ( const page of pages ) { const textContent = await page . getTextContent () for ( const item of textContent . items ) { allChunks . push ({ text : item . text , x : item . transform [ 4 ], y : item . transform [ 5 ], size : item . size }) } } // Sort by reading order const sorted = sortByReadingOrder ( allChunks ) // Generate DOCX const doc = new Document ({ sections : [{ properties : {}, children : sorted . map ( chunk => new Paragraph ({ children : [ new TextRun ( chunk . text )] }) ) }] }) const blob = await doc . pack () result . value = blob processing . value = false } interface TextChunk { text : string x : number y : number size : number } function sortByReadingOrder ( chunks : TextCh