Activity

NellowTCS

Shipped this project!

Hours: 2.27
Cookies: 🍪 65
Multiplier: 23.66 cookies/hr

i made a website that is trying its absolute hardest to make you leave.

it randomly inverts your screen. it eats 7% of your keypresses silently. it changes your fonts to Comic Sans mid-sentence. it zooms in and out for no reason. it spawns popups that say “This is not a virus. Probably.” it fake-loads things. it makes elements randomly draggable and then leaves them wherever you dropped them. it has a cookie banner with multiple lives. it misses you when you switch tabs.

there’s also a secret input box in the corner with easter eggs. i’m not telling you what they are. that’s the point.

this started as “haha funny idea” and turned into 600+ lines of pure documented chaos with modules and eslint and github pages betrayal and one enlightenment moment involving window.onload that i will never fully recover from.

it has 5 devlogs. it has a TODO list with “make the website more yandere haha (yes seriously)” still on it. i am normal about this project. everything is fine.

p.s. this would’ve been a perfect Borked UI Jam submission and i am a little upset i didn’t finish it in time. a moment of silence for that missed opportunity. 🕯️

try it if you dare

NellowTCS

SO THE TODO LIST IS DEAD
IT’S GONE
I KILLED IT
OMG OMG OMGOMGOMG IM DONE AHAHAHAAAA

okay so i had like. a handful of TODOs left. and i sat down and just. destroyed them. one by one. with extreme prejudice.

random page zoom changes
the page just. scales. randomly. with a little bouncy spring animation. then snaps back like nothing happened. you didn’t imagine it. it did that. 0.8x. 1.2x. gone. you’re normal now. or are you.

this was literally the last TODO on the main list and when i deleted it from TODO.md i felt something. i don’t know what. peace? emptiness? both? i think i ascended.

keyboard sabotage (finally)
7% of your keypresses just don’t work. silently. no feedback. no error. your input simply Does Not Happen. Tab and F5 are safe because i’m not a monster. everything else? good luck typing.

fake “are you sure you want to leave?” dialogs
50/50 chance every time you try to close or navigate away. are you sure? the browser asks. you are not sure. you will never be sure again.

popup windows that multiply
every few seconds there’s a chance that 2-4 little windows appear scattered across the screen. they say things like “This is not a virus. Probably.” and “You can never close all of us!” one of them says “Pop! Pop! Pop!” for no reason. i love them. they are my children.

the easter egg input box
okay this one i’m actually proud of. there’s a little text box in the corner now. you can type things. typing annoy makes the whole page shake for 2 seconds (MAXIMUM ANNOYANCE MODE). typing oops tells you that you made a mistake and tries to close your tab. typing github makes it scream “NOOO MY SOURCE CODEEEEE” and redirect you there. typing easteregg tells you it’s not going to be that easy. typing anything else gets a polite “no easter egg for: [your thing]”.

i have zero regrets.

THE TODO LIST IS EMPTY
EMPTY!!!!
THERE IS NOTHING LEFT ON IT!!!
I DID EVERYTHING!!!
EVERY. SINGLE. ITEM.
DONE. IMPLEMENTED. WEAPONIZED. SHIPPED.

well. okay. there’s still “make the website more yandere haha (yes seriously)” in the later section and i am. thinking about it. i am always thinking about it.

but the main list?? gone. dead. deleted. i win.

the website now inverts your screen. eats your keypresses. zooms randomly. spawns popups. won’t let you leave. changes your fonts to Comic Sans. makes elements draggable for no reason. fake-loads things. confettis your clicks. misses you when you switch tabs.

this was supposed to be a funny little project.

it has 600+ lines of pure chaos.

i am so normal about this. i am completely fine. everything is fine. AHHAHAHAHAAA

Changelog

Attachment
Attachment
Attachment
0
NellowTCS

SO LIKE ALMOST DONE
BUT LIKE PAIN
BUT NOW THE DEMO WORKS

spaghetti → lasagna

i took my 700‑line chaos soup and turned it into:

  • fakeProgress.js
  • formAnnoyances.js
  • inputAnnoyances.js
  • mouseTrail.js
  • marqueeFooter.js
  • navigation.js
  • sounds.js

basically i split the hydra into seven smaller hydras
because that’s better somehow

eslint + prettier enter the chat

i installed linting and formatting like a responsible adult

prettier: “i fixed your code :)”
me: “PUT IT BACK.”

but now everything is clean and pretty
which is hilarious because the website itself is a war crime

github pages said “no ❤️”

i deployed
i refreshed
i stared

half the site worked
half the site died
none of it made sense

my console was like:

“DOMContentLoaded? never heard of her.”

i tried:

  • changing base paths
  • adding defer
  • removing defer
  • inline scripts
  • external scripts
  • crying
  • begging
  • threatening
  • ritual sacrifice

github pages:
“lol no”

the forbidden knowledge

i replaced:

window.addEventListener("DOMContentLoaded", ...)

with:

window.onload = () => { ... }

AND IT JUST
WORKED

i hate it
i hate that it works
i hate that THIS is the fix
i hate that i spent an hour debugging a browser event from 1999

but also
i am free now
i have ascended
i have seen the meta

my suffering

i made commits like:

  • PLEASE GITHUB PAGES I NEED THIS
  • i think i found the meta
  • modularize
  • modularize 2
  • eslint and prettier

my commit history looks like a mental breakdown in slow motion

phase 6: victory royale

after:

  • 2141 additions
  • 111 deletions
  • 3 refactors
  • 1 browser betrayal
  • 1 enlightenment moment

the site works
the modules work
the chaos works
the sound works
the trail works
the fake progress works
the notifications work
the everything works
i am unhinged
i am annoyed
i am be‑annoyed

Changelog

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

I completed a few more TODOs

  • Fake progress bars that never complete
  • trailing mouse cursor
Attachment
Attachment
Attachment
0
NellowTCS

I think this was too easy oh noes

Because I:

Finished the entire TODO list like a menace

Every single item?
Done.
Implemented.
Live.
Weaponized.

Typing fields now randomly insert “oops” like a passive‑aggressive gremlin.
Checkboxes betray their siblings.
Dropdowns gaslight you.
Links run away.
The cursor randomly becomes “wait” like it’s buffering its own existence.
Scroll wheel enters combat mode.
The Tab key chooses violence.

Added EVEN MORE TODOS because my brain refuses to stop

I finished the list and immediately went:
“haha what if I made it worse.”

So now the TODO list includes:

  • fake progress bars
  • popup windows that multiply
  • mouse trails
  • random font chaos
  • fake system notifications
  • random draggable elements
  • keyboard sabotage
  • fake exit dialogs
  • auto‑scrolling marquee that covers content
  • random zoom changes
  • make the website more yandere haha (this is funny, trust the process)

The website now behaves like it has abandonment issues 😔

Switch tabs?
It goes:

  • “Hey come back!”
  • “I miss you!”
  • “Where you going?”
  • “Don’t leave me!”

This is the first website in history to develop a parasocial relationship with the user. Okay maybe not the first, but the most funny subjectively >:3

Cookie banner resurrection mechanic

You click “Accept”?
It disappears.
Then comes back.
Then disappears.
Then comes back.
Then disappears.
Then comes back.
Then disappears forever.

It has lives.

Scroll jacking but with a cooldown counter

You scroll?
The page scrolls back.
But only three times at once because even chaos needs rate limiting.

Fake notifications

“Download complete!”
“Battery low!”
“Connection lost!”
All lies.
All vibes.

Confetti explosions on click

Every click is now a birthday party you did not consent to.
It’s a ✨ surprise ✨

Changelog

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

I DID A THING!!!
I DID… SOMETHING!!!
I HAVE MODULARIZED (read: copy‑pasted my entire Web‑Template).

The repo now has folders.
And files.
And a build system that I absolutely did not write today.
And a TODO list that is 500% more ambitious than anything I actually did.

So uh.
Anyway.

Modularized (in the loosest possible definition of the word)

I took my entire Web‑Template starter kit
and just
slammed it into this repo
then filled it in

Boom.
Instant structure.
Instant legitimacy.
Instant “wow this looks like a real project.”

Added a TODO list that Future Me will cry about

I wrote 29 items of pure chaos:

  • random resizing
  • fake loading spinners
  • cursor betrayal
  • fake 404s
  • confetti
  • console spam
  • cookie banners that respawn
  • background “music” that is just nail tapping

Deleted the old index.html

Yeeted it.
Gone.
Replaced with the Build/

Changelog

Attachment
1

Comments

NellowTCS
NellowTCS about 1 month ago

Even the banner is purposely less tall to be annoying :3

NellowTCS

Shipped this project!

Hours: 2.11
Cookies: 🍪 66
Multiplier: 26.13 cookies/hr

I built Tamaru, a physics-driven virtual trackball widget for the web, and this ship is v0.1.1 with the Chrome extension!

Download the Chrome extension on the release page here!

The extension lets Tamaru haunt any website you visit, with a full interesant settings popup that themes itself to match your orb. It autosaves everything, has a floating orb header, themed section icons, sliders with live value displays, the works.

This ship also includes a handful of fixes and additions to the core library: audio now properly resumes on first interaction (browsers are rude about AudioContext and suspend it until a user gesture, so I added resumeIfNeeded() on every pointer event), a new startMinimized config option so the orb can boot as a tiny lil widget instead of slamming its full UI into your face, and the extension’s dependency situation got untangled so it actually builds from source properly.

The hardest part was honestly wiring the extension together cleanly. Getting the Vite alias pointing at the right build output, Chrome storage syncing correctly, the popup theming matching the orb, and audio not being silent on first load all had their own little moments. But it works now and it’s so cool seeing Tamaru just… exist on every page.

NellowTCS

I PUBLISHED V0.1.1!!!
IT’S ON NPM.
IT’S REAL.
THE EXTENSION EXISTS NOW.
As a .crx file 😔 but still

So uh.
I was supposed to just clean things up a bit.

Anyway.

THE EXTENSION IS NOW A REAL PROJECT.
And also the orb can make sounds again.
And also it can start already minimized.
And also dependencies are no longer lying about what version of Tamaru they use.
The math checks out. totally.

What I Did

Fixed audio!!
Browsers are incredibly rude about AudioContext. They suspend it until a user gesture happens, which means if you load the extension and haven’t touched anything, the orb just silently judges you.
Fixed by adding resumeIfNeeded() that gets called on every pointerdown, on the trackball, on the widget handle, AND on the stick mode button.
The orb now makes sounds again. as it should. it was sad without them.

Added startMinimized!!!
New config option: startMinimized: boolean (defaults to false)
If you set it to true, Tamaru boots up as a tiny lil orb instead of the full widget.
Super useful for the extension so it doesn’t slam its whole UI into your face the second you open a tab.
Added it to the demo config panel, the docs, and the types. it exists everywhere now.
The extension’s popup even has a “Start Minimized” toggle defaulting to checked, because yes, unobtrusive is the vibe.

Fixed the extension’s dependency situation (twice lol)
First I removed the broken "tamaru": "0.1.0" from dependencies because that was the old version and it was Lying
Then I wired up a Vite alias so the extension points directly at ../Build/dist/main.mjs
So it builds from source, properly, like a grown-up project
Then I bumped the npm package to v0.1.1 and updated the lock file to match
Also added @types/node, prettier, typecheck and make-pretty scripts to the extension because Past Me left it looking a little eeeh in there

