Choosing a video hosting provider for hobby projects

Something embarrassing happened to me recently. I was at a networking event and I was excited to show someone my website and passion project Jamroom. As soon as I pulled them up, I remembered how long it’d been since I created these projects. The UI/UX I’d built a couple of years ago felt stale, and there were unpolished edges I never went back to.

So I spent last weekend to revamp the UI, rethink some of the UX, and I even finally recorded a demo for Jamroom1 to showcase on my website!

A new challenge I was presented with then was where to host the demo video. In the past I’d always reach for the quick and dirty solutions like hosting on Vimeo, YouTube‘s unlisted videos, or GitHub even. This time I decided it was time for change, I wanted to host my assets on my own infra. To do this I came up with two requirements:
1) The solution needs to be free/dirt cheap
2) The setup can’t take more than a few minutes, time is money!

Did some research and landed on Cloudflare R2, they offer a generous free tier. But mid-onboarding I realized in order to use the free tier, I’d have to either:
1) Switch my DNS provider to Cloudflare, having to migrate all my DNS records, which would absolutely take more than a few minutes.
2) Setup URL pre-signing on my backend to provide public access to the videos in my private bucket. Which would be unnecessary work for my needs at this time.

So I kept digging and voila! Enter Vercel Blob storage, their free tier offers free public access to your assets, albeit it’s very limited. But it’s good enough for now! There’ll come a time when I’ll have to finally setup the URL pre-signing to take advantage of the more generous free services out there, but till then I can invest my time in more pressing projects.

Here are my realistic options, optimizing for ease of setup/use and cost.

provider cheapest tier tier limits public URL access
Backblaze B2 forever free 10GB storage, unlimited free egress, 1M writes + 10M reads/mo Yes—public buckets support direct URLs , but cannot map custom domain without Cloudflare workaround
Supabase Storage free forever 1GB file storage, 5GB bandwidth, 50K MAUs Yes—public buckets provide direct asset URLs without auth
DigitalOcean Spaces $5/month 250GB storage, 1TB outbound transfer Yes—files can be set to public permissions with direct URLs
Vercel Blob free (Hobby) 1GB storage, 10GB Blob Data Transfer/month Yes—native public access with permanent URLs
AWS S3 free 12mo 5GB storage, 20K GET + 2K PUT requests/month, 100GB data transfer out Yes via public bucket or presigned URLs

Links

Demo video
Jamroom Website
My Website

  1. Jamroom is a passion project I built a couple of years ago to teach myself about real time messaging systems and experiment with music. You can join a room with your friends and play virtual instruments (keyboard and drums) together in real time. ↩

Mock What Prisma Returns, Not What Your API Returns

I’m building RunHop in public — a social + event platform for running races, built on NestJS.

Today I finished the organization and membership e2e test suites. Along the way, I spent an embarrassing amount of time on a TypeError that came down to one wrong mock.

The Crash

// organization.service.ts
async list(cursor?: string, take: number = 20) {
    const args: Prisma.OrganizationFindManyArgs = {
        take,
        where: { deletedAt: null },
        orderBy: { createdAt: 'desc' }
    };

    if (cursor) {
        args.skip = 1;
        args.cursor = { id: cursor }
    }

    const result = await this.prisma.organization.findMany(args);
    const nextCursor = result.at(-1)?.id;

    return { data: result, meta: { cursor: nextCursor } };
}

The unit test:

it('should return orgs without cursor', async () => {
    mockPrisma.organization.findMany.mockResolvedValue(
        { data: [mockOrg], nextCursor: mockOrg.id }  // <-- the problem
    );
    const result = await service.list();
    // TypeError: result.at is not a function
});

result.at(-1) crashed because the mock returns an object, not an array. And .at() is an Array method.

Why This Happens

The mock was shaped like the API response — { data: [...], nextCursor: '...' }. That’s what the frontend sees. But Prisma’s findMany returns a plain array. Always. The { data, meta } wrapping happens later, in a completely different layer.

Here’s the actual data flow in a NestJS app with a response interceptor:

Prisma findMany  →  returns [org1, org2, org3]        (plain array)
     ↓
Service          →  returns { data: [...], meta: {} }  (structured response)
     ↓
Controller       →  returns service result as-is
     ↓
TransformInterceptor  →  sees data + meta keys → passes through untouched
     ↓
HTTP Response    →  { data: [...], meta: { cursor: '...' } }
For non-paginated endpoints, the interceptor wraps automatically:

Service          →  returns { id: '123', name: 'Org' }  (plain object)
     ↓
TransformInterceptor  →  no data/meta keys → wraps in { data: response }
     ↓
HTTP Response    →  { data: { id: '123', name: 'Org' } }
The interceptor code is straightforward:
// transform.interceptor.ts
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, any> {
    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        return next.handle().pipe(
            map((response) => {
                if (!response) return { data: null };
                if (response.data && response.meta) return response;
                return { data: response };
            })
        );
    }
}

When you write a unit test for the service, you’re testing everything above the controller. The mock replaces Prisma — so it should return what Prisma returns:

mockPrisma.organization.findMany.mockResolvedValue([mockOrg]); // plain array

The Pagination Test That Compared a List to Itself

While I was at it, I wrote a pagination e2e test that verified page 2 doesn’t overlap with page 1. First attempt:

const newList = result.body.data;
const bIds = new Set(newList.map((item) => item.id));
const same = listOrgs.length === newList.length
    && newList.every((item) => bIds.has(item.id));

