Retrieval Finds Candidates. Reranking Finds the Right One.

A hiring analogy that finally makes RAG Reranking click

First, What Is RAG?

Before we get into the analogy, let me give you a 30 second crash course on RAG because this is where reranking lives.
RAG stands for Retrieval Augmented Generation.

Here’s the problem it solves:

Large Language Models (LLMs) like GPT or Claude are incredibly powerful but they only know what they were trained on. They don’t know about your company’s internal documents, last week’s product update, or your customer support knowledge base.

RAG fixes that by giving the LLM a memory it can search.
Here’s how it works in three simple steps:

Retrieve — When a user asks a question, the system searches your document library and pulls the most relevant chunks
Augment — Those retrieved chunks are added to the prompt as context
Generate — The LLM reads the context and generates a grounded, accurate answer

Think of it like an open book exam. The LLM doesn’t have to memorize everything it just needs to find the right page and read it. Simple enough. But here’s where most RAG systems quietly fail.

The Hiring Analogy That Changes Everything

One of my friends recently asked me a simple but powerful question. “Why do we even need reranking after retrieval? Isn’t finding the right documents enough?. “Instead of going technical, I said “Let me tell you about a hiring process.“
Think of embedding based retrieval as your HR or Talent Acquisition team.

Their job is to:

  1. Scan thousands of resumes
  2. Filter based on keywords, skills, and experience
  3. Shortlist candidates that look relevant

This is exactly what vector similarity does. It retrieves documents that are “close enough” based on embeddings fast, broad, and essential.

But here’s the problem nobody talks about:

  1. 👉 Relevance is not correctness.
  2. 👉 Similarity is not suitability.

Just because a resume matches keywords doesn’t mean the candidate can actually solve the hiring manager’s real problem.

The same way, just because a document is topically similar doesn’t mean it actually answers the user’s question.

Now enters the Hiring Manager.

The hiring manager:

  1. Reviews the shortlisted candidates deeply
  2. Evaluates beyond surface level keywords
  3. Matches candidates against the actual needs of the role
  4. Rejects those who don’t truly fit
  5. Surfaces the one who genuinely belongs

This step is exactly what we call Reranking.

In AI Terms

  • Retrieval gives you Top-K similar documents (the shortlist)
  • Reranking evaluates semantic relevance to the actual question (the deep review)
  • It pushes the most useful answer to the top and filters out the noise

Real World Example: Cohere Reranking Model

One of the most popular and production ready reranking solutions today is Cohere’s Rerank API.

Here’s how it fits into a RAG pipeline in practice:

import cohere

co = cohere.Client("your-api-key")

# Step 1: Your retrieval system fetches top-K documents
query = "What is the refund policy for enterprise customers?"

retrieved_docs = [
    "Our refund policy allows returns within 30 days.",
    "Enterprise customers get dedicated support and SLA guarantees.",
    "Enterprise plans include custom refund terms negotiated at contract signing.",
    "Refunds are processed within 5–7 business days.",
    "Customer support is available 24/7 for enterprise accounts."
]

# Step 2: Cohere Reranker evaluates each document against the query
response = co.rerank(
    model="rerank-english-v3.0",
    query=query,
    documents=retrieved_docs,
    top_n=3  # Return only the top 3 most relevant
)

# Step 3: Most relevant documents bubble to the top
for result in response.results:
    print(f"Rank {result.index + 1} | Score: {result.relevance_score:.4f}")
    print(f"Document: {retrieved_docs[result.index]}")
    print()

What Cohere Rerank does differently:

  1. It doesn’t just compare embeddings it reads the query and document together
  2. It uses a cross encoder architecture that understands the relationship between the question and each document
  3. It returns a relevance score for each document so you know exactly why something ranked higher
  4. It works on top of any retrieval system FAISS, Pinecone, Weaviate, you name it

Sample Output:

_Rank 1 | Score: 0.9821
Document: Enterprise plans include custom refund terms negotiated at contract signing.

