Header logo.
small hallucinations
homeyearstagsaboutrss

Building a Landing Page with Codex, Astro, and Cloudflare

I created a landing page for a project I've wanted to start for a long time. Much of the delay came from struggling to wrap my head around the project.

The best way to beat procrastination is to do something—anything, really—to get started. For me, that “something” was building a landing page.

I asked Codex to generate five landing-page mockups in vastly different styles. I then refined the design by asking it to develop variants of the two mockups I liked best.

Codex now (as of August 2026, with gpt-5.6-sol) understands images remarkably well. It can generate design mockups and accurately interpret their layouts.

After some discussion, I asked Codex to build the site with server-side rendering so that users receive fully rendered HTML without much JS fluff. I also wanted the landing page to include a waitlist feature.

This is my first time using Astro. Judging by the code Codex produced, Astro handles the job cleanly and elegantly.

What's remarkable is that Astro creates a server-side endpoint to receive signup requests without adding any bloat. The endpoint then inserts each email address into a database table.

Cloudflare makes this especially simple without any hassle. I deployed the landing page on Cloudflare Pages and connected it to a D1 database to store incoming waitlist signups. And it just worked.

A lightweight Swift workflow with Zed

I played with Swift over the weekend, and Xcode felt sluggish from time to time on my machine. I also had to ask Codex for help quite often just to navigate Xcode's UI.

When I tried coding with Zed, it failed to recognize structs, variables, and the like defined in other files in the project.

Quick googling led me to a very helpful blog post, “How I Got Zed Editor Working with Swift Projects,” and this GitHub repository: xcode-build-server.

Following the instructions in the blog post, I installed xcode-build-server, generated a buildServer.json file with the following command:

xcode-build-server config -scheme SCHEME_NAME -project PROJECT_NAME.xcodeproj

Zed could now recognize the project's symbols.

I wanted to use the simulator without opening Xcode. After some back and forth, I got hot reload working too. This is what I did:

  • Navigate to the project directory and run open -a Simulator.
  • Run xcodebuild to build the codebase as an .app bundle.
  • Run xcrun to install and launch the built bundle in the simulator.

I've shared the Bash script I'm using in a GitHub Gist. Update values such as the project name, scheme, and directories, then install fswatch to watch for file changes. Now you can run this command and have fun:

open -a Simulator && dev.sh