expect(same).toBe(false);

This always passes. bIds is built from newList, and then we check if every item in newList is in bIds. Of course it is — we’re comparing the list against itself.

The fix:

const page2Ids = new Set(newList.map((item) => item.id));
const hasOverlap = listOrgs.some((item) => page2Ids.has(item.id));

expect(hasOverlap).toBe(false);

Now we’re checking: do any page 1 items appear in page 2? That’s the actual invariant.

Membership E2E: Permission Boundaries

The org-membership suite tests 6 scenarios from the plan:

describe('Add a member', () => {
    it('should create and return org membership');         // 201
    it('should reject duplicate member');                   // 409
});

describe('Update Member', () => {
    it('should update member role as owner');               // 200
    it('should reject role update as admin');               // 403
});

describe('Remove member', () => {
    it('should remove a member');                           // 200
    it('should reject removing owner');                     // 403
});

Setup is three users with distinct roles: owner creates the org (gets OWNER automatically via the transaction in create()), admin gets added with ADMIN role, member gets added with default MEMBER role. Tests run sequentially because membership state carries between them — the admin added in the update suite’s beforeAll is still there for the remove tests.

The permission model: the controller calls verifyRole(userId, orgId, minRole) before every mutation. The service checks the caller’s role against a hierarchy map:

const ROLE_HIERARCHY: Record<string, number> = {
    MEMBER: 1,
    ADMIN: 2,
    OWNER: 3
};

OWNER can do everything. ADMIN can add and remove members but not change roles. MEMBER can’t mutate anything.

Takeaway

Mock the layer you’re replacing, not the layer you’re consuming. If you’re mocking Prisma, return what Prisma returns. If you’re mocking a service, return what the service returns. The moment your mock matches a different layer’s shape, your test is lying to you — and you’ll chase TypeErrors that have nothing to do with your actual logic.

Building a Privacy-First Google Ads Analyzer (No Server, No Data Upload)

We built a Google Ads CSV analyzer that finds wasted spend and suggests optimizations. The twist? Everything runs in the browser. No server uploads. No data leaves your machine.

Here’s how we built it and why we made those choices.

The Problem

Small business owners often waste 20-40% of their Google Ads budget on irrelevant search terms and underperforming keywords. Enterprise tools cost $100-500/month to identify these issues.

We wanted something free, but privacy was a concern — businesses are reluctant to upload their ad spend data to unknown servers.

The Solution: Client-Side Processing

Instead of uploading CSVs to a server, we process everything in the browser using the FileReader API.

Reading CSV Files

 ⁠javascript
const handleFileUpload = (file: File) => {
const reader = new FileReader();

reader.onload = (e) => {
const text = e.target?.result as string;
const rows = parseCSV(text);
analyzeData(rows);
};

reader.readAsText(file);
};

⁠ ### Parsing CSV Data

Google Ads exports aren’t clean — they have summary rows, currency symbols, and percentage signs. We strip all of that:

 ⁠javascript
const parseCSV = (text: string): Record[] => {
const lines = text.split(‘n’).filter(line => line.trim());
const headers = lines[0].split(‘,’).map(h => h.trim().toLowerCase());

return lines.slice(1)
.filter(line => !line.startsWith(‘Total’))
.map(line => {
const values = line.split(‘,’);
const row: Record = {};
headers.forEach((header, i) => {
row[header] = values[i]?.replace(/[$%”,]/g, ”).trim() || ”;
});
return row;
});
};

⁠ ### Analysis Logic

We check for three main issues:

1. Wasted Spend (Zero Conversions)
 ⁠javascript
const wastedKeywords = keywords.filter(kw =>
parseFloat(kw.conversions) === 0 &&
parseFloat(kw.cost) > 10
);

⁠ 2. Low Quality Score
 ⁠javascript
const lowQualityKeywords = keywords.filter(kw =>
parseInt(kw.quality_score) < 5
);

⁠ 3. Irrelevant Search Terms
 ⁠javascript
const irrelevantTerms = searchTerms.filter(term =>
parseFloat(term.ctr) < 0.01 &&
parseInt(term.impressions) > 100
);

Why Client-Side?

  1. Privacy — Ad spend data is sensitive. Businesses don’t want it on someone else’s server.
  2. Speed — No upload/download latency. Analysis is instant.
  3. Cost — No server infrastructure to maintain.
  4. Trust — Users can verify via DevTools that no network requests are made.

The Trade-offs

  • No AI analysis — Can’t use LLMs without sending data somewhere
  • Browser memory limits — Very large CSV files could crash the tab
  • No historical tracking — Each analysis is one-time

For our use case, these trade-offs were worth it.

Try It

Live tool: siteauditr.io/ads-audit

Upload your Google Ads CSVs (campaigns, keywords, search terms) and get instant insights. Open DevTools Network tab if you want to verify — zero external requests.

Would love feedback on what else would be useful to add. Thinking about bid adjustment recommendations or ad copy analysis.

JSON API Response Looks Garbled in Chrome Fix

Staring at unreadable JSON data in Chrome’s Network tab ruins your debugging flow. If your json api response garbled chrome issue is blocking development work, the fastest fix is enabling proper JSON formatting in Chrome DevTools. The root cause is Chrome’s default raw response display that doesn’t automatically format JSON content. This article covers manual fixes and a permanent solution using browser extensions.