Rank 2 | Score: 0.7134
Document: Our refund policy allows returns within 30 days.

Rank 3 | Score: 0.4821
Document: Refunds are processed within 5–7 business days._

Notice how the document that specifically answers the enterprise refund question jumps to the top even though all five documents were “about” refunds or enterprise. That’s the hiring manager effect in action.

The Real Insight

Without Reranking:

  • You get good looking answers
  • But not always correct or truly useful ones
  • Your LLM is working with noisy, approximate inputs

With Reranking:

  • You move from approximate similarity → precise relevance
  • Your LLM gets exactly the right context to generate sharp, accurate answers
  • The difference in output quality is night and day.

One Line Takeaway

Retrieval is about finding options. Reranking is about making the right decision.

The next time someone asks why reranking matters skip the jargon.
Just say: “HR shortlists the candidates. The hiring manager picks the right one. Your AI needs both.”
Because in RAG systems, just like in hiring, getting the right candidates in the room is only half the battle. Choosing the right one is where the magic happens.

Thanks
Sreeni Ramadorai

Value Types vs Reference Types, Struct vs Class, and Boxing & Unboxing — The Complete C# Guide

Value Types vs Reference Types, Struct vs Class, and Boxing & Unboxing

These three topics are deeply connected.

You cannot fully understand struct vs class without understanding the stack and heap.

You cannot fully understand boxing and unboxing without understanding value and reference types.

This guide covers all three together — the way they should be taught.

How .NET Manages Memory

Before anything else, you need to understand two memory regions.

The Stack

  • Fast allocation and deallocation
  • LIFO (Last In, First Out) structure
  • Stores value types and method call frames
  • Memory is automatically reclaimed when the method exits
  • Limited in size

The Heap

  • Slower allocation
  • Stores reference types (objects)
  • Managed by the Garbage Collector (GC)
  • Much larger than the stack
  • Memory is reclaimed by the GC, not deterministically

This distinction is the foundation of everything that follows.

Value Types

A value type holds its data directly in memory.

When you assign a value type to another variable, a copy is made.

int a = 10;
int b = a;
b = 20;

Console.WriteLine(a); // 10 — not affected
Console.WriteLine(b); // 20

Common value types

  • int, float, double, decimal
  • bool, char
  • DateTime, TimeSpan
  • struct
  • enum
  • Nullable value types (int?)

Where they live

Value types declared as local variables live on the stack.

Value types declared as fields inside a class live on the heap alongside the object.

Reference Types

A reference type stores a reference (pointer) to the actual data on the heap.

When you assign a reference type to another variable, both variables point to the same object.

var person1 = new Person { Name = "Alice" };
var person2 = person1;

person2.Name = "Bob";

Console.WriteLine(person1.Name); // Bob — both point to the same object

Common reference types

  • class
  • string
  • interface
  • delegate
  • Arrays
  • object

Where they live

The reference (the pointer) can live on the stack.

The actual object data always lives on the heap.

The Key Difference — Side by Side

Value Type Reference Type
Stores Actual data Reference to data
Assignment Creates a copy Shares the reference
Memory Stack (usually) Heap
Null Not nullable by default Nullable
GC involvement None Yes
Examples int, struct class, string

Struct vs Class

struct is a value type.

class is a reference type.

This single difference drives all the behaviour below.

Struct example

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // copy

p2.X = 99;

Console.WriteLine(p1.X); // 1 — unaffected
Console.WriteLine(p2.X); // 99

Class example

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // same reference

p2.X = 99;

Console.WriteLine(p1.X); // 99 — both affected
Console.WriteLine(p2.X); // 99

Struct vs Class — Feature Comparison

Feature Struct Class
Type Value type Reference type
Memory Stack (typically) Heap
Inheritance Cannot inherit Supports inheritance
Default constructor Implicit (zeroes fields) Can define
Nullable No (unless Nullable<T>) Yes
Passed by Value (copy) Reference
GC pressure None Yes
Best for Small, immutable data Complex objects, behaviour

