<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Alex&#039;s Notebook</title>
	<atom:link href="https://blog.alexseifert.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.alexseifert.com</link>
	<description>Technology and Horror</description>
	<lastBuildDate>Tue, 30 Jun 2026 11:47:57 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2024/02/cropped-favicon.png?fit=32%2C32&#038;ssl=1</url>
	<title>Alex&#039;s Notebook</title>
	<link>https://blog.alexseifert.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">3693140</site>	<item>
		<title>Building a Web App in Swift Using Only SwiftNIO</title>
		<link>https://blog.alexseifert.com/2026/06/29/building-a-web-app-in-swift-using-only-swiftnio/</link>
					<comments>https://blog.alexseifert.com/2026/06/29/building-a-web-app-in-swift-using-only-swiftnio/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 12:18:27 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Featured]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Web Development]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12418</guid>

					<description><![CDATA[I’ve been experimenting a lot with Swift for web applications using frameworks like Vapor, but I got curious as to how I could create a low-level, framework-less web application using only SwiftNIO. This is the result.]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="1024" height="559" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Gemini_Generated_Image_unxjd1unxjd1unxj.png?resize=1024%2C559&#038;ssl=1" alt="AI-generated image of a man presenting &quot;Web App Using SwiftNIO&quot; to an audience" class="wp-image-12422" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Gemini_Generated_Image_unxjd1unxjd1unxj.png?resize=1024%2C559&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Gemini_Generated_Image_unxjd1unxjd1unxj.png?resize=400%2C218&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Gemini_Generated_Image_unxjd1unxjd1unxj.png?resize=150%2C82&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Gemini_Generated_Image_unxjd1unxjd1unxj.png?resize=768%2C419&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Gemini_Generated_Image_unxjd1unxjd1unxj.png?w=1408&amp;ssl=1 1408w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">AI-generated image of a man presenting &#8220;Web App Using SwiftNIO&#8221; to an audience</figcaption></figure>
</div>


<p class="wp-block-paragraph">Recently, I’ve been experimenting a lot more with Swift as a language for web applications. I’ve primarily been focusing my efforts on the web framework <a href="https://www.vapor.codes" target="_blank" rel="noreferrer noopener">Vapor</a> which is the most complete and popular web framework for Swift and is used by the likes of some very large projects such as the <a href="https://swiftpackageindex.com" target="_blank" rel="noreferrer noopener">Swift Package Index</a>.</p>



<p class="wp-block-paragraph">The framework is based on <a href="https://github.com/apple/swift-nio" target="_blank" rel="noreferrer noopener">SwiftNIO</a> which is Apple’s open-source “cross-platform asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers &amp; clients.” I also recently read somewhere that Apple itself uses SwiftNIO to power its newer web services as it is low-level and low-latency — both critical aspects considering the sheer enormity of the number of requests that Apple’s web services have to process. Whether or not that is true is something I can’t confirm, but it sounds plausible considering it is an Apple-developed framework that does exactly what a high-performance web service would need.</p>



<p class="wp-block-paragraph">So, naturally, I asked myself what it would take to make my own basic web app using SwiftNIO without any other dependencies beyond the three others that SwiftNIO itself relies on. As it turns out, it doesn’t actually take as much as I thought. Since I wanted to keep this experiment simple, I focused on basic functionality rather than implementing a fully featured web framework. If I wanted that, I would just go with Vapor.</p>



<p class="wp-block-paragraph">Before we start, I want to mention that I pushed the entire experiment to <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless" target="_blank" rel="noreferrer noopener">GitHub</a> with thoroughly commented code so that you can see what I did and/or play around with it yourself. There will be several links to various parts of the code on GitHub through the post.</p>



<h2 class="wp-block-heading"><strong>Setting Up the Server</strong></h2>



<p class="wp-block-paragraph">The first, and arguably most critical, feature any web application needs is a server. In this case, one that uses SwiftNIO for network communication. In order to accomplish that, I created two classes: <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/55c5c2cff25867d5610b187556beb48410a42f06/Sources/HTTPServer.swift#L166" target="_blank" rel="noreferrer noopener">HTTPServer</a> and <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/55c5c2cff25867d5610b187556beb48410a42f06/Sources/HTTPServer.swift#L28" target="_blank" rel="noreferrer noopener">HTTPHandler</a>. As the names might suggest, the first sets up the server and network infrastructure while the latter handles each individual connection.</p>



<p class="wp-block-paragraph">The <code>HTTPServer</code> class includes a method called <code>start()</code> which uses SwiftNIO’s <a href="https://swiftpackageindex.com/apple/swift-nio/main/documentation/nioposix/serverbootstrap" target="_blank" rel="noreferrer noopener">ServerBootstrap</a> to bind a TCP port and configure how connections are handled:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">public func start() async throws {
    let router = self.router

    let bootstrap = ServerBootstrap(group: group)
        // How many pending connections the OS kernel should queue before the
        // application has a chance to accept them.
        .serverChannelOption(ChannelOptions.backlog, value: 256)
        // Allow the port to be reused immediately after the process restarts,
        // without waiting for the OS TIME_WAIT period to expire.
        .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
        // For each accepted connection, configure the NIO pipeline:
        //   1. Built-in HTTP/1.1 encoder + decoder
        //   2. Our custom HTTPHandler for routing
        .childChannelInitializer { channel in
            channel.pipeline.configureHTTPServerPipeline().flatMap {
                channel.pipeline.addHandler(HTTPHandler(router: router))
            }
        }

    let channel = try await bootstrap.bind(host: host, port: port).get()
    print(&quot;Server running on http://\(host):\(port)&quot;)

    // Suspend here until the channel is closed. In normal operation this never
    // returns — the server runs until the process is killed.
    try await channel.closeFuture.get()
}
</code></pre>
</div>



<p class="wp-block-paragraph">That’s all that the <code>HTTPServer</code> class actually does. The rest happens in the <code>HTTPHandler</code> class which, as you may have noticed is added as a handler to the channel pipeline.</p>



<p class="wp-block-paragraph">For each incoming request, a new <code>HTTPHandler</code> instance is created and is responsible for four different tasks:</p>



<ol class="wp-block-list">
<li>Receiving request parts</li>



<li>Bridging to Swift concurrency via Task</li>



<li>Writing the response</li>



<li>Closing the connection</li>
</ol>



<p class="wp-block-paragraph">This is the lifecycle of a single request:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">Client connects
        ↓
ServerBootstrap accepts → creates HTTPHandler
        ↓
SwiftNIO fires channelRead(.head)  → store head
SwiftNIO fires channelRead(.body)  → accumulate bytes  (0 or more times)
SwiftNIO fires channelRead(.end)   → dispatch to router via Task
        ↓
Router matches route → calls handler → returns HTTPResponse
        ↓
eventLoop.execute → write response head + body + end
        ↓
Connection closed
</code></pre>
</div>



<h3 class="wp-block-heading">Receiving Request Parts</h3>



<p class="wp-block-paragraph">The <code>HTTPHandler</code> class implements SwiftNIO’s <code>ChannelInboundHandler</code> protocol which expects, among other things, the function <code>channelRead()</code>. This function is called multiple times per request and a value identifying which part of the request is currently being handled is passed via one of three different enums: <code>.head</code>, <code>.body</code> and <code>.end</code>. With these, we can determine our course of action and how to handle the data being sent.</p>



<p class="wp-block-paragraph">One gotcha, however, is that SwiftNIO breaks down large bodies into chunks which means there is the potential for multiple <code>.body</code> parts. I go into more detail about that in the section below where I describe how I implemented the RESTful methods.</p>



<h3 class="wp-block-heading">Bridging to Swift Concurrency via Task</h3>



<p class="wp-block-paragraph">SwiftNIO operates with event loops which, like when working with Node.js, should never be blocked. As such, we need to ensure that route handlers are executed asynchronously. I implemented that by stuffing them inside a Swift <code>Task</code> when SwiftNIO passes <code>.end</code>:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">Task { [head = head] in
    let response = await router.handle(head: head, body: body)
    // ↑ runs on Swift's cooperative thread pool, not the NIO thread
    
    eventLoop.execute {
        self.write(response: response, context: context)
        // ↑ hop back onto the NIO event loop to write the response
    }
}
</code></pre>
</div>



<h3 class="wp-block-heading">Writing The Response</h3>



<p class="wp-block-paragraph">The <code>HTTPHandler</code> class is also responsible for serializing the <code>HTTPResponse</code> object returned by the routes (more on that below) into SwiftNIO’s three-part format which includes:</p>



<ul class="wp-block-list">
<li>HTTPResponseHead (status &amp; headers)</li>



<li>ByteBuffer (body bytes)</li>



<li>End marker (signals that the response is complete)</li>
</ul>



