This post was last updated on Sep 17, 2026.

AI Usage Disclaimer

I promised in the last devlog that I'd write posts more often. Let me share what I've been up to.

Running LLMs on Android

I have made this cards app that I've been using it almost everyday for the past year or so. It's like cards feature of Bitwarden, extracted into a standalone app. It works completely offline (no INTERNET permission in the manifest), and I want to keep it that way.

I've been thinking about how I could add credit card bills and expenses tracking to this app without using internet. The obvious way is to let the user enter the amounts. That's trivial but feels like a very cumbersome UX for the users.

The other way is to let the user select the PDF statements from their device, and the app extracts and parses the text. This would work if there was a set standard in place that all PDF files followed, but sadly there isn't.

Each bank has their own fancy-looking PDFs, and it's impossible for me to have access to all of them. Also, it would require updating the parser logic every time some bank updates their bill's PDF layout. So a deterministic parser doesn't seem to be the solution.

I was curious if we could do better by running smaller LLM models on the device. I built a quick prototype in Flutter to play around with different <=1GB models from Hugging Face.

I tried different ways to process the PDFs - ranging from feeding the LLM text blocks with positions encoded for each text block, rendering the PDF to PNG and using vision-enabled small LLM models, OCR results with position-encoded text and so on. So far, I've not been able to achieve any reliable results. Maybe my ideas are wrong, or small on-device LLM models are just not there yet.

Vylk

theo-image
theo-image

I feel like I'm on the far left here, I've been this way even before the AI happened, but does it even matter atp? We're in a world where everyone just makes what they want. This feels like the era of personalized software, and open source is slowly dying. There's no good answer to "If I can vibe code an app the way I want it, why should I use yours?".

Indeed, I spent an hour searching for a self-hosted markdown editor that's

  1. Low on resources
  2. Functional enough
  3. Can auto-save
  4. Work offline when the server is down, and sync when the server comes back up
  5. Can work reliably from all of my devices (Android, MacOS and Linux)
  6. Has decent UI and is mobile friendly

but I couldn't find one. Not even a single person vibecoded this? NOO :(

Every solution there tried to do too much, or the solutions that remotely match my needs, the UI feels like it's from the 2010s.

So, I did what I was trying to avoid all along, write a 1 viewer vibe coded slop website, yet again.

I wrote a prompt in about 5 minutes, gave permissions and went AFK. Came back in an hour, and it had a working prototype ready. Polished the UI a little bit for another hour and now it's deployed. It just works on my 1vCPU VPS. I'm using it right now, to write this draft.

Update: I've polished it a bit more, you can check the source code, even though it feels pointless.

Btw, only used deepseek-v4-flash from opencode free plan for the MVP, and then terra-5.6-high from OpenAI for the sync functionality. while wr're at it let me glaze real quick - deepseek-flash-v4 model is so underrated for how fast and useful it is.

Here's how it looks:

vylk-screenshot
vylk-screenshot

Commiting secrets to git repo

Ever since I've come across gpg, I've been finding so many usecases for it - be it signing / encrypting the messages, or password protecting any files before sharing.

I recently made an rsync wrapper script to sync specific files, the way I wanted. The specific dirs, I didn't want them to be in my codebase, so I added the encrypted syncm_vars.py to version control.

Now, the script that installs syncm, asks for gpg passphrase at install time, and upon successful entry, replaces the content between # BEGIN SYNCM_VARS and # END SYNCM_VARS. I know it's probably better to leave secrets out of the repo, but for small one-off things like these - things that are not-so-secret but are still secrets, this is fantastic.

Alright, that's about it.

  1. Watching Go's new garbage collector move through the heap

  2. When random.bytes() runs but doesn't work

By the way, if you're interested, you can check out all devlogs here.

Edits

  1. (2026-09-17) - Renamed MDNotes to Vylk.