Welcome to my website! I’m Jonathan Y. Chan (jyc, jonathanyc, 陳樂恩, or 은총), a 🐩 Yeti fan, 🇺🇸 American, and 🐻 Californian, living in 🌁 San Francisco: the most beautiful city in the greatest country in the world. My mom is from Korea and my dad was from Hong Kong. I am a Christian. My professional endeavors include:

a failed startup (co-founded, YC W23) that let you write Excel VLOOKUPs on billions of rows;

Parlan was a spreadsheet with an interface and formula language that looked just like Excel. Under the hood, it compiled formulas to SQL then evaluated them like Spark RDDs. Alas, a former manager’s prophecy about why startups fail proved prescient…

3D maps at Apple, where I did the math and encoding for the Arctic and Antarctic;

I also helped out with things like trees, road markings, paths, and lines of latitude!

various tasks at Figma, which had 24 engineers when I joined;

… including copy-paste, a high-fidelity PDF exporter, text layout, scene graph code(gen), and putting fig-foot in your .fig files—while deleting more code than I added!

Blog

Resetting iCloud Photos sync

Photos were not syncing from my Mac to my other devices. On my Mac, the number of photos it was aware of (displayed at the bottom of the main screen) was different from the number of photos displayed in Photos on my iPhone. Both said “Last synced X minutes ago”, where X was some small number, so it seemed like it wasn’t even aware that the two were out of sync! Bizarrely, new photos from my iPhone would still show up on my Mac.

A few things I tried didn’t work: I waited a few days for this to resolve itself and tried checking/unchecking “iCloud Photos” in the “iCloud” section of Settings in Photos on my Mac.

Finally I read about the Photos Repair tool:

To get to the Photos Repair Library tool on your Mac, follow these steps:

  • If Photos is open, close the app. Then, while you click to open Photos, hold down the Command and Option keys at the same time.
  • In the window that opens, click Repair to start the repair process. You might be asked to enter your user account password.

This fixed it, but it’s pretty lame that I had to do this. It doesn’t inspire much confidence to learn that Apple’s synchronization system can be unaware that it is out of sync.

UPDATE: I’m not sure if it’s related, but today I started running out of space on my computer. I narrowed it down to ~/Library/Caches/CloudKit/comp.apple.bird:

$ sudo du -h /System/Volumes/Data | grep "G\t"
# 309G	/System/Volumes/Data/Users/jyc/Library/Caches/CloudKit/com.apple.bird
# 309G	/System/Volumes/Data/Users/jyc/Library/Caches/CloudKit/com.apple.bird/3593ccf6c2fd2e7e5212117d4c22421ae134a9a7
# 309G	/System/Volumes/Data/Users/jyc/Library/Caches/CloudKit/com.apple.bird/3593ccf6c2fd2e7e5212117d4c22421ae134a9a7/MMCS
# 309G	/System/Volumes/Data/Users/jyc/Library/Caches/CloudKit/com.apple.bird/3593ccf6c2fd2e7e5212117d4c22421ae134a9a7/MMCS/ClonedFiles
# 310G	/System/Volumes/Data/Users/jyc/Library/Caches/CloudKit
# 336G	/System/Volumes/Data/Users/jyc/Library/Caches

It’s seems like this shouldn’t be related to Photos because “Optimize Mac Storage” is set in Photos’ iCloud setings: And looking at the contents of that folder, the files appear to be a mix of random data from iCloud: ZIP files, HTML files, and some images, but no images from my Photos Library. Strange.

Jus soli

A map of the world indicating countries with jus soli (birthright) citizenship. The United States and almost all American countries have jus soli citizenship; few countries outside do.

Countries in dark blue grant jus soli without restrictions; all other countries require at least one parent to have citizenship or residency.

Jus soli is the predominant rule in the Americas…

Almost all states in Europe, Asia, Africa and Oceania grant nationality at birth based upon the principle of jus sanguinis (“right of blood”), in which nationality is inherited through parents rather than birthplace, or a restricted version of jus soli in which nationality by birthplace is automatic only for the children of certain immigrants.

The alternative, jus sanguinis (by blood) citizenship, is in my opinion an abomination.

From the Wikipedia article “Jus soli.”

Attaching Livebook and IEx to a running Phoenix instance

I run Phoenix (Rails-like web framework for Elixir) like so:

elixir --name [email protected] --cookie bar --erl \"-elixir ansi_enabled true\" -S mix phx.server

… then connect IEx (the Elixir REPL) using:

random=$(head /dev/urandom | LC_ALL=C tr -dc A-Za-z0-9 | head -c 8)
iex --name "iex-$random" --remsh [email protected] --cookie bar

… and connect Livebook (Jupyter for Elixir) using:

export LIVEBOOK_DEFAULT_RUNTIME=attached:[email protected]:bar
livebook server @home

The server node (Erlang VM instance) has the name [email protected], and IEx nodes have names like [email protected].

Now I can recompile code in the server node (which is the same node that Livebook is attached to) by entering recompile in IEx. Sometimes it looks like Phoenix.CodeReloader even reloads the code automatically, so recompile evaluates to :noop, which is nice! This means I can use Livebook to test out server functions as I iterate on them.

I tried running Livebook in its own node for a bit and including server code using:

Mix.install(
  [
    {:foo, path: "..."}
  ],
  config_path: ".../config.exs"
)

This also works alright, but it’s nice to be connected to the same server node because you can e.g. look at the state of GenServer processes with :sys.get_state.

