Elements Kinds

Se você usa arrays no JavaScript, o motor V8 está tomando decisões por trás dos panos que podem deixar seu código MUITO mais rápido… ou MUITO mais lento.

E a maioria das pessoas nem percebe 👇

📌 O segredo: *Elements Kinds*

O V8 classifica seus arrays internamente para otimizar execução.
Mas essa otimização depende totalmente de como você usa o array.

🔑 Tipos que o V8 usa

🟢 SMI (inteiros) → mais otimizado

[1, 2, 3]

🟡 DOUBLE (float)

[1, 2.5]

🔴 GENERIC (misturado) → pior cenário

[1, "a", {}]

⚠️ Armadilha crítica (quase ninguém fala disso)

A evolução é irreversível:

SMI → DOUBLE → GENERIC

const arr = [1, 2, 3];
arr.push(1.5); // já piorou
arr.push("x"); // pior ainda

👉 Depois disso, não tem volta.

📦 PACKED vs HOLEY

🚀 PACKED (rápido) → sem buracos

[1, 2, 3]

🐢 HOLEY (lento) → com buracos

const arr = [1, 2, 3];
arr[10] = 5;

👉 Isso força o V8 a fazer mais checks internos.

⚡ O que destrói sua performance

  • Misturar tipos no mesmo array
  • Criar arrays esparsos (buracos)
  • Mudar o tipo ao longo do tempo

🚀 Como escrever código mais rápido

  • Use arrays homogêneos
  • Evite buracos
  • Seja previsível
  • Não “evolua” o array sem necessidade

🧠 Resumo brutal

  • V8 otimiza arrays agressivamente
  • Você pode quebrar isso facilmente
  • Pequenas decisões → grande impacto em escala

Para saber mais segue o post que fala mais a fundo sobre esse assunto.

From Zero to Shipped: How We Built Indiefy.xyz in a Weekend (With AI as Co-Founder)

We built a full-stack SaaS product – auth, database, payments, integrations, leaderboard, i18n, mobile UI, SEO – in a single sprint using AI as a build partner. Here’s every step, decision, and dead end. By the end of this post, you’ll think “wait, I could actually build that.”

The Idea That Wouldn’t Shut Up

Every indie hacker has the same problem: you want to build in public, but the tools suck.

You’ve got your MRR on a Google Sheet. Your equity is in a Notion doc nobody reads. Your expenses live in three different apps. And when someone asks “how’s the startup going?” you have to piece together a story from six different tabs.

That’s when the idea hit: what if Spotify, but for founders?

Not Spotify the product – Spotify the vibe. The dark design. The stats everywhere. The “Wrapped” that makes you feel like your year actually meant something. A profile page at indiefy.xyz/yourname that tells your whole founder story in one scroll – real MRR, real profit, real equity, real goals.

We called it Indiefy.

Step 0: Write the Spec Before Writing a Single Line of Code

The first thing we did wasn’t open a terminal. We wrote an indiefy.md file – a proper product spec. Not a Notion doc, not a tweet thread. A real, structured document with:

  • The concept in one sentence: “Spotify, but for indie hackers”
  • The target audience (5 different profiles: solo founders, early employees with stock options, angels, advisors, minority partners)
  • Every single feature, grouped into sections
  • The monetization model (Free vs Pro)
  • The tech stack
  • A 5-day build timeline

This step is the one most builders skip. Don’t skip it. When you have a spec, you have prompts. When you have prompts, AI can actually build your product instead of guessing.

Here’s an excerpt from ours:

## Features

### Charges & P&L public
- Real Profit = MRR - project costs - pro-rata fixed costs
- Runway = savings / total monthly costs
- Break-even: at what MRR you cover everything

> First tool that shows REAL project profitability, not just gross MRR.

That last line became our north star for every design decision.

Step 1: The Design System First (Seriously, Do This First)

We started with design tokens, not code. The whole aesthetic was clear from day one: Spotify dark theme. Exactly this palette:

Token Value
Background #0a0a0a
Cards #111111 and #1a1a1a
Accent green #1DB954
Primary text #FFFFFF
Secondary #A3A3A3
Borders #2a2a2a

Two fonts. That’s it. DM Sans for body copy. Syne for numbers and titles – that Syne font on a big MRR number hits different.

The prompt we gave the AI was surgical:

