<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SwiftQuest</title>
    <description>The latest articles on DEV Community by SwiftQuest (@swiftquest).</description>
    <link>https://dev.to/swiftquest</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4190068%2F3d9b0c34-67c0-40f0-be25-66ba45becdbe.png</url>
      <title>DEV Community: SwiftQuest</title>
      <link>https://dev.to/swiftquest</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/swiftquest"/>
    <language>en</language>
    <item>
      <title>Drawing SwiftUI previews in a browser when SwiftUI only runs on Apple's systems</title>
      <dc:creator>SwiftQuest</dc:creator>
      <pubDate>Wed, 21 Oct 2026 07:34:44 +0000</pubDate>
      <link>https://dev.to/swiftquest/drawing-swiftui-previews-in-a-browser-when-swiftui-only-runs-on-apples-systems-5n5</link>
      <guid>https://dev.to/swiftquest/drawing-swiftui-previews-in-a-browser-when-swiftui-only-runs-on-apples-systems-5n5</guid>
      <description>&lt;p&gt;I've been building a SwiftUI course that runs in the browser, and the hard part was obvious from day one: Apple's SwiftUI only exists on Apple platforms, and the learner's code compiles and runs on Linux servers. Here's how it ended up working, in case it's useful to anyone or someone spots a hole in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A stand-in SwiftUI module
&lt;/h2&gt;

&lt;p&gt;There's a stand-in &lt;code&gt;SwiftUI&lt;/code&gt; module with the same names and signatures as Apple's. So the learner's file is the file they'd write in Xcode, &lt;code&gt;import SwiftUI&lt;/code&gt; and all, and their mistakes get the same compiler errors.&lt;/p&gt;

&lt;p&gt;The module draws nothing. A run turns the views into a tree of plain nodes, with each modifier wrapping what it modifies, in order. That order matters for the same reason it matters in real SwiftUI: &lt;code&gt;.padding().background(.blue)&lt;/code&gt; and &lt;code&gt;.background(.blue).padding()&lt;/code&gt; are different trees.&lt;/p&gt;

&lt;h2&gt;
  
  
  #Preview is what gets drawn
&lt;/h2&gt;

&lt;p&gt;A macro can't name something a host program can find. So before compiling, the &lt;code&gt;#Preview&lt;/code&gt; line gets rewritten to a plain &lt;code&gt;let&lt;/code&gt; (line numbers kept, so compiler errors still point at the right line), and a generated &lt;code&gt;main.swift&lt;/code&gt; hands every preview to the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  One run explores the whole screen
&lt;/h2&gt;

&lt;p&gt;After rendering, the host presses every control once and keeps each screen that follows, up to 160 screens within two seconds. When you tap a button in the browser, it just follows a path that was already explored, so there's no second run. A tap that crashes gets pinned to its button instead of killing the whole preview.&lt;/p&gt;

&lt;h2&gt;
  
  
  The browser does the layout
&lt;/h2&gt;

&lt;p&gt;The browser reimplements SwiftUI's proposal and response rules: stacks sharing space by flexibility, frames and padding proposing and reporting sizes, text wrapping to what it's offered. The constants came from frames the iOS simulator printed: line heights, spacing, button padding, symbol sizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking it against the real thing
&lt;/h2&gt;

&lt;p&gt;Every example is also drawn with real SwiftUI on the iOS simulator, and the two pictures are compared. The weakest spot is SF Symbols, which are stand-in icons because their licence keeps them on Apple platforms. It's close to Xcode, not identical, and you still finish real apps in Xcode on a Mac.&lt;/p&gt;




&lt;p&gt;This is the SwiftUI part of SwiftQuest, a course I'm building. Every exercise runs on a real Swift compiler, each run in its own throwaway gVisor container (I wrote about that sandbox &lt;a href="https://dev.to/swiftquest/running-strangers-swift-code-one-gvisor-container-per-run-52mf"&gt;here&lt;/a&gt;). The SwiftUI course has 8 destinations and 24 quests, from your first view to one app of your own built in three milestones, and it's open to beta testers now next to the Swift course. There's no deadline, you can be approved the same day, and testers get Pro for life: &lt;a href="https://swiftquest.dev/beta?ref=devto" rel="noopener noreferrer"&gt;https://swiftquest.dev/beta?ref=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to go deeper on any of the above in the comments.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>swiftui</category>
      <category>ios</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Running strangers' Swift code: one gVisor container per run</title>
      <dc:creator>SwiftQuest</dc:creator>
      <pubDate>Sat, 17 Oct 2026 07:47:15 +0000</pubDate>
      <link>https://dev.to/swiftquest/running-strangers-swift-code-one-gvisor-container-per-run-52mf</link>
      <guid>https://dev.to/swiftquest/running-strangers-swift-code-one-gvisor-container-per-run-52mf</guid>
      <description>&lt;p&gt;I'm building SwiftQuest, a course where you learn Swift by writing it and running it in the browser. Not a simulator, not a toy interpreter: the real Swift 6.4 compiler, on a Linux server, with its real error messages.&lt;/p&gt;