Setting up Plex on a Synology NAS with ZeroTier

  1. Go to http://plex.tv/claim/ to create a “claim code”.
  2. In Package Center, search for and install “Plex Media Server.”
  3. Make sure to select the “Claim” option when installing, not the default option! Enter the claim code you generated in step 1. Otherwise you’ll have to reinstall; I was able to sign in to my Plex account but then kept getting the error “Not authorized: you do not have access to this server”.
  4. In File Station, right-click the volume(s) you want Plex to read, click “Properties”, “Permission”, “Create”, then enter “PlexMediaServer” under “User or group” and check “Read” and “Write”.
  5. In Plex, click the settings wrench icon at the top-right, go to Manage > Libraries, click “Add Library”, then select the volume(s). In my case they were under “volume1”.
  6. In Plex, go to Settings > Network and click “Show Advanced.”
    • For “Preferred network interface”, select your ZeroTier network interface (e.g. ztabcdef (10.147.17.123)).
    • For “LAN Networks”, enter “192.168.0.0/16,10.0.0.0/8”.
    • For “List of IP addresses and networks that are allowed without auth,” enter the same.

You should be all set! Now you can stream directly from your NAS even when away from home, and you don’t have to expose anything to the public Internet.

1960s-era encryption systems often included a punched card reader for loading keys. The mechanism would automatically cut the card in half when the card was removed, preventing its reuse.

From “Securing Record Communications: The TSEC/KW-26” via “Stream cipher attacks” on Wikipedia.

How to reinstall a Homebrew package from main/master

Here’s what I use to reinstall the main branch of the neovim Homebrew package:

brew unlink neovim && brew install neovim --HEAD --fetch-HEAD

My searches weren’t turning up good results.

Toasty Tech GUI Gallery

Ran across the Toasty Tech GUI Gallery today. I assumed it was abandoned because of the pristine “IE is EVIL!!!” banner until I noticed the Windows 11 review:

A screenshot of the Toasty Tech GUI Gallery homepage.

Don’t miss the “screen shots” of the “Realworld Desk” GUI. Awesome website.

From the Wikipedia article on Hattori Hanzo:

He died at the age of 54 or 55 in 1597. There are three theories about his death. One asserts that he was assassinated by a rival Samurai, the pirate Fūma Kotarō. After Hanzo tracked him down to the Inland Sea, Kotarō lured him and his men into a small channel and used oil to set the channel on fire. The second theory is that Hanzo became a monk in Edo where he lived out the rest of his days until he died of illness. The third theory is that he died because of illness and it was a natural death.

Backing up iCloud Photos using rsync

Here’s the copy-icloud-photos script I use to backup my photos stored on iCloud to my Synology NAS:

#!/bin/bash
set -euo pipefail

args=(
  --delete
  --human-readable
  --no-perms
  --partial
  --progress
  --times
  -v
)

src="/Users/jyc/Pictures/Photos Library.photoslibrary/originals/"
cd "$src"
find ./ -cmin +1440 -print0 |
  rsync --files-from=- --from0 \
    "${args[@]}" \
    "./" \
    nas.home:/var/services/homes/jyc/Photos/iCloudViaMac

I added find recently because it’s annoying to accidentally backup temporary photos, like screenshots, that only live in my iPhone’s Camera Roll for a minute or so before I delete them.

I have launchd run that script daily using a configuration plist at ~/Library/LaunchAgents/jyc.copy-icloud-photos.service:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Disabled</key>
  <false/>
  <key>Label</key>
  <string>copy-icloud-photos</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/fdautil</string>
    <string>exec</string>
    <string>/Users/jyc/bin/copy-icloud-photos</string>
  </array>
  <key>StandardErrorPath</key>
  <string>/tmp/copy-icloud-photos.err</string>
  <key>StandardOutPath</key>
  <string>/tmp/copy-icloud-photos.out</string>
  <key>StartInterval</key>
  <integer>86400</integer>
</dict>
</plist>

I set it up via LaunchControl, which is a third-party shareware GUI for launchd that also provides the fdautil wrapper script that makes it possible for the copy-icloud-photos script to have full disk access. I think it’s possible to get this to work without LaunchControl but I haven’t tried.

Unfortunately a big caveat is that this will back up recently deleted photos until they are truly deleted by iCloud. Here’s some lists filenames of non-deleted non-hidden photos under ~/Pictures/Photos Library.photoslibrary/originals/ when run on the database at ../Photos.sqlite:

select substr(ZFILENAME, 1, 1) || '/' || ZFILENAME
from ZASSET
where ZTRASHEDSTATE = 1 and ZHIDDEN = 0;

… but even when I grant bash, copy-icloud-photos, and sqlite3 Full Disk Access in System Settings > Privacy & Security, I can’t get it to work. I thought I might just need to grant my script Photos access as well, but that doesn’t work. Maybe Apple really is trying to block all programmatic access except through PhotoKit.

I am Culgi, who has been chosen by Inana for his attractiveness.

Because I am a powerful man who enjoys using his thighs, I, Culgi, the mighty king, superior to all, strengthened the roads, put in order the highways of the Land.

So that my name should be established for distant days and never fall into oblivion, so that my praise should be uttered throughout the Land, and my glory should be proclaimed in the foreign lands, I, the fast runner, summoned my strength and, to prove my speed, my heart prompted me to make a return journey from Nibru to brick-built Urim as if it were only the distance of a double-hour.
A praise poem of Shulgi (Shulgi A)

Sumerians didn’t skip leg day or cardio.

Fun

is the card of the week.

I'm computing the week number by dividing the number of days we are into the year by 7. This gives a different week number from ISO 8601. Suits are ordered diamonds, clubs, hearts, spades (like Big Two, unlike Poker) so that red and black alternate. On leap years there are 366 days in the year; the card for the 366th day is the white joker. Karl Palmen has proposed a different encoding.