<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>aliou.me</title><description>I&apos;m Aliou Diallo, an applied AI consultant and engineer building agent harnesses and tools. I write about what I learn and don&apos;t want to forget.</description><link>https://new.aliou.me/</link><item><title>How do I design the right primitive?</title><link>https://new.aliou.me/links/how-do-i-design-the-right-primitive/</link><guid isPermaLink="true">https://new.aliou.me/links/how-do-i-design-the-right-primitive/</guid><pubDate>Tue, 30 Jun 2026 15:58:31 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Agents naturally gravitate toward the smallest stable abstraction. So, in an agent-first world, the most important question facing product teams is not what features and why, but instead how do I design the right primitive ?&lt;/p&gt;
&lt;p&gt;Building great developer tools (e.g. primitives) has always been an exercise in exquisite taste and deep domain expertise because it requires balancing a set of competing, and often contradictory, forces: namely the tension between expressivity and simplicity.
— &lt;a href=&quot;https://www.amplifypartners.com/blog-posts/the-primitive-is-the-product&quot;&gt;The primitive is the product&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Will be super interesting to see how companies adopt this and it will truly be a differentiator in adoption.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>How I use `/tree` and `/spawn` to investigate bugs</title><link>https://new.aliou.me/posts/how-i-use-tree-and-spawn-to-investigate-bugs/</link><guid isPermaLink="true">https://new.aliou.me/posts/how-i-use-tree-and-spawn-to-investigate-bugs/</guid><pubDate>Tue, 30 Jun 2026 15:49:38 GMT</pubDate><content:encoded>&lt;p&gt;Simple context management for when you don’t know what you’re looking for.&lt;/p&gt;</content:encoded><category>post</category></item><item><title>Using MicroVMs</title><link>https://new.aliou.me/links/using-microvms/</link><guid isPermaLink="true">https://new.aliou.me/links/using-microvms/</guid><pubDate>Tue, 30 Jun 2026 14:40:25 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;A VM that can reach the host’s local network is a VM that can reach things it has no business reaching. So QEMU doesn’t run in the host’s network namespace at all. We &lt;code&gt;unshare&lt;/code&gt; into fresh user, net and mount namespaces first. Inside that namespace a small wrapper bind-mounts a resolv.conf pointing at &lt;code&gt;127.0.0.1&lt;/code&gt; so that QEMU’s built-in slirp DNS isn’t used, then installs blackhole routes for every special-use IP range (RFC 6890, so private networks, link-local, loopback, etc.) before it execs QEMU. &lt;code&gt;slirp4netns&lt;/code&gt; then provides the namespace’s outbound internet connection, with &lt;code&gt;--disable-host-loopback&lt;/code&gt;, sandbox and seccomp all on. QEMU runs inside that namespace, and the guest’s network card is attached to QEMU’s own built-in user-mode networking. So every packet from the guest takes two hops: guest → QEMU’s slirp → the namespace’s &lt;code&gt;slirp4netns&lt;/code&gt; → the internet. The guest never sees the host’s network and the host’s network never sees the guest. All of this is done without needing any privileges!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve been reading a lot about microVMs and &lt;a href=&quot;https://firecracker-microvm.github.io/&quot;&gt;Firecracker&lt;/a&gt; lately as I work on async agent side project&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;More on that soon hopefully. &lt;/span&gt;&lt;/span&gt; and I find it super interesting how people tackel the same UX problems (in this case: locking down the network).&lt;/p&gt;
</content:encoded><category>link</category></item><item><title>obsidian-flint: v0.2.0</title><link>https://new.aliou.me/releases/obsidian-flint-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/obsidian-flint-v0-2-0/</guid><pubDate>Tue, 30 Jun 2026 13:42:14 GMT</pubDate><content:encoded>&lt;p&gt;Release v0.2.0&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-ts-aperture: v0.8.0</title><link>https://new.aliou.me/releases/pi-ts-aperture-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-ts-aperture-v0-8-0/</guid><pubDate>Mon, 29 Jun 2026 15:26:53 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;61e66dc: Filter disabled gateway providers out of proxy and dedicated flows.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/api/providers&lt;/code&gt; lists every provider regardless of its &lt;code&gt;disabled&lt;/code&gt; flag, so
disabled providers and their models leaked into the proxy settings/onboarding
checklist, the dedicated provider list, the dedicated model registration,
and the proxy gateway-model check.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ApertureClient.providers()&lt;/code&gt; now also fetches &lt;code&gt;/v1/models&lt;/code&gt; (which only lists
models for enabled providers) and:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;drops any provider whose models are all absent from &lt;code&gt;/v1/models&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;intersects each surviving provider’s &lt;code&gt;models&lt;/code&gt; with &lt;code&gt;/v1/models&lt;/code&gt;, so only
callable models are exposed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because every consumer (proxy settings, proxy onboarding, dedicated settings,
dedicated onboarding, dedicated runtime, &lt;code&gt;checkMissingModels&lt;/code&gt;, &lt;code&gt;health&lt;/code&gt;)
goes through &lt;code&gt;providers()&lt;/code&gt;, all of them now exclude disabled providers
without further changes. If &lt;code&gt;/v1/models&lt;/code&gt; is unreachable, &lt;code&gt;providers()&lt;/code&gt; falls
back to the unfiltered &lt;code&gt;/api/providers&lt;/code&gt; result so a transient models-endpoint
failure never blocks the rest of the client.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;9f0f70e: Rename connector discovery meta-tools with an &lt;code&gt;aperture_&lt;/code&gt; prefix and remove the resource proxy tools.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The four discovery meta-tools are now &lt;code&gt;aperture_connector_list&lt;/code&gt;,
&lt;code&gt;aperture_connector_tool_search&lt;/code&gt;, &lt;code&gt;aperture_connector_tool_describe&lt;/code&gt;,
and &lt;code&gt;aperture_connector_tool_call&lt;/code&gt;. The prefix avoids collisions with
other extensions and signals the Aperture provenance.&lt;/li&gt;
&lt;li&gt;Removed &lt;code&gt;connector_resource_search&lt;/code&gt;, &lt;code&gt;connector_resource_describe&lt;/code&gt;, and
&lt;code&gt;connector_resource_serve&lt;/code&gt;. Pi does not support MCP resources well enough
yet, so the resource browsing flow is gone until that lands. The MCP
session still exposes the resource methods; only the Pi tool wrappers
were removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Keeping the harness&apos;s primitives consistent</title><link>https://new.aliou.me/links/keeping-the-harness-primitives-consistent/</link><guid isPermaLink="true">https://new.aliou.me/links/keeping-the-harness-primitives-consistent/</guid><pubDate>Mon, 29 Jun 2026 14:17:23 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;this is a huge reason why i use pi.&lt;/p&gt;
&lt;p&gt;i absolutely do not want my harness regularly changing behavior out from under me, including system prompt changes, on top of an already stochastic llm&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Imagine I have /skill-1 and /skill-2, both with disable-model-invocation: true
I tell Claude Code: “/skill-1, /skill-2 do XYZ”&lt;/p&gt;
&lt;p&gt;Which of the skills will be invoked?&lt;/p&gt;
&lt;p&gt;I’ll give you a clue - the behaviour was one thing 3 weeks ago, another thing 1 week ago, and back to the original today.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://x.com/mattpocockuk/status/2071318488401006919&quot;&gt;Matt Pocock (@mattpocockuk) on Twitter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve been playing around making my own harness&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; recently, and also noticed how the behaviour of many Claude features seemingly seem to change every time I spin it up to test something. For my harness, I just decided on a simple principle: trying to keep the primitives (i.e. skills and mid-turn messages affect the agent&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;) consistent with how I expect agent to react.&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://pi.dev&quot;&gt;Pi&lt;/a&gt; remains my main harness, but trying to think about how I work in this agent-looping world. &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;Most of the time i want immediate steering, but I realized I do use follow-up quite a lot when leaving agents do their thing, especially when running in sandboxes and/or building prototypes where the result itself doesn’t matter much for me. &lt;a href=&quot;#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><category>link</category></item><item><title>Tau — a coding agent as a curriculum</title><link>https://new.aliou.me/links/tau-coding-agent-curriculum/</link><guid isPermaLink="true">https://new.aliou.me/links/tau-coding-agent-curriculum/</guid><pubDate>Mon, 29 Jun 2026 14:15:49 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;introducing tau τ — an educational agent harness that teaches you how to build agent harnesses&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is so cool. I truly think we’re moving towards a world where writing/making a great agent harness ends up becoming accessible to most people because the knowledge is just readily available.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>pi-toolchain: v0.10.0</title><link>https://new.aliou.me/releases/pi-toolchain-v0-10-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-toolchain-v0-10-0/</guid><pubDate>Mon, 29 Jun 2026 13:31:50 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;e86681a: Add a &lt;code&gt;pythonToPython3&lt;/code&gt; rule that rewrites bare &lt;code&gt;python&lt;/code&gt; command invocations to &lt;code&gt;python3&lt;/code&gt; when enabled.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-edgee: v0.1.0</title><link>https://new.aliou.me/releases/pi-edgee-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-edgee-v0-1-0/</guid><pubDate>Mon, 29 Jun 2026 11:36:02 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;983a7b2: Initial Edgee provider extension for Pi.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>The Rebel Alliance</title><link>https://new.aliou.me/links/the-rebel-alliance/</link><guid isPermaLink="true">https://new.aliou.me/links/the-rebel-alliance/</guid><pubDate>Sun, 28 Jun 2026 20:06:17 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Application scope and user experience&lt;/strong&gt;: Sun Microsystems co-founder Bill Joy famously said: “No matter who you are, most of the smartest people work for someone else.” That applies with particular force to AI. The range of what agents can do — and the variety of industries, workflows, and real-world contexts they touch — is so vast that no single company can build the best solution for all of them. We see a near-infinite opportunity to rewrite the world using agentic AI as foundational infrastructure, and it will require many companies with focused effort.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Thinking a lot about this lately, especially around my own use. I want from using Anthropic models and tools exclusively a year ago to barely using them and instead using different models in different harnesses depending on the task at hand.&lt;/p&gt;
&lt;p&gt;It’s very likely that I’m part of the minority of AI users in this case currently but I wonder how long this will become more common ground amongst the majority of users.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>pi-ts-aperture: v0.7.0</title><link>https://new.aliou.me/releases/pi-ts-aperture-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-ts-aperture-v0-7-0/</guid><pubDate>Sat, 27 Jun 2026 20:40:25 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;6d03cf5: Add connectors extension and restructure shared config.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connectors: new extension that discovers MCP tools from Aperture’s &lt;code&gt;/v1/mcp&lt;/code&gt; endpoint and registers them with Pi. Splits tools into pinned (first-class Pi tools) and proxied (reached through discovery meta-tools).&lt;/li&gt;
&lt;li&gt;Connectors config: &lt;code&gt;connectors.enabled&lt;/code&gt; master switch (default &lt;code&gt;false&lt;/code&gt;), &lt;code&gt;connectors.pinnedTools&lt;/code&gt; stored as &lt;code&gt;{ connectorId; toolName }&lt;/code&gt; objects, and &lt;code&gt;connectors.discoveryTools&lt;/code&gt; toggle (default &lt;code&gt;true&lt;/code&gt;) for the list / search / describe / call meta-tools.&lt;/li&gt;
&lt;li&gt;Resource proxy tools (&lt;code&gt;connector_resource_search&lt;/code&gt; / &lt;code&gt;connector_resource_describe&lt;/code&gt; / &lt;code&gt;connector_resource_serve&lt;/code&gt;) for browsing gateway resources.&lt;/li&gt;
&lt;li&gt;Connector UI redesign with &lt;code&gt;@aliou/pi-utils-ui&lt;/code&gt; components and Markdown rendering.&lt;/li&gt;
&lt;li&gt;Settings: new Connectors tab with pinned-tools submenu driven by &lt;code&gt;FilterableChecklist&lt;/code&gt;, reading live gateway tool state.&lt;/li&gt;
&lt;li&gt;Config: extract shared config, types, and sync bus to &lt;code&gt;src/shared/&lt;/code&gt;; add JSON Schema generation and &lt;code&gt;schema.json&lt;/code&gt;; parse Aperture provider config as hujson so commented gateway configs work in settings.&lt;/li&gt;
&lt;li&gt;API: Typebox schemas with response validation, live integration tests, and API-verified connector IDs.&lt;/li&gt;
&lt;li&gt;Feature request/register event dispatching between aperture and connectors extensions.&lt;/li&gt;
&lt;li&gt;Settings: cancel in-flight Aperture fetches when the user presses Esc on an async-loading submenu (&lt;code&gt;AsyncEditor&lt;/code&gt;), instead of letting them run to the 5s timeout in the background. The abort signal is threaded through &lt;code&gt;ApertureClient&lt;/code&gt;, &lt;code&gt;createMcpSession&lt;/code&gt;, and &lt;code&gt;listTools&lt;/code&gt;, and late resolves/rejects on a dismissed submenu are ignored so a slow gateway can no longer leave the settings panel unresponsive or silently mutate the draft.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;8bb0984: Tolerate the admin-only &lt;code&gt;/aperture/config&lt;/code&gt; endpoint so non-admin Aperture grants can use the extension.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ApertureClient._fetch&lt;/code&gt; now throws an &lt;code&gt;ApertureHttpError&lt;/code&gt; that carries the HTTP status, so callers can tolerate specific responses.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;providerConfigInfos()&lt;/code&gt; (and &lt;code&gt;providerBaseUrls()&lt;/code&gt;) return an empty map on HTTP 403 instead of throwing. &lt;code&gt;/aperture/config&lt;/code&gt; requires &lt;code&gt;role:admin&lt;/code&gt; and is the only endpoint a non-admin grant cannot access; everything else (&lt;code&gt;/api/providers&lt;/code&gt;, &lt;code&gt;/api/connectors&lt;/code&gt;, &lt;code&gt;/v1/mcp&lt;/code&gt;, model calls) returns 200 for non-admin grants.&lt;/li&gt;
&lt;li&gt;Proxy provider matching already falls back to IDs via &lt;code&gt;/api/providers&lt;/code&gt; when provider config info is empty, so onboarding and the proxy settings submenu keep working for non-admin users. Admin users keep the richer base-URL matching. Dedicated mode and connectors never read &lt;code&gt;/aperture/config&lt;/code&gt; and are unaffected.&lt;/li&gt;
&lt;li&gt;Other non-403 errors still propagate.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>New `us` jurisdiction for Durable Objects</title><link>https://new.aliou.me/links/us-non-us-llm-access/</link><guid isPermaLink="true">https://new.aliou.me/links/us-non-us-llm-access/</guid><pubDate>Fri, 26 Jun 2026 20:53:20 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Durable Objects now supports a new &lt;code&gt;us&lt;/code&gt; jurisdiction. Create namespaces restricted to the US to keep compute and storage local.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;i feel like i have a aluminium hat&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;it’s called a tinfoil hat dummass &lt;/span&gt;&lt;/span&gt;, but it seems like everyone is preparing for us/non-us tiered system around llm access.&lt;/p&gt;
</content:encoded><category>link</category></item><item><title>pi-guardrails: v0.15.0</title><link>https://new.aliou.me/releases/pi-guardrails-v0-15-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-guardrails-v0-15-0/</guid><pubDate>Fri, 26 Jun 2026 20:40:25 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;9f0d736: Add a “decline and stop” option to the permission-gate prompt. Choosing it (press &lt;code&gt;s&lt;/code&gt;, or select “Decline and stop” in the RPC fallback) blocks the dangerous command, emits a &lt;code&gt;guardrails:action:blocked&lt;/code&gt; event with the new &lt;code&gt;user-stop&lt;/code&gt; block source, and aborts the current agent turn so the assistant does not keep going.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;09934f0: Update AGENTS.md to reflect the current three-extension layout and remove the unused, broken test harness.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-toolchain: v0.9.0</title><link>https://new.aliou.me/releases/pi-toolchain-v0-9-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-toolchain-v0-9-0/</guid><pubDate>Sun, 21 Jun 2026 20:40:25 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;11546d1: Add nix shell / devShell rewriting rule&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;f0406b1: Update Pi devDependencies to 0.79.9. Widen peerDependency ranges for &lt;code&gt;@earendil-works/pi-coding-agent&lt;/code&gt; and &lt;code&gt;@earendil-works/pi-tui&lt;/code&gt; from &lt;code&gt;&gt;=0.74.0 &amp;#x3C;1&lt;/code&gt; to &lt;code&gt;*&lt;/code&gt; to match the canonical Pi package convention in docs/packages.md. No runtime behavior changes; no post-0.74 APIs are used.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.18.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-18-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-18-0/</guid><pubDate>Sun, 21 Jun 2026 16:17:07 GMT</pubDate><content:encoded>&lt;p&gt;Release v0.18.0&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.20.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-20-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-20-0/</guid><pubDate>Sat, 20 Jun 2026 20:40:25 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;d3a22a8: Add a configurable utility API proxy for quotas and web search, with optional auth gating.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-guardrails: v0.14.0</title><link>https://new.aliou.me/releases/pi-guardrails-v0-14-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-guardrails-v0-14-0/</guid><pubDate>Fri, 19 Jun 2026 20:40:25 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;e012ea0: Migrate &lt;code&gt;pathAccess.allowedPaths&lt;/code&gt; from a flat &lt;code&gt;string[]&lt;/code&gt; (trailing-slash convention) to an explicit &lt;code&gt;{ kind, path }&lt;/code&gt; discriminated array.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;file&lt;/code&gt; grants match the exact path; &lt;code&gt;directory&lt;/code&gt; grants match the directory and its descendants.&lt;/li&gt;
&lt;li&gt;Removes the implicit trailing-slash convention from config, storage, and runtime access matching.&lt;/li&gt;
&lt;li&gt;Fixes skill &lt;code&gt;baseDir&lt;/code&gt; grants being matched as exact files instead of directory boundaries.&lt;/li&gt;
&lt;li&gt;Adds migration &lt;code&gt;010-allowed-paths-objects&lt;/code&gt; to convert existing string entries; &lt;code&gt;009&lt;/code&gt; made format-agnostic so it no longer re-runs on migrated configs.&lt;/li&gt;
&lt;li&gt;Settings UI &lt;code&gt;Allowed paths&lt;/code&gt; editor now toggles kind per entry (Tab) instead of relying on trailing slashes.&lt;/li&gt;
&lt;li&gt;Regenerates &lt;code&gt;schema.json&lt;/code&gt; with the new &lt;code&gt;AllowedPath&lt;/code&gt; definition.&lt;/li&gt;
&lt;li&gt;Bumps &lt;code&gt;@aliou/pi-utils-settings&lt;/code&gt; to &lt;code&gt;^0.17.0&lt;/code&gt; and switches migrations to its built-in &lt;code&gt;Migration.message&lt;/code&gt; field. Migration warnings now flow through &lt;code&gt;ConfigLoader.drainMessages()&lt;/code&gt; instead of guardrails’ manual &lt;code&gt;addPendingWarning&lt;/code&gt; queue (which is retained only for non-migration warnings like invalid regex patterns). The &lt;code&gt;001&lt;/code&gt; config-backup failure path drops to &lt;code&gt;console.error&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a7f2980: Bump &lt;code&gt;@aliou/pi-utils-settings&lt;/code&gt; to &lt;code&gt;^0.17.0&lt;/code&gt; and switch migration warnings to its built-in &lt;code&gt;Migration.message&lt;/code&gt; field.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Migration warnings now flow through &lt;code&gt;ConfigLoader.drainMessages()&lt;/code&gt; (drained and rendered in the &lt;code&gt;session_start&lt;/code&gt; handler) instead of guardrails’ manual &lt;code&gt;addPendingWarning&lt;/code&gt; queue.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;001&lt;/code&gt; config-backup failure path drops to &lt;code&gt;console.error&lt;/code&gt; (it fires on an error path, not a successful run, so it cannot use the &lt;code&gt;message&lt;/code&gt; field).&lt;/li&gt;
&lt;li&gt;Removes the now-unused &lt;code&gt;src/shared/warnings.ts&lt;/code&gt; module. Invalid-regex handling in pattern compilation silently matches nothing for now (TODO: surface via &lt;code&gt;ctx.ui.notify&lt;/code&gt; once compilation is pre-cached at setup).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-toolchain: v0.8.0</title><link>https://new.aliou.me/releases/pi-toolchain-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-toolchain-v0-8-0/</guid><pubDate>Fri, 19 Jun 2026 20:40:25 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;0aca4d0: Rewrite: tool_call-based command mutation, new config surface, removed bash integration&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Breaking config changes (migrations provided):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FeatureMode&lt;/code&gt; &lt;code&gt;&quot;rewrite&quot;&lt;/code&gt; renamed to &lt;code&gt;&quot;mutate&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;features.enforcePackageManager&lt;/code&gt; renamed to &lt;code&gt;features.packageManager&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;features.rewritePython&lt;/code&gt; renamed to &lt;code&gt;features.python&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ui.showRewriteNotifications&lt;/code&gt; renamed to &lt;code&gt;ui.showMutationNotifications&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash.sourceMode&lt;/code&gt; and &lt;code&gt;BashSourceMode&lt;/code&gt; removed entirely&lt;/li&gt;
&lt;li&gt;Config migrations automatically handle all renames and strip stale keys&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;New features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Command mutation via &lt;code&gt;tool_call&lt;/code&gt; hook — &lt;code&gt;event.input.command&lt;/code&gt; is mutated in place before shell execution, replacing the spawn-hook/bash-integration approach&lt;/li&gt;
&lt;li&gt;All three behaviors (block, mutate, notify) handled by a single &lt;code&gt;tool_call&lt;/code&gt; handler&lt;/li&gt;
&lt;li&gt;Execution order: blockers first, then mutation, then notifications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Removed:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bash-integration.ts&lt;/code&gt; and &lt;code&gt;bash-composition.ts&lt;/code&gt; deleted&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createBashTool&lt;/code&gt; / &lt;code&gt;BashSpawnContext&lt;/code&gt; / spawn hook registration removed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;override-bash&lt;/code&gt; and &lt;code&gt;composed-bash&lt;/code&gt; source modes removed&lt;/li&gt;
&lt;li&gt;Mutation notifications no longer have &lt;code&gt;[override-bash]&lt;/code&gt;/&lt;code&gt;[composed-bash]&lt;/code&gt; prefix&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Architecture:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/&lt;/code&gt; — pure functions, zero Pi runtime imports&lt;/li&gt;
&lt;li&gt;&lt;code&gt;extensions/toolchain/&lt;/code&gt; — Pi extension entry point, hooks, settings UI&lt;/li&gt;
&lt;li&gt;40 tests covering config defaults, migrations, feature predicates, tool_call hook, and pure mutation engine&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-ts-aperture: v0.6.0</title><link>https://new.aliou.me/releases/pi-ts-aperture-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-ts-aperture-v0-6-0/</guid><pubDate>Fri, 19 Jun 2026 20:40:25 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;b365a22: Refactor Aperture into a single extension with independent dedicated and proxy capabilities.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replaced the legacy &lt;code&gt;mode&lt;/code&gt; setting with independent &lt;code&gt;dedicated.enabled&lt;/code&gt; and &lt;code&gt;proxy.enabled&lt;/code&gt; flags. Existing configs are migrated automatically.&lt;/li&gt;
&lt;li&gt;Kept dedicated enabled by default while allowing proxy to be enabled at the same time.&lt;/li&gt;
&lt;li&gt;Moved Pi extension code under &lt;code&gt;extensions/aperture/&lt;/code&gt; and kept Pi-agnostic Aperture API/provider mapping code under &lt;code&gt;src/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Switched provider discovery to Aperture’s &lt;code&gt;/api/providers&lt;/code&gt; and &lt;code&gt;/aperture/config&lt;/code&gt; endpoints.&lt;/li&gt;
&lt;li&gt;Added shared provider mapping so onboarding and settings use the same local Pi registry matching behavior.&lt;/li&gt;
&lt;li&gt;Improved proxy matching with base URL child-path matching and provider ID fallback, including the Codex root URL special case.&lt;/li&gt;
&lt;li&gt;Removed dedicated model ID prefixes, persisted gateway model cache, temporary model-sync onboarding skill/tools, and &lt;code&gt;x-upstream-provider-id&lt;/code&gt; request headers.&lt;/li&gt;
&lt;li&gt;Updated onboarding to choose dedicated, proxy, or both, then reload Pi after saving so selected providers register cleanly.&lt;/li&gt;
&lt;li&gt;Updated settings to edit connection, capabilities, proxy providers, dedicated provider filters, onboarding state, and onboarding extension state.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-neuralwatt: v0.7.0</title><link>https://new.aliou.me/releases/pi-neuralwatt-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-neuralwatt-v0-7-0/</guid><pubDate>Thu, 18 Jun 2026 20:40:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;9384557: Handle Neuralwatt stream rate limits before the OpenAI SDK drops response headers. Show layer-specific 429 messages, keep Pi auto-retry detection working, and parse SSE quota comments for live quota updates.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;57e5ac2: Sync model list with live Neuralwatt API. Remove deprecated glm-5-fast (no longer served). Fix zai-org/GLM-5.1-FP8 context window from 202736 to 1048560 (matches the GLM-5.2-backed 1048K deployment).&lt;/li&gt;
&lt;li&gt;7ba08db: Sync model list with live Neuralwatt API. Add glm-5.2-fast and promote zai-org/GLM-5.1-FP8 from legacy alias to a standalone canonical entry (now serving a GLM-5.2 test build). Update glm-5.1 and glm-5.1-fast context windows to 1048560 (GLM-5.2-backed, 1048K).&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.19.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-19-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-19-0/</guid><pubDate>Thu, 18 Jun 2026 20:40:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;181230b: Add GLM-5.2 model and update &lt;code&gt;syn:large:text&lt;/code&gt; alias&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;hf:zai-org/GLM-5.2&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;contextWindow: 524288&lt;/li&gt;
&lt;li&gt;maxTokens: 65536&lt;/li&gt;
&lt;li&gt;cost: input $1.4, output $4.4 per 1M tokens (cacheRead $1.4)&lt;/li&gt;
&lt;li&gt;input: text only&lt;/li&gt;
&lt;li&gt;reasoning: two effective levels — &lt;code&gt;max&lt;/code&gt; (default, highest) and &lt;code&gt;high&lt;/code&gt; (lower), per the GLM-5.2 chat template. The Synthetic OpenAI shim rejects literal &lt;code&gt;max&lt;/code&gt; (and &lt;code&gt;xhigh&lt;/code&gt; errors), so &lt;code&gt;thinkingLevelMap&lt;/code&gt; maps Pi’s &lt;code&gt;high&lt;/code&gt; -&gt; &lt;code&gt;&quot;high&quot;&lt;/code&gt; and &lt;code&gt;xhigh&lt;/code&gt; -&gt; &lt;code&gt;&quot;medium&quot;&lt;/code&gt; (which falls through to &lt;code&gt;max&lt;/code&gt;); &lt;code&gt;minimal&lt;/code&gt;/&lt;code&gt;low&lt;/code&gt;/&lt;code&gt;medium&lt;/code&gt; are hidden.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;syn:large:text&lt;/code&gt; alias target from &lt;code&gt;hf:zai-org/GLM-5.1&lt;/code&gt; to &lt;code&gt;hf:zai-org/GLM-5.2&lt;/code&gt;. Alias inherits the new two-level thinking map from its target.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>preview-markdown: v0.7.0</title><link>https://new.aliou.me/releases/preview-markdown-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/preview-markdown-v0-7-0/</guid><pubDate>Wed, 17 Jun 2026 16:17:17 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.7.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/preview-markdown/pull/9&quot;&gt;https://github.com/aliou/preview-markdown/pull/9&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/preview-markdown/compare/v0.6.1...v0.7.0&quot;&gt;https://github.com/aliou/preview-markdown/compare/v0.6.1…v0.7.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-utils-ui: v0.5.0</title><link>https://new.aliou.me/releases/pi-utils-ui-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-ui-v0-5-0/</guid><pubDate>Wed, 17 Jun 2026 16:17:08 GMT</pubDate><content:encoded>&lt;p&gt;Release v0.5.0&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-neuralwatt: v0.6.0</title><link>https://new.aliou.me/releases/pi-neuralwatt-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-neuralwatt-v0-6-0/</guid><pubDate>Tue, 16 Jun 2026 20:40:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;64a0791: Add a setting for showing legacy Neuralwatt model IDs. Legacy IDs now default to disabled, and existing config files are migrated with a notice pointing users to &lt;code&gt;/neuralwatt:settings&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2d60a83: Add Kimi K2.7 Code model&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-dev-kit: v0.8.0</title><link>https://new.aliou.me/releases/pi-dev-kit-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-dev-kit-v0-8-0/</guid><pubDate>Sun, 14 Jun 2026 16:17:01 GMT</pubDate><content:encoded>&lt;p&gt;Release v0.8.0&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Open Source must win</title><link>https://new.aliou.me/links/open-source-must-win/</link><guid isPermaLink="true">https://new.aliou.me/links/open-source-must-win/</guid><pubDate>Sat, 13 Jun 2026 12:22:57 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This article will sound like a schizo ramble, so prepare.
To start off I want to state my true beliefs on the matter:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;LLMs are not conscious and won’t ever be.&lt;/li&gt;
&lt;li&gt;LLMs are inaccurate statistical machines.&lt;/li&gt;
&lt;li&gt;Open Source AI is good but must get better.&lt;/li&gt;
&lt;li&gt;I have no hatred, or malice for any closed source lab&lt;/li&gt;
&lt;li&gt;I respect what I see as my peers at OpenAI, Anthropic, xAI etc.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You might think, well if that’s the case what’s the problem?
These models aren’t intelligent, they can’t really replace people, can they?&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><category>link</category></item><item><title>Something is jamming GPS over Europe. Here&apos;s what we found</title><link>https://new.aliou.me/links/gps-jamming-europe/</link><guid isPermaLink="true">https://new.aliou.me/links/gps-jamming-europe/</guid><pubDate>Fri, 12 Jun 2026 17:36:37 GMT</pubDate><content:encoded>&lt;p&gt;Yeah that’s my shit.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>pi-utils-settings: v0.17.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-17-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-17-0/</guid><pubDate>Wed, 10 Jun 2026 20:40:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a9e9152: Add optional &lt;code&gt;message&lt;/code&gt; field to &lt;code&gt;Migration&lt;/code&gt; interface for user-facing migration notifications&lt;/p&gt;
&lt;p&gt;Migrations can now declare a &lt;code&gt;message&lt;/code&gt; that is queued when the migration runs successfully.
Extensions drain messages via &lt;code&gt;ConfigLoader.drainMessages()&lt;/code&gt; and display them however they want
(typically via &lt;code&gt;ctx.ui.notify&lt;/code&gt; in &lt;code&gt;session_start&lt;/code&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Migration.message&lt;/code&gt;: &lt;code&gt;string | ((before, after, filePath) =&gt; string | undefined)&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Static strings are used as-is&lt;/li&gt;
&lt;li&gt;Factory functions receive both pre-migration and post-migration config&lt;/li&gt;
&lt;li&gt;Returning &lt;code&gt;undefined&lt;/code&gt; from a factory skips the message&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drainMessages()&lt;/code&gt; returns &lt;code&gt;string[]&lt;/code&gt;, queue is cleared on drain&lt;/li&gt;
&lt;li&gt;Failed migrations do not queue messages&lt;/li&gt;
&lt;li&gt;Message factory errors are caught gracefully (logged, not queued)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>exe.dev&apos;s ssh host header</title><link>https://new.aliou.me/links/ssh-host-header/</link><guid isPermaLink="true">https://new.aliou.me/links/ssh-host-header/</guid><pubDate>Mon, 08 Jun 2026 16:07:14 GMT</pubDate><content:encoded>&lt;p&gt;been playing with &lt;a href=&quot;https://exe.dev/&quot;&gt;exe.dev&lt;/a&gt; this weekend and was curious how they made &lt;code&gt;ssh [vm-name].exe.dev&lt;/code&gt; just work, and it’s pretty cool: &lt;a href=&quot;https://blog.exe.dev/ssh-host-header&quot;&gt;https://blog.exe.dev/ssh-host-header&lt;/a&gt;&lt;/p&gt;</content:encoded><category>link</category></item><item><title>sesame: @aliou/sesame@0.10.0</title><link>https://new.aliou.me/releases/sesame-v0-10-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-10-0/</guid><pubDate>Sun, 07 Jun 2026 20:40:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;84d611e: Fix high CPU in &lt;code&gt;sesame watch&lt;/code&gt; caused by repeated full-directory re-indexing and redundant file I/O:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;readFirstLine&lt;/code&gt; utility that reads only the first 4 KiB of a file instead of loading the entire contents into memory.&lt;/li&gt;
&lt;li&gt;Fix &lt;code&gt;PiParser.canParse()&lt;/code&gt; to use &lt;code&gt;readFirstLine&lt;/code&gt; instead of reading the whole file just to check the header.&lt;/li&gt;
&lt;li&gt;Fix indexer mtime-skip path to use &lt;code&gt;readFirstLine&lt;/code&gt; instead of &lt;code&gt;readFileSync&lt;/code&gt; of the entire file.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;indexFile(db, filePath)&lt;/code&gt; for targeted single-file re-indexing without scanning the whole directory.&lt;/li&gt;
&lt;li&gt;Watch handler now does per-file debounce and targeted indexing for &lt;code&gt;.jsonl&lt;/code&gt; changes, falling back to full scan only when no filename is available.&lt;/li&gt;
&lt;li&gt;Queue adds &lt;code&gt;SourceConfig.files&lt;/code&gt; for targeted paths, &lt;code&gt;mergeSource()&lt;/code&gt; to coalesce pending work, and a 2 s cooldown between consecutive indexing runs to prevent back-to-back re-indexes when Pi keeps appending to the active session.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Rift</title><link>https://new.aliou.me/links/rift/</link><guid isPermaLink="true">https://new.aliou.me/links/rift/</guid><pubDate>Fri, 05 Jun 2026 17:36:37 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;we landed on a pretty good workflow for doing parallel work in OpenCode&lt;/p&gt;
&lt;p&gt;this demo is with git worktrees but i also preview an alternative we’re working on at the end&lt;/p&gt;
&lt;p&gt;this will be in 1.6.0&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://x.com/thdxr/status/2062695857820926248&quot;&gt;@thdxr&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rift looks interesting.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>Pi Agent explained in 6min</title><link>https://new.aliou.me/links/pi-agent-explained/</link><guid isPermaLink="true">https://new.aliou.me/links/pi-agent-explained/</guid><pubDate>Thu, 04 Jun 2026 17:36:37 GMT</pubDate><content:encoded>&lt;p&gt;Take a look, I really like how this is explained.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>sesame: @aliou/sesame@0.9.0</title><link>https://new.aliou.me/releases/sesame-v0-9-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-9-0/</guid><pubDate>Tue, 02 Jun 2026 20:40:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;dc5e9aa: Library changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;listSessions&lt;/code&gt; and &lt;code&gt;getSession&lt;/code&gt; to the public library API.&lt;/li&gt;
&lt;li&gt;Keep the parser and indexing flow explicitly Pi-only after the workspace split.&lt;/li&gt;
&lt;li&gt;Refresh the Sesame skill and usage documentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CLI changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bundle &lt;code&gt;@aliou/sesame&lt;/code&gt; into the standalone SEA binary.&lt;/li&gt;
&lt;li&gt;Replace lazy dynamic command imports with static imports in the CLI entrypoint.&lt;/li&gt;
&lt;li&gt;Refresh CLI help, metadata, and usage documentation.&lt;/li&gt;
&lt;li&gt;Split release automation so the library publishes to npm and the CLI creates its own GitHub release with binaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Repository changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Split the project into &lt;code&gt;@aliou/sesame&lt;/code&gt; and private &lt;code&gt;@aliou/sesame-cli&lt;/code&gt; workspace packages.&lt;/li&gt;
&lt;li&gt;Upgrade Biome and lint plugins.&lt;/li&gt;
&lt;li&gt;Remove the unused Pi extension package.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sh: v0.2.0</title><link>https://new.aliou.me/releases/sh-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sh-v0-2-0/</guid><pubDate>Tue, 02 Jun 2026 20:40:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;5ef2b75: Add agent skill for AST usage&lt;/p&gt;
&lt;p&gt;New &lt;code&gt;skills/sh-ast/SKILL.md&lt;/code&gt; provides documentation and examples for agents
using the parser programmatically: extracting commands, analyzing pipelines,
finding variables, checking for unsafe patterns, and traversing the AST.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.16.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-16-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-16-0/</guid><pubDate>Mon, 01 Jun 2026 20:40:24 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;923804e: Allow &lt;code&gt;buildSchemaUrl&lt;/code&gt; callers to customize schema hosting URLs with &lt;code&gt;baseUrl&lt;/code&gt; or &lt;code&gt;template&lt;/code&gt; options.&lt;/li&gt;
&lt;li&gt;ec86921: Add &lt;code&gt;onBeforeClose&lt;/code&gt; to intercept settings UI close requests before discarding drafts.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>obsidian-flint: v0.1.0</title><link>https://new.aliou.me/releases/obsidian-flint-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/obsidian-flint-v0-1-0/</guid><pubDate>Mon, 01 Jun 2026 16:17:09 GMT</pubDate><content:encoded>&lt;p&gt;Release v0.1.0&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Thoughts on Claude Code&apos;s Workflow feature</title><link>https://new.aliou.me/tils/claude-code-workflow/</link><guid isPermaLink="true">https://new.aliou.me/tils/claude-code-workflow/</guid><pubDate>Fri, 29 May 2026 16:00:13 GMT</pubDate><content:encoded>&lt;p&gt;I like the idea of Claude’s workflow thingy but since it’s writing code, it keeps failing with these kind of errors:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.aliou.me/blog/claude-code-workflow/Screenshot-2026-05-29-at-10.21.48-AM@2x.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In addition, the UI of Claude feels a bit overwhelming to me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.aliou.me/blog/claude-code-workflow/Screenshot-2026-05-29-at-10.29.55-AM@2x.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;There’s also some strong validations around making the workflow deterministic and I wonder if it could still be handled? I’m also curious on why they’re not giving guidance to the model about this and/or linting workflow code to have this be caught before the harness runs the workflow.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.aliou.me/blog/claude-code-workflow/Screenshot-2026-05-29-at-10.49.59-AM@2x.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded><category>til</category></item><item><title>granola-cli: v0.2.0</title><link>https://new.aliou.me/releases/granola-cli-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/granola-cli-v0-2-0/</guid><pubDate>Thu, 28 May 2026 16:17:16 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.2.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/granola-cli/pull/2&quot;&gt;https://github.com/aliou/granola-cli/pull/2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/granola-cli/compare/v0.1.0...v0.2.0&quot;&gt;https://github.com/aliou/granola-cli/compare/v0.1.0…v0.2.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>granola-cli: v0.1.0</title><link>https://new.aliou.me/releases/granola-cli-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/granola-cli-v0-1-0/</guid><pubDate>Thu, 28 May 2026 16:17:16 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.1.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/granola-cli/pull/1&quot;&gt;https://github.com/aliou/granola-cli/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;new-contributors&quot;&gt;New Contributors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;@github-actions[bot] made their first contribution in &lt;a href=&quot;https://github.com/aliou/granola-cli/pull/1&quot;&gt;https://github.com/aliou/granola-cli/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/granola-cli/commits/v0.1.0&quot;&gt;https://github.com/aliou/granola-cli/commits/v0.1.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Claude Opus&apos;s fast mode</title><link>https://new.aliou.me/tils/claude-opus-fast-mode/</link><guid isPermaLink="true">https://new.aliou.me/tils/claude-opus-fast-mode/</guid><pubDate>Thu, 28 May 2026 16:05:27 GMT</pubDate><content:encoded>&lt;p&gt;Missed that claude/opus has a fast mode so I had Pi make a quick extension&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; to play with it. Below, a comparison with the vanilla Opus.&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;Don’t look too closely at that horrendous code that Kimi with reasoning disabled produced. &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><category>til</category></item><item><title>pi-synthetic: v0.18.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-18-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-18-0/</guid><pubDate>Wed, 27 May 2026 20:40:24 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;0fde0fa: Add syn:large:text, syn:small:text, syn:large:vision, syn:small:vision alias models&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-neuralwatt: v0.5.0</title><link>https://new.aliou.me/releases/pi-neuralwatt-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-neuralwatt-v0-5-0/</guid><pubDate>Mon, 25 May 2026 20:40:24 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;3c467b3: Remove live model sync from provider endpoint. Models are now purely hardcoded in &lt;code&gt;src/extensions/provider/models.ts&lt;/code&gt; and validated against the Neuralwatt &lt;code&gt;/v1/models&lt;/code&gt; API at test time.&lt;/p&gt;
&lt;p&gt;Removed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/lib/fetch-models.ts&lt;/code&gt; (live model fetch + &lt;code&gt;mapApiModel&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/utils/is-offline.ts&lt;/code&gt; and its test (only used by fetch flow)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/extensions/provider/provider-payload.ts&lt;/code&gt; (&lt;code&gt;buildModelsPayload&lt;/code&gt; wrapper)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NeuralwattModelConfig&lt;/code&gt; type extension (uses &lt;code&gt;ProviderModelConfig&lt;/code&gt; directly)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fast&lt;/code&gt; field on model entries&lt;/li&gt;
&lt;li&gt;Live re-registration on &lt;code&gt;session_start&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Simplified:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NEURALWATT_MODELS_CACHE&lt;/code&gt; → &lt;code&gt;NEURALWATT_MODELS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Provider registers once on startup with hardcoded list&lt;/li&gt;
&lt;li&gt;Tests now fetch live API and compare prices, context windows, reasoning, vision, and model existence&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;c10a189: Add &lt;code&gt;requiresReasoningContentOnAssistantMessages&lt;/code&gt; compat flag for reasoning models. Neuralwatt docs confirm these models need &lt;code&gt;reasoning_content&lt;/code&gt; on replayed assistant turns to preserve chain-of-thought across turns in agentic conversations.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-guardrails: v0.13.0</title><link>https://new.aliou.me/releases/pi-guardrails-v0-13-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-guardrails-v0-13-0/</guid><pubDate>Fri, 22 May 2026 20:40:24 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;0f4f478: Add &lt;code&gt;guardrails:action:prompted&lt;/code&gt; event that fires when guardrails shows an interactive prompt to the user, before the user has responded. This complements the existing &lt;code&gt;guardrails:action:blocked&lt;/code&gt; (post-decision) and &lt;code&gt;guardrails:risk:detected&lt;/code&gt; events.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>preview-markdown: v0.6.0</title><link>https://new.aliou.me/releases/preview-markdown-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/preview-markdown-v0-6-0/</guid><pubDate>Tue, 19 May 2026 16:17:17 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.6.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/preview-markdown/pull/7&quot;&gt;https://github.com/aliou/preview-markdown/pull/7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/preview-markdown/compare/v0.5.0...v0.6.0&quot;&gt;https://github.com/aliou/preview-markdown/compare/v0.5.0…v0.6.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-guardrails: v0.12.0</title><link>https://new.aliou.me/releases/pi-guardrails-v0-12-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-guardrails-v0-12-0/</guid><pubDate>Mon, 18 May 2026 20:40:24 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;bd90cdf: Remove the permission gate command explainer and its subagent runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5cef4eb: Split Guardrails into separate policy, path-access, and permission-gate extensions backed by shared config, generated JSON schema support, and refreshed README documentation.&lt;/p&gt;
&lt;p&gt;Breaking: renamed public event bus events to &lt;code&gt;guardrails:action:blocked&lt;/code&gt;, &lt;code&gt;guardrails:risk:detected&lt;/code&gt;, &lt;code&gt;guardrails:feature:request&lt;/code&gt;, and &lt;code&gt;guardrails:feature:register&lt;/code&gt;. Blocked and risk events now use core &lt;code&gt;Action&lt;/code&gt; and &lt;code&gt;Safety&lt;/code&gt; payload shapes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;7b01ab4: Move config migrations into shared modules and only show onboarding when no guardrails config exists.&lt;/li&gt;
&lt;li&gt;5d76145: Update settings utilities to the latest version.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>vite-plugin-obsidian: @aliou/vite-plugin-obsidian@0.2.0</title><link>https://new.aliou.me/releases/vite-plugin-obsidian-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/vite-plugin-obsidian-v0-2-0/</guid><pubDate>Sun, 17 May 2026 16:17:12 GMT</pubDate><content:encoded>&lt;p&gt;Release @aliou/vite-plugin-obsidian@0.2.0&lt;/p&gt;</content:encoded><category>release</category></item><item><title>nvim-pi: v0.2.0</title><link>https://new.aliou.me/releases/nvim-pi-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/nvim-pi-v0-2-0/</guid><pubDate>Fri, 15 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h2 id=&quot;changes&quot;&gt;Changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Update Pi compatibility to 0.74.0.&lt;/li&gt;
&lt;li&gt;Migrate Pi imports and package metadata to @earendil-works scope.&lt;/li&gt;
&lt;li&gt;Migrate TypeBox usage to typebox 1.x.&lt;/li&gt;
&lt;li&gt;Update bundled Pi utility dependencies.&lt;/li&gt;
&lt;li&gt;Improve Neovim hook cancellation/path handling for current Pi APIs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;verification&quot;&gt;Verification&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;pnpm typecheck&lt;/li&gt;
&lt;li&gt;pnpm lint&lt;/li&gt;
&lt;li&gt;pnpm test&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-linear: 0.1.0</title><link>https://new.aliou.me/releases/pi-linear-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linear-v0-1-0/</guid><pubDate>Fri, 15 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;feat: show delegated agents in assignee output by @aliou in &lt;a href=&quot;https://github.com/aliou/pi-linear/pull/1&quot;&gt;https://github.com/aliou/pi-linear/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;new-contributors&quot;&gt;New Contributors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;@aliou made their first contribution in &lt;a href=&quot;https://github.com/aliou/pi-linear/pull/1&quot;&gt;https://github.com/aliou/pi-linear/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/pi-linear/commits/0.1.0&quot;&gt;https://github.com/aliou/pi-linear/commits/0.1.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Self-upgradable software: a Pi setup</title><link>https://new.aliou.me/links/self-upgradable-software/</link><guid isPermaLink="true">https://new.aliou.me/links/self-upgradable-software/</guid><pubDate>Fri, 15 May 2026 15:57:09 GMT</pubDate><content:encoded>&lt;p&gt;Closing my open tabs and finally read Mikker’s post. Borrowed his review workflow but wanted to have it in a Ghostty split (thanks to AppleScript).&lt;/p&gt;
&lt;p&gt;I then had Kimi and GLM figure it out and make me an extension.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>vite-plugin-obsidian: v0.1.0</title><link>https://new.aliou.me/releases/vite-plugin-obsidian-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/vite-plugin-obsidian-v0-1-0/</guid><pubDate>Thu, 14 May 2026 16:17:12 GMT</pubDate><content:encoded>&lt;p&gt;Release v0.1.0&lt;/p&gt;</content:encoded><category>release</category></item><item><title>A prompt for knowledge gaps</title><link>https://new.aliou.me/tils/knowledge-gap-prompt/</link><guid isPermaLink="true">https://new.aliou.me/tils/knowledge-gap-prompt/</guid><pubDate>Wed, 13 May 2026 15:48:58 GMT</pubDate><content:encoded>&lt;p&gt;Been using variations of this prompt every time I notice a gap in my knowledge. Choosing a starting point that I broadly understand and then throwing stuff I literally don’t know about works super well for me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.aliou.me/blog/knowledge-gap-prompt/knowledge-gap-prompt.jpg&quot; alt=&quot;Knowledge gap prompt&quot;&gt;&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Full verbatim prompt&lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;I want to learn more and understand what’s fine tuning for llm models, what’s lora, how does &lt;a href=&quot;https://thinkingmachines.ai/&quot;&gt;https://thinkingmachines.ai/&lt;/a&gt; ‘s products fit in this etc. to do so, i want to use coding agent traces as training data, from hugging face (see &lt;a href=&quot;https://huggingface.co/changelog/agent-trace-viewer&quot;&gt;https://huggingface.co/changelog/agent-trace-viewer&lt;/a&gt; ) , in particular traces from pi.&lt;/p&gt;
&lt;p&gt;let’s assume i know nothing about what is finetuning and how it works. let’s go through this blog post &lt;a href=&quot;https://leoniemonigatti.com/blog/fine-tuning-lfm2-5-1-2b-instruct-with-grpo.html&quot;&gt;https://leoniemonigatti.com/blog/fine-tuning-lfm2-5-1-2b-instruct-with-grpo.html&lt;/a&gt; and quizz me on it to see what is my current understanding. use the current directory as scratchpad / notes. download the markdown of the article here so I/we can annotate it . curl it using this url &lt;code&gt;https://markdown.new/https://leoniemonigatti.com/blog/fine-tuning-lfm2-5-1-2b-instruct-with-grpo.html&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;</content:encoded><category>til</category></item><item><title>LLM CLI tools in shebang lines</title><link>https://new.aliou.me/tils/llm-shebang-scripts/</link><guid isPermaLink="true">https://new.aliou.me/tils/llm-shebang-scripts/</guid><pubDate>Mon, 11 May 2026 16:04:26 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;New TIL: I figured out how to use my LLM CLI tool in a shebang line, which means you can write executable scripts in English, or hook up more complex scripts with a snippet of YAML template&lt;/p&gt;
&lt;p&gt;— &lt;a href=&quot;https://x.com/simonw/status/2053914709720764770&quot;&gt;@simonw&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s becoming a meme at this point, but here’s a Pi extension to do the same:&lt;/p&gt;
&lt;p&gt;Also watch GLM-5.1 completely overthink while trying to speed up the video and add a fast-forward overlay.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>pi-processes: v0.9.0</title><link>https://new.aliou.me/releases/pi-processes-v0-9-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-processes-v0-9-0/</guid><pubDate>Sat, 09 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;8f15f76: Prevent crash on &lt;code&gt;/new&lt;/code&gt; with running background processes&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Crafting Interpreters</title><link>https://new.aliou.me/links/crafting-interpreters/</link><guid isPermaLink="true">https://new.aliou.me/links/crafting-interpreters/</guid><pubDate>Sat, 09 May 2026 15:48:29 GMT</pubDate><content:encoded>&lt;p&gt;Current status.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>pi-linkup: v0.11.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-11-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-11-0/</guid><pubDate>Fri, 08 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a9ca926: Migrate Pi core package dependencies from &lt;code&gt;@mariozechner/*&lt;/code&gt; to &lt;code&gt;@earendil-works/*&lt;/code&gt; namespace.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@mariozechner/pi-coding-agent&lt;/code&gt; → &lt;code&gt;@earendil-works/pi-coding-agent&lt;/code&gt; 0.74.0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@mariozechner/pi-ai&lt;/code&gt; → &lt;code&gt;@earendil-works/pi-ai&lt;/code&gt; 0.74.0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@mariozechner/pi-tui&lt;/code&gt; → &lt;code&gt;@earendil-works/pi-tui&lt;/code&gt; 0.74.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;cbec48a: Truncate Linkup search, answer, and fetch result blocks, with full content saved to temp files when truncated.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.17.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-17-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-17-0/</guid><pubDate>Fri, 08 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ef66a62: Migrate Pi core package dependencies from &lt;code&gt;@mariozechner/*&lt;/code&gt; to &lt;code&gt;@earendil-works/*&lt;/code&gt; namespace.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@mariozechner/pi-coding-agent&lt;/code&gt; → &lt;code&gt;@earendil-works/pi-coding-agent&lt;/code&gt; 0.74.0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@mariozechner/pi-tui&lt;/code&gt; → &lt;code&gt;@earendil-works/pi-tui&lt;/code&gt; 0.74.0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@aliou/pi-utils-settings&lt;/code&gt; bumped to &lt;code&gt;^0.15.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@aliou/pi-utils-ui&lt;/code&gt; bumped to &lt;code&gt;^0.4.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-toolchain: v0.7.0</title><link>https://new.aliou.me/releases/pi-toolchain-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-toolchain-v0-7-0/</guid><pubDate>Fri, 08 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;e4b7f9e: Migrate Pi core package dependencies from &lt;code&gt;@mariozechner/*&lt;/code&gt; to &lt;code&gt;@earendil-works/*&lt;/code&gt; namespace.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@mariozechner/pi-coding-agent&lt;/code&gt; → &lt;code&gt;@earendil-works/pi-coding-agent&lt;/code&gt; 0.74.0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@aliou/pi-utils-settings&lt;/code&gt; bumped to &lt;code&gt;^0.15.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-dev-kit: v0.7.0</title><link>https://new.aliou.me/releases/pi-dev-kit-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-dev-kit-v0-7-0/</guid><pubDate>Thu, 07 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;e3d118d: Update Pi dev kit guidance, entrypoints, and runtime dependencies for Pi 0.74.0 and the &lt;code&gt;@earendil-works/*&lt;/code&gt; package namespace.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.15.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-15-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-15-0/</guid><pubDate>Thu, 07 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;af07e77: Update dependencies to use &lt;code&gt;@earendil-works/pi-&lt;/code&gt; packages and bump &lt;code&gt;@aliou/pi-utils-ui&lt;/code&gt; to v0.4.0&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-ui: v0.4.0</title><link>https://new.aliou.me/releases/pi-utils-ui-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-ui-v0-4-0/</guid><pubDate>Thu, 07 May 2026 20:40:23 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;e8bf742: Migrate package scopes from &lt;code&gt;@mariozechner&lt;/code&gt; to &lt;code&gt;@earendil-works&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;All &lt;code&gt;@mariozechner/pi-coding-agent&lt;/code&gt; and &lt;code&gt;@mariozechner/pi-tui&lt;/code&gt; dependencies,
peer dependencies, and imports are now &lt;code&gt;@earendil-works/pi-coding-agent&lt;/code&gt; and
&lt;code&gt;@earendil-works/pi-tui&lt;/code&gt;. Minimum supported version raised to &lt;code&gt;0.74.0&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>GitHub PR and issue autocomplete for Pi</title><link>https://new.aliou.me/tils/gh-pr-issue-autocomplete/</link><guid isPermaLink="true">https://new.aliou.me/tils/gh-pr-issue-autocomplete/</guid><pubDate>Thu, 07 May 2026 15:52:24 GMT</pubDate><content:encoded>&lt;p&gt;Currently cleaning up a backlog of issues and PRs in a client’s repo and was tired of alt-tabbing between Pi and my browser, so made a quick Pi extension to autocomplete GitHub PRs and issues.&lt;/p&gt;
&lt;p&gt;Never going to use this ever again, but might be useful to other people so sharing it as a gist.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>pi-synthetic: v0.16.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-16-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-16-0/</guid><pubDate>Wed, 06 May 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;62e6902: Use event-driven Synthetic quota updates without polling.&lt;/p&gt;
&lt;p&gt;Quota data is now extracted from the &lt;code&gt;x-synthetic-quotas&lt;/code&gt; response header on Synthetic provider responses and stored centrally. Usage status and quota warnings read the latest quota snapshot through short-lived callbacks from fresh Pi lifecycle contexts, avoiding stale &lt;code&gt;ExtensionContext&lt;/code&gt; crashes after reloads or session switches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1176b1d: Avoid stale contexts in status update.&lt;/p&gt;
&lt;p&gt;Use events to retrieve quotas from the shared store.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.14.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-14-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-14-0/</guid><pubDate>Tue, 05 May 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;405e0e5: Update settings editor components to render inside shared &lt;code&gt;@aliou/pi-utils-ui&lt;/code&gt; panels while preserving the existing pi-utils-settings component APIs.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-ui: v0.3.0</title><link>https://new.aliou.me/releases/pi-utils-ui-v0-3-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-ui-v0-3-0/</guid><pubDate>Tue, 05 May 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;4fcf693: Rewrite components from scratch&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Amp&apos;s read thread tool</title><link>https://new.aliou.me/links/amp-read-thread/</link><guid isPermaLink="true">https://new.aliou.me/links/amp-read-thread/</guid><pubDate>Tue, 05 May 2026 15:57:05 GMT</pubDate><content:encoded>&lt;p&gt;Was about to make a Pi extension to communicate between sessions, but actually only needed Amp’s read thread tool.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.aliou.me/blog/pi-session-communication/amp-read-thread.jpg&quot; alt=&quot;Amp read thread tool&quot;&gt;&lt;/p&gt;</content:encoded><category>link</category></item><item><title>pi-synthetic: v0.15.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-15-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-15-0/</guid><pubDate>Sat, 02 May 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;85d9896: Add setting to control Synthetic-proxied models. New installs default to Synthetic-hosted models only, while existing configs keep proxied models enabled.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.14.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-14-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-14-0/</guid><pubDate>Sat, 02 May 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;327f098: Update Pi peer dependencies to 0.72.0. Migrate &lt;code&gt;reasoningEffortMap&lt;/code&gt; to &lt;code&gt;thinkingLevelMap&lt;/code&gt; per Pi 0.72.0 API. Replace &lt;code&gt;session_switch&lt;/code&gt; event with &lt;code&gt;session_start&lt;/code&gt;. Swap &lt;code&gt;@sinclair/typebox&lt;/code&gt; for &lt;code&gt;typebox&lt;/code&gt;. Add Kimi K2.5 model. Set &lt;code&gt;reasoning: false&lt;/code&gt; for Llama 3.3 70B.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-neuralwatt: v0.2.0</title><link>https://new.aliou.me/releases/pi-neuralwatt-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-neuralwatt-v0-2-0/</guid><pubDate>Wed, 29 Apr 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;6f4672e: Fetch live models from Neuralwatt API on session start. The extension registers with a hardcoded model cache immediately on startup, then fetches &lt;code&gt;/v1/models&lt;/code&gt; on session start and re-registers the provider with live data (including pricing, capabilities, and limits from the new API metadata). A notification is shown when live models differ from the cache. Falls back to the hardcoded cache if the fetch fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;0669972: Align model definitions with Neuralwatt API metadata: set reasoning true for GPT-OSS 20B, set reasoning false for Kimi K2.6 Fast, and remove unsupported supportsReasoningEffort from GLM-5.1, Kimi K2.5, Kimi K2.6, MiniMax M2.5, Qwen3.5 397B, and Qwen3.6 35B. Add supportsReasoningEffort to GPT-OSS 20B.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-ts-aperture: v0.5.0</title><link>https://new.aliou.me/releases/pi-ts-aperture-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-ts-aperture-v0-5-0/</guid><pubDate>Thu, 23 Apr 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;b51d282: Rewrite extension architecture. Moves core logic to &lt;code&gt;src/lib/&lt;/code&gt;, introduces &lt;code&gt;ApertureRuntime&lt;/code&gt; class with dependency injection, replaces lifecycle hooks with &lt;code&gt;session_start&lt;/code&gt; + &lt;code&gt;onSync&lt;/code&gt; callback pattern, and adds provider unregistration with user notification.&lt;/li&gt;
&lt;li&gt;00ba115: Add &lt;code&gt;streamSimple&lt;/code&gt; wrapper that sends &lt;code&gt;x-session-id&lt;/code&gt; header with the Pi session ID. This groups all requests from the same Pi session together in the Aperture dashboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;cda19d3: Drop &lt;code&gt;models&lt;/code&gt; from &lt;code&gt;registerProvider&lt;/code&gt; call. Rely on the baseUrl-override path instead, which preserves built-in model definitions (reasoning, compat, thinking levels) and only updates the endpoint URL.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Just</title><link>https://new.aliou.me/links/just/</link><guid isPermaLink="true">https://new.aliou.me/links/just/</guid><pubDate>Tue, 21 Apr 2026 15:48:29 GMT</pubDate><content:encoded>&lt;p&gt;Can’t help but re-read this every time I write “just” somewhere.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>pi-neuralwatt: v0.1.0</title><link>https://new.aliou.me/releases/pi-neuralwatt-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-neuralwatt-v0-1-0/</guid><pubDate>Mon, 20 Apr 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;6b95048: Initial release of pi-neuralwatt — Neuralwatt inference API provider with energy transparency.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-processes: v0.8.0</title><link>https://new.aliou.me/releases/pi-processes-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-processes-v0-8-0/</guid><pubDate>Mon, 20 Apr 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;da0de08: Remove @aliou/pi-evals devDependency&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-guardrails: v0.11.0</title><link>https://new.aliou.me/releases/pi-guardrails-v0-11-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-guardrails-v0-11-0/</guid><pubDate>Fri, 17 Apr 2026 20:40:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;11e88c0: Fix dd pattern (if= to of=) and expand dangerous command detection&lt;/p&gt;
&lt;p&gt;Fixed the dd pattern to check for of= (output file) instead of if= (input file),
as of= is the actual dangerous write operation. Also extracted dangerous command
matchers to a separate module and added new patterns for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Privilege escalation: doas, pkexec&lt;/li&gt;
&lt;li&gt;Secure destruction: shred, wipefs, blkdiscard&lt;/li&gt;
&lt;li&gt;Disk partitioning: fdisk, sfdisk, cfdisk, parted, sgdisk&lt;/li&gt;
&lt;li&gt;Container escapes: docker/podman run with —privileged, —pid=host,
—network=host, —userns=host, root mounts, docker socket mounts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Improved existing matchers to handle long options like —recursive,
—force, etc.&lt;/p&gt;
&lt;p&gt;Fixes #22&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ba06d72: Add path access feature: restrict tool access to current working directory with allow/ask/block modes. Grants can be file-level (exact match) or directory-level (trailing slash convention). Session grants persist in memory, project grants persist in local config.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2db56c2: Fix permission gate bypass in RPC mode: deny-by-default when &lt;code&gt;ctx.ui.custom()&lt;/code&gt; returns undefined, with fallback to &lt;code&gt;ctx.ui.select()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.13.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-13-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-13-0/</guid><pubDate>Thu, 16 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;7e83e5f: feat(settings): make synthetic features configurable&lt;/p&gt;
&lt;p&gt;Add shared Synthetic feature settings with a &lt;code&gt;synthetic:settings&lt;/code&gt;
command and &lt;code&gt;pi config&lt;/code&gt; support. Web search, usage status, quota
warnings, quotas command, and subBar integration can now be enabled
or disabled individually. Web search, usage status, quota warnings,
and subBar polling react to settings changes live. The quotas command
still requires restart to fully unload.&lt;/p&gt;
&lt;p&gt;Add an initial &lt;code&gt;v1-seed-defaults&lt;/code&gt; migration that writes the current
defaults to disk and bumps &lt;code&gt;configVersion&lt;/code&gt; to 1. On first load, fresh
installs seed the global config automatically. A one-time notice is
shown on session start pointing users to &lt;code&gt;pi config&lt;/code&gt; and the
&lt;code&gt;/synthetic:settings&lt;/code&gt; command.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ed77440: feat(usage-status): footer status bar showing live quota usage&lt;/p&gt;
&lt;p&gt;Add usage-status extension that displays live quota percentages
(weekly credits, rolling 5h, etc.) in the footer status bar when a
Synthetic model is active. Colors follow the same severity
assessment as quota-warnings for consistency. Auto-refreshes every
60s and after each turn. Hides for non-Synthetic models.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.12.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-12-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-12-0/</guid><pubDate>Tue, 14 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;015e984: Add quota-warnings extension: automatic notifications when approaching or exceeding Synthetic API quotas&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extract quota severity logic into shared &lt;code&gt;src/utils/quotas-severity.ts&lt;/code&gt; (4-level RiskSeverity: none/warning/high/critical with usedFloor gating, showPace/paceScale support, limited flag handling)&lt;/li&gt;
&lt;li&gt;Refactor quotas TUI display to use shared severity utils&lt;/li&gt;
&lt;li&gt;New quota-warnings extension hooks into session_start and agent_end to check quotas and emit ctx.ui.notify() on severity transitions&lt;/li&gt;
&lt;li&gt;Transition-only notifications: escalation always notifies, high/critical have no cooldown, warning has 60min cooldown&lt;/li&gt;
&lt;li&gt;Notification messages use correct terminology (regen/tick/resets) and precise time formatting (2h13m)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;c1256cf: Fix GLM-4.7 model config: input text-only, reduced input/cacheRead cost&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-ui: v0.2.0</title><link>https://new.aliou.me/releases/pi-utils-ui-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-ui-v0-2-0/</guid><pubDate>Tue, 14 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;e0ad809: Refactor &lt;code&gt;Frame&lt;/code&gt; to extend &lt;code&gt;Container&lt;/code&gt; from &lt;code&gt;pi-tui&lt;/code&gt;, inheriting &lt;code&gt;addChild&lt;/code&gt;, &lt;code&gt;removeChild&lt;/code&gt;, and &lt;code&gt;clear&lt;/code&gt;. &lt;code&gt;borderColor&lt;/code&gt; is now required. Children are no longer accepted via constructor options — use &lt;code&gt;addChild&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;Add Tree component for rendering nested data with box-drawing characters&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.11.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-11-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-11-0/</guid><pubDate>Mon, 13 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;abe28bd: Add &lt;code&gt;r&lt;/code&gt; key binding to the quotas command to refetch and refresh quota data without closing the panel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;52ee513: Rework quotas command display: unified progress bar with single-char pace marker, updated labels (Credits / week, Requests / 5h, Search / hour), percent%/total stat format, and +amount in time subtitles. Drops legacy subscription fallback.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-guardrails: v0.10.0</title><link>https://new.aliou.me/releases/pi-guardrails-v0-10-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-guardrails-v0-10-0/</guid><pubDate>Fri, 10 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;6356335: Add command-based onboarding for new users.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add &lt;code&gt;/guardrails:onboarding&lt;/code&gt; command and session-start hint when setup is pending&lt;/li&gt;
&lt;li&gt;replace auto-open onboarding with explicit overlay flow&lt;/li&gt;
&lt;li&gt;add onboarding completion marker for config compatibility and first-run state&lt;/li&gt;
&lt;li&gt;improve onboarding wizard copy and defaults/recap UX&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;828c019: Fix dangerous command confirmation UI scrolling for long multi-line commands.&lt;/li&gt;
&lt;li&gt;97597c2: Fix home-directory default policy rules so &lt;code&gt;~&lt;/code&gt;-based patterns match correctly and expand to the current user’s home directory during blocking and existence checks.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.10.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-10-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-10-0/</guid><pubDate>Wed, 08 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;9d40b3f: Add support for new Synthetic API quota format with weekly token credits and rolling 5-hour limits&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Display weekly token quota with credits-based tracking ($X.XX/$Y.YY format)&lt;/li&gt;
&lt;li&gt;Show rolling 5-hour request quota with tick-based regeneration&lt;/li&gt;
&lt;li&gt;Use simple indicator bar for new quota types (marker instead of fill)&lt;/li&gt;
&lt;li&gt;Display regeneration info: ”+$X.XX in Xh” for credits, “+X in Xm” for requests&lt;/li&gt;
&lt;li&gt;Maintain backward compatibility with legacy subscription format&lt;/li&gt;
&lt;li&gt;Fix division-by-zero bugs and fragile currency parsing&lt;/li&gt;
&lt;li&gt;Harden edge cases with safePercent() and parseCurrency() helpers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.9.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-9-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-9-0/</guid><pubDate>Mon, 06 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a85b467: Switch to Pi AuthStorage for credential handling&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace direct env var reads with AuthStorage wrapper&lt;/li&gt;
&lt;li&gt;Remove preflight subscription gating - tools/commands always register&lt;/li&gt;
&lt;li&gt;Credentials resolved at call time, not module load&lt;/li&gt;
&lt;li&gt;Resolve key inside each poll tick for sub-integration&lt;/li&gt;
&lt;li&gt;Clear error messages guide users to ~/.pi/agent/auth.json&lt;/li&gt;
&lt;li&gt;Remove web-search/hooks.ts (no longer needed)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.13.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-13-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-13-0/</guid><pubDate>Mon, 06 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;cb080f5: Remove &lt;code&gt;displayToStorageValue&lt;/code&gt; helper. The default change handler now stores raw strings as-is instead of coercing “on”/“off”/“enabled”/“disabled” to booleans. Use &lt;code&gt;onSettingChange&lt;/code&gt; to convert display values to the correct storage types.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Building SyntaQLite</title><link>https://new.aliou.me/links/building-syntaqlite/</link><guid isPermaLink="true">https://new.aliou.me/links/building-syntaqlite/</guid><pubDate>Sun, 05 Apr 2026 15:48:29 GMT</pubDate><content:encoded>&lt;p&gt;Banger.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>Custom providers in Pi</title><link>https://new.aliou.me/posts/custom-providers-in-pi/</link><guid isPermaLink="true">https://new.aliou.me/posts/custom-providers-in-pi/</guid><pubDate>Sat, 04 Apr 2026 15:12:15 GMT</pubDate><content:encoded>&lt;p&gt;Two ways to connect Pi to a custom model endpoint, using Synthetic as an example&lt;/p&gt;</content:encoded><category>post</category></item><item><title>pi-ts-aperture: v0.4.0</title><link>https://new.aliou.me/releases/pi-ts-aperture-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-ts-aperture-v0-4-0/</guid><pubDate>Fri, 03 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2240e43: Extract pure core functions from Pi glue.&lt;/p&gt;
&lt;p&gt;Move decision-making logic into pure functions in src/core/:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL helpers: normalizeInputUrl, resolveGatewayUrl, resolveProviderBaseUrl&lt;/li&gt;
&lt;li&gt;Plan builders: buildApplyPlan, planConfigChange&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All core logic is now unit-testable with no Pi dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;80ef5c2: Add per-provider gateway model checking.&lt;/p&gt;
&lt;p&gt;Validates which models are available on the gateway per configured provider.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5e2d45f: Add gateway model checking to settings UI.&lt;/p&gt;
&lt;p&gt;Shows model availability status in the settings interface.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;748d8e1: Rewrite setup wizard with Wizard + FuzzyMultiSelector.&lt;/p&gt;
&lt;p&gt;Improved UX for configuring Aperture with better multi-select support.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;c60bd7f: Co-locate unit tests with source files.&lt;/p&gt;
&lt;p&gt;Moves core unit tests from tests/core/ to src/core/*.test.ts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;124404c: Rewrite e2e tests to use RpcClient.&lt;/p&gt;
&lt;p&gt;Modernizes test infrastructure for better reliability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ccf5c1d: Replace local ModelInfo type with Model&lt;api&gt; from pi-ai.&lt;/api&gt;&lt;/p&gt;
&lt;p&gt;Uses Pi canonical model type instead of duplicating the shape.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7fb1c7c: Update Pi packages to 0.64.0.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-processes: v0.7.0</title><link>https://new.aliou.me/releases/pi-processes-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-processes-v0-7-0/</guid><pubDate>Thu, 02 Apr 2026 20:40:20 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;80f81ff: perf: replace polling timers with event-driven output rendering&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a8965ad: Add runtime log watch alerts for managed processes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;logWatches&lt;/code&gt; option on &lt;code&gt;process&lt;/code&gt; tool &lt;code&gt;start&lt;/code&gt; action&lt;/li&gt;
&lt;li&gt;Watches match log lines on &lt;code&gt;stdout&lt;/code&gt;, &lt;code&gt;stderr&lt;/code&gt;, or &lt;code&gt;both&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Default one-time behavior (&lt;code&gt;repeat: false&lt;/code&gt;), with optional repeat mode&lt;/li&gt;
&lt;li&gt;On watch match, emit visible UI event and trigger an immediate agent turn&lt;/li&gt;
&lt;li&gt;Invalid watch config (including bad regex patterns) now fails fast at start time&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2f33586: Process lookup now uses exact ID matching only. Fuzzy name/command matching via &lt;code&gt;find()&lt;/code&gt; has been removed. The &lt;code&gt;id&lt;/code&gt; parameter in tool actions accepts only the process ID returned by &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;list&lt;/code&gt;. The &lt;code&gt;/ps&lt;/code&gt; list UI merges the ID and Name columns into a single “Process” column showing &lt;code&gt;name (id)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-dev-kit: v0.6.0</title><link>https://new.aliou.me/releases/pi-dev-kit-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-dev-kit-v0-6-0/</guid><pubDate>Wed, 01 Apr 2026 20:38:31 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ff71e6f: Overhaul tool rendering guidelines and update extension tools to match.&lt;/p&gt;
&lt;p&gt;Skill updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tools.md: throw errors, ToolCallHeader/ToolBody/ToolFooter from pi-utils-ui, stable isPartial, conditional footer, truncateHead + temp file, promptSnippet/promptGuidelines, multi-action tool pattern&lt;/li&gt;
&lt;li&gt;structure.md: core/lib pattern, action modules, config migrations, settings command, auth wizard&lt;/li&gt;
&lt;li&gt;additional-apis.md: two-tier guidance (per-tool metadata vs system prompt hooks)&lt;/li&gt;
&lt;li&gt;testing.md: unit testing core logic, testable execute with DI, handler pattern, Pi stub&lt;/li&gt;
&lt;li&gt;SKILL.md: new critical rules, updated checklist, standalone repo references&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tool updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All tools: add promptSnippet/promptGuidelines, throw errors, simplify details&lt;/li&gt;
&lt;li&gt;changelog-tool: conditional footer, keyHint for expand&lt;/li&gt;
&lt;li&gt;docs-tool: ToolBody with showCollapsed, conditional footer&lt;/li&gt;
&lt;li&gt;package-manager-tool: throw on missing package.json&lt;/li&gt;
&lt;li&gt;version-tool: simplified details&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-linkup: v0.10.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-10-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-10-0/</guid><pubDate>Wed, 01 Apr 2026 20:38:31 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;42481a6: refactor: per-extension entries to fix duplicate command registration&lt;/p&gt;
&lt;p&gt;Replace monolithic entry with 4 separate extension entries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;web-search, web-answer, web-fetch (tools)&lt;/li&gt;
&lt;li&gt;command-balance (shows API credits)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Delete broken singleton pattern from setup.ts that caused
commands to appear 3x in palette (GitHub #24). Pi loads each
extension in separate module context, so module-level guards
failed. Each entry now calls ensureLinkupReady() independently.&lt;/p&gt;
&lt;p&gt;Also remove obsolete command-settings (guidance settings no
longer exist; tools declare their own promptGuidelines).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-linkup: v0.9.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-9-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-9-0/</guid><pubDate>Wed, 01 Apr 2026 20:38:31 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;92703e6: Truncate large &lt;code&gt;linkup_web_fetch&lt;/code&gt; outputs in tool results and save the full fetched content to a temp file.&lt;/li&gt;
&lt;li&gt;aac0899: Add a &lt;code&gt;limit&lt;/code&gt; parameter to &lt;code&gt;linkup_web_search&lt;/code&gt; so agents can cap returned result count.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-toolchain: v0.6.0</title><link>https://new.aliou.me/releases/pi-toolchain-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-toolchain-v0-6-0/</guid><pubDate>Tue, 31 Mar 2026 20:38:31 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;c1cc093: Add configurable &lt;code&gt;bash.sourceMode&lt;/code&gt; with deterministic &lt;code&gt;override-bash&lt;/code&gt; and &lt;code&gt;composed-bash&lt;/code&gt; routing for rewrite features.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.12.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-12-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-12-0/</guid><pubDate>Sun, 29 Mar 2026 20:38:31 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;9659754: Add subOptions support and showHints option to FuzzyMultiSelector&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>linear-cli: v0.5.0</title><link>https://new.aliou.me/releases/linear-cli-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/linear-cli-v0-5-0/</guid><pubDate>Fri, 27 Mar 2026 16:17:14 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.5.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/linear-cli/pull/7&quot;&gt;https://github.com/aliou/linear-cli/pull/7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/linear-cli/compare/v0.4.0...v0.5.0&quot;&gt;https://github.com/aliou/linear-cli/compare/v0.4.0…v0.5.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.11.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-11-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-11-0/</guid><pubDate>Wed, 25 Mar 2026 20:38:42 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ef47f9c: Add small-list mode to &lt;code&gt;FuzzySelector&lt;/code&gt; via &lt;code&gt;searchThreshold&lt;/code&gt; (default &lt;code&gt;7&lt;/code&gt;). When item count is at or below the threshold, it now renders a simple Up/Down/Enter list without a search input while keeping callbacks and &lt;code&gt;currentValue&lt;/code&gt; pre-selection behavior consistent.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>linear-cli: v0.4.0</title><link>https://new.aliou.me/releases/linear-cli-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/linear-cli-v0-4-0/</guid><pubDate>Wed, 25 Mar 2026 16:17:14 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.4.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/linear-cli/pull/6&quot;&gt;https://github.com/aliou/linear-cli/pull/6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/linear-cli/compare/v0.3.0...v0.4.0&quot;&gt;https://github.com/aliou/linear-cli/compare/v0.3.0…v0.4.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Pi Init extension</title><link>https://new.aliou.me/tils/pi-init-extension/</link><guid isPermaLink="true">https://new.aliou.me/tils/pi-init-extension/</guid><pubDate>Mon, 23 Mar 2026 15:52:24 GMT</pubDate><content:encoded>&lt;p&gt;Needed to scaffold a project this morning with an AGENTS.md and liked how Claude Code’s new &lt;code&gt;/init&lt;/code&gt; looked, so had Pi figure out how it works and make its own version.&lt;/p&gt;
&lt;p&gt;Another one-time use extension.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/aliou/pi-undercooked/tree/f9e32dd0e1577a01839724dd4cfd335b6bc9ada1/extensions/pi-init&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;</content:encoded><category>til</category></item><item><title>pi-dev-kit: v0.5.0</title><link>https://new.aliou.me/releases/pi-dev-kit-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-dev-kit-v0-5-0/</guid><pubDate>Fri, 20 Mar 2026 20:38:31 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a214efa: Rename the package from &lt;code&gt;@aliou/pi-extension-dev&lt;/code&gt; to &lt;code&gt;@aliou/pi-dev-kit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The package continues on the same release line under the new name. Update installation docs and package metadata to match the rename.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>linear-cli: v0.3.0</title><link>https://new.aliou.me/releases/linear-cli-v0-3-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/linear-cli-v0-3-0/</guid><pubDate>Fri, 20 Mar 2026 16:17:14 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.3.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/linear-cli/pull/5&quot;&gt;https://github.com/aliou/linear-cli/pull/5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/linear-cli/compare/v0.2.2...v0.3.0&quot;&gt;https://github.com/aliou/linear-cli/compare/v0.2.2…v0.3.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-linkup: v0.8.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-8-0/</guid><pubDate>Wed, 18 Mar 2026 20:38:36 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a4905b5: Expose Linkup tools as separate Pi extensions so &lt;code&gt;pi config&lt;/code&gt; can enable or disable them individually.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7334153: Redesign all tool UIs to use structured rendering components&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;web-search: ToolCallHeader, Container/Markdown/ToolFooter, collapsed/expanded states with blockquote snippets&lt;/li&gt;
&lt;li&gt;web-answer: ToolCallHeader, answer preview collapsed, full Markdown answer expanded with sources list&lt;/li&gt;
&lt;li&gt;web-fetch: ToolCallHeader, Markdown content preview matching read_url pattern (8 lines collapsed, full expanded)&lt;/li&gt;
&lt;li&gt;All tools: throw errors instead of returning error details, proper error display via content extraction&lt;/li&gt;
&lt;li&gt;All tools: use keyHint for expand hints, ToolFooter for metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cb71445: Redesign web search tool UI to match pi-synthetic pattern&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use ToolCallHeader and ToolFooter from @aliou/pi-utils-ui for consistent styling&lt;/li&gt;
&lt;li&gt;Collapsed view shows result count with first result name and expand hint&lt;/li&gt;
&lt;li&gt;Expanded view shows each result with name, URL, and a 5-line blockquote snippet rendered as Markdown&lt;/li&gt;
&lt;li&gt;Error handling uses throw instead of returning error details, matching the pi framework convention&lt;/li&gt;
&lt;li&gt;Errors now display the actual error message instead of misleading “no results”&lt;/li&gt;
&lt;li&gt;Footer shows result count only&lt;/li&gt;
&lt;li&gt;Show search depth as option arg in header when non-standard&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;290df71: Add a Vitest-based Pi runtime harness to verify per-tool &lt;code&gt;pi config&lt;/code&gt; loading, shared command registration, and dynamic Linkup system prompt guidance. Closes #17.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>You still need to think</title><link>https://new.aliou.me/links/you-still-need-to-think/</link><guid isPermaLink="true">https://new.aliou.me/links/you-still-need-to-think/</guid><pubDate>Wed, 18 Mar 2026 15:48:29 GMT</pubDate><content:encoded>&lt;p&gt;The models are getting better and better which makes it easy to forget to not delegate your thinking.&lt;/p&gt;</content:encoded><category>link</category></item><item><title>pi-synthetic: v0.8.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-8-0/</guid><pubDate>Tue, 17 Mar 2026 20:38:36 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;606e829: Redesign web search tool UI to match read_url pattern&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use ToolCallHeader and ToolFooter from @aliou/pi-utils-ui for consistent styling&lt;/li&gt;
&lt;li&gt;Collapsed view shows result count with first result title and expand hint&lt;/li&gt;
&lt;li&gt;Expanded view shows each result with title, URL, published date, and a 5-line blockquote snippet rendered as Markdown&lt;/li&gt;
&lt;li&gt;Error handling uses throw instead of returning error details, matching the pi framework convention&lt;/li&gt;
&lt;li&gt;Errors now display the actual error message instead of misleading “no results”&lt;/li&gt;
&lt;li&gt;Footer shows result count only (no redundant “failed: no”)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>preview-markdown: v0.5.0</title><link>https://new.aliou.me/releases/preview-markdown-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/preview-markdown-v0-5-0/</guid><pubDate>Tue, 17 Mar 2026 16:17:17 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.5.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/preview-markdown/pull/6&quot;&gt;https://github.com/aliou/preview-markdown/pull/6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/preview-markdown/compare/v0.4.1...v0.5.0&quot;&gt;https://github.com/aliou/preview-markdown/compare/v0.4.1…v0.5.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Linear&apos;s agent interaction guidelines in Pi</title><link>https://new.aliou.me/tils/linear-agent-guidelines-pi/</link><guid isPermaLink="true">https://new.aliou.me/tils/linear-agent-guidelines-pi/</guid><pubDate>Mon, 16 Mar 2026 15:52:24 GMT</pubDate><content:encoded>&lt;p&gt;Finally got around to reading &lt;a href=&quot;https://linear.app/developers/aig&quot;&gt;Linear’s Agent interaction guidelines&lt;/a&gt; and did a quick implementation with Pi. It was pretty simple to set up and I kinda like the UX!&lt;/p&gt;
&lt;p&gt;Take a look at &lt;a href=&quot;https://github.com/aliou/pi-undercooked/tree/0701190315c442927b453515a4ca80334e79bac8/integrations/linear&quot;&gt;the code on GitHub&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>fastmail-cli: v0.2.0</title><link>https://new.aliou.me/releases/fastmail-cli-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/fastmail-cli-v0-2-0/</guid><pubDate>Sun, 15 Mar 2026 16:17:15 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.2.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/fastmail-cli/pull/3&quot;&gt;https://github.com/aliou/fastmail-cli/pull/3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/fastmail-cli/compare/v0.1.1...v0.2.0&quot;&gt;https://github.com/aliou/fastmail-cli/compare/v0.1.1…v0.2.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.7.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-7-0/</guid><pubDate>Thu, 12 Mar 2026 20:38:36 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;4547220: Add NVIDIA Nemotron-3-Super-120B-A12B-NVFP4 model&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;018f25d: Fix Qwen3.5-397B-A17B output pricing (3 -&gt; 3.6 per million tokens)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Restoring Ghostty tabs and splits</title><link>https://new.aliou.me/tils/ghostty-tabs-restore/</link><guid isPermaLink="true">https://new.aliou.me/tils/ghostty-tabs-restore/</guid><pubDate>Thu, 12 Mar 2026 15:48:58 GMT</pubDate><content:encoded>&lt;p&gt;Had to update Ghostty to fix that annoying auto-scroll bug but didn’t want to lose my open tabs. Asked Pi/GPT-5.4 to dump and restore my tabs and splits. It mostly worked and I’m never going to reuse this.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>pi-toolchain: v0.5.0</title><link>https://new.aliou.me/releases/pi-toolchain-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-toolchain-v0-5-0/</guid><pubDate>Wed, 11 Mar 2026 20:38:36 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;520acf2: Add optional warning-level notifications for rewritten commands so demos and debugging make toolchain rewrites visible.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-toolchain: v0.4.0</title><link>https://new.aliou.me/releases/pi-toolchain-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-toolchain-v0-4-0/</guid><pubDate>Tue, 10 Mar 2026 20:38:36 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;346fee2: Feature modes: rewriter features now support &lt;code&gt;&quot;disabled&quot;&lt;/code&gt; | &lt;code&gt;&quot;rewrite&quot;&lt;/code&gt; | &lt;code&gt;&quot;block&quot;&lt;/code&gt; instead of booleans. In &lt;code&gt;&quot;block&quot;&lt;/code&gt; mode the bash tool is not overridden — commands are blocked via tool_call hook instead. Config is auto-migrated on first load. &lt;code&gt;preventBrew&lt;/code&gt; and &lt;code&gt;preventDockerSecrets&lt;/code&gt; have been removed; use @aliou/pi-guardrails instead.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>linear-cli: v0.1.0</title><link>https://new.aliou.me/releases/linear-cli-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/linear-cli-v0-1-0/</guid><pubDate>Tue, 10 Mar 2026 16:17:14 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/linear-cli/commits/v0.1.0&quot;&gt;https://github.com/aliou/linear-cli/commits/v0.1.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.10.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-10-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-10-0/</guid><pubDate>Fri, 06 Mar 2026 20:38:36 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;494cebe: fix: local scope no longer resolves to ~/.pi, creates .pi/extensions/ in cwd when missing&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;findLocalConfigPath&lt;/code&gt; now stops before $HOME so &lt;code&gt;~/.pi&lt;/code&gt; is never matched as project-local&lt;/li&gt;
&lt;li&gt;&lt;code&gt;save(&quot;local&quot;)&lt;/code&gt; falls back to &lt;code&gt;{cwd}/.pi/extensions/{name}.json&lt;/code&gt; when no &lt;code&gt;.pi&lt;/code&gt; dir exists in the tree&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sesame: v0.8.0</title><link>https://new.aliou.me/releases/sesame-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-8-0/</guid><pubDate>Fri, 06 Mar 2026 20:38:36 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;9ef404e: allow searching &quot;&quot; to list sessions&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.9.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-9-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-9-0/</guid><pubDate>Thu, 05 Mar 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1006f56: Add JSON Schema support: &lt;code&gt;buildSchemaUrl&lt;/code&gt; helper and &lt;code&gt;schemaUrl&lt;/code&gt; option for ConfigLoader. When set, &lt;code&gt;save()&lt;/code&gt; injects &lt;code&gt;$schema&lt;/code&gt; as the first key and &lt;code&gt;load()&lt;/code&gt; strips it from parsed config.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-guardrails: v0.9.0</title><link>https://new.aliou.me/releases/pi-guardrails-v0-9-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-guardrails-v0-9-0/</guid><pubDate>Wed, 04 Mar 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;78f640d: Improve settings UX with guided policy creation and top-level examples tab.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a real wizard flow for creating a new policy in settings (name, protection, patterns, review), then open the policy editor.&lt;/li&gt;
&lt;li&gt;Move policy examples into a dedicated top-level &lt;code&gt;Examples&lt;/code&gt; tab using &lt;code&gt;extraTabs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ask target scope each time an example is applied; do not persist last selected scope.&lt;/li&gt;
&lt;li&gt;Upgrade &lt;code&gt;@aliou/pi-utils-settings&lt;/code&gt; to &lt;code&gt;^0.8.0&lt;/code&gt; to use &lt;code&gt;extraTabs&lt;/code&gt; and combined settings theme support.&lt;/li&gt;
&lt;li&gt;Keep pattern editor compact while preserving &lt;code&gt;Ctrl+R&lt;/code&gt; regex toggle in form mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-guardrails: v0.8.0</title><link>https://new.aliou.me/releases/pi-guardrails-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-guardrails-v0-8-0/</guid><pubDate>Wed, 04 Mar 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;e8eea2f: Redesign file protection from legacy &lt;code&gt;envFiles&lt;/code&gt; to a new &lt;code&gt;policies&lt;/code&gt; system with per-rule protection levels (&lt;code&gt;noAccess&lt;/code&gt;, &lt;code&gt;readOnly&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt;), add migration from old config fields, and replace the old env hook with a general policies hook.&lt;/li&gt;
&lt;li&gt;e762afc: Add opt-in LLM command explanations to the permission gate dialog with configurable model and timeout settings, plus graceful fallback when model resolution or explanation calls fail.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;e4a8438: Update docs and migration semantics for config schema versioning. Bump &lt;code&gt;@aliou/pi-utils-settings&lt;/code&gt; to latest &lt;code&gt;0.5.x&lt;/code&gt;, clarify fallback behavior in README/AGENTS, ignore &lt;code&gt;.pi/settings.json&lt;/code&gt;, and ensure migrated configs write the current schema version without lexicographic version comparisons.&lt;/li&gt;
&lt;li&gt;d9f91cd: Harden permission-gate command explanation prompt handling, fix dangerous-pattern matching flow after successful AST parses, and improve policy enforcement by skipping empty rules and resolving onlyIfExists checks relative to session cwd. Also refresh README/AGENTS docs for the policies-based architecture.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-processes: v0.6.0</title><link>https://new.aliou.me/releases/pi-processes-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-processes-v0-6-0/</guid><pubDate>Wed, 04 Mar 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;67da7e3: Add &lt;code&gt;/ps:dock&lt;/code&gt;, &lt;code&gt;/ps:focus&lt;/code&gt;, &lt;code&gt;/ps:logs&lt;/code&gt; commands. Add deprecated &lt;code&gt;/process:*&lt;/code&gt; commands. Replace status widget with log dock. Preserve ANSI colors. Fix duplicate notifications. Use proper ThemeColor type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8cd4247: Exclude local implementation plan documents from version control.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;47bd895: Split widget hook into focused modules for types, status rendering, and setup.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;905a499: Add &lt;code&gt;write&lt;/code&gt; action to write to process stdin&lt;/p&gt;
&lt;p&gt;The process tool now supports writing to a running process’s stdin:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;process action=write id=proc_1 input=&quot;hello\n&quot;&lt;/code&gt; - write data to stdin&lt;/li&gt;
&lt;li&gt;&lt;code&gt;process action=write id=proc_1 input=&quot;quit\n&quot; end=true&lt;/code&gt; - write and close stdin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Useful for interactive programs, testing RPC mode, and any scenario requiring input to be sent to a background process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;265d8ff: Reorganize process commands into per-command directories and split settings command internals.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;9fa2188: Restore bottom border line on log overlay&lt;/li&gt;
&lt;li&gt;dbcd3d1: Split commands into separate files for better organization&lt;/li&gt;
&lt;li&gt;d0814e6: Improve tool result display when collapsed: show last 2 output lines, first 3 processes with status. Remove redundant action/status footer on success.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-ts-aperture: v0.3.0</title><link>https://new.aliou.me/releases/pi-ts-aperture-v0-3-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-ts-aperture-v0-3-0/</guid><pubDate>Wed, 04 Mar 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;dffb404: Refactor the Aperture routing implementation into focused modules and improve startup model discovery.&lt;/p&gt;
&lt;h3 id=&quot;what-changed&quot;&gt;What changed&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Split the previous large &lt;code&gt;src/index.ts&lt;/code&gt; into a clearer architecture:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/providers/aperture.ts&lt;/code&gt; for routing/bootstrap/model refresh logic&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/providers/model-config.ts&lt;/code&gt; for model synthesis/merge helpers&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/lib/aperture-api.ts&lt;/code&gt; for Aperture API discovery calls&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/state/provider-model-cache.ts&lt;/code&gt; for in-memory model cache state&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;src/index.ts&lt;/code&gt; as orchestration only (load config, register hooks/commands).&lt;/li&gt;
&lt;li&gt;Preserve and explicitly inject provenance headers when routing through Aperture:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Referer: https://pi.dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-Title: npm:@aliou/pi-ts-aperture&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fix active model refresh timing by awaiting model re-resolution before request execution.&lt;/li&gt;
&lt;li&gt;Improve OpenRouter CLI model selection reliability by bootstrapping discovered models from Aperture when needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-minor&quot;&gt;Why minor&lt;/h3&gt;
&lt;p&gt;This release introduces observable behavior improvements (model availability/routing reliability and header behavior) in addition to internal refactoring.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-utils-settings: v0.8.0</title><link>https://new.aliou.me/releases/pi-utils-settings-v0-8-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-utils-settings-v0-8-0/</guid><pubDate>Wed, 04 Mar 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;b404f50: Add optional &lt;code&gt;extraTabs&lt;/code&gt; support to &lt;code&gt;registerSettingsCommand&lt;/code&gt; so extensions can render non-scope top-level tabs (for example, an &lt;code&gt;Examples&lt;/code&gt; tab) after scope tabs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;ExtraSettingsTab&lt;/code&gt; and &lt;code&gt;ExtraSettingsTabContext&lt;/code&gt; types and export them from package root.&lt;/li&gt;
&lt;li&gt;Keep existing scope-tab &lt;code&gt;buildSections&lt;/code&gt; and save semantics unchanged.&lt;/li&gt;
&lt;li&gt;Make tab switching cycle across scope + extra tabs.&lt;/li&gt;
&lt;li&gt;Validate tab id collisions with reserved scope ids.&lt;/li&gt;
&lt;li&gt;Update README and skill/reference docs with &lt;code&gt;extraTabs&lt;/code&gt; examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;29a909d: Add Wizard-safe settings theme support by introducing a combined &lt;code&gt;SettingsTheme&lt;/code&gt; that works as both &lt;code&gt;SettingsListTheme&lt;/code&gt; and full pi &lt;code&gt;Theme&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add and export &lt;code&gt;SettingsTheme&lt;/code&gt; (&lt;code&gt;SettingsListTheme &amp;#x26; Theme&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Add and export &lt;code&gt;getSettingsTheme(theme)&lt;/code&gt; helper to build a combined theme object.&lt;/li&gt;
&lt;li&gt;Extend &lt;code&gt;registerSettingsCommand&lt;/code&gt; &lt;code&gt;buildSections&lt;/code&gt; ctx with &lt;code&gt;theme: SettingsTheme&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Extend &lt;code&gt;ExtraSettingsTabContext&lt;/code&gt; with &lt;code&gt;theme: SettingsTheme&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep existing &lt;code&gt;getSettingsListTheme()&lt;/code&gt; consumers and existing callbacks backward-compatible.&lt;/li&gt;
&lt;li&gt;Update README and example reference to show &lt;code&gt;ctx.theme&lt;/code&gt; usage for both settings list and full Theme methods.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-harness: @aliou/pi-utils-settings@0.7.0</title><link>https://new.aliou.me/releases/pi-harness-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-harness-v0-7-0/</guid><pubDate>Mon, 02 Mar 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a3039d9: Add a reusable &lt;code&gt;SettingsDetailEditor&lt;/code&gt; component for focused second-level settings editing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support typed detail fields: text, enum, boolean, nested submenu, and destructive action with confirmation&lt;/li&gt;
&lt;li&gt;Add keyboard UX for detail panels (&lt;code&gt;↑/↓&lt;/code&gt; or &lt;code&gt;j/k&lt;/code&gt;, &lt;code&gt;Enter&lt;/code&gt;, &lt;code&gt;Esc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Show selected field descriptions and keep nested submenu handoff/return clean&lt;/li&gt;
&lt;li&gt;Add tests for navigation and field callback behavior&lt;/li&gt;
&lt;li&gt;Update docs with guidance on &lt;code&gt;SectionedSettings&lt;/code&gt; vs &lt;code&gt;SectionedSettings + SettingsDetailEditor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Extend reference example with an array-of-objects pattern (&lt;code&gt;profiles&lt;/code&gt;) using nested detail editors&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sesame: v0.7.0</title><link>https://new.aliou.me/releases/sesame-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-7-0/</guid><pubDate>Sat, 28 Feb 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;54d0fca: Migrate from Bun to Node 25 + tsdown SEA for binary builds.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;bun:sqlite&lt;/code&gt; with &lt;code&gt;node:sqlite&lt;/code&gt; as the sole SQLite backend.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;bun build --compile&lt;/code&gt; with tsdown exe (Node SEA) for standalone binaries.&lt;/li&gt;
&lt;li&gt;Migrate CLI tests from &lt;code&gt;bun:test&lt;/code&gt; to vitest.&lt;/li&gt;
&lt;li&gt;Bump minimum Node version to &lt;code&gt;&gt;=25.0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;CI builds binaries for linux-x64, linux-arm64, and darwin-arm64 via matrix strategy.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;cc89035: Fix legacy SQLite migration order so tree indexes are created after schema migrations.&lt;/p&gt;
&lt;p&gt;This prevents &lt;code&gt;sesame status&lt;/code&gt; from failing on existing databases with &lt;code&gt;no such column: parent_session_id&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.6.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-6-0/</guid><pubDate>Wed, 25 Feb 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;628616b: Update model configurations and add automated API validation tests&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fixed &lt;code&gt;GLM-4.7&lt;/code&gt; maxTokens from 64000 to 65536&lt;/li&gt;
&lt;li&gt;Fixed &lt;code&gt;MiniMax-M2.5&lt;/code&gt; input modalities from [“text”,“image”] to [“text”]&lt;/li&gt;
&lt;li&gt;Updated pricing for &lt;code&gt;MiniMax-M2.1&lt;/code&gt;, &lt;code&gt;Kimi-K2.5&lt;/code&gt;, and &lt;code&gt;Qwen3-Coder-480B-A35B&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;maxTokens&lt;/code&gt; and &lt;code&gt;reasoning&lt;/code&gt; field validation test&lt;/li&gt;
&lt;li&gt;Added vitest for testing with &lt;code&gt;pnpm test&lt;/code&gt; and &lt;code&gt;pnpm test:watch&lt;/code&gt; scripts&lt;/li&gt;
&lt;li&gt;Added test step to pre-commit hook&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;3f41a60: Add identification headers to API requests&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;Referer: https://pi.dev&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;X-Title: npm:@aliou/pi-synthetic&lt;/code&gt; header&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sesame: v0.6.0</title><link>https://new.aliou.me/releases/sesame-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-6-0/</guid><pubDate>Wed, 25 Feb 2026 20:38:28 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;5e1475e: Add support for session forks and custom_message entries&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parse and index &lt;code&gt;custom_message&lt;/code&gt; entries (extension-injected LLM context)&lt;/li&gt;
&lt;li&gt;Track session fork relationships via &lt;code&gt;parent_session_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Track entry tree structure via &lt;code&gt;entry_id&lt;/code&gt;, &lt;code&gt;parent_entry_id&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;source_type&lt;/code&gt; on chunks&lt;/li&gt;
&lt;li&gt;Add database migration for new columns&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.5.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-5-0/</guid><pubDate>Tue, 24 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;9faaa42: Add pi-sub integration via sub-core events&lt;/li&gt;
&lt;li&gt;eee2c68: Redesign quotas display with tabbed interface and pace tracking&lt;/li&gt;
&lt;li&gt;562cbf7: Add Qwen3.5-397B-A17B model to the available models list&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;b29fe7c: Return JSON in RPC mode instead of plain text&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>preview-markdown: v0.4.0</title><link>https://new.aliou.me/releases/preview-markdown-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/preview-markdown-v0-4-0/</guid><pubDate>Sun, 22 Feb 2026 16:17:17 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.4.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/preview-markdown/pull/3&quot;&gt;https://github.com/aliou/preview-markdown/pull/3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/preview-markdown/compare/v0.3.0...v0.4.0&quot;&gt;https://github.com/aliou/preview-markdown/compare/v0.3.0…v0.4.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-processes: v0.5.0</title><link>https://new.aliou.me/releases/pi-processes-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-processes-v0-5-0/</guid><pubDate>Sat, 21 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;da665cd: Add opt-in blocker for background bash commands: when enabled, &lt;code&gt;bash&lt;/code&gt; tool calls that would spawn a background process (&lt;code&gt;&amp;#x26;&lt;/code&gt;) are held for approval before execution.&lt;/p&gt;
&lt;p&gt;Fix process list column truncation on narrow terminals. Move &lt;code&gt;@mariozechner/pi-tui&lt;/code&gt; to peer dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;3ccf461: Fix TUI crash when rendered lines exceed terminal width. Add width guards to widget status line, process list panel, and process picker component.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-ts-aperture: v0.2.0</title><link>https://new.aliou.me/releases/pi-ts-aperture-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-ts-aperture-v0-2-0/</guid><pubDate>Thu, 19 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;926f0a9: Improve &lt;code&gt;/aperture:setup&lt;/code&gt; provider and connectivity flow.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add URL health check during setup (&lt;code&gt;/v1/models&lt;/code&gt;) before provider selection, with retry/cancel UX.&lt;/li&gt;
&lt;li&gt;Build provider choices from Pi’s runtime model registry so extension-registered providers (for example &lt;code&gt;pi-synthetic&lt;/code&gt;) appear in the setup list.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2263fc2: mark pi SDK peer deps as optional to prevent koffi OOM in Gondolin VMs&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-harness: @aliou/pi-utils-settings@0.4.0</title><link>https://new.aliou.me/releases/pi-harness-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-harness-v0-4-0/</guid><pubDate>Tue, 17 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;7df01a2: Pass &lt;code&gt;ExtensionCommandContext&lt;/code&gt; to &lt;code&gt;onSave&lt;/code&gt; callback in settings command options&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-ts-aperture: v0.1.0</title><link>https://new.aliou.me/releases/pi-ts-aperture-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-ts-aperture-v0-1-0/</guid><pubDate>Tue, 17 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ebb9556: Initial release. Route Pi LLM providers through Tailscale Aperture.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/aperture:setup&lt;/code&gt; interactive wizard (base URL + provider multi-select)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/aperture:settings&lt;/code&gt; settings UI for updating configuration&lt;/li&gt;
&lt;li&gt;Auto-registers selected providers with Aperture base URL on load&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;7388139: Fix providers not taking effect immediately after setup/settings save. Register directly on modelRegistry and re-resolve the active model when it belongs to a reconfigured provider.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sesame: v0.5.0</title><link>https://new.aliou.me/releases/sesame-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-5-0/</guid><pubDate>Sat, 14 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;54b7450: Add schema_migrations table for lightweight DB migrations. Replaces inline ALTER TABLE with a tracked migration system using sequential migration files.&lt;/li&gt;
&lt;li&gt;ca7cb24: Add tool call success/failure status to index and search. New &lt;code&gt;is_error&lt;/code&gt; column on chunks, &lt;code&gt;status&lt;/code&gt; search filter, and &lt;code&gt;toolName&lt;/code&gt;/&lt;code&gt;toolsOnly&lt;/code&gt; now work with wildcard queries.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-linkup: v0.7.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-7-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-7-0/</guid><pubDate>Thu, 12 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;675ab1d: Export tool definitions as a library via &lt;code&gt;@aliou/pi-linkup/tools&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sesame: v0.4.0</title><link>https://new.aliou.me/releases/sesame-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-4-0/</guid><pubDate>Thu, 12 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;9b03f3c: Support &lt;code&gt;*&lt;/code&gt; query to list all sessions with filters. When searching with &lt;code&gt;*&lt;/code&gt;, returns all sessions ordered by modification date (newest first), respecting cwd/after/before/limit filters. Default limit of 10 ensures context safety.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-harness: @aliou/pi-utils-settings@0.3.0</title><link>https://new.aliou.me/releases/pi-harness-v0-3-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-harness-v0-3-0/</guid><pubDate>Wed, 11 Feb 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;756552a: Add FuzzySelector component for picking one item from a large list using fuzzy search. Refresh sections after cycling value changes so dependent settings update immediately.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>When Opus thought it was on Linux</title><link>https://new.aliou.me/tils/opus-linux-macos-tmux/</link><guid isPermaLink="true">https://new.aliou.me/tils/opus-linux-macos-tmux/</guid><pubDate>Wed, 11 Feb 2026 15:48:58 GMT</pubDate><content:encoded>&lt;p&gt;Got absolutely screwed by Opus thinking it was running on Linux instead of macOS while debugging some CLI stuff with tmux.&lt;/p&gt;
&lt;p&gt;But the session of Opus figuring out what happened is pretty fascinating:&lt;/p&gt;
&lt;p&gt;Also created a small tool that uses BM25 to find content in previous sessions. It’s super useful to just say “I was running a Pi session at the same time, could it be related?” This is completely borrowed from &lt;a href=&quot;https://ampcode.com/news/find-threads&quot;&gt;Amp&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>Why I Use Pi</title><link>https://new.aliou.me/posts/why-i-use-pi/</link><guid isPermaLink="true">https://new.aliou.me/posts/why-i-use-pi/</guid><pubDate>Wed, 11 Feb 2026 15:17:00 GMT</pubDate><content:encoded>&lt;p&gt;I randomly read Mario Zechner’s &lt;a href=&quot;https://mariozechner.at/posts/2025-11-30-pi-coding-agent/?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;article about what he learned building his own coding agent&lt;/a&gt;, which inspired me to try it. After a few days I made some small contributions, and within that first week I moved on from Claude Code to using Pi entirely.&lt;/p&gt;
&lt;p&gt;Pi is also one of the &lt;a href=&quot;https://x.com/hjanuschka/status/2009361876736442724&quot;&gt;building blocks&lt;/a&gt; of OpenClaw, which you’ve probably seen going around the past few weeks.&lt;/p&gt;
&lt;p&gt;I mentioned to some friends that I had been contributing to the Pi repo and some asked why I started using it and whether they should too. Here’s why I use it, which might help answer that.&lt;/p&gt;
&lt;h2 id=&quot;a-tool-that-works-for-me-first&quot;&gt;A tool that works for me first&lt;/h2&gt;
&lt;p&gt;One of the first reasons is that Pi is a tool that is easier to make work for me, the same way I’ve made Neovim work for me for the past 15 years.&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-3-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-3-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;In other words, &lt;a href=&quot;https://www.generativist.com/notes/2026/Feb/10/pi-is-vim?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;Pi is Vim&lt;/a&gt;. &lt;/span&gt;&lt;/span&gt; One thing that always bothered me with the different agents I’ve tried, whether it’s Claude Code or Amp, is that they’re either catered towards everyone or highly opinionated. That completely makes sense business-wise, but I always found myself trying to adapt to them instead of the tools adapting to the way I work. It felt somewhat backwards in a world where AI lets you create any tool you need that completely works for you. Why shouldn’t that extend to the tools you use to create those tools?&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-2&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 2&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-2&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 2&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;Claude Code has a &lt;a href=&quot;https://code.claude.com/docs/en/plugins?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;plugin system&lt;/a&gt; and you can set up marketplaces to package different plugins. I still felt limited. I also hear Amp is &lt;a href=&quot;https://x.com/sqs/status/2016380743854051473&quot;&gt;working on a plugin system&lt;/a&gt; I haven’t tried but will probably take a look at when it comes out. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;easily-extensible&quot;&gt;Easily extensible&lt;/h2&gt;
&lt;p&gt;Pi by itself is very tiny but very complete. It has everything needed for a coding agent and you can simply use it without any changes. On top of that, you can create extensions that register custom tools for the agent to use or custom commands for you to use. What makes this even more powerful is that Pi knows it is extensible and knows where to find its own documentation because its system prompt references it. What ends up happening is that while you’re working, you can ask the agent “this was a difficult workflow, let’s abstract this into an extension we can reuse across projects.”&lt;/p&gt;
&lt;p&gt;For example, I’ve needed multiple times to have the agent run a process in the background and continue working, whether it’s TDD with tests running on a loop while it implements features (&lt;a href=&quot;https://assets.aliou.me/blog/why-i-use-pi/pi-session-tdd.html?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;session&lt;/a&gt;) or having the agent run a server and generate an OpenAPI client from it as it iterates (&lt;a href=&quot;https://assets.aliou.me/blog/why-i-use-pi/pi-session-openapi.html?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;session&lt;/a&gt;, &lt;a href=&quot;https://assets.aliou.me/blog/why-i-use-pi/pi-session-openapi.mp4?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;video&lt;/a&gt;). Before, I would just watch the agent struggle: either it wouldn’t try to start the server or it would try and stall because the server wasn’t running in the background. To avoid this, I created my own &lt;a href=&quot;https://new.aliou.me/projects/pi-processes/&quot;&gt;extension&lt;/a&gt; that runs processes in the background and that the agent knows about.&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-2-3&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 3&quot;&gt;&lt;label for=&quot;sn-user-content-fn-2-3&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 3&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;This is not a novel idea; Claude Code has &lt;a href=&quot;https://code.claude.com/docs/en/interactive-mode#background-bash-commands?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;background bash commands&lt;/a&gt; as well. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-trade-offs&quot;&gt;The trade-offs&lt;/h2&gt;
&lt;p&gt;With all of this comes some trade-offs. You are building your own harness, which means you cannot delegate keeping up with models and tools to someone else. With Claude Code, the team building the models is also building the harness. With Amp, the team &lt;a href=&quot;https://ampcode.com/news/model-evaluation?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;thoroughly tests how new models can improve their product&lt;/a&gt;. With Pi, you figure out which model provides what you need to do your work.&lt;/p&gt;
&lt;p&gt;Pi is open source, and every time I’ve seen a bug or something that doesn’t work as expected or a small addition that could improve the tool, I open an issue or a pull request (e.g., &lt;a href=&quot;https://github.com/badlogic/pi-mono/pull/267?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;allowing the tool to be put in the background&lt;/a&gt;, &lt;a href=&quot;https://github.com/badlogic/pi-mono/pull/1271?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;fixing image forwarding during streaming&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;My extensions are also my responsibility. They can break for different reasons, and if I want to improve them, that’s on me too. (&lt;a href=&quot;https://github.com/aliou/pi-extensions?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;My extensions&lt;/a&gt;.)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Pi is &lt;a href=&quot;https://github.com/badlogic/pi-mono?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;open source&lt;/a&gt; if you want to look at it. Mario Zechner wrote about &lt;a href=&quot;https://mariozechner.at/posts/2025-11-30-pi-coding-agent/?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;why he built it&lt;/a&gt; and Armin Ronacher wrote about &lt;a href=&quot;https://lucumr.pocoo.org/2026/1/31/pi/?utm_source=aliou.me&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=why-i-use-pi&quot;&gt;why he uses it&lt;/a&gt;. Both are worth reading.&lt;/p&gt;
</content:encoded><category>post</category></item><item><title>pi-linkup: v0.6.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-6-0/</guid><pubDate>Tue, 10 Feb 2026 20:38:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;e9fdef1: Replace &lt;code&gt;deep&lt;/code&gt; boolean with &lt;code&gt;depth&lt;/code&gt; enum on web-answer tool for consistency with web-search&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-linkup: v0.4.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-4-0/</guid><pubDate>Tue, 10 Feb 2026 20:38:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;052fc2b: Add fast mode support to linkup_web_search tool. The depth parameter now accepts “fast”, “standard”, or “deep” modes:
&lt;ul&gt;
&lt;li&gt;fast: Sub-second latency using pre-indexed atoms of information&lt;/li&gt;
&lt;li&gt;standard: Single iteration retrieval, balanced speed/depth (default)&lt;/li&gt;
&lt;li&gt;deep: Up to 10 iterations with chain-of-thought reasoning&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;a238ba7: Add User-Agent header to all Linkup API requests for attribution.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sesame: v0.2.0</title><link>https://new.aliou.me/releases/sesame-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-2-0/</guid><pubDate>Tue, 10 Feb 2026 20:38:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;8beab04: Migrate runtime from Bun APIs to Node.js APIs and switch project workflows to pnpm while keeping Bun only for binary builds.&lt;/p&gt;
&lt;p&gt;Key updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use &lt;code&gt;node:sqlite&lt;/code&gt; instead of &lt;code&gt;bun:sqlite&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;replace Bun file APIs with Node fs APIs&lt;/li&gt;
&lt;li&gt;run tests with Vitest&lt;/li&gt;
&lt;li&gt;use pnpm lockfile/workflows in CI and release pipelines&lt;/li&gt;
&lt;li&gt;keep &lt;code&gt;build:binary&lt;/code&gt; on Bun for compiled binaries&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;patch-changes&quot;&gt;Patch Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2c99cb6: Enable TypeScript type emission by removing &lt;code&gt;allowImportingTsExtensions&lt;/code&gt; and setting &lt;code&gt;noEmit: false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sesame: v0.3.0</title><link>https://new.aliou.me/releases/sesame-v0-3-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-3-0/</guid><pubDate>Tue, 10 Feb 2026 16:17:13 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Updating @aliou/sesame to version 0.3.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/sesame/pull/6&quot;&gt;https://github.com/aliou/sesame/pull/6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/sesame/compare/v0.2.0...v0.3.0&quot;&gt;https://github.com/aliou/sesame/compare/v0.2.0…v0.3.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>preview-markdown: v0.3.0</title><link>https://new.aliou.me/releases/preview-markdown-v0-3-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/preview-markdown-v0-3-0/</guid><pubDate>Mon, 09 Feb 2026 16:17:17 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.3.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/preview-markdown/pull/2&quot;&gt;https://github.com/aliou/preview-markdown/pull/2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/preview-markdown/compare/v0.2.0...v0.3.0&quot;&gt;https://github.com/aliou/preview-markdown/compare/v0.2.0…v0.3.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>sesame: v0.1.0</title><link>https://new.aliou.me/releases/sesame-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sesame-v0-1-0/</guid><pubDate>Mon, 09 Feb 2026 16:17:13 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.1.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/sesame/pull/1&quot;&gt;https://github.com/aliou/sesame/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;new-contributors&quot;&gt;New Contributors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;@github-actions[bot] made their first contribution in &lt;a href=&quot;https://github.com/aliou/sesame/pull/1&quot;&gt;https://github.com/aliou/sesame/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/sesame/commits/v0.1.0&quot;&gt;https://github.com/aliou/sesame/commits/v0.1.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>pi-harness: @aliou/pi-toolchain@0.2.0</title><link>https://new.aliou.me/releases/pi-harness-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-harness-v0-2-0/</guid><pubDate>Thu, 05 Feb 2026 20:38:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;7a3f659: Add memory scope for ephemeral settings overrides&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-harness: @aliou/pi-guardrails@0.6.0</title><link>https://new.aliou.me/releases/pi-harness-v0-6-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-harness-v0-6-0/</guid><pubDate>Wed, 04 Feb 2026 20:38:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;29b61a5: Remove toolchain features (preventBrew, preventPython, enforcePackageManager) — moved to @aliou/pi-toolchain. Replace custom config loader and settings UI with @aliou/pi-utils-settings.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-harness: @aliou/pi-toolchain@0.1.0</title><link>https://new.aliou.me/releases/pi-harness-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-harness-v0-1-0/</guid><pubDate>Wed, 04 Feb 2026 20:38:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;29b61a5: Initial release: package manager rewriting, git rebase rewriting, python rewriting, and brew blocking via spawn hooks. Extracted from @aliou/pi-guardrails.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>sh: v0.1.0</title><link>https://new.aliou.me/releases/sh-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/sh-v0-1-0/</guid><pubDate>Wed, 04 Feb 2026 20:38:17 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;0f1209b: Initial release.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-linkup: v0.3.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-3-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-3-0/</guid><pubDate>Tue, 03 Feb 2026 20:38:41 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;e57f6fe: Enhanced balance command to show remaining requests per operation type (standard search, deep search, fetch with/without JS) using a themed custom message renderer instead of a transient notification.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-linkup: v0.2.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-2-0/</guid><pubDate>Tue, 03 Feb 2026 20:38:38 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;2307674: Update pi packages to 0.51.0. Adapt tool execute signatures to new parameter order.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Coding Agents at Team Scale</title><link>https://new.aliou.me/posts/coding-agents-team-scale/</link><guid isPermaLink="true">https://new.aliou.me/posts/coding-agents-team-scale/</guid><pubDate>Tue, 03 Feb 2026 16:10:32 GMT</pubDate><content:encoded>&lt;p&gt;The previous posts in this series covered &lt;a href=&quot;https://new.aliou.me/posts/agents-md-can-be-shorter-now/&quot;&gt;keeping your AGENTS.md lean&lt;/a&gt;, &lt;a href=&quot;https://new.aliou.me/posts/skills-for-domain-knowledge/&quot;&gt;loading domain knowledge with Skills&lt;/a&gt;, and &lt;a href=&quot;https://new.aliou.me/posts/infrastructure-around-your-agent/&quot;&gt;using infrastructure to enforce rules&lt;/a&gt;. All of those assume one thing: that someone on the team is keeping those files up to date.&lt;/p&gt;
&lt;p&gt;Unfortunately, that’s not always the case. At the pace our industry moves, and as priorities shift within a team or company, it’s easy to forget to keep those files up to date. On a recent client project, the AGENTS.md referenced a library and one of its commands that had been deprecated weeks earlier (they had moved from Kysely to Drizzle). Every day, the agent kept running &lt;code&gt;kysely migrate latest&lt;/code&gt; instead of the new Drizzle equivalent. It would then read the &lt;code&gt;package.json&lt;/code&gt;, notice the error, and correct itself — so developers wouldn’t see the issue immediately and never thought to update the AGENTS.md. The file wasn’t particularly long, but it was already stale.&lt;/p&gt;
&lt;p&gt;Context rots as the code and priorities change. That’s one of the problems at team scale, and this post is about the unglamorous, less talked about part that determines whether your agent setup actually works over time: maintenance.&lt;/p&gt;
&lt;h2 id=&quot;the-drift-problem&quot;&gt;The drift problem&lt;/h2&gt;
&lt;p&gt;Your AGENTS.md is probably already out of date and it’s not particularly your fault. The codebase just moved or someone renamed a package or a new service got added or the schema changed.&lt;/p&gt;
&lt;p&gt;Agents make decisions based on what they read and the first thing they read is that file. If it’s stale, the decisions are stale too and the cost compounds over weeks. Every outdated instruction is a wrong turn that humans need to correct or additional inference costs.&lt;/p&gt;
&lt;p&gt;Eventually you stop trusting the agent, you stop maintaining the file and the cycle goes on.&lt;/p&gt;
&lt;h2 id=&quot;ownership&quot;&gt;Ownership&lt;/h2&gt;
&lt;p&gt;The fix starts with someone specific owning the AGENTS.md, the same way a team would own a package in a monorepo. In most smaller teams, this can end up being the tech lead or a senior developer. This doesn’t mean nobody else can change it, it means that this person makes sure it stays current and leverages their complete vision of the project and of the company/business.&lt;/p&gt;
&lt;p&gt;Over time, this can change as the codebase and the team grows. What they do is review changes, flag drift and push back when the file gets too bloated.&lt;/p&gt;
&lt;p&gt;To enforce this, make the boundary explicit. On GitHub, the best way is a CODEOWNERS file so that changes to any AGENTS.md request a review from the right people:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# CODEOWNERS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;**/AGENTS.md       @platform-team @tech-leads&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;.agents/skills/    @platform-team&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also add CI checks: e.g. if the &lt;code&gt;package.json&lt;/code&gt; file changes, the nearest AGENTS.md should be reviewed or confirmed as still accurate.&lt;/p&gt;
&lt;p&gt;You can also have a check on the length of the AGENTS.md. In one of my projects, this wasn’t a hard rule, but some necessary friction so that developers would ask themselves whether what they’re adding is relevant and if it is, whether it should be split into a child file or moved into tooling&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;As covered in &lt;a href=&quot;https://new.aliou.me/posts/infrastructure-around-your-agent/&quot;&gt;The Infrastructure Around Your AGENTS.md&lt;/a&gt;. &lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id=&quot;maintenance-strategies&quot;&gt;Maintenance strategies&lt;/h2&gt;
&lt;p&gt;However, ownership alone isn’t enough without processes behind it. Here are a few things that have worked for me.&lt;/p&gt;
&lt;h3 id=&quot;hooks-and-reminders&quot;&gt;Hooks and reminders&lt;/h3&gt;
&lt;p&gt;Set up hooks that prompt updates when key files change. They’re just nudges so that someone with enough context can actually make the changes. A CI annotation that says “key files changed, consider reviewing AGENTS.md” is sometimes more than enough:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# .github/workflows/agents-md-check.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;Check AGENTS.md freshness&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;  pull_request&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;    paths&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;package.json&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;packages/*/package.json&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;jobs&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;  check-freshness&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;    steps&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;uses&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;Warn if no AGENTS.md changed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;        run&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;          changed_files=&quot;$(git diff --name-only origin/${{ github.base_ref }}...HEAD)&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;          echo &quot;$changed_files&quot; | grep -Eq &apos;(^|/)AGENTS\.md$&apos; &amp;#x26;&amp;#x26; exit 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;          echo &quot;::warning::Key files changed but no AGENTS.md was updated. Consider reviewing agent context for drift.&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;regular-review-cadence&quot;&gt;Regular review cadence&lt;/h3&gt;
&lt;p&gt;Do it weekly or every other week, but not quarterly. Things are moving way too fast for that. You can pair it with something that already happens: dependency updates, sprint planning, sprint retrospectives or internal demos. Most of the time, five minutes is enough assuming you’re doing it regularly and consistently.&lt;/p&gt;
&lt;h3 id=&quot;llm-audits&quot;&gt;LLM audits&lt;/h3&gt;
&lt;p&gt;This is the one that has given me the best results: using an LLM and an agentic loop to audit your own AGENTS.md files against what the code actually looks like. What is pretty great is that it checks for actual staleness concretely. It looks in the AGENTS.md and checks whether the commands mentioned there still exist. It checks if the paths are still relevant and if new packages or services that are important are mentioned.&lt;/p&gt;
&lt;p&gt;The most realistic way to do this is to run the audit on a schedule and open an issue when drift is detected. It’s a maintenance signal, and we still leave the actual update to a person.&lt;/p&gt;
&lt;p&gt;The setup is pretty simple: a GitHub Action that runs on the main branch, gathers repository facts and sends them to a model with the right prompt. Here’s one of the prompts I’ve used as a starting point. For each team, you would iterate on it:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;Review all AGENTS.md files in this repository for drift and bloat.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;Definition of &quot;stale&quot; (use these checks):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Commands mentioned in AGENTS.md that do not exist in package.json / Makefile / task runner configs.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; File paths or package names mentioned that no longer exist (moved/renamed/deleted).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Stack/tool versions mentioned that conflict with version sources (lockfiles, .tool-versions, .nvmrc, Dockerfiles).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Missing coverage: new workspace packages or services that exist but are not referenced.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;For each AGENTS.md file:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; List concrete findings (quote the exact line and explain what it conflicts with).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Propose a minimal fix (1-3 bullet points). Prefer deleting or moving text over adding more.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Enforce brevity: if a file is &gt;50 lines, suggest what to cut or split into a child AGENTS.md.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For bigger teams, allocate budget for this the same way you would for faster CI runners. You can use cheaper and/or open source models. It’s a fairly simple task, but something that LLMs excel at.&lt;/p&gt;
&lt;h2 id=&quot;what-triggers-drift&quot;&gt;What triggers drift&lt;/h2&gt;
&lt;p&gt;Triggers usually come from small changes over time, not from big rewrites. It’s adding a new script, updating a package, removing a package or renaming a service. Here’s a table of what usually starts drift and which guardrail you could attach to avoid it&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 2&quot;&gt;&lt;label for=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 2&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;I’ll be honest: this table was extracted from coding agent sessions by an LLM, based on actual work I’ve been doing with different teams. &lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;What changed&lt;/th&gt;&lt;th&gt;Typical symptom&lt;/th&gt;&lt;th&gt;Guardrail&lt;/th&gt;&lt;th&gt;Who updates&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;New/changed scripts in &lt;code&gt;package.json&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Agent suggests commands that don’t exist&lt;/td&gt;&lt;td&gt;PR rule: if scripts change, touch the nearest AGENTS.md&lt;/td&gt;&lt;td&gt;Package owner&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Schema change (Prisma/Drizzle/migrations)&lt;/td&gt;&lt;td&gt;Agent generates migrations wrong, references renamed columns&lt;/td&gt;&lt;td&gt;CI nudge + PR checklist item&lt;/td&gt;&lt;td&gt;DB/service owner&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;New service or package added&lt;/td&gt;&lt;td&gt;Agent doesn’t know it exists&lt;/td&gt;&lt;td&gt;Root AGENTS.md has a services list; new package means updating it&lt;/td&gt;&lt;td&gt;Repo owner&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Deprecated endpoint or behavior&lt;/td&gt;&lt;td&gt;Agent keeps calling the old route&lt;/td&gt;&lt;td&gt;Deprecation note in AGENTS.md + failing contract test&lt;/td&gt;&lt;td&gt;API owner&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Renamed or moved module&lt;/td&gt;&lt;td&gt;Agent references old import paths&lt;/td&gt;&lt;td&gt;PR checklist item + optional CI grep for old paths&lt;/td&gt;&lt;td&gt;Package owner&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;The &lt;a href=&quot;https://new.aliou.me/posts/agents-md-can-be-shorter-now/&quot;&gt;first post&lt;/a&gt; was about keeping instructions lean. The &lt;a href=&quot;https://new.aliou.me/posts/skills-for-domain-knowledge/&quot;&gt;second&lt;/a&gt; was about loading context on demand. The &lt;a href=&quot;https://new.aliou.me/posts/infrastructure-around-your-agent/&quot;&gt;third&lt;/a&gt; was about enforcement through tools. This one is about making all of that work at team scale.&lt;/p&gt;
&lt;p&gt;This has been a series about the best way of working with coding agents as I’ve seen it in the last year of working with teams trying to increase their productivity with these new tools. Every team’s setup is of course different. If you want help figuring out what works for yours, feel free to &lt;a href=&quot;https://general-dexterity.com/contact/?utm_source=blog&amp;#x26;utm_medium=post&amp;#x26;utm_campaign=coding-agents-series&amp;#x26;utm_content=team-scale&quot;&gt;reach out&lt;/a&gt; or &lt;a href=&quot;https://general-dexterity.com/?utm_source=blog&amp;#x26;utm_medium=post&amp;#x26;utm_campaign=coding-agents-series&amp;#x26;utm_content=team-scale&quot;&gt;learn more about what I do&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>post</category></item><item><title>pi-synthetic: v0.4.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-4-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-4-0/</guid><pubDate>Mon, 02 Feb 2026 20:38:36 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;5cca252: Add &lt;code&gt;/synthetic:quotas&lt;/code&gt; command to display API usage quotas&lt;/p&gt;
&lt;p&gt;A new slash command that shows your Synthetic API subscription quotas in a rich terminal UI:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Visual usage bar with color-coded severity (green/yellow/red based on usage)&lt;/li&gt;
&lt;li&gt;Aligned columns showing limit, used, and remaining requests&lt;/li&gt;
&lt;li&gt;ISO8601 renewal timestamp with relative time formatting (e.g., “in 5 hours”)&lt;/li&gt;
&lt;li&gt;Closes on any key press&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command is only registered when &lt;code&gt;SYNTHETIC_API_KEY&lt;/code&gt; environment variable is set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a8cacfb: Add Synthetic web search tool&lt;/p&gt;
&lt;p&gt;New tool &lt;code&gt;synthetic_web_search&lt;/code&gt; allows agents to search the web using Synthetic’s zero-data-retention API. Returns search results with titles, URLs, content snippets, and publication dates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Search is a subscription-only feature. The tool will only be registered if the &lt;code&gt;SYNTHETIC_API_KEY&lt;/code&gt; belongs to an active subscription (verified via the usage endpoint).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.3.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-3-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-3-0/</guid><pubDate>Thu, 29 Jan 2026 20:38:33 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;5f67daf: Switch from Anthropic to OpenAI API endpoints&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change API endpoint from &lt;code&gt;/anthropic&lt;/code&gt; to &lt;code&gt;/openai/v1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Update from &lt;code&gt;anthropic-messages&lt;/code&gt; to &lt;code&gt;openai-completions&lt;/code&gt; API&lt;/li&gt;
&lt;li&gt;Add compatibility flags for proper role handling (&lt;code&gt;supportsDeveloperRole: false&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Use standard &lt;code&gt;max_tokens&lt;/code&gt; field instead of &lt;code&gt;max_completion_tokens&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.2.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-2-0/</guid><pubDate>Thu, 29 Jan 2026 20:38:31 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;58d21ca: Fix model configurations from Synthetic API&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update maxTokens for all Synthetic models using values from models.dev (synthetic provider)&lt;/li&gt;
&lt;li&gt;Fix Kimi-K2-Instruct-0905 reasoning flag to false&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-synthetic: v0.1.0</title><link>https://new.aliou.me/releases/pi-synthetic-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-synthetic-v0-1-0/</guid><pubDate>Thu, 29 Jan 2026 20:38:29 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;4a32d18: Initial release with 19 open-source models&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add Synthetic provider with Anthropic-compatible API&lt;/li&gt;
&lt;li&gt;Support for DeepSeek, Qwen, MiniMax, Kimi, Llama, GLM models&lt;/li&gt;
&lt;li&gt;Vision and reasoning capabilities where available&lt;/li&gt;
&lt;li&gt;Hardcoded model definitions with per-token pricing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>pi-harness: @aliou/pi-guardrails@0.5.0</title><link>https://new.aliou.me/releases/pi-harness-v0-5-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-harness-v0-5-0/</guid><pubDate>Thu, 29 Jan 2026 20:38:26 GMT</pubDate><content:encoded>&lt;h3 id=&quot;minor-changes&quot;&gt;Minor Changes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;cb97920: Add enforce-package-manager guardrail&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;enforcePackageManager&lt;/code&gt; feature (disabled by default)&lt;/li&gt;
&lt;li&gt;Supports npm, pnpm, and bun (npm is default)&lt;/li&gt;
&lt;li&gt;Blocks commands using non-selected package managers&lt;/li&gt;
&lt;li&gt;Configurable via &lt;code&gt;packageManager.selected&lt;/code&gt; setting&lt;/li&gt;
&lt;li&gt;Also documents the existing &lt;code&gt;preventPython&lt;/code&gt; feature&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>release</category></item><item><title>Using Kimi K2.5 with Pi via Synthetic</title><link>https://new.aliou.me/tils/pi-synthetic-kimi/</link><guid isPermaLink="true">https://new.aliou.me/tils/pi-synthetic-kimi/</guid><pubDate>Thu, 29 Jan 2026 15:48:58 GMT</pubDate><content:encoded>&lt;p&gt;Very meta: wanted to use Kimi K2.5 via Synthetic, so I had Pi set up a one-off extension that creates the provider and defines its model. Then reloaded Pi and asked Kimi to create the Synthetic provider.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>pi-linkup: pi-linkup@0.1.0</title><link>https://new.aliou.me/releases/pi-linkup-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/pi-linkup-v0-1-0/</guid><pubDate>Wed, 28 Jan 2026 13:54:15 GMT</pubDate><content:encoded>&lt;p&gt;Release pi-linkup@0.1.0&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Pi Guardrails extension</title><link>https://new.aliou.me/tils/pi-guardrails/</link><guid isPermaLink="true">https://new.aliou.me/tils/pi-guardrails/</guid><pubDate>Mon, 26 Jan 2026 15:52:24 GMT</pubDate><content:encoded>&lt;p&gt;Another one, but this one probably more for me than other people: guardrails!&lt;/p&gt;</content:encoded><category>til</category></item><item><title>Pi Processes extension</title><link>https://new.aliou.me/tils/pi-processes-extension/</link><guid isPermaLink="true">https://new.aliou.me/tils/pi-processes-extension/</guid><pubDate>Mon, 26 Jan 2026 15:52:24 GMT</pubDate><content:encoded>&lt;p&gt;Fun extension I made for Pi: processes. Let your agent handle running processes in the background and be notified when they finish / die etc.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>Jellybeans theme for Pi</title><link>https://new.aliou.me/tils/jellybeans-theme-for-pi/</link><guid isPermaLink="true">https://new.aliou.me/tils/jellybeans-theme-for-pi/</guid><pubDate>Mon, 26 Jan 2026 15:48:58 GMT</pubDate><content:encoded>&lt;p&gt;Ported by beloved jellybeans-mono for Pi, both dark and light!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.aliou.me/blog/jellybeans-theme-for-pi/jellybeans-light.jpg&quot; alt=&quot;Jellybeans theme light mode&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.aliou.me/blog/jellybeans-theme-for-pi/jellybeans-dark.jpg&quot; alt=&quot;Jellybeans theme dark mode&quot;&gt;&lt;/p&gt;</content:encoded><category>til</category></item><item><title>fastmail-cli: v0.1.0</title><link>https://new.aliou.me/releases/fastmail-cli-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/fastmail-cli-v0-1-0/</guid><pubDate>Fri, 23 Jan 2026 16:17:15 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.1.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/fastmail-cli/pull/1&quot;&gt;https://github.com/aliou/fastmail-cli/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;new-contributors&quot;&gt;New Contributors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;@github-actions[bot] made their first contribution in &lt;a href=&quot;https://github.com/aliou/fastmail-cli/pull/1&quot;&gt;https://github.com/aliou/fastmail-cli/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/fastmail-cli/commits/v0.1.0&quot;&gt;https://github.com/aliou/fastmail-cli/commits/v0.1.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>The Infrastructure Around Your AGENTS.md</title><link>https://new.aliou.me/posts/infrastructure-around-your-agent/</link><guid isPermaLink="true">https://new.aliou.me/posts/infrastructure-around-your-agent/</guid><pubDate>Tue, 20 Jan 2026 16:10:32 GMT</pubDate><content:encoded>&lt;p&gt;In the &lt;a href=&quot;https://new.aliou.me/posts/agents-md-can-be-shorter-now/&quot;&gt;previous post&lt;/a&gt;, we talked about loading specific domain knowledge when it is most relevant thanks to &lt;a href=&quot;https://new.aliou.me/posts/skills-for-domain-knowledge/&quot;&gt;Skills&lt;/a&gt;. But there is something I intentionally left out: how do you actually make sure the agent is following what you have written in those Skills?&lt;/p&gt;
&lt;p&gt;One of the answers I found working for my projects and for personal and client work is not adding more instructions but setting up an infrastructure.&lt;/p&gt;
&lt;p&gt;One thing I realized is that the AGENTS.md is about 10% of the work. The ecosystem around it is the rest. A lean AGENTS.md works if you have guardrails that enforce rules and make sure the agent is following them when writing code.&lt;/p&gt;
&lt;h2 id=&quot;deterministic-tools-beat-prose-rules&quot;&gt;Deterministic tools beat prose rules&lt;/h2&gt;
&lt;p&gt;This is a simple rule that has been working for both my client projects and my personal projects: &lt;strong&gt;deterministic tools beat prose rules.&lt;/strong&gt; If something can be linted, do not write it in your AGENTS.md. Instead of a convention section that says “use consistent formatting,” “keep imports sorted,” “use camelCase,” and so on, configure your linter — Biome for TypeScript, Ruff for Python. Then tell your agent to run it when it is done with a piece of code or with work.&lt;/p&gt;
&lt;p&gt;You can even skip telling your agent to run the linter and simply have a formatter in place that rewrites the code the way you expect it.&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;The only caveat to automating formatting is that agents have a hard time with files that change right after they write them. Even though models are increasingly trained to re-read files before additional updates, they sometimes revert formatting changes, which is why linting is preferred here, at least for me. &lt;/span&gt;&lt;/span&gt; It also works with type checkers, where they immediately catch APIs that do not exist and have been hallucinated by your agent.&lt;/p&gt;
&lt;p&gt;This is what I mean by deterministic: the feedback is immediate, unambiguous, and can be automated. This way, contrary to prose rules that can feel like suggestions, these are blockers for your agent.&lt;/p&gt;
&lt;h2 id=&quot;scripts-as-the-interface&quot;&gt;Scripts as the Interface&lt;/h2&gt;
&lt;p&gt;An additional pattern I have seen work pretty well is using scripts as an explicit interface instead or in addition to documentation.&lt;/p&gt;
&lt;p&gt;As an example, if you use Drizzle for migrations, you could have written the following in your AGENTS.md:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold&quot;&gt;## Database Migrations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;When creating migrations:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Update the schema file first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Use snake_case for table names&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Run the generate command with the format...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;4.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Name migrations using YYYYMMDD_description...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, you could define those scripts in the package.json:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  &quot;scripts&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;    &quot;db:generate&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;drizzle-kit generate&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;    &quot;db:migrate&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;drizzle-kit migrate&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in your AGENTS.md:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold&quot;&gt;## Database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Drizzle ORM + PostgreSQL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; Scripts in package.json. Never write migrations by hand.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have noticed that agents already read the package.json file naturally, so there is no need to document how your migrations should be written or created. They can “just” call the script.&lt;/p&gt;
&lt;h2 id=&quot;tests-as-the-feedback-loop&quot;&gt;Tests as the feedback loop&lt;/h2&gt;
&lt;p&gt;The best thing you can give to your agent is a small and simple feedback loop. As I wrote about this a couple of weeks ago in &lt;a href=&quot;https://new.aliou.me/posts/smaller-and-simpler-feedback-loops/&quot;&gt;Smaller and simpler feedback loops&lt;/a&gt;, it is worth repeating here. If the agent can run tests and see its results immediately, it can iterate instead of guessing or instead of you having to describe what needs to be changed.&lt;/p&gt;
&lt;p&gt;For UI components, using Storybook interaction tests works extremely well. The agent sees the test fail, reads the error, fixes the code, and rebounds. In addition, if you have it set up, it can also check what the component looks like by using Playwright via MCP or any other custom tools.&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 2&quot;&gt;&lt;label for=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 2&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;Tools like: &lt;a href=&quot;https://github.com/microsoft/playwright-mcp&quot;&gt;Playwright MCP&lt;/a&gt;, &lt;a href=&quot;https://github.com/remorses/playwriter&quot;&gt;Playwriter Chrome extension&lt;/a&gt;, &lt;a href=&quot;https://github.com/SawyerHood/dev-browser&quot;&gt;dev-browser tool&lt;/a&gt;, &lt;a href=&quot;https://github.com/badlogic/pi-skills/blob/main/browser-tools/SKILL.md&quot;&gt;browser tools skill&lt;/a&gt;. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;example-setups&quot;&gt;Example setups&lt;/h2&gt;
&lt;p&gt;Here is what a minimal but effective infrastructure looks like for the stacks I have worked with the most, both for client projects and personal projects:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JavaScript/TypeScript:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://biomejs.dev/&quot;&gt;Biome&lt;/a&gt; for formatting + linting (one tool, one config)&lt;/li&gt;
&lt;li&gt;TypeScript strict mode (&lt;code&gt;noUncheckedIndexedAccess&lt;/code&gt;, &lt;code&gt;strictNullChecks&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vitest.dev/&quot;&gt;Vitest&lt;/a&gt; for unit tests&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://storybook.js.org/&quot;&gt;Storybook&lt;/a&gt; interaction tests for components&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Python:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astral.sh/uv/&quot;&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt; for package/env management&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astral.sh/ruff/&quot;&gt;&lt;code&gt;ruff&lt;/code&gt;&lt;/a&gt; for linting + formatting&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astral.sh/ty/&quot;&gt;&lt;code&gt;ty&lt;/code&gt;&lt;/a&gt; for type checking&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pytest.org/en/stable/&quot;&gt;&lt;code&gt;pytest&lt;/code&gt;&lt;/a&gt; for unit tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For other languages, the idea is the same: fast tools, strict settings, immediate and concise feedback.&lt;/p&gt;
&lt;h2 id=&quot;what-goes-where&quot;&gt;What goes where&lt;/h2&gt;
&lt;p&gt;When I need to figure out where to put things, here is the template I follow.&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;What&lt;/th&gt;&lt;th&gt;Where&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Project context, north star&lt;/td&gt;&lt;td&gt;AGENTS.md&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Non-obvious conventions&lt;/td&gt;&lt;td&gt;AGENTS.md&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Style, import order, naming&lt;/td&gt;&lt;td&gt;Lint rules&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;API shapes, data structures&lt;/td&gt;&lt;td&gt;Type definitions&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Behavior verification&lt;/td&gt;&lt;td&gt;Tests&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;To repeat myself, but if it is something that can be enforced, it should be enforced either via rules or via scripts. If it is more context for the agent, it should be in the AGENTS.md.&lt;/p&gt;
&lt;h2 id=&quot;the-setup-in-action&quot;&gt;The setup in action&lt;/h2&gt;
&lt;p&gt;When the infrastructure is set up correctly, here is what a change made by an agent can look like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Agent makes a change and commits it&lt;/li&gt;
&lt;li&gt;Pre-commit hooks run the formatter → code is styled correctly&lt;/li&gt;
&lt;li&gt;Linter runs → catches import issues, naming violations&lt;/li&gt;
&lt;li&gt;Type checker runs → catches hallucinated APIs, missing null checks&lt;/li&gt;
&lt;li&gt;Tests run → verify behavior works&lt;/li&gt;
&lt;li&gt;Human reviews → focuses on intent, not nitpicks&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At each of those steps, the agent has the possibility of fixing its mistakes or the issues. It does the fix, retries it by running the test, and then it moves on. Finally, by the time you actually are reviewing, you can focus on what is important:&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-3-3&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 3&quot;&gt;&lt;label for=&quot;sn-user-content-fn-3-3&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 3&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;At least in my opinion. &lt;/span&gt;&lt;/span&gt; the UX of the feature, the intent of the update, whether it matches the direction of the product you are working on, and so on.&lt;/p&gt;
</content:encoded><category>post</category></item><item><title>preview-markdown: v0.2.0</title><link>https://new.aliou.me/releases/preview-markdown-v0-2-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/preview-markdown-v0-2-0/</guid><pubDate>Fri, 16 Jan 2026 16:17:17 GMT</pubDate><content:encoded>&lt;h2 id=&quot;whats-changed&quot;&gt;What’s Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;release: v0.2.0 by @github-actions[bot] in &lt;a href=&quot;https://github.com/aliou/preview-markdown/pull/1&quot;&gt;https://github.com/aliou/preview-markdown/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;new-contributors&quot;&gt;New Contributors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;@github-actions[bot] made their first contribution in &lt;a href=&quot;https://github.com/aliou/preview-markdown/pull/1&quot;&gt;https://github.com/aliou/preview-markdown/pull/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/preview-markdown/compare/v0.1.0...v0.2.0&quot;&gt;https://github.com/aliou/preview-markdown/compare/v0.1.0…v0.2.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Skills for Domain-Specific Knowledge</title><link>https://new.aliou.me/posts/skills-for-domain-knowledge/</link><guid isPermaLink="true">https://new.aliou.me/posts/skills-for-domain-knowledge/</guid><pubDate>Wed, 07 Jan 2026 15:12:25 GMT</pubDate><content:encoded>&lt;p&gt;Progressive disclosure for coding agents: load context only when needed&lt;/p&gt;</content:encoded><category>post</category></item><item><title>preview-markdown: v0.1.0</title><link>https://new.aliou.me/releases/preview-markdown-v0-1-0/</link><guid isPermaLink="true">https://new.aliou.me/releases/preview-markdown-v0-1-0/</guid><pubDate>Fri, 02 Jan 2026 16:17:17 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Full Changelog&lt;/strong&gt;: &lt;a href=&quot;https://github.com/aliou/preview-markdown/commits/v0.1.0&quot;&gt;https://github.com/aliou/preview-markdown/commits/v0.1.0&lt;/a&gt;&lt;/p&gt;</content:encoded><category>release</category></item><item><title>Your AGENTS.md can be shorter now</title><link>https://new.aliou.me/posts/agents-md-can-be-shorter-now/</link><guid isPermaLink="true">https://new.aliou.me/posts/agents-md-can-be-shorter-now/</guid><pubDate>Sat, 20 Dec 2025 15:12:11 GMT</pubDate><content:encoded>&lt;p&gt;The new wave of coding models needs less hand-holding than you think&lt;/p&gt;</content:encoded><category>post</category></item><item><title>Smaller and simpler feedback loops</title><link>https://new.aliou.me/posts/smaller-and-simpler-feedback-loops/</link><guid isPermaLink="true">https://new.aliou.me/posts/smaller-and-simpler-feedback-loops/</guid><pubDate>Fri, 28 Nov 2025 15:12:18 GMT</pubDate><content:encoded>&lt;p&gt;How coding agents changed the way I worked&lt;/p&gt;</content:encoded><category>post</category></item><item><title>Upgrading the nvim-treesitter plugin</title><link>https://new.aliou.me/posts/upgrading-nvim-treesitter/</link><guid isPermaLink="true">https://new.aliou.me/posts/upgrading-nvim-treesitter/</guid><pubDate>Fri, 17 Oct 2025 16:10:32 GMT</pubDate><content:encoded>&lt;p&gt;Every couple of weeks, I run &lt;a href=&quot;https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-deps.md&quot;&gt;MiniDeps&lt;/a&gt;’s &lt;code&gt;:DepsUpdate&lt;/code&gt; and cross my fingers that everything is going to be fine. Most of the time it is but not this morning&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;You’d think after more than 15 years, I’d understand that Friday are not the days to be nonchalant about potentially breaking things. &lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Immediately, I got the following errors:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Error detected while processing ~/.config/nvim/init.lua:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;E5113: Error while calling lua chunk: ~/.config/nvim/lua/_/ui/treesitter.lua:1: module &apos;nvim-treesitter.configs&apos; not found:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no field package.preload[&apos;nvim-treesitter.configs&apos;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no file &apos;/nix/store/w997z7g0ii6figi4z98id0b0yn2jzala-luajit-2.1.1741730670-env/share/lua/5.1/nvim-treesitter/configs.lua&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no file &apos;/nix/store/w997z7g0ii6figi4z98id0b0yn2jzala-luajit-2.1.1741730670-env/share/lua/5.1/nvim-treesitter/configs/init.lua&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no file &apos;/nix/store/bmdviv9ljgqb6siq2p3dsll497fjzpif-luajit2.1-fzy-1.0.3-1/share/lua/5.1/nvim-treesitter/configs.lua&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no file &apos;/nix/store/bmdviv9ljgqb6siq2p3dsll497fjzpif-luajit2.1-fzy-1.0.3-1/share/lua/5.1/nvim-treesitter/configs/init.lua&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no file &apos;/nix/store/w997z7g0ii6figi4z98id0b0yn2jzala-luajit-2.1.1741730670-env/lib/lua/5.1/nvim-treesitter/configs.so&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no file &apos;/nix/store/bmdviv9ljgqb6siq2p3dsll497fjzpif-luajit2.1-fzy-1.0.3-1/lib/lua/5.1/nvim-treesitter/configs.so&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no file &apos;/nix/store/w997z7g0ii6figi4z98id0b0yn2jzala-luajit-2.1.1741730670-env/lib/lua/5.1/nvim-treesitter.so&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        no file &apos;/nix/store/bmdviv9ljgqb6siq2p3dsll497fjzpif-luajit2.1-fzy-1.0.3-1/lib/lua/5.1/nvim-treesitter.so&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Investigating further, I saw that the &lt;a href=&quot;https://github.com/nvim-treesitter/nvim-treesitter/tree/master&quot;&gt;&lt;code&gt;master&lt;/code&gt; branch&lt;/a&gt; of &lt;code&gt;nvim-treesitter&lt;/code&gt; is now frozen and they’re now working on the &lt;a href=&quot;https://github.com/nvim-treesitter/nvim-treesitter/tree/main&quot;&gt;&lt;code&gt;main&lt;/code&gt; branch&lt;/a&gt;. The new branch is a complete rewrite. The fix could have been a simple:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;diff&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  MiniDeps.add({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    source = &apos;nvim-treesitter/nvim-treesitter&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#B31D28;--shiki-dark:#FDAEB7&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;-&lt;/span&gt;   checkout = &apos;master&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-dark:#85E89D&quot;&gt;&lt;span style=&quot;user-select: none;&quot;&gt;+&lt;/span&gt;   checkout = &apos;main&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    hooks = { post_checkout = function() vim.cmd(&apos;TSUpdate&apos;) end },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, since I was there and I figured I’ll just do the update properly. First, my configuration looked like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;lua&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; configs &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;nvim-treesitter.configs&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; mdx &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;mdx&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;---&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;@diagnostic&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; disable-next-line:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; missing-fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;configs.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;setup&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  auto_install &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  highlight &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { enable &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  endwise &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { enable &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  indent &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { enable &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rewrite removes the need for manual config except for customization on where the parsers are installed. To keep the behaviours I had before, in my case, the &lt;code&gt;auto_install&lt;/code&gt; option and the highlighting, I update my config like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, I installed parsers for files in my dotfile on startup. Thankfully, this is a no-op when they’re already installed:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;lua&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; ts &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;nvim-treesitter&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;-- Wait at most 30 seconds to finish installation.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;ts.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &apos;lua&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;vim&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;vimdoc&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;query&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &apos;markdown&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;markdown_inline&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &apos;json&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;yaml&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- Do not print summary, as this will run at startup always, all the time.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  { summary &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;):&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;wait&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;30000&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Then, I install the parser and enable highlighting and other plugin using an &lt;code&gt;autocmd&lt;/code&gt; that triggers on &lt;code&gt;FileType&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;lua&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;---&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;@type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; fun(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;vim.api.keyset.create_autocmd.callback_args&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;): &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;boolean&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;local&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; install_parser_and_enable_features&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(event)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  local&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lang &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; event.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;match&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- Try to start the parser install for the language.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  local&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; ok, task &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; pcall&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(ts.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;install&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, { lang }, { summary &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; ok &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- Wait for the installation to finish (up to 10 seconds).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  task&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;wait&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;10000&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- Enable syntax highlighting for the buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  ok, _ &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; pcall&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(vim.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;treesitter&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;start&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, event.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;buf&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, lang)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; ok &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- Enable other features as needed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- Enable indentation based on treesitter for the buffer.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- vim.bo.indentexpr = &quot;v:lua.require&apos;nvim-treesitter&apos;.indentexpr()&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- Enable folding based on treesitter for the buffer.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  -- vim.wo.foldexpr = &apos;v:lua.vim.treesitter.foldexpr()&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;-- Install missing parsers on file open.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;vim.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;api&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;nvim_create_autocmd&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;FileType&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  group &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; vim.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;api&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;nvim_create_augroup&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;ui.treesitter&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, { clear &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  pattern &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;*&apos; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  callback &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; install_parser_and_enable_features&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And like that, no more errors and treesitter works as expected. I should probably read more about the reasons of the rewrite, but I’ll leave this for another Friday.&lt;/p&gt;
</content:encoded><category>post</category></item><item><title>In-Memory DuckDB with GCS Parquet Files in Cube</title><link>https://new.aliou.me/tils/duckdb-inmemory-gcs-parquet-cube/</link><guid isPermaLink="true">https://new.aliou.me/tils/duckdb-inmemory-gcs-parquet-cube/</guid><pubDate>Thu, 21 Aug 2025 19:37:08 GMT</pubDate><content:encoded>&lt;p&gt;I needed to quickly investigate some data and used the following pattern to do so. Using the model of an e-commerce store as example, here’s what I did.&lt;/p&gt;
&lt;h2 id=&quot;the-setup&quot;&gt;The Setup&lt;/h2&gt;
&lt;p&gt;First, setup a &lt;code&gt;cube.js&lt;/code&gt; defining the &lt;code&gt;dbType&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;exports&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  dbType: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;duckdb&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, define the &lt;a href=&quot;https://cube.dev/docs/product/configuration/reference/config#driver_factory&quot;&gt;&lt;code&gt;driverFactory&lt;/code&gt;&lt;/a&gt;, and configure the access to the GCS bucket:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; driverFactory&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; () &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;DuckDBDriver&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;@cubejs-backend/duckdb-driver&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; DuckDBDriver&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    database: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;:memory:&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    initSql: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      INSTALL httpfs;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      LOAD httpfs;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      CREATE SECRET (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;        TYPE gcs,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;        KEY_ID &apos;YOUR_KEY_ID&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;        SECRET &apos;YOUR_SECRET_ID&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;      );&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    `&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, add to &lt;code&gt;initSql&lt;/code&gt; the commands to setup our views and access our data:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; VIEW&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; orders&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; AS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; read_parquet(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;gs://tmp-bucket/orders/*.parquet&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; VIEW&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; products&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; AS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; read_parquet(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;gs://tmp-bucket/products/*.parquet&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; VIEW&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; line_items&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; AS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; read_parquet(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;gs://tmp-bucket/line_items/*.parquet&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; VIEW&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; users&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; AS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; read_parquet(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;gs://tmp-bucket/users/*.parquet&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; VIEW&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; inventory_transactions&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; AS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; FROM&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; read_parquet(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;gs://tmp-bucket/inventory/*.parquet&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;key-implementation-details&quot;&gt;Key Implementation Details&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Views, Not Tables&lt;/strong&gt;: I specifically used &lt;code&gt;CREATE VIEW&lt;/code&gt; instead of &lt;code&gt;CREATE TABLE AS&lt;/code&gt; to avoid loading all the data into memory. Views just store the query definition - DuckDB streams the Parquet data chunk by chunk when you actually query it. With tables, DuckDB would copy everything into its internal format, eating up memory or disk space.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero Infrastructure&lt;/strong&gt;: The &lt;code&gt;:memory:&lt;/code&gt; database means no provisioning. Each Cube process gets a fresh DuckDB instance that reads directly from existing Parquet files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Direct Access&lt;/strong&gt;: No ETL pipeline, no data loading. The &lt;code&gt;read_parquet()&lt;/code&gt; function streams data directly from GCS, and DuckDB’s columnar engine only fetches the columns needed for each query.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wildcard Patterns&lt;/strong&gt;: The &lt;code&gt;/*.parquet&lt;/code&gt; pattern lets you treat multiple files as a single table without any preprocessing. Perfect for data that’s already partitioned by date or other dimensions.&lt;/p&gt;
&lt;h2 id=&quot;performance-characteristics&quot;&gt;Performance Characteristics&lt;/h2&gt;
&lt;p&gt;In this example, you could run complex queries across orders, products, line items, and inventory - joining across millions of rows within seconds. What would normally require hours of data pipeline setup can be done immediately.&lt;/p&gt;
&lt;p&gt;DuckDB’s aggressive caching means the first query might take a few seconds to fetch metadata, but subsequent queries run pretty much instantly even on larger datasets.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zero data movement—queries run directly against GCS files&lt;/li&gt;
&lt;li&gt;No infrastructure to manage&lt;/li&gt;
&lt;li&gt;Instant setup for ad-hoc analysis&lt;/li&gt;
&lt;li&gt;Reuses existing Parquet files from other processes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Drawbacks:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:memory:&lt;/code&gt; means state vanishes on restart&lt;/li&gt;
&lt;li&gt;Not suitable for production workloads&lt;/li&gt;
&lt;li&gt;Performance degrades on datasets larger than available RAM&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;
&lt;p&gt;This pattern works for any quick data investigation. Got Parquet files? Throw them in GCS, point Cube at them with this config, and you’re analyzing data in minutes. For more permanent setups, swap &lt;code&gt;:memory:&lt;/code&gt; for a file path to persist the DuckDB catalog between restarts.&lt;/p&gt;
&lt;p&gt;Your data lake is already an OLAP database. You just need DuckDB to unlock it.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>Template Literal Types for Dynamic API Generation</title><link>https://new.aliou.me/tils/template-literal-types-for-joins/</link><guid isPermaLink="true">https://new.aliou.me/tils/template-literal-types-for-joins/</guid><pubDate>Fri, 15 Aug 2025 19:37:01 GMT</pubDate><content:encoded>&lt;p&gt;Continuing my work on &lt;a href=&quot;https://gnldxt.link/cube-records&quot;&gt;&lt;code&gt;cube-records&lt;/code&gt;&lt;/a&gt;, I wanted to support joined field names like &lt;code&gt;orders.total&lt;/code&gt; or &lt;code&gt;users.email&lt;/code&gt; - matching exactly how the underlying Cube names them. Instead of manually defining these combinations, TypeScript’s template literal types generate them automatically.&lt;/p&gt;
&lt;h2 id=&quot;the-pattern&quot;&gt;The Pattern&lt;/h2&gt;
&lt;p&gt;Here’s a simplified version of the Cube schema:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// User defines their schema&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;interface&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Schema&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;  users&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    fields&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;email&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    joins&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;orders&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;  orders&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    fields&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;total&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;status&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    joins&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we use a template literal type to defined our joined fields:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// Template literal magic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; JoinedFields&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Schema&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  Schema&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;joins&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;number&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;extends&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; infer&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Join&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;    ?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Join&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Schema&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;      ?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Join&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}.${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;keyof&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Schema&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Join&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;fields&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;      :&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; never&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;    :&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; never&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we can also use the type to create our own custom type.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// TypeScript now knows these fields exist:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; UserJoinedFields&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; JoinedFields&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;users&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// Result: &apos;orders.total&apos; | &apos;orders.status&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;infer&lt;/code&gt; keyword extracts each join name, then template literals compose the dot-notation paths.&lt;/p&gt;
&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;
&lt;p&gt;With the schema defined above and our new type, we can define a &lt;code&gt;Query&lt;/code&gt; type like the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Schema&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;  model&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; T&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;  fields&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Schema&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;fields&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; JoinedFields&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt;)[];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And in our code, we can declare a query serenely thanks to the type system preventing us from writing a query with invalid fields:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; query&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Query&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  model: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;users&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  fields: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &apos;email&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,          &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// ✓ users field&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &apos;orders.total&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,   &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// ✓ joined field&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &apos;orders.status&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// ✓ joined field&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &apos;invalid.field&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;   // ✗ Type error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The real implementation has more conditionals for safety, but the core idea remains the same: extract the join names, then use template literals to compose the field paths.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zero maintenance—add a field to the schema, get autocompletion everywhere&lt;/li&gt;
&lt;li&gt;Catches typos at compile time instead of runtime&lt;/li&gt;
&lt;li&gt;Scales to hundreds of field combinations without manual work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Drawbacks:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Type computation can slow down in massive schemas&lt;/li&gt;
&lt;li&gt;Error messages become cryptic when deeply nested&lt;/li&gt;
&lt;li&gt;Only works with predictable string patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;
&lt;p&gt;This pattern shines when you control the schema and need to generate predictable string patterns. It’s less suitable for user-defined schemas or when you need runtime validation anyway. The sweet spot is internal APIs where you want compiler-enforced consistency between your types and runtime behavior.&lt;/p&gt;
&lt;p&gt;What you don’t write is as important as what you do. No manual string unions. No keeping field lists in sync. Add a new join relationship, and TypeScript immediately knows about every possible field combination.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>Smooth Theme-Aware Images with CSS Transitions</title><link>https://new.aliou.me/tils/theme-aware-images/</link><guid isPermaLink="true">https://new.aliou.me/tils/theme-aware-images/</guid><pubDate>Tue, 12 Aug 2025 19:37:08 GMT</pubDate><content:encoded>&lt;p&gt;When &lt;a href=&quot;https://ghostty.org/&quot;&gt;Ghostty&lt;/a&gt; came out, I wrote &lt;a href=&quot;https://new.aliou.me/posts/enabling-font-ligatures-ghostty/&quot;&gt;this blog post&lt;/a&gt; which contained a few screenshots. I forgot how I managed to have both light and dark mode work seamlessly in Eleventy, so I figured I’d write it down for future me or anyone that sees this.&lt;/p&gt;
&lt;h2 id=&quot;the-pattern&quot;&gt;The Pattern&lt;/h2&gt;
&lt;p&gt;I use an Eleventy shortcode that conditionally renders different images based on theme preference. The basic version wraps images in figure tags with captions:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;config.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;addShortcode&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;image&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;withDarkMode&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; lastDotIndex&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; src.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;lastIndexOf&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; basePath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; src.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, lastDotIndex);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; extension&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; src.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;slice&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(lastDotIndex);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; lightPath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;basePath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}-light${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; darkPath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;basePath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}-dark${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  return&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `&amp;#x3C;figure&gt;${&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    withDarkMode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;      ?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `&amp;#x3C;img class=&quot;not-dark:hidden&quot; src=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;darkPath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot; alt=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;        `&amp;#x3C;img class=&quot;dark:hidden&quot; src=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;lightPath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot; alt=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;      :&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `&amp;#x3C;img src=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot; alt=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;  }&amp;#x3C;figcaption&gt;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&amp;#x3C;/figcaption&gt;&amp;#x3C;/figure&gt;`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;smooth-transitions&quot;&gt;Smooth Transitions&lt;/h2&gt;
&lt;p&gt;The images would swap instantly when toggling themes, which felt jarring. Adding Tailwind’s transition utilities creates a cross-fade effect:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;withDarkMode&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  ?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `&amp;#x3C;img class=&quot;not-dark:hidden not-dark:opacity-0 dark:opacity-100 starting:dark:opacity-0 transition-discrete transition-opacity duration-1000&quot; src=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;darkPath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot; alt=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot;&gt;`&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; +&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    `&amp;#x3C;img class=&quot;dark:hidden dark:opacity-0 not-dark:opacity-100 starting:not-dark:opacity-0 transition-discrete transition-opacity duration-1000&quot; src=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;lightPath&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot; alt=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  :&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; `&amp;#x3C;img src=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;src&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot; alt=&quot;${&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;alt&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;starting:&lt;/code&gt; modifiers prevent the fade animation on initial page load, which I borrowed from how Tailwind’s own docs handle theme switching.&lt;/p&gt;
&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;
&lt;p&gt;Now &lt;code&gt;{% raw %}{% image &quot;ghostty-screenshot.png&quot; &quot;Terminal with ligatures&quot; true %}{% endraw %}&lt;/code&gt; automatically looks for &lt;code&gt;ghostty-screenshot-light.png&lt;/code&gt; and &lt;code&gt;ghostty-screenshot-dark.png&lt;/code&gt;, then smoothly transitions between them when you toggle themes.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Smooth 1-second cross-fade between theme variants&lt;/li&gt;
&lt;li&gt;Both images preload—no flicker on theme switch&lt;/li&gt;
&lt;li&gt;Simple to maintain with clear naming conventions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Drawbacks:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downloads both variants even if user never switches themes&lt;/li&gt;
&lt;li&gt;Requires maintaining two versions of every screenshot&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;
&lt;p&gt;This approach works well for static sites where you control every image. For dynamic content or high-traffic sites, you’d want lazy loading for the hidden variant or CSS filters for automatic adjustments.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;Figured how to create custom shortcode after &lt;a href=&quot;https://anhvn.com/posts/2022/markdown-optimizations&quot;&gt;reading this article&lt;/a&gt; by &lt;a href=&quot;https://anhvn.com/&quot;&gt;Anh&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>til</category></item><item><title>Type-Safe Domain Models with Interface Augmentation in TypeScript</title><link>https://new.aliou.me/tils/type-safe-domain-models-with-interface-augmentation/</link><guid isPermaLink="true">https://new.aliou.me/tils/type-safe-domain-models-with-interface-augmentation/</guid><pubDate>Thu, 07 Aug 2025 19:37:01 GMT</pubDate><content:encoded>&lt;p&gt;While working on &lt;a href=&quot;https://gnldxt.link/cube-records&quot;&gt;&lt;code&gt;@general-dexterity/cube-records&lt;/code&gt;&lt;/a&gt;, I needed a way to define&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;Or use codegen with &lt;a href=&quot;https://gnldxt.link/cube-records-codegen&quot;&gt;another library&lt;/a&gt;. &lt;/span&gt;&lt;/span&gt; my domain-specific &lt;a href=&quot;https://cube.dev/product/cube-core&quot;&gt;Cube&lt;/a&gt; models in a way that would provide type-safe autocompletion. The solution was ended up pretty simple: global interface augmentation.&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The lib defines an empty global interface that users can augment with their own cube definitions. However, after publishing, I discovered that &lt;code&gt;tsup&lt;/code&gt; was optimizing the empty interface into a type alias during the build:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// What we write&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; interface&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordMap&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// What tsup outputs with dts: true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordMap&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// Now augmentation fails&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;declare&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; global {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  interface&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordMap&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;// Error: can&apos;t augment a type alias&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    users&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;/* ... */&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-solution&quot;&gt;The Solution&lt;/h2&gt;
&lt;p&gt;Adding a dummy property prevents this optimization:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; interface&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordMap&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;  __empty&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    measures&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    dimensions&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    joins&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;__empty&lt;/code&gt; property isn’t arbitrary - it ensures the interface remains augmentable through the entire build pipeline&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 2&quot;&gt;&lt;label for=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 2&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;This is specifically a build tool optimization when generating declaration files, not a TypeScript language limitation. &lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;
&lt;p&gt;Users can now extend the interface in their projects:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;declare&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; global {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  interface&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordMap&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;    users&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;      measures&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;count&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; number&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; } };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;      dimensions&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;        id&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;        email&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;      joins&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; readonly&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;orders&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;    // ... other models and views&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The library extracts type-safe cube names and fields:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordName&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; keyof&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordMap&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordMeasure&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordName&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  keyof&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CubeRecordMap&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;T&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;][&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;measures&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;trade-offs&quot;&gt;Trade-offs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interface stays augmentable through the build pipeline&lt;/li&gt;
&lt;li&gt;Zero runtime overhead - types exist only at compile time&lt;/li&gt;
&lt;li&gt;Incremental cube definitions with immediate IDE support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Drawbacks:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The interface includes a dummy property&lt;/li&gt;
&lt;li&gt;Requires some documentation to explain the pattern&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;
&lt;p&gt;This isn’t just for my personal benefits&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-3-3&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 3&quot;&gt;&lt;label for=&quot;sn-user-content-fn-3-3&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 3&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;Even though it was pretty fun to try to figure this out. &lt;/span&gt;&lt;/span&gt;, this pattern provides domain-specific autocompletion without runtime cost. Instead of searching through Cube models for field names, TypeScript provides instant feedback as you type.&lt;/p&gt;
</content:encoded><category>til</category></item><item><title>Adding Custom Language Support to Shiki</title><link>https://new.aliou.me/tils/custom-shiki-languages/</link><guid isPermaLink="true">https://new.aliou.me/tils/custom-shiki-languages/</guid><pubDate>Wed, 06 Aug 2025 19:37:16 GMT</pubDate><content:encoded>&lt;p&gt;While writing my &lt;a href=&quot;https://new.aliou.me/posts/enabling-font-ligatures-ghostty/&quot;&gt;Ghostty blog post&lt;/a&gt;, I wanted syntax highlighting for the configuration snippets. Since Shiki doesn’t support Ghostty’s config format, I added a custom language definition.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; ghostty&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  name: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;ghostty&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  scopeName: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;source.ghostty&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  fileTypes: [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;ghostty&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  repository: {},&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  patterns: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      include: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;#strings&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      name: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;comment.line.number-sign.ghostty&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      match: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;^&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;s*#.*$&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      match: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;b(font-family|font-feature)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;b&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      name: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;keyword.other.ghostty&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I only defined patterns for comments (&lt;code&gt;#&lt;/code&gt;) and the font keywords I was actually using. The grammar follows TextMate rules but doesn’t try to implement the full Ghostty spec - just enough for my examples.&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;highlighter &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; createHighlighter&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  themes: [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;github-dark&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;github-light&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  langs: [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;ruby&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;javascript&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;json&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;elixir&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;typescript&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, ghostty],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>til</category></item><item><title>Enabling font ligatures for GitHub Monaspace in Ghostty</title><link>https://new.aliou.me/posts/enabling-font-ligatures-ghostty/</link><guid isPermaLink="true">https://new.aliou.me/posts/enabling-font-ligatures-ghostty/</guid><pubDate>Sat, 28 Dec 2024 15:11:58 GMT</pubDate><content:encoded>&lt;p&gt;Enabling Monaspace’s stylistic sets for code ligatures in Ghostty&lt;/p&gt;</content:encoded><category>post</category></item><item><title>Using named captures to extract information from ruby strings</title><link>https://new.aliou.me/posts/regexp-named-captures/</link><guid isPermaLink="true">https://new.aliou.me/posts/regexp-named-captures/</guid><pubDate>Thu, 09 Apr 2020 16:10:32 GMT</pubDate><content:encoded>&lt;p&gt;For an internal project at work, I recently had to parse the names of Heroku &lt;a href=&quot;https://devcenter.heroku.com/articles/github-integration-review-apps-old&quot;&gt;review applications&lt;/a&gt; to retrieve some data. The application names looked like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;project_name&gt;-pr-&amp;#x3C;pull_request_id&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first, since each part I needed was separated by a dash, I had some code that looked like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;project_name, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, pull_request_id &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; application_name.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;-&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;project_name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; project_name.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;join&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;-&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the project name could also have some dashes in it, I needed to rejoin it after extracting the pull request data.
At first, for a prototype, this worked fine. But when this internal project transitioned into being an important part of my team’s tooling, I started looking at a better and cleaner way to achieve the same result.&lt;/p&gt;
&lt;p&gt;Since we were already validating the format of the application name with a regular expression, I figured I’d use it to also retrieve the data using named captures.&lt;/p&gt;
&lt;h2 id=&quot;regular-expressions-in-ruby&quot;&gt;Regular expressions in Ruby&lt;/h2&gt;
&lt;p&gt;For a refresher on a &lt;a href=&quot;https://www.rubyguides.com/2015/06/ruby-regex/&quot;&gt;regular expressions&lt;/a&gt;, I highly recommend &lt;a href=&quot;https://daneden.me/2019/11/23/regex-for-designers-and-writers/&quot;&gt;this article&lt;/a&gt;&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;While the article is intended for designers and UX writers, I found that it was an excellent introduction to regular expressions for everyone. &lt;/span&gt;&lt;/span&gt; by &lt;a href=&quot;https://daneden.me&quot;&gt;Dan Eden&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a reminder, there are multiple ways to create regular expressions in Ruby:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;/xxxx/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Using percent literal : &lt;code&gt;%r{}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Using the class initializer: &lt;code&gt;Regexp#new&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With your newly created regular expression, there are two main ways to check if a string matches a regular expression:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Calling &lt;code&gt;String#match&lt;/code&gt; with the regular expression as argument:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;abc&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;match&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;/a/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# =&gt; #&amp;#x3C;MatchData &quot;a&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Calling &lt;code&gt;Regexp#match&lt;/code&gt; on the regular expression with the string as argument:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;/a/&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;match&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;abc&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# =&gt; #&amp;#x3C;MatchData &quot;a&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the String matches the regular expression, it will return a &lt;a href=&quot;https://www.rubydoc.info/stdlib/core/MatchData&quot;&gt;&lt;code&gt;MatchData&lt;/code&gt;&lt;/a&gt; object, otherwise it will return &lt;code&gt;nil&lt;/code&gt;. The &lt;a href=&quot;https://www.rubydoc.info/stdlib/core/MatchData&quot;&gt;&lt;code&gt;MatchData&lt;/code&gt;&lt;/a&gt; object encapsulates the result of matching a String against a Regexp, including the different submatches. It also contains the eventual captures and named captures.&lt;/p&gt;
&lt;h2 id=&quot;named-captures&quot;&gt;Named captures&lt;/h2&gt;
&lt;p&gt;Named captures allow you to describe submatches of a regular expression and then retrieve them from the resulting &lt;a href=&quot;https://www.rubydoc.info/stdlib/core/MatchData&quot;&gt;&lt;code&gt;MatchData&lt;/code&gt;&lt;/a&gt; object. In our case, our regular expression looked like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;/.*-pr-&lt;/span&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold&quot;&gt;\d&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;+/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use named captures, we first need to add capture them into groups to our regular expressions. Adding capture groups is as simple as wrapping them inside parentheses:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;/(.*)-pr-(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold&quot;&gt;\d&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;+)/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, name the different captures. To do this, we need to prefix the content of the capture group with its name:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;/(?&amp;#x3C;project_name&gt;.*)-pr-(?&amp;#x3C;pull_request_id&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold&quot;&gt;\d&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;+)/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we’ve done this, we can easily retrieve the data we want from the application name using our resulting object:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;expression&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt; /(?&amp;#x3C;project_name&gt;.*)-pr-(?&amp;#x3C;pull_request_id&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold&quot;&gt;\d&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#DBEDFF&quot;&gt;+)/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;application_name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &apos;my_app-pr-1234&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;matches&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; expression.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;match&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(application_name)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;matches[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:project_name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# =&gt; &apos;my_app&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;matches.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;named_captures&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt; # =&gt; {&quot;project_name&quot;=&gt;&quot;my_app&quot;, &quot;pull_request_id&quot;=&gt;&quot;1234&quot;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;small&gt;Thanks to &lt;a href=&quot;https://twitter.com/caouibachir&quot; target=&quot;_blank&quot;&gt;Bachir Çaoui&lt;/a&gt;, Alexis Woo and Alexis Focheux for reviewing draft versions of this post.&lt;/small&gt;&lt;/p&gt;
</content:encoded><category>post</category></item><item><title>Changing perception of objects by overriding the inspect method</title><link>https://new.aliou.me/posts/overriding-inspect/</link><guid isPermaLink="true">https://new.aliou.me/posts/overriding-inspect/</guid><pubDate>Tue, 18 Feb 2020 16:10:32 GMT</pubDate><content:encoded>&lt;p&gt;While writing my &lt;a href=&quot;https://new.aliou.me/posts/attributes-api-and-value-objects/&quot;&gt;previous post&lt;/a&gt;, I realized that while using the attributes API allows us to avoid making mistakes when instantiating our value objects, it might not entirely convey that there’s a limited number of objects that can be instantiated &lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;Of course, we could look at the file defining the constants, but where would be the fun in that? &lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;To this end, I thought about a way to prevent this: let’s pretend that our value objects are constants.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;generating-constants&quot;&gt;Generating constants&lt;/h2&gt;
&lt;p&gt;First, let’s take our &lt;code&gt;Ship::Category&lt;/code&gt; from the previous post:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Ship::Category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  VALID_CATEGORIES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; %w(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    shuttle supply_carrier troop_carrier war_ship&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;  )&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;freeze&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; initialize&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(category)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;    raise&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &quot;invalid category: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;#{category.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; unless&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; category.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;in?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;VALID_CATEGORIES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    @raw_category &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; to_s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    raw_category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  private&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  attr_reader&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; :raw_category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s start by defining constants for each of our values. Under the &lt;code&gt;initialize&lt;/code&gt; method &lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 2&quot;&gt;&lt;label for=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 2&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;Because our constants are set directly in the class, the &lt;code&gt;new&lt;/code&gt; method needs to be already defined, hence defining them under the initialize method. &lt;/span&gt;&lt;/span&gt;, let’s create the constants using &lt;a href=&quot;https://www.rubydoc.info/stdlib/core/Module:const_set&quot;&gt;&lt;code&gt;const_set&lt;/code&gt;&lt;/a&gt; and a bit of metaprogramming:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Shift::Category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  VALID_CATEGORIES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;each&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; do&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; |raw_category|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    const_set&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(raw_category.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;upcase&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(raw_category))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Calling &lt;a href=&quot;https://www.rubydoc.info/stdlib/core/Module:constants&quot;&gt;&lt;code&gt;Ship::Category.constants&lt;/code&gt;&lt;/a&gt; show us that our constants have correctly been created:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;pry&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(main)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;constants&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# =&gt; [:WAR_SHIP, :TROOP_CARRIER, :SHUTTLE, :SUPPLY_CARRIER, :VALID_CATEGORIES]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, inspecting the constant reveals our trickery:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;pry&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(main)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;SHUTTLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# =&gt; #&amp;#x3C;Ship::Category:0x00007fbddc853350 @raw_category=&quot;shuttle&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, how do we really pretend that our &lt;code&gt;Ship::Category&lt;/code&gt; object is truly a constant ? We can do this by overriding the &lt;a href=&quot;https://www.rubydoc.info/stdlib/core/Object:inspect&quot;&gt;&lt;code&gt;inspect&lt;/code&gt;&lt;/a&gt; method:&lt;/p&gt;
&lt;h2 id=&quot;overriding-inspect&quot;&gt;Overriding inspect&lt;/h2&gt;
&lt;p&gt;As we can see above, by default, &lt;code&gt;inspect&lt;/code&gt; returns the class name, a representation of the memory address of the object and a list of instance variables of the object.&lt;/p&gt;
&lt;p&gt;In our case, we want &lt;code&gt;inspect&lt;/code&gt; to instead display how the object should be accessed. This means making it look like the constants we’ve created above:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Shift::Category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; inspect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;#{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;#{raw_category.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;upcase&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, the value object is now displayed as a constant when inspecting the object or in logs:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# Before we had:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;pry&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(main)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;SHUTTLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# =&gt; #&amp;#x3C;Ship::Category:0x00007fbddc853350 @raw_category=&quot;shuttle&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# Now we have&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;pry&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(main)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;SHUTTLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# =&gt; Ship::Category::SHUTTLE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;!-- As I write this, I realize that this might be one of those time where Ruby allows you to do --&gt;
&lt;hr&gt;
&lt;p&gt;Besides indulging in my whims, there are other interesting reasons to override inspect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is useful to hide sensitive values like emails or encrypted passwords, as &lt;a href=&quot;https://github.com/heartcombo/devise/blob/v4.7.1/lib/devise/models/authenticatable.rb#L120-L125&quot;&gt;Devise does it&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Since Rails 6, you can &lt;a href=&quot;https://github.com/rails/rails/pull/33756/files&quot;&gt;configure ActiveRecord to filter attributes from inspection&lt;/a&gt;. This is also done by &lt;a href=&quot;https://github.com/rails/rails/pull/33756&quot;&gt;overriding the &lt;code&gt;inspect&lt;/code&gt; method&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;IPAddr&lt;/code&gt; class also &lt;a href=&quot;https://github.com/ruby/ruby/blob/v2_7_0/lib/ipaddr.rb#L457-L468&quot;&gt;overrides the inspect method&lt;/a&gt; to display a human readable representation of the IP address.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;small&gt;The code examples in this post are also available &lt;a href=&quot;https://github.com/aliou/ships-category&quot;&gt;on GitHub&lt;/a&gt;{target=“_blank”}.
Thanks to &lt;a href=&quot;https://twitter.com/caouibachir&quot; target=&quot;_blank&quot;&gt;Bachir Çaoui&lt;/a&gt; and Stéphanie Chhim for reviewing a draft version of this post.&lt;/small&gt;&lt;/p&gt;
</content:encoded><category>post</category></item><item><title>Using Rails&apos;s Attributes API to serialize Value Objects</title><link>https://new.aliou.me/posts/attributes-api-and-value-objects/</link><guid isPermaLink="true">https://new.aliou.me/posts/attributes-api-and-value-objects/</guid><pubDate>Tue, 08 Oct 2019 15:16:55 GMT</pubDate><content:encoded>&lt;p&gt;Continuing my deep dive into Rails features &lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;See my previous post about &lt;a href=&quot;https://new.aliou.me/posts/namespaced-rails-validators/&quot;&gt;Namespaced Rails validators&lt;/a&gt;. &lt;/span&gt;&lt;/span&gt;, I recently read about the &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html&quot;&gt;Attributes API&lt;/a&gt;{:target=“_blank”} and
more particularly about how it can be used with custom types.&lt;/p&gt;
&lt;h3 id=&quot;the-attribute-api&quot;&gt;The &lt;code&gt;attribute&lt;/code&gt; API&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;attributes&lt;/code&gt; method allows you to define an attribute with a type on a model. This type can be a completely custom.&lt;/p&gt;
&lt;p&gt;In our example, we have spaceships that are defined by their category. Those categories are both immutable and interchangeable, and are good candidates to be transformed into &lt;a href=&quot;https://www.martinfowler.com/bliki/ValueObject.html&quot;&gt;value objects&lt;/a&gt;{:target=“_blank”}.&lt;/p&gt;
&lt;p&gt;Our current model looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# app/models/ship.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  validates &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;presence:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  validates &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;presence:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  validates &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;inclusion:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;in:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;VALID_CATEGORIES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And our &lt;code&gt;Ship::Category&lt;/code&gt; value type looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# app/models/ship/category.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Ship::Category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  VALID_CATEGORIES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; %w(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    shuttle supply_carrier troop_carrier war_ship&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;  )&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;freeze&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; initialize&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(category)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;    raise&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &quot;invalid category &apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;#{category.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;inspect&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; unless&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; category.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;in?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;VALID_CATEGORIES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    @raw_category &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; to_s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    raw_category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  # Conform to comparable.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; &amp;#x3C;=&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(other)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;    VALID_CATEGORIES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(to_s) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&amp;#x3C;=&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; VALID_CATEGORIES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;index&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(other.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;to_s&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  private&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  attr_reader&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; :raw_category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, let’s update our model to retrieve our value object:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# app/models/ship.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;  # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    @category &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;||=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;self&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this does what we want, this can be improved by creating a custom type.&lt;/p&gt;
&lt;h3 id=&quot;creating-a-custom-type&quot;&gt;Creating a custom type&lt;/h3&gt;
&lt;p&gt;We create our custom type by inheriting from &lt;code&gt;ActiveRecord::Type::Value&lt;/code&gt; and overriding the necessary methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt; is the type of our object when saved in the database. In our case this will be &lt;code&gt;:string&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cast&lt;/code&gt; is the method called by ActiveRecord when setting the attribute in the model.
In our case, we will instantiate our value object.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deserialize&lt;/code&gt; converts the value from the database to our value object. By default it calls &lt;code&gt;cast&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;serialize&lt;/code&gt; converts our value object to a type that the database understands. In our case, we’ll send back the string containing the raw category.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For our type it looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# app/types/ship_category.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; CategoryType&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;ActiveRecord::Type::Value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;    :string&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; cast&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;    Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; deserialize&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    cast&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; serialize&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    value.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;to_s&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;registering-our-type&quot;&gt;Registering our type&lt;/h3&gt;
&lt;p&gt;Now that our type is created, we need to register it so ActiveRecord knows about it:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# config/initializers/types.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;Type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;register&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:ship_category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;CategoryType&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;small class=&quot;ma0&quot;&gt;You will need to restart your Rails server or re-register your type every time you update it.&lt;/small&gt;&lt;/p&gt;
&lt;h3 id=&quot;using-it-in-our-model&quot;&gt;Using it in our model&lt;/h3&gt;
&lt;p&gt;Finally, we can use it in our model:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  attribute &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:ship_category&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  validates &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;presence:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  validates &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:category&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;presence:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;At this point, you might be wondering: &lt;em&gt;Why would I do this?&lt;/em&gt;
Personally, I feel like it is &lt;em&gt;cleaner&lt;/em&gt; to let Rails handle the instantiation of objects instead of the usual memoization-with-instance-variables dance.&lt;/p&gt;
&lt;p&gt;Furthermore, it allows you to add additional features to your model without having pollute the model class. In our case, we allow our ships to be compared based on their categories by implementing &lt;a href=&quot;https://ruby-doc.org/core/Comparable.html&quot;&gt;Comparable&lt;/a&gt;{:target=“_blank”} in our Category.&lt;/p&gt;
&lt;p&gt;However, there are ways to make this particular use case fall down. In our example above, we limit the category to the values defined in &lt;code&gt;VALID_CATEGORIES&lt;/code&gt;. This means that creating a row in our database with a value that isn’t valid will make our application raise when trying to instantiate the row into a &lt;code&gt;Ship&lt;/code&gt; object:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; ships(id, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, category, created_at, updated_at)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;USS Enterprise&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;interstellar_liner&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;NOW&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;NOW&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;enterprise&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; Ship&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;find&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# =&gt; Error: invalid category &apos;interstellar_liner&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;For the purpose of this blog post, I chose a fairly simple example to present the attributes API.
To achieve a similar result, you could also define the categories as a ActiveRecord Enum, as a Postgres Enum&lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 2&quot;&gt;&lt;label for=&quot;sn-user-content-fn-2-2&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 2&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;However, this would require you to change your schema format to &lt;code&gt;:sql&lt;/code&gt; or to override Rails’s Schema dumper to handle Postgres Enums. &lt;/span&gt;&lt;/span&gt; or even have them in their own table and have a Postgres association between a Ship and its category, that is backed by a foreign key to achieve integrity of your data.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Thanks to &lt;a href=&quot;https://twitter.com/caouibachir&quot; target=&quot;_blank&quot;&gt;Bachir Çaoui&lt;/a&gt; and Baicheng Yu for reviewing draft versions of this post.&lt;/small&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;further-reading&quot;&gt;Further reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Martin Fowler’s &lt;a href=&quot;https://www.martinfowler.com/bliki/ValueObject.html&quot;&gt;post on Value Objects&lt;/a&gt;{:target=“_blank”}.&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://jetrockets.pro/blog/rails-5-attributes-api-value-objects-and-jsonb&quot;&gt;post on using the Attributes API with JSONB&lt;/a&gt;{:target=“_blank”}.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>post</category></item><item><title>Three ways to ignore files in Git</title><link>https://new.aliou.me/posts/three-ways-to-ignore-files-in-git/</link><guid isPermaLink="true">https://new.aliou.me/posts/three-ways-to-ignore-files-in-git/</guid><pubDate>Thu, 17 Jan 2019 16:10:32 GMT</pubDate><content:encoded>&lt;p&gt;TIL I learned that there are different ways to ignore files in Git:&lt;/p&gt;
&lt;h3 id=&quot;1-using-a-gitignore-file-in-a-repository&quot;&gt;1. Using a &lt;code&gt;.gitignore&lt;/code&gt; file in a repository&lt;/h3&gt;
&lt;p&gt;When created in a Git repository, this &lt;code&gt;.gitignore&lt;/code&gt; is only applied to the
directory it is in and its children. This means that you can ignore files in the
whole repository and also ignore some files in some subdirectories.&lt;/p&gt;
&lt;p&gt;Start by creating a &lt;code&gt;.gitignore&lt;/code&gt; in a subdirectory:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# lib/.gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the following directory structure:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;├──&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; lib&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;│  &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ├──&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; .gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;│  &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ├──&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; todo.md&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;        # &amp;#x3C;- Will be ignored&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;└──&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; Readme.md&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;          # &amp;#x3C;- Will not be ignored&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file should be version-controlled and includes files that all developers working on the repository will want to ignore.&lt;/p&gt;
&lt;h3 id=&quot;2-using-the-local-exclusion-file-gitinfoexclude&quot;&gt;2. Using the local exclusion file &lt;code&gt;.git/info/exclude&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Start by creating the &lt;code&gt;info&lt;/code&gt; directory and the exclude file in our repository
&lt;code&gt;.git&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; .git/info&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;touch&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; exclude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can add files or pattern of files you want to ignore:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;TODO.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;NOTES.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I mainly use it to ignore files that do not need to be shared with other developers.
I usually leave notes a list of TODOs at the root of a project and ignore them in this file.&lt;/p&gt;
&lt;h3 id=&quot;3-using-a-global-gitignore&quot;&gt;3. Using a global &lt;code&gt;.gitignore&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Start by making a &lt;code&gt;.gitignore&lt;/code&gt; file in your home directory,
with the files you want to ignore, and place in your home directory:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# ~/.gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.vimrc.local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.swp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.idea&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.DS_Store&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, tell Git to use this file as global &lt;code&gt;.gitignore&lt;/code&gt; by running in your shell:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; --global&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; core.excludesfile&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ~/.gitignore&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use it to ignore file I never want to be committed, e.g. backup or temporary
files, build  artifacts, etc.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;further-reading&quot;&gt;Further reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Documentation for &lt;a href=&quot;https://git-scm.com/docs/gitignore&quot;&gt;&lt;code&gt;gitignore&lt;/code&gt;&lt;/a&gt;{:target=“_blank”}.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>post</category></item><item><title>Postgres timestamp ranges in Ecto</title><link>https://new.aliou.me/posts/postgres-tsranges-in-ecto/</link><guid isPermaLink="true">https://new.aliou.me/posts/postgres-tsranges-in-ecto/</guid><pubDate>Tue, 18 Sep 2018 16:10:32 GMT</pubDate><content:encoded>&lt;p&gt;I recently read a post on &lt;a href=&quot;https://tapoueh.org/blog/2018/04/postgresql-data-types-ranges&quot;&gt;Postgres’s range types&lt;/a&gt;{target=’_blank’} and have
been trying to take advantage of them in my code.&lt;/p&gt;
&lt;p&gt;However, because some of these types aren’t shared between the different SQL
databases, most Object Relation Mapping like &lt;a href=&quot;https://guides.rubyonrails.org/active_record_basics.html&quot;&gt;Ruby on Rails’s ActiveRecord&lt;/a&gt; and
database wrappers (e.g. &lt;a href=&quot;https://hexdocs.pm/ecto/Ecto.html&quot;&gt;Elixir’s Ecto&lt;/a&gt;{target=’_blank’}) don’t support them.&lt;/p&gt;
&lt;p&gt;Thankfully, Ecto allows us to define our custom types that can represent an
unknown database type. We’ll now try to implement one to represent timestamp
ranges.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Let’s say we need to schedule chores between different members of a team in a spaceship. &lt;input type=&quot;checkbox&quot; id=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-toggle&quot; aria-label=&quot;Toggle sidenote 1&quot;&gt;&lt;label for=&quot;sn-user-content-fn-1-1&quot; class=&quot;sidenote-ref&quot; role=&quot;doc-noteref&quot; aria-label=&quot;Note 1&quot;&gt;&lt;/label&gt;&lt;span class=&quot;sidenote&quot; role=&quot;note&quot;&gt;&lt;span class=&quot;sidenote-body&quot;&gt;If you know me this &lt;a href=&quot;https://www.snapshift.co&quot;&gt;might be familiar&lt;/a&gt;{:target=“_blank”}. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The simplest way to do this would be to store the range of our chore and who is
assigned to it. With Ecto, the migration creating this table would look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;create &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;table&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:chores&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  add&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;references&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;null:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  add&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:note&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  add&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:tsrange&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;null:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  timestamps&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;default:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; fragment&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;NOW()&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to make sure a user can’t have multiple chores overlapping with
each other. For this we’ll add &lt;a href=&quot;https://www.postgresql.org/docs/current/static/ddl-constraints.html#DDL-CONSTRAINTS-EXCLUSION&quot;&gt;an exclusion constraint&lt;/a&gt;{target=’_blank’} on our range:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# Add the btree_gist extension to allow using `gist` indexes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# with scalar types, in our case the `user_id`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;execute&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;CREATE EXTENSION btree_gist&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;create&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  constraint&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;    &quot;chores&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;    :no_overlaping_chores_for_user&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;    exclude:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ~s|gist (user_id with =, range with &amp;#x26;&amp;#x26;)|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;creating-the-schema&quot;&gt;Creating the schema&lt;/h3&gt;
&lt;p&gt;We now create our schema representing a chore in the application. Let’s try to
use the &lt;code&gt;:tsrange&lt;/code&gt; as the type of our chore range:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;defmodule&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Chore&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  use&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Ecto&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Schema&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  schema &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;chores&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    field&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:note&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    field&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:tsrange&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    belongs_to&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:user&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    timestamps&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; changeset&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(chore, attrs) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    chore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;    |&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; cast&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(attrs, [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:note&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;    |&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; validate_required&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:user_id&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When compiling this, we have an error:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;== Compilation error in file lib/chore.ex ==&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;** (ArgumentError) invalid or unknown type :tsrange&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    for field :range&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because &lt;code&gt;:tsrange&lt;/code&gt; is not a type known by Ecto, we will need to create our own type
adopting the &lt;a href=&quot;https://hexdocs.pm/ecto/3.0.6/Ecto.Type.html&quot;&gt;&lt;code&gt;Ecto.Type&lt;/code&gt; behaviour&lt;/a&gt;{:target=“_blank”}.
But first we’ll create a struct that represents a timestamp range.&lt;/p&gt;
&lt;h3 id=&quot;representing-our-range&quot;&gt;Representing our Range&lt;/h3&gt;
&lt;p&gt;We define our &lt;code&gt;Timestamp.Range&lt;/code&gt; as a struct with the first and last elements of the
range and with options for the inclusivity of those elements in the range.&lt;/p&gt;
&lt;!--
We allow `nil` values to represent the lack of first and last elements: an
infinite range.
--&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;defmodule&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Timestamp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  defstruct&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:first&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:last&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;opts:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; []]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  @type t &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; %&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;          first:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; NaiveDateTime&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;          last:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; NaiveDateTime&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;          opts:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;            lower_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; boolean&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;            upper_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; boolean&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;          ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also define a convenience function to create a &lt;code&gt;Timestamp.Range&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;@default_opts [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;lower_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;upper_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;@spec &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;NaiveDateTime&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(), &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Keyword&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;t&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;::&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; t&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(first, last, opts &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; []) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    opts &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Keyword&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;merge&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(@default_opts, opts)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    %&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;__MODULE__&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      first:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; first,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      last:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; last,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      opts:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; opts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now represent a Postgres’s &lt;code&gt;tsrange&lt;/code&gt; in Elixir.&lt;/p&gt;
&lt;h3 id=&quot;implementing-the-ectotype-behaviour&quot;&gt;Implementing the &lt;code&gt;Ecto.Type&lt;/code&gt; behaviour&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;Ecto.Type&lt;/code&gt; behaviour expects four functions to be defined:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;type/0&lt;/code&gt;: The underlying type of our custom type, known by either Ecto or
&lt;a href=&quot;https://github.com/elixir-ecto/postgrex&quot; target=&quot;_blank&quot;&gt;Postgrex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cast/1&lt;/code&gt;: A function to transform anything into our custom type.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;load/1&lt;/code&gt;: A function to transform something from the database into our custom
type.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dump/1&lt;/code&gt;: A function to transform our custom type into something understood by
the database.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;type&lt;/code&gt; implementation:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; type&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;do:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; :tsrange&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;cast&lt;/code&gt; implementation: we only allow our custom type
to be cast:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; cast&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(%&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Timestamp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{} &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range), &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;do:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, range}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; cast&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;do:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; :error&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;load&lt;/code&gt; implementation receives a &lt;code&gt;Postgrex.Range&lt;/code&gt; and transforms it to a
&lt;code&gt;Timestamp.Range&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; load&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(%&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Postgrex&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{} &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;    Timestamp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      range.lower,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      range.upper,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      lower_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range.lower_inclusive,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      upper_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range.upper_inclusive&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    )}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; load&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;do:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; :error&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally, the &lt;code&gt;dump&lt;/code&gt; implementation takes a &lt;code&gt;Timestamp.Range&lt;/code&gt; and transforms
it to a &lt;code&gt;Postgrex.Range&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; dump&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(%&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Timestamp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{} &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  [&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;lower_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lower_inclusive, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;upper_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; upper_inclusive] &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range.opts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  {&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:ok&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    %&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Postgrex&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      lower:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range.first,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      upper:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range.last,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      lower_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; lower_inclusive,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;      upper_inclusive:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; upper_inclusive&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;    }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; dump&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;_&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;do:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; :error&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;using-our-new-type-in-the-schema&quot;&gt;Using our new type in the schema&lt;/h3&gt;
&lt;p&gt;Now that we have our custom Ecto type, we can use it in our schema:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;schema &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;chores&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; do&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  field&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:note&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:string&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  field&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Timestamp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  belongs_to&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:user&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;  timestamps&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we can insert new chores into the table:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;elixir&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range_start &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ~N[2018-09-17 10:00:00]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; range_end &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ~N[2018-09-17 12:00:00]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; attrs &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; %{&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;user_id:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;range:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Timestamp&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Range&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(range_start, range_end)}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;iex&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Chore&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;changeset&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(%&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Chore&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{}, attrs) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;|&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Repo&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;insert!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;%&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Radch&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;Chore&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  __meta__:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt; #Ecto.Schema.Metadata&amp;#x3C;:loaded, &quot;chores&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  id:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  note:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; nil&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  range:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt; #Timestamp.Range&amp;#x3C;~N[2018-09-17 10:00:00], ~N[2018-09-17 12:00:00]&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  user_id:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  updated_at:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ~N[2018-09-17 16:30:05]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;  inserted_at:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ~N[2018-09-17 16:30:05]&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;small&gt;The code examples in this post are also available &lt;a href=&quot;https://github.com/aliou/radch&quot;&gt;on GitHub&lt;/a&gt;{:target=“blank”}.
Thanks to &lt;a href=&quot;https://twitter.com/caouibachir&quot; target=&quot;_blank&quot;&gt;Bachir Çaoui&lt;/a&gt; for reviewing a
draft version of this post.&lt;/small&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;further-reading&quot;&gt;Further reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Documentation on the &lt;a href=&quot;https://hexdocs.pm/ecto/3.0.0/Ecto.Type.html&quot;&gt;&lt;code&gt;Ecto.Type&lt;/code&gt; behaviour&lt;/a&gt;{:target=“_blank”}&lt;/li&gt;
&lt;li&gt;Documentation on &lt;a href=&quot;https://www.postgresql.org/docs/10/static/rangetypes.html&quot;&gt;Postgres’ range types&lt;/a&gt;{:target=“_blank”}&lt;/li&gt;
&lt;li&gt;More reading on &lt;a href=&quot;https://tapoueh.org/blog/2018/04/postgresql-data-types-ranges&quot;&gt;Postgres’ range types&lt;/a&gt;{:target=“_blank”}&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>post</category></item><item><title>Namespaced Rails validators</title><link>https://new.aliou.me/posts/namespaced-rails-validators/</link><guid isPermaLink="true">https://new.aliou.me/posts/namespaced-rails-validators/</guid><pubDate>Mon, 27 Aug 2018 16:10:32 GMT</pubDate><content:encoded>&lt;p&gt;While going source spelunking, I came across this piece of code in Rails’
ActiveModel:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;#{key.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;to_s&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;camelize&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;Validator&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E36209;--shiki-dark:#FFAB70&quot;&gt;  validator&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; key.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;include?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;::&quot;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;freeze&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; key.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;constantize&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;const_get&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;rescue&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; NameError&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  raise&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; ArgumentError&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&quot;Unknown validator: &apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;#{key}&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;small&gt;&lt;a href=&quot;https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/validations/validates.rb#L116-L122&quot;&gt;&lt;code&gt;active_model/validations/validates.rb&lt;/code&gt;&lt;/a&gt;{:target=“_blank”}&lt;/small&gt;
{: .ma0}&lt;/p&gt;
&lt;p&gt;This means that you can namespace your custom validators:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# lib/internal/email_validator.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;module&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Internal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; EmailValidator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;    def&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; validate_each&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(record, attribute, value)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;      return&lt;/span&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt; if&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; value.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;ends_with?&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;@private_domain.com&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;      record.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;errors&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;add&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(attribute, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;not from private domain&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;  end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then use them like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;ruby&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# app/models/admin.rb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;class&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt; Admin&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt; &amp;#x3C; &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;  validates &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;:email&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;internal/email&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;small&gt;Thanks to &lt;a href=&quot;https://twitter.com/caouibachir&quot; target=&quot;_blank&quot;&gt;Bachir Çaoui&lt;/a&gt; for reviewing a
draft version of this post.&lt;/small&gt;&lt;/p&gt;</content:encoded><category>post</category></item><item><title>A global .gitignore</title><link>https://new.aliou.me/posts/a-global-gitignore/</link><guid isPermaLink="true">https://new.aliou.me/posts/a-global-gitignore/</guid><pubDate>Sun, 26 Aug 2018 16:35:20 GMT</pubDate><content:encoded>&lt;p&gt;A small tip that I’ve come across recently: It is possible to have a global &lt;code&gt;.gitignore&lt;/code&gt; file
that applies to every Git repository on your machine.&lt;/p&gt;
&lt;p&gt;Start by making a &lt;code&gt;.gitignore&lt;/code&gt; file in your home directory,
with the files you want to ignore, and place in your home directory:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6A737D;--shiki-dark:#6A737D&quot;&gt;# ~/.gitignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.vimrc.local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#D73A49;--shiki-dark:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;.swp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.idea&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;.DS_Store&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, tell Git to use this file as global &lt;code&gt;.gitignore&lt;/code&gt; by running in your shell:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light github-dark&quot; style=&quot;--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;span style=&quot;--shiki-light:#005CC5;--shiki-dark:#79B8FF&quot;&gt; --global&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; core.excludesfile&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt; ~/.gitignore&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also take inspiration from my own
&lt;a href=&quot;https://github.com/aliou/dotfiles/blob/master/git/gitignore&quot; target=&quot;_blank&quot;&gt; global .gitignore file&lt;/a&gt;. Enjoy!&lt;/p&gt;</content:encoded><category>post</category></item></channel></rss>