Page 4

Bootstrap is still pretty good

The pace of web development changes really quickly, and so does the flavour of CSS frameworks. Though Tailwind.css is the hottest thing at the moment, we can all reminisce a time when Bootstrap was king.

My web dev skills are quite poor, so I’m just happy to use anything that looks pretty. For my last web app project, I’ve used Github’s Primer. However, because I don’t use any CSS tooling, (and just prefer a zipped/minimised CSS file), it looked a bit complicated for a recent web app I’ve made. So instead, I went back to the good old Bootstrap.

And a lot has changed since I last used it. A feature that I really liked (and I think it actually relies on third party code) is the VStack and HStack classes. All the flex box grid stuff doesn’t make much sense to me, but the former classes definitely do. So wrapping my content in these classes created a very decent user interface and I was able to ship very quickly.

I like to think that my nature is in backend development, so having anything that can translate my ideas into something pretty is a godsend. And despite all the hubbub of atomic classes and whatnot, Bootstrap is still really good if you need an all-in-one package.

post

Dev Log #5

I failed. I’ve failed for a long time, but I wasn’t sure how to phrase this. But here we go.

Indie development can be like jazz, where you can start freestyling whenever you open the code editor. Shipping as soon as possible is an exciting and daunting task. However, it’s important to understand that rushing through the development process without proper planning and organisation can lead to creative blocks and setbacks.

My book reading app was one such case. Because I thought it could be shipped early, I dove into the development process headfirst without a proper allocation of tasks or time management. As a result, I hit a creative block and struggled to make progress on the app.

I also had to ask myself whether the app was something I really wanted to make. And the current iteration of the app is not something I want for myself. This could have been avoided if I had a clear roadmap or task list.

I’m pausing development on this app for now. I may come back to it later. But not right now.

In the future, I plan to incorporate a principle called “2 x 2 x 2” method. This involves dedicating 2 hours, 2 days, and 2 weeks to prototyping different app ideas to see if they are viable for the app market. This approach allows me to quickly test ideas and validate them without dedicating too much time or resources to a single idea. This will help me to quickly move on from ideas that are not viable and focus on those that show promise.

I’ve been working on something else in the meantime, but I want to be more methodical about its approach. Stay tuned for updates, because I do love blogging. But I’ll be pausing updates on this miniseries for now.

post

Dev Log #4

To track books, users need to be able to add them from somewhere. I created a search feature which uses Google Book’s API (but I might switch to Amazon soon!) but it only returns 10-40 results at a time. During the weekend, I finally buckled down and created a lazy loading/infinite scroll.

SwiftUI’s List or ScrollView is notorious for its bugs. So I wasn’t feeling confident about tackling the problem. Luckily, this article provided great guidance.

The basic principle is that each List Item will be appear lazily. They all have a .onAppear modifier which checks whether the list item is near the end of the current list. If it is, then we will fetch more items based on a new index.

In conclusion, the problem wasn’t very difficult, but I am very proud of implementing this feature. It is one of those minor features that greatly improve the user experience. I love scrolling endlessly too, so I am one very happy (amateur) developer

post

Dev Log #3

I took a little break from development, but the next action on the roadmap was enabling note taking. The app would allow users to track their book reading through sessions, which opens up a timer and a scratchpad to jot down notes and thoughts. Afterwards, the scratchpad would split into different notes based on headers that the user can later reflect on. That means that the scratchpad can be thought of as a tree - there will be multiple nodes/notes branching from different headings.

The initial plans for the app was to create Markdown notes, and I wanted to create formatting based on syntax. My Mastodon posts detailed my journey (also you can follow me here!) where I created some basic parsing units, such as Literal and Match. I had to give it up for the time being due to complexity but I still think it’s worthwhile to write about it.

The Foundation framework provides NSScanner or Scanner, which has backtracking to maintain the position of the parsed string. But my research showed that some key methods were deprecated so I felt hesitant to move forward with it. TextMarkupKit initially gave me the idea to create a Packrat Parser, but I had trouble using their text editor.

In the future, I may write more about this if I implement a parsing grammar in the future. In the meantime, if you want to try it yourself, these links may be prove useful:

  1. Guido van Rossum’s PEG Parsing Series
  2. String parsing in Swift
  3. NSScanner
  4. Patterns
  5. Packrat Parsing from Scratch
  6. MarkdownParser.swift
post

Dev Log #2

Edited and updated 30th January 2023

During the holidays, I’ve been studying other codebases to understand how to write better code. In particular, Apple’s CareKit framework has been largely beneficial in knowing how to structure my Core Data models better. A lot of tutorials mix views and models together for simplicity, but this can quickly become unwieldy as the codebase becomes more complex.

