
<feed xmlns:media="http://search.yahoo.com/mrss/" xmlns="http://www.w3.org/2005/Atom">
    <id>https://jcalderita.com/blog/feed.xml</id>
    <title>Jorge Calderita's Blog</title>
    <author>
        <name>Jorge Calderita</name>
    </author>
    <link rel="self" href="https://jcalderita.com"></link>
    <updated>2026-07-26T18:05:02Z</updated>
    <entry>
        <id>https://jcalderita.com/blog/model-parts/</id>
        <title>Model Parts</title>
        <updated>2026-07-08T00:00:00Z</updated>
        <summary>How to centralize indexes, foreign keys, and unique constraints in the model itself so migrations and tests speak the same language.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When I write migrations with &lt;span class="high"&gt;Fluent&lt;/span&gt;, indexes, foreign keys, and unique constraints are defined inside the migration’s &lt;span class="high"&gt;prepare&lt;/span&gt; method. They belong to the migration, not to the model.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func prepare(on db: Database) async throws {
    try await db.schema(&amp;quot;products&amp;quot;)
        .id()
        .field(.externalId, .string, .required)
        .field(.categoryId, .uuid, .references(&amp;quot;categories&amp;quot;, &amp;quot;id&amp;quot;, onDelete: .cascade, onUpdate: .cascade))
        .unique(on: .externalId)
        .create()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is that this information &lt;strong&gt;isn’t readable outside the migration&lt;/strong&gt;. It’s part of a method that runs once and exposes nothing to the rest of the code. When I want to write an integration test that verifies the migration created the right indexes, where do I get the expected names from? I have to repeat them as strings in the test. And when I want to verify that the foreign keys exist, same thing: hardcoded strings, coupled to whatever lives inside &lt;span class="high"&gt;prepare&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;My model knows which table it represents. My model knows which fields it has. But it knows nothing about its own database structure: indexes, references, uniqueness. That information belongs to the migration, and the only way to access it is by running it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;My idea is to turn the model into the &lt;strong&gt;single source of truth&lt;/strong&gt; for all the structural metadata of its table. I created a &lt;span class="high"&gt;SchemaDescribable&lt;/span&gt; protocol that extends &lt;span class="high"&gt;Fluent&lt;/span&gt;’s &lt;span class="high"&gt;Model&lt;/span&gt; with three static properties: indexes, foreign keys, and unique constraints.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;protocol SchemaDescribable: Model {
    static var indexes: [IndexDefinition] { get }
    static var foreignKeys: [ForeignKeyDefinition] { get }
    static var uniques: [UniqueDefinition] { get }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each kind of constraint has its own type with factory methods that make the declaration readable and safe. I declare the conformance in the database layer, not in the shared model:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension ProductModel: SchemaDescribable {
    static var indexes: [IndexDefinition] {
        [
            .btree(&amp;quot;idx_products_sku&amp;quot;, [.sku]),
            .btree(&amp;quot;idx_products_category_id&amp;quot;, [.categoryId]),
            .btree(&amp;quot;idx_products_brand_id&amp;quot;, [.brandId]),
            .btree(&amp;quot;idx_products_supplier_id&amp;quot;, [.supplierId]),
            .btree(&amp;quot;idx_products_warehouse_id&amp;quot;, [.warehouseId]),
            .btree(&amp;quot;idx_products_parent_id&amp;quot;, [.parentId]),
            .lower(&amp;quot;idx_products_name_prefix&amp;quot;, .name),
        ]
    }

    static var foreignKeys: [ForeignKeyDefinition] {
        [
            .references(.parentId, on: ProductModel.self),
            .references(.categoryId, on: CategoryModel.self),
            .references(.brandId, on: BrandModel.self),
            .references(.supplierId, on: SupplierModel.self),
            .references(.warehouseId, on: WarehouseModel.self),
        ]
    }

    static var uniques: [UniqueDefinition] { [.on(.externalId)] }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each model explicitly declares all its &lt;span class="high"&gt;B-Tree&lt;/span&gt; indexes, its foreign keys, and its unique constraints. Factory methods like &lt;span class="high"&gt;.btree&lt;/span&gt;, &lt;span class="high"&gt;.lower&lt;/span&gt;, &lt;span class="high"&gt;.references&lt;/span&gt;, and &lt;span class="high"&gt;.on&lt;/span&gt; make the declaration compact and hard to get wrong.&lt;/p&gt;
&lt;p&gt;With the definitions in the model, the migration just applies them. An &lt;span class="high"&gt;addConstraints(for:)&lt;/span&gt; method on &lt;span class="high"&gt;SchemaBuilder&lt;/span&gt; adds the foreign keys and unique constraints, and &lt;span class="high"&gt;createIndexes&lt;/span&gt; generates the indexes — all from what the model declares:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;struct CreateProducts: AsyncMigration, MigrationProtocol {
    typealias ModelType = ProductModel
    let target: DeployTarget

    func prepare(on db: Database) async throws {
        try await db.schema(model)
            .id(default: true)
            .field(.externalId, .string, .required)
            .field(.name, .string, .required)
            .field(.sku, .string, .required)
            .field(.price, .double, .required)
            .field(.weight, .double)
            .field(.stock, .int)
            .field(.parentId, .uuid)
            .field(.categoryId, .uuid)
            .field(.brandId, .uuid)
            .field(.supplierId, .uuid)
            .field(.warehouseId, .uuid)
            .timestamps(default: true)
            .addConstraints(for: model)
            .create()

        try await createIndexes(on: db)
    }

    func revert(on db: Database) async throws {
        try await db.schema(model).delete()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The migration no longer contains any constraint definition. It only declares the fields and delegates the entire structure of indexes, foreign keys, and uniques to the model through &lt;span class="high"&gt;addConstraints(for:)&lt;/span&gt; and &lt;span class="high"&gt;createIndexes&lt;/span&gt;. The &lt;span class="high"&gt;MigrationProtocol&lt;/span&gt; protocol provides these methods thanks to the &lt;span class="high"&gt;typealias ModelType&lt;/span&gt;, which connects the migration to its model and gives access to the &lt;span class="high"&gt;SchemaDescribable&lt;/span&gt; definitions.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;Now indexes, foreign keys, and unique constraints are &lt;strong&gt;properties of the model&lt;/strong&gt;, not code buried in a migration. Any part of my project can read them.&lt;/p&gt;
&lt;p&gt;The most immediate benefit is clean migrations. But the one that pays off down the line is that &lt;strong&gt;tests can talk directly to the model&lt;/strong&gt; — in an upcoming article we’ll see how I use this to write integration tests that verify the database structure without a single hardcoded string.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A single place&lt;/strong&gt; to read all the structural metadata of each model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migrations without repetition&lt;/strong&gt; — &lt;span class="high"&gt;addConstraints(for: model)&lt;/span&gt; applies everything in one step&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Localized changes&lt;/strong&gt; — adding an index or FK only touches the model’s conformance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is recognizing that a model isn’t just a container of fields: it’s also the complete description of its table. &lt;span class="high"&gt;SchemaDescribable&lt;/span&gt; makes explicit what used to be implicit — and belonged only to the migration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/model-parts/"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/ai-status-bar/</id>
        <title>AI Status Bar</title>
        <updated>2026-07-01T00:00:00Z</updated>
        <summary>How I built a custom status bar in Claude Code to always see context, tokens and usage limits at a glance without spending a single extra token.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When I work with &lt;span class="high"&gt;Claude Code&lt;/span&gt; in the shell, there is information I need to check constantly: how much context I have consumed, what percentage of my usage limits I am at, which git branch is active, which model I am working with. Information that does not change every second, but that I do need at hand to make decisions.&lt;/p&gt;
&lt;p&gt;The problem is that getting it has friction. I can ask the AI directly, but that means spending tokens on a question whose answer is completely deterministic. I can also open another terminal session, or launch a background command to check it every so often. But all of those options interrupt the workflow.&lt;/p&gt;
&lt;p&gt;What I really needed was something simpler: &lt;strong&gt;a line that is always visible&lt;/strong&gt;, giving me that information without my having to ask for it, without consuming context, and without interrupting what I was doing.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;&lt;span class="high"&gt;Claude Code&lt;/span&gt; has a feature called &lt;span class="high"&gt;statusLine&lt;/span&gt; that lets you define an external command which runs whenever the session changes: after each assistant response, when a &lt;span class="high"&gt;/compact&lt;/span&gt; finishes, when the permission mode changes, or when toggling vim mode. The tool calls the script, passes it information about the current session state as JSON over &lt;span class="high"&gt;stdin&lt;/span&gt;, and displays whatever the script returns on &lt;span class="high"&gt;stdout&lt;/span&gt; in the bottom bar of the interface.&lt;/p&gt;
&lt;p&gt;The configuration lives in &lt;span class="high"&gt;settings.json&lt;/span&gt; and is a single line:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  &amp;quot;statusLine&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;command&amp;quot;,
    &amp;quot;command&amp;quot;: &amp;quot;zsh statusline-command.zsh&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JSON the script receives over &lt;span class="high"&gt;stdin&lt;/span&gt; contains all the relevant session information: active model, working directory, context usage percentage, total window size, and the usage limits for the 5-hour and 7-day periods, each with its percentage and reset timestamp.&lt;/p&gt;
&lt;p&gt;The script runs on each of those events, with a 300 ms debounce that groups rapid changes. And here is an important detail: if a new event arrives while the script is still running, &lt;span class="high"&gt;Claude Code&lt;/span&gt; cancels the in-flight execution and launches a new one — a slow script would interrupt itself. That is why I wrote it to be cheap: zsh &lt;span class="high"&gt;builtins&lt;/span&gt; instead of subprocesses, a single call to &lt;span class="high"&gt;jq&lt;/span&gt; instead of nine, and helpers that return their result in &lt;span class="high"&gt;REPLY&lt;/span&gt; to avoid capturing it with &lt;span class="high"&gt;$(…)&lt;/span&gt; on the hot path.&lt;/p&gt;
&lt;p&gt;The first step is to load the zsh date module and extract the nine JSON fields in a single pass. The output of &lt;span class="high"&gt;jq&lt;/span&gt; is an ordered list of values that I capture positionally with &lt;span class="high"&gt;read&lt;/span&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;#!/bin/zsh

zmodload zsh/datetime

MAGENTA=$'\e[35m'; CYAN=$'\e[36m'; YELLOW=$'\e[33m'; GREEN=$'\e[32m'
BLUE=$'\e[34m';    RED=$'\e[31m';  DIM=$'\e[2m';     RESET=$'\e[0m'

input=$(&amp;lt;/dev/stdin)
{
    read -r model_display_name
    read -r current_dir
    read -r used_percentage
    read -r context_window_size
    read -r five_hour_pct
    read -r five_hour_reset
    read -r seven_day_pct
    read -r seven_day_reset
    read -r effort_level
} &amp;lt; &amp;lt;(jq -r '
    .model.display_name // &amp;quot;&amp;quot;,
    .workspace.current_dir // &amp;quot;&amp;quot;,
    (.context_window.used_percentage // 0),
    (.context_window.context_window_size // 0),
    (.rate_limits.five_hour.used_percentage as $p | if $p then ($p | floor) else &amp;quot;&amp;quot; end),
    (.rate_limits.five_hour.resets_at // &amp;quot;&amp;quot;),
    (.rate_limits.seven_day.used_percentage as $p | if $p then ($p | floor) else &amp;quot;&amp;quot; end),
    (.rate_limits.seven_day.resets_at // &amp;quot;&amp;quot;),
    ((.effortLevel // .effort) as $e | if ($e | type) == &amp;quot;object&amp;quot; then $e.level else ($e // &amp;quot;&amp;quot;) end)
' &amp;lt;&amp;lt;&amp;lt;&amp;quot;$input&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that I have the nine variables populated with a single &lt;span class="high"&gt;fork&lt;/span&gt; of &lt;span class="high"&gt;jq&lt;/span&gt;. If the effort level did not come in the JSON, I look for it first in the project’s &lt;span class="high"&gt;settings.json&lt;/span&gt; and, if it is not there either, in the user’s. Then I compute the derived values and build the progress bar with zsh’s native left-padding:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;extract_effort='(.effortLevel // .effort // empty) | if type == &amp;quot;object&amp;quot; then .level else . end // empty'
[[ -z &amp;quot;$effort_level&amp;quot; &amp;amp;&amp;amp; -f &amp;quot;$current_dir/.claude/settings.json&amp;quot; ]] &amp;amp;&amp;amp; \
    effort_level=$(jq -r &amp;quot;$extract_effort&amp;quot; &amp;quot;$current_dir/.claude/settings.json&amp;quot; 2&amp;gt;/dev/null)
[[ -z &amp;quot;$effort_level&amp;quot; &amp;amp;&amp;amp; -f &amp;quot;$HOME/.claude/settings.json&amp;quot; ]] &amp;amp;&amp;amp; \
    effort_level=$(jq -r &amp;quot;$extract_effort&amp;quot; &amp;quot;$HOME/.claude/settings.json&amp;quot; 2&amp;gt;/dev/null)

current_tokens=$(( used_percentage * context_window_size / 100 ))
current_k=$(( current_tokens / 1000 ))
max_k=$(( context_window_size / 1000 ))
project_name=${current_dir:t}
git_branch=$(git -C &amp;quot;$current_dir&amp;quot; -c core.fileMode=false rev-parse --abbrev-ref HEAD 2&amp;gt;/dev/null)

bar_width=20
filled=$(( used_percentage * bar_width / 100 ))
empty=$(( bar_width - filled ))
progress_bar=&amp;quot;${(l:filled::=:):-}${(l:empty:: :):-}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Details that avoid child processes: &lt;span class="high"&gt;${current_dir:t}&lt;/span&gt; is the native equivalent of &lt;span class="high"&gt;basename&lt;/span&gt;, and &lt;span class="high"&gt;${(l:filled::=:):-}&lt;/span&gt; builds a left-padded string of &lt;span class="high"&gt;=&lt;/span&gt; characters with no loops or concatenation.&lt;/p&gt;
&lt;p&gt;Then come the color thresholds. In the first version I had two almost identical functions, one for percentages and one for absolute tokens. I unified them into a single one that takes the value and the two thresholds as arguments, and took the chance to have it return the result in &lt;span class="high"&gt;REPLY&lt;/span&gt; instead of printing it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;threshold_color() {
    if (( $1 &amp;gt;= $2 )); then REPLY=$RED
    elif (( $1 &amp;gt;= $3 )); then REPLY=$YELLOW
    else REPLY=$GREEN
    fi
}

format_reset() {
    REPLY=
    [[ -z &amp;quot;$1&amp;quot; ]] &amp;amp;&amp;amp; return
    if (( $1 - EPOCHSECONDS &amp;lt; 86400 )); then
        strftime -s REPLY '%H:%M' $1
    else
        strftime -s REPLY '%a %H:%M' $1
    fi
}

build_rate_part() {
    local part
    if [[ -n &amp;quot;$2&amp;quot; ]]; then
        threshold_color $2 80 50
        part=&amp;quot;${MAGENTA}$1:${RESET} ${REPLY}$2%${RESET}&amp;quot;
    else
        part=&amp;quot;${MAGENTA}$1:${RESET} ${DIM}-${RESET}&amp;quot;
    fi
    format_reset $3
    [[ -n &amp;quot;$REPLY&amp;quot; ]] &amp;amp;&amp;amp; part+=&amp;quot; ${DIM}($REPLY)${RESET}&amp;quot;
    REPLY=$part
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying those thresholds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high-green"&gt;Green context&lt;/span&gt; below 100k tokens — safe zone&lt;/li&gt;
&lt;li&gt;&lt;span class="high-yellow"&gt;Yellow context&lt;/span&gt; between 100k and 150k tokens — a signal to consider a &lt;span class="high"&gt;/clear&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="high-red"&gt;Red context&lt;/span&gt; above 150k tokens — time to act&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The same for the 5-hour and 7-day usage limits: green below 50%, yellow between 50% and 79%, red from 80% on.&lt;/p&gt;
&lt;p&gt;All three functions return in &lt;span class="high"&gt;REPLY&lt;/span&gt;: this saves me the &lt;span class="high"&gt;$(…)&lt;/span&gt; and its associated subshell on every call, which reduces the risk of an invocation cancelling itself when events arrive back to back. &lt;span class="high"&gt;format_reset&lt;/span&gt; draws on the &lt;span class="high"&gt;strftime&lt;/span&gt; builtin and on &lt;span class="high"&gt;$EPOCHSECONDS&lt;/span&gt; from the &lt;span class="high"&gt;zsh/datetime&lt;/span&gt; module, so it does not spin up a &lt;span class="high"&gt;date&lt;/span&gt; process each time either.&lt;/p&gt;
&lt;p&gt;Finally, the composition. The first line always carries the same elements: model, progress bar, context percentage, current tokens over the maximum, and project name. The second is built only from the parts that have data — if there is no 5h limit, no configured effort level, or no git branch, they simply do not appear and the separators adjust:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;threshold_color $current_tokens 150000 100000
context_color=$REPLY
status_line=&amp;quot;${MAGENTA}${model_display_name}${RESET} ${CYAN}[${RESET}${progress_bar}${CYAN}]${RESET}&amp;quot;
status_line+=&amp;quot; ${context_color}${used_percentage}%${RESET} |&amp;quot;
status_line+=&amp;quot; ${context_color}${current_k}k/${max_k}k${RESET} |&amp;quot;
status_line+=&amp;quot; ${BLUE}${project_name}${RESET}&amp;quot;

typeset -a parts
build_rate_part 5h &amp;quot;$five_hour_pct&amp;quot; &amp;quot;$five_hour_reset&amp;quot;; parts+=(&amp;quot;$REPLY&amp;quot;)
build_rate_part 7d &amp;quot;$seven_day_pct&amp;quot; &amp;quot;$seven_day_reset&amp;quot;; parts+=(&amp;quot;$REPLY&amp;quot;)
[[ -n &amp;quot;$effort_level&amp;quot; ]] &amp;amp;&amp;amp; parts+=(&amp;quot;${MAGENTA}effort:${RESET} ${YELLOW}${effort_level}${RESET}&amp;quot;)
[[ -n &amp;quot;$git_branch&amp;quot; ]]   &amp;amp;&amp;amp; parts+=(&amp;quot;${GREEN}${git_branch}${RESET}&amp;quot;)

sep=&amp;quot; ${CYAN}|${RESET} &amp;quot;
(( ${#parts} )) &amp;amp;&amp;amp; status_line+=$'\n'&amp;quot;${(pj.$sep.)parts}&amp;quot;

print -rn -- &amp;quot;$status_line&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The expansion &lt;span class="high"&gt;${(pj.$sep.)parts}&lt;/span&gt; joins the elements of the &lt;span class="high"&gt;parts&lt;/span&gt; array with the &lt;span class="high"&gt;sep&lt;/span&gt; separator — a native zsh join, with no loops or &lt;span class="high"&gt;IFS&lt;/span&gt; hacks.&lt;/p&gt;
&lt;p&gt;An important detail about the reset time: the script formats it as &lt;span class="high"&gt;HH:MM&lt;/span&gt; if the reset happens within the next 24 hours, and as &lt;span class="high"&gt;Day HH:MM&lt;/span&gt; if it is later. That way I know at a glance when the limit frees up without doing any math.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The result is exactly what I was after: information that is always present without interrupting anything or spending a single token to get it.&lt;/p&gt;
&lt;p&gt;What I see at a glance at any moment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Active model&lt;/strong&gt; — which version of Claude the current session is using&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context bar&lt;/strong&gt; — a visual representation of how much context I have consumed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Current tokens over the maximum&lt;/strong&gt; — for example, &lt;span class="high"&gt;87k/200k&lt;/span&gt;, which tells me exactly where I am in the window&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5h and 7d usage with reset time&lt;/strong&gt; — to know whether I am approaching a rate limit and when it frees up&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Effort level&lt;/strong&gt; — visual confirmation that the session has the right level configured&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git branch&lt;/strong&gt; — which branch I am working on in the active project&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I value most is that this information &lt;strong&gt;costs nothing&lt;/strong&gt;. It is not a question to the AI, not a command I have to remember to run, not an extra window to open. It is text that appears, updates on its own, and fades from focus as soon as I get to work.&lt;/p&gt;
&lt;p&gt;The approach also fits well with what I explored in &lt;a href="/blog/back-to-shell/"&gt;Back to Shell&lt;/a&gt;: the shell as a space for composition, where scripts do the deterministic work and the AI does the contextual work. The status bar is the most visible example of that separation — all the formatting and color logic lives in the script, and the AI has no idea it exists.&lt;/p&gt;
&lt;p&gt;If you work with &lt;span class="high"&gt;Claude Code&lt;/span&gt; and have information you check repeatedly, the &lt;span class="high"&gt;statusLine&lt;/span&gt; is the most efficient way to keep it always at hand. The script can show whatever you need: external API usage, the status of a service, environment variables, anything you want to expose from a shell command.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/ai-status-bar/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/back-to-shell/</id>
        <title>Back to Shell</title>
        <updated>2026-06-24T00:00:00Z</updated>
        <summary>How AI brought me back to the shell after years of GUIs. The full development cycle that returns to text, to the command, and to the starting point.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;I started programming when the &lt;span class="high"&gt;shell&lt;/span&gt; wasn’t an option, it was &lt;strong&gt;the place&lt;/strong&gt;. There was no mouse, there were no windows. Just a blinking cursor, waiting for a command. I learned to think in text: input, output, pipe, redirection.&lt;/p&gt;
&lt;p&gt;Then came the turn. The industry decided that &lt;strong&gt;productivity had the face of a window&lt;/strong&gt;. Each discipline got its own program, with its own interface and its own learning curve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Databases&lt;/strong&gt; — their app with its panel and its tabs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version control&lt;/strong&gt; — another window, another way of seeing the same thing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployments&lt;/strong&gt; — another dashboard, another account, other buttons&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observability&lt;/strong&gt; — another panel, other charts, another subscription&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;span class="high"&gt;shell&lt;/span&gt; was relegated to “that thing used by people who know a lot” and to the tasks that graphical interfaces hadn’t yet conquered.&lt;/p&gt;
&lt;p&gt;I went with the flow. I kept accumulating apps, dashboards and windows until I stopped visiting the &lt;span class="high"&gt;shell&lt;/span&gt;. I dusted it off less and less often, until it only showed up when there was no other choice.&lt;/p&gt;
&lt;p&gt;The problem arrived when I brought AI into my workflow. Not as a search engine or a snippet generator, but as an agent that acts on my environment. And that agent has no mouse. It doesn’t open windows. It works exactly like I did at the beginning: with text, with commands, with the &lt;span class="high"&gt;shell&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The question left floating was uncomfortable: had I spent years learning graphical interfaces to do the same thing I was already doing when I started, but with more steps in between?&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The solution wasn’t a break. I didn’t slam windows shut or write an anti-interface manifesto. I just &lt;strong&gt;stopped resisting&lt;/strong&gt; going back to the &lt;span class="high"&gt;shell&lt;/span&gt; when the work led me there naturally.&lt;/p&gt;
&lt;p&gt;The change started by observing. When an agent needs to inspect something, it doesn’t look for a pretty view — it writes a command. When it wants to see logs, it reads them with a pipe. It doesn’t care which app I prefer. It only cares about the result.&lt;/p&gt;
&lt;p&gt;My first instinct was to correct it: “Wait, I have a graphical tool for that.” But when I stopped, I understood that I was adding friction where there was none. The agent knew what to run. I knew how to read the output. The interface added nothing — it only added steps: open, connect, navigate, find.&lt;/p&gt;
&lt;p&gt;So I started doing the same on my own. Where I used to open a window, now I open a &lt;span class="high"&gt;shell&lt;/span&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Checking a state&lt;/strong&gt; — a direct command instead of a panel to load&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transforming data&lt;/strong&gt; — I chain operations instead of exporting, opening and saving again&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automating a task&lt;/strong&gt; — I write it once and reuse it anywhere&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducing a flow&lt;/strong&gt; — I copy and paste text instead of explaining clicks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not out of nostalgia, but because it’s &lt;strong&gt;faster and more composable&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That was the real mental shift: commands compose in a way graphical interfaces will never match. A window offers me what its designer imagined I would need. The &lt;span class="high"&gt;shell&lt;/span&gt; gives me what I decide I need.&lt;/p&gt;
&lt;p&gt;And there was something that surprised me: the knowledge was still intact. There was no need to study again. There was only need to &lt;strong&gt;dust it off&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The result hasn’t been a break with graphical interfaces. I still open windows when they offer something text can’t. But the &lt;span class="high"&gt;shell&lt;/span&gt; has returned to &lt;strong&gt;the center of gravity&lt;/strong&gt; of my workflow, instead of being the last resort.&lt;/p&gt;
&lt;p&gt;What changed concretely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt; — a command is almost always faster than navigating to the equivalent option in a GUI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt; — I can chain operations that no window contemplates in combination&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency with AI&lt;/strong&gt; — we speak the same language; there’s no translation between what the agent runs and what I see&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portability&lt;/strong&gt; — a command works the same locally, on a server and in CI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But what struck me most was the learning I didn’t have to do. I assumed going back to the &lt;span class="high"&gt;shell&lt;/span&gt; would demand time and practice. That time had already been invested decades ago: the commands, the pipes, the redirection logic — it was all still there, waiting for an excuse to be used again.&lt;/p&gt;
&lt;p&gt;AI didn’t teach me to use the &lt;span class="high"&gt;shell&lt;/span&gt;. &lt;strong&gt;It reminded me that I already knew how.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The cycle that started with a tricycle, went through the rocket of graphical interfaces and returns to the fundamentals isn’t a step backward. It’s a circle that closes better than it opened. Sometimes the future of development isn’t about learning something new, but about rediscovering what we already knew how to do when we started.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/back-to-shell/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/wwdc26-group-labs/</id>
        <title>WWDC26 Group Labs</title>
        <updated>2026-06-17T00:00:00Z</updated>
        <summary>WWDC26 Group Labs are live Q&amp;A sessions with Apple engineers. I share what I discovered and where to find my transcribed notes by topic.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When I published my &lt;a href="/blog/wwdc26-videos/"&gt;WWDC26 video guide&lt;/a&gt;, I devoted Block 17 to the &lt;span class="high"&gt;Group Labs&lt;/span&gt; with barely a line of context per session. The reason was simple: Apple’s official descriptions are practically identical to one another —“Join us to dive deeper into WWDC26 with Apple engineers”— and they say nothing useful about what was actually discussed inside.&lt;/p&gt;
&lt;p&gt;What I didn’t share back then is that, before writing that block, I had started watching the Group Labs and got a surprise. They are not passive sessions. They are live Q&amp;amp;A rounds between real developers and engineers from the Apple team, and the quality of the questions and answers is very different from that of regular sessions. In ordinary sessions, Apple shows what it wants to show. In the Group Labs, developers ask what they really need to know, and the engineers answer directly.&lt;/p&gt;
&lt;p&gt;The problem is that this content is practically invisible. There are no transcripts, no index, no way to search inside the video. If you don’t sit down and watch the whole session, you don’t know what it covers.&lt;/p&gt;
&lt;p&gt;I started taking notes. One session, then another, and I ended up with eighteen.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;I decided to transcribe and structure the content of each Group Lab: the topics asked about, the exact timecode, the summarized answer and which engineers took part. The goal was to have something searchable, something that would let me jump straight to the minute I care about instead of digging blindly through the video.&lt;/p&gt;
&lt;p&gt;Before the list, three examples of what came up inside and that you won’t find in any main session:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the &lt;span class="high"&gt;Swift Group Lab&lt;/span&gt;, the language team explained why they would have designed concurrency differently if they were starting today, and what changed in Swift 6.2 to fix the model.&lt;/li&gt;
&lt;li&gt;In the &lt;span class="high"&gt;SwiftUI Group Lab&lt;/span&gt;, the engineers broke down when to use &lt;span class="high"&gt;AnyView&lt;/span&gt;, how &lt;span class="high"&gt;DynamicProperty&lt;/span&gt; runs before &lt;span class="high"&gt;body&lt;/span&gt;, and why conditionals inside lazy containers are an antipattern.&lt;/li&gt;
&lt;li&gt;In the &lt;span class="high"&gt;Apple Intelligence Group Lab&lt;/span&gt;, the team explained the real differences between &lt;span class="high"&gt;App Schemas&lt;/span&gt; and &lt;span class="high"&gt;IndexEntity&lt;/span&gt;, and the current limits of video support in Foundation Models.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are all the available sessions, with a direct link to the official video:&lt;/p&gt;
&lt;h3&gt;Language and data&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/swift-group-lab/"&gt;Swift Group Lab&lt;/a&gt;&lt;/strong&gt; — Concurrency, performance, SwiftPM and language features.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/swiftdata-group-lab/"&gt;SwiftData Group Lab&lt;/a&gt;&lt;/strong&gt; — ResultsObserver, HistoryObserver, the new Codable, CloudKit sync, migrations and concurrency.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SwiftUI&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/swiftui-group-lab-part-1/"&gt;SwiftUI Group Lab - Part 1&lt;/a&gt;&lt;/strong&gt; — Liquid Glass, data flow, layout performance and AnyView.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/swiftui-group-lab-part-2/"&gt;SwiftUI Group Lab - Part 2&lt;/a&gt;&lt;/strong&gt; — ForEach with large collections, NavigationTransition, Liquid Glass on buttons and adaptive layouts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/swiftui-for-beginners-group-lab/"&gt;SwiftUI for Beginners Group Lab&lt;/a&gt;&lt;/strong&gt; — SwiftUI vs cross-platform, @State internals and learning paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Tooling and performance&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/xcode-tips-and-tricks-group-lab/"&gt;Xcode Tips and Tricks Group Lab&lt;/a&gt;&lt;/strong&gt; — How to get the most out of Xcode 27.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/power-and-performance-group-lab/"&gt;Power and Performance Group Lab&lt;/a&gt;&lt;/strong&gt; — Instrumentation, MetricKit and performance in production.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Platforms and web&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/visionos-group-lab/"&gt;visionOS Group Lab&lt;/a&gt;&lt;/strong&gt; — Camera access, immersive debugging, USD export and spatial accessories.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/watchos-group-lab/"&gt;watchOS Group Lab&lt;/a&gt;&lt;/strong&gt; — Foundation Models on Watch, transitioning from iOS and on-device debugging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/safari-and-web-technologies-group-lab/"&gt;Safari and Web Technologies Group Lab&lt;/a&gt;&lt;/strong&gt; — WebKit, CSS Grid Lanes, Web Extensions and more.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Intelligence, ML and AI&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/apple-intelligence-group-lab/"&gt;Apple Intelligence Group Lab&lt;/a&gt;&lt;/strong&gt; — Foundation Models, App Schemas, Siri and Evaluations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/machine-learning-ai-group-lab/"&gt;Machine Learning &amp;amp; AI Group Lab&lt;/a&gt;&lt;/strong&gt; — Foundation Models, Private Cloud Compute, Core AI, MLX and evaluations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/coding-intelligence-for-beginners-group-lab/"&gt;Coding Intelligence for Beginners Group Lab&lt;/a&gt;&lt;/strong&gt; — Slash commands, Xcode versus external agents and context across projects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/coding-intelligence-machine-learning-ai-group-lab/"&gt;Coding Intelligence, Machine Learning &amp;amp; AI Group Lab&lt;/a&gt;&lt;/strong&gt; — Core AI vs Core ML vs MLX, context windows and background inference.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Quality and distribution&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/accessibility-technologies-group-lab/"&gt;Accessibility Technologies Group Lab&lt;/a&gt;&lt;/strong&gt; — VoiceOver, Dynamic Type and accessibility in custom controls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/privacy-and-security-group-lab/"&gt;Privacy and Security Group Lab&lt;/a&gt;&lt;/strong&gt; — App Attest, Trust Insights and secure design of agentic features.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/camera-and-photo-technologies-group-lab/"&gt;Camera and Photo Technologies Group Lab&lt;/a&gt;&lt;/strong&gt; — AVFoundation, RAW capture and Center Stage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="/wwdc26/app-store-connect-group-lab/"&gt;App Store Connect Group Lab&lt;/a&gt;&lt;/strong&gt; — In-App Purchase, subscriptions and StoreKit.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The value of the Group Labs is not in the official descriptions —it’s inside. Developer questions surface the use cases Apple doesn’t cover in the main sessions: the bugs the team acknowledges, the design decisions they explain off the record, the patterns they recommend in practice and not in marketing.&lt;/p&gt;
&lt;p&gt;If you already have my &lt;a href="/blog/wwdc26-videos/"&gt;video guide&lt;/a&gt; as a map of the catalog, the Group Labs are the layer on top: where Apple engineers really talk about what they built.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/wwdc26-group-labs/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/wwdc26-videos/</id>
        <title>WWDC26 Videos</title>
        <updated>2026-06-11T00:00:00Z</updated>
        <summary>WWDC26 publishes over a hundred sessions. I built my own grouping and checklist to watch them in my order and tick off blocks throughout the year.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;Every year the same ritual: the Monday after the Keynote I open the &lt;a href="https://developer.apple.com/videos/all-videos/?collection=wwdc26"&gt;WWDC26&lt;/a&gt; videos and find more than a hundred sessions. Apple offers filters by topic, platform and framework that you can stack until the catalog fits your needs — a great starting point.&lt;/p&gt;
&lt;p&gt;What happens to me is that, even with those filters active, if I don’t set my own order I end up jumping from video to video based on whatever title catches my eye in the moment. I watch a bit of &lt;span class="high"&gt;SwiftUI&lt;/span&gt;, then I jump to &lt;span class="high"&gt;AI&lt;/span&gt;, then I move on to tools, and by the end of the day I’ve consumed five disconnected sessions that don’t reinforce each other. Comprehension is shallow because each topic block needs prior context that I don’t have.&lt;/p&gt;
&lt;p&gt;For a while I’ve wanted to put together my own scheme over what Apple offers. A path with a sense of direction, that builds knowledge cumulatively, that puts the things providing context for everything else first — and that also works as a checklist to tick off blocks as I progress throughout the year.&lt;/p&gt;
&lt;p&gt;This year I’ve done it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;My criterion is simple: &lt;strong&gt;first the overall vision, then the language core, then the tooling, and finally the specific areas based on what I use most in my work&lt;/strong&gt;. Each block prepares the ground for the next.&lt;/p&gt;
&lt;p&gt;I leave out only the ASL versions and the recaps (Dub Dub Daily, State of the Union recap). Everything else is in — including Group Labs, which although they were live interactive sessions, Apple keeps them in the catalog, so I leave them at the end as a topic reference.&lt;/p&gt;
&lt;p&gt;What follows is the grouping I use, and that I tick off block by block throughout the year. If it works for you, make it your own: what matters is having a path to follow, not that it’s mine.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 0 — The presentations&lt;/h3&gt;
&lt;p&gt;Before any technical session, these videos set the frame for the year. The teaser whets the appetite; Keynote and State of the Union are the ones that matter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/394/"&gt;Get ready for WWDC26&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The pre-event teaser from WWDC itself. Marginal once you’ve seen the Keynote, but it’s in the catalog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/101/"&gt;Keynote&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The big announcement of the year. &lt;span class="high"&gt;Liquid Glass&lt;/span&gt;, &lt;span class="high"&gt;Apple Intelligence&lt;/span&gt; with the new &lt;span class="high"&gt;Siri&lt;/span&gt;, &lt;span class="high"&gt;Xcode 27&lt;/span&gt; with &lt;span class="high"&gt;code agents&lt;/span&gt;. Not technical — it’s the why behind everything else.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/102/"&gt;Platforms State of the Union&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The Keynote for developers. Here the technical layer already shows up: what changes on the platforms, what priorities Apple has for this cycle, and what will matter for day-to-day work. This video is the map of the territory.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 1 — Swift and tooling&lt;/h3&gt;
&lt;p&gt;With the year’s context clear, I move into the core. &lt;span class="high"&gt;Swift&lt;/span&gt; first because everything else is built on top of the language.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/262/"&gt;What’s new in Swift&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Language update: ergonomic improvements, enhanced &lt;span class="high"&gt;concurrency&lt;/span&gt;, safer high-performance code, Embedded Swift and C and Java interop. The state of the language in 2026.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/258/"&gt;What’s new in Xcode 27&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Updates to the environment: &lt;span class="high"&gt;code agents&lt;/span&gt;, &lt;span class="high"&gt;Device Hub&lt;/span&gt;, &lt;span class="high"&gt;localization&lt;/span&gt;, &lt;span class="high"&gt;performance&lt;/span&gt; and &lt;span class="high"&gt;testing tools&lt;/span&gt;. If I’m going to live here, I need to know what’s changed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/260/"&gt;Get the most out of Device Hub&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Deep tour of &lt;span class="high"&gt;Device Hub&lt;/span&gt;: how it speeds up the development flow and how to quickly diagnose and reproduce issues with devices and simulators. The natural companion to the previous video.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/259/"&gt;Xcode, agents, and you&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
How to fold the &lt;span class="high"&gt;Xcode code agents&lt;/span&gt; into a real workflow. From the initial prototype to polishing a finished app. The practical side of what was shown in the previous video.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/227/"&gt;Create UI prototypes using agents in Xcode&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Prototyping with agents&lt;/span&gt;: techniques to iterate over layouts, generate creative solutions and refine ideas into a polished experience. The creative side of agents in Xcode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/213/"&gt;Translate your app using agents in Xcode&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
How &lt;span class="high"&gt;agents translate String Catalogs&lt;/span&gt; using the app’s context. Strategies to review the output and adjust localizations. The practical side for a global product.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/261/"&gt;Build, deliver, and automate with Xcode Cloud&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
What’s new in &lt;span class="high"&gt;Xcode Cloud&lt;/span&gt;: simplified onboarding, cloud tests, webhooks and distribution. Fundamentals for anyone using it in CI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/268/"&gt;Profile, fix, and verify: Improve app responsiveness with Instruments&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
A full flow to tackle performance problems: &lt;span class="high"&gt;Swift Concurrency instrument&lt;/span&gt;, &lt;span class="high"&gt;Time Profiler&lt;/span&gt; and &lt;span class="high"&gt;System Trace&lt;/span&gt;. Find the bottleneck, measure the improvement, confirm the fix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/222/"&gt;Meet the new MetricKit&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;MetricKit&lt;/span&gt; refreshed with vital performance metrics and actionable diagnostics. Cross-referencing metrics with app state via the &lt;span class="high"&gt;StateReporting framework&lt;/span&gt; for the complete optimization picture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/267/"&gt;Migrate to Swift Testing&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Adopt &lt;span class="high"&gt;Swift Testing&lt;/span&gt; alongside existing XCTests using framework interop. Patterns to introduce the new testing capabilities incrementally. Connects with what I cover in &lt;a href="/blog/sigbus/"&gt;SIGBUS&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 2 — SwiftUI and persistence&lt;/h3&gt;
&lt;p&gt;The UI and data stack. I watch them together because in practice they go hand in hand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/269/"&gt;What’s new in SwiftUI&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
New &lt;span class="high"&gt;Document&lt;/span&gt; protocol with direct disk access, reorder APIs in lists and grids, toolbar improvements, new presentations, &lt;span class="high"&gt;AsyncImage caching&lt;/span&gt; and lazy state for &lt;span class="high"&gt;Observable&lt;/span&gt;. The overview of what’s new in the framework.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/274/"&gt;What’s new in SwiftData&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Persistence of external types with &lt;span class="high"&gt;Codable&lt;/span&gt;, grouping results into sections for SwiftUI, &lt;span class="high"&gt;ModelResultsObserver&lt;/span&gt; and &lt;span class="high"&gt;HistoryObserver&lt;/span&gt; to observe changes outside the view. The updates to Apple’s ORM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/275/"&gt;Code-along: Add persistence with SwiftData&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Hands on keyboard: adding &lt;span class="high"&gt;persistence&lt;/span&gt; to an existing app step by step. Defining models, integrating persisted data with SwiftUI, managing app state. The best place to cement what was shown in the previous video.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/271/"&gt;Code-along: Build powerful drag and drop in SwiftUI&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Building Solitaire to explore the new drag and drop capabilities: &lt;span class="high"&gt;reorder API&lt;/span&gt;, drag containers to move multiple items, and a custom lifecycle. Practical and direct.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/321/"&gt;Dive into lazy stacks and scrolling with SwiftUI&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
List and scroll performance in SwiftUI. When to use lazy stacks, how to &lt;span class="high"&gt;optimize scroll behavior&lt;/span&gt;, which patterns to avoid.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/322/"&gt;Compose advanced graphics effects with SwiftUI&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Advanced &lt;span class="high"&gt;graphics effects&lt;/span&gt; with SwiftUI. Layer composition, blending modes and visual effects that go beyond the basics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/272/"&gt;Use SwiftUI with AppKit and UIKit&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Incremental adoption of SwiftUI in existing AppKit or UIKit apps: the &lt;span class="high"&gt;Observation&lt;/span&gt; framework to update views, integrating SwiftUI components into existing hierarchies, and adding full SwiftUI scenes without changing the architecture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/278/"&gt;Modernize your UIKit app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Updating iPhone layouts to work well with &lt;span class="high"&gt;iPhone Mirroring&lt;/span&gt; and on iPad. New tab and navigation bar APIs, preparing the app for Apple Intelligence, and a skill for the code agent that modernizes the codebase.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 3 — Apple Intelligence and Foundation Models&lt;/h3&gt;
&lt;p&gt;The block I’m most interested in this year. I watch it as a unit because each video builds on the previous one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/241/"&gt;What’s new in the Foundation Models framework&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Access to &lt;span class="high"&gt;Private Cloud Compute&lt;/span&gt;, integration with third-party and open source models, vision capabilities, context management APIs, &lt;span class="high"&gt;built-in semantic search&lt;/span&gt; and primitives for agentic experiences. The overview of Apple’s main AI framework.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/242/"&gt;Build agentic app experiences with the Foundation Models framework&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Dynamic profiles, dynamic instructions, &lt;span class="high"&gt;context management&lt;/span&gt; and orchestration patterns between local and server models. The advanced layer on top of the previous video.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/339/"&gt;Bring an LLM provider to the Foundation Models framework&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Extending the framework by implementing a &lt;span class="high"&gt;LanguageModelExecutor&lt;/span&gt; for new models. Interacting with the session transcript, managing state, optimizing the KV cache and supporting custom segment types. The door to integrating your own model into Apple’s stack.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/243/"&gt;Debug and profile agentic app experiences with Instruments&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
How to debug and profile agentic experiences with &lt;span class="high"&gt;Instruments&lt;/span&gt;. Because when something fails in an agent, you need to know where and why.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/298/"&gt;Meet the Evaluations framework&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
In a probabilistic world, unit tests aren’t enough. &lt;span class="high"&gt;Quantitative and qualitative metrics&lt;/span&gt;, model judges, aggregate statistics to ensure AI features work reliably.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/299/"&gt;Create robust evaluations for agentic apps&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The &lt;span class="high"&gt;advanced evaluations layer&lt;/span&gt;: flows with tool calling, dynamic conditions, synthetic data generation, judges and dataset validation. The real work behind getting an agent to behave correctly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/335/"&gt;Improve your prompts by hill-climbing with Evaluations&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Using the evaluation loop to improve prompts iteratively. The closing piece of the evaluations block.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/334/"&gt;Build AI-powered scripts with the fm CLI and Python SDK&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Scripts with the &lt;span class="high"&gt;Foundation Models CLI&lt;/span&gt; and Python SDK. Automation and prototyping outside the Swift environment.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 4 — Siri and App Intents&lt;/h3&gt;
&lt;p&gt;The bridge between the app and the system’s intelligence. I go from the conceptual to the practical.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/121/"&gt;Announcing Apple’s next big step for Siri and iPhone&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The big announcement of the new Siri. The essential context before diving into the technical integration videos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/345/"&gt;Discover new capabilities in the App Intents framework&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;ValueRepresentation&lt;/span&gt;, &lt;span class="high"&gt;RelevantEntities&lt;/span&gt;, &lt;span class="high"&gt;EntityCollection&lt;/span&gt;, &lt;span class="high"&gt;SyncableEntity&lt;/span&gt;, richer parameter types and long-running intents. What’s new in the base framework.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/343/"&gt;Explore advanced App Intents features for Siri and Apple Intelligence&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Advanced techniques so Siri can interact with the app naturally: content discovery, &lt;span class="high"&gt;semantic indexing&lt;/span&gt;, structured search and connection with notifications and Now Playing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/240/"&gt;Build intelligent Siri experiences with App Schemas&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Adopt &lt;span class="high"&gt;App Schemas&lt;/span&gt; so people can ask questions about app data and take actions through natural language. Practical example with calendar events and Spotlight.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/344/"&gt;Code-along: Make your app available to Siri&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The code-along of everything above: integrating &lt;span class="high"&gt;App Intents&lt;/span&gt; into a real calendar app, creating entities, triggering Siri actions and customizing response snippets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/295/"&gt;Validate your App Intents adoption with AppIntentsTesting&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;AppIntentsTesting&lt;/span&gt;: validate intents with the same infrastructure used by Siri, Shortcuts and Spotlight. Execute intents, inspect results, test entities and queries without UI automation. The closing piece of the block — test what you’ve integrated.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 5 — Core AI and ML&lt;/h3&gt;
&lt;p&gt;For anyone who wants to go beyond high-level frameworks and step into the model layer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/324/"&gt;Meet Core AI&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The &lt;span class="high"&gt;new framework&lt;/span&gt; for deploying AI models on device: a full ecosystem from Python for conversion and optimization to the Swift API for inference. Deep integration with Xcode and ahead-of-time compilation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/326/"&gt;Integrate on-device AI models into your app using Core AI&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
A collection of open source models optimized for Apple silicon — &lt;span class="high"&gt;Qwen&lt;/span&gt;, &lt;span class="high"&gt;Mistral&lt;/span&gt;, &lt;span class="high"&gt;SAM3&lt;/span&gt; — and how to integrate them in a few lines of code. AOT compilation and on-device specialization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/325/"&gt;Dive into Core AI model authoring and optimization&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Authoring and optimizing models with the Python tools in &lt;span class="high"&gt;Core AI&lt;/span&gt;. The deep layer for anyone who wants to control the full pipeline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/330/"&gt;Optimize custom machine learning operations with Metal tensors&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The &lt;span class="high"&gt;Metal Tensor API&lt;/span&gt; and the &lt;span class="high"&gt;MPP Tensor Ops&lt;/span&gt; library. Create portable operations that take advantage of the Neural Accelerators on M5 and A19, build custom kernels for Core AI apps and work with quantized formats. The low-level layer to push performance to the limit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/328/"&gt;Explore numerical computing in Swift with MLX&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
MLX for &lt;span class="high"&gt;numerical computing&lt;/span&gt; in Swift. The primitives everything else in ML builds on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/233/"&gt;Explore distributed inference and training with MLX&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Distributed inference and training with &lt;span class="high"&gt;MLX&lt;/span&gt;. Scale to multiple Macs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/232/"&gt;Run local agentic AI on the Mac using MLX&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
AI agents running &lt;span class="high"&gt;locally with privacy&lt;/span&gt;, low latency and offline. &lt;span class="high"&gt;OpenCode&lt;/span&gt;, integration with Xcode and techniques to scale across Macs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/319/"&gt;Build with the new Apple Foundation Model on Private Cloud Compute&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Using Apple’s new model on &lt;span class="high"&gt;Private Cloud Compute&lt;/span&gt;. The end of the spectrum: when the device isn’t enough but privacy still comes first.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 6 — Networking, services and infrastructure&lt;/h3&gt;
&lt;p&gt;Three videos that cover the communication and runtime layer: from the client to the service, and the infrastructure that runs them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/265/"&gt;Build real-time apps and services with gRPC and Swift&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;gRPC Swift&lt;/span&gt; for real-time experiences: the open source RPC framework with bidirectional streaming, built on Swift concurrency. From the service definition in &lt;span class="high"&gt;Protobuf&lt;/span&gt; to production deployment. Protobuf messages are 50% smaller than JSON and the runtime is modern and safe.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/389/"&gt;Discover container machines&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Container machines&lt;/span&gt; on Apple platforms. Infrastructure for development and deployment of Swift services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/224/"&gt;Expand the capabilities of your Virtualization app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
New capabilities in macOS 27 for &lt;span class="high"&gt;Virtualization&lt;/span&gt; apps. Automating macOS guest setup at first boot, USB accessory passthrough, custom network topologies and port forwarding.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 7 — Design and accessibility&lt;/h3&gt;
&lt;p&gt;The layer you don’t see but that defines whether an app is good or not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/250/"&gt;Principles of great design&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The foundational principles of Apple &lt;span class="high"&gt;app design&lt;/span&gt;. The conceptual frame before diving into concrete APIs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/251/"&gt;Communicate your brand identity on iOS&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Express &lt;span class="high"&gt;brand identity&lt;/span&gt; on iOS with the new visual personalization tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/292/"&gt;Design intuitive search experiences&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Designing searches&lt;/span&gt; that people understand and use. Patterns and antipatterns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/290/"&gt;Craft clear names for features and labels in your app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Naming features and labels&lt;/span&gt;. One of those videos that seems minor and ends up being among the most practical.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/220/"&gt;Refine accessibility for custom controls&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Accessibility on custom controls&lt;/span&gt;. How to make what you build work for everyone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/289/"&gt;Modernize your AppKit app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Modernizing AppKit to current macOS conventions. Input with &lt;span class="high"&gt;control events&lt;/span&gt; and gesture recognizers beyond tracking loops, keyboard navigation, state restoration, and new corner concentricity APIs to fit the macOS aesthetic.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 8 — System integration&lt;/h3&gt;
&lt;p&gt;The APIs that put my app inside the system: surfaces like Live Activities, widgets, Lock Screen or CarPlay; integration with native apps (Workouts), Bluetooth accessories, and shortcuts. If the app lives beyond its own window, this block is relevant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/223/"&gt;Live Activities essentials&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Fundamentals of &lt;span class="high"&gt;Live Activities&lt;/span&gt;: where they show up, the new landscape &lt;span class="high"&gt;Dynamic Island&lt;/span&gt; style, how to structure content and data, and how to trigger them in real time with ActivityKit and push notifications. The foundation if I’m going to use this API.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/277/"&gt;WidgetKit foundations&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The widget types, what makes them memorable, and how to keep them up to date. &lt;span class="high"&gt;Personalization via App Intents&lt;/span&gt; and dynamic styles. If the app needs to live outside its icon, start here.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/226/"&gt;Create live communication experiences&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;LiveCommunicationKit&lt;/span&gt; turns real-time communication apps into integrated experiences. Native conversation UI, full-screen presentation on Lock Screen, multitasking with Dynamic Island. Incoming, outgoing and group conversations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/212/"&gt;Rev up your CarPlay app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
New features for audio, navigation and voice-driven conversation apps on &lt;span class="high"&gt;CarPlay&lt;/span&gt;. Video apps to play content in compatible vehicles when parked. Thumbnails, media info and voice controls.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/207/"&gt;Deliver workout insights with HealthKit workout zones&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;HealthKit&lt;/span&gt; with workout insights — heart-rate zones and cycling power zones. Built-in or custom zones, current zone and time in each zone to guide during and after training.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/369/"&gt;Find your accessory with Bluetooth Channel Sounding&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Channel Sounding&lt;/span&gt; adds distance and direction to Bluetooth accessories. New Nearby Interaction and Core Bluetooth APIs and the required changes on the accessory. Optimized power consumption without losing responsiveness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/310/"&gt;What’s new in Shortcuts&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Building shortcuts with app content, new system automations, and the &lt;span class="high"&gt;Use Model&lt;/span&gt; feature to refine how an App Entity is presented to LLMs. Shortcuts that store rich information synchronized across devices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/246/"&gt;LLM search using Core Spotlight&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Turning basic search into a &lt;span class="high"&gt;RAG&lt;/span&gt; system with &lt;span class="high"&gt;SpotlightSearchTool&lt;/span&gt; and LanguageModelSession. Integration with Core Spotlight, delegate-based hydration, PipelineStages for tasks like sentiment analysis. Real contextual search over the system index.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 9 — Security and administration&lt;/h3&gt;
&lt;p&gt;Five videos that matter more than ever this year. With all the added AI, assuming that app integrity, prompt injection or user manipulation aren’t your problem is a risk. And for apps living in managed environments — education, enterprise, fleet — the admin posture matters too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/347/"&gt;Secure your app: mitigate risks to agentic features&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Evaluating threats from &lt;span class="high"&gt;indirect prompt injection&lt;/span&gt;: data exfiltration, unintended actions. System safeguards and best practices with App Intents and Foundation Models — user confirmations, safe prompt design, authentication. A must if I have agentic features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/201/"&gt;Secure your apps with App Attest&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;App Attest&lt;/span&gt; against unauthorized modification and fraud. How attackers spoof data and skip checks, and how to defend. Generating and managing keys in the &lt;span class="high"&gt;Secure Enclave&lt;/span&gt;, validating attestations and assertions, and using the fraud metric to detect abuse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/379/"&gt;Meet Trust Insights&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Trust Insights&lt;/span&gt; uses privacy-preserving ML to detect when someone might be being manipulated into risky actions. Integration, signal interpretation and designing interventions that protect while respecting privacy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/206/"&gt;What’s new in managing Apple devices&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Updates to &lt;span class="high"&gt;declarative device management&lt;/span&gt;, Apple Business and Apple School Manager. Streamlining deployment, strengthening security and improving the managed fleet experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/230/"&gt;What’s new in assessment on macOS&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Automatic Assessment Configuration&lt;/span&gt; on macOS for education apps. APIs to create secure, configurable exam environments that incorporate more system features. Automatic prechecks and accessibility controls for a reliable exam experience.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 10 — visionOS, RealityKit and spatial content&lt;/h3&gt;
&lt;p&gt;The largest block in the catalog. I only watch it if I’m working with Vision Pro or if I want to experiment with spatial scenes. If it’s not my area, I leave it for later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/287/"&gt;Build next-generation experiences with visionOS 27&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The &lt;span class="high"&gt;visionOS 27 overview&lt;/span&gt;: the different paths to build experiences — native Apple tools, immersive streaming from Mac or PC, third-party engines, porting iOS apps. Advances in 3D content creation, immersive media and object tracking. The video to start the block with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/279/"&gt;Explore advances in RealityKit&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
What’s new in &lt;span class="high"&gt;RealityKit&lt;/span&gt;: interactive cloth simulations, &lt;span class="high"&gt;NavMesh&lt;/span&gt; pathfinding, mixed reality lighting, customizable reverb meshes for spatial audio. Better shading, character rendering and support for &lt;span class="high"&gt;Gaussian splatting&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/234/"&gt;Design immersive environments for visionOS apps and the spatial web&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Creating &lt;span class="high"&gt;photorealistic environments&lt;/span&gt; for visionOS apps, websites and SharePlay experiences. Design principles, reference capture, high-fidelity CG asset preparation and real-time effects like motion and lighting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/280/"&gt;Iterate your spatial scenes faster with Reality Composer Pro 3&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
What’s new in &lt;span class="high"&gt;Reality Composer Pro 3&lt;/span&gt;: content, VFX, lighting and interactivity without leaving the editor. AI-assisted features integrated into the flow. The tool you’ll live in if you build spatial content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/281/"&gt;Extend Reality Composer Pro 3 functionality with Xcode&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Project-specific &lt;span class="high"&gt;plugins&lt;/span&gt; in Reality Composer Pro 3: custom components, custom systems, custom &lt;span class="high"&gt;ScriptGraph&lt;/span&gt; nodes. Full control over the spatial authoring workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/282/"&gt;Discover the Spatial Preview framework&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The new &lt;span class="high"&gt;Spatial Preview framework&lt;/span&gt; sends content from Mac directly to visionOS. Live-syncing and bidirectional editing across platforms. The SpatialPreview API, device discovery, 2D/3D integration and new Quick Look capabilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/283/"&gt;Explore enhancements to visionOS object tracking&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Advances in &lt;span class="high"&gt;object tracking&lt;/span&gt; and spatial accessories. Tracking moving and handheld objects, new supported classes of spatial accessories, and how to build custom accessories to unlock unique interaction models.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/284/"&gt;Collaborate on structured 3D models in visionOS&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Structured 3D models in visionOS: &lt;span class="high"&gt;USDZ&lt;/span&gt; preparation, manipulating entities within hierarchical assemblies, inspecting with a cross-section cut plane. Exploded-view animations for design and collaboration on Vision Pro.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/285/"&gt;Discover USDKit and what’s new in OpenUSD&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;USDKit&lt;/span&gt; in Swift, the new spatial preview API, enhanced spatial web. Updates to the &lt;span class="high"&gt;OpenUSD&lt;/span&gt; standard: accessibility, Gaussian splats, compressed geometry. Expanded USD editing and rendering tools in Preview for Mac.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/286/"&gt;Use foveated streaming to bring immersive content to visionOS&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Foveated streaming&lt;/span&gt;: scenes rendered remotely to Vision Pro in maximum fidelity. Combines native capabilities with third-party streaming wirelessly, demonstrated with an &lt;span class="high"&gt;OpenXR&lt;/span&gt; scene and NVIDIA CloudXR. Dynamic foveated streaming without compromising privacy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/320/"&gt;Explore immersive website environments in visionOS&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The new &lt;span class="high"&gt;Immersive API&lt;/span&gt; in JavaScript takes your website visitors into virtual environments on Vision Pro. Transitions from an inline model element, video docking, real-scale optimization. A few lines of code running on the web.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/393/"&gt;Supercharge your spatial workflows with Reality Composer Pro 3&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Rich interactivity and full visual effects inside Reality Composer Pro: &lt;span class="high"&gt;Shader Graph&lt;/span&gt; for materials, &lt;span class="high"&gt;Animation Graph&lt;/span&gt; for blending skeletal animations, &lt;span class="high"&gt;Compute Graph&lt;/span&gt; for particles. Script Graph for interactivity, Sequencer for events and Behavior Trees for NPCs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/338/"&gt;Build live production tools for Apple Immersive Video&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Live production of &lt;span class="high"&gt;Apple Immersive Video&lt;/span&gt;. Packaging immersive video, spatial audio and scene metadata for IP transport with the &lt;span class="high"&gt;SMPTE 2110&lt;/span&gt; standard. Immersive Media Support, Video Toolbox and AVFoundation for real-time flows.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 11 — Games and Metal&lt;/h3&gt;
&lt;p&gt;If I work on a game, this block is a mandatory stop. If not, I watch it out of curiosity because there are performance pieces that also apply to non-game apps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/356/"&gt;Bringing Cyberpunk 2077 to Mac&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
How CD PROJEKT RED brought &lt;span class="high"&gt;Cyberpunk 2077 to Mac&lt;/span&gt; taking advantage of Apple hardware, software and tools. Techniques applicable to other games. The &lt;span class="high"&gt;For this Mac&lt;/span&gt; preset that automatically optimizes settings to balance visual fidelity and framerate across the Mac lineup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/357/"&gt;Speedrun your game port with agentic coding&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The new &lt;span class="high"&gt;agentic skills&lt;/span&gt; in Game Porting Toolkit 4 speed up porting. Adopting &lt;span class="high"&gt;Metal 4&lt;/span&gt;, integrating &lt;span class="high"&gt;MetalFX&lt;/span&gt;, tuning the game for Apple hardware. Agents that diagnose GPU rendering issues autonomously.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/358/"&gt;Make your game great with touch&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Convincing touch controls for games. Insights from indie to AAA devs, best practices, and how to take advantage of the &lt;span class="high"&gt;Touch Controller framework&lt;/span&gt; and Metal for optimal performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/252/"&gt;Design no-code games with Reality Composer Pro 3&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;ScriptGraph&lt;/span&gt; in Reality Composer Pro 3 for no-code 3D content. Visual nodes for animations, interactive moments, and integration with SwiftUI to add speech bubbles and UI to the experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/359/"&gt;Build real-time neural rendering pipelines with Metal&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
ML integrated into the rendering pipeline with &lt;span class="high"&gt;Metal 4&lt;/span&gt;. &lt;span class="high"&gt;MetalFX neural denoising&lt;/span&gt; with insights from Maxon Redshift Live. Training and deploying a neural tone mapper inline with the ML command encoder, and the new tensor API for small networks inside your shaders.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/388/"&gt;Find and fix performance issues in your Metal games&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Hunting performance problems with Metal tools: &lt;span class="high"&gt;Game Performance Overview&lt;/span&gt; in Instruments, background traces with metalperftrace and Control Center, and the new &lt;span class="high"&gt;StateReporting API&lt;/span&gt; to correlate metrics with the game’s runtime state. Hours of telemetry turned into actionable insights.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 12 — Camera, photography and imaging&lt;/h3&gt;
&lt;p&gt;For apps with camera or image processing. Some videos are very specific (RAW, Center Stage), others apply even if I’m just displaying a preview.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/237/"&gt;What’s new in image understanding&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Updated Vision framework and Foundation Models. The new &lt;span class="high"&gt;tap-to-segment&lt;/span&gt; request to segment images, Vision on watchOS, and image support in the Apple Foundation Model combined with OCR, barcode scanning and custom tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/297/"&gt;Best practices for integrating visual intelligence in your app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
How &lt;span class="high"&gt;Visual Intelligence&lt;/span&gt; transforms content discovery. Defining entities, processing images, handling multiple result types. Optimizing speed and relevance, and intents for direct actions like opening or playing content with a tap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/303/"&gt;Build a responsive camera app that launches quickly&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Optimizing the full camera startup sequence — from launch to the &lt;span class="high"&gt;first preview frame&lt;/span&gt;. New APIs for faster launches, smooth rendering and sustained performance. So people never miss the moment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/304/"&gt;Implement high resolution photo capture&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
High-resolution photo capture with AVFoundation. The three options — &lt;span class="high"&gt;RAW&lt;/span&gt;, &lt;span class="high"&gt;exposure-bracketed&lt;/span&gt;, &lt;span class="high"&gt;fully processed&lt;/span&gt; — and when to use each one. Configuring 24MP and 48MP capture on Main, Tele and Ultra Wide cameras. Deferred photo processing so the app stays responsive even with rapid bursts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/305/"&gt;Enhance RAW image processing with Core Image&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Version 9 of &lt;span class="high"&gt;Core Image&lt;/span&gt;’s RAW APIs: sharper detail, more defined color, Apple Neural Engine for performance. &lt;span class="high"&gt;CIRAWFilter&lt;/span&gt; to edit exposure, noise, sharpness and contrast. New CIImageProcessor APIs with fine control over tile sizing and buffers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/341/"&gt;Support the Center Stage front camera in your iOS app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Center Stage&lt;/span&gt; with AVCapture APIs on the front camera of iPhone 17, 17 Pro and Air. Zoom and rotate, flexible framing for selfies and videos, everyone framed in group photos. Integration into video calls with auto-framing and real-time stabilization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/375/"&gt;Create high quality images using Image Playground&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Image generation with &lt;span class="high"&gt;Image Playground&lt;/span&gt;. Generative model on &lt;span class="high"&gt;Private Cloud Compute&lt;/span&gt;, images in almost any style including photorealistic, specific dimensions, and modification through natural language and touch.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 13 — Web and Safari&lt;/h3&gt;
&lt;p&gt;For web devs: what’s new in WebKit and the new pieces of CSS and HTML that Safari premieres this year.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/204/"&gt;What’s new in WebKit for Safari 27&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The overview of WebKit and &lt;span class="high"&gt;Safari 27&lt;/span&gt;: Grid Lanes, Customizable Select, HTML Model, Immersive Environments, the latest in Web Extensions. Over 1,000 engine improvements to make the web more reliable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/215/"&gt;Get started with the HTML Model Element&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The &lt;span class="high"&gt;model element&lt;/span&gt; brings interactive 3D content to web pages on iOS, iPadOS, macOS and visionOS. Tools to create and optimize 3D assets, model element features, and where web standards in 3D are heading.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/216/"&gt;Create web extensions for Safari&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Build and test Safari &lt;span class="high"&gt;web extensions&lt;/span&gt; from scratch — without Xcode. Content blocking, page modification, native messaging and the permissions mode working together for a powerful, privacy-respecting experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/314/"&gt;Learn CSS Grid Lanes&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Grid Lanes&lt;/span&gt; for adaptable web layouts with elements of varied shapes. Clean and flexible CSS, plus &lt;span class="high"&gt;flow-tolerance&lt;/span&gt; to refine accessibility without losing malleability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/315/"&gt;Rediscover the HTML select element&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Full styling control over select menus on the web. New CSS &lt;span class="high"&gt;appearance&lt;/span&gt; value and new pseudo-elements, rich content inside options with new HTML possibilities. Selects tailored to the design system without giving up accessibility or robustness.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 14 — StoreKit, App Store, subscriptions and Wallet&lt;/h3&gt;
&lt;p&gt;For everything that touches monetization and App Store Connect. If I sell anything in my app, this block matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/210/"&gt;What’s new in Apple In-App Purchase&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The new &lt;span class="high"&gt;monthly subscriptions with a 12-month commitment&lt;/span&gt; as a more affordable option. Configure and test with App Store Connect, StoreKit APIs and Xcode testing. Improvements to offer code redemption and to the App Review experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/205/"&gt;Enhance your presence on the App Store&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Reimagining marketing on the App Store using images and videos in new places. New visual placements on the product page, search results and Apple Ads campaigns. The new &lt;span class="high"&gt;Asset Library&lt;/span&gt; centralizes assets and a tool lets you preview the product page before publishing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/209/"&gt;What’s new in Wallet&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
New pass styles for rich designs. New barcode formats and a flexible pass actions API. &lt;span class="high"&gt;Pass Designer&lt;/span&gt; and &lt;span class="high"&gt;Pass Builder&lt;/span&gt; simplify designing, personalizing and distributing passes at scale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/309/"&gt;Explore Retention Messaging in App Store Connect&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Retention Messaging&lt;/span&gt; to reach subscribers before they cancel. Configure it in App Store Connect, add subscription offers, and use the API for real-time messaging and alternatives that encourage them to stay.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/378/"&gt;Unlock in-game content with StoreKit and Background Assets&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Native In-App Purchases in Unity with the new &lt;span class="high"&gt;StoreKit plug-in&lt;/span&gt;. Smaller download sizes with the &lt;span class="high"&gt;Background Assets plug-in&lt;/span&gt; delivering per-language packs. And a Steam Asset Converter to migrate existing builds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/391/"&gt;Offer subscriptions to groups and organizations&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Group Purchases&lt;/span&gt;: one subscriber buys multiple seats and invites others from the app. &lt;span class="high"&gt;Volume Purchasing&lt;/span&gt; via Apple Business and Apple School Manager puts your subscriptions in front of enterprise and education buyers who already purchase apps at scale.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 15 — Music, audio and subtitles&lt;/h3&gt;
&lt;p&gt;For apps with multimedia content. There’s a new framework this year (&lt;span class="high"&gt;Music Understanding&lt;/span&gt;) worth attention even if you don’t build music apps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/253/"&gt;Meet the Music Understanding framework&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The new &lt;span class="high"&gt;Music Understanding&lt;/span&gt; framework that analyzes audio across six dimensions, on-device: key, rhythm, structure, pace, instrument activity and loudness. Plus a sample app, Music Understanding Lab, to visualize results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/254/"&gt;Integrate MusicKit into your app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Apple Music inside your app with &lt;span class="high"&gt;MusicKit&lt;/span&gt;. Authorization, subscription, music selection, playback control and sharing songs across storefronts. New &lt;span class="high"&gt;Music Picker&lt;/span&gt; to browse the catalog and personal libraries. Differences between SystemMusicPlayer and ApplicationMusicPlayer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/256/"&gt;Discover generated subtitles and subtitle styles&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Generated subtitles&lt;/span&gt; on-device that transcribe or translate from another language. Caption style preview to customize and preview during playback. Implementation with AVKit, AVPlayerLayer and Media Accessibility.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/312/"&gt;Meet the Now Playing framework&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
The new Swift &lt;span class="high"&gt;Now Playing&lt;/span&gt; framework connects your app’s playback with system surfaces: Lock Screen, Control Center, Dynamic Island, CarPlay. Publish state, respond to commands via an observable API, and &lt;span class="high"&gt;remote playback sessions&lt;/span&gt; to render media on external devices.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 16 — Accessibility, reading and text&lt;/h3&gt;
&lt;p&gt;The text and reading layer. This isn’t just new APIs — it’s how the app fits with each person’s preferences.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/219/"&gt;Enhance the accessibility of your reading app&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Robust reading experiences for &lt;span class="high"&gt;VoiceOver&lt;/span&gt; and &lt;span class="high"&gt;Speak Screen&lt;/span&gt;. Intuitive text selection, clear navigation between lines and paragraphs, and continuous reading across elements and pages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/221/"&gt;Prepare your tvOS apps for Dynamic Type&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;span class="high"&gt;Dynamic Type&lt;/span&gt; on tvOS: implementing font scaling, adapting layouts for large sizes, optimizing grids and media-focused carousels. So any text size remains comfortable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/370/"&gt;Elevate your app’s text experience with TextKit&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Combining the convenience of built-in text views with the control of &lt;span class="high"&gt;TextKit&lt;/span&gt;. New APIs to extend UITextView and NSTextView with custom behaviors like line numbers or collapsible sections. TextKit architecture, caching and reuse policies for text attachments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/372/"&gt;Unwrap PaperKit&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Canvas-based applications with &lt;span class="high"&gt;PaperKit&lt;/span&gt;. New data model APIs to access, create and modify markup elements. Custom controls, annotations, and best practices to integrate a full creative canvas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/203/"&gt;Read between the strokes with PencilKit&lt;/a&gt;&lt;/strong&gt;&lt;br /&gt;
Handwriting recognition with the same technology behind Freeform and Notes. Recognition across many alphabets and languages, and new capabilities to integrate &lt;span class="high"&gt;PencilKit&lt;/span&gt; into more types of apps.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Block 17 — Group Labs&lt;/h3&gt;
&lt;p&gt;The Group Labs were live Q&amp;amp;A sessions with Apple engineers and designers during the event week. Apple keeps them in the catalog even though the interactive session is over, so they serve as a quick reference for whichever area I’m working on. The official descriptions are essentially the same boilerplate, so I group them by topic with a one-line context.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Language core and main frameworks&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8001/"&gt;Swift Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the week’s Swift announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8006/"&gt;SwiftUI Group Lab&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8120/"&gt;SwiftUI Group Lab (second round)&lt;/a&gt;&lt;/strong&gt; — Two Q&amp;amp;A sessions on the SwiftUI announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8002/"&gt;SwiftUI for Beginners Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A to get started with SwiftUI from zero.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8017/"&gt;SwiftData Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the SwiftData announcements.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Xcode, performance and platforms&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8013/"&gt;Xcode Tips and Tricks Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A to get the most out of Xcode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8003/"&gt;Power and Performance Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the power and performance announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8004/"&gt;visionOS Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the visionOS announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8014/"&gt;watchOS Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the watchOS announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8015/"&gt;Safari and Web Technologies Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the Safari and web technologies announcements.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intelligence, ML and AI&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8011/"&gt;Apple Intelligence Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the Apple Intelligence announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8016/"&gt;Machine Learning &amp;amp; AI Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the ML and AI announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8007/"&gt;Coding Intelligence for Beginners Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A to get started with coding intelligence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8121/"&gt;Coding Intelligence, Machine Learning &amp;amp; AI Group Lab&lt;/a&gt;&lt;/strong&gt; — Combined Q&amp;amp;A on coding intelligence, ML and AI.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Platform, distribution and design&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8005/"&gt;Accessibility Technologies Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the accessibility announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8009/"&gt;Privacy and Security Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the privacy and security announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8010/"&gt;App Store Connect Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the App Store Connect announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8018/"&gt;Camera and Photo Technologies Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A on the camera and photography announcements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2026/8012/"&gt;Icon Composer for Beginners Group Lab&lt;/a&gt;&lt;/strong&gt; — Q&amp;amp;A to get started with Icon Composer.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;With this order I skip the catalog chaos and start seeing results from the first week.&lt;/p&gt;
&lt;p&gt;My summarized sequence:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Block 0&lt;/strong&gt; — The presentations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 1&lt;/strong&gt; — Swift and tooling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 2&lt;/strong&gt; — SwiftUI and persistence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 3&lt;/strong&gt; — Apple Intelligence and Foundation Models&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 4&lt;/strong&gt; — Siri and App Intents&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 5&lt;/strong&gt; — Core AI and ML&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 6&lt;/strong&gt; — Networking, services and infrastructure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 7&lt;/strong&gt; — Design and accessibility&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 8&lt;/strong&gt; — System integration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 9&lt;/strong&gt; — Security and administration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 10&lt;/strong&gt; — visionOS, RealityKit and spatial content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 11&lt;/strong&gt; — Games and Metal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 12&lt;/strong&gt; — Camera, photography and imaging&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 13&lt;/strong&gt; — Web and Safari&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 14&lt;/strong&gt; — StoreKit, App Store, subscriptions and Wallet&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 15&lt;/strong&gt; — Music, audio and subtitles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 16&lt;/strong&gt; — Accessibility, reading and text&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Block 17&lt;/strong&gt; — Group Labs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’ve been watching &lt;span class="high"&gt;WWDC&lt;/span&gt; for years you know the catalog never expires — the videos are available all year at &lt;a href="https://developer.apple.com/videos/all-videos/?collection=wwdc26"&gt;developer.apple.com&lt;/a&gt;. June is for the new stuff, the rest of the year for going deep. As I covered in &lt;a href="/blog/wwdc26/"&gt;WWDC 26&lt;/a&gt;, the technical sessions are the main course — and now I already have my own checklist to tick off as I go.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/wwdc26-videos/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/claude-voice/</id>
        <title>Claude Voice</title>
        <updated>2026-06-10T00:00:00Z</updated>
        <summary>How a trail running accident forced me to communicate with AI using my voice and ended up teaching me a skill I didn't know I needed.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;I have a problem with spoken expression that has been with me my whole life: &lt;strong&gt;when I talk, ideas pile up on me&lt;/strong&gt;. My head moves faster than my voice. I start saying something, another related idea pops up, then another, and before I finish the first sentence I’ve already lost the thread. The result is that I express myself worse than I think.&lt;/p&gt;
&lt;p&gt;Writing, on the other hand, gives me time. Writing is slow by nature, and that slowness forces me to organize. While my finger searches for the letter, the thought settles. That’s why ever since I started using &lt;span class="high"&gt;Claude Code&lt;/span&gt; intensively, I’ve always worked with text. The keyboard is my natural filter layer.&lt;/p&gt;
&lt;p&gt;That worked well until it stopped working.&lt;/p&gt;
&lt;p&gt;I had an accident in a trail race. A silly fall, not-so-silly consequences, weeks of recovery, and what hurt most at the time: &lt;strong&gt;little mobility to type normally&lt;/strong&gt;. All at once, my preferred way of working was blocked.&lt;/p&gt;
&lt;p&gt;I had two options: either I used my voice or I accepted that I was going to work at an absurdly slow pace. It wasn’t a philosophical choice about modes of communicating with AI. It was pure necessity.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;I started using voice with &lt;span class="high"&gt;Claude Code&lt;/span&gt; by force, without romanticizing the process. The first few days were exactly as bad as I expected. Cut-off sentences, ideas arriving in the wrong order, context lost along the way. The model responded well, but I was giving it mediocre material.&lt;/p&gt;
&lt;p&gt;What I discovered right away is that the problem wasn’t the transcription tool, it was me. I didn’t know how to structure a thought for verbalizing before verbalizing it. With the keyboard I correct myself on the fly without noticing. With voice, the mistake comes out and that’s it.&lt;/p&gt;
&lt;p&gt;So I changed my approach. Instead of trying to improvise, I started &lt;strong&gt;mentally preparing what I was going to say&lt;/strong&gt; before speaking. No scripts, just a second of silence to organize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What context does the model need?&lt;/li&gt;
&lt;li&gt;What exactly do I want it to do?&lt;/li&gt;
&lt;li&gt;What constraints should I mention?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That second of pause was the biggest change. It’s exactly what I do with the keyboard unconsciously, but with voice I have to do it consciously and explicitly.&lt;/p&gt;
&lt;p&gt;The other thing I learned is that &lt;strong&gt;voice favors brevity&lt;/strong&gt;. When I write, I tend to add nuances. When I speak, I get to the point because keeping a long sentence in my head is exhausting. And it turns out &lt;span class="high"&gt;Claude&lt;/span&gt; responds just as well to concise prompts as to detailed ones, as long as the context is clear.&lt;/p&gt;
&lt;p&gt;What I ended up doing was a kind of informal protocol for voice sessions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start with the &lt;strong&gt;what&lt;/strong&gt; before the &lt;strong&gt;how&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;One idea per turn, no chaining requests&lt;/li&gt;
&lt;li&gt;If the response isn’t what I expected, reformulate from scratch instead of correcting on top&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With that schema, the sessions started to work. Not perfect, but functional.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;There are situations where voice clearly wins. When I’m thinking out loud about an architecture, when I want to explore several options before writing code, when I need to describe a behavior that’s easier to narrate than to specify. The keyboard is still my primary mode, but voice is no longer the last resort.&lt;/p&gt;
&lt;p&gt;What changed is that I learned to structure better before speaking. And that, curiously, also improved how I write prompts. The habit of pausing, of organizing before emitting, transferred over.&lt;/p&gt;
&lt;p&gt;The accident forced me to train a skill I never would have trained voluntarily. Not because I didn’t know it existed, but because I had a comfortable alternative that I always used. When the alternative disappears, you surprise yourself with what you’re capable of learning.&lt;/p&gt;
&lt;p&gt;If you work with language models regularly and have never tried voice, you don’t need to break anything to give it a shot. A week of voluntary keyboard restriction is enough to see if there’s something worthwhile there. It took me an accident to discover it. You have the advantage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/claude-voice/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/wwdc26/</id>
        <title>WWDC 26</title>
        <updated>2026-06-03T00:00:00Z</updated>
        <summary>WWDC is not just another conference for me. It's the event of the year I enjoy more than any movie or TV show premiere, like a kid at Christmas.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;Every June I hear people talking about the big premiere of the season. The summer movie, the new season of some show, the sequel they had been waiting on for years. And I nod and think about my own thing: in a few days &lt;span class="high"&gt;WWDC&lt;/span&gt; arrives.&lt;/p&gt;
&lt;p&gt;My problem is that it’s very hard to explain that feeling to someone outside Apple’s development ecosystem. Saying you’ve had the date marked on the calendar for weeks, that you take Keynote day as a holiday, that your nerves go up as the hour approaches — said without context, it sounds odd. For anyone who doesn’t program in &lt;span class="high"&gt;Swift&lt;/span&gt;, a two-hour keynote talking about APIs is the furthest thing from a movie premiere.&lt;/p&gt;
&lt;p&gt;It’s not just nerd stuff. &lt;span class="high"&gt;WWDC&lt;/span&gt; has something no premiere has: what they announce directly affects my work, the decisions I made six months ago that will now get an official answer. When the lights go down in my private screening room, I’m not just another spectator — I’m the target audience.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;I stopped trying to justify it. &lt;span class="high"&gt;WWDC&lt;/span&gt; is my premiere of the year, and I enjoy it more than any movie or TV show. I have a blast like a kid.&lt;/p&gt;
&lt;h3&gt;The week before&lt;/h3&gt;
&lt;p&gt;It starts before Monday. Rumors peak, the community is in full speculation mode, and I’ve spent days thinking about what they’re going to announce and how it will affect what I’m working on. It’s the same energy as the week before a highly anticipated premiere, with one fundamental difference: the outcome actually matters to me — as someone whose job changes based on what’s announced.&lt;/p&gt;
&lt;h3&gt;The Keynote as a ritual&lt;/h3&gt;
&lt;p&gt;In Spain, the Keynote hits at 7:00 PM. The perfect hour — I wrap up the workday, early dinner, and from then on only the Keynote exists.&lt;/p&gt;
&lt;p&gt;My ritual includes a screen setup that would look absurd from the outside. On the main one, Apple’s official stream. On a second one, a group of nutters doing live commentary — they comment every slide, predict the next, react in real time. And on a third, I follow X, technical blogs, and developer Slacks and Discords: nuances missed in the slides and first analyses of the real impact.&lt;/p&gt;
&lt;p&gt;What I love most is that the Keynote mixes two things that rarely go together: the thrill of the show and the technical information that changes my work. I process every feature on two levels at once — spectator enjoying it and developer already thinking about how to integrate it.&lt;/p&gt;
&lt;p&gt;And then there are the “yes!” moments — when they announce something you’d been asking for. Last year, with the improvements to &lt;span class="high"&gt;Swift Concurrency&lt;/span&gt;, I jumped off the couch. Literally.&lt;/p&gt;
&lt;h3&gt;The sessions are the real content&lt;/h3&gt;
&lt;p&gt;The Keynote is the appetizer. The &lt;span class="high"&gt;technical sessions&lt;/span&gt; are the main course, and they last for weeks.&lt;/p&gt;
&lt;p&gt;I go through the catalog like someone browsing the lineup of a film festival. With over a hundred sessions, selecting and prioritizing is an activity in itself. And they don’t expire: Apple keeps them on &lt;a href="https://developer.apple.com/videos/"&gt;developer.apple.com&lt;/a&gt; indefinitely. June is for the new stuff, and the rest of the year is for going deeper into the ones I left for later.&lt;/p&gt;
&lt;p&gt;This connects with &lt;a href="/blog/swift-pills/"&gt;Swift Pills&lt;/a&gt;: dense knowledge that deserves to be consumed at a calm pace. And with &lt;a href="/blog/apple-coding-speed/"&gt;Apple Coding Speed&lt;/a&gt;: the comprehension speed you train is what lets you get the most out of every session.&lt;/p&gt;
&lt;h3&gt;The betas as a playground&lt;/h3&gt;
&lt;p&gt;The day of the Keynote, the betas open up. From there the game begins: install, compile against the new SDK, see what breaks, explore the APIs before they have complete documentation. It’s early access, but I’m the one playing while I develop — and what I discover can shift design decisions I’ve been mulling over for months.&lt;/p&gt;
&lt;h3&gt;The community in WWDC mode&lt;/h3&gt;
&lt;p&gt;The technical conversation among Apple developers goes through the roof. Everyone has something to comment on, something they discovered, some detail that changed how they saw a problem.&lt;/p&gt;
&lt;p&gt;The conferences in the ecosystem — like the ones I follow in &lt;a href="/blog/conferences-25/"&gt;Conferences 25&lt;/a&gt; — have that energy, but &lt;span class="high"&gt;WWDC&lt;/span&gt; takes it to the extreme: it’s the moment of the year when the entire community looks at the same thing at the same time.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;I no longer try to explain it. I accept that &lt;span class="high"&gt;WWDC&lt;/span&gt; is my event of the year and I enjoy it without hang-ups.&lt;/p&gt;
&lt;p&gt;What it gives me every June:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Genuine anticipation&lt;/strong&gt; — the event that directly affects my work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Keynote as a show&lt;/strong&gt; — two hours in dual mode: spectator and developer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technical sessions that last months&lt;/strong&gt; — dense material I consume calmly the rest of the year&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The betas as a lab&lt;/strong&gt; — early access to the new APIs before anyone else&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The community at its peak&lt;/strong&gt; — the richest technical conversation of the year&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Answers to problems sitting on your desk&lt;/strong&gt; — unmatched satisfaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have an event of the year that makes you feel like a kid, don’t justify it. Enjoy it. Mine is &lt;span class="high"&gt;WWDC&lt;/span&gt;, and I’m already looking forward to the next one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/wwdc26/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/swift-pills/</id>
        <title>Swift Pills</title>
        <updated>2026-05-27T00:00:00Z</updated>
        <summary>Five-minute Swift pills that teach you things you hadn't even considered. Knowledge that slips into your day without asking for a slot in the agenda.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;Keeping up with &lt;span class="high"&gt;Swift&lt;/span&gt; is one of those things I always have on the list and almost never manage to cross off. Every WWDC brings new stuff, the language evolves, the community publishes non-stop, and meanwhile I’m with the project, with the meetings, with life. The information exists — the problem is finding the gap to get into it.&lt;/p&gt;
&lt;p&gt;And almost all the technical content comes in big formats: fifty-minute videos, entire WWDC sessions, mile-long articles, books. All of that demands time and focus, and in my day-to-day I don’t always have either of the two. What ends up happening is that I keep saving links “for when I have a moment” — and that moment never comes.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://blog.arturorivas.com/"&gt;Arturo Rivas&lt;/a&gt;’s blog changes the way you consume content about &lt;span class="high"&gt;Swift&lt;/span&gt;. What he publishes are knowledge pills: short articles, around five minutes of reading, where he tells you one specific thing and then leaves. No detours, no filler, no “before we begin, allow me to set the context”.&lt;/p&gt;
&lt;p&gt;And what’s hooked me the most is that most of the time they’re pills about things you hadn’t even stopped to consider. Language details, subtle behaviors, APIs that had been there for versions and you’d never crossed paths with. Things that when you read them you think “huh, didn’t know that” — and from that point on you don’t forget them.&lt;/p&gt;
&lt;h3&gt;Five minutes change the equation&lt;/h3&gt;
&lt;p&gt;That they’re five-minute pills isn’t a minor detail — it’s what makes it work. I don’t need to reserve a slot, I don’t need to switch into study mode, I don’t even need to be at the computer. I read them between tasks, while waiting for a build, with the morning coffee. Learning stops competing with work and slips into the margins of the day.&lt;/p&gt;
&lt;p&gt;And since each pill goes to a single thing, retention is brutal. When an article is long I need to process it in depth, sit down to study it and go over it several times so it sticks. When I read a pill about, for example, how &lt;span class="high"&gt;@discardableResult&lt;/span&gt; works or some detail of &lt;span class="high"&gt;Sendable&lt;/span&gt; I hadn’t seen, that concept goes in directly. It was the only focus during those five minutes, and my head treats it as such.&lt;/p&gt;
&lt;h3&gt;Things you hadn’t even considered&lt;/h3&gt;
&lt;p&gt;This for me is the most valuable thing about the blog. Arturo doesn’t write about “what is Swift” nor about things that are already in any tutorial. He writes about corners of the language, about behaviors that have their reason but that normally you don’t stop to investigate, about APIs that get covered up by each year’s big announcements.&lt;/p&gt;
&lt;p&gt;It’s the kind of knowledge you don’t actively look for because you don’t even know it exists — but once you have it, it changes the way you read and write code. You realize there’s a better pattern, a tool that had been available for a while, a detail that explains why something behaves the way it does.&lt;/p&gt;
&lt;h3&gt;It fits into my flow without asking for permission&lt;/h3&gt;
&lt;p&gt;I started treating the pills as a natural part of the day, not as a separate task. Five minutes before getting down to business, another five between two meetings, while Xcode compiles something heavy. Without the feeling of “now I have to learn” — just letting them in when there’s a gap.&lt;/p&gt;
&lt;p&gt;The contrast with long formats is brutal. For a WWDC video I need to plan it, sit down, have full attention. For a pill, I just open it when the chance appears. And it turns out the chances are far more frequent than I thought.&lt;/p&gt;
&lt;p&gt;This connects with something I already covered in &lt;a href="/blog/apple-coding-speed/"&gt;Apple Coding Speed&lt;/a&gt;: comprehension speed isn’t trained only in long courses. It’s also trained through constant exposure to well-explained concepts — and the pills are exactly that, continuous training that happens between more structured sessions.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;If you work with &lt;span class="high"&gt;Swift&lt;/span&gt; and your “for when I have a moment” links are piling up, the problem probably isn’t the amount of content — it’s the format. Drop by &lt;a href="https://blog.arturorivas.com/"&gt;blog.arturorivas.com&lt;/a&gt; and try the pill model for a week.&lt;/p&gt;
&lt;p&gt;What I’ve taken away:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I learn things I wasn’t even looking for&lt;/strong&gt; — most of the pills touch on details I wouldn’t have searched for on my own, and they’re the ones that have given me the most&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Knowledge enters effortlessly&lt;/strong&gt; — five minutes fit into any gap, so learning stops depending on planning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What I read sticks&lt;/strong&gt; — a single concept per pill makes retention much higher than with long articles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I recognize patterns faster&lt;/strong&gt; — when I come across an API or a pattern I saw in a pill, I identify it without having to search for it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No more “I have to read this” debt&lt;/strong&gt; — low-threshold content gets consumed in the moment, it doesn’t get postponed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want medicine of the new kind, you have it in knowledge pills here. Five minutes, one idea, one thing you probably hadn’t considered. You’ll hardly find a better ratio between time invested and learning that sticks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/swift-pills/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/apple-coding-speed/</id>
        <title>Apple Coding Speed</title>
        <updated>2026-05-20T00:00:00Z</updated>
        <summary>"What Apple Coding Academy's training really builds: not just Swift code, but speed of comprehension, analysis, and development across the Apple ecosystem."</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When I decided to specialize in the Apple ecosystem, I was looking for approachable training with a very specific profile: rigorous, professional, structured, and backed by an active technical community. Not another introduction to &lt;span class="high"&gt;SwiftUI&lt;/span&gt;, but the complete ecosystem — &lt;span class="high"&gt;Swift Concurrency&lt;/span&gt;, &lt;span class="high"&gt;server-side&lt;/span&gt;, &lt;span class="high"&gt;accessibility&lt;/span&gt;, &lt;span class="high"&gt;AI agents&lt;/span&gt; — at the level of the official documentation.&lt;/p&gt;
&lt;p&gt;That search led me to &lt;a href="https://acoding.academy/"&gt;Apple Coding Academy&lt;/a&gt;, a benchmark in specialized training for native development with Apple technologies. And, once inside, a more interesting question appeared than the one that had brought me there: what does structured training from Apple Coding Academy give me that reading the documentation on my own would not?&lt;/p&gt;
&lt;p&gt;The answer took longer than expected to arrive, and when it did, it was not what I anticipated.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;What Apple Coding Academy’s training gave me was not the knowledge. It was the &lt;strong&gt;speed&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Not execution speed — that is a byproduct. Speed of comprehension. Speed of analysis. Speed to reach the core of a problem before it expands. And that speed, once you have it, transfers to everything.&lt;/p&gt;
&lt;h3&gt;What you really train at Apple Coding Academy&lt;/h3&gt;
&lt;p&gt;When you enter a structured program at &lt;a href="https://acoding.academy/"&gt;Apple Coding Academy&lt;/a&gt; — whether the &lt;span class="high"&gt;Swift Developer Program&lt;/span&gt;, the &lt;span class="high"&gt;Swift Agentic Engineering Program&lt;/span&gt;, or any of their courses — the first thing you notice is the pace. It is high. Concepts pile up fast because there is a designed curve, an order that maximizes retention.&lt;/p&gt;
&lt;p&gt;The result is that after several courses, when I come across a new API in &lt;span class="high"&gt;Swift&lt;/span&gt;, I do not process it the way I did before. The pattern recognition is faster. I see the method signature, identify the protocols it implements, infer the behavior before reading the documentation. It is not intuition — it is trained speed.&lt;/p&gt;
&lt;h3&gt;Speed of comprehension&lt;/h3&gt;
&lt;p&gt;The first thing that changes is how you read code. Before the training, reading an unfamiliar &lt;span class="high"&gt;Swift&lt;/span&gt; file was a sequential process: line by line, chasing references, building the mental model slowly.&lt;/p&gt;
&lt;p&gt;After enough hours of training, reading approaches scanning. You identify the patterns — &lt;span class="high"&gt;async/await&lt;/span&gt;, protocol composition, the structure of an &lt;span class="high"&gt;actor&lt;/span&gt; — before processing the detail. Comprehension time collapses.&lt;/p&gt;
&lt;p&gt;That matters more than it seems. Now, with the use of AI, I spend more time reading code than writing it. If reading and understanding speed goes up, delivery time goes down without quality changing.&lt;/p&gt;
&lt;h3&gt;Speed of analysis&lt;/h3&gt;
&lt;p&gt;The second thing that changes is how I analyze problems. Apple Coding Academy’s training has a trait that I found uncomfortable at first: the problems are not well defined. You have a goal, a set of tools, and the expectation that you are the one who traces the path.&lt;/p&gt;
&lt;p&gt;That trains something specific: separating what matters from what does not before you start working. Identifying the real point of friction, not the surface symptom.&lt;/p&gt;
&lt;p&gt;In my day to day, that training translates into not jumping straight to fixing what is visible when a bug or an architecture decision appears. First I analyze. The analysis is faster because I have more trained context. The solution arrives sooner and with fewer iterations.&lt;/p&gt;
&lt;h3&gt;Speed of development&lt;/h3&gt;
&lt;p&gt;The third thing that changes is execution. When comprehension and analysis are faster, development is too — not because I write faster, but because, with the use of AI, I write less code by hand and spend that time reviewing and steering what the model proposes. Less exploration code, fewer discarded prototypes, fewer refactors of things that should never have been written that way.&lt;/p&gt;
&lt;p&gt;Apple Coding Academy’s training carries this in its DNA: Apple’s design philosophy rewards clarity and economy. Less surface for the same behavior. You absorb that even if nobody explains it to you explicitly — you pick it up from the ecosystem.&lt;/p&gt;
&lt;h3&gt;The courses I have taken at Apple Coding Academy&lt;/h3&gt;
&lt;p&gt;I have gone through several stages within the &lt;a href="https://acoding.academy/"&gt;Apple Coding Academy&lt;/a&gt; catalog. The &lt;span class="high"&gt;Bootcamp&lt;/span&gt; gave me the fundamentals of the ecosystem: types, memory management, &lt;span class="high"&gt;Swift Concurrency&lt;/span&gt;, protocol composition, integration with native frameworks. Five months of immersion that cover the full path to a professional Apple development profile.&lt;/p&gt;
&lt;p&gt;Then came more specific courses: &lt;span class="high"&gt;server-side&lt;/span&gt; modules with &lt;span class="high"&gt;Swift&lt;/span&gt;, &lt;span class="high"&gt;accessibility&lt;/span&gt;, and the visual redesign with &lt;span class="high"&gt;Liquid Glass&lt;/span&gt;. Each module targets a different layer of the stack and demands a level of depth I rarely reach on my own by just reading.&lt;/p&gt;
&lt;p&gt;More recently, the &lt;span class="high"&gt;Swift Agentic Engineering Program&lt;/span&gt; — sixty hours focused on developing &lt;span class="high"&gt;AI agents&lt;/span&gt;, &lt;span class="high"&gt;MCP&lt;/span&gt;, &lt;span class="high"&gt;Spec Driven Development&lt;/span&gt;, and the &lt;span class="high"&gt;Foundation Model Framework&lt;/span&gt;. Training that does not exist in the official documentation because it combines pieces that are still being defined in the ecosystem.&lt;/p&gt;
&lt;p&gt;Each layer added something different. The fundamentals gave me the vocabulary. The server courses gave me the perspective of what happens when the code is in production under real load. The system design ones taught me to think in seams — the points where behavior can vary without what is behind it changing. The agent ones gave me the framework to integrate AI into development workflows rigorously.&lt;/p&gt;
&lt;p&gt;What they have in common is that they all forced me out of my comfort zone at a speed I would not have sustained alone. That is what I cannot get from self-taught documentation: the external pace.&lt;/p&gt;
&lt;h3&gt;The compound effect&lt;/h3&gt;
&lt;p&gt;The interesting thing about this speed is that it compounds. You do not add hours of training linearly — the layers reinforce each other.&lt;/p&gt;
&lt;p&gt;Fast comprehension accelerates analysis because you process more context in less time. Fast analysis accelerates development because you reach the real problem sooner. And cleaner development reduces future maintenance time.&lt;/p&gt;
&lt;p&gt;The return is not in the first months. It is in year three, when you look back and confirm that the level of decisions you make in five minutes used to take you an afternoon.&lt;/p&gt;
&lt;p&gt;This connects directly with what I explore in &lt;a href="/blog/claude-code/"&gt;Claude Code&lt;/a&gt;: AI amplifies what you already know. If your understanding of the ecosystem is deep, the model’s suggestions are more useful because you know how to evaluate them. If it is shallow, the model hands you code that compiles but you do not understand. Training makes AI a better tool, not the other way around. That is precisely the bet behind &lt;a href="https://acoding.academy/"&gt;Apple Coding Academy&lt;/a&gt;’s &lt;span class="high"&gt;Swift Agentic Engineering Program&lt;/span&gt;: train the judgment before delegating to the model.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The answer to the question I asked myself — what does training from &lt;a href="https://acoding.academy/"&gt;Apple Coding Academy&lt;/a&gt; give me that the documentation alone would not — is this: the pace. The designed pressure. The environment that forces the fast connection of pieces.&lt;/p&gt;
&lt;p&gt;The documentation gives you the what. The training trains you the how and, above all, the speed with which you reach the why.&lt;/p&gt;
&lt;p&gt;What I notice in my work now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Faster diagnosis&lt;/strong&gt; — I identify the real problem before touching code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fewer iterations&lt;/strong&gt; — design decisions arrive before writing the first line&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More efficient code reading&lt;/strong&gt; — I process unfamiliar files without going line by line&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sounder judgment&lt;/strong&gt; — I know when a pattern is correct for the context and when it is just familiar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better use of AI&lt;/strong&gt; — I evaluate the model’s suggestions because I understand the ecosystem deeply&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/apple-coding-speed/"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/save-tokens/</id>
        <title>Save Tokens</title>
        <updated>2026-05-13T00:00:00Z</updated>
        <summary>How to cut tokens in Claude Code by moving the bash logic of your skills into reusable scripts that the AI invokes with a single line.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;I work with &lt;span class="high"&gt;Claude Code&lt;/span&gt; every day and started to notice a pattern that caught my attention: every time a skill or a conversation needed to run something in the shell, the AI burned tokens before executing anything. It reasoned about which command to build, drafted it, launched it, read the output, and then interpreted it to keep working.&lt;/p&gt;
&lt;p&gt;In a one-off operation, that cost is barely noticeable. The real problem shows up the moment there is &lt;strong&gt;repetition&lt;/strong&gt;. A skill that iterates over several files, a conversation that reorganizes directories, a flow that needs to perform the same operation with different arguments — all of that multiplies the reasoning cost by each iteration. Every loop pass rebuilds a command that, at heart, is identical to the previous one except for a single parameter.&lt;/p&gt;
&lt;p&gt;And here is the key point: that reasoning is &lt;strong&gt;deterministic work&lt;/strong&gt; dressed up as reasoning. I am not asking the AI to decide anything — only to rewrite a command it has already written before. It is exactly the kind of task a script solves better than a model.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The idea is simple: if a skill or a flow needs to run shell logic, that logic should not live in the AI’s head — it should live in a &lt;span class="high"&gt;.sh&lt;/span&gt; file that the AI invokes with a single line.&lt;/p&gt;
&lt;h3&gt;The mental shift&lt;/h3&gt;
&lt;p&gt;The natural instinct when configuring a skill is to describe step by step what the AI has to do:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;List the files in the directory, filter the ones ending in &lt;span class="high"&gt;.json&lt;/span&gt;, read each one, extract the &lt;span class="high"&gt;version&lt;/span&gt; field, find the largest, and return it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That description forces the AI to build each command, execute it, interpret the output, and chain the next step. Instead, the instruction should be:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Run the script &lt;span class="high"&gt;latest-version&lt;/span&gt; /path.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The difference is that all the reasoning about &lt;strong&gt;how&lt;/strong&gt; the operation is done stops happening on every invocation. It happens just once, when you write the script. From then on, the AI only needs to know &lt;strong&gt;what&lt;/strong&gt; to run and &lt;strong&gt;what format&lt;/strong&gt; to expect back.&lt;/p&gt;
&lt;h3&gt;Why loops are the critical case&lt;/h3&gt;
&lt;p&gt;The savings show up even in one-off operations, but they become &lt;strong&gt;huge&lt;/strong&gt; when the skill iterates. Without a script, every loop pass pays the cost of reasoning and building the command. With a script, the AI fires a single invocation and gets back the full aggregate.&lt;/p&gt;
&lt;p&gt;The practical rule I apply: if the operation can be aggregated in a single shell step, the script should do it — not the AI iterating externally.&lt;/p&gt;
&lt;h3&gt;The structure I use in my skills&lt;/h3&gt;
&lt;p&gt;Each of my skills follows the same shape: a short &lt;span class="high"&gt;SKILL.md&lt;/span&gt; describing the contract, and a &lt;span class="high"&gt;scripts/&lt;/span&gt; folder with the packaged logic.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/.claude/skills/
└── my-skill/
    ├── SKILL.md          ← instructions for the AI (what to run, what to expect)
    └── scripts/
        ├── operation-a.zsh
        └── operation-b.zsh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every time the skill needs a new capability, I add a script instead of adding inline bash instructions. The skill gains functionality without bloating the context the AI has to process on every invocation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I use &lt;span class="high"&gt;.zsh&lt;/span&gt; because I work on macOS, where it has been the default shell since Catalina and lets me take advantage of features bash does not have. The pattern works exactly the same with &lt;span class="high"&gt;.sh&lt;/span&gt; and bash. Pick the one that matches your environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And one important detail: I write the scripts with the help of the AI itself, just once. That reasoning — designing the right command, handling the edge cases, returning the proper JSON — happens while creating the script. After that, the work stays &lt;strong&gt;crystallized&lt;/strong&gt; in the file and is never paid for again.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The change is directly observable as soon as you separate deterministic logic from reasoning. The AI shifts from building commands to consuming them. From interpreting variable outputs to reading structured JSON. From iterating externally to receiving the full aggregate in a single call.&lt;/p&gt;
&lt;p&gt;The concrete benefits I am seeing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fewer tokens per invocation&lt;/strong&gt; — the reasoning about how to perform the operation happens just once, when writing the script, not every time the skill runs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt; — the script always returns the same format; the AI does not interpret variable outputs on every execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainability&lt;/strong&gt; — when the logic changes, I edit the script in one place; the &lt;span class="high"&gt;SKILL.md&lt;/span&gt; is not touched&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability in loops&lt;/strong&gt; — the script aggregates before returning; one call replaces N iterations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simpler skills&lt;/strong&gt; — the &lt;span class="high"&gt;SKILL.md&lt;/span&gt; describes &lt;strong&gt;what&lt;/strong&gt; to do, not &lt;strong&gt;how&lt;/strong&gt;; operational complexity lives in the scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern applies to any skill or flow that has to operate on the filesystem, process structured text, or run repetitive logic. If you find yourself describing in the &lt;span class="high"&gt;SKILL.md&lt;/span&gt; a sequence of bash commands the AI should build and execute, that sequence should probably be a script.&lt;/p&gt;
&lt;p&gt;As I explored in the article on &lt;a href="/blog/claude-code/"&gt;Claude Code&lt;/a&gt;, the key is not to give the AI more reasoning capacity so it solves more things — it is to structure the work so that it &lt;strong&gt;only reasons where it adds value&lt;/strong&gt;. The deterministic part is solved by a script. The creative and contextual part is solved by the AI. Each in its place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/save-tokens/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/uuid-v7/</id>
        <title>UUID v7</title>
        <updated>2026-05-06T00:00:00Z</updated>
        <summary>Swift uses UUID v4 by default, but with a small extension you can leverage all the advantages of UUID v7 without sacrificing performance.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In my project I use &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; as the database and &lt;span class="high"&gt;UUID&lt;/span&gt; as the identifier on every model. It’s the usual standard: universal, collision-free, secure. But I ran into a problem when my tables started to grow.&lt;/p&gt;
&lt;p&gt;&lt;span class="high"&gt;UUID v4&lt;/span&gt; is fully random. That means each new record is inserted at a random position inside the &lt;span class="high"&gt;B-Tree&lt;/span&gt; index. The index becomes fragmented, pages split, and write performance degrades progressively. On tables with millions of records, the impact is real.&lt;/p&gt;
&lt;p&gt;The solution has existed for a long time in databases like &lt;span class="high"&gt;MongoDB&lt;/span&gt; with &lt;span class="high"&gt;ObjectID&lt;/span&gt; or in systems that use &lt;span class="high"&gt;ULID&lt;/span&gt;: embed the timestamp into the identifier so new records always get inserted at the end of the index.&lt;/p&gt;
&lt;p&gt;&lt;span class="high"&gt;UUID v7&lt;/span&gt;, defined in &lt;span class="high"&gt;RFC 9562&lt;/span&gt;, brings exactly that to the UUID standard. &lt;span class="high"&gt;PostgreSQL 18&lt;/span&gt; already includes the native &lt;span class="high"&gt;uuidv7()&lt;/span&gt; function. But Swift — and by extension &lt;span class="high"&gt;Fluent&lt;/span&gt; — still generates &lt;span class="high"&gt;UUID v4&lt;/span&gt; by default with &lt;span class="high"&gt;UUID()&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;If the identifier is generated by my application before persisting it, I’m losing the v7 benefits even though my database already supports it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;I decided to create an extension on &lt;span class="high"&gt;UUID&lt;/span&gt; that implements &lt;span class="high"&gt;UUID v7&lt;/span&gt; generation directly in Swift, following the &lt;span class="high"&gt;RFC 9562&lt;/span&gt; specification.&lt;/p&gt;
&lt;h3&gt;Monotonic state&lt;/h3&gt;
&lt;p&gt;The first thing I need is a shared state that guarantees two UUIDs generated within the same millisecond are distinct and ordered. I use a &lt;span class="high"&gt;Mutex&lt;/span&gt; from the &lt;span class="high"&gt;Synchronization&lt;/span&gt; framework to make it thread-safe without resorting to &lt;span class="high"&gt;DispatchQueue&lt;/span&gt; or &lt;span class="high"&gt;actor&lt;/span&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;import Foundation
import Synchronization

private let v7State = Mutex(V7State())

private struct V7State: Sendable {
    var lastTimestamp: UInt64 = 0
    var sequence: UInt16 = 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;UUID generation&lt;/h3&gt;
&lt;p&gt;The main extension implements the static method &lt;span class="high"&gt;v7()&lt;/span&gt; that generates a UUID compliant with &lt;span class="high"&gt;RFC 9562&lt;/span&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension UUID {
    public static func v7() -&amp;gt; UUID {
        var bytes = (
            UInt8(0), UInt8(0), UInt8(0), UInt8(0),
            UInt8(0), UInt8(0), UInt8(0), UInt8(0),
            UInt8(0), UInt8(0), UInt8(0), UInt8(0),
            UInt8(0), UInt8(0), UInt8(0), UInt8(0)
        )

        let (timestamp, seq) = v7State.withLock { state -&amp;gt; (UInt64, UInt16) in
            var now = UInt64(Date().timeIntervalSince1970 * 1000)

            if now == state.lastTimestamp {
                if state.sequence &amp;gt;= 0x0FFF {
                    while now == state.lastTimestamp {
                        now = UInt64(Date().timeIntervalSince1970 * 1000)
                    }
                    state.lastTimestamp = now
                    state.sequence = UInt16.random(in: 0...0x0FFF)
                } else {
                    state.sequence += 1
                }
            } else {
                state.lastTimestamp = now
                state.sequence = UInt16.random(in: 0...0x0FFF)
            }

            return (now, state.sequence)
        }

        // Bytes 0–5: 48-bit timestamp (big-endian)
        bytes.0 = UInt8(truncatingIfNeeded: timestamp &amp;gt;&amp;gt; 40)
        bytes.1 = UInt8(truncatingIfNeeded: timestamp &amp;gt;&amp;gt; 32)
        bytes.2 = UInt8(truncatingIfNeeded: timestamp &amp;gt;&amp;gt; 24)
        bytes.3 = UInt8(truncatingIfNeeded: timestamp &amp;gt;&amp;gt; 16)
        bytes.4 = UInt8(truncatingIfNeeded: timestamp &amp;gt;&amp;gt; 8)
        bytes.5 = UInt8(truncatingIfNeeded: timestamp)

        // Byte 6: version (0x7_) | top 4 bits of sequence
        bytes.6 = 0x70 | UInt8(seq &amp;gt;&amp;gt; 8)

        // Byte 7: lower 8 bits of sequence
        bytes.7 = UInt8(truncatingIfNeeded: seq)

        // Bytes 8–15: random, then set variant
        var random: UInt64 = 0
        withUnsafeMutableBytes(of: &amp;amp;random) { buf in
            _ = SecRandomCopyBytes(kSecRandomDefault, buf.count, buf.baseAddress!)
        }
        bytes.8  = UInt8(truncatingIfNeeded: random &amp;gt;&amp;gt; 56)
        bytes.9  = UInt8(truncatingIfNeeded: random &amp;gt;&amp;gt; 48)
        bytes.10 = UInt8(truncatingIfNeeded: random &amp;gt;&amp;gt; 40)
        bytes.11 = UInt8(truncatingIfNeeded: random &amp;gt;&amp;gt; 32)
        bytes.12 = UInt8(truncatingIfNeeded: random &amp;gt;&amp;gt; 24)
        bytes.13 = UInt8(truncatingIfNeeded: random &amp;gt;&amp;gt; 16)
        bytes.14 = UInt8(truncatingIfNeeded: random &amp;gt;&amp;gt; 8)
        bytes.15 = UInt8(truncatingIfNeeded: random)

        // Byte 8: variant 0b10xx_xxxx
        bytes.8 = (bytes.8 &amp;amp; 0x3F) | 0x80

        return UUID(uuid: bytes)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The structure of the generated UUID respects the &lt;span class="high"&gt;RFC 9562&lt;/span&gt; specification:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bytes 0–5&lt;/strong&gt; (48 bits): &lt;span class="high"&gt;timestamp&lt;/span&gt; in milliseconds since epoch, big-endian. This is what guarantees chronological order.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Byte 6&lt;/strong&gt; (high nibble): version &lt;span class="high"&gt;0x7&lt;/span&gt; — identifies the UUID as v7.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bytes 6–7&lt;/strong&gt; (lower 12 bits): sequence counter. Guarantees monotonicity within the same millisecond.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bytes 8–15&lt;/strong&gt; (64 bits): random, generated with &lt;span class="high"&gt;SecRandomCopyBytes&lt;/span&gt;, with the two highest bits of byte 8 forced to &lt;span class="high"&gt;0b10&lt;/span&gt; to mark the &lt;span class="high"&gt;RFC 4122&lt;/span&gt; variant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The monotonic logic inside &lt;span class="high"&gt;withLock&lt;/span&gt; works like this: when two calls happen in the same millisecond, the counter is incremented. If the counter reaches the maximum (&lt;span class="high"&gt;0x0FFF&lt;/span&gt;, that is 4095 UUIDs in a single millisecond), the implementation actively waits for the next millisecond before continuing — it never generates two identical UUIDs.&lt;/p&gt;
&lt;h3&gt;Inspection utilities&lt;/h3&gt;
&lt;p&gt;In addition to generation, I added two properties to inspect any UUID: one that checks whether it is v7 and another that extracts the embedded &lt;span class="high"&gt;timestamp&lt;/span&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension UUID {
    public var isV7: Bool {
        (uuid.6 &amp;gt;&amp;gt; 4) == 0x07 &amp;amp;&amp;amp; (uuid.8 &amp;gt;&amp;gt; 6) == 0x02
    }

    public var v7Timestamp: Date? {
        guard isV7 else { return nil }

        let ms = UInt64(uuid.0) &amp;lt;&amp;lt; 40
            | UInt64(uuid.1) &amp;lt;&amp;lt; 32
            | UInt64(uuid.2) &amp;lt;&amp;lt; 24
            | UInt64(uuid.3) &amp;lt;&amp;lt; 16
            | UInt64(uuid.4) &amp;lt;&amp;lt; 8
            | UInt64(uuid.5)

        return Date(timeIntervalSince1970: Double(ms) / 1000.0)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span class="high"&gt;isV7&lt;/span&gt; verifies that the high nibble of byte 6 is &lt;span class="high"&gt;0x07&lt;/span&gt; (version 7) and that the two highest bits of byte 8 are &lt;span class="high"&gt;0b10&lt;/span&gt; (RFC 4122 variant). &lt;span class="high"&gt;v7Timestamp&lt;/span&gt; reconstructs the 48 bits of the timestamp from bytes 0–5 and converts them into a &lt;span class="high"&gt;Date&lt;/span&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;Adoption is immediate. In any &lt;span class="high"&gt;Fluent&lt;/span&gt; model, all I have to do is replace &lt;span class="high"&gt;UUID()&lt;/span&gt; with &lt;span class="high"&gt;UUID.v7()&lt;/span&gt; in the initializer:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// Before
self.id = UUID()

// After
self.id = UUID.v7()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The database type does not change — it’s still &lt;span class="high"&gt;UUID&lt;/span&gt;. &lt;span class="high"&gt;Fluent&lt;/span&gt; needs no modification. The change is completely transparent to the ORM.&lt;/p&gt;
&lt;p&gt;The benefits I got are direct:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ordered indexes&lt;/strong&gt; — new records are always inserted at the end of the &lt;span class="high"&gt;B-Tree&lt;/span&gt;, eliminating fragmentation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native chronological ordering&lt;/strong&gt; — &lt;span class="high"&gt;ORDER BY id&lt;/span&gt; is equivalent to &lt;span class="high"&gt;ORDER BY created_at&lt;/span&gt; without an extra column&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedded timestamp&lt;/strong&gt; — I can extract when a record was created directly from the UUID with &lt;span class="high"&gt;v7Timestamp&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backward compatible&lt;/strong&gt; — the format is still standard UUID; any system that accepts &lt;span class="high"&gt;UUID v4&lt;/span&gt; accepts &lt;span class="high"&gt;v7&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thread-safe&lt;/strong&gt; — works correctly from multiple concurrent threads thanks to the &lt;span class="high"&gt;Mutex&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;let id = UUID.v7()

print(id.isV7)          // true
print(id.v7Timestamp!)  // the creation date embedded in the UUID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you’re already on &lt;span class="high"&gt;PostgreSQL 18&lt;/span&gt;, the native &lt;span class="high"&gt;uuidv7()&lt;/span&gt; function generates UUIDs with the same structure. UUIDs generated from Swift and from PostgreSQL are interchangeable and comparable to each other.&lt;/p&gt;
&lt;p&gt;The change from &lt;span class="high"&gt;UUID()&lt;/span&gt; to &lt;span class="high"&gt;UUID.v7()&lt;/span&gt; is a one-liner. The advantages, permanent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/uuid-v7/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/sigbus/</id>
        <title>SIGBUS</title>
        <updated>2026-04-29T00:00:00Z</updated>
        <summary>How Swift Testing crashes with SIGBUS when formatting errors from enums with associated values, and how I fixed it with three custom helpers.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;I’m writing tests with &lt;span class="high"&gt;Swift Testing&lt;/span&gt;, Apple’s native stack. I compare two models with &lt;span class="high"&gt;#expect(a == b)&lt;/span&gt; and verify errors with &lt;span class="high"&gt;#expect(throws: MyError.self)&lt;/span&gt; — the API the library ships with out of the box. I run the tests. And my binary dies with this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-text"&gt;*** Signal 10: Backtracing from 0x18a3c4e8c... done ***
Process terminated by signal 10 (SIGBUS)
Stack trace:
  ...
  _swift_buildDemanglingForMetadata
  ...
  swift_testing.Issue.record(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span class="high"&gt;SIGBUS&lt;/span&gt;. It’s not an assertion failure. It’s the &lt;strong&gt;Swift runtime&lt;/strong&gt; breaking inside the test runner itself. And it only happens when a test &lt;strong&gt;fails&lt;/strong&gt; — if everything passes, I never notice the problem.&lt;/p&gt;
&lt;p&gt;The pattern is very specific. The runtime crashes when I use &lt;span class="high"&gt;#expect(==)&lt;/span&gt; on &lt;span class="high"&gt;enums with associated values&lt;/span&gt;, or when I use &lt;span class="high"&gt;#expect(throws:)&lt;/span&gt; on an error type that is an enum with associated values. As soon as the test fails, swift-testing tries to build the error message by asking the runtime to reconstruct the type metadata of the values it compared. And there it blows up: &lt;span class="high"&gt;_swift_buildDemanglingForMetadata&lt;/span&gt;, signal 10, end of story.&lt;/p&gt;
&lt;p&gt;It’s bug &lt;a href="https://github.com/swiftlang/swift/issues/76608"&gt;swiftlang/swift#76608&lt;/a&gt;, open since September 2024 and reproducible on Swift 6.3.1 on macOS 26 — both in debug and release.&lt;/p&gt;
&lt;p&gt;The curious thing is that the happy path never touches this bug. If the comparison passes, there’s no error message to format, no &lt;span class="high"&gt;Mirror&lt;/span&gt;, no SIGBUS. The bug only shows up exactly when you need the test output the most: when something fails. And I couldn’t move a single piece forward until I solved it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The key clue is &lt;strong&gt;where&lt;/strong&gt; swift-testing crashes: on the &lt;em&gt;failure message&lt;/em&gt; path, formatting the values. If I do the comparison myself and only pass swift-testing a &lt;span class="high"&gt;Bool&lt;/span&gt;, there’s nothing for it to reconstruct. No metadata to reflect on. The mirror doesn’t break because it’s never used.&lt;/p&gt;
&lt;p&gt;The trick that avoids the SIGBUS fits in two lines:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;let isEqual = actual == expected
#expect(isEqual)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s the whole idea. I compute the equality myself, swift-testing only receives an already-resolved &lt;span class="high"&gt;Bool&lt;/span&gt;. With no values to reflect on, there’s no &lt;span class="high"&gt;Mirror&lt;/span&gt;, no SIGBUS. The price is losing the values in the failure message — but I get that back by formatting the text by hand before passing it to &lt;span class="high"&gt;Issue.record&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;From there I built a &lt;span class="high"&gt;TestKit&lt;/span&gt; package with three helpers that apply the same idea to the three cases where I was tripping over the bug:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;expectEqual(actual, expected)&lt;/span&gt; — comparison of any &lt;span class="high"&gt;Equatable&lt;/span&gt;: structs, models, enums with associated values. Replaces &lt;span class="high"&gt;#expect(a == b)&lt;/span&gt; across every test in the monorepo.&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;expectThrows(E.self) { … }&lt;/span&gt; — verifies that a closure throws an error of the expected type. It does &lt;span class="high"&gt;do/catch&lt;/span&gt; by hand and checks with &lt;span class="high"&gt;catch is E&lt;/span&gt; — only the type, never the value. Covers what &lt;span class="high"&gt;#expect(throws:)&lt;/span&gt; tried to provide out of the box. Has both sync and async overloads.&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;expectEqualLines(actual, expected)&lt;/span&gt; — line-by-line diff for verifying generated SQL and other inline snapshots. The comparison is also reduced to &lt;span class="high"&gt;Bool&lt;/span&gt; before touching the reporter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Three helpers, one single idea: &lt;strong&gt;compute the result outside the macro, pass only the boolean&lt;/strong&gt;. When Swift 6.4 eventually closes &lt;a href="https://github.com/swiftlang/swift/issues/76608"&gt;issue 76608&lt;/a&gt;, I just have to swap the body of the helpers for direct &lt;span class="high"&gt;#expect&lt;/span&gt; calls and the suite won’t even notice.&lt;/p&gt;
&lt;p&gt;There’s one extra detail to keep failure messages readable: I conformed the error and schema types to &lt;span class="high"&gt;CustomStringConvertible&lt;/span&gt; with a static &lt;span class="high"&gt;switch&lt;/span&gt; over the cases — &lt;strong&gt;never interpolating &lt;span class="high"&gt;(self)&lt;/span&gt;&lt;/strong&gt;, because that would invoke &lt;span class="high"&gt;Mirror&lt;/span&gt; again and reopen the trap.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;My test suite runs again without SIGBUS and with reasonably readable failure messages. The rule I now apply across the monorepo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;expectEqual(a, b)&lt;/span&gt; for any comparison between structs, enums or models&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;expectThrows(E.self) { … }&lt;/span&gt; to verify the type of error thrown&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;#expect(…)&lt;/span&gt; directly only when the argument is already &lt;span class="high"&gt;Bool&lt;/span&gt;, &lt;span class="high"&gt;nil&lt;/span&gt; or &lt;span class="high"&gt;contains&lt;/span&gt; — there’s no value metadata to reflect on, and the bug is not triggered&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A suite that doesn’t crash&lt;/strong&gt; — failures count as failures, not as process aborts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Minimal stack&lt;/strong&gt; — I only depend on &lt;span class="high"&gt;Testing&lt;/span&gt; and Swift’s own toolchain&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom messages&lt;/strong&gt; — I control how each side is printed on failure, thanks to &lt;span class="high"&gt;CustomStringConvertible&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime bug isolation&lt;/strong&gt; — when Swift 6.4 fixes it, I just swap the body of the helpers and the suite won’t even notice&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lesson I’m taking away: when a runtime tool breaks on the error path, the solution isn’t to give up on the language, it’s to keep the tool from going down that path. I do the comparison myself, I pass a boolean to the reporter, and the mirror stays intact.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/sigbus/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/two-bigs/</id>
        <title>Two Bigs</title>
        <updated>2026-04-22T00:00:00Z</updated>
        <summary>Most tech content bores me because it lacks passion. My three filters to find what deserves my time and why Julio and Oliver always pass them.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;I consume a lot of tech content. Podcasts, live streams, newsletters, articles. And over time I’ve developed a filter I can’t turn off: within the first thirty seconds I can tell whether the person speaking feels passion for what they’re saying or is simply repeating what they’ve read.&lt;/p&gt;
&lt;p&gt;And most of the time, it’s the latter.&lt;/p&gt;
&lt;p&gt;I’m not talking about technical quality. There’s content that is well produced, well structured, with great titles, that bores me deeply. Because there’s no one behind it who gets excited about what they’re saying. It’s content manufactured to exist, not because someone needed to tell it.&lt;/p&gt;
&lt;p&gt;When there’s no passion, I lose interest in less than a minute. I don’t care how relevant the topic is. If the speaker doesn’t vibrate with what they’re saying, neither do I. And it’s not a conscious decision — it’s a physical reaction. My brain disconnects and there’s no way back.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;I’m always actively searching for content within my areas of interest. Technology, development, AI, Apple, and so on. And I always give what I find a chance, because I know what it takes to create. Behind every video, every podcast, every article there are hours of work, preparation, and decisions. That deserves at least an honest try on my part.&lt;/p&gt;
&lt;p&gt;But I don’t have all the time in the world, so I’ve naturally ended up applying three filters.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Passion.&lt;/strong&gt; If in the first few seconds I don’t sense that the speaker cares about what they’re saying, my attention shuts off on its own. No passion, no attention.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manners and respect.&lt;/strong&gt; I want to learn from someone who argues without attacking and disagrees without belittling. Passion without respect is noise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Satisfaction.&lt;/strong&gt; That when it’s over I feel I’m taking something with me. A new idea, a perspective I didn’t have before. If not, I don’t come back.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;These filters have left me with a short list of favorites. And on that list are &lt;a href="https://www.linkedin.com/in/jcfmunoz/"&gt;Julio César Fernández&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/oliverac/"&gt;Oliver Nabani&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When the stars align and these two come together for a live stream, &lt;a href="https://www.youtube.com/watch?v=z6RIj_j6knE"&gt;Un futuro sin apps&lt;/a&gt;, something happens that doesn’t happen with anyone else. I enjoy it like a kid. I laugh out loud. I talk to the screen. I rewind to jot down on paper something they dropped in passing that I want to look into later. I’m bursting to go to the bathroom but I don’t pause the video because I don’t want to miss a single sentence.&lt;/p&gt;
&lt;p&gt;And always, always, when it’s over I think the same thing: “they were only on for fifteen minutes, they keep making these shorter.” I check the clock. Almost three hours. I didn’t even notice time passing.&lt;/p&gt;
&lt;p&gt;That’s passion. That’s what I’m looking for. That’s what these two transmit every time they sit in front of a camera together.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Two greats. Two passions. Two friends. And the rest of us are their guests.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/two-bigs/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/astro-to-saga/</id>
        <title>Astro to Saga</title>
        <updated>2026-04-15T00:00:00Z</updated>
        <summary>Why I migrated my portfolio from Astro to Saga, a static site generator written in Swift, and how I removed Node from my stack for good.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;My portfolio was running on &lt;span class="high"&gt;Astro&lt;/span&gt; with &lt;span class="high"&gt;Bun&lt;/span&gt;. Everything worked fine. Fast, comfortable, no technical complaints.&lt;/p&gt;
&lt;p&gt;But something didn’t feel right. Every time I opened the project I found a &lt;span class="high"&gt;package.json&lt;/span&gt;, a &lt;span class="high"&gt;tailwind.config&lt;/span&gt;, an &lt;span class="high"&gt;astro.config&lt;/span&gt;, and a &lt;span class="high"&gt;node_modules&lt;/span&gt; folder with hundreds of dependencies I didn’t even know existed. That feeling of losing control over what lives inside your own project bothers me. I like to know what my code runs and why it’s there. I had already removed Tailwind in a &lt;a href="/blog/tailwind-to-css/"&gt;previous migration to vanilla CSS&lt;/a&gt;, but the rest of the JavaScript ecosystem was still there.&lt;/p&gt;
&lt;p&gt;And at the end of the day, I’m a Swift developer. My daily work is Swift. Yet to generate my own portfolio I depended on a completely foreign ecosystem. If someone visited my repository, they wouldn’t see a Swift developer. They’d see just another JavaScript project.&lt;/p&gt;
&lt;p&gt;The question was simple: if I trust Swift for everything else, why don’t I trust Swift for this?&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;I stopped to think about what I actually needed. My portfolio is not a web application. It has no state and no complex interactivity. It’s a set of static HTML pages generated from Markdown. I don’t need React, Vue, or hydration. I need something that reads Markdown, transforms it into HTML, and writes it to disk.&lt;/p&gt;
&lt;p&gt;I found &lt;a href="https://github.com/loopwerk/Saga"&gt;Saga&lt;/a&gt;, a static site generator written in Swift. Deliberately minimalist: it reads files, applies transformations, and writes HTML. What it doesn’t include, you decide. It also features hot reload for development, something I didn’t expect to find in such a small project. That philosophy convinced me more than any feature list.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Advantages&lt;/th&gt;
&lt;th&gt;Disadvantages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Entire stack in Swift — one language, no context switching&lt;/td&gt;
&lt;td&gt;Small ecosystem — if something doesn’t exist, you build it yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiler-verified HTML thanks to the typed DSL&lt;/td&gt;
&lt;td&gt;Learning curve with the DSL syntax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native image pipeline with no external dependencies&lt;/td&gt;
&lt;td&gt;Image pipeline only works on macOS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node removed from the local environment&lt;/td&gt;
&lt;td&gt;Limited community and documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full control over every dependency in the project&lt;/td&gt;
&lt;td&gt;More upfront work for features that come out of the box in other frameworks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;The underlying reflection&lt;/h3&gt;
&lt;p&gt;The decision wasn’t technical. It was about coherence. I want anyone visiting my repository to see Swift. I’m not saying &lt;span class="high"&gt;Astro&lt;/span&gt; is bad — it’s excellent. But my portfolio is my business card, and that card has to speak about me.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The site you’re reading right now is generated with &lt;span class="high"&gt;Saga&lt;/span&gt;, compiled with Swift, and deployed on &lt;span class="high"&gt;Cloudflare Pages&lt;/span&gt; without Node ever touching my machine.&lt;/p&gt;
&lt;p&gt;What surprised me wasn’t the technical outcome. It was the feeling of coherence. Opening my portfolio and seeing that everything, from the first line to the last deploy, is Swift gives me a sense of calm that’s hard to explain.&lt;/p&gt;
&lt;p&gt;If you’re a Swift developer and your portfolio runs on Node, I’m not saying you should change. I’m saying it’s worth asking yourself why. In the end, the tool you choose to present yourself says something about you. I chose the one that represents me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/astro-to-saga/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/my-first-macro/</id>
        <title>My First Macro</title>
        <updated>2026-04-01T00:00:00Z</updated>
        <summary>My first Swift macro: automatically generating the id, timestamps, init and namespace in every Fluent model at compile time with SwiftSyntax.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In my project I have dozens of &lt;span class="high"&gt;Fluent&lt;/span&gt; models, and there is a block of code that repeats in absolutely every single one of them:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;public static let space: String? = &amp;quot;sales&amp;quot;

@ID() public var id: UUID?
public init() {}

@Timestamp(.createdAt, on: .create) public var createdAt: Date?
@Timestamp(.updatedAt, on: .update) public var updatedAt: Date?
@Timestamp(.deletedAt, on: .delete) public var deletedAt: Date?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Always the same block. In every model. No exceptions.&lt;/p&gt;
&lt;p&gt;The namespace changes, but the structure is identical. With every new model, I copy and paste, adjust the namespace, and hope I don’t forget anything. With 30 models, that boilerplate becomes noise that makes it harder for me to read what actually matters: the model’s logic.&lt;/p&gt;
&lt;p&gt;My solution in Swift for this kind of problem is a &lt;span class="high"&gt;macro&lt;/span&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;A Swift macro can generate new members in a class or struct at compile time. Exactly what I need. The result I’m looking for is to write this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;@FluentModel(.sales)
public final class ProductModel: Model {
    public static let schema = &amp;quot;products&amp;quot;

    @Field(.name) public var name: String
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And have the compiler automatically inject &lt;span class="high"&gt;space&lt;/span&gt;, &lt;span class="high"&gt;id&lt;/span&gt;, &lt;span class="high"&gt;init()&lt;/span&gt;, and the three &lt;span class="high"&gt;timestamps&lt;/span&gt;. Without writing them. Without maintaining them.&lt;/p&gt;
&lt;h3&gt;Package structure&lt;/h3&gt;
&lt;p&gt;Swift macros require two separate targets: the &lt;strong&gt;public interface&lt;/strong&gt; (what I consume as a developer) and the &lt;strong&gt;plugin&lt;/strong&gt; (the implementation that the compiler executes). In my case, both live in the same &lt;span class="high"&gt;Macros&lt;/span&gt; package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;Macros&lt;/span&gt; — declares the macro and the &lt;span class="high"&gt;DatabaseSpace&lt;/span&gt; enum&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;MacrosPlugin&lt;/span&gt; — implements the expansion with &lt;span class="high"&gt;SwiftSyntax&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;DatabaseSpace — namespaces as types&lt;/h3&gt;
&lt;p&gt;Before defining the macro, I need to model the available namespaces. Instead of using loose strings, I decided that the &lt;span class="high"&gt;DatabaseSpace&lt;/span&gt; enum should make them exhaustive and safe at compile time:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;public enum DatabaseSpace: String, CaseIterable, Sendable {
    case sales, warehouse
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows writing &lt;span class="high"&gt;@FluentModel(.sales)&lt;/span&gt; instead of &lt;span class="high"&gt;@FluentModel(“sales”)&lt;/span&gt;. If the namespace doesn’t exist, the compiler tells you before anything runs.&lt;/p&gt;
&lt;h3&gt;The macro declaration&lt;/h3&gt;
&lt;p&gt;The public interface of the macro is surprisingly compact:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;@attached(member, names: named(space), named(id), named(init), named(createdAt), named(updatedAt), named(deletedAt))
public macro FluentModel(_ space: DatabaseSpace? = nil) = #externalMacro(
    module: &amp;quot;MacrosPlugin&amp;quot;,
    type: &amp;quot;FluentModelMacro&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;span class="high"&gt;@attached(member, names:)&lt;/span&gt; attribute tells the compiler two things: that this macro adds members to the declaration where it’s applied, and exactly which names it will generate. Declaring the names is mandatory — Swift needs them to resolve the symbol tree before expanding the macro.&lt;/p&gt;
&lt;h3&gt;The implementation — FluentModelMacro&lt;/h3&gt;
&lt;p&gt;The implementation conforms to the &lt;span class="high"&gt;MemberMacro&lt;/span&gt; protocol and returns an array of &lt;span class="high"&gt;DeclSyntax&lt;/span&gt; — fragments of Swift code that the compiler inserts into the model:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;import SwiftSyntax
import SwiftSyntaxMacros

public struct FluentModelMacro: MemberMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingMembersOf declaration: some DeclGroupSyntax,
        conformingTo protocols: [TypeSyntax],
        in context: some MacroExpansionContext
    ) throws -&amp;gt; [DeclSyntax] {
        [
            spaceDecl(from: node),
            &amp;quot;@ID() public var id: UUID?&amp;quot;,
            &amp;quot;public init() {}&amp;quot;,
            &amp;quot;@Timestamp(.createdAt, on: .create) public var createdAt: Date?&amp;quot;,
            &amp;quot;@Timestamp(.updatedAt, on: .update) public var updatedAt: Date?&amp;quot;,
            &amp;quot;@Timestamp(.deletedAt, on: .delete) public var deletedAt: Date?&amp;quot;,
        ]
    }

    private static func spaceDecl(from node: AttributeSyntax) -&amp;gt; DeclSyntax {
        let value = node.arguments?.as(LabeledExprListSyntax.self)?
            .first?.expression.as(MemberAccessExprSyntax.self)
            .map { &amp;quot;\&amp;quot;\($0.declName.baseName.text)\&amp;quot;&amp;quot; } ?? &amp;quot;nil&amp;quot;
        return &amp;quot;public static let space: String? = \(raw: value)&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;span class="high"&gt;expansion&lt;/span&gt; method directly returns the array of declarations, with no intermediate variables. Each element is a Swift literal that the compiler injects as-is into the model.&lt;/p&gt;
&lt;p&gt;The key is &lt;span class="high"&gt;spaceDecl&lt;/span&gt;, which encapsulates all the extraction and generation logic in a single method. It navigates the attribute’s syntax tree with &lt;span class="high"&gt;SwiftSyntax&lt;/span&gt; using optional chaining: it accesses the arguments as &lt;span class="high"&gt;LabeledExprListSyntax&lt;/span&gt;, takes the first expression, casts it to &lt;span class="high"&gt;MemberAccessExprSyntax&lt;/span&gt; (because the argument is an enum case like &lt;span class="high"&gt;.sales&lt;/span&gt;), and with &lt;span class="high"&gt;.map&lt;/span&gt; converts it into a quoted string. If any step in the chain fails, the &lt;span class="high"&gt;??&lt;/span&gt; operator returns &lt;span class="high"&gt;“nil”&lt;/span&gt;. Finally, &lt;span class="high"&gt;\(raw:)&lt;/span&gt; interpolates the value directly into the &lt;span class="high"&gt;DeclSyntax&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, I need a &lt;span class="high"&gt;CompilerPlugin&lt;/span&gt; to register the macro — it’s the entry point that the compiler loads to know which macros are available:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;import SwiftCompilerPlugin
import SwiftSyntaxMacros

@main
struct MacrosPlugin: CompilerPlugin {
    let providingMacros: [Macro.Type] = [
        FluentModelMacro.self,
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;With the macro installed, each model is clean and noise-free:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;@FluentModel(.sales)
public final class ProductModel: Model {
    public static let schema = &amp;quot;products&amp;quot;

    @Field(.name) public var name: String
    @OptionalField(.description) public var description: String?
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiler expands &lt;span class="high"&gt;@FluentModel(.sales)&lt;/span&gt; and automatically generates:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;public static let space: String? = &amp;quot;sales&amp;quot;
@ID() public var id: UUID?
public init() {}
@Timestamp(.createdAt, on: .create) public var createdAt: Date?
@Timestamp(.updatedAt, on: .update) public var updatedAt: Date?
@Timestamp(.deletedAt, on: .delete) public var deletedAt: Date?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if a model doesn’t belong to any namespace — like views — you simply omit the argument:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;@FluentModel()
public final class OrderSummaryModel: Model {
    public static let schema = &amp;quot;order_summaries&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In that case, &lt;span class="high"&gt;space&lt;/span&gt; is generated as &lt;span class="high"&gt;nil&lt;/span&gt; and Fluent ignores the schema prefix.&lt;/p&gt;
&lt;p&gt;The benefits in numbers: &lt;strong&gt;32 models&lt;/strong&gt; with the macro applied. &lt;strong&gt;6 lines removed&lt;/strong&gt; per model. Over &lt;strong&gt;190 lines of boilerplate&lt;/strong&gt; that no longer exist in the repository and will never need to be maintained again.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/my-first-macro/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/db-admin-vs-developer/</id>
        <title>DB Admin vs Dev</title>
        <updated>2026-03-23T00:00:00Z</updated>
        <summary>Separating database administration from development is not an opinion but a responsibility: where the instance ends and migrations begin.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In many projects I’ve seen how application code ends up doing things it shouldn’t: creating roles, configuring database server parameters, managing instance-level permissions. Everything mixed in the same place, as if it were a single responsibility.&lt;/p&gt;
&lt;p&gt;But it’s not. The database has two distinct worlds: &lt;span class="high"&gt;administration&lt;/span&gt; and &lt;span class="high"&gt;development&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Mixing them is one of the most common mistakes, and also one of the most expensive. A developer configuring the instance from their code is crossing a boundary that doesn’t belong to them. And an administrator trying to decide which tables or schemas the application uses is doing the same from the other side.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The separation is conceptually clean: administration belongs to the server, development belongs to the code.&lt;/p&gt;
&lt;h3&gt;The administrator’s territory&lt;/h3&gt;
&lt;p&gt;The administrator manages the database instance. Their work includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Server configuration&lt;/strong&gt; — performance parameters, instance-level extensions, settings that require a restart&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Roles and credentials&lt;/strong&gt; — creating users, assigning secure passwords, defining who can connect&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Databases&lt;/strong&gt; — creating them, setting timeouts, revoking default access&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this is infrastructure. These are decisions made once, executed by someone with superuser privileges, and they have nothing to do with business logic. Ideally, they live as versioned SQL scripts in the repository, but separate from the application code.&lt;/p&gt;
&lt;h3&gt;The developer’s territory&lt;/h3&gt;
&lt;p&gt;The developer manages the application’s data structure: schemas, tables, indexes, views, and everything that needs to exist for the application to work.&lt;/p&gt;
&lt;p&gt;This responsibility is expressed through &lt;span class="high"&gt;migrations&lt;/span&gt; — versioned, reversible, and reviewable pieces of code like any other change. It doesn’t matter if you use Swift, Python, Go, or TypeScript: the principle is the same. Your application’s data structure must be automatically reproducible in any environment.&lt;/p&gt;
&lt;h3&gt;The gray area: admin or development?&lt;/h3&gt;
&lt;p&gt;There are cases that seem ambiguous. Database roles are a good example. The role itself — with its password and connection permissions — is an infrastructure concept. The administrator creates it.&lt;/p&gt;
&lt;p&gt;But permissions on specific tables — what it can read, what it can write, on which schema — that depends on the developer. The table has to exist before it can have permissions, so those permissions go in migrations, not in infrastructure scripts.&lt;/p&gt;
&lt;p&gt;The rule I apply is simple: if the object exists before the application, it’s administration. If its existence depends on the application creating it, it’s development.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;When you apply this separation, the setup process becomes clear and reproducible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The administrator prepares the instance → server configured, roles created, database ready&lt;/li&gt;
&lt;li&gt;The developer runs the migrations → schemas, tables, and application permissions applied automatically&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each layer has its own tool. The administrator doesn’t touch the application code. The developer doesn’t need superuser credentials. Nobody steps on the other’s territory.&lt;/p&gt;
&lt;p&gt;This separation also makes teamwork easier: the administration scripts are run by whoever has server access, just once. The migrations are run by any developer on the team in their local environment, as many times as needed.&lt;/p&gt;
&lt;p&gt;The database is not yours alone as a developer. But the data structure of your application is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/db-admin-vs-developer/"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/migrate-spaces/</id>
        <title>Migrate Spaces</title>
        <updated>2026-03-14T00:00:00Z</updated>
        <summary>How to automate PostgreSQL namespace creation using FluentKit migrations in Vapor, replacing error-prone manual setup with versioned, reversible code.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In the previous article about &lt;a href="/blog/schema-space/"&gt;Schemas and Spaces&lt;/a&gt; we saw how to assign a namespace to a model using the &lt;span class="high"&gt;space&lt;/span&gt; property. Many of you asked the same question: how are those spaces created in the database?&lt;/p&gt;
&lt;p&gt;The most common answer was to do it manually, running a &lt;span class="high"&gt;CREATE SCHEMA&lt;/span&gt; in the PostgreSQL console before launching the migrations. It works, but it breaks something fundamental: if the database doesn’t exist or the environment is new, the process fails before reaching the actual migrations.&lt;/p&gt;
&lt;p&gt;The other problem is that managing namespaces manually doesn’t scale. As soon as you have multiple environments (development, staging, production) or a team, keeping that manual sync becomes a source of silent errors.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The solution is to turn namespace creation into just another &lt;span class="high"&gt;migration&lt;/span&gt;. This way, it runs automatically in the correct order, is reversible, and is versioned alongside the rest of the code.&lt;/p&gt;
&lt;p&gt;The key lies in combining &lt;span class="high"&gt;FluentKit&lt;/span&gt; with &lt;span class="high"&gt;SQLKit&lt;/span&gt;. FluentKit manages the migration lifecycle, but to execute arbitrary SQL we need to access the underlying driver through the &lt;span class="high"&gt;SQLDatabase&lt;/span&gt; protocol. To keep the code organized, we encapsulate the namespaces in an enum with an action that determines whether they are created or dropped:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;import FluentKit
import SQLKit

enum SRSchema: String, CaseIterable {
    case account, ai, device, location, sport, task, view

    enum Action {
        case create, drop
    }

    static func execute(_ action: Action, on db: Database) async throws {
        let sql = db as! any SQLDatabase
        let template: (String) -&amp;gt; String =
            switch action {
            case .create: { &amp;quot;CREATE SCHEMA IF NOT EXISTS \($0)&amp;quot; }
            case .drop: { &amp;quot;DROP SCHEMA IF EXISTS \($0) RESTRICT&amp;quot; }
            }
        for schema in allCases {
            try await sql.raw(&amp;quot;\(unsafeRaw: template(schema.rawValue))&amp;quot;).run()
        }
    }
}

struct SchemasMigration: AsyncMigration {
    func prepare(on db: Database) async throws {
        try await SRSchema.execute(.create, on: db)
    }

    func revert(on db: Database) async throws {
        try await SRSchema.execute(.drop, on: db)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some important details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SRSchema as CaseIterable&lt;/strong&gt;: by iterating over &lt;span class="high"&gt;allCases&lt;/span&gt;, we ensure all defined spaces are created without having to list them manually in the migration. Adding a new space only requires adding a case to the enum.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Action&lt;/strong&gt;: the inner enum models the two possible operations (&lt;span class="high"&gt;create&lt;/span&gt; and &lt;span class="high"&gt;drop&lt;/span&gt;), allowing the same &lt;span class="high"&gt;execute&lt;/span&gt; method to be reused in both the migration’s &lt;span class="high"&gt;prepare&lt;/span&gt; and &lt;span class="high"&gt;revert&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Switch expression&lt;/strong&gt;: a Swift &lt;em&gt;switch expression&lt;/em&gt; is used to select the SQL template based on the action. Each branch returns a closure that generates the corresponding statement, keeping the logic compact and readable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CREATE SCHEMA IF NOT EXISTS&lt;/strong&gt;: makes the migration idempotent. If the schema already exists, it doesn’t fail — it simply continues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DROP SCHEMA … RESTRICT&lt;/strong&gt;: in the &lt;span class="high"&gt;revert&lt;/span&gt;, the &lt;span class="high"&gt;RESTRICT&lt;/span&gt; modifier prevents dropping a schema that contains tables. It’s a safety net that avoids accidental data loss when reverting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unsafeRaw&lt;/strong&gt;: used to interpolate the schema name directly into the SQL. It’s safe here because the value comes from our own enum, never from external input.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;This migration must be the first one registered. Before creating any table, the namespaces need to exist. In the migration runner, the order looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;fluent.migrations.add(
    SchemasMigration()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When running migrations in a new environment, the entire process is automatic:&lt;/p&gt;
&lt;p&gt;And if at any point you need to add a new domain, you just add a case to the &lt;span class="high"&gt;SRSchema&lt;/span&gt; enum and the next time migrations run, the schema appears. No touching the database manually, no extra documentation to maintain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/migrate-spaces/" rel="alternate"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/tailwind-to-css/</id>
        <title>Tailwind to CSS</title>
        <updated>2026-03-07T00:00:00Z</updated>
        <summary>Why I migrated my portfolio from Tailwind CSS to vanilla CSS and the benefits I gained in performance, bundle size, and full control over the code.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;My portfolio had been running perfectly with &lt;span class="high"&gt;Tailwind CSS&lt;/span&gt; for months. Everything was fine. Styles were in place, components looked great, and the site loaded fast. So why change? Because working well and working the best way possible are two different things. When you stop to analyze what’s under the hood, you sometimes discover you’re carrying a layer you &lt;span class="high"&gt;don’t need&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Don’t get me wrong: &lt;span class="high"&gt;Tailwind CSS&lt;/span&gt; is an amazing tool. I’ve used it in professional projects and will keep using it where it makes sense. But in a personal portfolio built with &lt;span class="high"&gt;Astro&lt;/span&gt;, I started noticing a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unnecessary dependency&lt;/strong&gt;: 3 extra packages (&lt;span class="high"&gt;tailwindcss&lt;/span&gt;, &lt;span class="high"&gt;@tailwindcss/vite&lt;/span&gt;, &lt;span class="high"&gt;@tailwindcss/typography&lt;/span&gt;) for a project that didn’t really need them&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Abstraction layer&lt;/strong&gt;: Tailwind generates CSS from utility classes. It’s a layer between what you write and what the browser interprets. In a small project, that layer adds overhead without adding value&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extra weight&lt;/strong&gt;: The generated CSS included utilities I wasn’t always taking full advantage of. ~20KB extra that the user downloaded unnecessarily&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Less control&lt;/strong&gt;: When you want something very specific, you end up fighting the framework instead of writing exactly what you need&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The idea was simple: remove Tailwind and replace it with &lt;span class="high"&gt;vanilla CSS&lt;/span&gt; using a &lt;strong&gt;design tokens&lt;/strong&gt; system built on CSS &lt;span class="high"&gt;custom properties&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;What are design tokens? They’re CSS variables that define your design system:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;:root {
  --color-primary: oklch(0.55 0.2 260);
  --color-gray-100: oklch(0.97 0 0);
  --color-gray-900: oklch(0.21 0.006 285.75);

  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-xl: 1.25rem;

  --spacing: 0.25rem;
  --radius-lg: 0.5rem;

  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --transition-colors: color 0.15s, background-color 0.15s, border-color 0.15s;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, I get consistency across the entire project without depending on any framework. Just pure CSS that the browser understands &lt;span class="high"&gt;directly&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The process meant migrating 38 files in a single commit. Each &lt;span class="high"&gt;Astro&lt;/span&gt; component went from using Tailwind classes to having its own scoped &lt;span class="high"&gt;&amp;lt;style&amp;gt;&lt;/span&gt; block:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before (Tailwind):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;&amp;lt;header class=&amp;quot;flex items-center justify-between px-6 py-4 bg-white dark:bg-gray-900&amp;quot;&amp;gt;
  &amp;lt;nav class=&amp;quot;flex gap-4&amp;quot;&amp;gt;
    &amp;lt;a class=&amp;quot;text-sm font-medium text-gray-700 hover:text-blue-500&amp;quot;&amp;gt;Blog&amp;lt;/a&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After (Vanilla CSS):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-astro"&gt;&amp;lt;header class=&amp;quot;header&amp;quot;&amp;gt;
  &amp;lt;nav class=&amp;quot;nav&amp;quot;&amp;gt;
    &amp;lt;a class=&amp;quot;nav-link&amp;quot;&amp;gt;Blog&amp;lt;/a&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;style&amp;gt;
  .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: calc(var(--spacing) * 4) calc(var(--spacing) * 6);
    background-color: var(--color-white);
  }

  :global(.dark) .header {
    background-color: var(--color-gray-900);
  }

  .nav {
    display: flex;
    gap: calc(var(--spacing) * 4);
  }

  .nav-link {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--color-gray-700);
    transition: var(--transition-colors);
  }

  .nav-link:hover {
    color: var(--color-blue-500);
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;More lines? Yes. Clearer and more maintainable? Absolutely.&lt;/p&gt;
&lt;p&gt;The migration wasn’t just “remove Tailwind and add CSS.” There were interesting pitfalls worth sharing.&lt;/p&gt;
&lt;h3&gt;Scoped styles and &lt;span class="high"&gt;&amp;lt;slot&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;In &lt;span class="high"&gt;Astro&lt;/span&gt;, styles inside &lt;span class="high"&gt;&amp;lt;style&amp;gt;&lt;/span&gt; are scoped by default. This means each component receives a unique attribute (&lt;span class="high"&gt;data-astro-cid-*&lt;/span&gt;) and styles only affect that component.&lt;/p&gt;
&lt;p&gt;The catch: content passed via &lt;span class="high"&gt;&amp;lt;slot&amp;gt;&lt;/span&gt; does not receive that attribute. If a parent component tries to style slotted content, the styles won’t apply.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The solution&lt;/strong&gt;: use &lt;span class="high"&gt;:global()&lt;/span&gt; for selectors targeting slotted content:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;.container :global(a) {
  color: var(--color-primary);
  text-decoration: underline;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;span class="high"&gt;opacity&lt;/span&gt; vs background transparency&lt;/h3&gt;
&lt;p&gt;With Tailwind, I used classes like &lt;span class="high"&gt;bg-opacity-70&lt;/span&gt;. When migrating, my first instinct was to reach for the &lt;span class="high"&gt;opacity&lt;/span&gt; property. &lt;strong&gt;Mistake&lt;/strong&gt;: &lt;span class="high"&gt;opacity&lt;/span&gt; affects the entire element, including its children.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The correct solution&lt;/strong&gt;: &lt;span class="high"&gt;color-mix()&lt;/span&gt; for background-only transparency:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-css"&gt;.modal-overlay {
  /* BAD: affects everything */
  opacity: 0.7;

  /* GOOD: only the background is transparent */
  background-color: color-mix(in oklab, var(--color-gray-900) 70%, transparent);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The numbers speak for themselves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;~20KB less&lt;/strong&gt; CSS delivered to the browser&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3 dependencies removed&lt;/strong&gt; from &lt;span class="high"&gt;package.json&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 abstraction layers&lt;/strong&gt; between your code and the browser&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Faster builds&lt;/strong&gt; by eliminating Tailwind’s processing step&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full control&lt;/strong&gt; over every line of CSS generated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;span class="high"&gt;package.json&lt;/span&gt; went from having &lt;span class="high"&gt;tailwindcss&lt;/span&gt;, &lt;span class="high"&gt;@tailwindcss/vite&lt;/span&gt;, and &lt;span class="high"&gt;@tailwindcss/typography&lt;/span&gt; to no styling dependencies at all. Just pure CSS. And the best part: the &lt;span class="high"&gt;tailwind.config.mjs&lt;/span&gt; file was completely removed. &lt;span class="high"&gt;One less configuration&lt;/span&gt; to maintain.&lt;/p&gt;
&lt;p&gt;The styling system ended up organized into 4 CSS files:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;design-tokens.css&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Colors, typography, spacing, shadows, transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;base-reset.css&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Minimal CSS reset and utilities like &lt;span class="high"&gt;.sr-only&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;prose.css&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Blog content typography with dark mode support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;layout.css&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Global layout classes (&lt;span class="high"&gt;.bodyLayout&lt;/span&gt;, &lt;span class="high"&gt;.mainLayout&lt;/span&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All imported from a single &lt;span class="high"&gt;global.css&lt;/span&gt;. Clean, predictable, and no black magic.&lt;/p&gt;
&lt;p&gt;Migrating from &lt;span class="high"&gt;Tailwind CSS&lt;/span&gt; to &lt;span class="high"&gt;vanilla CSS&lt;/span&gt; isn’t for everyone or every project. In large teams or projects with many developers, Tailwind remains a fantastic choice for its consistency and development speed. But in a personal project like a portfolio built with &lt;span class="high"&gt;Astro&lt;/span&gt;, where performance matters and full control is a luxury you can afford, removing that abstraction layer is liberating. In fact, I ended up taking this philosophy even further and migrated the entire portfolio from Astro to Swift — I tell the full story in &lt;a href="/blog/astro-to-saga/"&gt;Astro to Saga&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s like painting a picture: you can use templates and tools to guide you, or you can pick up the brush and create exactly what you have in mind. Both options are valid. But when the canvas is yours, &lt;span class="high"&gt;painting by hand has its charm&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/tailwind-to-css/" rel="alternate"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/cupertino-mcp/</id>
        <title>Cupertino MCP</title>
        <updated>2026-02-11T00:00:00Z</updated>
        <summary>Discover Cupertino MCP, the tool that integrates all of Apple's documentation directly into your AI for iOS/macOS development.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When I develop for iOS, macOS, or any Apple platform, I know the drill:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I’m coding peacefully&lt;/li&gt;
&lt;li&gt;I need to check how something works in SwiftUI&lt;/li&gt;
&lt;li&gt;I open Safari / my favorite browser&lt;/li&gt;
&lt;li&gt;I search on Google / DuckDuckGo&lt;/li&gt;
&lt;li&gt;I land on Apple’s official documentation&lt;/li&gt;
&lt;li&gt;I read, understand, go back to my IDE&lt;/li&gt;
&lt;li&gt;I repeat this process 47 times a day&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or even worse: I ask my favorite AI and it gives me &lt;span class="high"&gt;outdated&lt;/span&gt; or straight-up &lt;span class="high"&gt;hallucinated&lt;/span&gt; information because its knowledge isn’t up to date with the latest versions of Swift or SwiftUI.&lt;/p&gt;
&lt;p&gt;What if I told you there’s a better way?&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mihaelamj/cupertino"&gt;Cupertino MCP&lt;/a&gt;&lt;/strong&gt; is a tool that &lt;span class="high"&gt;locally indexes all of Apple’s documentation&lt;/span&gt; and makes it available to my AI through the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In plain English: it’s like giving my AI (Claude, in my case) a direct, verified gateway to all of Apple’s official documentation.&lt;/p&gt;
&lt;p&gt;How much documentation are we talking about?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;302,424+ pages&lt;/strong&gt; of official documentation&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;307 frameworks&lt;/strong&gt; indexed&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;9,699 Swift packages&lt;/strong&gt; cataloged&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;606 sample code projects&lt;/strong&gt; from Apple&lt;br /&gt;&lt;br /&gt;
Complete &lt;strong&gt;Human Interface Guidelines&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Swift Evolution proposals&lt;/strong&gt; (~400 proposals)&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Apple Archive guides&lt;/strong&gt; (legacy but valuable documentation)&lt;/p&gt;
&lt;p&gt;All of this, available offline, no internet needed, and with &lt;span class="high"&gt;zero risk of AI hallucinations&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;h3&gt;Absolute precision&lt;/h3&gt;
&lt;p&gt;When Claude answers me about Apple APIs, it no longer guesses. It searches the real official documentation and gives me &lt;span class="high"&gt;100% verified&lt;/span&gt; information.&lt;/p&gt;
&lt;p&gt;No more: &lt;em&gt;“I think in SwiftUI 6 you use it like this…”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now it’s: &lt;em&gt;“According to the official SwiftUI 6 documentation…”&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Development speed&lt;/h3&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Question → Wait for response → Doubt → Open Safari →
Search Google → Read docs → Go back to IDE → Implement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Question → Response with official documentation → Implement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span class="high"&gt;Massive time savings&lt;/span&gt; on every query.&lt;/p&gt;
&lt;h3&gt;Powerful search&lt;/h3&gt;
&lt;p&gt;Cupertino uses SQLite FTS5 with BM25 ranking. In plain English: &lt;span class="high"&gt;ultra-fast&lt;/span&gt; searches (under 100ms) with relevant results sorted by importance.&lt;/p&gt;
&lt;p&gt;I can filter by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Specific framework&lt;/li&gt;
&lt;li&gt;Platform version (iOS 17, macOS 14, etc.)&lt;/li&gt;
&lt;li&gt;Documentation type (API, examples, guides)&lt;/li&gt;
&lt;li&gt;Sample code search&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Contextualized learning&lt;/h3&gt;
&lt;p&gt;I don’t just get the correct API, I also get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real code examples from Apple&lt;/li&gt;
&lt;li&gt;Documented best practices&lt;/li&gt;
&lt;li&gt;Official design patterns&lt;/li&gt;
&lt;li&gt;Alternatives and deprecations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s like having an Apple mentor inside Claude.&lt;/p&gt;
&lt;h3&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;Cupertino MCP is a perfect example of how AI tools become truly useful when they have access to &lt;span class="high"&gt;verified, up-to-date information&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;It’s not about giving AI more power to “guess better.” It’s about &lt;span class="high"&gt;connecting it with reliable sources&lt;/span&gt; so it gives you precise answers.&lt;/p&gt;
&lt;p&gt;If you develop for Apple ecosystems, Cupertino MCP is a 5-minute investment (installation) that will save you hours every week. And if you use Claude Code like I do, the experience gets even better.&lt;/p&gt;
&lt;p&gt;Do I recommend it? Absolutely. It’s a must-have if you develop with Swift, SwiftUI, or any Apple framework.&lt;/p&gt;
&lt;p&gt;Is it for everyone? Only if you develop for Apple platforms. If you work with other technologies, look for similar tools — there are surely equivalents for Android, web, and more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/cupertino-mcp/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/which-ai/</id>
        <title>Which AI</title>
        <updated>2026-02-04T00:00:00Z</updated>
        <summary>How to deal with AI tool fatigue as a developer: why chasing every new model is impossible and how to pick the right tools without burning out from FOMO.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Déjà Vu: The Web 2.0 Frameworks&lt;/h2&gt;
&lt;p&gt;Remember that golden era when a &lt;span class="high"&gt;new web framework&lt;/span&gt; popped up every week?&lt;/p&gt;
&lt;p&gt;One that was more modern, more powerful, more performant, more beautiful… One that came to &lt;span class="high"&gt;“kill” all the previous ones&lt;/span&gt; and was going to become the &lt;span class="high"&gt;only framework you’d ever need&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Spoiler: none of them killed anything, and now we have more frameworks than ever.&lt;/p&gt;
&lt;p&gt;Well then, &lt;span class="high"&gt;welcome to the AI era&lt;/span&gt;: same concept, but on steroids.&lt;/p&gt;
&lt;h2&gt;Notification Fatigue&lt;/h2&gt;
&lt;p&gt;Picture this: you’re peacefully coding with your trusted AI, workflow on point, productivity at its peak… and suddenly:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Notification:&lt;/strong&gt; &lt;em&gt;“Revolutionary new AI just launched!”&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;em&gt;“How to use it in 10 minutes”&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Tutorial:&lt;/strong&gt; &lt;em&gt;“Integrate it into your workflow TODAY”&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Thread:&lt;/strong&gt; &lt;em&gt;“Why you should switch right now”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And here comes the funniest part: the content creator who just 1 hour ago had a video explaining “Why the old AI is the future”, now has another video about “Why the new AI is better than everything”.&lt;/p&gt;
&lt;p&gt;I don’t blame them, honestly. It’s their job to stay up to date and create relevant content. But as a consumer… it’s exhausting.&lt;/p&gt;
&lt;h2&gt;The Impossible Race&lt;/h2&gt;
&lt;p&gt;If it’s already hard to keep up with the daily updates of the AI you’re using (and trust me, there are so many updates), imagine having to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Try every new AI that comes out&lt;/li&gt;
&lt;li&gt;Learn its interface and quirks&lt;/li&gt;
&lt;li&gt;Integrate it into your workflow&lt;/li&gt;
&lt;li&gt;Compare it with the ones you already use&lt;/li&gt;
&lt;li&gt;Decide if the switch is worth it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span class="high"&gt;Result:&lt;/span&gt; Mission impossible.&lt;/p&gt;
&lt;p&gt;And don’t get me wrong: I love having options. In fact, it’s wonderful that so much competition and innovation exists. More options = more better.&lt;/p&gt;
&lt;h2&gt;My Red Line&lt;/h2&gt;
&lt;p&gt;Now, there’s something I’m adamant about:&lt;/p&gt;
&lt;p&gt;&lt;span class="high"&gt;I will never use an AI that requires full permissions from the start&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;None of that &lt;em&gt;“Give me full access to your machine and trust me”&lt;/em&gt; stuff. That’s not for me.&lt;/p&gt;
&lt;p&gt;My philosophy is clear:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tell me what you’re going to do (or plan it out)&lt;/li&gt;
&lt;li&gt;I review the changes&lt;/li&gt;
&lt;li&gt;I approve (or don’t approve)&lt;/li&gt;
&lt;li&gt;Then you proceed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No free will for AI with admin privileges. &lt;span class="high"&gt;Full control&lt;/span&gt; is my mantra, as I already shared in my article about &lt;a href="/blog/claude-code/"&gt;Claude Code&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;It’s No Longer Just a Model&lt;/h2&gt;
&lt;p&gt;And here’s what’s truly fascinating: AI is no longer simply &lt;em&gt;“a model”&lt;/em&gt; or &lt;em&gt;“an app”&lt;/em&gt; that you install and use.&lt;/p&gt;
&lt;p&gt;Now we have complete ecosystems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Base + fine-tuned models&lt;/li&gt;
&lt;li&gt;IDE integrations&lt;/li&gt;
&lt;li&gt;Plugins and extensions&lt;/li&gt;
&lt;li&gt;APIs and SDKs&lt;/li&gt;
&lt;li&gt;Orchestration platforms&lt;/li&gt;
&lt;li&gt;Specialized tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The speed at which all of this is evolving is incredible. What was science fiction 2 years ago is now your &lt;span class="high"&gt;daily coding assistant&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Living in an Incredible Era&lt;/h2&gt;
&lt;p&gt;At the end of the day, I believe we’re living through something &lt;span class="high"&gt;historic&lt;/span&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Battle of the IDEs&lt;/strong&gt;: VSCode vs. Cursor vs. WindSurf vs. Zed vs…&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Battle of the Frameworks&lt;/strong&gt;: React vs. Vue vs. Svelte vs. Solid vs…&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Battle of the AIs&lt;/strong&gt;: Claude vs. ChatGPT vs. Gemini vs. Copilot vs…&lt;/p&gt;
&lt;p&gt;Trying to keep up with everything is exhausting, I’ll admit it. Personally, I find it &lt;span class="high"&gt;impossible&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;But it’s also exciting. Every day there’s something new to learn, something to try, something that makes you rethink how you work.&lt;/p&gt;
&lt;h2&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;The takeaway from all of this is simple:&lt;/p&gt;
&lt;p&gt;You don’t need to use everything. Really, you don’t.&lt;/p&gt;
&lt;p&gt;Find the tools that work for you, learn to use them well, stay up to date with their updates, and it’s perfectly fine to explore alternatives from time to time.&lt;/p&gt;
&lt;p&gt;But don’t drive yourself crazy trying to ride every wave at once. Pick your surfboard, learn to use it well, and enjoy the ride.&lt;/p&gt;
&lt;p&gt;And if one day you decide to switch boards, go for it. But make it a conscious decision, not a reaction to the &lt;span class="high"&gt;FOMO&lt;/span&gt; (&lt;em&gt;Fear Of Missing Out&lt;/em&gt;) created by 47 simultaneous notifications.&lt;/p&gt;
&lt;p&gt;&lt;span class="high"&gt;Happy coding&lt;/span&gt; (with whichever AI you prefer)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/which-ai/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/conferences-25/</id>
        <title>Conferences 25</title>
        <updated>2026-01-28T00:00:00Z</updated>
        <summary>Curated list of the best iOS, Swift, and Apple developer conferences in 2025 with dates, locations, and links to watch every talk for free.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;iOSConfSG&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; January 15-17&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Singapore&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://2025.iosconf.sg/"&gt;iosconf&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=N1H9lvHwQxc&amp;amp;list=PLED4k3CZkY9RBltAgj-o9xSFOMOhBdmXm"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;AppDevCon&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; March 18–21&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Amsterdam&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://appdevcon.nl"&gt;appdevcon&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://appdevcon.nl/videos/"&gt;videos&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Swift Heroes&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 8–9&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Turin&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://swiftheroes.com"&gt;swiftheroes&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=cVNtQK34xdw&amp;amp;list=PLfCiO1zYKkARXhFxrqv3WR6b6JEp4LaAN"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;try! Swift Tokyo&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 9–11&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Tokyo&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://tryswift.jp/en"&gt;tryswift&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=qY09lmDo7GU&amp;amp;list=PLCl5NM4qD3u_Azg7gKw5CK_DqSLeb4QMY"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Deep Dish Swift&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; April 27–29&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Chicago&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://deepdishswift.com"&gt;deepdishswift&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=eN6NJR67HL4&amp;amp;list=PLGLg44jP5U-4l1OERkMTPrzgG7nQkGGYy"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;iOSKonf&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; May 13–15&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Skopje&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://ioskonf.mk"&gt;ioskonf&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=AIvCc1v5F4g&amp;amp;list=PLVKQDFwOy1XZXhFOdWfXKdjCz4r_LBeto"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Swift Craft&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; May 19–21&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Kent&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://swiftcraft.uk"&gt;swiftcraft&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=XR4XrVvHUQo&amp;amp;list=PLugrLwuQvERolaa2XA0dl4UK9Z8XEwiAA"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;WWDC&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; June 9–13&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Cupertino&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://developer.apple.com/wwdc25"&gt;apple&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=IrGYUq1mklk&amp;amp;list=PLjODKV8YBFHZKEn1wsUCL1n-q7tzysEBM"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;NSSpain&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; September 17-19&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Logroño&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://nsspain.com"&gt;nsspain&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=NjW7lxtZwIM&amp;amp;list=PLztE34GS_piKKQ6y1dkkuhW76jLBHm3NV"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Swift Connection&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; October 6–7&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Paris&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://swiftconnection.io"&gt;swiftconnection&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=N05WEfGPp3M&amp;amp;list=PLZsRQnRG-mlIkHsjeax_cRq6kAclNrWBF"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;SwiftLeeds&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; October 7–8&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Leeds&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://swiftleeds.co.uk"&gt;swiftleeds&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=iNiQHGKULQw&amp;amp;list=PL-wmxEeX64YTpDbpfszWMV76oZZO3wxZH"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Pragma Conference&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; October 30–31&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Bologna&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://pragmaconference.com"&gt;pragmaconference&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=7o6Fkj3buxM&amp;amp;list=PLAVm70iJlMuvTihK1OzK9S4Vzw_KO71b0"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Do iOS&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; November 12-13&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Place:&lt;/strong&gt; Amsterdam&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Web:&lt;/strong&gt; &lt;a href="https://do-ios.com"&gt;do-ios&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Video:&lt;/strong&gt; &lt;a href="https://youtube.com/watch?v=q-Y44dh6sTo&amp;amp;list=PLJEA4wsA0WeSP_NB1LcqcA7tBjhcg1u4U"&gt;youtube&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/conferences-25/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/async-concurrent-map/</id>
        <title>Async Concurrent Map</title>
        <updated>2026-01-21T00:00:00Z</updated>
        <summary>How to combine concurrent processing with chunking to balance speed and resource usage in massive asynchronous operations over collections.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In backend applications with &lt;span class="high"&gt;Vapor&lt;/span&gt;, when we process large volumes of data concurrently, we face an optimization dilemma:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sequential processing with &lt;span class="high"&gt;asyncMap&lt;/span&gt;: guarantees resource control, but is slow by processing elements one by one.&lt;/li&gt;
&lt;li&gt;Fully concurrent processing with &lt;span class="high"&gt;concurrentMap&lt;/span&gt;: maximizes speed, but can saturate resources by launching thousands of simultaneous tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, when processing 10,000 records with external API calls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;asyncMap&lt;/span&gt;: 10,000 sequential calls → very slow but controlled.&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;concurrentMap&lt;/span&gt;: 10,000 simultaneous calls → very fast but can exhaust connections/memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need a solution that combines both approaches: divide the work into manageable groups and process each group concurrently, balancing speed and resource usage.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;We extend &lt;span class="high"&gt;Collection&lt;/span&gt; with a function that combines chunking (division into groups) and concurrent processing, allowing configuration of chunk size and timeout per chunk.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Collection where Element: Sendable {
    func asyncConcurrentMap&amp;lt;T: Sendable&amp;gt;(
        chunkSize: Int? = nil,
        timeout: Double? = nil,
        _ transform: @escaping @Sendable (Element) async throws -&amp;gt; T
    ) async throws -&amp;gt; [T] {
        guard let chunkSize else {
            return try await concurrentMap(transform)
        }

        return try await chunks(ofCount: chunkSize)
            .asyncMap(timeout: timeout) {
                try await $0.concurrentMap(transform)
            }.flatMap { $0 }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;span class="high"&gt;chunkSize&lt;/span&gt; is not specified, uses pure &lt;span class="high"&gt;concurrentMap&lt;/span&gt; (fully concurrent processing).&lt;/li&gt;
&lt;li&gt;If &lt;span class="high"&gt;chunkSize&lt;/span&gt; is specified, divides the collection into groups with &lt;span class="high"&gt;chunks(ofCount:)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Processes the chunks sequentially with &lt;span class="high"&gt;asyncMap&lt;/span&gt; (with optional timeout).&lt;/li&gt;
&lt;li&gt;Within each chunk, processes the elements concurrently with &lt;span class="high"&gt;concurrentMap&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Flattens the results with &lt;span class="high"&gt;flatMap&lt;/span&gt; to return a unified array.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// Process 10,000 records in chunks of 100
// 100 concurrent tasks at a time, 100 times
let results = try await records.asyncConcurrentMap(
    chunkSize: 100,
    timeout: 30.0
) { record in
    try await apiClient.process(record)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this approach:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Perfect balance&lt;/strong&gt;: combines the speed of concurrent processing with the control of sequential processing by chunks.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Resource control&lt;/strong&gt;: limits the number of simultaneous tasks to the chunk size, avoiding saturation.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Timeout per chunk&lt;/strong&gt;: detects and handles problematic chunks without blocking all processing.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Full flexibility&lt;/strong&gt;: use chunking when you need it, or pure concurrent processing when you don’t.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: allows processing millions of records by adjusting chunk size according to available resources.&lt;/p&gt;
&lt;p&gt;If you need pure parallel execution, I covered that in &lt;a href="/blog/concurrent-map/"&gt;Concurrent Map&lt;/a&gt;. And if your sequential chunks need rate limiting between them, I added that capability in &lt;a href="/blog/async-map-timeout/"&gt;Async Map Timeout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This solution is the natural evolution of &lt;span class="high"&gt;asyncMap&lt;/span&gt; and &lt;span class="high"&gt;concurrentMap&lt;/span&gt;, combining the best of both worlds to optimize massive data processing in backend applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/async-concurrent-map/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/async-map-timeout/</id>
        <title>Async Map Timeout</title>
        <updated>2026-01-14T00:00:00Z</updated>
        <summary>Extending asyncMap with an optional timeout parameter to control rate limiting between async operations and avoid 429 errors from external APIs.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="/blog/async-map/"&gt;post about AsyncMap&lt;/a&gt; we saw how to process collections in a sequential asynchronous manner. However, when working with external APIs that implement rate limiting, we face a critical problem:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// Process 1000 URLs sequentially
let results = try await urls.asyncMap { url in
    try await apiClient.fetch(url)  // ⚠️ 1000 calls without pause
}
// Error 429: Too Many Requests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Making consecutive calls without pauses can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exceed rate limits&lt;/strong&gt;: APIs reject with &lt;span class="high"&gt;429 Too Many Requests&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saturate external services&lt;/strong&gt;: overload of simultaneous connections.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Waste resources&lt;/strong&gt;: forcing retries consumes more time and bandwidth.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Temporary blocks&lt;/strong&gt;: some APIs block the IP after multiple violations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We need a way to control the pace of sequential operations, adding intentional pauses between each processing.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;We update &lt;span class="high"&gt;asyncMap&lt;/span&gt; by adding an optional &lt;span class="high"&gt;timeout&lt;/span&gt; parameter that introduces a configurable pause after processing each element.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Sequence {
    func asyncMap&amp;lt;T&amp;gt;(
        timeout: Double? = nil,
        _ transform: (Element) async throws -&amp;gt; T
    ) async throws -&amp;gt; [T] {
        var results = [T]()
        results.reserveCapacity(underestimatedCount)
        for element in self {
            try await results.append(transform(element))
            if let timeout {
                try await Task.sleep(for: .seconds(timeout))
            }
        }
        return results
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key changes from the original version&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Optional and backward-compatible &lt;span class="high"&gt;timeout: Double? = nil&lt;/span&gt; parameter.&lt;br /&gt;&lt;br /&gt;
If timeout is specified, adds &lt;span class="high"&gt;Task.sleep(for: .seconds(timeout))&lt;/span&gt; after each element.&lt;br /&gt;&lt;br /&gt;
Maintains original behavior when timeout is not specified (no pauses).&lt;br /&gt;&lt;br /&gt;
Allows dynamic rate limiting adjustment according to each API’s limits.&lt;/p&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// Rate limiting: 1 call per second
let results = try await urls.asyncMap(timeout: 1.0) {
    try await apiClient.fetch($0)
}

// Aggressive rate limiting: 1 call every 5 seconds
let results = try await endpoints.asyncMap(timeout: 5.0) {
    try await scraper.parse($0)
}

// Without timeout: original behavior (maximum speed)
let results = try await localFiles.asyncMap {
    try await processFile($0)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this update:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configurable rate limiting&lt;/strong&gt;: controls the call pace according to each API’s limits.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Block prevention&lt;/strong&gt;: avoids &lt;span class="high"&gt;429&lt;/span&gt; errors and temporary IP suspensions.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Full backward compatibility&lt;/strong&gt;: without timeout it works exactly as before.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Flexibility per use case&lt;/strong&gt;: adjusts timeout based on external service tolerance.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Predictable processing&lt;/strong&gt;: easily calculate total time (n elements × timeout).&lt;/p&gt;
&lt;p&gt;This update turns &lt;span class="high"&gt;asyncMap&lt;/span&gt; into a complete tool for controlled sequential processing, ideal for integration with APIs that impose rate limits and need a regulated request flow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/async-map-timeout/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/copy-to/</id>
        <title>Copy To</title>
        <updated>2026-01-07T00:00:00Z</updated>
        <summary>Export large datasets from PostgreSQL to CSV in Vapor using the native COPY TO command, avoiding manual serialization and reducing memory consumption.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In backend applications with &lt;span class="high"&gt;Vapor&lt;/span&gt;, we often need to export large volumes of data from the database to files for different purposes: backups, offline analysis, integration with external systems, or data audits.&lt;/p&gt;
&lt;p&gt;Using traditional queries with &lt;span class="high"&gt;Fluent&lt;/span&gt; and then manually serializing the results presents several drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High memory consumption&lt;/strong&gt;: loading thousands of records into memory to process them one by one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow processing&lt;/strong&gt;: manual serialization to &lt;span class="high"&gt;CSV&lt;/span&gt; requires iterating and formatting each record.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of optimization&lt;/strong&gt;: doesn’t leverage the native export capabilities of the database engine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unnecessary complexity&lt;/strong&gt;: manual management of formats, character escaping, and null value handling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For bulk export scenarios, we need a strategy that leverages &lt;span class="high"&gt;PostgreSQL&lt;/span&gt;’s native capabilities to generate files efficiently.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;We extend &lt;span class="high"&gt;Database&lt;/span&gt; with a function that executes the &lt;span class="high"&gt;COPY … TO&lt;/span&gt; command from &lt;span class="high"&gt;PostgreSQL&lt;/span&gt;, allowing data export directly from the table schema to &lt;span class="high"&gt;CSV&lt;/span&gt; files in the filesystem.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Database {
    func exportCSV(
        _ model: any Model.Type,
        file: PathEnum)
        async throws {
        let query = SQLQueryString(
            &amp;quot;&amp;quot;&amp;quot;
            COPY \&amp;quot;\(unsafeRaw: model.space ?? &amp;quot;public&amp;quot;)\&amp;quot;.
            \&amp;quot;\(unsafeRaw: model.schema)\&amp;quot;
            TO '\(unsafeRaw: file.rawValue)'
            WITH (FORMAT csv, HEADER true, DELIMITER ',',
            QUOTE '&amp;quot;', ESCAPE '&amp;quot;', NULL '')
            &amp;quot;&amp;quot;&amp;quot;
        )

        try await self.sqlDatabase
            .raw(query).run()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;PostgreSQL&lt;/span&gt;’s &lt;span class="high"&gt;COPY … TO&lt;/span&gt;, the most efficient method for bulk exports.&lt;/li&gt;
&lt;li&gt;The &lt;span class="high"&gt;model.space&lt;/span&gt; parameter supports custom schemas (defaults to &lt;span class="high"&gt;“public”&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;model.schema&lt;/span&gt; automatically obtains the table name from the &lt;span class="high"&gt;Fluent&lt;/span&gt; model.&lt;/li&gt;
&lt;li&gt;Standard &lt;span class="high"&gt;CSV&lt;/span&gt; configuration: headers included, delimiters, and proper null value handling.&lt;/li&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;unsafeRaw&lt;/span&gt; for direct interpolation in the SQL query.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func exportRegions() async throws {
    try await db.exportCSV(
        LocationRegionModel.self,
        file: file
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this approach:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimal performance&lt;/strong&gt;: &lt;span class="high"&gt;COPY … TO&lt;/span&gt; is much faster than manual serialization.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Resource efficiency&lt;/strong&gt;: &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; writes directly to the file without loading data into application memory.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Consistent format&lt;/strong&gt;: the database engine guarantees a valid &lt;span class="high"&gt;CSV&lt;/span&gt; with proper escaping.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Native integration&lt;/strong&gt;: leverages optimized capabilities of the &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; engine.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: allows exporting millions of records without impacting application performance.&lt;/p&gt;
&lt;p&gt;This solution is the perfect complement to &lt;span class="high"&gt;importCSV&lt;/span&gt;, forming a pair of functions that enables bidirectional data movement between &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; and the filesystem efficiently and reliably. If you haven’t seen the import side yet, I explain how to build it using PostgreSQL’s &lt;span class="high"&gt;COPY FROM&lt;/span&gt; in &lt;a href="/blog/copy-from/"&gt;Copy From&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/copy-to/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/copy-from/</id>
        <title>Copy From</title>
        <updated>2025-12-31T00:00:00Z</updated>
        <summary>How to perform bulk inserts in Vapor using the PostgreSQL COPY command to import CSV files and speed up massive database insertions.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In backend applications with &lt;span class="high"&gt;Vapor&lt;/span&gt;, when we need to insert large volumes of data into the database (initial migrations, catalog imports, bulk loading from external APIs), using &lt;span class="high"&gt;.save()&lt;/span&gt; in a loop generates multiple individual transactions.&lt;/p&gt;
&lt;p&gt;This results in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High latency&lt;/strong&gt;: each insert opens/closes connection and transaction overhead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Poor throughput&lt;/strong&gt;: doesn’t leverage the database engine’s bulk insertion capabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeout risk&lt;/strong&gt;: slow operations that may fail in environments with time constraints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For mass import scenarios (thousands or millions of records), we need a bulk insert strategy that leverages native &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; capabilities.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;We extend &lt;span class="high"&gt;Database&lt;/span&gt; with a function that executes &lt;span class="high"&gt;PostgreSQL’s&lt;/span&gt; &lt;span class="high"&gt;COPY&lt;/span&gt; command, allowing us to import &lt;span class="high"&gt;CSV&lt;/span&gt; files directly from the file system into the table schema.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Database {
    func importCSV(
        _ model: any Model.Type,
        file: PathEnum
        ) async throws {
        let query = SQLQueryString(
            &amp;quot;&amp;quot;&amp;quot;
            COPY \&amp;quot;\(unsafeRaw: model.space ?? &amp;quot;public&amp;quot;)\&amp;quot;.
            \&amp;quot;\(unsafeRaw: model.schema)\&amp;quot;
            FROM '\(unsafeRaw: file.rawValue)'
            WITH (FORMAT csv, HEADER true, DELIMITER ',',
            QUOTE '&amp;quot;', ESCAPE '&amp;quot;', NULL '')
            &amp;quot;&amp;quot;&amp;quot;
        )

        try await self.sqlDatabase
            .raw(query).run()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;PostgreSQL’s&lt;/span&gt; &lt;span class="high"&gt;COPY&lt;/span&gt;, the fastest method for bulk insert from files.&lt;/li&gt;
&lt;li&gt;The &lt;span class="high"&gt;model.space&lt;/span&gt; parameter supports custom schemas (defaults to &lt;span class="high"&gt;“public”&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;model.schema&lt;/span&gt; automatically gets the table name from the &lt;span class="high"&gt;Fluent&lt;/span&gt; model.&lt;/li&gt;
&lt;li&gt;Standard &lt;span class="high"&gt;CSV&lt;/span&gt; configuration: headers, delimiters, and null value handling.&lt;/li&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;unsafeRaw&lt;/span&gt; for direct interpolation in the SQL query.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;private func importRegions() async throws {
    try await db.importCSV(
        RegionModel.self,
        file: .LocationFile(&amp;quot;regions&amp;quot;, .csv)
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Benefits of this approach:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extreme performance&lt;/strong&gt;: &lt;span class="high"&gt;COPY&lt;/span&gt; is up to 10-100x faster than individual inserts.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Atomic transaction&lt;/strong&gt;: the entire import happens in a single operation, guaranteeing consistency.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Resource efficiency&lt;/strong&gt;: minimizes memory usage and database connections.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Native integration&lt;/strong&gt;: leverages optimized &lt;span class="high"&gt;PostgreSQL&lt;/span&gt; engine capabilities.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Scalability&lt;/strong&gt;: allows importing millions of records without significant degradation.&lt;/p&gt;
&lt;p&gt;This implementation uses &lt;span class="high"&gt;COPY … FROM&lt;/span&gt; with file system files. There is currently an open issue in Vapor to implement native support for &lt;span class="high"&gt;COPY … FROM STDIN&lt;/span&gt;, which would allow performing bulk inserts directly from memory without intermediate files.&lt;/p&gt;
&lt;p&gt;I’m actively monitoring this issue to integrate this functionality when available, which will provide an even more flexible and efficient API for mass import operations.&lt;/p&gt;
&lt;p&gt;If you also need the reverse operation — exporting data from PostgreSQL to CSV files — I covered that in &lt;a href="/blog/copy-to/"&gt;Copy To&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/copy-from/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/swift-package/</id>
        <title>Swift Package</title>
        <updated>2025-12-24T00:00:00Z</updated>
        <summary>Complete guide to Swift Package Manager cleanup commands: clean, reset, purge-cache, and when to use each one to resolve dependency issues.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;The broken dependencies dilemma&lt;/h2&gt;
&lt;p&gt;When working with &lt;strong&gt;Swift Package Manager (SPM)&lt;/strong&gt;, you’ll eventually encounter a compilation error that makes no sense. You’ve tried building multiple times, restarted Xcode, but the error persists. The solution often lies in properly cleaning SPM caches and artifacts, but which command should you use?&lt;/p&gt;
&lt;p&gt;There are multiple ways to “clean” in SPM, each with a specific purpose. Using the wrong command may not solve your problem or, worse yet, force you to download gigabytes of dependencies again.&lt;/p&gt;
&lt;h2&gt;The four paths to clean&lt;/h2&gt;
&lt;p&gt;SPM offers three official commands plus a manual alternative. Each affects different parts of the system:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Deletes local .build&lt;/th&gt;
&lt;th&gt;Deletes Package.resolved&lt;/th&gt;
&lt;th&gt;Deletes global cache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;swift package clean&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;No (only compiled binaries)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;swift package reset&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Yes (complete)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;swift package purge-cache&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="high"&gt;rm -rf .build&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Yes (complete)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Swift package clean&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br /&gt;
Removes only the final compiled binaries inside the &lt;span class="high"&gt;.build&lt;/span&gt; folder, but keeps downloaded dependencies and intermediate files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After changing build configurations&lt;/li&gt;
&lt;li&gt;To force a complete rebuild without re-downloading dependencies&lt;/li&gt;
&lt;li&gt;When binaries are corrupted but sources are fine&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Does not download dependencies again nor resolve package cache issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Swift package reset&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br /&gt;
Completely removes the &lt;span class="high"&gt;.build&lt;/span&gt; folder (including downloaded dependencies) and the &lt;span class="high"&gt;Package.resolved&lt;/span&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When there are conflicts in dependency versions&lt;/li&gt;
&lt;li&gt;After significant changes to &lt;span class="high"&gt;Package.swift&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;To resolve “dependency not found” errors&lt;/li&gt;
&lt;li&gt;When &lt;span class="high"&gt;Package.resolved&lt;/span&gt; is outdated or corrupted&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The next build will download and resolve all dependencies from scratch. This can take several minutes depending on the number of packages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Swift package purge-cache&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package purge-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Available from Swift 5.7 onwards.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br /&gt;
Removes the global package cache located at:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/Library/Caches/org.swift.swiftpm/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This cache contains cloned repositories and binary artifacts shared across all your projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When multiple projects have the same issue&lt;/li&gt;
&lt;li&gt;After updating Xcode or Swift tooling&lt;/li&gt;
&lt;li&gt;To free up disk space (can take up several GB)&lt;/li&gt;
&lt;li&gt;When you suspect the global cache is corrupted&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;All your projects will need to re-download common dependencies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;rm -rf .build&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;rm -rf .build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br /&gt;
Manually deletes the complete &lt;span class="high"&gt;.build&lt;/span&gt; folder, similar to &lt;span class="high"&gt;reset&lt;/span&gt; but without touching &lt;span class="high"&gt;Package.resolved&lt;/span&gt;. It’s less aggressive than reset because it doesn’t re-resolve dependencies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To clean build artifacts while maintaining version resolution&lt;/li&gt;
&lt;li&gt;In CI/CD scripts where you want full control&lt;/li&gt;
&lt;li&gt;When &lt;span class="high"&gt;swift package reset&lt;/span&gt; is not available&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Preserves &lt;span class="high"&gt;Package.resolved&lt;/span&gt;, which means dependency versions won’t be re-resolved.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Troubleshooting strategy&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Level 1: Light cleanup&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package clean
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Try the least invasive first. Solves 30% of issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Level 2: Complete project reset&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;If level 1 fails. Solves 60% of remaining issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Level 3: Purge global cache&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package purge-cache
swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;For persistent issues or those affecting multiple projects.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Level 4: Nuclear&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;rm -rf .build
rm Package.resolved
swift package purge-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Last resort. Complete fresh start.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Real-world use cases&lt;/h2&gt;
&lt;h3&gt;Scenario 1: Error after updating Xcode&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package purge-cache
swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Build tools changed and the cache may have incompatible artifacts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Scenario 2: Dependency version conflicts&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package reset
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You need to re-resolve all dependencies with the new constraints.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Scenario 3: Disk space full&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;swift package purge-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;The global cache can grow to several GB without you noticing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Scenario 4: CI/CD builds&lt;/h3&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;rm -rf .build
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;In continuous integration environments, you want clean but reproducible builds with versioned &lt;span class="high"&gt;Package.resolved&lt;/span&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Understanding the components&lt;/h2&gt;
&lt;h3&gt;.build (local)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Current project’s build artifacts&lt;/li&gt;
&lt;li&gt;Project-specific downloaded dependencies&lt;/li&gt;
&lt;li&gt;Intermediate build files&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Package.resolved&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“Lockfile” that pins exact dependency versions&lt;/li&gt;
&lt;li&gt;Guarantees reproducible builds&lt;/li&gt;
&lt;li&gt;Should be versioned in Git for shared projects&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Global cache&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Shared across all your Swift projects&lt;/li&gt;
&lt;li&gt;Contains clones of dependency repositories&lt;/li&gt;
&lt;li&gt;Pre-compiled binary artifacts&lt;/li&gt;
&lt;li&gt;Can reach several GB over time&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Best practices&lt;/h2&gt;
&lt;p&gt;Version &lt;span class="high"&gt;Package.resolved&lt;/span&gt; in Git to ensure the whole team uses the same versions&lt;br /&gt;&lt;br /&gt;
Use &lt;span class="high"&gt;reset&lt;/span&gt; after changes to Package.swift to ensure clean resolution&lt;br /&gt;&lt;br /&gt;
Purge the cache periodically if you work with many projects&lt;br /&gt;&lt;br /&gt;
In CI/CD, keep &lt;span class="high"&gt;Package.resolved&lt;/span&gt; but delete &lt;span class="high"&gt;.build&lt;/span&gt; for clean builds&lt;br /&gt;&lt;br /&gt;
Don’t ignore &lt;span class="high"&gt;.build&lt;/span&gt; in .gitignore - it’s already ignored by default&lt;br /&gt;&lt;br /&gt;
Don’t delete &lt;span class="high"&gt;Package.resolved&lt;/span&gt; unless you really need to re-resolve versions&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Understanding the difference between these commands saves you time and frustration. Not all build issues need a nuclear cleanup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;clean&lt;/span&gt; → Only compiled binaries&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;reset&lt;/span&gt; → Complete project + Package.resolved&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;purge-cache&lt;/span&gt; → Shared global cache&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;rm -rf&lt;/span&gt; → Surgical manual control&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next time SPM shows you a strange error, you’ll know exactly which tool to use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/swift-package/"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/sub-process/</id>
        <title>Subprocess</title>
        <updated>2025-12-17T00:00:00Z</updated>
        <summary>Migrating from Process to Subprocess, Apple's new cross-platform package for launching processes in Swift with native async/await support.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When working with external processes in &lt;span class="high"&gt;Swift&lt;/span&gt; applications, the traditional &lt;span class="high"&gt;Process&lt;/span&gt; class presents several significant limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Manual resource management&lt;/strong&gt;: Requires explicit configuration of executable URLs, arguments, and output handling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lack of async/await support&lt;/strong&gt;: Uses synchronous methods that block the execution thread&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complex error handling&lt;/strong&gt;: Makes it difficult to capture and process errors from child processes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verbose configuration&lt;/strong&gt;: Each execution requires multiple lines of repetitive configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the previous code, I needed to execute &lt;span class="high"&gt;Ghostscript&lt;/span&gt; to convert PDF files to PNG images, but the implementation with &lt;span class="high"&gt;Process&lt;/span&gt; was extensive and inelegant.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func _PDFToImages(
    _ fileName: PathEnum
) async throws {
    let process = Process()

    process.executableURL = URL(
        fileURLWithPath: &amp;quot;/opt/homebrew/bin/gs&amp;quot;
    )

    process.arguments = [
        &amp;quot;-dNOPAUSE&amp;quot;, &amp;quot;-dBATCH&amp;quot;,
        &amp;quot;-dQUIET&amp;quot;, &amp;quot;-sDEVICE=png16m&amp;quot;,
        &amp;quot;-r300&amp;quot;,
        &amp;quot;-sOutputFile=\(fileName.rawValue)-%d.png&amp;quot;,
        fileName.rawValue.appending(&amp;quot;.pdf&amp;quot;),
    ]

    try process.run()
    process.waitUntilExit()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;Apple’s new &lt;span class="high"&gt;Subprocess&lt;/span&gt; library provides a modern and robust API for process execution in &lt;span class="high"&gt;Swift&lt;/span&gt;. This cross-platform library offers native support for &lt;span class="high"&gt;async/await&lt;/span&gt; and automatic resource management.&lt;/p&gt;
&lt;p&gt;Key features:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Native async/await API&lt;/strong&gt; for non-blocking operations.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Automatic resource management&lt;/strong&gt; and process cleanup.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Granular output control&lt;/strong&gt; (stdout, stderr).&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Integrated termination status verification.&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;
Concise and expressive syntax.&lt;/p&gt;
&lt;p&gt;The modernized implementation uses the &lt;span class="high"&gt;run&lt;/span&gt; function from &lt;span class="high"&gt;Subprocess&lt;/span&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func _PDFToImages(
    _ fileName: PathEnum
) async throws {
    let result = try await run(
        .path(.init(&amp;quot;/opt/homebrew/bin/gs&amp;quot;)),
        arguments: [
            &amp;quot;-dNOPAUSE&amp;quot;, &amp;quot;-dBATCH&amp;quot;,
            &amp;quot;-dQUIET&amp;quot;, &amp;quot;-sDEVICE=png16m&amp;quot;,
            &amp;quot;-r300&amp;quot;,
            &amp;quot;-sOutputFile=\(fileName.rawValue)-%d.png&amp;quot;,
            fileName.rawValue.appending(&amp;quot;.pdf&amp;quot;),
        ],
        output: .discarded,
        error: .string(limit: .max)
    )

    try guardAndLogError(
        result.terminationStatus == .exited(0),
        message: result.standardError,
        status: .internalServerError
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;.path&lt;/span&gt;: Specifies the executable directly&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;output: .discarded&lt;/span&gt;: Discards standard output since we don’t need to process it&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;error: .string(limit: .max)&lt;/span&gt;: Captures errors as a string for logging&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;result.terminationStatus&lt;/span&gt;: Verifies that the process terminated successfully&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;The migration to &lt;span class="high"&gt;Subprocess&lt;/span&gt; transforms process management code into a cleaner, safer, and more efficient solution:&lt;/p&gt;
&lt;p&gt;Benefits achieved:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Improved performance&lt;/strong&gt; with true asynchronous operations.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Better error handling&lt;/strong&gt; with integrated stderr capture.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;More readable code&lt;/strong&gt; with less manual configuration.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Seamless integration&lt;/strong&gt; with the modern Swift concurrency ecosystem.&lt;/p&gt;
&lt;p&gt;The new implementation not only reduces complexity but also improves system robustness by providing better error visibility and more elegant asynchronous execution handling in &lt;span class="high"&gt;Vapor&lt;/span&gt; applications.&lt;/p&gt;
&lt;h3&gt;Note on DYLD_LIBRARY_PATH&lt;/h3&gt;
&lt;p&gt;In earlier versions of &lt;span class="high"&gt;Swift&lt;/span&gt;, there was a known issue with the &lt;span class="high"&gt;DYLD_LIBRARY_PATH&lt;/span&gt; environment variable when executing external processes. Due to macOS’s System Integrity Protection (SIP) restrictions, this variable was automatically removed when launching subprocesses, causing “Library not loaded” errors in certain cases.&lt;/p&gt;
&lt;p&gt;The temporary solution required manually configuring library paths using &lt;span class="high"&gt;install_name_tool&lt;/span&gt; with &lt;span class="high"&gt;@rpath&lt;/span&gt;, or alternatively setting the &lt;span class="high"&gt;DYLD_LIBRARY_PATH&lt;/span&gt; variable instead.&lt;/p&gt;
&lt;p&gt;Good news! This issue has been resolved in recent versions of &lt;span class="high"&gt;Swift&lt;/span&gt;. The &lt;span class="high"&gt;Subprocess&lt;/span&gt; library correctly handles system environment variables, including &lt;span class="high"&gt;DYLD_LIBRARY_PATH&lt;/span&gt;, without requiring additional configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/sub-process/"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/json-snakecase/</id>
        <title>Json Snake Case</title>
        <updated>2025-12-11T00:00:00Z</updated>
        <summary>Conveniences to encode and decode JSON in snake_case without boilerplate, reusable in both the iOS client and the Vapor server.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When integrating REST APIs, it’s very common for JSON keys to come in &lt;span class="high"&gt;snake_case&lt;/span&gt; (for example, first_name) while in Swift we model properties in &lt;span class="high"&gt;camelCase&lt;/span&gt; (firstName).&lt;br /&gt;
If we don’t configure anything, we have to manually write &lt;span class="high"&gt;CodingKeys&lt;/span&gt; in each model or accept decoding errors.&lt;/p&gt;
&lt;p&gt;We’re looking for a centralized and reusable way to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decode JSON without manual &lt;span class="high"&gt;CodingKeys&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Encode our models when sending data.&lt;/li&gt;
&lt;li&gt;Maintain the same behavior in both iOS/macOS apps (URLSession) and Vapor (req/res content).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;We create extensions for &lt;span class="high"&gt;JSONDecoder&lt;/span&gt; and &lt;span class="high"&gt;JSONEncoder&lt;/span&gt; that expose convenience constructors and static &lt;span class="high"&gt;snakeCase&lt;/span&gt; shortcuts.&lt;br /&gt;
Advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zero boilerplate in models: avoids repetitive &lt;span class="high"&gt;CodingKeys&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Self-explanatory name when using them: &lt;span class="high"&gt;JSONDecoder.snakeCase&lt;/span&gt; / &lt;span class="high"&gt;JSONEncoder.snakeCase&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Consistency across the entire project (client and server).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension JSONDecoder {
    convenience init(keyDecodingStrategy: KeyDecodingStrategy) {
        self.init()
        self.keyDecodingStrategy = keyDecodingStrategy
    }

    static var snakeCase: JSONDecoder {
        .init(keyDecodingStrategy: .convertFromSnakeCase)
    }
}

extension JSONEncoder {
    convenience init(keyEncodingStrategy: KeyEncodingStrategy) {
        self.init()
        self.keyEncodingStrategy = keyEncodingStrategy
    }

    static var snakeCase: JSONEncoder {
        .init(keyEncodingStrategy: .convertToSnakeCase)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: if a model needs a specific key name, you can still use &lt;span class="high"&gt;CodingKeys&lt;/span&gt; locally; the &lt;span class="high"&gt;snake_case&lt;/span&gt; strategy will act as the default value.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;Usage examples&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// iOS / macOS: reading data
let data: Data = ...
let user = try JSONDecoder.snakeCase
    .decode(UserDTO.self, from: data)

// iOS / macOS: sending data
let body = CreateUserDTO(firstName: &amp;quot;Ada&amp;quot;, lastName: &amp;quot;Lovelace&amp;quot;)
request.httpBody = try JSONEncoder.snakeCase.encode(body)

// Vapor
let input = try req.content
    .decode(CreateUserDTO.self, using: JSONDecoder.snakeCase)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these shortcuts we get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fewer errors and greater readability: properties remain in idiomatic Swift camelCase.&lt;/li&gt;
&lt;li&gt;Immediate interoperability with legacy snake_case APIs.&lt;/li&gt;
&lt;li&gt;Single configuration reusable throughout the project (tests included).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This standardizes how we serialize/parse JSON without sacrificing clarity or fine-grained control when needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/json-snakecase/"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/array-dictionary/</id>
        <title>Array to dictionary</title>
        <updated>2025-12-03T00:00:00Z</updated>
        <summary>Convert an Array of Identifiable elements (with optional ID of type UUID?) into a [UUID: Element] dictionary, ignoring null IDs and maintaining O(1) access.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When working with lists of models (e.g., results, events, or users), we often need &lt;span class="high"&gt;O(1)&lt;/span&gt; access by identifier for lookups, merges, or deduplication.&lt;/p&gt;
&lt;p&gt;However, in many domains the id may be optional (&lt;span class="high"&gt;UUID?&lt;/span&gt;) until the backend assigns it. If we build a dictionary directly, two frictions arise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to filter out elements without ID to avoid invalid entries.&lt;/li&gt;
&lt;li&gt;We must guarantee uniqueness of keys or the &lt;span class="high"&gt;Dictionary(uniqueKeysWithValues:)&lt;/span&gt; constructor will fail at runtime if there are duplicates.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;We extend &lt;span class="high"&gt;Array&lt;/span&gt; (when its elements are &lt;span class="high"&gt;Identifiable&lt;/span&gt; with &lt;span class="high"&gt;ID == UUID?&lt;/span&gt;) to expose &lt;span class="high"&gt;toDictionary()&lt;/span&gt;.&lt;br /&gt;
The function uses &lt;span class="high"&gt;compactMap&lt;/span&gt; to discard elements without ID and builds the dictionary with &lt;span class="high"&gt;Dictionary(uniqueKeysWithValues:)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Precondition&lt;/strong&gt;: existing IDs must be unique within the collection. If you expect collisions, consider a variant with &lt;span class="high"&gt;Dictionary(_, uniquingKeysWith:)&lt;/span&gt; to resolve duplicates.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Array where Element: Identifiable, Element.ID == UUID? {
    func toDictionary() -&amp;gt; [UUID: Element] {
        Dictionary(uniqueKeysWithValues: self.compactMap {
            guard let id = $0.id else { return nil }
            return (id, $0) }
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Elements with &lt;span class="high"&gt;id == nil&lt;/span&gt; are not included.&lt;/li&gt;
&lt;li&gt;Key-based access is &lt;span class="high"&gt;O(1)&lt;/span&gt; and simplifies in-memory merges/joins.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;A small, clear helper to convert from an array to a map by ID:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster to query &lt;span class="high"&gt;O(1)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safer&lt;/strong&gt;: avoids inserting elements without identifier.&lt;/li&gt;
&lt;li&gt;More expressive and reusable in services and view models.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/array-dictionary/"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/schedule-queues/</id>
        <title>Schedule Queues</title>
        <updated>2025-11-26T00:00:00Z</updated>
        <summary>Schedule the same ScheduledJob multiple times per hour with an expressive API: every N minutes, without repeating configuration or cron strings.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When using &lt;span class="high"&gt;Vapor Queues&lt;/span&gt; to schedule jobs, the typical API is:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;app.queues
    .schedule(MyJob()).hourly().at(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This schedules once per hour. If we want to run it every N minutes (e.g., every 5, 10, or 15), we have to manually register multiple &lt;span class="high"&gt;.at(…)&lt;/span&gt; calls, which results in repetitive code, prone to errors (duplicated/forgotten minutes), and difficult to read.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;We extract a helper over &lt;span class="high"&gt;Application.Queues&lt;/span&gt; that registers the same &lt;span class="high"&gt;ScheduledJob&lt;/span&gt; multiple times within the hour using a &lt;span class="high"&gt;stride&lt;/span&gt; with step &lt;span class="high"&gt;minutes&lt;/span&gt;. This way, with a single call we express: &lt;em&gt;“run it every N minutes”&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Application.Queues {
    func scheduleEvery(
        _ job: ScheduledJob,
        minutes: Int
    ) {
        for minuteOffset in stride(
            from: 0,
            to: 60,
            by: minutes
        ) {
            schedule(job).hourly()
                .at(.init(integerLiteral: minuteOffset))
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;stride(from: 0, to: 60, by: minutes)&lt;/span&gt; to generate minute offsets within the hour.&lt;/li&gt;
&lt;li&gt;For each offset it registers &lt;span class="high"&gt;hourly().at(…)&lt;/span&gt;, avoiding logic duplication.&lt;/li&gt;
&lt;li&gt;If &lt;span class="high"&gt;minutes&lt;/span&gt; doesn’t divide 60, the last execution will be the largest multiple &amp;lt; 60 (e.g., &lt;span class="high"&gt;minutes = 7&lt;/span&gt; ⇒ 0, 7, 14, …, 56).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;Usage example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func configure(_ app: Application) throws {
    app.queues.scheduleEvery(MyJob(), minutes: 5)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A concise and self-expressive API for short periodic tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Less boilerplate&lt;/strong&gt;: a single call registers all triggers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fewer errors&lt;/strong&gt;: no manual lists of &lt;span class="high"&gt;.at(…)&lt;/span&gt; or duplicated minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Readable and testable&lt;/strong&gt;: the pattern is evident and easy to verify.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/schedule-queues/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/task-list/</id>
        <title>Async Task List</title>
        <updated>2025-11-20T00:00:00Z</updated>
        <summary>Implementation of a robust system for managing asynchronous task lists with database state persistence using Swift and Vapor.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In distributed systems with microservices, complex processes can create control and reliability challenges. One solution is to divide them into atomic tasks, enabling more granular flow control, improved observability, ensured idempotency, and easier recovery from failures.&lt;/p&gt;
&lt;p&gt;The challenge is to design a pattern that allows for controlled degradation, so that a single task failure doesn’t affect the entire flow, guaranteeing resilience and fault tolerance in production environments.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;Create a &lt;span class="high"&gt;task executor&lt;/span&gt; that orchestrates asynchronous tasks.&lt;br /&gt;
The &lt;span class="high"&gt;execute&lt;/span&gt; function wraps each task, manages errors with &lt;span class="high"&gt;do-catch&lt;/span&gt; as a &lt;span class="high"&gt;circuit breaker&lt;/span&gt;, updates state on success, logs and persists failures, and ensures an atomic transaction to maintain data consistency.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func execute(
    status: StatusEnum,
    process: ProcessModel,
    _ work: () async throws -&amp;gt; ProcessModel
) async throws {
    do {
        let job = try await work()
        process.setStatus(job.status)
    } catch {
        process.setError(type: status, message: &amp;quot;\(error)&amp;quot;)
    }
    try await repo.updateProcess(process)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;This implementation provides a high-level abstraction that enables precise orchestration of complex asynchronous processes with atomicity and durability guarantees.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separation of Concerns:&lt;/strong&gt; each task isolates its execution context and error handling.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Workflow Orchestration:&lt;/strong&gt; enables task chaining through the pipeline pattern.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Observability:&lt;/strong&gt; generates a complete audit trail for debugging and monitoring.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Performance:&lt;/strong&gt; maintains high concurrency without compromising data consistency.&lt;br /&gt;&lt;br /&gt;
&lt;strong&gt;Resilience:&lt;/strong&gt; incorporates automatic fail-fast and recovery patterns.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await execute(status: .loadImages, process: process) {
    // Your implementation
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/task-list/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/multi-step/</id>
        <title>Multi-Step Process</title>
        <updated>2025-11-12T00:00:00Z</updated>
        <summary>A Swift pattern to orchestrate multi-step processes with a repeat-while state machine, deterministic control and recovery after failures.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In distributed systems and backend processes, it’s common for a complex operation to require executing multiple sequential steps with dependencies between them: file creation, upload, response generation, validation, cleanup, etc.&lt;br /&gt;
Controlling this state flow is critical to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensure each step executes in the correct order.&lt;/li&gt;
&lt;li&gt;Allow for recovery in case of error or system restart.&lt;/li&gt;
&lt;li&gt;Maintain data consistency even if the process is interrupted.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without a clear strategy, the code can become fragile, difficult to scale, and prone to errors.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;A stage-based state management function is implemented to control the advancement of a &lt;span class="high"&gt;ProcessModel&lt;/span&gt; through its lifecycle.&lt;br /&gt;
The idea is to encapsulate the state transition logic in a single function that evaluates the current state and executes the corresponding action, until the process reaches its final state.&lt;/p&gt;
&lt;p&gt;The pattern relies on a &lt;span class="high"&gt;repeat-while&lt;/span&gt; loop that re-evaluates the state after each operation, ensuring transitions occur in a deterministic and resilient manner.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func checkProcess(
    _ process: ProcessModel
) async throws {
    var process = process
    var status = process.status

    repeat {
        status = process.status
        process = try await checkStatus(process)
    } while status != process.status
}

func checkStatus(
    _ process: ProcessModel
) async throws -&amp;gt; ProcessModel {
    switch process.status {
        case .filesCreated:
            try await _uploadFiles(process)
        case .filesUploaded:
            try await _createResponses(process)
        case .responsesCreated, .responsesReasoning:
            try await _checkResponses(process)
        case .responsesCompleted:
            try await _deleteFiles(process)
        case .filesDeleted:
            try await _deleteResponses(process)
        case .responsesDeleted:
            try await _finishResponses(process)
        case .responsesFinished:
            try await _deleteProcess(process)
        default: process
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Implementation key points:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;State centralization&lt;/strong&gt;: a single control point defines all transitions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous re-evaluation&lt;/strong&gt;: the &lt;span class="high"&gt;repeat-while&lt;/span&gt; loop allows automatic advancement as long as there are state changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operation isolation&lt;/strong&gt;: each &lt;span class="high"&gt;switch&lt;/span&gt; case delegates to specialized functions (&lt;span class="high"&gt;_uploadFiles&lt;/span&gt;, &lt;span class="high"&gt;_createResponses&lt;/span&gt;, etc.), keeping the code clean and testable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;This multi-stage process management pattern provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Resilience&lt;/strong&gt;: each transition is atomic and can be retried if a failure occurs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: adding new steps only requires adding a new case to the &lt;span class="high"&gt;switch&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clarity&lt;/strong&gt;: the complete process flow is understood with a single read.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Application example: data processing pipelines, content publishing workflows, or any long-running process that requires precise control of each stage without compromising data integrity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/multi-step/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/concurrent-map/</id>
        <title>Concurrent Map</title>
        <updated>2025-10-27T00:00:00Z</updated>
        <summary>Build a concurrentMap extension on Sequence using Swift structured concurrency to run async transformations in parallel with Sendable safety and error handling.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When processing collections in Swift, the &lt;span class="high"&gt;map&lt;/span&gt; method executes transformations sequentially.&lt;br /&gt;
For intensive operations or those involving I/O—such as HTTP requests, file reading, or database queries—this can become a bottleneck.&lt;br /&gt;
The goal is to leverage concurrency to execute multiple transformations in parallel, ensuring safety with &lt;span class="high"&gt;Sendable&lt;/span&gt; and error handling.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;A &lt;span class="high"&gt;Sequence&lt;/span&gt; extension is created that adds &lt;span class="high"&gt;concurrentMap&lt;/span&gt;.&lt;br /&gt;
Internally, it uses &lt;span class="high"&gt;withThrowingTaskGroup&lt;/span&gt;, which allows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Launching a task for each element in the sequence.&lt;/li&gt;
&lt;li&gt;Executing all transformations in parallel, respecting Swift’s structured concurrency model.&lt;/li&gt;
&lt;li&gt;Automatically propagating the first error that occurs, canceling the remaining tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The use of &lt;span class="high"&gt;@Sendable&lt;/span&gt; ensures that both elements and results are safe in concurrent environments.&lt;br /&gt;
This way, any asynchronous operation can benefit from parallel execution without sacrificing code clarity.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Sequence where Element: Sendable {
    func concurrentMap&amp;lt;T: Sendable&amp;gt;(
        _ transform: @escaping @Sendable (Element) async throws -&amp;gt; T
    ) async throws -&amp;gt; [T] {
        try await withThrowingTaskGroup(of: T.self) { group in
            for element in self {
                group.addTask {
                    try await transform(element)
                }
            }
            return try await group.reduce(into: []) {
                $0.append($1)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;With &lt;span class="high"&gt;concurrentMap&lt;/span&gt;, you achieve significant performance gains in asynchronous operations that can execute in parallel.&lt;br /&gt;
The pattern respects Swift’s &lt;span class="high"&gt;structured concurrency&lt;/span&gt; rules, avoids &lt;em&gt;data races&lt;/em&gt;, and maintains the same declarative style as &lt;span class="high"&gt;map&lt;/span&gt;, making its adoption in existing projects straightforward.&lt;/p&gt;
&lt;p&gt;Usage example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;let urls: [URL] = [...]
let contents = try await urls.concurrentMap {
    try await fetchContent(from: $0)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach is ideal for batch HTTP requests, image processing, bulk data reading, or any scenario requiring maximum safe parallelism.&lt;/p&gt;
&lt;p&gt;If you need guaranteed ordering or want to limit resource consumption to one task at a time, I covered that in &lt;a href="/blog/async-map/"&gt;Async Map&lt;/a&gt;. And if you want to combine both strategies — concurrent processing within controlled chunks — I built that in &lt;a href="/blog/async-concurrent-map/"&gt;Async Concurrent Map&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/concurrent-map/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/async-map/</id>
        <title>Async Map</title>
        <updated>2025-10-20T00:00:00Z</updated>
        <summary>Build a sequential asyncMap extension on Sequence in Swift to transform collections with async/await while preserving order and limiting resources.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When processing collections with asynchronous transformations, the standard &lt;span class="high"&gt;map&lt;/span&gt; method falls short because its closures cannot be &lt;span class="high"&gt;async&lt;/span&gt;.&lt;br /&gt;
A naive solution would be to use a &lt;span class="high"&gt;for&lt;/span&gt; loop with &lt;span class="high"&gt;await&lt;/span&gt;, but this sacrifices readability and consistent error handling.&lt;br /&gt;
The goal is to have a sequential &lt;span class="high"&gt;map&lt;/span&gt; that accepts &lt;span class="high"&gt;async throws&lt;/span&gt; functions, preserves element order, and simplifies the workflow.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;We create an extension on &lt;span class="high"&gt;Sequence&lt;/span&gt; that adds &lt;span class="high"&gt;asyncMap&lt;/span&gt;.&lt;br /&gt;
Its execution is sequential within the same task, which allows us to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintain deterministic ordering of results.&lt;/li&gt;
&lt;li&gt;Limit resource consumption by executing only one operation at a time.&lt;/li&gt;
&lt;li&gt;Uniformly propagate the first error that occurs, stopping the process.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension Sequence {
    @inlinable
    func asyncMap&amp;lt;T&amp;gt;(
        _ transform: @escaping @Sendable (Element) async throws -&amp;gt; T
    ) async throws -&amp;gt; [T] {
        var results: [T] = []
        results.reserveCapacity(underestimatedCount)
        for element in self {
            let value = try await transform(element)
            results.append(value)
        }
        return results
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;With &lt;span class="high"&gt;asyncMap&lt;/span&gt;, we achieve a clear and safe workflow for asynchronous transformations without parallelism.&lt;br /&gt;
It’s especially useful when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need guaranteed ordering.&lt;/li&gt;
&lt;li&gt;We must limit resource consumption (one task in flight).&lt;/li&gt;
&lt;li&gt;There’s dependency between operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you need parallel execution instead of sequential, I explored that approach in &lt;a href="/blog/concurrent-map/"&gt;Concurrent Map&lt;/a&gt;. And if your sequential calls hit rate limits, I added a timeout mechanism in &lt;a href="/blog/async-map-timeout/"&gt;Async Map Timeout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Usage example:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;let urls: [URL] = [...]
let contents = try await urls.asyncMap {
    try await fetchContent(from: $0)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/async-map/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/guard-log/</id>
        <title>Guard and LogError</title>
        <updated>2025-10-13T00:00:00Z</updated>
        <summary>Build a generic Swift function that combines guard-let unwrapping, error logging, and exception throwing in a single reusable line for Vapor backends.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;In multiple sections of my code, I need to execute three operations every time I handle an &lt;span class="high"&gt;Optional&lt;/span&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Unwrap the &lt;span class="high"&gt;Optional&lt;/span&gt; content&lt;/li&gt;
&lt;li&gt;Log an error in the logging system if the value is &lt;span class="high"&gt;nil&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Throw an exception when the value doesn’t exist&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This pattern repeats frequently, generating duplicate code and reducing maintainability.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The strategy consists of encapsulating the three operations in reusable functions that work in a coordinated manner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Helper function:&lt;/strong&gt; &lt;span class="high"&gt;logError&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func logError(
    _ message: String, 
    status: HTTPResponseStatus
) throws -&amp;gt; Never {
    self.logMessage(message, level: .error)
    throw Abort(status, reason: message)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function executes the error logging in the logging system and subsequently terminates execution by throwing an &lt;span class="high"&gt;Abort&lt;/span&gt; exception. The &lt;span class="high"&gt;Never&lt;/span&gt; return type is fundamental, as it indicates to the compiler that this function never returns normally, ensuring that execution is completely interrupted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main function:&lt;/strong&gt; &lt;span class="high"&gt;guardAndLogError&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;func guardAndLogError&amp;lt;T&amp;gt;(
    _ optional: T?,
    message: String,
    status: HTTPResponseStatus = .noContent
) throws -&amp;gt; T {
    guard let optional else {
        try logError(message, status: status)
    }
    return optional
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This generic function implements the complete pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;span class="high"&gt;guard let&lt;/span&gt; to safely unwrap the Optional&lt;/li&gt;
&lt;li&gt;If the value is &lt;span class="high"&gt;nil&lt;/span&gt;, invokes &lt;span class="high"&gt;logError()&lt;/span&gt; to log the failure and terminate execution&lt;/li&gt;
&lt;li&gt;If it contains a value, returns it successfully&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The genericity &lt;span class="high"&gt;T&lt;/span&gt; allows using this function with any type of optional data.&lt;/p&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;With this implementation, a single line of code executes the three required operations: safe unwrapping, error logging, and exception handling.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;let fileName = try guardAndLogError(
    fileName, 
    message: &amp;quot;fileName value not found&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Benefits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Reduction of duplicate code&lt;/li&gt;
&lt;li&gt;Consistent error handling&lt;/li&gt;
&lt;li&gt;Centralized and structured logs&lt;/li&gt;
&lt;li&gt;Reusability through genericity&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Extensibility&lt;/h3&gt;
&lt;p&gt;This pattern can be extended for more specific use cases:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;// For boolean validations
func guardAndLogError(
    _ condition: Bool, 
    message: String
) throws { ... }

// For arrays
func guardAndLogError&amp;lt;T&amp;gt;(
    _ optionals: T?..., 
    message: String
) throws -&amp;gt; [T] { ... }

// For tuples
func guardAndLogError&amp;lt;T, U&amp;gt;(
    _ first: T?, 
    _ second: U?
    , message: String
) throws -&amp;gt; (T, U) { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link rel="alternate" href="https://jcalderita.com/blog/guard-log/"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/schema-space/</id>
        <title>Schemas and Namespaces</title>
        <updated>2025-10-06T00:00:00Z</updated>
        <summary>Learn how to use the space property in Vapor's Fluent models to organize database tables into namespaces, and avoid a subtle optional type declaration bug.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;When I define a data model in Vapor, I can specify the &lt;span class="high"&gt;schema&lt;/span&gt;, which corresponds to the table name in the database. But keeping every table in the default schema doesn’t scale for me: to maintain an organized architecture, I want to group my tables into different namespaces by functional criteria instead of concentrating them all in one place. That way I get a logical separation by domain or application module.&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;To assign a table to a specific namespace, I override the static &lt;span class="high"&gt;space&lt;/span&gt; property on the model. This property lets me define the namespace where the table will reside, giving me a more granular organization of my database structure.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;public final class LocationCityModel: Model {
    public static let schema = &amp;quot;cities&amp;quot;
    public static let space: String? = &amp;quot;location&amp;quot;

    @ID() public var id: UUID?
    @Field(.name) public var name: String

    public init() { }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;There’s one detail I had to get right: I must declare the &lt;span class="high"&gt;space&lt;/span&gt; property as type &lt;span class="high"&gt;String?&lt;/span&gt; (optional). When I declared it as &lt;span class="high"&gt;String&lt;/span&gt; (non-optional), it didn’t override the property inherited from the &lt;span class="high"&gt;Model&lt;/span&gt; protocol, but created a new property with the same name instead. That made the framework ignore my namespace configuration and keep the tables in the default schema, without any apparent error to warn me.&lt;/p&gt;
&lt;p&gt;If you’re wondering how to create these namespaces automatically in the database, I explain how to turn that into a migration in &lt;a href="/blog/migrate-spaces/"&gt;Migrate Spaces&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/schema-space/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/partial-index/</id>
        <title>Partial Indexes</title>
        <updated>2025-09-29T00:00:00Z</updated>
        <summary>Extend Vapor's SQLCreateIndexBuilder to support partial indexes with WHERE clauses on NULL columns, avoiding raw SQL while keeping type safety in Swift.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;A &lt;span class="high"&gt;partial index&lt;/span&gt; is an index that is created only on a subset of rows in a table, defined by a specific &lt;span class="high"&gt;WHERE&lt;/span&gt; condition. Instead of indexing all rows, the index only includes those that meet certain criteria, which can improve performance and reduce space usage.&lt;/p&gt;
&lt;p&gt;In my particular case, I needed to index fields based on whether their values were &lt;span class="high"&gt;NULL&lt;/span&gt; or &lt;span class="high"&gt;NOT NULL&lt;/span&gt;. For example, the following partial indexes in SQL:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE INDEX index_field_null 
ON table(field) 
WHERE field IS NULL;

CREATE INDEX index_field_not_null 
ON table(field) 
WHERE field IS NOT NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s also possible to create partial indexes that involve multiple columns:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE INDEX index_field1_null_field2_null 
ON table(field1, field2) 
WHERE field1 IS NULL AND field2 IS NULL;

CREATE INDEX index_field1_not_null_field2_not_null 
ON table(field1, field2) 
WHERE field1 IS NOT NULL AND field2 IS NOT NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Not all databases support partial indexes. In my particular case, I’m using &lt;span class="high"&gt;PostgreSQL&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By default, Vapor doesn’t offer direct support for creating partial indexes, since the standard index generation doesn’t include the ability to add a &lt;span class="high"&gt;WHERE&lt;/span&gt; clause in the index definition.&lt;/p&gt;
&lt;p&gt;The following code snippet creates a normal index, either on one or several fields, but doesn’t allow specifying a condition for a partial index:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await db.sqlDatabase
    .create($0.key)
    .on(table)
    .colums($0.colums)
    .run()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code works for creating simple indexes, but lacks the ability to add a &lt;span class="high"&gt;WHERE&lt;/span&gt; predicate.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This snippet has been adapted by me; the original builder’s &lt;span class="high"&gt;.create&lt;/span&gt; method exposes more configuration options, but in this example I show a custom and simplified implementation that I currently use.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The &lt;span class="high"&gt;SQLCreateIndexBuilder&lt;/span&gt; builder supports an optional &lt;span class="high"&gt;predicate&lt;/span&gt;. If this is not &lt;span class="high"&gt;nil&lt;/span&gt;, it adds a &lt;span class="high"&gt;WHERE&lt;/span&gt; clause to the index.&lt;/p&gt;
&lt;p&gt;Therefore, I extended this builder to include a &lt;span class="high"&gt;where&lt;/span&gt; method that accepts a list of columns and a partial index type (for example, &lt;span class="high"&gt;null&lt;/span&gt; or &lt;span class="high"&gt;not null&lt;/span&gt;). This method builds the appropriate logical expression for the predicate and assigns it to the index.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension SQLCreateIndexBuilder {
    @discardableResult
    func `where`(
        _ columns: [FieldKey],
        _ partialIndex: SQLPartialIndexEnum?
    ) -&amp;gt; Self {
        guard let partialIndex else {
            return self
        }
        let op: SQLBinaryOperator = partialIndex == .null ? .is : .isNot

        let conditions = columns.map {
            self.where($0, op)
        }

        let combined: SQLBinaryExpression = conditions.dropFirst()
            .reduce(conditions[0]) { .init($0, .and, $1) }

        return self.where(combined)
    }

    private func `where`(
        _ column: FieldKey,
        _ binary: SQLBinaryOperator
    ) -&amp;gt; SQLBinaryExpression {
        .init(
            left: SQLIdentifier(column.description),
            op: binary,
            right: SQLLiteral.null
        )
    }

    private func `where`(_ expression: SQLExpression) -&amp;gt; Self {
        self.createIndex.predicate = expression
        return self
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code allows calling the &lt;span class="high"&gt;where&lt;/span&gt; method for one or several fields, specifying whether you want a partial index for &lt;span class="high"&gt;NULL&lt;/span&gt; or &lt;span class="high"&gt;NOT NULL&lt;/span&gt; values. If no value is provided for &lt;span class="high"&gt;partialIndex&lt;/span&gt;, the index is returned without a predicate, behaving like a normal index.&lt;/p&gt;
&lt;p&gt;The logic consists of creating a list of binary expressions &lt;span class="high"&gt;SQLBinaryExpression&lt;/span&gt; for each column, combining them with the logical operator &lt;span class="high"&gt;AND&lt;/span&gt; and assigning the result as the index predicate.&lt;/p&gt;
&lt;h3&gt;Alternative&lt;/h3&gt;
&lt;p&gt;The most direct way to create partial indexes is to execute &lt;span class="high"&gt;raw SQL&lt;/span&gt; statements, as shown in the initial examples. This involves building a method to execute raw &lt;span class="high"&gt;CREATE INDEX&lt;/span&gt; statements with the corresponding &lt;span class="high"&gt;WHERE&lt;/span&gt; clause.&lt;/p&gt;
&lt;p&gt;Although effective, this approach loses the advantage of abstraction and safety that Vapor offers when building migrations and database schemas using Swift code.&lt;/p&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;With this extension, I can now create partial indexes easily in Vapor, adding only one line to the original code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await db.sqlDatabase
    .create($0.key)
    .on(table)
    .colums($0.colums)
    .where($0.colums, $0.partial)
    .run()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;span class="high"&gt;$0.partial&lt;/span&gt; is an optional value that indicates the desired partial index type &lt;span class="high"&gt;null&lt;/span&gt; or &lt;span class="high"&gt;not null&lt;/span&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This approach is specifically designed for partial indexes based on the presence or absence of &lt;span class="high"&gt;NULL&lt;/span&gt; values in columns. However, the implementation can be adapted to support other conditions and more complex use cases, simply by modifying the predicate construction.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The solution offers a clean and reusable way to create partial indexes within the Vapor ecosystem, maintaining the consistency and safety of Swift code.&lt;/p&gt;
&lt;p&gt;If you also want to organize your tables into namespaces using Fluent’s &lt;span class="high"&gt;space&lt;/span&gt; property, I covered that in &lt;a href="/blog/schema-space/"&gt;Schemas and Namespaces&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/partial-index/" rel="alternate"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/my-first-data-race/</id>
        <title>My First Data Race</title>
        <updated>2025-09-22T00:00:00Z</updated>
        <summary>How a refactoring toward concurrency in Swift led me to a race condition, and how I solved it by applying Swift actor patterns in my project.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;During a refactoring process to convert previously sequential functionality into concurrent processing, I ran into a classic problem: ensuring record uniqueness when multiple tasks attempt to create events simultaneously. While the goal was to improve performance by processing thousands of events in parallel using Swift, the transition exposed a typical concurrency challenge: avoiding duplicates and maintaining data integrity under simultaneous access.&lt;/p&gt;
&lt;p&gt;The bug turned out to be a logical race condition of the check-then-act kind. When multiple concurrent tasks attempt to create the same event, they can all check that it doesn’t exist and proceed to create it simultaneously. Only one of the resulting instances gets stored, while the rest become orphaned, causing inconsistencies and broken references in other data structures.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;For example: two concurrent tasks check if an event exists. Both see that it doesn’t, both create it, but only one survives in the dictionary; the other reference is now lost.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;I reached the final fix through a progression of attempts, each one exposing the limits of the previous.&lt;/p&gt;
&lt;h3&gt;Actor with array&lt;/h3&gt;
&lt;p&gt;My first attempt kept the events in an array guarded by an actor.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;private actor EventsActor {
    private var events: [SportEventModel] = []

    func getOrCreate(
        name: String,
        cityId: UUID,
        build: () async throws -&amp;gt; EventModel
    ) async throws -&amp;gt; EventModel {
        if let event = events.first(
            where: {
                $0.normalizedName == name
                &amp;amp;&amp;amp; $0.$city.id == cityId
            }
        ) { return event }

        let event = try await build()
        events.append(event)
        return event
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplicity.&lt;/li&gt;
&lt;li&gt;Safety against race conditions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Disadvantage:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Inefficient search for large volumes &lt;span class="high"&gt;O(n)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Actor with dictionary&lt;/h3&gt;
&lt;p&gt;To speed up lookups, I swapped the array for a dictionary keyed by name and city.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;private actor EventsActor {
    private var events: [String: EventModel] = [:]

    func getOrCreate(
        name: String,
        cityId: UUID,
        build: () async throws -&amp;gt; EventModel
    ) async throws -&amp;gt; EventModel {
        let key = &amp;quot;\(name)\(cityId.uuidString)&amp;quot;
        if let event = events[key] {
            return event
        }
        let event = try await build()
        events[key] = event
        return event
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fast search and insertion &lt;span class="high"&gt;O(1)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Ideal for large data volumes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bug:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logical race condition&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Actor with a task per key&lt;/h3&gt;
&lt;p&gt;To prevent this, I serialize not only access but also creation by key. If there’s already a creation in progress for that key, concurrent tasks must wait for the result of the first one, ensuring that all of them share exactly the same resource.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;private actor EventsActor {
    private var events: [String: EventModel] = [:]
    private var builds: [String: Task&amp;lt;EventModel, Error&amp;gt;] = [:]

    func getOrCreate(
        name: String,
        cityId: UUID,
        build: @Sendable @escaping () async throws -&amp;gt; EventModel
    ) async throws -&amp;gt; EventModel {
        let key = &amp;quot;\(name)\(cityId.uuidString)&amp;quot;
        if let event = events[key] {
            return event
        }

        if let building = builds[key] {
            return try await building.value
        }

        let buildTask = Task {
            try await build()
        }
        builds[key] = buildTask

        let event = try await buildTask.value
        events[key] = event
        builds.removeValue(forKey: key)
        return event
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;An actor alone doesn’t prevent logical race conditions of the “check-then-act” type.&lt;/li&gt;
&lt;li&gt;In concurrent scenarios, serializing resource construction by key is fundamental to maintaining data integrity.&lt;/li&gt;
&lt;li&gt;It’s essential to test under load and concurrent scenarios, not just in sequential mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/my-first-data-race/" rel="alternate"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/timestamps-migrations/</id>
        <title>Timestamps in Migrations</title>
        <updated>2025-09-15T00:00:00Z</updated>
        <summary>How to avoid repeating the createdAt, updatedAt and deletedAt fields in every Fluent migration with a cleaner SchemaBuilder extension.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;My Problem 🤔&lt;/h2&gt;
&lt;p&gt;If you’re like me, when you design a database model you want all tables to always include three key fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="high"&gt;createdAt&lt;/span&gt;: indicates when the record was created.&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;updatedAt&lt;/span&gt;: reflects the last time it was updated.&lt;/li&gt;
&lt;li&gt;&lt;span class="high"&gt;deletedAt&lt;/span&gt;: marks when it was logically deleted (without physically deleting the record).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you can see, in each migration we have to manually write the three timestamp fields. This is not only repetitive, but also error-prone if we forget a field or misspell the data type.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await db.schema(model)
    .id()
    .field(.name, .string, .required)
    .field(.createdAt, .datetime, .required)
    .field(.updatedAt, .datetime, .required)
    .field(.deletedAt, .datetime)
    .create()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Solution 🧩&lt;/h2&gt;
&lt;p&gt;The solution is to create a &lt;span class="high"&gt;SchemaBuilder&lt;/span&gt; extension that adds a &lt;span class="high"&gt;timestamps()&lt;/span&gt; method. This extension encapsulates the repetitive logic in one place, making the code cleaner and more maintainable.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;extension SchemaBuilder {
    func timestamps() -&amp;gt; Self {
        self.field(.createdAt, .datetime, .required)
            .field(.updatedAt, .datetime, .required)
            .field(.deletedAt, .datetime)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;My Result 🎯&lt;/h2&gt;
&lt;p&gt;Now our migrations are much cleaner and more readable. By calling &lt;span class="high"&gt;.timestamps()&lt;/span&gt; we add the three necessary fields. This reduces the possibility of errors and makes the code easier to maintain.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-swift"&gt;try await db.schema(model)
    .id()
    .field(.name, .string, .required)
    .timestamps()
    .create()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/timestamps-migrations/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/claude-code/</id>
        <title>Claude Code</title>
        <updated>2025-09-01T00:00:00Z</updated>
        <summary>Discover how Claude Code became my favorite development assistant and the rules and workflow I apply to get the most out of its potential.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;What is Claude Code for me?&lt;/h2&gt;
&lt;p&gt;To move away from what everyone talks about, I’m going to tell you about artificial intelligence from my personal experience.&lt;/p&gt;
&lt;p&gt;I’ve integrated Claude Code into my projects, but for me it’s much more than a tool: it’s my digital assistant, another member of my development team, an extension of my capabilities or, as the image represents well, my technological shadow.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It doesn’t replace me nor do I think it replaces any developer at the moment. Claude Code is only as good as you are or as you know how to use it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;My Golden Rules&lt;/h2&gt;
&lt;p&gt;I have four fundamental rules that I religiously apply with Claude Code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Total Change Control&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;I review everything before implementing any changes&lt;/li&gt;
&lt;li&gt;It doesn’t have permissions to modify files without my approval&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flow:&lt;/strong&gt; It provides me the plan → I review the changes → I understand them → I approve them (or not)&lt;/li&gt;
&lt;li&gt;This rule is super important to me. Giving it blind permissions doesn’t align with my philosophy&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="2"&gt;
&lt;li&gt;&lt;strong&gt;Claude.md as Project Bible&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;I work intensively on the &lt;span class="high"&gt;CLAUDE.md&lt;/span&gt; file&lt;/li&gt;
&lt;li&gt;I define what the project is and establish clear guidelines&lt;/li&gt;
&lt;li&gt;It’s my way of “training” Claude on my style and preferences&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result:&lt;/strong&gt; Responses more aligned with my vision&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;&lt;strong&gt;Intelligent Configuration&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;I configure its settings with official documentation paths&lt;/li&gt;
&lt;li&gt;When I ask questions about specific technologies, it uses official sources&lt;/li&gt;
&lt;li&gt;This guarantees more precise and up-to-date responses&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="4"&gt;
&lt;li&gt;&lt;strong&gt;Collaborative Work, Not Dependency&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;I never start from an empty IDE&lt;/li&gt;
&lt;li&gt;I always work on a code base I’ve already developed&lt;/li&gt;
&lt;li&gt;My flow: I start creating → I get stuck → I ask Claude&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recurring question:&lt;/strong&gt; &lt;em&gt;“Is there a cleaner and more efficient way to write this code?”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Limitless imagination&lt;/h2&gt;
&lt;p&gt;And here comes the interesting part: Claude Code isn’t just for programming. You can use it for whatever comes to mind. Just ask yourself this question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;How can I make this idea work with Claude Code?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the coolest ways I use it outside of programming is with my technical content consumption:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I consume tons of programming content:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Online courses&lt;br /&gt;&lt;br /&gt;
Knowledge pills&lt;br /&gt;&lt;br /&gt;
Conferences and talks&lt;br /&gt;&lt;br /&gt;
Technical debates&lt;br /&gt;&lt;br /&gt;
Specialized podcasts&lt;br /&gt;&lt;br /&gt;
Practical workshops&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The typical problem:&lt;/strong&gt; You’re programming and remember seeing something useful in a video, but… which one was it? At what minute?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My solution with Claude:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I ask: &lt;em&gt;“Where is this talked about?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;It returns:
&lt;ul&gt;
&lt;li&gt;The specific video or videos&lt;/li&gt;
&lt;li&gt;The exact timestamp where it’s mentioned&lt;/li&gt;
&lt;li&gt;A brief summary of the content&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Wonderful! Instant access to all my consumed knowledge.&lt;/p&gt;
&lt;h2&gt;Final Reflection&lt;/h2&gt;
&lt;p&gt;Claude Code has become my technological shadow. It’s not magic, it doesn’t do the work for you, but if you know how to use it correctly, it can enormously enhance your productivity and creativity.&lt;/p&gt;
&lt;p&gt;The key is in establishing clear limits, maintaining control and using it for what it really is: an exceptional assistant that amplifies your capabilities, not one that replaces them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/claude-code/" rel="alternate"></link>
        <media:content medium="image" url="https://jcalderita.com/static/card.png"></media:content>
    </entry>
    <entry>
        <id>https://jcalderita.com/blog/love-vapor/</id>
        <title>Love Vapor</title>
        <updated>2025-08-25T00:00:00Z</updated>
        <summary>Discover why Vapor became my favorite Swift server-side framework at the Full Stack Bootcamp, from gRPC to PassKeys and scheduled tasks.</summary>
        <content type="html">&lt;hr /&gt;
&lt;h2&gt;Swift on the server side&lt;/h2&gt;
&lt;p&gt;When I attended the &lt;em&gt;Swift Full Stack Bootcamp&lt;/em&gt; at &lt;a href="https://acoding.academy"&gt;Apple Coding Academy&lt;/a&gt;, one of the modules we were taught was &lt;em&gt;Swift on the server side&lt;/em&gt; using the &lt;a href="https://vapor.codes"&gt;Vapor&lt;/a&gt; framework.&lt;/p&gt;
&lt;p&gt;From the very start of the theory portion, my heart began to race, and by the middle of that very first class, I was already completely in love.&lt;/p&gt;
&lt;p&gt;So, if you ask me &lt;em&gt;“What was your favorite module in the Bootcamp?”&lt;/em&gt;, without a doubt, I’d say this one.&lt;/p&gt;
&lt;h2&gt;Why do I like it?&lt;/h2&gt;
&lt;p&gt;I’ve always thought that the backend is the brain of any application or website.&lt;br /&gt;
It’s where all the &lt;em&gt;real action&lt;/em&gt; happens: where information is processed (usually from a database) and prepared so that the end user receives it in the most simple and transparent way possible.&lt;/p&gt;
&lt;p&gt;To be clear: I’m not undervaluing the frontend.&lt;br /&gt;
In fact, I truly admire those who create incredible designs—heroes in my eyes—because that’s something I personally find impossible… although I do get along quite well with SwiftUI.&lt;/p&gt;
&lt;h2&gt;Playing with Vapor&lt;/h2&gt;
&lt;p&gt;In future blog posts, I’ll go into much more detail, but I can already share that I’ve been having a lot of fun with Vapor:&lt;/p&gt;
&lt;p&gt;Standalone package to configure different services&lt;br /&gt;&lt;br /&gt;
gRPC server&lt;br /&gt;&lt;br /&gt;
Data model with views (including &lt;em&gt;materialized views&lt;/em&gt;), indexes, per-application permissions, and schemas&lt;br /&gt;&lt;br /&gt;
Enums with automatic migrations&lt;br /&gt;&lt;br /&gt;
PassKey&lt;br /&gt;&lt;br /&gt;
&lt;em&gt;Bulk inserts&lt;/em&gt; for populating huge datasets&lt;br /&gt;&lt;br /&gt;
Scheduled tasks with asynchrony&lt;br /&gt;&lt;br /&gt;
… and much more&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In various blog posts, I’ll be sharing a bit of everything.&lt;br /&gt;
That said, even though I really enjoy Vapor and Swift on the server side, it won’t all be about backend… there will be room for many other topics I’m passionate about.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep coding, keep running&lt;/strong&gt; 🏃‍♂️&lt;/p&gt;</content>
        <link href="https://jcalderita.com/blog/love-vapor/" rel="alternate"></link>
        <media:content url="https://jcalderita.com/static/card.png" medium="image"></media:content>
    </entry>
</feed>