I've been building a Notion clone + publishing tool. Here I'll post dev updates (as well as on Twitter).

Site: http://mindgarden.app

2023-08-08 Just added Stripe payments and created landing page for http://mindgarden.app, my Notion clone (note-taking app) + publishing tool

Next I will prob make a video for the landing page showing what this is + how to use it b/c I don't think it's very clear. Then will try to get it in front of people to get feedback to validate the idea and/or refine the product/marketing.

I know the landing page is ugly because it's not a separate page, but a file directly in the app. I thought it'd be cool to immediately load the app and allow people to instantly start using the product (also I personally hate landing pages, but maybe I need to get over that). This makes it tough to style this page, but perhaps can figure something out later.

I'm building out some sites using http://mindgarden.app. Currently http://codefire.dev (built a couple days ago) and http://jbernier.com use the MindGarden API, and planning to build out those sites + more to show what it's capable of.

Will this succeed? Who knows. In any case I use the product myself, so at least there's one happy customer. We'll see if I can turn this into something useful for others as well.

Let me know what you think / open to feedback. I'm new to the whole entrepreneurship thing - this is actually my first time adding payments to anything I've made (wish I'd done this sooner).

http://mindgarden.app

https://twitter.com/jeremybernier/status/1688814645598208000

jeremybernier🔗 | 8 months ago

2023-08-24 http://mindgarden.app can now edit files on your local device. Essentially a Notion-like markdown editor that works offline and doesn't require saving to any cloud. Installable as a desktop/mobile app (PWA). 100% free, no registration required

Next up going to make it simultaneously be able to sync offline and to the cloud (currently it's either/or). One of the cools things with that is that it allows turning a folder of documents into an API (how I'm powering blogs at http://jbernier.com and http://codefire.dev). Already can do this when saving to cloud, aiming to get this working with offline folders tomorrow

https://twitter.com/jeremybernier/status/1694756964994371902 (video in link)

jeremybernier🔗 | 8 months ago

2023-08-19 Saturday, 7:02pm (Osaka, Japan) - Offline mode finally in a usable state and nearly ready! Today did the following:

  • When file save to disk fails (Chrome bug I think, DOMException issue I mentioned yesterday), show a red X next to the file in the sidebar
  • Default to only having first depth of folders expanded (fixed the lagginess of defaulting to opening 1,500 files)
  • Upon selecting a file, make that file visible in the sidebar, expanding all parent folders. Unfortunately this still fails sometimes, so I'll need a better solution

There's still a ton of work left in the offline part:

  • Changing title doesn't seem to be successfully renaming files anymore (`DOMException: Failed to execute 'move' on 'FileSystemFileHandle': Failed to move links.md. A FileSystemHandle cannot be moved while it is locked.`)
  • When selecting file, scroll it into view in sidebar (mentioned above)
  • When creating a new file, show it in sidebar (will be easy now that I created state for that today, excluding the intermittent failing issue)
  • When editing a file, quickly changing to another file, then changing back, sometimes the old file content is there

Nice-to-haves:

  • Ability to create a new folder upon creating a project
  • Ability to not import all files inside a folder at once, loading them on demand

What's next

I want to ship this ASAP even if it's still buggy. The main obstacle now is that it's not integrated with the cloud yet, and thus would break cloud functionality which I need for my public sites served by MindGarden's API (jbernier.com, codefire.dev).

Goals for tomorrow

My goal for tomorrow will be to reintegrate cloud saving, but just disable it by default and allow the user to decide if they want to save to the cloud.

Really happy with the progress, and excited to get this out.

Random Zustand thoughts

  • I'm creating reducer functions for Zustand state when I need to reuse the functionality. So almost redux like, but without the action dispatching nonsense.
  • With state management libraries like Zustand, you want to update the state all at once when possible, this way you only trigger one re-render.
  • I'm thinking it's probably better to lean more towards many leaner Zustand state hooks, if only for code organization. But still figuring that one out
jeremybernier🔗 | 8 months ago

Managed to get folder imports working, including recursive importing all subfolders as well (breadth first search), and ignoring hidden folders.

The challenge was that importing was initially unbearably slow when testing on my folder with ~1,500 files, and then after that was fixed there was an infinite loop causing the page to freeze.

The slowness of the import was caused by me updating the zustand state individually while importing each file, which was likely triggering a bunch of UI re-renders on each import. The fix was to load all files at once in memory, and then update the zustand state in a single call.

I still don't know the exact cause of the infinite loop, but I know that it was related to some hacky code that had been rushed to get the feature out. Basically I needed a way to get all of a file's children, and the file data is structured such that every file has a parentId. Essentially I wanted a selector/cache on the Zustand state that maps a fileId to an array of its children. I had hastily created this in a suboptimal way, and I needed to redesign it.

Essentially there are two ways to create such a cache:

  1. A selector function that computes fileChildrenMap by iterating through every file, and this function recomputes on every file change (ideally only on relevant file change like a file's parentId changing, a new file being created, or a file being deleted)
  2. Storing fileChildrenMap as its own variable and imperatively updating it any time a relevant file change is made

Option #1 is the declarative solution and significantly easier in terms of code since you're just defining a function, and thus has much less chance of bugs due to not having to manually keep a separate cache object in sync. It's basically like what React is to UI's - specify a function of state -> HTML, and whenever the state changes React handles the UI updates..

The downside to #1 though is that without some magical React-like library optimizing things for you, you'll have to recompute the entire function on every file change - which is outrageously expensive if you have thousands of files and multiple updates per second. If you wanted to optimize this, then you'd need to 1. filter out updates that don't warrant a recompute, and/or 2. recompute more optimally, essentially implementing #2 from above.

I went with #2 because I absolutely need the performance here. I added another Zustand state variable called fileChildrenMap, and am updating that whenever a parentId changes or a file is added or removed. I don't love this, but I don't see a better alternative.

In an ideal world I could go with #1 and some fancy React-like library that'd just let me define a selector function and automatically handle optimize the state transitions. But I'm not sure if that exists, or if it does exist then I'm not sure if it'd actually end up simpler.

I could be off-base here and missing some obvious superior solution, these are just my stream of thoughts from tackling this just now. Please let me know if there's a better way to handle this.

Anyways now I'm able to import the 1,500 files, but performance is ungodly slow when I type into any file with this many files open. I'll tackle that next. Until this point haven't done any performance optimization, but now at the point where that is very high priority since if I can't use MindGarden for my 1,500 file folder, then this software is practically useless for offline mode.

2:49am update: Ok the unusable performance issue was an easy fix, just debouncing post updates. Also there was some unnecessary re-rendering going on.

Had one cool moment when I asked Bing Chat a question related to the Lexical text editor, and it regurgitated my StackOverflow answer.

Anyways the main issue now is that when I load my 1,500 file folder, some files just can't be saved to, and Chrome gives the following error:

Uncaught (in promise) DOMException: An operation that depends on state cached in an interface object was made but the state had changed since it was read from disk.

Seems it may just be a Chrome bug. This of course is not good. I'll have to see if I can figure out a workaround. Thankfully it seems to only affect a small number of files, I think that the OS is locking (might even be Windows Subsystem for Linux specific).

Less urgently I'm also getting console warnings like the following that I want to fix

[Violation] 'setTimeout' handler took 390ms

Although that file locking bug sucks, it may be a unique edge case, and regardless I'm very happy with the progress today and where it's at now. It's looking like I should be able to release offline mode soon. Will just need to reintegrate it with cloud syncing.

Random fyi - I wasted 30 minutes today not being able to login here to zsync, then testing locally and getting the same issue. After 30 minutes debugging this and losing my mind, I restarted Chrome and everything was fixed. Just a random reminder to not neglect the "turn it off and turn it on" debugging method.

EDIT: and the code snippet screwed up the layout of this post...gotta fix that

jeremybernier🔗 | 8 months ago

Was on vacation in Japan visiting my grandparents (my grandfather is 93!), but now getting back to the grind. Currently in Osaka, Japan.

My goal now is making MindGarden default to being an offline text editor that can save to one's file system, while also supporting syncing/saving to various other data sources.

Made solid progress on that today. I think in another day or two offline mode should mostly be ready, and then I'll need to integrate it so that it works simultaneously with saving to the cloud. Hoping to have offline mode launchable by next Wednesday or so, and then also with cloud saving re-integrated by this Friday (though that may be ambitious).

I love this project. I'm basically building the editor I wish existed. I wish we had an economic system where everyone could work on what they loved.