# Ditto: type it once, and Gemma 4 on your laptop fills every form after that

> Source: <https://dev.to/vivaan_teotia_/ditto-type-it-once-and-gemma-4-on-your-laptop-fills-every-form-after-that-331>
> Published: 2026-10-22 15:37:35+00:00

*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*

In school, when an answer was the same as the line above, you didn't write it again. You drew two little strokes, **〃**, the *ditto mark*, and moved on.

The internet never learned that trick. Job portals, bank KYC forms, clinic intakes, college applications and rental forms all ask the same forty questions in slightly different words. *Legal first name. Father's / Spouse's Name. Date of Birth (DD/MM/YYYY). Aadhaar No. (last 4 digits only). Is your correspondence address the same as your permanent address?* Browser autofill handles your name and email, then gives up, and you type the rest again. Every form. Forever.

So I built **Ditto**: a Chrome extension that fills **any** web form from a private profile, using **Gemma 4 running on my own laptop through Ollama**. Press Alt+Shift+. (or click the toolbar button) and a small open model reads the form the way you would (labels, sections, instructions) and fills it, field by field, at sloth speed, while you do nothing:

It's designed to be *safely* lazy:

**Who gets to be lazier?** Anyone who fills forms, which is everyone. Job seekers retyping their resume into applicant-tracking portals. Students on admission and scholarship forms. Parents on school forms. Patients at a new clinic. And anyone opening a bank account in India, where KYC is less a process and more a lifestyle.

*A real run on my laptop's CPU, with the waiting sped up 5×. Watch for the BLOCK LETTERS, the DD/MM/YYYY dates, "last 4 digits only" for Aadhaar, the income bracket picked from the salary, the permanent address (Chennai) kept apart from the current one (Bengaluru), and the nominee section filled with Meera's mother, not Meera. The declaration checkbox is left for the human.*