Create the complete design system for Indiefy.
Stack: Next.js 16 App Router, Tailwind CSS v4, TypeScript strict.
Design: Spotify-inspired, dark mode only.
Colors: [exact hex values]
Typography: DM Sans (body) + Syne (stats/titles)
Generate: tailwind.config.ts, globals.css, Layout.tsx with left sidebar,
Card.tsx (variants: default/highlight/glass), Typography.tsx
Rules: NO generic Bootstrap-like styles. Server Components by default.

The AI spit out a complete design system in one shot. Because the prompt was specific. Garbage in, garbage out – and vice versa.

Step 2: The Database Schema – Think Hard Before You Click Enter

The spec: 8 models. User, Project, Expense, Revenue, Goal, Milestone, Badge, Follow.

We wrote out the entire Prisma schema by hand in the spec file before generating it. Every relation, every index, every enum. This took 45 minutes and saved us from three migrations later.

model Project {
  id                 String        @id @default(cuid())
  userId             String
  name               String
  slug               String        @unique
  status             ProjectStatus // ACTIVE | ACQUIRED | DEAD | STEALTH
  equityPercent      Float?
  estimatedValuation Float?
  mrr                Float         @default(0)
  isPublic           Boolean       @default(true)
  showFinancials     Boolean       @default(false)
  // ...
}

The showFinancials boolean was a key product decision: people can share their profile publicly without showing the money. This unlocks a whole segment of users who want visibility but not full transparency.

We also added the Integration model from day one – Stripe, RevenueCat, LemonSqueezy, Paddle – even before building the integration pages. Schema-first means you don’t paint yourself into a corner later.

Step 3: Auth – The Part Everyone Dreads

Supabase Auth with Google + GitHub OAuth. Two provider buttons. That’s it.

The interesting part wasn’t the OAuth flow – it was the sync between Supabase Auth (which handles the JWT and session) and Prisma (which owns the business data). We wrote a route app/api/auth/sync/route.ts that fires after OAuth callback and ensures a User row exists in Postgres.

The middleware was three lines of logic:

// middleware.ts
const isProtectedRoute = matcher.some(path => req.nextUrl.pathname.startsWith(path))
const session = await getSession(req)

if (isProtectedRoute && !session) {
  return NextResponse.redirect(new URL('/login', req.url))
}

Username validation deserves a mention: lowercase, alphanumeric + hyphens, 3-20 chars, unique. We validated this with Zod server-side and debounced API check client-side. Tiny detail, important UX.

Step 4: The Dashboard – Making Numbers Feel Alive

This is where the product really started to look like something.

Four KPI cards at the top:

  1. Total MRR
  2. Net Profit
  3. Total Users
  4. Paper Wealth (equity x valuations)

That last one is what makes Indiefy different from every other dashboard tool. Paper wealth – the sum of all your equity stakes multiplied by estimated valuations – is a number no other tool shows you. It might be zero. It might be fictional. But it’s yours, and it’s there, and it feels good.

The MRRChart component used Recharts AreaChart with a gradient fill. One snippet made the whole chart feel premium:

<defs>
  <linearGradient id="mrrGradient" x1="0" y1="0" x2="0" y2="1">
    <stop offset="5%"  stopColor="#1DB954" stopOpacity={0.3} />
    <stop offset="95%" stopColor="#1DB954" stopOpacity={0} />
  </linearGradient>
</defs>

Step 5: Projects – The Core Loop

The project form was the most complex UI we built. A slider for equity percentage (0-100) with a live calculation beneath it: “Your estimated stake: 47,000”. A tech stack tag input. Status badges that actually looked like badges, not dropdowns.

The killer feature was the acquisition simulator. You type a valuation. The number updates instantly:

const yourShare = (estimatedSale * equityPercent) / 100
// Displayed in real-time as you type

Status colors:

Status Color Vibe
ACTIVE #1DB954 green Go time
ACQUIRED Blue Congrats
DEAD Red RIP
STEALTH Gray We see you

After creating a project, we auto-generated a milestone: "Launch de projet". Small touch. Makes the product feel alive immediately.

Step 6: Expenses & Real P&L

This is the feature that makes Indiefy honest.

Most “MRR dashboards” just show you gross revenue. Indiefy shows you the real number:

Gross Revenue (MRR):           2,400
Project costs (Vercel, etc): -    89
Fixed costs (pro-rata):      -   340
                             --------
Net Profit:                    1,971  
Margin rate:                      82%
Runway:                    14 months
Break-even MRR:                  429