When to Use Struct

Use a struct when:

  • The data is small (typically under 16 bytes)
  • The type is logically a single value (a point, a colour, a coordinate)
  • Immutability is desired
  • You want to avoid heap allocation and GC pressure
  • The type will be used heavily in tight loops or collections
// Good struct candidates
public struct Coordinate { public double Lat; public double Lon; }
public struct Rgb { public byte R; public byte G; public byte B; }
public struct Money { public decimal Amount; public string Currency; }

When NOT to use struct

  • The data is large (copying becomes expensive)
  • The type needs inheritance
  • The type has mutable state that gets passed around
  • The type will be used as an interface (triggers boxing — explained below)

Boxing and Unboxing

Boxing and unboxing happen when a value type is treated as a reference type.

Boxing

Converting a value type to object (or an interface type).

int number = 42;
object boxed = number; // Boxing — a copy is placed on the heap

What happens under the hood:

  1. Memory is allocated on the heap
  2. The value is copied from the stack into the heap object
  3. The object variable holds a reference to that heap memory

Unboxing

Converting a boxed object back to a value type.

object boxed = 42;
int unboxed = (int)boxed; // Unboxing — copies value back from heap to stack

What happens under the hood:

  1. The runtime checks the type matches
  2. The value is copied from the heap back to the stack

Why Boxing is Expensive

Boxing seems harmless. It isn’t.

Every boxing operation:

  • Allocates heap memory
  • Triggers the Garbage Collector more frequently
  • Copies data (twice — once to box, once to unbox)
  • Adds CPU overhead from type checking

In hot paths — tight loops, high-frequency operations, large collections — boxing degrades performance significantly.

The Classic Boxing Trap — ArrayList

// ❌ Old way — ArrayList boxes every int
var list = new ArrayList();
for (int i = 0; i < 1_000_000; i++)
{
    list.Add(i); // Boxing happens 1,000,000 times
}

// ✅ New way — List<T> avoids boxing entirely
var list = new List<int>();
for (int i = 0; i < 1_000_000; i++)
{
    list.Add(i); // No boxing
}

List<T> uses generics — no boxing required.

ArrayList treats everything as object — boxing on every insert.

Another Common Trap — Interfaces and Structs

public interface IPrintable
{
    void Print();
}

public struct Message : IPrintable
{
    public string Text;
    public void Print() => Console.WriteLine(Text);
}

IPrintable msg = new Message { Text = "Hello" }; // ❌ Boxing!

Assigning a struct to an interface causes boxing.

The struct is copied to the heap so it can be referenced as an interface.

Spotting Boxing at Runtime

Boxing shows up as:

  • Increased GC collections (especially Gen0)
  • Memory pressure in profilers
  • System.Object allocations in heap snapshots

Use tools like:

  • dotMemory (JetBrains)
  • BenchmarkDotNet with memory diagnostics
  • Visual Studio Diagnostic Tools
  • GC.GetTotalMemory(false) for quick checks

Real-World Scenario

Scenario: Logging with string interpolation

int userId = 101;
bool isActive = true;

// ❌ Older pattern — potential boxing with format args
string.Format("User {0} active: {1}", userId, isActive);

// ✅ Modern — string interpolation compiles efficiently
$"User {userId} active: {isActive}";

// ✅ Best for hot paths — structured logging with no boxing via generics
_logger.LogInformation("User {UserId} active: {IsActive}", userId, isActive);

ILogger<T> uses generic overloads to avoid boxing on primitive arguments.

Interview-Ready Summary

  • Value types store data directly — assignment copies the value
  • Reference types store a pointer — assignment shares the reference
  • Stack = fast, limited, deterministic; Heap = larger, GC-managed
  • struct is a value type; class is a reference type
  • Use struct for small, immutable, short-lived data
  • Boxing converts a value type to object and allocates on the heap
  • Unboxing copies the value back from the heap
  • Boxing in loops or large collections silently destroys performance
  • Generics (List<T>) were introduced specifically to eliminate boxing
  • Assigning a struct to an interface triggers boxing

