AI 资讯
How to Repair Corrupted PDFs in the Browser with Vue 3 and pdf-lib
A corrupted PDF is one of the most frustrating file problems. You have important content inside, but the document won't open, opens with garbled text, or shows missing pages. The file might be damaged from a bad download, a converter error, or a storage glitch. Recovering content from a broken PDF doesn't require complex forensic tools. Often, the individual pages are still readable — it's the document's structure (cross-reference tables, object streams) that's damaged. By extracting pages one by one into a fresh PDF, we can bypass the structural corruption. Here's how to build a browser-based PDF repair tool with Vue 3 and pdf-lib . The repair strategy The core insight: PDF structure and page content are somewhat independent . A PDF can have a broken cross-reference table or missing trailer objects, but the actual page content streams may still be perfectly readable. The repair approach: Load the damaged PDF and attempt to read each page For each successfully read page, copy it to a new PDF document Discard unreadable pages (they're lost anyway) Save the new document This is fundamentally different from "fixing" the original PDF. We're extracting what we can and rebuilding from the ground up. The stack Vue 3 with Composition API pdf-lib for PDF reading and page extraction Vite for bundling The core implementation < script setup lang= "ts" > import { ref } from ' vue ' import { PDFDocument } from ' pdf-lib ' const file = ref < File | null > ( null ) const totalPages = ref ( 0 ) const recoveredPages = ref ( 0 ) const repairing = ref ( false ) const result = ref < Uint8Array | null > ( null ) const error = ref < string | null > ( null ) async function repairPdf () { if ( ! file . value ) return repairing . value = true error . value = null try { const arrayBuffer = await file . value . arrayBuffer () const damaged = await PDFDocument . load ( arrayBuffer , { ignoreEncryption : true , updateMetadata : false , }) totalPages . value = damaged . getPageCount () const resu
AI 资讯
Why Your ZATCA Phase 2 Invoice Passes Compliance and Fails Reporting
If you are integrating ZATCA Phase 2 (Saudi Arabia's Fatoora e-invoicing) and you have seen this: { "type" : "ERROR" , "code" : "signed-properties-hashing" , "category" : "CERTIFICATE_ERRORS" , "message" : "Invalid signed properties hashing, SignedProperties with id='xadesSignedProperties'" } ...after your invoice sailed through /compliance/invoices , this post is for you. It is the single most confusing failure mode in the whole integration, and the fix is not what the error suggests. The trap: SignedProperties exists in two byte-shapes The XAdES SignedProperties block is referenced twice in your signed document: ds:Reference URI="#xadesSignedProperties" carries a digest of the block. The block itself is embedded inside ds:Object > xades:QualifyingProperties . The natural assumption is that both refer to the same bytes. They do not. The hashed shape carries namespace declarations and starts at column 0: <xades:SignedProperties xmlns:xades= "http://uri.etsi.org/01903/v1.3.2#" Id= "xadesSignedProperties" > <xades:SignedSignatureProperties> <xades:SigningTime> 2026-08-07T02:14:33 </xades:SigningTime> <xades:SigningCertificate> <xades:Cert> <xades:CertDigest> <ds:DigestMethod xmlns:ds= "http://www.w3.org/2000/09/xmldsig#" Algorithm= "http://www.w3.org/2001/04/xmlenc#sha256" /> The embedded shape carries no namespace declarations (they are inherited from ancestors) and its root element is indented to column 32 : <xades:SignedProperties Id= "xadesSignedProperties" > <xades:SignedSignatureProperties> Embed the hashed shape verbatim - the intuitive thing to do - and the gateway rejects with signed-properties-hashing , even though your indentation "looks right". The second half of the trap: the digest encoding The digest is not the raw SHA-256 bytes in base64. It is base64 of the hex string : const crypto = require ( ' crypto ' ); // hashedShape = the namespaced, column-0 variant above const propsDigest = Buffer . from ( crypto . createHash ( ' sha256 ' ). update ( Buffer .
开发者
Excited to finally join DEV!
👋 Hello DEV Community! I'm excited to finally join DEV! I'm a developer, entrepreneur, and lifelong learner who enjoys building practical web solutions with WordPress, PHP, and modern web technologies. Over the past few years I've been working on: 🚀 WordPress plugins and starter websites 💻 Affordable web solutions for individuals and small businesses 📈 Web analytics and digital marketing tools 🌱 Exploring software architecture, clean code, and open-source development I'm also building and experimenting with digital products that solve real-world problems while documenting what I learn along the way. Here you'll find posts about: WordPress development PHP programming Building and launching web products Software engineering lessons Productivity and business insights for developers Occasionally, mathematics and calculus when it connects to programming or analytics I'm looking forward to learning from this amazing community, contributing where I can, and connecting with fellow developers. Thanks for having me! 😊
AI 资讯
Why Plumeria?
"CSS Modules are fine after all." If you build web interfaces for a living, you have probably said this. After wrestling with runtime CSS-in-JS configuration, chasing specificity bugs across dynamic boundaries, or watching a utility-first framework bloat your markup, returning to the humble CSS Module feels like a relief. That isn't a compromise made for lack of features. CSS Modules win because they are predictable : the CSS you write behaves exactly as written. There is no runtime parser guessing your intent, no injection-order races between chunks, and almost no runtime JavaScript — just a class mapping object. But the safety has a price. You give up TypeScript-integrated styling, compile-time validation, dynamic theming, and seamless colocation. Plumeria is designed to eliminate this compromise. It matches — and in several areas exceeds — the predictability of CSS Modules, while delivering the type-safe developer experience of a modern CSS-in-JS library. The Zero-Trace Runtime Try compiling this — note that the style is actually applied, not left unused: import * as css from ' @plumeria/core ' ; const styles = css . create ({ box : { padding : 16 , color : ' red ' } }); export const Box = () => < div classStyle = { styles . box } > Box </ div >; Here is the entire JavaScript build output: export const Box = () => < div className = { ' xqqbxt1d xq96bg3w ' } > Box </ div >; The declarations move to a generated stylesheet: .xqqbxt1d { padding : 16px ; } .xq96bg3w { color : red ; } The style still renders, yet import * as css from '@plumeria/core' and the entire css.create declaration have vanished. This is not dead-code elimination — nothing in this file is unused, and no bundler could remove a live call for you. The compiler resolves the class names statically and rewrites the call site, so the library never has a runtime form to eliminate in the first place. That disappearing import is the most concise illustration of a Zero-Trace Runtime — anything that shouldn'
AI 资讯
TypeScript Enums Are Still Controversial in 2026: Here Is When to Use Them and When to Reach for `const` Objects
TypeScript Enums Are Still Controversial in 2026: Here Is When to Use Them and When to Reach for const Objects This article was written with the assistance of AI, under human supervision and review. Most TypeScript enum debates stem from a single misunderstanding: developers treat enums as a pure type-level construct when they generate real runtime code. This disconnect creates bundle bloat, unexpected behavior at runtime, and type safety gaps that only surface in production. Teams that reach for enums by default pay a hidden cost in every build. The enum controversy persists because TypeScript enums violate a core expectation: types should disappear at compile time. Unlike interfaces or type aliases that vanish during transpilation, enums produce JavaScript objects that ship to the browser. This runtime footprint matters when bundle size directly affects load time and business metrics. The alternative pattern— const objects with as const assertions—delivers the same developer experience without the runtime overhead. When developers understand the tradeoffs, the choice becomes mechanical: use enums where their runtime behavior adds value, use const objects everywhere else. Key Takeaways TypeScript enums generate runtime JavaScript objects that increase bundle size, while const objects with as const provide the same type safety with zero runtime overhead. Numeric enums enable reverse mapping and bitwise flags, making them valuable for low-level APIs and performance-critical code where runtime lookup is required. The const enum feature eliminates runtime code but breaks module boundaries and fails with external libraries, creating maintenance hazards in shared codebases. Const objects work seamlessly with tree-shaking, module systems, and JSON serialization, making them the default choice for API contracts and configuration. Migration from enums to const objects requires runtime validation at module boundaries to preserve type safety guarantees when data enters your s
AI 资讯
npm Staged Publishing Available, Adding a Human Approval Step Before Packages Go Live
npm has introduced staged publishing for Node.js, requiring maintainer approval before a version is installable. Versions are queued and must pass a two-factor authentication challenge for release. This feature aims to enhance security amid rising supply chain threats. It is available in npm CLI 11.15.0+ and Node 22.14.0+, alongside new configurable permission flags. By Daniel Curtis
AI 资讯
I Built a Photo-to-Cross-Stitch Pattern Maker That Runs in Your Browser
Photo-to-cross-stitch conversion looks like a resizing problem. It is not. A pixelated preview can look convincing and still be frustrating to stitch. It may contain too many colors, lack readable symbols, provide no reliable dimensions, or become useless when printed. I built StitchFromPhoto to handle the practical part of that workflow. It turns an image into a counted cross-stitch chart in the browser, lets you tune the result before committing to it, and keeps the source photo on your device. The useful output is a pattern, not a pixelated image A cross-stitch preview only answers one question. It shows roughly what the finished piece might look like. A usable pattern must also tell you how many stitches wide and tall the design is, which thread color belongs in each square, whether similar colors remain distinguishable on paper, and how large the result will be on your chosen fabric. That distinction shaped the app. The color preview is useful, but the symbol chart, thread key, stitch totals, fabric dimensions, and printable pages are the real deliverables. What the photo-to-cross-stitch pattern maker does The workflow starts with a sample image, so anyone can explore the controls before uploading a file. It also accepts JPG, PNG, and WebP images up to 20 MB. The main controls are stitch width, DMC color count, and fabric count. You can choose a pattern from 30 to 120 stitches wide, limit the palette to between 6 and 36 DMC colors, and calculate the finished size for 14, 16, 18, or 22 count Aida. You can move between the original photo, a color stitch preview, and a high-contrast symbol view. The thread key lists every retained DMC color code and the number of stitches assigned to it. Creating and previewing a pattern is free. High-resolution PNG and print-ready PDF downloads are unlocked per source image. I wanted that boundary to be visible before checkout rather than hidden behind the final button. How the browser turns pixels into stitches The conversion pi
AI 资讯
React useEvent Hook: Stable Callbacks Without Stale Closures (2026)
Every React developer eventually meets the same fork in the road. You write an event handler that reads state, pass it to a child or an effect, and now you must choose: leave it as a plain inline function and watch every render create a new reference — breaking React.memo , re-running effects, re-subscribing listeners — or wrap it in useCallback and start playing dependency-array whack-a-mole, where one forgotten dependency means the handler sees state from three renders ago. That second failure mode has a name — the stale closure — and it's arguably the most common React bug in production code. The fix has a name too: useEvent , proposed in an official React RFC in 2022 , and available today as useEvent in @reactuses/core . It gives you a function whose identity never changes across renders but whose body always sees the latest state and props . Both halves of the fork, no trade-off. This post covers the API, the three-line implementation trick that makes it work, how it compares to useCallback and to React 19.2's built-in useEffectEvent , real patterns, and the one rule you must respect (don't call it during render). TypeScript-first. The Problem in Thirty Seconds Here's the bug factory. A chat component sends a heartbeat with the current draft text: function Composer ({ roomId }: { roomId : string }) { const [ draft , setDraft ] = useState ( '' ); useEffect (() => { const id = setInterval (() => { sendHeartbeat ( roomId , draft ); // ⚠️ which draft? }, 3000 ); return () => clearInterval ( id ); }, [ roomId ]); // draft intentionally omitted — we don't want to reset the timer return < textarea value = { draft } onChange = { e => setDraft ( e . target . value ) } />; } The interval closes over the draft that existed when the effect ran — the empty string. Every heartbeat sends '' forever. Add draft to the dependency array and the closure is fresh, but now the interval tears down and restarts on every keystroke . useCallback doesn't help: it has the exact same depen
AI 资讯
QA-Testing Audio Trimming Workflows Before You Ship a Web Editor
If you're building — or integrating — a browser-based audio trimmer, the question that eventually reaches your inbox isn't "does it cut audio?" The real question is: does it cut audio correctly across the inputs we actually receive from users? That shift, from feature presence to behavior under fuzzy conditions, is what turns a demo into a product. This article walks through the QA matrix I use when reviewing client-side trimmers before release, with an emphasis on the silent failures that don't show up in a happy-path recording. The tool under review for most of this article is the Lizely audio cutter ( in-depth walkthrough ), but the principles apply to any browser trimmer that decodes via AudioContext or OfflineAudioContext . What "Trim" Actually Means Once You Leave the Lab In the lab, you upload a 44.1 kHz stereo WAV, drag two handles, click export, and verify the output. In production, users upload M4A recordings from iPhone Voice Memos, AMR files from old Android handsets, mono 8 kHz captures from cheap conference mics, and — occasionally — files renamed from .wav to .mp3 without re-encoding. Each of those paths stresses a different layer of the pipeline. The first thing to test, before any UI work, is the decode step. Browsers expose this through the decodeAudioData method on BaseAudioContext , documented on MDN's BaseAudioContext page . MDN is explicit about something engineers often miss: decodeAudioData detaches the input ArrayBuffer . If your trimmer holds a reference to the original buffer for "undo" and reuses it, you'll decode an empty buffer the second time around and get a silent result. That's a real defect class, not a theoretical one. The second thing to test is what happens when decoding fails. The spec says decodeAudioData invokes the error callback with a DOMException , but the browser-specific error messages vary. Chrome tends to surface "Decoding error" with no detail; Firefox appends the underlying codec name. Your QA suite should assert on
AI 资讯
The Silent Costs of AI APIs Nobody Warns You About
I remember the exact moment the excitement turned to dread. I had just integrated GPT-4 into a side project—a small document summarization tool. The pricing page said $0.03 per 1K input tokens and $0.06 per 1K output tokens. Clean, simple, two numbers. I calculated roughly $0.01 per summary and smiled. Two weeks later the bill arrived: $87.43 for what I thought would be maybe $15. I wasn't being careless. I had read the docs. I knew about tokens. But the silent costs—the ones nobody puts in a neat table—had quietly multiplied my burn rate by six. That experience taught me that AI API pricing is a lot like buying a printer. The upfront cost is seductive; the real expense hides in the ink cartridges, the proprietary drivers, and the forced upgrades you never planned for. Let's talk about those hidden costs, because I'll bet you've either already hit them or you're about to. The Token Trap That Isn't What You Think Everyone knows tokens are the unit of billing, but the gap between "understanding tokens" and "feeling tokens" is enormous. First, there's the input/output asymmetry . GPT-4 charges double for output tokens. That's fine for short answers, but what about chain-of-thought? If you ask the model to reason step-by-step, those intermediate steps count as output tokens—and they add up fast. I had a single query balloon from 500 output tokens to 2,400 because the model decided to work through a logic puzzle aloud. My cost quadrupled without me changing a thing in my prompt. Then there's the system prompt tax . Many developers stuff context into system messages: instructions, examples, formatting rules. Those are input tokens paid every single time, even when the user's query is tiny. If your system prompt is 1,500 tokens and you handle 10,000 requests, that's 15 million input tokens you're paying for—whether the model uses them or not. And don't get me started on retry costs . You hit a rate limit or your request times out? The token count for that failed request? S
AI 资讯
How to Set Up Rate Limiting in Nuxt
Rate limiting is one of those things that doesn't feel urgent—until someone hammers your login endpoint at 3am and you wake up to a flooded database and a locked-out user base. I added this to my Nuxt base layer after realising I'd shipped several projects with zero protection on auth routes. Not great. This post walks through the exact setup I now use: Redis-backed, an in-memory fallback when Redis is down, named presets for different sensitivity levels, and a 429 page that shows a live countdown instead of just dying on the user. The structure Three pieces, each with one job: createRateLimiter() — a factory that builds the limiter, using Redis with an in-memory fallback applyRateLimit() — what you call inside handlers to enforce a limit server/middleware/rateLimiter.ts — global middleware so every route gets a baseline for free 1. Install npm install rate-limiter-flexible ioredis rate-limiter-flexible does the heavy lifting: sliding windows, Redis integration, and the insurance fallback pattern we'll use. 2. The factory Create server/utils/rateLimiter.ts : import { RateLimiterRedis , RateLimiterMemory , type RateLimiterAbstract , } from ' rate-limiter-flexible ' import { getRedisClient } from ' ./redis ' export interface RateLimiterConfig { keyPrefix : string // Must be unique per limiter, e.g. 'rl:auth' limit : number // Maximum requests within the window windowSeconds : number } export interface RateLimitResult { allowed : boolean limit : number remaining : number resetAt : number // Unix timestamp in seconds when the window resets retryAfter : number // Seconds until retry; 0 if allowed } function buildLimiter ( config : RateLimiterConfig , ): RateLimiterAbstract { const insurance = new RateLimiterMemory ({ keyPrefix : config . keyPrefix , points : config . limit , duration : config . windowSeconds , }) const redis = getRedisClient () if ( ! redis ) { return insurance } return new RateLimiterRedis ({ storeClient : redis , keyPrefix : config . keyPrefix , points
开发者
10 Best Terminal-Based API Testing Tools for Developers in 2026
When I first started testing APIs, my workflow looked a lot like everyone else's. I'd open a GUI...
AI 资讯
Fixing your site's metadata: a practical checklist
You've done it. You're finally done building the website or application you've been working on for quite a while. Proud and elated, you go to share this on your socials or to your buddies — uh oh, what's this now? The preview in WhatsApp shows no headline, your avatar is cropped and dimensions seem wrong. I've been there too. The site looked fine in the browser. The problem was everything outside the browser: link previews, search snippets, and tab icons all use a separate metadata layer most of us skip until something breaks. So, how do you fix this? Use this as a pre-launch checklist — or run it on a site that's already live but sharing badly. What I ran into on my own portfolio When I ran this audit on shwethaadiraj.com , the site rendered fine — but sharing it told a different story. I had pointed both the favicon and Open Graph image at my profile avatar. At tab size the illustration was unreadable; in link previews it got cropped awkwardly. An OG validator then flagged two things I hadn't considered: the image was 512×512 (most platforms expect 1200×630 ), and there was no headline or CTA on the image itself — so Slack and LinkedIn showed a plain square with none of the context from my meta title. I replaced the favicon with a simplified monogram, regenerated the OG image at the correct aspect ratio with my name, tagline, and site URL on it, and re-ran the debuggers. Even then, previews didn't update until I hit Scrape Again — platforms cache OG data aggressively, so fixes on your end won't show up until you bust that cache. None of this required rethinking the app. It was a metadata pass — the kind of work that's easy to defer and annoying to discover at the share button. Before we get into the specifics, here's a primer on what metadata can actually impact: What is metadata for? Metadata, simply put, is data about data. Search engines, crawlers and social sites all parse different metadata from your app. Search & Discovery: The title and description in your
AI 资讯
Typing Vue 3 provide/inject Without Losing Autocomplete
Strict prop types and typed emits get most of the attention in Vue 3 + TypeScript setups, but provide / inject is where type safety quietly falls apart if you use the API the way the docs show it by default. inject() without a type hint returns unknown , which means every consumer of an injected value either casts it blindly or loses autocomplete entirely — and a typo in the injection key becomes a runtime undefined instead of a compile-time error. The Default Setup Is Untyped by Construction The naive version compiles, but gives you nothing: // Provider provide ( ' theme ' , currentTheme ); // Consumer const theme = inject ( ' theme ' ); // type: unknown Nothing here catches a typo in the key string, and nothing tells the consumer what shape theme actually has. Both problems come from using a plain string as the injection key. InjectionKey Fixes Both Problems at Once Vue exports an InjectionKey<T> type specifically for this. Define it once, typed, and both provide and inject become fully type-checked against the same symbol: // keys.ts import type { InjectionKey } from ' vue ' ; export interface Theme { mode : ' light ' | ' dark ' ; accentColor : string ; } export const ThemeKey : InjectionKey < Theme > = Symbol ( ' theme ' ); // Provider import { ThemeKey } from ' ./keys ' ; provide ( ThemeKey , { mode : ' dark ' , accentColor : ' #4f46e5 ' }); // Consumer import { ThemeKey } from ' ./keys ' ; const theme = inject ( ThemeKey ); // type: Theme | undefined The | undefined in that last type isn't a quirk — it's inject being honest that a consumer might render without a matching provider above it in the tree, which is a real runtime possibility TypeScript is right to force you to handle. Handling the undefined Case Without Littering ?. Everywhere The common mistake is providing a default value to silence the undefined type instead of actually checking for it: const theme = inject ( ThemeKey , { mode : ' light ' , accentColor : ' #000 ' }); // default masks missing pro
AI 资讯
Google Quietly Dropped 12 Free AI Tools. Developers Should Probably Care.
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. A few years ago the AI conversation looked like this. "Should I pay $20?" "No, $200." "Actually this new tool is $39/month." My wallet started looking like it had gone through a startup funding winter. Then Google quietly walked into the room and started dropping free AI tools like Oprah handing out cars. "You get an AI IDE!" "You get a workflow builder!" "You get a GitHub coding agent!" ...except nobody really noticed because Google announced them across five different events, Labs pages, GitHub repos, and random blog posts. So I spent some time collecting the ones developers will actually find useful. No "AI that writes your wedding speech." No "AI that guesses your spirit animal." Just tools that can actually help you ship software. Bookmark this one. 1. Pomelli https://labs.google/pomelli If you've ever launched a side project, you already know the painful truth. Building the product is fun. Writing 37 LinkedIn posts explaining the product... not so much. Pomelli takes your website, understands what your product does, builds a brand profile, then generates social posts around it. Think of it as hiring an intern that actually reads your landing page before tweeting. Would I let it post automatically? No. Would I happily let it generate the first draft so I don't stare at a blinking cursor? Absolutely. Perfect for: Indie hackers SaaS founders Open-source maintainers pretending they enjoy marketing 2. Stitch https://stitch.withgoogle.com Remember when designing an app meant opening Figma... ...moving a button 3 pixels... ...asking for feedback... ...moving it back 3 pixels? Stitch skips a surprising amount of that. You describe the interface. Or upload a sketch. Or even paste a wireframe. It generates modern UI designs and can even produce
AI 资讯
I built a Markdown resume builder for the AI-paste workflow — here's everything that broke
There's a workflow that basically didn't exist three years ago and now half the job-seekers I know use it: ask ChatGPT, Claude, Gemini, or any AI to write your resume bullets, get back beautifully structured text… and then spend forty minutes mangling it into Word or a drag-and-drop resume builder, fixing bullet indentation and font sizes by hand. Here's the thing that bugged me: LLMs already speak Markdown. Ask any chatbot for a resume and you get ## Experience , **Senior Engineer** , - Shipped X — clean, structured Markdown. Then every resume tool on earth makes you throw that structure away and re-enter it into form fields. So I built ResumeMD: a split-pane editor where you paste Markdown on the left, see a typeset resume on the right, pick a template, and download a PDF. No signup to start, everything in localStorage by default. This post is about the parts that fought back. Decision 1: Markdown is the source of truth Most resume builders store your resume as a proprietary JSON blob mapped to form fields. I wanted the document itself to be portable text. That means the entire product is "just" a Markdown renderer with opinions: h2 = section headers (Experience, Education) — these get the decorative treatment per template: uppercase, border, background, prefix glyphs. h3 = job titles — plain, bold, primary color. One weird trick I'm genuinely fond of: the sidebar template splits a single Markdown document into main column and sidebar using an HTML comment ( <!-- sidebar --> ) as the split marker. Content above the marker is the main column; below is the sidebar. It keeps the document valid Markdown everywhere else. The preview is react-markdown + remark-gfm with a 300ms debounce, styled by a template system that turned out to need three parallel implementations of every template: CSS classes for the live preview, inline-style functions shared between preview and template cards, and pure-JS styles for the PDF renderer. Thirty-two templates, three layers each. When
AI 资讯
Building a Reliable AI Image Pipeline: Tasks, Failures, and Credit Refunds
Most AI image generators look like a prompt box with a Generate button. That is also how my first version started. But once real users entered the workflow, the difficult problems appeared somewhere else: browser refreshes, external task IDs, reference images, partial failures, credit refunds, private assets, and public artwork moderation. While building Magggic , I learned that an AI image generator is less like a form submission and more like a small distributed job system. This article covers the decisions that made that workflow more reliable. The code samples below are intentionally simplified. The important part is the shape of the workflow, not a specific database or image provider. The prompt box is only the beginning A synchronous prototype is easy to imagine: const images = await provider . generate ( prompt ); return images ; That version works until the request takes a minute, the provider times out, one of four requested images fails, or the user refreshes the page. The production workflow I needed looked more like this: Prompt + references ↓ Create a local queued task ↓ Charge credits with an idempotency key ↓ Submit work to the image provider ↓ Persist every completed output immediately ↓ Finalize the task and refund failed outputs ↓ Keep the result private until the user publishes it The provider request is only one step. The local task is the source of truth for what the user sees. 1. Persist the task before calling the provider The first important decision was to create a generation record before making the external API request. A generation stores the information needed to reconstruct the job: type Generation = { id : string ; userId : string ; idempotencyKey : string ; prompt : string ; referenceImages : string []; model : string ; ratio : string ; resolution : string ; count : number ; cost : number ; status : " queued " | " generating " | " completed " | " failed " ; outputs : string []; providerRequestIds : string []; failureReason : string |
AI 资讯
How to Turn Any Android Tablet into a Production-Grade Dev Rig in 5 Minutes. Published in #developer #android #terminal #productivity
If you've ever tried coding on an iPad, Galaxy Tab, or Chromebook, you know the frustration: Standard desktop tutorials assume a Mac or high-spec Linux laptop. Neovim configuration takes 4 hours of plugin debugging. Touch input on mobile terminals sucks without a dedicated extra-keys bar. I built DevDock (dock) to solve this permanently. What is DevDock? DevDock is a turnkey developer environment manager built specifically for mobile devices, Termux, Chromebooks, and low-spec hardware. Instead of fighting configuration files, one command installs a complete, high-performance terminal stack: bash curl -fsSL https://get.devdock.io | bash -s -- --profile=fullstack ⚡ Key Features Sub-5ms Terminal Rendering: Uses Starship prompt + Zsh lazy-loading tuned for ARM chips. Termux Touch Optimization: Automatically injects an ESC/TAB/CTRL touch bar and enables mouse scrolling in Tmux. Low-Memory Neovim: Starts in <50ms and uses under 50MB RAM while providing full Language Server Protocol (LSP) support for TS, Go, Python, and Rust. Curated Profiles: fullstack: Web + API tools frontend: React, TS, Vite & Tailwind preset backend: Go, Rust, Python, Postgres & Redis CLI tools devops: Kubectl, Helm, Terraform, and Cloud CLIs 🛠 Trying It Out bash Check your mobile terminal health: dock doctor View available developer stacks: dock profiles Initialize a frontend stack: dock init frontend 🔗 Open Source & Community DevDock is 100% open source under the MIT License! GitHub Repo: github.com/devdock/devdock Web Showcase: devdock.io Give it a spin on your Android phone, tablet, or cloud shell and let me know what you think in the comments below!
AI 资讯
CSS Specificity Isn't Your Biggest Problem
Also available in Español The Problem A team ships clean CSS. Every selector is deliberate. Every class name means something. Code review catches the sloppy stuff before it merges. Six months later, someone adds !important to fix a button. A year later, three more !important s exist — each one written to fix the last one. Nobody planned this. Nobody stopped caring. The team is exactly as disciplined as it was on day one. The codebase didn't get sloppy. The architecture never had a way to stay clean. That's the part worth sitting with. Specificity problems get treated as a discipline failure — bad naming, careless nesting, someone in a hurry. But teams with excellent discipline hit this wall too. Given enough time and enough contributors, almost every CSS codebase drifts toward the same place: overrides stacked on overrides, each one a patch for the last. Something structural is happening here. Not a people problem. A tooling gap. Why the Problem Exists CSS specificity was built to answer one narrow question: if two rules target the same element, which one wins? The browser calculates an answer. Count the IDs. Count the classes and attributes. Count the elements. Higher count wins. If the count ties, whichever rule appears later in source order wins. That's the entire mechanism. It's fast, deterministic, and was never meant to do more than that. Notice what it doesn't ask. It doesn't ask whether a rule is a foundational default or a one-off exception. It doesn't ask whether a rule was written to be overridden, or written to never be touched again. It doesn't know the difference between a base style and a utility class — it only knows how many selectors each one used. Specificity resolves conflicts. It was never given a way to encode intent. That gap is old. It predates component-based frontend architecture, design systems, and teams of a hundred engineers touching the same stylesheet. The web platform gave developers a scoring system for which rule wins by the number
AI 资讯
Matching 90M+ music tracks across six platforms: ISRCs, fuzzy matching, and what breaks
I run a music metadata API as a solo developer. Under it sits a catalog of 90M+ recordings aggregated from six platforms: Spotify, Apple Music, Tidal, Beatport, Discogs, and MusicBrainz. The core job is cross-referencing: take whatever you know about a track (an ISRC, a platform ID, or a messy "artist + title" string from a DJ export) and resolve it to one canonical recording with everything else attached. When I started, I assumed this was mostly a plumbing problem. Every platform has an API, recordings have a standard identifier, join on it, done. Almost none of that survived contact with real data. This post is the parts I had to learn the hard way: why one song legitimately carries many ISRCs, how fuzzy matching on artist and title actually has to work, why recording-to-composition mapping is many-to-many in both directions, and the failure modes I now check for routinely. The ISRC almost solves it The ISRC (International Standard Recording Code) is a 12-character identifier for a specific recording. Daft Punk's "One More Time" is GBDUW0000053 : country prefix GB , registrant code DUW , year 00 , then a designation number. Every commercially released recording is supposed to have one, and most platforms expose it. So the naive architecture writes itself: one isrc column on the track table, join all six platforms on it, ship. That was my first schema, and it was wrong in a way that took a while to surface. Labels mint a fresh ISRC for every commercial variant of a recording. The radio edit gets one. The extended mix gets one. The 2001 release and the anniversary remaster get different ones. A reissue through a new distributor often gets one even when the audio is bit-identical. Regional releases sometimes get their own. None of this is an error; it is how the system is designed to work, because each of those is a distinct commercial product even when it is the same performance. The consequence: one canonical recording legitimately carries many ISRCs, and differen