This is something I wish I implemented as a hobby project but never found the time to do it. Congrats! I see you implement a directory as a file containing a b-tree. Could you provide more info on this?
The file is divided into 4KiB blocks, where each block is either used or unused. If it is used, it contains a B-tree node, with the directory entries and block number of child nodes. If it is unused, it is chained into a doubly linked free list so that new nodes can be allocated. Basically memory management and B-tree are handled simultaneously in the I/O process.