A strong interview answer:

“Value types hold their data directly and live on the stack by default, while reference types store a pointer to heap memory. Structs are value types best suited for small, immutable data. Boxing wraps a value type in a heap-allocated object — it’s implicit, easy to miss, and expensive at scale. Generics eliminate boxing by preserving type information at compile time.”

⭐ Add-On — Why This Matters More Now With record struct

C# 10 introduced record struct — a value type with built-in immutability and value equality.

public record struct Coordinate(double Lat, double Lon);

var a = new Coordinate(1.0, 2.0);
var b = new Coordinate(1.0, 2.0);

Console.WriteLine(a == b); // True — value equality, no boxing

This gives you the clean syntax of records without the heap allocation of reference-type records.

For high-performance scenarios where you want immutable data structures, record struct is now the go-to choice.

Why You Need An Intention For Your Coding Career

Last week, I wrote about the most painful lesson my best job taught me. The concept of a “plan” generated some discussion. Here I’m expanding on that.

It took me over 10 years to connect the dots.

For years, I didn’t have a career plan. I jumped from job to job feeling something was missing. OK, when I say “jump,” I mean fired, bored, and laid off. That was my most painful lesson. It cost me my health at the lowest point.

A “plan” sounds like a blueprint with every career scenario figured out in advance.

Nobody starts with a perfect plan. The early stages of our careers are about discovery, experimentation, and building our coding skills while learning to navigate the corporate world.

Plans are hard to follow when layoffs are always around the corner and AI is changing job descriptions.

An intention helps you decide when to move

Instead of “plan,” think of an intention:

A guiding principle that helps you decide whether to stay or move on.

There’s always luck, setbacks, and resets. It’s impossible to account for all of them in a “plan.” But an intention pushes you to act instead of waiting.

Here’s how an intention helps you in practice.

If your intention is money, a couple of years without raises or bonuses should push you to move. That’s not the job that will fill your pockets. Maybe a better choice would be joining a startup in early stages.

If your intention is growing a network, sitting in a cubicle isn’t the best idea. A better role might be DevRel, evangelist, or consultant, positions that takes you out of your cubicle.

Money was just an example. It could be gaining leadership experience, climbing the ladder, or learning opportunities.

Use your career intention as your flashlight. Otherwise, wait to leave only when bored, fired, or burned out. I wish someone had told me that when I started my coding career. It would have saved lots of headaches. Set your intention today.

If you liked this lesson, you’re going to like, Career Lessons From the Trenches, my free 7-day email course where I distill 10+ years of career lessons into 7 short emails–to help you navigate your coding career.

MiniStack v1.1.2 — Cognito, EC2, EMR, 656 Tests, and Zero Docker Leaks

We just shipped MiniStack v1.1.2. This is the biggest release since the initial launch — full Amazon Cognito support, partial EC2, EMR, a complete test suite overhaul, and a pile of infrastructure fixes that make running MiniStack day-to-day significantly cleaner.

If you’re not familiar: MiniStack is a free, open-source local AWS emulator. One port, no account, no license key. A drop-in replacement for LocalStack — which moved its core services behind a paid plan.

What’s new in v1.1.0

Amazon Cognito — full emulation

This was the most requested feature since launch. v1.1.0 ships complete Cognito support across both planes:

User Pools (cognito-idp)

  • Full user lifecycle: SignUp, ConfirmSignUp, AdminCreateUser, AdminDeleteUser, AdminGetUser, ListUsers
  • Auth flows: USER_PASSWORD_AUTH, ADMIN_USER_PASSWORD_AUTH, REFRESH_TOKEN_AUTH, USER_SRP_AUTH (returns PASSWORD_VERIFIER challenge)
  • FORCE_CHANGE_PASSWORD challenge on first login
  • Self-service: ForgotPassword, ConfirmForgotPassword, ChangePassword, GetUser, DeleteUser
  • Groups, domains, MFA config, tags — all covered

