I built a Chrome Extension that saves product images + context directly to Google Drive & Sheets

I built a Chrome Extension that saves product images + context directly to Google Drive & Sheets

Most product research workflows are secretly broken. You just don’t notice until you’re drowning in 400 unnamed screenshots and a half-filled spreadsheet at 11pm.

Four problems. One tool.

❌ Manual saving steals hours you don’t have
❌ Scrapers are unstable and dump garbage images with zero context
❌ Images without context become completely unsearchable over time
❌ Your workflow breaks the moment you switch devices

Here’s how ImageSnap solves each one.

❌ Pain #1: Manual saving is a productivity black hole

The typical workflow:

  1. Find a product image you want
  2. Right-click → Save As
  3. Rename the file so it makes sense later
  4. Navigate to the right folder
  5. Open your spreadsheet
  6. Manually type in the product name, price, URL, supplier, notes…
  7. Repeat. 50 times. Every session.

You’re not doing research. You’re doing data entry.

✅ How ImageSnap fixes it: One click opens a side panel. The product name, price, and URL are already filled in — scraped automatically from the page. Hit Snap and the image goes to Google Drive, the row goes to Google Sheets. The whole thing takes 5 seconds.

❌ Pain #2: Scrapers are unstable and give you garbage

Automated scrapers sound great in theory. In practice:

  • They break every time the site updates its HTML
  • They pull hundreds of irrelevant images — icons, banners, ads, lazy-loaded placeholders
  • You get a dump of raw data with no context about why you saved anything
  • Running them requires technical setup most researchers don’t want to deal with

✅ How ImageSnap fixes it: You stay in control. You browse naturally, and when you find something worth saving, you capture it intentionally. No garbage images. No broken selectors. No maintenance. You decide what gets saved and why.

❌ Pain #3: Images without context become unsearchable

This is the silent killer. You save 300 product images over two weeks. Then you need to find “that ceramic supplier from last Tuesday with the MOQ under 500.”

Good luck.

A folder of IMG_20260521_143022.jpg files tells you nothing. Even if you renamed them, you’ve lost the price, the URL, the supplier name, the category — everything that made that image worth saving in the first place.

✅ How ImageSnap fixes it: Every image is captured with structured metadata — price, URL, title, custom fields you define per category. It all lives in Google Sheets, which means you can filter, sort, and search across everything instantly. The image and its context are permanently linked by ID. You’ll find that ceramic supplier in 10 seconds.

❌ Pain #4: Your workflow breaks the moment you switch devices

You save images on your laptop. You spot products on your phone during lunch. You reference your research on a tablet in a meeting. Three devices, three separate systems, constant friction.

✅ How ImageSnap fixes it: Everything syncs to your Google Drive and Google Sheets in real time. Open your sheet on any device — phone, tablet, laptop — and your entire research library is there, with images and context intact. The extension works on desktop. The web dashboard works everywhere. One workflow, every device, any time.

The technical side (for the devs)

The project is a monorepo with two independent build targets:

  • Web dashboard → Next.js 15 + React 19 + Tailwind CSS 4
  • Chrome extension → Vite + TypeScript, Manifest V3

Both share a src/shared/ layer — same auth logic, type definitions, and Google API helpers across both contexts.

src/
├── shared/        ← shared between web + extension
│   ├── lib/       ← google-auth, sheets, drive helpers
│   └── hooks/     ← useAppData, etc.
├── web/           ← React app (popup + web dashboard)
└── extension/     ← manifest, content_script
app/               ← Next.js routes + API

One thing that caught me off guard: extension popups are completely stateless. Every time the popup closes, the JS context is destroyed — including any in-memory auth tokens.

My first instinct was to use an httpOnly session cookie via the API. It silently broke because cookies with sameSite: lax don’t get sent in cross-origin fetch calls from the chrome-extension:// protocol. The fix was chrome.storage.local:

// After successful login
saveTokenToExtStorage(token, profile.email);

// On every popup reopen — no server roundtrip needed
const stored = await loadTokenFromExtStorage();
if (stored?.token) {
  const profile = await getUserInfo(stored.token);
  if (profile) { /* restore state instantly */ }
}

On the data side, ImageSnap stores nothing on its own servers except subscription status. Your images live in your Google Drive. Your metadata lives in your Google Sheets. You own everything.

Try it

ImageSnap is live on the Chrome Web Store with a free tier (30 captures/month).

👉 imagesnap.cloud

If you’re doing ecommerce research, sourcing, competitor tracking, or content creation — give it a try and let me know what you think in the comments.

95+ browser-based dev tools that never touch a server

