Designing a self-directed learning network v0.3

enabling infinite rabbit holes of learning: documenting the process and lessons

I published v0.2 a year after v0.1 (read this if you need context to this post), and now I am publishing v0.3 six months after, so I’m hoping there’s some form of Moore’s law occurring here. The delay isn’t so much the lack of motivation, but the lack of stable health. When I started this experimental journey, I envisioned a lot of time working on these projects, but in truth more time has gone to recovery and coping. But knowing similar stories is helping me to cope:

“Darwin had spent most of the past three decades — during which time he’d struggled heroically to write On the Origin of Species housebound by general invalidism. Based on his diaries and letters, it’s fair to say he spent a full third of his daytime hours since the age of twenty-eight either vomiting or lying in bed.” — source

So I try to keep my head down and maximise moments when I seem to be in good enough health. This time around I experimented with coding for 2–4 hours only to see how much I can accomplish.

Trying to return to a coding project after six months of absence was very challenging for a amateur like me. I wasn’t sure if I would be able to understand what I did previously. Coding is not like cycling or swimming. It is not something we really retain most of without practice, at least not for me. I documented most of how I slowly eased back to it on tumblr.

Why am I working on this

The reason remains the same: I want to use it myself. There’s nothing like this that exists as far as I know. There’s a ton of bookmarking/collection apps but it gets unwieldy when our data grows. There’s no hierarchy, wayfinding, or sense of completion. Most learning content exists as either a flat list of links or a curriculum.

On a metaphysical level, I see this project as an expression/mirror of me. I have a vision of how it can be, but I don’t have any idea what will it truly become. Watching it unfold while working on it, I feel like I am unfolding parts of myself too.

The goal of this sprint

It is to allow a stack of cards to have other stacks within, enabling infinite rabbit holes for navigation (previously I named it “path” instead of “stack”):

This is a card:

It can belong to a stack of cards:

This stack can belong to a parent stack of cards:

So it can enable infinite rabbit holes by going from a stack to a stack to a stack…

Why are stack to stack relationships important

Everything in the tree of knowledge is interconnected, yet we don’t have good ways of surfacing these connections. Want to learn philosophy? We can’t neglect the historical context. We can’t understand history without learning what drives a human being — psychology. Right now knowledge is organised in silos of categories, expressed in unending bunches of links with not much context.

Want to find out more on Wikipedia? Good luck to winning the link lottery. :P
This amazing resource is organized alphabetically, could it be expressed as a network of connections?

Enabling stacks within stacks is a first step towards trying to express connected subjects better, to allow someone to dive deeper into a topic, and be able to surface back upwards. I think it will be even more meaningful once the app can record completion states.

Since it is not a single source of truth but rather each stack is an expression of its creator, we will be able to see how different people learn: the different ways they curate and connect topics together.

Not to mention being able to leverage on people’s stacks to make our own stacks, making our own network of knowledge!

The outcome

pardon the GIF, optimizing for file size

If you’re not interested in the boring technical details and how I embarrass myself because of my inexperience, you can stop at this section.


Challenges

Meta information for a single page app

I already had a URL parser working when we create a card, so I thought I could just parse a stack page like parsing any other page. All I needed was to create meta information in the page head, except being an amateur I didn’t know that would be challenging to do so for a single page app.

I contemplated re-writing the app in Nuxt.JS or using some form of server side rendering, but to be honest I don’t think I am at that competency level to dive into that.

Is this a prototype or not?

I frequently conflicted between: am I really building a prototype to demonstrate the idea well enough, or am I subconsciously trying to build something that is usable? If this is a prototype it doesn’t make sense for me to rewrite the entire thing just to get meta information working. At least not at this point.

Data modelling is hard

Working with data denormalization is already hard, but not designing the structure right is really painful. In this sprint I had to delete my existing data twice because I realised belatedly I had forgot to account for some data I needed. I think a lot of it is due to my inexperience. For example, I neglected to cater to the total count of stacks a card belongs to. So each time I wanted to display a count I have to programatically calculate how many stacks a card is in instead of retrieving a simple property from the object.

Spaghetti code from inexperience

I am writing long chunks that repeats in different places (my mind keeps replaying my ex-colleague telling me D-R-Y) and hence I have found myself creating bugs in unexpected places because I made changes in one part of the code and forgot to update it elsewhere.

For example, the cards remaining in a stack has to be reordered when I remove a card. It doesn’t affect the prototype now but this is to prepare for a future implementation of custom ordering the cards.

working out how reordering should work with a sketch

There was a bug in that function, so I fixed it. But the next day, it started recurring again. It turns out I entirely forgot to account for the scenario when I delete the card completely. Previously when deleting a card, it will check for the stacks it belong to, remove its references and reorder the remaining card in those stacks. A separate function exists for removing a card from a stack, with similar repeating code for reordering.