Your ideas are wrong (and your instincts aren't)

I've been reading Life at the Speed of Play by Mark Pincus, founder of Zynga, the social gaming company behind Farmville.

Here are a few takeaways I found fascinating:

  1. Instincts and ideas are two different things. Your instincts are almost always right. Your ideas are often wrong. Recognizing this distinction makes it much easier to abandon failing ideas without giving up the core insight. Your instinct tells you where the market is going or which problems are worth solving. Your ideas are a specific way to build a product or find a solution. Ideas tend to be wrong because they involve countless permutations of details. Which brings us to:
  2. Proven, better, new. When you follow an instinct, find which features are proven to work in competing products and copy them. Identify which details in existing products you can objectively make better. Introducing new features or ways of interaction is risky. That's why:
  3. A “minimum idea state” is much more useful than a “minimum viable product”. MVPs are too heavy. Instead, build out just enough of your idea to rapidly gather data and test the waters.

Alpaca WebSocket authentication

I was trying to stream market data from Alpaca's WebSocket endpoint.

Alpaca's documentation lists the following three steps to receive market data from the stream.

  1. Connect to the endpoint.
  2. Send an authentication frame.
  3. Send a subscription frame.

Step 2 proved to be tricky.

The library I'm using is WebSockex. Per best practice, you should either send the auth frame in the handle_connect function or reply with an auth frame inside handle_frame when the “connected” message is received. In this particular case, both would fail or time out.

The handle_connect function is called immediately after the WebSocket connection is established. Inside handle_connect, you cannot return {:reply, auth_frame, state}. You can only return {:ok, state} or {:close, state}.

If you want to send the auth frame to the server, you'll have to use WebSockex.send_frame(pid, frame). ChatGPT keeps telling me to do the following, although it invariably causes a CallingSelfError. Apparently, you cannot just ask yourself (the same process) to send a frame.

1def handle_connect(_conn, state) do
2  # This will cause an error.
3  WebSockex.send_frame(self(), {:text, auth_frame})
4  {:ok, state}
5end

But in handle_frame function, you can return a tuple like this: {:reply, auth_frame, state}. The only thing you need to do is pattern match incoming frames. When you see "connected", return that reply tuple. This should have worked if it didn't time out. (Curiously, replying with a subscription frame worked.)

In the end, I did the following: I sent the auth frame inside the start_link function.

 1  def start_link(state) do
 2    connected = WebSockex.start_link(@uri, __MODULE__, state)
 3
 4    case connected do
 5      {:ok, pid} ->
 6        WebSockex.send_frame(pid, {:text, auth_json})
 7        {:ok, pid}
 8
 9      {:error, reason} ->
10        Logger.info(reason)
11    end
12  end

Reading “Elixir in Action”

I finished reading  "Elixir in  Action." It's a great book that explains complex and novel (at least to me) ideas in simple and clear language.

In each  chapter, the author illustrates a set of new concepts with simple code examples and occasional exercises. As you go deeper  into the book, you'll clearly see how new concepts are built on top of the simpler concepts introduced earlier. It feels like recursion.

At its core, Elixir is very, very simple. You define small functions that perform specific tasks. You can specify the conditions under which these functions run using the when clause and pattern matching of parameters. You then build larger functions by iteratively composing smaller functions.

The notions of pure functions and immutability go hand in hand. They  may  seem  like  constraints on the  surface, until you  realize they have liberated you from asking  yourself,  "Am I manipulating this list in place or  not?" or  "Is this pointer mutable or  not?" No, you didn't change the list in place. No, it's not  mutable, and you don't need to worry about pointers.

The most basic way to coordinate processes is by sending messages.  A process quits when it has received and processed a message. To persist  a process, you recursively call the function that receives messages. There are complex features, but at the core are these simple notions. This makes it much easier to think about than  goroutines and  channels.

Go and read it.

Two more learning tricks

1: Ask your coding agent to generate a minimal, runnable code snippet to demonstrate a concept.

2: After vibe-coding, ask your coding agent to summarize the changes it has made and create Anki flashcards.

Using Elixir Observer on a Mac

tl;dr

On a Mac, if you want to use the Observer GUI for Elixir/Erlang, just use the Homebrew distribution of Erlang. It comes with wxWidgets correctly pre-configured.

Erlang distributions installed via asdf do not support Observer out of the box. When you install wxWidgets via homebrew separately, Erlang will complain that it was not compiled with the --enable-compat30 flag, making it incompatible. (Do not even think about compiling from the source code on your own.)

context

Elixir/Erlang ships with a GUI observer that shows how processes interact. This seems very helpful when you have a complex mix of Supervisors, GenServers, and processes.

For some reason, I couldn't use it on my Mac. The error message mentioned something about wx. I decided to look into it and came across this blog post. Following the instructions, I tried installing wxwidgets using Homebrew. The installation failed because a few dependencies were the x86_64 version, but they should have been arm64.

It took me some time to realize my Homebrew was, for some reason, for x86_64. I don't recall how that happened, but that explained why I remember occasionally seeing architecture mismatches when adding dependencies. I also had a version of Elixir installed from MacPorts. I removed MacPorts, removed Homebrew. Reinstalled Homebrew for the correct architecture.

Now with the right version of Homebrew in place, I installed wxWidgets from Homebrew again, carefully set environment variables including KERL_CONFIGURE_OPTIONS="--with-wx", and tried installing Erlang and Elixir using the ASDF version manager. Erlang failed to install with an error message that said something like: “wxWidgets was not compiled with --enable-compat30, wx will NOT be useable”.

So, I tried building wxWdigets from source code. It was ChatGPT who suggested all the steps: clone the repo and configure it with a few flags, notably --enable-compat30, which is exactly what the last error message indicated.

The configuration script then noticed missing submodules, which I downloaded following the instructions given by the script. I repeated this step a few times for a handful of missing submodules.

When I finally got the compilation working, I was quickly faced with an error message: fp.h was missing, which was required by the built-in libpng. ChatGPT told me to install libpng via Homebrew and pass a parameter to the configuration script. I did so and reran the configuration script. This then happened again with libtiff.

I said, “Argh. I just wanted to use :observe,” as I was about to give up.

At that very moment, ChatGPT finally brought this up, as if it were an afterthought.

Yeah, this rabbit hole is brutal. If your real goal is just :observer, you don’t actually need to hand-build wxWidgets at all. ... Use Homebrew’s Erlang (it comes with the GUI bits working on macOS).

Bruh...

Why didn't you tell me this before I attempted to compile C++ on my own?