Last verified: March 2026 , All steps tested on Chrome 134 (latest stable). Extension data verified against Chrome Web Store.

Last tested: March 2026 | Chrome latest stable

Quick Fix

  1. Open Chrome DevTools (F12), go to Network tab
  2. Click the API request, select Response tab
  3. Click the “Format” button or use Ctrl+Shift+P and search “Pretty Print”

The Ultimate Chrome JSON Extension , dcode

Why Chrome JSON API Response Looks Garbled

Chrome displays API responses in their raw format by default, which makes JSON data nearly impossible to read during development. Understanding why this happens helps you pick the right solution.

Raw Response Display Mode

Chrome’s Network tab shows server responses exactly as transmitted, without any client-side processing. When APIs return minified JSON (single-line format without spaces), the result appears as one continuous text block. This happens because Chrome prioritizes showing authentic server data over readability. The browser receives compressed JSON that might contain 2,000+ characters on a single line, making it impossible to identify specific object properties or nested structures.

Missing Automatic Content Detection

Unlike specialized API tools, Chrome doesn’t automatically detect JSON content types and apply formatting. Even when the Content-Type header correctly identifies application/json, the DevTools Response tab displays the raw text. Chrome processes over 95% of web requests as generic HTTP responses, so automatic JSON formatting isn’t built into the core browser experience.

Memory Conservation Strategy

Chrome’s process architecture reserves formatted display features for specific developer tools rather than every HTTP response. Automatic JSON parsing for every API call would consume significant memory across multiple tabs. The browser handles thousands of network requests per session, so keeping raw responses lightweight maintains overall performance.

How to Fix Chrome JSON API Response Garbled

These manual methods work immediately without installing extensions. Each approach has specific use cases and trade-offs for different development workflows.

Enable Pretty Print in DevTools

The fastest solution uses Chrome’s built-in formatting feature. Open DevTools using F12 (Windows) or Cmd+Option+I (Mac), navigate to the Network tab, and find your API request. Click the request, then select the Response tab. Look for the Format button at the bottom of the response panel, or use the keyboard shortcut Ctrl+Shift+P (Windows) or Cmd+Shift+P (Mac) to open the command palette and search “Pretty Print Source”. This immediately formats JSON with proper indentation and line breaks.

This method works for any JSON response but requires manual activation for each request. You’ll need to repeat the process every time you refresh the page or make new API calls. The formatting applies only to the current DevTools session and doesn’t persist across browser restarts.

Use Console JSON Parsing

Copy the garbled JSON response and paste it into Chrome’s Console tab. Wrap it with JSON.parse() to convert the string into a formatted JavaScript object. For example: JSON.parse('{"name":"value","data":[1,2,3]}'). The Console automatically expands objects with proper indentation and allows you to explore nested properties by clicking the disclosure triangles.

“The JSON.parse() static method parses a JSON string, constructing the JavaScript value or object described by the string.” , JSON.parse() – JavaScript – MDN Web Docs

This approach gives you interactive object exploration but requires copying and pasting for each response. It’s most effective for small JSON payloads under 500 lines.

Copy as Formatted JSON

Right-click on the garbled response in the Network tab and select “Copy response”. Open any text editor, paste the content, then use the editor’s JSON formatting feature. VS Code, Sublime Text, and most IDEs include JSON formatting commands. In VS Code, use Shift+Alt+F (Windows) or Shift+Option+F (Mac) to format pasted JSON automatically.

External editors provide the most formatting control and syntax highlighting, but switching between Chrome and your editor breaks the debugging workflow. This method works best when you need to save formatted JSON for documentation or sharing with team members.

Install JSON Viewer Extension

Chrome Web Store includes several JSON formatting extensions that activate automatically. Search for “JSON Viewer” in the Extensions section and install one with good reviews. These extensions intercept JSON responses and replace Chrome’s default display with formatted, syntax-highlighted content.

Most JSON viewer extensions work by detecting Content-Type headers and applying JavaScript formatting to matching responses. They typically add color coding, collapsible sections, and search functionality. However, extension-based solutions can conflict with other developer tools and may not work with all API authentication methods.

Fix It Permanently with JSON Formatter Pro

Manual formatting works but interrupts your development flow every time you debug API calls. JSON Formatter Pro provides automatic JSON formatting that activates as soon as Chrome loads a JSON response. The extension runs continuously in the background without requiring manual intervention.

JSON Formatter Pro detects JSON content automatically and applies syntax highlighting, proper indentation, and collapsible object sections. Unlike manual DevTools formatting, the extension preserves your formatting preferences across browser sessions and handles multiple API calls simultaneously. The extension processes JSON responses in real-time, so you see formatted data immediately when requests complete.

The tool includes additional features like JSON validation, error highlighting for malformed data, and customizable themes for better readability. With a 4.8/5 rating on the Chrome Web Store and regular updates (last updated 2026-03-02), the extension provides a reliable solution for consistent JSON formatting. At 738KiB, it adds minimal memory overhead while delivering professional-grade JSON handling.

Manual fixes require repetitive actions for every debugging session, while JSON Formatter Pro transforms Chrome into a dedicated API development environment. You’ll spend more time analyzing data and less time fighting with formatting.

Try JSON Formatter Pro Free

Related Reading

FAQ

Does Chrome have built-in JSON formatting?

Yes, Chrome includes basic JSON formatting through the Pretty Print feature in DevTools. However, it requires manual activation for each response and doesn’t provide syntax highlighting or persistent formatting preferences.