The silver lining is I learned how to consolidate the code properly, so now when we delete a card: for each stack it belongs to, it will call the function that removes a card from a stack. So the code to reorder just exists in that function instead of appearing in two places.

Having to work out the cascading effect of every implementation to find the right one

A wrong step along the way can cause the house of cards to fall. I’ve already mentioned being blinded sided by the capabilities of a single-page app, and not being careful with the data modelling.

one mistake can cause cascading effects

The cascading effect has to be roughly mapped out to know what are the pitfalls of an implementation. I deliberated several ways to implement a stack within a stack:

  • process the stack page by scraping the meta information like any url: apart from a single page app not supporting meta information properly, it would mean that the card object’s information would be static since the meta information is being fetched once when the card is being created. If I update the stack’s information elsewhere, it would not be reflected on the card.
  • having a card object reference the stack object key as a property and retrieving information from the stack object each time it is called for rendering: this idea made sense to me at first until I realised it feels absurd to have firebase make multiple calls to the database each time I render a stack page. I might be wrong, but it also doesn’t feel performant speed wise.

Two things helped. Writing pseudo code, and mapping out the object model according to a sketch of the card of the stack:

writing the above pseudo code allowed me to see how convoluted my possible implementation was

The Solution

It actually took me two days of going back and forth, and implementing the wrong solution halfway, to realise (duh!):

To have a stack exist as a card, it means a card object has to be created. Instead of creating it only when it is first being added to another stack, why not just assume that a stack will always be ready to be added to another stack?

Hence, I created a corresponding/twin card object each time I created new stack (which meant that I had to delete all my existing stack data because I don’t know what to do otherwise), and adding a stack to another stack is as simple as adding that corresponding card object, just like adding any other card object to the stack. I just had to made sure that the corresponding card object contained the data fields I needed.

The bonus is: each time I update the stack’s data it updates the corresponding card’s object as well, so when rendered in a parent stack it will always show the most updated information. i.e. how many cards this stack contain:

rough prototypey design of a stack card

We can see which other stacks it belongs to:

Pardon the prototypey design

What I learned

Write good comments even if it is our own code

N00b mistake here, I didn’t spend a lot of effort on code commenting the last sprint because well, how can I not understand my own code, right?

Destruction is unavoidable

Despite lamenting above that my inexperience has caused me to make expensive time-consuming mistakes, I don’t think I could avoid them. It is the willingness to make those mistakes that will enable me to learn from them instead of being paralysed by inaction. With experience comes more insight, and the price of that insight is the ability to see how terrible my code was. Being able to see what needs to be refactored or done better is a clear sign of levelling up.

A few hours a day does go a long way

The last sprint I coded for more than 8 hours a day and it resulted in yet another burnout and I stopped working on this for 6 months. But aiming to restrict my coding time to 2–4 hours a day was way more sustainable. It was hard for me to leave problems unsolved because I have an obsessive mind, but it was easier for me to get back to it the next day because I already have an unsolved problem waiting for me. I definitely didn’t accomplish as much in the last sprint, but if I could make consistent slow progress, it is better than having to stop for long periods of time and losing my momentum.

Somehow despite the 6 months away, this time I felt like I was quicker at coding breakthroughs — being able to solve coding challenges in a couple of hours instead of beating at the same problem for 4 hours straight. I think having a well-rested mind to approach problems really makes a difference.

I still love building

Somehow even with the chronic pain I keep coming back to this. There is something magical with typing a few lines on a keyboard and seeing something come to life.

I have accepted that there will not be a definite timeline for this, and it is somewhat liberating to know I will keep on building this for as long as it takes, and I have no reason to rush. I am seeing this as a lifelong project, maybe one day I’ll be good enough to build an API where different apps can be built on top of it.

There is beauty in embracing the rawness

At this point I am used to showing this very rough, in-progress work that may not even make sense to other people, but when I first started out it was deeply uncomfortable and vulnerable. Now, I am able to appreciate its becoming and the spirit of sharing it with people.

Why am I writing this long-winded post that most people would probably be bored reading

I love reading people’s in-process/progress posts. I think we have a tendency to show completed work, which in my opinion generates two extremes: making something is either really easy or really hard.

I guess I just want to demonstrate that it is neither that easy or hard, but it can be done with enough will, with the caveat being this tweet, keeping my health in mind:

I am also writing this for my future self, because I know there will come days when I will forget how and why I did stuff.

What is next

I am thinking of enabling multi-player mode. I don’t think this is usable for the general public at all, it has no UI messages, everything is really rough. But I will need to start provisioning for other users in the design in order to demonstrate its true potential:

  • collaboration on a stack
  • cloning and remixing someone else’s stack
  • adding cards from people’s stacks

Play with the prototype here

(though I may destroy my data once again and the mobile version is janky)

Support this experiment on Patreon:

More:

Phew! I thought this was just going to be a minor update because I deliberately kept this iteration small, but nothing is ever minor, is it?