Skip to content
AFM Studio
  • Pages

    AFM Studio
  • Work
  • Lab
  • About
  • CV
  • Contact
  • Projects

    AksaraA searchable library for Otorita IKN's knowledge, where the server checks every reader's access across three levels
  • Daily TaqwaTamper-resistant prayer attendance system for ~2,000 OIKN government employees at Masjid Negara IKN
  • Lantara v2A permit engine for Otorita IKN where administrators add new permit types from a screen, without a developer
  • eKiosk IKNTwo control rooms for Nusantara: kiosk content management and live bus tracking with arrival times that grade themselves
  • E-Monev IKNA monitoring platform for Nusantara's 23-year build, tracking every unit's quarterly progress against 24 national KPIs
  • JDIH OIKNOIKN's public legal document portal, with a controlled publishing workflow and AI summaries that never leave the server
  • Jejak PenanamanA check-in app for IKN tree-planting events that proves each participant was on site with a geofenced selfie
  • Dashboard SAKTIA nightly pipeline and dashboard that replaces OIKN's manual budget downloads for all six of its budget units
  • DoseRxA bedside dose calculator for 127 drugs that rounds each dose to tablets and syrups Indonesian pharmacies sell
  • CubiqA speedcubing trainer with competition-grade timing and a solver I wrote for each of the 8 major puzzles
  • ScimotionAn interactive science library of 86 articles, each explained next to animations the reader can control
  • WA Daily Scrum BotA WhatsApp bot that chases missing standup reports for a 14-person team and flags tasks stuck for days

↑ ↓ to move · Enter to open · Esc to close

Stemmer Visualizer

A step-by-step view of how Indonesian words are cut to their roots, dead ends and double roots included

Screenshots
root words in an editable dictionary
~30,000
stemming rules stored as cited data
44
words compared against Sastrawi, differences classified
315
NLP libraries at runtime
0
On this page

The problem

Almost every Indonesian search engine or language tool first reduces words to their root, so that mempelajari, pelajaran and belajar all count as ajar. This step is called stemming. In Indonesia it nearly always uses the Nazief and Adriani algorithm, usually through the Sastrawi library, as a black box: word in, root out.

That has three costs for the students, lecturers and developers who rely on it. A wrong root looks just like a right one, because both are plausible Indonesian words. The answer depends on a hidden dictionary of root words as much as on the rules. And some words have two valid roots. Beruang is either ber- plus uang ("has money") or the root beruang ("bear"). The algorithm picks one and says nothing.

What I built

Every step is on screen, including the dead ends

Type a word and the trace shows each step as a numbered line: what was removed, which rule did it, the word before and after, and whether the dictionary recognised the result. Steps the algorithm tried and abandoned stay visible, struck through in grey. You can walk through it one step at a time, and an animation peels each prefix or suffix off the word.

The rules are data a linguist can read

None of the 44 rules is written as code. Each one lives in a data file with its pattern, its effect, its priority and a citation. The engine reads that file and applies it. A linguist can audit the rules without reading any code, and the trace can name the exact rule that fired. The build fails if any rule is missing a citation.

The dictionary is something you can edit

I made the dictionary an explicit input to every calculation. The dictionary panel lets you search it, add a word or remove one, and watch a stem that worked a moment ago break. Edits are saved in the link, so a broken-on-purpose state can be shared. A comparison view shows the same word with and without the dictionary, which makes clear what the dictionary contributes.

Ambiguous words get more than one answer

The same rules drive two searches. One follows the algorithm and returns its single answer. The other explores every way the word could be split. For beruang, the app shows both valid roots and explains why the algorithm chose the one it did. A gallery of six words the algorithm gets wrong is recalculated live, each labelled with how and where it failed.

Sastrawi is a reference to test against

I compared my results with Sastrawi on 315 words and found nine differences. Three were my bugs: two wrong dictionary entries and one missing rule. The other six are classified in writing, and a test pins each one so nobody can quietly change it. I couldn't get the original papers, so every rule is marked unverified and a gaps file says where to check them.

Result

Stemmer Visualizer is live, public and open source, in Indonesian and English, and runs offline. It has seven pages, including a rule reference generated from the rule data and a document mode that flags ambiguous words in a pasted paragraph. It ships Sastrawi's root list of about 30,000 words. That fixed two differences but broke one word, perusakan, which now returns rusa (deer). I accepted that and wrote it up. The site links to Sastrawi as the library to use in production. I built it in about nine hours on 5 August 2026.

Under the hoodTechnical detail for engineers
  • The engine in lib/engine/ is seven pure modules. Signature: (word, variant, ruleSet, dictionary) → StemTrace. No React, DOM, clock or module state; identical input gives a byte-identical trace.
  • interpret.ts follows precedence; enumerate.ts explores every segmentation. Tests assert soundness (the returned root is in the candidate set), not equality.
  • Rules live in data/rules/na96.json with stable ids such as na96.awalan.me.luluh.ny. pnpm rules:validate checks schema, citations and precedence conflicts with Zod, and gates build and deploy.
  • All 44 rules carry verification: "unverified". The 1996 paper, the 2005 extensions and the 2007 TALIP version couldn't be obtained; data/rules/GAPS.md records what was tried. A test asserts the unverified count, so verifying one forces an update.
  • Oracle: PySastrawi 1.2.1 over 315 words → 9 divergences. Fixed: hargai and sertai wrongly listed as roots, and a missing per- before-consonant rule (aturan 22), which also fixed pertemuan → temu. The other six are classified in tests/oracle/divergences.md.
  • Tests pin to a 293-word curated dictionary, because every fixture was authored against it. The shipped list is 29,932 words (about 290 KB), vendored as data under Sastrawi's MIT licence and loaded as its own chunk to keep app JS under 200 KB gzipped. No Sastrawi code ships.
  • The 2005 variant is only partial: the dictionary-free comparison ships, but the rule pack would have to be invented, so it doesn't exist.
  • Pencil grey was split into two tones so secondary text passes WCAG AA. The project was called Kupas in the PRD; kupas survives as the trace route.
  • Next.js 14 static export, 7 routes × 2 locales, 20 components, five runtime dependencies and no NLP library. 59 Vitest tests in 6 files. About 5,480 lines, 42 commits.

Screenshots

Beranda
Kupas
Kandidat
Kamus
Aturan
Galeri Kegagalan

Hiring, or building something similar?

I'm open to full-time roles and select freelance work.