Will JSON extensions slow down Chrome?

Modern JSON extensions like JSON Formatter Pro add less than 1MB memory usage and process responses asynchronously. The performance impact is negligible compared to the time saved on manual formatting tasks.

Can I format JSON responses from APIs with authentication?

Both manual DevTools methods and reputable extensions work with authenticated APIs. The extensions operate on response content after Chrome receives it, so authentication headers and cookies don’t interfere with JSON formatting functionality.

“JSON is a text-based data format following JavaScript object syntax. Even though it closely resembles JavaScript object literal syntax, it can be used independently from JavaScript.” , Working with JSON – Learn web development – MDN

For more Chrome debugging techniques, check out advanced DevTools tips and network debugging strategies. You can also explore Chrome extension recommendations for additional development tools.

Update History

Date Change
March 18, 2026 Initial publication. All data verified against Chrome Web Store and DataForSEO.
March 18, 2026 Added FAQ section based on Google People Also Ask data.
March 18, 2026 Schema markup added (Article, FAQ, Author, Breadcrumb).

This article is actively maintained. Data is re-verified monthly against Chrome Web Store.

Built by Michael Lip , More tips at zovo.one

{
“@context“: “https://schema.org“,
“@type”: “Article”,
“headline”: “JSON API Response Looks Garbled in Chrome”,
“description”: “Expert guide on json api response garbled chrome”,
“image”: “https://zovo.one/images/default-hero.jpg“,
“author”: {
“@type”: “Person”,
“name”: “Michael Lip”,
“url”: “https://zovo.one/about“,
“sameAs”: [
“https://www.linkedin.com/in/michaellip“,
“https://github.com/theluckystrike“,
“https://www.upwork.com/freelancers/~theluckystrike“
],
“jobTitle”: “Chrome Extension Engineer”
},
“publisher”: {
“@type”: “Organization”,
“name”: “Zovo”,
“url”: “https://zovo.one“,
“logo”: {
“@type”: “ImageObject”,
“url”: “https://zovo.one/images/zovo-logo.png“
}
},
“datePublished”: “2026-03-18”,
“dateModified”: “2026-03-18”,
“wordCount”: 1000,
“mainEntityOfPage”: {
“@type”: “WebPage”,
“@id“: “https://zovo.one/guides/json-api-response-garbled-chrome/“
}
}

{
“@context“: “https://schema.org“,
“@type”: “BreadcrumbList”,
“itemListElement”: [
{
“@type”: “ListItem”,
“position”: 1,
“name”: “Home”,
“item”: “https://zovo.one“
},
{
“@type”: “ListItem”,
“position”: 2,
“name”: “Guides”,
“item”: “https://zovo.one/guides/“
},
{
“@type”: “ListItem”,
“position”: 3,
“name”: “JSON API Response Looks Garbled in Chrome”,
“item”: “https://zovo.one/guides/json-api-response-garbled-chrome/“
}
]
}

O que aprendi usando Lovable por 8 meses para criar sites bonitos, padronizados e fáceis de manter

Nesse final de semana decidi dar um upgrade no meu website e em poucas horas fui capaz de me repaginar a identidade visual utilizando Lovable.

Bonito, estruturado, com vídeo no hero gerado por IA — e sem escrever CSS na mão.

Mas antes de chegar lá, precisei resolver um problema que a maioria ignora no vibe coding:

Sem estrutura, você produz rápido. E refatora o dobro depois.

Aqui está o que aprendi usando Lovable desde agosto 2025, e o template que criei para nunca mais começar do zero.

A armadilha do vibe coding sem framework

O Lovable é incrível. Você descreve o que quer, ele gera. Em minutos você tem algo visual funcionando.

O problema aparece no prompt 5 sem ter definido cores, fontes, arquitetura de pastas, padrões de código. O Lovable começa a tomar decisões por você — e nem sempre as mesmas em cada prompt.
Resultado: cores hardcoded espalhadas, componentes duplicados, estrutura que ninguém mantém.

A solução não é usar menos IA. É entrar com mais contexto.

Defina antes de gerar — 3 perguntas obrigatórias

  1. Product — O quê e para quem? Problema, público, North Star.
  2. Design System — Como vai parecer? Tokens HSL, tipografia, componentes. Tudo antes de criar qualquer tela.
  3. System Design — Como vai funcionar por baixo? Stack, arquitetura, banco, deploy.
    São coisas diferentes. A confusão entre elas é um dos erros mais comuns.

Design System ≠ System Design

Design System é o vocabulário visual: tokens de cor, tipografia, componentes reutilizáveis. O como parece.
System Design é a arquitetura técnica: como o banco conecta com o frontend, como os módulos se comunicam. O como funciona.

No meu site:
→ Design System: tokens HSL, fontes Plus Jakarta Sans + Inter, variantes de Button e Badge via CVA, glassmorphism e gradient text.
→ System Design: React 18 + Vite + Supabase, feature-based architecture, Event Bus, Ports & Adapters.

Por que NÃO usei microservices???

Lovable + Supabase é um monolito modular, e essa é a decisão certa.
Microservices resolvem problemas de escala operacional para múltiplos times com deploys independentes. Para um SaaS early-stage ou site pessoal, eles adicionam complexidade sem retorno nenhum: service discovery, network latency, infraestrutura de orquestração.