<p class="wp-block-paragraph">This logic takes place in the <code>write()</code> function (<a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/55c5c2cff25867d5610b187556beb48410a42f06/Sources/HTTPServer.swift#L127" target="_blank" rel="noreferrer noopener">see on GitHub</a>):</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">private func write(response: HTTPResponse, context: ChannelHandlerContext) {
    // Allocate a NIO `ByteBuffer` sized to the response body and copy the bytes in.
    var buffer = context.channel.allocator.buffer(capacity: response.bodyData.count)
    buffer.writeBytes(response.bodyData)

    var headers = HTTPHeaders()
    headers.add(name: &quot;Content-Type&quot;, value: response.contentType)
    // Telling the client the exact byte count avoids chunked transfer encoding
    // and lets browsers display progress correctly.
    headers.add(name: &quot;Content-Length&quot;, value: &quot;\(buffer.readableBytes)&quot;)
    headers.add(name: &quot;Connection&quot;, value: &quot;close&quot;)

    let responseHead = HTTPResponseHead(version: .http1_1, status: response.status, headers: headers)

    // Write all three parts. The first two use `write` (buffered); the last uses
    // `writeAndFlush` to push everything to the network in one syscall.
    context.write(wrapOutboundOut(.head(responseHead)), promise: nil)
    context.write(wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil)

    let channel = context.channel
    context.writeAndFlush(wrapOutboundOut(.end(nil))).whenComplete { _ in
        // Close the TCP connection once all bytes have been sent.
        channel.close(promise: nil)
    }
}
</code></pre>
</div>



<p class="wp-block-paragraph"><code>HTTPResponse</code> is a <code>Sendable</code> struct I created that contains the response HTTP status, the content type and the body data as a Swift <code>Data</code> object. You can view it on <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/55c5c2cff25867d5610b187556beb48410a42f06/Sources/Router.swift#L16" target="_blank" rel="noreferrer noopener">GitHub</a>.</p>



<h3 class="wp-block-heading">Closing The Connection</h3>



<p class="wp-block-paragraph">The <code>write()</code> function above also contains the code that closes the connection after every response. This is the bit of code responsible for doing that:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">let channel = context.channel
context.writeAndFlush(wrapOutboundOut(.end(nil))).whenComplete { _ in
    // Close the TCP connection once all bytes have been sent.
    channel.close(promise: nil)
}
</code></pre>
</div>



<h3 class="wp-block-heading">Starting the Server</h3>



<p class="wp-block-paragraph">Now that we have everything in place, all that is left is to instantiate the <code>HTTPServer</code> class and call the <code>start()</code> function at the end of the <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/main/Sources/main.swift" target="_blank" rel="noreferrer noopener">main.swift</a> file to start the server:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">let server = HTTPServer(host: &quot;127.0.0.1&quot;, port: 8080, router: router)
try await server.start()
</code></pre>
</div>



<p class="wp-block-paragraph">Now that we’ve looked at how the custom server works, let’s see what else the application can do using the server.</p>



<h2 class="wp-block-heading">What This Basic Web App Can Do</h2>



<p class="wp-block-paragraph">I’ve mentioned several times already that the web app I made is basic and simple. It does, however, contain everything necessary for a basic API application or even a web application with light templating needs. Here is an overview:</p>



<ul class="wp-block-list">
<li>RESTful methods</li>



<li>Routing</li>



<li>Basic templating with XSS safety</li>



<li>Static content</li>



<li>HTTP/1.1 support</li>
</ul>



<h3 class="wp-block-heading">RESTful Methods</h3>



<p class="wp-block-paragraph">First off, the application includes support for routes with all RESTful methods (GET, POST, PUT, PATCH, DELETE) including body and header parsing. Initially, I wasn’t going to include support for anything but GET, but SwiftNIO made it surprisingly trivial to include all of them.</p>



<p class="wp-block-paragraph">Supporting body parsing was a bit trickier, but also wasn’t all that difficult once I figured out how SwiftNIO actually handles requests. It doesn’t actually ever give you a complete HTTP request in one go. Instead, it streams it piece by piece. These can be identified via enums like this: <code>.head  →  .body  →  .body  →  ...  →  .end</code>. As you can see though, the <code>.body</code> case can arrive multiple times for large payloads since SwiftNIO breaks it down into chunks. That made it a bit trickier to solve, but also not terrible. I just had to accumulate the chunks and then put them together at the end of the request:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">private var bodyBuffer: ByteBuffer?

// ...

case .body(var buffer):
    // Accumulate body chunks. For small bodies this will be a single call;
    // for larger bodies NIO may deliver multiple chunks.
    if bodyBuffer == nil {
        bodyBuffer = buffer
    } else {
        // Append the new chunk to the existing buffer.
        bodyBuffer!.writeBuffer(&amp;buffer)
    }

case .end:
    if var buf = bodyBuffer, let bytes = buf.readBytes(length: buf.readableBytes) {
        body = Data(bytes)   // ByteBuffer → [UInt8] → Data
    } else {
        body = Data()        // no body (GET, HEAD, etc.)
    }
</code></pre>
</div>



<p class="wp-block-paragraph">The code above is heavily cherry-picked. You can see the whole context on <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/55c5c2cff25867d5610b187556beb48410a42f06/Sources/HTTPServer.swift#L71" target="_blank" rel="noreferrer noopener">GitHub</a>.</p>



<h3 class="wp-block-heading">Routing</h3>



<p class="wp-block-paragraph">Of course, no web application would be worth anything if there wasn’t some form of routing. To accomplish this, I created a <code>Router</code> class that is responsible for registering all routes with their respective methods and handler functions, parsing the incoming URLs and calling said handler functions. The latter always return an <code>HTTPResponse</code> object as described above. I figured the best way to do this would be the regex-route (pun intended) which can be seen in the <code>add()</code> function of the <code>Router</code> class:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">public func add(_ method: HTTPMethod, _ path: String, _ handler: @escaping Handler) {
    var paramNames: [String] = []

    // Replace each `:paramName` segment with a regex capture group `([^/]+)`,
    // and record the parameter name so we can map captures back to names later.
    let regexString = path.replacing(/:([a-zA-Z0-9_]+)/) { match in
        paramNames.append(String(match.output.1))
        return &quot;([^/]+)&quot;
    }

    guard let regex = try? Regex(&quot;^&quot; + regexString + &quot;$&quot;) else { return }
    lock.withLock { routes.append(Route(method: method, regex: regex, paramNames: paramNames, handler: handler)) }
} 
</code></pre>
</div>



<p class="wp-block-paragraph">Then all you have to do is add your routes and handlers in the <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/main/Sources/main.swift" target="_blank" rel="noreferrer noopener">main.swift</a> file:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">router.add(.GET, &quot;/&quot;) { head, _, _, _ in
    let pageHtml = layoutView(title: &quot;Homepage&quot;, content: homeView())
    return HTTPResponse(status: .ok, contentType: &quot;text/html&quot;, body: pageHtml.description)
}

router.add(.GET, &quot;/hello/:name&quot;) { head, params, urlComponents, _ in
    let name = params[&quot;name&quot;] ?? &quot;unknown&quot;

    // Extract the optional `?german=true` query parameter.
    let germanParam = urlComponents.queryItems?.first(where: { $0.name == &quot;german&quot; })?.value
    let isGerman = germanParam == &quot;true&quot;

    let title = isGerman ? &quot;Hallo \(name)!&quot; : &quot;Hello \(name)!&quot;
    let pageHtml = layoutView(title: title, content: helloView(name: name, isGerman: isGerman), name: name)

    return HTTPResponse(status: .ok, contentType: &quot;text/html&quot;, body: pageHtml.description)
}
</code></pre>
</div>



<p class="wp-block-paragraph">You might have noticed in the example that parameterization of URLs is also supported. This includes both inline parameters (i.e. <code>/hello/:name</code>) as well as query parameters (i.e. <code>/hello/:name?german=true</code>). Inline parameters are parsed using the regex above and query parameters are extracted from the <code>urlComponents</code> variable injected into the handler function.</p>



<p class="wp-block-paragraph">You can find the entire <code>Router</code> class on <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/main/Sources/Router.swift" target="_blank" rel="noreferrer noopener">GitHub</a>.</p>



<h3 class="wp-block-heading">Basic Templating with XSS Safety</h3>



<p class="wp-block-paragraph">There were a number of approaches I considered when it came to adding templating to the project. My first instinct was, of course, to reach for one of the off-the-shelf solutions such as <a href="https://docs.vapor.codes/leaf/overview/" target="_blank" rel="noreferrer noopener">Vapor’s Leaf</a>, but I wanted to avoid extra dependencies and keep it as simple as possible. As such, I just decided on simple string interpolation where each template is a standard Swift function that returns a string of HTML. That may not be the best approach for a large web application with a complex frontend, but works perfectly well for this experiment.</p>



<p class="wp-block-paragraph">Fortunately, that also means it’s fairly straightforward. There is a layout view that provides the structure for the site as well as a specific view for each route that should return HTML.</p>