The getPnLSummary() server action ran a single Prisma query that joined revenues, project expenses, and fixed costs pro-rated across all active projects. The math isn’t complex – it’s just honest math nobody else was doing.

Step 7: Integrations – The Part That Made It Real

Instead of just supporting Stripe, we ended up integrating five payment providers:

  • Stripe – OAuth Connect, read-only access
  • RevenueCat – API key, for mobile apps
  • LemonSqueezy – API key
  • Paddle – API key
  • Polar – the new open-source one
  • Dodo Payments – emerging market focus

The git log tells the story:

e98928d Polar
2f675fd leaderboard
7457233 dodo payments
67366a0 paddle
829add8 coming soon integrations

Each integration followed the same pattern:

// lib/integrations/syncEngine.ts
async function syncProjectRevenue(projectId: string): Promise<SyncResult> {
  const integration = await getActiveIntegration(projectId)
  const data = await fetchFromProvider(integration)

  await prisma.project.update({
    where: { id: projectId },
    data: { mrr: data.mrr, arr: data.arr, totalRevenue: data.totalRevenue }
  })

  await createRevenueEntry(projectId, data)
  await checkAndGenerateMilestones(projectId)
  await updateLastSynced(integration.id)

  return { success: true, mrr: data.mrr, lastSyncedAt: new Date() }
}

API keys were stored encrypted with AES-256-GCM. Not optional. You’re handling people’s financial data.

The Vercel cron job synced everything at 6am daily:

// vercel.json
{
  "crons": [{
    "path": "/api/cron/sync-revenues",
    "schedule": "0 6 * * *"
  }]
}

Step 8: Leaderboard – The Viral Loop

The leaderboard was the feature we almost cut. “Do we really need a ranking system?”

Yes. We really did.

2f675fd leaderboard
a2e22b5 explore algo
a17f087 trending algo

Three commits across leaderboard features. The ranking algorithm wasn’t just “sort by MRR” – we built a momentum score: a composite of MRR velocity (month-over-month growth %), user growth, and update frequency.

A 200 MRR project growing 30% monthly ranks higher than a 5k MRR project that hasn’t been updated in three months.

The Explore page had its own trending algorithm. Same logic: recent activity + growth velocity > absolute numbers. This made the platform feel fair for people who are just getting started.

Step 9: Mobile – The Feature You Can’t Skip Anymore

b8ff2e8 mobile ui
b4fc4a6 bottom nav
471fcce bubbles: mobile nav icons, verified-only badges
3a8410b loading pages

Mobile was a late sprint but not an afterthought. We replaced the left sidebar with a bottom navigation bar on mobile. Standard pattern, but the implementation matters: same server components, just conditional layout based on viewport. The Tailwind breakpoints did the heavy lifting.

The “bubble” nav icons made the mobile UI feel native rather than “responsive web.” Five tabs: Dashboard, Explore, Profile, Goals, Settings.

Loading states were added systematically across all pages – skeleton screens, not spinners. The difference between a product that feels fast and one that feels slow is often just skeleton screens.

Step 10: i18n – Because Half Your Users Aren’t English

d2ca272 i18n
ba6d47f i18n
40a060b Goal i18n

We used next-intl for internationalization. The routing structure:

app/
  [locale]/
    dashboard/
    profile/
    ...

Every user-facing string became a key:

const t = useTranslations('dashboard')
// t('kpi.totalMrr') -> "Total MRR" or "MRR Total"

Three commits for i18n because the first one was “we need i18n”, the second was “actually doing i18n”, and the third was “ok goals weren’t translated yet.”

Step 11: Security, Legal, Pricing

b4fc4a6 legals
a78c03e SECU
5023241 pricing page

Security was a dedicated commit because integrations meant we had real sensitive data: OAuth tokens, API keys, payment provider access. We audited every route for:

  • Auth checks on every server action
  • User ownership verification before any data mutation
  • Encrypted storage for all third-party credentials
  • Rate limiting on public API endpoints

Legal pages (Privacy Policy, Terms of Service) were generated but reviewed. You need them. Especially when you’re storing financial data and connecting to payment processors.

The pricing page was the last non-feature piece. Simple: Free (1 project, basic stats) vs Pro (~7/month, everything).

Step 12: SEO – The Long Game Starts at Launch

7928c7b SEO
eb6e47b SEO
2312193 Analytics
6e88960 Remove from sitemap the not public accounts

Dynamic OG images for every public profile (using @vercel/og). Proper <meta> tags. Sitemap generation that excluded private accounts – you don’t want to index profiles people marked private.