O que importa aqui é ter módulos bem isolados dentro do mesmo processo. Isso é o que feature-based architecture + Event Bus entrega — benefícios de separação de responsabilidades sem a complexidade operacional.

Regra que uso: features nunca importam entre si diretamente. Comunicação via Event Bus tipado. Integrações externas via Ports & Adapters.

SOLID no contexto React — na prática

→ S: um componente faz uma coisa. SectionHeader só renderiza header.
→ O: extensão via CVA variants. Novo estilo de botão? Adiciono uma variant. Não toco no button.tsx.
→ L: Ports & Adapters garante substituição. Troco Supabase Storage por Cloudinary mudando um adapter.
→ I: hooks granulares — useAuth, useOrganization, useFeatureGate. Nada de um useApp() que faz tudo.
→ D: features dependem de interfaces, não do Supabase diretamente.

O vídeo do hero? Gerado com IA também

O background do hero é um vídeo criado com PixVerse (app.pixverse.ai), text-to-video com estilo futurista/tech, exportado como .mp4 e usado com “video autoPlay muted loop” com overlay para garantir legibilidade.

Visual cinematográfico sem banco de imagens, sem designer, sem Adobe Premiere.

O resultado

→ Site: raphaelmoraes.dev
→ Stack: React 18 + Vite + Tailwind + shadcn/ui + Supabase + Cloudflare + PixVerse

→ Tempo: poucas horas de vibe coding estruturado
Documentei tudo num Playbook de 8 prompts prontos para colar no Lovable, com Design System completo, tokens reais do repositório, traduções pt-BR + en, checklists SOLID e regras transversais.

🔗 Site: https://raphaelmoraes.dev

📄 Playbook completo (Gist): https://gist.github.com/rdemoraes/e08d7318de3e3aa0e4c67d69fa1d0fa3

🎬 Vídeo hero gerado com: https://app.pixverse.ai

Lovable #VibeCoding #WebDevelopment #React #Supabase #DesignSystem #SystemDesign #SOLID #AI #GenerativeAI #PlatformEngineering

Why Enterprise Should Embrace Open Source

Open Source Is Not a Charity. It’s a Competitive Edge.

Most executives hear “open source” and think: free software made by volunteers. That assumption leaves money on the table.

Open source is a global pool of production-grade software, maintained by thousands of contributors, that your team can use, extend, and build on without a vendor contract. The question is not whether it matters to your business. It is whether you are using it strategically, or spending development dollars on a stack of paywalled tools just to make things work.

The Contribution Economy

When a company contributes to an open source project, you are effectively commissioning development:

  • Bug fixes your team needs
  • Features aligned with your roadmap
  • Integrations with your existing stack

Every other organization using that same project is doing the same thing. Which means they may be building features you did not know you needed.

The strategic move is not just to consume open source software. It is to contribute. A small engineering investment in a project your team depends on pays back through roadmap influence, early access to fixes, and visibility in the developer community you recruit from.

The Accessibility Stack

Three tools make open source practical for teams that are not full of engineers.

Docker removes the single biggest complaint about open source software: “it doesn’t work on my machine.” Docker packages any application with everything it needs to run, so your team can deploy a tool in minutes instead of spending a day on setup. You do not need to understand it deeply to benefit from it. You need one person who does.

Git is version control: a way of tracking every change made to a file, by whom, and when. Most people associate it with code. It applies equally to data pipelines, configuration files, and documentation. The mental model it teaches (work in branches, merge when ready, roll back when wrong) is a thinking skill, not just a technical one.

LLMs as a learning layer. Tools like Claude or ChatGPT are not replacements for understanding. They are on-ramps. A non-developer who hits an error in an open source tool can paste the message into an LLM and get a plain-language explanation and a fix in seconds. The feedback loop that used to take days now takes minutes.

Finding What Exists Before You Buy

Before your team renews a SaaS subscription, check two places.

Linux Foundation (linuxfoundation.org/resources/open-source-guides): Guides on why, how, and when to adopt open source at an organizational level.

OpenAlternative (openalternative.so): A curated directory of open source alternatives to popular paid tools. Search what you are currently paying for. There is often a production-ready equivalent that is actively maintained.

Who Is Actually Building Linux

This is where the “volunteer project” assumption falls apart completely.

Over 84% of Linux kernel commits in 2025 came from paid corporate developers, across more than 1,780 organizations. This is coordinated infrastructure investment, not charity.

  • Intel: Top contributor by changesets in 2025, nearly double second-place Google
  • Red Hat (IBM): Consistent top-3 contributor, historically held the number one spot
  • Google: Nearly 1 in 8 patches handled by Google developers; Android, Google Cloud, and Chrome OS all run Linux
  • Oracle: Top contributor to kernel core components across multiple recent releases; cloud infrastructure and database performance
  • Huawei: 8.9% of changesets in kernel 5.10; consistent top-5 since
  • Samsung: Mid-tier contributor since at least 2013; Android devices and Tizen OS
  • Meta: 1 in 8 patches handled by a Meta maintainer in kernel 6.15; data center infrastructure running billions of users
  • AMD: Active GPU and CPU driver contributor each release cycle; 30% performance gains for legacy AMD GPUs in recent kernel
  • Microsoft: Hyper-V and virtualization code; Azure cloud VMs require Linux kernel compatibility
  • NVIDIA: Rust-based GPU driver jointly developed with Google and Arm; GPU drivers for AI and ML workloads