<p class="wp-block-paragraph">The layout view:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">func layoutView(title: String, content: HTML, name: String = &quot;&quot;) -&gt; HTML {
    return &quot;&quot;&quot;
    &lt;!DOCTYPE html&gt;
    &lt;html&gt;
        &lt;head&gt;&lt;title&gt;\(title)&lt;/title&gt;&lt;/head&gt;
        &lt;link rel=&quot;stylesheet&quot; href=&quot;/css/main.css&quot;&gt;
        &lt;script src=&quot;/js/scripts.js&quot;&gt;&lt;/script&gt;
        &lt;body&gt;
            &lt;nav&gt;
                &lt;a href=&quot;/&quot;&gt;Home&lt;/a&gt;
            &lt;/nav&gt;
            &lt;main&gt;\(content)&lt;/main&gt;
            &lt;p&gt;Input your name: &lt;input type=&quot;text&quot; id=&quot;name&quot; value=&quot;\(name)&quot;&gt;&lt;/p&gt;
            &lt;div class=&quot;buttons&quot;&gt;
                &lt;button type=&quot;button&quot; id=&quot;sayHelloButton&quot;&gt;Say Hello!&lt;/button&gt;
                &lt;button type=&quot;button&quot; id=&quot;sayHelloGermanButton&quot;&gt;Say Hello in German!&lt;/button&gt;
            &lt;/div&gt;
        &lt;/body&gt;
    &lt;/html&gt;
    &quot;&quot;&quot;
}
</code></pre>
</div>



<p class="wp-block-paragraph">The <code>/hello/:name</code> route’s view:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">func helloView(name: String, isGerman: Bool = false) -&gt; HTML {
    let greeting = isGerman ? &quot;Hallo \(name)!&quot; : &quot;Hello \(name)!&quot;
    return &quot;&lt;h1&gt;\(greeting)&lt;/h1&gt;&quot;
}
</code></pre>
</div>



<p class="wp-block-paragraph">The layout and route-specific views get put together in each route’s handler. If you look back at the previous examples of adding routes, you’ll see the <code>layoutView()</code> function takes a content parameter. This is where the route-specific view is injected into the layout.</p>



<p class="wp-block-paragraph">You might have also noticed the mysterious <code>HTML</code> type. This doesn’t come from SwiftNIO, but instead is a custom interpolation struct that provides XSS (cross-site scripting) safety. While a little excessive for this particular basic project, I happened to find out while making it that Swift supports such a thing and decided to give it a shot. I won’t go into detail about it here, but you can view the commented struct on <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/main/Sources/HTML.swift" target="_blank" rel="noreferrer noopener">GitHub</a>.</p>



<h3 class="wp-block-heading">Static Content</h3>



<p class="wp-block-paragraph">If you have templates, you have to have static content as well. What modern website doesn’t have CSS and JavaScript? Even my basic example does, but more as a proof of concept than anything else.</p>



<p class="wp-block-paragraph">Static files are located in the <code>Public</code> folder at the root of the project. The application will automatically figure out the path to it relative to the executable and then return the requested file via a wildcard GET route added to the router after all of the user-defined routes have been added. If no file is found, a 404 “not found” error is returned.</p>



<p class="wp-block-paragraph">This was surprisingly one of the trickier parts of the whole experiment to figure out, but once I had it, it wasn’t terrible to implement. Essentially, I had to read the static file as a Swift <code>Data</code> object and pass it along with the <code>mimeType</code> to the <code>HTTPResponse</code> to be handled in the same way it handles a standard string response:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">guard let data = try? Data(contentsOf: URL(fileURLWithPath: fullPath)) else {
    return HTTPResponse(status: .notFound, body: &quot;Not Found&quot;)
}

let ext = (fullPath as NSString).pathExtension
return HTTPResponse(status: .ok, contentType: mimeType(for: ext), data: data)
</code></pre>
</div>



<p class="wp-block-paragraph">There is a little bit of magic that also happens in there in the form of path sanitization so that a user can’t use standard Unix/Linux paths like &#8220;/..&#8221; to reach other files in the OS:</p>



<div class="wp-block-jetpack-markdown"><pre><code class="language-swift">let sanitized = filePath
    .components(separatedBy: &quot;/&quot;)
    .filter { !$0.isEmpty &amp;&amp; $0 != &quot;..&quot; }
    .joined(separator: &quot;/&quot;)

guard !sanitized.isEmpty else {
    return HTTPResponse(status: .notFound, body: &quot;Not Found&quot;)
}
</code></pre>
</div>



<p class="wp-block-paragraph">You can see the wildcard route and how the files are read on <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless/blob/55c5c2cff25867d5610b187556beb48410a42f06/Sources/Router.swift#L121" target="_blank" rel="noreferrer noopener">GitHub</a>.</p>



<h3 class="wp-block-heading">Only HTTP/1.1 is Supported</h3>



<p class="wp-block-paragraph">One notable limitation of the web application in its current form is that it only supports HTTP/1.1. SwiftNIO also has additional Apple-developed libraries that extend it to support HTTP/2 (<a href="https://github.com/apple/swift-nio-http2" target="_blank" rel="noreferrer noopener">swift-nio-http2</a>) and even an experimental library to allow it to support HTTP/3 (<a href="https://github.com/apple/swift-nio-http3" target="_blank" rel="noreferrer noopener">swift-nio-http3</a>), but I figured sticking with HTTP/1.1 would be good enough for this small experiment.</p>



<h2 class="wp-block-heading">What Else It Could Have Done</h2>



<p class="wp-block-paragraph">I enjoyed creating this project enough that it was hard to keep it simple. I could have just kept tacking on feature after feature until it did everything a standard web application can do, but I was (fairly) disciplined and kept it basic. These are some of the ways I thought it might be nice to expand it and, perhaps, I will fire up a new repo one day to do so.</p>



<p class="wp-block-paragraph">As mentioned above, the server currently only supports HTTP/1.1. It would probably be fairly trivial to add at least support for HTTP/2 with <a href="https://github.com/apple/swift-nio-http2" target="_blank" rel="noreferrer noopener">swift-nio-http2</a>, but I haven’t attempted that yet.</p>



<p class="wp-block-paragraph">Also, a proper templating engine might be useful for a real application. I’ve worked with <a href="https://docs.vapor.codes/leaf/overview/" target="_blank" rel="noreferrer noopener">Vapor’s Leaf</a>, but have also dabbled with <a href="https://github.com/stencilproject/Stencil" target="_blank" rel="noreferrer noopener">Stencil</a> which is another template language for Swift. Either one would be a good choice.</p>



<p class="wp-block-paragraph">Then, of course, rare is the web app that doesn’t have a database behind it. The easiest solution would be to statically link to the operating system’s C SQLite library and call it using raw queries directly in your Swift code. For other databases such as MySQL and PostgreSQL, the makers of Vapor also provide NIO-libraries: <a href="https://github.com/vapor/mysql-nio" target="_blank" rel="noreferrer noopener">MySQLNIO</a> and <a href="https://github.com/vapor/postgres-nio" target="_blank" rel="noreferrer noopener">PostgresNIO</a>. If you would prefer a full-fledged ORM, then <a href="https://docs.vapor.codes/fluent/overview/" target="_blank" rel="noreferrer noopener">Vapor’s Fluent</a> is probably the way to go.</p>



<p class="wp-block-paragraph">However, if you’ve already got Leaf and Fluent, then you might as well save yourself the trouble of everything else and just use <a href="https://www.vapor.codes" target="_blank" rel="noreferrer noopener">Vapor</a> itself which, as mentioned above, is also based on SwiftNIO.</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">So, would I ever use this on an actual production system? Honestly, probably not. For the vast majority of projects, a complete web framework like Vapor makes a lot more sense because it makes development so much faster and easier with much less code to maintain yourself. The performance penalty is so negligible that you probably wouldn’t be able to measure the difference in anything but nanoseconds.</p>



<p class="wp-block-paragraph">However, if you are serving requests at the same scale as Apple, then every nanosecond counts and you might want to consider going with raw SwiftNIO. The margin for error is wide, but the ability to finely tweak performance is vast. Since no project I have ever worked on — professionally or personally — has ever even come close to receiving the amount of requests an Apple service does, I honestly would always go with a framework like Vapor.</p>



<p class="wp-block-paragraph">That said, I did enjoy the experiment and it was interesting to piece together how it works behind the scenes. I could have easily just kept bolting on more features until I had my own mini framework, but I decided to just keep it simple and focus on the core of the application. After all, the point of the experiment was to figure out how it worked and if I could even make it work at all. Fortunately, I was successful in both endeavors.</p>



<p class="wp-block-paragraph">And just as a small aside: The README file in the <a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless" target="_blank" rel="noreferrer noopener">GitHub repository</a> has a few more technical details than I’ve included here. There are also instructions on how to get it to run and build on Mac (with and without Xcode) as well as Linux. It will only work on Windows with WSL since SwiftNIO relies on POSIX calls for its thread-pooling.</p>



<h2 class="wp-block-heading">Links</h2>