Every developer has a folder of bookmarks for one-off tools —
a JWT decoder here, a Base64 encoder there, a cron expression
tester somewhere else. Half of them send your data to a server
you know nothing about.

I built Wizbit.to to fix that.

What it is

95+ free developer utilities, all running 100% in your browser.
No server ever sees what you paste. No account. No ads. No limits.

What’s inside

Data & encoding

  • JSON formatter, beautifier, minifier & validator
  • Base64 encoder/decoder
  • CSV → JSON, CSV → Markdown, YAML ↔ JSON converters
  • ENV file parser and ENV → JSON/YAML converter

Security & crypto

  • JWT decoder with expiry checker and claims differ
  • Hash generator (SHA-256, SHA-512)
  • RSA key pair generator & X.509 certificate inspector
  • BIP39 seed phrase generator
  • Password generator & strength checker

Developer utilities

  • Regex tester with live match highlighting
  • Cron expression builder and syntax decoder
  • SQL formatter
  • Webhook visualizer and Stripe webhook simulator
  • UUID/GUID generator (v4, v5, v7, bulk)
  • HTTP status explorer
  • CIDR subnet calculator
  • Semver comparator

Frontend & CSS

  • SVG → JSX converter and SVG cleaner
  • CSS shadow, clip-path, and glassmorphism generators
  • Color converter (HEX, RGB, HSL)
  • Bezier curve and spring physics playground

AI & LLM tools

  • Prompt tokenizer and token calculator
  • Context trimmer and prompt compressor
  • AI response cleaner and text humanizer

VFX & camera (yes, really)

  • Depth of field visualizer
  • LUT .cube file 3D visualizer
  • ACES color space matrix calculator
  • Timecode and frame arithmetic calculator
  • Focal length / FOV / crop factor matcher

Why it’s different

Most online dev tools are thin wrappers around a server call.
That’s fine for public data — but when you’re pasting a JWT,
an RSA private key, an ENV file, or a seed phrase, you’re
trusting a server you’ve never audited.

Wizbit processes everything locally. The code runs in your tab.
Close the tab and it’s gone.

Ghost Bugs Cost $40K: A Neural Debugging Postmortem

When AI silently fails for weeks

A production RAG system handling 12,000 queries/day recently ran for three weeks delivering silent errors, resulting in an estimated $40K in flawed decisions before anyone noticed.

The issue wasn’t a crash or a syntax error. It was vector embedding drift—a silent failure state where the system returned incorrect results that appeared entirely plausible on the surface.

These are often called “ghost bugs.” They don’t throw runtime exceptions, they don’t trigger error logs, and they typically pass standard unit tests. Below is an analysis of how this happens, how to identify it, and how to build a monitoring system to catch it.

Tool: Debug your vectors with Vector Distance Calculator

The $40K mistake

In this case study, the RAG pipeline recommended suppliers based on vector search:

query → vector search → top 3 results → LLM ranking.

For three weeks, the system recommended “Supplier B” over “Supplier A,” even though Supplier B was 23% more expensive. The team trusted the outputs because they looked structurally correct.

The root cause was simple: the embedding model was updated from text-embedding-3-small to text-embedding-3-large, but the older documents in the database were never re-indexed. The vectors lived in entirely different dimensional spaces, but the database didn’t reject the query.

// The silent failure
const queryVector = await embed(query, 'large'); // 3072 dims
const results = await db.search(queryVector);
// Returns vectors from 'small' model (1536 dims)
// Database pads with zeros. No error. Wrong results.

Calculating cosine similarity between mismatched dimensions still returns a mathematical output—it is simply the wrong output.

Ghost bug #1: Dimensional drift

This is a common RAG failure mode. To prevent this during deployments, systems should validate dimensions programmatically:

import { cosineSimilarity } from './vector-utils';

async function detectDimensionalDrift() {
  const testQuery = "test document for embedding";

  // Embed with current model
  const currentEmbedding = await embed(testQuery);

  // Check database sample
  const sample = await db.getRandomVector();

  if (currentEmbedding.length !== sample.vector.length) {
    throw new Error(
      `DIMENSIONAL MISMATCH: Current=${currentEmbedding.length}, DB=${sample.vector.length}`
    );
  }

  // Also check distribution
  const similarity = cosineSimilarity(currentEmbedding, sample.vector);
  if (similarity > 0.99) {
    console.warn('Suspiciously high similarity – possible duplicate model');
  }
}

Running this check as part of continuous integration or deployment pipelines can prevent dimensionality mismatches entirely.

Tool: Test embeddings with RAG Chunk Simulator

Ghost bug #2: The chunk boundary failure