The companies shaping your cloud infrastructure, your devices, and your AI tools are doing it through open source. The question is whether your organization is paying attention.

Quantify Your Life: Building a High-Performance Health Data Lake with InfluxDB, Grafana, and Python 🚀

We live in an age of “The Quantified Self.” Between our Apple Watches tracking heart rate variability, Strava recording every weekend century ride, and MyFitnessPal logging every gram of protein, we are generating gigabytes of personal health data. But here is the problem: this data is siloed.

If you want to correlate your sleep quality (Apple Health) with your training load (Strava) and your caloric intake (MyFitnessPal), you’re stuck flipping between three apps. In this guide, we’re going to solve this using Data Engineering best practices. We will build a personal Data Lake using InfluxDB for time-series storage, Python for ETL (Extract, Transform, Load), and Grafana for that sweet, mission-control style dashboard.

By the end of this, you’ll have a “Single Source of Truth” for your health metrics, running entirely in Docker.

The Architecture: From Silos to Insights 🏗️

Handling heterogeneous data (JSON from APIs, CSVs from health exports) requires a robust pipeline. We need to normalize these different formats into a unified Time-Series format.

graph TD
    A[Apple Health Export] -->|XML/CSV| B(Python ETL Script)
    C[Strava API] -->|JSON| B
    D[MyFitnessPal] -->|Web Scraping/Export| B
    B -->|Clean & Transform| E{InfluxDB}
    E -->|Query via Flux| F[Grafana Dashboard]
    F -->|Visualization| G[Your Big Screen]

    style E fill:#f96,stroke:#333,stroke-width:2px
    style F fill:#3262a8,stroke:#333,stroke-width:2px

Prerequisites 🛠️

Before we dive in, ensure you have the following ready:

  • Docker & Docker Compose installed.
  • Python 3.9+ (for our ETL logic).
  • Access to your Strava API credentials.
  • A “Learning in Public” mindset! 🥑

Step 1: Setting up the Infrastructure 🐳

We’ll use Docker Compose to spin up our stack. InfluxDB is the perfect choice here because health data is essentially a sequence of timestamps and values (Heart Rate, Weight, Steps).

# docker-compose.yml
version: '3.8'
services:
  influxdb:
    image: influxdb:2.7
    ports:
      - "8086:8086"
    volumes:
      - influxdb_data:/var/lib/influxdb2
    environment:
      - DOCKER_INFLUXDB_INIT_MODE=setup
      - DOCKER_INFLUXDB_INIT_USERNAME=admin
      - DOCKER_INFLUXDB_INIT_PASSWORD=password123
      - DOCKER_INFLUXDB_INIT_ORG=my_health_org
      - DOCKER_INFLUXDB_INIT_BUCKET=health_metrics

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    depends_on:
      - influxdb
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  influxdb_data:
  grafana_data:

Step 2: The Python ETL Pipeline 🐍

The trickiest part of Data Engineering for health data is cleaning. Apple Health exports XML that can be multi-gigabyte. Strava provides nested JSON. We’ll use the influxdb-client to push normalized data.

For more production-ready patterns on handling large-scale data ingestion and advanced ETL architectures, I highly recommend checking out the deep dives over at WellAlly Tech Blog. They cover the nuances of data consistency that are crucial when your data lake starts growing.

import pandas as pd
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS

# Configuration
token = "YOUR_INFLUXDB_TOKEN"
org = "my_health_org"
bucket = "health_metrics"

client = InfluxDBClient(url="http://localhost:8086", token=token, org=org)
write_api = client.write_api(write_options=SYNCHRONOUS)

def process_strava_data(json_data):
    """
    Transforms raw Strava JSON into InfluxDB Points.
    """
    for activity in json_data:
        point = Point("fitness") 
            .tag("source", "strava") 
            .tag("type", activity['type']) 
            .field("distance", float(activity['distance'])) 
            .field("heart_rate_avg", float(activity.get('average_heartrate', 0))) 
            .time(activity['start_date'], WritePrecision.NS)

        write_api.write(bucket, org, point)
    print("✅ Strava data ingested successfully!")

# Example usage with mock data
mock_strava = [{"type": "Run", "distance": 5000.5, "average_heartrate": 155, "start_date": "2023-10-27T08:00:00Z"}]
process_strava_data(mock_strava)

Step 3: Handling Heterogeneous Data 💎

When dealing with Apple Health XML, you’ll encounter different “Records.” You need to map these to specific measurements in InfluxDB.

Source Metric Transformation Logic InfluxDB Measurement
HKQuantityTypeIdentifierStepCount Sum daily activity
HKQuantityTypeIdentifierBodyMass Latest value per day vitals
MyFitnessPal Calorie Summary Map Macros to Fields nutrition

Step 4: Visualizing with Grafana 📊

  1. Open Grafana at http://localhost:3000.
  2. Add InfluxDB as a Data Source (Select Flux as the query language).
  3. Create a new Dashboard and add a Time Series panel.
  4. Use this Flux query to see your heart rate over time:
from(bucket: "health_metrics")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["_measurement"] == "fitness")
  |> filter(fn: (r) => r["_field"] == "heart_rate_avg")
  |> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
  |> yield(name: "mean_hr")

Pro-Tip: The “Official” Way to Scale 🥑

Building a local setup is great for a hobby, but if you’re looking to turn this into a production-grade health platform or integrate AI-driven insights (like predicting burnout), you need a more robust approach.