<ul class="wp-block-list">
<li><a href="https://github.com/eiskalteschatten/swift-webapp-frameworkless" target="_blank" rel="noreferrer noopener">GitHub repository with my experiment</a></li>



<li><a href="https://github.com/apple/swift-nio" target="_blank" rel="noreferrer noopener">SwiftNIO</a></li>



<li><a href="https://www.vapor.codes" target="_blank" rel="noreferrer noopener">Vapor</a></li>
</ul>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/06/29/building-a-web-app-in-swift-using-only-swiftnio/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12418</post-id>	</item>
		<item>
		<title>ScratchBook 2.2 Released</title>
		<link>https://blog.alexseifert.com/2026/06/15/scratchbook-2-2-released/</link>
					<comments>https://blog.alexseifert.com/2026/06/15/scratchbook-2-2-released/#comments</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Mon, 15 Jun 2026 12:09:42 +0000</pubDate>
				<category><![CDATA[Haunted House Software]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[Programs and Tools]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[ScratchBook]]></category>
		<category><![CDATA[ScratchPad]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12400</guid>

					<description><![CDATA[ScratchBook 2.2 has just been released in the Mac App Store with bugfixes and a new menu item to reset text formatting to the default.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">ScratchBook 2.2 has just been released in the <a href="https://apps.apple.com/us/app/scratchbook/id6759719038" data-type="link" data-id="https://apps.apple.com/us/app/scratchbook/id6759719038" target="_blank" rel="noreferrer noopener">Mac App Store</a>!</p>



<p class="wp-block-paragraph">Changes include:</p>



<ul class="wp-block-list">
<li>Added a new menu item to reset the selected text&#8217;s or an entire note&#8217;s formatting. You can find it under Format -> Reset Formatting</li>



<li>Fixed a bug where using cmd+option+shift+v didn&#8217;t paste anything</li>
</ul>



<p class="wp-block-paragraph">You can download ScratchBook for free on the <a href="https://apps.apple.com/us/app/scratchbook/id6759719038" data-type="link" data-id="https://apps.apple.com/us/app/scratchbook/id6759719038" target="_blank" rel="noreferrer noopener">Mac App Store</a>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://apps.apple.com/us/app/scratchbook/id6759719038" target="_blank" rel=" noreferrer noopener"><img data-recalc-dims="1" decoding="async" width="156" height="40" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?resize=156%2C40&#038;ssl=1" alt="Download on the Mac App Store" class="wp-image-12140" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?w=156&amp;ssl=1 156w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?resize=150%2C38&amp;ssl=1 150w" sizes="(max-width: 156px) 100vw, 156px" /></a></figure>
</div>


<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/06/15/scratchbook-2-2-released/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12400</post-id>	</item>
		<item>
		<title>The Relaunch of The Old West and Why I Chose Vanilla PHP</title>
		<link>https://blog.alexseifert.com/2026/06/02/the-relaunch-of-the-old-west-and-why-i-chose-vanilla-php/</link>
					<comments>https://blog.alexseifert.com/2026/06/02/the-relaunch-of-the-old-west-and-why-i-chose-vanilla-php/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Tue, 02 Jun 2026 08:26:38 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[History]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Next.js]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[The Old West]]></category>
		<category><![CDATA[Web Development]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12337</guid>

					<description><![CDATA[I rewrote my project, The Old West, using vanilla PHP despite it not being glamorous. This is why. ]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" decoding="async" width="1024" height="654" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Screenshot-2026-06-02-at-09.38.11.png?resize=1024%2C654&#038;ssl=1" alt="The Old West in 2026" class="wp-image-12338" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Screenshot-2026-06-02-at-09.38.11-scaled.png?resize=1024%2C654&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Screenshot-2026-06-02-at-09.38.11-scaled.png?resize=400%2C256&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Screenshot-2026-06-02-at-09.38.11-scaled.png?resize=150%2C96&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Screenshot-2026-06-02-at-09.38.11-scaled.png?resize=768%2C491&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Screenshot-2026-06-02-at-09.38.11-scaled.png?resize=1536%2C981&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/06/Screenshot-2026-06-02-at-09.38.11-scaled.png?resize=2048%2C1308&amp;ssl=1 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">The Old West in 2026</figcaption></figure>
</div>


<p class="wp-block-paragraph">Two days ago, I relaunched a website that <a href="https://blog.alexseifert.com/2021/04/19/the-old-west/" data-type="link" data-id="https://blog.alexseifert.com/2021/04/19/the-old-west/" target="_blank" rel="noreferrer noopener">I first set up in 2021</a> called <a href="https://www.the-old-west.com" target="_blank" rel="noreferrer noopener">The Old West</a>. It not only features a new design, but also a lot of new content. The purpose of the website is to consolidate Wikipedia articles about the old American west into one spot, making it easy to browse them. I import the articles into my database using Wikipedia&#8217;s API and display them under their&nbsp;<a href="https://en.wikipedia.org/wiki/Wikipedia:Copyrights" target="_blank" rel="noreferrer noopener">Creative Commons Attribution-ShareAlike License</a>. The articles are defined in config files and I wrote a script that imports and updates them. The script is run via a cronjob once a week to keep the articles up to date with the latest changes from Wikipedia.</p>



<p class="wp-block-paragraph">A complete rewrite was long overdue as I could no longer build the old website. At the time, I was heavily into React and <a href="https://nextjs.org" data-type="link" data-id="https://nextjs.org" target="_blank" rel="noreferrer noopener">Next.js</a> which is the technology I chose to power the website. Unfortunately, I also used the <a href="https://mui.com/material-ui/" data-type="link" data-id="https://mui.com/material-ui/" target="_blank" rel="noreferrer noopener">Material UI</a> component library which became the main liability. Material UI released a major new update at some point and I <em>really</em> didn&#8217;t like their new approach to CSS handling. Not only did I dislike it, it would have basically required a major rewrite of the website and all of its CSS. Therefore, I decided not to update. Of course, as time went on, the old version became incompatible with newer versions of React and Next.js and I got stuck with Next.js 12 which is several versions behind.</p>



<p class="wp-block-paragraph">That by itself isn&#8217;t as much of an issue, however, Node itself also continued to move on and eventually, Next.js 12 no longer supported the version of Node I had installed locally. I&#8217;m aware I could have used <a href="https://github.com/nvm-sh/nvm" data-type="link" data-id="https://github.com/nvm-sh/nvm" target="_blank" rel="noreferrer noopener">nvm</a> combined with a specific Node version defined in a <code>.nvmrc</code> file at the root of the project, but that is more than I was willing to do to support the old, increasingly fragile technology powering the project. At that point, I had also become very disillusioned by React, Next.js and the general highly volatile Node ecosystem with its well-known dependency churn and increasing number of supply chain attacks.</p>



<p class="wp-block-paragraph">So, when I decided to finally completely rewrite The Old West, I chose to go in a fully different direction with the technology. I chose vanilla PHP.</p>



<p class="wp-block-paragraph">With so many shiny new programming languages and web frameworks on offer, why did I choose PHP? Well, I didn&#8217;t want dependencies. I wanted to write the code once, deploy it and be able to forget about it for years at a time even if it meant a little bit more work up front. My other requirement was that I still wanted to be able to update it at anytime without having to first spend hours trying to sort out dependency or runtime version incompatibilities just to get it to build again.</p>



<p class="wp-block-paragraph">Vanilla PHP is not only perfect for that, but really the only feasible way to do it. Combined with Apache, Linux and MariaDB (the truly classic LAMP stack), it is practically bulletproof. No supply chain attacks, no version incompatibilities, no dependency update hell &#8212; nothing but stability and peace of mind.</p>



<p class="wp-block-paragraph">PHP is incredibly backwards-compatible which means I can safely update the version on my server using my package manager and don&#8217;t have to worry about too many, if any updates to my code. The only &#8220;dependencies&#8221; I rely on are official PHP extensions such as PDO for database connectivity which I&#8217;m not concerned about because they are maintained and updated with PHP itself. </p>



<p class="wp-block-paragraph">I wrote my own autoloader for namespaces, a tiny, fully custom router module, as well as a very lightweight ORM layer with an inheritable Model class, a QueryBuilder class, and a migration class that processes raw <code>.sql</code> files for migrations. Templating is handled with pure PHP. There is hardly any JavaScript on the website and I use pure CSS without anything like SCSS or Less. I do use the Bootstrap CSS classes for responsiveness, but I copied the CSS files into my project by hand which means they will work forever unless I feel like updating them.</p>



<p class="wp-block-paragraph">That&#8217;s really all there is to it. The website is lightweight, lightning-fast, and entirely customized to my specific needs. It will run stably for as long as the server runs and even if the server craps out at some point, moving the project to another server requires minimal effort.</p>



<p class="wp-block-paragraph">Vanilla PHP may not be glamorous, but it has been the stablest way to create a website for decades and, since it still powers the majority of the web, I think we can safely say it will remain that way for decades to come.</p>



<p class="wp-block-paragraph">If you&#8217;d like to see the new design, you can find The Old West here: <a href="https://www.the-old-west.com" target="_blank" rel="noreferrer noopener">www.the-old-west.com</a>.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/06/02/the-relaunch-of-the-old-west-and-why-i-chose-vanilla-php/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12337</post-id>	</item>
		<item>
		<title>My New 14&#8243; MacBook Pro M5</title>
		<link>https://blog.alexseifert.com/2026/04/30/my-new-14-macbook-pro-m5/</link>
					<comments>https://blog.alexseifert.com/2026/04/30/my-new-14-macbook-pro-m5/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Thu, 30 Apr 2026 09:40:24 +0000</pubDate>
				<category><![CDATA[Hardware]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[MacBook]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12271</guid>

					<description><![CDATA[I finally got a new MacBook. It's my first brand new MacBook since 2014.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Well, I finally got a new MacBook Pro. It&#8217;s my first brand new MacBook since I bought a 15&#8243; MacBook Pro in 2014. I&#8217;ve been wanting to buy a new one for several months, but was unsure about whether I should wait for the rumored M6 MacBooks that will supposedly get a new lighter and thinner design later this year or just ignore the rumors and buy a new one now.</p>



<p class="wp-block-paragraph">But once I found out that <a href="https://www.cyberport.de" data-type="link" data-id="https://www.cyberport.de" target="_blank" rel="noreferrer noopener">cyberport.de</a> had MacBooks on sale (I&#8217;m not sponsored), I decided that I couldn&#8217;t wait anymore and bought one. The basic specs are:</p>



<ul class="wp-block-list">
<li>M5 chip</li>



<li>32 GB of RAM</li>



<li>2 TB SSD</li>



<li>14&#8243; display</li>
</ul>



<p class="wp-block-paragraph">I got that for €2500 which is about €500 less than directly from Apple and essentially made the 2 TB SSD upgrade free. I also could have gone with a 16&#8243; instead, but I decided I&#8217;d prefer a larger SSD. Most of the time I use it plugged into an external monitor anyway and when I&#8217;m traveling with it, I like the smaller, lighter form factor.</p>



<p class="wp-block-paragraph">I did play around with the idea of getting a 15&#8243; MacBook Air instead since the specs I care most about are  the same, but in the end I decided the fans might be nice to have even though I&#8217;ll probably only rarely, if ever, need them.</p>



<p class="wp-block-paragraph">Here are a few pictures:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://blog.alexseifert.com/img_7151/"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="768" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7151.jpg?resize=1024%2C768&#038;ssl=1" alt="My new 14&quot; M5 MacBook Pro still sealed in the box" class="wp-image-12289" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7151.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7151.jpg?resize=400%2C300&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7151.jpg?resize=150%2C113&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7151.jpg?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7151.jpg?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7151.jpg?w=2000&amp;ssl=1 2000w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">My new 14&#8243; M5 MacBook Pro still sealed in the box</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://blog.alexseifert.com/img_7152/"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="768" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7152.jpg?resize=1024%2C768&#038;ssl=1" alt="My new 14&quot; M5 MacBook Pro still unopened" class="wp-image-12290" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7152.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7152.jpg?resize=400%2C300&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7152.jpg?resize=150%2C113&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7152.jpg?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7152.jpg?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7152.jpg?w=2000&amp;ssl=1 2000w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">My new 14&#8243; M5 MacBook Pro still unopened</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://blog.alexseifert.com/img_7153/"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="768" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7153.jpg?resize=1024%2C768&#038;ssl=1" alt="My new 14&quot; M5 MacBook Pro's first boot screen" class="wp-image-12292" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7153.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7153.jpg?resize=400%2C300&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7153.jpg?resize=150%2C113&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7153.jpg?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7153.jpg?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7153.jpg?w=2000&amp;ssl=1 2000w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">My new 14&#8243; M5 MacBook Pro&#8217;s first boot screen</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://blog.alexseifert.com/img_7154/"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="768" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7154.jpg?resize=1024%2C768&#038;ssl=1" alt="My new 14&quot; M5 MacBook Pro installing macOS 26.4.1" class="wp-image-12291" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7154.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7154.jpg?resize=400%2C300&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7154.jpg?resize=150%2C113&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7154.jpg?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7154.jpg?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7154.jpg?w=2000&amp;ssl=1 2000w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">My new 14&#8243; M5 MacBook Pro installing macOS 26.4.1</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://blog.alexseifert.com/img_7155/"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="768" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7155.jpg?resize=1024%2C768&#038;ssl=1" alt="My new 14&quot; M5 MacBook Pro transferring files from an external SSD with a Time Machine backup of my old 2019 MacBook Pro" class="wp-image-12293" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7155.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7155.jpg?resize=400%2C300&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7155.jpg?resize=150%2C113&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7155.jpg?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7155.jpg?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/04/img_7155.jpg?w=2000&amp;ssl=1 2000w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">My new 14&#8243; M5 MacBook Pro transferring files from an external SSD with a Time Machine backup of my old 2019 MacBook Pro</figcaption></figure>
</div>


<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/04/30/my-new-14-macbook-pro-m5/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12271</post-id>	</item>
		<item>
		<title>ScratchBook 2.1 Released</title>
		<link>https://blog.alexseifert.com/2026/04/28/scratchbook-2-1-released/</link>
					<comments>https://blog.alexseifert.com/2026/04/28/scratchbook-2-1-released/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Tue, 28 Apr 2026 08:13:18 +0000</pubDate>
				<category><![CDATA[Haunted House Software]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[Programs and Tools]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[ScratchBook]]></category>
		<category><![CDATA[ScratchPad]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12257</guid>

					<description><![CDATA[ScratchBook 2.1 has just been released in the Mac App Store and now includes support for Sonoma and Sequoia.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">ScratchBook 2.1 has just been released in the <a href="https://apps.apple.com/us/app/scratchbook/id6759719038" data-type="link" data-id="https://apps.apple.com/us/app/scratchbook/id6759719038" target="_blank" rel="noreferrer noopener">Mac App Store</a>!</p>



<p class="wp-block-paragraph">Changes include:</p>



<ul class="wp-block-list">
<li>Added support for macOS 14 (Sonoma) and macOS 15 (Sequoia)</li>



<li>Fixed an issue with truncated text on the import sheet</li>
</ul>



<p class="wp-block-paragraph">You can download ScratchBook for free on the <a href="https://apps.apple.com/us/app/scratchbook/id6759719038" data-type="link" data-id="https://apps.apple.com/us/app/scratchbook/id6759719038" target="_blank" rel="noreferrer noopener">Mac App Store</a>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://apps.apple.com/us/app/scratchbook/id6759719038" target="_blank" rel=" noreferrer noopener"><img data-recalc-dims="1" loading="lazy" decoding="async" width="156" height="40" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?resize=156%2C40&#038;ssl=1" alt="Download on the Mac App Store" class="wp-image-12140" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?w=156&amp;ssl=1 156w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?resize=150%2C38&amp;ssl=1 150w" sizes="auto, (max-width: 156px) 100vw, 156px" /></a></figure>
</div>


<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/04/28/scratchbook-2-1-released/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12257</post-id>	</item>
		<item>
		<title>Examples of Server-Side Swift in the Wild</title>
		<link>https://blog.alexseifert.com/2026/04/04/examples-of-server-side-swift-in-the-wild/</link>
					<comments>https://blog.alexseifert.com/2026/04/04/examples-of-server-side-swift-in-the-wild/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Sat, 04 Apr 2026 08:38:41 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Notes]]></category>
		<category><![CDATA[Programs and Tools]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Vapor]]></category>
		<category><![CDATA[Web Development]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12133</guid>

					<description><![CDATA[Ever since I experimented with the Swift server-side framework Vapor last year, I've been interested in actually seeing it in the wild. Here are a couple of examples.]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="640" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/new-backend-overview.png?resize=1024%2C640&#038;ssl=1" alt="Swift-based architecture for Things Cloud" class="wp-image-12224" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/new-backend-overview.png?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/new-backend-overview.png?resize=400%2C250&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/new-backend-overview.png?resize=150%2C94&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/new-backend-overview.png?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/new-backend-overview.png?resize=1536%2C960&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/new-backend-overview.png?w=1680&amp;ssl=1 1680w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">Swift-based architecture for Things Cloud</figcaption></figure>
</div>


<p class="wp-block-paragraph">Ever since <a href="https://blog.alexseifert.com/2025/06/29/experimenting-with-the-swift-web-framework-vapor/" data-type="link" data-id="https://blog.alexseifert.com/2025/06/29/experimenting-with-the-swift-web-framework-vapor/" target="_blank" rel="noreferrer noopener">I experimented with the Swift server-side framework Vapor</a> last year, I&#8217;ve been interested in actually seeing it in operation in the wild. I know there have been a number of companies, including Apple itself, that have started using it for their server-side applications, but it&#8217;s nice to see some concrete examples of it.</p>



<p class="wp-block-paragraph">I&#8217;ve been following the <a href="https://www.swift.org/blog/" data-type="link" data-id="https://www.swift.org/blog/" target="_blank" rel="noreferrer noopener">official Swift blog</a> for a while now, but I just recently discovered two posts about companies that are using Swift and Vapor at large scale to run their server-side applications.</p>



<p class="wp-block-paragraph">The first of which is the syncing service for the very popular Mac/iOS productivity application, <a href="https://culturedcode.com/things/" data-type="link" data-id="https://culturedcode.com/things/" target="_blank" rel="noreferrer noopener">Things</a>. I&#8217;ve played around with Things before in the past, but it&#8217;s too expensive for my basic needs, so I don&#8217;t use it. Nonetheless, it is still a very popular application and the fact that Swift and Vapor power its syncing backend is great news for those who are questioning the framework&#8217;s reliability or scalability.</p>



<p class="wp-block-paragraph">You can read more about Things using Swift and Vapor <a href="https://www.swift.org/blog/how-swifts-server-support-powers-things-cloud/" target="_blank" rel="noreferrer noopener">here</a>. The post also includes more information about their infrastructure in general.</p>



<p class="wp-block-paragraph">The second usage I found recently was for the <a href="https://telemetrydeck.com" data-type="link" data-id="https://telemetrydeck.com" target="_blank" rel="noreferrer noopener">TelemetryDeck analytics service</a>. Unlike Things, I had never heard of them, so I can&#8217;t say a whole lot about them, but they apparently have a massive amount of traffic that Swift and Vapor handle flawlessly.</p>



<p class="wp-block-paragraph">You can read about it <a href="https://www.swift.org/blog/building-privacy-first-analytics-with-swift/" target="_blank" rel="noreferrer noopener">here</a>.</p>



<p class="wp-block-paragraph">Of course, something to keep in mind is that both of these posts are on the official Swift blog which is unlikely to report on any negative experiences that companies have had with server-side Swift. However, I still find it great that companies are having success with using Swift and Vapor in production environments with large traffic loads.</p>



<p class="wp-block-paragraph">If you know of any other examples of server-side Swift in the wild, let me know. I&#8217;d love to read about it &#8212; good or bad!</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/04/04/examples-of-server-side-swift-in-the-wild/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12133</post-id>	</item>
		<item>
		<title>Trying to Use My iPad as a Desktop Replacement</title>
		<link>https://blog.alexseifert.com/2026/03/26/trying-to-use-my-ipad-as-a-desktop-replacement/</link>
					<comments>https://blog.alexseifert.com/2026/03/26/trying-to-use-my-ipad-as-a-desktop-replacement/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Thu, 26 Mar 2026 15:53:44 +0000</pubDate>
				<category><![CDATA[Featured]]></category>
		<category><![CDATA[Operating Systems]]></category>
		<category><![CDATA[Programs and Tools]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[iPad]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12163</guid>

					<description><![CDATA[I've already tried using my iPad as a laptop replacement while traveling, but this time, I try to use it as a desktop replacement. This is how it went.]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="768" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/img_7039.jpg?resize=1024%2C768&#038;ssl=1" alt="My desk setup using my iPad Air M2 in desktop mode" class="wp-image-12184" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/img_7039.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/img_7039.jpg?resize=400%2C300&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/img_7039.jpg?resize=150%2C113&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/img_7039.jpg?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/img_7039.jpg?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/img_7039.jpg?w=2000&amp;ssl=1 2000w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">My desk setup using my iPad Air M2 in desktop mode</figcaption></figure>
</div>


<p class="wp-block-paragraph">A couple of months ago, I took my 11&#8243; iPad Air M2 with me to the city of Regensburg to try <a href="https://blog.alexseifert.com/2026/01/26/trying-to-use-my-ipad-as-a-laptop-replacement/" data-type="link" data-id="https://blog.alexseifert.com/2026/01/26/trying-to-use-my-ipad-as-a-laptop-replacement/" target="_blank" rel="noreferrer noopener">working with it as a laptop replacement</a>. Unfortunately, the tiny 11&#8243; screen and the non-standard, smaller keyboard made it less than a stellar experience. However, I thought I could mitigate those problems while taking advantage of iPadOS 26&#8217;s new windowing feature by using a normal keyboard, mouse and a large external screen &#8212; essentially, turning it into a desktop replacement.</p>



<h2 class="wp-block-heading">Equipment</h2>



<p class="wp-block-paragraph">My equipment list for this experiment isn&#8217;t as exciting as it was for the laptop replacement experiment because I just used my usual desktop accessories:</p>



<ul class="wp-block-list">
<li>Logitech MX Keys keyboard</li>



<li>Apple mouse</li>



<li>LG widescreen monitor that supports USB-C</li>
</ul>



<p class="wp-block-paragraph">Normally, I use a Logitech MX Master 3S mouse instead of the Apple mouse, but I&#8217;ve already used up all three device connections that it supports, so I had dig out my old Apple mouse. Otherwise, the most important part was the monitor with USB-C support. With that, I could just plug my iPad in and it not only activated external display mode, but importantly, it also charged.</p>



<h2 class="wp-block-heading">My Experience</h2>



<p class="wp-block-paragraph">I know that the iPad is limited as to what it is capable of doing primarily due to the limitations of iPadOS. Development is nearly impossible on it which already means it&#8217;s not a viable desktop replacement for me full-time. However, I also write a lot, so I thought the best way to test it was to do some writing.</p>



<p class="wp-block-paragraph">On my Mac, I use Pages anyway and most of it gets saved to iCloud Drive. As such, writing in Pages on the iPad means there isn&#8217;t really much of a change of workflow. I already knew that from when I tried using my iPad as a laptop replacement, but what I didn&#8217;t count on what the severe limitations I ran into when using the mouse and keyboard.</p>



<p class="wp-block-paragraph">It turns out, unsurprisingly, that iPad apps are not optimized for them. That didn&#8217;t surprise me at all, but what did surprise me was just <em>how</em> limited it would be. Even first party-applications like Pages were severely restricted. For example, there is no way to correct a misspelled word with the mouse and keyboard. You have to use the touch screen to tap on the word that is underlined in red and then you get the popup menu with the spelling suggestions. There is absolutely not way to replicate that functionality with the mouse and keyboard which means I had to drag Pages from my big screen down to the iPad&#8217;s touch screen to use it &#8212; not a good experience at all.</p>



<p class="wp-block-paragraph">Also, for some reason, forward delete doesn&#8217;t seem to work at all on the iPad. A full-size Mac keyboard has its own forward delete key and on the smaller Mac keyboards (such as on the MacBooks), you use fn+delete. Neither of those worked on the iPad at all which made trying to edit text much more tedious.</p>



<p class="wp-block-paragraph">Even worse, though, were third-party apps. For example, I tried watching a YouTube video using the YouTube app. I could click on a video, but couldn&#8217;t exit it. There was literally no way to leave the video or access buttons like &#8220;Save&#8221; or &#8220;Like&#8221;. It just didn&#8217;t work at all. Again, I had to drag the app down to my iPad&#8217;s screen and use the touch interface.</p>



<p class="wp-block-paragraph">The worst issue I kept encountering, though, was that the iPad would regularly crash entirely. As in, both screens would go black and there would be a loading spinner on the iPad&#8217;s screen that lasted for several seconds before I could use it again&#8230; except all of the apps I had open were closed. That happened with startling frequency even with only three of my most-used apps open: Mail, Safari and Pages. So much for windowed multitasking.</p>



<p class="wp-block-paragraph">After this happened four times in a half an hour, I gave up on the experiment and went back to my Mac.</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">Needless to say, I won&#8217;t be using my iPad as a desktop or laptop replacement anytime soon. After the experiment with trying to use the iPad as a laptop replacement, I went into this one less than optimistic. Unfortunately, my pessimism was confirmed by the experience.</p>



<p class="wp-block-paragraph">I find it really disappointing that, despite the improvements Apple made to desktop mode in iPadOS 26, it still doesn&#8217;t even come close to being a viable, reliable option. Frankly, my iPad with its M2 processor is several times more powerful than my old Intel MacBook Pro, but the software cripples it to the point of irrelevance.</p>



<p class="wp-block-paragraph">The idea of having a truly all-in-one, portable device is extremely appealing to me. I love using my iPad to read my RSS feeds, eBooks, articles online, magazines, etc which is what I primarily use it for. While I have an Apple Pencil, I rarely use it to take notes or draw anything. Unfortunately, unless Apple makes some major changes to iPadOS, it&#8217;s just going to remain a glorified electronic reader for me.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/03/26/trying-to-use-my-ipad-as-a-desktop-replacement/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12163</post-id>	</item>
		<item>
		<title>Server Stability Issues</title>
		<link>https://blog.alexseifert.com/2026/03/13/server-stability-issues/</link>
					<comments>https://blog.alexseifert.com/2026/03/13/server-stability-issues/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Fri, 13 Mar 2026 09:33:04 +0000</pubDate>
				<category><![CDATA[General]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Web server]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12152</guid>

					<description><![CDATA[As some of you may have noticed, my websites keep going down and are not reachable for hours at a time. I've been having some stability issues with my server.]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="559" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Gemini_Generated_Image_hwm3hihwm3hihwm3.png?resize=1024%2C559&#038;ssl=1" alt="AI-generated image of a server maliciously laughing at a server admin" class="wp-image-12153" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Gemini_Generated_Image_hwm3hihwm3hihwm3.png?resize=1024%2C559&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Gemini_Generated_Image_hwm3hihwm3hihwm3.png?resize=400%2C218&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Gemini_Generated_Image_hwm3hihwm3hihwm3.png?resize=150%2C82&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Gemini_Generated_Image_hwm3hihwm3hihwm3.png?resize=768%2C419&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Gemini_Generated_Image_hwm3hihwm3hihwm3.png?w=1408&amp;ssl=1 1408w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">AI-generated image of a server maliciously laughing at a server admin</figcaption></figure>
</div>


<p class="wp-block-paragraph">As some of you may have noticed, my websites keep going down and are not reachable for hours at a time. I&#8217;ve been having some stability issues with my server and, of course, it always happens over night German time which is why it takes me hours to get to it. I only see it when I get up in the morning.</p>



<p class="wp-block-paragraph">Ubuntu has been killing the Apache and MariaDB services due to out-of-memory issues despite my monitoring reporting RAM usage only reaching 20% of the server&#8217;s full capacity. However, I have discovered that there is a massive spike in traffic during that time and I suspect it&#8217;s due to bots. I still need to confirm my theory though.</p>



<p class="wp-block-paragraph">In the meantime, I&#8217;ve reconfigured the Apache and MariaDB services to automatically restart when they die. That should at least solve the immediate problem of my websites being unreachable for hours at a time. I could have sworn that I&#8217;d already set them to automatically restart, but apparently not. Oh well.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/03/13/server-stability-issues/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12152</post-id>	</item>
		<item>
		<title>ScratchBook Now Available on the Mac App Store</title>
		<link>https://blog.alexseifert.com/2026/03/11/scratchbook-now-available-on-the-mac-app-store/</link>
					<comments>https://blog.alexseifert.com/2026/03/11/scratchbook-now-available-on-the-mac-app-store/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Wed, 11 Mar 2026 10:19:21 +0000</pubDate>
				<category><![CDATA[Haunted House Software]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[Programs and Tools]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[ScratchBook]]></category>
		<category><![CDATA[ScratchPad]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12105</guid>

					<description><![CDATA[I'm excited to announce that ScratchBook is now available as a free download on the Mac App Store!]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="640" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Full-Screenshot-1.jpg?resize=1024%2C640&#038;ssl=1" alt="ScratchBook screenshot" class="wp-image-12148" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Full-Screenshot-1.jpg?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Full-Screenshot-1.jpg?resize=400%2C250&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Full-Screenshot-1.jpg?resize=150%2C94&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Full-Screenshot-1.jpg?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Full-Screenshot-1.jpg?w=1280&amp;ssl=1 1280w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">ScratchBook screenshot</figcaption></figure>
</div>


<p class="wp-block-paragraph">I&#8217;m excited to announce that ScratchBook is now available as a free download on the Mac App Store!</p>



<p class="wp-block-paragraph">If you are interested in downloading it, you can visit its page on the <a href="https://apps.apple.com/us/app/scratchbook/id6759719038" data-type="link" data-id="https://apps.apple.com/us/app/scratchbook/id6759719038" target="_blank" rel="noreferrer noopener">Mac App Store</a>.</p>



<p class="wp-block-paragraph">I&#8217;m especially excited about this release because it is the first time I&#8217;ve published anything on any app store and because it&#8217;s a much needed update to the original ScratchPad whose Objective C codebase dates back to Mac OS X Tiger.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://apps.apple.com/us/app/scratchbook/id6759719038" target="_blank" rel=" noreferrer noopener"><img data-recalc-dims="1" loading="lazy" decoding="async" width="156" height="40" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?resize=156%2C40&#038;ssl=1" alt="Download on the Mac App Store" class="wp-image-12140" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?w=156&amp;ssl=1 156w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/download-mac-app-store.png?resize=150%2C38&amp;ssl=1 150w" sizes="auto, (max-width: 156px) 100vw, 156px" /></a></figure>
</div>


<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/03/11/scratchbook-now-available-on-the-mac-app-store/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12105</post-id>	</item>
		<item>
		<title>Status Update for ScratchBook</title>
		<link>https://blog.alexseifert.com/2026/03/06/status-update-for-scratchbook/</link>
					<comments>https://blog.alexseifert.com/2026/03/06/status-update-for-scratchbook/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Fri, 06 Mar 2026 09:12:57 +0000</pubDate>
				<category><![CDATA[Haunted House Software]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[Programs and Tools]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[ScratchBook]]></category>
		<category><![CDATA[ScratchPad]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12094</guid>

					<description><![CDATA[Unfortunately, ScratchBook is still waiting for review. It's taking much longer than Apple claims is the average for 90% of submitted apps.]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="699" height="248" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-06-at-10.01.56.png?resize=699%2C248&#038;ssl=1" alt="ScratchBook waiting for review" class="wp-image-12096" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-06-at-10.01.56.png?w=699&amp;ssl=1 699w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-06-at-10.01.56.png?resize=400%2C142&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-06-at-10.01.56.png?resize=150%2C53&amp;ssl=1 150w" sizes="auto, (max-width: 699px) 100vw, 699px" /><figcaption class="wp-element-caption">ScratchBook waiting for review</figcaption></figure>
</div>


<p class="wp-block-paragraph">Unfortunately, ScratchBook is still waiting for review. Apparently, it&#8217;s tough to review a simple application. Most likely though, it just doesn&#8217;t have much priority. It&#8217;s a small, free application from an unknown developer that Apple isn&#8217;t going to make any money from.</p>



<p class="wp-block-paragraph">I submitted it for review on Tuesday, March 3rd. According to the email I received from Apple confirming its submission, the average approval time for 50% of submitted apps is 24 hours and for 90%, it&#8217;s 48 hours:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="679" height="332" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-06-at-10.02.29-1.png?resize=679%2C332&#038;ssl=1" alt="The average wait time for app reviews" class="wp-image-12098" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-06-at-10.02.29-1.png?w=679&amp;ssl=1 679w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-06-at-10.02.29-1.png?resize=400%2C196&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-06-at-10.02.29-1.png?resize=150%2C73&amp;ssl=1 150w" sizes="auto, (max-width: 679px) 100vw, 679px" /><figcaption class="wp-element-caption">The average wait time for app reviews</figcaption></figure>
</div>


<p class="wp-block-paragraph">Apparently ScratchBook falls within that 10% where it takes much longer. I have no explanation for this as I have received absolutely not communication from Apple since its submission. No change in status, no emails, no notifications, deafening silence.</p>



<p class="wp-block-paragraph">In any case, I&#8217;ll post again here when it&#8217;s been approved or denied.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/03/06/status-update-for-scratchbook/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12094</post-id>	</item>
		<item>
		<title>Announcing Haunted House Software</title>
		<link>https://blog.alexseifert.com/2026/03/03/announcing-haunted-house-software/</link>
					<comments>https://blog.alexseifert.com/2026/03/03/announcing-haunted-house-software/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Tue, 03 Mar 2026 15:01:26 +0000</pubDate>
				<category><![CDATA[Haunted House Software]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[iPad]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[ScratchBook]]></category>
		<category><![CDATA[ScratchPad]]></category>
		<category><![CDATA[software]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12010</guid>

					<description><![CDATA[I've been working on a small software entity I've dubbed "Haunted House Software" and I'm excited to announce that it is live!]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="1024" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/02/Gemini_Generated_Image_l1fla7l1fla7l1fl.png?resize=1024%2C1024&#038;ssl=1" alt="AI-generated image of a ghost programming" class="wp-image-12012" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/02/Gemini_Generated_Image_l1fla7l1fla7l1fl.png?w=1024&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/02/Gemini_Generated_Image_l1fla7l1fla7l1fl.png?resize=400%2C400&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/02/Gemini_Generated_Image_l1fla7l1fla7l1fl.png?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/02/Gemini_Generated_Image_l1fla7l1fla7l1fl.png?resize=768%2C768&amp;ssl=1 768w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">AI-generated image of a ghost programming</figcaption></figure>
</div>


<p class="wp-block-paragraph">Recently, I&#8217;ve been <a href="https://blog.alexseifert.com/category/technology/haunted-house-software/" data-type="category" data-id="914" target="_blank" rel="noreferrer noopener">posting quite a bit</a> about my project, ScratchBook (formerly ScratchPad). Just today, I submitted it to the Mac App Store for review and I anxiously await the results. However, I haven&#8217;t been content to just stop with just releasing a single app.</p>



<p class="wp-block-paragraph">Instead, I&#8217;ve been working on a small software entity I&#8217;ve dubbed &#8220;Haunted House Software.&#8221; Obviously, it&#8217;s a play on my interest in horror, ghosts and, well, haunted houses. While it is not a legal entity (yet), I do intend to use the name to release not only ScratchBook, but also a couple of other apps I&#8217;ve been working on. Maybe someday I&#8217;ll turn it into a proper company, but for now, I&#8217;m content just to use it as a collective for my apps.</p>



<p class="wp-block-paragraph">I&#8217;ve spent most of my afternoon finishing up a website for it which you can find at <a href="https://www.hauntedhousesoftware.com" target="_blank" rel="noreferrer noopener">www.hauntedhousesoftware.com</a>. Conveniently, I just shut down my horror blog, Haunting Alex, which meant I could reuse some of the styling and images. I happened to really like the logo I made for the blog, so I reused it for the Haunted House Software:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="1024" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/logo.png?resize=1024%2C1024&#038;ssl=1" alt="Haunted House Software Logo" class="wp-image-12085" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/logo.png?resize=1024%2C1024&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/logo.png?resize=400%2C400&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/logo.png?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/logo.png?resize=768%2C768&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/logo.png?w=1028&amp;ssl=1 1028w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">Haunted House Software Logo</figcaption></figure>
</div>


<p class="wp-block-paragraph">There will be more announcements coming because I&#8217;ve got big plans. However, I do first have to actually sit down and finish up releasable versions of the other apps I&#8217;ve been working on. Unlike ScratchBook, they are not only for Mac, but also for iOS and iPadOS.</p>



<p class="wp-block-paragraph">As always, I&#8217;ll post them here on my blog.</p>



<h2 class="wp-block-heading">Haunted House Software Links</h2>



<ul class="wp-block-list">
<li>Website: <a href="https://www.hauntedhousesoftware.com" target="_blank" rel="noreferrer noopener">https://www.hauntedhousesoftware.com</a></li>



<li>GitHub: <a href="https://github.com/Haunted-House-Software">https://github.com/Haunted-House-Software</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/03/03/announcing-haunted-house-software/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12010</post-id>	</item>
		<item>
		<title>ScratchBook Submitted to App Store for Review</title>
		<link>https://blog.alexseifert.com/2026/03/03/scratchbook-submitted-to-app-store-for-review/</link>
					<comments>https://blog.alexseifert.com/2026/03/03/scratchbook-submitted-to-app-store-for-review/#respond</comments>
		
		<dc:creator><![CDATA[Alex Seifert]]></dc:creator>
		<pubDate>Tue, 03 Mar 2026 09:27:59 +0000</pubDate>
				<category><![CDATA[Haunted House Software]]></category>
		<category><![CDATA[News]]></category>
		<category><![CDATA[Programs and Tools]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[ScratchBook]]></category>
		<category><![CDATA[ScratchPad]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://blog.alexseifert.com/?p=12058</guid>

					<description><![CDATA[I'm excited to announce that I just submitted my very first app for review to the Mac App Store!]]></description>
										<content:encoded><![CDATA[<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-03-at-10.13.26.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="613" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-03-at-10.13.26.png?resize=1024%2C613&#038;ssl=1" alt="ScratchBook submitted for review to the Mac App Store" class="wp-image-12059" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-03-at-10.13.26.png?resize=1024%2C613&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-03-at-10.13.26.png?resize=400%2C239&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-03-at-10.13.26.png?resize=150%2C90&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-03-at-10.13.26.png?resize=768%2C460&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-03-at-10.13.26.png?resize=1536%2C920&amp;ssl=1 1536w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Screenshot-2026-03-03-at-10.13.26.png?w=1849&amp;ssl=1 1849w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">ScratchBook submitted for review to the Mac App Store</figcaption></figure>
</div>


<p class="wp-block-paragraph">I&#8217;m excited to announce that I just submitted my very first app for review to the Mac App Store! Hopefully, all goes well and ScratchBook will be available for free soon. Of course, I&#8217;ll post about it when it is available to download.</p>



<p class="wp-block-paragraph">As you may have noticed, I did decide to rename ScratchPad to ScratchBook in order to better distinguish it from another application in the Mac App Store called Scratchpad. I wrote about it in more detail <a href="https://blog.alexseifert.com/2026/02/23/do-i-need-to-rename-scratchpad/" data-type="link" data-id="https://blog.alexseifert.com/2026/02/23/do-i-need-to-rename-scratchpad/" target="_blank" rel="noreferrer noopener">here</a>.</p>



<p class="wp-block-paragraph">In my opinion, the most tedious part was preparing the images and the text for the application description. This is the description I went with:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Take notes, keep them organized. ScratchBook is a free, simple, easy-to-use, multi-page scratchpad application for macOS.</p>



<p class="wp-block-paragraph">ScratchBook is version 2.0 of a small application that used to be called ScratchPad. This version is a complete rewrite from the ground up using modern macOS technologies such as SwiftUI. It runs natively on both Intel and Apple Silicon Macs and supports modern macOS features such as Dark Mode.</p>



<p class="wp-block-paragraph">Features:</p>



<ul class="wp-block-list">
<li>Jot things down quickly and simply: no choosing folders, adding tags or any other distractions.</li>



<li>Multiple pages allow you to just add a new one when you need a new note. Your existing notes stay safe.</li>



<li>ScratchBook takes advantage of macOS&#8217;s built-in rich text capabilities. You can change fonts, colors, add images, make lists, and so much more.</li>



<li>Automatic saving.</li>



<li>Notes are saved in TextEdit-compatible .rtfd files which can be stored anywhere on your Mac or iCloud.</li>



<li>You can keep ScratchBook above all other windows for easier note-taking.</li>



<li>You can make ScratchBook as transparent as you&#8217;d like to see what&#8217;s behind it while taking notes.</li>



<li>Export individual notes as TextEdit-compatible .rtfd files.</li>



<li>Import all your notes from ScratchPad 1.0.</li>



<li>Full support for Liquid Glass, including both dark and light modes.</li>



<li>Free and open source.</li>
</ul>
</blockquote>



<p class="wp-block-paragraph">And, of course, here are the images:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Full-Screenshot.jpg?ssl=1"><img data-recalc-dims="1" height="640" width="1024" decoding="async" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Full-Screenshot-1024x640.jpg?resize=1024%2C640&#038;ssl=1" alt="ScratchBook screenshot" class="wp-image-12067"/></a><figcaption class="wp-element-caption">ScratchBook screenshot</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Toolbar.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="640" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Toolbar.jpg?resize=1024%2C640&#038;ssl=1" alt="ScratchBook toolbar screenshot" class="wp-image-12070" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Toolbar.jpg?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Toolbar.jpg?resize=400%2C250&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Toolbar.jpg?resize=150%2C94&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Toolbar.jpg?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Toolbar.jpg?w=1280&amp;ssl=1 1280w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">ScratchBook toolbar screenshot</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Settings.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="640" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Settings.jpg?resize=1024%2C640&#038;ssl=1" alt="ScratchBook settings screenshot" class="wp-image-12069" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Settings.jpg?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Settings.jpg?resize=400%2C250&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Settings.jpg?resize=150%2C94&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Settings.jpg?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Settings.jpg?w=1280&amp;ssl=1 1280w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">ScratchBook settings screenshot</figcaption></figure>
</div>

<div class="wp-block-image">
<figure class="aligncenter size-large"><a href="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Import-from-ScratchPad-1.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="640" src="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Import-from-ScratchPad-1.jpg?resize=1024%2C640&#038;ssl=1" alt="ScratchBook import screenshot" class="wp-image-12068" srcset="https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Import-from-ScratchPad-1.jpg?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Import-from-ScratchPad-1.jpg?resize=400%2C250&amp;ssl=1 400w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Import-from-ScratchPad-1.jpg?resize=150%2C94&amp;ssl=1 150w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Import-from-ScratchPad-1.jpg?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/blog.alexseifert.com/wp-content/uploads/2026/03/Import-from-ScratchPad-1.jpg?w=1280&amp;ssl=1 1280w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">ScratchBook import screenshot</figcaption></figure>
</div>


<p class="wp-block-paragraph">As a little easter egg, I used a section of the default wallpaper from Mac OS X Tiger as my background for these images in a nod to ScratchPad first being released for Tiger about twenty years ago.</p>



<p class="wp-block-paragraph">It will certainly be interesting to see whether the application will be accepted or not. As always, I will keep you up-to-date here on my blog.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.alexseifert.com/2026/03/03/scratchbook-submitted-to-app-store-for-review/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">12058</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Object Caching 27/119 objects using Memcached
Page Caching using Disk: Enhanced (Page is feed) 
Database Caching using Memcached

Served from: blog.alexseifert.com @ 2026-06-30 15:02:30 by W3 Total Cache
-->