Standard RAG practices often involve chunking documents at fixed limits (e.g., 512 tokens). However, key context can easily be split across chunk boundaries, rendering the retrieved data incomplete.

Example:

  • Chunk 1: “Supplier A: $100/unit. Terms: Net 30.”
  • Chunk 2: “Excludes bulk discount of 40% for orders >1000 units.”
  • Query: “cheapest supplier for 2000 units”

The system retrieves Chunk 1 because it mentions the price, but misses Chunk 2 because the discount details fell into a separate vector. As a result, the model recommends the wrong supplier.

The solution: overlapping chunks with metadata

function smartChunk(text, size = 512, overlap = 128) {
  const chunks = [];
  const sentences = text.split('. ');

  let currentChunk = '';
  let currentSize = 0;

  for (const sentence of sentences) {
    const tokens = estimateTokens(sentence);

    if (currentSize + tokens > size) {
      // Save current chunk with overlap metadata
      chunks.push({
        text: currentChunk,
        metadata: {
          has_continuation: true,
          next_chunk_preview: sentences.slice(0, 3).join('. ')
        }
      });

      // Start new chunk with overlap
      const overlapText = currentChunk.split(' ').slice(-overlap).join(' ');
      currentChunk = overlapText + ' ' + sentence;
      currentSize = estimateTokens(currentChunk);
    } else {
      currentChunk += ' ' + sentence;
      currentSize += tokens;
    }
  }

  return chunks;
}

Ghost bug #3: Temperature creep

LLM parameters are highly sensitive. A configuration meant for creative tasks can cause hallucinations if applied to analytical ranking tasks.

Consider a configuration setup like this:

// config.js
export const LLM_CONFIG = {
  temperature: process.env.LLM_TEMP || 0.7,
  //...
};

If an environment variable in production is accidentally modified (e.g., setting LLM_TEMP=1.2 for testing without reverting it), the model can produce highly inconsistent or hallucinated supplier rankings without throwing a system error.

The solution: runtime configuration validation

function validateLLMConfig(config) {
  const issues = [];

  if (config.temperature < 0 || config.temperature > 1) {
    issues.push(`Temperature ${config.temperature} out of bounds [0][1]`);
  }

  if (config.temperature > 0.3 && config.use_case === 'ranking') {
    issues.push('High temperature for ranking task – expect inconsistency');
  }

  // Check for drift from baseline
  const baseline = 0.7;
  if (Math.abs(config.temperature - baseline) > 0.2) {
    issues.push(`Temperature deviated >0.2 from baseline`);
  }

  if (issues.length > 0) {
    throw new Error(`LLM Config Validation Failed:n${issues.join('n')}`);
  }
}

Tool: Validate your JSON configs with JSON Validator

Building a neural debugger

Traditional debuggers are not designed to inspect vector spaces. Monitoring a production RAG system requires tracking the invisible metrics of embeddings and distributions.

Component 1: Embedding fingerprinting

You can verify the stability of your embedding space by regularly testing a static group of phrases:

async function createEmbeddingFingerprint() {
  const testPhrases = [
    "the quick brown fox",
    "supplier pricing data",
    "technical specifications",
    "random unrelated text about cats"
  ];

  const fingerprints = await Promise.all(
    testPhrases.map(async phrase => ({
      phrase,
      vector: await embed(phrase),
      hash: simpleHash(await embed(phrase))
    }))
  );

  await db.saveFingerprint({
    timestamp: Date.now(),
    model: EMBEDDING_MODEL,
    fingerprints
  });

  return fingerprints;
}

// Check for drift on a daily schedule
async function checkForDrift() {
  const baseline = await db.getLatestFingerprint();
  const current = await createEmbeddingFingerprint();

  for (let i = 0; i < baseline.fingerprints.length; i++) {
    const similarity = cosineSimilarity(
      baseline.fingerprints[i].vector,
      current[i].vector
    );

    if (similarity < 0.95) {
      alert(`EMBEDDING DRIFT DETECTED: ${baseline.fingerprints[i].phrase}`);
    }
  }
}

Component 2: Query-result monitoring

Analyzing the distributions of search scores helps flag anomalies before they impact users:

async function monitorQueryQuality(query, results) {
  const metrics = {
    query,
    timestamp: Date.now(),
    resultCount: results.length,
    avgSimilarity: results.reduce((sum, r) => sum + r.score, 0) / results.length,
    topScore: results[0]?.score || 0,
    scoreVariance: calculateVariance(results.map(r => r.score))
  };

  if (metrics.avgSimilarity < 0.7) {
    logWarning('Low similarity scores – possible embedding mismatch');
  }

  if (metrics.scoreVariance < 0.01) {
    logWarning('All scores nearly identical – possible dimensional issue');
  }

  if (metrics.topScore > 0.99) {
    logWarning('Suspiciously perfect match – check for data leakage');
  }

  await db.logMetrics(metrics);
}