Bugs Fixed

  • audio not playing on first interaction (fixed via resumeIfNeeded on every pointer event)
  • extension depending on the wrong version of Tamaru (fixed, twice, don’t ask)
  • orb spawning full-size on every page and startling people (fixable now via startMinimized)

Changelog

Attachment
0
NellowTCS

I was busy…

Anyway.
THE POPUP HAS BEEN REVAMPED.
THE ORB CONTROL PANEL IS NOW A WHOLE APP.


What I Did

  • Added icons!!!
    (this was supposed to be the only thing I did…)
  • And then I revamped the entire UI
  • Rewrote the popup from scratch
    it now looks like a tiny cyberpunk settings dashboard
  • Added theme classes so the popup changes theme with the orb
    (yes the popup is now fashionable)
  • Added autosave because clicking “Save” is for mortals
    sliders? autosave
    checkboxes? autosave
    dropdowns? autosave
    breathing near the UI? autosave
  • Added a background script for absolutely no reason (/j it’s for the future)
    it just logs “installed”
  • Cleaned up the content script
    now it actually listens for config changes like a polite little gremlin
  • Deleted like 500 lines
    Added like 600 more
    The math checks out. totally

Popup UI Now Has

  • floating orb header
  • themed section icons
  • cards
  • sliders with live value displays
  • custom selects
  • toggles
  • scrollable container
  • footer button

ah yes consistency

The extension stole (with permission) Tamaru’s themes.

theme-aqua
theme-red
theme-glossy
theme-metal
theme-neon
theme-sunset

The popup now cosplays as your orb :D


Autosave Because I’m Extra

  • sliders -> autosave
  • selects -> autosave
  • checkboxes -> autosave
  • text inputs /j -> autosave

Replaced with ✨ better ✨ things.

better build

  • added base: "./"
  • added @types/chrome
  • reorganized everything into src/ and styles/
  • extension now looks like a Real Project™ now
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

Very indecisive, am I not?

I had started trying to figure out Dioxus (it’s actually pretty cool, and like Sakko so that’s a bonus)

But I decided after making that… yeah no I really should plan this

You get images of Dioxus components that I plan to use lollll as I didn’t really do much in terms of imageworthy :P

Changelog

Attachment
Attachment
Attachment
0
NellowTCS

SO I STARTED AN EXTENSION THINGY
VERY BROKEN
BUT STILL

Anyway.
THE ORB HAS ESCAPED THE DEMO.
IT CAN HAUNT ANY WEBSITE NOW.

What I Did

  • Made a whole Chrome extension folder because
  • Added a content script that just… summons Tamaru on every page.
  • Wrote a popup UI that is WAY too powerful for a first version yet even more broken
    Like. It has sliders. Dropdowns. Checkboxes. A whole form.
  • Added a settings panel with:
    • size
    • theme
    • sound
    • rollSoundLevel
    • haptics
    • scroll mode
    • fallback mode
    • friction
    • sensitivity
    • snap distance
    • stick mode
    • stick mode cycle key
    • stick mode cycle snap

uhh will make it better in upcoming devlogs

Changelog

Attachment
Attachment
Attachment
Attachment
0
NellowTCS

Shipped this project!

Hours: 8.32
Cookies: 🍪 276
Multiplier: 27.67 cookies/hr

Ship message if you can’t read it:

Updated Project: I originally made this in a Python class the year before last, near the end of the class (Spring 2024).
It’s a bit silly and very questionably coded haha but it really works well and is kinda adorable :D.
Text-based computer (yeah it’s technically not a OS nor a computer but more a kernel but with text based apps) oops.


I built… an entire text‑based operating system.
Like, I sat down thinking “haha I’ll remake my old Python class project :)”
and then suddenly I had a kernel, a scheduler, a VFS, a package manager, a theme engine, a settings app, a login system, a shell, a whole UI framework, and… uh… a full TUI IDE running inside my fake OS.

The hardest part?
Pyodide.
Hours upon hours of “why is my Python file being imported as HTML,”
“why is sys.path like this,”
and “why is my kernel suddenly a <DOCTYPE html>.”
But after enough suffering, I built a bundler, a filesystem injector, a terminal bridge, and a whole web demo environment.
And then it worked.
THE WEB DEMO LIVES.

I’m really proud of how far this went: it started as a silly little remake and somehow turned into a fully themed, app‑driven, browser‑compatible OS simulation with an IDE.
I love it :3

NellowTCS

so um
i sat down thinking:

“hehe let me just add a little IDE :)”

and then
and THEN

I made… this

i think the terminal possessed me


THE IDE (aka: VSCode but it runs in a potato on a potato as a potato)

i have now built:

  • a toolbar
  • clickable buttons (??? in a terminal ??? me from a day ago didn’t even know you could do this)
  • tabs
  • syntax highlighting
  • a file tree with icons
  • a status bar
  • a help overlay
  • dialogs
  • a run panel
  • scroll logic
  • cursor logic
  • auto‑indent
  • triple‑quote string detection
  • a whole rendering engine
  • a whole input engine
  • a whole everything engine

THE FILE TREE

i made a file tree
a REAL file tree
with:

  • expand/collapse
  • icons
  • scrolling
  • depth indentation
  • selection highlighting
  • directory walking
  • ignoring __pycache__ like a civilized person

it is so cute
it is so powerful
it is so…
WHY DID I DO THIS

the editor

the editor now has:

  • line numbers
  • cursor line highlighting
  • syntax highlighting
  • auto‑indent
  • home/end logic
  • page up/down
  • horizontal scrolling
  • vertical scrolling
  • tab insertion
  • newline indentation
  • triple‑quote string parsing
  • number detection
  • keyword coloring
  • comment detection
  • a whole buffer model
  • a whole scroll model

uh yeah I don’t know why I decided to lock in so much for the IDE specifically but oh well might as well keep it /j

running your code (random guy: Blasphemy! I write my code WITHOUT running it like in the old days)

i said:

“hehe maybe i’ll add a run button :)”

and then i built:

  • a subprocess runner
  • a web‑safe exec sandbox
  • stdout/stderr capture
  • scrollback
  • color‑coded output
  • headers
  • separators
  • timeouts
  • error handling
  • environment isolation

this is not a run button
this is a terminal within a terminal within a terminal pretty much

I Am… Parity

pyodide said:

“you can’t run Python files safely”

and i said:

“watch me”

i built:

  • a stdout redirector
  • a stderr redirector
  • a fake cwd
  • a fake sys.path
  • a safe exec environment
  • a fallback for imports
  • a cleanup routine

i basically made a tiny python interpreter
inside a python interpreter
inside a browser
inside a terminal
inside my OS
inside my repo

ah my silly astounds even me sometimes

buttons

i made clickable toolbar buttons
in a TUI
in 2026
in the year of our lord ANSI /j

WHY

the question’s actually
WHY NOT

Input

i have now:

  • ctrl‑shortcuts
  • F1
  • tab focus cycling
  • alt‑tab
  • ctrl‑tab
  • shift‑tab
  • escape sequences
  • raw mode
  • web mode
  • native mode
  • keymaps
  • dialogs
  • help overlays

I think i’m ready to ship
(voices in the audience: YA THINK?!??!?!?!?!?!?)


Oh also, I added the web demo publishing thingy (sigh *GitHub Workflow to deploy the Demo)
here:
https://nellowtcs.me/pyComputer/

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

settings app

I said:

“haha settings app coming soon :)”

and then I blinked
and suddenly I had built:

  • a full configuration system
  • persistent JSON settings
  • a UI for editing literally everything
  • theme selection
  • username editing
  • password hashing
  • splash toggles
  • confirmation toggles
  • editor width
  • reset to defaults
  • table rendering
  • live theme switching
  • VFS integration
  • manifest updates
  • permissions
  • a whole UX flow

I implemented a full OS configuration layer because apparently that’s who I am now.

theme engine

I added:

  • retro green phosphor (the hacker aesthetic)
  • light mode (for the brave)
  • dark mode (for the sane)
  • mono (for the minimalists)
  • pastel (for the Tumblr‑coded)
  • default (for the normies)
  • custom palettes via tuiro (for the gods)

And then I made it all work:

  • in native Python
  • in the browser
  • through xterm.js
  • through CSS variables
  • through JS injection
  • through the renderer
  • through appstdlib
  • through kernel boot settings

auth cause why not

I added:

  • password hashing
  • username support
  • login prompts
  • kernel‑level auth gating
  • error messages
  • welcome messages

manifest validation

I implemented:

  • manifest validation
  • required fields
  • type checking
  • permission checking
  • entry file validation
  • error messages
  • install flow integration

Apps now have:

"name": "snake",
"version": "0.1.0",
"entry": "main.py",
"permissions": ["fs"],
"description": "A seemingly simple Snake game."

rm, cd, pyfetch, and the Shell Renaissance

I added:

  • rm with recursive mode
  • confirmation logic tied to settings
  • directory removal
  • error handling
  • cd cleanup
  • pyfetch as a built-in (neofetch clone)
  • help command updates
  • shell registry updates

The shell is now:
better

witchcraft

The browser now:

  • reads settings
  • applies themes
  • syncs with Python
  • updates xterm.js
  • updates CSS
  • updates buttons
  • doesn’t override user choice
  • exposes setWebTheme globally

what’s this? Your Kernel is evolving!

The kernel now:

  • loads settings
  • applies themes
  • handles authentication
  • respects splash toggles
  • boots with logs
  • launches shell as a process
  • integrates with scheduler
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

so
i thought
“haha what if i just make a little web demo :)”
‘how bad can it be?’ famous last words

thus began
THE TWO+ HOUR DESCENT INTO MADNESS

the suffering

i have now experienced
firsthand
why people (maybe?? allegedly??)
hate pyodide

because pyodide debugging is like:

  • “why is this HTML file being imported as Python”
  • “why is sys.path like that”
  • “why is the browser pretending to be a filesystem”
  • “why is my kernel suddenly a <DOCTYPE html>
  • “why is everything a coroutine”
  • “why is nothing a coroutine”
  • “why does every error message point to wasm”
  • “why does every fix cause three new problems”
  • “why is the empty string in sys.path allowed to exist”
  • “why is the dev server serving my Python files as HTML”
  • “why is my OS bootloader being eaten by Vite”

i have seen things
i have SEEN THINGS
no sane developer should see

the breakthrough

and then
after hours of pain
after fighting the browser
after fighting pyodide
after fighting my own code
after fighting the concept of “paths” as a philosophical construct (my least favorite thing)

and then
in a burst of clarity
i built:

  • a bundler
  • a file walker
  • a JS -> Python FS injector
  • a virtual filesystem rebuilder
  • a path sanitizer
  • a web‑aware VFS
  • a terminal IO bridge
  • a bootloader rewrite
  • a shell wrapper
  • a whole demo environment

and suddenly
it worked
IT WORKED
THE WEB DEMO LIVES
THE OS BOOTS
THE SHELL RUNS
THE FILESYSTEM EXISTS

i have achieved
pyodide enlightenment

the result

after 2 hours of pure, unfiltered, uncut suffering
i now have:

  • a working web demo
  • a functioning OS in the browser!!!!!!!!!!!!!!
  • a bundled filesystem
  • a booting kernel
  • a running shell
  • and a newfound respect for anyone who has ever touched pyodide

also fun 3d logo :3

Changelog

edit: remove typo

Attachment
Attachment
Attachment
1

Comments

elioanon20
elioanon20 about 1 month ago

WOW

NellowTCS

so um
i sat down today thinking:

“i’ll just fix that one snake input bug :)”