The experts at WellAlly Tech have published extensive guides on building scalable data systems. Their insights into Time-Series optimization and Data Privacy were a huge inspiration for this architecture. Definitely give them a read if you’re planning to take your Quantified Self journey to the “Pro” level!

Conclusion: Take Back Your Data! ✊

By building your own health data lake, you’re no longer at the mercy of proprietary app dashboards. You can run your own correlations, detect long-term trends, and truly own your digital twin.

What’s next?

  • Add a Telegram bot to alert you if your resting heart rate stays high (Overtraining alert!).
  • Integrate GPT-4o to analyze your monthly trends and give you health advice based on your real data.

Did you find this helpful? Drop a comment below with your favorite health metric to track! 👇

Can you stand the rain?

This is a submission for the 2026 WeCoded Challenge: Echoes of Experience

The story of how Alicia Keys accidentally got me a career in tech

Can you stand the rain?

That’s what New Edition asked about love and life. I was listening to the song while reading about the WeCoded challenge, and it made me realize the rain in my own life shaped me more than sunny days. I knew this was the story I wanted to tell. The way I ended up in tech came from the most unlikely place: an Alicia Keys concert my mom took me to by accident, and the online community of Black and brown people I found because of it. This is how diversity, real diversity, the kind with no panel moderator and no walls got me into tech.

The Rain

I grew up in a small suburb in the south right by the Mason-Dixon line, in a family that made no sense to anyone around me. My mom was Guyanese. My family is diverse. “Mix up” is how we called it. At home my kitchen smelled like chicken curry and roti and dhal while every other house around my block ran on Kraft mac and cheese from a microwaveable cup. I was too different from the world around me. I said “sangwich” instead of “sandwich” (Guyanese patois that I forced myself to fix after years of getting laughed at). I brought lychee for snack time and watched kids give me stares and called me weird.
I lived in a small town. Same kids. Everyone knew everyone (or thought they did) type energy. But still, in that world, there was no room for nuance. Everything was black and white. You were smart or dumb. You were from here or you weren’t. You were either white or not, and if you weren’t, people just filled in the rest for you from their worldview.

I actually still remember my first day of kindergarten and it wasn’t because I was crying for my mom not to leave (but I did do that too). I remember sitting on a rainbow round carpet, the kind that was woven and had some fuzzy threads sticking out. A group of kids walked up and sat down next to me. They started rubbing my skin, asking each other, “is she dirty?” Then one turned to me and said “Ew, did you take a bath?”

In eighth grade, a girl I had been in Girl Scouts and on the basketball team with for years pulled me aside in the hallway and whispered, “are you… Black?” like she was diagnosing something. Like it was shameful. What shook me then and still does now is not the question itself. It was the tone. The serious way she was asking it, like it was some deep dark secret. Her discomfort. And it wasn’t just her, it was basically everyone around me back then. I heard this stuff all the time.

Between those moments was a decade of being called the n-word, of “go back to your country” from most of the neighborhood kids. Being told that races should not mix and that brown people were “mud people.” I don’t even remember what else. That was the rain. It was constant, and it started before I could spell my own name.
But here is the thing about rain, people usually don’t just stand in it.

They get a raincoat, buy an umbrella, or they find shelter.
I found my people on the internet. And that community became my raincoat.

The Accidental Ticket

By the time I was twelve, I was already online. I had taught myself HTML by using Lisa Explains It All, for my Neopets profile. I built little websites on GeoCities.

But I was doing all of it alone. The internet gave me somewhere to go, but it didn’t give me anyone to go with.

What changed that was a concert ticket my mom bought by mistake.
When I was thirteen, my mom heard me talking about Aaliyah, the R&B singer who had recently passed away. She saw concert tickets for this new artist named Alicia Keys and bought them, thinking they were the same person. They were not the same person, but my mom didn’t know that, and I wasn’t about to turn down a concert.

I still remember the day we drove all the way to Cincinnati to go to the Taft Theater, excited for my first concert ever. Friday night. We sat up in the balcony section, they had these old red velvet seats, and I remember watching someone carry a keyboard onto the stage before the show and thinking, “oh, is that going to be for Alicia Keys?” It wasn’t. But I remember that small moment because I was already leaning forward, already pulled in, before a single note had been played. Something in me already knew.

Then she performed her album, Songs in A Minor, and I only knew “Fallin” before that night, but by the time the show was over I knew I had witnessed something much bigger than a concert. The way the whole show flowed like a theater production. The classical training underneath the soul. The rawness. Here was someone who didn’t fit neatly into one box either, bridging worlds that people said didn’t go together. I left that concert knowing I needed to find out everything about this world immediately.

The Room

I had been exploring the internet before that concert, but after that night I wasn’t just exploring anymore. I was searching with purpose. More of whatever THAT was. More of that artistry, that story, that connection.

Then I found it, the Alicia Keys Message Board, and it became, without exaggeration, the most important community I had ever been part of during that time.

Not because of the music. Because of the people. First time I was in a room where diversity wasn’t a concept anyone had to explain, it was just the room. I talked to everyone on there. We had organized live chatrooms on Yahoo Messenger and MSN, conversations that went on for hours.

I met Nafiesa, who was from Amsterdam, but originally Suriname, a country right next to Guyana. She was the first Caribbean girl I ever met online. I told her I wanted to visit Guyana one day, she said, “GIRL, WHY WOULD YOU WANNA GO THERE? You know there are snakes that can KILL you? It’s all jungle!” and I laughed so hard because that was it. That was what I had been missing the whole time. Someone who understood the reference without a confused blank stare or asking me if I meant Ghana, who knew the culture, who could roast me and get me at the same time.