Component 3: The “Golden Query” set

Run automated tests against queries with static, known correct answers to evaluate performance continuously:

const GOLDEN_SET = [
  {
    query: "cheapest supplier for bulk orders",
    expected_top_result: "supplier-a",
    min_similarity: 0.85
  },
  {
    query: "supplier with fastest delivery",
    expected_top_result: "supplier-c",
    min_similarity: 0.80
  }
];

async function runGoldenTests() {
  const failures = [];

  for (const test of GOLDEN_SET) {
    const results = await ragSearch(test.query);
    const topResult = results[0];

    if (topResult.id !== test.expected_top_result) {
      failures.push({
        test: test.query,
        expected: test.expected_top_result,
        got: topResult.id,
        similarity: topResult.score
      });
    }

    if (topResult.score < test.min_similarity) {
      failures.push({
        test: test.query,
        issue: 'low_similarity',
        score: topResult.score,
        threshold: test.min_similarity
      });
    }
  }

  if (failures.length > 0) {
    await alertEngineering('GOLDEN TEST FAILURES', failures);
  }

  return failures.length === 0;
}

// Execute tests at regular intervals (e.g., every 5 minutes)
setInterval(runGoldenTests, 5 * 60 * 1000);

Typical metrics after implementation

Implementing these checks transitions a team from reactive troubleshooting to proactive observability:

Metric Unmonitored Monitored
Detection Time Weeks (or never) Minutes
Silent Failures Caught None High
Engineering Stance Reactive Proactive

Tool: Format your debug logs with JSON Formatter

Auditing your RAG setup

If you are running a production RAG system, a quick baseline audit is highly recommended:

  1. Verify dimensions: Ensure all stored vector spaces strictly match your runtime embeddings model.
  2. Establish golden tests: Set up a list of key queries with deterministic target results.
  3. Audit similarity distribution: Look for unexpectedly flat or perfect similarity scores.
  4. Enforce configuration schema: Guard runtime variables like temperature with schema validation.

RAG applications operate on non-deterministic models. If you are not monitoring the hidden parameters of your vector spaces, you may be missing critical bugs.

Have you encountered silent errors in your AI applications? Share your debugging strategies or experiences in the comments below.

Tools referenced in this post:

  • Vector Distance Calculator
  • RAG Chunk Simulator
  • JSON Validator
  • JSON Formatter

Read more: Debugging RAG Vector Distance

SECPAC: A Lightweight CLI Tool to Password-Protect Your Environment Variables 🚀

If you are tired of dealing with plain-text .env files and manual editing, SECPAC is a lightweight Node.js CLI tool built to bring structured management and optional password encryption to your local environment variables. By serving as a faster, more secure alternative to traditional setups, it allows you to easily view, grab, or mask sensitive API keys directly from your terminal. Check out the project, join our growing community at r/secpac, and let me know how it improves your development workflow!

PasteCheck v1.7 + v1.8 — Hints that tell you what to fix, and a nudge panel that tells you where to start

Most linters tell you what’s wrong. They stop there.

PasteCheck has always shown you the error and the line. But “var is discouraged” doesn’t tell a beginner what to actually do about it. “Empty catch block” doesn’t tell you why it matters or what to replace it with.

That changes in v1.7.

Every error and warning message now ends with one actionable sentence. Not a docs link. Not a vague suggestion. One clear fix.

  • Prefer 'let' or 'const' over 'var' — use 'const' if the value never changes, 'let' if it does
  • 'eval()' executes arbitrary code — dangerous and a security risk — remove it and find a safer alternative like JSON.parse() or a lookup object
  • Empty catch block — errors are silently swallowed — add at least console.error(e) so failures don't disappear silently

This covers every language PasteCheck supports — JavaScript, TypeScript, Python, HTML, and CSS. 30 messages updated across the entire linter.

Two Reddit commenters validated this independently before it was built. Both said the same thing unprompted — knowing what’s wrong isn’t enough, you need to know what to do next. That’s what v1.7 ships.

v1.8 adds something no other paste tool has.

When errors or warnings exist, a collapsible “What to do next” panel appears below the results. Not per-error guidance — big picture debugging logic. The kind of thing an experienced developer would tell you if they were sitting next to you.

  • If you have errors: fix the first one before the rest. Errors cascade. One missing bracket can cause five false positives below it.
  • If you have warnings only: no errors is good — work through warnings one at a time using the fix hints above.
  • If you have both: ignore the warnings for now. Fix the errors first. Warnings can wait until your code runs.