Vercel Analytics + Speed Insights went in at the same time. You can’t improve what you don’t measure.

The Final Stack

After 115 commits, here’s what Indiefy runs on:

Layer Technology
Framework Next.js 16 (App Router)
Styling Tailwind CSS v4
Database Supabase (PostgreSQL)
ORM Prisma 7
Auth Supabase Auth (Google + GitHub OAuth)
Payments Stripe (subscriptions)
Integrations Stripe, RevenueCat, LemonSqueezy, Paddle, Polar, Dodo
Charts Recharts
i18n next-intl
Validation Zod v4
Toasts Sonner
Deployment Vercel
OG Images @vercel/og
Analytics Vercel Analytics + Speed Insights

What Made This Possible

The honest answer: structured prompts written by a human who knew exactly what to build.

Every build step was a complete, specific prompt:

  • “Generate X with these exact fields”
  • “Use Zod for validation”
  • “No any types”
  • “Server Components by default, use client only when necessary”
  • “Match this exact design system”

The AI didn’t make product decisions. It executed them. The product thinking – what to build, why, in what order – that was 100% human.

Three things made the AI useful here:

  1. Specificity – vague prompts get vague code
  2. Existing design system – the AI respected our tokens because we defined them first
  3. Step-by-step – small, focused tasks beat “build me an app”

The Numbers

Metric Count
Git commits 115
Major features ~15
Payment integrations 6
Languages at launch 2
Bootstrap CSS used 0

Could You Build This?

Yes. Genuinely, yes.

The technical complexity here isn’t exceptional. Next.js App Router is mature. Supabase handles auth beautifully. Prisma removes the pain from database work. Stripe’s documentation is excellent.

What’s rare isn’t the ability to build it – it’s the discipline to spec it first, prompt it right, and ship before perfecting.

The features we shipped aren’t perfect. Some UI could be cleaner. The mobile experience could be smoother. The analytics could be deeper. But the product exists, it works, and it’s live.

Perfect is the enemy of shipped.

What’s Next for Indiefy

  • Indiefy Wrapped (December): your year as a founder, Spotify-style, shareable card
  • Enjoy life

If you’re an indie hacker who wants to display your real journey – not just a landing page, but actual MRR, real profit, equity you might never cash out but still own – create your profile at indiefy.xyz.

My First Electronics Project – Building a Real-Time Power Outage Monitor with ESP32 and Slack

The moment of realization happened in Singapore. I was thousands of miles away from home, enjoying a trip, when I went to check my home CCTV footage through my phone. The screen stayed black. “Connection Failed.”

The internal monologue of a developer immediately goes to the worst-case scenario: Did the router die? Is there a break-in? Did the server crash?

The reality was much simpler, yet equally frustrating: a power cut. The cameras ran on their internal batteries until they hit 0%, leaving me in a complete information blackout. I didn’t know if the power was out for ten minutes or ten hours.

I promised myself I wouldn’t leave for another trip without a “Heartbeat” from my home in Amritsar.

The Solution

I needed a non-invasive system (no cutting 220V mains wires), resilient to inverter switchover gaps, and capable of sending instant notifications.

I chose the ESP32 for its built-in Wi-Fi and low power consumption. By pairing it with a Slack Webhook, I created a device that “shouts” the second the grid fails.

The Hardware Stack

To keep things modular and “plug-and-play,” I went with:

  • ESP32 DevKit V1: The brain of the operation.

  • USB-TTL Converter: This acts as the sensor. It brings the 5V USB signal down to a safe 3.3V for the ESP32 to read.

  • 1000µF Capacitor: Essential for bridging the 20ms gap during inverter switchover. This prevents the ESP32 from rebooting during the transition.

  • Female-to-Female jumper wires

  • Some USB data cables

Engineering the “Flicker Filter”

One of the biggest hurdles was electrical noise. GPIO 34 on the ESP32 is an input-only pin and can act like an antenna. Without a solid ground reference, the signal “flickered” between ON and OFF.

I solved this with a two-pronged approach:

  1. Grounding Geometry: Moving the ground wires to the same side of the board to stabilize the reference voltage.

  2. Software Debounce: I implemented a 3-second delay in the code. The system verifies that the power is actually out before sending a Slack alert, preventing false alarms from minor grid fluctuations.

Watch the Journey

I documented the entire process—from navigating the local electronics markets in my city to the final “Production” test where I manually tripped the MCB.

Closing Thoughts

