A couple weeks ago I built my own EPUB reader called Scroll, and since then have pretty much moved off Marvin. Here’s what Scroll looks like (light and dark themes):
Thus far I’ve read two books using it, and while there are still a few small issues, overall I’m very happy with it. I don’t plan to release it anytime soon, but as a not-even-close-to-the-same-thing substitute, here are some notes:
I’d made decent enough progress with epub2pdf that it was usable, but reflowability and theme-changing are nice, and having quick navigation between books is even nicer, and printing a book to PDF in Firefox is slow, and browsers already natively support the HTML that’s inside EPUBs, so I abandoned the PDF route.
Scroll (after both the noun and the verb) is a PWA without a backend. It’s actually just static HTML files: I wrote a script that concatenates all the HTML in an EPUB into one long HTML file, with some processing to fix links and wrap things and bake the table of contents out. Then there’s a little bit of vanilla JS for the reader functionality (saving the debounced current scroll location to localStorage (I wish Safari supported scrollend), restoring it on the pageshow event, calculating pagination, jumping to pages, switching themes, etc.), and another vanilla JS file listing the current books (so that I don’t have to re-build all the other book files when I start reading a new book).
While I said “pagination” above, I’m not actually chunking the book into pages; I’ve chosen to stick with vertical scrolling for now instead of horizontal paging, primarily because being able to make sub-page progress is nicer than I realized. That said, I still want to know how many “pages” I’ve read, so I use the scroll percentage (scrollTop divided by scrollHeight, super simple) multiplied by a rough heuristic of 1,200 characters per page, ignoring whitespace. It’s not perfect but it’s good enough for my needs.
I added a slight blur to the text so that it feels a little less digital. For now I’ve opted against a background image, but I may change my mind about that.
Whenever I add/remove a book, I copy the baked files to a private directory on my server
I’ve saved the root HTML file (which is just a list of the current books, loaded via JS) to my home screen as a PWA
Since the phone is now the window to the soul, here’s my current home screen:
Things of possible interest (or more likely imminent boredom):
30 Seconds is a system shortcut I made that starts a thirty-second timer, which I use for my physical therapy exercises morning and night (I used to use a third-party app until I realized I didn’t need one)
Projectile (an app version of my project tracker), Bookshelf (reading tracker), Slash (blog engine), Storybook (writing app), Momentum (time tracker), and Liszt (to-do list) are all PWAs I’ve built
While it’s kind of weird (they’re the same exact app), I love that Marvin SxS is on the App Store alongside Marvin; I use the dark-background Marvin for fiction and the white-background Marvin for nonfiction
From left, the three bottom apps in the dock are Codex (notes app), Saturn (launcher), and Gate (Drafts clone), which are also PWAs I’ve built, and clearly I have a problem
I currently have 136 tabs open in mobile Safari, and clearly I have two problems
Saturn is my launcher app for my phone. It’s a small Go web app. The name comes from the Saturn V rocket launcher, and I wrote it as a replacement for Launch Center Pro (when it switched to a subscription model, because apparently I am allergic to those).
Overview
This is what it looks like:
The blue buttons are direct links to pages. There’s a very hard to see dark textbox at the bottom, and if I type something in there, the green buttons take that and execute a search somewhere else. Finally, the pink buttons open secondary panels, like this Notebooks panel:
On this panel, the lighter rectangle above the buttons is a search box that allows filtering through Vinci notebooks.
How I use Saturn
On my phone, I have it saved to my dock. It’s not set to be a PWA, because then the links would open in the in-app Safari; I prefer having them open in normal Safari.
I use Saturn pretty much every day. I mainly use it to set reminders, search Amazon, Goodreads, and eReaderIQ, and get to my journal and my daily review list.
The future
I’m largely happy with it as-is. Maybe some more refactoring of the buttons (the Search button is largely obsolete now that I’ve moved all those buttons to the main screen, for example), and it could use a little design love to make things more consistent (search boxes, for example), but that’s it.
Still another entry in the will-it-ever-end series talking about my personal productivity tools.
Storybook is my fiction writing app. It’s a Python app running Django. The name comes from, uh, books with stories in them.
Overview
First, the dashboard, which lists weekly writing stats and active stories at top and backburnered stories at bottom:
The writing view has a stats bar at top (showing how close I am to meeting my daily 1,000-word goal) and then the textbox for the actual writing:
The menu has some overall story stats and an outline (with somewhat vague and hopefully unspoilery scene titles), and some admin links:
Syntax
As you can see from the screenshots, it expects Markdown. I’ve put in a convention hack where h2 tags (##) delineate scenes. Also, scene titles that begin with “Chapter X” create chapter divisions. (Clarification: a story has a flat list of scenes. The chapter divisions are display-only.)
How I use Storybook
On my laptop, I have it open in Firefox as a pinned tab. On my phone, I have it saved to my homescreen as a PWA.
I mostly avoid using Storybook (cough) but somehow still manage to put in a thousand words a day, one word after another.
There’s a payload syntax so I can send writing from Gate or Quill to Storybook, but I never use it.
The future
Same old story: I’m planning to move it to FastAPI and start using plain text files for storage instead of a database.
At some point I want to refactor the outline UI and add search functionality.
Yet another entry in the forever-long series talking about my personal productivity tools.
Bookshelf is my reading tracking app. It’s a Python app running Django. The name comes from, uh, the thing that holds books.
Overview
Behold the books:
At the top there’s the stats panel, which shows how much I read the last six days with color coding for the genre tags (and yes, Wednesday and Friday I didn’t meet my 100-pages-per-day goal), my page total so far this month (932), how many books I’ve finished so far this month (2), and how much of my reading this month has come from each tag (I usually try to read around 50% nonfiction, but I usually fail).
And then there’s the book list itself. Title, progress bar with some extra data (including how long since I started the book), current page number (clicking this opens a panel where I can record the page I’m on along with a comment), and how long it’s been since my last entry.
Each book has a staleness limit (default is five days), where if I haven’t read the book at all in that period of time, it changes the color of the title to a glaring, awful red, and that’s sufficient motivation for me to get back to that book. (To be honest, lately I haven’t seen it come up much since I’ve been reading only a few books at a time, but in those crazy days when I was reading twenty to thirty books at a time, I saw it a lot.)
Also: the sixth book (in case you were wondering) is A Disciple’s Life, which is only visible on Sundays (I reserve it for Sunday reading).
And the mobile view, for the heck of it, and since it’s the one I use almost all of the time:
There’s also a stats page, since the statistics are surprisingly helpful in motivating me to make time for reading:
(Yes, as of a couple days ago I’ve read more this year so far than all of last year in total. This makes me inordinately proud even though it really doesn’t matter.)
And, lastly, the hopefully self-evident history page:
How I use Bookshelf
On my phone, I have it saved to my homescreen as a PWA, and that’s primarily where I use it, since I mainly read on my phone these days. On my laptop, I have it open in Firefox as a pinned tab.
I use Bookshelf every day to track my reading, both for individual books and for my daily/monthly reading goals. It’s handy, too, as a bookmark that toddlers can’t pull out.
The future
The desktop view needs some love, particularly that stats page. (I added those genre tags to it a month or two ago and realize now that I never actually looked at the desktop version. Whoops.)
Also (this should be no surprise by now), I’m planning to switch it to FastAPI along with plain text files for storage, for the same reasons I gave in those other posts.
Another entry in the neverending series talking about my personal productivity tools.
Momentum is my daily goal app, for keeping a goal chain/streak going. It’s a Python app running Django. The name comes from the momentum that a long streak gives.
Overview
Goals can be either binary flags (whether I did it that day or not) or timed (in which case Momentum keeps track of the time spent). The default mode is focus mode, which shows only the top unfinished goal at a time and looks like this (with dummy data):
The thin red line along the top is a progress bar showing how close I am to finishing my Momentum goals for the day. The red boxes show the last few weeks of the streak, the green box at the right is the button for saying I’ve completed that goal for the day, and the blue text under the goal name shows how long the total streak is.
When focus mode is off, it looks like this:
You can see a partially completed timed goal along with a binary goal. Momentum also supports ignoring goals for Saturdays and/or Sundays (the gray boxes among the red), which I use for things I don’t usually do on the weekends.
When the timer on a goal is running, the favicon changes and the page looks like this, with the pink box at right showing the elapsed time for the current session:
(The idea with the timer is that it may take multiple sessions spread throughout the day to meet the daily goal, by the way — if I wanted to make sure I spend an hour writing each day but don’t usually have time to do it all in one block, for example.)
How I use Momentum
On my laptop, I have Momentum open in Firefox as a pinned tab. On my phone, I have it saved to my homescreen as a PWA.
I use Momentum every day for my morning routine, primarily on my phone. The goals I put into it (as opposed to just adding things to my to-do list in Liszt) are things I want to do each day and, to some degree, are things I might not do if I didn’t have a streak pushing me forward (thus “Momentum”).
The future
I’m happy with the app as it is, but I’ve been thinking about merging it into Liszt, since goals like these are fundamentally to-do items. (Every morning Liszt already automatically adds all the items in my ::streak list to my ::today list, so that I can work off my to-do list without necessarily having to go back to Momentum as much.)
Giving Liszt items the ability to be timed is already in place with belt mode, so I’d just need to add the ability to keep track of both partially finished goals and total streaks. Seems worthwhile.
Gate is a small quick entry app for my phone. It’s a Go web app, and the name comes from it being a gateway to my other apps. I used to use Drafts, but when it switched to a subscription model I decided to do my own thing (which worked out well for me, since I was able to make other customizations I’d long been wanting to make).
Overview
The main screen looks like this:
Just a textbox and some basic controls at the bottom. When I tap the Submit button, it opens up a dialog with options for where to send the contents of the textbox (the payload):
Red buttons go to Liszt, blue to Vinci, green to Slash. And this is why I have the text-based payloads in all those apps.
How I use Gate
I have it saved as a PWA to the homescreen on my phone, nestled safely in my dock. I use it all the time. (I suppose I could use it from my laptop web browser as well, but I have Quill for that use case, so I never do.)
The future
I’m happy with the app itself. There are, however, some recent bugs with text controls in PWAs in iOS Safari where the keyboard either won’t come up or can’t be dismissed, but that’s out of my control. Hoping those get fixed soon (they didn’t crop up until sometime in the last year or so, I think).
First in the series introducing my personal productivity tools. Buckle up, this is going to be nerdy. And long.
Liszt is my to-do list app. Named after the composer, though I regret it a little since I butcher his name by pronouncing it lisht to differentiate it from the ordinary list. Heresy. The next version will have a better name, though. It’s a Python app running on Django.
Disclaimer: I don’t think this app is perfect. (Or any of the other tools I’ll talk about, for that matter.) I’ll describe things as they are, acknowledging here that there’s a lot of room for improvement.
Overview
This is what the dashboard looks like, populated with some dummy data:
And on mobile, where the controls move to the bottom for easier access:
The top bit is my stats panel, with the data pulled in from my other tools’ APIs. Daily writing counts, daily words left, total words written on the novel (all three from Storybook), daily pages left (from Bookshelf), and daily goals left (from Momentum). I’ll cover those tools later.
There’s also a slide-in menu on the side, with my most commonly used top-level lists:
Double-clicking on a list item opens up an edit panel, which also allows me to move the item to another list with some commonly used lists included as buttons (this whole panel is kind of clunky and needs improvement):
Payload syntax
The basic idea behind Liszt (and this is common to many of my apps) is the text-based payload, which enables some nice cross-app integrations (more on this when I cover Gate and Quill). Adding items looks like this:
A Liszt payload (the text entered into the add tray) has one item per line, with optional blank lines and optional list specifiers. If there’s no specifier, it’ll assume the current list if there is one, otherwise it’ll default to ::inbox. (I use the double-colon prefix to mark lists, with a slash to specify sublists.) Here’s a fuller example of the syntax, again with dummy data:
Process email :5
Review the merge requests :10
::work/notifications
Read up on Python futures ::: https://docs.python.org/3/library/concurrent.futures.html
Refactor [::work/notifications/refactor]
Write up the design doc
The first two items (which have belt-mode durations, I’ll explain those in a minute) would go into the ::today list (which is the dashboard list). The last three items would go into the ::work/notifications list, as pictured here:
Of these, the first line sets a subtitle by putting it after the triple-colon marker. I use this all the time.
The second line is a symlink of sorts, pulling in the top item from the linked list (different meaning here) and showing it in place, with the Refactor text shown as the subtitle. I used to use this more often but haven’t as much lately.
You can also see that this list has a child list (::work/notifications/refactor).
Belt mode
If an item has a duration marker included, that triggers belt mode (ala conveyer belts), as evidenced by the new bar at the top of the screen in the image above.
Brief backstory: I initially wrote an Electron app called Belt that did the same sort of thing, then a few months ago ported it to Go as a menubar app. Shortly after I finished that, I realized it would make much more sense in Liszt and brought the functionality in.
And what is that functionality? It’s just an easy way to time tasks from the list. When I hit Start, it switches into belt mode (also changing the favicon so I can tell that it’s running and turning on focus mode so I can only see the task I’m actively working on):
When the timer runs out, it plays a sound and brings up a panel allowing me to continue on to the next item in the list, stop belt mode, or add more time to the timer. (There are also keyboard shortcuts for all of this.)
How I use Liszt
On my laptop, I have Liszt open in Firefox as a pinned tab. On my phone, I have it saved to my homescreen as a PWA.
Every morning I go through the main lists and move the items I’m going to work on to the today list. I then work out of the today list the rest of the day, opening it often.
I use belt mode most days, primarily to help me stop avoiding tasks I don’t really want to work on (but that still need to be done).
The future
Lately I’ve grown enamored of the idea of storing data as plain text files in directories, rather than using an actual database like Postgres or Mongo. There are plenty of apps where this doesn’t make sense, but for personal, small tools, it works nicely, so I’m planning to migrate Liszt off SQLite to plain text, and I am very excited about it. Yes, I am that kind of a person.
While Django is fine (we use it at work and I love it), I’m planning to move to FastAPI, which I’m already using for Ditto and Arc. It’s a bit faster and feels more lightweight. I think in my mind I mostly use Django because of the ORM and admin; once I’ve given that up, the baby goes out with the bathwater.
I’m also looking forward to simplifying things, removing vestigial functionality, and sanding down as many of the friction points as I can.