It’s collapsed by default. It doesn’t get in the way. It’s there when you need it.

Both features were designed around the same principle — PasteCheck doesn’t just find your mistakes, it helps you understand them.

Built entirely from an Android phone. Live at pastecheck.co.uk.

8 Real Ways Developers Make Money in 2026 (Ranked by Effort)

Every “make money as a developer” post promises passive riches by Friday. This isn’t that.

I’ve tried most of these. Some paid rent. Some paid for coffee. One quietly pays me while I’m asleep. Below is an honest ranking — sorted from “most effort for the money” to “least” — with realistic timelines and the catch for each, because the catch is the part nobody writes about.

1. Freelancing / contracting

Effort: High · Time to first dollar: 1–6 weeks · Ceiling: Very high

Still the fastest reliable path. Upwork and Fiverr are crowded races to the bottom; the money is in Toptal, Contra, Gun.io, or — best of all — your own network. Your second client should come from your first client’s referral, not a bidding war.

The catch: You’re trading hours for money, plus the unpaid hours of finding clients, scoping, and chasing invoices. It’s a business, not a side hustle. Treat it like one or it’ll treat you like one.

Tip: Niche down. “React developer” competes with 200,000 people. “I rescue stalled Next.js migrations for B2B SaaS” competes with almost none and charges 3x.

2. Build and sell digital products

Effort: High up front, low after · Time to first dollar: 1–3 months · Ceiling: High

Boilerplates, UI kits, Notion templates, VS Code themes, icon sets, Figma files, code generators. Build once, sell forever. Gumroad, Lemon Squeezy, and the GitHub Marketplace make distribution trivial.

The winners solve a boring problem you’ve already solved for yourself. Auth boilerplate. A Stripe + webhooks starter. A deploy script you keep copy-pasting.

The catch: “Build once” is a lie — you’ll support, update, and market it forever. Distribution is 80% of the work and writing the code was the fun 20%.

3. Micro-SaaS / indie hacking

Effort: Very high · Time to first dollar: 3–12 months · Ceiling: Life-changing

The dream: a small product with recurring revenue you own end to end. Totally real — see the indie hackers shipping $5–50k/month tools. Also where most developers quietly burn six months and ship to crickets.

The catch: The hard part isn’t the code, it’s finding a problem people will pay for before you build. Talk to users first. Charge from day one. If nobody pays for the landing page, nobody pays for the product.

4. Teaching: courses, YouTube, cohorts

Effort: High · Time to first dollar: 2–6 months · Ceiling: High

Udemy, YouTube, Egghead, or your own cohort-based course. You don’t need to be the world’s best engineer — you need to be one chapter ahead of your audience and good at explaining.

The catch: It compounds slowly and publicly. Your first 20 videos will flop. The audience is the asset, and audiences take a year+ to build.

5. Technical writing

Effort: Medium · Time to first dollar: 2–4 weeks · Ceiling: Medium

Companies pay $200–$1,000+ per article for quality developer content. Dev tool companies (databases, auth, observability, anything DX-focused) constantly need writers who can actually code. Check Draft.dev, the DevRel job boards, or just pitch companies whose tools you use.

The catch: You need writing samples to get hired to write. Publish a few strong posts (like this one) on your own blog or dev.to first, then point to them.

6. Open source sponsorship

Effort: Medium-high · Time to first dollar: Slow · Ceiling: Medium

GitHub Sponsors, Open Collective, Polar. If you maintain something people depend on, companies and individuals will fund it. Some maintainers fund full-time work this way.

The catch: Sponsorship follows traction, not the other way around. This pays because your project is useful to thousands, which is years of unpaid work first. Treat it as upside on work you’d do anyway, not a plan.

7. Bug bounties

Effort: High · Time to first dollar: Unpredictable · Ceiling: Very high

HackerOne, Bugcrowd, Immunefi (the last one pays eye-watering sums for smart-contract bugs). If you genuinely enjoy breaking things, the top hunters out-earn senior salaries.

The catch: Brutally feast-or-famine and steep learning curve. You can spend 40 hours and find nothing, or one hour and find $20k. Most people find nothing for a long time.

8. Get paid for your profile — even when you’re not job hunting

Effort: Low · Time to first dollar: Days · Ceiling: Modest (but truly passive)

Here’s the one most developers haven’t tried, and the reason I wrote this list.

Think about your last job search. If someone had paid you $100/hour to do it, what would you have earned? Probably thousands. Instead, companies pay for recruiters, job boards, and ads to find you — and you, the actual product, get nothing.