There were a lot of connections made there. Dee, a Black woman and IT professional who I looked up to, ran a thread where she told us all to post our report cards. If you had A’s, she sent you real money in the mail to encourage Black and brown kids to be excellent. I made sure to post my report card each quarter. There was Alana from DC, always typing “BRB” because she was studying for her IB classes, a joyful nerd who made being smart feel normal instead of weird. There was Cinna from Jamaica, who kept getting banned from the board for starting all the craziest drama (and I kept showing her how to hide her IP and slip back in). There was Sabina from the UK, Nic from Germany, Steph from Belgium. People watched The L Word together weekly episode by episode in the forum threads, posting reaction images before reaction GIFs were even a real thing. People argued music theory and shared relationship problems and asked for homework help. It was the first place where everything I was, the food, the family, the in-between-ness wasn’t a problem to solve. It was just who we were. That’s what a diverse community actually does. It doesn’t make you feel included. It makes you stop feeling like you ever needed to be.

That community didn’t just give me somewhere to belong. It gave me people I wanted to show up for, and eventually, a reason to use what I knew to make things better for the people around me.

The Curriculum

People on the message board posted about their broken computers ALL the time. Someone would upload screenshots to Photobucket talking about how their desktop was covered in popup windows and write “HELP, my PC has a virus, what do I do!????” and I would be in those threads immediately. “PM me your AIM screen name,” I would tell them. “Lemme check your startup programs. Open regedit. Open task manager. Let me Google this right quick.” I was like 15, walking grown adults through virus removal step by step over AOL Instant Messenger, and I hadn’t taken a computer class in my life yet. I learned because the people I cared about needed help. And every time I fixed something, I understood something new about how computers actually worked, from the inside out. It was fun. Not a curriculum. Just people, problems, and the curious need to figure it out.

That instinct kept showing up everywhere. In high school, a teacher assumed I was an ESL student because I was brown and never spoke in class. I used that assumption to slip out at lunch with the other ESL students and walk to the library, with my bootable Ubuntu CD, and use it for unfiltered internet access to my friends while everyone else ate in the cafeteria I had been avoiding for months.

It was always the same instinct. Nobody is giving this to me, so I’m going to figure it out myself. That’s how I first learned networking, operating systems, troubleshooting, and Linux administration. Not from a class. From need. The degree came later. The instinct came first.

Still Playing on the Internet

That instinct never stopped. It just expanded.

The girl fixing computers over AIM from a music forum is the same engineer who builds production systems now. The girl helping friends evade bans is the same woman who eventually published machine learning research. The girl who booted Linux off a CD in a school library is the same one who still looks at every locked door and thinks, “okay, so how does this open?”

My GitHub profile says “Playing on the internet” I was kind of joking when I wrote it, but tbh it’s probably the realest thing I’ve ever written as a bio. I’ve been online since I was like 11, and the play became the skill, and the skill became the career, and the career became the life.

None of it started in a classroom or a bootcamp or a lecture hall. It started because a community of people from the WORLD wide web made me feel like I belonged somewhere for the first time in my life. And when you belong somewhere, you build there. You contribute. You learn and grow and share.

That’s how diversity changed my life. Not a program or a panel about representation. A message board full of Black and brown people from Chicago, Jamaica, Suriname, Germany, DC, and a small suburb in the south. People who never once made me feel like I was too much or not enough. They just needed someone who could fix their computer and chill with, and I wanted to help because they were my people. That was the whole pipeline.

AI Chat Widgets: Managing Product and Policy Answers at Scale in WordPress

I built this plugin to solve a common WordPress problem: most chat widgets can generate text, but they do not actually know your site content, product catalog, or support policies.

On WooCommerce stores, that usually means bad recommendations, invented answers, and a support burden that never really goes away. I wanted a bot that answers from real site data only, and stays current without manually maintaining hundreds of canned Q&A entries.

The core approach is retrieval augmented generation. The plugin indexes selected WordPress content types like posts, pages, and WooCommerce products, splits them into chunks, and stores them for semantic retrieval. When a visitor asks a question, the system fetches the most relevant content first, then sends that context to the model so the reply is grounded in published data instead of guesswork.

I also added automatic re-indexing, so content changes propagate back into the knowledge base when products, policies, or pages are updated. That matters for stores where pricing, stock, and shipping details change often.

For commerce use cases, the chat layer can render product recommendations as structured cards with live images, pricing, and links, instead of dumping plain text. That makes the widget useful for both support and pre-sales discovery.

Under the hood, I kept the model layer flexible. The plugin supports multiple providers, so switching between OpenAI, Claude, Gemini, Mistral, or Azure is a configuration choice rather than a rewrite. I also exposed controls for temperature, token limits, chunk sizing, caching, and rate limiting, because API cost and abuse prevention matter as much as answer quality.

Inside wp-admin, I added conversation logs, token usage tracking, ratings, persona prompts, and display rules. That lets site owners tune behavior by role, page, or trigger, and review exactly what the assistant is doing.

If you want to see the full plugin, I published it here: Nexu AI Chatbot for WordPress.

My goal was simple: make a WordPress chat assistant that behaves more like a retrieval system with a UI, and less like a generic chatbot pasted onto a website.