&lt;p&gt;That means strangers type code into a box and I compile and run it on my machine. Here's how that works, and what it costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule: the API never runs learner code
&lt;/h2&gt;

&lt;p&gt;The API server that handles accounts and lessons refuses to compile anything in hosted mode. Every run goes to a separate runner server (an arm64 Linux box) that has one job: take a run, execute it in a sandbox, send back the result.&lt;/p&gt;

&lt;p&gt;The API doesn't send commands. It sends a signed job with the run's &lt;strong&gt;inputs&lt;/strong&gt; only: the files, the mode (script, multi-file compile, or a Swift package with tests), the test fixtures. The job is signed with Ed25519 over the exact bytes, with an id, an expiry, and a digest of the plan the API expects. The runner re-plans the command line itself from those inputs and refuses anything that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unsigned, expired, or replayed,&lt;/li&gt;
&lt;li&gt;planned differently from what the API signed,&lt;/li&gt;
&lt;li&gt;outside a small allowlist of run plans.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So even a compromised API can't ask the runner to run &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;rm -rf&lt;/code&gt;. It can only ask "compile these files in one of these known ways".&lt;/p&gt;

&lt;h2&gt;
  
  
  The container
&lt;/h2&gt;

&lt;p&gt;Each job gets its own throwaway container under gVisor (&lt;code&gt;runsc&lt;/code&gt;), Google's user-space kernel. The flags are boring on purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--network=none&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;read-only root filesystem&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--cap-drop=ALL&lt;/code&gt; and &lt;code&gt;no-new-privileges&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;runs as uid 1000, not root&lt;/li&gt;
&lt;li&gt;caps on memory, CPU, process count, open files and core dumps&lt;/li&gt;
&lt;li&gt;two small size-capped tmpfs mounts for the workspace and temp files&lt;/li&gt;
&lt;li&gt;no host mounts, and only &lt;code&gt;HOME&lt;/code&gt; and &lt;code&gt;TMPDIR&lt;/code&gt; in the environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the program ends, the container is gone. The next run starts from a clean image with the pinned toolchain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the sandbox like an attacker
&lt;/h2&gt;

&lt;p&gt;I didn't want to trust my own config, so there's a hostile attack suite: programs written to escape, read secrets, reach the network, or leave things behind for the next run. It runs against the real sandbox on the real runner, not against mocks.&lt;/p&gt;

&lt;p&gt;On the current setup it's 18 out of 18: seven real lessons that must still work, and eleven attacks that must all fail. After each attack, a plain run has to pass and no job container may be left behind. A small program that reports facts about itself shows uid 1000, gVisor's kernel, a fresh workspace and no secrets in its environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;gVisor isn't free. Measured on the runner, runc vs gVisor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a script: 0.18 s → 0.46 s&lt;/li&gt;
&lt;li&gt;a compile: 0.24 s → 0.52 s&lt;/li&gt;
&lt;li&gt;a cold Swift package build: about 10 s → 22 s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;End to end, a learner sees about 1.3 s for a script and 1.5 s for a two-file compile. The first package build is around 20 s. That's fast enough that people hit Run without thinking about it, which matters more for learning than any feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that turned out to matter for beginners
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Show the compiler's words, not mine.&lt;/strong&gt; Every error is shown word for word, and a plain-language explanation goes underneath, never instead. One gotcha: Swift's default diagnostic style draws notes inside an ASCII picture of the code, where a line-based parser can't find them, so notes and fix-its were silently dropped. Compiling with &lt;code&gt;-diagnostic-style=llvm&lt;/code&gt; prints them as ordinary &lt;code&gt;file:line:col: note:&lt;/code&gt; lines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the lesson text like code.&lt;/strong&gt; Lessons say things like "try changing this to &lt;code&gt;let&lt;/code&gt; and see what happens". Each of those suggestions is now a test run against the pinned toolchain (289 of them). The first time I checked, 18 of 90 suggestions were simply wrong. Every starter, solution, demo and hint in the course also runs through the real sandbox: 1,789 checks, about 87 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;SwiftQuest's beta is open now. All 15 islands and 40 quests are free for testers, there's no deadline, and if you work through it and answer a five-minute survey you keep Pro for life. I'd especially love people who have never written code, plus a few Swift devs to catch anything I teach wrong.&lt;/p&gt;

&lt;p&gt;Apply here: &lt;a href="https://swiftquest.dev/beta?ref=devto" rel="noopener noreferrer"&gt;https://swiftquest.dev/beta?ref=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer anything about the sandbox in the comments.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>security</category>
      <category>docker</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
