ForeverList update: after a week of work, you can download it!

tl;dr – After a week of hard work, the app is mostly feature-complete and available for download as a preview.

Go download the app here.

This past week I’ve sunk about 13 or so hours into working on ForeverList. I’ve done a lot of work. Here’s the list:

  • added deleting of entries
  • added drag and drop support for images and links
  • added drag and drop for notes (you can reorder them or move to to other notes as sub-notes)
  • added undo/redo support
  • cleaned up the UI overall

The biggest amount of work was the undo/redo support. I basically had to refactor the core model for the app as it couldn’t handle undo/redo in its current state. The new version is much cleaner architecturally. It now uses the diff as its atomic unit of work on the database in memory.

Whenever you modify an entry by deleting it or modifying its contents, the change is encapsulated in a “diff” object. At the same time, a reverse diff object is created to undo the action. The diff is recorded to our database journal. Then, if you wish to undo the action, we do two things: 1. we remove the last diff from the journal and 2. we apply the reverse diff to get the database back into its old state. The removal from the journal is necessary because when we write the journal of diffs to disk, we only want to keep around the actions that weren’t undone.

I did a lot of UI cleanup, but there is more on the way. Here’s what the app looks like now. It’s usable, but has a few rough edges here and there.

I’ve decided to put a preview build of the app online to force me to complete it. 🙂 The preview is fully functional and then file format isn’t going to change, so feel free to start using it regularly. A future update will be posted to the Mac App Store.

ForeverList update: You can now reorder notes in a list

I spent about an hour and a half tonight adding reordering of notes. You can now pick up a note and re-position it relative to other notes in the list. I had to add code not only in the UI support this, but also back-end support in the storage to record the reordering of notes in a list.

In the future you’ll be able to drag and drop entries from one list to another as well, but this isn’t supported yet. That particular feature will be great because the whole purpose of this app is to allow you to organize your notes hierarchically in a tree, so there’s a big hole if you can’t pick up a note and move it to a sub-tree.

I’ve been coding on a tear in the last few days, but that’s led to a build-up of technical debt. I think it’s acceptable as I get a basic minimum viable product working. Once I get into a state where the bulk of the features are supported, I’ll revisit some of the debt. This app is pretty simple, so that debt isn’t going to be a very big problem. Additionally, I’ve laid out the work ahead so that the debt is intentionally preventing other work from proceeding efficiently, so I can’t really move forward without attending to it.

More ahead…

ForeverList update: Added image support

I went a little nuts this evening and spent a few hours adding drag and drop image support to the app. Thankfully I’ve worked with images and dragging and dropping on the Mac before. In spite of that, it was still a little painful to get this working, mostly because of the pasteboard data format. I had some trouble getting the right NSURL data out of the pasteboard. I also had to work a bit to save the right image representation when writing the document to disk.

Anyway, I’ll have more info on how this all works later, but check out this real in-app screenshot.

The other cool thing is that all images are saved directly in the document package.

ForeverList update: Starting to implement transaction log as storage

Quick update on the ForeverList project: Today I spent two hours and implemented basic loading and storing of the ForeverList document as a transaction log.

First of all, its worth noting what “document” in ForeverList will be. ForeverList lets you create lists of notes, where each note is made up of text and possibly more child notes. This way, you can organize your notes hierarchically.

Now onto the transaction log: When you edit a ForeverList document, ForeverList will store individual changes as transactions in a log. A transaction is simply a description of a change to the document. For instance, if you create a new note, the app will store an entry in the log that says “user created a new note and here is the text they entered”. If you go and change the text in that note later, it will create a log entry that says “user changed the text for this note to ‘blah blah blah'”.

When the user saves the document, which is made up of many notes, the app will simply store this log of changes and not the notes themselves. If you close the app and later re-open the document, the app will load this log of changes and play it back from start to finish to re-create the document and its notes.

Why do it this way instead of storing the document’s list of notes verbatim? Well, using transaction logs will allow a document to be edited by multiple devices. That is, it will let you open up the document on multiple Macs or iPhones (once I write that version of the app) because each device will get its own transaction log. When you save from that device, it will only write the changes that were made from that device. However, when you open a file, we’ll take all the changes made from all devices and merge them into a mega log file and play back all the changes in chronological order. This is basically how I implemented the storage for Lil Todo.

Sharing a single file across multiple devices requires a lot of coordination. If your Mac and iPhone are editing at the same time, how do you let them save different copies of the same file? How do you resolve conflicts if one of them saves first and the other one notices? By breaking down the changes into a log with timestamps, we avoid coordination by forcing each device to write to its own file (nobody else is allowed to write to it!) and we avoid conflicts by letting the latest change “win”.

One important bit about how the document is stored: the document is actually going to be a directory that contains multiple files, i.e. all the transaction logs from all devices. (In the future, when you drag and drop images into the app, I’ll also store the images in this folder as well!) The way I’m handling this is via Document Packages on the Mac. Cocoa on the Mac allows you to write a Document-based app where instead of saving to a specific file, you provide a “document package” object which describes a list of files that will be written to disk instead of a single one.

At this point, I don’t have cross-device syncing support yet. In the future, you’ll be able to use a cloud storage provider like Dropbox or OneDrive. For now, what you will be able to do is save a ForeverList document to something like Dropbox and you’ll still be able to open and edit that document from multiple clients because each client will only save to its own transaction logs. If they all shared the same file, however, we would have to worry about different file versions and synchronizing access to the file and potential conflicts in the changes. Dropbox will take care of syncing the individual transaction log contents for you.

In summary:

  • Each device will record each change that is made to a document and save those changes to its own transaction log.
  • The ForeverList “document” will simply be a folder that contains multiple transaction logs, one from each device.
  • When ForeverList opens a document, it will open up the folder, collect all the transaction logs from all devices and then merge them into a single transaction log, which it will then “play” from start to finish to reconstruct the document.

Simple macOS app illustrating undo/redo and other concepts

For my ForeverList project, I wanted to learn how to use NSUndoManager with a list, so I spent an hour and put together a simple app demo. Check out my SimpleLists project on github. It illustrates a few basic concepts in Document-based macOS app development.

The app lets you create a simple list. You can enter new items and select existing items to delete one by one. You can save and load to a simple JSON array. As mentioned, it supports undo using NSUndoManager.