Building the Bookmark Decay Webapp
The Journey So Far⌗
I’m building a web app/Chrome extension pair that allows your bookmarks to “decay” (grow old and disappear) over time; the intention is to encourage you to actually read those tabs you have open as opposed to letting them linger until your browser crashes. Now that I’ve completed the MVP for the extension, it’s time to work on the web app!
Missed another part of this project series? Check out all the Bookmark Decay posts here!
Current Progress⌗
So far we have an MVP for the Bookmark Decay Extension that saves your bookmarks via Chrome’s Storage API, and have hashed out how a website can request and retrieve those bookmarks. Excellent! Now I’m working on the webapp to display those bookmarks and add more features.
As a reminder, here’s my initial feature list created at the start of the project:
- Ability to add URLs to a list, ordered by last addition date
- “Bookmarked” URLs should be removed from the list after a set time
- Settable “decay” time (default 7 days, no more than 30)
- There should be a visible decay for each listed “bookmark”
- A “Recently Deceased” section for the last X many days of removed bookmarks; displayed in a different area from the main list
- Absolutely no permanent history - the point here is that when it’s gone, it’s gone (Recently Deceased section aside)
Technically that first bullet is completed the extension, though the implied feature - that the webapp also displays them in that order - has not been completed yet.
It’s tough - this review of requirements is finding my organized “big-kid org” side at odds with my scrappy “don’t get bogged down in the weeds” side. I don’t know which side is winning. 🤔
When you’re working on “seat of your pants” projects like this, which way do you lean?
Technical Decisions⌗
The first natural step of creating a webapp is, so often, fetching your data and creating a basic display. Since, after my last post, I could now successfully fetch my data, it was time to display it.
Let’s take a moment to look at the data model I’d already implemented for the extension and was now porting to the webapp:
interface BookmarkData {
bookmarkDate: Date;
title: string;
url: string;
}
interface BookmarkDecayData {
bookmarks: BookmarkData[];
decayRate: number;
recentlyDeceased?: BookmarkData[];
}
I’m not doing much yet with recentlyDeceased
yet (that’s the list of bookmarks that have recently fully decayed, an idea to be fully implemented later), so I’m keeping it as an optional parameter, and decayRate
is intended to be a rate in days (defaulting to 7). Everything else is probably pretty self-explanatory!
Technical Speedbumps⌗
Inevitably, there were some initial speedbumps in my implementation. 😅
Date
isn’t JSON serializable⌗
You might notice from the above data models that bookmarkDate
is of type Date
. I knew this was saving sucessfully in the extension from some very advanced debugging I’d done (console.log
), but for some reason the date wasn’t displaying correctly in the webapp. Strange.
Unfortunately, a little additional digging showed me that while the bookmarkDate
was being created successfully, saving to local extension storage was not working. While I knew that the Chrome Storage API saved only JSON serializable information, I didn’t know that Date
isn’t JSON serializable. How did I not know this?? I guess I’ve never tried to JSONify a Date before. This is what happens when you don’t write tests, kids.
Simple solution? Use Date.parse
to immedidately convert the date to a number that can be converted easily back to a Date in the webapp. Nice.
PostCSS & Tailwind with Vite⌗
I’ve started using Tailwind recently so thought I’d just keep up the trend. Setting it up with PostCSS, however, proved a little wonky with Vite. The Tailwind/PostCSS setup should have been simple, but building with Vite I was getting this error:
[Failed to load PostCSS config: Failed to load PostCSS config (searchPath: /Users/MyPath/bookmark-decay): [Failed to load PostCSS config] Failed to load PostCSS config (searchPath: /Users/MyPath/bookmark-decay): [ReferenceError] module is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '/Users/MyPath/bookmark-decay/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
ReferenceError: module is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '/Users/MyPath/bookmark-decay/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
Luckily the solution is exactly what’s outlined above: change the file extension from .js
to .cjs
.
Oddly neither the Vite nor Tailwind docs mentioned this at all, despite it being an easily reproducible error. This lack of clarity made me second-guess myself for awhile on how to fix it (“surely this would be documented somewhere”, I said to myself. “It wasn’t”, said the narrator), but changing the filenames was all it took to move on.
useEffect
double-fetch⌗
With the updates in React 18 that changed how useEffect
works and using StrictMode
in development mode, fetching the extension’s data with a useEffect
like we used to, back in the olden tymes (with an empty dependency array), useEffect
is always being called twice. Practically, this doesn’t matter - it’s not like I’m making a heavy resource call or something. However, it still shouldn’t be happening, and I’d really like to figure out the “right” way to fetch this data in React 18.
From the docs it sounds like I need to be doing some cleanup in my useEffect
? I don’t have a solution for this yet…maybe it’ll come later.
Next Steps⌗
Coming up next is continued work on the webapp - specifically, visual display of the decayed state! Woo, CSS!