The CareKit code is interesting because all the transactions, such as insert, update, delete, were funnelled through the aptly named transaction() method. Core Data classes are prefixed with ‘CD’ and structs are passed around instead. I liked this approach because it created several layers of abstraction between the actual database and the user (something I’ve learned from using Elixir’s Phoenix framework). It also heavily utilised closures which enabled async. However, while it was straightforward to add new data, it was challenging to update and delete objects. This is because structs are value types, so new instances are created while classes, being reference types enabled direct manipulation of the objects. It made sense for the CareKit framework because data was ‘soft-deleted’ (the DeletedDate of the object was switched from nil to the date of deletion) and updates were essentially inserts with a pointer to the most current version. This may be ideal if you want to maintain a version history of the writes, but it was overkill for a CRUD app.

I initially found a compromise where I would create a new struct, compare it to an existing UUID in the database and then rewrite all the properties of that existing object. However, this was unwieldy because it felt awkward to write the update logic. I also felt that this was against the principles of Core Data, so I thought there could be a better solution.

This article enlightened me. I really enjoyed writing a closure for creating new classes. It felt so fresh compared to the usual var object = <Core Data Class>(context:) dance. However, I found the extra code at the end felt awkward too. Speaking metaphorically, writing the .sink and .receive methods felt like I was waddling with toilet paper trailing from my bum. There had to be something better.

I’m really happy with the solution I conceived. It uses the best of both worlds. I can still write my closures and I can still check if the operation completed successfully. This is the final code I came up with:

class Storage<Entity: NSManagedObject> {
    private let context = StorageProvider.shared.container.viewContext

    func add(_ body: @escaping (inout Entity) -> Void, completion: @escaping (Result<Entity, StorageError>) -> Void) {
        context.perform {
            var entity = Entity(context: self.context)
            body(&entity)
            do {
                try self.context.save()
                completion(.success(entity))
            } catch {
                completion(.failure(.invalidValue(reason: error.localizedDescription)))
            }
        }
    }

    func update(_ entity: Entity, _ completion: @escaping (Result<Entity, StorageError>) -> Void) {
        context.perform {
            do {
                try self.context.save()
                completion(.success(entity))
            } catch {
                completion(.failure(.deleteFailed(reason: error.localizedDescription)))
            }
        }
    }

    func delete(_ entity: Entity, _ completion: @escaping (Result<Void, StorageError>) -> Void) {
        context.perform {
            do {
                self.context.delete(entity)
                try self.context.save()
                completion(.success(()))
            } catch {
                completion(.failure(.deleteFailed(reason: error.localizedDescription)))
            }
        }
    }
}
post

Dev Log #1

Any app worth its salt starts off with a bunch of boxes and arrows drawn on a napkin. XXX is no different.

XXX is a book tracker and organiser. I love reading, but part of the motivation is the tracking feature in Good Reads. But its interface is clunky (I’m never going to download the Good Reads app) and I don’t want to log into a website every time I finish reading. Also, I’ve been reading a lot of technical books, and I forget to add them to my library. So wouldn’t it naturally be better if I could track my reading progress locally on my phone?

That was the motivation. Now we try to translate idea into something more tangible.

Insert Screenshots

Books have four statuses: Currently Reading, Want to Read, Read and Closed (the latter is in case the user decided to drop a book without deleting it completely). The user should also be able to organise books into different folders.

Book reading is organised by reading sessions. It’s like workouts but for book reading. A timer will start and there is a little scratchpad for taking notes.

post

Launching the Dev Log Miniseries

This blog has been lying pretty dormant since its inception. The reason is simple: I was busy.

I nicely wrapped my exams, and then I started a Christmas casual job. I’ve been moving, but it doesn’t mean I haven’t been thinking. I just haven’t had the time to record it.

For the longest time, I’ve been inspired to create an app. I thought I had the technical expertise to execute this year, so I took a risk and tried to build it. It was a valiant effort, but it was eating up my study time. But since the school year is over, I’ve been sharpening my software skills for another stab at it in 2023.

However, I’ve been inspired by an article, The Simplest App that Makes Money, and I’ve decided to launch an app before school starts. Just a proof of concept idea. Also to prove to myself that I’m capable of shipping (glacial-self-62998 was proof that I could ship a website, but I’ve never shipped an app before! On the App Store of all places!)

I’ve dusted off an experimental side project and have decided to use that as my practice app. Though I’m supposed to make the simplest app, my pride will not make it that easy. There are enough interesting ideas and concepts that will keep me on my toes.

Any spare time available in the past week has been spent on the app, and I think I’m at a stage where I can start developing it in public.

So without further ado, I proudly announce the Dev Log Miniseries.

post

glacial-self-62998

This was originally written in September 2021, but reuploaded. Names were redacted for privacy

So it’s finally over. I switched the domains around so now the awards web app is offline. I’m hosting a static site on the domain name now instead.

2 weeks ago, I experimented with getting a Rack-based Ruby app running on Heroku. I got that working by the 29th August and started working away on the app. Over that weekend I worked on creating an MVP - by Monday night, students could vote on award categories, vote on each other and fill out a profile. I created a rudimentary onboarding page where users/students could add a profile photo. I think the profile photo made a big difference because it allowed students to personalise their profile and recognise each other more easily.