Identity Pools (cognito-identity)

  • CreateIdentityPool, GetId, GetCredentialsForIdentity, GetOpenIdToken
  • SetIdentityPoolRoles, GetIdentityPoolRoles
  • Federated identity: MergeDeveloperIdentities, UnlinkIdentity

OAuth2

  • POST /oauth2/token — client_credentials flow, returns stub Bearer token

Stub JWTs are structurally valid base64url tokens — they pass format checks in most SDKs without needing real crypto.

import boto3

idp = boto3.client("cognito-idp", endpoint_url="http://localhost:4566",
                   aws_access_key_id="test", aws_secret_access_key="test",
                   region_name="us-east-1")

# Create a pool
pool = idp.create_user_pool(PoolName="my-app")
pool_id = pool["UserPool"]["Id"]

# Sign up a user
idp.sign_up(
    ClientId="local",
    Username="[email protected]",
    Password="Password123!",
    UserAttributes=[{"Name": "email", "Value": "[email protected]"}],
)

# Confirm and authenticate
idp.admin_confirm_sign_up(UserPoolId=pool_id, Username="[email protected]")
resp = idp.initiate_auth(
    AuthFlow="USER_PASSWORD_AUTH",
    AuthParameters={"USERNAME": "[email protected]", "PASSWORD": "Password123!"},
    ClientId="local",
)
print(resp["AuthenticationResult"]["AccessToken"])

644 tests — one file, all passing

We merged a separate QA test file into the main suite and fixed every test bug we found along the way:

  • test_s3_list_v1_marker_pagination — NextMarker only returned when Delimiter is set (AWS spec)
  • test_iam_inline_user_policy — boto3 deserialises PolicyDocument as a dict, not a string
  • test_kinesis_at_timestamp_iterator — boto3 already returns Data as bytes, no need to base64-decode
  • test_rds_snapshot_crud / test_rds_deletion_protection — added finally cleanup so containers are deleted after each test

644 tests across all 25 services. Single file. All passing.

Docker volume leak — fixed

This one was subtle. Every RDS (CreateDBInstance) and ElastiCache (CreateCacheCluster) call spins up a real Docker container. The postgres and mysql images declare VOLUME /var/lib/postgresql/data — so Docker was creating an anonymous volume for every container, even after the container was removed.

After 20 test runs: 30+ dangling volumes, 600MB+ of wasted space.

Two fixes:

  1. tmpfs on containers.run() — postgres/mysql data lives in container RAM. No volume created.
  2. container.remove(v=True) in reset() — volumes are removed with the container.
Before: 32 dangling volumes after 3 test runs
After:  2 volumes total (ministack + redis, always)

Clean up what you already have:

make purge

make purge — safe cleanup