and now we have:

  • OPFS
  • a theme engine
  • a widget system
  • async utilities
  • logging
  • text formatting tools
  • a matrix app
  • a fixed snake engine
  • a rewritten loader
  • a reorganized filesystem
  • and like 700 new lines of code

i think I ADHD’d (/j but maybe I have it who knows) too hard

OPFS (because why not add persistent storage at 4pm)

i added OPFS
like
actual persistent storage
with:

  • write
  • read
  • delete
  • mkdir
  • listdir
  • JSON helpers
  • a fallback for browsers
  • a whole .opfs directory

when a TUI framework has stuff most frameworks don’t smh

i added:

  • themes
  • colors
  • styles
  • presets
  • retro mode
  • widgets
  • buttons
  • listboxes
  • menus
  • dialogs
  • progress bars
  • input fields
  • text utilities
  • ANSI helpers
  • formatting tools
  • padding
  • wrapping
  • centering
  • stripping ANSI
  • highlighting

this is not a TUI

input system: Now handling chaos 200x faster!

i rewrote input.py
and now it has:

  • raw mode
  • keybindings
  • keymaps
  • history navigation
  • arrow keys
  • escape sequences
  • line editing
  • cursor movement
  • Windows fallback
  • a cleanup function
  • a whole vibe

snake no longer eats your shell input
snake is now polite
snake is now civilized
snake is now house‑trained

snake (the overengineered sequel)

i fixed the input bug
and then accidentally:

  • integrated the new input system
  • added centered overlays
  • added pad_center
  • added UI_Key support
  • cleaned up rendering
  • reorganized logic
  • made it smoother
  • made it faster
  • made it prettier

snake is now running on a custom game engine
inside a custom UI framework
inside a custom OS
inside python
inside your terminal

matrix app (because of course)

Red pill or blue pill?

Format

i formatted using Black Formatter (VSCode extension)

like a raccoon in a server room

the loader now:

  • injects src paths
  • resolves manifests
  • imports entrypoints
  • doesn’t explode (most of the time)

the kernel now:

  • launches shell as a process
  • handles shutdown
  • is slightly less confused

the VFS now:

  • resolves paths correctly
  • handles root
  • handles absolute paths
  • handles everything i throw at it

the scheduler now:

  • exists
  • chills

so

i think i might need to be stopped
or encouraged
i can’t tell which

pyComputer is now:

  • 40% OS
  • 40% TUI framework
  • 10% game engine
  • 10% “hehehehheheheheheheheheh”

and that’s probably better than most OS’s (ahem windows ahem) nowadays

0
NellowTCS

I implemented some more stuff

aforementioned stuff

  • a root filesystem
  • a usr/apps directory
  • a sys registry
  • app data folders
  • persistent history
  • a working cd command
  • a shell prompt that shows the cwd
  • apps that store files in their own directories
  • a VFS that actually respects the root
  • a registry that creates its own directory
  • and a notes app that is basically Notepad.exe but better and no AI (selling point :D)

why not FS

remember when the filesystem was “just stubs”
well
now there is:

root/
  apps/
    calculator/data/history.txt
    notes/data/*.txt
  sys/
    apps.json

like
a real directory tree
a real root
a real sys folder
a real apps folder
a real everything

the VFS now:

  • resolves paths
  • defaults to the root folder
  • creates directories
  • writes files
  • deletes files
  • moves files
  • checks existence
  • lists directories

cd

i added cd
and suddenly the shell has:

  • a working cwd
  • a dynamic prompt
  • directory validation
  • root fallback
  • actual navigation

the shell went from:

$ 

to:

[/] $ cd apps
[/apps] $ cd notes/data
[/apps/notes/data] $

it’s walking around (yes that was a filesystem pun >:3)

calculator

the calculator now:

  • stores history
  • loads history
  • writes history
  • has a data directory
  • uses math functions
  • has a menu
  • has options
  • has banners
  • has error handling
  • has safe eval

notes

  • stores each note as a separate file
  • sanitizes filenames
  • lists notes
  • views notes
  • edits notes
  • deletes notes
  • renames notes
  • shows tables
  • uses the renderer
  • uses the VFS
  • uses directories

registry

the registry:

  • has a real path
  • creates its own directory
  • loads apps
  • saves apps
  • persists apps
  • uses VFS
  • uses JSON
  • lives in /sys/apps.json

the loader

the loader now:

  • uses VFS
  • resolves paths
  • loads manifests from /usr/apps
  • imports entrypoints
  • handles sys.path
  • doesn’t explode

it’s basically a package manager’s cousin now

renderer upgrades

the renderer now has:

  • cursor movement
  • clear screen
  • clear line
  • hide/show cursor
  • bold/dim
  • colors (green, red, yellow, cyan, etc)
  • box drawing
  • box_at positioning
  • ANSI helpers
  • still uses tuiro
  • still adorable

this is no longer a renderer
this is a terminal UI toolkit hehehe

snake (i swear this was supposed to be simple)

so uhhh
i added snake

and by “added snake” i mean:

i wrote a full terminal game engine
with:

  • raw terminal mode
  • termios flag manipulation
  • a dedicated input thread
  • non‑blocking key polling
  • a tick‑based game loop
  • dynamic difficulty scaling
  • partial redraw optimization
  • overlay UI
  • pause menu
  • restart logic
  • centered modal boxes
  • ANSI cursor control
  • double‑buffer‑ish rendering
  • safe style fallbacks
  • custom coordinate transforms
  • a whole grid simulation
  • and a renderer that now behaves like a tiny ncurses clone

all of this
for a snake game
that originally printed:

snake game coming soon!

i did not “add snake”
i summoned a computational serpent ecosystem

the snake now has:

  • a head
  • a body
  • food
  • collision detection
  • speed levels
  • score
  • game over screen
  • pause screen
  • restart flow
  • a start prompt
  • a whole personality

and the funniest part?

the TODO list still says:

[ ] Snake game

because technically
i haven’t checked the box yet
since i’m still pretending this is “just the entrypoint”

this is the most overengineered “coming soon” screen in the history of computing
and i regret nothing

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

so today i sat down thinking i’d chill and work on pyComputer a bit, nothing serious

and then suddenly pyComputer is over here like:

“hey so i have a renderer now
and a scheduler
and a process model
and a package manager
and networking
and a VFS that actually does things
and also i can clear the screen
and autocomplete commands
and run apps with banners
and show spinners
and print tables
and-”

again with the too much in one devlog
but i’m only doing like tiny changes in a lot of places, it’s not my fault i swear-

the renderer

i added tuiro
because it’s great

and now apps can have:

  • banners
  • sections
  • info boxes
  • success messages
  • error messages
  • spinners
  • steps
  • tables

realshell

the shell now has:

  • tab completion
  • readline integration
  • a clear command
  • better history
  • better behavior
  • better everything

it used to be a little guy
now it’s a little guy with features

the VFS? How about FS

remember how i said the filesystem was “just stubs”
haha
yeah
about that

i accidentally implemented:

  • path normalization
  • directory creation
  • file reading
  • file writing
  • listdir
  • exists
  • mkdir
  • remove
  • move

like
a whole VFS
a real one
not a pretend one

get scheduled

i also gave pyComputer:

  • a scheduler
  • a process model
  • coroutine‑based execution
  • state transitions
  • ready/waiting/terminated logic
  • async event loop integration

this is the part where i realized:

“oh god
i’m actually building an OS
not a toy
not a joke
a real OS
with processes
and scheduling
and i did this in like 5 minutes
why am i like this”

the package manager

i added:

  • install
  • remove
  • list
  • registry integration
  • VFS integration
  • directory copying
  • app discovery

this was supposed to be a silly extra
it’s not

the networking layer

i added:

  • GET
  • POST
  • JSON helpers
  • error handling
  • fallback behavior

i don’t even know why
i don’t have a use case yet
i just saw the file and went
“yeah sure why not”

Attachment
Attachment
Attachment
0
NellowTCS

so, me like 30 ish minutes ago:

“wow.
this thing is…
impressively unfinished.”

which is funny because
i already knew that
i made the stubs
i put the placeholders there
i wrote the comments that literally say “stub” in them

and yet I was shocked

Welp

so what did i do?
i did the normal, rational thing:

i implemented an entire OS worth of missing pieces
in twenty minutes
powered entirely by confusion and spite

apps?
added
manifests?
added
entrypoints?
added
dynamic loading?
added
shell commands?
added
filesystem skeleton?
added
settings app?

added for no reason except why not

the whole thing was basically:

  • “oh this is missing”
  • implements stub instantly
  • “oh that’s missing too”
  • implements stub instantly
  • “oh god there’s more”
  • implements everything instantly

all the stubs

apps

i had built an entire OS around the idea of apps
and then realized i had…
zero apps

so now pyComputer has:

  • calculator (actually works)
  • notes (actually writes files)
  • snake (spiritually important)
  • settings (kinda needed)

they all have manifests
they all have entrypoints
they all pretend to be real software
and somehow they run well

the loader: from decorative to functional

the loader used to be a prop
like a fake steering wheel in a kid’s ride

now it:

  • finds apps
  • reads manifests
  • imports modules
  • returns main
  • executes it

and it does all of this without bursting into flames

##the shell shells shells

the shell used to be a little guy who said “hi” and then repeated whatever you typed like a parrot

now it:

  • parses commands
  • handles quotes
  • ignores empty lines
  • catches ctrl+c
  • catches ctrl+d
  • runs apps
  • lists apps
  • lists files
  • edits files
  • reads files

filesystem skeleton because I heard it might be needed

i also added:

  • OPFS stub
  • VFS stub
  • canonical paths

because apparently an OS needs a filesystem
wild concept
never heard of it

So

pyComputer is still 99% stubs
i know it
you know it

and yet somehow
in twenty minutes
it went from:

“i boot :)”

to:

“i boot, i parse, i run apps, i save notes, i evaluate math, i list files, i edit files, i read files, i have a settings app, i have a loader, i have a shell, i have a directory structure, i have a personality, i have dreams”

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
1

Comments

NellowTCS
NellowTCS about 1 month ago

btw if anyone was wondering, kernele was a typo on purpose

NellowTCS

okay so.
today was… a lot.

i basically walked into my repo, looked at the old python‑class files, and went
“hehe sorry guys but you’re going to the git shadow realm now”
and then i deleted EVERYTHING

anyway.

bye byee :D

i yeeted:

  • calculator
  • text editor
  • password thing
  • computer.py
  • that weird fromComputer.txt relic

all gone.
sent to git valhalla.
they served well.
they are retired now.
they can rest.

initial kernel hehe

i then proceeded to spawn an entire kernel neighborhood like:

src/kernel/
    boot.py
    io.py
    kernel.py
    loader.py
    process.py
    registry.py
    scheduler.py

each file is a tiny guy with a tiny job:

  • boot.py - dramatic theater kid who prints the logo
  • io.py - “i do input and output and i’m very stressed about it”
  • process.py - coroutine babysitter
  • scheduler.py - round‑robin gremlin doing laps
  • loader.py - app archaeologist
  • registry.py - librarian
  • kernel.py - kernel (aka the mayor)

THE FIRST ACTUAL BOOT OMG

the kernel now actually:

  • initializes subsystems
  • runs the boot sequence
  • prints fake hardware logs
  • launches the shell (or tries to 😔)
  • hands control to the scheduler
  • does not crash

actually event-ing

i replaced the fake event loop with:

await self.scheduler.run()

the scheduler is now actually… scheduling

Attachment
Attachment
0
NellowTCS

Shipped this project!

Hours: 13.63
Cookies: 🍪 462
Multiplier: 28.27 cookies/hr

okay so.

i built a whole image format.

like. a whole one. with a spec and everything. and then i forgot it existed for like a month and a half.

tBMP (Tiny Bitmap Format) started as “haha what if I made a tiny bitmap format because BMP is so… bad” and then spiraled into “oh god I’m writing a whole C library and a schema and a test suite and a CLI tool and a docs site and-”

classic me.

anyway here’s what it is now:

a C library (six encoding modes!! RAW, RLE, Zero-Range, Span, Sparse Pixel, Block-Sparse!!)
a Rust crate (on crates.io!!!!!!!!!!)
a WASM module + npm package
a web demo where you can drag in .tbmp files and see the palette and masks and metadata and zoom and pan and copy palette colors to your clipboard
a CLI with a whole little TUI because I accidentally built a terminal UI framework
a docs site
a logo (encoded as a tBMP. obviously.)

the format is just [HEADER][DATA][EXTRA][META].
predictable.
tiny.
no “here’s a chunk, good luck” energy. the whole thing is zero-allocation on parse which makes it work on embedded targets too which is so cool omg. (esp32 port when? Give me some time off, i’m eepy 😭)

I’m so proud of this one. I built a format :D (i mean, for the 4th or 5th time but still)

NellowTCS

I think I’m done
I published it
I PUBLISHED ITTTTTTTTTTTTTT
WAIT AAAAAAAAAAAAAAAAAAAAAA

freeze frame
yup. that’s me.
hi.
so, you’re probably wondering how I got in this situation.
uh.
so it all started when i did a little bit of work on tBMP.

anyway here’s the chaos:

C Library Cleanup

I started with “just cleaning up a few headers.”
and now:

  • the API is cleaner
  • the headers are reorganized
  • the WASM entry is less cursed
  • the tests are categorized
  • the test runner prints results like a real tool
  • the docs reference everything correctly
  • the metadata parser is sane
  • the encoder doesn’t scream anymore

Rust Bindings

So I added Rust bindings, of course.
Which meant:

  • writing a custom header
  • writing a build script
  • compiling the C core from Rust
  • generating bindings
  • fixing the cursed constant names
  • adding an example
  • adding CI
  • adding a release workflow
  • publishing to crates.io!!!!!!!!!!!!!!!

WASM + npm (aka “why is Emscripten like this”)

I rewrote the entire WASM build pipeline because the old one was held together with duct tape and hope.

Now it:

  • builds cleanly
  • exports a full API
  • generates TypeScript definitions
  • copies artifacts to both npm/ and Demo/
  • has a proper npm package
  • with a README
  • and a LICENSE
  • and a scoped name
  • and a CDN import example
  • and CI
  • and a release workflow

Docs

I updated the docs.
And by “updated” I mean:

  • rewrote the API index
  • added Decoder API
  • added Metadata API
  • added WASM API
  • added installation tabs
  • added a favicon
  • added a gallery
  • added screenshots
  • added examples
  • added guides
  • fixed links
  • fixed more links

en finalement

(oui, j’ai étudié et j’étudie Français)

tBMP is now:

  • a C library
  • a Rust crate
  • a WASM module
  • an npm package
  • a docs site
  • a demo
  • a CLI tool
  • a test suite
  • a whole ecosystem?

I built a format ;D

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

I did a bunch of code cleanup sigh

Stuff Done

  • WASM build improvements
  • formatting scripts
  • docs screenshots
  • demo gallery
  • metadata panel polish
  • API cleanup
  • header rewrites
  • MessagePack docs
  • TODO annihilation

Demo Gallery :D

I added a whole demo gallery to the docs and then I built a grid layout for it :D
It’s great :D

Miscellaneous Cleanup

I improved various things across the library, headers, variables, some WASM API parity improvements, etc!

Attachment
Attachment
0
NellowTCS

I did some more stuff hehe

Summary

  • implemented custom metadata
  • added nested metadata rendering
  • updated the CLI
  • updated the WASM
  • updated the demo
  • AND THEN
  • designed a tBMP logo
  • exported it as PNG, PPM, and tBMP (because of course)
  • and plastered it across the docs, demo, and README

CUSTOM METADATA (aka “why was this so fast??”)

I added full support for custom metadata!

  • MessagePack -> JSON conversion
  • arrays, maps, nested objects
  • nulls, bools, ints, strings
  • CLI printing
  • WASM export
  • web demo rendering
  • nested tables with captions like custom[0]
  • a new example file (custom_meta.tbmp)

This was supposed to be a “later” feature.
It became a “done in one sitting” feature.

THE LOGO ARC (aka “I accidentally became a graphic designer, not that I wasn’t already (unofficially, :P)”)

Then I made a tBMP logo.

A tiny pixel‑art logo.
A PNG.
A PPM.
A tBMP of the tBMP logo because self‑hosting is the only correct way.

And then I put it everywhere:

Web Demo

The header now proudly shows:

[tBMP Logo]   tBMP/demo

It looks so official yayayayayay

Docs

The homepage now has a big centered logo.
There’s even a whole Logo section with download links for the PNG and tBMP.

README

The README now has:

  • a flexbox header
  • the title
  • the tiny logo

i wish github rendered it right though 😔

Oops

At this point tBMP has:

  • a logo
  • a demo
  • built‑in examples
  • metadata
  • custom metadata
  • masks
  • palettes
  • WASM
  • CLI tools
  • docs
  • a README
  • more stuff

AND WE’RE ALMOST DONE :D

Attachment
Attachment
Attachment
Attachment
0
NellowTCS

So this was quick

I modularized the entire tBMP web demo which, in practice, meant:

  • copy‑pasting chunks of my own code into new files
  • Find‑and‑Replace‑ing function names like a gremlin
  • hoping nothing exploded
  • and somehow… it worked??? (i mean i do do this every time so I’d be more surprised if it didn’t work)

The demo now has:

  • canvas.js
  • dom.js
  • examples.js
  • render.js
  • state.js
  • wasm-helpers.js

Built‑in Examples!!!!!!

The demo now comes with a whole dropdown of example .tbmp files you can load instantly.

No more digging through folders.
No more dragging random files around.
Just click -> boom -> pixels.

Palette Click = Clipboard Copy 😶‍🌫️

I added a tiny feature that seems kinda obvious now:

clicking a palette color now copies its RGBA to your clipboard.

I don’t know why this feels so powerful but it does :O

Attachment
Attachment
0
NellowTCS

Today was The Day.
The day tBMP finally rendered a tBMP as a tBMP instead of me round‑tripping it through PNG like a coward. (I mean I did see it rendered in a JS Canvas, so can you really call it viewing a tBMP??? 😔)

I saw my own format… in its natural habitat… for the first time… heheheeeeeeee
Anyway.

Web Demo Time

I finally got the full web demo working:

  • WASM build
  • Vite project
  • Drag‑and‑drop loader
  • Pixel viewer
  • Zoom + pan + grid
  • Palette inspector
  • Masks inspector
  • Structured metadata panel
  • EXTRA chunk viewer
  • All the little UI bits that make it feel like a Real Tool™

The funniest part?

The palette reset bug.
One tiny array that refused to clear between loads.
One hour of debugging.
One hour of me whispering “why are you STILL here” at my screen.

Also:

I gave up on Cheerp.

Sharing state between JS and C++ felt like trying to pass a USB stick through a brick wall.
Emscripten may be cursed, but at least it’s predictably cursed.

And then…

format.sh.

Ah yes.
I ran the new format script i made.
And it…
uhh…
my lines changed.
We don’t talk about it.
We simply accept that clang‑format is a force of nature and move on.

But the funniest thing?

This was the first time I’ve ever seen a tBMP rendered as a tBMP.
Not converted to PNG.
Not dumped to PPM (gosh, that awful thing I made for converting a tbmp to ppm shudders).
Not viewed through a side tool.
Just… decoded directly by my own library, in my own viewer, in my own format.

It was tiny (well, the image was, but still).
It was pixelated.
It was perfect.

tBMP is officially a real format with a real viewer and not just a C library I yell at when C does C things and I question my life choices.

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

When you realize you forgot to implement 2/3rds of your library 😔

So today I had one of those moments where you stare at your own codebase and go:

“Wait…
WAIT…
DID I ACTUALLY IMPLEMENT ANY OF THIS??”

Turns out:
no.
No I did not.

The Great Encoding Reckoning

I looked at the encoder and realized I had:

  • RAW
  • RLE
  • …and then a bunch of nothing pretending to be other encodings

So I spent a bit implementing:

  • Zero‑Range
  • Span
  • Sparse Pixel
  • Block Sparse
  • All the size estimators
  • All the write paths
  • All the decode round‑trips
  • All the tests
  • All the fuzzing
  • All the examples

Basically:
I implemented the entire rest of the format in one sitting.

It didn’t even take that long though so I’m not sure why I left it unfinished 😭

Fuzzing (aka throwing your library/tool/anything to the winds to make it stronger)

I added a fuzz tester that:

  • downloads a random Picsum image
  • generates random palettes
  • encodes it in every mode
  • decodes it
  • compares pixels
  • fails me if anything is even slightly wrong

Examples Folder Explosion

I added a whole gallery of:

  • raw
  • rle
  • span
  • sparse
  • block‑sparse
  • zero‑range

All using the same Picsum images so I can visually sanity‑check the encodings.

It’s basically a museum of “what if I stored this image in the stupidest way possible.”

Block Sparse: The Beast

Block Sparse encoding is:

  • 8×8 tiles
  • detect empty tiles
  • store only the non‑empty ones
  • pack them raw
  • index them
  • pray

It works :D

Tests testing things to test

I added:

  • round‑trip tests
  • edge case tests
  • fuzz tests
  • palette tests
  • encoding tests

tBMP is now tested.

Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

tBMP has more wares now hehe

Auto‑Palette

tBMP can now generate palettes automatically for indexed formats.

Feed it a PNG -> get a palette.
No more hand‑crafting 16 colors like a medieval artisan.

Works with:

  • index1
  • index2
  • index4
  • index8

It’s actually good.
Like, suspiciously good, why is this so easy???

Floyd–Steinberg Dithering

Yes.
I added real dithering.

CLI flag:

--dither

It does the whole error‑diffusion thing.
Your pixel art looks crisp.
Your gradients look intentional.
Your tiny bitmaps look like they were processed by a real tool, which they were >:3

Best Encoding Picker (--pick-encoding)

I added a tiny heuristic that chooses between RAW and RLE based on estimated size.

It’s not fancy.
It’s not deep learning.
It’s literally:

“which one is smaller? ok do that.”

But it works shockingly well and makes the CLI feel smart.

Pixel Format Docs + Examples

I added a whole guide for pixel formats, with tables, examples, and actual PNGs.
Yes, I added PNGs to the repo.
Yes, they are adorable and like so coolll that tBMP -> PNG is so clean

Writer Improvements

The write path now supports:

  • auto‑palette
  • dithering
  • best‑encoding
  • indexed formats
  • more tests
  • cleaner API docs

It’s becoming a real ereader.
Wait. EREADER?!!
WDYM EREADER
*encoder, sigh

markdownlint

I linted the docs.
boringgg, I know, but I did it

CLI Updates

The CLI now supports:

  • --auto-palette
  • --dither
  • --pick-encoding

And the docs reflect all of it.

Docs Explosion (sadly not literally)

I updated:

  • CLI guide
  • encoding guide
  • pixel formats
  • design philosophy
  • versioning
  • metadata
  • examples

TODO Shrinkage

Checked off:

  • PNG exporter
  • badges
  • dithering
  • auto‑palette
  • best‑encoding
  • design philosophy
  • embedded systems guide
  • games guide
Attachment
Attachment
Attachment
0
NellowTCS

Okay so this one is small but also extremely funny because it started as
“hm something feels off”
and ended with
“oh right, PNM is cursed and my version check was also cursed.”

Version Validation Fix

I fixed the world’s most suspicious if statement:

if (head->version != TBMP_VERSION_1_0 &&
    head->version != TBMP_VERSION_1_0)

Yeah.
We don’t talk about it.

It now actually checks the version correctly.
We mostly do!

PNM Loader Improvements (again)

I went back into the PNM loader trenches because apparently I’m so good at reading docs wrong.

Changes:

  • fixed the magic‑number check so it actually matches P1–P4
  • corrected the ASCII/binary detection
  • fixed grayscale detection
  • fixed color detection
  • added proper P4 (1‑bit bitmap) decoding
  • fixed row padding
  • fixed bit indexing
  • fixed error messages
  • fixed the “I read the wrong byte” bug
  • fixed the “I read the wrong everything” bug

Basically: PNM is now actually supported instead of “supported in spirit.”

Better Error Messages for Image Loading

If stb_image fails, it now prints:

error: cannot load 'file': <reason>

instead of silently dying like a Victorian child.

General Cleanup

  • removed duplicate variables
  • removed unreachable branches
  • removed cursed logic
  • made the loader code less spaghetti
  • made the CLI image loader smarter about detecting PNM vs non‑PNM
Attachment
0
NellowTCS

tBMP got a validator, bulk metadata loading, more docs, and… “modularization” (aka moving files around until it feels right)

Validator CLI (tbmp validate)

tBMP now has a real validator command.

Two modes:

  • Basic: checks that the file parses correctly
  • Strict: goes full hall monitor:
    • rejects unknown/malformed EXTRA chunks
    • rejects invalid structured metadata
    • screams if anything looks cursed

It’s actually super nice for pipelines and tooling.

Bulk Metadata (--meta-file)

You can now load a whole structured metadata blob from disk:

tbmp encode input.png out.tbmp --meta-file meta.msgpack

And then override fields with flags afterwards.
It’s like layering metadata like a cake.

Custom Fields + Namespacing

Added proper docs + examples for custom metadata maps.

Recommended styles:

  • com.example.tool.build
  • mygame.level_id

And the CLI now validates custom maps so you can’t accidentally shove garbage in there.

CLI Modularization

I split the CLI into modules so it’s not one giant file anymore:

  • tbmp_imgio.c - image loading (PNM + stb_image)
  • tbmp_meta_cli.c - metadata flags, validation, custom maps
  • tbmp_ui.c - all the TUI stuff
  • tbmp.c - the command dispatcher

It looks way more professional now, even though I basically just copy‑pasted functions into new files and called it “architecture.”

Image Loading Improvements

Added a dedicated loader that:

  • handles P1–P6 PNM manually
  • falls back to stb_image for PNG/JPEG/etc
  • always outputs RGBA
  • frees memory correctly depending on backend

tBMP encode is now much more robust.

Docs Updates

Updated:

  • CLI guide
  • metadata guide
  • custom fields + namespacing
  • examples
  • README
  • validator docs

TODO Shrinkage

Checked off:

  • validator CLI
  • bulk metadata loading
  • custom fields docs
  • structured metadata examples
  • EXTRA chunk docs
  • CLI modularization

The list is getting smaller and smaller and it feels so good :D

Attachment
Attachment
Attachment
Attachment
0
NellowTCS

Okay so I was supposed to just fix a few more TODOs
and then I got distracted…
Oops.

TUI Time

I accidentally (/j) built a full little terminal UI framework for the tbmp tool.

It now has:

  • banners
  • sections + subsections
  • boxed tables
  • key/value displays
  • spinners (yes. spinners.)
  • status lines (OK / ! / ERROR)
  • auto‑detects terminal width
  • color accents
  • and a --ci mode that disables all styling for scripts

Structured Metadata (the big boi)

I replaced the old “arbitrary key/value map” metadata with a strict structured schema, and honestly, it feels so much better.

The new schema has required fields:

  • title
  • author
  • description
  • created
  • software
  • license
  • tags (array)

Optional fields:

  • dpi
  • colorspace
  • custom (array of raw MessagePack maps)

Everything is validated, length‑checked, type‑checked, and encoded cleanly.

The CLI now supports:

--title
--author
--description
--created
--software
--license
--tags tag1,tag2
--dpi N
--colorspace NAME
--custom-map FILE

If you provide any structured metadata, the whole schema must be complete, which keeps files consistent.

Metadata Rewrite

I rewrote the entire metadata parser to:

  • decode into a fixed TBmpMeta struct
  • enforce schema rules
  • reject unknown keys
  • reject duplicates
  • handle custom maps safely
  • avoid heap allocations
  • support truncation warnings

CLI Updates

The CLI got a huge upgrade:

  • unified tbmp tool
  • new TUI output
  • --ci mode
  • structured metadata flags
  • prettier inspect output
  • better error messages
  • spinner animations during encode/decode (because why not)

It feels like a tiny professional tool now. (and yes, I may have borrowed some logic from tuiro. Sue me, it’s MY own thing :P)

Docs

Updated:

  • CLI guide
  • metadata guide
  • examples
  • README
  • API docs

The docs now show the structured schema and how to use it.

TODO Shrinkage

Checked off:

  • structured metadata
  • schema validator
  • CLI metadata flags
  • TUI
  • inspect improvements
  • raw RGBA dump
  • malformed metadata tests
  • malformed EXTRA tests
  • sparse/block‑sparse edge cases
  • docs updates

The TODO list is looking way cleaner now BUT THERE’S STILL MOREEEE

Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

tBMP now has a proper CLI!

tbmp CLI

I merged everything into a single executable called tbmp, and it feels SO much nicer to use.

It now supports:

  • encode - convert PNG/JPEG/PPM/etc -> .tbmp
  • decode - .tbmp -> PPM
  • inspect - print header, EXTRA chunks, metadata, warnings
  • dump-rgba - raw RGBA bytes for debugging

It’s lightweight, script‑friendly, and honestly feels like a real tool now.

CLI Guide

I wrote a full guide for it in the docs!!

It covers:

  • build instructions
  • all commands
  • format/encoding options
  • examples
  • notes on behavior

Feels very official. I love it.

Metadata Validation Helpers

Added proper helpers for:

  • required keys
  • type checking
  • length bounds

These are exposed in the public API now, and the tests cover all the weird edge cases.

Round‑Trip Metadata Tests

Metadata now has:

  • encode -> decode -> compare tests
  • malformed MsgPack tests
  • malformed EXTRA tests
  • sparse/block‑sparse edge case tests

Basically: the metadata system is solid now.

Raw RGBA Dump Tool

tBMP -> raw RGBA is now a CLI command hehe.

Super useful for debugging or piping into other tools.

TODO Updates

Checked off:

  • metadata validation helpers
  • metadata round‑trip tests
  • malformed MsgPack tests
  • malformed EXTRA tests
  • sparse/block‑sparse edge case tests
  • unified CLI
  • raw RGBA dump tool

The TODO list is still long, but it’s getting smoller.

Next up: probably the WASM demo… or maybe the PNG exporter… or maybe I’ll get distracted and add something completely different. Who knows.

Attachment
0
NellowTCS

So I did the thing again.
I forgot this project existed.

Like fully.
Brain: empty.
Me: “oh yeah, I built a whole image format once.”

So anyway, here’s the “everything I did up to now” devlog because apparently I built an entire bitmap ecosystem and then moved on and forgot about it.

So. tBMP.

Right.

This started as “haha what if I made a tiny bitmap format because BMP is so… bad.” and then spiraled into “oh god I’m writing a whole C library and a schema and a test suite and a CLI tool and a docs site and-”

Classic me 😭

The Format

I wanted something simple.
Like:

  • predictable
  • tiny
  • no: update one thing? fine let me calculate everything else
  • and also because i wanted to >:3

So tBMP is literally:

HEADER
DATA
EXTRA
META

Does that format seem familiar? Anyone who has seen my “ideal music format” should know it as I might have borrowed it hehe.

It’s just the best way to make a binary format, smh my head

Encodings

I could’ve stopped at RAW.

But no.
Now we have:

  • RAW
  • RLE
  • Zero‑Range
  • Span
  • Sparse Pixel
  • Block‑Sparse

it’s so great omg the docs heheeeeeeeeeeeee: https://nellowtcs.me/tBMP/docs/guide/encoding/

EXTRA

This is where palettes and masks live.
And also anything else I decide to shove in there.

It’s literally:

  • 4‑byte tag
  • 4‑byte length
  • body

Simple.
Predictable.
Unlike PNG which is like “here’s a chunk, good luck.”

Metadata

Currently metadata is just “throw some key-value pairs, not my problem’

It’s now going to be structured because apparently I care about standards (lie /j):

  • title
  • author
  • description
  • created
  • software
  • dpi
  • colorspace
  • tags
  • license
  • comment
  • more idk what

Plus custom fields because I’m not a monster.

The Library

The codebase is split into modules like a real grown‑up library:

  • reader
  • decoder
  • writer
  • metadata
  • msgpack
  • pixel helpers
  • rotation transforms

Everything is tiny, just like the format (gosh, I’ve been wanting to make that joke for ages now)

The Test Suite (not tiny smh my head again)

There are tests for:

  • decoding
  • encoding
  • metadata
  • edge cases
  • sparse pixels
  • block‑sparse
  • rotation
  • reader
  • writer

It’s… a lot.

(so much handwritten things like i often had to write binary by hand which was so much pain i hate it but i will do it again cause i’m silly and it’s kinda the fastest way to do it)

Docs

DocMD is great
Mic drop.

Stuff I did these two commits

  • prettified like a bunch tables in the docs
  • added metadata validation helpers
  • added metadata round‑trip tests
  • added malformed MsgPack tests
  • added malformed EXTRA tests
  • added sparse/block‑sparse edge case tests
  • checked off TODOs like a gremlin on a speedrun
  • updated TODO.md

What’s Left (haha everything)

There’s a TODO.md, go read it

Attachment
Attachment
Attachment
Attachment
0
NellowTCS

Shipped this project!

Hours: 7.04
Cookies: 🍪 227
Multiplier: 26.88 cookies/hr

I built an EPUB engine…

lexepub started as a small “what if I just unzip an EPUB” idea and slowly turned into a full high-performance streaming EPUB parser with Rust, WASM, and C/C++ adapters.

I forgot to log a bunch of the progress at the beginning, so the first devlog is basically the full summary of everything that happened before I realized I could submit this to Flavortown haha!

The hardest part was dealing with how inconsistent EPUBs are. Every file structure is different, every path behaves differently, and every book seems to interpret the spec in its own way. I spent a lot of time normalizing paths, resolving chapter relative links, and figuring out why some EPUBs hide their CSS in places that make no sense. (Seriously, I mean it, one of my future projects will be an ebook format that’s just better. Like the “ideal” format for ebooks. (like flo was for audio!))

But I got it working, and I am happy with how it turned out!

What I built

  • A Rust EPUB parsing engine
  • Streaming chapter extraction with low memory usage
  • HTML to AST parsing
  • CSS parsing and application
  • Cover extraction
  • TOC extraction with real chapter titles
  • A WASM adapter for browsers
  • A C/C++ adapter through Diplomat
  • A browser demo that actually renders EPUBs

What was challenging

  • Rewriting the WASM bindings multiple times (this is why I made Saikuro sigh)
  • Getting EPUB path resolution correct
  • Making the demo load images, CSS, and internal resources properly
  • Fixing GitHub Pages deployment after breaking it repeatedly
  • Updating the docs so they matched reality

What I am proud of

  • The demo looks like a real reader (mostly because it’s just a modified version of HTMLReader sob), and someone even thought it was epubjs instead of my own custom library.
  • The documentation is complete and consistent (well, complete enough, I didn’t document excessively like I did for some other projects)
  • The API matches across Rust, WASM, and C/C++
  • It is published on crates.io and npm
  • I actually finished it and it exists as a real project :O

Here have these again:
Demo: https://nellowtcs.me/lexepub/
Docs: https://nellowtcs.me/lexepub/docs

I learned a lot about EPUB internals, streaming design, WASM, and cross language APIs while building this. It was a fun project and I am glad I saw it through!

NellowTCS

I PUBLISHED ITTTTTT!!!

This was the final cleanup sprint before shipping lexepub v0.1.0, and it ended up being a surprisingly big set of changes across docs, CI, workflows, and the README. Nothing glamorous, but all the stuff that makes a project feel real instead of “a folder on my computer.”

Docs Cleanup + Expansion

I went through the entire docs site and updated everything:

  • Rust adapter docs now include resource APIs, TOC APIs, and link normalization notes.
  • WASM adapter docs now list the full API surface, including get_resource() and the chapter‑relative resource helpers.
  • C/C++ adapter docs got updated with the new functions and a clearer API reference.
  • Quickstart now includes useful commands, WASM build instructions, and resource‑loading examples.
  • The homepage got a new “Resource + TOC Utilities” card and a clearer adapter parity section.

Basically: the docs now actually reflect what lexepub can do.

README Overhaul

The README got the cleanup it always deserved (and needed)

  • Proper formatting
  • Clearer feature list
  • NPM + crates.io links
  • Better installation instructions
  • Cleaner demo section
  • Updated build instructions

GitHub Pages Workflow Refactor

I rewrote (ahem, copied from Tamaru) the entire Pages deployment workflow:

  • fixed Node version
  • fixed root/base paths
  • fixed demo paths
  • fixed docs paths
  • fixed the deploy directory structure
  • fixed the index redirect

Release CI

I added proper release workflows:

  • cargo publish (with dry‑run option)
  • wasm-pack build
  • C header generation
  • release tarball creation
  • GitHub Release upload

It even handles platform‑specific artifacts cleanly and doesn’t hardcode paths anymore (I fixed that… again).

Resource API + WASM Updates

I added:

  • get_resource(path)
  • better resource resolution
  • normalized internal paths
  • improved TOC extraction
  • updated TypeScript definitions

The WASM adapter is now fully capable of powering the demo without hacks.

I Published It!!!!!!!!!!!!!!

Yep!
lexepub is now on crates.io and npm!

Attachment
Attachment
Attachment
0
NellowTCS

lexepub now has a Proper browser demo that actually renders EPUBs, images, CSS, TOC, everything!!!!
using the WASM build. I didn’t plan on building a renderer for v0.1.0, but here we are.

HTMLReader‑borrowed Demo

I put together a small demo that was yanked from HTMLReader (hey, again why remake the wheel if I already have a good UI/modular thing) that loads EPUBs through the WASM adapter and renders them directly in the browser. It works well!!!
(and shows off what lexepub can do without needing any external libraries)

It now supports:

  • chapter images
  • proper table of contents (using inferred chapter titles instead of raw filenames)
  • CSS from <link> tags
  • internal resource loading
  • normalized paths
  • and a clean UI for navigating chapters

Honestly, it’s starting to feel like a real reader, which feels insane…
I showed it to a friend and they actually thought it was like ePubJS or something haha, not my own thing!!!

Chapter Images + Resource Loading

I added get_chapter_resource and resolve_chapter_resource_path to the WASM API so the demo can fetch images and other linked assets.
This required adding:

  • path normalization
  • chapter-relative resolution
  • fallback logic for weird EPUB structures (why is this such a badly standardized format omg… I have ANOTHER project idea now, a new ebook format sob)

Images now display correctly inside chapters.

Proper TOC Support

Chapters now have a title field inferred from:

  1. <h1>, <h2>, or <title> in the AST
  2. otherwise the first non-empty text
  3. otherwise the filename stem

The WASM adapter exposes get_toc() and get_toc_json(), and the demo uses this to build a real table of contents.

CSS from <link> Tags

Linked CSS files now load and apply correctly.
This required:

  • resolving relative paths
  • reading CSS resources
  • parsing them
  • applying them to the AST before rendering

It’s still a simple CSS engine, but it’s enough for EPUBs.

Internal Path Normalization

EPUBs love weird paths (../, ./, backslashes, nested folders), so I added a normalize_internal_path() helper and integrated it into:

  • resource resolution
  • AST link normalization
  • href/src rewriting

This fixed a bunch of rendering/random other issues

TODO Updates

Added then checked off:

  • chapter images
  • proper TOC
  • CSS from <link>
  • demo added
  • internal path fixes

All that’s left really is cleanup and publishing and docs and so on, nothing major honestly, the TODO all has stuff that is beyond v0.1.0

Attachment
Attachment
Attachment
Attachment
1

Comments

Cyclic(John fire department) not FD

This is cool

NellowTCS

I added a basic demo!
The demo is based off HTMLReader, which i modified and stripped out epubjs from and replaced with the WASM compilation of LexePub. It works cleanly but there’s no formatting sob since like LexePub doesn’t have a proper renderer yet haha.

Attachment
Attachment
Attachment
0
NellowTCS

Today (well yesterday, I was tired, okay?) ended up being a pretty productive set of commits, nothing too dramatic, but a lot of important groundwork and cleanup that makes lexepub feel more complete and consistent across all adapters.

API Parity Across Rust, WASM, and C/C++

I finally checked off the “1‑1 API functionality” item, then unchecked it when I added CSS, then rechecked it in the same commit loll
This mostly meant adding the missing sync wrappers (get_metadata_sync, has_cover_sync, cover_image_sync) and wiring them into the C‑FFI layer.
(very annoying thing dealing with Diplomat’s restrictions, the thing I made, Saikuro is so much betterrrr and much cleaner and more languages are automatically supported.

Minimal CSS Parser that turned into a pretty average simple one

I added a small CSS parser, handrolled because cssparser from Servo, is difficult to deal with (future project, better api for it, 👀), just enough to handle basic selectors, declarations, and at‑rules.
It’s simple, sadly, but it works well for EPUB‑level CSS.
There’s a tiny AST (Stylesheet, CssRule, StyleRule), comment removal, declaration parsing, and some tests to make sure it doesn’t defy my expectations for CSS (copied from an ebook btw).

Documentation Pass

I added docs!

  • Rust adapter page includes full API references, sync wrappers, and CSS/AST behavior.
  • C/C++ adapter page lists the full generated API and includes a full example.
  • WASM adapter page has a proper API reference and example usage.
  • Quickstart has with optional features, sync wrappers, and convenience functions.

README Cleanup

The README got a big trim.
Most of the detailed examples and API references moved into the docs site, which is where they belong.
The README is now much cleaner and points people to the proper documentation. (Though I don’t know what late night me was thinking with the links being code lines like what?!?

TODO Updates

Checked off:

  • API parity
  • CSS parsing + application
  • Streaming cover image support

The majority of the TODO’s left are stuff that are probably for the future haha, but I do need to make a small HTMLReader-but-using-LexePub-demo

Attachment
Attachment
Attachment
0
NellowTCS

Just a tiny devlog this time, I’m learning :O

But I did add my default Docs with a capital D setup and the accompanying CI, and added streaming cover image support.

What I Did

I added docs.
And yes, I did borrow the folder structure from other projects.
I’m not reinventing the wheel when I already invented it twice (Tamaru, Saikuro, S-eco, need I name more?).
Please respect my efficiency.

And THEN, ONE MORE TODO: I implemented streaming cover image support.
As in:
cover_image_to_writer
Zero allocations.
Direct streaming.
AsyncWrite.
The whole thing.

It works, streams, and is like actually really nice!!!

TODO Updates

The TODO list shrank again, -2 more things…
I’m starting to worry I’m going to run out of TODOs and have to invent new ones.

Attachment
Attachment
Attachment
0
NellowTCS

Chaos Devlog time!!!

I added EPUB version detection.
Like, real version detection.
lexepub now looks at <package version="3.0"> and goes “oh okay cool” instead of staring blankly like a goldfish.

Then I added cover image format detection!
The manifest used to be a cute little HashMap<String, String> and now it’s a full (href, media-type) tuple because I decided lexepub should know MIME types like a sommelier knows wine.
This broke EVERYTHING.
Every. Thing.
Every .join(href) became .join(&href.0) aaaagh.

And then I fixed WASM.
Not “fixed WASM” like “haha a typo,”
I mean FIXED WASM like “rewrote half the bindings because Past Me was clearly having a moment’ (second time i’ve said that today haha).
Everything returns proper Result<T, JsValue> now.
Metadata serializes.
Chapters serialize.
Cover extraction works.

Oh and AST parsing?
Yeah that’s real now.
extract_ast() actually does AST things instead of returning ast: None like a liar.
WASM uses it too.
ParsedChapter is serializable.
Chapter is serializable (but I skipped the raw bytes because I’m not a monster).
This was supposed to be a “later” thing.
It is no longer a “later” thing.

Now we got:

EPUB Version: 3.0
Has Cover: true
Cover Format: image/jpeg

And the TODO list?
Oh my god the TODO list.
I chugged through TODOs fast as fluff.
WASM support? Done.
AST parsing? Done.
Version detection? Done.
Cover format detection? Done.
I swear the TODO list is shrinking faster than my sanity.

I also updated integration tests because apparently I’m responsible now.
They actually check MIME types and cover presence and error cases and everything.
Who am I.

Anyway.
I love how lexepub is turning out.
It started as a tiny little “haha unzip EPUB” thing and now it’s a full parsing engine with metadata, ASTs, WASM bindings, cover extraction, version detection, and a manifest that actually knows what it’s doing.

Attachment
Attachment
0
NellowTCS

SHOOOOOOOOOOOOT
I’m so good at forgetting projects existed sighhh.

So this is a lot less hours logged than I originally spent (a majority was googling HOW ePubs work (and why they’re so… hard to parse)) but I guess 2 and a half hours is fine whatever…

Okay so you all get the everything I did up to now devlog. Warning, this will be insanely rambly:

So. EPUBs.
Right.

You’d think “oh it’s just a zip file with some HTML in it” and you would be CORRECT but also WRONG because the way they organize everything is kind of a nightmare and I spent way too long just figuring out the file structure before I wrote a single line of Rust.

Okay so the gist: an EPUB is a zip file, inside that zip file is a META-INF/container.xml which points you to an OPF file (like OEBPS/content.opf or wherever), and THAT file has all the metadata AND a manifest (list of all files) AND a spine (the reading order). So you can’t just iterate the zip entries in order, you have to parse the OPF spine to figure out what order chapters actually go in.
Which is fun.
Very fun.
Super fun.

So I built the whole thing in layers basically. There’s an EpubExtractor at the bottom that just knows how to open a zip and read files out of it, and it can do this from a file path, from raw bytes, OR from a streaming async reader. That last one was kind of annoying to get right because async_zip has opinions about what traits your reader needs to implement and I had to do some fun (/sarc) stuff to get it to work.

Then on top of that there’s the actual parsing layer, ContainerParser for container.xml, OpfParser for the OPF file (metadata + spine + manifest), and ChapterParser / extract_text_content for turning the XHTML chapters into actual readable text using the scraper crate.

The main LexEpub struct is what you actually use and it caches chapters and metadata so you’re not re-parsing the whole thing every time you call get_metadata() twice.

Oh also there’s a lowmem feature flag that swaps out the scraper-based HTML parser for a dumb little hand-rolled state machine that just strips tags manually. It’s not as good at handling block elements and whitespace but it doesn’t build a full DOM tree which is the point. Useful for embedded targets theoretically.

The streaming story is… partially done. ChapterStream implements futures::Stream so you can consume chapters one at a time without loading all of them into memory at once. The benchmarks use jemalloc to actually measure heap allocation delta per operation which I’m pretty happy about as a setup.

For some reason I thought making benchmarks would be fun so there’s Criterion benches for from_bytes, from_reader, and extract_text_only. The CI runs cargo bloat to check binary size which is something I actually care about for once because the end goal is for this to be usable in WASM and potentially on embedded stuff (esp32 ahem)

WASM bindings exist in theory (src/wasm.rs) but they’re kind of broken right now, extract_with_ast() doesn’t exist, has_cover() doesn’t exist, cover_image() doesn’t exist. Those are all in the TODO. The C FFI via Diplomat is in better shape and actually generates a real header file.

The test suite is… extensive? Like maybe embarrassingly extensive for something this early. There’s unit tests, integration tests, API tests, edge case tests, streaming tests, performance tests, and a memory threshold test that reads /proc/self/status to check RSS delta. I went a little overboard. The edge case tests especially are kind of a placeholder graveyard right now, most of them are just “open the test epub and hope nothing crashes” because actually testing edge cases properly requires mock EPUBs and I have not built those yet.

The big things left:

  • A lot (just kidding, there’s a TODO.md)

Anyway that’s the summary and stuff thanks for coming to my TED Talk hope you enjoy.

Attachment
Attachment
0
NellowTCS

Shipped this project!

Hours: 8.92
Cookies: 🍪 318
Multiplier: 29.74 cookies/hr

I built Tamaru, a physics‑driven virtual trackball widget for the web!
It lets you scroll any page or container by flicking a little 3D orb, complete with inertia, audio, haptics, themes, and now… Stick Mode (pointer‑lock scrolling), which nearly broke me (/silly) but was so worth it.

The hardest part was definitely Stick Mode. Pointer lock, scroll‑target cycling, highlight logic, restoring state, and making it all feel like a real trackball was a whole boss fight. I figured it out by rewriting half the scroll engine, adding a target‑cycling system, and bribing the browser with sheer stubbornness (yes your browser’s now corrupt 😔).

I’m really proud of how polished it feels now, the physics, the audio, the theming, the demo, the docs, everything. It started as a tiny 6 AM experiment and somehow turned into a full npm package with real features and real users. I’m honestly so happy with how it turned out.

NellowTCS

So uh.

I DID IT.
I ACTUALLY SHIPPED TAMARU.
It’s on npm.
It’s real.
It’s alive.
It has provenance logs and everything.
I’m screaming.

(I’m using the bookmarklet, by the way, in the first two images)

Attachment
Attachment
Attachment
2

Comments

NellowTCS
NellowTCS about 2 months ago

*Last two images, the upload order is backwards

NellowTCS
NellowTCS about 2 months ago

HEY REVIEWERS/READERS

SO someone asked me to make this an extension. firstly WHY DIDN’T I THINK OF THAT, and secondly I’m working on that and will reship soon™

NellowTCS

so uh. stick mode.

I finally did it.
I added stick to cursor mode.
And let me tell you:

this was
SO
HARD.

like “why am I doing this to myself” hard
like “pointer lock is a demon” hard
like “scroll target cycling is a cursed ritual” hard

BUT ALSO
SO
WORTH IT.

the descent into stick‑mode madness

pointer lock hell

I had to fight the browser for custody of the mouse.
It did NOT want to give it to me.
I won.
with a smirk cause why not >:3

mini‑mode orb

stick mode now force‑minimizes the orb
centers it

absolute movement scrolling

no more dragging the orb
no more “go to the corner”
just pure raw mouse movement -> scroll velocity
like a REAL trackball
but digital!

target cycling

hold Shift
scroll wheel (or two fingers on laptop)
Tamaru goes:

“oh you want to scroll THAT div?
okay let me highlight it for you”

it even scrolls it into view configurably
because I’m extra.

state management purgatory

enter stick mode
exit stick mode
restore position
restore size
restore sanity (attempted, not guaranteed, often fails)

UI updates

added a cute little ⌖ button
it mocks me
but it works

do these exist? yes. was it pain? yes.

  • stick mode button (desktop only because mobile said “no”)
  • pointer‑lock based scrolling
  • scroll target cycling with Shift + wheel
  • highlighting the active scroll target
  • auto‑snap into mini mode when entering stick mode
  • auto‑restore when exiting
  • scrollIntoView because I’m dramatic
  • scroll engine now respects stick mode like it’s a law of nature

bugs I fixed (and bugs I created then fixed)

  • stick mode not restoring size
  • stick mode not restoring position
  • stick mode scrolling the wrong thing
  • stick mode scrolling nothing
  • stick mode scrolling EVERYTHING
  • highlight not clearing
  • highlight clearing too fast
  • highlight clearing too slow
  • scroll engine forgetting what a scrollable element is
  • me forgetting what a scrollable element is
  • me forgetting who I am

demo

updated the demo again
because of course I did
stick mode works
it’s beautiful
it’s terrifying
I love it

Attachment
Attachment
Attachment
0
NellowTCS

So uh…
I kinda want to ship this today…
Maybe a bit obsessed but here’s another devlog haha.

I decided to try not to do like 10 things in one devlog.
And then I…
did not do that.
Apparently I cannot touch this project without accidentally doing a full renovation.

Anyway.

Stuff I Did

  • fixed the GitHub Pages workflow (for real this time… I think… I hope…)
  • added a favicon because the docs looked naked without one
  • added an icon.png because why stop at one image when you can have two
  • updated the docs config so the favicon actually shows up instead of silently judging me
  • added OpenGraph images so sharing Tamaru links doesn’t look like a 404
  • added a “Try the Demo” link because yes, you should try the demo (duh)
  • updated the README to stop lying about “no external dependencies” (pain)
  • tweaked the TODO list because I keep pretending I’m done
  • moved “tweak base config” to Done because it actually is good now
  • stared at the workflow file like it personally betrayed me
  • committed something
  • realized I forgot something
  • committed again
  • realized I forgot something else
  • and realized i made the devlog look like an essay again

Bugs I Fixed

  • “I thought I changed this” (I did not)
  • “why is the favicon not showing up” (wrong path)
  • “why is the docs folder empty” (wrong folder)
  • “why is the README lying” (me)
  • “why is the demo favicon broken” (also me)
  • “why is this taking so long” (me again)

Demo

Updated the demo again and it now is curvomorphic and prettyyy :3
Now with a working favicon.
Probably.
Confirmed now, yeah it works
I love it, it’s perfect :D

Attachment
Attachment
0
NellowTCS

So uh.

I did… a lot of things.
Like, a silly amount of things for someone who allegedly “was just fixing bugs.”

This commit arc is basically:

“I’m only going to clean my room a little”
proceeds to renovate the entire house, add two new rooms, repaint the walls, and fix a installed sound system

Anyway.

Stuff I Did

  • rewrote some janky code because Past Me was clearly having a moment
  • fixed like 6 bugs that were all conspiring together
  • made mini‑mode actually behave
  • added visibility + blur handlers so the orb stops rolling when you tab away (polite orb!!)
  • fixed pointer‑events so the widget stops gatekeeping interaction (polite orb x2!!!)
  • made the rolling sound smoother, less “grate‑y”, more “roll‑y”
  • tuned the filters again because apparently I’m an audio engineer now (if that was ever a question)
  • fixed the tiny audio gap when continuously scrolling (this one was haunting me, I don’t like not seamless sound)
  • improved haptics with iOS detection + rate limiting so it doesn’t vibrate itself into another dimension
  • added scrollbar‑hiding CSS because WebKit refuses to listen to me (seriously Apple?!! why must you be so different)
  • updated the theme loader
  • updated the README
  • updated the demo
  • updated the TODO (again…)
  • oh and uhhh

I added two new themes

neon

gradient, heheheheheheh
(I NEED MORE GIVE ME MOREEEEEEEEEE)

sunset

Orange, mic drop.
(but also it looks like mars sob)

Features

  • smoother rolling sound with smarter fade logic
  • mini‑mode that doesn’t break when you click it (yay)
  • scrollbars that disappear like they were never there
  • haptics that actually work and don’t fire 900 times a second
  • glossy theme is now ✨ glossier
  • rolling physics + audio feel way more natural now
  • the orb is just… nicer?? like it has manners now almost??

Bugs I Fixed

  • rollSoundLevel mysteriously breaking (fixed)
  • rolling continuing after tabbing away (fixed)
  • clicking the mini trackball breaking the ball (fixed)
  • stuff near the trackball not being interactive (fixed)
  • WebKit scrollbars ignoring me (fixed via CSS war crimes)
  • tiny audio gap during continuous scroll (fixed)
  • haptics not working (fixed)
  • glossy theme not glossy enough (fixed)
  • rolling too grate‑y (fixed)

Demo

Updated the demo again because if I’m suffering, you get to see the results :3

Attachment
Attachment
Attachment
0
NellowTCS

So… I did a bunch of one thing:

This commit message best summarizes it:

I just wrote a manual audio engine
d6e51b1
 · 
2 hours ago
this took ages

help
help me
someone
/j this is normal for me I made an *audio codec*
no it isn't why didn't i use audio files
WAIT
OH
OMG
AAAAAAAAAAAAAAAAAAA

So uh.
I sat down to “add some sounds.”

Anyway, I guess I wrote a manual audio engine.

Like.
A whole one.

From scratch.

Why?
I don’t know.
Ask the version of me who thought “hm yes, procedural rolling‑bearing noise with dynamic playbackRate modulation sounds fun.”

Stuff I Did (aka: why am I like this)

  • Built a full WebAudio pipeline with:

    • master gain
    • compressor
    • noise generator
    • rolling loop buffer
    • bandpass / highshelf filters
    • waveshaper distortion
    • dynamic envelopes
    • speed‑scaled playback
    • and a tiny sprinkle of “why didn’t I just use .wav files”
  • Added per‑event sound design:

    • grab -> crunchy mechanical click
    • release -> softer mechanical click
    • snap -> UI‑meets‑industrial “thunk”
    • spin -> tick‑tick‑tick with speed‑based pitch
    • stop -> inertia decay with settling noise
    • rolling -> continuous bearing rumble with dynamic stuff
  • Implemented a rolling sound layer that:

    • loops seamlessly
    • crossfades
    • modulates playbackRate based on speed
    • tears itself down when idle like a polite guest
  • Added speed‑aware feedback everywhere so the widget feels alive and so realistic.

Features

  • Rolling bed sound with adjustable intensity (rollSoundLevel).
  • Speed‑scaled spin ticks so fast spins sound fast and slow spins sound like a sleepy hamster wheel.
  • Grab/release/snap/stop events that feel like a physical object instead of a div.
  • Automatic fade‑outs, teardown, and cleanup so your CPU doesn’t cry.
  • Configurable everything because apparently I can’t stop adding knobs.

A Bug I Fixed (and by “bug” I mean “my sanity”)

  • Prevented the rolling layer from stacking 400 overlapping loops because I forgot to gate it.
    (It sounded like a jet engine and now my ears hurt.)

Demo

Updated the demo to expose rollSoundLevel because if I suffered for this, you get to play with the dial.

1

Comments

NellowTCS
NellowTCS about 2 months ago

Yes I’m serious, I actually forgot that music files existed and that people have made audio clips of trackballs before.

NellowTCS

DEVLOG TIMEEEEEEE
Hackatime is behaving much more nicely, kudos to the maintainers. XD

I did stuff.

Okay, actually, I turned the little trackball experiment into a proper, modular toolkit with theming, config, and haptics.

Stuff I did

  • Added a config/type system so users can pass options and they merge with sensible defaults (types.ts, config wiring in main.ts).
  • Theme system: JSON themes + a loader, added multiple theme files (default, aqua, glossy, metal, red) and wired them to CSS variables so themes actually apply
  • Scroll mode & utilities: initial scroll helpers landed (page/nearest/horizontal/momentum), plus a scroll engine to drive snapping and scroll behavior
  • Modularization: split the code into small modules: DOM manager, controls manager, physics loop, scroll engine, sound, and haptics, so each piece is easier to reason about and test
  • Haptics: integrated the tactus package and added a simple haptic engine that calls triggerHaptic(duration)
  • Auto-reconfig: when config changes after mount, things reapply (theme, physics params), so runtime updates are respected (main.ts, physicsEngine.ts).

Features (what you, yes you John, get now)

  • themes loaded from JSON.
  • CSS-variable theming so switching a theme is immediate and global.
  • Trackball scrolling with multiple scroll modes and snap behavior.
  • Haptics through tactus for native-feeling pulses.
  • Auto reconfiguration so changing options at runtime updates behavior.

A bug/UX fix

  • Made theme application safe and non-destructive so it won’t “explode” rendering; also added a small delay/guard where needed so controls hide/show behavior is nicer.

Demo (cause we all need one in our lives dont we)

  • Updated the demo to show the new stuff using something really cool hehe
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

So first devlog for Tamaru!!!

I initially made this today at like 6 AM as a small JS experiment, then was like, this is actually so useful!!!

Then i decided to make this an npm module/library, for fun :3.

Stuff I did to the original JS module

  • Made into TypeScript to be cleaner (almost just a rename to .ts, not much complexity)
  • MODULARIZATION: core movement/physics, DOM helpers, and entry point.
  • Styles are imported as text and injected at runtime for a self-contained build (I don’t like having to import styles separately for a few reasons)
  • Used tsup for TypeScript bundling.
  • Symlinked Build/dist to Demo/dist for easy demo updates. (so useful omg i should do this everywhere)
  • Copied a bunch of CI, Config, etc from another project, Sairin, and edited it for Tamaru
  • Created Demo/index.html to showcase the widget.
  • Added a large block of lorem ipsum in a scrollable div to test trackball scrolling.
  • Disabled native page scrolling; only the trackball or scroll area can scroll content.

Features

  • pseudo-3d ball
  • pseudo-3d ball (it’s so cool omg)
  • Draggable, floating widget with snap-to-edge logic.
  • Trackball area for scrolling with inertia.
  • Minimize/restore toggle.

A bug that was fixed

  • Controls popup uses a delay before hiding, so i don’t have to speed click before the controls hide.
Attachment
Attachment
Attachment
Attachment
1

Comments

NellowTCS
NellowTCS 2 months ago

i used the demo page to make the banner
that’s so funny to me
it’s just the Tamaru with the orb and it’s perfect

NellowTCS

Shipped this project!

Hours: 8.71
Cookies: 🍪 159
Multiplier: 18.22 cookies/hr

So… this is floEditor.

A metadata editor for a new audio format I made.
The hardest thing by far was knowing when to stop. :P

flo has SO MANY FEATURES, but a practical metadata editor can’t implement everything in one go; it’s bad for both developer UX, user UX, and most importantly, the UI.

I had to optimize (I do this for every project as a habit I ingrained) to get it working extremely quickly and smoothly on a Chromebook (my preferred testing device, because it’s so bad that everything else just works)!

I am so proud of the theming, it took ages to perfect, and especially since color is really, really important for your app’s personality and stuff.

Feel free to check it out and

NellowTCS

It seems my mysteriously missing coding time has returned!

But all I did was clean up the repo to get ready to ship it!

0
NellowTCS

Two more things finished (yesterday)!

  • remix_chain (Remixes of remixes of remixes of remixes of the original track?)
  • artist_signature(Image of the artist’s signature or watermark)

I also reorganized the Artwork section to reduce the time it takes to scroll the page.

Attachment
Attachment
Attachment
0
NellowTCS

Well 3 more things done!
user_urls (custom URLs)
musician_credits (extra musicians)
involved_people (other involved people)

And I improved the lyrics stuff!!!!

Now you can add multiple SYLT/USLT pairs to your flo files, and choose between them in the players that support them (HTMLPlayer apparently doesn’t support it yet (but only the UI) 😭)!

This was also added to ID3Editor haha

Attachment
Attachment
Attachment
Attachment
0
NellowTCS

Well I added a TODO.md and almost completely finished it within an hour and a half.

Productive, much?

The list of tags supported is quite large, so it will be in a comment, but in short, almost every tag that flo supports is editable/viewable/set.

I added two new components:

  • AdvancedTags.tsx: for the slightly excessive/extra tags that are useful, but unneeded for most people. This is collapsed by default, and can expand with a click.
  • ViewInfo.tsx: tags that encoders such as reflo set and aren’t editable

Looking at the UI, it’s getting a bit long.
I may split it into tabs, but that’d require a rather large UI revamp… 🤦

But first I want to get all tags supported!

What’s left?
(this is copy-pasted straight from the TODO lol):


## Unimplemented/Missing metadata fields in floEditor

### INVOLVED PEOPLE / CREDITS

- [ ] `involved_people` (array of [role, name])
- [ ] `musician_credits` (array of [instrument, name])

### URLS (not surfaced in UI, though TS has them and some are )

- [ ] `user_urls`

### COMPLEX/ADVANCED (might complete after v0.1.0)

- [ ] `spectrum_fingerprint`
- [ ] `integrated_loudness_lufs`
- [ ] `loudness_range_lu`
- [ ] `true_peak_dbtp`
- [ ] `remix_chain`
- [ ] `artist_signature`

### COVERAGE

- [ ] Some `PictureType` and `SectionType` options exist in Rust but are not accessible in the UI
  - [ ] “bright_coloured_fish”, ”video_screen_capture”,
  - [ ] Section markers like “breakdown”, “drop”, “instrumental”, “silence”, “other”
Attachment
Attachment
Attachment
1

Comments

NellowTCS
NellowTCS 4 months ago

What I finished:

Read-only by user:

  • encoding_time
  • tagging_time
  • encoder_settings
  • flo_encoder_version
  • source_format
  • original_filename
  • Encoded By

IDENTIFICATION / BASIC INFO

  • subtitle
  • content_group
  • original_album
  • set_subtitle
  • original_artist
  • original_lyricist
  • playlist_delay

DATES / TIMES

  • recording_time (string)
  • release_time
  • original_release_time

RIGHTS / LEGAL / SORTING

  • produced_notice
  • file_owner
  • radio_station
  • radio_station_owner
  • album_sort
  • artist_sort
  • title_sort

INVOLVED PEOPLE / CREDITS

  • remixer

URLS

  • url_copyright
  • url_audio_file
  • url_artist
  • url_audio_source
  • url_radio_station
  • url_payment
  • url_publisher
NellowTCS

WE HAVE A LOGO

Also finished 3 more of flo’s features!

BUT WE HAVE A LOGO

Attachment
Attachment
Attachment
Attachment
Attachment
3

Comments

NellowTCS
NellowTCS 4 months ago

oh no the banner borked

NellowTCS
NellowTCS 4 months ago

Stuff left that flo supports:
Loudness profile (LUFS points)
Remix chain (track history)
Spectrum fingerprint (audio fingerprint)
Flo-specific: encoder version, source format, custom fields

NellowTCS
NellowTCS 4 months ago

It seems I am missing a lot more stuff than I realized
A LOT more.

NellowTCS

Some more tiny improvements.

I added the Popularimeter tag for rating and play counts
Also KeyChanges for “[tracking] musical key changes over time” (basically BpmMap but for the key, as both bpm and key are usually not constant throughout the entire song)

(i’m not sure if markdown links work in flavortown lol, i hope they do)

Attachment
Attachment
2

Comments

NellowTCS
NellowTCS 4 months ago

they do!

NellowTCS
NellowTCS 4 months ago

Stuff left that flo supports:
Loudness profile (LUFS points)
Creator notes (timestamped notes)
Collaboration credits (roles and names)
Remix chain (track history)
User text (custom key-value pairs)
Spectrum fingerprint (audio fingerprint)
Flo-specific: encoder version, source format, custom fields

NellowTCS

This is something cool that flo supports: pre-generated waveform peaks.

Basically it stores the peaks in the audio data to help make visualizers (such as the ones in HTMLPlayer) easier to generate/play. Instead of generating the data on demand, it generates it while encoding, directly into the file.

floEditor now does this automatically!

If you’re worried about the file size increase, I calculated it, and there’s only a 5-6 KB increase for a 2 minute song, so, no need to worry ;D

Attachment
1

Comments

NellowTCS
NellowTCS 4 months ago

Stuff left that flo supports:
Key changes (key shifts)
Loudness profile (LUFS points)
Creator notes (timestamped notes)
Collaboration credits (roles and names)
Remix chain (track history)
User text (custom key-value pairs)
Popularimeter (rating/play count)
Spectrum fingerprint (audio fingerprint)
Flo-specific: encoder version, source format, custom fields

NellowTCS

A retheme.
It looks so good :O

This better follows flo’s colors and looks more unique from the original base.

Also a tiny bugfix for the Process and Download icon.

(just noticed that it says FloEditor but should be floEditor, will fix that)

Attachment
Attachment
0
NellowTCS

I got so much done :D

Stuff left that flo supports:
Key changes (key shifts)
Loudness profile (LUFS points)
Creator notes (timestamped notes)
Collaboration credits (roles and names)
Remix chain (track history)
User text (custom key-value pairs)
Popularimeter (rating/play count)
Waveform data (peaks for visualization)
Spectrum fingerprint (audio fingerprint)
Flo-specific: encoder version, source format, custom fields

Attachment
1

Comments

NellowTCS
NellowTCS 5 months ago

WHAT
MY TIME SPENT
It should not be that little :O

NellowTCS

Well a bunch of debugging why my library wasn’t initializing later (Vite being picky about WASM):

IT WORKS

mostly

Attachment
2

Comments

mannlohchab
mannlohchab 5 months ago

great work . are u using wasm in this ?

NellowTCS
NellowTCS 5 months ago

great work . are u using wasm in this ?

Thank you!
The main flo libraries are Rust-based, so I am using those compiled to wasm, yep