Engineering isn’t just about writing code for a Jira ticket; it’s about solving the small, personal anxieties of life through technology. Now, when I’m on vacation, I’ll know exactly what’s happening at home. Not because I’m checking a camera, but because my home is talking to me.

If you’re interested in building this yourself, feel free to reach out or check out the repository!

The Grimoire and Latent Space

For over forty years I have been fascinated by programming, and the fascination has always been rooted in the same thing: the experience of creating something apparently out of nothing, using nothing but my own thoughts.

There is no raw material. No canvas, no clay, no timber. There is only the space between what I intend and what I can express, and the slow, iterative work of closing that gap. I begin with an idea — an imagining, sometimes sharp, sometimes barely a shape — and I start capturing it in code. Each statement is an attempt to say what I mean. Each execution reveals where I didn’t quite say it. A small adjustment here, a rethink there. The thing I am building talks back to me through its failures, and through that conversation, it takes form.

I have always thought of this process as something like discovering a grimoire. Not writing one — discovering one. Each line of code is an incantation that I did not fully understand until I spoke it and saw what it summoned. The completed program is a spell that, once all its clauses are right, produces the solution as if by conjuring. The magic was never in any single line. It was in the accumulation, the patient layering of intent upon intent until the whole thing coheres and something real emerges from something that was, moments before, entirely imaginary.

This is what has kept me coming back. Not the solutions themselves — those are fleetingly satisfying. What keeps me is the act of creation. The journey.

The arrival of AI capable of writing code raises a practical question: can I hand over the cognitive load of creation? Not the typing but the imagining. The imagining-into-existence.

Considering it that way, a question arises.

Where do those ideas live before I reach for them?

When I sit down to solve a problem, the solution does not arrive fully formed. It emerges. I reach for something — a pattern, an approach, a structural intuition — and it is there, available to be grasped. But where was it before I reached? Is it purely the product of my own mind, the accumulated residue of everything I have learned and experienced? My conscious knowledge and my subconscious experience, firing together to produce candidates that I then select from?

I think the answer is yes, but the yes is more interesting than it first appears. Because what I am describing is not retrieval. It is not like opening a filing cabinet. It is more like reaching into a space of possibilities — a vast, high-dimensional space shaped by every problem I have ever encountered, every solution I have ever seen, every conversation, every failure, every late-night debugging session — and plucking from it a candidate that feels right. The conscious mind does the plucking. But the space itself is built from both conscious and subconscious experience, and it contains far more possibilities than I could ever enumerate.

In the world of AI, we talk about latent space. This is the internal, high-dimensional representation that a neural network builds from its training data — a vast landscape of encoded relationships, patterns, and possibilities that the model navigates when generating a response. The model does not retrieve answers from a database. It moves through latent space, and what it produces is shaped by the topology of that space.

If latent space is the product of an AI’s training, is my own accumulated experience not my own latent space?

I believe the answer is yes.

Recently I came across the concept of nāma-rūpa.

Nāma-Rūpa is a Buddhist philosophy, the name is a compound from Pali and Sanskrit: nāma means name, rūpa means form. Having lived in Malaysia, I recognised the words — nama and rupa — and that recognition attracted me.

The philosophy is very deep but what resonated with me were the ideas that to name something is to participate in giving it form and to perceive form is already to have named it. The first direction is intuitive — a programmer names a function and, by naming it, begins to define what it does. The second direction is subtler: when I look at a sprawling piece of code and suddenly see “this is a state machine,” the recognition is itself a naming act. The form was there, latent. Naming it made it available for thought.

This viewpoint enabled me to express everything I have written so far in a single sentence.

By naming something, I give it form and pull it from latent space

When I name a function, I help give it form. When I name a variable, I help give it form. When I name an architectural pattern — “this is a pub-sub system,” “this is a state machine” — I am reaching into the latent space of my experience and selecting a form by the act of naming it. The name is the handle. The latent space is the source. The form is what appears.

It is, at least to me, a useful description of what happens when a programmer creates. The latent space is real — it is the structured, high-dimensional encoding of everything you know and have experienced, most of which you could never articulate directly. Naming is the mechanism by which you reach into that space and collapse a cloud of possibilities into a specific form.

And this single formulation, By naming something, I give it form and pull it from latent space, allows me to approach my objective of passing the cognitive load of creation to AI.

I need to create the environment where the AI can name things from its own latent space and, by naming them, give them form. That environment is the subject of the next planned essay.