Today
Front-end, in one read.
The latest from the sources worth following, newest first.
Web Tools WeeklyMulti Author Blogsread at source
Web Tools Weekly Issue #691
Fimo Review, CSS/HTML, Testing, Uncats
DEV CommunityThe Giantsread at source
RevCheck: paste two used-car listings, get a second opinion before you drive across town
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle What I Built Buying a used car in Bulgaria goes like this. You have twelve tabs open on mobile.bg and cars.bg. Half of them say "нов внос" (fresh import), "реални километри" (real mileage) and "без забележки" (nothing to fix). The same car shows up on both sites. Then you lose a Saturday driving to Plovdiv for a car that turned out to have a different engine, a seat that's been through a dog, and a price nobody else is asking. So I built RevCheck . You paste two listings, from either site, and about 30 seconds later you get a side-by-side report and a verdict: Every photo is checked by Gemma 4: color, dents, scratches, mismatched panels, and the equipment you can actually see (M Sport bumpers, panoramic roof, leather, navigation). It also flags extras the seller lists but the photos contradict. The market position. It finds similar cars for sale on both sites (same model, body type, fuel and engine power, year ±1) and tells you "priced higher than 91% of 44 similar listings". A car listed on both sites only counts once. A fair price range from TabPFN , trained on those comparables during the request: "€4,718 – €8,665, so this one is €2,276 above the expected price". The known weak spots of that exact engine. DeepSeek V4 names the likely engine code from year, power and displacement (N47D20C, N52 or N53, and so on) and lists what to check at the viewing: timing chain rattle on a cold start, swirl flaps, the ZF gearbox, the high-pressure fuel pump. Plain checks with no AI: a missing VIN, unusually low or high km per year, too few photos, a US or Canada import (go and run that VIN). The verdict: which car to go and see, why, what to check on each one, and what to ask the seller before you get in the car. English and Bulgarian , with the flags at the top. The report is written in whichever language you pick. Who would pay for this? Anyone about to spend half a day and a tank of fuel on a viewing. People already pay mechanics and inspectors to look at a car in person before they buy. RevCheck is the step before that: it tells you which of the two is worth the trip. I'd keep a couple of comparisons a day free and charge flippers and small dealers, who compare dozens of cars a week, a small monthly fee. Demo Live site: https://georgievalex.github.io/revcheck/ Try it with two BMW 320d estates that are for sale right now: https://georgievalex.github.io/revcheck/?a=https://www.mobile.bg/obiava-11744111052659454-bmw-320-2-0d&b=https://www.mobile.bg/obiava-11787314145036432 Paste two links, tick the box, click Compare: The verdict comes back first. Each car card fills in as soon as that car is done: The market position, the TabPFN fair price range and what the photos show: And the known weak spots for the engine, with what to check at the viewing: Code GeorgievAlex / revcheck Compare two used-car listings from mobile.bg or cars.bg with open-weight AI (Gemma 4 vision + TabPFN) RevCheck The pre-purchase check from the RevSofia family. Paste two used-car listings from mobile.bg or cars.bg and get a side-by-side report and a verdict on which one is the better buy. Built from scratch on 15–18 October 2026 for the DEV Hacktoberfest Open-Source AI Challenge, Week 2: Side Hustle . Site: https://georgievalex.github.io/revcheck/ How the listings are read mobile.bg and cars.bg refuse requests from DigitalOcean (a Cloudflare challenge or a 403). From Cloudflare's own network they answer. So the function reads pages through revcheck-reader ( cloudflare/reader/ ), a small Cloudflare Worker: Honest about who it is: a RevCheck/0.1 (+github link) User-Agent, plus the CF-Worker header Cloudflare adds to every Worker request. Either site can block it with one rule, and if they do, we stop. Not a public proxy: it requires a shared secret that only the DigitalOcean function has, and it only fetches a mobile.bg listing, a cars.bg offer, or… View on GitHub How I Built It Same rule as my last two entries: no framework, no database, no npm dependencies. A static page on GitHub Pages, one DigitalOcean Function and one tiny Cloudflare Worker. browser ── GitHub Pages (static) + Cloudflare Turnstile │ start → bot check, returns a 3-minute signed ticket for these two links │ car ×2 → one request per car, in parallel │ verdict → the written comparison ▼ DigitalOcean Function revcheck/compare ├─ reads listings + search pages ──► Cloudflare Worker ──► mobile.bg / cars.bg ├─ Gemma 4 (vision): every photo, 8 per call ┐ ├─ DeepSeek V4 Pro: engine + known weak spots ├ DigitalOcean Serverless Inference ├─ Gemma 4: the verdict ┘ └─ TabPFN 3.5: fair price from the comparables ──► Prior Labs API Job Model Why this one Look at the photos gemma-4-31B-it (open-weight) Reads a dashboard, a bumper and a seat in one pass, in Bulgarian or English Engine and weak spots deepseek-v4-pro (open-weight) I tried four open models on the same car. Two returned broken JSON, and of the other two only DeepSeek brought up the gearbox Fair price TabPFN 3.5 Trains on the ~50 comparables inside the request, no training pipeline of my own The verdict gemma-4-31B-it Weighs all of the above and writes like a friend who knows cars A few things I'm happy with: The browser data is untrusted. The verdict step gets each car's results back from the page, so every field is rebuilt from a known list with size caps before it reaches the model. Photo URLs have to point at the two sites' image servers, because Gemma downloads them. The scary stuff is decided in code, not by the model. The odometer flag, the VIN check, km per year and US imports are all rules. The model can only suggest something to check (more on why below). Every slow step has a deadline. If the known-issues lookup hangs, you get the car without it instead of no car at all. 26 tests , all node:test , including the ugly real-world cases from the next section. The commit history has one commit per lesson below, if you want to see them land. What I got wrong on the way The listing sites block DigitalOcean. My first version fetched listings from the function, and on my laptop it worked perfectly. Once deployed, mobile.bg answered with a Cloudflare "Just a moment..." page and cars.bg with a 403. They block cloud servers, which is fair, they're tired of scrapers. The browser can't read them either, because neither site allows other websites to (no CORS headers). I built a small browser extension, and then threw it away, because nobody is going to install an extension from a site they've never heard of. What worked was a Cloudflare Worker that does nothing but fetch a listing, a search page or a model list. It sends RevCheck/0.1 and a link to the repo as its User-Agent, and Cloudflare adds a header naming the Worker, so either site can see exactly who's asking and block it with one rule. If they do, that's their call and I'll stop. It needs a secret only my function has, and it refuses any other URL, so it isn't an open proxy. Gemma accused a seller of odometer fraud. On a 2012 Audi Q5 listed at 104,000 km, it read the dashboard photo as 266,777 km and the verdict called it "severe odometer fraud". I zoomed into the photo myself: the dash says 104,677. A blurry digit nearly got a real person called a fraudster on a public page. Now the model only reads the odometer and rates the reading clear or unclear, and the code decides. A flag only appears if the reading is clear and more than 15% off, and even then it says "check the dash in person". The prompts now ban accusations outright. "Not enough photos" on a listing with 10 photos. DigitalOcean's endpoint takes at most 8 image links per call, so I check photos in parallel batches of 8. The last batch had just 2 interior photos, and the model, seeing only those, wrote "very few photos, interior only". The model doesn't get to judge photo count any more. The code does. A coupe compared against sedans. One cars.bg seller typed the body type as "Kупе", with a Latin K. My body-type filter didn't recognise it and quietly searched without it. Funny enough, cars.bg's own search form spells it with a Latin K too. Body types are now compared after mapping Latin look-alike letters to Cyrillic, and there's a test with that exact string. The AI invented a recall. I wanted RevCheck to list manufacturer campaigns, the "VANOS bolts" kind. DeepSeek confidently listed a VANOS bolt campaign for an N52 engine, which as far as I know was for different engines. I'd also gotten something wrong myself while testing: I told it a 2007 272 hp 330i had to be an N53, when the N52 made the same power until the changeover that year. So two changes. Recalls aren't generated at all: the page tells you to have a dealer check open campaigns by VIN, and links to a VIN decoder. And when an engine changed mid-year, the model has to give both and say "likely", never "certain". Requests died at exactly 40 seconds. With photos, TabPFN and the known-issues lookup, a full comparison took 33–45 seconds, and every so often the page said "couldn't reach the comparer". I deployed a test function that just sleeps: DigitalOcean's web gateway answers 202 Response not yet ready after exactly 40 seconds, whatever the function's own timeout is. So a comparison is now three kinds of short requests (start in ~2 s, each car in ~19 s in parallel, the verdict in ~8 s), tied together by a signed ticket so the bot check still covers all of them. As a bonus, the cars now show up before the verdict. Why Does Open Innovation Matter? The right model for each job, behind one endpoint. Gemma looks at photos, DeepSeek knows engines, TabPFN does the numbers. With open-weight models on DigitalOcean's serverless inference, switching the known-issues step from Gemma to DeepSeek after testing was one string. With a single closed API, I'd have been stuck with whatever that vendor's one model knows about BMW timing chains. It has to be cheap per comparison, or the side hustle doesn't work. One comparison is 6 to 8 model calls. Open models on prepaid inference keep that cheap enough to give a couple away free every day, and the prepaid balance with auto-reload off is a hard cap on the bill. TabPFN trains on today's market, inside the request. No dataset to collect, no model to retrain. The 50 cars for sale right now are the training set, which is exactly what you want for used-car prices. Open code is how you earn trust when you read other people's sites. The Worker that fetches listings is about 50 lines in the repo. Anyone, including mobile.bg and cars.bg, can read exactly what it fetches and how often. Prize Categories Best Use of DigitalOcean: a DigitalOcean Function runs the whole backend, and DigitalOcean Serverless Inference serves both open-weight models (Gemma 4 and DeepSeek V4 Pro). The 40-second gateway lesson above is a DigitalOcean story too. Best Use of Gemma: gemma-4-31B-it checks every photo, with damage, equipment and odometer readings, and writes the final verdict in English or Bulgarian. Best Use of TabPFN: TabPFN 3.5 predicts a fair price range (10th to 90th percentile) for each car, trained on live comparables from two sites, and that prediction feeds the verdict. It's one step inside the larger prediction pipeline, not a standalone demo. What's next Exact engine codes and campaigns from the VIN. The BMW decoders I use myself don't allow automated lookups, so for now RevCheck just links to them. OEMNavigations sells an official BMW VIN API, which would give the exact engine, build date and campaigns instead of "likely". Asking mobile.bg and cars.bg for proper access , now that there's something real to show them. mobile.de , through its official API, for the "what would this car cost in Germany plus import" question that every Bulgarian buyer asks. Buying a used car in Bulgaria? Paste your two favourites into RevCheck before you drive anywhere.
DEV CommunityThe Giantsread at source
Portable PL Test Equipment: Keeping Sample IDs Attached to the Data
A folder with three files called reading_01.txt, reading_02.txt and reading_03.txt looks tidy enough. Now someone asks which one came from sample PV-017, whether it was taken at the center or the edge, and which settings were active. The filenames don't answer any of that. That is the small software problem I want to look at here. When portable test equipment is used across several samples, the saved result needs an unambiguous link back to the sample and the measurement setup. I work on technical content for TDM Technology. The hardware reference for this example is its SE10 portable PL metrology instrument , which lists local storage and a Bluetooth connection to a Windows PC. That product description is not an API specification. It does not establish a file-export format or a public Bluetooth protocol. The example below starts with a completed file already on the computer; obtaining suitable files from the actual instrument software must be confirmed separately. There are no SE10 SDK calls here. Start with the sample, not the filename For this example, each local file gets a small JSON record beside it. This is often called a sidecar file. It holds the context without changing the original data. I would begin with four references: the sample, the measurement position, the instrument and the setup. PV-017 identifies the sample. P03 identifies a position on a photo or drawing kept with the session. portable-demo-01 identifies the instrument. setup-v3 points to a preserved settings record or notebook entry. Those references need somewhere to point. A setup name is not much use if the settings behind it have been overwritten. Likewise, a position label needs a definition that another person can follow. The code only records these labels. It cannot verify that the operator selected the right sample or used the stated settings. For a real workflow, I would make that confirmation part of the acquisition process, rather than reconstructing it from memory at the end of the day. A small Python sidecar writer This script reads a file, calculates its SHA-256 digest and writes the references into a neighboring JSON file. It leaves the input untouched and refuses to overwrite an existing sidecar. Save it as pl_record.py. It uses the Python standard library and can be run with Python 3.10 or later. """Attach sample metadata to a completed local file; no device connection.""" import argparse import hashlib import json from datetime import datetime , timezone from pathlib import Path from uuid import uuid4 def add_metadata ( path : Path , * , sample : str , position : str , setup : str , instrument : str ) -> Path : refs = { " sample_id " : sample . strip (), " position_ref " : position . strip (), " setup_ref " : setup . strip (), " instrument_ref " : instrument . strip (), } if not all ( refs . values ()): raise ValueError ( " Sample, position, setup and instrument are required. " ) digest = hashlib . sha256 () with path . open ( " rb " ) as source : for chunk in iter ( lambda : source . read ( 1024 * 1024 ), b "" ): digest . update ( chunk ) record = { " schema_version " : 1 , " record_id " : str ( uuid4 ()), ** refs , " logged_at_utc " : datetime . now ( timezone . utc ). isoformat (), " file_name " : path . name , " file_sha256 " : digest . hexdigest (), } destination = path . with_name ( path . name + " .metadata.json " ) payload = json . dumps ( record , indent = 2 , ensure_ascii = False ) + " \n " with destination . open ( " x " , encoding = " utf-8 " ) as output : output . write ( payload ) return destination def main () -> None : parser = argparse . ArgumentParser ( description = __doc__ ) parser . add_argument ( " file " , type = Path ) parser . add_argument ( " --sample " , required = True ) parser . add_argument ( " --position " , required = True ) parser . add_argument ( " --setup " , required = True ) parser . add_argument ( " --instrument " , required = True ) args = parser . parse_args () try : print ( add_metadata ( args . file , sample = args . sample , position = args . position , setup = args . setup , instrument = args . instrument , )) except ( OSError , ValueError ) as exc : parser . exit ( 2 , f " Error: { exc } \n " ) if __name__ == " __main__ " : main () To try it without an instrument, create a file named demo_reading.txt containing any clearly labeled synthetic text. Then run: python pl_record.py demo_reading.txt --sample PV-017 --position P03 --setup setup-v3 --instrument portable-demo-01 The output is: demo_reading.txt.metadata.json There are now two files to keep together: demo_reading.txt demo_reading.txt.metadata.json The script does not interpret the file as a spectrum or assume any particular PL data format. The .txt extension is just for this demonstration. A missing file or blank reference produces an error. Running the same command again also produces an error, because Python's exclusive-create mode, "x", will not replace the existing metadata file. That protects against an accidental overwrite by this script. It does not make the record immutable or protect it against someone editing it with another program. Two fields that are easy to misread The timestamp is called logged_at_utc, deliberately. It records when this script created the record, using a timezone-aware UTC datetime . It is not the acquisition time. If the instrument supplies a trustworthy measurement timestamp, preserve it separately, together with the clock's timezone or reference. A file's modification time or the moment it arrives on a laptop should not silently become the time of measurement. The second field is file_sha256. Python's hashlib computes the digest from the file's bytes. Saving it gives a later check something to compare against when looking for file changes. It does not prove that a measurement is correct, identify a physical sample, or establish that two identical files came from the same acquisition. Two separate acquisitions might produce identical file contents. Do not automatically throw one away as a duplicate measurement just because its hash matches. Each sidecar therefore gets its own record_id. Even that identifies this logging record, not a device-generated acquisition. A future instrument adapter would need to preserve the device's acquisition identity separately, if available. Keep the first version small—and be clear about its limits Run this only on completed files that are no longer being written. The script does not detect transfer completion, lock the input or recover a half-written sidecar after an interrupted write. It is a local example, not a multi-user lab database. The next useful step would be a simple operator screen: select the sample and position, confirm the setup, attach the completed file, and review the record before moving to the next sample. The screen should make the active sample obvious. Convenience is not worth quietly carrying the previous sample ID into the next measurement. For portable PL test equipment, that handover between the physical sample and the software record deserves attention. Wireless connectivity can be part of the hardware workflow; it does not tell an application what an operator is measuring. The acceptance test I have in mind is modest. Hand the session folder, its setup records and its position drawing to a colleague. Can they work out which file belongs to which sample without asking the person who collected it? That would be a useful first result, before writing any plotting code.
DEV CommunityThe Giantsread at source
Building Custom AI Agents with JavaScript, React, and Ext JS
AI-powered development tools are evolving beyond code completion and conversational assistance. Modern AI agents can be designed to understand project context, interact with external tools, retrieve relevant resources, and coordinate multi-step workflows. For JavaScript developers, this creates an opportunity to build custom AI-powered experiences directly into existing development environments. By combining JavaScript, React, agent communication protocols, and reusable UI components, developers can create interfaces that make AI workflows easier to monitor, understand, and control. This article explores the architecture behind custom AI agents and how React and Ext JS can contribute to the user experience. What Is a Custom AI Agent? A traditional AI assistant typically responds to a prompt with generated text or code. An AI agent can be designed to go further by coordinating multiple actions toward a defined objective. Depending on its implementation and permissions, an agent might: Inspect project files and documentation. Retrieve information from external tools. Help developers investigate errors. Coordinate multi-step coding tasks. Display execution progress and diagnostic results. Support repeatable engineering workflows. These capabilities depend on the agent's model, available tools, context, permissions, and execution architecture. They should not be assumed to work automatically simply because an application uses an LLM. Why JavaScript and React Are Useful JavaScript is already widely used for web applications and developer tooling. React provides a component-based approach to creating interactive interfaces. Together, they can provide a foundation for building an AI agent interface that developers can use inside a web application or a Visual Studio Code extension. A typical interface might contain: A prompt or task-entry panel. An execution-status indicator. A conversation history. A tool activity panel. A structured diagnostic table. A results view for generated recommendations. Separating these interface elements into reusable components makes it easier to develop, test, and maintain the application. Understanding MCP and ACP AI agent development often involves more than calling a language model API. Model Context Protocol (MCP) MCP provides a standardized way for compatible AI applications to interact with tools and contextual resources. Depending on the implementation, these integrations can expose documentation, project information, external services, and other approved capabilities. Developers should carefully control which tools are available, what information they can access, and whether an operation requires user approval. Agent Client Protocol (ACP) ACP provides a protocol for communication between compatible agent clients and agent backends. Using an appropriate protocol can help separate the interface from the underlying agent implementation. This can be valuable when developing client experiences that need to work with supported agent backends. MCP and ACP have different roles: one concerns connections to tools and contextual resources, while the other concerns communication between compatible agent clients and agents. Building the Agent Interface with React A useful AI interface should make the agent's activity understandable instead of presenting everything as a stream of generated text. React components can divide the interface into focused sections. For example: Task panel: Allows developers to describe the operation they want the agent to perform. Conversation panel: Displays messages and relevant explanations. Activity panel: Shows progress, tool invocations, and execution status. Diagnostics panel: Presents structured findings, errors, and results. Approval controls: Allow developers to review sensitive actions before they proceed. This separation helps developers understand what the agent is doing and where human intervention may be needed. Where Ext JS and a React Grid Component Fit In As an agent becomes more capable, it may generate structured information that is difficult to review in a chat window. Consider an agent that examines a project and reports deprecated APIs, failed checks, file references, or proposed changes. A structured data grid can make this information easier to filter, compare, and inspect. Sencha's Ext JS provides enterprise-oriented UI components, including data grids. ReExt allows developers to integrate Ext JS components into React applications. A React Grid component built with an appropriate grid library can be useful for displaying: Tool execution history. Diagnostic findings. Validation results. File-level analysis. Task status and timestamps. Structured output from agent workflows. The important distinction is that Ext JS is a UI framework, not an AI agent engine. It contributes to the presentation layer; the agent's reasoning, tool execution, and permissions need their own implementation. ReExt is worth evaluating when an application already uses React and needs Ext JS components within the same interface. A High-Level Architecture A custom AI agent can be organized into several layers: React interface: Collects tasks and displays progress and results. Extension or application host: Manages the surrounding environment and approved interactions. Agent orchestration: Coordinates model responses, tool calls, and multi-step execution. Protocol integration: Uses compatible protocols such as MCP and ACP where appropriate. Tool and data layer: Provides approved access to files, documentation, and external services. Governance layer: Enforces permissions, approval requirements, and logging. Keeping these responsibilities separate makes the application easier to reason about and test. Security and Reliability Considerations AI agents that can interact with development environments require careful safeguards. Before deploying an agent, consider: Restricting access to sensitive files and credentials. Validating tool inputs and outputs. Requiring approval for consequential actions. Handling timeouts, failed tools, and malformed responses. Keeping an audit trail for relevant operations. Treating repository content and external tool responses as untrusted input. Testing whether the agent follows the intended permission boundaries. A polished interface is useful, but it cannot replace appropriate controls in the execution layer. A Practical Development Roadmap Start with a small, clearly defined workflow rather than attempting to build a fully autonomous coding system immediately. Step 1: Choose a task, such as reviewing project configuration or identifying deprecated API usage. Step 2: Build a simple React interface for submitting tasks and displaying responses. Step 3: Connect the agent to a limited set of tools with explicit permissions. Step 4: Add execution status, structured results, and diagnostic reporting. Step 5: Evaluate whether a React grid component or Ext JS data grid would improve the presentation of structured output. Step 6: Add validation, error handling, approval flows, and automated tests. Measure the results against a baseline before expanding the agent's responsibilities. Conclusion Custom AI agents combine model integration, tool access, orchestration, and a clear user interface. JavaScript and React provide a practical foundation for developing these experiences, while MCP and ACP can support different parts of the integration architecture. For agent interfaces that display structured diagnostic information, Ext JS and ReExt are worth evaluating alongside other React UI libraries. The best design makes agent activity understandable, keeps sensitive operations under appropriate control, and gives developers the tools they need to inspect results. Further reading: Building Custom AI Agents with JavaScript, React, and ReExt — Sencha.
DEV CommunityThe Giantsread at source
I Built a Free, Client-Side Alternative to ChatPDF – No Upload, No Tracking
I was tired of every "Chat with PDF" tool asking me to upload my contracts and research papers to their server. So I built my own. Live: https://ai.toolscase.com What it is Toolscase AI is the AI hub of Toolscase – my collection of 35+ free tools. It centralizes 4 AI products from my verified portfolio: AskFiles AI - Chat with PDF - Chat, summarize, extract from any PDF. PDF.js + client-side text extraction, file never leaves your browser where possible ApplyFlowAI - AI Proposal Creator - Generates proposals from job descriptions LeadIntel AI - B2B Sales Lead Finder - AI lead enrichment FocusPulse AI - Screen Time Tracker All part of bigger ecosystem: Toolscase PDF Tools - 108 PDF Tools , Image Tools, CV Builder, Time Tracker, and Static Site Converter. Why client-side? I built Toolscase with my own Static Site Converter to keep infra cost $0/month. Most tools run on WASM / PDF-lib / PDF.js. No backend, no tracking, no signup. You can verify in DevTools > Network – 0KB upload when you chat with a PDF. For devs worried about privacy for legal docs, theses, client contracts – this matters. Tech stack Frontend: Vanilla JS + WebAssembly PDF parsing: PDF.js AI: RAG over extracted text, runs in browser Hosting: Static files on CDN, converted via Static Site Converter Extensions: Available on Chrome Web Store (AskFiles AI, ApplyFlowAI, PixGrab etc.) Free forever No paywall, no watermark, used by 50k+ users/month across Toolscase. Built by me at Devonsite in Lahore, PK. Try it: ai.toolscase.com Voice version is live at voice.toolscase.com Feedback welcome – what AI tool should I add next?
DEV CommunityThe Giantsread at source
26 test cases for a 20-line bash function (and what each one taught me about Claude Code)
I shipped a Claude Code hook pack last week called Did-It-Actually . The headline feature is one hook: a PostToolUse script that catches Claude when it claims an edit happened but nothing actually changed on disk. The hook itself is twenty lines of bash. Writing it was an afternoon. Testing it was two days. That gap surprised me enough that I want to walk through it, because I think it says something about the hook-authoring job that isn't obvious from the docs. If you're writing hooks of your own, the lesson is: the hook is the easy part. Deciding what counts as "a real change" is where the actual thinking lives. Here are the 26 test cases I ended up with, grouped by the question each one answers. Category 1: the "did anything change at all" cases (6 tests) These are the ones you expect to write. They establish the baseline: given a PostToolUse event on Edit , did git see a diff? test-01-basic-edit-produces-diff-passes test-02-identical-content-write-fails test-03-empty-file-to-empty-file-fails test-04-create-new-file-passes test-05-delete-via-write-fails # Write cannot delete; this is the empty-content edge test-06-same-content-different-line-endings-fails Nothing surprising here. The one worth calling out is test 06 : Claude Code occasionally normalizes line endings on write, so a file with CRLF that gets rewritten as LF is a diff — but it's a diff that nobody intended. In practice this fires on Windows users under WSL, so I handle it with a git diff --ignore-cr-at-eol fallback. Category 2: the whitespace cases (5 tests) This is where the fun starts. test-07-trailing-newline-only-fails test-08-leading-whitespace-only-passes test-09-trailing-whitespace-only-passes test-10-indentation-change-only-passes test-11-blank-line-insertion-passes The judgment call: a trailing-newline-only diff is almost always a POSIX-compliance edit, not meaningful work. Nearly every modern editor adds one. Claude's model has internalized this and often adds a newline on write without being asked. If I count that as "the edit changed something", the hook never fires on a real no-op, because every save produces at least that one byte of diff. So I strip the final-newline-only case explicitly, and treat it as a no-op. But the other four — leading/trailing whitespace on content lines, indentation, blank-line insertion — I treat as real diffs. If Claude intended to fix indentation, that's work. The hook shouldn't second-guess intent; it should check whether work happened. I expose this as a config flag, because teams that use pre-commit for whitespace will want to tighten it. The default is "trailing-newline-only is a no-op, everything else is real." Category 3: the Claude-specific cases (6 tests) These are the ones I only knew to write after I'd been bitten. test-12-old-string-identical-to-new-string-fails test-13-old-string-already-not-in-file-fails-silently test-14-write-of-identical-content-after-read-fails test-15-multiedit-with-one-noop-chunk-passes test-16-multiedit-with-all-noop-chunks-fails test-17-edit-of-a-generated-dotfile-fails-with-warning Test 12 is the classic. Claude's Edit tool takes old_string and new_string ; if they're identical (which the model occasionally produces when it "fixes" something that was already correct), the file is rewritten byte-for-byte and git sees no change. Without this test, the hook silently passes. Test 13 is the one I lost half a day to. The Edit tool is supposed to error when old_string is not found. In some paths (particularly when the file was recently rewritten by a sibling tool call), the error is swallowed, the edit is reported as applied, and the file is untouched. The hook needs to catch the file being unchanged , independent of whether the tool reported success — because the tool report lies. Test 14 is the one that comes up most in logs. Claude does a Read , then decides to "ensure" something is in the file, then does a Write with the exact bytes it just read. The write is successful; the diff is empty. Tests 15 and 16 are the MultiEdit versions. MultiEdit applies a sequence of edits in one call. If any chunk produces a diff, I pass the hook (the model did real work, even if not all of it). Only an all-no-op MultiEdit fails. Test 17 is a late addition. Some projects have generated dotfiles ( .pre-commit-config.yaml , .github/workflows/* ) that Claude will touch and then re-generate. The regen is a no-op, but the file is tracked. I log it but don't exit 2, because exit 2 would create a loop (Claude re-edits in response to the correction, which also produces a no-op, which also fires the hook). Category 4: the git-state cases (5 tests) These are the ones that have nothing to do with Claude and everything to do with git being quirky. test-18-edit-of-untracked-file-passes-if-content-differs test-19-edit-of-ignored-file-passes-via-fallback-diff test-20-edit-during-merge-conflict-skips-hook test-21-edit-of-file-in-detached-head-passes test-22-edit-when-repo-has-no-commits-passes git diff HEAD behaves surprisingly in several of these. For untracked files, git diff HEAD -- path returns empty. For .gitignore 'd files, same. For a brand-new repo with no commits, there's no HEAD to diff against. The fallback is a direct cmp against the file's state as it was captured by a sibling PreToolUse hook. The pre-hook stashes the pre-edit bytes in a tempfile keyed on the file path and the tool call ID. The post-hook does cmp -s of the current file against that tempfile. If they differ, there was a change; otherwise it's a no-op. The merge conflict case is special: during a merge, the file is in an unstable state. The hook skips entirely and logs. Category 5: the integration cases (4 tests) test-23-hook-under-concurrent-edits test-24-hook-with-large-file-10mb test-25-hook-with-binary-file test-26-hook-with-symlink The concurrency test runs two Edit calls back-to-back on the same file; the hook has to key its state by tool-call ID or the second edit will see the first edit's pre-state and report a no-op. The 10MB test is pure performance — I need the hook to complete in under 100ms so it doesn't make every Edit feel laggy. It currently runs in about 40ms on a 10MB text file; cmp is fast. The binary case was unexpected: Claude occasionally edits binary files (compiled fixtures, PNGs). I bail out early if file --mime-type returns anything non-text, because comparing bytes is wasteful and false-positives aren't useful for binary. The symlink case was the last to be written. I resolve symlinks in both the pre- and post-state, because otherwise an edit to a symlink target reads as a no-op on the symlink file itself. What this cost me Writing the test cases took about twice as long as writing the hook. Reading them back now, I'd say: Six of the 26 are tests I would have written without being bitten (the Category 1 baselines). Twelve are tests I added after specific failures in real sessions. Eight are tests I wrote because I was going through the git/filesystem edge-case list systematically, trying to find corners the hook didn't handle. The 12 "added after failure" tests are the ones that matter most, and the reason the hook is worth running at all. If I'd shipped it with only Category 1, it would have caught maybe a third of the no-op cases I see in practice. If you're writing hooks Three things I'd pass on, now that this is behind me: A hook is a classifier, not a filter. You're building something that answers "is this call a real edit or a no-op?" — the hook infrastructure around it is maybe 10% of the work. The other 90% is the classifier, and the classifier is where every edge case lives. Write the test for every failure you see in a real session. Not "the kind of failure" — the specific sequence. Keep the Edit JSON, keep the file-before, keep the file-after. Replay them. These replays catch regressions when you change the normalization logic later. Exit 2 is a feedback channel, not a punishment. Your stderr goes back to Claude as a correction prompt. Think of it as writing an error message for a specific audience that will try to work around it on the next turn. "The edit of foo.py produced no diff" is informative; "if the file already contained what you intended, say so explicitly" is actionable. The second gets the agent to produce a different behavior on the retry. The full test suite + the hook source is in the public GitHub repo . The packaged install (with the other three hooks and an audit CLI) is Did-It-Actually on Gumroad — $9.50 with the SHOWDEV50 code through Oct 21. Happy to answer specific "how do you handle X" questions in the comments — the no-op detector has edge cases I probably still haven't seen. This post was drafted with AI assistance and reviewed for accuracy.
AdactioTop Front-end Bloggersread at source
I am afraid my stuffed dinosaur will eat us all. He must be stopped
My computed probability of the risk that Chomp will eat me, or in statistical terms, P(CHOMP), is approximately 95%. This has been a tough realization for me, because I consider Chomp a close friend, and I insist that no harm or regulation should come to him. However, if he should succeed in making me his first human sacrifice, it gives me no joy to inform you that there is nothing to stop him from eating us all. I know what you’re thinking, because I’ve heard it all before: Aren’t you just making up the words he’s saying? You’re the one who’s saying he wants to eat humans . I have to say, I’m insulted, and kind of sad for you. This kind of ad hominem attack only bolsters my argument. Why won’t you engage with the facts I have presented, namely that a stuffed dinosaur wants to devour all humans and only I can save us from that outcome? adactio.com/links/22800
Hacker News: Show HNLibraries, Frameworks, etc.read at source
Show HN: server-noonien – conflict-free agent memory across machines, no server
Comments
Hacker News: Show HNLibraries, Frameworks, etc.read at source
Show HN: Rails-preflight, a static check of what blocks your next Rails upgrade
Comments
Hacker News Front PageThe Giantsread at source
Show HN: Unfenced – Land for sale that borders public land
Hacker News: Show HNLibraries, Frameworks, etc.read at source
Show HN: Unfenced – Land for sale that borders public land
Comments
Hacker News: Show HNLibraries, Frameworks, etc.read at source
Show HN: Custom Firmware for Insta360 Wave
Comments
Flavio CopesMore Front-end Bloggersread at source
How to use OpenCode
Learn how to install OpenCode, connect a model, make your first code change, and configure project instructions and permissions in this hands-on tutorial.
Hacker News: Show HNLibraries, Frameworks, etc.read at source
Show HN: Theorem – Offline PDF/ePub reader that writes highlights to Local Vault
Comments
HeyDesignerDeveloper/Designer Newsread at source
Design systems directory
Agentic systems are the future of brands, The best way to use icons in Web UIs, Fit height to background pattern size with calc-size().
Hacker News Front PageThe Giantsread at source
Aidlc: Adaptive workflow steering rules for AI coding agents
Hacker News Front PageThe Giantsread at source
Show HN: Flipflop, Iroh-powered persistent tunnels for filesharing
AbduzeedoMulti Author Blogsread at source
Editorial Web Design: Rambling Muse by SocioDesign
Discover the sophisticated editorial web design for Rambling Muse by SocioDesign, featuring modular grids, custom icons, and refined typography. Commissioned by writer Clarisse Lehmann, London-based a...
Hacker News Front PageThe Giantsread at source
The Double Headed Stack Allocator
Jake Archibald's blogTop Front-end Bloggers63 min read
JPEG XL vs AVIF for web use-cases
JPEG XL has landed, but how does it compare?
Ahmad ShadeedMore Front-end Bloggersread at source
Fit height to background pattern size with calc-size()
Ana RodriguesMore Front-end Bloggersread at source
Goodbye sweet Jessie
This post has a content note of pet loss. Visit the link directly if you're okay with reading it.
Hacker News Front PageThe Giantsread at source
LFM2.5-24B-A2B: Built for fast on-device agents
AbduzeedoMulti Author Blogsread at source
Four Squares Cafe Is a Masterclass in Cafe Branding
Studio Moara gave Four Squares Cafe a chunky four-square wordmark, a line-drawn blob mascot, and a warm green-and-rust identity for lingering. Most cafe identities choose between type and character. F...
SitePointThe Giantsread at source
CSS Container Queries: Build Reusable Components That Adapt to Their Layout
null Continue reading CSS Container Queries: Build Reusable Components That Adapt to Their Layout on SitePoint .
SitePointThe Giantsread at source
HTML Semantics in Practice: Building More Accessible and Maintainable Web Pages
null Continue reading HTML Semantics in Practice: Building More Accessible and Maintainable Web Pages on SitePoint .
SitePointThe Giantsread at source
Progressive Enhancement: Building Websites That Work Before JavaScript Loads
null Continue reading Progressive Enhancement: Building Websites That Work Before JavaScript Loads on SitePoint .
SitePointThe Giantsread at source
Generate Visual Concepts for Imaginary Festivals
null Continue reading Generate Visual Concepts for Imaginary Festivals on SitePoint .
SitePointThe Giantsread at source
How to Optimize Web Fonts: WOFF2, Subsetting, and Loading Without Layout Shift
null Continue reading How to Optimize Web Fonts: WOFF2, Subsetting, and Loading Without Layout Shift on SitePoint .
AbduzeedoMulti Author Blogsread at source
Inside Robot Food’s Packaging Design for Postcard
Robot Food rebrands Singapore's Oasis Beauty Kitchen into Postcard with stamp-illustrated packaging design and custom-molded cleansing bars. The brief was refreshingly blunt. When founder Hildra Grace...
AbduzeedoMulti Author Blogsread at source
Editorial Design: Fons Hickmann Posters by Slanted
Explore Fons Hickmann Posters — Now and Then by Slanted Publishers, a masterclass in editorial design, linen binding, and typography. Monographs dedicated to poster work often stumble into a predictab...
Mux BlogCompany/Startup Blogs2 min read
Introducing spending alerts: Get notified when your spend crosses thresholds you set
Mux spending alerts email your team when your organization’s spend crosses a threshold you set. Learn how to set up alerts.
Mux BlogCompany/Startup Blogs3 min read
How Change.org uses video to turn signatures into stories
Change.org uses video to put a human face on petitions. See how switching to Mux gave their teams better insight into engagement, errors, and bot traffic.
SitePointThe Giantsread at source
How to Test Real Mobile Behavior on Actual Devices
Why emulators and simulators miss the bugs that only show up on real phones - network drops, battery throttling, OEM quirks and how to build a real-device testing process that catches them first. Continue reading How to Test Real Mobile Behavior on Actual Devices on SitePoint .
Thoughts From EricMore Front-end Bloggers6 min read
My Interop 2027 Rankings
In which I explore why I ranked what I did for Interop 2027.
Next.js BlogLibraries, Frameworks, etc.read at source
Next.js Security Update for Upstream Vulnerabilities
The October 14, 2026 out-of-band security update for Next.js is now available
AbduzeedoMulti Author Blogsread at source
Web Design: Point of View by Anna Hodarkovskaya
Anna Hodarkovskaya designed the web design for Point of View, tracing eyewear history through scroll transitions and typographic craft. Most digital longreads treat typography like an afterthought, du...
Bram.usMore Front-end Bloggers4 min read
Scroll-Triggered Animations Debugger (DevTools Extension)
I’m extremely pleased to announce the release of Scroll-Triggered Animations Debugger , a DevTools extension to visualize and debug CSS Scroll-Triggered Animations .
Google for DevelopersYouTube ChannelsVideoWatch on YouTube
Gemini 4 Argon: What’s new and what’s next
Heroku BlogCompany/Startup Blogs3 min read
Improved Static Website Support on Heroku with Front-End Web Cloud Native Buildpack
Recently, we expanded Heroku’s industry-shaping primitives, adding support for client-side browser apps, composed of HTML, CSS, and JavaScript. This built-in support for static web apps joins the existing Heroku Buildpacks: Ruby, Python, Go, Java, Clojure, Scala, Node.js, PHP, and .NET. With the new Front-End Web Cloud Native Buildpack (CNB), you get a complete static web […] The post Improved Static Website Support on Heroku with Front-End Web Cloud Native Buildpack appeared first on Heroku .
CodePenCompany/Startup Blogs1 min read
444: A Gosh-Dang Full-Blown Read/Write API
Marie and Chris are on the podcast to talk about our very new v2 API. This baby (slaps the hood) can do it all. It’s complete read/write access to all your Pens, Collections, and Assets. This API isn’t hamstrung by anything, so the door is open to whatever you want to build. We’ve already seen […]
Frontend Masters BlogMore Front-end Bloggers8 min read
Building a Fancy Scrollspy Using Modern CSS
Scrollspy navigation used to mean IntersectionObserver and a pile of JavaScript. With scroll-target-group, :target-current, and a little anchor positioning, a plain list of anchor links can highlight the current section all on its own.
Frontend Masters BlogMore Front-end Bloggers1 min read
“WebMCP is Agentic WebPerf”
Yoav Weiss makes a good point: “WebMCP is Agentic WebPerf”. I’ve seen both pushback to WebMCP in the form of “websites should just be better, or agents should be smarter, there shouldn’t be alternative paths to using them built.” And I’ve seen plenty of excitement for WebMCP in the form of “ooooo AI droolface”. But […]
AbduzeedoMulti Author Blogsread at source
Branding: PUNO Coffee by CreativeWise
CreativeWise built the PUNO Coffee identity on a stepped pyramid from the Andes. Bold type, earthy color, one system from bag to bottle. Most coffee identities pick one: the romance of origin or the r...
SitePointThe Giantsread at source
Your JavaScript Character Counter Is Probably Wrong: Unicode Explained
null Continue reading Your JavaScript Character Counter Is Probably Wrong: Unicode Explained on SitePoint .
AbduzeedoMulti Author Blogsread at source
Florian Cornut’s Brand Identity for Live Current
Florian Cornut and Baptiste De Almeida craft a fluid brand identity for Chicago's Live Current using Neue Montreal type and merging spheres. Most mindfulness brands lean on clichés. They reach for lot...
CSS WizardryTop Front-end Bloggersread at source
Web-Perf Wednesday 013 – CrUX Adds Soft-Navigation Core Web Vitals
CrUX adds experimental Core Web Vitals for SPA soft navigations, while Firefox and Safari improve cache evidence, image delivery, and rendering.
SyntaxPodcastsAudioListen
1047: Adobe has a big problem
Someone just clean-room ported seven Adobe apps to Rust, and Wes is shocked at how good it is. Scott and Wes break down what it gets right, what’s still missing, and why copying software is easy but inventing what comes next is the hard part. Show Notes 00:00 Welcome to Syntax!…
W3C BlogBrowsers, engines, etc.2 min read
Introducing group activity pages
W3C groups now have an Activity page that shows, month by month, their charters, publications, meetings, participants and GitHub work in one place. The pages are in beta, and we would like your feedback.
Flavio CopesMore Front-end Bloggersread at source
What “open weights” means for AI models
Learn what open weights means for AI models, how it differs from open source, and what the Qwen3.8 and Kimi K3 downloads show about licenses and scale.
SitePointThe Giantsread at source
Building a Reliable After-Hours Call-Routing Workflow with TypeScript
null Continue reading Building a Reliable After-Hours Call-Routing Workflow with TypeScript on SitePoint .
HeyDesignerDeveloper/Designer Newsread at source
Taste is not a design methodology
Emerging patterns: Design leadership in 2026, The new age of software, Inside Anthropic’s design team.
Bruce Lawson's personal siteMore Front-end Bloggersread at source
Farage believes Andrew Tate is “an important voice”
Now we know that the execrable Andrew Tate is wanted by British police for allegedly making indecent images of a child, rape, stranging women during sex, and possession of extreme pornography depicting sexual intercourse between people and live animals — one involving an eel and another involving a horse. Let us not forget that Nigel […]
Flavio CopesMore Front-end Bloggersread at source
How to handle web requests in C with CGI 1.1
Learn CGI 1.1 by building a C web application: read requests, decode forms, return HTML and errors, and run the executable through Apache.
Zach LeathermanTop Front-end Bloggers1 min read
All of my 300+ browser tabs are beautiful
I am a habitual tab hoarder so I created a Firefox addon to help me manage every one of my very special browser tabs. Sort tabs by oldest, unload them, find duplicates, and consolidate tabs to a single window — buncha useful stuff in there! Download from Mozilla Addons Source…
AdactioTop Front-end Bloggersread at source
Installing Websites Must Be Easy
A message to Apple from the W3C Technical Architecture Group: We expect user agents to make installation an obvious affordance to users. Specifically, we expect the install option to clearly name the action, and to be easily reachable from the browser’s UI. An entry inside a menu with an unrelated label (such as Share), or several menu layers deep, does not meet this expectation. adactio.com/links/22799
AdactioTop Front-end Bloggersread at source
Zero
The term “no-brainer” refers to a suggestion that is so compelling that it would be foolish to even consider not doing it. I came across a no-brainer recently, thanks to Matt Hobbs: Make all UK Government websites free to access without mobile data charges. When I was done slapping my forehead at how obvious this proposal is, I signed the petition . If you’re a UK resident, you should do the same. And then spread the word. This is the kind of proposal that I imagine any politician would be happy to support, regardless of party affiliation, so don’t be shy about contacting your local MP. I guess in theory this could be opposed on the grounds that it technically violates net neutrality, but I would hope that the obvious societal benefits would outweigh that objection. Like I said, a no-brainer.
AdactioTop Front-end Bloggersread at source
Reading The Tusks of Extinction by Ray Nayler.
Auth0 BlogCompany/Startup Blogsread at source
One SDK, Multiple Platforms: Introducing the Auth0 SDK for Kotlin Multiplatform
Add login, logout, and secure token storage to iOS and Android apps using the Auth0 Kotlin Multiplatform SDK. Simplify cross-platform auth.
Frontend FocusMulti Author Blogsread at source
The best way to use icons in Web UIs
🚀 Frontend Focus #762 — October 14, 2026 | Read on the web What the iPhone Duo Means for Web Development — The Duo is out next week, so Darius fired up the simulator to try the Device Posture and Viewport Segments APIs (both available behind a flag). Using Video.js components and CSS, he tried to recreate Netflix's folded-player UI, but Apple's fullscreen restrictions made things a tad interesting. Darius Cepulis (Video.js) The Best Way to Use Icons in Web UIs — Lea shows how to insert and tweak icons without touching any HTML, using --icon custom properties, @property , style queries, and mask . The result? A pure CSS icon system that’s “ more flexible and maintainable than what you've been using ”. Lea Verou Become the TypeScript Expert on Your Team — Be the person your team trusts to review AI-generated TypeScript. Learn generics, conditional types, and discriminated unions in depth, then use that knowledge to catch subtle errors and steer AI toward cleaner types. Master.dev sponsor The 2027 Web Platform Feature Ranking Returns — Back for a second year, Firefox's ranker lets you sign in with GitHub, compare and rank the Interop 2027 proposals. Jake Archibald says last year’s findings (taken from ~1,900 rankings) helped “ prioritise a lot of work this year” , so it's worth having your say. Firefox ⚡️ IN BRIEF 📊 The results of the 2026 Stack Overflow Developer Survey are out. With over 30,000 responses, JavaScript came in as #1 language for the 12th year in a row. 🦊 Mozilla now offers an Enterprise version of Firefox, focusing on things like compliance, regulatory requirements, security, and telemetry controls. 🔒 Starting February next year all new/renewed Let's Encrypt certificates will use a 64 day lifetime by default (down from 90). 🧭 Apple has shared a new page making their case for switching over from Chrome to Safari. 🛡️ If you miss Arc, Crest is a new browser hoping to scratch a similar itch . It curiously offers both Chromium and WebKit rendering. 📙 Articles, Opinions & Tutorials Lazy Loading Images and Media: How Far Ahead Browsers Fetch — Ben measures how far ahead Chrome, Firefox and Safari fetch lazy-loaded media. He also shares what we can lazy load (including video and audio in Chromium browsers), and covers where lazy , eager and fetchpriority fit. Ben Schwarz (Calibre) How Sindre Sorhus' Personal Site is Now Made in Swift — A prolific JavaScript developer explains why he dropped Astro and now builds his personal site, styling and markup included, with Apple's Swift language. Sindre Sorhus Agentic Mobile Development, from First Prompt to the App Store — Expo gives your coding agent the mobile infrastructure it is missing: native builds, store submits and live updates. Expo sponsor Recursion is DEAD and HTML Killed It — A fun, practical look at using Chrome’s new out-of-order HTML streaming (known as declarative partial updates ) to let the browser assemble nested comment threads for you. Chromium-only, but exciting. Dave Rupert 🖼️ I Guess I’m Using WebP Now — It’s been 16 years since Google introduced the WebP image format, support is now widespread ( 97% ), and hey, maybe they’re worth another look? Gavin Anderegg Scroll-Triggered Animation as the Missing Piece for Scrollytelling on the Web — A look at the CSS Animation Triggers spec, an Interop 2027 candidate, and how it kicks off an animation based on scroll position. Five demos show what it enables. Brecht De Ruyte Firefox 158 Enables JPEG XL by Default — Coming shortly after Chrome did the same, Firefox now supports JPEG XL by default, adding image/jxl to its image Accept header. Mozilla 🌐 In other browser news, Chrome 156 beta is out (stable next week) and adds support for CSS random() . Also, Safari Technology Preview 254 is out with both calc-size() and interpolate-size support. Fingerprints are the New Cookies — Several ways JavaScript and browser APIs can assign a unique identifier to you. Roy Fish How to Test an iframe 's postMessage Handler Keshav Malik 🧰 Tools, Code & Resources Bootstrap 6 Alpha — An early look at the popular framework's first major release since 2021. Modernization is the order of the day, with most Sass variables now CSS variables, ESM-only JS plugins, and it leans on more browser-native features. Mark Otto reseter.css 3.0: The Modern CSS Reset — v3 of this popular CSS reset is a major rework. Both the 'full' and 'mini' variants now ship wrapped in @layer reset for predictable overrides, use logical properties throughout, and are built with Lightning CSS. And all just ~1KB compressed. Krish Gupta Publish to Miris Straight from Adobe Substance 3D Painter — Stop sharing decimated 3D content. Stream your high-fidelity Adobe Substance 3D Painter assets via the Miris plugin. Miris sponsor ascii.rest: Animated ASCII Art for Web Pages — A collection of over 200 animated ASCII pieces, from lovely art pieces to more practical components like loaders, spinners, charts, shapes, etc. Shubham Kira Screen Ruler: Measure, Inspect, and Edit Web Pages — A popular Chromium extension for taking page measurements, CSS inspection, and live edits. Comes with an MCP server too. Myster Violets webvitals.fyi: Explore and Compare Every Performance Tool — Currently includes 29 tools, each given an opinionated rating, based on capabilities and role (e.g. frontend vs. performance engineer). Phil Gourley FontGlyph: Local-First Tool to Convert SVG & PNG Icons into a WOFF2 Icon Font cwittenberg Vite Plugin Registry: Discover and Compare Plugins for Vite, Rolldown, & Rollup VoidZero Lift HTML: A Tiny Library for Building HTML Web Components Yaroslav Lapin 📰 Classifieds Bring your wild ideas to Mozilla Festival . Gather and connect with other developers and creatives in Barcelona, Oct. 28-30. Get your badge . 📸 Add barcode scanning to your web app using STRICH , a lean JS SDK. Free 14-day trial, try the demo app today!