Understanding how to upload images was my first technical challenge. The only other challenge before that was the Elo algorithm, but I just needed to understand how it worked. Image uploading was a different beast altogether, because I’ve never done this before. My first attempt was to save the images as a binary blob directly into the database. My test runs showed that things were loading reasonably so I shipped this version. The virtual awards ceremony was now officially on!

But it quickly ran into issues.

First, there was not enough users. People read my promotion messages and quickly dismissed it. That was okay, because there were some technical issues with it. But, as I mentioned previously, the thing that hurt me the most was that my closest friends didn’t seem too really care about it. I’ll be forever grateful to R***, C*** and J*** for being the early adopters. They’ve forever earned my trust and I’ll do anything for them. The rest… eh we’ll see.

I quickly discovered that the login feature was broken for some reason and that I would have to iterate on what I currently had. Over the next couple of days, I’ve experimented with uploading images to S3 (again, first time I’ve ever done this! It was terrifying!), fixing the login situation and updating the algorithm so you can vote on 4 students rather than 2 (as was my original vision). I also rewrote the voting code so that it didn’t create new vote sessions each time the user loaded a voting page. To keep track of the state of the voting match, I previously created a new record with the voting match id and populated by the different students loaded. Then, a POST request would update the winner of that voting match. However, I’ve noticed that a lot of users would load the page, then move to a different page, which meant that there were a lot of rows with NULL for the winner column. I was very proud of fixing that, as the code was kind of bloated.

I also learnt how to do to JOIN queries by the 3rd September. This meant that I could update the awards pages to show the student rankings, load voting history for each student and view their shoutouts. Looking at my calendar, 3/9 was Thursday night. That was when I had 2 cons sessions and I had a crazy idea moment in the middle of cons. I thought about creating an additional interactive feature, called shoutouts, so that students could compliment each other. That was when I both hated and loved myself. I loved myself for having that creativity and realising that creativity doesn’t have to be limited to art - it can be applied to engineering too. I also hated myself because I knew I wouldn’t be able to get rid of the idea until I followed through with it.

Thursday was also when I created default profiles for students, rather than encouraging them to sign up. This meant that more students could vote on other students without having to wait for those students to register. I also implemented a privacy function so that students could opt out of the voting process. Interestingly, only 2 students opted out by the end of the awards ceremony.

By Saturday, I almost had everything finished. But I had some login issues. At this point, I was exhausted and decided to do “security by obscurity”. Version 2 was live.

The reception was a lot better. And this time, a couple of my friends did register early on and played around with it. I got to see their reactions and it was gratifying to see their having fun.

The voting was going well for the next week. I started nagging people to check out the website. The one thing that bothered me was that new account profiles were being created because students’ personal email addresses didn’t line up with the one saved in the database. But by Wednesday, I had a fix that also solved the login issue: if students haven’t signed up and had a different email address, the profile saved in the database was merged with their email address they input. I used the same code to merge the existing accounts in the database. My last commit was Wednesday, 1pm. I was officially done with coding. 8 days worth of coding.

The next couple of days was spent promoting the website and spending time away from programming. I also thought of different projects, stuff more related to FerrisWheel.app. On Friday I did the presentation and the whole thing was finally over. I stepped away from talking to people over the weekend and now it’s Monday. I feel recharged enough.

As a final note, I want to share some statistics. Also, can I say Heroku Dataclips has really saved my bacon and helped me understand SQL more. I really do appreciate it now!

  • J*** had the highest score of 1596 for the “Most Influential Denstagram” award. E*** would be please to know that she came in 2nd place.
  • C*** had a score of 1564 for the “Party Enamel” award. L*** tied with her for the same award.
  • J*** had a score of 1564 for the “Fireball Award”.
  • D*** had a score of 1564 for the “Colgate Award”.
  • I had a score of 1548 for the “I see Both Sides Like Chanel” award. I was tied with V***, but I gave V*** the award.
  • There were 129 shoutouts (some more was made over the weekend). I made the bulk of the shoutouts but A*** comes as a close second.
  • Only 37 students didn’t end up checking out the site.
  • There were 648 student votes in total.
  • There were 1.5K rows for the Award Votes and yet that loaded in 42ms. Damn, databases are fast.

This was a fun project and I’ve learnt so much from it. I would rewrite this in Rails just to see if I can iterate faster and to see what it would be like to have things autogenerated for you. I wrote everything by hand and would like to avoid that in the future. It would also be nice to have everything structured correctly. Plus I would like to write tests.

post

White Night

When despair for the world grows in me

and I wake in the night at the least sound

in fear of what my life and my children’s lives may be,

I go and lie down where the wood drake

rests in his beauty on the water, and the great heron feeds.

I come into the peace of wild things who do not tax their lives with forethought

of grief. I come into the presence of still water.

And I feel above me the day-blind stars

waiting with their light. For a time

I rest in the grace of the world, and am free.

post

Language Models (Mostly) Know What They Know. Language models can evaluate the validity of their own answers and predict which questions they’ll answer correctly, with performance improving when they consider multiple attempts, though calibration on new tasks remains challenging.