🌐 **Website and five demo forms:** [dragon-slayer27.github.io/ditto](https://dragon-slayer27.github.io/ditto/). Install the extension, click **Load Meera** (a fictional person with about 50 facts) and try any of the forms.

I removed three facts from Meera's profile (blood group, allergies, emergency contact phone) and opened a clinic form. Ditto left those fields empty instead of guessing. I typed them in by hand and clicked **Remember what I typed**:

Ditto proposed exactly those three facts. It deliberately ignores free-text answers, like the reason for a clinic visit, because those only make sense once. Then I opened a completely different form, a hackathon registration, and the allergy and emergency contact phone filled themselves:

That's the slow and steady part: every form you fill by hand is the last time you type those answers.

**A Chrome extension that fills any web form from your private profile, using Gemma 4 running on your own computer through Ollama.**
Job portals, bank KYC, clinic intake, rental applications, college forms: they all ask the same forty questions in slightly different words. Ditto answers them for you, and your passport number never leaves your laptop.

*A 26-field bank form filled by Gemma 4 E4B on a laptop CPU. Green came from the profile, amber means "please check", grey means "needs you".*

**Website and demo forms:** [https://dragon-slayer27.github.io/ditto/](https://dragon-slayer27.github.io/ditto/)

Browser autofill knows your name and email. It has no idea what "Father's / Spouse's Name", "Aadhaar No. (last 4 digits only)" or "Name as per PAN, in BLOCK LETTERS" means, so you type it all again, on every form, forever.

You *could* paste the form into a cloud chatbot along with your ID numbers, address and…

**The open pieces:**

**What happens when you press the shortcut:**

`<label>`, ARIA, neighbouring table cells, nearby text or placeholders), the `maxlength`.
**1. Ollama said 403.** Ollama rejects requests from browser extensions unless you set `OLLAMA_ORIGINS`. I didn't want anyone editing environment variables, so Ditto adds a `declarativeNetRequest` *session rule* that removes the `Origin` header only for the extension's own requests (`tabIds: [-1]`). Zero setup, and web pages still can't reach your Ollama through Ditto.

**2. 134 seconds for one form.** My first output format was an array of `{id, value, from, sure}` objects, about 45 tokens per field at about 10 tokens a second. Switching to a per-form schema, `{"values": {"d1": …, "d2": …}, "unsure": [...]}`, cut the output 3.5× and the same bank form went from **134 s to 51 s**. A bonus: every field id is a *required* key, so the model can't skip a field or invent one.

**3. Someone's height came out as `"},{"`.** Every number field got junk like `", "`. The parser was fine. Gemma really wrote it. My schema only allowed a string or null, the model wanted to write a bare `162`, the grammar blocked that token, and the model derailed. Allowing numbers in the schema fixed all seven fields at once. A good reminder that constrained decoding can push a model off a cliff when the schema fights its instincts.

**4. Gemma forgot BLOCK LETTERS after section 1.** The values were right, just in normal case. Instead of pleading harder in the prompt, Ditto spots "BLOCK LETTERS" on the page and uppercases in code. That became my rule for the whole project: **the model finds the facts, code enforces the formats.**

**5. Confident guesses.** Asked for "Monthly take-home income", Gemma divided the annual salary by 12 (ignoring tax). Asked "Number of people who will live here", it said 1. Neither is in the profile. So every value now goes through a **tripwire**: every word and number in it must trace back to the profile (dates in any format, digit runs as substrings, short numbers as standalone tokens). Both guesses now turn amber on their own.

**6. My own shortcut didn't work.** The first time I installed Ditto in my everyday browser, the toolbar button worked but Alt+Shift+D did nothing. Dark Reader already owned that combination, and Chrome doesn't warn anyone: it just leaves the new extension's shortcut empty. A GitHub code search found 488 public manifests suggesting Alt+Shift+D and 23 suggesting Alt+Shift+Period, so the default is now Alt+Shift+.. More importantly, Ditto now asks Chrome which shortcut it actually got (`chrome.commands.getAll()`). If the answer is "none", the popup says so and links straight to `chrome://extensions/shortcuts`. A CI test reproduces the conflict with a stand-in extension to keep it that way.

I built five demo forms to be annoying in the ways real forms are:

`<input>` s.
That's 114 fields of ground truth, 18 of which must stay **empty** because the honest answer is "the profile doesn't know". [`eval/run-eval.mjs`](https://github.com/dragon-slayer27/ditto/blob/main/eval/run-eval.mjs) loads the real extension in Chromium, fills every form, and scores each field.

| Form | Fields | Right | Wrong but shown green | First fill | Same form again | 
|---|---|---|---|---|---|
| Job application (React) | 28 | 28 | 0 | 50 s | 0.1 s | 
| Bank KYC (table layout) | 26 | 26 | 0 | 54 s | 0.1 s | 
| Clinic intake | 23 | 23 | 0 | 45 s | 0.1 s | 
| Rental application | 18 | 16 | 0 | 27 s | 0.1 s | 
| Hackathon sign-up | 19 | 19 | 0 | 26 s | 0.1 s | 
| **Total** | **114** | **112 (98.2%)** | **0** | **202 s** | **0.4 s** | 

"Right" counts fields filled correctly *and* fields correctly left empty. The two wrong answers are the take-home pay and the head count from above, and the tripwire flagged both amber. **Not one wrong value was shown green.** React's state received all 23 values Ditto typed into the job form.

I ran the same eval with the smaller **Gemma 4 E2B** (4.6 GB instead of 6.1 GB):

| Model | Right | Wrong but shown green | Output speed on my CPU | 
|---|---|---|---|
| **Gemma 4 E4B** | **112 / 114** | **0** | 9.7 tokens/s | 
| Gemma 4 E2B | 104 / 114 | 8 | 11.9 tokens/s | 

E2B's big mistake was instructive: it put the *current* Bengaluru address into the bank form's *Permanent address* section. Those are real facts in the wrong place, so my tripwire, which catches facts that aren't in your profile, couldn't see them: six silent mistakes in one go. That's why Ditto picks E4B when it's installed, and why I measured both on my own forms instead of trusting a leaderboard.

The eval runs in the cloud too: a GitHub Actions workflow installs Ollama on a standard runner, pulls Gemma and fills all 114 fields with the real extension. [Its first run](https://github.com/dragon-slayer27/ditto/actions/runs/39649957127) finished in under five minutes and reproduced the finding on different hardware: E2B scored 106/114, with the same permanent-address mistake.

**Forms are where we hand over our most sensitive facts.** PAN, Aadhaar, passport number, home address, medical conditions, family members. I'd never paste those into a cloud chatbot every time a form asks. With an open-weight model on my laptop, the profile lives in my browser, the model reads it locally, and the only place Ditto's manifest lets it connect to is my own machine (`127.0.0.1`), where Ollama runs. It can't even read a page until you press the button.

**It costs nothing per form, forever.** No API key, no subscription, no per-token bill, nothing that can be repriced or switched off. That changed how I built it, too: I ran the full 114-field eval over and over while tuning prompts and schemas, for free, on the same machine that will run it for real.

**I could open the hood when things broke.** The `"},{"` bug was a fight between the model and the grammar constraining it. I found it by reading raw model output and fixed it by changing the schema. Local control over decoding (JSON schemas, temperature, context size, how long the model stays warm) is what made Ditto reliable rather than lucky.

**I could choose the model's size.** E2B for 8 GB machines, E4B for accuracy, measured on my own task. A closed API picks that trade-off for you.

**Anyone can check it.** MIT-licensed code, Apache-licensed weights. If a tool is going to fill in your passport number, you should be able to read every line that touches it.

And honestly, it fits the theme: at about 10 tokens a second, Gemma on my CPU *is* a sloth. That's fine. It's a sloth that works while I make chai, and it only has to be slow once per form.

I built Ditto with GitHub Copilot's agent as my pair programmer, from start to finish: brainstorming ideas, benchmarking Gemma 4 on my CPU before writing any code, building the extension and the eval harness, and debugging the `"},{"` bug from raw model output.

Across the five demo forms, Ditto fills **95 fields**: 1,352 typed characters plus 24 dropdown, radio and checkbox choices. Another 19 fields need a person no matter what (essays, consent boxes, questions the profile can't answer), so I left them out of both sides.

To estimate the by-hand time without fooling myself, I used the **Keystroke-Level Model**, the classic HCI method for predicting how long a skilled user takes with a keyboard and mouse (0.28 s per keystroke at about 40 wpm, 1.1 s to point, 1.35 s to recall or decide), plus 15 s for each value nobody knows by heart, like a PAN or a policy number. The Ditto side uses the times measured in the eval.

|  | Five forms by hand | With Ditto | 
|---|---|---|
| Your time | **11 min 26 s to 14 min 25 s** | **1 min 06 s** (press the shortcut, glance at green, check amber) | 
| Wall clock, first time | same as above | 4 min 28 s, and you're free while the sloth works | 
| Bank form alone | 2 min 35 s to 3 min 20 s | 14 s of your time | 

That's roughly **a 10× cut in hands-on time**, and the model is generous to the human: it assumes no typos, no switching tabs to copy an address, and no hunting for your PAN card for longer than 15 seconds. If you apply for jobs, a portal asks most of these questions again on every application: 20 job forms like the demo one take **about an hour of typing by hand, and about 6 minutes of glancing with Ditto**.

The calculation is a script in the repo ([`eval/payoff.mjs`](https://github.com/dragon-slayer27/ditto/blob/main/eval/payoff.mjs)), with every assumption written down next to the per-form table.

*Solo project. Thanks for reading, and may you never type your information into a form again.* 🦥