purge: stop-compose
    docker rm -f $(docker ps -aq --filter "label=ministack") 2>/dev/null || true
    docker volume prune -f
    rm -rf ./data/s3/*

Every container MiniStack spins up is labelled ministack=true. The purge target uses that label — it won’t touch your other Redis, Postgres, or MySQL containers.

Bug fixes

  • Lambda GetFunctionConcurrency — was returning 404 after DeleteFunctionConcurrency. Now returns {} matching AWS behaviour
  • ElastiCache ModifyCacheParameterGroup — parameter key format was wrong (member vs ParameterNameValue). Modified params were silently ignored
  • RDS ModifyDBInstance — DeletionProtection=False with ApplyImmediately=True now correctly applies immediately
  • Cognito GetCredentialsForIdentity — response field is SecretKey (correct boto3 wire name)
  • Port conflict on pip install — ministack now prints a clear error if port 4566 is already in use instead of a raw uvicorn traceback

AWS CLI docs fix

A user reported credentials failing. The README was showing aws configure --profile local but then omitting --profile local from the example commands. Fixed — two working options now documented:

Option A — environment variables

export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1

aws --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket

Option B — named profile

aws configure --profile local
# Access Key: test / Secret: test / Region: us-east-1

aws --profile local --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket

Get it

# PyPI
pip install --upgrade ministack

# Docker
docker pull nahuelnucera/ministack:latest
docker compose up -d

GitHub: github.com/Nahuel990/ministack

25 AWS services. Free. MIT licensed. No account required.

What’s next

  • SFN Activities (CreateActivity, GetActivityTask) — already requested
  • State persistence for Secrets Manager, SSM, DynamoDB — PERSIST_STATE=1 currently only covers API Gateway
  • ACM — last item on the original roadmap

Issues and PRs welcome.

Foggy Odoo Bridge: Governed MCP Access to Odoo Data with Permission Preservation

I just open-sourced Foggy Odoo Bridge.

It is an Odoo addon that gives AI clients governed MCP access to Odoo data while preserving Odoo permission rules.

What it does

Most “AI + ERP” demos stop at connectivity. Foggy Odoo Bridge focuses on governed access instead — it keeps authentication, model visibility, and row-level rules inside Odoo before the query reaches the engine.

Highlights

  • API-key auth from Odoo
  • ir.model.access based model visibility
  • ir.rule conversion into query filters before execution
  • Multi-company isolation
  • Built-in semantic models for common Odoo objects
  • Built-in AI Chat inside Odoo
  • Support for embedded Python engine or external Foggy Python / Java services

Deployment Options

  • Embedded Python engine inside Odoo
  • External Foggy Python service
  • External Foggy Java service

Dependency Notes

If you only use it as a standalone MCP service, you do not need openai or anthropic in the Odoo environment. Those SDKs are only optional dependencies for the built-in AI Chat feature.

Repo: https://github.com/foggy-projects/foggy-odoo-bridge

The Ultimate Guide to Building Enterprise Micro-Frontends with Angular 21 & Native Federation

Introduction

Micro-Frontend (MFE) architecture is no longer a “luxury” for big tech—it’s a necessity for enterprise teams that need to scale. But in 2026, the way we build them has changed. If you’re still relying on heavy Webpack configurations, you’re already behind.

In this guide, I’ll show you how to build a production-ready MFE system using Angular 21 and Native Federation.

Why Native Federation?

Standard Module Federation is tied to Webpack. Native Federation is framework-agnostic and works natively with Vite and esbuild.

•
Speed: 10x faster builds.

•
Future-Proof: No more Webpack version hell.

•
Simplicity: Uses standard browser features (Import Maps).

The 3 Pillars of Enterprise MFEs

  1. The Shell (The Orchestrator)

The Shell isn’t just a wrapper; it’s the brain. It handles:

•
Authentication: Centralized JWT handling.

•
Routing: Dynamic loading of remote modules.

•
Shared State: Using NgRx Signals for lightweight, reactive data sharing.

  1. The Remotes (The Features)

Each remote app (Auth, Dashboard, Settings) should be independently deployable. If your Dashboard team breaks something, your Auth app should still work.

  1. The Shared Library

Don’t duplicate your UI components. Use a shared library (@mfe/shared) for your Tailwind configurations, Angular Material themes, and common utilities.

The “Hidden” Challenge: RTL & Multi-language

Most MFE tutorials ignore the Middle Eastern market. If you’re building for clients in the UAE or KSA, Arabic RTL support isn’t optional.

•
The Problem: How do you sync the RTL direction across 5 different micro-apps instantly?

•
The Solution: A centralized DirectionService in the Shell that propagates state via Import Maps.

🚀 Stop Reinventing the Wheel

Building this architecture from scratch takes 100+ hours of configuration, testing, and debugging.

I’ve already done the hard work for you. I built NgMFE, a production-ready boilerplate that includes:

•
✅ Angular 21 + Native Federation

•
✅ Full JWT Auth & Route Guards

•
✅ Native Arabic RTL Support

•
✅ CI/CD with GitHub Actions

Save weeks of development and launch your enterprise app today.

👉 Get the NgMFE Starter Kit on Gumroad

https://mhmoudashour.gumroad.com/l/hdditr

🔴 View the Live Demo

https://ng-mfe-shell.vercel.app/auth

Conclusion

Micro-frontends are powerful, but only if the foundation is solid. Don’t get stuck in “configuration hell.” Focus on building features, not architecture.

Have questions about Native Federation? Drop a comment below!

5 Tanda Bisnis Kamu Sudah Butuh Sistem Kasir Digital (POS)

Tidak semua bisnis langsung membutuhkan sistem kasir digital. Namun, ada tanda-tanda tertentu yang menunjukkan bahwa bisnis kamu sudah saatnya beralih ke sistem yang lebih modern.

Jika kamu mulai mengalami hal-hal berikut, mungkin ini saat yang tepat untuk upgrade.

📈 1. Transaksi Semakin Banyak

Seiring berkembangnya bisnis, jumlah transaksi akan meningkat.

Jika kamu masih menggunakan pencatatan manual, proses ini akan menjadi:

  • Lebih lambat
  • Lebih rawan kesalahan
  • Sulit dikontrol saat jam ramai

😵 2. Sering Terjadi Kesalahan Perhitungan

Kesalahan seperti:

  • Salah total
  • Salah harga
  • Salah kembalian

Mungkin terlihat kecil, tapi jika terjadi terus-menerus bisa berdampak pada keuangan bisnis.

📦 3. Stok Barang Tidak Akurat

Masalah yang sering terjadi:

  • Barang tercatat ada, tapi ternyata habis
  • Stok tidak pernah di-update
  • Sulit melacak keluar-masuk barang

Ini bisa menyebabkan kehilangan penjualan atau overstock.

🕒 4. Antrian Pelanggan Mulai Panjang

Proses transaksi manual biasanya memakan waktu lebih lama.

Akibatnya:

  • Pelanggan harus menunggu
  • Pengalaman belanja menurun
  • Potensi kehilangan pelanggan

📊 5. Tidak Punya Data Penjualan yang Jelas

Tanpa sistem digital, kamu mungkin kesulitan menjawab:

  • Produk apa yang paling laris?
  • Kapan waktu penjualan tertinggi?
  • Berapa total omzet harian?

Tanpa data, keputusan bisnis hanya berdasarkan perkiraan.

💡 Kenapa Sistem POS Menjadi Solusi?

Sistem POS membantu bisnis dengan cara:

  • Mempercepat proses transaksi
  • Mengurangi kesalahan pencatatan
  • Mengelola stok secara otomatis
  • Menyediakan laporan penjualan real-time

🧠 Insight

Banyak bisnis tidak sadar bahwa masalah kecil yang terjadi setiap hari sebenarnya adalah tanda bahwa sistem mereka sudah tidak efisien.

Menggunakan sistem yang tepat bukan hanya mempermudah kerja, tapi juga meningkatkan kualitas operasional bisnis.

🎯 Kesimpulan

Jika bisnis kamu mengalami beberapa tanda di atas, itu berarti kamu sudah siap untuk beralih ke sistem kasir digital.

Dengan sistem yang lebih terstruktur, bisnis bisa berjalan lebih cepat, akurat, dan scalable.

Expo app : RedirectTo field with Supabase Auth always set to localhost

You are building an Expo Mobile app with Supabase as a backend?

You have correctly set the redirect url to exp:///auth/callback** in the URL Configuration of Supabase Auth and want to try your flow on Expo Go?

If you keep seeing localhost as the redirectTo address in the reset password link set by email, here is the fix:

npx expo start --tunnel