Webtrotion 2.4.x has landed with support for tab blocks and Heading 4.
I think one of the biggest reasons why I'm still maintaining this whole setup instead of using one of the prevalent Notion-to-website converters out there is: no one else is going to introduce and add components at the rate that I need them, and Notion has been shipping fast lately. I think this gives me a good idea of what is available, what can be done versus what can’t be done.
See, here are the headings in order, depicted by non-Taylor Swift songs.
Manchild
So Easy to Fall in Love
bad idea right?
And see, heading 4 is real now
Yes, Notion now supports full tiny-heading mode. Something to note here is that in Webtrotion, we set the page title as H1 and then move down every corresponding heading, so the H1 inside the page becomes H2, H2 becomes H3, and so on. So this H4 on the web page actually renders as H5.
And so are tab blocks
Say, if you have a Notion database where you keep adding the same URL 17 times (same link, different casing, extra tracking params, whatever), this script [a] will: pull all pages from the database, normalize the URL (so duplicates actually match), keep the oldest page, archive the duplicates.
And yes I’m showing both versions in tabs [b]. And the whole point with tabs is: I can keep the site clean without a ton of toggles and such.
The image shows how it looks on the notion pages.
pyjs
#!/usr/bin/env python3"""dedupe_notion_urls.pyArchives duplicate rows in a Notion database based on a URL property.Keeps the oldest page (by created_time) for each normalized URL.Setup: export NOTION_TOKEN="secret_..." python dedupe_notion_urls.py --db <DATABASE_ID> --property "External URL" --dry-runThen remove --dry-run to actually archive duplicates.Requires: pip install requests"""from __future__ import annotationsimport argparseimport osimport reimport sysfrom urllib.parse import urlparse, urlunparseimport requestsNOTION_VERSION = "2022-06-28"def normalize_url(u: str) -> str: u = (u or "").strip() if not u: return "" # make sure it parses (allow people to paste without scheme) if not re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*://", u): u = "https://" + u p = urlparse(u) # normalize host + strip tracking + strip fragments scheme = p.scheme.lower() or "https" netloc = p.netloc.lower() path = re.sub(r"/+$", "", p.path) # trim trailing slashes # drop query entirely (simple, aggressive dedupe) # (you can keep whitelisted params if you want) query = "" fragment = "" return urlunparse((scheme, netloc, path, "", query, fragment))def notion_headers(token: str) -> dict: return { "Authorization": f"Bearer {token}", "Notion-Version": NOTION_VERSION, "Content-Type": "application/json", }def query_database(token: str, database_id: str, start_cursor: str | None = None) -> dict: url = f"https://api.notion.com/v1/databases/{database_id}/query" payload = {"page_size": 100} if start_cursor: payload["start_cursor"] = start_cursor r = requests.post(url, headers=notion_headers(token), json=payload, timeout=60) r.raise_for_status() return r.json()def archive_page(token: str, page_id: str) -> None: url = f"https://api.notion.com/v1/pages/{page_id}" payload = {"archived": True} r = requests.patch(url, headers=notion_headers(token), json=payload, timeout=60) r.raise_for_status()def get_url_prop(page: dict, prop_name: str) -> str: prop = page.get("properties", {}).get(prop_name) if not prop: return "" if prop.get("type") != "url": return "" return prop.get("url") or ""def main() -> None: p = argparse.ArgumentParser() p.add_argument("--db", required=True, help="Database ID") p.add_argument("--property", default="External URL", help="URL property name") p.add_argument("--dry-run", action="store_true") args = p.parse_args() token = os.environ.get("NOTION_TOKEN") if not token: print("Missing NOTION_TOKEN env var", file=sys.stderr) sys.exit(1) pages = [] cursor = None while True: data = query_database(token, args.db, cursor) pages.extend(data.get("results", [])) if not data.get("has_more"): break cursor = data.get("next_cursor") # group by normalized URL groups: dict[str, list[dict]] = {} for pg in pages: raw = get_url_prop(pg, args.property) norm = normalize_url(raw) if not norm: continue groups.setdefault(norm, []).append(pg) duplicates = [] for norm, items in groups.items(): if len(items) <= 1: continue items.sort(key=lambda x: x.get("created_time", "")) keep = items[0] trash = items[1:] for t in trash: duplicates.append((norm, keep["id"], t["id"])) print(f"Found {len(duplicates)} duplicate pages to archive") for norm, keep_id, dupe_id in duplicates: print(f"- {norm}\n keep: {keep_id}\n archive: {dupe_id}") if not args.dry_run: archive_page(token, dupe_id) if args.dry_run: print("(dry-run) No pages were archived")if __name__ == "__main__": main()
// dedupe_notion_urls.js//// Archives duplicate rows in a Notion database based on a URL property.// Keeps the oldest page for each normalized URL.//// Setup:// export NOTION_TOKEN="secret_..."// node dedupe_notion_urls.js --db <DATABASE_ID> --property "External URL" --dry-run//// Then remove --dry-run to actually archive duplicates.//// Requires Node 18+ (global fetch).const NOTION_VERSION = "2022-06-28";function normalizeUrl(u) { u = (u ?? "").trim(); if (!u) return ""; // allow missing scheme if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(u)) u = "https://" + u; const url = new URL(u); url.protocol = (url.protocol || "https:").toLowerCase(); url.hostname = url.hostname.toLowerCase(); // trim trailing slashes url.pathname = url.pathname.replace(/\/+$/, ""); // drop query + fragment (aggressive dedupe) url.search = ""; url.hash = ""; return url.toString();}function parseArgs() { const args = process.argv.slice(2); const out = { property: "External URL", dryRun: false }; for (let i = 0; i < args.length; i++) { const a = args[i]; if (a === "--db") out.db = args[++i]; else if (a === "--property") out.property = args[++i]; else if (a === "--dry-run") out.dryRun = true; } if (!out.db) { console.error("Usage: node dedupe_notion_urls.js --db <DATABASE_ID> [--property \"External URL\"] [--dry-run]"); process.exit(1); } return out;}function headers(token) { return { Authorization: `Bearer ${token}`, "Notion-Version": NOTION_VERSION, "Content-Type": "application/json", };}async function queryDatabase(token, databaseId, startCursor) { const res = await fetch(`https://api.notion.com/v1/databases/${databaseId}/query`, { method: "POST", headers: headers(token), body: JSON.stringify({ page_size: 100, ...(startCursor ? { start_cursor: startCursor } : {}) }), }); if (!res.ok) throw new Error(`Query failed: ${res.status} ${await res.text()}`); return res.json();}async function archivePage(token, pageId) { const res = await fetch(`https://api.notion.com/v1/pages/${pageId}`, { method: "PATCH", headers: headers(token), body: JSON.stringify({ archived: true }), }); if (!res.ok) throw new Error(`Archive failed: ${res.status} ${await res.text()}`);}function getUrlProp(page, propName) { const prop = page?.properties?.[propName]; if (!prop) return ""; if (prop.type !== "url") return ""; return prop.url ?? "";}(async function main() { const token = process.env.NOTION_TOKEN; if (!token) { console.error("Missing NOTION_TOKEN env var"); process.exit(1); } const args = parseArgs(); const pages = []; let cursor = undefined; while (true) { const data = await queryDatabase(token, args.db, cursor); pages.push(...(data.results ?? [])); if (!data.has_more) break; cursor = data.next_cursor; } const groups = new Map(); for (const pg of pages) { const raw = getUrlProp(pg, args.property); const norm = normalizeUrl(raw); if (!norm) continue; if (!groups.has(norm)) groups.set(norm, []); groups.get(norm).push(pg); } const duplicates = []; for (const [norm, items] of groups.entries()) { if (items.length <= 1) continue; items.sort((a, b) => (a.created_time ?? "").localeCompare(b.created_time ?? "")); const keep = items[0]; for (const dupe of items.slice(1)) { duplicates.push({ norm, keepId: keep.id, dupeId: dupe.id }); } } console.log(`Found ${duplicates.length} duplicate pages to archive`); for (const d of duplicates) { console.log(`- ${d.norm}\n keep: ${d.keepId}\n archive: ${d.dupeId}`); if (!args.dryRun) await archivePage(token, d.dupeId); } if (args.dryRun) console.log("(dry-run) No pages were archived");})().catch((err) => { console.error(err); process.exit(1);});
Footnotes
This code is written by AI and not human verified. It’s just an example. Don’t run it.
Unfortunately the tabs below will not show the icons associated with the tabs.
Webtrotion 2.3: Multiple Authors, External Content, Gallery Layout, and Copy as Markdown
Webtrotion 2.3 adds five new features: multiple authors with dedicated pages, external content import from GitHub repos, gallery layouts for post listings, copy-as-markdown export, and cover images as hero backgrounds.
If you haven't looked at how Webtrotion has improved this year, then you should first check Webtrotion 2.0: Footnotes, Citations, and JSON5 Config out, which is a release that adds citations and footnotes to Webtrotion.
If you are here without knowing what Webtrotion is, you should see Introducing Webtrotion.
Webtrotion 2.3 adds multiple author support with dedicated author pages and custom bios, lets you pull content straight from GitHub repos while keeping Notion as the metadata source, adds gallery layouts for visual post listings, exports clean Markdown files with a floating copy button, and turns cover images into hero backgrounds. The External URL property now does two things: link-out posts (send readers to an external article while keeping your Notion metadata) and GitHub-sourced content (write in your repo, render on your site).
If you cloned Webtrotion before this release, double-check that your database has a URL property named External URL and a multi-select property named Authors, or create them (checkout details here) if you want to use the following features.
If you're struggling with the new configuration keys, you can use Webtrotion 2.x Config Generator which I created, so that you can copy the generated code, and use it directly in your github repo.
I spent the past four weeks with LLM coding assistants shipping five new features, mostly because I finally sat down and decided to just get themdone. Multiple author support is something I wanted for potential guest posts and collaborative blogs down the line, external content (especially custom components) from GitHub was something I wanted for my own playable interfaces (like LLM chat components), gallery layouts make visual content work better (and hopefully is used by someone), copy as markdown is for the LLM era where I'm pasting posts into Claude and ChatGPT all the time anyway, and cover backgrounds finally make those Notion cover images do something useful instead of just sitting there being read in Webtrotion but never used.
Some of these were harder than they looked (OG images for author pages, ugh, that was an interesting hassle to complete), and some were easier, like cover backgrounds took maybe an hour [a].
Most of these plans were drafted using Claude Code with a mixture of Opus 4.5 and Sonnet 4.5. I initially had them drafted by GPT 5.2 on Codex, but they weren't as good, so I modified them (and used Claude Code for that too!). The initial implementation was always done by Claude Code again with a mixture of Sonnet and Opus on the 4.5 series, and then fixes were done by Codex with the GPT 5.1 codex model on medium thinking as well as with Gemini Pro 3.0. The biggest reason why I preferred Codex over Gemini is it's really hard to control Gemini to not make edits to things, and I wish either Codex or Gemini shipped with a plan-only mode like Claude Code has, because why do they keep making edits to things without being asked? I realized that if I have a new feature in mind, the best way to get started is always Claude Code for me, for some reason.
Multiple Author Support
I went back and forth on how to implement this. The obvious approach was to make a separate Authors database and use Notion's relation property to link posts to authors, which would give maximum flexibility (each author could have a full page with custom layout, rich content, whatever), but it also means asking users to create a second database, get another database ID, manage relations, and I'd have to parse two data sources during the build, which just felt like too much complexity for what's essentially a guest post feature.
I also considered using a rich text property where people could just type names and color/link them however they want, but then I'd have to parse arbitrary text to extract author names, and building individual author pages from that would be a nightmare because how do you even tell where one author name ends and another begins if someone decides to get creative with their formatting.
So I went with a multi-select property, which is simple (add an Authors multi-select to your database, create options for each author, and assign them to posts), and you lose some flexibility in how author pages look, but honestly? Author pages aren't that important, this feature is mostly for guest posts anyway, so keeping it simple made more sense than building out this whole elaborate author management system that maybe three people would use.
// <<shiki-transform>>// constants-config.json5{ // === Site Information === // Basic details about your website used for metadata, RSS feeds, and SEO. "site-info": { // Your name or the name of your site's author. author: "Your Name", // Your custom domain name (e.g., "example.com"). Do NOT include "https://" or trailing slash. "custom-domain": "", // The sub-directory your site is deployed in (e.g., "/blog"). Leave empty if at root. "base-path": "", // === Multi-Author Support === // If the Authors property doesn't exist in your Notion database, settings below are ignored. // Personal URL for the site's author (from "author" field above). // Leave blank to link to author page (if enabled) or show as plain text. "site-author-url": "", // Avatar/photo URL for the site's author. "site-author-photo": "", // Configure author handling for posts with a multi-select Authors property in Notion. // When a post's Authors property is empty, the site's author (above) is used as fallback. authors: { // If true, builds /authors/ index and /authors/[author]/ pages. "enable-author-pages": true, // If true, only show bylines and build author pages when at least one post has a non-default author. "only-when-custom-authors": true, }, },}
How Author Pages Work
Set "enable-author-pages": true under site-info → authors in constants-config.json5, and Webtrotion builds an /authors/ index page listing all authors who have written posts, plus individual /authors/[author]/ pages for each author, showing their bio, photo, and all their posts. The author page layout is similar to tag pages, with the addition of author image on the left, name on right with post count, description on bottom [b] as shown below, and posts listed below.
If author pages are enabled, they also generate OG images, which was an interesting project and hassle to complete, which I did. The OG image generation pulls the author photo if you provided one and uses a two-column layout with the photo and description, similar to how the actual author page is structured.
If you set "only-when-custom-authors": true, bylines and author pages only appear when at least one post has a non-default author, so when all posts use the site author, the feature stays hidden (clean and minimal for solo blogs that don't need all this author attribution stuff).
Example author page on Webtrotion’s demo website which has a author-photo-url associated with it
Author Bios and Photos
In your Notion database, edit the Authors multi-select property and add descriptions to individual author options, using shortcodes to specify URLs and photos like this:
<<author-url>>https://example.com<<author-url>> <<author-photo-url>>https://example.com/photo.jpg<<author-photo-url>> Author description text goes here.
For the site author (the one specified in site-info → author in your config), use site-author-url and site-author-photo in your config instead of the shortcode system, because the site author isn't part of the multi-select, they're the fallback.
Example of the property and description in my Demo Webtrotion database.
Bylines
Bylines now show "By [Author Name]" in post listings, stream views, and hero sections, and the logic works like this: if you don't have an Authors property on your database because you're still using an older version, it always uses the site author from your config; if you have the Authors property but it's empty on a post, it falls back to the site author; and if the Authors property has values, it uses only those authors, ignoring the site author completely.
If an author has an external URL (set via the shortcode in the multi-select description), you'll see a little icon in the byline that links out, and clicking the author name takes you to their author page if enabled, or on the author page itself, if there's an external URL, you'll see a "Visit my website" button, which is good for guest posts where authors want to link back to their own site and get a backlink from here.
The above example renders this byline [c] in the listing.
Example with multiple authors, one with an external url (hence shows an icon), the other doesn’t.
Image Format Conversion
Satori, which is the library used for OG image generation, only accepts JPG, JPEG, or PNG, so originally if your featured image or author photo wasn't one of those formats, I just ignored it and you'd get a broken OG image or no image at all, but now I try to use Sharp to convert it to PNG first, so if you link to a WebP or AVIF or whatever, it downloads it, converts it, and uses it, allowing more flexibility and fewer edge cases where OG images just don't generate.
External Content
The External URL property is new in 2.3, and it does two things depending on what URL you give it.
If the External URL matches one of the GitHub folder prefixes you configured in external-content → sources, Webtrotion treats it as content to import. It downloads the files from that GitHub folder during the build, rewrites all the asset paths, and renders everything with the same typography, popovers, margin notes, cite-this sections you'd get with native Notion content. This works for any Markdown/MDX/HTML you have in a repo, for example Jupyter notebook outputs, R Markdown docs etc.
If the External URL doesn't match any of those folder prefixes, Webtrotion treats it as a link-out post. It still renders your Notion metadata (excerpt, tags, publish date, everything) in listings and on the post page, but clicking the post preview opens that external URL in a new tab instead of rendering your post content. Perfect for "I wrote this elsewhere" or "I was on this podcast" announcements where you want the post to show up on your site but the actual content lives somewhere else.
// === External Content ===// URL prefixes that tell the site to fetch GitHub content and render it locally.// Add public *parent* GitHub folder/file URLs (/tree/... or /blob/...) used in Notion's "External URL" field.// Each External URL in Notion should point to a post folder inside one of those parents.// If it matches a prefix, the build downloads and renders it as internal Markdown/MDX/HTML."external-content": { // Toggle external content pulled from remote sources via Notion's "External URL" field. Must be true to use features below. enabled: false, // Remote folders containing external content files. sources: { // Each subfolder is treated as a markdown post. Supports media, styled using corresponding Notion blocks. // Example: github.com/yourname/repo/tree/main/markdown markdown: "", // Each subfolder is treated as an MDX post. Supports custom components/media, lightly Notion-styled. // Example: github.com/yourname/repo/tree/main/mdx mdx: "", // Each subfolder is treated as an HTML post. Supports assets, no styling applied. // Example: github.com/yourname/repo/tree/main/html html: "", }, // Remote component folders MDX can import from (downloaded to src/external-content/custom-components). // Also usable for markdown codeblocks via `mdx-inject` shortcode above; supports media, no styling applied. "custom-components": "",}
Link-Out Posts
If you want your "Now" or "Linklog" collection to send people to an external article, you can set External URL to that destination (YouTube, another blog, a PDF, whatever), and Webtrotion still renders the Notion excerpt, tags, publish date, even comments count in listings, but clicking the preview opens the external link in a new tab instead of rendering your post.
If you already use Webtrotion, make sure your database has a URL property literally named External URL (reuse it for everything external and you're done, no need to create multiple properties for different use cases of External Content).
Example of how linked out content looks like
Pulling Content from GitHub Repos
Create a folder for each post (like markdown/my-post/) under those repo paths, drop your index.md or index.mdx or index.html plus any media inside that folder, then link your Notion page's External URL to that folder URL, and at build time (and in astro dev too, so local preview works), Webtrotion hits the GitHub Tree API, copies anything under those folders into src/external-posts/<folder>, mirrors assets under public/external-posts/<folder>, and writes a short hash as the cache key so if nothing changed, we skip the redownload and you don't waste time refetching stuff that's already there.
The frontmatter still comes from Notion (title, excerpt, tags, publish date), so your listings stay consistent even though the body comes from GitHub, which means you can have content living in multiple places but everything still looks unified (kinda?) on your site.
Markdown Posts
Markdown posts get parsed by the same block builder Notion uses, and I try to convert them as closely as possible to Notion block format. Callouts, toggles, tables, inline code, even the shortcode triggers like <!DOCTYPE html> <!-- inject --> behave exactly like their Notion equivalents, and footnotes, citations, interlinked sections are all extracted the same way, so the page still gets margin notes, backlinks, cite-this sections if you configured them.
I'm aiming for "looks indistinguishable from Notion," but if you drop raw HTML inside your Markdown you'll need to style it yourself because I can't parse arbitrary HTML and figure out what it's supposed to look like.
MDX Posts
MDX posts compile into real Astro components, and I only apply minimal base typography so you're free to design entirely custom layouts, import data, render charts, whatever, without fighting the Notion look. MDX is slightly messier and I wasn't perfect here, so if you want to contribute improvements, I'd love the help, because there are definitely edge cases I haven't handled well.
HTML Posts
HTML posts just get their <body> inner HTML dropped into the page after the asset URLs are rewritten. I don't touch the styling at all, whatever CSS you bundled with that HTML controls the entire experience, so use this for microsites or experiments where you already have complete markup somewhere else and you just want it to shove it on to your Notion-based site.
All three formats (Markdown, MDX, HTML) get included in RSS feeds and post listings, so external content shows up everywhere native Notion posts do. The real advantage here is that you can build completely custom HTML, interactive components, or experimental layouts that would be impossible in Notion, while still keeping everything organized in your Notion database with proper metadata, tags, and publish dates.
Notion Still Controls Metadata
Even when a post is 100% external, Notion is the source of truth for title, excerpt, author (from your Notion page title/fields), tags and collections (so listing filters, pinned posts, and RSS feed keep working), and publish date plus updated date (so Last Updated hero blocks tell the truth), which means you can mix and match however you want. Write a short intro in Notion, flip the "External URL" to a GitHub folder, and the page glues them together, or keep Notion blank and let the external Markdown [d] own the whole content, whatever works for your workflow.
Custom Components
The custom components repository slot is optional but powerful. Anything inside it ends up in src/components/custom-components for MDX imports and public/custom-components for assets, so external MDX posts and code blocks in your normal Notion based posts can import FancySlider from '@/components/custom-components/FancySlider.astro' and reuse the same interactive slider you already show on native posts.
To use custom components, set "external-content" → "enabled": true in your config. If you only want custom components and don't need to import external content, you can leave the sources object empty.
You can use these components directly inside Notion code blocks using the mdx-inject shortcode. Build a before/after image comparer, a "what's my setup?" component, whatever you want in your custom components repo, then drop it into any Notion post inside a code block. It compiles at build time, so there's still zero runtime JS unless your component adds it.
The simple "external HTML embed" shortcode you've been using didn't change, this release just makes it way easier to keep a shared library of components without copy/pasting them into multiple codeblocks every time you want to use something.
Important: The folder inside your GitHub repo must be named exactly custom-components (with a hyphen, not an underscore). This is a hard requirement because that's how the sync knows where to look. Don't name it components or custom_components or anything else, it won't work.
Gallery Layouts
Set "listing-view": "gallery" under collections-and-listings in constants-config.json5, and instead of the traditional vertical list with excerpts, you get a card-based grid layout. The layout shows three cards per row on large screens, two on medium, and one on small devices, and each card uses the FeaturedImage as a thumbnail, or if there's no featured image, it generates a colored block with the first letters of the post title, which keeps things visually consistent even when you forget to add images.
// <<shiki-transform>>// constants-config.json5{ // === Collections & Post Listings === // Configure how your posts are organized and displayed in lists. "collections-and-listings": { // The slug of the Notion page you want to use as your website's home page. "home-page-slug": "home", // If true, a section with the 5 most recent posts will be displayed on the home page. "recent-posts-on-home-page": true, // The number of posts to show on each page of a paginated list. "number-of-posts-per-page": 10, // The name of the Notion 'Collection' property value for pages in the main navigation menu. "menu-pages-collection": "main", // An array of 'Collection' names where posts should be displayed in full on list pages. "full-preview-collections": ["Stream"], // If true, any post with a slug starting with underscore will be hidden from lists. "hide-underscore-slugs-in-lists": true, // Listing view style for post lists. // "list" (default): Traditional vertical list with title, date, excerpt, and tags. // "gallery": Card-based grid layout with FeaturedImage thumbnails. "listing-view": "gallery", },}
Tags are capped at three visible (if a post has five tags, it shows three and then "+2" so card sizes stay consistent and you don't end up with weird wrapping), and the cards have a bouncy thing when you hover over them.
This is a global setting, so it affects everywhere: collections that aren't stream-based, recent posts if you have that enabled, tag pages, author pages, everything shows gallery layout instead of the traditional list view. You can still use "listing-view": "list" which is the default for traditional vertical lists, and I'm not making this configurable per collection [e] for the foreseeable future.
Gallery view on the demo website if I set listing-view to gallery. It uses the featured image where available, and if there's no featured image, it generates a card in the accent color with the first letters of the title. Featured images get auto-optimized, and if they're originally WebP, they get converted to PNG for the OG image.
Copy as Markdown
This feature does two things: first, it generates an index.md file alongside the index.html for every post, so at posts/my-post/ you get both the rendered HTML and a clean Markdown version, which means you can curl that Markdown file if you need to reference a post in Claude Code or use it for documentation or whatever (I don't know why you'd need this but just in case you do, it's there).
// <<shiki-transform>>// constants-config.json5{ // === Block Rendering & Visual Display === // Global settings for how Notion content blocks and media are displayed. "block-rendering": { // If true, clicking on an image will open it in a full-screen lightbox/modal view. "enable-lightbox": true, // If true, embedded content like tweets will span full width on smaller screens. "full-width-social-embeds": false, // If true, images from Notion will be optimized (converted to WebP, resized). // Warning: Can increase build time but helpful for site size. "optimize-images": true, // If true, all pages/posts are converted to markdown format and saved alongside HTML. "process-content-to-markdown": true, },}
Second, it adds a floating copy button on every page except your home page, and clicking it copies the Markdown version straight to your clipboard, ready to paste into Claude or ChatGPT or whatever LLM you're using.
Turn on "process-content-to-markdown": true under block-rendering in constants-config.json5 (this is on by default), and during every build, Astro runs a markdown-exporter integration that grabs the .post-body HTML for each rendered page, removes scripts, popover templates, margin-note helpers, basically anything that wouldn't make sense in a Markdown mirror, rewrites links and image sources so they're absolute, and converts the result with Turndown plus GFM, folds in footnotes and citations from the cache, and slaps a YAML frontmatter block on top with title, slug, canonical URL, timestamps, tags, excerpt, author, external URL.
The exporter caches output in tmp/markdown-cache, so if you rebuild without touching a post, it reuses the previous .md and doesn't waste time regenerating the same content.
Why? Because I copy content into LLMs all the time. I have a changelog for a library and I ask "what do I need to change given this changelog?" and paste it into ChatGPT or Claude, and if I copy the HTML, important stuff might get lost because sections weren't expanded or popover content didn't get included or whatever. The Markdown export gives me a clean version with everything flattened out. I made the output as clean as I could, but if you find edge cases where the conversion is weird and you have a patch in mind, let me know.
Set "cover-as-hero-background": true under theme in constants-config.json5, and when a post has a cover image, Webtrotion displays it as a low-opacity tinted background behind the hero section (title, date, tags), with the text staying readable thanks to an overlay.
// <<shiki-transform>>// constants-config.json5{ // === Theme Customization === // Customize the look and feel of your site, including colors and fonts. theme: { // Currently only the classic theme is supported. preset: "classic", // Define your site's color palette. // Colors MUST be in RGB format, with spaces between numbers (e.g., "255 255 255"). colors: { bg: { light: "249 249 249", dark: "28 30 32" }, text: { light: "34 39 42", dark: "255 255 255" }, link: { light: "85 123 118", dark: "199 200 203" }, accent: { light: "203 41 65", dark: "41 188 136" }, "accent-2": { light: "17 17 17", dark: "237 237 237" }, quote: { light: "203 41 65", dark: "205 254 183" }, }, // Just specify the font names - no URLs, no escaping needed! // Automatically loads weights [400, 500, 600, 700] and styles [normal, italic]. "fontfamily-google-fonts": { // Main body/UI font - can be sans-serif OR serif. "sans-font-name": "Roboto", // Monospace font for code blocks. "mono-font-name": "Roboto Mono", }, // Enable using the cover image as the hero background (with overlay for text readability). // When enabled, displays a low-opacity tinted banner behind title/date/tags. "cover-as-hero-background": true, },}
This applies in two places: stream view when you're using full post previews, and individual post pages, so anywhere there's a hero section, the cover image can now be the background instead of just sitting there unused.
Cover images were always being read from Notion but never used. When this codebase started (forked from the original Astro Notion blog), the original developer was making use of them, so I figured I should too, and instead of just appending the image at the top like a banner, which looks kind of not my style, this provides a tinted background behind the hero section, which feels more editorial, like you're reading a magazine feature instead of a plain blog post.
The height is defined by the content length, not the image size, so it doesn't matter if your cover image is portrait or landscape or square, it just fills whatever space the hero content needs. The opacity gradient moves from 50-70% top to bottom, and the tint opacity itself is set to 0.4, so I tried to make sure everything stays readable even with busy background images, but if you pick a really chaotic cover image you might need to adjust things.
If a post doesn't have a cover image, the hero section renders normally (no cover, no background, simple as before).
Cover background in full post page
The above is an example of cover rendering on a normal post page and you can see that I tried my best to make the text still be pretty visible.
And this is the cover rendering on a stream-based page, so it still covers the same area in the stream view for a collection.
Example of this post with Notion page cover as the background, shown in dark mode. I tried to make the tint match the background color so readability isn't heavily impacted under any theme.
Optimizations and Pagespeed Improvements
After I was done with all these feature implementations, I ran pagespeed, and was disappointed to see a significant decline in scores. So, here are the things I did to bump it up.
Pagespeed scores [f] on the left (before optimization) vs on the right (after optimization)
Cover Image Background Optimization
I wasn't planning to optimize this, but after implementing the basic cover background feature, I realized cover images load immediately and can be pretty large files, so I spent a couple more hours adding progressive loading.
I tried generating two versions: a tiny quality 5 placeholder that shows up instantly with just enough pixels to show color and rough composition, and a full quality 85 version that gets swapped in later. The swap happens on idle using requestIdleCallback, or falls back to a 250ms timeout after page load if the browser doesn't support it, so the full resolution image downloads in the background without blocking anything important. If the full image fails to load for any reason, the placeholder just stays there, which is fine because it's already visually similar enough that most people won't notice.
Then I ran PageSpeed and checked LCP, and it didn't work. The placeholder image wasn't being considered LCP — it was the full image.
So I tried multiple AI coding models, aka, Gemini, Claude, and Codex but none of them could solve the problem correctly. They even suggested checking the user agent and not swapping the image if it was Lighthouse or PageSpeed, which is extremely stupid. So I went back to Google like a caveman and found this:
This is such a well-written article, and it boils down to two things. First, I was setting the quality to 5, but that's not a high enough threshold. The BPP threshold [^ft_what-is-bpp] should be at least 0.05 to count as LCP, so I set the quality to 20 or 25. Second, the strategy uses CSS's multiple backgrounds feature: high-res on top, low-res underneath. The browser loads both, but the low-res shows first and counts as LCP if it meets the BPP threshold. When the high-res loads, it automatically covers the low-res with no JavaScript needed.
And this worked! I decided to use 5 vs 65 qualities and they no longer show up in LCP!
From the link, the second restriction we need to get around is the bits per pixel (BPP) threshold announced in Apr 2023. Again, to stop people gaming the system, Chrome decided that only images of a certain quality (or entropy) will be considered as your LCP element. This prevents people using incredibly low quality images in order to register a fast LCP time.
Well, you might think that was the end of my problems, but apparently it wasn't because some of the Unsplash images decided to be at least 5MB and Notion does none of the optimization on its own end. So I decided to do some optimization myself, and even with quality set to 65, I was getting images ranging from 0.5MB (for the LQIP) to 1.5MB (for the full-quality image), which was diabolical for an image that loads immediately as LCP. Given that this site uses a middle-aligned layout with reasonable max-width for reading, I decided that 2100px max width is good enough for cover images. I didn't do this optimization for other images because someone might want to host their gallery on this or whatever. But for cover images, 100% I'm going to cap them at 2100 pixels wide, which accounts for 3x DPR on a ~700px layout (700×3 ≈ 2100).
Other Optimizations
Beyond cover images, I made several other performance improvements:
Notion icons are now cached: Before this, I was just linking out to Notion's page for default SVG icons, which meant the browser had to make external network requests to Notion's servers every time someone loaded a page with icons. Now I'm actually downloading and caching them locally during the build, so if you use the same icon across multiple posts, it only gets fetched once and then reused everywhere from your own domain. This shaves off a bunch of network requests for visitors and makes page loads faster, especially if you have a lot of pages with a lot of icons.
OG images cached for authors, tags, and collections: I was already caching OG images for posts, but author pages, tag pages, and collection pages were regenerating their OG images on every build even when nothing changed. Now all of them are cached properly, and I'm relying on the data source's last edited time to decide whether to regenerate those OG images or not. This makes incremental builds way faster since OG image generation with Satori isn't exactly quick.
Font format optimization: Astro's experimental font API was including both woff and woff2 formats by default when pulling from Google Fonts, but modern browsers support woff2 across the board now, and it's smaller and better compressed. I created a provider wrapper over the Google Font provider that defaults to only woff2 (unless no woff2 is found), cutting down on font file sizes and eliminating redundant font downloads.
Search CSS optimization: The pagefind CSS (the styles for the search functionality) was being included in the critical path where it didn't need to be unless someone was actually using search, which was blocking initial render. Now it only loads the first time someone clicks the site search button, presses ⌘+K, or uses a query (q) parameter, so most page loads don't pay that CSS cost at all and initial render is faster.
Cover background quality adjustment: After implementing the dual-image loading strategy for cover backgrounds, I realized the full-resolution image quality was set to 85, which was unnecessarily high for a background image that's tinted and partially obscured anyway. I dropped it to 65, which cut the file size significantly without any noticeable visual difference behind the tinted overlay. I also had to fix an issue where external cover images from Unsplash (which Notion uses as a cover provider integration) weren't being downloaded and optimized. The problem was that my code was checking if the file extension was an image format, but Unsplash URLs include query parameters like https://images.unsplash.com/photo-1663789049904-a1e1e216c829?ixlib=rb-4.1.0&q=85&fm=jpg&crop=entropy&cs=srgb, so the extension check was failing. I added special handling to look at the fm= (format) parameter in the query string to determine whether something can be downloaded as an image, if the url was from Unsplash, so now Unsplash cover images get pulled down during build and optimized properly.
Bluesky comments lazy loading: The Bluesky comments component was firing its API calls immediately when the custom element connected to the DOM, which meant it was happening during initial page load, if there was a cover image (because that takes long to load) even though the comments are way down at the bottom of the page. I had to fix this issue, and the solution now works in four stages: first, wait for page load by checking window.onload (or immediately if document.readyState is already complete); second, wait for the browser to be truly idle using requestIdleCallback (with a setTimeout fallback for browsers that don't support it); third, only then attach the scroll, mousemove, and other interaction listeners; and fourth, when the user finally interacts, trigger the initialization which starts the IntersectionObserver and the background fetch. This keeps comments from blocking initial render and lets the page become interactive faster.
KaTeX fonts: I ran into a font loading time problem with KaTeX fonts. I discovered that KaTeX now, as of two months ago provides an option to use swap as the default method. I asked multiple AI models to fix this, and they suggested complicated solutions but never thought to check for this simple option. I had to Google it myself, again, like a Neanderthal. Here's the GitHub issue that led me to the fix.
This is the last release for 2025, and early next year whenever I hyperfocus on improving Webtrotion again, I'm going to be focusing on two things:
Theme Presets
I added a theme → preset variable in the config but it doesn't do anything yet. Planning to actually ship theme presets in 2026, right now I'm thinking Scholar (clean, academic, serif-heavy with good typography for long-form reading), Neon Techno (dark backgrounds, bright accent colors, that cyberpunk aesthetic), Pastel/Barbie (soft pinks and purples, rounded corners, very 2024), Playful (squiggles, hand-drawn borders, asymmetric layouts), and New Brutalist (raw, minimal, high contrast, no bullshit).
You can already customize colors, fonts, and cover-as-hero backgrounds in the config, so I'm trying to figure out what else should change per theme. Layout spacing? Border styles? Button shapes? Heading treatments? If you have opinions on this, open an issue or discussion on the repo.
Autogenerated "More Like This" Section
This one's pretty far out into the future, maybe before custom CMS but I don't know when it'll happen. The idea is to embed posts, rely on tags, and calculate a similarity score with weighted importance on tags, title, and embedded content, then auto-add a "More Like This" section at the bottom of each post.
The mechanics of doing this on a static site are interesting and frustrating. I really do not want to ask for another key to be set up in the Github environment, and then I would need to spend time especially figuring out caching and making it work with GitHub Actions. I want to try implementing it just to see how good it is from an LLM/small embedding model perspective and whether it's even possible, and to see if it would actually be valuable for the kind of people who use Webtrotion.
Maybe a Custom CMS?
This is pure speculation, but maybe by next year there's a custom CMS that covers everything I use Notion for and I can de-associate this project from Notion entirely. The dream is being able to use terminal apps like Claude Code to write in Markdown without actually writing long-form Markdown myself, buttons should be able to compose in rich text, something that gives me Notion's WYSIWYG editing but stores everything as clean Markdown that LLMs can edit directly.
Maybe coding LLMs get powerful enough to build something like that just for me, but realistically that's 2028 territory, not 2026, so I'm not holding my breath.
I'm planning to write a post about why I keep using Notion as a CMS despite all its quirks and limitations, and once I've written it, I'll link it here too.
For now, enjoy the some sprinkled new features and the "bring your own content" workflow. Happy holidays and happy token burning on your cute-sy (and fun!) side projects 😃.
Footnotes
Well I am laughing Dec 17, 2025, because I decided to optimize loading cover images, so I ended up spending 2 more hours on that. 🤷♀️
This is done using the shortcodes defined in the multi-select property description. The author page extracts the URL using the <<author-url>> delimiter on both ends of the url, the photo using <<author-photo-url>> on both ends of the image url, and treats any remaining text as the author description
You see how there is an icon in this case? It is because I have set a URL for this author using a shortcode and that icon links out to the URL where is the name links to the author page.
This feature only works for markdown front matter and isn't comprehensive, so I recommend using Notion-based metadata instead
I could have used a shortcode in the collection description to do this but I think that is a waste of time and that much configurability goes against the feeling of what webtrotion is.
I only care about mobile pagespeed scores because desktop scores didn’t ever slip below 95, given how optimized astro already is!
A UI interface made by Gemini 3 which can generate the constants config that you can directly copy paste into your forked github repo rather than you needing to fiddle with JSON5 yourself
Now that Webtrotion supports direct font names, has simplified font configuration, you might want to test how different fonts would look on your website. To do this, open the dev console [a] on any Webtrotion powered website, like this one, and paste this snippet into the console tab after changing the sans and mono font names to the ones you want to try [b].
(function() { const sans = 'Inter'; // change to any Google Sans font const mono = 'Geist Mono'; // change to any Google Monospace font // Create Google Fonts URL with all weights + italics const googleFontsURL = `https://fonts.googleapis.com/css2?family=${sans.replace(/ /g,'+') }:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=${mono.replace(/ /g,'+') }:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap`; // Inject link tag for Google Fonts const link = document.createElement('link'); link.href = googleFontsURL; link.rel = 'stylesheet'; document.head.appendChild(link); // Override CSS variables globally const style = document.createElement('style'); style.textContent = ` :root { --font-sans: '${sans}', system-ui, sans-serif !important; --font-mono: '${mono}', monospace !important; --default-font-family: '${sans}', system-ui, sans-serif !important; --default-mono-font-family: '${mono}', monospace !important; } `; document.head.appendChild(style); // Apply sans font to body immediately document.body.style.fontFamily = `var(--font-sans)`;})();
Footnotes
The Chrome developer console shortcut is Ctrl + Shift + J for Windows/Linux and Cmd + Opt + J for Mac.
Note that font names are case-sensitive, so using geist mono won't work since the font name is Geist Mono.
Webtrotion 2.0: Footnotes, Citations, and JSON5 Config
Added footnotes and BibTeX citations to Webtrotion. Now you can add all your tangents and cite everyone on the internet without making readers sit through it. They can just hover/click if they care.
If you are here without knowing what Webtrotion is, you should see Introducing Webtrotion.
Webtrotion 2.3 adds multiple author support with dedicated author pages and custom bios, lets you pull content straight from GitHub repos while keeping Notion as the metadata source, adds gallery layouts for visual post listings, exports clean Markdown files with a floating copy button, and turns cover images into hero backgrounds. The External URL property now does two things: link-out posts (send readers to an external article while keeping your Notion metadata) and GitHub-sourced content (write in your repo, render on your site).
Webtrotion (github link) now renders proper footnotes and supports inline citations with BibTeX (for details, take a look at Introducing Webtrotion and Making Webtrotion). You get hover previews and backlinks, and you keep the optional sitewide "All Footnotes" index (as before). Everything renders at build time and is controlled from a single config file, which you can now easily create/modify through a UI interface as well, thanks to Gemini 3.
If you cloned Webtrotion before this release, double-check that your database has a URL property named External URL and a multi-select property named Authors (to support 2.3 release), or create them (checkout details here) if you want to use v2.3 featurs.
If you're struggling with the new configuration keys, you can use Webtrotion 2.x Config Generator which I created, so that you can copy the generated code, and use it directly in your github repo.
I finally shipped two things I've been wanting since day one: footnotes[a] and citations[1]. These have been on my list forever, and I'm really happy to have them working now. These are the first and probably only pseudo blocks I wish Notion had [b], so I ended up creating them myself [c]. They work across posts and pages, play nicely with dark/light themes, and slot right into Webtrotion's interlinked content system. No client-side JS btw. Just clean static output.
Why I Built This
Footnotes make the page calmer. They keep the main thread clean while preserving context and side-quests. And I do have many side quests. Citations are the other half: you get to attribute ideas properly, link sources, and still keep readers in the flow with hover previews and a clean reference section at the end. You could just use footnotes for citing stuff, but I like my bib files, so I built proper citation parsing too.
All of this renders statically during the build, which is how I like it.
Moving to JSON5 Config
One more thing: Webtrotion's configuration file now uses JSON5 format. This means you can finally have comments directly in your config file [d]. The file is now called constants-config.json5 instead of constants-config.json.
Breaking change: This is a new version and isn't backwards compatible. You will have to fill in the keys in the new constants-config.json5 file. Don't try to just copy-paste your old values because the key names have changed too.
JSON5 lets you:
Add inline comments to explain what each setting does
Use trailing commas without breaking things
Write cleaner, more readable config files
The configuration structure has also changed significantly. Instead of a flat structure, settings are now organized into logical sections like notion, site-info, collections-and-listings, theme, tracking, comments, block-rendering, shortcodes, and auto-extracted-sections. This makes the config much more navigable, especially as the file has grown to support more features.
Main fonts: You had to provide a combined-url with the full Google Fonts URL, escape spaces in font names (Roboto\\ Mono), and pick between sans/serif/mono
OG image fonts: You had to provide title-font-url and footnote-font-url with full Google Fonts URLs
New way:
Main fonts: Just type the font name. "sans-font-name": "Roboto" and "mono-font-name": "Roboto Mono". No URLs, no escaping. Webtrotion auto-loads weights [400, 500, 600, 700] and styles [normal, italic] [e].
OG image fonts: Just type the font name and weight for both the title and the footnote. Example: "title-font-name": "Kreon" and "title-font-weight": 500.
The system handles all the URL generation and loading for you now.
How Footnotes Work in Webtrotion
Handling footnotes in digital writing can be a pain. You want the context without derailing the paragraph. Webtrotion's build‑time footnote system lets you choose the authoring style that fits your workflow.
Configuration: footnotes
Here's the relevant section from constants-config.json5:
// === Footnotes Configuration ===// Settings for both manual (sitewide) and automatic (in-page) footnotes.footnotes: { // The slug for a single, dedicated page that contains all footnotes for the entire site. // This is a legacy method for manual footnote management. "sitewide-footnotes-page-slug": "_all-footnotes", // Settings for the modern, automatic in-page footnote system. "in-page-footnotes-settings": { enabled: true, // Defines where the system should look for footnote content. // Important: Only ONE of these source options can be 'true' at a time. source: { // Looks for definitions at the end of a text block, like `[^ft_1]: My footnote content.`Sepearate it by two soft new lines (using shift+enter). This is the method that will work when you export Notion content to Markdown. "end-of-block": true, // Uses inline LaTeX-style footnote command: \footnote{content here}. Use \{ and \} for literal braces (useful for LaTeX compatibility). "inline-latex-footnote-command": false, // Treats the first n set of child block(s) indented under a parent block as the footnote content. (n = number of footnote markers in the parent block) "start-of-child-blocks": false, // Uses Notion comments on a block as footnote content. // Requires granting "Read comments" permission to your Notion integration. Else falls back to 'end-of-block' method. "block-comments": false, // Not yet implemented. "block-inline-text-comments": false, }, // The prefix for footnote markers in your Notion text. e.g., `[^ft_1]` for a prefix of `ft_`. "marker-prefix": "ft_", // If true, a collated list of all footnotes will be automatically generated at the bottom of the page. "generate-footnotes-section": true, // (Recommended) On large screens, displays footnotes in the margin. On smaller screens, it falls back to a popover. "show-in-margin-on-large-screens": true, },},
Key Settings for Footnotes
sitewide-footnotes-page-slug: Legacy manual collector (e.g., _all-footnotes). The in‑page system is recommended for most occurences.
enabled: Toggle in‑page footnotes.
source: Pick exactly one of the three modes as defined here.
marker-prefix: Default is ft_. Change to note_ if you want markers like [^note_1].
generate-footnotes-section: Auto‑collate the footnote section at page bottom (also generates backlinks with popovers to where the footnotes came from).
show-in-margin-on-large-screens: Shows footnotes in the margin on large screens, falls back to popovers on smaller screens. Set to false for popovers everywhere.
Pick One Source Mode
Configure a single source under footnotes → in-page-footnotes-settings → source in constants-config.json5. Only one should be true at a time.
The Markdown‑friendly way (end-of-block)
Portability first. If you export to Markdown later, your footnotes will still make sense. Put the definitions at the end of the block, separated by two newlines (made using Shift+Enter).
In Notion, this is some text with a footnote[f]. It's a really interesting topic.
In Notion, this is some text with a footnote[^ft_desc]. It's a really interesting topic.This is the content of the footnote. It can be long detailed and have links.
The LaTeX inline way (inline-latex-footnote-command) Uses inline LaTeX-style footnote command: \footnote{content here}. Use \{ and \} for literal braces (useful for LaTeX compatibility) inside the footnote content. Make sure that the footnote command is not inside inline code [g]. This method allows you to write footnotes directly inline with your text using LaTeX syntax, which can be particularly useful if you're already familiar with LaTeX or planning to copy your content to LaTeX format later.
The structured way (start-of-child-blocks)
Prefer a tidier Notion outline or want footnotes with varying kinds of content? Indent child blocks as the footnote content. For n markers in a block, the first n child block(s) become the note. These child blocks can contain any block type and can have their own nested children.
This is how you’ll write it. Some text with a first footnote [^ft_a1] and a second footnote [^ft_b1].
This is the content of the footnote, written in a child block.
And this will be part of the footnote
This is the second footnote
And this child is not a footnote.
Note: The above will not be rendered because my config is set to end-of-block.
The Notion‑native way (block-comments)
Permissions: Your Notion integration needs "Read comments" permission for this mode to work.
Use Notion comments as your footnote source to keep the notion document even more cleaner and you can have attachments in your footnotes this way. Highlight text/block [^ft_a2] with the marker definition [h], add a comment, and start it with your marker definition. Comments can include attachments, which will be rendered in the footnote.
ℹ️
Note: The above will not be rendered because my config is set to end-of-block.
Choose Your Own Marker Prefix
The marker-prefix setting is just a namespace for your footnote markers. By default it's ft_, so your markers look like [^ft_1], [^ft_note][i].
Why have a prefix at all? Because if you're using standard Markdown footnote syntax elsewhere (like [^1]), you don't want collisions. The prefix keeps Webtrotion's footnotes separate. You can change it to note_ or fn_ or anything you want. Just keep it consistent across your site.
Markers inside code blocks or inline code are not processed. Footnote marker names can only contain letters, numbers, underscores, and hyphens (e.g., [^ft_my-note-1]).
Maybe Show a Collated Footnote Section at The End of Page
Set generate-footnotes-section to true and Webtrotion will automatically collect all your footnotes and dump them in a neat little section at the bottom of the page. It's like endnotes in a website or a white paper. Each one gets a popover based backlink so readers can jump back to where the footnote was mentioned.
If you set it to false, footnotes only exist as inline popovers or margin notes. No bottom section at all. I keep mine on because some people like to scroll down and read all the tangents at once.
Choose Footnote Display Mode
Set show-in-margin-on-large-screens to control how footnotes appear.
Margin notes on large screens (show-in-margin-on-large-screens: true): This is the option I use. On desktop or tablets with enough space, footnotes appear in the right margin — like marginalia. On phones, they fall back to popovers because there's no margin space.
Always popover (show-in-margin-on-large-screens: false): Every footnote marker, when hovered (on larger screens) or clicked (on smaller screens), shows a little popover overlay. Simple, consistent across all screen sizes.
The Older Sitewide Footnotes Page
This is the legacy method, from before I built the automatic in-page system. You'd create a single dedicated Notion page (like "All Footnotes") and manually add your footnote content there, then reference it from other pages. Set the slug in sitewide-footnotes-page-slug.
The in-page system is way better — it's automatic, it's per-page, and you don't have to maintain a giant footnote index manually. I'm keeping the sitewide option around for backwards compatibility, as well as for situations where you need to reference the same “footnote” at multiple places.
These footnotes won’t be collected at the bottom. They are ideal for when you just want popovers for something. Like, please buy me a coffee.
Citations with BibTeX
For students, researchers, and anyone who writes with sources, the citation system should be useful. Manage citations in a standard .bib file and have them rendered with in-text markers and an optional bibliography section. All processing happens at build time, and you get hover or click previews just like with footnotes.
For example, I can cite where the tufte style footnotes came from, they originated here [1]. Note how if the citation has a URL, which it does in this case, it also renders the URL as a clickable link.
This is how it renders:
Showing URL in popover and bibliography section if exists in bibtex
Configuration: citations
Here's the relevant section from constants-config.json5:
// === Citations Configuration ==="auto-extracted-sections": { citations: { // If true, a "Cite this page" section will be added to each page. "add-cite-this-post-section": false, // Configure how BibTeX citations are extracted and processed. "extract-and-process-bibtex-citations": { // If true, enables BibTeX citation processing. enabled: false, // Supported .bib file sources: GitHub Gist, GitHub repo file, Dropbox, and Google Drive public share links. "bibtex-file-url-list": [ // Example: "https://gist.githubusercontent.com/username/gist-id/raw/file.bib" ], // The pattern for in-text citations. Only one format is supported at a time. "in-text-citation-format": "[@key]", // or "\cite{key}" or "#cite(key)" // The citation style for the bibliography. Only one format is supported at a time. "bibliography-format": { "simplified-ieee": true, // For ICML/NeurIPS style apa: false, // For ACL-style academic writing }, // If true, a collated bibliography section will be automatically generated at the bottom of the page. "generate-bibliography-section": false, // If true, show citations in the right margin on large screens (≥1024px). On smaller screens, citations will display as popovers. "show-in-margin-on-large-screens": false, }, },}
Key Settings for Citations
add-cite-this-post-section: Auto‑add a "Cite this page" block with a pre‑formatted reference, so that people can add your post to their own bibtex file.
enabled: Turn on BibTeX processing.
bibtex-file-url-list: One or more .bib file URLs. Supports GitHub Gist, GitHub repo files, Dropbox, and Google Drive public share links. Note: Google Drive and Dropbox links don't provide public last-updated timestamps, so these files will be fetched and processed on every build.
in-text-citation-format: Choose your marker pattern. Supports [@key] (pandoc), \cite{key} (LaTeX), or #cite(key) (Typst).
bibliography-format: simplified-ieee (for ICML/NeurIPS style) or apa (for ACL-style academic writing).
generate-bibliography-section: Auto‑add a bibliography to the page.
show-in-margin-on-large-screens: Shows citations in the margin on large screens, falls back to popovers on smaller screens. Set to false for popovers everywhere.
Add "Cite this page" Section
Set add-cite-this-post-section to true if you want Webtrotion to automatically add a "Cite this page" section at the end of each page. This generates a pre-formatted BibTeX entry for the page itself, making it easy for readers to cite your work in their own papers or bibliographies.
The generated entry includes your page title, author (from your config), the publication date, and the URL. Handy if you're publishing research notes, technical articles, or anything citation-worthy. I keep mine off because most of my posts are informal, but if you're writing academic content, turn it on.
Enable BibTeX citation processing
This is the master switch. Set enabled to true under extract-and-process-bibtex-citations to turn on the entire citation extraction system. If this is false, none of the citation processing happens — no parsing, no formatting, no bibliography generation. Everything else in this section only matters if this is true.
Simple on/off toggle. Turn it on when you need citations, leave it off if you don't.
Link your BibTeX files
The bibtex-file-url-list is where you tell Webtrotion where to find your .bib files. You can have one or multiple files, and they need to be publicly accessible URLs.
GitHub Gist (recommended): Supports smart caching via last-modified timestamps. Use the raw URL or the Gist page URL.
GitHub repo file (recommended): Same smart caching as Gist. Use the raw URL or the file URL.
Dropbox: Public share links work (dl=0 or dl=1). No last-modified timestamp, so it fetches every build.
Google Drive: Use the export URL format shown above. Fetches every build like Dropbox.
Why use Dropbox or Google Drive? Better bib can auto-sync from Zotero to these.
Performance Note
Webtrotion doesn't parse your entire 6,000-entry .bib file and convert it all to JSON. That was the first version and it crashed immediately. Instead, it only parses and caches the entries actually cited on each page.
On subsequent builds, if the .bib file hasn't changed (based on last-modified timestamp for GitHub sources), Webtrotion skips the fetch entirely. This keeps build times fast even with large bibliographies.
Dropbox and Google Drive don't provide public last-modified timestamps, so those get fetched on every build. If build speed matters and you have a large .bib file, use GitHub or Gist instead.
Example BibTeX File
Here's what a typical .bib file looks like:
@book{sweig42, title = {The Impossible Book}, author = {Stefan Sweig}, year = 1942, month = mar, publisher = {Dead Poet Society}}@article{steward03, title = {Cooking behind bars}, author = {Martha Steward}, year = 2003, publisher = {Culinary Expert Series}}@book{impossible, title = {The impossible book}, author = {Stefan Sweig}, year = 1942, month = mar, publisher = {Dead Poet Society}}
You can use any standard BibTeX entry types: @article, @book, @inproceedings, @misc, etc. Webtrotion parses the common fields (author, title, year, publisher, journal, booktitle, etc.) and formats them appropriately.
Choose your Citation Syntax
The in-text-citation-format setting controls which syntax you use in your Notion text to mark citations. You have three options, and they all produce the same output — pick whichever feels natural.
Standard Markdown/pandoc style ([@key]): If you're used to writing in Markdown or use pandoc elsewhere, this will feel familiar. This is also supported by Typst. Example: [@sweig42]
LaTeX style (\cite{key}): For the LaTeX users, muscle memory is real. Example: \cite{sweig42}
Typst style (#cite(key)): If you use Typst for typesetting, this is especially handy because using the @ symbol in Notion triggers the annoying date/page popup. Example: #cite(sweig42)
The key is whatever you defined in your BibTeX entry. For example, if your .bib file has:
@book{sweig42, title = {The Impossible Book}, author = {Stefan Sweig}, year = 1942, month = mar, publisher = {Dead Poet Society}}
Then you'd cite it in Notion as [@sweig42], \cite{sweig42}, or #cite(sweig42), depending on which format you chose in your config.
Markers inside code blocks or inline code are not processed. Footnote marker names can only contain letters, numbers, underscores, and hyphens (e.g., [^ft_my-note-1]).
During the build, Webtrotion will:
Find all citation markers in your Notion content
Look up each key in your .bib files
Format the in-text citation based on your chosen bibliography-format
Collect and de-duplicate all citations for the bibliography section
Pick your bibliography style
The bibliography-format setting controls how citations are formatted both in-text and in the bibliography. You have two options:
simplified-ieee: Numbered citations in square brackets. In-text: [1], [2], etc. Good for NeurIPS, ICML style. The bibliography lists entries in citation order with [1], [2] prefixes (de-duplicated for multiple occurences).
apa: Author-year style. In-text: [Sweig, 1942], [Steward, 2003]. Better for humanities and social sciences, ACL-style papers. The bibliography lists entries alphabetically by author.
Both formats get hover previews. Readers can click or hover over [1] or [Sweig, 1942] to see the full reference in a popover, and then click through to jump to the bibliography if you have generate-bibliography-section enabled.
Generate the bibliography section
Set generate-bibliography-section to true and Webtrotion collects all citations from the page and builds a formatted bibliography at the end. Each entry gets backlink buttons that jump you to where you cited it — same UX as footnotes, but better because one source can be cited multiple times.
If you set it to false, citations still format properly and you still get hover previews, just no collected references section. Good for informal writing where you want to drop paper links without the formality of a bibliography.
The "Back to Content" button
When you click a citation to jump to the bibliography, you get a dynamic "back to content" button that takes you right back to where you were reading. I hate one-way jumps in PDFs, so I made sure this works smoothly.
Your browser does not support the video tag.
Choose Citation Display Mode
Set show-in-margin-on-large-screens to control how the first occurrence of citations appear. That is, you get margin notes on large screens if true, otherwise they’re always popovers. For multiple occurrences of the same citation key, they’re always popovers after the first one.
Some Other Things That I Worked On
Here are the commit messages for the past 12-ish months in descending order on this repository. I work in bursts when other things in my life aren't going great, so this has been in bursts; in late December 2024 (cache deletion workflow), late January (Notion page embedding and SEO fixes), the last week of February (Tailwind v4 migration and HTML caching), early September (UI improvements like tags redesign and search from query parameters), and then the biggest push in late October through early November 2025 (the main 2.0 release with footnotes, citations, JSON5 config, and pagespeed optimizations).
Around the same time last year, I wrote One year of Webtrotion. Of course, they're pretty messy, almost non-descriptive, and sometimes just merges from the branches I worked on, but they do give me a rough idea of the work I did.
Mon Nov 03 2025: add extra delay because that didn't work well enough on long pages like this oneMon Nov 03 2025: wait for fonts and RAF before initializing margin notes in second phaseSun Nov 02 2025: show margin notes even if just on for citations and citations are enabledSun Nov 02 2025: citations are now extracted inside footnotes and shown in margin if the setting is enabledSat Nov 01 2025: set pagefind weight to 10 for title and remove pagefind for citethispageSat Nov 01 2025: fix bluesky tab not found on idle rather than clickSat Nov 01 2025: preload only few fonts instead of all fontsSat Nov 01 2025: simpler config for fonts of og imageSat Nov 01 2025: simplify font config and usageSat Nov 01 2025: keep jump backs to "Jump to"Sat Nov 01 2025: custom emojis, files and external links for icons now work in callout and page icons.Sat Nov 01 2025: optimize ncode mermaid loadingSat Nov 01 2025: improve pageinsights by using astro experimental font api and removing googlefontsoptimizerFri Oct 31 2025: att2 at improving pagespeedFri Oct 31 2025: aim to fix pagespeed insightsFri Oct 31 2025: do not show jump to bibliography, cite this page, footnotes section at the end of the post preview full componentFri Oct 31 2025: fix citation caching issueFri Oct 31 2025: weird parsing bug fix in github actions att1Fri Oct 31 2025: don't show url in formatted citation textFri Oct 31 2025: support url in formatted citationsFri Oct 31 2025: fix footnote positioning, and feed pages show footntoe popovers on hover on large screensThu Oct 30 2025: load margin notes after font load and fix caching for bibThu Oct 30 2025: avoiding duplicate footnote/citation IDs when the same content appears multiple times in a page in popoversThu Oct 30 2025: simplify key for footnote displayWed Oct 29 2025: fix marker extraction for citationWed Oct 29 2025: do not parse markers from inline code for citations and footnotesWed Oct 29 2025: style: update footnote and citation display terminology from 'popup' to 'popover' for consistencyTue Oct 28 2025: fix-citation-storageTue Oct 28 2025: style: remove Cite This Page component from PostPreviewFull and [...page] for cleaner layoutTue Oct 28 2025: style: disable citation features in configuration for general useTue Oct 28 2025: style: remove unsupported feature note in citations configuration and clarify in-text citation pattern descriptionTue Oct 28 2025: Merge pull request #35 from nerdymomocat-templates/add-and-process-citationsTue Oct 28 2025: cleaned up unsused codeTue Oct 28 2025: style: simplify author formatting logic in formatCitation function for APA in-text citationsTue Oct 28 2025: style: update version and cacheVersion in package.json for release 2.1.0Tue Oct 28 2025: style: reorder citation and footnotes sections for improved clarity and consistencyTue Oct 28 2025: style: refactor footnote handling to utilize FootnoteContent component for improved structure and readability and handle all attachment types consistentlyTue Oct 28 2025: style: update footnote handling to show popovers in footnotes sectionTue Oct 28 2025: style: update footnote source options and improve footnote renderingTue Oct 28 2025: style: update footnote and citation marker styles for improved readabilityMon Oct 27 2025: style: update font styles for citation and footnote markersMon Oct 27 2025: refactor: update import paths for NBlocksPopover, Icon, BibliographySection, and CiteThisPage componentsMon Oct 27 2025: npm run formatMon Oct 27 2025: move citation marker component to popover folderMon Oct 27 2025: Merge branch 'main' into add-and-process-citationsMon Oct 27 2025: Merge pull request #34 from nerdymomocat-templates/re-arrange-componentsMon Oct 27 2025: refactor: remove unused imports and clean up code across multiple componentsMon Oct 27 2025: move files aroundMon Oct 27 2025: match citation marker and footnote marker to nblocks popoverMon Oct 27 2025: refactor: remove debug logging from popover click event handlingMon Oct 27 2025: Attemptt 1 for bibliographySun Oct 26 2025: plan: update in-text citation format and bibliography configuration for improved citation processingSun Oct 26 2025: Merge branch 'main' of github.com:nerdymomocat-templates/webtrotion-astro-notion-cms-website-blog into add-and-process-citationsSun Oct 26 2025: refactor: move linked content symbol logic to utils and update footnote display to use alphabetical indexingSun Oct 26 2025: feat: update citations configuration and desired planSun Oct 26 2025: feat: update citations configuration and implementation plan to support new features and processing optionsSun Oct 26 2025: feat: add initial citations configuration and processing documentationSun Oct 26 2025: Merge pull request #33 from nerdymomocat-templates/fix-footnotes-not-on-pages-only-postsSun Oct 26 2025: refactor: remove unused footnote functions and integrate footnotes handling in main page (menu bar pages) contentSun Oct 26 2025: Merge branch 'main' of github.com:nerdymomocat-templates/webtrotion-astro-notion-cms-website-blogSun Oct 26 2025: Remove 'v' query parameter from Notion links to ensure correct page and block link parsing.Sun Oct 26 2025: Merge pull request #32 from nerdymomocat-templates/fix-nested-popovers-for-footnote-content-and-popover-show-for-margin-contentSun Oct 26 2025: refactor: streamline popover creation and visibility handlingSun Oct 26 2025: refactor: optimize popover event handling, popovers work for margin notes and improve device-specific selectorsSun Oct 26 2025: refactor: remove breaking prettier-ignore comments from BaseHead and PostComments componentsSun Oct 26 2025: fix: specify event types for repository creation trigger in GitHub Actions workflowSun Oct 26 2025: Merge pull request #31 from nerdymomocat-templates/refractor--change-references-to-interlinked-contentSat Oct 25 2025: refrac formatting errorsSat Oct 25 2025: feat: add citations configuration for processing BibTeX citations and in-text citation formattingSat Oct 25 2025: refactor: update footnotes and interlinked content references to use new auto-extracted sections structureSat Oct 25 2025: refractor base.astro by moving scripts into their own files and moving style to global css and updating popover scripts to use latest versions of their libraries and remove config for headings (instead use constant)Sat Oct 25 2025: npm run formatSat Oct 25 2025: refactor: remove popovers configuration and simplify related components for cleaner codeSat Oct 25 2025: fix: handle errors in cache file access checks and improve logging commentsSat Oct 25 2025: changed name reference to interlinked content in constants and usage in filesSat Oct 25 2025: run formatSat Oct 25 2025: feat: update popover styles and dynamic background color generation for improved UI consistencySat Oct 25 2025: refactor: enhance workflow clarity by restructuring jobs and improving comments; streamline cleanup and build processesSat Oct 25 2025: feat: add shallow checkout step to improve workflow speed during initializationSat Oct 25 2025: fix: enhance cleanup detection logic for upstream template sync; implement robust checks for push events and SHA comparisonSat Oct 25 2025: fix: enhance cleanup detection logic for upstream template sync; improve commit message parsingSat Oct 25 2025: feat: streamline GitHub Actions workflow by consolidating initialization and cleanup steps; enhance clarity and efficiencySat Oct 25 2025: feat: consolidate template cleanup logic into main workflow; remove redundant template-cleanup.ymlSat Oct 25 2025: feat: add automated template cleanup workflow for new repository initialization and upstream syncSat Oct 25 2025: fix: improve footnotes styling and enhance comments in constants-config.json5; aim to fix margin note positioning on loadSat Oct 25 2025: npm run format aftermathSat Oct 25 2025: Add upgrade notice for version 2.0.0Sat Oct 25 2025: Merge pull request #30 from nerdymomocat-templates/claude-code-features-and-fixes-webtrv2-fixSat Oct 25 2025: webtrotion v.2.0.0Sat Oct 25 2025: fix: fix rss links and footnote links (space issue)Sat Oct 25 2025: add comments for: 1 level Nested table of contents for headings (callouts, toggles, columns) [apparently was already implemented]Sat Oct 25 2025: dark mode and theme correction for footnotesSat Oct 25 2025: implement json5 reading for constants-configSat Oct 25 2025: failed: Theme change animationSat Oct 25 2025: fix: Fix symbols in interconnected stuff to also include combos and not just repetitions so that the length remains smallerSat Oct 25 2025: fix: Descriptions in OG images for tags and collectionsSat Oct 25 2025: fix: Links inside simple tables are not read right nowSat Oct 25 2025: updated docsSat Oct 25 2025: completed-implementationFri Oct 24 2025: almost everything worksThu Sep 11 2025: Merge pull request #26 from nerdymomocat-templates/feature/replace-numeric-iterator-with-symbolsThu Sep 11 2025: fix: update tmpHTMLCache version from vC to vDThu Sep 11 2025: feat: Update linkText to "Jump to" for specific sectionsThu Sep 11 2025: feat: Update symbols, rename functions, and fix sr-only issueThu Sep 11 2025: feat: Replace numeric iterators with Unicode symbols for interlinked contentThu Sep 11 2025: Merge pull request #25 from nerdymomocat-templates/feature/query-param-searchThu Sep 11 2025: feat: Implement search from query parameterThu Sep 11 2025: Merge pull request #24 from nerdymomocat-templates/feature/update-tags-page-uiThu Sep 11 2025: fix: adjust padding for tag count display in tags indexThu Sep 11 2025: feat: Update tags UI across the siteThu Sep 11 2025: feat: Update tags UI across the siteThu Sep 11 2025: feat: Update tags UI across the siteThu Sep 11 2025: feat: Update tags UI across the siteThu Sep 11 2025: feat: Update tags page UIThu Sep 11 2025: feat: Update tags page UIThu Sep 11 2025: Merge pull request #23 from nerdymomocat-templates/fix-twitter-bookmark-error-emojiThu Sep 11 2025: fix(bookmark): prevent twitter emoji from being displayed as imageThu Sep 11 2025: Merge pull request #22 from nerdymomocat-templates/fix/def-inject-render-shortcode-wraps-to-newlineThu Sep 11 2025: fix: Add robust, commented handling for newlines in code directivesWed Sep 10 2025: fix: add checkout step to recover cache workflowWed Sep 10 2025: feat: add GitHub Actions workflow to recover cache based on user-defined keyWed Sep 10 2025: Merge pull request #21 from nerdymomocat-templates/remove-pnpm-mentionsWed Sep 10 2025: Merge branch 'main' into remove-pnpm-mentionsWed Sep 10 2025: Merge pull request #20 from nerdymomocat-templates/fix-inline-code-backgroundThu Sep 11 2025: Fix(style): Retain inline code background color from NotionWed Sep 10 2025: chore: update .gitignore and .prettierignore to remove pnpm references; modify package.json and package-lock.json to remove pnpm versionThu Sep 04 2025: Merge pull request #19 from nerdymomocat-templates/gemini-update-to-new-notion-apiThu Sep 04 2025: fix: update @notionhq/client version to 5.0.0Tue Sep 02 2025: refactor: npm run formatTue Sep 02 2025: refactor: enhance getBlock function to support forced refreshTue Sep 02 2025: refactor: remove unused AppBskyFeedDefs import from utils.tsTue Sep 02 2025: refactor: move giscus to first positionTue Sep 02 2025: refactor: update data source caching to use dynamic file naming based on resolved data source IDTue Sep 02 2025: refactor: update Notion client integration to use data source ID and improve data retrieval logicTue Aug 26 2025: Merge pull request #18 from nerdymomocat-templates/update-and-cleanupTue Aug 26 2025: chore: update dependencies and improve buildcache directory handlingTue Apr 29 2025: chore: update HTML cache version in package.jsonTue Apr 29 2025: refactor: rename checkbox icon keys for consistency in style helpersSat Mar 01 2025: Update README.mdSat Mar 01 2025: refactor: shorten readme, remove netlify.toml and update .editorconfig for tab indentation and final newline settingsFri Feb 28 2025: refactor: fix typo in echo message and update cache clearing condition in Astro workflowFri Feb 28 2025: refactor: update cache clearing condition for constants-config in Astro workflowFri Feb 28 2025: refactor: remove unnecessary condition for saving cached constants-config in Astro workflowFri Feb 28 2025: refactor: update cleanup logic for constants-config cache to add all trimming if constants-config changedFri Feb 28 2025: refactor: improve cache cleanup messages in Astro workflow for clarityFri Feb 28 2025: refactor: enhance caching logic for constants-config in workflows and always try to process filesFri Feb 28 2025: refactor: update caching logic for old_package.json in workflowsFri Feb 28 2025: refactor: update cache handling in Astro workflow to improve file trimming logicFri Feb 28 2025: Merge pull request #17 from nerdymomocat-templates/segmented-caching-and-invalidationFri Feb 28 2025: refactor: enhance caching logic in GitHub Actions workflows and update package.json cache versioningFri Feb 28 2025: refactor: remove console log statements in blocksHtmlCacher integrationFri Feb 28 2025: refractor: fix headings setup and add predev and prebuild-local scripts to remove html-cacheFri Feb 28 2025: refactor: simplify cache removal condition in GitHub Actions workflow (cache-hit is not true for partial match so we just check if tmp exists)Fri Feb 28 2025: refactor: remove unnecessary console log for cache value in ReferencesSection componentFri Feb 28 2025: Merge pull request #16 from nerdymomocat-templates/notion-blocks-html-cacheFri Feb 28 2025: refactor: invalidating the HTML cache not only when a blog post is updated but also when any linked pages (referenced within the post) are modified. ideally just slug, title & excerpt, but couldn't do that rn.Fri Feb 28 2025: refactor: enhance caching mechanism for static references and blocks in ReferencesSection componentFri Feb 28 2025: refactor: rename headings cache directory for consistency in PostPreviewFull and related filesFri Feb 28 2025: refactor: update import paths to use tsconfig based paths for improved clarity and consistency, @ doesn't work with astro integrations (other than type imports)Fri Feb 28 2025: refactor: standardize indentation and formatting in [...page].astroFri Feb 28 2025: refactor: improve JSX formatting and consistency in MentionLink componentFri Feb 28 2025: refactor: remove unused jsxImportSource directive in MentionLink componentFri Feb 28 2025: refactor: change imports to use type imports for Mention and RichText interfacesFri Feb 28 2025: refactor: replace hardcoded paths with BUILD_FOLDER_PATHS constants for improved maintainabilityFri Feb 28 2025: refactor: add directory creation integration for cache and output pathsFri Feb 28 2025: feat: implement headings caching with superjson for improved performanceFri Feb 28 2025: feat: use HTML caching for blog posts to try to imp??? performanceFri Feb 28 2025: feat: add block HTML caching integration to optimize build processFri Feb 28 2025: move to superjson for date parrsing (fixing ogimage read) and setup for html cacheThu Feb 27 2025: feat: add helper function to transform GitHub Gist URLs for embeddingThu Feb 27 2025: feat: enhance theme toggle functionality with system preference supportThu Feb 27 2025: theme toggle now actually works with system preference listenerTue Feb 25 2025: fix: add data-pagefind-ignore attribute for non-mermaid code blocksTue Feb 25 2025: remove unused depsTue Feb 25 2025: fix: increase request timeout from 2000ms to 10000ms for improved reliabilityMon Feb 24 2025: fix: remove request timeout constant and set default timeout to 2000msMon Feb 24 2025: fix: update color variables to use color-mix for improved theming consistencyMon Feb 24 2025: run formatMon Feb 24 2025: fix: update BASE_PATH constant to include BASE environment variableMon Feb 24 2025: fix: update RSS stylesheet link generation to use BASE_PATH and clean up loggingMon Feb 24 2025: testing base path issues in integrationMon Feb 24 2025: fix: correct syntax for retry parameters in Notion API clientMon Feb 24 2025: fix: add logging for base path in RSS styles and configure retry parameters for Notion API callsMon Feb 24 2025: fix: normalize basePath in redirect paths and adjust mobile button indentationMon Feb 24 2025: fix: update RSS stylesheet link generation to use dynamic navigation helperMon Feb 24 2025: chore: remove commented-out integrations from astro.config.ts for cleaner codeMon Feb 24 2025: Merge pull request #15 from nerdymomocat-templates/tailwind-v4Mon Feb 24 2025: fix huge rss issue that happened due to trailing slash in slugMon Feb 24 2025: style: update mobile button class for improved layout consistencyMon Feb 24 2025: fix border upgradeMon Feb 24 2025: style: clean up unused CSS properties and enhance datatable styling for better layoutMon Feb 24 2025: fix: update simple-datatables to use latest version for improved stabilityMon Feb 24 2025: chore: remove Tailwind CSS configuration fileMon Feb 24 2025: run formatMon Feb 24 2025: style: adjust iframe scaling for better responsiveness in NotionEmbed componentMon Feb 24 2025: feat: add color normalization function for theme colors in CSSMon Feb 24 2025: style: improve CSS for better touch action and scroll behaviorMon Feb 24 2025: style: enhance datatable sorter styles for better visibility in dark modeMon Feb 24 2025: run formatMon Feb 24 2025: add cursor pointer to buttons for improved interactivityMon Feb 24 2025: increase text size in footerMon Feb 24 2025: Merge remote-tracking branch 'origin/main' into tailwind-v4Mon Feb 24 2025: refactor print styles for SocialList component for improved visibilityMon Feb 24 2025: refactor print styles for improved layout consistency across componentsMon Feb 24 2025: fix screen print query issue kinda, still issue in codeblockMon Feb 24 2025: update media query syntax for responsive font size in theme CSSMon Feb 24 2025: add custom variant for dark mode in theme CSSMon Feb 24 2025: at least it runs now and ran formatMon Feb 24 2025: Update Callout.astro to show border when color is defaultFri Jan 31 2025: changed mentions -- config file still needs to be correctedThu Jan 30 2025: run formatThu Jan 30 2025: Merge pull request #14 from nerdymomocat-templates/fix-trailing-slashesThu Jan 30 2025: fix trailing slashes for seoThu Jan 30 2025: fix: simplify Notion embed URL validation with regex patternFri Jan 24 2025: update astroFri Jan 24 2025: add notion page emedding and run formatterMon Dec 23 2024: fix: improve cache deletion regex for better matching of keysMon Dec 23 2024: fix: update site-page-link class to use text-link for better styling consistencyMon Dec 23 2024: fix: add support for cache deletion using a GitHub token in workflowsMon Dec 23 2024: test: if new gh cli actions work with actions workflow tokenMon Dec 23 2024: fix: update cache deletion logic to use repository context instead of repository IDMon Dec 23 2024: test: update cache clearing logic to include both 'web' and 'blog' trotion and testMon Dec 23 2024: fix new no cache ymlMon Dec 23 2024: try adding deletion in actions workflow -- might revertSun Dec 22 2024: refactor: remove font-weight classes from link elements for consistencySat Dec 14 2024: refactor: clean up unused functions and imports in various modulesTue Dec 10 2024: update all packages
Performance and Pagespeed
The footnotes and citations features, as well as the other features I'd added before, added a lot of JavaScript and content to pages, which initially tanked my PageSpeed Insights scores. So I spent a few days fixing that. I think given that Webtrotion is optimized for text-heavy websites and not marketing sites with shaders and such, it should have a good enough pagespeed score.
Font loading: I switched to Astro's experimental Font API and ditched the Google Fonts optimizer. Now I only preload the fonts actually needed instead of loading everything upfront. This alone made a noticeable difference.
Icon handling: Instead of using img src for icons (even Notion's default ones), I now download and serve them locally. This reduces external requests and improves load times, plus it means less calls to notion’s own tracking.
Caching strategy: I implemented HTML caching for rendered blog posts and added BibTeX file caching so the site doesn't re-parse citation data on every build. The cache also invalidates when linked pages change, not just when the post itself updates.
Margin notes timing: This was tricky. Margin notes need to calculate their position, but if they initialize before fonts load, the positioning breaks. So now they wait for fonts and requestAnimationFrame before initializing. I also added extra delays for really long pages because even that wasn't enough sometimes.
GitHub Actions improvements: Rewrote the caching workflow and added template cleanup workflows to be smarter. They now detect when config changes and invalidate the right caches automatically. Also added a workflow that lets you manually delete caches using a personal github API key, which is useful if you do not like to see clutter.
Conditional preconnects: Added smart preconnects for external resources that are conditionally loaded. If you have simple-datatables enabled for tables, glightbox for image galleries, or giscus for comments, the site now adds <link rel="preconnect"> tags, and only adds them when those features are actually used on the page. This means the browser can start establishing connections to those domains earlier, but only when needed, keeping the critical path lean for pages that don't use those features.
Other optimizations: Optimized Mermaid diagram loading, adjusted Pagefind search weights so titles rank higher, and cleaned up a bunch of unnecessary components from the preview layouts. I also made sure that all external scripts (like glightbox) and external CSS (for example, for simple-datatables) are imported dynamically and avoid the critical path.
It's not perfect, but the scores are way better now and the site still feels snappy.
Pagespeed metrics for home page and THIS page on both mobile and desktop devices.
Improving UI Components
While working on footnotes and citations, I ended up fixing a bunch of smaller UI issues that had been bothering me.
Icon improvements: Custom emojis, files, and external links now work for callout and page icons. Before, you were limited to Notion's default emoji set, but now you can use any custom emoji or even link to external icon files, same as you can in the Notion UI.
Tags redesign: The tags page got a complete refresh. The old design felt cluttered and the tag counts were hard to read. Now it's cleaner, with better spacing and more consistent styling across the site. Also the tags on the side show the number of posts with that tag too!
Theme toggle with system preference: The theme toggle now has an option that respects system preferences. If your OS is set to dark mode, the site defaults to dark mode too. It also listens for system theme changes, so if you switch your OS theme while the site is open, it updates automatically.
Search from query parameter: You can now trigger search directly from the URL. Useful for linking to specific search results or building search-based workflows. Just add ?search=your-query to any page URL and it will open the desired pagefind dialog.
Dark mode polish: A lot of small dark mode issues got fixed. Data table sorters weren't visible enough in dark mode, and some interactive elements weren't obvious as clickable. Added cursor pointers to buttons and improved the overall consistency of hover states.
Responsive improvements: Fixed iframe scaling in embedded content so it doesn't break the layout on mobile. Improved touch interactions and scroll behavior across the board. Also spent time on print styles because apparently some people still print web pages, and the old print layout was embarrassingly broken.
Small fixes: Twitter bookmark embeds were showing an emoji as an image, which looked ridiculous. Fixed that. Also cleaned up a bunch of unused CSS and tightened up the datatable styling so it doesn't feel so cramped.
Better Notion Fidelity
Notion page embedding: Added support for embedding Notion pages (including database pages and database views) directly into your posts. This is useful when you want to reference or showcase other Notion pages within your blog content. And this is especially nice with the new maps view!
Styling improvements: Made the site match Notion's styling more closely in a few places [j]. Callouts with default colors now show a subtle border like they do in Notion. Small things, but they make the site feel more cohesive.
How It Was Built
I built this with AIs' help; mostly Claude Code for the heavy lifting (core implementation, complex logic) and Gemini-CLI for cleanup, refactoring, and writing this post. Claude Code handled the architecture well. Gemini-CLI is less useful for big-picture stuff but fine for smaller tasks.
Some bugs that came up:
The Ghost Popover on Mobile: This was a hair-pulling bug. On mobile, the citation popovers would appear, but you couldn't click on anything inside them. Clicks were just passing right through to the content behind it. After many false leads (was it a Tailwind class? Padding? Z-index?), I discovered the solution was to reuse the exact same Astro component that was already working for other popovers (NBlocksPopover) and ensure the DOM structure was identical, right down to an empty <span> with a special data attribute. I still do not know why the original error was happening.
Nested Popovers: Getting popovers to work when footnote content itself contained popovers (like links or mentions) was trickier than expected. The event handling kept interfering with itself. After several attempts at streamlining popover creation and visibility handling, I finally got it working with careful device-specific selectors. Desktop and mobile needed different interaction patterns, and getting both to work without breaking each other took multiple tries.
Performance Boost: Initially, I had three separate functions that traversed the entire block tree to find footnotes, citations, and interlinked content. This was inefficient. I refactored this into a single, unified extractPageContent function that collects everything in one pass, making the build process significantly faster.
The "Back to Citation" Button: After getting the core feature working, I realized that jumping to the bibliography was a one-way trip. And I always hate that when I read papers in pdf. So, I added the "Back to citation" button that dynamically appears on the bibliography entry you jumped from, making for a much smoother user experience.
The BibTeX → JSON Cache Overflow: Claude Code had it initially set up such that it tried to convert and cache the entire .bib file into JSON. When I tested it with 6,000 entries, everything crashed. I had to walk the AI through fixing that to only parse the citations actually used in each page.
Tailwind v4 Migration: Upgrading to Tailwind v4 was more painful than expected. It's a breaking change that required rewriting a lot of styling code. Media query syntax changed, dark mode variants needed updating, and there were CSS issues everywhere. One commit message I made said "at least it runs now" which pretty much sums up how that went. Had to fix dozens of small styling issues after the initial upgrade.
Cache Deletion Workflow: Getting GitHub Actions to properly delete caches took way too many attempts. The GitHub CLI and API kept changing requirements for authentication and permissions. First tried using the default workflow token, then had to switch to using the repository context instead of repository ID, then finally got it working with a personal access token. Ended up with a separate workflow that lets you manually delete caches when things get stuck.
RSS Trailing Slash Bug: Discovered a huge bug where RSS feeds were completely broken due to trailing slash inconsistencies in slug formatting. This affected how URLs were generated across the entire site and took a while to track down because the error only showed up in the RSS feed, not on the actual pages.
Same id for footnote/citation popovers: Claude Code fixed this immediately (gemini-cli went on a wild unproductive chase). This is because I [k] was assigning random ids to normal popovers but not here.
BibTeX caching: The amount of errors I had to fix here made me think I was dealing with a 2023 AI model, not clade sonnet 4.5. I don't know why it believes it should complicate everything, but it did. Gemini cli is good enough to fix targeted issues like these.
Handling Margin Notes: The more conditions you have, the harder it is to get AI to code something without missing edge cases. I wanted to support both footnotes and citations in the margin, with each being toggleable. But this creates a cascade of questions [l], like: What happens if a citation is inside a footnote? Does it show up in the margin or not? Unlike footnotes, citations can repeat - does only the first occurrence show in the margin, or all of them? What about subsequent occurrences - do they become popovers? What if citations are shown in the margin but footnotes aren't, and the first occurrence of a citation is inside a footnote? Do we show it in the margin anyway? And if the second occurrence is in the main content but we only show first occurrences in the margin, what then? Oh, and then what if the same citation appeared in the same block multiple times and that block is the first occurrence of that citation? How do we handle that? 🤦♀️
Using native <Image /> components in Astro: One of the persistent issues in the build was content layout shift caused by storing all images in public. And adding all notion image to downloads to public meant I had to manually write code to optimize images to webp when optimization was enabled, handle different URL patterns based on optimization settings, and couldn't reliably determine layout sizes or generate mobile-specific srcset attributes. So I refactored the code to use Astro's native Image component, which turned out to be far from straightforward. Why? First, I was downloading files during the post-build process rather than before, but Vite needed to glob all these files beforehand. Second, my hacky HTML caching system skips Astro component calls, so I had to copy optimized files from the previous build into dist to account for that. Third, dist also carries the original files as a fallback, but I didn’t want that increased size, so I wrote an integration to delete those [m]. Finally, Astro doesn't natively support setting srcset with just mobile width and original size, so I had to hack that functionality into the code.
It was a great learning experience, and it's incredibly rewarding to see these powerful features come to life in Webtrotion.
Footnotes
Just like these ones!
Well, maybe besides universal page blocks and universal task blocks.
I should have done this from the very start rather than relying on readme or webpages for configuration help.
It is fine if you don’t have all the weights or styles in your chosen font, it will fallback gracefully.
This is the content of the footnote. It can be long and detailed and have links.
For example, the way the marker at start of this paragraph is written won’t work, it needs to be in normal text.
We need this because Notion API only returns block ids, not the text span that was commented on. When that is fixed, I’ll implement block-inline-text-comments.
Or whatever your heart desires ❤️
I tried to make it so that inline code now retains its background color from Notion instead of getting stripped out, but the API doesn't provide the color for inline code background 😭
The term I here is claude code, but I approved that code, so 🤷♀️
I think AI is a boon and curse here. People say AI helps them quickly figure out what looks good, I think AI helped me figure out if the code complexity (because it wasn't able to implement it quickly) was worth the outcome and if I really even wanted the outcome.
I added a 3-way Theme toggle enhancing the existing 2-way toggle, allowing switching between system, light, and dark modes, which I'm quite happy about. This was implemented using GitHub Copilot with Claude 3.5s Sonnet.
Most of the time was spent finding a suitable icon (tried searching for light dark and sun moon with a single path that would work with my system. I found one on Iconify but had to modify it. Something to note, while I attempted to use LLMs for the modification, it wasn't successful, so I used SVG Path Editor instead. As shown in the image below, you can now switch between three theme modes rather than just two.
You can look at the GitHub gist below to see the conversation with GitHub Copilot.
Webtrotion is a single-file configuration system that uses constants-config.json. Using LLMs, I created a simple HTML script that generates JSON you can copy directly into your constants-config.json.
While this isn't polished, I created it mainly to test an LLM's ability to generate pure HTML rather than defaulting to React for 1000+ line projects. I didn't spend time refining it since I expect limited usage, but it's here if you need it.
My partner always hated how opening the screen on the laptop used to lead to turning on the laptop. Of course he switched off from macOS but I would like to collect these tips and trips together at some place just to make sure that I have them at hand and don't go searching over the internet. And so this is written by MacRumors linked below and I'm copy pasting the relevant stuff here.
#!/bin/bash# This script modifies the Mac's startup behavior using the nvram command.# Run this script in Terminal with the appropriate option.echo "Choose an option to modify startup behavior:"echo "1) Prevent startup when opening the lid or connecting to power"echo "2) Prevent startup only when opening the lid"echo "3) Prevent startup only when connecting to power"echo "4) Undo changes (restore default behavior)"read -p "Enter your choice (1-4): " choicecase $choice in 1) sudo nvram BootPreference=%00 echo "Startup is now prevented when opening the lid or connecting to power." ;; 2) sudo nvram BootPreference=%01 echo "Startup is now prevented only when opening the lid." ;; 3) sudo nvram BootPreference=%02 echo "Startup is now prevented only when connecting to power." ;; 4) sudo nvram -d BootPreference echo "Startup behavior restored to default." ;; *) echo "Invalid choice. No changes made." ;;esac# The user will be prompted for their administrator password when running the script.# Terminal does not display the password while typing.
One of the major reasons I avoid screenshots/videos in my posts generally is because of how cumbersome it is to take them. And I wish I could kinda "tear off" commands from different apps and place it onto a temporary menu bar.
For example, when I was writing this post All The Menu Bar Weather Apps for MacOS — I really wanted to convert all screenshots to text. Why, you might ask? Because the workflow requires so many clicks in order.
So, I usually have my clop optimize on clipboard on. But given this required stitching of different images — I didn’t want to optimize each image and have it progressively get worse. So, this is what it looked like —
Pause auto-optimizations in Clop, then show desktop in Supercharge. Use Shottr to capture any window (note that this doesn't create a file). Copy the image in Shottr, then capture additional windows (like settings or preferences). Open clipboard history in Raycast (which doesn’t have a paste stack) multiple times to Paste all images into the last Shottr image, turn optimization back on in Clop, and copy the final image from Shottr.
Oh, now I want to generate image captions, here is what that looked like —
First, paste the image into Notion, then open ChatGPT and paste the image into ChatGPT to generate a caption. Copy the generated caption into Notion, replace line breaks using Shift+Enter, and finally cut and paste the text into the image caption field that is opened by a click on the caption button.
I wish there was a way to create a workflow hotbar on the go, one where I could put all the actions I need to take across various apps into one place, and then discard the hotbar. Sure, I can assign keyboard shortcuts to all these actions — but I possibly never need to use these actions again, and if I need to, I am not going to remember the shortcuts I assigned to them. I don’t even know if tear-off hotbars are possible in MacOS, but I wish they were.
I have wanted a menu bar weather app for a long time, and I can’t find any that fit all my criteria. So, off I went looking at all the recommendations for weather apps, and found 17 of them. Yes, SEVENTEEN.
The image shows a collection of weather apps on a screen, including icons for DatWeatherDoe, Weatherly, Mercury, Meteorologist, and others arranged in a grid in Launchpad.
Free or cheap one-time purchase (Apparently this is hard because Apple Weather API costs money? What? Why? Apple is retrieving that data anyway, might as well provide a local API?)
Shows feels like temperature
Doesn’t show up in the dock
Allows for switching between a list of cities in the menu bar popup
Shows weather for the week and next few hours
(Extra) Shows sunrise and sunset times
(Extra) Opens on login and has a shortcut to open/close the menu bar dropdown
Things I personally do not care about:
Hyperlocal weather information
Radar maps or any other graphical information
Making weather apps fun
And where the weather information is coming from. Till it is coming from any trusted source, I am happy.
Update time being more frequent than 30 or 60 minutes.
Desktop widgets, or control panel widgets because I never ever open or use those sections.
How hard can it be to find something like this? Apparently, very hard. I was so excited when Apple Weather launched their menu bar option in macOS 15.2, but apparently, it doesn’t show feels like — and only shows temperature for the next few hours. 🤷♀️
Apps
Here are some other apps that I have tried grouped as, “Considering” and “Not Considering”. Something to consider is, given that apple now has their menu bar option that would be enough for most people, some apps might stop receiving updates and may not be compatible with future versions of OS.
You can open toggles below to see screenshots of the app in the menu bar and most of the available settings for that app. Captions and alt text generated using GPT-4o.
Considering
Big Weather (Mac App StoreBig Weather): Has everything I need for free, except it doesn’t show feels like temperature in menu bar but does show it in the popup. Also the hourly graph is kinda hard to understand, but I will probably get used to it. Sometimes doesn’t get current location, which is a pain. Some permission toggle switching later, it works, yay!
Left Image: Displays a weather app showing the current temperature and feels-like temperature. It includes details like UV index, wind speed, humidity, and pressure. Forecast charts are shown for different timeframes, along with hourly rain predictions displayed in blue bars. Right Image: App settings screen with sections for style preferences, including layout options, animations, and background settings. Units for temperature, rain, pressure, and wind can be customized. Menu bar options include warnings and temperature display settings, along with startup and keyboard shortcut settings.
Menu Weather Pro (Mac App StoreMenu Weather Pro): Honestly, the perfect app for me, and the one I used to use, except since macOS 15, the popup opens in bottom left rather than sticking to the menu bar area. I also realized that I remembered incorrectly - even this app doesn’t show feels like in the menu bar icon, I need to open it to see that information. But it has everything I need for free.
Left Panel: Displays a weather menu popup showing the current temperature, feels-like temperature, wind speed, UV index, and humidity. It includes an hourly forecast with precipitation chances and a weekly weather forecast for upcoming days. Right Panel: Shows appearance settings for the app. Options include radar customization (temperature, clouds, and precipitation), layout selection (expanded or compact), and toggles to control displayed elements like current temperature, alerts, condition graphs, and weather maps. Weekly forecast days can also be adjusted. The menu popup incorrectly opens at the bottom left instead of appearing in the menu bar.
Weather Forecast App: Menu Bar (Mac App StoreWeather Forecast App: Menu bar): Costs $10/year. Allows for multiple locations. Shows feels like, UV index and everything else I might need in the popup. The status bar value is the real temperature and there is no option to change that though.
Displays the current weather conditions for the selected location. It shows hourly forecasts for the early morning with cloud and rain icons, and a weekly forecast with expected conditions. Additional details include feels-like temperature, humidity, wind speed, and UV index. Sunrise time is also displayed for the current day.
Weatherly (Mac App StoreWeatherly): Exactly what I need, but is $10/year. It doesn’t show UV data but that is okay, I can see that on mobile when I leave my house, I do not need it on my laptop.
Left Panel: Shows general settings for the app. Users can toggle options like units (Celsius or Fahrenheit), startup preferences, and display options for the menu bar, including current conditions, feels-like temperature, and humidity. Additional settings include wind speed units, time format, and the option to display dates in daily forecasts. Right Panel: Displays the weather menu with current conditions, including temperature, feels-like temperature, humidity, wind details, sunrise, and sunset times. It also provides an hourly weather forecast for the night and early morning, along with a weekly forecast showing weather conditions for upcoming days.
Not Considering
I don’t like UI/UX
Meteorologist (SourceForgeMeteorologist): This is known as the most powerful app and it definitely looks that way. But I do not really like the submenu deign which requires me to hover over stuff to understand the weather. It is too text heavy for me, similar to Forecast Bar, and I am not a fan of that, even though I know some people might be.
Top Panel: Displays a weather menu showing current conditions such as temperature, humidity, dew point, visibility, pressure, wind speed, location coordinates, and sunrise/sunset times. The menu includes options for the radar, hourly forecast, extended forecast, and controls. Bottom Panel: Shows the app’s preferences window with customization options. Sections include settings for menu bar font, update frequency, and forecast limits. Users can toggle features such as weather icons, humidity, radar, feels-like temperature, extended forecasts, and hourly forecast options. The interface also allows enabling alerts, 24-hour time format, and access to location-based weather.
Forecast Bar (Mac App StoreForecast Bar - Weather + Radar): Something about this app is too hard for me to understand and it also requires a subscription, thought that is cheaper at $5/year. It does have location switching, weather forecast etc., it also shows feels like temperature on popup — I am unsure if it can show feels like temperature in the menu bar because currently it just shows the app icon.
Left Panel: Displays the current weather conditions, including temperature, feels-like temperature, and rain forecast. Below, a detailed hourly precipitation graph is shown, followed by a multi-day forecast with weather descriptions, temperature ranges, and precipitation percentages. Middle Panel: Shows general app settings. Options include startup behavior, keyboard shortcuts, radar provider selection, language preferences, and units of measurement. Additional features like enabling 24-hour time format, data saving, and server-side data management are available. Right Panel: Focuses on the appearance settings for the app. Users can customize the menu bar style, icons, and text color. It also provides options for forecast summaries, background images, hourly graphs, and touch bar customization. A summary section allows users to include or exclude specific weather details like humidity, wind, and moon phase.
Doesn’t support multiple locations
Vetero (Mac App StoreVetero° Weather): Also $10/year for showing feels like temperature, and doesn’t have location switcher.
Left Panel: Displays current weather details such as temperature, feels-like temperature, wind speed, pressure, UV level, humidity, and condition. An hourly forecast for the next few hours is shown, with an option to unlock additional features through “Vetero Plus.” Middle Panel (Weather Settings): Allows users to customize weather display options. Settings include toggles for wind, pressure, UV, humidity, and hourly weather. Users can control refresh intervals and enable premium features like additional hourly forecasts. Bottom Panel (General Settings): Offers options for units (System, Imperial, Metric) and status bar display. Users can enable weather symbols, temperature, and feels-like temperature for quick access. Right Panel (Subscription): Promotes the “Vetero Plus” subscription, offering more frequent updates and extra features. Options include monthly and yearly plans, supporting app development while unlocking premium features.
Weather Dock (Mac App StoreWeather Dock): Fixed to a single location. You can type another location in, but there isn’t a list you can switch between. You can show feels like temperature in the menu bar. Also costs $10/year.
Left Panel: Displays the current weather with details such as temperature, weather condition (light rain), humidity, wind speed, pressure, and air quality index. It includes an hourly forecast for the day with precipitation percentages and a weekly forecast for upcoming days. Middle Panel (General Settings): Allows customization of units of measurement (metric/imperial), pressure units, and weather icon styles. Options for hourly and daily data settings are also shown, with premium features like additional data display available. Right Panel (Menu Bar Settings): Enables customization of the menu bar button to display specific weather information. Options include feels-like temperature, temperature unit, air quality index, short weather descriptions, and city names. Premium features such as humidity, wind, pressure, and visibility are locked behind a subscription.
DatWeatherDoe: Free, shows UV index, allows only for one location and doesn’t show feels like temperature.
The image shows a weather menu displaying current weather conditions, including temperature, light rain, sunrise and sunset times, wind speed and direction, UV index, and air quality index (AQI). Below this, settings allow customization of the display. Options include selecting units (metric), weather source, and adjusting the refresh interval. Toggles are available to show or hide the weather icon, humidity, UV index, and unit symbols. Additional options include rounding off data, customizing text display, and positioning weather condition information. A refresh button and “See Full Weather” option are also visible.
Sparrow Weather (Mac App StoreSparrow Weather): Shows colorful today and tomorrow’s weather at current location. Is free, but doesn’t support multiple locations, and doesn’t show feels like temperature.
The image shows a weather menu for the current location with a detailed hourly forecast. Each hour is displayed with a temperature value and corresponding weather icon. Key details include sunrise and sunset times, which are highlighted in gray. One of the selected time slots, 9 AM, shows additional weather information in a pop-out box, including drizzle conditions, feels-like temperature, humidity, dew point, UV index, cloudiness, wind speed, wind direction, and gust speed. Below the hourly forecast, there are options for Tomorrow’s weather, Settings, and general app information like Help & FAQ and Weather. A Quit button is located at the bottom of the menu.
Weather Graph (App StoreWeathergraph weather widget): It is $24/year, so already more expensive than the apps that do what I need to. It doesn’t support multiple locations even in the paid plan. This is pretty, has loads of options to customize but none of the things I need.
The image shows a weather menu displaying the current temperature, condition, and feels-like temperature in the menu bar. Details include light rain forecasts, humidity, wind speed, UV index, and sunrise time. The weather data is provided by Open-Meteo.
The image shows the full weather app interface with a detailed hourly and daily forecast. The current temperature and “feels like” value are prominently displayed at the top, with a summary of current conditions (“Overcast now, then light rain”). The main section features an hourly timeline with temperature trends and weather conditions, displayed as a graph and list. Additional details include upcoming rain (drizzle at 3:10 AM), total precipitation, wind speed, humidity, and UV index. A daily forecast is also shown at the bottom with temperature ranges and icons for weather conditions. The app design is clean and modern, and the display format can likely be customized based on user preferences.
The image shows the preferences screen for a weather app with a dark theme. The interface displays various customization options organized into sections: Location and forecast settings are at the top, including options for weather source and alerts. System settings follow, with toggles for launch at login and menu bar icon display. The central section shows extensive graph customization options, including vertical and horizontal layouts, with sample weather visualizations showing temperature and conditions over time. Users can choose between different view styles like hourly graph, dual view, and vertical layout. The bottom portion contains detailed settings for data display, including options for sun glow, cloud style, temperature units, wind display, humidity metrics, and UV index. The interface ends with data privacy information and debug details.
Menubar Weather (Mac App StoreMenuBar Weather): It is a location permission issue — similar issue to BigWeather, so I needed to toggle permission on and off multiple times to get it to work. It is pretty good, has option to show feels like temperature, just doesn’t have a location switcher. Free if you use your own API (though I couldn’t get that to work) or pay $10/year for apple weather. Uses own API key, and I even when I provided that, I couldn’t get it to work. Also, the screenshots on App Store do not show an option to use feels like or switch locations. I even tried the trial option that remains stuck on the two seconds screen too.
When it works, it works pretty well. This is all the information I need for a given city at any point, especially in a dropdown. This includes current temperature, feels like, nowcast, forecast for 7 days, UV index, dew point, humidity, sunrise and sunset.
The image displays a weather app loading screen. The text reads “Two seconds…” followed by “We’re fetching your current weather”, with a loading spinner icon below.
It gets stuck on the settings screen, if you go out of the popup before you go back to weather display. This is the settings panel for MenuBar Weather with options for General, Customisation, and Keyboard shortcuts. Key settings include showing a gradient background, displaying temperature in Celsius with units, and showing weather icons, metric to display in menubar such as “Feels like temperature,” and keyboard shortcut for toggling the menu bar app.
Missing items in menu bar
Looks Like Rain (App StoreLooks Like Rain): This is a really pretty app with a location switcher and feels like temperature in the menu bar and costs $10/year. Also, it has an option to click on a day and see predictions for that day. But, it doesn't show the actual weather in the menu bar without clicking, which is a sad choice. If it showed the current temperature on the menu bar, it would be my preferred weather app.
The left panel shows the menu bar popup, providing a compact weather summary for New York. It displays the current temperature and condition (“Mostly Cloudy”), rain predictions for the next hour, and an hourly temperature forecast for the next 24 hours. Below, a daily timeline illustrates weather conditions like rain, clouds, and clear skies, with sunrise and sunset times. The menu bar icon though only shows if you need an umbrella. The right panel is the desktop app version, offering an expanded view with the same details as the popup but in a more comprehensive format. It includes a detailed hourly forecast for the next 24 hours and extended daily weather timelines. Each day is broken into visual bars showing weather changes, temperature ranges, and significant events like sunrise, noon, and sunset.
Dear Sun (Mac App StoreDear Sun): Doesn’t show feels like temperature. Costs $10/year. Everything else is perfect. I wish it had the feels like view though. I really wish this showed the feels like temperature, I would grab it in a second over Weather Forecast App or Weatherly.
Left Panel: Displays current weather conditions for New York, including temperature, UV index, wind speed, and precipitation. It shows hourly and weekly forecasts with weather icons, temperatures, and precipitation percentages. However, the “feels like” temperature is not included. Middle Panel (General Settings): Provides options to edit the display in the status bar and menu. Users can configure the unit system, number rounding, data sources, and the refresh rate. A toggle for launching the app on login is also available. Top Right Panel: Customizes what appears in the menu display, enabling options like Nowcast, hourly forecast, and daily forecast. Bottom Right Panel: Allows customization of the status bar display, enabling icons and temperatures while keeping options like UV index, wind speed, and humidity disabled.
Mercury Weather (App StoreMercury Weather): Too expensive for what I need ($14/year), doesn’t have any feature that I want in the menu bar. App of the year though, but doesn’t meet my needs at all.
Left Panel: Displays the main weather dashboard, showing current temperature, feels-like temperature, humidity, wind speed, and flood alerts. The hourly forecast is displayed above, while a daily forecast graph shows temperature trends and weather conditions for the week. A sidebar lists upcoming trips and locations. Top Right Panel: Shows an hourly weather overview with detailed forecasts for the next hours and days. It includes buttons to toggle the “Show Mercury” feature and quit the app. This is the menu bar dropdown Middle Right Panel (General Settings): Provides options to configure temperature settings, data sources, hourly chart details, and weather warnings. Users can toggle features like precipitation in charts and hiding alerts. Far Right Panel (Premium Features): Promotes “Mercury Premium,” offering additional features such as trip forecasts, menu bar integration, more locations, historical weather data, weather widgets, and Apple Watch support. The subscription includes a free 7-day trial and an annual fee.
Qwik Weather: Only shows the “nowcast”, aka, the current conditions in a location, and does have a location switcher. Allows to use your own API key, which makes sense given it is free. I want some basic hourly and current week forecast though.
Left Panel: A menu bar dropdown displays the current location (New York, US) with options to switch locations, refresh weather data, manage locations, access Preferences, and quit the app. However, it only displays the nowcast and does not include any forecast information. Middle Panel (Preferences): The Preferences window allows users to customize weather settings, including measurement units (Celsius), weather refresh intervals, and menu display formats. Users can toggle options like weather icons, AQI data, and color icons, as well as enable GeoIP-based weather updates. Right Panel: A menu format dropdown shows customizable options for the menu display. It includes variations of weather data such as temperature, cloud conditions, humidity, wind speed, and city names. Despite the display options, the app does not show a forecast beyond the current weather (nowcast).
Doesn’t work
Tenki (Mac App StoreTenki): I am unable to get the city to change and there doesn’t seem to be an option for multiple locations.
The image displays a weather app showing the current weather for Hulunbuir, China. The temperature is prominently displayed along with a “feels like” temperature, weather condition (clear sky), humidity, wind speed, air quality index (AQI), and UV index. Below, a daily forecast provides temperature ranges and weather icons for the upcoming week. At the bottom, it mentions the data source as OpenWeather, and options for Settings and Quit are included. The city appears to be stuck in Hulunbuir, China and cannot be changed.
I am going to try both big weather and weather forecast app or hope that menu weather pro fixes itself sometime in the near future, I guess. If there is any information that you want me to add about an app, leave a note on the bluesky thread or giscus and I’ll add that information for the app.
Remember how I was excited about the preview options in Arc, and then when I stopped using Arc, I shifted to Chrome and found an extension called MaxFocus to give me the same preview options (Read more here: Link Previews in Chrome using MaxFocus)? Well, Safari has an inbuilt option for link previews (and I learnt about it here) — you can use force touch on your touchpad or use ⌃+⌘+D while hovering over the link (similar to bringing up the dictionary) to bring up the preview popup. That is awesome! Honestly, there are so many shortcuts with ^ and ⌥ that I am still learning them (see other shortcuts you might like here).
Almost exactly an year ago, I had published Webtrotion. Webtrotion was mainly meant to be used by me, and it is still very much mainly just used by me, and no one else - and that is okay.
Almost exactly an year ago, I had published Webtrotion. Webtrotion was mainly meant to be used by me, and it is still very much mainly just used by me, and no one else - and that is okay. I made sure to provide enough configurability such that if I wanted to change things around later down the line without directly modifying the code, I could, and hopefully that would help other people adopt the website too.
In the last 2 weeks, I added 3 features: integrated umami tracking, added bluesky as comments functionality, and added RSS with content. And Dec 5, 2024, I updated to Astrov5.
Not much has changed honestly on first glance, and the images below are a bit different also because of the changes in original page, but you can see the slight differences here.
Screenshot of the newer implementation
Screenshot of the older implementation
Background Context
I had started my personal website back in May of 2023 based off Quarto. But I never enjoyed writing content in Quarto. Sure, I could make it look nice and fancy, but I had to accept the fact, that I would never like writing long-form content in markdown. I really love block based editors. It is a pretty simple UX change, but the fact that I can drag things around and put things side by side, apparently, matters a lot to me. So, when I could not stick to using Quarto and the convoluted yaml system I had, I decided to find ways that allowed me to author in anything else than markdown, and those that were free. Especially because, I just wanted to host them as a static website, I did not need fancy stuff, and I wanted it to be portable enough such that if my plans change, I am not hoisting it around.
Which landed me on deciding on Notion. Sure, I played around with other WYSIWYG editors, but no other editor comes close to Notion, and ultimately, that is why I was looking around in the first place, right? And I found two major camps of options — using Notion’s public site and render it on your own, similar to super or react notion, or use Notion’s API. I wanted to go down the API route. Why? It feels safer in a way. The biggest problem though, is that you cannot render collections through API — and I kinda made peace with it; if I was choosing markdown, I wouldn’t be rendering Notion collections anyway, so why should this make a difference.
Choosing to Develop Webtrotion
Honestly, when I first decided to use Notion as a CMS, there were hundreds of existing options. From GitHub solutions using public pages to platforms like super.so that could render databases (which I still can't), the choices were many. Then there were indie apps claiming to use Notion as a CMS - some popular ones with high price tags, and free ones charging based on the number of websites. I'm glad I chose none of those options. Since I didn't want to make my pages public, most options were automatically eliminated. I'm also relieved I avoided those newer apps because, except for maybe two (the expensive ones), all of them are now defunct - and you never want your blog to be defunct. That's why I use GitHub Pages. While GitHub Pages might not last beyond 10 years, that lifespan is acceptable to me. The typical one-year or six-month lifetime of an indie project, hoping for adoption from the Notion community though, doesn’t sit well with me. So, I decided to find a base repository I could develop that didn't use React (which I wanted to avoid) but worked with vanilla JavaScript and CSS.
Deciding on the Base Repository
Long story short, I found Otoyo’s github repository that did most of what I wanted, in a template format that I didn’t really like. I really like Astro’s cactus template, not because I had used Astro before, but because it was pretty aesthetic 🤷♀️. I knew that I didn’t know enough about the area to start this on my own, so I asked someone on fiverr to merge the two for me. The major change I wanted to make was, I needed to host the complete website, not just the blog component. So, late October, 5 months after I started using Quarto, I asked someone on fiverr to help me out — to combine otoyo’s github repository with Astro cactus’ looks.
And they did it! It didn’t have everything I wanted, but it did have enough for me to get started a month later, thanksgiving of 2023. And here were my initial commits to the repository, mostly focussing on improving the looks and making it look like I wanted to.
Adding Features
Over the past few months, I added multiple features to the setup. While I was making a decision, I did think, what if I found the perfect integration — and then realized that if I was dependent on someone else for a solution, I wouldn’t be able to integrate things that I wanted. So, I set out to do just that. Making it look more like Notion when need be, look a tiny bit better in many use cases, but also integrate some shortcode like functionality.
So, here is a list of all features added above and over what already existed in Otoyo’s and Astro Cactus’ implementation:
Improvements
Performance
Webp image transformation for reduced image size
Cache system that works with github pages
Preload and LazyLoad strategies for videos
Twitter and TikTok embed improvements
Access
Support for slugs in any language (auto and explicit)
Theme toggle between light and dark mode
Responsive font sizes (14px mobile, 16px desktop)
Scroll to top functionality
Improved accessibility with ARIA labels
Better semantic HTML structure
Enhanced keyboard navigation
Improved print styling and optimization
Converted checkboxes to icons in ToDo component
Match what Notion Does
Support for audio blocks
Support for custom emoji in mentions
Match colors with exact Notion colors
Block based linking support
Support for link mentions and link previews
Better table of contents with floating functionality (that also works on mobile)
Improved table styling with Notion-like gray tables
Tag descriptions support
Additional Features
Setup and Layout
Stream-like preview for posts (Google Blogger style)
View transitions support
Generate OpenGraph images
Add last updated date option
Add social links to footer
Allow font configuration for both text and open graph images
Support for google analytics and umami
Command+K (⌘K) or Ctrl+K search functionality
Support for BlueSky and giscus comments
Pin posts to top functionality
Recent posts on homepage option
Interlinked content section
Generate RSS, now with content
Content
Shiki transforms for code blocks
Popover implementation for links and mentions
HTML rendering options via shortcodes (@July 4, 2024):
<!DOCTYPE html> <!-- iframe --> for iframe rendering
<!DOCTYPE html> <!-- inject --> for direct body injection
Support for data tables with search and sort functionality
Looking Back
Honestly, I enjoyed this project a lot. It got me back into using JavaScript and experimenting with different interesting styles. I really like Astro, though there's an issue with it being so new - every update might bring unexpected issues that I run into, even within the same major version. And then, I do not like the newer content loaders for Notion, which tend to flatten everything. That's probably the next thing I'll look into.
I really enjoyed this process. I can add custom components and use this website for whatever I want. While it may not support newsletters (like substack) or have an integration with ghost/buttondown, and isn’t as future safe and easy/quick to build as a markdown based website, I think it was worth continuing because I've never updated a site as frequently as I have with this setup.
This project also led me to accept that I don't like writing in Markdown, which has been a significant "accept what I can’t change, change what I can, and know the difference" moment for me. I realized I prefer writing in block based, rich-text editors (so, mostly, Craft and Notion). Notion did launch their Sites 2.0 feature by the way, which offers almost everything you'd want, but it requires a custom domain and additional payments for that custom domain to be connected. I really wanted a static site, and while previous issues like changing slugs are now fixed, it's still a trade-off. You can at least showcase your inline database when using Notion's built-in site feature, though I wish there was a way around some limitations. But I get to do nice code blocks using shiki, and, I get bluesky comments.
All in all, I really liked this experience as I achieved my main goal: having a website that I actually want to write and publish on. That has been the most important outcome.
After Emily’s post, there was a flurry of efforts to use bluesky as a commenting system. I am actually surprised more people didn’t use Twitter as a commenting system when their API was free for all. That led to Cory creating a package, which was react based. So, I tried finding if people had previously added bluesky comments to astro, and of course they had: see graysky, snorre (also has the option to reply from the comment section but needs bluesky app password, I’ll revisit this after OAuth), and Jade’s (statically builds comments, and doesn’t update in real-time). Then I found Lou’s post here and was relieved to know that you can do this without react. I ended up using the version by Matt here (the person whose code I am using for Bluesky post embeds now work on Webtrotion as well). The good part about Matt’s version is that it has an option to search for did:{xx}plc:{{yy} format, instead of expecting people to find the exact at format URI. And lastly, I used this post from Jack to auto-search a post (and the idea of using a echo-feed-emoji). There is a react version of this (modified from Cory’s version) here by Nico.
Update Nov 27, 2024, 04:20 AM: I saw this cool trick by Garrickhere to filter out hidden, blocked or not found replies and added that to the script.
All this together, and webtrotion now has its bluesky commenting system. I am not going to add an explicit URL here to show that auto-search works, while I added an explicit URL to the Bluesky post embeds post.
How to use Bluesky Comments in Webtrotion
Remember, these are currently only read-only comments, interaction needs to happen on Bluesky (there is a handy-dandy link added on top of comments). Once Bluesky has OAuth, I’ll try to make it so that people can also comment through your webpage.
Step 1: If you are just now duplicating the template here, you should be fine. If you have already been using Webtrotion, add a property called Bluesky Post Link to the database with type as URL. Make sure that the property name matches.
Step 2: In your constants-config.json file, you will see this:
show-comments-from-bluesky decides whether you want to show a bluesky comment section at all or not. It is false by default, but if you turn it to true, bluesky comment rendering script will be executed.
Now, whichever link you paste into Bluesky Post Link for a post, that thread will be used to render the comments on your post.
If we used this system as is, given Webtrotion builds every 8 hours (configurable), we would need to wait for it to compile to post, then make a post on Bluesky, then copy that URL and paste it into the Bluesky Post Link field, and wait for it to build again. auto-search-for-match allows you to automatically search your profile for posts that mention the link (only parent posts, not in replies).
turn-on-auto-search decides whether you want to turn auto-search on or off.
author is your bluesky handle. You can specify this as handle (do not include @ sign) or as the did: protocol value. If you do not specify a handle here, auto-search will not be executed.
echo-feed-emoji: Only searches posts that mention the echo-feed character. If you set it to empty, it will search all your parent posts.
Initially, I hadn't even considered that Bluesky posts wouldn’t embed correctly on Webtrotion. I'd spent lots of time working on tweet embeds to make them look more aligned with Notion's style. Interestingly, Bluesky embeds don't even work in Notion right now—they just show up as cut-off web pages.
Then I saw this post (which I'll embed below) that lets you use Bluesky comments as post comments and shows threads in those comments. That sounded awesome! While implementing that feature will take some time, I tried to mention the post and discovered that embeds actually work. So now Bluesky post embeds work on Webtrotion—yay!
They still don't have dark mode support, and I need to adjust the line spacing and font sizing, but I wanted to share this now since, you know, just in case the platform takes off.
As of Nov 5, 2024, Stackie has multiple Twitter posts that could be interpreted as endorsements of Trump. I want to clarify that my testing of this app occurred before I was aware of that position, and it doesn't reflect the values I hold dear.
Stackie (FAQ) is a new app that was suggested to me on Twitter feed from HeyDola (another app that I use for scheduling from images and text, because it works with whatsapp and is free). And it looked fun! It is very similar in idea to what I have implemented in Use Notion’s Property Description As Text → DB add-itor (the example there being a money tracker) and comes halfway through what I mentioned in The Components of a PKMS. But this is a self contained app, has way better UX than hosting your own script, is clean and somehow really clicked for me, because it comes really close to what I wanted (want?) to make in Trying to Build a Micro Journalling App.
To be honest, Notion's AI properties and Notion’s AI add option will get you there pretty often. It is probably too much for you would want if all you are looking for is tracking. There have been other apps that do something similar — hints being the one I can recall off the top of my head, but they all integrate with external apps or are meant for power users or developers (for example, AI add to Supabase database).
When you open the app it starts with a baseline of inbox database. It comes with its own templates, and ideally you should be prompted to select at least one during onboarding to get a feel of how it works. The templates are prompted databases, where each field can either be a date/time, number, boolean or text. The templated database and properties are all customizable which is a huge win!
The entry box when you have created all your “stacks” let's you type in anything and chooses which stack it is most likely to belong to — another affordance I really appreciate. It works with photos too, both understanding the text in the photo (so you can capture a snippet of an event you attended if you are tracking all events you attend in a month), and understands the objects in the photo — so you can click a photo of a cheeseburger and it will understand that it should go to the calorie tracking stack and figuring out the breakdown of nutrients for that log. And it works with voice, so you can speak and it will transcribe and process that information. It seems to use internal dictate option, so doesn't seem to be as good as whisper (proper nouns are hard for example) — but I might be wrong about their processing mechanism.
It can process into multiple databases and add multiple entries at once! It seems to only be additive at the moment though, you cannot edit entries through the universal text box (you can go to the entry and edit it though). There is no export option, but that disappointingly seems to be the norm for iOS and beta apps. You currently cannot do anything with the data you record like you can do in Notion (add it up, set limits etc), so it might not be satisfying to use as a habit tracker and hard to get a view of data you might want, but it is a great starting point. It is what Collections DB could look like, with integrated AI. The app is iOS only, so wouldn’t be something I use, but definitely something worth looking at.
Some images from the app
This screen displays all my current stacks: Inbox, Calorie Record, and Mood Tracker. Inbox is a special pre-existing stack where you can create notes without assigning them to a specific stack. Later, you can combine these notes into a new stack if desired. Calorie Record has multiple properties, such as food input, unit, meal time, and more. The Mood Tracker simply contains two values: the recorded time and the general mood.
You can create a stack through prompting, which is similar to building a database but using natural language instead. This approach allows you to specify properties, and conversions into structured formats. It's an excellent method for creating a database without using the detailed user interfaces found in tools like Collections or Notion.
The best part is the option to create stacks from templates. These templates come with pre-prompted fields and extraction prompts, ranging from AI-generated content for all fields to logging and note-keeping with multiple date properties.
So, for example, if you select the calories record, you will see the stack name, the prompt stack indicating which properties should be there, and each property by itself. You can have any number of properties, and you can edit and rearrange them as needed.
Here, I click into a property in the calorie record named "calories." If it's not provided in the input text, the prompt estimates its value. This property is a number, which I can delete, edit, or change its type.
Here's what that stack looks like. I've added some random records, not actual data from real life, but this is how it appears. One thing to note is that you can use phrases like "yesterday," "today," or "two days ago," and it will adjust the date and time accordingly. It stores both the specified date and time in the field as well as the creation time, similar to how it would work in a tool like Notion.
I can also prompt or add text that contains multiple instances I want to record in a stack. For example, I'm mentioning that I ate cabbage last night and Maggi yesterday morning. My hope is to record these two instances separately as different food items.
It does indeed. The app records two separate items at different times, estimating meal times for dinner and morning quite accurately. Overall, it captures the entire arrangement quite well.
Here's my attempt to try out the Spanish words template. The idea here is that you aren't adding any information at all. You're just adding the word, and all of the other information is actually generated by AI. So it's not just structured extraction; it's generation and extraction combined into one, which is really cool.
So, here I added the word "beuno" to the stack. I'm using the general input, so my hope is I would first determine that it needs to be added to the Spanish word stack and then add that information to the stack itself.
It accomplished that quite successfully, so now I have a Spanish word, created a sample sentence, explored multiple variations, chose the correct approach, and decided on the appropriate action.
And this is what my main page looks like now, with a new stack added beyond the calorie and mood tracker.
Disclaimer: I received a free license key to test this app and post a review. The review content is entirely my own, with no conditions placed on its content or sentiment.
Popclip is the usual app that people use for interactions with content on MacOS. Xylect takes it one step further, it aims to function like Google’s smart lens AI feature, it tries to predict what someone might want to do with selected text, rather than giving you a list of all possible options.
Language selection dropdown, Keyboard shortcut recording button, Auto popup when double clicked checkbox, Enable debug logging checkbox, Translate or Summarize preference radio buttons, Enable explain & summarization checkbox, Enable "Add to calendar" checkbox, Enable spell check checkbox, Enable math expression evaluation checkbox, Enable flight tracking checkbox
So, let’s go through all the five (six?) claimed abilities at the moment. [Expand toggles to see screenshots]
The summarization feature works fine, but with the new writing tools in the latest version of Apple Mac OS, you probably won't need it. You can simply right-click on non-native apps or click the magic button on native apps to summarize content, and it will show up in a pop-up. I know it's an intentional step rather than an automatic pop-up, but I don't think it's worth getting the app just for this feature. Xylect is faster than Apple’s summarization, and that time savings adds up when you use the tool multiple times a day.
Popup showing the summarization from Xylect
Popup showing the summary from Apple’s new writing tools
I checked out the translation feature, and it seems to send the text to a backend model for translation, which is fine as it includes contextual information. However, it responds with a lot of text, even when I'm trying to translate just a single word. I wish this was more thoughtfully designed.
Provides a full explanation of the translation for a Chinese word instead of just the translation itself.
The "Add to Calendar" feature works, but it requires a very specific date and time format. It doesn't fit in the title itself; it just creates a Google Calendar or Apple Calendar link based on the time. I'm not a big fan of that. Instead, I use Hey Dola for these purposes. It's free right now, though it might not stay that way. I'll link it here if you want calendaring with text or images, which makes it much easier.
It works when you just select a time string
It doesn’t work when you select a title and time string
The spell check feature in Xylect works per word and doesn't understand context. So, while it can correctly spell "enable," it corrects "flght" to "fight" instead of "flight" because it's choosing the nearest correct word rather than the one that fits the context, even though you have the word "tracking" nearby.
Correctly suggest the fix Enable for spelling correction
Incorrectly (contextually doesn’t make sense) correct “flght” to “fight” instead of “flight”
The calculation option for the selected math text didn't work for me at all. It seems like it's sending the text as a prompt to a machine learning model and returning the answer, rather than performing the calculation on the backend, so I wouldn't rely on this feature.
The calculation option absolutely did not work for me, the answer is incorrect.
The correct answer to the expression shown in Google calculator.
The flight feature is nice, but I often end up using Raycast for flight tracking anyway. Some flights work, some don't. It's integrated with FlightAware, so I'm not sure how much of it is a Xylect’s issue versus a FlightAware issue. It seems to work for US flights but not for Indian flights, whereas I can use Raycast for both types of flights.
It cannot pull in information from a Indian flight number (6E 798)
It can pull in information and show as a FlightAware popup for an American flight — here I use DL 866 as an example.
Checking out the Raycast competitor, created by a single person, featuring some interesting modes. The question is, will it live up to its promises? Let's find out!
Aug 19, 2024: ^ + Delete does work for deleting a clipboard entry (here), monarch notes popup on screen even in full screen mode unlike many other apps including Apple Notes’ Quick Notes (here), and system-wide snippets are in work (here)
Introduction
Last November, a new launcher called Monarch was released (public version available since Februrary 2024), and I really liked the idea behind it. I wanted to test it out, but I usually wait 10 to 12 months before writing about or testing an application. This is especially true given the current indie app development culture, where apps are often launched for a short period to gauge interest and then shut down if they don't gain enough traction. Now, 10 months have passed since Monarch's launch, and it's become quite popular on Reddit, so I decided to give it a try.
This is the Monarch window that appears when you first open the app. I used it when they released version 0.6.7, which includes checklists in note capture, code blocks, improvements to superlinks, the ability to edit and delete actions from superlinks, and custom themes for notes and highlight colors within the application.
Notably, this will be a completely blind test for me—I haven't used Monarch before, seen it in action, or watched others use it. It's rare for me to have the chance to test an app without any prior exposure, so it will be interesting to see how this experience unfolds.
When you open Monarch, it greets you with multiple permissions, which is fairly normal for any Apple application. Then, it shows you a box with the latest update for Monarch. The screenshot below is a scroll capture, but it has a normal-sized pop-up window similar to what you would see in Raycast.
When you open Monarch, you don't get a walkthrough like you do in Raycast, which makes it significantly more difficult for me. Even though I've used Raycast before, part of the difficulty comes from the fact that you don't have any other method to use this application without relying on key combinations that aren't shown to you unless you've read the user manual.
It starts in a collapsed mode similar to Raycast, rather than showing all previous commands, which is fine as a decision. Then you have to switch modes using the tab key. While it does say you can press tab to move to a different mode, it's very unclear how to trigger certain actions within that mode.
Initial Walkthrough
Playing with Clipboard History
For example, when I took a screenshot, it got saved to clipboard history. You can press tab to move to a different mode, but the only visible option is using the enter key, and everything else is hidden. You might see numbers on clipboard items because I pressed ⌘ + ⇧ + 4 to take a screenshot, which I wouldn't have known without reading the user manual. Unlike Raycast, where you can press the ⌘ + K to see actions associated with an item, you can't do that here.
Monarch window displaying clipboard history and quick paste commands for the top 10 recent items.
Another important point is that clicking doesn't work at all in Monarch for me; you always need to use keyboard shortcuts. This doesn't sit well with me, even though I'm a keyboard person. Sometimes I prefer clicking around rather than trying to remember or figure out key commands, especially since it's hard to know which key commands might clash with other system-wide shortcuts.
Playing with Unit Conversions
One of the first things I try with any launcher application is how well it handles unit conversions and similar tasks that I used to rely on Google for. Since I started using Raycast, I haven't needed Google for these tasks, and Monarch works pretty well with unit conversions.
However, one odd thing missing in Monarch, which kind of works in Raycast, is the ability to combine two currencies and get an output in another currency, even if it's one of the two. This feature works for measurements, lengths, distances, and similar tasks, as shown in the screenshots here.
I tried doing a calculation as 5 INR + 50 USD in INR and it responded in USD rather than responding in INR
I tried calculating 5 feet plus 22 inches in centimeters, and it worked perfectly, giving the result in centimeters.
Another feature is that all calculations you perform are saved as commands and appear in your recent history. However, filtering these commands by their origin, such as whether they came from the calculator, isn't easy, so you often see a mix of commands based on your activities. I really like Raycast's option to view just the calculator history, which includes all conversions and calculations, but that's something Monarch lacks. Additionally, I couldn't open just the calculator history without performing a calculation, which might be why I found the feature to be missing.
Monarch also seems to be missing features like a flight tracker for pasted flight codes. I also had trouble with time calculations, especially with daylight savings changes, but don't quote me on that since I didn't test it thoroughly. Overall, Monarch seems fine to me. It's not particularly special, but it handles all the calculations you might need with a system like this.
Playing with Settings
The first thing I wanted to do was change Monarch's shortcut. It was such a hassle with the cursor sometimes not working and the main instruction at the bottom not doing its job properly. I had to go into the settings, change it, and press enter to save it because initially, the enter key prompt kept getting stuck on copying the answer to the clipboard. I really didn't like that experience. You can change the shortcut and kind of change the theme, but you can't sync it to the system, at least not in the way I saw it. So, you always have this dark screen, dark theme pop up. I'm a fan of system-synced settings rather than dark themes, which might be influencing my opinion here.
Monarch's setting window where you can decide the shortcut as to how to open Monarch and set the theme of the application
Just a heads-up: the "Manage Keyboard Shortcuts" is actually a button, which wasn't obvious to me at first. In hindsight, it should have been. Clicking it opens a panel within the application where you can change the keyboard shortcuts. The process is quite unintuitive. After clicking to change a shortcut, you need to click another button on top of the pop-up that says "Click here to record a new shortcut," then press Enter to save it, and press Enter again to save all shortcuts. I expected to just click and change the shortcut directly.
Monarch’s managed keyboard shortcuts window lets you set shortcuts for emoji clipboard, toggle clipboard history, color picker, note capture, audio devices, and instant send, though the process can be a bit convoluted.
One crucial point to mention is that you can only have three keys in a shortcut, with two being modifiers, or four keys, with three being modifiers and one a letter. This is important because many people, like me, are tired of clashing keyboard shortcuts and often use a hyperkey, where the capital key maps to Command (⌘), Option (⌥), Control (^), and Shift (⇧) pressed together. Unfortunately, you can't set shortcuts that work with a hyperkey in Monarch.
Starting with Using Monarch
Reading the Manual
So I think this is where I ended up reading the manual. Much of it didn't make sense to me. There were so many hidden keyboard shortcuts. I'm not usually a keyboard person per se, even though I use keyboard shortcuts a lot. It seemed like it was still very much an alpha version (even though it is a public release and not a closed release version), aka it's still a 0.6.7 version, not a 1.0 version. But there were some things that did not feel intuitive to me at all. And so I ended up reading the manual that they had on their website.
Hidden Commands
After reading the manual, I discovered several hidden commands that don't appear when you type them in the search bar. This means that even if you're specifically looking for them, they won't show up, which I find frustrating.
When I type the command symbol in app launcher mode, it doesn't display the command in the list of available options, even though symbol search is available in Monarch.
Monarch has a great emoji and symbol picker. Personally, I'm not a huge fan of Raycast’s picker and often use Rocket because I prefer Slack-style emojis. As you'll see in the screenshots, even if you type "symbol," you won't find the symbol command in the application. You really need to read the manual to use the app effectively, which isn't a great user experience. If someone types "emoji" or "symbol," they should be able to find what they're looking for. While there's a shorthand using a colon to search for emojis or symbols, this should be included as a learning feature rather than the only method.
And as we saw when l explored the settings and keyboard shortcuts, you'll find an option to set a shortcut for emoji search. However, unless you know this feature exists, you won't go to the keyboard settings to set it up. You can't set the shortcut right when you open the panel; you need to go to the settings. This is one advantage I miss from Raycast, where you can easily configure and set up commands with keyboard shortcuts directly in the panel. Monarch only allows certain commands to have shortcuts, and you must go to the settings to configure them, unlike Raycast, where you can do it in the panel itself.
Popup Window Woes
Your browser does not support the video tag.
Showing that Monarch does not remember window position between instantiations
One of the odd things about Monarch is that it doesn't remember the position of the popup window. As you'll see in the video, whenever you drag the popup window to a new spot, it won't launch in the same position next time. Additionally, some windows can be dragged while others cannot.
For example, the window that shows updates and the settings window cannot be dragged, but the general popup window can be moved around, although it won't remember its place. This is quite frustrating for me because I often have something on the side that I don't want to be covered by the popup window. You can't resize it, which is expected and doesn't bother me, but I really hope this issue gets fixed in the next version.
Monarch Modes
I read Monarch's blog and found their mode-switching system, which you can access using the Tab key, quite confusing. The distinction between modes and built-in applications isn't clear to me. When I look through the commands, it's hard to tell what qualifies as a mode versus a non-mode. For instance, there's an app launcher mode, a clipboard history mode, a color picker mode, an emoji picker mode, and even a note mode, currently not mentioned in the user manual. However, I don't see why the emoji picker is a mode while the definition command is part of the app launcher. It seems more practical to integrate all these functions into one command or panel. Raycast has a similar setup with sandboxed components, but Monarch doesn't seem to utilize this approach effectively by separating out and giving direct access to the history. Overall, I think the concept could be improved by simplifying it into built-in commands versus system information access, rather than cycling through different in-command applications.
App Launcher
I won't go into much detail about app launcher mode because I think you're already familiar with it, similar to how you'd use Spotlight. You can toggle various system commands, open applications, use shortcuts for those applications, perform calculations, convert units, manage time zones, control volume, open folders, search between them, create superlinks and variables (which I'll discuss below), open webmarks in any browser, view your command history, and quit or launch applications. It's pretty much what you'd expect from any Spotlight or Spotlight replacement that acts as a launcher.
Clipboard History
Alright, the clipboard history here is similar to any other clipboard history. However, one unique feature compared to Raycast is that you can paste multiple items and pause the clipboard.
You might wonder why you’d want to pause a clipboard. Well, all clipboard histories, whether in Raycast or here, avoid storing information from keychain access, 1Password, Bitwarden, etc. Sometimes, when working on something confidential or in an incognito tab, you don't want the clipboard history to be synced. In such cases, you can pause the clipboard history here, which you can't do in Raycast. Passwords aren't saved in either, so that's fine.
Now, let me explain why I dislike the missing command key or action key here. I had to thoroughly read the entire clipboard history manual to figure this out. You can navigate with arrow keys, but deciding which items you want—whether all items, text, links, or images—is really hard to figure out. The cursor sometimes works, sometimes doesn't. You can edit a clipboard history by hovering over it and clicking the edit button.
The manual says you can delete a clipboard history, but as you'll see in the screen recording, pressing ^ + Delete didn't delete the clipboard history item for me.
Update Aug 19, 2024: I just realized that I am using ^ + D not ^ + Delete to delete the clipboard item. That is on me. The ^ + Delete shortcut does work, please ignore my attempts at deleting the clipboard entry in the video.
You can rename clipboard items, and you'll see in this screen recording that I can rename any item and then search based on that name. Another interesting feature is that if you have a color in your clipboard history, it renders that color, making it easy to identify. You can also multi-paste by selecting multiple items, holding the Shift key, and then pasting them all together.
Your browser does not support the video tag.
Showing clipboard history in action, with rename, edit, paste — and showing that I could not get delete history to work
However, I didn't see OCR search, which is built into Raycast. I might be wrong, and if I am, I'll issue a correction here, but I think OCR history is one of the biggest reasons I use Raycast clipboard history.
Monarch also doesn't show the number of items and the history length. Raycast does, but it's pretty hidden on their website. It has a storage limit of 256 items, so if Monarch has more, that's a good deal.
Color Picker
The color picker here is like any other color picker, but there are many hidden keyboard commands. To pick a color, you need to press Command + 1 or click on the color picker, which is fine, but it wasn't obvious to me at first. Personally, I use the extension color picker in Raycast. One thing I like about this color picker is that all the commands are contained inside this panel, unlike Raycast where each command can show up when you search for something. This might be something you like or dislike. For me, it's something I'm still ambivalent about. Some days I like seeing each command in the search, some days I don't. Here, you have the option to pick colors, add them to palettes, and save them.
You can create multiple palettes and see all the recent colors you've picked, then save them to different palettes. There's nothing special about it, but one thing I like, unlike Raycast, is that it names the colors. I find that really interesting. I don't know where these names come from, but seeing names like "ebony clay" or my usual "Nerdy Momo Cat color," which is apparently "albescent white" (which I thought was a form of light pastel orange), is really interesting. I have no qualms about this; it works as it is supposed to.
Additionally, the colors copied to your clipboard show up as tiny colored snippets, making it easy to go through the list and pick all the colors quickly rather than adding them to palettes. Great job on the color picker mode here!
Color picker adds all colors to recent colors
Color added to a palette in color picker
Notes
Lastly, let's talk about notes. These pop-ups appear in the same window, and you can set a keyboard shortcut to make them immediately visible. The notes are saved as markdown files in a folder you choose.
You can type in markdown and also use keyboard based formatting aka ⌘ + B bolds text and so does **, create task lists, and paste images, which is pretty cool for a markdown-based note-taking app. It saves images in base64 format, and I've attached a screenshot. The links don't seem to work for me, though they might for others. You can have a scrolling node, but only one note can be open at a time. You can create multiple notes, but it's a pretty basic popup like note application.
Your browser does not support the video tag.
Working with notes in monarch showing use of H1, bold and adding an image
The note is saved a markdown file and the added image is converted to base64
I'm unsure why you would use this over something like Apple's Quick Note or opening a note in a new tab and making it stay on top. As someone who has invested time in various note-taking applications and isn't a fan of markdown, this is nice but not essential for me.
Update Aug 19, 2024: You might prefer Monarch notes if you want to avoid system lock-in, maintain a repository of notes on your own system rather than in an inaccessible database, and use floating notes in full-screen mode. Monarch is one of the few pop-up note applications that works with full-screen applications, unlike Apple's QuickNote or many other similar tools.
According to the developer, there's no syncing of notes between installations. However, if you sync the folder itself, you can achieve syncing by pointing to the same folder where the notes are saved. The notes work fine, but I couldn't find a way to keep them on top, which I really wanted. The developer mentioned on Reddit that there's a way to do this, possibly with a keyboard shortcut, but I couldn't find it.
However, it's better than any Raycast implementation of notes I've found. It's not a sticky; it's an actual note, which might be a useful differentiation from Raycast if you're considering why you might need another note-taking app.
Available Commands in App Launcher Mode
Search the Web
Search on the web as an option in app launcher mode
There are several reasons why I've been considering whether I need a launcher other than Raycast. I'm not necessarily looking to switch, but if there was something unique, I might. One reason is that you can't search the web directly within a Raycast window; it always opens the search term in a browser, which isn't what I'm usually looking for.
Sometimes, I just want to see the results in an iframe within the application. I'm not sure how difficult that is to implement in a macOS application, but that's my ideal user experience. I was curious if Monarch could do this, but unfortunately, when you type a term and click "search the web," it opens in the default web browser instead of showing results within the app. This is unlike some launchers on Linux (the names that I cannot recall at the moment), which display results in a pop-up, making it especially useful for looking up places on Google Maps, or searching for a term quickly for example.
Shortcuts for applications
Using gc as a shorthand for launching an application but it is not ordered by recency (google chat shows up first, because it is in alphabetical order)
You can create shortcuts for accessing applications, like typing "GC" to open Google Chrome instead of typing the full name every time. However, keep in mind that these applications are ordered alphabetically, not by frequency of use. So, even if you click on Google Chrome a hundred times, Google Chat will still appear above it in the pop-up, as shown in the screenshot. It doesn't learn from your habits. I'm not sure how difficult it is to implement a feature that learns from usage, but for now, you'll need to type "GC" or something similar to open Google Chrome each time.
Emojis, Symbols and Definitions
Continuous select in emoji picker
As I mentioned before in hidden commands, emojis and symbols are a really nice addition and quite useful. In Monarch, you also have the option of continuous select, which I discovered by accident when I kept pressing the command key to take a screenshot. You can press command and enter to continuously select multiple emojis in a row and then paste them all together. The search is fine, though it doesn't offer synonym-based search or mapping particular emojis to search terms for later use. It's pretty basic but somehow feels better than Raycast in general. You can also search for symbols here, so if you type "rupee," you'll find the rupee symbol, which is easier than using the globe key Apple inbuilt search, which I've never been a fan of and never ended up using.
Your browser does not support the video tag.
In the screen recording, you'll see me trying to type the command "define," but it doesn't show up. However, when I use this shorthand, Monarch internally defines the word, and you can see its definition, which I find pretty useful.
Another useful feature is definitions. Definitions are another hidden command we talked about before. You can't use "define" as a command, but you can use quotation marks and then type the word to open its definition. It's very similar to how you would do it with a right-click popup in the Apple system itself.
Open Camera
The "Open camera" command launches your default camera app, but the escape key doesn't fully close it; instead, it minimizes to a single entry view without actually shutting down, which can be a bit buggy. Be cautious using this command if you don't want to force quit and restart the app. This issue though was observed on the new macOS 15.1 beta, suggesting it could be a beta-related problem rather than an issue with the app itself.
Variables
Your browser does not support the video tag.
Here’s a screen recording of me defining a DFN variable, which stands for "days from now," as mentioned in the manual. This variable will only expand in Monarch and not elsewhere.
Variables in Monarch are like snippets or text expansions that work exclusively within the app. They can be quite handy if you need an expansion that you only want to use inside Monarch. For instance, you can set up a frequently accessed link using the "search the web" command with your preferred browser. You can also use variables for calculations, such as managing a specific rent value or an account with a particular interest rate.
Unlike Raycast, where snippets expand anywhere, Monarch's variables expand only within the app.
Update Aug 19, 2024: The developer informed me that a system-wide snippet expansion tool is in the works and should be available within Monarch soon. It's important to note that the developer views variables differently from snippets, which is accurate. For instance, you can set a variable like "rent," and since it's sandboxed within Monarch itself, you don't have to worry about every occurrence of "rent" in your entire system being replaced by your specific rent value. However, you can still perform calculations using the rent variable. Moving forward, you can consider variables as Monarch-sandboxed expansions, while the upcoming snippets feature will function as system-wide text expansions.
Superlinks
The Superlink screenshot displays entry fields for name, description, link, match check, and fallback links. Not pictured is the option to choose which browser or application the link opens in.
I'm honestly not someone who uses superlinks much, so I'm just going to leave you with a screenshot showing all the options you can configure in superlink. You can decide whether you want a superlink to go to a particular link and which browser to open it with. You can also set it to match a check so that it brings an already open tab into focus in your browser. I don't think Raycast has this feature at the moment. You can define dynamic variables in these links, etc. Personally, I've never found an obvious use for superlink. I often forget the query term I need, whether I'm navigating to GitHub or doing a Google search.
If you have any superlink users or applications you think I should try out, I'd love to hear more about it in the discussion below. It seems to have robust support for superlinks, similar to how they work in Raycast.
Instant Send
Instant Send is a feature that lets you call Monarch based on a text selection, similar to how you might use PopClip, but now with Monarch. It includes built-in recognition tools. For example, if you select a link, it offers options to create a super link to open with a specific app, copy a clean URL by removing query parameters, search the web for that URL, and trim the text.
When you select a general piece of text, you can invoke an instant send option, along with options to search the web, trim, change cases, and view the word and character count for the selected text.
If you select normal text, it still offers web search but also allows you to trim the text and convert it into different cases like sentence case, Pascal case, Camel case, etc. The most useful one might be converting it to title case and back to sentence case.
This demonstrates how I selected general text, but it retrieved information from my clipboard about a link. When the instant send option receives a link as information, it offers the choice to create a superlink, open it, clean the URL, search the web, or trim the text.
This highlights the error I encountered 90% of the time when using instant send, which could be because I'm on the beta 15.1 version of MacOS.
Currently, I'm on macOS beta 15.1, and often when I select text and press the Instant Send key, it triggers an error in Privacy and Security. I'm unsure if this is a macOS beta issue or a Monarch issue. Out of 15 attempts, it either didn't use the selected information and instead used the clipboard data, possibly due to the assistive API not working, or it opened an error box, as shown in the screenshot. I only managed to get to the screen for selecting normal text and seeing its actions about one out of 20 times. When I pressed sentence case or title case, it sometimes ran the text action but couldn't replace it, likely because it was reading from the clipboard instead of the selection.
I know assistive APIs in macOS can be messy, but I feel this feature isn't polished enough and needs improvement. I should note that I'm using it with the Electron app Notion, and I'm not sure if that interaction affects it, but I wanted to make that clear.
Conclusion
I generally like the idea of Monarch Launcher, but it doesn't quite match up to Raycast. There were three main reasons I was looking for an alternative to Raycast, one of which was notes. I want something like Numi but without built-in calculations. It should stay on top, allow multiple notes in a single window, and enable quick switching. I don't need markdown, but I do want highlights. Monarch comes closest to fulfilling my needs in this area.
Additionally, I appreciate the ability to pause clipboard history and select multiple items to paste, a feature I wish Raycast had. It feels like Raycast is adding more features for pro users, which I find disappointing. My main reason for exploring other launchers was to see if anyone had replicated Raycast AI using personal API keys. I like how Rayacast AI stays on top, allows multiple conversations, and has a nice UI/UX, but it only works for the inbuilt Raycast AI mechanism, which canmot be replicated by any extension. I was curious if Monarch could use API keys for cloud or OpenAI, especially since Raycast Pro is expensive and has usage limits.
The UI/UX polish of Monarch is geared towards heavy keyboard users. I missed Raycast's popup transparency when I switched back. I don't like how keyboard-focused Monarch is without visible actions. I prefer keyboard shortcuts for frequent tasks but want visible access for less common actions. Configuring keyboard shortcuts for specific commands is also difficult, and the animations feel janky, possibly due to my reduced motion settings. It works as a Windows application, and the list movement feels janky and non-macOS-like.
I might revisit Monarch in a year or two, but for now, Raycast offers much more. I also wanted to add that, personally, I don't mind apps with many features as long as they meet my needs. I prefer apps that can maximize functionality down the road rather than those where a selling point is having a limited feature set, and this is especially true for apps I use often and are deeply integrated into my workflow. Monarch seems designed for users who want a better spotlight without a freemium model, but maybe, I'm not the target audience for daily-use apps that aren't cluttered by features I don't often use.
I do app reviews for fun and would never put them behind a paywall. If this helped you in anyway, please consider buying me a coffee here:
I came across this post on reddit today where they use birefnet for background removal and then Florence for object detection, and the Florence model ends up restoring their background. No, this isn’t a training data issue. TIL, Apparently some background removal apps just set the transperancy of background pixels to 0 — they still have all the original color information in them. And all someone would need to do to restore the image would be to set all pixels to be opaque.
A pretty huge privacy risk, especially for someone like me, who uses this as an alt account
A screenshot of a reddit post asking why a model can read background from their “background removed” image, showing florence input on left with no background and complete original image on right with all information.
Aug 1, 2024, I saw ProperHonestTech’s video on my Youtube timeline titled “Mac owner? STOP doing these 11 things!” — and I don’t usually watch these, but this one I did. And I learnt two new things.
MacOS has inbuilt screensharing
You can assign documents to behave as templates
Screensharing
You can learn more about screensharing on MacOS here. It seems like it internally uses a Facetime API because you see those controls pop up in the menu bar.
[Expand toggles to see screenshots]
Screensharing is a hidden application. It doesn’t show up on your Application or Launchpad. You need to open screensharing app using Raycast or Spotlight.
A raycast window showing screen sharing application
It opens up a screen showing all the connections you have. You can also create groups to manage all connections you have had.
Then you can enter a hostname or apple id (yes, any apple id) to connect to.
And voila, now you are screensharing.
Screensharing screen showing the controls: namely remote control, dynamic resolution, hdr sharing, launchpad, mission control, and desktop buttons with zoom buttons on top right of the window.
You can also modify what you see on the toolbar
And modify the settings for display scaling and quality
Display settings menu showing options for screen scaling and scrolling behavior on larger screens, with selected choices highlighted.
Quality settings menu for remote screens, showing options to adapt quality to network conditions or display at full quality, with adaptive option selected.
Screensharing shares microphone by default. You can mute the microphone in the Connection menu item. It also shares clipboard by default which can be changed in the Edit menu item.
You can copy paste or drag text, images and files during screensharing. Universal clipboard is not available during screensharing. You can’t have both Screen Sharing and Remote Management on at the same time.
Document Templates
You can create document templates, such that you don’t need to worry about modifying the original document, hence, called “stationery” in MacOS. To do this, select the document and use Get Info or ⌘ + I . In the general section, select Stationery pad. Make sure it is a document you can edit, and not a folder or an alias.
Now, everytime you open that document, it will duplicate itself — without you needing to worry about modifying the original.
I have this little shortcut that takes an URL, gets HTML of the page, takes a screenshot, tags it based off some heuristics and saves it to Notion. The most frustrating part has been to use my trackpad to click done. Well, apparently you can press fn + return or 🌐 + return to do it! And no, ⌘ + return or just return doesn’t work here.
Side note: Accepting text replacement on MacOS doesn’t happen with tab or return; it happens with space.
A screenshot of a popup saying Auto-Tag Tools in Notion with a link pasted — and Cancel/Done options. Use fn + return or 🌐 + return to “click” Done here.
Today I learnt that Tailwind doesn’t automatically scale base font sizes based off screen or viewport sizes. Sure there are plugin options and complicated stuff, but I am not a front end wizard, and adding this to my global.css helped. You check out the difference in font size for this website on mobile and desktop.
If you are like me and use CapsLock as your hyperkey and sometimes wonder why it didn’t get triggered, apparently there is a built in delay. You can turn it off by using the following command in your terminal.
TIL: You can set an accent color in CSS and it magically updates across your HTML user-interface controls! Just one line: accent-color and your whole app gets a color refresh!
Just like this →
body { accent-color: #ffc0cb;}
Below are test html iframes with different accent colors
Leaflet is a new publishing medium from hyperlink.academy, made to distribute pages (or expanded leaflets) in the wild — think very simplified Google Docs, but nested. I tested out their alpha version.
The main reason this feedback is in Notion rather than in Leaflet, is (a) I like it to be in one place where I can access it later rather than it being lost, but, more importantly, (b) I really want toggles in leaflet for controlling information overload visibility.
Last tested Jul 13, 2024
Testing on Jul 13, 2024
This is the first time I am trying Leaflet. Instead of trying to use Leaflet and then jot down my notes later, I instead decided to record my thoughts and then summarize them.
tl;dr
(disclosure: generated with gemini)
Initial Setup & UI Quirks:
Link Redirection with Notion-esque IDs: Right off the bat, Leaflet generated a unique ID for my document. The format reminded me a lot of Notion, which threw me for a second.
Button Placement & Responsiveness: The layout is clean with a top button and a cool gradient button at the bottom that says "Write something." Resizing the window works well; the buttons adjust nicely.
Confusing Toolbar Situation: Clicking "Write something" unleashes two toolbars – one at the top right and one at the bottom. Here's where it gets weird:
Both toolbars stick around even when the screen size changes.
Three icons are duplicated on both toolbars. Not sure what the thought process was there.
Tooltips on the top icons appear faster than the bottom ones. I almost missed them entirely!
There's a random black "icon" on the bottom toolbar that seems pointless.
Writing Experience & Missing Features:
No Slash Commands: Sadly, my beloved slash commands don't work here.
Intrusive "Add New Block" Toolbar: Typing a slash transforms the bottom toolbar, while the right toolbar awkwardly shifts below. It's very in-your-face, even when grayed out.
Image Handling:
Adding images is smooth – rectangles, squares, pasting – it all works.
No column blocks, which is a bummer.
Link Functionality:
Pasting a link renders a preview with the OG image and description.
Pasting a Leaflet link into Notion doesn't work as expected. It doesn't pull the Leaflet document title.
The Leaflet link structure is cool though, not as clunky as Notion's.
Mobile Friendliness:
Link previews render fully on mobile, unlike Notion's cropped versions. More like Craft, which I prefer.
Not a True Block-Based Editor:
This was a letdown. I was expecting block-based functionality.
Adding content above existing elements, especially images, is a nightmare.
No drag-and-drop for rearranging content. Huge miss!
Text Formatting & Highlights:
Basic Formatting Toolbar: Selecting text reveals a standard toolbar (bold, italic, etc.).
Keyboard Shortcut Confusion:
Tooltips show "Control + B" for bold, which is odd for a Mac. Should be "Command + B." And neither shortcut actually works.
Highlight Color Choices:
Default gray highlight is an odd choice. Green would fit the "Leaf" theme better.
The color picker options don't look interactive at first glance.
You can select and overwrite the hex codes, which is a plus.
The color selection pop-up stays open even after you've picked a color. A bit strange.
Love the real-time preview when changing highlight colors!
Inline Links:
No keyboard shortcut for adding inline links.
Pasting a link over selected text doesn't auto-create a link (unlike Notion or Github).
Clicking the inline link button makes the text selection disappear, which is disorienting.
Markdown Support:
Basic Markdown for headings (H1, H2, H3) works.
Converting between heading levels is clunky. It seems to append hashtags internally instead of properly converting.
The paragraph behavior is strange, especially when switching from a heading.
Cards & Collaboration:
Card UX:
Opening a card creates a right-side pane, but it doesn't auto-scroll to the center.
Cursor placement in the "Write something" field is slightly off, which is visually distracting.
Card Design Similarities:
"Add a card" and "External URL" look too similar. Different colors or backgrounds would help differentiate them.
Real-Time Card Updates:
The card preview dynamically updates with content and formatting changes. Super cool!
Only the H1 and the paragraph directly below it appear in the card preview.
Collaborative Features:
Collaboration works smoothly.
Gradient Button Confusion:
The gradient button controls document design, which isn't immediately obvious.
Design Options:
Background colors, images, and accent text customization are all great additions.
A color picker for the page color would be fantastic.
It's unclear what "accent" text affects until you have content on the page.
Card Focus Issues:
Clicking on a card with existing text doesn't place the cursor in the document or center the pane.
Document Navigation:
"Cut" functionality works despite the message saying it's not supported.
Block selection doesn't seem to work on desktop.
Inserting content between images remains a challenge.
The "multi-column view" seems to refer to the side tabs, which is a neat feature.
Card Overload:
Adding multiple cards can lead to a long scroll. Collapsible sections or a way to limit visible cards would be helpful.
Missing Features & Future Ideas:
Essential Text Editing:
Table of contents or navigation for headings is a must-have.
Find and replace functionality is needed.
Columns within pages would be a game-changer.
Embeds & Rich Content:
Embeds for services like Partiful or Luma would be amazing.
Comments and annotations are essential for collaboration.
Power User Features:
Ink & Switch-style math input fields would be incredible.
A built-in sketch block would be a huge differentiator.
Toggles for hiding and revealing content are crucial for organization.
Media & File Handling:
File uploads, tables, and video embeds are important for various use cases.
Lower Priority Features:
User accounts, home pages, and granular sharing aren't critical right now.
"Key-value blocks" seem like Google Docs variables, which could be interesting but not a high priority.
Kiko Card for creating pretty image rolls out of long text iOS only
For some time, I had Kiko card on my list of apps that I would recommend to anyone. Then I decided to download it on the teeny tiny iOS device I have — iPhone SE3 for the sole purpose of airtag tracking and using Mela. And while it is nice, the lifetime plan is pretty expensive at $30 for what is basically a text → image app (pretty similar to Typeshare). And while the split text is pretty nice, it is “not intelligent”, aka it splits more poorly than LaTeX does, which isn’t a compliment. I do love the options bar though, and it renders really pretty images, so if the free plan templates are enough for you, give it a shot.
I launched my passion project, Webtrotion (you're reading this post on a site hosted using it), on Product Hunt. To my surprise, it reached the top 15 with 100 upvotes.
It was Sunday, and for the first time since November when I started working on Webtrotion, I decided to run npm run format. This would ensure the code was properly formatted rather than the mess it had become after my extensive changes.
When I saw the beautifully formatted code, I thought, "What if I launch it on Product Hunt?" To be clear, this wasn't my ideal first launch on Product Hunt, the first time I had put much more effort in, but ended up not launching in the end (the failed launch tweet that I had to specifically ask for removal).
Look at those screenshots (on larger screens only)!
For Webtrotion, the entire process, from the idea of launching to actually doing it, took about four hours with effort ranging from 5 to 15 minutes. Incredibly, I woke up this afternoon to find myself in the top 15 for the day with 100 upvotes, and I'm still in disbelief about how it happened. Some comments and reposts on my Twitter post definitely helped.
It's astonishing how something I worked on for myself and decided to launch in a matter of 15 minutes, with hastily scraped together screenshots annotated using Shottr, ended up being such an amazing moment compared to a launch I had prepared and planned for. I guess one of the main differentiating factors is the freedom I felt. This was an open-source GitHub repo with no money to be made, no Lemonsqueezy link, and no tiers—just a passion project. It solidifies the one thing I know about myself: passion projects and figuring stuff out drive me more than money. Wanting something often cripples me, but because Webtrotion is a free-for-all niche project, I had nothing to lose and everything to gain. And I gained 😊.
One reason I believe I was having trouble getting pages indexed was due to the DOM size. This assumption is based on two observations: (a) when I had the same site in Quatro, it was much easier to get indexed; and (b) Notion returns a very shell-based HTML to Google's crawler (no images, colors, or icons). Most of my pages are stuck in "Discovered - currently not indexed." While I can't guarantee this will help, one major factor contributing to the large DOM size was the popovers I was creating.
So, I asked Claude and GPT-4, and they told me about the <template> tag. I never knew about it before, but it reduced the DOM size from around 2000 to about 1200 (see here). Not sure if it would make a difference, but a smaller DOM size always helps!
Popovers, both pages and blocks, now use template tags to avoid rendering in the DOM, significantly reducing its size.
So, Notion's got these new site versions, right? And now we gotta think about whether to stick with Notion or go for something external. Let's break it down.
First up, pricing and domains. You need a Plus plan for custom domains, which is free for education but costs extra otherwise. Notion charges you $8 for the custom domain thing, and you get five sites or however many domains you want. Sounds cool, but it might be a pain if you're trying to host a bunch of project sites with custom domains. Webtrotion, works with Notion's free plan. You can host a site from a single database on GitHub Pages for free, which is neat but has its limits. And you can host on subpaths as well, aka, example.com/blog.
Notion sites are great if you want sub-pages inside posts, inline databases, table row colors, cropped images, post display options (Webtrotion has either a stream or a list view) or those fancy new preview cards. Webtrotion can't do that stuff unless you get into some complicated scraping business. But here's where Webtrotion shines: consistent URLs (Notion's URLs go crazy if you change titles), custom tag pages, no unnecessary property visibility, better custom headers, custom persistent footers, floating table of contents on mobile, stream view, custom styling, theming and colors, add html embeds, actually useful (sortable and searchable) simple tables and it can pull out references, block mentions, post comments using giscus (you need to be signed into Notion to comment), and even make RSS feeds. Oh, and it's got this cool Shiki transform for code blocks that I added recently.
When it comes to hosting and SEO, Webtrotion on GitHub Pages can be a bit iffy with Google indexing. Notion's got the upper hand here with their new Google Analytics and SEO stuff. But Webtrotion auto-generates those social media (OG) sharing images, while with Notion, you gotta do it manually every time. Update-wise, Notion sites are instant. Webtrotion on GitHub? Every eight hours, but you can tweak that to be 10 minutes, but not instant.
So, why am I sticking with Webtrotion? Because I want and prefer traditional website, and using Notion as my CMS. I want to be able to provide people with a URL and know where it lands, not having to worry about link rot, or add redirects, and change some fonts, pin posts etc. But honestly, it depends on what you need. I wish someday Notion lets us embed blocks into public websites. That'd be the best of both worlds.
If you ever wanted a voice based quick note app, something that is similar to Google Keep but does have some integrated cool AI features that actually work, give Voicenotes a go.
Honestly, ever since Whisper first came out and people realized you could do proper and really good voice translation, there has been no shortage of audio apps on the web. They all claim to do the same thing: think out loud, speak whatever you want for as long as you want, and eventually get a clean result, whatever "clean" might mean.
None of them have particularly caught my eye, except for this app called Voicenotes.
I usually avoid reviewing or using AI apps until they've been out for six to ten months. Many either go defunct or get acquired, causing data loss and changes in UX. They also claim to be in beta and frequently update, making early reviews seem unfair. However, this app appeared polished at launch, and as you can see from the title, I have mildly positive reviews for it. With its lifetime offer running, it seemed worth reviewing now.
Onboarding
Interface of a note-taking app displaying features like AI assistance, multi-language support, and privacy commitment, with options to record a note, ask AI, or create content.
The first thing it gets right is that when you open the app, you can directly record a note. It displays all the basic features right there. You can record notes, create summaries, or modify specific notes or groups of notes. We'll discuss that later. Additionally, you can use the AI features, which include intelligent suggestions. However, some of these points are a bit vague and don't really show up in the initial walkthrough or usage of the app.
Press the record button and start speaking. When done, press stop. A new pop-up with a play button will appear, indicating your voice is being transcribed. The interface is clean and free of intrusive pop-ups or CTAs, something I'd highly recommend to other apps.
Minimalist interface of a note-taking app with a search bar, displaying a message "Creating transcript from your voice" and options to record a note, ask AI, or create content.
Using the app
Code-switched recordings
Some context
Here's something to know about me: I'm mostly bilingual and kind of trilingual. I primarily think in English and Hindi. For work, I think in English, but when reminiscing about my life or day, I often switch to Hindi. This is called code-switching.
There's something unique about Hindi and English compared to, say, French and English. There's a secondary language called Hinglish, a romanization of Hindi using the Latin alphabet. Hinglish is even available on the Facebook app. It's a blend of Hindi and English, commonly used for chatting online because English keyboards are more prevalent.
Example: I drink water in Hindi is मैं पानी पीता हूँ which in Hinglish is written as main paani peeta/peeti hoon
With that context, you'll understand how I'm testing this app. You cannot have separate workspaces here. Since I can't separate my personal Hinglish thoughts into a different workspace, I tried 3 code-switched recordings. Each recording is transcribed differently, making it messier than I had hoped.
Interface displaying multiple text notes with options to add, edit, tag, create, share, and more. Notes include personal thoughts and experiences in both Hindi and English.
In one recording, everything is transcribed to Hindi, including my English speech and English words in Devanagari characters. In the second recording, there's a mix of Hindi and English characters, but it inconsistently uses English characters for English words, which is odd and not very useful. In the third recording, everything I said in Hindi was translated into English.
As a speech and language researcher working with LLMs and speech models, I know this isn't an issue with VoiceNotes specifically but inherent to Whisper modes. However, there are ways around it. Users could choose between languages, selecting up to three for transcription. The main option they’ve provided is to always have transcriptions in a certain language, which is often messy and inaccurate, contrary to the claims in the FAQ.
Note Features
Overview
Clicking on a note gives you around 10 options. You can add to it as a thread, a recently released feature, or edit the transcript. Tagging notes, which we'll discuss later, is also possible. You can create a version with specific instructions or use default versions. Sharing the note publicly makes it appear in the sidebar for quick access. Additionally, you can regenerate the title and transcript since the audio is stored and can be replayed.
Interface showing a detailed text note in Hindi and English with options to add, edit, tag, create, share, and more. Features include regenerating title or transcript and seeing related notes.
Interface displaying an editable text note titled "Can't Sleep with Migraine, Fever, and Jaw Pain" with content describing discomfort and sleep issues, and options to save or cancel changes.
Add a threaded note
Popup on an interface showing an audio recording being added to the note "Can't Sleep with Migraine, Fever, and Jaw Pain," with a progress bar, cancel button, and a timer indicating recording duration.
The app now lets you add to a thread, linking one note to another, but I'm unsure of its actual functionality beyond seeing them in a delve view, like "Oh, I was thinking about X and Y." I'm uncertain how that helps solve a problem. One specific thing to note is Voicenotes' Related Notes feature, which we'll discuss later. It seems to either malfunction when adding to a thread or inherit the parent’s Related Notes.
Popup on an interface titled "Adding Thread in UI - A Cool Idea," displaying a note with options to edit, tag, create, share, and more, along with links to related notes.
The "Add to thread" feature has a bug where any new note shows a popup saying "Adding to XYZ thread," even if it's not being added. You need to click the "X" button to close out of the popup.
The not-good tagging system
Remember how I mentioned you can apply tags to these notes? It's true—you can define your own tags, and they show up in the sidebar, as you can see in the screenshot here. However, tags are probably the weakest point of this app experience.
Interface showing a note tagged with "health" and "starred" in a popup, with a sidebar also highlighting the "health" tag, indicating organization and categorization of notes within the app.
The founders claim tags are obsolete because all necessary information is in the transcription modality. I disagree. People still value organization, glanceable views, and not having to chat with an AI. An AI-run system struggles with tens of thousands of notes. With tags, you know you're accessing all the information you've created in the app.
My disappointment with tags in this application stems from two main issues: the lack of auto-tagging based on predefined tags and the inability to scope based on tags. You can't click on a tag to ask questions solely based on that tag or a collection of tags, nor can you perform boolean queries on tags for searching or chatting. This feature would integrate with the AI mindset while giving users control over filtering. It would also mitigate the absence of a workspace component, allowing tags for personal, work, or multi-use purposes. Nested tags would enhance organization, especially if added automatically, while still letting you decide the scope of tags for different situations.
Create something from your note
In the video below, I'll demonstrate the create option's capabilities: generating summaries, extracting main points, a handy copy feature, creating tweets, and following custom instructions.
Your browser does not support the video tag.
Video showing transformation options for a note
Content creation menu offering options to create a summary, to-do list, email, tweet, or blog post, with a section for custom instructions and a list of selectable notes for content generation.
The custom instructions option isn't highlighted in the note’s create menu. You need to go to the toolbar at the bottom to find it, which isn't obvious. I only discovered you could add custom options by toggling all the buttons during this review.
Using the bottom toolbar to create something is a cool feature that lets you select multiple notes at once, perfect for creating a to-do list, for example. However, you'll notice the absence of filtering and sorting options due to the lack of a tag ecosystem, which I sorely missed during testing.
Search
The search functionality is not semantic, which is disappointing, especially since you already have representations stored for related notes. I expected a hybrid search mechanism to bring up related notes. For example, when I searched for "headache," a note mentioning "migraine" didn't appear in the results. I was expecting some kind of semantic search or an option to choose between keyword versus semantic search. The default expected option is for people to use "Ask My AI," but it doesn't feel like a UX I want to use. I'm still accustomed to using search boxes and entering short queries rather than formulating questions for a chatbot.
Search interface displaying "No results found" for the query "headache.”
AI Features
Related Notes
The related note option is quick, which is great. One thing to test over time is how well it works with over 500 or 600 notes, especially since you can't use tags to refine the model's understanding. Customization for defining related notes would be useful, addressing either a customization issue or a general problem with the related note architecture.
Interface displaying a note with options to add, edit, and share, and a section titled "Related notes" listing other relevant entries linked for easy reference.
I feel the app relies too much on basic cosine similarity over entire content or asking a smaller model to pick relations, neglecting hybrid or more nuanced matches. Semantic similarity isn't effective for longer texts, which they should improve. Additionally, the related notes feature fails with bilingual notes, not matching or calculating relations correctly. They should consider using an English vector representation for all notes to enhance this feature. Hopefully, this will improve in the coming months.
Ask Me Something
Interactive prompt in a dark interface asking "What's on your mind? Ask me something" with additional text and buttons for recording a note, asking AI, or creating content.
The "Ask me something" call to action at the top of the navigation toolbar is meant for journaling, where the app asks you a question to answer as a recorded note. However, this isn't clear within the app. It would be better to replace it with a pop-up prompt like "Suggest something for me to talk about." The current phrasing is too similar to asking the app something and may not be perceived as the app asking the user a question.
Ask my AI
The Ask AI feature is the main selling point, similar to Google Notes but with AI capabilities. Unlike Notion's AI, you can dictate notes using audio instead of typing. It works well for English notes but struggles with bilingual or code-switched notes, posing a significant issue for tasks involving two languages simultaneously.
AI chat interface with a user question displayed but no answer provided, accompanied by a note explaining that "AI models can make mistakes.”
When the "Ask My AI" feature works, it's cool because it shows the sources it used. However, the sources are often scattered, highlighting weaknesses in the related notes feature and the overall retrieval and re-ranking processes. These basic issues need fixing to make the app more viable, contributing to my negative experience.
If you ask a question through voice, you have 20 seconds, and the answer is spoken back to you, which is a cool interaction I'd like to see in more apps. If you type something, you read it in text; if you speak something, you might just want to hear it back, and I really like that. However, the answers are pretty generic and cover many irrelevant sources.
AI chat interface showing a voice-to-text response feature; a user's spoken question is transcribed and answered verbally, with sources listed below for reference.
The Ask AI feature includes a chat history similar to Notion's Ask AI. While this history isn't searchable using AI or semantic search, which is a bit of a miss, it does allow you to revisit past conversations. One notable omission is the inability to save the model's response as a note in a database, especially since notes are meant to be voice notes. It would be beneficial to add context to a thread or use the new "add to a thread" feature to include text notes, similar to how Create adds content to a note. I wish you could also attach AI responses to those notes.
Interface displaying a chat history panel with a list of past AI interactions, including topics like "Suggestions for Activities" and "Understanding Machine Learning.”
Settings
Settings menu displaying user profile options including name, about section, language settings, personalized AI memory for specific names, and account details with options to edit email and password.
The language setting option lets you choose a default language for transcription.
The setting option lets you modify AI responses. While I'm unsure if it affects suggested questions, the “what would you like your AI to know about you” option influences persona-based answers, like “Suggest me something to do for the week.” The "names to remember" feature improves transcription accuracy by recognizing specific terminology or names, as shown in the screenshot.
Interface showing a highlighted note titled "Exploring Transcription Accuracy with Specific Keywords like Ariba, Diksha," detailing the challenges of transcription accuracy with specific names and technical terms, showing improvement after adding “names” in settings.
The FAQs offer valuable insights into how the founders envision users engaging with the app.
The shorter, the better. Shorter notes tend to focus on one subject and are often easier and more enjoyable to rediscover in the future. See here
They say "the shorter, the better," which confuses me, especially since one benefit of subscribing or opting for a believer plan is the ability to record notes longer than one minute. I would expect them to appreciate or have a way to process longer notes rather than condensing them into a single vector.
Are my notes really private?
Yes. My wife and I use the app for our most personal notes, so we designed the platform to be private knowing we'll have to hire team members in the future.
All notes are secured on the cloud, not used for AI training, and only retrieved upon authenticated user requests.
I'm not a fan of how they've written this privacy policy. It tries to be conversational, but privacy, especially with AI, needs specific addressing. They ensure they won't train AI on these systems, but they should mention that this data isn't processed using a secure cloud-hosted whisper. Instead, it's sent to OpenAI for transcription. They later discuss which models they use, so the data is transmitted to those models, which they haven't included in their privacy promises.
What AI models do you use?
We use the best-in-class (and therefore most expensive) models such as GPT-4 and Claude Opus for most tasks. We sometimes use a faster model like Claude Haiku for features such as Related Notes which do not require high intelligence. See here
This highlights why the related note features are weak. Claude Haiku isn't effective for matching across multiple long notes, especially in different languages. They'd need a Cartesian product of all notes to determine related metrics. Using Gemini Flash or a vector store with better embedding models and a hybrid search would be more beneficial. Or decomposing into important topics and adding auto tags would significantly improve the related note section.
How does Voicenotes compare with apps like X and Y?
The honest answer is that we don’t know. I’m sure there are some great products for specific use-cases, but Aleesha and I set out to build Voicenotes because we couldn’t find anything that we would use ourselves. And our use-case was very simple: take quick notes and make sense of it when we need them with minimal effort.
Our goal is to build the most delightful product for our daily use.
I don't like this answer. Apps should always be compared with others in the same genre to help users decide why they should choose yours. Highlight the unique features your app offers. Sindre does this well, using FAQs to address competitors directly. For example, they can say, "Our competitor is AudioPen, which doesn't offer semantic search and is just for transformation," or "Your Android speech recorder doesn't do XYZ or have a state-of-the-art whisper model." While it's challenging to keep up with evolving software, you're already tracking competitors, so make this information visible to your users.
They've made some cool updates since the first release. The UI is better, and they finally added dark mode, so you don't blind your eyes at night. The ability to pause recordings is nice. However, I don't see the point of keyboard shortcuts on the web app; I'd prefer a menu bar app since you still have to click on the webpage to use keybindings. They added custom instructions in April, which is a nice touch. Related notes were released in May, and I hope this feature gets more refined as the app matures.
They released Android and iOS apps with an offline feature, but it's unreliable. When I used them, they lost multiple voice recordings and couldn't transcribe them, no matter how many times I tried. This feature feels very much in the alpha phase and isn't worth buying the app for.
Comparison to Other Apps
AudioNote apps are dime a dozen, and your phone likely has built-in functionality for this. Major non-native recording apps like AudioPen or TalkNote focus on quickly capturing thoughts and transforming them into readable formats like text blogs, quotes, or tweets. VoiceNotes stands out with its integration of personal knowledge management (PKM) features, such as related notes and AskMyAI, offering enhanced audio notes with an inbuilt retrieval system.
The main difference compared to general recording apps like Google Recorder is that Voicebnotes lacks multi-speaker diarization options, so you can't have speaker labels. This is fine since it's designed for personal use rather than multi-speaker scenarios. It doesn't handle transcriptions well, especially for closed captions, so you won't get accurate phrases like "Oh, noise about this." Scrolling through a transcription can be messy, which ties back to the founders' preference for shorter pieces. However, I believe transcriptions should be as long as you need, as speaking out loud should capture all your thoughts rather than limiting them.
While Google Recorder struggles with accents and code-switching, Whisper handles them better. Google Recorder also has a default language limitation, whereas VoiceMemos currently lacks transcription until the new iOS version in September 2024. VoiceMemos' keyword search feature is a bit unreliable, but Google Recorder excels in this area. These are the main differences you'll notice as compared to the usual recording apps on your phone. Additionally, the PKM-like retrieval in Voicenotes is a nice bonus.
Conclusion
Even after two months, I keep coming back to this app because I genuinely enjoy using it. The UX is fantastic. I'm tempted to buy the believer plan, but I'm unsure if I'd use it enough to justify the $50 cost. I love how easy it is to use and the helpful features throughout the platform, like sources in AskMyAI and specific instruction for transformations.
I feel it's not for me or many others. If your needs are simple and you do well with keyword-based searches, the Google recorder app or voice memos will suffice. However, if you seek something more powerful and enhanced, it lacks enough features for me to recommend it widely.
For example, there's no auto-tagging, and the related notes feature isn't very useful. I tested it with specific paper readings and tried to find notes or audio notes. It's quite generic, so you might be better off using a recording app on your phone, exporting the transcriptions to Notion or Google Drive, and then using NotebookLM or Gemini with extensions to search or reason over your notes.
This app nails the UX but leaves me uncertain about its overall appeal. However, it’s a hit on Twitter, especially among those who write newsletters or tweet to build communities. For journaling or research ideation, it feels both underpowered and overpowered, yet still not enough for me. However, it hits all the right spots in taste and design, which is a bummer because I love the design. I'm not usually a design person, but the UX feels snappy and well thought out for this application.
I do app reviews for fun and would never put them behind a paywall. If this helped you in anyway, please consider buying me a coffee here:
So, a week back I received an email saying that I now have access to impel ai.
Email screenshot announcing that "impel" software is now free and available for download, highlighting new features like meeting notes and cloud integration.
Website look comparison Thankfully, the main concept of the website has remained consistent throughout the year, which is more than can be said for many AI apps out there right now.
This is how their website looked back in 2023:
And this is how it looks Jun 19, 2024:
Setup
The first thing I did was open up impel.ai but it is a completely different website. The actual website is tryimpel.com.
Screenshot of the Impel website showcasing AI tools: "The AI Revolution" article, task management for onboarding, AI-driven summaries, and a focus on privacy.
This is the screen that opens up, so at this point, I expect a summarize and chat feature, smart tasks, and a privacy-focused experience.
The second step is to provide every single access known to man, with no way to move forward without it. I understand they need microphone access to summarize meetings, but there are other features they claim to have that should be accessible without these elevated permissions.
Impel’s accessibility settings, with options for granting permissions for accessibility, screen recording, and microphone access.
Onboarding
A screenshot showcases all the onboarding screens, some simply displaying "Next," while others offer "Try it out" options that link to demonstrations of how the feature works.
Techcrunch WWDC 2024 website with summary on the top-right
The first option takes you to the WWDC 2024 keynote on TechCrunch and provides a summary of the article on the right, always pinned to the top. Additionally, a summary is provided on the right, pinned near the top right close to the menu bar. You need to click the "Create Summary" button, but it's not clear when or if you need to click it.
Just a heads-up: the pop-up on the side doesn't close automatically. So, whenever you try something new in the on-body, you'll need to manually close the pop-up to see the next one appear on the window.
The second onboarding screen directs you to a YouTube video where you need to click the summarize button. First, remember to click the close button, then proceed to the summarize button. If you check the screenshot, you might notice something odd—it starts off in Chinese. I'm not sure why that happens.
Official WWDC 2024 Apple’s video with summary on the top-right
Random summary about hot honey and manually closing side panel.
So, here's a weird thing that happened during the onboarding process. While I was navigating away from the video, I had Twitch running in the background. I was dictating my thoughts about the app for this post, and somehow, the app started using the Twitch audio along with what I was saying. It either picked up the audio through the app itself or through my headset's microphone—I’m not sure which. The result was a strange, jumbled summary of the Twitch conversation and my own words. It was pretty weird and intrusive because, although I had given microphone access, this was not what I expected. It felt very omnipresent and not something I was looking for.
Okay, so the next onboarding screen took me to this integration spot where it says the app uses carbon to connect your data. The options it provides include Dropbox, Google Drive, Notion, and a web scraper. I used the Notion connection, but as you'll see later in the post, it didn't seem to impact my searches. I couldn't find anything from Notion or referenced in the answers provided. It might not be implemented, live, or discoverable—none of which are great at the moment.
Connect to apps through Carbon screen
The all summaries view page, each summary is shown as a colored card.
After completing the onboarding, you'll arrive at a summary screen displaying all the summaries the app has created and the estimated time it has saved you. I'm not sure how this is calculated—probably based on the number of words and reading speed, which I find a bit misleading since people don't usually read that way, and that's what the AI is doing. But aside from that, the screen looks fine. However, you'll notice it already includes Chinese characters and some odd summaries, like a mix-up of what I was saying on the microphone—things I didn't ask for or expect at all.
The main distinguishing feature they've been promoting is that you don't have to do anything with the prompt. When the model sees your laptop, it understands what should be added to your backlog based on what's on the screen. However, it's still not available. They claimed it was trained at least 12 months ago, which is absolutely disappointing.
Interface showing upcoming smart tasks feature with sample tasks like sending sync notes and automated messages, and a reminder for an Amazon subscription.
Random conversation trying to trigger retrieval from Notion connection, but it doesn’t
I then checked if the Notion Connection impacted search or conversation, but it didn't seem to. I posted a recent article I wrote about defining and using AI to add locks, but it didn't reference anything from the Notion Connection.
The Summaries view has a quirky bug with the select drop-downs, allowing multiple to open at once and overlap. It's not a major issue, but worth mentioning in terms of design. Overall, the app looks really cool and is great for demos. It includes basic filtering mechanisms to identify whether the summary was generated from an article, video, or other sources. However, it lacks the auto-tagging features you'd expect from an LLM application. It does have sorting, making it similar to a gallery view of database points you'd expect from any modern application.
The summary filter view. And a dropdown option for sorting and filtering content.
Comparison of app privacy options showing data stored locally (summaries, chats, sensitive information) versus minimal data stored in the cloud (authentication, bug reports).
It says they prioritize privacy by avoiding external data transfers, which makes it unclear whether they're sending data to OpenAI or not. I've linked the privacy policy here: Privacy Policy
The app claims to store all data locally, including summaries, chats, sensitive information, and documents. However, I'm unsure where they're getting the summaries from. It doesn't seem to be a local model because it hasn't changed any data storage availability on my device, suggesting it might be cloud-based. The privacy policy indicates that all generated content is stored on a computer which seems to be specific to what happens after generation rather than during generation.
The last thing I tried was uploading a PDF about prompting techniques. I was extremely disappointed. The UI is pretty cool, with a card-like structure that opens up into a center panel, reminiscent of a notion center view. The colors are really nice. But when you go into the chat and ask about the document, it doesn't understand or process much.
Interface displaying a document on prompt design and engineering for AI models, with sections on advanced techniques and importance in language models, and a failed chat on the right.
I'm not sure what's happening here. Even a basic RAG should have performed much better than their current outputs. I don't know if they're trying to feed everything into the context, but it's baffling how this can be so poor when it should just be a minimal v0 implementation of either a RAG or a large context window.
Conclusion
You can skip this for now; there's nothing much to see. But if you're looking for design inspiration, their designs and UI/UX are impeccable to look at, if not impressive to use.
I do app reviews for fun and would never put them behind a paywall. If this helped you in anyway, please consider buying me a coffee here:
Why todoist? Because it has really nice project/section handling — and works really well with NLP for date parsing.
Why habitify? I bought habitify when it had a nice student discount running. It has a lifetime purchase option, has an API, is multi-platform and integrates with Google Fit (aka, checks all my major habit tracker checklist boxes)
See the complete code here. Something to note is that this relies on val.town specific imports, specifically blob and you’d need to modify that to host somewhere else.
import { blob } from "https://esm.town/v/std/blob";import process from "node:process";import { TodoistApi } from "npm:@doist/todoist-api-typescript";import Instructor from "npm:@instructor-ai/instructor";import Jimp from "npm:jimp";import OpenAI from "npm:openai";import { z } from "npm:zod";const force_update_database = false; // set force update database to true// if you added new items to habitify after running this script// for the first time.// INST: Add these keys in val env variables// NOTE: the image notes don't work at the moment, and if you add a image note,// the task is skippedconst TODOIST_API_KEY = process.env.TODOIST_API_KEY;const HABITIFY_API_KEY = process.env.HABITIFY_API_KEY;const OPENAI_API_KEY = process.env.OPENAI_API_KEY;const DEF_TIMEZONE = "America/Los_Angeles"; // Get your timezone from here: https://stackoverflow.com/a/54500197var add_to_habitify_todoist_project_id = "XXXX"; // INST: Project ID goes herevar todoist_dict_mapping = { // Note: You can choose to leave this dict empty and it will still work "Image Notes": { // This is for a section that is image notes only for any habit without adding a log "todoist-section-id": "XXXX", // INST: section id for just image notes section goes here "habitify-area-name": "undefined", // keep this value as undefined specialPrompt: "only_image", // keep the value as only_image }, "Text Notes": { // This is for a section that is text notes only for any habit without adding a log "habitify-area-name": "undefined", // keep this value as undefined "todoist-section-id": "XXXX", // INST: section id for just image notes section goes here specialPrompt: "only_text", // keep the value as only_text }, Cats: { "todoist-section-id": "XXXX", // section id for the area goes here "habitify-area-name": "Cats", // INST: habitify area name goes here. Match exactly specialPrompt: "", // keep the value as empty text }, Food: { // You do not need to have a section that tracks calories, it is just a bonus "todoist-section-id": "XXXX", // section id for the area goes here "habitify-area-name": "Health", // INST: habitify area name goes here. Match exactly specialPrompt: "track_calories", // keep the value as track_calories if you want to // estimate calories based on entry. the estimation is pretty wild and can vary by +/- 500 kCal at least }, // ADD any other sections as areas here to help the model choose amongst less habits. // Keep specialPrompt value as empty strng};const todoistapi = new TodoistApi(TODOIST_API_KEY);const oai = new OpenAI({ apiKey: OPENAI_API_KEY ?? undefined,});const client = Instructor({ client: oai, mode: "TOOLS",});function getHabitifyAreaName(section_id) { if (!section_id) { return ["undefined", ""]; } for (var key in todoist_dict_mapping) { if (todoist_dict_mapping[key]["todoist-section-id"] === section_id) { return [ todoist_dict_mapping[key]["habitify-area-name"], todoist_dict_mapping[key]["specialPrompt"].toLowerCase(), ]; } } return ["undefined", ""];}function convertDateObject(due) { function convertToISOWithOffset(datetimeStr, timezoneStr) { const date = new Date(datetimeStr); const [, sign, hours, minutes] = timezoneStr.match( /GMT ([+-])(\d{1,2}):(\d{2})/, ); date.setUTCMinutes( date.getUTCMinutes() + (parseInt(hours) * 60 + parseInt(minutes)) * (sign === "+" ? 1 : -1), ); return ( date.toISOString().split(".")[0] + `${sign}${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}` ); } const formatDate = (date, datetime, timezone) => { let isoString = datetime ? datetime : date; if (timezone && timezone.startsWith("GMT") && timezone.length > 3) { return convertToISOWithOffset(datetime, timezone); } else { return isoString; } }; const date_as_string = due ? formatDate(due.date, due.datetime, due.timezone) : new Date().toISOString(); return date_as_string;}async function getCommentsForTask(taskId) { try { const comments = await todoistapi.getComments({ taskId }); return comments; } catch (error) { console.error(`Error fetching comments for task ${taskId}:`, error); return []; }}async function resizeAndConvertImage(imageBuffer) { try { const image = await Jimp.read(imageBuffer); const resizedImage = await image.resize(800, 800, Jimp.RESIZE_BILINEAR); const jpegBuffer = await resizedImage.quality(80).getBufferAsync(Jimp.MIME_JPEG); if (jpegBuffer.length >= 2000000) { console.error("Error: Resized image size should be smaller than 2MB"); return null; } return jpegBuffer; } catch (error) { console.error("Error processing image:", error); return null; }}async function downloadImage(url) { try { const response = await fetch(url, { headers: { Authorization: `Bearer ${TODOIST_API_KEY}`, }, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const imageBuffer = await response.buffer(); const resizedImageBuffer = await resizeAndConvertImage(imageBuffer); return resizedImageBuffer; } catch (error) { console.error("Error downloading or processing image:", error); return null; }}async function createZodSchema(habitKeys, specialPrompt) { const transformDate = (date) => { if (!date.includes("T")) { date = date + "T23:00:00"; // if no time is included, consider it completed at 11pm } const parsedDate = Date.parse(date); const dateObj = isNaN(parsedDate) ? new Date() : new Date(parsedDate); const pad = (num) => String(num).padStart(2, "0"); const year = dateObj.getFullYear(); const month = pad(dateObj.getMonth() + 1); const day = pad(dateObj.getDate()); const hours = pad(dateObj.getHours()); const minutes = pad(dateObj.getMinutes()); const seconds = pad(dateObj.getSeconds()); // console.log(date); let offsetSign, offsetHours, offsetMinutes; const [datePart, timePart] = date.split("T"); if ( date.endsWith("Z") || !timePart || (timePart && !timePart.includes("+") && !timePart.includes("-")) ) { // Create a DateTimeFormat object for Pacific Time const pacificTime = new Intl.DateTimeFormat("en-US", { timeZone: DEF_TIMEZONE, hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }); // Format the date to get the correct Pacific Time offset const pacificParts = pacificTime.formatToParts(dateObj); const pacificHours = parseInt( pacificParts.find((p) => p.type === "hour").value, 10, ); const pacificMinutes = parseInt( pacificParts.find((p) => p.type === "minute").value, 10, ); // Calculate the offset in minutes between the local time and UTC let offset = (pacificHours - dateObj.getUTCHours()) * 60 + (pacificMinutes - dateObj.getUTCMinutes()); // Adjust for day boundary crossing if (offset > 720) { offset -= 1440; // Subtract a full day in minutes } else if (offset < -720) { offset += 1440; // Add a full day in minutes } const absOffset = Math.abs(offset); offsetHours = pad(Math.floor(absOffset / 60)); offsetMinutes = pad(absOffset % 60); offsetSign = offset <= 0 ? "-" : "+"; } else { const offset = dateObj.getTimezoneOffset(); const absOffset = Math.abs(offset); offsetHours = pad(Math.floor(absOffset / 60)); offsetMinutes = pad(absOffset % 60); offsetSign = offset <= 0 ? "+" : "-"; } return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${offsetSign}${offsetHours}:${offsetMinutes}`; }; let habitSchema; if (specialPrompt === "only_text" || specialPrompt === "only_image") { habitSchema = z.object({ name: z.enum(habitKeys), note: z.string().default(""), date: z.string().transform(transformDate), }); } else if ( specialPrompt && (specialPrompt.includes("calorie") || specialPrompt.includes("food intake")) ) { habitSchema = z.object({ unit: z.string().default("kCal"), name: z.enum(habitKeys), value: z.number().default(1), note: z.string().default(""), date: z.string().transform(transformDate), }); } else { habitSchema = z.object({ unit: z.string().default("rep"), name: z.enum(habitKeys), value: z.number().default(1), note: z.string().default(""), date: z.string().transform(transformDate), }); } // Define the schema for the list of habits const habitsListSchema = z.array(habitSchema); return z.object({ habits: habitsListSchema });}function createPrompt(habitKeys_str, specialPrompt) { let prompt = "You are processing content into a list of habit objects. Based on the options of habits, infer appropriate values for the fields:\n"; prompt += "The habits you can choose from are: " + habitKeys_str + ".\n"; if (specialPrompt === "only_image" || specialPrompt === "only_text") { prompt += "You are extracting the name of the habit, the date of the habit (if mentioned, or otherwise an empty string), and any note if it exists about a habit (if mentioned, or otherwise an empty string). Remember, a note is associated with only 1 habit and will say 'note that' or something like that.\n"; } else if ( specialPrompt && (specialPrompt.includes("calorie") || specialPrompt.includes("food intake")) ) { prompt += "You are tracking habits related to calorie or food intake. You are extracting the name of the habit which will be something like 'track calories' or 'track food intake'. As the next step, estimate the calories for the food mentioned in the text as and use that as the value, the unit will be kCal, the date of the habit (if mentioned, or otherwise an empty string), and the food mentioned in the text will be the note for the habit along with the estimated calories in paranthesis.\n"; } else { prompt += "You are choosing the unit (like min or kg), the default is rep. You are choosing the value of the habit, the default is 1. You are getting the date of the habit, if mentioned, or otherwise an empty string. You are extracting any note if it exists about a habit, if mentioned, or otherwise an empty string. Remember, a note is associated with only 1 habit and will say 'note that' or something like that.\n"; } prompt += "Infer and in step 1 "; prompt = prompt + (specialPrompt && (specialPrompt.includes("calorie") || specialPrompt.includes("food intake")) ? "a list with a SINGLE habit object that matches the calorie or food intake option from valid options" : "create a list of all the habits extracted from text that match the options") + "; and then as step 2 assign values to each of these habits' properties based on the provided content and the aforementioned context."; return prompt;}async function process_text(habits_list, text, specialPrompt) { // console.log(habits_list); const habitKeys = Object.keys(habits_list); const zod_schema = await createZodSchema(habitKeys, specialPrompt); const sys_prompt = createPrompt(habitKeys.join(), specialPrompt); const processed_message = await client.chat.completions.create({ messages: [ { role: "system", content: sys_prompt }, { role: "user", content: text }, ], model: "gpt-4o", response_model: { schema: zod_schema, name: "habit and value extraction", }, max_retries: 3, }); return processed_message;}async function addLog(habit_id, unit_type, value, target_date) { const url = `https://api.habitify.me/logs/${habit_id}`; const headers = { Authorization: HABITIFY_API_KEY, "Content-Type": "application/json", }; const body = { unit_type: unit_type, value: value, target_date: target_date, }; try { const response = await fetch(url, { method: "POST", headers: headers, body: JSON.stringify(body), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const jsonResponse = await response.json(); return jsonResponse; } catch (error) { console.error("Error:", error); process.exit(1); }}async function addTextNote(habit_id, created, content) { const url = `https://api.habitify.me/notes/${habit_id}`; const headers = { Authorization: HABITIFY_API_KEY, "Content-Type": "application/json", }; const body = { created: created, content: content, }; try { const response = await fetch(url, { method: "POST", headers: headers, body: JSON.stringify(body), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const jsonResponse = await response.json(); return jsonResponse; } catch (error) { console.error("Error:", error); process.exit(1); }}async function addImageNote(habit_id, created_at, imageBuffer) { const url = `https://api.habitify.me/notes/addImageNote/${habit_id}?created_at=${encodeURIComponent(created_at)}`; const headers = { Authorization: HABITIFY_API_KEY, "Content-Type": "image/jpeg", }; try { const response = await fetch(url, { method: "POST", headers: headers, body: imageBuffer, }); if (!response.ok) { console.log(response); throw new Error(`HTTP error! status: ${response.status}`); } const jsonResponse = await response.json(); return jsonResponse; } catch (error) { console.error("Error:", error); process.exit(1); }}async function get_habitify_database(){ const habits_list = await blob.getJSON("habitify_database"); if (!habits_list || force_update_database) { const HABITIFY_API_KEY = process.env.HABITIFY_API_KEY; const url = "https://api.habitify.me/habits"; const headers = { Authorization: HABITIFY_API_KEY, "Content-Type": "application/json", }; try { const response = await fetch(url, { method: "GET", headers: headers }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const jsonResponse = await response.json(); // Filter habits const filteredHabits = jsonResponse.data.filter( (habit) => !habit.is_archived && habit.log_method === "manual", ); // Group habits by area const habitsByArea = {}; filteredHabits.forEach((habit) => { const area = habit.area && Object.keys(habit.area).length > 0 ? habit.area.name : "undefined"; if (!habitsByArea[area]) { habitsByArea[area] = {}; } habitsByArea[area][habit.name] = { id: habit.id, name: habit.name, unit_type: habit.goal.unit_type, }; if (area !== "undefined") { if (!habitsByArea["undefined"]) { habitsByArea["undefined"] = {}; } habitsByArea["undefined"][habit.name] = { id: habit.id, name: habit.name, unit_type: habit.goal.unit_type, }; } }); // Log the result JSON console.log(JSON.stringify(habitsByArea, null, 2)); await blob.setJSON("habitify_database", habitsByArea); return habitsByArea; } catch (error) { console.error("Error:", error); process.exit(1); } } else { return habits_list; }}export default async function(interval: Interval) { const habits_list = await get_habitify_database(); var tasks = await todoistapi.getTasks({ projectId: add_to_habitify_todoist_project_id, }); for (const task of tasks) { console.log(task); task.imageComments = null; if (task.commentCount > 0) { const imageComments = []; const todoist_comments = await getCommentsForTask(task.id); for (const comment of todoist_comments) { // console.log(comment); if (comment.attachment && comment.attachment.resourceType === "image") { const imageBuffer = await downloadImage(comment.attachment.image); if (imageBuffer) { imageComments.push(imageBuffer); } } } task.imageComments = imageComments ? imageComments : null; } /// START---REMOVE ONCE UPLOAD IS FIXED if (task.imageComments) { continue; } /// END---REMOVE ONCE UPLOAD IS FIXED const [habitifyAreaName, specialPrompt] = getHabitifyAreaName( task.sectionId, ); const processed_task = await process_text( habits_list[habitifyAreaName], task.content + " dated " + convertDateObject(task.due) + "\n" + task.description, specialPrompt, ); const habits = processed_task.habits; console.log(habits); for (const habit of habits) { if (specialPrompt != "only_image" || specialPrompt != "only_text") { await addLog( habits_list[habitifyAreaName][habit.name].id, habit.unit, habit.value, habit.date, ); } if (habit.note) { await addTextNote(habits_list[habitifyAreaName][habit.name].id, habit.date, habit.note); } if (task.imageComments) { for (const imageComment of task.imageComments) { await addImageNote( habits_list[habitifyAreaName][habit.name].id, habit.date, imageComment, ); } } } await todoistapi.deleteTask(task.id); }}
Note: The image notes option doesn’t work at the moment because the habitify API documentation isn’t clear about the kind of object type it expects and no matter what I tried it refused to work.
Unlike the other times I have done these scripts, let’s do a walkthrough of this script, in case you want to write one of your own. I’ll walk you through my thought process, my desired coverage, the issues I ran into, and the resultant code.
Imports and constants
import { blob } from "https://esm.town/v/std/blob";import process from "node:process";import { TodoistApi } from "npm:@doist/todoist-api-typescript";import Instructor from "npm:@instructor-ai/instructor";import Jimp from "npm:jimp";import OpenAI from "npm:openai";import { z } from "npm:zod";const force_update_database = false; // set force update database to true// if you added new items to habitify after running this script// for the first time.const TODOIST_API_KEY = process.env.TODOIST_API_KEY;const HABITIFY_API_KEY = process.env.HABITIFY_API_KEY;const OPENAI_API_KEY = process.env.OPENAI_API_KEY;const DEF_TIMEZONE = "America/Los_Angeles"; // Get your timezone from here: https://stackoverflow.com/a/54500197
This script integrates multiple APIs and libraries to manage tasks and data across different platforms. Here's a breakdown of its components and functionalities:
Imports and Dependencies:
The script begins by importing various modules:
blob from an ESM source to handle binary data.
process from Node.js to access environment variables.
TodoistApi from the Todoist API package to interact with Todoist tasks.
Instructor from instructor package, used for easier function calling with OpenAI APIs
Jimp for image processing.
OpenAI to utilize AI models provided by OpenAI.
z from Zod, a validation library to ensure data integrity.
Configuration Variables:
force_update_database: A boolean flag set to false by default. If set to true, it indicates that the database should be forcefully updated, useful when new items are added to Habitify.
API keys for Todoist, Habitify, and OpenAI are fetched from environment variables, ensuring sensitive data is not hard-coded into the script.
DEF_TIMEZONE is set to "America/Los_Angeles", which is used to handle timezone-specific operations. This can be modified by the user themselves to match their timezones.
This is required because Todoist doesn’t provide the user timezone and a significant part of code is to try mapping two timezone values to acceptable parameters.
Define todoist information
var add_to_habitify_todoist_project_id = "XXXX"; // INST: Project ID goes herevar todoist_dict_mapping = { // Note: You can choose to leave this dict empty and it will still work "Image Notes": { // This is for a section that is image notes only for any habit without adding a log "todoist-section-id": "XXXX", // INST: section id for just image notes section goes here "habitify-area-name": "undefined", // keep this value as undefined specialPrompt: "only_image", // keep the value as only_image }, "Text Notes": { // This is for a section that is text notes only for any habit without adding a log "habitify-area-name": "undefined", // keep this value as undefined "todoist-section-id": "XXXX", // INST: section id for just image notes section goes here specialPrompt: "only_text", // keep the value as only_text }, Cats: { "todoist-section-id": "XXXX", // section id for the area goes here "habitify-area-name": "Cats", // INST: habitify area name goes here. Match exactly specialPrompt: "", // keep the value as empty text }, Food: { // You do not need to have a section that tracks calories, it is just a bonus "todoist-section-id": "XXXX", // section id for the area goes here "habitify-area-name": "Health", // INST: habitify area name goes here. Match exactly specialPrompt: "track_calories", // keep the value as track_calories if you want to // estimate calories based on entry. the estimation is pretty wild and can vary by +/- 500 kCal at least }, // ADD any other sections as areas here to help the model choose amongst less habits. // Keep specialPrompt value as empty strng};
Project and Section IDs:
add_to_habitify_todoist_project_id: This variable holds the Todoist project ID where tasks related to Habitify habits will be added. The placeholder "XXXX" should be replaced with the actual project ID.
Dictionary for Mapping:
todoist_dict_mapping: This dictionary maps specific Habitify areas to Todoist sections. Each key represents a different category or type of task, such as "Image Notes" or "Text Notes".
Each category contains:
todoist-section-id: The ID of the Todoist section corresponding to the Habitify area. This needs to be replaced with actual section IDs.
habitify-area-name: The name of the area in Habitify. For some categories, this is intentionally set to "undefined" to indicate that the category does not correspond to a specific area in Habitify. Otherwise this should match exactly to the area name in habitify.
specialPrompt: A string used to trigger specific behaviors or features. For example, "only_image" and "only_text" are used to specify sections that handle only images or text, respectively.
Special Cases:
specialPrompt like “track_calories" , “only_text” and “only_image” are more specific:
"only_image" and "only_text" are used to specify sections that handle only images or text notes, respectively, and do not add any logs to any of the habits.
For "Food", there is an additional feature to track calories, indicated by specialPrompt set to “track_calories". This feature provides a rough calorie estimate for entries.
Flexibility and Customization:
The script allows for the addition of more sections by adding them to the todoist_dict_mapping dictionary. The specialPrompt can be left as an empty string if no special behavior is required for those sections.
Instantiate clients
const todoistapi = new TodoistApi(TODOIST_API_KEY);const oai = new OpenAI({ apiKey: OPENAI_API_KEY ?? undefined,});const client = Instructor({ client: oai, mode: "TOOLS",});
This code snippet initializes clients for interacting with the Todoist API, OpenAI, and an AI-driven Instructor tool. Here's a breakdown of each line:
Todoist Client Initialization:
const todoistapi = new TodoistApi(TODOIST_API_KEY);
This line creates a new instance of the TodoistApi class using the TODOIST_API_KEY. This client is used to interact with Todoist's API for task and project management.
OpenAI Client Initialization:
const oai = new OpenAI({ apiKey: OPENAI_API_KEY ?? undefined });
This initializes a new instance of the OpenAI client with the apiKey set to OPENAI_API_KEY. If OPENAI_API_KEY is not available, it defaults to undefined. This client enables the use of OpenAI's capabilities, such as GPT models for natural language processing.
This line creates a new instance of the Instructor configured with the OpenAI client (oai). The mode is set to "TOOLS", specifying the operational mode of the Instructor instance, is function calling.
This code snippet defines several helper functions to facilitate operations related to task management and image processing:
getHabitifyAreaName Function:
This function retrieves the Habitify area name and a special prompt associated with a given Todoist section ID.
It checks if the section_id is provided; if not, it returns ["undefined", ""].
It iterates over the todoist_dict_mapping dictionary to find a matching section ID and returns the corresponding Habitify area name and a lowercase version of the special prompt.
convertDateObject Function:
Converts a date object into an ISO string with an optional timezone offset.
It defines a nested function convertToISOWithOffset to adjust the date object based on the provided timezone string.
The main function formats the date based on whether a specific datetime or just a date is provided, applying the timezone adjustment if necessary.
If no date is provided, it defaults to the current date in ISO format.
resizeAndConvertImage Function:
Jimp is chosen over Sharp due to its less restrictive permission requirements. Unlike Sharp, Jimp does not require read access to the current working directory, making it more suitable for environments where granting such permissions is problematic.
Asynchronously processes an image buffer to resize the image to 800x800 pixels using bilinear resizing and converts it to JPEG format with 80% quality.
Checks if the resulting JPEG buffer exceeds 2MB; if so, it logs an error and returns null.
Handles errors during the image processing and logs them.
downloadImage Function:
Asynchronously downloads an image from a provided URL using the Todoist API key for authorization (oops, I did not remember this initially and debugged this error for a long time!).
After downloading, it passes the image buffer to resizeAndConvertImage for resizing and conversion.
Handles HTTP errors and logs them, returning null if the download or processing fails.
The getCommentsForTask function is designed to retrieve comments associated with a specific task in Todoist. Here’s a concise breakdown:
Function Purpose: Fetches comments for a given taskId from Todoist.
Parameters: Accepts taskId as an input, which is the identifier for the Todoist task.
Process:
The function makes an asynchronous call to todoistapi.getComments, passing an object with taskId.
If successful, it returns the comments array.
If an error occurs during the fetch operation, it logs the error and returns an empty array to ensure the function handles failures gracefully.
Error Handling: Errors are caught in the catch block, logged with a descriptive message including the taskId, and the function returns an empty array as a fallback.
This function is essential for integrating comment data from Todoist into other parts of the application, allowing for enhanced task management and user interaction.
Create the zod schema
async function createZodSchema(habitKeys, specialPrompt) { const transformDate = (date) => { if (!date.includes("T")) { date = date + "T23:00:00"; // if no time is included, consider it completed at 11pm } const parsedDate = Date.parse(date); const dateObj = isNaN(parsedDate) ? new Date() : new Date(parsedDate); const pad = (num) => String(num).padStart(2, "0"); const year = dateObj.getFullYear(); const month = pad(dateObj.getMonth() + 1); const day = pad(dateObj.getDate()); const hours = pad(dateObj.getHours()); const minutes = pad(dateObj.getMinutes()); const seconds = pad(dateObj.getSeconds()); // console.log(date); let offsetSign, offsetHours, offsetMinutes; const [datePart, timePart] = date.split("T"); if ( date.endsWith("Z") || !timePart || (timePart && !timePart.includes("+") && !timePart.includes("-")) ) { // Create a DateTimeFormat object for Pacific Time const pacificTime = new Intl.DateTimeFormat("en-US", { timeZone: DEF_TIMEZONE, hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }); // Format the date to get the correct Pacific Time offset const pacificParts = pacificTime.formatToParts(dateObj); const pacificHours = parseInt( pacificParts.find((p) => p.type === "hour").value, 10, ); const pacificMinutes = parseInt( pacificParts.find((p) => p.type === "minute").value, 10, ); // Calculate the offset in minutes between the local time and UTC let offset = (pacificHours - dateObj.getUTCHours()) * 60 + (pacificMinutes - dateObj.getUTCMinutes()); // Adjust for day boundary crossing if (offset > 720) { offset -= 1440; // Subtract a full day in minutes } else if (offset < -720) { offset += 1440; // Add a full day in minutes } const absOffset = Math.abs(offset); offsetHours = pad(Math.floor(absOffset / 60)); offsetMinutes = pad(absOffset % 60); offsetSign = offset <= 0 ? "-" : "+"; } else { const offset = dateObj.getTimezoneOffset(); const absOffset = Math.abs(offset); offsetHours = pad(Math.floor(absOffset / 60)); offsetMinutes = pad(absOffset % 60); offsetSign = offset <= 0 ? "+" : "-"; } return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${offsetSign}${offsetHours}:${offsetMinutes}`; }; let habitSchema; if (specialPrompt === "only_text" || specialPrompt === "only_image") { habitSchema = z.object({ name: z.enum(habitKeys), note: z.string().default(""), date: z.string().transform(transformDate), }); } else if ( specialPrompt && (specialPrompt.includes("calorie") || specialPrompt.includes("food intake")) ) { habitSchema = z.object({ unit: z.string().default("kCal"), name: z.enum(habitKeys), value: z.number().default(1), note: z.string().default(""), date: z.string().transform(transformDate), }); } else { habitSchema = z.object({ unit: z.string().default("rep"), name: z.enum(habitKeys), value: z.number().default(1), note: z.string().default(""), date: z.string().transform(transformDate), }); } // Define the schema for the list of habits const habitsListSchema = z.array(habitSchema); return z.object({ habits: habitsListSchema });}
The createZodSchema function dynamically constructs a Zod schema based on provided habit keys and a special prompt. This schema is used to validate and parse data related to habits. Here’s a detailed explanation:
Function Purpose: Creates a Zod schema for validating habit data, including names, notes, dates, and optionally, units and values.
Parameters:
habitKeys: An array of strings representing valid habit names.
specialPrompt: A string that modifies the schema based on specific requirements (e.g., handling only text or image data, or tracking calorie intake).
Date Transformation:
transformDate: A nested function that adjusts a given date string to include a time component if missing, parses it, and formats it into an ISO string with a timezone offset. The timezone handling accounts for both UTC and specific timezones like Pacific Time.
Schema Construction:
Depending on the specialPrompt, the function constructs different Zod object schemas:
For prompts like "only_text" or "only_image", the schema includes name, note, and date.
For prompts related to calorie tracking, it includes unit, name, value, note, and date.
The default schema includes unit, name, value, note, and date, with defaults set for unit, value, and note.
Schema Compilation:
A list schema (habitsListSchema) is defined to handle an array of habits according to the constructed habit schema.
The final schema returned is an object containing this list schema under the key habits.
This function is crucial for ensuring that habit data conforms to expected formats and values, facilitating reliable data processing and storage.
The get_habitify_database function retrieves and processes habit data from Habitify, caching it locally for efficiency. Here’s a concise breakdown:
Function Purpose: Fetches habit details from the Habitify API and processes them into a structured format, grouping habits by their respective areas.
Data Retrieval and Caching:
Initially, the function attempts to load the habits list from a local cache using blob.getJSON("habitify_database").
If the cache is empty or force_update_database is true, it proceeds to fetch data from the Habitify API.
API Interaction:
Constructs the API request using the Habitify API key and sends a GET request to the Habitify habits endpoint.
If the response is successful, it processes the JSON data; otherwise, it throws an error with the HTTP status.
Data Processing:
Filters out archived habits and those not logged manually.
Groups habits by their area, creating a nested object where each area contains its habits, identified by name, ID, and unit type.
Any habit whether it does or does not belong to a specific area, it is grouped under "undefined".
Error Handling and Logging:
Errors during API interaction or data processing are logged, and the process exits with an error status.
Successfully processed data is logged for verification and then cached using blob.setJSON.
Return Value:
Returns the structured habit data, either from the cache or freshly fetched and processed from the API.
Use LLMs to process text into matched habits
function createPrompt(habitKeys_str, specialPrompt) { let prompt = "You are processing content into a list of habit objects. Based on the options of habits, infer appropriate values for the fields:\n"; prompt += "The habits you can choose from are: " + habitKeys_str + ".\n"; if (specialPrompt === "only_image" || specialPrompt === "only_text") { prompt += "You are extracting the name of the habit, the date of the habit (if mentioned, or otherwise an empty string), and any note if it exists about a habit (if mentioned, or otherwise an empty string). Remember, a note is associated with only 1 habit and will say 'note that' or something like that.\n"; } else if ( specialPrompt && (specialPrompt.includes("calorie") || specialPrompt.includes("food intake")) ) { prompt += "You are tracking habits related to calorie or food intake. You are extracting the name of the habit which will be something like 'track calories' or 'track food intake'. As the next step, estimate the calories for the food mentioned in the text as and use that as the value, the unit will be kCal, the date of the habit (if mentioned, or otherwise an empty string), and the food mentioned in the text will be the note for the habit along with the estimated calories in paranthesis.\n"; } else { prompt += "You are choosing the unit (like min or kg), the default is rep. You are choosing the value of the habit, the default is 1. You are getting the date of the habit, if mentioned, or otherwise an empty string. You are extracting any note if it exists about a habit, if mentioned, or otherwise an empty string. Remember, a note is associated with only 1 habit and will say 'note that' or something like that.\n"; } prompt += "Infer and in step 1 "; prompt = prompt + (specialPrompt && (specialPrompt.includes("calorie") || specialPrompt.includes("food intake")) ? "a list with a SINGLE habit object that matches the calorie or food intake option from valid options" : "create a list of all the habits extracted from text that match the options") + "; and then as step 2 assign values to each of these habits' properties based on the provided content and the aforementioned context."; return prompt;}async function process_text(habits_list, text, specialPrompt) { // console.log(habits_list); const habitKeys = Object.keys(habits_list); const zod_schema = await createZodSchema(habitKeys, specialPrompt); const sys_prompt = createPrompt(habitKeys.join(), specialPrompt); const processed_message = await client.chat.completions.create({ messages: [ { role: "system", content: sys_prompt }, { role: "user", content: text }, ], model: "gpt-4o", response_model: { schema: zod_schema, name: "habit and value extraction", }, max_retries: 3, }); return processed_message;}
This code snippet consists of two functions, createPrompt and process_text, which uses large language models (LLMs) to process text and extract habit-related information based on specified criteria.
createPrompt Function:
Purpose: Generates a text prompt for an LLM based on available habit keys and a special prompt condition.
Parameters:
habitKeys_str: A string containing all possible habit names, concatenated and separated by commas.
specialPrompt: A string that specifies the type of processing required, such as handling only text or image data, or focusing on calorie intake.
Behavior:
Constructs a base prompt explaining the task of processing content into habit objects.
Modifies the prompt based on specialPrompt to provide specific instructions for handling different types of data (e.g., extracting names, dates, notes, or calorie estimates).
Concludes with instructions to infer and assign values to habit properties in a structured format.
process_text Function:
Purpose: Processes a given text to match and extract habit data using an LLM, guided by the generated prompt.
Parameters:
habits_list: An object containing habit data.
text: The text input that needs to be processed.
specialPrompt: A string indicating special processing requirements.
Process:
Extracts habit keys from habits_list.
Calls createZodSchema to generate a Zod schema based on habit keys and the special prompt.
Generates a system prompt using createPrompt.
Submits the system prompt and user text to an LLM (specified as gpt-4o) for processing, structured according to the generated Zod schema.
Attempts up to three retries if necessary.
Return Value:
Returns the processed message from the LLM, which includes extracted and structured habit data.
This code snippet defines three asynchronous functions (addLog, addTextNote, and addImageNote) that interact with the Habitify API to add different types of entries: logs, text notes, and image notes. Here's a detailed breakdown of each function:
addLog Function:
Purpose: Adds a log entry for a specific habit.
Parameters:
habit_id: Identifier for the habit.
unit_type: Type of measurement unit for the log.
value: Value of the log entry.
target_date: Date for which the log is applicable.
Process:
Constructs a POST request to the Habitify logs endpoint with the habit ID.
Sends the request with headers for authorization and content type, and a body containing the log details.
Handles the response, throwing an error if the request is unsuccessful.
Return Value:
Returns the JSON response from the API if successful.
addTextNote Function:
Purpose: Adds a text note to a specific habit.
Parameters:
habit_id: Identifier for the habit.
created: Timestamp for when the note was created.
content: Content of the text note.
Process:
Constructs a POST request to the Habitify notes endpoint with the habit ID.
Sends the request with appropriate headers and a body containing the note details.
Manages the response, throwing an error for unsuccessful requests.
Return Value:
Returns the JSON response from the API if successful.
addImageNote Function:
Purpose: Adds an image note to a specific habit.
Parameters:
habit_id: Identifier for the habit.
created_at: Timestamp for when the image note was created.
imageBuffer: Buffer containing the image data.
Process:
Constructs a POST request to a specialized Habitify image notes endpoint with the habit ID and created timestamp.
Sends the request with headers set for authorization and content type set to "image/jpeg", along with the image buffer as the body.
Manages the response, logging the full response on error and throwing an error for unsuccessful requests.
Return Value:
Returns the JSON response from the API if successful.
Each function is designed to handle specific types of data entries in Habitify, ensuring that logs, text notes, and image notes are correctly added to the system with robust error handling and response management.
Main function
export default async function(interval: Interval) { const habits_list = await get_habitify_database(); var tasks = await todoistapi.getTasks({ projectId: add_to_habitify_todoist_project_id, }); for (const task of tasks) { console.log(task); task.imageComments = null; if (task.commentCount > 0) { const imageComments = []; const todoist_comments = await getCommentsForTask(task.id); for (const comment of todoist_comments) { // console.log(comment); if (comment.attachment && comment.attachment.resourceType === "image") { const imageBuffer = await downloadImage(comment.attachment.image); if (imageBuffer) { imageComments.push(imageBuffer); } } } task.imageComments = imageComments ? imageComments : null; } /// START---REMOVE ONCE UPLOAD IS FIXED if (task.imageComments) { continue; } /// END---REMOVE ONCE UPLOAD IS FIXED const [habitifyAreaName, specialPrompt] = getHabitifyAreaName( task.sectionId, ); const processed_task = await process_text( habits_list[habitifyAreaName], task.content + " dated " + convertDateObject(task.due) + "\n" + task.description, specialPrompt, ); const habits = processed_task.habits; console.log(habits); for (const habit of habits) { if (specialPrompt != "only_image" || specialPrompt != "only_text") { await addLog( habits_list[habitifyAreaName][habit.name].id, habit.unit, habit.value, habit.date, ); } if (habit.note) { await addTextNote(habits_list[habitifyAreaName][habit.name].id, habit.date, habit.note); } if (task.imageComments) { for (const imageComment of task.imageComments) { await addImageNote( habits_list[habitifyAreaName][habit.name].id, habit.date, imageComment, ); } } } await todoistapi.deleteTask(task.id); }}
The main function brings together the integration between Todoist and Habitify, processing tasks from Todoist and logging corresponding activities in Habitify based on various criteria. Here's a concise breakdown of its operations:
Function Overview:
Purpose: Synchronizes tasks from Todoist with Habitify by processing text and images associated with tasks and logging them as habits.
Parameters:
interval: An object that might specify the frequency or timing for running this function, although its usage isn't explicitly shown in the snippet.
Process Flow:
Retrieve Habit Data: Fetches a list of habits from Habitify.
Fetch Todoist Tasks: Retrieves tasks from a specific project in Todoist.
Task Processing:
Iterates over each task from Todoist.
Logs task details for debugging.
Initializes or clears imageComments.
If a task has comments, it fetches these comments and downloads any images attached, storing them in imageComments.
Text and Image Processing:
Skips further processing if there are image comments (temporary measure noted for removal).
Retrieves the Habitify area name and special prompt based on the task's section ID.
Processes the task's content, due date, and description to match and extract habit data.
Log Habits in Habitify:
Iterates over the extracted habits.
Depending on the specialPrompt, logs the habit data in Habitify using addLog and addTextNote.
If there are image comments, each image is logged as an image note in Habitify using addImageNote.
Task Cleanup:
Deletes the processed task from Todoist.
Error Handling:
The function includes try-catch blocks within called functions but does not explicitly handle errors in the main flow. It relies on the robustness of the individual functions to manage exceptions. If there are exceptions in logging, it just stops the program from running 🤷♀️.
MaxFocus is a new-ish extension focussing on creating link previews on any link you hover on. It works on apps that do not allow for iframe embeds (like Notion), and hence is different than the majority extensions out there. You really do not need the PRO option with this one, unless you really want AI summaries of the page, but I can usually skim the page faster than the AI can generate a good summary of. The looks and feel are customizable and you can make it look just like Arc, which is a feature that I always missed when I switched back to Chrome.
The features setting is a bit messy, but this quick guide should probably help.
Screenshot of MaxFocus Link Preview settings with options for interaction preferences, activation triggers, advanced protection, performance, popup behavior, AI settings, appearance, and disabling on specific websites.
And this is how the popup looks like
Screenshot of a MaxFocus popup displaying an article. Annotations highlight features: share option, open in new tab, change ratio, maximize, close, pin popup, reader mode, AI tools, zoom, and settings.
Something you might find frustrating is, you cannot access right click menu inside this popup. When I open articles like these, I often want to save them to reader. But because clicking on the top link opens it in a new tab, and I cannot access right click to use the Save to Reader option, I often end up needing to copy link address and then saving it in Reader. I wish the share option copied or provided MacOS or custom share menu than the QR code it has at the moment.
Sidenote Jun 17, 2024: Chrome is testing native Link Previews too, and it would be fun to see what they entail
Chrome Flags entry for "Link Preview." Description: Enables previewing a linked page in a small window before navigating. Triggered by context menu or user actions. May be unstable on some platforms. Options: Default.
This is what the native Chrome link previews look like. They are basically a headless window, always on bottom right, if you click on them you open the site on a new tab. Not a huge fan at the moment.
The native Link preview option on Chrome shows a popup on bottom right.
So, like everyone else, I have too wandered down the roads of using doodle, when2meet, zcal and all other 800 alternatives to the problem. Until 5 months back that is, when I found, schej.it; and trust me, it is the best app ever (in my opinion) for finding a time to meet. Allows you to overlay your google calendar, restrict participant visibility, send google calendar invite based on the decided time, everything you can think of.
My Annoyances With Notion: I use Notion so much, that my appreciation and complaints about Notion are spread out through out this website. But this is a dump of my tiny “annoyances” with Notion.
Using val.town for the first time: Val.town has now been my go to cloud function platform of choice, for all those functions that take mere milliseconds to run, but you want them to run often.
Link Previews in Chrome using MaxFocus: This isn’t as much of a friction log, as it is a tips and explainer once I did figure this extension out. The extension replicates everything I loved about Arc’s link previews.
I used Leaflet: A new document writing app, made for ephemeral documents that supports nesting, formatting and does not have visible markdown.
I used Monarch Launcher: A kind of new kid on the block as a replacement for a Raycast Launcher which is developed by a single person and aims to do things much more intuitively I guess.
AI Apps
Jun 18, 2024: I decided to do “AI apps” friction logs. There are a few differences that come with AI based apps - they change quickly, they are “expected” to have multiple hitches, and the problems inherent to LLMs are mixed with how they are perceived as a whole. Hence, these would be all timestamped on my blog.
Was I influenced by all the tweets wanting there to be an MKBHD of AI apps? Yes. Do I think there cannot be an MKBHD of AI apps? No (kinda!), especially because there are no clear versions to review, something is always in beta or coming, and their design problems are intermingled with LLM problems.
Voicenotes: Hitting Half of the Right Notes: An audio notes + retrieval app, which is pretty good for what it is, but still not all the way there. The UX and design feels and works really well though, and the AI part is thoughtfully integrated, making it one of the better apps out there
Apps on my backlog
✅
impel ai
✅
voicenotes
⬜
mymind
⬜
augmend
Down the Rabbit Hole
Apparently, given friction logs are a thing, you should check out these too:
I have been wanting to unlike all pages on Facebook that I liked as a teen in 2010. Because I stopped using Facebook pretty early (around 2014) — I thought unliking all of them made perfect sense. I did find some extensions to do so, that are now defunct; and 2 console tricks, that no longer work because Facebook keeps changing it layouts.
That is, until I landed on this comment on a gist.
Long story short; log into Facebook, open this link, install TamperMonkey if you don’t have it, paste and save the code below as user script, refresh that page and let it run. This uses the activity log to unlike pages, so when you go to your likes; there might still be brands that you ned to go to pages of and unlike and unfollow manually. The reload built into the script is required because after a few iterations, Facebook stops loading your earlier activity unless you refresh the page.
// ==UserScript==// @name Facebook Auto Unlike// @namespace http://tampermonkey.net/// @version 2024-04-24// @description Automatically unlikes posts on Facebook// @author You// @match https://www.facebook.com/usrID/allactivity?activity_history=false&category_key=LIKEDINTERESTS&manage_mode=false&should_load_landing_page=false// @icon https://www.google.com/s2/favicons?sz=64&domain=facebook.com// @grant none// ==/UserScript==(function() { 'use strict'; function runAutomation() { setTimeout(() => { const options = [].slice.call(document.querySelectorAll('[aria-label="Action options"]')); options.forEach(option => option.click()); setTimeout(() => { const menuItems = [].slice.call(document.querySelectorAll('[role="menuitem"]')); const unlikeItems = menuItems.filter(item => item.innerText.includes('Unlike')); unlikeItems.forEach(item => item.click()); setTimeout(() => { location.reload(); }, 3000); // 10 seconds after clicking unlike }, 3000); // 5 seconds after clicking action options }, 3000); // Start 5 seconds after page load } // Run the automation function when the page loads window.addEventListener('load', runAutomation);})();
I'm trying to create a highly personal and experimental micro journal app tailored to my unique preferences, circumventing common app limitations and Notion's page layout constraints, with a focus on free-form input and LLM-powered structured data extraction in a timeline format with blocks as the main input format.
Note that this experiment is very well, experimental. I do not know front-end or app development so I would mostly be hacking things together here to see if I can make something that works for me. This is also great, because I know there are 800 note/journal apps in the wild and absolutely no-one is ever going to use mine, so I don’t even have to pretend to make it work for other people, which usually adds complexity in organization/features/scoping/hard-coding.
And, if Raindrop ends up having workspaces or Habitify ends up solving most of my tracking wants and needs, I might abandon this project anyway.
Why?
This is a two-fold problem on my end:
Any kind of tracking or micro journalling app usually does not work for me for either of these 4 reasons:
Not free-form or no structured extraction from free-form
Not cross platform/cross device with sync and offline abilities (the only time I care about offline abilities)
Has a rich text editor/viewer → I really do not want markdown
Allows for stacks or workspaces
Why not just use Notion? One of the main pain-points I have with Notion, is its inability to do anything with blocks. While blocks are these great contained pieces of information, we do not have the ability to filter them, or search within them.
One of the things I am most excited by in scope of LLMs is structured data extraction from curated unstructured data, while still allowing for manual pushes in the right direction. And this gives me a good, yet, private ground for RAG, memory and context testing of LLMs on my end, for purely research purposes.
Ideally something like this would use generative UI like the vercel AI sdk, but, I am no shape or form equipped enough to be good at that, so that isn’t happening. Instead I might just try doing something like potluck — but again, that is a far fetched dream.
Journalistic: Honestly, pretty nice and should work for most of my purposes, except it doesn’t allow for images or audio (I usually don’t need this, but the day I do, I do not want to be searching for another app), doesn’t have an offline mode, and doesn’t allow to change timestamps of when something is added.
Supernotes: Pretty cool, has a calendar view and has targetted date, tags, links to blocks, labels, works offline, across devices. Has a WYSIWYM editor instead of WYSIWYG and does not allow for embeds which uh, is not ideal and I am used to the capabilities of Notion’s editor. It also costs $5/mo even with student discount on an annual plan, and while I might shell that out for a perfect app that fits all my needs, this isn’t it at the moment.
Columns: This is so cool, has some of the UX features I would want, but is more task focussed, doesn’t have an API and I don’t really know when it is and when it isn’t under active development.
Bundled Notes: Pretty good, but doesn’t have the calendar/timeline feature I am looking for, and does not have API access, so I cannot do the insights thing that is inbuilt into Journalistic. It also hasn’t been updated in like 6 months, which is a huge deal in web dev/mobile app time period.
Pile, Strflow, Workflowy, Qept, Listy, Lazy, Q.page, Streams and tetr: All of these have things I like and things I dislike but gist of this is they are either not multi-device, or they do not have an API or they do not look good, and I don’t think any of them work long term for my needs.
Walling, xTiles, Scrintal, Heptabase: While these are all nice, they are overpowered for micro journalling and still do not have the things I need, while also being expensive, so they are mostly a no go.
Use Tana or Capacities: I might. The biggest problem I've had with Tana is the lack of ability to write longer documents — which I do not have here and you are kind of making a custom app. It's already outliner like so you can timestamp it anyway. The biggest problem I've had with Capacities is the lack of collaboration — and maybe if I finagle my way into using this - I do not need multiplayer for tracking+journaling.
Just use Raindrop, Anybox, Google Keep: Either these are uni-device/platform, do not support just notes or do not have workspaces.
Use Monoline, Habitify, Conjure, Awesome Habits, Futureland, Gami: While these are the closest to what I want from the life tracking part of this “app”, they are either structured entry only (even with really customizable rules), or do not have LLM based data extraction or aren't cross-platform.
Use mymind, webbites, napkin, sublime, capture, fabric, mymemo, saveday or the 100 other AI-native notes app: They are (a) more expensive than I want or need (yes I know AI calls cost money, no they do not cost $10/mo), (b) are inflexible in how AI is applied for data extraction, and, (c) focus on talking to bookmarks — and I don’t enjoy that idea.
I do like mymemo’s content types though
The image displays a user interface from the mymemo app, showcasing various content types such as articles, videos, notes, tweets, and files, organized by categories like Image, Links, Videos, Tweets, Notes, Files, and a Recap, with a highlighted tab for "ALL.”
Drafts, Funnel: I do not need a quick capture app, I want a whatsapp like app where each message is a block and it can be filtered.
There are many AI native apps in this category (Dot, LangFriend, Rosebud and others) and they all are not my cup of tea for a few reasons, (a) I do not want to chat with my journal, (b) I do not want my to give me it's thoughts, suggestions or provocatively offer me things to read, consider or explore.
I think this is one of the biggest reasons I wouldn’t even consider making this a polished open source app. AI speaking and “theory”-izing for you is the trend; and I am not interested in it. I am interested in memory computation, calculation, accurate search, but none of the “thinking” insights.
I don't understand why people rave about Linear. Sure, it is a bit faster than Notion but then I have way less content in Linear. It also does not instant search for some reason? So the search results show up when you press enter and then you have to retype your query if it doesn't bring up what you wanted it to. The flat design is up my alley, but interactions are heavily keyboard based and require reading documentation — example how do you move an existing issue as a subtask of another issue? You then use Cmd+K to type Mark Issue As which isn't obvious at all? You can't drag issues into one another for some reason? You can't assign project just by typing like Todoist. 🤷🏽♀️ Am I missing something here?
I finally synced the block list and muted word list between my main and alt accounts on twitter, and I feel so relieved 😅. You might be wondering how I did it?
At the moment, it's a completely non-automated process because API costs money. I downloaded the archived data through X, extracted the block list and mute list from block.js and mute.js using GPT4 code analysis, and then copied and pasted the curl commands into a for loop for all the darn accounts with userid as a variable. I even took a scrolling screenshot of muted words, converted it into a list using GPT4-V, and muted those through a bash script with cookie tokens.
You can sense my desperation from this. I was tired of seeing the same nonsense across multiple accounts for over a year, and I just had to do something about it.
Mar 3, 2024, 08:52 PM: I got tired of trying to find userids from usernames for new blocks, because of course I wasn’t going to download the archive every time. There are websites you can do it from, but I wanted an option to not leave the website, and hence this comment from github helped. Paste it into console and voila.
id = JSON.parse(document.evaluate( '//script[@type="application/ld+json"]', document.lastChild, null, XPathResult.ANY_TYPE, null).iterateNext().textContent).author.identifier;
Someday, I will use these libraries but today is not that day
Sep 24, 2024: Introduce a "Send a copy" option in macOS 15 and iOS/iPadOS 18 (here), handwriting parsing improvements (here), snap to grid (here), connector improvements (here), scenes (here), math notes (here), and apple intelligence (here)
This post is a work in progress and will be updated sporadically (and often after public beta releases of new OS versions). I’d suggest bookmarking the page rather than saving it to your read-it-later app. This is going to end up being long, and I’ll suggest reading this in parts rather than skimming it over. It isn’t a course (and neither is it paid), but you’d gain much more if you treat it as one.
I have tried to add screenshots/gifs to every point. Click the ▶ to see that image.
Callouts like these will show extra apps that I use in place of or in conjunction with default apple apps, to enhance their functionality. While I no longer use iOS, you can see a list of my suggested apps here Apps That Caught My Attention.
And these callouts consist of notes, thoughts or extra information about a particular point. They may also be used to answer possible questions that might arise with the use of a particular feature mentioned.
Introduction
The freeform app is a digital whiteboarding solution from Apple. It is a pretty good app, that is again, Apple only, but supports all devices (yes, including Apple Vision Pro, yes, you can have an 80 inch whiteboard right in front of you). It is collaborative like most Apple productivity apps, and it is better built than most people give it credit for.
The official guides for Freeform can be found here: MacBook, iPhone, iPad
Freeform is an example of what one might call an infinite canvas app (see more of them here).
It is hard to compare this app to an ecosystem, because Google no longer has a whiteboarding app, Microsoft whiteboard is something I won’t touch with a 10ft pole, Figjam, while awesome is not really made for iPad (and you want whiteboards to support iPad), Miro/Milanote are good, but then, they are equivalent to comparing Notion/Tana/Obsidian to Apple Notes, Goodnotes/Concepts are not whiteboarding apps, Explain Everything is very education focussed, tldraw has poor feel with Apple pencil, and Heptabase doesn’t work on iPad at all.
A much more enhanced version of Freeform would be Nothing comes close to Muse (also read I Tried Many Canvas Apps For Paper Reading). Muse is possibly one of the best white-boarding app that has been built ever, but of course it is a subscription, which is prohibitive for collaborative usecases.
The Basics
Freeform is an infinite canvas. So, unlike the notes app, you can extend it in all directions. It is collaborative, so you can ask other people to join in. And it supports apple pencil, so you can actually use it as a whiteboard. Because there are so many whiteboarding apps, and I have tried to use many of them, I’ll possibly talk some enhancements that other apps provide, relevant to the feature that i am talking about.
Board
The core component of the freeform app is a board. The board is a container, which allows you to add any kind of content to it. A board can contain any kind of item, and we will talk about items next. You can sort, duplicate or favorite a board. If you duplicate a shared board, the copy isn’t shared.
Content Types
Items are the kind of elements you can add to your board. Freeform supports eight types of elements: sticky notes, handwriting and drawing, shapes, images, links, files, textboxes and connectors. As a short introduction to the uncommon ones, sticky notes allow you to add “a note” to top of anything on the board, links show a preview (Open Graph (OG)) image, and connectors allow you to connect two items with a line where the line moves when either of the content it is connected to moves. Most other files show first page as previews.
Formatting and Styling
Freeform provides all basic styling options that you might need, namely, bold underline, italics, colors (both from a list of presets, and the color wheel), alignment, list types, font types and font sizes.
To copy and paste styling in Freeform, select the formatted item, tap the tools, choose Copy Style. Then select the item to style, tap tools, choose Paste Style to match fonts, colors, line weights, corners, or shadows.
To apply consistent text styling, first format an item as you want it to look. Tap Copy Style, then tap to select other items and Paste Style to match fonts, colors, etc. You can also Save as Insert Style so new items added will automatically have that styling.
Formatting options in Freeform
Alignment and grouping options in Freeform
Sharing and Collaboration
To manage sharing settings for a Freeform board, open Shared boards, Control (^) + click the board, and choose Manage Shared Board. You can lock for view only, allow editing, enable participant adding, change individual access/permissions, invite others with a link, or copy the link to share.
To collaborate in Freeform, click the Collaborate button, enable Participant Cursors, then jump to another user's location by clicking their name, or follow along as they work by choosing Follow Along from their More options.
Starting with macOS 15 and iOS/iPadOS 18, you have the option to send a copy. Using this feature provides you with a link that, when shared, duplicates the board into the recipient's account. This is particularly useful for sending templates or versions of a board where you don't want people to see synced changes. If you make changes after generating a link, these won't be reflected in the original link (similar to how it works when sharing Apple Shortcuts).
You can also export a board as a PDF while browsing All Boards or when you have opened the board. To do it while browsing All Boards, touch and hold the thumbnail or name of the board, then tap Export as PDF. You cannot export boards to png or an image though which is a weird decision on Apple’s end.
Apple Intelligence
Freeform currently supports the available writing tools in Apple Intelligence for MacOS 15.1 and iOS/iPadOS 18.1 in supported regions. These writing tools include Proofread, Rewrite, Friendly, Professional, Concise, Summary, Key Points, List, and Table.
Image Playground and image generation that is deeply integrated into a Freeform board are planned features, but they have not yet been released as of Sep 24, 2024.
The Details
Board: By default, the board comes with this dotted pattern as the background. You can toggle the dots using the square button (the tooltip will say Hide the dot grid at the bottom right). You can also snap to grid using by turning on the Snap To Grid option in the View menu. You can now pan around the board on macOS easily by clicking and dragging on the canvas while the spacebar is pressed. You can zoom using pinch but also using the zoom menu on the bottom left, which allows for percentage based zooming, and two other options, namely, zoom to fit selection and zoom to fit content. If you lock any item on the boards, they can’t be moved or changed.
The board in freeform is not nestable/embeddable, so you cannot embed one board into another. This makes it harder to have those zoom in for more details containers that apps like Muse provide.
Sticky Notes: Sticky notes can be placed over any content on the board. They are mostly used to provide feedback or add comments to the board. If you use the double click to add text to sticky note, the text will move along with the note (behaves similarly to adding text to a shape in any other app), but if you handwrite it, it won’t move along with the note.
While you can group your handwriting with the shape or sticky note for them to move together like a “real” shape or sticky note would - it doesn’t feel as fluid. This is one cool thing about Muse app.
Images: You can also drag a photo or video into your board from Photos, the Finder, or another app. To customize images in Freeform, use the many formatting options in Format > Image, like removing backgrounds, adding shadows, rounding corners, and more. To put an image inside a shape, overlay a shape on top of the image, then select both the image and the tape. Tap Mask with Shape to put the image inside the shape as a frame or mask.
Handwriting and Drawing: Starting with MacOS 15, iOS/iPadOS 18, Freeform now allows you to work with handwritten text in a similar way to Apple Notes, providing options to search, translate, and copy as text. The app has tools like Fill, Crayon, Fountain Pen, and Ruler, and you can swipe right on the toolbar to see more. Tap a tool to select it. Double tap to adjust qualities like thickness and opacity. Using the select tool, you can then delete, resize, or straighten portions. The Snap to Shape option helps smooth hand-drawn lines. Drawings can also be cut, copied, or duplicated like other items by tapping to select and using the formatting options. Hold the pencil on screen after completing the shape in one stroke to snap it to the correct/clean shape.
Shapes: Tap to add shapes, lines, and arrows. Select to format - change fill, stroke, shape. Or draw freehand then smooth and straighten strokes. To easily create a connected diagram like a flowchart use the shape picker and connectors, drag an arrow to where you want the next shape, then select it from the menu that pops up. You can also quickly add evenly spaced, aligned shapes by holding down on a connector arrow and releasing when the preview matches what you want.
In Freeform, multipart shapes can be split into individual parts for editing. Overlapping shapes can be combined in different ways - united, intersected, subtracted from, or excluded. After adding shapes, tap More to break apart or Arrange to combine using various options. To change size, width or length, tap to select any shape or line, drag the blue dots to adjust overall size and proportions. For shapes, green dots alter specific attributes like number of sides; for lines, they adjust curvature.
To apply consistent text styling, first format an item as you want it to look. Tap Copy Style, then tap to select other items and Paste Style to match fonts, colors, etc. You can also Save as Insert Style so new items added will automatically have that styling.
Files: You can add media by tapping the plus icon and selecting options like Stickers. On an iPad, you can also drag and drop files into Freeform from Split View, or copy/paste items. Double-tap any added photo, video, or file to preview the full item within Freeform. When you preview a PDF file in Quick Look, you can mark up the file directly in the preview window without needing to open the full file. If you are previewing a 3D object file, you can rotate and view the object from all angles by clicking the Rotate button and dragging.
You can insert files from your iPad or iCloud Drive into Freeform, and in most cases they are copied over. When linking to collaborative iCloud documents though, it instead creates a “live” link and other participants can open the live file from Freeform to work on it together.
Unfortunately, unlike Notes, Freeform doesn’t have inline views for pdf pages. But the major missing feature here is the lack of excerpting, which is really useful for “sourcing content”. Excerpting is the ability to have a button to tap and open the location in the original file. This is a feature that I absolutely love in Muse and something I have been trying to find for long in other apps (BoardNotes, GUGA and Liquid Text are other apps with this feature).
Connectors: You can use connector lines between any two items. Click the Show Connectors button in the lower-right corner, then drag one of the arrows up, down, left, or right, to add a connector line to the other item.
Remember that you can only have one connector line between 2 items, so if you are using connector lines to create a diagram, you can’t show a 2 way process like A → B, and B → A that uses the same shapes.
Starting with macOS 15 and iPadOS/iOS 18, connectors can be anchored anywhere on objects and shapes, allowing for more detailed diagrams and structures. They remain magnetic and move along with shape positioning, with suggested anchors at corners, edge centers, and shape centers. However, users now have the flexibility to attach connectors anywhere on a shape.
Your browser does not support the video tag.
Descriptions: You can add descriptions to most multimedia content in the board, this is mainly to enable accessibility, but also allows you to improve search inside boards as mentioned below.
Search: To search all Freeform boards, enter text in the sidebar search field. To find/replace text in one board, open it, press Command-F, enter text, use arrows to navigate results, click Replace and enter new text, or click All to replace all. tarting with MacOS 15, iOS/iPadOS 18, Freeform now allows you to work with handwritten text in a similar way to Apple Notes, providing options to search, translate, and copy as text. Unlike the Notes app though, Freeform does not index content inside files attached inside the boards, and just searches the text associated with text boxes, links and descriptions.
Position Items on Board: You can select an item, then drag from the center to reposition it, or drag a side dot to freely resize in any proportion. On iPad, you can also rotate an item by placing two fingers on it and turning. To move multiple items together, select them then align or group - on Mac Control-click and select Constrain Proportions before resizing proportionally. On iPad, match one item's size to another by starting to resize, touching the item to match, then lifting both fingers when Match Size appears. With grouped items, you can move, rotate, or resize them together as one unit for easier manipulation. You can also align objects such that they are evenly distributed on the board. Select all the items you want to arrange choose Arrange > Align Objects or Distribute Objects to evenly align or space items vertically, horizontally, or distributed across the board.
Scenes: Scenes act as bookmarks on the canvas, created based on your current view. When you click "Add Scene," your current view becomes a scene that you can rename, replace, print, and export as a PDF. This feature is useful when working with multiple pipeline information and you want to export only one part. Scenes can be deleted and have different zoom levels, allowing you to create nested scenes within a particular scene. Unlike frames, which have a table of contents structure, scenes are more like canvas placements bookmarked for easy viewing.
Math Notes: Freeform only supports math notes on iOS and iPadOS. If you write or type a basic or scientific expression with an equals sign, you'll see a popup saying “solve”, clicking on which results in a solution inserted or suggested inline on your board. Unfortunately, I wasn't able to get graph mode working in Freeform, which is disappointing. Freeform doesn't support sliders to change numerical values in math mode, as that feature is limited to macOS, which isn’t under Freeform’s current math mode coverage.
The main things that I wish existed in Freeform that don’t can be summed up as excerpting, auto-layout for diagrams like Google Slides or flowchart.fun, ability to add bookmarks to board locations, synced cards between boards and the ability to embed one board inside the other.
The Arcana
If you have already read through the basic and the details section, you might find repetitions in this section.
Freeform is (from what I remember) one of the only apps that Apple has made, that doesn’t turn on iCloud by default. When you first open Freeform, you’ll see a dialog box asking you start syncing with iCloud. This is primarily because Freeform does store all the files that you add to the board with it, with each board easily ramping up to being 10s of GBs in size.
The image shows a dialogue box asking to enable iCloud Syncing. It suggests that to sync boards across devices, one should sign in to iCloud and turn on Freeform in iCloud settings. There are two buttons: "Go to System Settings" and "Not Now".
By default, the board comes with this dotted pattern as the background. You can toggle the dots using the square button at the bottom right (the tooltip will say Hide the dot grid at the bottom right).
Option to hide the dot grid on bottom right of the board.
You can zoom the board by pinching out, but also through the zoom menu on the bottom left of the board. That provides you with two other options, zoom to selection and zoom to fit content. If you have sometimes zoomed in too much to the exact point you needed, you can zoom to fit content to get an overhead view. Zoom to fit selection is nice, if you want to for example, have a grid view, created using 4 rectangles in 2x2 shape, and want to zoom into one of the rectangles.
Zoom menu on bottom left of the freeform board showing options from 10% to 400%, and option for zoom to selection and zoom to fit content.
To collaborate in Freeform, click the Collaborate button, enable Participant Cursors, then jump to another person’s location by clicking their name, or follow along as they work by choosing Follow Along from their More options.
If you share the collaboration link in Messages - you'll get notifications in the conversation when changes are made.
To apply consistent text styling, first format an item as you want it to look. Tap Copy Style, then tap to select other items and Paste Style to match fonts, colors, etc. You can also Save as Insert Style so new items added will automatically have that styling.
On iPad, to match item sizes while resizing any shape, start resizing with one finger, then use a second finger to touch and hold the sized item to match. When Match Size appears, lift both fingers. Even Muse doesn’t index content inside the board, so there is that.
You can insert files from your iPad or iCloud Drive into Freeform, and in most cases they are copied over. When linking to collaborative iCloud documents though, it instead creates a “live” link and other participants can open the live file from Freeform to work on it together.
When you share stuff to a Freeform board from any other app, it gets added to the right. Hence, if you are creating sections in your board to have “unsorted stuff”, you should put your unsorted boards to the right.
If you enjoyed reading this and found something that clicked for you, please consider supporting me by buying me a coffee here:
Just a tiny Shottr appreciation post, for everything it has. When I started using Shottr, I didn’t have Cleanshot X. The only reason I got Cleanshot X was because (a) it had a deal on AppSumo; and; (b) I can record videos with my face in them easily.
But for everything screenshot related (and now you can paste in multiple screenshots too!); you should definitely use Shottr.
Sep 24, 2024: Apple intelligence (here), highlight text (here), collapsible sections (here), improved PDF search (here), audio content (here), handwriting improvements (here), math notes (here). Fixed handwriting and live transcription device support to include models with A14 bionic (h/t u/ciappetti)
Sep 10, 2024: macOS 15, iOS 18 and iPadOS 18 have notes about what’s new in Notes, I will add them to the sections in due time but you can check them here
iPadOS 18 notes: Handwriting refinement, Handwriting reflow, Audio recording, Math in Notes, Collapsible sections, PDF inline search, Spell-check for handwritten words, Paste as handwriting, Live transcription, Access Math Notes from Calculator, Highlighting
iOS 18 notes: Audio recording, Live transcription, Phone call transcription, Math in Notes, Access Math Notes from Calculator, Collapsible sections, Highlighting, PDF inline search
MacOS 15: Notes features such as Audio Recording, Live Transcription, Math in Notes, Collapsible Sections, Highlighting, and PDF Inline Search have been released, along with upcoming Siri enhancements like Personal Context Understanding and In-App Actions, which are part of Apple Intelligence expected to be released later in 2024 or early 2025.
Feb 3, 2024: Syncing Apple Notes to Google Keep or Gmail (here)
This post is a work in progress and will be updated sporadically (and often after public beta releases of new OS versions). I’d suggest bookmarking the page rather than saving it to your read-it-later app. This is going to end up being long, and I’ll suggest reading this in parts rather than skimming it over. It isn’t a course (and neither is it paid), but you’d gain much more if you treat it as one.
I have tried to add screenshots/gifs to every point. Click the ▶ to see that image.
Callouts like these will show extra apps that I use in place of or in conjunction with default apple apps, to enhance their functionality. While I no longer use iOS, you can see a list of my suggested apps here Apps That Caught My Attention.
And these callouts consist of notes, thoughts or extra information about a particular point. They may also be used to answer possible questions that might arise with the use of a particular feature mentioned.
Introduction
The notes app that comes with Apple is pretty good. It is very much a slightly watered down, but single app version of Google Docs (you might not know how much it can do, check that here: Google Docs Is Underrated), just locked to Apple devices. But there are a few things that make it special, which we will talk about here. Most of these suggestions will focus on the MacOS app, but I will add some details about the iOS app too.
The official guides for Notes can be found here: MacBook, iPad, iPhone
The Basics
Notes app is inbuilt into the apple ecosystem. Notes in your app are associated with accounts if you enable online sync, and with your device, if you keep it offline (use Enable the On My Mac account). You can have notes contained in different accounts visible in the same application. If you sync them via iCloud, they are available almost everywhere, including, but not exclusive to iPhone, iPad, MacBook. You can also enable Access iCloud Data on the Web, allowing you to read notes on iCloud online.
Content Types
You can have 10 types of content on the notes app: Images, Audio, Math Notes, Attachments, Text, Tables, Checklist, Sketch, Tags and App Links. Besides the obvious ones, App Links are deep links to specific components of an app: for example, if you use Spark Email, you can link to a particular email using app links.
Formatting Options
You can format text in Notes app as: Bold, Italic, Underline, Strikethrough and Highlight (highlight is newly added in macOS 15, iOS/iPadOS 18) from the top toolbar. From the right click context menu though, you can change fonts, make the size bigger or smaller, show as subscript or superscript, and change colors. You can also change indentation, but that is only available from the Format menu up top, rather that right click menu.
You also have multiple options for paragraph styles, all accessible from the toolbar itself, namely, Title, Heading, Subheading, Body, Monostyled (code styling but you cannot highlight syntax), Bulleted List, Dashed List, Numbered List and Block Quote.
Headings and subheadings in Apple Notes are now collapsible (starting with macOS 15 and iOS/iPadOS 18), allowing you to write long notes without excessive scrolling.
You can also add URLs (of websites or internal links of notes) to your note using ⌘+K. You can also think about using Pronotes if you want to use markdown to write your text.
The image displays the Toolbar formatting options for Apple Notes. It includes bold, italic, underline, strikethrough, and highlight for styling (along with five color options: purple, pink, orange, mint, and blue), as well as other paragraph styles.
Font and Paragraph Style Options for Apple Notes. The image shows two sub-menus covering various options such as bold, italic, bigger, smaller, baseline for font on the left, and title, heading, subheading etc for paragraph styles on the right.
Access Options
You can control access to your notes in 2 ways: sharing and locking. Locked notes can be opened using either a password or through touch id. Sharing allows you to share notes through a link or especially invite other people. You can decide whether the invited person can just view or edit (collaborate on) the note, and whether they can invite other people. Access also works on a folder level and we will talk about it in the details part.
You can quickly save content from other apps to Notes without switching apps by using the Share feature - just hit Share on the content, and select Notes to create a new note with the content inserted.
Apple Intelligence
The Notes app includes built-in writing tools such as Proofread, Rewrite, Friendly, Professional, Concise, Summary, Key Points, List, and Table. These tools function differently compared to third-party or other Apple-specific apps. When selecting a text range, a purple AI Siri icon appears on the left, leading to the writing tools. Upon choosing an option, the text is replaced directly instead of displaying a pop-up. Options to revert, show original, retry, and provide feedback with thumbs up or down are available (which may change once the feature is no longer in beta).
Your browser does not support the video tag.
The latest beta also adds audio summaries, with audio being a new type of content recently added to Notes that is much more integrated than other attachment formats.
Of course, there are planned features that would be released in late 2024 or early 2025. These include integration with Siri for on-screen awareness of what you're talking about when the Notes app is open, the ability to ask questions like "What was the quote from the book notes I took last night about circulation?" and give commands such as "Add this photo to my recipe for whole wheat no-knead bread," which will automatically choose the appropriate note to append it to.
Complete Apple Intelligence will also bring Image Playground, to be released later, which will let you use an image generation wand with different stylistic choices to create images directly in the Notes app.
The Details
Content
Attachments
You can change the size of the pdf document to show as much or as little information as you need. You can markup these documents and the best part is, the document is horizontally scrollable and the markup shows up in the previewed embed, making it amazing for quick referencing. The view as option shows up by clicking the arrow near the file name. The markup on pdfs is collaborative and in my opinion, it is the easiest way for a group of people to mark up a pdf.
Showing small, medium and large embeds of a pdf attachment with and without markup.
You can crop a PDF or a scanned document, to zoom in on a specific area. To do this, open Markup view, Control (^)+click a thumbnail, select Crop Page, drag the handles to enclose the area to keep, then click Done to save changes or Cancel to revert. You can rotate a PDF or scanned document to be oriented in landscape or portrait by using Control(^)+click on a thumbnail, then choosing Rotate Left or Right.
Images: For images on the other hand, you can view them only as small or large and add markups to them. But the view as option shows up by selecting the image and opening the right click menu.
Same image added as small and large with markup in Apple Notes
Audio: Starting with macOS 15 and iOS/iPadOS 18, you can now record audio directly into your notes. This feature includes live transcription (devices with A14 Bionic, or M1 and above), which is automatically summarized to provide a brief preview. The transcription is synchronized with the audio recording, and the note remains editable while you're recording, making it ideal for classroom environments. On iOS 18 and above, you can also record phone conversation as text and audio.
Your browser does not support the video tag.
Math Notes [Video incoming tbd]:
Math notes are an integration with the calculator application to be able to perform some mathematical calculations inside your notes. You can open special math notes from the calculator, or use the math note feature inline. You can now also perform unit conversions similar to how you used to in Spotlight inside your notes app, with the answer being suggested inline (works with currency, volume, weight etc.).
Math notes consist of variables that can be assigned a value. Variables cannot have space between them, so x = 7 works but hello world = 7 assigns 7 to world, not hello world. On MacOS and iPadOS, hovering and staying over a value brings up a slider that allows you to change the value of a variable. On iPadOS/iOS, this works with tapping the numeric value as well (handwritten or typed).
That brings me to my last point, tapping the variable y or = on iOS/iPadOS (but not on MacOS) gives you the option to insert a graph (a new one or add a line to the current one). This is especially nice if you want to see different graphs for varying coefficients or intercepts by changing the numerical value using the slider option.
App Links: You can have App Links on your notes, that can be added using the attachment icon on the top right of the note. App Links are deep links to specific components of an app: for example, if you use Spark Email, you can link to a particular email using app links.
The add app link option in Apple Notes
Checklist: You can self sort checklist in a group, such that the checked items move to the bottom of the group. This can be enabled using Automatically sort checked items option in the app settings. The good part about this is that you can separate these lists by just some text in between, allowing you to create “grouped” lists.
Completed items move the bottom of each checklist group. A group is defined by any other content that exists between two checkboxes.
Tables: You can create pretty basic tables in Apple Notes. These behave very similarly to the tables you would have in a word processing software, as in, they are not spreadsheets, they don’t do calculations, you can’t merge cells, all you can do is add or remove a column/row in-between or at the end. You can drag rows or columns to different positions. They are very much like Notion’s simple tables in a sense that you cannot have any other content besides text in any of the cells.
Sketch aka Handwriting
Sketch is possibly the main reason I still use Apple Notes besides inline markup. You can add a sketch on your iPhone or iPad but not on your MacBook. Sketches that you add through your iPhone or iPad though will appear in the Notes app on MacBook too. You can add all basic markup in a sketch.
A note doesn’t need to be either sketch or text. You can mix and merge these two things across the complete note vertically but not horizontally, i.e., you can have text below or above a sketch but not beside a sketch. This is similar to how Notes work in general, i.e., they have a linear flow and do not support columns unlike Notion. You can select handwritten notes and paste them as typed text in another area using the Copy as Text option.
Starting with iPadOS 18 ((devices with A14 Bionic, or M1 and above), handwriting got a massive upgrade. Apple now uses on device models to automatically refine your chicken scratch into more legible (smoother and straighter) handwriting while trying to maintain your writing style. Spell-check now works for handwritten words too. Remember, how you could previously copy handwriting as text to paste into other apps? Now you can paste text as handwriting — with additional benefit of reflow (handwriting reflow should work on iPad), i.e., the content will auto-adjust and move to next line if needed when you are pasting something in between, working similar to scribble (cross to remove a sentence, add space in middle etc) while retaining your handwriting.
For both sketch and text, if you type or write phone numbers, addresses or dates, you get relevant Apple application options, for example, to Facetime a person or add to contacts, or add an event to calendar.
Contextual options such as add to contacts or facetime for a typed phone number
Contextual option to add to calendar for handwritten text. [Image attribution]
Organization and Search
Honestly, Apple Notes excels in search. That is the jam. Sure, it hangs a bit when you have too many notes, but which app doesn’t (yes, Obsidian does too). And sure the iCloud sync is wonky, but hey, you signed up for it when you entered an all apple ecosystem.
You can view all your notes in a list or a gallery. You get six ways to organize content in Apple Notes: Tags, Folders, Smart Folders, Search, Smart Search, and Pins.
Tags are the method to have a note appear in multiple collections. Clicking on the tag on the left sidebar have two options for filtering: any and all, with, each tag have 3 states: included, unselected, and excluded. This is a special part with Apple Notes and I really appreciate that. Tags can be written on the top of the note or anywhere inline and they are automatically associated with the note.
Tags seem to only work with one account (the iCloud account in my case), and that is just unfortunate.
Folders allow you to place notes into specific collections. One note can reside in only one folder. You can share folders with people, sort them or group them. You can have any level of nesting for folders.
Folder options in Apple Notes, namely Rename, Delete, Share, Sort, Group and Convert to Smart Folder
Smart Folders allow you to collect all notes based of either all or any based on a collection of rules. You can have 1 or no (but not multiple) rule from each category: Tags, Date Created, Date Edited, Shared, Mentions, Checklists, Attachments, Folders, Locked Notes, Pinned Notes and Quick Notes. The 1 or none constraint implies that you cannot do something like: Include notes matching any of the following filters: Attachments: Scans+Attachments: Maps.
The image displays a settings interface for creating a "New Smart Folder" with various filter options.
Basic search works using keywords, finding information inside attachments and handwritten content. You can search in one account or across multiple accounts. On iOS, you have the option to search within a note and can choose whether to match whole words and make the search case-sensitive.
Smart Search has some of the options from Smart Folders, but you are looking them up on the fly. These namely are searching for shared or locked notes, or, filter notes based on whether they have checklists, tags, drawings, scans, and, attachments. You can only select one of these smart search filters.
Options for smart search in apple notes.
Pinned notes work on an account level, so, you can pin in the All Notes folder.
The Arcana
If you have already read through the basic and the details section, you might find repetitions in this section.
You can drag and drop emails from your Mail account to your Notes app. This links them directly to the mail account. It is very similar to adding an app link. The app link method works for other clients too (I use it with Spark), whereas the drag and drop can be a hit or a miss.
While you cannot send a checklist item to reminders, you can drag and drop a note from the Notes app to the reminders app to create an internal link (check it out here).
If you sync your notes under a gmail account, they show up in Google Keep or in Google Mail. It seems to (? unsure, can change, no official Apple guideline) depends on which account is the default. If you have an iCloud account set as the default, they sync to GMail, otherwise they seem to sync to Google Keep.
Notes app indexes all content inside the attachment and in handwritten content, so you can always search for a word in the PDF that you attached or a handwritten lecture you wrote. PDF search now takes you to the exact location of the match inside a PDF, starting with macOS 15 and iOS/iPadOS 18, instead of just finding the matching attached PDF.
You can add a scanned document or a sketch to a Notes app that is open on your MacBook, through your iPhone or iPad.
Scan documents or Add sketch through iPhone or iPad in the Notes app.
The sketch component has many amazing features that are not even prevalent in the most common handwriting apps. I think it is one of the least “known” features in the space, because most videos are either made by productivity gurus who type their notes, or people who use goodnotes or notability instead. The following points combine features of the sketch component (available on both iPhone and iPad) with benefits of Apple Pencil (works only on iPad).
You can move the tool palette around on iPad. You can write using apple pencil as text using the A pencil which works in scribble mode rather than drawing mode.
When you draw, you can tilt your Apple Pencil to shade a line. You can draw perfect shapes by pausing the tip on screen after drawing a rough shape with a single stroke.
To add a space between words, use your Apple Pencil to tap and hold the spot where you want to add it. To add space between handwritten text in Notes, use the hold down on the screen to select the text, then drag the triangle that appears up or down, or use the Insert Space Above option to add a draggable horizontal line for adjustable spacing.
You can select handwritten notes and paste them as typed text in another area using the Copy as Text option.
For both sketch and text, if you type or write phone numbers, addresses or dates, you get relevant Apple application options, for example, to Facetime a person or add to contacts, or add an event to calendar. (Example here) You can use this trick to associate a note
Starting with iPadOS 18 (on M series iPads), handwriting got a massive upgrade. Apple now uses on device models to automatically refine your chicken scratch into more legible (smoother and straighter) handwriting while trying to maintain your writing style. Spell-check now works for handwritten words too. Remember, how you could previously copy handwriting as text to paste into other apps? Now you can paste text as handwriting — with additional benefit of reflow, i.e., the content will auto-adjust and move to next line if needed when you are pasting something in between, working similar to scribble (cross to remove a sentence, add space in middle etc) while retaining your handwriting.
You can create a new note by saying "Hey Siri, start a new note" and then dictating the content, which Siri will transcribe and save for you.
You can crop a PDF or a scanned document, to zoom in on a specific area. To do this, open Markup view, Control (^)+click a thumbnail, select Crop Page, drag the handles to enclose the area to keep, then click Done to save changes or Cancel to revert. You can rotate a PDF or scanned document to be oriented in landscape or portrait by using Control(^)+click on a thumbnail, then choosing Rotate Left or Right.
You can add links to other notes using the right click context menu, >> or ⌘ + K. These are not backlinks, but rather internal links, which is often good enough for daily use. You can add the link to a word in a current text or use the connected note title as preview text.
Add link to another note dialog box
To import text files into Notes as individual notes, go to File > Import and select TXT, RTF, RTFD, HTML, or ENEX formats. To export a note as a PDF, click the note, go to File > Export as PDF, name it, add tags, and click Save. You can also open a note directly in Pages by selecting the note and going to File > Open in Pages.
If you want to export Apple Notes in a useful format, I’ll suggest installing Obsidian, and using its import plugin to import Apple Notes. You don’t need to use Obsidian once you are done with the install.
Quick Notes are one of the most integrated features of the Notes app, basically new notes tagged by a specific type that are meant for one-off or “draft” content.
If you enable quick notes, you can tap on a lock screen with apple pencil and open a quick note.
In Note app settings, you can decide whether you want to open a new quick note every single time, or resume from the last quick note.
You can set hot corners in your Settings app to open quick notes.
You can set a new note from iPhone control center, the only app you can use to write something without unlocking your phone. In the control center, if you press and hold the new note button, you can directly open the context menu for new note, photo, scan, checklist as well, to directly jump into a “template” you want.
On MacOS and iPadOS, hovering and staying over a value brings up a slider that allows you to change the value of a variable. On iPadOS/iOS, this works with tapping the numeric value as well (handwritten or typed). Tapping the variable y or = on iOS/iPadOS (but not on MacOS) gives you the option to insert a graph (a new one or add a line to the current one). This is especially nice if you want to see different graphs for varying coefficients or intercepts by changing the numerical value using the slider option.
On the top of the note where it says the date, it usually shows the last updated time. Clicking on it changes it to showing Created time, and clicking again will show the Edited time. If you move to a different note, the date UI moves back to its default state.
If you often find yourself wanting to create a backdated note, such that you can use the sorting feature, there is an Evernote template trick. If you export a note from Evernote or even have a template .enex file, you can modify the created and updated time to be a date that you want to set the new note to be and then import that file into Apple Notes.
You think you know about the apple ecosystem but you don’t. I spent 100 hours trawling through various settings and options to create this guide just for you.
but that won’t be me, so here you are.
The inbuilt apple apps are pretty good. And for people who do not have specific or complex needs, and use an all apple device setup, should definitely explore using just apple apps for their digital information system.
This post is a work in progress and will be updated sporadically (and often after public beta releases of new OS versions). I’d suggest bookmarking the page rather than saving it to your read-it-later app. This is going to end up being long, and I’ll suggest reading this in parts rather than skimming it over. It isn’t a course (and neither is it paid), but you’d gain much more if you treat it as one.
I have tried to add screenshots/gifs to every point. Click the ▶ to see that image.
Callouts like these will show extra apps that I use in place of or in conjunction with default apple apps, to enhance their functionality. While I no longer use iOS, you can see a list of my suggested apps here Apps That Caught My Attention.
And these callouts consist of notes, thoughts or extra information about a particular point. They may also be used to answer possible questions that might arise with the use of a particular feature mentioned.
The App List
Apple comes with multiple in-built apps. While some of them have very defined scopes, like Apple Music or Apple Arcade, some of them don’t (like Notes, Reminders). They are easy to get into but hard to experience all features of. Those are the apps we will focus on in this post.
This document is interspersed with personal opinions, and is not just a listing of features, similar to how I did for Google Docs (Google Docs Is Underrated ). I am no longer an apple devices only person (was one for an year), so you will see some comments about how/why/whether the interoperability can matter.
The one “app” we will look at besides these — is the System Settings app. There are some hidden treasures in there, that we should definitely explore! The last thing we will look at is the magic of⌥or theOptionkey. It changes the context menus so often, that you might miss so many options in the operating system.
The notes app that comes with Apple is pretty good. It is very much a watered down, but single app version of Google Docs (you might not know how much it can do, check that here: Google Docs Is Underrated), just locked to Apple devices. But there are a few things that make it special, which we will talk about here. Most of these suggestions will focus on the MacOS app, but I will add some details about the iOS app too.
The freeform app is a digital whiteboarding solution from Apple. It is a pretty good app, that is again, Apple only, but supports all devices (yes, including Apple Vision Pro, yes, you can have an 80 inch whiteboard right in front of you). It is collaborative like most Apple productivity apps, and it is better built than most people give it credit for.
Freeform is an example of what one might call an infinite canvas app (see more of them here).
It is hard to compare this app to an ecosystem, because Google no longer has a whiteboarding app, Microsoft whiteboard is something I won’t touch with a 10ft pole, Figjam, while awesome is not really made for iPad (and you want whiteboards to support iPad), Miro/Milanote are good, but then, they are equivalent to comparing Notion/Tana/Obsidian to Apple Notes, Goodnotes/Concepts are not whiteboarding apps, Explain Everything is very education focussed, tldraw has poor feel with Apple pencil, and Heptabase doesn’t work on iPad at all.
A much more enhanced version of Freeform would be Nothing comes close to Muse (also read I Tried Many Canvas Apps For Paper Reading). Muse is possibly one of the best white-boarding app that has been built ever, but of course it is a subscription, which is prohibitive for collaborative usecases.
Other Applications
One of the most common use cases that I see is recording content. MacBook comes with an inbuilt screen recorder that can be accessed using shortcuts mentioned here or through the app. Even though the app is named screenshot, it also acts as a screen recorder. You can also choose the option to show/hide mouse clicks, and whether you want to record a portion of the screen or the whole screen. Screenshot also interacts with ⌥ , Space and ⇧ keys.
It does have 2 issues though: it doesn’t work with presenter overlay so you cannot record your webcam, and it has no way to record output audio. QuickTime Player, another Apple app allows you to record internal audio (output audio), but it is an either/or situation - you either record your microphone or the internal sound. While you can use something like blackhole as audio device, that will be complicated and it still does not solve the lack of webcam.
For complicated screen recording needs, you might want a different software. Some suggested apps are Cleanshot, Screenflow, ScreenStudio. I usually use Cleanshot. Cleanshot and Shottr (mentioned here) also give you better screenshot options.
MacOS comes with inbuilt video conferencing features, namely, Portrait, Center Stage, Studio Light and Reactions. Reactions are turned on by default in MacOS Sonoma, and I will sincerely suggest to turn them off. Portrait blurs your background, Center Stage kinda (not really, because it is inbuilt you know) shifts the camera direction so that you are in center of the screen, and Studio Light relights your image and background.
One of the newer features though is Presenter Overlay, which allows you to customize screen share, only in Facetime and Zoom. To add a presenter overlay when screen sharing in FaceTime, click the Video icon, select Screen Share, then under Presenter Overlay choose Off for a separate window, Small for a movable cropped view, or Large to prioritize the video feed over the shared screen.
Screen Sharing
You can learn more about screensharing on MacOS here. It seems like it internally uses a Facetime API because you see those controls pop up in the menu bar.
[Expand toggles to see screenshots]
Screensharing is a hidden application. It doesn’t show up on your Application or Launchpad. You need to open screensharing app using Raycast or Spotlight.
A raycast window showing screen sharing application
It opens up a screen showing all the connections you have. You can also create groups to manage all connections you have had.
Then you can enter a hostname or apple id (yes, any apple id) to connect to.
And voila, now you are screensharing.
Screensharing screen showing the controls: namely remote control, dynamic resolution, hdr sharing, launchpad, mission control, and desktop buttons with zoom buttons on top right of the window.
You can also modify what you see on the toolbar
And modify the settings for display scaling and quality
Display settings menu showing options for screen scaling and scrolling behavior on larger screens, with selected choices highlighted.
Quality settings menu for remote screens, showing options to adapt quality to network conditions or display at full quality, with adaptive option selected.
Screensharing shares microphone by default. You can mute the microphone in the Connection menu item. It also shares clipboard by default which can be changed in the Edit menu item.
You can copy paste or drag text, images and files during screensharing. Universal clipboard is not available during screensharing. You can’t have both Screen Sharing and Remote Management on at the same time.
Finder
You can create document templates, such that you don’t need to worry about modifying the original document, hence, called “stationery” in MacOS. To do this, select the document and use Get Info or ⌘ + I . In the general section, select Stationery pad. Make sure it is a document you can edit, and not a folder or an alias. Now, everytime you open that document, it will duplicate itself — without you needing to worry about modifying the original.
You can batch rename items. Select multiple items and ^ + click one of them and then choose Rename. You can choose to replace, add or change text in the names, define the format by choosing where to put the index, counter and date.
On desktop, you can quick arrange your files using Stacks. When you save a file to it, it will be added to the appropriate stack. Right click on desktop or press ^ + ⌘ + 0 and choose Use Stacks. You can browse files in desktop stack using two fingers on trackpad, expand a stack by clicking it, collapsing by clicking the down arrow, and change the grouping mechanism using Right click > View > Group Stacks By or ^ + click > Group Stacks By .
Apps I have don’t use
Photos: I have never used the Photos app that comes with Apple devices. I do not want them to be locked into an ecosystem, and I very much prefer Google Photos’ features, so that won’t change anytime. Here is the official guide to Photos app though:
Photo’s live text that can be enabled by first selecting text and then using Control (^) + Click on selected text allows you to perform these actions: copy, definitions, translations, web searches, sharing, calling, emailing, and opening links.
Duplicates appear automatically in the Duplicates album in the sidebar.
To copy the subject of a photo, simply perform a Control (^) + Click on the photo and select the option Copy Subject. You can then paste the subject into various applications such as documents, emails, text messages, or notes.
To access information about popular landmarks, art, plants, flowers, pets, and other objects in a photo, simply open the photo and locate the Info button in the toolbar. If a Visual Look Up icon is present on the photo, click on it to gather relevant details.
The “System Settings” App
Did you know you can use iCloud keychain passwords in Chrome? All you need is to install this extension.
You can set a legacy contact, a person who can access your data after your death. Makes things much easier with photos and notes (more here). Automatic Verification privately authenticates you to participating apps and websites, so that you don't have to complete a CAPTCHA. All you have to do is sign in to the app or site (more here).
A screenshot of an Apple device's 'Sign-In & Security' settings page, highlighting 'Setup a person as legacy account such that they can access data after your death'. The Automatic Verification toggle is turned on, allowing iCloud to bypass CAPTCHAs.
You can set basic app limits using Screen Time feature in Settings. These can be defined on a per app basis, and can be set to be blocked during downtime.
Screenshot of the 'Screen Time' menu in Apple device settings, displaying options for activity tracking and usage limits.
Did you know Chrome was testing an integration with screentime? It used to monitor all the websites you would visit just like it currently does in Safari. Unfortunately, it seems like that chrome flag has been axed for the moment.
Spotlight
and QuickTime Player
Spotlight search range. You can decide what kind of things you want to show up in Spotlight using Siri & Spotlight. For example, you can choose to exclude Fonts, because who, other than designers ever wants to string match with a font?
Screenshot of 'Siri & Spotlight' settings on an Apple device, with a list of categories for search results customization.
I use Raycast instead of Spotlight and I set the default keyboard shortcut ⌘ + Space to open Raycast.
You don’t need a different app to manage your storage, you can do that through settings (and figure out the apps/files using the most space)
Screenshot of the Storage settings on a Mac, showing storage breakdown by category. Includes options to manage iCloud storage and optimize storage usage, saying to use the information symbol to see files sorted by size.
I usually set the double click on window title to zoom, use tabs for documents and set up hot corners, as the bottom right hot corner to open quick note. This is because I have dock on left, which triggered by moving the cursor to bottom left corner.
Screenshot of Mac Dock settings menu, showing options for size, position, magnification, minimization behavior, and window reopening preferences.
Sometimes when you zoom (or maximize) instead of full screen, it just expands vertically but not horizontally. That is just how MacOS is 🤷♀️.
Keyboard Shortcuts
Turn on tab for navigation, such that you can move between button and other pointer event components in MacOS
Screenshot displaying 'Keyboard' settings on an Apple device, with options for key repeat rate, backlight, and a toggle for keyboard navigation enabled.
Want to change keyboard shortcuts for any menu options? For example Duplicate Tab still does not have a shortcut in Google Chrome. You can set it under Keyboard Shortcuts, on a global or per app basis.
Screenshot of 'Keyboard' settings on an Apple device showing App Shortcuts for various applications and actions.
One of the first things I change are the shortcuts for screenshot. I switch around the shortcut for copy to clipboard and save to file, because often, I am just pasting it somewhere, and I just need it copied to clipboard. An extra tip here courtesy ProperHonestTech, if you use ⌘ + ⇧ + 5 to take a screenshot for a window, it auto adds a shadow around it. You can avoid it by pressing the ⌥ key before you click the Capture button.
Screenshot of Apple device's 'Keyboard' settings focused on shortcuts for screenshot functions.
I use Shottr and Clop to improve my screenshots, mainly to annotate and optimize them respectively.
If you look at the services tab, you not only decide the keyboard shortcut for them, but also whether they show up or not. For example, for files and folders, you can turn on New Terminal at Folder as an example of inbuilt app, or Send File via KDE Connect as an example of installed app.
Screenshot of 'Services' settings in an Apple device's keyboard preferences, detailing file and folder service shortcuts.
You can set up other things too, such as Clop installs Optimize with Clop under Pictures or turn on the option for Search with Google under Searching.
Screenshot of 'Services' settings within an Apple device's keyboard preferences, with options for internet and picture services.
You can decide services that work with text selection. Did you know that you can make a sticky note out of selected text for example and set up a keyboard shortcut to do it?
Screenshot of 'Text' services in Apple's keyboard settings, showing shortcuts for text conversion and other text-related actions.
You can set keyboard and scroll modifier in accessibility settings to zoom into specific parts of the screen in full-screen or picture-in-picture mode. This makes it much easier to highlight content while video recording (similar to Screen Studio) or during screen sharing.
/demo coming soon
Extensions
If you scroll down to Privacy and Security section, you will see extensions. Extensions add more options to your files.
The markup options in Actions and Photos should be selected by default
Screenshot of 'Text' services in keyboard settings on an Apple device, listing options like converting text and opening URLs.
Did you know that quick look options can be extended? For example, the two apps I mentioned above show a better preview of markdown files or of code files, using syntax highlighting.
Screenshot of the 'Extensions' settings on an Apple device, showing Quick Look extensions like preview, markdown, and thumbnails selected.
Share extensions can be used by in-built and installed apps. One of the main ones that adds more to this is — Neardrop, that allows you to send files like Airdrop to android phones. This is how, for example, Mela adds the extension to the share menu.
Screenshot of 'Added Extensions' in the Extensions settings on an Apple device, with various apps like Gifski and Todoist enabled.
Did you know that apple has an inbuilt option to convert images, remove background, trim videos and create pdfs? Yup. You can turn them on in Quick Actions in the Extensions section.
Screenshot of Finder Quick Actions settings on an Apple device, with actions like Rotate Left, Markup, and Remove Background selected.
The Magic of ⌥ and ^ Keys
Along with the ones below, there's also this post by Saurabh I found Mar 2, 2025 about interesting shortcuts in many other Apple apps. Check it out below!
Most people know basic shortcuts that come use the Command (⌘) key. But the Option (⌥) and Control (^) keys are not often ignored, even though in combination with Command (⌘) and/or Shift (⇧) , they can have many different functions.
When you start using Shortcuts, you’ll often find that most shortcuts pick 1, 2 or 3 keys from ⌘, ^, ⇧, ⌥. So, if you want to decide your own shortcuts and given there are so so many of them, you can choose to have a hyperkey. Hyperkey is a shortcut that is set to use all the modifier keys above + another key. Because you ain’t pressing 5 keys in one go unless you train to play piano everyday, I’ll suggest using Hyperkey, Superkey (Hyperkey+Seek & Click) or Better Touch Tool to set your hyperkey (which most of the people set to caps lock).
⌘ + Click: On a non-focussed window to click on something without focussing it.
⌘ + ,: Open the preferences menu in many apps (default for Apple apps)
^ + ⌘ + Space: Character Viewer
What does character viewer look like?
Screenshot of the Character Viewer on a Mac with the search term "lam" entered, showing various characters and symbols including the Latin small letter 'l'.
⌥ + ⌘ + V: Move the files in the Clipboard from their original location to the current location.
I use the app Command X to cut and paste into a different location instead of remembering this shortcut.
⌘ + Y: Use Quick Look to preview the selected files. ⌘ + ⌥ + Y: View a Quick Look slideshow of the selected files.
⌘ + ⌥ + H: Hide all windows but the current one.
⌘ + Tab: Switch to the next most recently used app among your open apps. if you use the shortcut ⌘ + ⌥ + H to hide the windows, then you can access them using ⌘ + Tab, unlike when you minimize them. ⌘ + `: Switch between the windows of the app you're using.
Note: The second key might vary based on keyboard layout. It is the key right above the Tab key on the keyboard.
⌥ + ⌘ + power button: Put your Mac to sleep.
⌃ + ⌘ + Q: Immediately lock your screen.
⌃ + ⇧ + ⌘ + T: Add selected Finder item to the Dock.
⌥ + ⇧ + Keyboard/Display Brightness Up/Down: Adjust the display brightness in smaller steps. Add the Control (^) key to this shortcut to make the adjustment on your external display, if supported by your display.
^ + Down Arrow: Show all windows of the front app.
⌥ + Drag: Copy the dragged item. The pointer changes while you drag the item.
⌥ + ⌘ + Drag: Make an alias of the dragged item. The pointer changes while you drag the item.
⌥ + Right click: Show the option to change the always open with property.
⌥ + click on green traffic light: Maximize window instead of go into full screen. I also set double click on window title to maximize (here).
⌘ + ⇧ + / , aka ⌘ + ? : Search through menu bar items
Documents and Text
⌥ + ⇧ + ⌘ + V: Paste and Match Style: Apply the style of the surrounding content to the item pasted within that content.
⌃ + P: Move up one line.
⌃ + N: Move down one line.
⌃ + A: Move to the beginning of the line or paragraph.
⌃ + E: Move to the end of a line or paragraph.
⌥ + ⇧ + ↑: Extend text selection to the beginning of the current paragraph, then to the beginning of the following paragraph if pressed again.
⌥ + ⇧ + ↓: Extend text selection to the end of the current paragraph, then to the end of the following paragraph if pressed again.
⌥ + ⇧ + ←: Extend text selection to the beginning of the current word, then to the beginning of the following word if pressed again.
⌥ + ⇧ + →: Extend text selection to the end of the current word, then to the end of the following word if pressed again.
⌃ + D: Delete the character to the right of the insertion point. Or use Fn + Delete.
⌘ + K: Add a web link.
⌃ + ⌘ + D: Show or hide the definition of the selected word. This also shows link previews in Safari! (the more you know)
You can create document templates, such that you don’t need to worry about modifying the original document, hence, called “stationery” in MacOS. To do this, select the document and use Get Info or ⌘ + I . In the general section, select Stationery pad. Make sure it is a document you can edit, and not a folder or an alias. Now, everytime you open that document, it will duplicate itself — without you needing to worry about modifying the original.
I recently discovered this app called Clop. And man, it is so good. I often take these 3MB full screen screenshots and directly paste them into Notion (including on these pages rendered as a website), and Clop basically auto-optimizes any images on clipboard, whether shot directly, or if you use ⌘+C in your finder on an image file.
The free version works for all images (though doesn’t have an option to auto-convert to webp, I so wish it did!). The paid version (or with limits on the free version) can also work with videos and pdfs, though, I haven’t tested that part much.
I maintain a logbook of my issues with Notion, because I do not trust myself to remember why I looked somewhere else anytime.
Notion Feeling Clunky
I was asked to provide some feedback regarding when I feel like using Notion feels slow or clunky
Block and Page complaints
Simple Tables are heavily underpowered for me, you can't bulk apply colors, you can't add anything other than text in those boxes (each cell imo should be a block, that I can add images or bookmarks or content of any kind into). I would have expected them to behave like basic tables in craft (you can sort filter them, have dropdowns, but can't have complicated formulas etc) -- so something that can still be represented as a markdown but is actually capable.
Workspace buttons: I replicate so many callouts throughout my document, that I usually end up needing to have a page of "templated blocks" that are used on various pages. It would be great if we could add template blocks to slash commands that are available throughout the workspace
backlinks are the things i was most excited for, and i used the least in notion - there is no way to see which blocks the backlinks come from, it shows just the pages, the backlinks are not filterable nor editable in-place, and you can't even get a page's backlinks in API
there is a single consolidation block in notion (table of contents, which also, doesn't work for headings inside toggles, and if placed inside a toggle, still shows the complete page's ToC) -- which makes it really hard to consolidate stuff for me on pages. There is no highlight, callout consolidation block to, for example, get all highlights from a page.
As someone who treats 99% of the things as "tasks"; I wish Notion had the ability to add a checkbox to anything (images, embeds, pdfs, files, web bookmarks). (basically todo block should kinda behave like a callout i guess?)
I would really like the ability to have icons inside text (and not needing to resort to emojis). on that note, i would like to be able to add alt text to images and videos (other than captions) for accessibility reasons.
synced block content not searchable within a non-original page in Notion when the filter is set to the current page
buttons on a "homepage" or "dashboard page" cannot add content to page X, they can only add page Y to database A.
i'd love better page mentions for properties, especially rendering @ page X, if it has a date property or person property or status/checkbox property, when mentioned on page, have those be inline and modifiable inline properties. (so checking a box inline would lead to checking the box on specific property)
task view in home is a nice thing, but I would really like the option to have checkboxes on page content be visible in one place. this might be less of an issue if backlinks worked by creating synced blocks in page that could be manipulated from the page, so i can @ page X in a todo block and then see all checkboxes there, but that doesn't work
more than page preview hover card, it would be helpful to have block mention hover card. i often can get a gist of what the page is about from the name and the cover kinda covers half of the card, but block mentions are usually hidden and it would be nice to hover and see a quick preview.
Buggy UI/UX
Cursor placement issues sometimes clicking on the bottom of the page forces the cursor inside the last element if it is a toggle or a callout and I want it to be a new block and there is no way to do that other than press enter, get to next line, press del usually.
Tabs cannot be rearranged in the desktop app. I get that it is an electron app, but so is VS code, so I know it is not an electron limitation
When I switch to a new workspace on desktop, often the last open page from the previous workspace still shows up on the main content part of the window
when you have a link and you hover on it, the popup copy button sometimes is so far that by the time the cursor reaches it, the popup closes. it probably needs the same triangle cursor movement thing that is suggested for sub-menus on navigation bars.
I think even basic pencil interactions are broken for iPad. Clicks sometimes do not register. Taps are annoying. It often works in mobile mode when there is no need to. For me, the app is unusable on iPad.
shortcuts sometimes work, sometimes they don't. sometimes cmd+opt+m actually asks me to write a caption. sometimes it minimizes the app?
I can't paste over blocks or paste blocks over selection? I can paste selected text over selected text just fine, but if either of the selections are blocks, it doesn't work.
The desktop app at a certain width tries to hide the sidebar if it opens side peek. it causes layout shift.
General UX complaints
brown, orange and yellow are too similar in dark mode
please remove ai from every context menu for people who are not subscribed to ai
checkboxes that align with the general rounded UI in notion please
quick add on android would have been awesome if it had an option to add a block to a page rather than adding a subpage with the specified content to a page. on quick add, please let us select values for properties rather than just the title and content.
I wish you could favorite pages into teamspace (or create collapsible headers in favorites) -- either works; but that is my usual usecase. Database pages are usually not accessible from sidebar directly unless i favorite them but if I favorite 20 pages, then I cannot find anything, because I am dumb.
please switch into insert mode rather than / mode when a new block is inserted. i add a new block, i need to press esc to exit menu then i can start typing 😅
Can't select non-contiguous items whether on board or blocks with Cmd+Click
Databases
database properties, i wish there was a way to delete multiple multi-select property options at once. and an option to remove multiple properties at once.
i feel like dates are the clunkiest elements of notion. there is no in-built date referencer so all the dates are kinda there, but you can't see everything that mentions date X. you need to write them a certain way - 22 jan 2024 doesn't work, jan 22 2024 doesn't? reminders need 3 clicks to happen because you can't write time in the database box (thankfully you can in mentions) -- but reminders still need a click. you can't reschedule items by a day or 2 days or change reminders or snooze them, which i get, but it is just really clunky in my experience.
remove duplicate efforts and have some auto-linking for relations mechanism (i.e., if page X from database A in mentioned on page Y from database B, and db B has a relation to db A, auto add page X to the relation)
i should not need to write scripts to identify possible duplicates in notion database, and this becomes really hard for something like reference management, because it is hard to know if something was already added or not.
my company uses notion and the sprint board template. for the planning meeting, someone has to go and change filters and it is a clunky process htat is frustrating to watch. ideally it should be possible to have a block that feeds into database views, so that you don't need to click around 10 times to get the temp view filter you want.
Speed Issues
Page loading is really slow for a database that is not-cached. Slow enough that I sometimes end up clicking the card 4-5 times before it actually opens
There is at least a 3s load time on data views I have never opened even if they only have 10 items in view
databases are not usable beyond 300 rows imo, they take too long to load, or open, or sort, or filter, and need to be broken down. but because you cannot reference and filter a whole database in formulas (and you need to add all pages as a relation that you want to filter on), you really can't have broken down databases consolidated into one view
settings speed - i have been staring at the sites in settings for over 2 minutes now with just the skeleton animation
Manual Drudgery
I was asked to provide some feedback regarding when I experienced any drudgery workflows on Notion and want to automate them
I am thankfully okay with scripting, so I used run a bunch of scripts that do some of this stuff, but they break so often, that I am just using 2 external scripts at the moment:
Quick add to database using AI and Ability to use Requests -- I know you mentioned that that you are from the AI team - so, one of the things I really needed was inferring properties (including title property) -- I run this script here - Use Notion’s Property Description As Text → DB add-itor that uses notion description properties. Similar issue with not having requests access in databases, because I collect academic papers and need to get their bib, abstract etc - I used to do this manually, and I know I do not have the discipline to do this manually, so I ended up adding using a different medium and run a script to fill details in - Notion Reference Manager
I have a hard time working with pds in Notion. And I use them a lot because of course all arxiv papers are in pdf format, and I do not want to clip the complete content into Notion because that heavily clutters search and speed. Instead of being able to do a text drag as a block onto page, that when clicked goes back to the location in pdf, I need to copy paste the text (it never pastes correctly), fix the formatting, write manually the reference to location in the pdf etc.
Issues with relations, and page content. I do a lot of long-form writing in Notion. Notion doesn't really have any consolidation blocks beyond ToC (which is also broken in a way). I can't have bookmarks to places in a notion page that accumulate somewhere. Or I can't have an accumulation block that collects all callouts on page with icon X. So, I usually need to manually collect them all on top of the page for quick walkthrough, leading to me needing to keep the same page open in sidepeek and main content when writing. This is also evident with relations. Given I mostly work with content on page, the amount of drudgery work that I need to do is HUGE.
The steps I need to do
Step 1: mention a page, Step 2: figure out which database the page is coming from, Step 3: notion doesn't auto-add mentioned pages to relations so now scroll to top and add it to relation, Step 4: notion doesn't cascade permissions by relations, so now if a project is shared with someone and i want to share the related page, go to the related page and that person as guest. Step 5: notion's backlinks show no context and are ineditable in-place, so go to the block that mentioned it, convert it to a synced block, paste the synced block in "backlinked content" section of the page. Step 6: notion has no option to collect checkboxes in a page, so if i say, we will implement algo in paper X by 28 jan, and do not want to remove it from context, and KEEP it a checkbox, i go to a project's task page and paste the synced block there too Step 7: oh now we want to set a reminder or move the date, it takes 3 clicks to change inline date Step 8: notion because it doesn't have a checkbox collector, i need to see them altogether in some place, so i go to todoist and paste this block's link under a task there too. Step 9: Get link to the task and write [todoist task] as a child block, paste the link. because i am collaborating with other people, and i do not want them to see unnecessary things (and it is amazing notion allows to set permissions per block), i copy link to block, reformat it to remove page attribute, open link in a new tab, set permission to private for that block. Step 10: go back to what i was writing 10 minutes later, and completely lose my flow.
I have been wanting to explore paper reading apps that work on a spatial canvas like structure. I do want them to work with Apple Pencil because I feel the most comfortable writing with papers, so heptabase is not on the list. I am not looking for paper annotation apps like Highlights, more for excerpting regions of paper into a canvas I can drag those “components” around in. The main thing I am looking for that isn’t a usual part of most canvas apps like tldraw or concepts, is the ability to locate the “snippet” or excerpt back in the pdf.
The main app that does this is Muse, but (a) it is an expensive (fairly priced subscription) for just paper reading, and (b) I would pay that much if you could share the boards on web, but you need the Muse app, unfortunately.
So, I tried other apps, and I am going to try BoardNotes for a bit, because that kinda checks the boxes of snippets, canvas, Apple Pencil, and pdf locator. GUGA on the other hand is collaborative, has snippets, has a great canvas, works well with Apple Pencil, but does not have pdf locator unless the specific pdf is already open. Neither of them have a macOS app like Muse does, or have publicly shareable boards, but they both are pretty cheap, a one-time purchase. Kosmik 2.0 seems to have public universes as a thing even for free plans, and there is an extract option, that doesn’t remember pdf location, but remembers the pdf the extraction came from. Or maybe I should stop trying to find something and just use Muse or Liquid Text (though I am unsure how it compares with MarginNote).
And you can create excerpts for ONE pdf in XNote, but not multiple ones that can be combined onto a board
Board Notes on the other hand works with multiple pdfs on the page and has pdf locator option even when pdf is not specifically opened.
Interface of liquid text is a bit dated, but it works across multiple devices (especially, it works on mac), you can create connections, ink stuff, but it is a paid app, and I am unsure if it is any better than Muse then.
BigPaper on the other hand imports pdf as a series of images that you can draw on etc, but it is not the snippets structure that I was looking for
Highlights might be good for pdf data labeling, because it has an option to extract just the “annotated” content but it is not what I am looking for
For GUGA though, you need to have the pdf open in side view before the locator option works
Kosmik has a web app and a mac app, it doesn’t work really well on the ipad but that is fine, I think? It does have public universes though releasing in Feb 24, so maybe it is worth a shot?
A video demonstrating the use of function - with an http endpoint
And the outcome of the demo video above. Shows 3 entries, 2 of type debit, 1 of credit, across various inferred categories etc.
[Image version] And the outcome of the demo video above. Shows 3 entries, 2 of type debit, 1 of credit, across various inferred categories etc.
Uses instructor and open ai (with gpt-4-turbo) to process any content into a notion database entry.
Use addToNotion with any database id and content.
await addToNotion( "DB_ID_GOES_HERE", "CONTENT_GOES HERE"//"for example: $43.28 ordered malai kofta and kadhi (doordash) [me and mom] jan 3 2024");
Prompts are created based on your database name, database description, property name, property type, property description, and if applicable, property options (and their descriptions).
Supports: checkbox, date, multi_select, number, rich_text, select, status, title, url, email. Filters properties where either type is title or description starts with ✨.
Uses NOTION_API_KEY, OPENAI_API_KEY stored in env variables and uses Valtown blob storage to store information about the database.
Use get_notion_db_info to use the stored blob if exists or create one, use get_and_save_notion_db_info to create a new blob (and replace an existing one if exists).
These two comments on reddit exactly explain why I don't watch productivity content. Whether it be youtube or twitter or anything else — I do not want to be productive running courses, I do not want to be productive running a productivity business - but the content creation grind requires so much input — that sooner than later, that becomes your job, either the content itself, or the course itself, or the business teaching about productivity. And I do not fault creators for that. But, that isn’t me. And now it isn’t relatable. Ali Abdaal used to stand out for his content being productive at being/or learning to be a doctor, but that is no longer the case.
That+sponsorships -- but the latter is not productivity sphere related.
simosaurus says - Like most YouTube productivity creators, my main issue is that his videos are 99% of the time focused on how to optimise making YouTube content about productivity. I get that it's what he does for a living, but... YouTube/online video as a career is unique and the approaches to work often aren't very applicable to people that do literally anything else. Juicecalculator says - Yes I completely agree. Now that he is a productivity content creator I struggle to understand his mission. He’s productive, but productive doing what? He talks about wanting to be more fit, but he doesn’t seem to be very successful with it over the years and he still talks a lot about playing World of Warcraft. Congrats on writing the book I will probably do the audiobook when it releases
As much as I like Scenario, and am on the fence about AI art, I really do not like terminology shift from training → pre-training, and fine-tuning → training. Companies can say things like "train on your art", which still means the model was pre-trained on scraped art.
And the math & opt-in/opt-out debate aside -- I do not like the "lying by omission" aspect of it.
Like, a platform isn't more ethical than midjourney just because it provides services to fine-tune a model on a set of art samples. 🤷🏽♀️
I know, I know. I decided not be critical in the new year and only reinforce positivity, but what is up with really poorly done AI images used as thread’s main points or OG images for links? You want to save money by not paying an illustrator but you also want to save time by not spending hours and hours actually getting the perfect image? Is that what it is?
Because otherwise, how do you explain these images at all?
Just look at how impossible that maze is
That apple pencil is broken
Do they think the actual characters in where is waldo not matter? And that they can have 3 hands?
Notion is inherently an outliner. Do you also write your drafts in your main shared page? I often do. Especially for collaborative/hobby projects. You can change visibility of a certain block on a shared page using blocklinks.
Steps:
Copy link to block which will look something like this
Paste it any place and delete everything between your username and the first #
Open the modified link in your browser
Change sharing permissions or connection permissions for that block
Can I see all checkboxes in one place to get a combined todo list?
No. And there is no app that works for it.
Can I plan tasks in notion if I have a master task list db?
No. But you can use routine.co, akiflow, amie for it.
Does notion have counters/countdowns?
No. But you can use indify or Kairo for those.
Can I add my notion page to my personal website?
No, you cannot , but you can use embednotion. They do have an API too in case you want to automate it.
Sketch blocks?
You wanna doodle? Use tldraw.com, select copy to multiplayer and embed the link.
Add research papers?
Do you really look at all of them? Really? Use notero for zotero syncing, use (Notion Reference Manager ) for a self-hosted version of to extract metadata and abstract, as well, as a bonus tldr.
If you find it hard to concentrate in hour long meetings, firstly protect your time, secondly use something like TactiqHQ. I use TactiqHQ because I found it earlier, it has an option to mark highlights, questions, ideas and gather them all.
Notion and data?
I recommend DeepnoteHQ. I do not recommend notion2sheets or airboxrHQ for its outrageous pricing given it is not a 2 way sync.
Emails to notion?
I surprisingly find this really useful. My go to is notionsender because their free plan is enough for my use case.
One of my goals this year is to create animated illustrations. Do you all have recommendations for programmatic animation libraries that can animate illustrations (not divs/elements/simple shapes)? I'd use procreate dreams but you cannot configure those with trigger states, so I can't have them be interactive and procreate is not vector based so I can't export a svg 😟
One of the hard parts about this not being associated with a project is I have open ended questions about the scope.
Do I need trigger animations for illustrations or is that need coming from data animations?
Am I looking for video exports, runtimes (like rive) or svgs?
Honestly, val.town is cool and fun and I have never been able to get something up and running as quickly as I have on val.town. Once repl.it stopped allowing you to run flask apps without upgrading, I knew it was only a matter of time, when I would need to switch to a different cloud function app.
I tried to use it as an IDE (based on around 3 hours of exploration), but there were a few challenges:
The all-scripts-on-one-page layout made it feel less like an IDE.
There was a noticeable delay between clicking run and seeing the output.
The auto-format code feature often didn't work as expected.
Every save increased the version, which was a personal irritation as I prefer versions to represent actual changes.
Sometimes, saving didn't work if I collapsed the tab group.
For it to feel like a true script IDE, it might need code folding functionality.
On the positive side, I appreciated the TypeScript argument suggestions. So, I've been using it more as an executable pastebin. Overall, I like how compact and clean the interface is, considering how much you can do with it.
One benefit of owning my codebase for notion based blog is -- I can fix things that no other developer would be interested in fixing.
For example, my post pages were fine, but my stream page had an overflow of code block, which I hacked a fix for in an hour 😅
BEFORE
The image displays a webpage titled "Nerdy Momo Cat," featuring a post under the "Stream" section dated January 5, 2024, about a "Notion Reference Manager." The post includes a list of instructions for setting up a Notion paper manager and a corresponding script for automation. A code snippet in a black box extends beyond the right edge of the visible page area, indicating that the content is not fully contained within the layout of the webpage.
AFTER
The image shows a webpage with text describing a Notion Reference Manager. It includes instructions for adding a paper name, setting an environment variable for the API key, adding a database ID, sharing the database for integration, and changing the script to run automatically. A section of code is displayed in a black box, indicating a script for hosting the automation elsewhere. The code block, which previously overflowed to the right, has been corrected to fit within the display area.
I wish there was a way to run python on valtown -- because this is a script I want to use with OpenAI in the future and I want instructor and pydantic for LLM-fuzzy-estimation-processing of fields.
This js script will do in the meantime I guess.
Add to a notion page as a callout or to a page name to notion database through todoist (corresponding valtown script)
Remember this code is in format of val town and hence the weird imports. You would need to switch that to require probably (I ain’t good at JS)
The script either adds a page with content as title to database (which I use with Notion Reference Manager), or adds the content in callout with date to a page.
Uses a single project in Todoist and sections to identify which page/database the content goes into
My list of apps to check out later has shortened -- Tana and Heptabase for notes, Superlist for todos, and Matter if they have a native android+tweet capture in 2025 I guess.
I think what helped was realizing, I want something that is online-first (that is sync across devices at the same time is a major thing) and collaboration first. That removed Capacities from the list. I don't know if Tana plans to have collaboration on objects (rather than workspace).
Normally, if you want to add a page with Astro, you need to place an .astro- or .md- or .html- file in your src/pages-folder.
Main future goal Dec 11, 2023, 01:38 AM
Currently, the repo reads all blocks off notion, and writes them directly to renderable HTML. For saving re-computation of API calls, it saves API response to a json for posts. Instead of doing that, it might be ideal to write it as a content file that is rendered using MDX. Why? Because astro just released this Incremental Content Caching (Experimental). Caching the json, only caches the API request, but the build still often takes around 500ms at the very least. That would be the main incremental future goal from here on.
On the Roadmap
Bugs
API issues
⬜
Fix issue with notion.site being messed up because it thinks everything is part of its own site
There is no difference in API response, which is why 🤷♀️
⬜
Image links
Doesn’t show up in API
⬜
API issue: Inline code background is overriden to default
⬜
Synced blocks currently do not update due to cache
⬜
Cannot correctly figure out if there is interlinked content or not which leads to empty table of contents sometimes, fix that
⬜
Datatables do not follow table-layout:auto
⬜
Fix this
⬜
Add lightbox expansion to youtube videos
⬜
Shift to to make the bluesky embeds smaller
Features
⬜
Download a single page as HTML, print a single page as PDF
⬜
Theming (I’ll only do this if AI can implement this end to end)
⬜
Theming using Headings (font, weight, and casing), Links (decoration and hover effects), Shadows (style and presence), Borders (width and style), Border Radius (for all elements), and monotone colors (instead of Notion colors?), Background image,
⬜
Theme change animation
⬜
Denser option - table of contents on left or recent posts on left and footnotes, bibliography, interlinked content on right permanently similar to Justin Duke’s site?
⬜
Support subpages inside pages (without needing to add tags and such, so they would be indexable but part of particular page)
Actually get expressive code to work because I do want annotations and collapsing unlike shiki. The tradeoff is that it doesn’t process the line, it needs metadata 😞 BUT
You might like/related section based on embeddings
⬜
Iframe scroller to text fragment linked content. Why? So, for example you are commenting on something, you can link to that part and people can click to show on an iframe in the side or a bottom panel (similar to popup but complete frame) and ideally this doesn’t close but is just replaced by different targets if needed.
⬜
needs a marker in text to say what should open in this iframe rather than just link out
⬜
To do this without needing to create a new page for self content.
⬜
Show markers on iframe content where links are in the existing page and
⬜
Maybe ability to link them back (moonshot)
⬜
Make it work with pdfs
Completed
In Jan 2026
✅
Because pagespeed js now loads later, when I first click search or press cmd+K, text box loses focus, fix that
In Dec 2025
✅
cover image usage
✅
Add gallery view rather than of list view
✅
Multiple authors
In Nov 2025
✅
Very much later, possibly never: include random .md or .html files on page with the nice layout, tags, rss, sitemap, post listings etc.
✅
Maybe this can be done by converting notion page into mdx instead of directly to html (keeping eventual output exactly the same)
✅
or it can be done by allowing a mixture of content — how?
✅
Maybe start with basic stuff, aka, what about content published elsewhere? Can we find a way to list it such that it redirects on clicking on listing?
In Oct 2025(Claude code, Gemini-cli and I)
✅
Bib files
✅
Support footnotes — i can try to rely on comments (though unfortunately they do not how block text, only block id) — but they won’t be replicated if the page is downloaded as markdown (comments are exported in html though); so I want to use in-page features.
✅
Fix symbols in interconnected stuff to also include combos and not just repetitions so that the length remains smaller
✅
Use tag descriptions for hover and tag page and collection description for collection page
✅
Tag descriptions
✅
Collection descriptions
✅
Descriptions in OG images
✅
1 level Nested table of contents for headings (callouts, toggles, columns) [Was done before idk when]
✅
Links inside simple tables are not read right now
✅
Popups don’t show on margin notes
✅
If you get block links/page links using copy link when the page is open in peek mode, it adds v= in the href that is then pasted leading to the href not being processed correctly.
✅
Footnotes not on pages only on posts. We might want them on pages though?
✅
Uploaded icons to pages do not show up in page mentions (linked ones do)
✅
Try adding this for images instead of optimizing myself?
Add option to download cached content using workflow
✅
Pre-filled search results based off query parameters
✅
Notion wraps default shortcode for inject and iframe
✅
Fix:
✅
Fix too many tags all in new line issue:
In Dec 2024
✅
Need to fix this: [WARN] Astro.request.headers is unavailable in "static" output mode, and in prerendered pages within "hybrid" and "server" output modes. If you need access to request headers, make sure that output is configured as either "server" or output: "hybrid" in your config file, and that the page accessing the headers is rendered on-demand. — started happening when I upgraded to astro 4.16 😠
No longer happens in v5, but I will check it out if it reappears
✅
Add option to remove caches from repo using github token
So automatic authentication doesn’t work with gh actions-cache extension AND it is deprecated
Fix the display option of bluesky comments, aka, if no post is found or there is an error, remove that tab (and remove the complete comments div if there is no giscus involved)
Collections are being cleared in slug for chinese characters and tags Mar 8, 2024
✅
Icon has issues in satori when deploying on custom domains or has base URL? Mar 7, 2024
✅
Remove pnpm Feb 25, 2024
✅
OG image font for a different language Mar 7, 2024
In Sept 2024
✅
Paste as mentions in notion doesn’t work?
The fudge, does not show up in API too?
Links pasted as mention don’t show up?
In Aug 2024
✅
#IMP
✅
There is overscroll on chrome on iOS (not on safari, not even on arc). Aug 18, 2024: apparently this is only on iPhone SE and I do not have the patience to figure it out
✅
And the back to top button overlaps the toc button on pages,
✅
and the toc size is too tall for iOS (because address bar thing and SE is tiny)
✅
Currently the font size is fixed for any screen/viewport size but this shouldn’t be the case. so i need to find a way to fix that.
✅
Search on mobile also has open tags which is irritating
✅
Headings on same page should not show a popup, they should directly link to that position on click. The popup is useless in that case.
Problem? Currently there is no way to know if the linked block is on the same page or is on a different page; and the idea is we want the headings popup to show up if it is on a different page.
In June/July 2024
✅
Issue with meta scraper sometimes (some URLs are fine in the same run)
Details
Seems like a kofi blocking thing? Yeah and found Jul 25, 2024 that even using dub.sh or something like that also does not work
I tried doing something like this but I do not like how it looks:
<!--Commenting here so that it does not inject --><!DOCTYPE html> <!-- inject --><script src='https://storage.ko-fi.com/cdn/scripts/overlay-widget.js'></script><script> kofiWidgetOverlay.draw('nerdymomocat', { 'type': 'floating-chat', 'floating-chat.donateButton.text': 'Buy me a coffee', 'floating-chat.donateButton.background-color': '#F3E1C6', 'floating-chat.donateButton.text-color': '#4F1116' });</script>
i tried this too but did not like how long af it was\
<!--Commenting here so that it does not inject --><!DOCTYPE html> <!-- inject --><a href="https://ko-fi.com/nerdymomocat" target="_blank" rel="noopener noreferrer" class="flex w-full min-w-0 max-w-full grow select-none items-stretch overflow-hidden rounded border border-gray-200 no-underline dark:border-gray-800"> <div class="flex-bookmark-text overflow-hidden p-3 text-left text-textColor/90" > <div class="mb-0.5 h-6 overflow-hidden truncate whitespace-nowrap leading-5" > Support Nerdy Momo Cat on Ko-fi! Buy her a hot chocolate ❤️ </div> <div class="h-8 overflow-hidden text-xs leading-4 opacity-80" > Support Nerdy Momo Cat On Ko-fi. Ko-fi lets you support the people and causes you love with small donations </div> <div class="mt-1.5 flex max-w-full"> <div class="mr-1.5 h-4 w-4 min-w-4"> <img class="inline-block max-w-full" loading="lazy" src="https://www.google.com/s2/favicons?domain=ko-fi.com" alt="title" decoding="async" /> </div> <div class="overflow-hidden truncate whitespace-nowrap text-xs leading-4" > https://ko-fi.com/ </div> </div> </div> <div class="flex-bookmark-image relative hidden sm:block" > <img class="!absolute h-full w-full object-contain object-right" loading="lazy" src="https://storage.ko-fi.com/cdn/useruploads/138404e3-801f-4922-beb3-0db70efc0578_ed507fa6-95c6-4a2f-9dc9-e97d8d19cab4.png" alt="title" decoding="async" /> </div></a>
thought about using this but i do not like how it adds extra useless javascript
<!--Commenting here so that it does not inject --><!DOCTYPE html> <!-- inject --><script type='text/javascript' src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script><script type='text/javascript'>kofiwidget2.init('Support my bread addiction', '#4A1510', 'I2I6X8MM3');kofiwidget2.draw();</script>
this looks ugly
<!--Commenting here so that it does not inject --><!DOCTYPE html> <!-- inject --><div> <a href="https://www.buymeacoffee.com/yourusername" target="_blank" rel="noopener noreferrer" style=" background-color: #f59e0b; color: white; font-weight: 500; border-radius: 6px; padding: 8px 16px; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; transition: background-color 0.3s ease-in-out; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); " onmouseover="this.style.backgroundColor='#d97706'" onmouseout="this.style.backgroundColor='#f59e0b'" > <span style="font-size: 1.125rem; margin-right: 8px;">☕</span> Buy me a coffee </a></div>
✅
Fix plain text does not match, using plaintext
✅
scrolling expanding table of contents (this can be done using intersection observer — I am not sure it is worth doing though? Or it might be)
✅
Fix the distance between toc and toc button on mobile
✅
Fix:
[Shiki] 10 instances have been created. Shiki is supposed to be used as a singleton, consider refactoring your code to cache your highlighter instance; Or call highlighter.dispose() to release unused instances.
Solved — use this transformers= {TRANSFORMER_FUNCTIONS_ARR} instead of changing array based on shortcode because it creates a new highlighter then.
✅
⚠️ keep an eye on build times though!
✅
And maybe move metascraper out and just use noembed instead?
Use expressive code? though I am not sure how we will get that information from Notion? Maybe from a caption? Or from a shortcode at start of the code block like code inject?
Sometimes when the page height is tiny, like this one because information is all hidden in toggles or stream posts, giscus loads immediately causing a 500ms block time at least. Look into this for that
Do I need to use npm or can I do styling with CSS/Tailwind and generate mds using n2y?
And what happens if I do that?
What is not supported in mdx basically?
I will need to fix this stuff
class CalloutBlock(Block): def __init__(self, client, notion_data, page, get_children=True): super().__init__(client, notion_data, page, get_children) self.rich_text = client.wrap_notion_rich_text_array( self.notion_type_data["rich_text"], self ) # the color and icon are not currently used def to_pandoc(self): content = self.rich_text.to_pandoc() if self.has_children: children = self.children_to_pandoc() result = [Para(content)] result.extend(children) else: result = Para(content) return resultclass AudioBlock(ContentBlock): passclass VideoBlock(ContentBlock): passclass ToggleBlock(Block): """ Generates a bulleted list item with indented children. A plugin may be used to add html classes and replicate the interactive behavior found in Notion. """ def __init__(self, client, notion_data, page, get_children=True): super().__init__(client, notion_data, page, get_children) self.rich_text = client.wrap_notion_rich_text_array( self.notion_type_data["rich_text"], self ) def to_pandoc(self): header = self.rich_text.to_pandoc() children = self.children_to_pandoc() content = [Para(header)] content.extend(children) return BulletList([content])
Initial stuff about filter done but remember it sometimes gives error, so figure that out!
✅
Default slugs
Nov 24, 2023, 02:32 AM
✅
same page href
✅
code copy
i added svg to make it look nice AND GRR
The issue you're encountering is due to event delegation. When you click on the svg or path, they become the target of the event, not the button. Since the data-code attribute is on the button, clicking on the svg results in null because ev.target is the svg which does not have the data-code attribute. To consistently get the data-code attribute from the button regardless of where the click occurs (on the button or its child elements), you should use ev.currentTarget instead of ev.target. The currentTarget is always the element to which the event handler has been attached, in this case, the button.
✅
fix code highlight! move to shiki because that is default with astro!
including import "../styles/syntax-coloring.css"; worked! but i am still gonna try a tiny bit to move to shiki!
nah prism is good enough!
✅
WTF is the z-index changing to be on top of the header on mobile man?
Well, I found a nice svg and thought, hey, i can start with -90 degree angle, then rotate it on click, BIG MISTAKE!
If I want notes on my homepage which I very well might, change here src/pages/[...page].astro.
✅
how do i embed any html in page?
✅
Image optimization maybe Nov 24, 2023 (I used astro image tools, I am not sure it works though?)
✅
Stream/JSON/preview — i really do not know the solution
YAY! I DID IT!
So, I think initially I was thinking of using notion relations and items and sub-items to load them into a single page. Instead I have an idea. What if the collection page preview shows the complete content/full post instead of just the preview? That basically solves our problem, right?
✅
Remove notion extras
✅
Figure out how to create post-preview with all blocks
I think this is where the information is: src/pages/posts/collection/ [collection]/ [...page].astro
---// import type { CollectionEntry } from "astro:content";// import type { HTMLTag, Polymorphic } from "astro/types";import type { HTMLTag } from "astro/types";import FormattedDate from "../FormattedDate.astro";import type { Post } from "src/lib/interfaces";import { getPostBySlug } from "@/lib/notion/client";// type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {// post: CollectionEntry<"post">;// withDesc?: boolean;// };//FIXME: why is this a new method?interface Props { post: Post; as?: HTMLTag; withDesc?: boolean;}const { post, as: Tag = "div", withDesc = false } = Astro.props;//TODO: what is this?// const postDate = post.data.updatedDate ?? post.data.publishDate;---<FormattedDate date={new Date(post.Date)} class="min-w-[120px] text-gray-500" /><Tag> <a href={`/posts/${post.Slug}/`} class="cactus-link" rel="prefetch" > {post.Title} </a></Tag>{withDesc && <q class="line-clamp-3 block italic">{post.Excerpt}</q>}
This is how it is done for direct root pages
<PageLayout meta={{ title: page.Title || siteConfig.title }}> <section> <NotionBlocks blocks={blocks} isRoot={true} /> </section> <!-- { //NOTE: This added a recent posts thing to my page. I can add it back later if needed !slug && ( <section aria-label="Blog post list" class="mt-16"> <h2 class="title mb-4 text-xl">Posts</h2> <ul class="space-y-4 sm:space-y-2"> {rankedPosts.map((p) => ( <li class="flex flex-col gap-x-2 sm:flex-row"> <PostPreview post={p} /> </li> ))} </ul> </section> ) } --></PageLayout>
✅
Figure out how to create post preview with all blocks for only particular collections
✅
toggle issue, if i toggle parent, the arrow for all child blocks toggles too (the content does not show, but it does toggle)
✅
HTML Injection?
⬜
Figure out later how to scope these scripts or load these into iframe instead maybe?
Should these scripts be at a component level or the whole blogpost level?
Deciding whether to place scripts at the component level or at the whole blog post level in an Astro project depends on the specific use case and context of the scripts. Here are some guidelines to help you make this decision for the two scripts you've mentioned:
Mermaid Initialization Script:
If the Mermaid charts are used only within a specific component and not throughout the entire blog post, it makes sense to keep the Mermaid initialization script at the component level. This way, the script will only run where it's needed, which can improve performance and maintainability.
If Mermaid charts are a common feature across various parts of your blog post or site, you might consider placing this script at a higher level (like the layout or page level) so that it's available throughout the blog post or across multiple pages.
Button Copy Script:
If the copy functionality for code blocks is specific to a component (like a code snippet component), then it's appropriate to have this script at the component level. This approach encapsulates the functionality within the component, making it reusable and easy to manage.
However, if you have a similar copy functionality required in multiple components across the blog post, you might want to centralize this script at the blog post level to avoid redundancy and keep the code DRY (Don't Repeat Yourself).
Here are some additional considerations:
Performance and Load Times: Keeping scripts at the component level ensures they're only loaded and run when the component is used, which can be beneficial for performance.
Reusability: If you foresee the need to use these features (like Mermaid charts or copy buttons) in multiple components across different parts of your site, extracting the scripts to a higher level and making them globally available might be more efficient.
Maintainability: Keeping scripts close to where they are used enhances maintainability, as it's easier to understand and modify the code when everything related to a particular feature is in one place.
Ultimately, the decision depends on the specific requirements of your project and how you plan to scale or maintain it in the future.
If you have multiple <AlertButton /> components on a page, Astro will not run the script multiple times. Scripts are bundled and only included once per page. Using querySelectorAll ensures that this script attaches the event listener to every button with the alert class found on the page. GRRRR (link here)
✅
Make sure all onclick are either inside the element or have selector all.
✅
Move tweets to static tweet-astro
Nov 25, 2023, 12:13 AM
✅
fix site properties !MAJOR
In the original otoyo’s repo, those things are in src/layouts/Layout.astro
Can’t use astro global in typescript 😠
getSite is repeated in blog-helpers.ts and astro.config.ts at the moment.
Somehow could not run getDatabase in site.config.ts; so I created a file of my own: siteInfoSetup
THE WHOLE FUDGING ISSUE WAS IN DATE.TS NOT IN CONFIG FILE
Now it is not running at all?
So, there is a circular dependency, getNavLink was calling pathJoin in index, which was trying to import siteInfo.ts. Awesome
import type { SiteConfig } from "@/types";export const siteConfig: SiteConfig = { // Used as both a meta property (src/components/BaseHead.astro L:31 + L:49) & the generated satori png (src/pages/og-image/[slug].png.ts) //todo: get this from notion maybe? and we do not need locale. author: "Chris Williams", // Meta property used to construct the meta title property, found in src/components/BaseHead.astro L:11 title: "Astro Theme Cactus", // Meta property used as the default description meta property description: "An opinionated starter theme for Astro", // HTML lang property, found in src/layouts/Base.astro L:18 lang: "en-GB", // Meta property, found in src/components/BaseHead.astro L:42 ogLocale: "en_GB", // Date.prototype.toLocaleDateString() parameters, found in src/utils/date.ts. date: { locale: "en-GB", options: { day: "numeric", month: "short", year: "numeric", }, }, homePageSlug: "index", // Include view-transitions: https://docs.astro.build/en/guides/view-transitions/ includeViewTransitions: false, webmentions: { link: "https://webmention.io/astro-cactus.chriswilliams.dev/webmention", },};
So, now after 6 hours, we are at a point, where build works but dev does not and I do not know why.
Cannot read properties of undefined (reading 'date') It has something to do with date. I am not sure what.
Error dump
npm run devastro-notion-cms-cactus@3.5.0 devastro dev🚀 astro v3.4.0 started in 491ms┃ Local http://localhost:4321/┃ Network use --host to expose(node:44831) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.(Use `node --trace-deprecation ...` to show where the warning was created)8:57:33 PM [vite] Error when evaluating SSR module /src/utils/date.ts:|- TypeError: Cannot read properties of undefined (reading 'date')at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)8:57:33 PM [vite] Error when evaluating SSR module /src/utils/index.ts: failed to import "/src/utils/date.ts"|- TypeError: Cannot read properties of undefined (reading 'date')at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)8:57:33 PM [vite] Error when evaluating SSR module /src/lib/notion/client.ts: failed to import "/src/utils/index.ts"|- TypeError: Cannot read properties of undefined (reading 'date')at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)8:57:33 PM [vite] Error when evaluating SSR module /src/site.config.ts: failed to import "/src/lib/notion/client.ts"|- TypeError: Cannot read properties of undefined (reading 'date')at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)8:57:33 PM [vite] Error when evaluating SSR module /src/components/BaseHead.astro: failed to import "/src/site.config.ts"|- TypeError: Cannot read properties of undefined (reading 'date')at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)8:57:33 PM [vite] Error when evaluating SSR module /src/layouts/Base.astro: failed to import "/src/components/BaseHead.astro"|- TypeError: Cannot read properties of undefined (reading 'date')at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)8:57:33 PM [vite] Error when evaluating SSR module /Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/pages/[...page].astro: failed to import "/src/layouts/Base.astro"|- TypeError: Cannot read properties of undefined (reading 'date')at eval (/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:5:75)at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)error Cannot read properties of undefined (reading 'date')File:/Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:9:53Code:> 9 | const dateFormat = new Intl.DateTimeFormat(siteInfo.date.locale, siteInfo.date.options);| ^11 | export function getFormattedDate(12 | date: string | number | Date,Stacktrace:TypeError: Cannot read properties of undefined (reading 'date')at /Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/src/utils/date.ts:9:53at async instantiateModule (file:///Users/nerdymomocat/Documents/Websites/astro-notion-cms-copy-to-cactus-theme/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:56052:9)
So basically, astro config can only be static in dev mode at least, in build mode I can use github process variables. But I need database information? So, I do not really know what to do about it?
Like the getSite we wrote technically doesn’t really work?
export const CUSTOM_DOMAIN = import.meta.env.CUSTOM_DOMAIN || process.env.CUSTOM_DOMAIN || 'alpacat.com' // <- Set your costom domain if you have. e.g. alpacat.comexport const BASE_PATH = import.meta.env.BASE_PATH || process.env.BASE_PATH || '' // <- Set sub directory path if you want. e.g. /docs/
import { CUSTOM_DOMAIN, BASE_PATH } from "./src/constants";const getSite = function () { if (CUSTOM_DOMAIN) { return new URL(BASE_PATH, `https://${CUSTOM_DOMAIN}`).toString(); } if (process.env.VERCEL && process.env.VERCEL_URL) { return new URL(BASE_PATH, `https://${process.env.VERCEL_URL}`).toString(); } if (process.env.CF_PAGES) { if (process.env.CF_PAGES_BRANCH !== 'main') { return new URL(BASE_PATH, process.env.CF_PAGES_URL).toString(); } return new URL( BASE_PATH, `https://${new URL(process.env.CF_PAGES_URL).host .split('.') .slice(1) .join('.')}` ).toString(); } return new URL(BASE_PATH, 'http://localhost:4321').toString();};
So, right now it works with me using export key=value on CLI
Nov 25, 2023, 08:26 AM
⬜
Do not show pages with slug starting with underscore in
✅
collection index page,
✅
posts page,
✅
tag page,
✅
rss,
⬜
sitemap
⬜
fix robots.txt somehow?
✅
check audio embeds
✅
So, I've got this mobile button that opens and closes a navigation bar when I click on it. But here's the thing - when the menu is open, I want it to close if I click anywhere outside of it.
JSON options rather than everything in env? Only Notion values in env!
⬜
Look at the cache stuff in otoyo’s post. Maybe it is just for cloudfare? Idk!
✅
Change getMenu to be: instead of excluding pages with no rank or rank 0, just set ranks of those to 99. remember that homePageSlug if available will always have the top rank.
Look at the cache stuff in otoyo’s post. Maybe it is just for cloudfare? Idk! #LATER
✅
Set favicon!
✅
Why does otoyo add everything to public and not to src? Maybe adding to src will help with optimization?
This is gonna be difficult because while Image has bundling things, everything else does not? and because everything else does not, it needs to be referenced using public anyway. So, I can technically do this where I route downloaded images, videos, audio and files to different places and import using that, but that is too much work and I ain’t doing that!
Links on this page #Medium Term LATER Might need some flattening like this. And see this is a good use of not having all headings show up in ToC because I can use them for linking internally while still not cluttering stuff! Win Win!
⬜
Add any html/md content to posts. I am not sure how this works and I’ll need to figure out a way but that is too much work man.
I don’t know what I messed up, restart time for CSS, dumping modified header code here (Changed <main id="main" class="mx-auto max-w-prose px-8"> to have no class
Okay, so moving max-w-prose from body to main basically does not work. Great! Wrapping the main into a different div and applying max-w-prose to it also does not work.
✅
Social icons in footer
Astro Icon also supports Iconify out-of-the-box with minimal configuration. To use an icon set from Iconify follow the instructions below:
Alright, as expected site/base is messed up because it is hardcoded but I should have that fixed by tonight and we are done!
//Might need to modify this to also include collection pathsexport const getPageLink = (page: number, tag: string) => { if (page === 1) { return tag ? getTagLink(tag) : path.join(BASE_PATH, "/"); } return tag ? path.join(BASE_PATH, `/posts/tag/${encodeURIComponent(tag)}/page/${page.toString()}`) : path.join(BASE_PATH, `/posts/page/${page.toString()}`);};
✅
Pagefind does not work?
Some final bug squashes Nov 27, 2023, 07:00 PM
💡
Well, you cannot fork your own repo and I want to use that repo as a template, so irritating! Now, searching for a solution.
✅
Now transferring giscs is an issue and someone has had that problem before of course
use two files buildtimestart.txt and buildtimeend.txt in the folder .tmp/
✅
know that the last updated time does not pass to parent blocks other than pages; so if a post is updated, you cannot use some blocks and not use some blocks; do not make it complicated
✅
remember notion does not tell you when pages have been deleted, so they will still exist in the cache? OMG? maybe I do retrieve every post in a call but use cached blocks — wtf.
Points
Steps:
✅
Add partytown for GA script
✅
Check how many times getAllPosts is being called
✅
Cache getAllPosts results and move filters out
✅
Cache block results but invalidate them if post is new
✅
Cache og-images generated or just generate them in public folder so that they are easier to figure out if they have been cached?
I tried using <Code> component from Astro, but it is hard to add styles to it
For lightbox, doing import "../styles/glightbox.css" adds some error to captions
I need to use import
I need to use window loaded or something to connect it
Can’t get dynamic jsx based import to work
Landed on CDN for both
And need npm install glightbox
Dec 3, 2023, 07:51 AM
Mainly adding stuff for on this page and linked to this page
✅
get all on this page
✅
get all to this page
✅
decide on a tooltip library
✅
trying flowbite
Dec 3, 2023, 11:18 PM
✅
I got this error once while building but cannot reproduce it: error Cannot read properties of undefined (reading 'title')
Orphan heading found: Heading 2.@notionhq/client warn: request fail { code: 'object_not_found', message: 'Could not find block with ID: 3cb777e3-4815-41c8-8f30-d790b6ce7685. Make sure the relevant pages and databases are shared with your integration.'}Request was aborted
✅
look at tailwind aspect ratio plugin that is imported later (might help with google maps and everything there!!
✅
popover
✅
fix current page block popover for links in post body
⬜
add bookmark as popover for external links?
✅
add references to table of contents
✅
FLOWBITE IS SO DARN HEAVY
The general performance is not perfect anyway
Trying preline
Nah, pretty much the same: first load of preline || Mobile
Second load on preline || mobile
Second load on flowbite || Mobile
Nah, pretty much the same: first load of preline || Desktop
btw, the audio element in popup is causing horizontal scroll:
✅
lol at z-index and gpt absolutely making sure to be incorrect (patchy fix - does not work for gray/light-gray)
GPT
Dec 8, 2023
Updating to astro 4
Dec 9, 2023, 11:53 PM
✅
Astro-tweet has not updated and I do not like looks of @astro-community/astro-embed-twitter. Maybe I just copy paste the component from astro-tweet
✅
Remove URL normalization for same page popups on anchor links (the interlinked content part works fine)
✅
Add URL normalization for different page links in popups
No need. Somehow on build, there is a trailing slash added, but not in dev? So, basically if I do navlink#block-id, that causes issues in build but not in dev — if I do navlink/#block-id that causes issues in dev but not in build.
The problem is, if I move from one one triggerEl to another triggerEl quickly within 100ms, then nothing closes that has already been opened. Why? Because it goes to mouseleave here which has a timeout of 100ms to be able to move cursor to popover offset.
So, entering into a new triggerEl just clears the universal hovertimeout. And the hidePopover never gets called.
This is a good thing for nested triggers, because we want their parents to not be closed. But we want only 1 popup of each LEVEL. Let me explain level.
For example: Page has Trigger A and Trigger B. Trigger A has a popover which has Trigger A1 and Trigger A2. Trigger A2 has a popup that has Trigger A21 and Trigger A22.
Say, I hover over Trigger A and then suddenly move under 100ms to Trigger B, so popover from Trigger A will never close because moving to Trigger B has cleared timeout.
But if I hover over Trigger A, and it opens a popup, we hover over Trigger A1 inside the popover from trigger A, and that makes it so that the parent popup does not close which is a good thing. Because we want one popup each level. But if we also suddenly move from Trigger A1 to Trigger A2, the popup from Trigger A1 will not close too because moving to Trigger A2 clears timeout.
The complicated part here is maintaining and tracking levels. Especially consider that this code adds event handlers to all triggers no matter how nested, so this tracking needs to be taken care of in showPopover (maybe add trigger element and the containment level somewhere, check if there is a document contain or popover contain match for trigger element, and then immediately hidepopover if there is).
Finished a bunch of optimizations; I am not even sure if most of them worked, but at least now I know what is going into it!
Lol, should have known you can run lighthouse locally and not push stuff 100 times 🤣
^ Oh doesn’t really do what I want
Partytown in define vars thing
and without it
I will later try to figure out what happens with no partytown
Btw,
The main benefit of using lazysizes for me is just that it does not add lazy to the first image on the page, so it reduces that blocking time. If there is another way to do that, we should do it.
Dec 12, 2023, 07:45 PM
Hmm, even after changing popovers to hidden, it still takes really long
Lol
Note: In Firefox, the loading attribute must be defined before the src attribute, otherwise it has no effect (Firefox bug 1647077).
No, script based change to eager does not really work. As expected to be fair. OMG WHAT THE HELL
i am gonna laugh so much if this works. i keep forgetting i can use ts files as normal js files for key store values and keep trying to pass build time variables through components or whatever that thing is. 🤣
Update Dec 12, 2023, 08:53 PM I am laughing 😅
Dec 13, 2023, 06:05 AM Moved code highlighting to inbuilt shiki
Dec 13, 2023, 08:44 AM
Astro code highlighting
Jan 31, 2024, 09:54 PM
Apr 10, 2024, 08:49 AM
Trying to add collections by streaming a public page component into an iframe similar to embednotion.
Trying to make clipboard code better with these rules:
/*Rules:1. Two things to do: remove lines and clean up comments in remaining lines2. For remove lines, only consider these commands: [!code --] or [!code warning] or [!code error]3. Remove the line when the command is after the comment mark (any number of spaces between comment mark and coommand are fine). there can be text after command.4. do not consider command or comment mark if they are part of a string literal enclosed with ` ` or " "5. if a line is not removed, then consider these commands: [!code --] [!code ++] [!code warning] [!code error] [!code highlight] [!code highlight:number_here] [!code word:word_here:number_here] [!code word:word_here] [!code focus] [!code focus:number_here].6. if these occur right after the comment mark (any number of spaces between comment mark and coommand are fine)and are not part of string literal, just remove the command from if it occurs after the comment mark (and is not in string literal).do not remove the line. there can be stuff after the command.7. if the part after comment mark is empty or only white spaces, remeove the comment mark. again remember comment marks that are part of string literals are not considered for this.*/
Nothing works and i have been trying for 3 hours now.
code at Jun 25, 2024, 06:37 PM
type CommentStyle = string | string[];const commentStyles: Record<string, CommentStyle> = { // Double slash comments "//": [ "default", "c", "cpp", "java", "javascript", "typescript", "csharp", "go", "swift", "kotlin", "scala", "rust", "dart", "css", "php", "pascal", "groovy", ], // Hash comments "#": [ "python", "ruby", "perl", "bash", "powershell", "php", "yaml", "toml", "r", "julia", "elixir", ], // Double dash comments "--": ["haskell", "lua", "sql", "plsql", "ada"], // Percent comments "%": ["matlab", "octave", "latex", "katex", "erlang"], // Semicolon comments ";": ["lisp", "clojure", "scheme", "ini"], // Other specific comment styles "⍝": ["apl"], "!": ["fortran"], "'": ["vb"], "*>": ["cobol"], "<!--": ["html"], // Languages without standard comment syntax "": ["brainfuck", "whitespace"],};// Invert the commentStyles object for easier lookupconst languageCommentStyles: Record<string, CommentStyle> = Object.entries(commentStyles).reduce( (acc, [style, langs]) => { (Array.isArray(langs) ? langs : [langs]).forEach((lang) => { acc[lang] = style; }); return acc; }, {} as Record<string, CommentStyle>,);const getCommentStyle = (language: string): CommentStyle => { return languageCommentStyles[language.toLowerCase()] || languageCommentStyles.default;};const escapeRegExp = (string: string): string => string.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");const createCommentRegex = (style: string | string[]): RegExp => { const patterns = Array.isArray(style) ? style : [style]; const escapedPatterns = patterns.map(escapeRegExp).join("|"); return new RegExp(`^(.*?)(${escapedPatterns})(.*)$`);};const notationRegex = /\[!code (focus|--|\+\+|highlight|word:[^\]]+|error|warning)\]/g;const createRemoveLineRegex = (style: string | string[]): RegExp => { const patterns = Array.isArray(style) ? style : [style]; const escapedPatterns = patterns.map(escapeRegExp).join("|"); return new RegExp(`(${escapedPatterns})\\s*\\[!code (--|error|warning)\\](?!\\s{2,})`);};const processCode = (code: string, language: string): string => { if (!languageCommentStyles[language] || language === "plaintext") { return code; } const style = getCommentStyle(language); const commentRegex = createCommentRegex(style); const removeLineRegex = /^\s*(\/\/|#|--|%|;|⍝|!|'|\*>|<!--)\s*\[!code\s+(--|error|warning)\]/; const cleanupRegex = /\[!code\s+(--|error|warning|highlight|word:[^\]]+|\+\+|focus)(:[\d]+)?\]/g; return code .split("\n") .filter((line) => { const commentMatch = line.match(commentRegex); if (commentMatch) { const [, before, commentStart, commentContent] = commentMatch; // Check if the comment start is within a string literal const stringLiteralRegex = /(['"`])(?:(?!\1)[^\\]|\\.)*\1/g; let isInStringLiteral = false; let match; while ((match = stringLiteralRegex.exec(before)) !== null) { if (match[0].includes(commentStart)) { isInStringLiteral = true; break; } } if (isInStringLiteral) { return true; } // Check if the line should be removed return !removeLineRegex.test(line); } return true; }) .map((line) => { return line.replace(commentRegex, (_, before, commentStart, commentContent) => { // Clean up the comment content const cleaned = commentContent.replace(cleanupRegex, (match) => { // Only remove the command if it's at the start of the comment return commentContent.trim().startsWith(match) ? "" : match; }).trim(); if (cleaned) { return `${before}${commentStart} ${cleaned}`; } else { return before.trimEnd(); } }); }) .join("\n");};// Usage remains the sameconst code = block.Code.RichTexts.map( (richText: interfaces.RichText) => richText.Text.Content,).join("");const language = block.Code.Language.toLowerCase() === "plain text" ? "plaintext" : block.Code.Language.toLowerCase();const modifiedCode = processCode(code, language);const code_inject = SHORTCODES["html"] ? code.startsWith(SHORTCODES["html"]) : false;const plainTextCaption = block.Code?.Caption.map((richText) => richText.PlainText).join(" ");
While these rules make sense, shiki’s own regex isn’t this complicated:
Errors: in second line, comment mark is removed? and in last line it does remove comment mark from both places.
also btw, a line with this console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') // [!code --] [!code focus] becaxztomes console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') console.log('hewwo') [!code focus]
and this is rendered as ++ console.log('use // [!code ++]') // [!code --] with text as: console.log('use')
<h1>Welcome to My Website</h1> <h1>Welcome to My Website</h1> <!-- blah blah come! --><h1>Welcome to My Website</h1> <!-- helllaaa --><h1>Welcome to My Website</h1> <!-- [!code --] blah blah come! --><h1>Welcome [!code ++] to My Website</h1>
Okay, I think I'm done. Using expressive code is way more complicated because the markdown option does parse normally but the code option doesn't have just like internal parser. You can't just be like code equal to code. You have to parse every single thing out of it like deletions, insertions, etc. It feels like I'm writing a parser of my own and that would be a hassle. I went the easy route and like keyboard clipboard cleanup and did what Shaky was doing. It made more sense because if you wanted Shaky transformer highlighting then you would be using it properly anyway. So yeah.
Jun 26, 2024, 02:16 AM
Started a new block because this now about footnotes.
Footnote testing
[^abr3a]: footnote much before the mention itself
this is a normal footnote [^abra1]
[^abra1]: footnote content on a new block
this is a normal footnote marker with no space before[^abra2]or after [^abra2]: footnote content on new line, same block
this is a common invalid footnote [^ abr3a]
this is a common valid footnote with no attached footnote content [^abra3]
double mention of same footnote marker [^abra2]
footnote marker for multiline footnote [^abra4]
[^abra4]: hey how are you?
multiline comment using indented blocks
what if it has a callout?
random info in footnote
footnote marker for multiline again? [^abra5] [^abra5]: hey? how are you doing? multiline comment but in same block what is up?
[^abra6]: what if i have footnote content that is not a footnote marked anywhere? markdown just removes this
This is what I have.
Here is what I am thinking I guess
Identify footnote content when it starts with [^anywordwithoutspace]:
The footnote content is consider till the end of that block including children. This will consider when footnote content is a separate block, when it is in same block at end, when it has multiline footnote with child blocks, when it has multiline footnotes using enter key.
The only place this does not work is when in a single block someone has multiline footnotes and then the footnote ends when they press double enter in same block. But I am ignoring that case, why are you using notion, end that block?
Identify footnote marker if any content anywhere in plaintext has [^anywordwithoutspace]. It is not needed that this marker have spaces around it.
Jun 26, 2024
Error in run here:
I tried making it so that avif is default and oh my god, it doubled the time and increased econnreset errors so much!
Jun 30, 2024
Tried making it so that tags aren’t always open on mobile but this is not currently working for pagefind filter
On search, the auto table layout does not work and overflow X is always none
It fixes this:
borders between cells are back
I just pushed it out 🤷♀️
Jul 7, 2024
Reducing div size. Both openai and claude suddenly recommended <template> tag. But know that in astro, that tag should be the end of the file or it is not processed correctly 🤷♀️.
I thought maybe I can try changing color of all uploaded icons in dark mode but this doesn’t work
let iconURL = post.Icon && post.Icon.Type === "external" && isNotionIconURL(post.Icon.Url)?post.Icon.Url:null;//does not work because that needs to be in client side scriptif (rootHasDarkClass() && iconURL){iconURL = post.Icon.Url+"?mode=dark";}
So, I am removing this function
export function toggleClass(element: HTMLElement, className: string) { element.classList.toggle(className);}export function elementHasClass(element: HTMLElement, className: string) { return element.classList.contains(className);}export function rootHasDarkClass() { return elementHasClass(document.documentElement, "dark");}
export const getIconCssFilter = (iconUrl: string): string | null | undefined => { // Regular expression to match the pattern: // (1) Any characters after the last underscore // (2) Ending with ".svg" const regex = /_([^.]+)\.svg$/; const match = iconUrl.match(regex); // If a match is found, return the captured group, which is the color part. // Otherwise, return null if no match is found. return match ? match[1] : null;};
Not supporting reading time/count, so I removed the code related to that
Oct 28, 2025: Comprehensive Citation and Footnote System (checkout Webtrotion 2.0: Footnotes, Citations, and JSON5 Config). Webtrotion now has a complete citation and footnote system. You can now add citations from BibTeX files and have them automatically formatted. Footnotes are also supported, with popovers for easy reading.
Oct 25, 2025: Enhanced OG Images. OG images for tags and collections now include descriptions. A new automated workflow has been added to clean up template-specific files when you create a new repository from the template or pull updates from it. This keeps your project clean and free of unnecessary files.
Sep 11, 2025: Search from Query Parameters and UI improvements. You can now pre-fill the search box with a query parameter in the URL. The UI for the tags page has also been updated for a cleaner look.
Sep 10, 2025: Cache Recovery Workflow. A new GitHub Actions workflow has been added to allow you to recover your cache.
Sep 4, 2025: Upgraded Notion API. The project has been updated to use a newer version of the Notion API, ensuring better stability and compatibility with future Notion features.
Jul 6, 2024: Simple tables with search and sort. Unlike notion where your simple tables are basically just a form of displaying data, you can now search and sort your simple tables in webtrotion. Just have column headers and use <<🗂️>> in the first cell (modifiable in constants-config).
Your browser does not support the video tag.
Jul 4, 2024: Breaking change → the shortcode for HTML rendering in an iframe (if you want to use external libraries like d3.js has changed to <!DOCTYPE html> <!-- iframe --> and for injecting it directly into the body is now <!DOCTYPE html> <!-- inject -->. This helps you have code blocks that are in html and start with <!DOCTYPE html> without rendering them.
Jun 30, 2024: View transitions are back in and use ⌘K or ^K to search
Your browser does not support the video tag.
Jun 28, 2024: Floating table of contents and shiki transforms for code blocks (updated to astro 4.11), and added view transitions
Webtrotion is a extensively configurable, simple to install website builder, built with the Astro framework in conjunction with Notion. Use it to create an easy-to-use blogor website.
Webtrotion was built with the simple idea - most notion based builders, require at least one of these four things:
A custom domain, or being willing to use a vercel like domain
Just have blogposts be editable in Notion while pages are edited in the repo
Require some third party tools, or are not free to use.
Are not configurable because they are either based off third parties like super.so or the configuring requires editing multiple code files.
And I did not want that. We want something that converts well into a static site, can be hosted on github for free using the github.io domain, and can have both pages and blog posts and be configured pretty easily.
Why Notion and Astro
I use Notion for all my notes, and it did not make sense for me to download them into an md file, carefully figure out the logistics and push/pull with other SSGs like Quartro, Eleventy, Hugo or Jekyll.
I could have used another CMS but again, I use Notion, and it is easier to keep the content in one place. Notion also comes with interesting affordances that other CMS don’t: WYSIWYG for various components, block level permissions (to add drafts to post text), easy collaboration etc.
There are some NextJS options that kinda fulfill this criteria but I do not know NextJS and I did not want to figure it out at the moment, for example: Notion Next, Morethan-log, and Notion-Blog-NextJs
Key Features
Astro v5 and Tailwind v4
Integrates with Notion to create a website and not just a blog
Auto-generated related content and pages that link to this page
Pretty looking wikipedia like popups on hover that works with links to any block on any page.
Starting Webtrotion 2.0,
Footnotes and Citations[b]
Listing view supports both list and gallery layouts
Multiple authors with dedicated author pages
Cover images as hero backgrounds
Demo
Check out the Demo, hosted on Github using Github actions
We’re now on Webtrotion 2.0! When you clone the template, you'll get the latest version with JSON5 config, footnotes, citations, and all the new features. These docs reflect version 2.0.
Quick start
Notion Setup
Duplicate this database into your Notion account. Remember to duplicate it as a standalone new page, rather than into an existing page.
Your database title becomes the site title, the database icon becomes the favicon, and the description becomes the site description.
Recommended:Get your Data Source ID. This is the preferred connection method for Webtrotion 2.0 because it's more stable. You can get it from:
Open your database in the Notion app
Click the ••• menu in the top right
Scroll down to Manage data sources
Click Copy data source ID
Alternative: Get your database ID. Database ID is the 32 character alphanumeric string right after your workspace in the URL. It is the easiest to get this ID on a web browser rather than the Notion app. If you use the database ID, Webtrotion will automatically find the Data Source ID from it.
Database id from notion url
Which ID should I use? Data Source ID is preferred because it's more stable. Database ID works as a fallback - Webtrotion will auto-find the first Data Source ID from it. If you provide both, Data Source ID takes priority.
Github Setup
Create a new repo from this template if you want to have a one and done standalone repository. If you want to be able to access and sync changes made in the template to your repository, choose to fork it instead.
If you do not choose to add a custom domain, the name of your repository matters. If the name of the repository you create is <username>.github.io; your website will be accessible at <username>.github.io if you use github actions. If you use any other name, the website will instead be hosted at <username>.github.io/<reponame>.
When you first fork the repository, you will see that the github action output fails. This is because we haven’t added or changed notion integration information. Don’t worry, once you add those, it will run successfully.
Go to Environments → github-pages; Scroll down to Environment Secrets, click Add New Secret. Set name as NOTION_API_SECRET and the value to the API secret you obtained in Notion Setup
Very Optional (if you are tech-savvy or want to try): Get a personal access token to delete caches and set another environment secret named DELETE_CACHES_GH_TOKEN to this token
Details
Obtain a personal access token that you can limit to this repository using the process mentioned here: GitHub DocsManaging your personal access tokens - GitHub Docs. You only need the repo scope and remember to set the validity to no expiration. Remember to save this token somewhere because you will not see it again.
In the environment secrets section, add another environment secret named DELETE_CACHES_GH_TOKEN and set it to the token you obtained in the previous step.
Go to the actions tab, and choose I understand and want to run actions. On the left sidebar click the action “Deploy Github pages”. It will show a warning saying the scheduled action is disabled in forks. Click Enable workflow.
Set up giscus as mentioned on the website giscus.app. Keep the script it produces open as we will use it in the next step.
Go back to your cloned (forked or created through a template) repository.
Open file constants-config.json5[c] in the web UI. This file is the complete setup file for your website. For now, we will make four major modifications:
Under notion, change data-source-id to your data source id that you obtained from Notion (or use database-id as fallback)
Under site-info, add your name to author (this is used for RSS feeds, OG images and metadata)
Under socials, add your social media profile URLs (you should also remove the value for this-github-repo and point it to yours)
Under comments → giscus, add your giscus information to data-repo and other fields. If you do not want to use Giscus, leave data-repo empty and that will disable the feature.
Save the file and commit+merge to the main repo.
The github action by default runs every 8 hours or on commits to the repo. This can be modified in .github/astro.yml file. You can choose any cron duration.
And we are done! You can access your website on <username>.github.io or <username>.github.io/<reponame> depending on what you chose. Checkout all Supported blocks in Webtrotion but tl;dr all blocks are supported except child databases and child pages.
Remember, the cron schedule is by default set to every 8 hours. You can change it to run every 2 hours or if you want to push out a change immediately, you can also manually run the github action if you are deploying on github. The workflow name is “Deploy Github Pages”.
Preview
Light mode preview
Dark mode preview
Notion Properties
Your database title becomes the site title, the database icon becomes the favicon, and the description becomes the site description.
Pages or posts are only published if: their Published property is checked ANDif the explicit publish date is empty or is before the current date.
Property
Usage
Page
Title of the page or post being rendered
Tags
Tags displayed for the post
Excerpt
Description used in OG images and RSS feeds
Collection
Collection in which the post/page goes. Anything tagged with main or your menu-pages-collection value (default "stream") is rendered as a page.
Published
Your post or page is only published when you tick publish
FeaturedImage
Image used to generate your page’s or post’s open graph image
Authors
Used to specify multiple authors for a post using Multi Select (tag description can be used to specify author-url, photo and description in shortcodes)
Specific Slug
The template generates a slug using formula, but if you want a specific slug, you can specify it here
Explicit Publish Date
By default, the formula considers the date notion page was created to be publish date. You can override it by setting a date for this property.
Explicit Last Updated Date
By default, the formula considers the date notion page was last edited to be last edited date. You can override it by setting a date for this property.
Rank
Want to order single pages (Updates before Papers?) Set rank in ascending order
Pinned
Pins post to the top of any collection page, except those tagged with menu-pages-collection.
Bluesky Post Link
Directly link to a bluesky post if you are using Bluesky for post comments
Don’t rename the columns of DB. They are used in the astro code. You can reorder the columns or add any other columns you want.
Local Run
You can run the code locally by cloning the repo.
cd folderexport NOTION_API_SECRET=YOUR_KEY_HEREnpm installnpm run build #we need to build once because that is when all icons are downloadednpm run dev#use npm run build-local to reflect code changes
Notion connection: data-source-id (preferred, more stable) or database-id (fallback, auto-finds data source if data-source-id empty)
site-info
Basic site info: author (name for RSS/OG/metadata), custom-domain (without https:// or slash, empty for Vercel/Netlify), base-path (subdirectory like "/blog", empty if root), site-author-url (URL for site author, only when author name matches site author), site-author-photo (avatar URL for site author)
site-info → authors
Multi-author support: enable-author-pages (builds /authors/ index and /authors/[author]/ pages), only-when-custom-authors (only show bylines and author pages when at least one post has a non-default author)
collections-and-listings
Post organization: home-page-slug (default "home"), recent-posts-on-home-page (show 5 recent posts), number-of-posts-per-page (default 10), menu-pages-collection (pages in nav, default "main"), full-preview-collections (array for stream-style display), hide-underscore-slugs-in-lists (hide _ prefixed from lists/sitemap/search), listing-view ("list" for traditional or "gallery" for card-based grid with FeaturedImage thumbnails)
theme → colors
RGB values (with spaces, e.g. "255 255 255") for light/dark modes: bg (background), text (main text), link (links), accent (primary highlights), accent-2 (secondary highlights), quote (blockquotes)
theme → fontfamily-google-fonts
Font names only (no URLs, no escaping): sans-font-name (body/UI font, can be sans or serif), mono-font-name (code blocks). Auto-loads weights [400, 500, 600, 700] and styles [normal, italic]
theme → cover-as-hero-background
Use cover image as background behind hero/post preview
socials
Social profile URLs (keys are fixed, match icons): email, github, googlescholar, semanticscholar, dblp, substack, facebook, twitter, threads, instagram, mastodon, bluesky, calendar, kofi, buy-me-a-coffee, this-github-repo
tracking → google-analytics
Google Analytics: use-ga (enable GA tracking), public-ga-tracking-id (GA4 Measurement ID like G-XXXXXXXXXX)
tracking → umami
Umami analytics: use-umami (enable), data-website-id (website ID from Umami dashboard), self-hosted (true for self-hosted instance), self-hosted-umami-url (script URL for self-hosted)
tracking → google-search-console-html-tag
Google Search Console verification: paste the 'content' attribute from verification meta tag
og-setup
OG image generation: columns (1 = single column with bg, 2 = side-by-side), excerpt (include excerpt), title-font-name (any Google font), title-font-weight (default 400), footnote-font-name (author/date font), footnote-font-weight (default 400)
comments → giscus
GitHub Discussions comments: data-repo (username/repo), data-repo-id (from giscus.app), data-category (discussion category name), data-category-id (from giscus.app), data-mapping (how to map pages, e.g. "pathname"), data-input-position ("top" or "bottom"), data-reactions-enabled (enable emoji reactions)
comments → bluesky-comments
Bluesky comments: show-comments-from-bluesky (enable), auto-search-for-match with turn-on-auto-search (auto-find posts), author (your Bluesky handle), echo-feed-emoji (emoji marker for blog posts)
comments → webmention
Webmentions: webmention-api-key (API key from webmention.io), webmention-link (endpoint URL)
block-rendering
Content display: enable-lightbox (click images for fullscreen), full-width-social-embeds (embeds span full width on small screens), optimize-images (convert to WebP, slower builds but smaller files), process-content-to-markdown (convert all pages/posts to markdown format alongside HTML output for use with LLMs and other tools)
shortcodes
Special markers in Notion code blocks: html-render (render HTML in iframe, default <!DOCTYPE html> <!-- iframe -->), html-inject (inject raw HTML, default <!DOCTYPE html> <!-- inject -->), expressive-code (not implemented), shiki-transform (advanced highlighting, default <<shiki-transform>>), mdx-inject (MDX compilation, default <!-- mdx inject -->), table (datatable features, default <<🗂️>>)
shortcodes → author-desc
Author metadata shortcodes in multi-select description field: author-url (start/end markers <<author-url>>), author-photo-url (start/end markers <<author-photo-url>>). Remaining text is the bio
auto-extracted-sections → footnotes
Footnotes: sitewide-footnotes-page-slug (legacy manual page slug, default "all-footnotes"), in-page-footnotes-settings with enabled (toggle system), source (only one true: end-of-block for definitions at block end, inline-latex-footnote-command for \\footnote{content}, start-of-child-blocks for first n child blocks, block-comments for Notion comments with Read permission, block-inline-text-comments not yet implemented), marker-prefix (default "ft" for [^ft_1], does not apply to inline-latex-footnote-command), generate-footnotes-section (collate at page bottom), show-in-margin-on-large-screens (margin on large, popovers on small)
auto-extracted-sections → interlinked-content
Interlinked content at page bottom (formerly references): site-links-in-page (links to other site pages), external-links-in-page (external website links), media-and-file-links-in-this-page (embedded media files), links-to-this-page (backlinks from other pages). Popovers enabled by default
auto-extracted-sections → citations
Citations: add-cite-this-post-section (add "Cite this page" section), extract-and-process-bibtex-citations with enabled (toggle BibTeX processing), bibtex-file-url-list (array of .bib URLs: GitHub Gist/repo, Dropbox, Google Drive; GitHub links check timestamps, Dropbox/Drive fetch on every build), in-text-citation-format ([@key] pandoc cite\{key\} LaTeX / #cite(key) Typst, renders as [firstName et al, year] or [1][2]), bibliography-format (only one true: simplified-ieee for numbered ICML/NeurIPS style, apa for author-year ACL style), generate-bibliography-section (collate at page end), show-in-margin-on-large-screens (margin on large ≥1024px, popovers on small)
external-content
Pull external content from GitHub via Notion's "External URL" field: enabled (toggle feature), sources with markdown (GitHub folder URL where each subfolder is a markdown post), mdx (GitHub folder URL for MDX posts), html (GitHub folder URL for HTML posts), custom-components (GitHub folder for MDX imports, downloaded to src/external-content/custom-components, also usable via mdx-inject shortcode)
redirects
301 redirects: object mapping old paths to new (e.g., "/old.html": "/new/") for SEO when migrating content
There are 3 shortcodes atm, two supported, two in works. html shortcode is used to embed html code blocks that start with this line directly into the page. shiki-transform is used to transform code blocks into more expressive representations. table is used to set the identifier for data-tables enabling search and sort.
references
To show related pages, external links, and mentioned media at the end of the post. Set popovers to true to display a snippet when hovering over a post link in the body.
theme
Colors for light mode and dark mode
fontfamily-google-fonts
Combined URL link for all fonts you want to use from google fonts. Remember to escape spaces in font names if needed
number-of-posts-per-page
Number of posts in a page for pagination purposes
enable-lightbox
Set to true if you want people to be able to click on your image to open in a new tab
request-timeout-ms
Timeout for API requests
menu-pages-collection
Collection select value in Notion database that decides if these are pages or blogposts. Set this to value you use for pages.
heading-blocks
Which top-level blocks blocks should form the table of contents on right
full-preview-collections
Stream like view, where each post is a mini-blog in the same page as a scrollable page instead of being links to individual pages. Any Collection name added to this list will be rendered as stream view.
hide-underscore-slugs-in-lists
If the slug starts with _, hide those in post lists, rss and sitemap, but still render them, so that you can share them
home-page-slug
By default is set to home but can be anything that you want for the renderer to recognize which is your home page from the database
og-setup
Open graph setup refers to the images displayed when sharing links. It includes excerpts and creates two columns if a featured image is available. You can customize the title and footnote fonts for the og-image, particularly for non-English blogs. Ensure that each URL has only one font and, if it has a weight, the weight is above 400
optimize-images
Converts images to next-gen formats like webp for more responsive sites
redirects
Intentional redirects, especially if you are moving systems that astro should redirect to
all-footnotes-page-slug
Defaults to _all-footnotes. This page is not visible in post list because it starts with a _ but you can use this show popup blocks on any of your pages.
bluesky-comments
show-comments-from-bluesky to turn on/off a Bluesky comment section. auto-search-for-match searches your profile for parent posts mentioning the link, controlled by turn-on-auto-search, with author specifying your Bluesky handle; echo-feed-emoji filters these searches.
License
MIT
Notes
Don’t rename the columns of DB. They are used in the astro code. You can reorder the columns or add any other columns you want.
Remember, the cron schedule is by default set to every 8 hours. You can change it to run every 2 hours or if you want to push out a change immediately, you can also manually run the github action if you are deploying on github. The workflow name is “Deploy Github Pages”.
Update Jun 19, 2024: I included a keep alive workflow which will hopefully work to keep the action running without any manual intervention. Fingers crossed 🤞.
Webtrotion relies on GitHub Actions if you're deploying on GitHub Pages. If you don't commit any changes to the repository, remember to manually enable the workflow every 60 days.
Screenshot of the email you’ll get warning you about the workflow being disabled.
[GitHub] The "Deploy to GitHub Pages" workflow in nerdymomocat/nerdymomocat.github.io will be disabled soon
Aggressive Caching
Remember that the setup uses aggressive caching for github actions. These caches are public. If you want to remove caches, you can go to github action workflow and manually delete the caches
Multilingual/Non-English blogs
Open graph setup refers to the images displayed when sharing links. It includes excerpts and creates two columns if a featured image is available. You can customize the title and footnote fonts for the og-image, particularly for non-English blogs. Just specify any Google font name (no URLs needed starting version 2.0), and set the font weight if desired (default is 400).
If you end up using webtrotion, please consider buying me a coffee here:
From September 2025 onwards, AI assistants have been used to develop complete features. Before this, GitHub Copilot and chat-based AI tools assisted with code writing but not full feature development.
Just like these!
For Webtrotion before 2.0, open constants-config.json and configure four essential settings: change DATABASE_ID to your Notion database ID, add your name to AUTHOR (used for HTML semantics and OG images), add your social media links, and add your Giscus information to the GISCUS key (or disable it by removing the value for data-repo in GISCUS).
Introducing Webtrotion: Webtrotion is a extensively configurable, simple to install website builder, built with the Astro framework in conjunction with Notion. Use it to create an easy-to-use blog or website.
Want to Unlike All Liked Pages on Facebook?: Did you like stupid stuff when you were an emo teen in 2010 on Facebook. Unlike and clean your timeline using this browser script.
Sync Mutes and Blocks: If you are like me and have multiple twitter accounts but dislike the same set of people and words, you can use this very very convoluted method to sync blocks and muted words across accounts.
Add to Habitify from Todoist w/ AI: I have never been able to maintain a habit tracker because I want to write “groomed cat1 and cat2 yesterday at 8pm” but something as simple as this takes 8 clicks. So, I wrote a script that uses LLMs to parse sentences into habit logs, such that I have the pretty read interface but a less frustrating write interface.
If you end up using any of these scripts, please consider buying me a coffee here:
If my repo has any commit message other than "huh?", "what", "done", "commit", it is because of this tiny lifesaver by @code. I don't even know where it is coming from (Copilot, internal, extension) -- but it has made things a lot easier!
A screenshot of a version control panel within Visual Studio Code. It highlights a feature where commit messages can be auto-generated using AI. The user interface shows a text box where a message "Fix sitemap filter and update site logo" has been typed, likely as a commit message for version control. There's also a tooltip visible with the option to "Generate Commit Message", indicating the AI's assistance in creating commit descriptions. The panel lists several changed files, like "astro.config.ts", "site.config.ts", and "types.ts", with a notification showing there are 4 changes in total.
The main thing to remember is it does not support child databases and child pages. I don’t plan to support those till Notion adds a native embed option.
Footnotes [a] and Citations, for example if we wanted to know why Tufte style footnotes are nice [1]. Citations won’t work here because they are not enabled by default.
PDFTriage: Question Answering over Long, Structured Documents
Download Preview
Toggle
Toggle
This is toggled details.
1
2
3
Toggle inside toggle
More toggles!
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut volutpat nunc. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut volutpat nunc.
Long enough toggle to see what happens to the marker here.
And that concludes my TED Talk on "Just Pick Your Tool, Keep Evaluating It." Remember, choosing the right note-taking app is all about finding the one that best meets your requirements and preferences. Once you've made your choice, continue evaluating it regularly to ensure it continues to serve your needs effectively.
By refining your requirements, assigning tool scores, and conducting checks on accessibility, stability, and usability, you can make an informed decision on which app to use. Don't be swayed by shiny new tools that may not fulfill your actual needs. Stay true to what works best for you and your note-taking process.
So, go ahead, pick your tool, and embark on a journey of continuous evaluation and improvement. Happy note-taking!
All Mentions
Dates: Nov 23, 2023, 05:27 AM Nov 22, 2023 Nov 17, 2023 to Nov 22, 2023 Nov 23, 2023, 05:00 PM and People:
I did something very normal Nov 22, 2023 which is a huge deal!
Loom/any video embed using Notion’s option is not supported at the moment
But it works if you use their embed URL
Direct Uploaded Video
Your browser does not support the video tag.
This is a direct uploaded video caption!
Known stuff that is not supported/has bugs
Multiple injections on a single page if not handled carefully, have clashes can have a problem!
Resizing for embedded html is a problem, for things like d3.js, but does it matter? How does distil handle it?
If you use npm run dev without npm run build, some things may not be download leading to:
CSS generation is also now associated with build. Run the build command once before running dev please.
Synced blocks do not change the update timestamp of the pages the block is synced to — so they are not updated unless you end up building without cache, or updating the fields in the individual pages where the blocks are synced.
Table column colors are not supported through API
Block mentions are not supported through API
Comments are not supported
Word breaks are an issue on smaller screen if there is no viable place to break them. Currently I am using word-wrap:normal;
I do not plan to add support for inline databases but I’d love if anyone wants to contribute?
Child Pages are too much work to support and I do not have a personal usecase for it!
Column widths and embed widths are not exposed in API
Backlinks are not exposed in API so I do not know how to do Links to This Note
Right now, if I click on a tag from “unlisted” URL and that tag is only attached to that post, no tag page is generated, and I think that isn’t a bad thing!
If you post a parent block with child sync block to another workspace, it will not show up in UI but does show up in API causing warnings/errors
Heading ids are generated based off text. If you have two headings with same names, that is an issue. I can change it to use block id, but then they are illegible to anyone using an href link for them to share.
Footnotes
Footnotes can be added like this. Please check here for details.
Missing information bothers me a ton if I have structured data inputs (food, journal, habits, medications etc). What GPT4 enabled for me is the ability to dump stuff on a page that I would have earlier found an app for. I can use it to extract structured data if need be.
It took me so long, but I realized I do not want time boxing for actual work. I want time boxing for everything other than work so that I know how much work I can get done, how much social energy can I expend when accepting a meeting, what is urgent vs important etc.
Work for me expands to fill the space I provide it based on the energy and brainpower I have. It is the most...finicky thing. Everything else affects work. Yes, it is the most important thing, but everything else on my calendar is there because it is either urgent or important.
So, it **has** to be there. Or I will end up not talking to my best friend for 3 months if I think that is moveable. So, that is what time-boxing needs to be for me. I do 2 day task slots now (it can be 16 hours in 1 day, or 5+10) but, that is what works the best for me.
Also hyperfocus is no fun, but it does not help me to fight against hyperfocus. So, work gets done how it needs to get done! Yes, there are review deadlines, but having a timer that says, oh, don't do this any longer, or something that says I need to do exact X has not helped
Every single time I use a local first app, I am reminded why I am not meant for local first apps. My @GoodnotesApp ended up with a corrupted/unliked database to pdf files, even with the auto-backup on, which cannot be fixed. Great!
This image shows a screenshot of Goodnotes app on iPad. The interface is displaying a folder named "Paper Reviews". Several documents are shown as thumbnails. Annotations on the thumbnails are visible, such as lines or marks made on the documents, but the actual content of the PDFs is not visible in the thumbnail preview suggesting data corruption.
In case people bring this up: 1. Maybe you deleted the files -- I did not intend to. 2. You should have exported to pdf -- I'll lose all ability to search handwriting, might as well print it out 3. Read pdf & review in markdown -- uh huh. 4. Beta OS switching problem -- and?
I tried @morgencalendar's new assist option. It is infinitely configurable (w/ some defaults), which is great! W/ new pricing +education discount, it's more expensive than @reclaimai, the question for me is, how much customization do I really want (I only need todoist+tasks)
Like, yes I can use morgen to do something like -- oh, propagate stuff based on regex (because I often add stuff like Migraine attack from 4pm-4am) and delete it from original calendar using JS, but maybe I can just do it using google apps script instead, ya know
I'm looking for readings on how to improve informal research-focused technical writing that doesn't read like reports. While there's some overlap with general copywriting—in terms of pacing and attention-grabbing—the usual content suggestions don't apply to me.
My aim is to write for insights and clarity. I want to keep the reader engaged without selling them anything or trying to convince them of something. Essentially, I want the reading experience to be joyful for those who choose to engage with it.
Realizing I use Procreate because it has double/triple tap for redo/undo & rotation of canvas; use Notion because it is a webapp, multi platform, block based with interesting blocks.
I've lost all rights to suggesting that people shouldn't have UX as a main deal breaker 🤦🏾♀️
I like it a lot but it needs a ton of stuff to be my day-to-day app.
I got access to Superlist about a month ago. I generally don't write about apps unless I've spent some quality time with them. The core concept of Superlist is like if Notion or Apple Notes had calendar views, scheduling, and checkbox assignments. It reminds me a lot of ColumnsApp.
What I appreciate about it is the focus on collaboration; I can't really work with a personal to-do app. Plus, it's multi-platform, which is a must for me. On the integrations side, it's decent; my go-to app, Todoist, doesn't offer much beyond Google Calendar integration.Tasks, Notes (including files and attachments), and Comments are all centralized, which is honestly fantastic. This is the core reason I wanted to check out Superlist, and it delivers.
The UI for managing tasks, comments, scheduling, tags, and lists is really well done. Everything shows up in an Inbox that can be filtered, scheduled, and tagged, making task management a breeze.
So why am I not committed yet? The app lacks export options, activity logs, watch apps, task statuses beyond a basic checkbox, quick capture options (Todoist really spoiled me there), limited keyboard shortcuts, and doesn't display subtask progress. It also has some syncing issues similar to Todoist.
Despite these drawbacks, I think it's a pretty solid app. It treats tasks as first-class citizens, unlike many note-taking apps, while still maintaining a lot of to-do app features.
Once it's out of beta, I think it's going to attract a lot of attention. I'm eager to see how it evolves.
📚 Stream🏷️ Notion🏷️ Task Management🏷️ Tools🏷️ Google Workspace
My Experiment with Google Workspace vs Notion
I tried using Google Workspace as an alternative to Notion and, although I appreciate many of its features, I've decided it's not the right fit for me right now.
I have several reasons for liking Google Workspace: it functions offline, almost everyone has a Google account, and its integration with Bard ( Jul 13, 2024, 06:14 PM: Gemini) is seamless-ish. Features such as toggles, previews, markdown-based writing, and rich text are also invaluable.
The tasks functionality, although not free, is impressive as they can be linked with Google tasks, mentioned in chats, and synced from documents. I find Sheets+API information straightforward, and the ease of handling data through inline dropdowns & places is superior to Notion. Its offline capability, compatibility with Google Calendar, Meet, Keep, Google Apps Script, Gmail integrations, outline on the side pane, and summary are features I truly adore.
However, there are some areas where Google Workspace falls short for me. I prefer the toggles in Notion, as Google Docs' toggles are restricted to headings. The use of embeds, callouts, and block-based editors is something I am accustomed to and enjoy in Notion. I miss features like synced blocks and block mentions, and I'm not a fan of viewing Google Drive as a cluttered space with numerous files. Notion's user experience, especially when opening a page with the same sidebar, feels more cohesive (Update: Google Docs is getting something similar). Another concern is the storage aspect; uploading to Google consumes my storage while Notion offers it for free. I'm more familiar with the Notion API, making it easier for me than Google Docs API. Public content presentation in Notion also feels superior to GDocs.
Perhaps in the future, if I become more comfortable with the editor and the absence of certain block features, I might reconsider Google Workspace. It encompasses many features I desire from Notion, particularly task collection and an advanced AI search via Bard (now Gemini) for GDrive. This post did enlighten me: if I could ever tolerate the sight of 10k randomly scattered files, Google Workspace would be my go-to. Surprisingly, I even prefer it over Obsidian, which further narrows down my options.
I think it is the concept of defaults. If you have time to add cute animations, you have time to make it accessible.
As someone who never thought about accessibility when I was in India, it took me time to come around to the idea that accessibility needs to be the default.
For example, yes, looking for color blind palettes and textures adds time to your graphing process, but that time should have been factored in. Considering non-color blind friendly graphs as a completed task was the problem for me. 😅
And now we have the vision API! If you can use AI to write your content, you can use AI to add alt for the images in the content.
AI Email Labeller is a nifty Google Apps Script that allows you to automatically label your emails based on your prompts. It’s hosted on Google Apps Script, enabling users to leverage the Gmail API directly, even those in a managed workspace like .edu domains. The only cost involved is the OpenAI API.
The one major thing GPT has enabled me to do is be able to create UIs for scripts that I would have just relied on code/spreadsheet for. Makes it much more likely that I will actually end up using something too!
Sketchnotes catch my eye but don't hold my attention
I've always been a fan of the concept of sketchnoting, but I have realized I don't consume information that way. And it really makes it much more unlikely for me to sketchnote, which honestly is sad. What I actually like consuming information from are infographics or supporting images, not icons.But, sketchnotes serve a different purpose for me. I don't know what part of sketchnotes makes me want to click on the link so much, but I do not actually want to learn or read the illustration? (Or maybe it is just that I have run out of ADHD meds at the moment, and have 0 visual focus, but that's unlikely).They are so irresistible for my mind. It's very much as if my mind goes Oooh, a pretty well thought-out illustration, that must have required so much effort. Let's click the link and read the article. They are the definition of possibly me judging a book by its cover.
Scratches and Scribbles – Friction Logs as a Service
I’m offering a basic no-deadline 100-word scribble for free, detailed scribbles for $500, a 5-minute video scribble for $700, and 30 min call for $1200, and custom pricing for continual testing.
I’m offering a basic no-deadline 100-word scribble for free, detailed scribbles for $500, a 10-minute video scribble for $700, and 30 min call for $1200, and custom pricing for continual testing. As you can see, the pricing here is not indicative of the time I would need to spend, with your app or course (other than the call version, where it would be 30 minutes on top of usage), but rather how much I would have to plan and prepare for the output. If you have a free app that you think I’d be interested in, feel free to shoot me a message. If it’s something I’m testing for personal use, I might do detailed scribbles for free. I’m not willing to pay to test apps.
On my main account, I do provide some consultancy from a research perspective (fine-tuning vs in-context learning, preference learning, generation evaluation, and verification), priced significantly higher (starting at $1800/hour). You can contact me if that is something you are looking for, and we can switch over to talking on my main account, if there’s a fit, so that you can be confident of my expertise.
If I have done it for free before, why am I charging for it?
I think my main thought process is monetizing some aspect of the service. Secondly, it’s also a time constraint and an action constraint. If you want screenshots, annotations as to what works, what doesn’t, details, etc., and you want them within a certain time limit, I need to mentally make space for it. If that isn’t what you need, you can just ask for a basic no-deadline scribble for free, as has always been the case.
Who Am I?
I am a student researcher, transitioning to being an industry researcher this year. I have majored in computer science, and my research is in the area of LLMs (evaluation, data transparency, and fine-tuning). I have ADHD, diagnosed as an adult, which significantly informs my app usage. I’ve also been involved in journaling and quantified self for at least 15 years. I know how to code, primarily scripting in Python, but can hack stuff together in JavaScript. I pursue art as a hobby, both in physical and digital mediums.
What devices do I have?
I use a MacBook - M1 Pro, 14-inch, and an 11-inch iPad. I use iOS at the moment but will be shifting to Android in Oct ’23. I use WatchOS at the moment but, again, will be shifting to WearOS in Oct ’23. I don’t use external monitors at the moment but am slowly transitioning to one by the end of Sep 2023.
Which softwares am I familiar with?
On a day-to-day basis, I use Spark Mail, Todoist, Texts, MacGPT, BoltAI, Notion, Canva, VS Code, Warp, Shottr, Raycast, Timer RH, Quick Draft, Numi, Rewind, Dato, Day Progress, Cloud Mounter, Flow, Data Jar, Mela, Aiko, Screen Studio, Screenflow, Screenrun, Mela, Bears, Bartender, Spotify, Overleaf, Google Suite, Procreate, Better Touch Tool, Quick Tally, and Habits. I have previously extensively used Arc Browser, Capacities, Tana, Superlist, TickTick, and possibly many others that I can’t think of at the moment.
What I can help with?
If you’re looking for a pro-user who likes to figure things out on her own, you’d most likely benefit from my feedback. I can test usability, onboarding, and the general use case of your app. I don’t approach it from the perspective of testing stated features. I usually go about it like this: I have X and Y use cases, so I’m looking for an app that fulfills them, and I envision using the app to fill a particular gap or replace a current tool.
My Use-Case Driven Scribbles
This is also why I can’t test/do scribbles for apps that I presently have no use case for. You’d be better served by going to someone else who either has that use case or is solely aiming to test features.
Use-case/problem-driven testing and onboarding also imply that I have a certain outcome in mind, and I will mention any hurdles that come with that use case. I make sure to be very specific and define the constraints under which I am testing something—so as to not imply a general perspective. We can talk about whether this matches when you first reach out to me.
Your Feature Driven Scribbles
Another kind of scribble I can do is test what you are asking me to judge an app for, where I set my use case to be the one that your target customer’s looks like. I can do that pretty well, because as an academic researcher, I review as a job, and I am used to seeing things from other people’s perspectives. And I am good at it. I will mention though, in such cases, I am still a proxy case study, and you are seeing through the lens of a customer who is aiming to be what you need, rather than one who is what you need.
Features I index on for long term usage
Multi-platform is important for me. Even though I’ve been Apple ecosystem-only for the past year, I don’t plan to remain in that state. Update Oct 1, 2023: I have moved back to Android, thank goodness. So, most apps that are single-ecosystem-only don’t really work for me, especially for long term usage.
If it’s a productivity app, I heavily index on collaboration. I don’t need team spaces or permissions, but I don’t work in isolation. So anything that is single-person use only is already something I can’t use for a long time.
If it’s a single-person use app, I really need to see an export option. I’m not keen on the idea of app lock-ins; even basic CSV export options are fine for many apps, in my opinion.
Features I test but do not have personal use case for
I don’t care much about E2E, local-first apps, and markdown. Specifically, I have a strong dislike for visible markdown in editors, and yes, as someone who uses VS Code 8 hours a day and codes for 10 hours a day, I still can’t bring myself to like markdown for long-form writing.
I know how LLMs perform
If you have an AI app that isn’t aiming to just be a direct RAG, or text generation, and is instead doing something cool with it, I would love to beta test it. This is not only from the perspective of a direct user, but also from the perspective of someone who has worked in the field of NLP for 9+ years and knows the ins and outs of how LLMs perform at various tasks. I can advise on whether you need guardrails, whether there are edge cases you haven’t considered, and so on.
On my main account, I do provide some consultancy from a research perspective (fine-tuning vs in-context learning, preference learning, generation evaluation, and verification), priced significantly higher (starting at $500/hour). You can contact me if that is something you are looking for, and we can switch over to talking on my main account, if there’s a fit, so that you can be confident of my expertise.
Why I Might Not Be a Good Fit?
Price sensitive
I am very price-sensitive. I live in the US, but a lot of the work I do is collaborative, which involves multiple people using a software. I will 100% judge the utility of the app based on its cost. For reference, my subscription-based apps are: Readwise (for Reader), Spotify, Netflix, Google One, iCloud, ChatGPT (for GPT-4), and Todoist. My particular belief is—I’m okay with paying a one-time cost in the range of $20-$30 for exceptional software, and around $4/month for apps that I use extensively (use for at least an hour a day). Any price over this, or any high margins (yes, looking at you, AI apps), and I will mention the cost—and expect a feature base that is in line with the cost.
Beta apps and new companies
I don’t necessarily judge apps based on how long they’ve been out. This is a common metric that people use to judge apps, but for me, I’m trying to integrate the app into my workflow. Sure, I have some apps that I keep an eye on for the future, but for me, an app is only useful if I can use it now, so I compare it to options available at the moment.
Animation
Animation is a huge sell for iOS apps. I suffer from motion sickness and use the reduce motion setting. Heavy animation bothers me much more than it entices me. However, I do love subtle, cute, or whimsical animations, and I will mention the ones that particularly made me happy!
AI Apps
I believe in the concept of bad GPT API wrappers and good GPT-based apps. I talk a bit about it here, but primarily, if your app calls an API and outputs text based on a template or constructed prompts, it’s very likely that my feedback won’t be overwhelmingly positive. I am still happy to talk about interactions, design, and tools, but if you’re making an AI generator for X, you’ll likely end up with a comment like this from my end.
Testing in isolation
I have and will always compare apps. I know that many indie hackers believe they are building in a market where multiple people can have a piece of the pie, and this is definitely true, for both indie hackers and big tech companies (or else Dropbox would not have existed). But as a consumer, or someone who chooses apps or recommends them, I will look at options and compare them. So it is likely that some of my scribbles will involve comparisons such as – look at X for feature A; I really like their implementation.
This ain’t necessarily marketing material
If you are looking for user-generated content for marketing, or looking for reviews that you can include on your homepage, I am not sure I am the right fit. It is very unlikely that my scribble will be all positive; it will definitely not be hype-y, and it won’t have any special touches that are targeted toward making it viral or quotable.
Onboarding Calls
I do not do onboarding calls. I just can’t do them due to time constraints and how little I get out of them. And I strongly believe that while onboarding calls are great for an average customer, it helps to know what someone experiences when they try to use your app for the first time, alone, with no onboarding sequence. Because I don’t read any onboarding sequence, and I believe I can figure stuff out.
Some people think I’m really good at this, which is honestly why this post exists in the first place. Here are some people talking about it: Daniel, Cyrus Zhang and Vlad.
Ever so often, I come across an app, I think I need, and it works perfectly, but I end up not needing it, at least not in the way I thought I'd need it. The latest app in the list is browser.horse, thanks to PascalPixel who very kindly allowed me to test the app a few months back. Browser horse claims to do one thing: create trail-like navigation structure to your tabs. And it does it perfectly.
On the worst of days, I have had 200+ tabs open in Horse; on the best, at least a 100. And it has been pretty stable for all of it. It is especially useful for situations where I open multiple references from a paper; if I'm deep diving into that paper. It feels like creating a flowchart, but in your browser.
So, why don't I use it? Tree-based navigation is not a new paradigm. SigmaOS and Stack_HQ are two other main browser apps that do something similar. And my lack of usage is not due to the shortcomings of browser horse; but rather due to my browsing pattern.
Anything I browse does not get added to Google browsing history (and I want that - Yes, I know the privacy concerns; and I choose convenience and recall)
Lack of extensions, so I cannot quick add to readwise, copy all links, quick add to todoist etc.
But most importantly, I realized that I'm not really looking for an origin → tree structure when I want tab organization (aka trails).
Unfortunately, I am often deep diving into multiple parent papers/objects with overlapping concepts; and that is where the pattern breaks. I am looking for auto-organize and auto-deduplicate based on set of NLP kind filters.
If paper on A; ≤3 months, group it. I have multiple parent saplings and have overlapping leaves; with weird usage constraints - and if you don't have those; you'll enjoy Horse. It of course has some drawbacks - I wish it had full support for extensions + customizable navigation.
I, personally, won't pay for a browser, but if that is something that you're open to; and you often find yourself dead in 200 tabs, you should see if Horse is up your alley.
I have three reading models: 1. Skimming (50%) 2. Reading for pleasure (40%) 3. Understanding and dissecting a research paper (10%)
Contrary to popular advice, I don't take any notes for the first two categories, which make up 90% of my reading. I simply highlight and quote. Oh, and I also don't do booknotes.
When reading on my Kindle, I sometimes highlight stuff and sync it through Readwise. I don't do podcast notes either, opting for podcast snips instead. For the 3rd model, most of my 'note-taking' is actually replaced by AI extraction. For example, I ask what the Research Questions (RQs) are in a particular paper.
The only notes I do write are focused on processes (how to do something), understanding (what's involved), or questions/ideas related to my work. For me, any other written notes just serve as search pointers. I do write a lot outside of note-taking, like blogs, journals, letters, and academic papers.
I decided in May to start micro-blogging on my own website here. But as I've mentioned before, I'm not a fan of writing in markdown. Quarto offers a combination of md, html, and yaml, which works for me. So, now I write my initial thoughts on Twitter and then clean them up into YAML format.
It serves as a timeline of my thoughts. If you're curious about how I convert Twitter posts to YAML, you can find the output here. The exact prompt to achieve this can be found here.
Anyone has suggestions for in-background AI-based document editing apps? I don't need the app to tell me what to write next. What I'm looking for is an app that suggests changes like, 'This needs rephrasing to connect well to another section,' or 'Merge these two repeated sections.'
I think my gripe with AI writing tools is that they're very generative rather than being aids for editing.
It's like someone misunderstood the term generative AI and decided that content generation is its best use. But writing a 5k+ token paper in my voice? That's a whole other ball game. It would need a strong mix of vectorization, concept mapping, segmentation, and context addition to be truly helpful as an editor.
I understand that achieving this level of sophistication is extremely difficult. Still, I find myself continually trying out these tools, secretly hoping for a miracle because, let's face it, I hate editing.
I have to say, WatchOS completely demolishes WearOS in the range and kinds of apps it provides. I have not been able to find a single useful app (for my purposes) on the WearOS store and that is just disappointing (and making me question my choices).
Something I have always done when I want to draw but I do not have something specific in my mind to draw. This is my favorite mode of relaxation - Alphabet designs.
I've got Twitch playing in the background all day, but is it worth subscribing to?
I "watch" Twitch more than I watch Netflix. Watch being in quotes, because I have it always playing in picture-in-picture mode while I am working, and then watch it for fun at night. And Twitch content is free. I usually do not mind ads but they are irritating. Should I subscribe to it? 😅
Five years (not really!) into the GPT era, and most apps still don't let you add tags using AI. What gives?
I can't believe it's been 5 years (or months) into having GPT APIs and most apps still don't let you add tags using AI. WHY IN THE EVER LOVING FUDGE IS THAT? They let you add summaries and even do QA, but tags? NOPE, they don't do them here. NotionHQ, readwise, can we get some AI tags please?
Sure, tana_inc does it, but that's beside the point. obsdmd does too through plain text, and I assume some AI plugin + templater combo, but I'm not looking for UI on top of plain text filters right now.
Let me tag my content with AI, it's one of the things it's good at!Gmail, listen up. I don't really want to write an email with AI; I want to add labels to the email with AI.
Why is there a shortage of integrating AI into our existing interfaces?
The Struggles of Finding the Perfect Document Writing App
I have a feeling, I'm going to keep using NotionHQ and still be disappointed by it, forever. It's a very similar experience with LaTeX and Overleaf.What is it about software made for document writing and note-taking that makes moving from an incumbent so hard? A significant factor is definitely collaboration. That's why I believe non-collaborative PKMs won't overtake any non-collaborative software. But what else is there?
Why Notion and Apple Notes Serve Different Purposes
Unpopular opinion: If you say Notion is overrated, I use Apple Notes, it is not Notion which is the problem, it is you. This is very much like saying trains are overrated, I use cars. Yes, they are in the same general vicinity, but they are not for the same purposes. If you want local first, write in plain text, why would you use Notion.
You can try Bear or Reflect or Craft, but Notion is like fitting a square peg solution in a round hole of needs. Upgrading to a premium round peg is the solution. If you're looking to level up your Apple notes experience, but do not collaborate, try Bear or maybe Reflect. Or for collaboration, Google Docs or Google Keep or Craft for that matter. Choose an app that does 10% over the app you are using. Try electric cars not trains.
All arguments about Notion vs Apple Notes to me sound like I was using a round peg that fits into a round hole. Instead of upgrading my round peg, I decided to use a square peg that kinda fits into a round hole. So, square pegs are overrated. Chat, it is that you don't have a square shaped hole in your life 🤷🏽♀️
Date me docs are what happens when you learn no dating skills from your Asian parents, are surrounded by the idea of arranged marriage forever, and are asked to write SAT essays instead of going on dates—ending up being 27, without a partner. Disclaimer: I'm SE Asian, and if I wasn't lucky enough to be in a relationship with my partner for the past 5 years; or if we are ever not together, I am 100% sharing a date me doc.
I need to have a Readwise Reader cleanup day and unsubscribe to all newsletters that I have subscribed to. I realized I do not really like newsletters written as newsletters. I like long detailed essays, and newsletters aren't that. In general, I am not gonna subscribe to scheduled newsletters anymore. Scheduled letters make it so that someone needs to have something to say; and I kinda don't enjoy that content.
I wanted to explore simple WYSIWYG website editors (markdown acceptable) for personal academic sites, focusing on those that can be hosted on GitHub Pages for free, or that you at least own your content. Here's what I discovered:
Montaigne is quick with Apple Notes and creates a presentable site.
Something just clicked for me right now, at midnight. I can't think and type at the same time. I can think and blabber out loud, even though blabbering is slower, but it's not enough of a difference to hamper my thinking.
This is similar to how reading out loud makes some people struggle with processing what they are reading, especially when their internal reading doesn't match the speed of their speaking abilities. But blabbering produces a huge wall of text, just like this one!
It gets even worse when I am reading a paper, for example, and I have thoughts about parts of it. In an ideal world, I will circle them, scribble around for context, but cropping them and typing out my comment 100% breaks the flow for me. And I can't read and process huge walls of text.
This is what I've been struggling with. This is why I scribble down stuff because scribbling down with colors and pens in a spatial manner makes it way easier for me to relate stuff, to pick bits and pieces that would go together. It makes it easy for me to read and process it once I am done with the dumping. Unfortunately, it makes it harder for me later organize it, search it, or document it.
I don't know how I had this realization, but it is so fascinating and disappointing at the same time.
Something just clicked for me right now, at midnight. I cannot think and type at the same time. I can think and blabber out loud, even though blabbering is slower, but it is not enough of a difference to hamper my thinking. This is similar to how reading out loud makes some people struggle with processing what they are reading, especially when their internal reading does not match the speed of their speaking abilities.
But blabbering produces a huge damn wall of text, just like this one! It gets even worse when I am reading a paper for example, and I have thoughts about parts of it. In an ideal world, I will circle them, scribble around for context, but cropping them and typing out my comment 100% breaks the flow for me. And I cannot read and process huge walls of text. This one is fine, because I am just posting it and never need to come back to it again.
But for long term stuff, I cannot do this!
This is what I have been struggling with. This is why I scribble down stuff because scribbling down with colors and pens in a spatial manner makes it way easier for me to relate stuff, to pick bits and pieces that would go together. It makes it easy for me to read and process it once I am done with the dumping. Unfortunately it makes it harder for me later organize it, search it or document it. I don't know how I had this realization, but it is so fascinating (I figured a piece of the puzzle!) and disappointing (no off the shelf note-taking system will match my needs) at the same time.
I really cannot do note-taking apps that aren't true WYSIWYG
What I specifically mean is, Obsidian has a live preview option that allows me to see everything else rendered other than the line I am editing. And that bothers me. Seeing markdown makes me feel like I am writing a paper in LaTeX and I do not necessarily enjoy that.
And note-taking needs to be an enjoyable process for me. It also needs to not have wild context shifts, messy layouts, having all metadata stored and cluttering the text.
I can't even have a differently colored highlight without it showing up in text. And that is just not gonna work.
I am one of those people who cannot handle multiple apps that do the same thing. Whether they are for personal or professional use, if both apps are about tasks, then it's really tough for me to use both.
However, I've realized that I can manage multiple apps if they serve very specific, compartmentalized purposes. For example, I want my journal and personal databases to be interconnected, but do I really need my shopping list to be linked up? It serves no purpose being in my PKM app.
Take bookmarks, for instance. It's quite a hassle to have bookmarks in Notion, and I'm considering alternatives like Chrome bookmarks or Raindrop. So, in short, it's all about compartmentalizing your digital life to make things less complicated.
Based on my experience with Notion, I have quickly realized, I do not really like how Notion handles pages. But I am waiting to switch.
There's obviously a hint of a struggle within me; I really like Notion and don't want to leave. My hesitation mainly comes from a keen interest in how different apps are going to integrate AI. To me, the low-hanging fruit of integration, like synthesis during content creation, is incredibly dull. I absolutely don't care about it. What I'm truly waiting for is to see how these apps will handle the transformation of unstructured content into structured forms and semantic searches.
Personal Knowledge Management (PKM) involves capturing, processing, and taking actions on information. App primitives and content primitives are the building blocks of PKM systems. Integrations and snippets are two types of capture methods, with integrations pulling data from other apps and snippets being direct input. Processing can be explicit (manual organization) or implicit (background processing by the PKM). Actions, such as creating and consuming content, are how users interact with the PKM. Rendering structures the information for easy access, while modification allows for changes and updates. AI can enhance PKM by providing context, recognition and automation to improve the efficiency and effectiveness of the PKM system.
Personal Knowledge management has remained a subject of discussion since ages. One of the most common way people manage their personal knowledge is spread across email, drive and notes apps. Most people end up using google docs, apple notes, or google keep as a starter. If they want to do something more complicated, they end up with something like Evernote or OneNote. Add another level of complexity and you end up with something similar to Notion, Tana etc. No matter the app chosen and the management philosophy, it all boils down to primitives, capture, processing and actions. So this post is a generalized discussion of these components. I also talk about how AI can contribute or interact with these components in short and long term.
Understanding the PKM Primitives
Primitives in my viewpoint are the basic building blocks of any system. In this case we are talking about personal knowledge management systems. Building blocks usually have 2 characteristics: they can be combined to build more complex systems; and they can be used to define the possibilities or infrastructure of any system.
App Primitives
App primitives, in my viewpoint are the capabilities or possibilities of a system. These are often implemented on a backend level and not on a content level. It does not matter what the content is, app primitives are most content-agnostic. Some common examples of these are share, collaboration. permissions, history etc. These apply to any content that is present in the app. This is also an implementation that many people are divisive over. For example files over app is a philosophy that is still relevant to app primitives, just that the storage is plaintext or markdown files.
Content Primitives
Content primitives are smaller building blocks that can combine together to create better or more complex systems. Content primitives define a set of actions associated to those “blocks” as well. the content primitives defined above should cover most cases, but one of the best abilities that an app can provide is creating new content primitives specific to the user, such as Notion or Tana. One of the missing aspect for all of these content primitives is default actions and default recognition. For example, task is a common content primitive in many apps — the idea there being that it has at least two states of not done vs done. Or a timestamp has a “change date” action. But there is a dire dearth of content primitives recognized by apps.
The Art of Capture
Capture is a largely unsolved problem. Every time a new capture app comes into picture; there is a reason why everyone is so excited. Capture has many problems to overcome → it has to be quick, it should be in context, it has to work offline, it has to work with multiple modes. Capture can be classified into two categories →capture from integrations or basically data input into a different app that is then pulled into your personal knowledge management system. Snippets on the other hand are content that is meant to be as a direct input into the knowledge system.
Integrations
Integrations refer to the general idea of having different apps that are specialized; to be later fed into your personal knowledge management system There can be multiple integrations that people have — the most common ones being chat and email. No one expects to be talking through their personal knowledge system (unless you are Google Chat and Spaces), but having integrations ensures that you still get the best experience for the particular component.
Integrations tie into various aspects of content primitives. For example, an email integration ideally integrates with person, text and files primitives. The special aspect of any great app is this idea of integration being automated through LLMs or AI; converting this unstructured data input into structured tags or relations with the baseline or user defined content primitives.
Snippets
Snippets are direct input of data. These can either be standalone snippets — for example that note you create about your partner, or can be positioned snippets. Positioned snippets tie back into the content primitives, that is, you have a particular kind of content, their position.
There are certain aspects of snippets that are best helped by AI. Consider standalone snippets. OCR, transcription, object recognition are some basic actions that can be performed on these snippets to improve the utility of a PKM. For positioned snippets, something that extracts context and embeds the context within the the captured input is possibly the north star of utility for a PKM app. You want a house in a meme vs house on a street show up in the same context during search or filtering.
Unlocking Knowledge Through Processing
Explicit Processing
Explicit processing refers to the idea of a person manually organizing the content in their PKM in a way that best makes sense. There are multiple ways this kind of processing is facilitated; for example, in the very basic apps, tags and folders are the most prominent methods of explicit processing. Bear and obsidian support hierarchical tags and that improves the kind of organization an app can provide. More complex apps provide linking between “snippets”, rolling up associated information, table of contents as possible processing of the input content.
Implicit Processing
Implicit processing on the other hand is something that a PKM performs in the background. Most apps that exist at the moment have none or barely existent implicit processing. Let’s try an analogy to understand what the very basic ideas of implicit processing is. — “Oh you open google keep everytime you come back from this location” or “It seems like you are not home. Should I turn off all lights” is a self inferred rule by an operating system or through a smart home system. For PKM, this can look like — oh, we create this scrollable mini map on the side that extracts the main theme/idea from each paragraph. Implicit processing is what makes any app seem seamless. While you can go along a fair bit with handcrafted rules, the best implicit processors are guided structuring processes that align with a user’s desired behavior and still run in the background.
Actions - The Grand Finale
Actions, as I speak of, are interactions that can occur with or on the data that is stored in a PKM. Actions define how you use the system that you are engaging with. Actions can occur not only on a singular snippet but on a combination of them. Actions and processing are also the key differentiators between choosing different applications.
Create
Creating is one of the primary actions that everyone here probably identifies with. Whether you are a content creator or not, creation of notes, long form content, sharing a set of snippets in a post, a listicle, creating easily glanceable information, creating a website, creating a repository, all of forms of creation actions. Creating directly feeds back into processing, given how it is at direct user-level interaction. It is an active action (funny, how active should usually imply actions anyway).
Consume
Consumption is usually the other side of coin for primary actions. Consumption though is a passive action. This hence, does not usually correlate with explicit processing. Learning the behavior of consumption though is a huge deal of processing — for example, if you look up “house”, what do you want to come up in search. This is mostly a form of data log modelling, and beyond the scope of this post.
Structured
Structured consumption in my perspective refers to a retrieval based action. This can be structured in either its attributes that can be used to filter or correlate, to search or bring up related to information or snippets, plot information on a graph for two related variables etc. I use the word structure specifically to refer to something that has no creation of information. The information all exists and is surfaced as needed.
Unstructured
Unstructured consumption in my opinion is when the app does some kind of synthesis of information. This can be as basic as synthesizing the summary of the snippet (as Notion AI and a million other PKM apps do), can be summarizing or conversing with the content stored in the PKM (such as mem) or can be more integrated into the creation process (such as expand, augment this content). While this is the lowest hanging fruit form of integration, getting a great unstructured integration using AI requires more than just dumping embeddings into a prompt interface, something that many of the current apps do not seem to onboard for.
Render
Any personal knowledge management system is a dynamic ecosystem of practical and user-friendly structures. Within this system, you will usually find galleries, boards, timelines, groups (for example in Notion), and flashcards (for example in Anki). These structures serve as valuable resources, containing carefully curated knowledge that is easily accessible and interactive. Galleries act as personalized exhibition spaces, housing a collection of images, diagrams, and multimedia content that are visually appealing and intellectually stimulating. Boards function as brainstorming hubs, enabling you to organize various pieces of information, such as cards, notes, or snippets, in a way that mirrors the natural process of pinning ideas on a bulletin board.
Modify
Modification actions are essentially changes applied to your content - it could be as simple as editing a typo or deleting an unnecessary file. They could also be higher-level actions, such as marking a task complete or archiving an old document. Modification also has a deep integration with implicit processing, which is what makes processing so difficult to execute correctly. If you add a name to your meeting note attendees, it should record the new information, but now that changes related notes and unstructured consumption of information about this person.
I've been using Notion primarily for long-form documents that have properties. So, for anything that requires extended content, Notion works fine for me.
However, when it comes to tasks, journalling, or lists, I find myself unintentionally avoiding the platform. I really dislike using Notion for tasks. Writing a 'done' list feels like a chore, and linking pages feels pointless because that information just seems to disappear.
I do not give verdicts, the above description is pretty detailed, go make your own decision! For me, I am personally leaning towards Texts at the moment; but it does lack some basic things I need.
Introduction
When @Beth messaged me that she has invites for Texts; I was excited.
Texts invitation from Beth Jun 24, 2023
I was equally excited about getting off Beeper’s waitlist a little over a month ago.
Invitation from Beeper May 18, 2023
Why did I even want to try Texts?
Well, if you are reading this, you know this is my alt account. While I do not have multiple apps that I run alt accounts for, Twitter is definitely one of them. And Beeper at the moment does not support multiple accounts for the same app.
As I have said before, I am very much a person who values utility over design; and especially would prefer free software that still works over paid beautiful ones (that also work, mind you).
What about Texts not having a mobile app?
While I do have Beeper installed on my phone, Texts not having a mobile app is not a deal breaker for me. This is specifically because the I do have per app notifications for personal messaging turned on. Hence, the benefit of Beeper comes to be in the extras — which I will talk about later, but in this case, is just scheduling; which I do use if need be.
Pricing
Texts pricing is a concern for me, though thankfully their education pricing is okay by me — which at the moment is $5/mo. Beeper at the moment is free, though comes with a very very extensive waitlist. They do propose to have paid plans in the future, and if it is anywhere in the range of $5/mo, I will pay for it.
tl;dr — I think $5/mo for messaging aggregator app is where I place the value to be.
Quick look
In case all you want is a quick look at what they can do or not do, the two graphics below almost cover everything. You can also go through each section/sub-section and look for checkmarks (has this option), meh (kinda), cross (does not have this option), plus (additional benefits) and minus (poor implementation).
Texts UI out of box
Beeper UI out of box
Design Comparison
Let’s get this out of the way. I know many people care about this and I do want to touch on it, because that is one of the major out-of-box difference between Texts and Beeper.
Design decisions of course affect the UI/UX of individual actions and I will discuss them while I discuss the functionality. This section is solely focussed on the looks, and not the UX of the two apps.
Basic Looks
Texts feels like a “beautiful app” out of box. On the other hand Beeper feels like a hacker version of Texts. Now, none of this is to say, one is more or less stable than the other. Of course you can modify CSS in Beeper but no immediate changes makes it look comprehensive.
There are of course differences in the setup page, but I do not care about it. You need to setup things ONCE. And yes, texts looks much better in that regard. Reduced number of popups, consistent design etc. But again, you will access it only once.
Texts UI out of box looks way cleaner than Beeper’s
What bothers me?
I am not the kind of person to care about Apple-esque design. If you are, then Beeper isn’t for you. That being said, there are still multiple things that bother me in Beeper’s design.
The message preview in the sidebar can be one or two lines making it feel inconsistent
The use of You on left and ticks/double-ticks misaligns the whole preview and very much prefers Texts implementation here. Even when the words (You) do not exist, the text in some cases does not align to the contact’s name. Presence of tick/double-tick signifies that it was sent by you anyway.
Chat settings open in a new modal on top of everything? Why?
Schedule send has an orange colored huge button, why?
There is no visual gradient or information in the sidebar list to identify which chat is opened at the moment.
Some actions such as search inside a chat or favorite a chat are hidden in the top menu bar of Beeper. That to me makes no sense!
Click on the gif to see the menu bar actions and search UX
App indicator on the right has an outlined mode and I don’t think I like it though it is easily fixable through CSS.
Low priority and archive should be up there with categories but it isn’t. There isn’t a need for a floating “Open Archives” button.
The sidebar opens with a stack icon which still confuses me. I would prefer a standardized icon like a hamburger menu.
Group chats show who sent the last message which is awesome but also shows their contact picture and it just makes a mess.
Functionality Comparison
Supported Apps
Beeper: ☑️ ➕
Texts: ☑️ ➖
Beeper supports more apps: specifically Google Chat, Instagram and android SMS are notably missing from Texts. This is important to me, because I do plan to shift to Android, and I would want my sms to be available on Mac. Also important because, while iMessage does already have a desktop sync component, google messages does not have one on Mac, and Beeper does shine there.
Google Chat is important for me as someone in education, where the university, my lab and colleagues; all use chat often for varying purposes.
Instagram has been constantly buggy; even for Beeper, but not longer than 2 days (the last time it happened, it was buggy for 12-ish hours). I believe that is due Meta’s API issues, but Beeper has fixed connections faster than Texts. I can assume that because while Instagram is back on Beeper, it is still missing from Texts.
:::{.column-margin}
Update speed in Beeper
:::
iMessages
Beeper: ☑️
Texts: ☑️ ➖
Texts needs an extra helper for keeping the messages synced. I believe that is because they are extracting or intercepting rather than syncing through an extra server as Beeper is. That allows Beeper. That is one of the main reasons why you can have iMessage on Android/Windows in Beeper, and is unlikely to come anytime soon to Texts. Personally, when I switch back to Android, I do not need iMessage and I will not care about this. Google messages on the other hand allows for a QR code based login (similar to whatsapp) — and I am disappointed that does not exist on Texts.
Speed and Updates
Beeper: ☑️ ➖
Texts: 😶
While Beeper in usage is snappier (Texts feels slower to me) — Beeper’s startup time from being closed as an app to starting up is atrocious. Thankfully I never shut down my laptop and my messaging app is never closed, so I do not really care about this. The only caveat to this being — Beeper literally sends out new updates every single day (sometimes twice a day); and once it does update, you have to put up with the slow startup speed (takes a minute or two).
Actions
Archive
Beeper: ☑️ ➖
Texts: ☑️
While both Beeper and Texts allow for archiving a conversation, only Texts allows for syncing state of the archive with the native platform. Texts also has an option of “Move to Other” and “Hide” which kind of do the same thing — and I am not really sure if they need to exist. But more options for organizing the cluttered inbox has never hurt anyone!
:::{.column-margin}
:::
Edit Messages
Beeper: ☑️ ➖
Texts: ☑️
Both Beeper and Texts support editing a text message on platforms that do support it. Unfortunately because Beeper does not have a platform specific context menu, sometimes you might try editing a message and end up with an error.
Mark as Unread
Beeper: ☑️
Texts: ☑️
Both Beeper and Texts have an option to mark a conversation as unread, neither of which syncs back to the native platform. Texts has an add on feature here called “Stealth mode” that lets you keep a conversation remain marked as unread even if you click on it and read it; unless you reply to them.
:::{.column-margin}
:::
Delete Conversation
Beeper:✖️
Texts: ☑️
Texts provides a feature to delete a complete conversation. This is especially useful for spam twitter conversations. While Beeper does put them automatically in to the low priority inbox (invites box for Texts) — you cannot delete the conversation/thread and I do not like that. Delete thread/conversation entirely does not seem to sync state with the original app and that is a bummer.
Select multiple messages
Beeper: ✖️
Texts: ☑️
Texts allows you to select multiple messages using Shift + ↑ which you can then use to copy, remind, forward or delete (not shown here because these are tips inbox) and that is amazing. I have yet to find this functionality in Beeper, and quite possibly it does not exist.
:::{.column-margin}
:::
Delete Message
Beeper: ☑️
Texts: ☑️ ➕
Both Beeper and Texts supports deleting a message, but only texts separately shows Delete for everyone vs Delete for me in Whatsapp, which is not important to me, but an attention to detail I can appreciate. These deletes are synced with the server (so you do see a similar state when you open the corresponding app)
Forward Message
Beeper: ☑️
Texts: ☑️ ➕
Both Texts and Beeper allow you to forward a message. Both allow you to forward a message to multiple users. While Texts does this as a popup modal, Beeper does this in sidebar for some reason; and I prefer Texts implementation here.
Reply to Message
Beeper: ☑️ ➕
Texts: ☑️
Both Beeper and Texts supports replying to a message and the, so that is awesome! Beeper adds an extra option to view thread when there are a series of replies in a thread.
Open Conversation in Corresponding App
Beeper: ✖️
Texts: ☑️ ➕
While most people might not find a use case for this; I would have heavily used this feature in 2 days. Whatsapp does not allow for calling through third party clients. Being able to open a conversation in Whatsapp to call my mom when she has called me 80 times; always helps. Trust me; it saves her a minute of panic attacks. But texts seems to be able to open webapps but not on desktop apps?
Texts also has a chrome extension that allows you to open a chat on twitter or other web platforms back in the texts app — and that is another one I appreciate.
Content
Search
Beeper: ☑️
Texts: ☑️
While both apps allow for searching across all chats and across a single chat; I prefer the design choice of Texts. Additionally, search in Texts highlights the word in a message whereas in beeper it just shows relevant messages.
Media
Beeper: ☑️
Texts: ☑️ ➖
In Texts, the files and media section does not load media (or messages for that matter) before install date unless and until you scroll up the message viewport to an earlier date. I wish there was a setting to specify the earliest date you want to load messages from, but that does not seem to be the case.
Call and Video
Beeper: ❓
Texts: ✖️
Beeper seems to support call and video for certain channels. It is not apparent to me, which ones those are — given I do not use Slack and Discord. It does not seem to support Messenger or Whatsapp for calling. Texts does not provide any notifications for incoming calls and that is a huge bummer.
Notifications
Beeper: ☑️ ➕
Texts: ☑️
Both Beeper and Texts have an app level notification setting and a conversation level notification setting. Beeper also has other kinds of notification settings, which I assume are really powerful for slack and discord — both of which I do not use at the moment.
:::{.column-margin}
:::
Add On Features
Send Later
Beeper: ☑️ Schedule and dismiss
Texts: ☑️ ➖ Schedule when app is active, dismiss and reschedule
Send later in Texts relies on the desktop being powered on and the app running. I believe they store it as an action on device. While on Beeper, you can instead send messages later because those actions are stored on their server.
Due to this difference in system, while send later is more convenient on Beeper, it also has a poor UX. When you schedule a message in Beeper, it disappears back into the button. You also cannot schedule two messages to the same person, which in my opinion is fine. On Texts, it shows up inline and feels way more native to the app itself. You can also not reschedule a message in Beeper (fine by me, I will just copy paste); while you can do so in Texts.
:::{.column-margin}
Schedule send UI in Beeper and how the scheduled send ends up looking like inside the Beeper interface
Send Later UI in Texts and how the scheduled send ends up looking like inside the Beeper interface
:::
View Thread
Beeper: ☑️
Texts: ✖️
Beeper allows you to show thread in a consecutive set of replies. Honestly, that has been a life-saver for me. As someone who often tends to have multiple conversations in parallel with the same person, show thread allows me to skim over the conversation way quicker.
:::{.column-margin}
:::
Auto-copy
Beeper: ☑️
Texts: ☑️
One of the best features of Beeper and Texts is: it auto copies the two factor auth code onto your clipboard to be pasted into the textboxes on the website. Yes, exactly that meme for iPhone but built by Beeper and Texts.
Auto-Archive
Beeper: ☑️
Texts: ☑️
Both Beeper and Texts have an option to auto-archive. Texts works in days by default, and Beeper in minutes. Both end up having the same functionality.
Remind Later
Beeper: ✖️
Texts: ☑️ Single or multiple messages, auto-archive on snooze
This is completely missing from Beeper unfortunately, and given they are better poised to be doing this (because they have their own servers) — I am disappointed. Remind later shows the same UI as scheduled send, just that in this case: you receive notification again on the set time. You can choose to archive threads that you have set a remind for in general settings. It is very similar to how snooze works in Spark Email.
OpenAI integration
Beeper: ☑️ ➖
Texts: ☑️
Texts has an option to draft replies and to transcribe voice messages through OpenAI. This is a part of their premium plan. I don’t know if I am very excited about that, but one potential usecase might be using the transcription to send to a note-taking app such as Capacities which does have integration with Telegram/Whatsapp.
Snippets
Beeper: ✖️
Texts: ☑️
Custom snippets or what Texts calls as Quick Replies allows you to create custom templated responses. I will be honest, I do not work in any customer oriented role and I cannot think of any use case for these, though I believe, it might be useful for other people.
:::{.column-margin}
Creating quick replies in Texts
:::
Undo Send
Beeper: ✖️
Texts: ☑️ Specify in seconds 0-10
Texts allows for undo send using Shift + Cmd + Z. This is very much equivalent to undo send in gmail and is awesome. The amount of typo filled messages I write when I am half asleep serves me no good.
Labels
Beeper: ✖️
Texts: ☑️
Texts supports labels, one of the major organization perks. You can have default labels assigned to a conversation, to an app, and assign labels of your own. Then you can filter or search using these labels through the hamburger menu on the top left.
Keyboard Shortcuts
Beeper: ☑️ ➖ Navigation
Texts: ☑️ ➕ Navigation and Actions
Beeper, in my experience does not support action based keyboard shortcuts and rather just navigation based shortcuts. So, you have a shortcut for search, a shortcut to move between apps, and between categories, but that is it. Texts on the other hand aims to be completely accessible through keyboard, supporting both navigation (moving through the app) and action (reply, reminders, copy notification, schedule a message, delete etc) through keyboard.
One of the best keyboard shortcuts is Focus App (that allows you to bring Texts to the front) instead of opening it through Raycast, and is something I have extensively used in the past 2 days.
The main concern is that current databases cannot be ported over to this new feature. And I am kinda okay with it?
I think it's a new user onboarding feature. It's kinda similar to buying templates from people, just that this one is made by Notion and has some features inbuilt. This feature isn't really meant for people who customize their systems heavily or for those who already have established systems.
One way to approach this is by asking myself: Do I want this company to make money and stay alive? If the answer is yes, I just make peace with it. On the other hand, if I don't like the direction things are taking, I look for an alternative.
It's worth noting that if companies don't focus on making money, they often end up shutting down. And here I am, working in research and choosing to make less money in return for increased, well, let's call it misery.
Muse recently launched their Muse for teams and I have two wishes. I wish they had an option of publicly sharing the board, and that other spatial canvases adopted how well they implement the spatial and handwriting aspect of it.
I have been sharing tips and tricks and templates over the course of past year on Twitter, but now the links are gonna change.
Why? Because I wanted to change the workspace where my Notion templates were hosted. And I wanted to change my homepage. Most URL shorteners do not allow you to edit the destination URL for free. So, I found a great repo on GitHub built by younho_9.
And now, I can edit the URL in my Notion database and still shorten all the URLs. That is awesome!
Make a Notion setup for a LaTex resume using Python and Replit. The template has two databases: resume info and structure. It has pre-filled buttons for easy info input and a modification block for changes. The resume has inline annotations and href for Notion links, no empty sections, and sorted entries.
Well, the notion template comes with a stylized file for latex. You can use it in your own system, or upload the zip to overleaf.
It is based off a very popular template by Deedy: modified to be rendered as a single column resume.
Template folder
See that resume_generated.tex file?
That is the one we would be generating anytime we need to update our resume.
Setup
Oops! I got ahead of myself.
Notion Setup
Let’s first talk about the template itself.
It consists of 2 databases:
The resume information (MEOW| Resume Database)
The resume structure (MEOW| Resume Database Formatting)
What do they do?
MEOW| Resume Database Formatting Database tells you which sections there are.
It has just 7 properties: - Name (name of the section) - Sort By (time end or start) - Sort Direction (ascending or descending) - Rank (Order in which you want sections) - Item Format (Individual item formats) - Section Format (Start and end of section formatting) - MEOW| Resume Database (Related resume items/entries)
Items are placed inside section format using NP:Items.
Item Properties inside the format are referred by NP:Property_Name
How Items and Property Names are used for formatting
MEOW| Resume Database includes each item. It only has 9 properties.
Name
URL
People
Organization
Time (Can be date, year, range)
Comments
Status
Geographic Location
MEOW| Resume Database Formatting (Section the item belongs to)
Properties in the MEOW| Resume Database
Any of the text properties in the free text can have linked urls, bold or italics and it is rendered equivalently in LaTeX.
Add One-Time Information
Adding information is super-duper easy.
The template comes with buttons for one time addition of basics.
One time addition buttons
It also has an add new information callout with pre-added buttons that you can just add as a synced block to your dashboard that you open everyday.
The buttons come with a ton of pre-filled information and pointers of things you need to add/replace to make it easy.
New information addition buttons
Pre-filled information in new information addition buttons
Example of newly add information using addition buttons
Make Modifications
The template also includes a modification block.
A submitted paper got accepted? A submitted grant got accepted? A student graduated?
Handle it through the modify callout. Remember to add this as a synced block to your workspace too.
Example of modify callout
Replit Setup
Then you go to the repl.
Fill in the value for these secrets. (Remember to share your template page in your workspace with the notion integration key)
Secret environment variables in replit
Generate
And click run!
And voila. It generates the resume_generated.tex
It automatically takes care of a few things - Adds inline annotations to match notion - Adds href when there are links in notion - Does not add empty sections - Sorts entries in a section.
Your browser does not support the video tag.
Generation example video
If you end up using the resume generator, please consider buying me a coffee here:
I absolutely despise the tedious task of manually tagging everything in a Bookmark Manager. It’s just an extra step that my ADHD mind struggles to keep up with. I mean, seriously, who has the time and patience for that?!
As a result, my bookmark inbox ends up becoming a chaotic mess with over 200 entries. It’s overwhelming and counterproductive, to say the least. But fear not, my fellow overwhelmed minds, there’s a solution on the horizon - automation!
With the power of automation, we can streamline our bookmark management process and save ourselves from the overwhelming task of manual tagging. It’s like having a personal assistant that takes care of all the grunt work for us. And let me tell you, it’s a game-changer.
How?
So, you might be wondering, what exactly does this automation do to streamline our bookmark management woes? Let me break it down for you:
Example of self-organizing bookmarks working
Extracts content of the page
Firstly, it extracts the content of the page you’re bookmarking. It ensures that the content is fully loaded and not hindered by any pesky JavaScript issues. This way, you can rest assured that you’re capturing the complete essence of the page.
Takes screenshot of the page using Screenia by @s_badaoui
Next, it takes a screenshot of the page using the nifty tool called Screenia by the talented @s_badaoui. This comes in handy when the automation couldn’t fetch the content of the page earlier. In such cases, it cleverly applies OCR (Optical Character Recognition) on the captured image to extract the necessary information.
Call OpenAI API
But wait, there’s more! The automation then calls the OpenAI API, which works its magic and provides a JSON file containing a summary of the page, along with relevant tags and categories. These tags and categories are selected from a pre-defined list, ensuring that your bookmarks are organized and easily searchable.
Add screenshot to dropbox
Now, here comes the cool part. The automation adds the screenshot of the page to Dropbox, making it easily accessible and shareable. It then retrieves the URL of the uploaded image, so you can conveniently view and reference it whenever needed.
Parses the information
Last but not least, the automation parses all the gathered information and seamlessly adds it to Notion. Notion acts as your centralized hub for bookmark management, allowing you to effortlessly categorize, search, and access your bookmarks with just a few clicks.
Run this on your own
If you want to run this on your own, you need:
iOS/MacOS (because this is dependent on shortcuts app)
Free Actions app by @sindresorhus [On both MacOS and iOS]
Connect your dropbox account in shortcuts (should hopefully prompt itself)
However, like with any automation, there are a few issues to be aware of. Let’s take a look at them:
One downside is that this automation may take some time to run. So, if you’re using it on iOS and your macOS device is in the background, you might see a spinner indicating that the process is still ongoing. Just keep in mind that it’s best not to add two bookmarks within a minute to avoid any potential conflicts.
Another issue to be mindful of is the rate limit of Screenia. While it’s a fantastic tool for taking screenshots, it does have its limitations. If you’re adding bookmarks at a rapid pace, you might run into the rate limit of Screenia. In such cases, you may need to wait for a while before the automation can successfully capture the screenshots.
Ah, the dreaded iOS permissions. Unfortunately, iOS has a tendency to bombard us with permission requests, and this automation is no exception. Each time you run it, you may have to deal with those annoying permission prompts. It can be quite frustrating, but don’t worry, you’re not alone in this struggle.
Despite these minor hiccups, the benefits of streamlining your bookmark management with Notion and AI far outweigh the inconveniences.
And that concludes my TED Talk on "Just Pick Your Tool, Keep Evaluating It." Remember, choosing the right note-taking app is all about finding the one that best meets your requirements and preferences. Once you've made your choice, continue evaluating it regularly to ensure it continues to serve your needs effectively.
By refining your requirements, assigning tool scores, and conducting checks on accessibility, stability, and usability, you can make an informed decision on which app to use. Don't be swayed by shiny new tools that may not fulfill your actual needs. Stay true to what works best for you and your note-taking process.
So, go ahead, pick your tool, and embark on a journey of continuous evaluation and improvement. Happy note-taking!
Introduction
Choosing the right note-taking or task management tool can be overwhelming with so many options available. But fear not, I'm here to help you navigate through the decision-making process. Once you've made your choice, I'll also guide you on what to do next. So let's dive in!
And don't forget to check out the flowchart below for a quick summary of the steps to choose your perfect note-taking or task management app!
Choose an App
Now that we have that out of the way, let's dive into how I would suggest choosing a note-taking app. I'll provide examples throughout to illustrate my points.
Define the scope
When selecting an app, it's important to first determine the scope of your needs. For instance, you might be looking for an app that not only allows you to take notes but also helps you manage tasks and schedule your activities. By identifying the specific functionalities you require, you can narrow down your options and find an app that meets your needs comprehensively.
To illustrate this, let's consider an example. Imagine you're a busy professional who needs a note-taking app that seamlessly integrates with your task management and scheduling. In this case, you would want an app that allows you to jot down important notes, create to-do lists, and even set reminders for upcoming deadlines or meetings. By defining the scope of your requirements, you can focus your search on apps that offer these combined features.
Define the quantity.
Now that you have identified the scope of your needs, it's important to be realistic about the quantity of apps you are willing to use within that scope. While it may be tempting to search for an all-in-one app that covers every aspect you desire, it's crucial to understand that such a comprehensive solution may not exist or may not meet all your requirements effectively.
To put things into perspective, let's consider an example. Imagine you are a creative individual who wants to have a note-taking app for jotting down ideas, a task management app for organizing your project deadlines, a separate app for scheduling your appointments, and another app for mood boarding. In this case, it would be unrealistic to expect a single app to fulfill all these diverse needs efficiently.
By being realistic and understanding that you may need to use multiple apps to cover all the functionalities you require, you can make a more informed decision and find the best combination of apps that work seamlessly together. Remember, it's about finding the right tools that enhance your productivity and meet your specific needs, even if it means using different apps for different purposes.
Requirements
Now you need to define the requirements for the app category you are exploring.
Once you have a clear understanding of the app category you are interested in, it's essential to define the specific requirements you have for that category. These requirements will serve as your criteria for evaluating and selecting the right app for your needs.
Let's take the example of a note-taking app. Here are a few possible requirements you might have:
No visible markdown ever: If you prefer a more intuitive and user-friendly interface, you may want to avoid apps that rely heavily on markdown formatting. Look for apps that allow you to focus on your content without the distractions of visible markdown codes.
Cloud sync native to the app: For seamless access to your notes across multiple devices, it's important to choose an app that offers built-in cloud synchronization. This ensures that your notes are automatically updated and accessible from anywhere, anytime.
Web app and mobile apps: If you work on both your computer and mobile devices, you may require an app that offers a web version along with dedicated mobile apps. This allows you to access and edit your notes conveniently, regardless of the device you are using.
Offline functionality: Consider whether you need an app that allows you to access and edit your notes even when you don't have an internet connection. Offline functionality can be crucial for those moments when you're in transit or in areas with limited connectivity.
By clearly defining your requirements, you can easily filter through the available options and focus on apps that meet your specific needs. Remember, everyone's requirements may vary, so it's important to prioritize what matters most to you in an app.
Prioritize and annotate your requirements.
To make an informed decision when choosing an app, it's important to prioritize your requirements based on their importance to you. This will help you determine which features are essential, which ones you want, and which ones are nice to have. Additionally, creating a tool score column will enable you to compare and evaluate different apps based on these priorities.
Let's continue with the note-taking app example and prioritize the requirements:
a. Need (1): No visible markdown ever - This requirement is crucial for you, as you prefer a clean and distraction-free interface for your note-taking. Apps that fulfill this requirement should be given the highest priority.
b. Want (2): Cloud sync native to the app - While cloud synchronization is important for accessing your notes across devices, it may not be an absolute necessity. Apps that provide this feature can be considered, but they can be ranked slightly lower in priority compared to the "need" requirement.
c. Good to Have (3): Web app and mobile apps, and Offline functionality - These requirements are desirable but not essential. Having a web app and dedicated mobile apps, as well as the ability to access notes offline, would enhance your user experience, but they can be considered lower priority compared to the previous requirements.
Discover potential tools/apps
To find potential apps that align with your requirements, it's helpful to seek recommendations from others or explore dedicated resources. For note-taking apps, you can ask for recommendations from friends, colleagues, or online communities. Additionally, you can refer to reliable sources like @noteappsinfo on Twitter for feature suggestions and reviews.
For the sake of this example, let's assume you have followed the previous steps and have identified your requirements for a note-taking app. Now, it's time to choose three promising options to further evaluate. It's worth noting that this step is often referred to as the "cold start problem" in the computer science world, where you have limited information to begin with.
Based on your requirements, you can choose three note-taking apps that seem to meet your needs. These could be apps that have been recommended to you or ones that you came across during your research. Remember, they don't have to be the final choices, but rather three options to start with.
For instance, considering the requirements you mentioned earlier, you might choose the following apps as your initial options:
App A: This app meets your "no visible markdown ever" requirement and offers native cloud synchronization. It also provides web and mobile apps, along with offline functionality.
App B: Although it doesn't fully meet the "no visible markdown ever" requirement, it does offer native cloud sync, web and mobile apps, and offline functionality.
App C: This app fulfills all your requirements, including "no visible markdown ever," native cloud sync, web and mobile apps, and offline functionality.
By selecting these three promising options, you can now move on to the next step and evaluate each app based on your requirements and tool scores. Completing these five steps within a weekend will help you make an efficient decision and find the most suitable app for your note-taking needs.
Now, let's be fair here. None of the note-taking apps I've tried so far have managed to fulfill all of my requirements. For instance, Notion falls short when it comes to offline functionality, and the mobile apps for Remnote and Logseq aren't as useful as I'd hoped. However, despite these limitations, they do offer enough features to make them worth considering.
Evaluation
Now, let's move on to the evaluation phase. Whether you're starting fresh with a new note-taking app or considering switching from your current one, the evaluation process remains the same.
When evaluating the different apps, it's important to consider two main factors: the time spent with tools in that category (requirements) and the time you've spent with the specific tool you're evaluating (checks).
To begin, I recommend spending the next 30 days exploring and using the three note-taking apps you've chosen. You don't have to try everything at once, but try to engage in similar activities across all three apps. For example, if you're looking to journal, take some time to journal in each app and see if any of them click with you.
Why 30 days? Well, this timeframe allows you to get a hang of each tool and truly understand their features and functionalities. It's long enough to give you a solid grasp, but not too long that you end up collecting an overwhelming amount of data, making it difficult to switch later on.
During these 30 days (or up to 90 days if you prefer), it's essential to refine your list of requirements and fill in the tool score column for each requirement. Take a look at your initial requirement list from step 3 and see if there's anything you need to add, remove, or change in terms of priority.
For example, you may realize that you don't care as much about offline mode because you can tolerate an hour of the service being offline once every 3-4 months. Or perhaps you discover that you need a feature like single page sharing because you often find yourself sharing information with your peers, partner, or advisor.
By continuously refining and updating your requirement list, you'll be able to narrow down your choices and find the note-taking app that best suits your needs. Remember, this evaluation process is all about finding the perfect fit for you!
Assign Tool Scores
Once you have refined your requirement list, it's time to assign a tool score to each app based on how well they meet those requirements. This score should be on a scale of 0 to 1, indicating the ease or difficulty of performing a specific task.
Here's a suggested scale you can use, but feel free to modify it to suit your preferences:
0 - Nope! The app doesn't fulfill the requirement at all. 0.25 - Very, very poorly. The app barely meets the requirement and is challenging to use. 0.5 - Plugin/workaround. The app does not have a native feature for the requirement, but there are ways to achieve it using plugins or workarounds. 0.75 - Mostly works. The app meets the requirement for the most part but may have minor limitations or inconveniences. 1 - Yup! The app fully satisfies the requirement without any issues.
As you evaluate each app, assign the corresponding tool score for each requirement. This will help you objectively compare the apps and determine which one aligns best with your needs and preferences. Don't forget to update your scores as you continue to use and explore the apps over the evaluation period.
Make a decision
During my initial evaluation of note-taking tools, I quickly realized that I needed a feature like block or page mentions with synced blocks. This requirement became a priority for me, and as a result, Walling was immediately eliminated from my list since it didn't offer this functionality.
Now that you have refined your needs and wants, it's time to choose a single app or a combination of apps that best meet your requirements. If you opt for a combination of apps, it's crucial to have a clear purpose in mind for each app. For example, you might decide that App A1 will be dedicated to everything related to X, while App A2 will be used for tasks or content of type Y.
By narrowing down your choices and having a fixed (or a fixed set) of apps, you can streamline your note-taking process and ensure that your content is organized effectively. This will help you stay focused and maximize the benefits of using the chosen app(s) for your specific needs.
Checks
Now, let's shift our focus to the checks you should conduct during your evaluation process.
There are three major checks that I recommend:
Accessibility: Consider whether you find it easy to enter information into the chosen app. Ask yourself if the thought of needing to open the app and add information discourages you from doing so. The app should be accessible and user-friendly, making it effortless for you to input and store your notes.
Stability: Evaluate the app's stability by examining how often it remains unbroken. A reliable note-taking app should function smoothly and rarely encounter technical issues. It's important to choose an app that consistently works well without frequent crashes or glitches.
Usability: Remember that a note-taking system is only as good as the information you can retrieve from it. Assess how easily you can retrieve the information you need from the app. Can you quickly search for and find specific notes or content? The app should offer a seamless and efficient user experience for retrieving your stored information.
To ensure that you maintain an effective note-taking system, continue evaluating your chosen app at regular intervals that you decide. It could be monthly, yearly, or any timeframe that suits you best. During these evaluations, repeat the assessment of your requirements and checks to see if the app continues to meet your needs.
If you come across a new and shiny app that catches your attention, give it a try for a weekend or a short period of time. Compare it against your evaluation metrics and see if it satisfies more of your higher-priority requirements with a better workability score. Additionally, consider the stability of the company behind the app, ensuring that they have a solid reputation and are likely to provide continued support.
If the new app fulfills more of your requirements with a higher score than your current tool, it may be worth considering a switch. However, don't be surprised if the new shiny tool falls short of meeting your actual needs and usage patterns. Sometimes, the tools that seem enticing at first glance may not align with your specific requirements and preferences.
It may not fit
Let me give you an example of how this evaluation process works in practice. I discovered Capacities after already using Notion and Readwise for some time.
Using a reader tool became my second priority requirement. However, I found that Reader integrates seamlessly with Notion, not Capacities. While Capacities offers some amazing features that I love, such as embedded pages, a side pane, and the ability to create objects anywhere, it falls short in two areas that are high priorities for me: single page sharing and API integration.
Although I'm drawn to Capacities for its unique capabilities, it doesn't fulfill my specific needs when it comes to sharing content with others or integrating with other tools via API. As much as I appreciate Capacities' features, I decide to stick with Notion since it aligns better with my highest priority items.
It's important to evaluate whether a tool meets your most critical requirements. Making an informed decision based on your priorities ensures that you choose a tool that truly addresses your specific needs and preferences.
Why not write everything in markdown
Now let's discuss the question of why not write everything in markdown.
While some people prefer writing in markdown due to its simplicity and portability, switching to a markdown-based note-taking system can be a significant decision, especially if you value consistent UI/UX.
There are a few reasons why someone might choose not to write everything in markdown:
(a) Personal Preference: Markdown may not be everyone's cup of tea. Some individuals simply dislike using markdown syntax and find it cumbersome or less intuitive compared to a more visually-oriented editor.
(b) UI/UX Considerations: Markdown-based note-taking apps often have a more code-like appearance, resembling text editors such as VS Code. For those who prefer a different aesthetic or a more polished user interface, markdown may not be the ideal solution.
(c) File Management: Writing in markdown typically involves working with local files, which may require manual organization and maintenance. This can be a hassle for individuals who prefer a more streamlined and centralized approach to note-taking, where files are managed within a dedicated app rather than as separate files on their computer.
Furthermore, even when using markdown files, interoperability between different markdown-based note-taking apps is not always seamless. For instance, if someone uses both Logseq and Obsidian in the same directory, they may encounter challenges with syncing, linking, or rendering certain markdown elements consistently between the two apps.
Ultimately, it's crucial to consider your own preferences, workflow, and the specific features and functionalities you require when deciding whether to write everything in markdown or opt for a different note-taking app that aligns better with your needs and desired user experience.
Apr 9, 2024: You’ll be able to use tabs in Google docs to organize information in a single document instead of linking to multiple files or searching endlessly.
You can publish your document to web (for free) AND embed them (unlike notion docs). You can decide whether you want to manually update the document or update it as you go.
While presentation slides embed as images, sheets embed as tables (making them look non-clunky). You can also pin header rows!
It is a one way sync. If you add something on the doc, it does not appear in the sheet but the vice-versa behaves just like slides.
Notification Settings
Varied notification settings:
E-signatures
I don’t have an image for this because I do not have an account that supports this — but google docs can now replace Docusign or any other tool you use to sign documents. Supported google workspaces can work with signatures on both — the pdf files and the docs. You can check out more information here.
There are also smartchips for app integrations and I do not know of many but Whimsical has one at the moment.
Create templated document dropdowns that you can use anywhere in the document. It comes with two that are preconfigured but you can configure one on your own.
Building Blocks
Building blocks are templates built using smart chips.
Some of those are integrated into other Google services though.
Email
For example, email draft, where you can draft a complete email in the doc, and then send it through gmail.
Meeting Notes
Another building block is meeting notes, which auto mentions the date, the document and creates a draft meeting note template right inside your doc when you select a calendar event.
It also gives you an option to attach the doc to the meeting.
Tables and Dropdown
Other building blocks are just templates that include dropdowns and tables. For example, this is the review tracker building block.
Keep Clippings
Last of the bunch: Keep clippings. At the start of the thread, when I did a design overview, you would see a a keep icon in the sidebar.
Add an existing note to document
Add a new note or a checklist that automatically attaches the document to it
Your browser does not support the video tag.
Templates
Tip: There are so many templates in Google Docs. Check them out at docs.google.com
The essay one looks like a craft doc!
Your browser does not support the video tag.
Alright, this ends my tool suggestion of @googledocs, everything that you probably missed in the last year while we all were obsessing over the new note taking apps.
As much as I love notion, nothing comes close in terms of ecosystem, collaboration & FREE writing as google docs. Use apple notes instead? Check this out: A Quick Guide to Apple’s Notes App
If you enjoyed reading this and found something that clicked for you, please consider supporting me by buying me a coffee here:
A step above beginner or almost intermediate tutorial/introduction to X. This time: Apple Shortcuts Automation 300. It is an intro to do(s), don't(s) and WTF is this thing in Apple Shortcuts.
Yes, tasker is the "more popular" alternative, but in terms of UI/UX, Automate is the closest option you will find.
In programming, you would be familiar with the concept of flexibility.
Flexible is one thing Apple Shortcuts tries to be not. It will fight you every step of the way.
So, we will go over the functions that Shortcuts provide out of box. And the quirks that come along with it.
Primitives
Constants and Variables
Shortcuts has inbuilt constants in form of Text, Number, Boolean, Array and Dictionary.
All objects are immutable in Shortcuts, i.e., changing any of these constants (mostly) returns a new object, rather than changing the original object.
Operators
Any programming language has 5 basic operators:
(a) Equals (b) Not (c) And (d) Or (e) Contains
Shortcuts has TWO: EQUALS/Not EQUALS and CONTAINS/Not CONTAINS.
Remember this!
Conditionals
Usually comparisons can have multi-parts in normal programming languages: and or or.
But we just learnt that shortcuts do not have andor as discussed above.
So, if X or Y: i1 else: i2 in Shortcuts is if X: (i1 if Y: i1 otherwise: i2) otherwise: i2
Loops
You might be used to 4 kinds of loops: for range, for each, for until and while. You might also be used to 2 commands: break and continue when dealing with loops.
Shortcuts only has TWO: For range (ex: run it 100 times), or For each (ex: run for each element in array)
Inbuilt Objects
Shortcuts also comes with an inbuilt set of objects. Each of these objects have pre-defined properties:
There are 22 inbuilt objects.
Each object has different inbuilt properties.
Depending on the object, it presents with a set of conditionals.
Images of all objects and their respective properties
Inbuilt Object Conversions
You can define that the object is a dictionary, boolean, number and text but you cannot define it is an array.
Object conversions also have no warning. So, you can try converting a URL into a dictionary and you'll know only when you run it!
Mass adoption of a tool is very different than “niche” critical. As someone who loves organization, I love delving into new apps. I love all the innovation that has been popping up with these apps.
Definition
Let’s define these terms first:
What is critical mass? That would be the amount of people using the tool needed for the toool to survive amongst the competitors in its niche areas.
What is a mainstream tool? It solves a problem that people have and is something that is a go-to for solving that problem.
Examples
Now before you come at me, for all those people who love note-taking, consider this. If someone asks you for a habit tracking tool, what do you suggest? Not an “all-in-one” tool, a specific app.
Say @Habitifyapp or @habitica?
Do you think better habit trackers don’t exist? Or someone asks for a journal app. Not an all in one app (for all you obsidian and notion people out there), but for journaling.
Did you suggest @dayoneapp or @myjourneyapp?
Why does the distinction matter?
Take a moment to think about why that is the case. Why are these tools “everlasting” and popular? Give me some specific examples you say?
@GoodNotesApp is the go-to solution to the problem of aesthetic, useful, organizable handwritten searchable notes which is why they still exist even though Apple Notes exists. @snipd_app is the go-to solution of “how do I remember what I heard” in podcasts, and hence, they are solving a “mainstream” problem. @airtable and @NotionHQ did not become popular because they were doing something “completely unexpected”.
They solved the problem of “how do we store data without complicated SQL?” or “what if I want to have notes for my Google Sheets?” People started using @Spotify because they did not want to buy albums and wanted good recommendations.
Imagine if spotify’s tagline was “we generate music playlists based on the mood we infer from your apple health data”, it would be cool but not mainstream. People work/think with people. How often are we in situations where we think/write/document about a “problem” start to end “alone”?
What we love vs what public needs
And any tool that is not collaborative or multi-platform lacks the ability to become mainstream. There is a reason @todoist is so popular. Coming back to the note-taking space. There are many features that us, note-taking people love.
It should have backlinks, should have markdown support, should have magical powers to transform my life when I zoom into the note-web I created. Ask any random person what they want in a note-taking app and their answer would be: frictionless adding, storage, surfacing, tagging, filtering.
That is why people use Apple Notes. And with minimal difference is why @evernote and @BearNotesApp became popular.
Lack of mainstream applications
The biggest problem with graph note-taking apps is the lack of “mainstream” applications.
Most mainstream graph based explorations require automatic content extraction (for example @RsrchRabbit which looks at “graph” of academic papers).
Do I hear you say, “but I love graphs?”. I think that is great that you do, but most people do not care about it. Most people who want a visual component to their notes, also want a visual component to the interaction, not use [[ to make a connection.
What about spatial canvas applications?
That is one of the main reason why I think @MuseAppHQ, @whimsical and @scrintal are headed in the right direction of “graphs” which is more akin to “whiteboarding” something that people know, that people are used to.
But these are not private. They are not local. They are not based in markdown. If you want all of those, good for you.
Older apps are here to stay
But given how many people use Google Docs, MS Word and Apple Notes, it is not hard to infer that people do not care about it. Am I being delusional? I sure hope so. But more than likely, I am not.
Do you know how many academics still use Latex and @overleaf for their papers (yes, even in CS)?
But even better, do you know how many use MS Word?
You would be surprised! In summary, all I want to say is as much as I am happy that @tana_inc exists, no it won’t kill @NotionHQ, just like Notion did not kill Google Docs.