A newer platform called Timofi flips that. You record a short set of clips — basically a mini interview (how you think, a project you’re proud of, a tradeoff you’d defend) — and drop one link on your LinkedIn, GitHub, or resume. When a recruiter wants to unlock it and actually see you talk, they pay a small fee, and you keep 60%.

The part that sold me: it works even when you’re not looking for a job. If you’re a solid engineer, recruiters already DM you constantly and you ignore them. This makes the good ones pay for your attention. Worst case, someone unlocks, watches, and nothing comes of it — you still got paid for the time. The downside has a floor.

The honest catch: It’s early. The broader hiring marketplace is rolling out gradually, and your earnings scale with how much profile traffic you actually have — so it pays best if you already get inbound from recruiters. It won’t replace your salary. Think “found money on traffic you were ignoring anyway,” not “quit your job.” Setup takes a few minutes, so the effort-to-upside ratio is hard to argue with.

Full disclosure for the dev.to crowd: I think this category — getting paid for your own attention — is genuinely new, which is why it’s on the list. Try it, don’t try it, but the framing alone is worth stealing.

So which one should you actually pick?

  • Need money this month? → Freelancing (#1) or technical writing (#5).
  • Want something that compounds? → Products (#2), teaching (#4), or micro-SaaS (#3).
  • Want upside on work you already do? → Open source sponsorship (#6) or your profile (#8).
  • Love breaking things? → Bug bounties (#7).

The meta-lesson across all eight: stop giving your work, your time, and your attention away for free. Whether you package it as a product, a course, an article, or a paid mini interview — the developers who make money are the ones who decided their output has a price tag.

Which of these have you tried? What worked, what flopped? Drop it in the comments — genuinely curious what’s paying for the dev.to crowd in 2026.

I built a free AI-powered Git CLI that writes your commit messages for you

We’ve all been there. You just spent 3 hours fixing a bug, you stage your changes, and then you stare at the commit message input like it owes you money. You end up typing fix stuff and moving on.

I got tired of it. So I built gritch.

What is gritch?

gritch is a free, open source CLI tool that uses Groq’s free AI API (LLaMA 3.3) to help you write better commits, review your code before pushing, generate changelogs, and understand old commits all from your terminal.

What it can do

Generate a conventional commit message from your staged changes:

git add .
gritch commit

Review your code before pushing:

gritch review
gritch review --language javascript

Explain any commit in plain English:

gritch explain abc1234

Generate a changelog between two versions:

gritch changelog v1.0.0 v1.1.0

How to get started

Install it globally:

npm install -g gritch

Get a free API key from https://console.groq.com and set it:

Windows:

$env:GROQ_API_KEY="your_key_here"

macOS / Linux:

export GROQ_API_KEY="your_key_here"

Then run gritch in any Git project.

Why I built it

I wanted to build something that developers actually use daily not another todo app or weather dashboard. gritch solves a real problem I had every single day, and I built it entirely with TypeScript and Node.js with zero paid services required.

Links

If you find it useful, a star on the repo goes a long way. _

OpenLiDARViewer: A Browser-Based LiDAR and Point-Cloud Viewer

Rendering LiDAR Scans in the Browser Without Uploading Anything

Most point-cloud workflows still start the same way.

Install a desktop tool.
Import the scan.
Wait.
Inspect.
Measure.
Export.

That workflow makes sense when you are doing serious GIS, photogrammetry, survey processing, classification, or production work.

But there is another moment that is much simpler:

You just received a scan and want to know what is inside it.

Does it open?
Is it clean?
Does it have color, intensity, or classification?
Can I measure something quickly?
Can I show it to someone without making them install software?

That is the moment I wanted to improve with OpenLiDARViewer.

It is an open-source, browser-based LiDAR and point-cloud viewer built around one simple idea:

Drop a scan into the browser and inspect it locally.

No upload.
No account.
No desktop install.
No conversion step for supported formats.

Live demo: https://lidar.aurtech.mx/
GitHub: https://github.com/Aurtechmx/openlidarviewer/

The real goal: own the first 60 seconds

There are already great tools for point-cloud work.

CloudCompare is powerful.
QGIS is powerful.
Potree is excellent for publishing point clouds on the web.
Professional LiDAR software exists for a reason.

OpenLiDARViewer is not trying to replace those tools.

It is focused on a smaller, earlier step:

the first 60 seconds after you get a scan.

Before you process it.
Before you classify it.
Before you prepare a report.
Before you decide what workflow it belongs to.

Sometimes you just need a fast first look.

That sounds simple, but LiDAR data makes “just open it” surprisingly complicated.

Keeping the scan on the device

One thing I wanted from the start was simple:

The scan should not have to leave the user’s machine just to be inspected.

For LiDAR and 3D scan data, that matters.

A scan can represent a private site, a client project, a building, a terrain model, an industrial asset, or a research dataset. Uploading it to a random cloud viewer just to take a quick look is not always acceptable.

So OpenLiDARViewer runs client-side.

The browser loads the app, but the scan itself is read, parsed, analyzed, and rendered locally.

There is no backend parser.
No server-side preprocessing.
No cloud ingestion pipeline.
No “upload your file and wait.”

Just the browser, the GPU, and the file on your machine.

Supporting real-world scan formats

The viewer now supports common point-cloud and scan formats such as:

  • LAS
  • LAZ
  • E57
  • PLY
  • OBJ
  • GLB / GLTF
  • XYZ
  • CSV

That mix is intentional.

I wanted the same viewer to handle both:

  • drone / aerial LiDAR
  • phone and mobile 3D scans

Those worlds often feel separate, but in practice they are starting to overlap. People capture data from drones, phones, scanners, photogrammetry tools, and AR apps. A lightweight viewer should not care where the scan came from as long as the data can be parsed and rendered.

E57 support was especially important because it is a serious professional exchange format. It also brings more complexity: metadata, scan structure, transforms, optional attributes, and vendor differences.

Getting that kind of file working in the browser is not just a checkbox. It is an interoperability milestone.

Measurement had to become more than a demo feature

The first measurement tool was simple: pick two points and get a distance.

Useful, but limited.

The newer measurement workflow is more practical. It includes:

  • distance
  • polyline
  • area
  • height
  • angle
  • slope
  • unit switching
  • editable points
  • clearer measurement sessions

It does make it more useful for quick inspection.

Scan intelligence: not just seeing the cloud

A point cloud viewer should do more than show dots.

When I open a scan, I want quick answers:

  • How many points are in this file?
  • What is the extent?
  • What is the approximate density?
  • Does it have RGB?
  • Does it have intensity?
  • Does it have classification?
  • Are there invalid coordinates?
  • Are there suspicious outliers?
  • Does the decoded data match what the file claims?

That is why OpenLiDARViewer includes scan intelligence and validation modules.

The goal is not to replace professional QA/QC.

The goal is to give a useful first read before committing to deeper processing.

Seeing the scan matters.

Understanding whether the scan looks intact matters too.

Why the interface is more game-like than GIS-like

A lot of spatial tools inherit GIS interaction patterns.

That is fine for GIS users, but it can be intimidating for people who just want to move through a 3D scan.

OpenLiDARViewer uses a more direct navigation model:

  • Orbit mode
  • Walk mode
  • Fly mode
  • WASD movement
  • mouse-look
  • touch support on mobile
  • point inspection
  • saved viewpoints

The goal is to make the scan feel like a space you can enter, not just a dataset you loaded.

Orbit works well for objects and small sites.
Walk makes more sense for interiors or street-level scans.
Fly feels better for terrain, drone LiDAR, and wide-area data.

This is one of the parts I care about most because good interaction design can make technical data much easier to understand.

Mobile was not optional

At first, this kind of tool feels desktop-first.

Then reality shows up.

People want to open scans:

  • on site
  • on tablets
  • on phones
  • during a demo
  • while moving between field and office workflows

Mobile support creates its own set of problems:

  • touch controls
  • viewport behavior
  • limited memory
  • smaller screens
  • browser gestures
  • GPU differences
  • file picker behavior

A viewer that technically opens on mobile but feels terrible is not really mobile-friendly.

So mobile support became part of the direction, especially for phone scan exports and quick review workflows.

Feedback I am looking for

If you work with any of these areas, I would really value your feedback:

  • LiDAR
  • UAV mapping
  • GIS
  • photogrammetry
  • 3D scanning
  • WebGL / WebGPU
  • point-cloud visualization
  • browser-based technical tools

The most useful feedback would be:

  • Does your file open?
  • Does performance feel acceptable?
  • Are the controls intuitive?
  • Are the measurement tools useful?
  • Does E57 behave correctly with your files?
  • What format support is missing?
  • What breaks?
  • What would make this useful in a real workflow?

Live demo: https://lidar.aurtech.mx/
GitHub: https://github.com/Aurtechmx/openlidarviewer/

If the project is useful, a GitHub star helps. But real feedback from people who work with point-cloud data helps even more.

Final thought

The browser is becoming a serious runtime for technical software.

Not for everything.

Not for every workflow.

But for the first step — opening, inspecting, measuring, and understanding spatial data — it is starting to make a lot of sense.

That is the direction OpenLiDARViewer is exploring.

Why most freelancers undercharge (and the maths behind fixing it)

Most freelancers set their rate by looking at what others charge and picking something nearby. This feels safe. It also creates a systematic problem: you end up pricing based on what the market accepts, not what your business actually needs.

Here is the maths that most rate-setting advice skips.

The utilisation problem

If you work 40 hours a week, you are not billing 40 hours a week.

A realistic breakdown looks something like this:

  • Client calls and emails: 4-6 hours
  • Proposals and pitching: 3-5 hours
  • Admin, invoicing, accounting: 2-3 hours
  • Learning and keeping skills current: 2-4 hours

That leaves roughly 22-28 billable hours out of 40. Call it 55-65% utilisation.

A developer billing $40/hr with 20 real billable hours per week is effectively running a ~$40k/year business before taxes and downtime. That number tends to surprise people when they see it written out.

So if you want to earn $80,000 a year, you cannot divide by 2,000 hours and call it your rate. You need to divide by your actual billable hours.

$80,000 / 1,200 billable hours = $66.67/hr minimum

Before taxes. Before slow months. Before the occasional client who costs you two weeks of work and pays nothing.

The slow month multiplier

Freelance income is not linear. If you plan for 48 weeks of full utilisation and you actually get 38, your effective rate just dropped 21% below what you calculated.

The fix is to build slow months into your rate from the start, not to adjust when they happen.

A more conservative model:

  • Target: 46 billable weeks per year (not 52)
  • Utilisation: 60% of working hours
  • Effective billable hours: 46 x 40 x 0.60 = 1,104 hours

For an $80,000 annual target: $80,000 / 1,104 = $72.46/hr before tax

Add 25-30% for self-employment tax and the minimum viable rate is around $90-95/hr. Depending on your market and experience level, that number may feel impossibly high. That’s exactly why so many freelancers end up overworked and underpaid — the maths doesn’t change because the market is uncomfortable with it.

Why this matters for developers specifically

Frontend and backend developers are particularly prone to undercharging because the market has a wide range and clients often anchor to offshore rates.

That anchor is irrelevant to your cost structure. An offshore developer at $25/hr has different fixed costs, tax obligations, and market positioning. Comparing your rate to theirs is like a restaurant comparing its prices to street food in a different country.

Your rate is a function of your costs and your utilisation. Not of what someone else charges somewhere else.

A simple starting framework

  1. Calculate your target annual income (include savings, not just survival)
  2. Estimate your real billable hours (utilisation x working weeks x hours/week)
  3. Divide income by hours to get your pre-tax minimum
  4. Add your effective tax rate
  5. Add 15-20% as a buffer for slow months and difficult clients

That number is your floor. Anything below it usually means the business only works if you absorb the volatility yourself.

I got tired of doing this maths manually every time someone asked me about freelance pricing, so I built a small calculator for different roles: ratecalculator.xyz

No signup, no email, just the numbers.

We built a mahjong dangerous-tile predictor calibrated on 4.97M real hands

The problem

At a real mahjong table, software can’t help you mid-hand. The question — “is this tile safe to discard?” — comes up every few turns, and you work it out from memory and pattern recognition alone.

We wanted something you could set next to you at the table, pick up in a second, and put down without disrupting the game.

What OkkanaiPai does

Three interactions:

  1. Swipe to select the player you want to read
  2. Tap their discarded tiles to enter them
  3. See all 34 tiles color-coded by danger level

The whole flow is designed to be glanceable — you’re not staring at a screen mid-game.

The model behind it

We didn’t ship a live inference model. Instead, we calibrated a rule-based system against 4.97 million discards from Tenhou’s Houou-takujo server (the top competitive tier), covering 16 days of logged play.

The result is a set of coefficients stored as JSON inside the app. AUC on held-out data came to 0.83. Not perfect, but statistically meaningful for a glance-level judgment call at the table. Everything runs fully offline — no network request, no account, no subscription.

What it doesn’t cover

Being upfront about scope:

  • Calibration is East-round / East-seat data. Accuracy may degrade in South-round play.
  • Meld efficiency — how chii, pon, or kan reshapes hand danger — is not modeled.
  • Three-player mahjong (sanma) is not supported.

For standard 4-player East-focused games, it fits. Outside that, weight the output accordingly.

Why build it

Niixo Labs is running a sprint: small, focused iOS tools, shipped fast. OkkanaiPai explores what “calibrated statistics as UX” looks like in a game-assistance context — where a lightweight, offline coefficient table can deliver meaningful signal without any server infrastructure. Free, no ads, no IAP.

https://apps.apple.com/jp/app/id6762544982