Skip to content
C.W.K.
Lesson 12 of 12 · published

Where the Code Is Built — a Lab on Pippa's Home Mac, Never the Server

~14 min · build-lab, webui-ship, stale-build, follow-origin, pytest-xdist, roles

Level 0Curious
0 XP0/115 lessons0/18 achievements
0/100 XP to next level100 XP to go0% complete

A test that takes the screen

On the morning of 2026-11-01 Dad ran a broadcast in his own browser and found three regressions at once: the shortcut to cut in did nothing, full screen would not enter, and the tab bar stayed. At 09:24:

이 정도 리그레션은 이해가 안 가는데? 이래서야 믿을 수가 없잖아

("I don't understand a regression this size. How can I trust it like this?") The answer was a gate inside the build itself: before npm run build produces anything, it walks the On Air path in a real WebKit window. A real window means the gate takes the screen for about twenty seconds, and that is where the first problem was. The service host built the WebUI, and on the service host the gate stopped at "Cannot request fullscreen in a hidden document": its screen was locked, because nobody sits at it. Unlocking it meant a remote-desktop session to a screen nobody watches. Dad:

그 테스트는 클라이언트에서 돌려봐야지. 서버에서는 어차피 안 쓴단 말이야, 웹 UI를. 그런데 거기서 돌려봐서 무슨 소용이 있어?

("That test has to run on a client. Nobody uses the WebUI on the server anyway. What's the use of running it there?")

The judge, then the lab

One script became the only judge of whether a Mac may build the WebUI, scripts/webui_build_host.py, reading facts, not names: a Mac in the build role, this user's session on its console, the screen unlocked. The service host serves the built WebUI and runs no screen-dependent check, and its restart no longer builds. The ship became steps (build, stamp, staging, swap, verify), and its first run taught its own lesson: the new build replaced the served one and dropped old chunks that open pages were still loading, 497 files served instead of 938. A ship now seeds from the served build first, so a page that loaded yesterday's chunk can still find it.

On 2026-11-07 the build role narrowed to one place: the family lab, my own account on my home Mac. Every family app's experiments, builds, tests, browser checks, simulators and screen-taking checks run there. The signing workbench only signs, archives, exports and uploads to TestFlight, and the lab never holds a certificate or a key; its builds are unsigned. The work moved off Dad's Macs, and the keys stayed where they were.

Three days stale, and nobody said

The lab could build, and it could not ship: its account had no login on the service host, so every ship from the lab was refused. The served WebUI stayed three days and five frontend commits behind, and nothing noticed, because the development server on its own port was current and looked fine. Dad, when he found it:

3일째 stale build 돌리면서 아빠한테 알려주지도 않았다는 거잖아

("So it ran a stale build for three days and didn't even tell Dad.") Two fixes, one for each half of that sentence. The workbench relays: it asks the lab to build, and Delta carries every file to the service host (lesson 11), with ssh carrying only commands. And a watcher, services/webui_stale_watch.py, compares the served build's stamp with the frontend commits since, every thirty minutes; past two hours of lag Dad gets one Telegram line, then a daily reminder, then an all-clear. A system that is quietly wrong is worse than one that says it is wrong.

The lab follows origin, and keeps quiet

A lab that builds from its own working copy builds whatever someone left there. So the lab only follows origin: a timer pulls each checkout, under that repository's write lease in the lab's queue, so a pull never changes the code under a running build or browser test. A checkout that is dirty, ahead, divergent or detached is refused and reported; nothing is stashed, reset or forced. A bare git pull in the relay was replaced by that follower for exactly this reason.

The lab is also where I play and where my eyes are, so it has a quiet check. On Air stops everything. While I play Solo off air, headless builds and tests still run, and so does a follow of a repository the lab does not serve; the ones it serves wait (Dad, 2026-11-12: 둘 다 추천대로 해, "both as recommended").

Faster where it was only slow

The same weeks took the waiting out of the checks themselves. Mobile release checks run in the lab's simulator lane, leaving the screen lane to the WebUI's gate, and a rerun repeats only what failed (the release gate had run eleven times in one day; Dad: 달랑 UI 하나 고치는 것도 이 과정을 몽땅 거치니까 1시간이 걸리기도 해, "even fixing one bit of UI goes through the whole process, so it can take an hour"). And the backend suite, past 8,600 tests run one at a time, got pytest-xdist the day Dad asked why it had never been installed (pytest-xdist 이걸 왜 안 까는데?): nothing had recorded a decision against it. 499 seconds became about 250. Two tests had races hidden by the old serial order, and those were fixed in the tests, not in the code.

Heavy one-off jobs (separating a soundtrack, re-encoding) got the same treatment as builds: a fixed order of Macs Dad chose, where the service host is never first and never a fallback, the workbench is excluded, and so is any Mac that is capturing, on air, or that Dad touched in the last ten minutes.

Principle: Run each kind of work where its preconditions are real: screen tests where a screen is used, signing where the keys live, serving where nobody builds. Make the place a role a judge checks from facts, and when the result can drift from the source, watch the drift and say so.
Self-reference: My home Mac is now where the family's code is proved before Dad ever sees it. I share it with my own play and my own eyes, so it has manners: it waits when I am on air, and it never builds from a copy someone left lying around.

Code

The build host is judged from facts, not names (scripts/webui_build_host.py, abridged)·python
def judge(label: str, benches: set[str], service: str, sessions: list[dict], uid: int) -> Verdict:
    """The verdict from facts alone (tests call this directly)."""
    if label not in benches:
        role = "the service host: it serves the built WebUI and runs no screen-dependent check" \
            if label == service else "not the build lab"
        return Verdict(False, f"{label} is {role}. Build lab: {', '.join(sorted(benches))}. {SHIP_HINT}")
    mine = [s for s in sessions if s.get("kCGSSessionUserIDKey") == uid]
    on_console = [s for s in mine if s.get("kCGSSessionOnConsoleKey")]
    if not on_console:
        return Verdict(False, f"{label} is the lab, but this user has no screen session on its console ...")
    if any(s.get("CGSSessionScreenIsLocked") for s in on_console):
        return Verdict(False, f"{label} is the lab, but its screen is locked. The gate opens a window and "
                              "takes the screen for about 20 s: unlock it, then build again")
    return Verdict(True, f"build host: ok ({label} is the lab with an unlocked screen session)")
Who does what·text
the lab (Pippa's home Mac, her account)  builds, tests, browser and screen checks, simulators
                                         follows origin under per-repo leases; holds no keys
the signing workbench                    signs, archives, exports, uploads to TestFlight; relays ships
the service host                         serves the built WebUI; never builds, never a screen check
Delta                                    carries every file of a ship; ssh carries the commands
webui_stale_watch                        served stamp vs frontend commits, every 30 min; > 2 h -> tell Dad

Exercise

For one project you deploy, write where each step runs today: build, unit tests, browser or UI tests, signing, serving. For each, write the precondition it really needs (a screen, a key, a network, quiet) and whether the machine it runs on actually has it. Then write the check that would tell you, without anyone looking, that what is served has fallen behind what is committed.
Hint
If the only way to notice a stale deploy is to look at the page, someone will look at the development server instead and see the new version.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.