<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>UnDercontrol Blog</title>
        <link>https://oatnil.com/blog</link>
        <description>UnDercontrol Blog</description>
        <lastBuildDate>Tue, 28 Apr 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Access Anywhere — Web, Desktop, CLI, and Beyond]]></title>
            <link>https://oatnil.com/blog/2026/04/28/multi-platform</link>
            <guid>https://oatnil.com/blog/2026/04/28/multi-platform</guid>
            <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[UnDercontrol provides four official clients — Web, Electron desktop, CLI, and Chrome extension — sharing a single self-hosted data source. Plus an open API for building your own.]]></description>
            <content:encoded><![CDATA[<p>When choosing a productivity tool, you often face a familiar dilemma: web apps are powerful but useless offline, desktop apps feel great but lock your data locally, and CLI tools are developer-friendly but lack a visual interface. The root cause is the absence of a Single Source of Truth — each platform is an island, and your data is scattered across them. UnDercontrol's answer: four form factors, one data source. And that data source is entirely under your control — self-hosted on your own server or stored on your local disk, never passing through any third party. Privacy and security are defined by you.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-sst.png" alt="Single Source of Truth architecture" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="web-app--remote-access-zero-install">Web App — Remote Access, Zero Install<a href="https://oatnil.com/blog/2026/04/28/multi-platform#web-app--remote-access-zero-install" class="hash-link" aria-label="Direct link to Web App — Remote Access, Zero Install" title="Direct link to Web App — Remote Access, Zero Install">​</a></h3>
<p>UnDercontrol's web frontend is built with Vite + React + TypeScript and serves as the foundation of the entire platform. Open a browser and you're in — no installation required.</p>
<p>Core capabilities:</p>
<ul>
<li>Full task, budget, and expense management</li>
<li>Tiptap rich-text editor (code blocks, Mermaid diagrams, tables, checklists)</li>
<li>AI chat integration (supports Claude, OpenAI, and other providers)</li>
<li>SSE real-time notifications with multi-tab sync</li>
<li>Responsive design that works on mobile</li>
<li>Bilingual interface (Chinese / English)</li>
</ul>
<p>The web app also serves as the rendering layer for the Electron desktop app — same codebase, zero duplication.</p>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>On the go, quickly check task progress or approve expenses from your phone's browser</li>
<li>Team members collaborate instantly — just open a link, no software to install</li>
<li>The convergence point for all clients — docs pushed from CLI, tasks created on desktop, pages clipped by the extension — all viewable and manageable in one place</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/web-app-dashboard.png" alt="UnDercontrol web app dashboard" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="electron-desktop-app--offline-first-local-data">Electron Desktop App — Offline First, Local Data<a href="https://oatnil.com/blog/2026/04/28/multi-platform#electron-desktop-app--offline-first-local-data" class="hash-link" aria-label="Direct link to Electron Desktop App — Offline First, Local Data" title="Direct link to Electron Desktop App — Offline First, Local Data">​</a></h3>
<p>The desktop app is far more than a web wrapper. It embeds a full Go backend and SQLite database, ready to use out of the box with no server configuration.</p>
<p><strong>Embedded backend architecture</strong></p>
<ul>
<li>Automatically launches the Go backend on startup</li>
<li>Dynamic port allocation to avoid conflicts</li>
<li>Data stored in <code>~/Library/Application Support/UnDercontrol/</code> (macOS)</li>
<li>Fully offline-capable — works without any network connection</li>
</ul>
<p><strong>Remote backend mode</strong></p>
<ul>
<li>The desktop app can also connect to a remote server, sharing the same data source as the web app</li>
<li>Switch the API address in settings — no reinstallation needed</li>
</ul>
<p><strong>Daemon mode</strong></p>
<ul>
<li>Background daemon process that listens to the task queue via SSE</li>
<li>Receives and automatically executes remotely dispatched tasks — ideal for AI Agent workflows</li>
</ul>
<p><strong>Desktop-exclusive features</strong></p>
<ul>
<li>System tray (Windows) — minimize to tray for quick access</li>
<li><code>.md</code> file association — double-click a Markdown file to open it in UnDercontrol</li>
<li>Multi-window editing — open multiple editor, task, and sticky-note windows simultaneously</li>
<li>Workspace management — local terminal windows for running commands</li>
</ul>
<p><strong>Build targets</strong></p>
<ul>
<li>macOS — DMG installer, Universal Binary (x64 + ARM64)</li>
<li>Windows — NSIS installer (x64)</li>
<li>Linux — AppImage (x64)</li>
</ul>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>On a plane or train with no network — create tasks and edit documents as usual; everything syncs when you're back online</li>
<li>At home, connect the desktop app to your company server — see exactly the same data as the web app</li>
<li>Receive remotely dispatched AI tasks via Daemon and execute them automatically on your local machine</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/web-app-task-detail.png" alt="UnDercontrol task detail page" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="cli--kubectl-style-ai-agent-friendly">CLI — kubectl-Style, AI Agent Friendly<a href="https://oatnil.com/blog/2026/04/28/multi-platform#cli--kubectl-style-ai-agent-friendly" class="hash-link" aria-label="Direct link to CLI — kubectl-Style, AI Agent Friendly" title="Direct link to CLI — kubectl-Style, AI Agent Friendly">​</a></h3>
<p>The <code>ud</code> CLI is a terminal tool designed for developers and automation, using a kubectl-style verb-resource command system.</p>
<p><strong>Interactive TUI</strong></p>
<p>Run <code>ud</code> to enter a full-screen TUI for browsing and managing tasks with your keyboard.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-sst.png" alt="Multi-platform architecture — Single Source of Truth" class="img_ev3q"></p>
<p><strong>One-liner operations</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud get task                          # list tasks</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud describe task abc123              # view details</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud apply -f task.md                  # create/update from file</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task query "status:todo tag:api"  # query and filter</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task nl "tasks completed last week" # natural language query</span><br></span></code></pre></div></div>
<p><strong>AI Agent integration</strong></p>
<p><code>ud prompt &lt;skill-name&gt;</code> outputs skill documents that teach AI Agents (Claude Code, Codex, OpenCode, and others) how to operate tasks via the ud CLI. Structured command output and Markdown-formatted input make it a natural fit for AI Agents to read and write.</p>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>CI/CD pipelines automatically push release notes to ud — the team reads them on the web, no need to dig through Git logs</li>
<li>Technical docs in your code repo (architecture designs, API specs, runbooks) are synced to ud via scripts — non-developers can read them on the web without accessing the repository</li>
<li>AI Agents automatically record progress and decisions in task notes during coding — the team sees updates in real time</li>
<li>Ops scripts periodically collect inspection results and <code>ud apply</code> them as tasks, forming a traceable operations log</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="chrome-extension--one-click-clipping">Chrome Extension — One-Click Clipping<a href="https://oatnil.com/blog/2026/04/28/multi-platform#chrome-extension--one-click-clipping" class="hash-link" aria-label="Direct link to Chrome Extension — One-Click Clipping" title="Direct link to Chrome Extension — One-Click Clipping">​</a></h3>
<p>The Chrome extension turns any web page into an UnDercontrol task.</p>
<ul>
<li><strong>One-click save</strong> — captures a full page snapshot (HTML + all embedded resources)</li>
<li><strong>Markdown extraction</strong> — automatically extracts the page body as clean Markdown</li>
<li><strong>Offline mode</strong> — save directly to local disk without logging in</li>
<li><strong>Remote mode</strong> — log in to create an UnDercontrol task directly, with the snapshot uploaded as an attachment</li>
</ul>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>Researching competitors — clip product pages as tasks, then compare and organize on the web later</li>
<li>Found a great technical article — clip it as Markdown into ud for the team to share</li>
<li>Received a customer bug report on a web page — clip it as a bug task with full context attached</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="sse-real-time-sync">SSE Real-Time Sync<a href="https://oatnil.com/blog/2026/04/28/multi-platform#sse-real-time-sync" class="hash-link" aria-label="Direct link to SSE Real-Time Sync" title="Direct link to SSE Real-Time Sync">​</a></h3>
<p>All clients connected to the same backend — whether browser tabs, the desktop app, or another device — stay in sync via SSE (Server-Sent Events).</p>
<ul>
<li>Task status changes, new comments, attachment uploads — all events pushed in real time</li>
<li>Daemon SSE Hub dispatches commands to desktop Daemon instances</li>
<li>Workspace session state is automatically coordinated between the desktop app and the backend</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-clients.png" alt="Four official clients" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="one-codebase-consistent-experience">One Codebase, Consistent Experience<a href="https://oatnil.com/blog/2026/04/28/multi-platform#one-codebase-consistent-experience" class="hash-link" aria-label="Direct link to One Codebase, Consistent Experience" title="Direct link to One Codebase, Consistent Experience">​</a></h3>
<p>UnDercontrol's multi-platform strategy isn't "build once for each platform." Instead:</p>
<ul>
<li><strong>Web app</strong> is the single frontend codebase</li>
<li><strong>Electron</strong> loads the web build directly — zero code duplication</li>
<li><strong>CLI</strong> shares the same API and data model</li>
<li><strong>Chrome extension</strong> uses the same API endpoints</li>
</ul>
<table><thead><tr><th>Capability</th><th>Web</th><th>Desktop</th><th>CLI</th><th>Extension</th></tr></thead><tbody><tr><td>Remote API access</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Embedded backend</td><td>❌</td><td>✅ (switchable to remote)</td><td>❌</td><td>❌</td></tr><tr><td>Offline mode</td><td>❌</td><td>✅</td><td>❌</td><td>✅</td></tr><tr><td>System tray</td><td>❌</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>TUI interface</td><td>❌</td><td>❌</td><td>✅</td><td>❌</td></tr><tr><td>Background Daemon</td><td>❌</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>Web clipping</td><td>❌</td><td>❌</td><td>❌</td><td>✅</td></tr><tr><td>Real-time SSE</td><td>✅</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>AI Agent friendly</td><td>✅</td><td>✅</td><td>✅</td><td>❌</td></tr></tbody></table>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-capabilities.png" alt="Capability comparison table" class="img_ev3q"></p>
<p>And there's more — the open API lets you build any client you want.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="beyond-official-clients--build-your-own">Beyond Official Clients — Build Your Own<a href="https://oatnil.com/blog/2026/04/28/multi-platform#beyond-official-clients--build-your-own" class="hash-link" aria-label="Direct link to Beyond Official Clients — Build Your Own" title="Direct link to Beyond Official Clients — Build Your Own">​</a></h3>
<p>Beyond the four official clients, UnDercontrol exposes a full RESTful API. Generate an API Key in your profile settings, pair it with the Swagger docs, and build your own client in any language — Python scripts, automation bots, internal tool integrations, or even your own mobile app.</p>
<ul>
<li><strong>API Key authentication</strong> — generate in Profile → API Key, call with Bearer Token</li>
<li><strong>Scoped permissions</strong> — authorize independently by module (Tasks, Expenses, Budgets, Files, AI)</li>
<li><strong>Swagger docs</strong> — browse all endpoints at <code>https://your-server/swagger/index.html</code></li>
<li><strong>X-UD-Channel audit</strong> — custom channel identifier to trace every request's origin</li>
<li><strong>CI/CD friendly</strong> — environment variable configuration for seamless automation pipeline integration</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-custom.png" alt="Build your own client" class="img_ev3q"></p>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>Build a Slack Bot in Python — your team types <code>/task</code> in Slack to create and query ud tasks</li>
<li>Connect your internal admin panel's ticketing system to ud via API for bidirectional status sync</li>
<li>Build a native mobile app calling the same API for a personalized phone experience</li>
<li>Monitoring system alerts automatically create ud tasks with context attached — on-call engineers handle them on the web</li>
</ul>
<hr>
<p>Different scenarios, the right tool for the job — data always in sync, experience always consistent.</p>]]></content:encoded>
            <category>Feature</category>
            <category>platform</category>
            <category>self-hosted</category>
        </item>
        <item>
            <title><![CDATA[Rich Markdown in Your Tasks — Code, Diagrams, and More]]></title>
            <link>https://oatnil.com/blog/2026/04/27/markdown-features</link>
            <guid>https://oatnil.com/blog/2026/04/27/markdown-features</guid>
            <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[UnDercontrol tasks support full markdown with syntax-highlighted code blocks, Mermaid diagrams, tables, checklists, and a slash command menu for fast formatting.]]></description>
            <content:encoded><![CDATA[<h3 class="anchor anchorWithStickyNavbar_LWe7" id="what-is-a-task">What Is a Task?<a href="https://oatnil.com/blog/2026/04/27/markdown-features#what-is-a-task" class="hash-link" aria-label="Direct link to What Is a Task?" title="Direct link to What Is a Task?">​</a></h3>
<p>In UnDercontrol, a Task is the core unit of information. If you've used Jira, think of it as an Issue; if you've used Obsidian, think of it as a Note. A Task is essentially a piece of content bound to a status, with first-class attributes (title, tags, link relationships, etc.) and support for unlimited custom metadata fields (key-value pairs), so you can attach any information to it. Through the Notes mechanism, a Task can continuously evolve — recording progress, discussions, and decisions, accumulating into a complete knowledge context over time.</p>
<p>This post covers the rich-text capabilities shared across all text input surfaces in UnDercontrol. We say "tasks", but it's not just tasks — whether it's a task description, a note, an expense memo, or an account annotation, they all use the same Markdown editor. The code blocks, Mermaid diagrams, tables, and checklists you use in tasks work identically in notes and every other context.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="wysiwyg--a-markdown-editor-built-on-tiptap-3">WYSIWYG — A Markdown Editor Built on Tiptap 3<a href="https://oatnil.com/blog/2026/04/27/markdown-features#wysiwyg--a-markdown-editor-built-on-tiptap-3" class="hash-link" aria-label="Direct link to WYSIWYG — A Markdown Editor Built on Tiptap 3" title="Direct link to WYSIWYG — A Markdown Editor Built on Tiptap 3">​</a></h3>
<p>UnDercontrol's editor is built on <a href="https://tiptap.dev/" target="_blank" rel="noopener noreferrer">Tiptap 3</a>. You can write directly in Markdown syntax or use the visual toolbar — the editor renders in real time, what you write is what you see. No switching between "edit mode" and "preview mode" — the input is the final output.</p>
<p>The editor also supports switching to Source Mode to view and edit the raw Markdown directly — for users who prefer plain text editing or need precise format control, you can freely toggle between WYSIWYG and raw Markdown at any time.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="slash-commands--format-without-leaving-the-keyboard">Slash Commands — Format Without Leaving the Keyboard<a href="https://oatnil.com/blog/2026/04/27/markdown-features#slash-commands--format-without-leaving-the-keyboard" class="hash-link" aria-label="Direct link to Slash Commands — Format Without Leaving the Keyboard" title="Direct link to Slash Commands — Format Without Leaving the Keyboard">​</a></h3>
<p>Type <code>/</code> anywhere to open the command menu. Insert headings, code blocks, tables, Mermaid diagrams, checklists, and more — all without touching the mouse.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/slide-2.png" alt="Slash command menu in the editor" class="img_ev3q"></p>
<p>The slash menu supports:</p>
<ul>
<li><strong>Headings</strong> (H1–H5) for document structure</li>
<li><strong>Task lists</strong> with interactive checkboxes</li>
<li><strong>Code blocks</strong> with language selection and syntax highlighting</li>
<li><strong>Mermaid diagrams</strong> for flowcharts, sequence diagrams, and more</li>
<li><strong>Tables</strong> with full cell editing</li>
<li><strong>Blockquotes</strong>, dividers, and images</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="syntax-highlighted-code-blocks">Syntax-Highlighted Code Blocks<a href="https://oatnil.com/blog/2026/04/27/markdown-features#syntax-highlighted-code-blocks" class="hash-link" aria-label="Direct link to Syntax-Highlighted Code Blocks" title="Direct link to Syntax-Highlighted Code Blocks">​</a></h3>
<p>Paste code snippets directly into your tasks. The editor supports 100+ programming languages with proper syntax highlighting — from TypeScript and Go to SQL and YAML.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/api-task-top.png" alt="Task with syntax-highlighted TypeScript code block" class="img_ev3q"></p>
<p>Code blocks include a language selector dropdown and a one-click copy button. Your code stays readable and properly formatted, whether you're documenting an API endpoint or saving a useful shell command.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="mermaid-diagrams--visualize-architecture-inline">Mermaid Diagrams — Visualize Architecture Inline<a href="https://oatnil.com/blog/2026/04/27/markdown-features#mermaid-diagrams--visualize-architecture-inline" class="hash-link" aria-label="Direct link to Mermaid Diagrams — Visualize Architecture Inline" title="Direct link to Mermaid Diagrams — Visualize Architecture Inline">​</a></h3>
<p>One of the most powerful features: embed <a href="https://mermaid.js.org/" target="_blank" rel="noopener noreferrer">Mermaid</a> diagrams directly in your task descriptions. Insert a Mermaid block via the slash menu, write your diagram syntax, and it renders live.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/arch-task-top.png" alt="Mermaid flowchart showing microservices architecture" class="img_ev3q"></p>
<p>Supported diagram types include:</p>
<ul>
<li><strong>Flowcharts</strong> — system architecture, decision trees</li>
<li><strong>Sequence diagrams</strong> — API flows, authentication handshakes</li>
<li><strong>Class diagrams</strong> — data models, entity relationships</li>
<li><strong>State diagrams</strong> — workflow states, lifecycle tracking</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/api-task-mermaid.png" alt="Mermaid sequence diagram showing authentication flow" class="img_ev3q"></p>
<p>The diagram viewer supports fullscreen preview, SVG download, and automatic dark/light theme switching.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="tables-for-structured-data">Tables for Structured Data<a href="https://oatnil.com/blog/2026/04/27/markdown-features#tables-for-structured-data" class="hash-link" aria-label="Direct link to Tables for Structured Data" title="Direct link to Tables for Structured Data">​</a></h3>
<p>Need to document status codes, compare metrics, or track a feature matrix? Insert a table and edit cells directly. Add or remove rows and columns, toggle header rows, and merge cells — all from a context menu.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/api-task-table-checklist.png" alt="Table with status codes and interactive checklist" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="checklists-that-actually-work">Checklists That Actually Work<a href="https://oatnil.com/blog/2026/04/27/markdown-features#checklists-that-actually-work" class="hash-link" aria-label="Direct link to Checklists That Actually Work" title="Direct link to Checklists That Actually Work">​</a></h3>
<p>Task lists render as interactive checkboxes. Check items off directly in the rendered view — no need to switch to edit mode. Great for tracking sub-steps, acceptance criteria, or deployment checklists.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/slide-5.png" alt="Interactive checklist in task description" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="images--attachments--no-more-obsidian-style-resource-management-headaches">Images &amp; Attachments — No More Obsidian-Style Resource Management Headaches<a href="https://oatnil.com/blog/2026/04/27/markdown-features#images--attachments--no-more-obsidian-style-resource-management-headaches" class="hash-link" aria-label="Direct link to Images &amp; Attachments — No More Obsidian-Style Resource Management Headaches" title="Direct link to Images &amp; Attachments — No More Obsidian-Style Resource Management Headaches">​</a></h3>
<p>If you've managed notes with images in Obsidian, you've probably experienced the pain: images scattered across local folders, paths break when you move things, attachments disappear on another device, multi-device sync creates endless conflicts. The root cause — Obsidian delegates resource management to the filesystem, and filesystems are inherently bad at cross-device sync.</p>
<p>UnDercontrol solves this at the foundation. All images and attachments are managed through the <code>resource://</code> protocol — upload once, stored in the database, no dependency on local paths. Whether you view content on the web, desktop, or via a shared link, images are always available — because resources follow the database, not the filesystem.</p>
<p>UnDercontrol also provides bidirectional local folder sync, keeping files in a local directory synchronized with the server resource library. We'll cover this feature in detail in a future post.</p>
<p><strong>Image Size Control</strong></p>
<p>Inserted images support three size presets — hover in edit mode to switch:</p>
<ul>
<li><strong>Small (25%)</strong> — thumbnail, suitable for inline display</li>
<li><strong>Medium (50%)</strong> — moderate size</li>
<li><strong>Large (100%)</strong> — full width</li>
</ul>
<p>Size information is stored in Obsidian-compatible format (<code>![description|s](resource://id)</code>). Click any image for fullscreen preview.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/image-size-controls.png" alt="Image size controls — S, M, L presets" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="drawio-diagrams--built-in-visual-drawing">Draw.io Diagrams — Built-in Visual Drawing<a href="https://oatnil.com/blog/2026/04/27/markdown-features#drawio-diagrams--built-in-visual-drawing" class="hash-link" aria-label="Direct link to Draw.io Diagrams — Built-in Visual Drawing" title="Direct link to Draw.io Diagrams — Built-in Visual Drawing">​</a></h3>
<p>UnDercontrol includes a built-in Draw.io editor, supporting <code>.drawio</code> and <code>.drawio.png</code> formats. Upload or create a Draw.io file, then edit it directly in the app — no desktop software needed. Saved as PNG with embedded XML source data, so it renders as a preview and can be re-edited at any time.</p>
<p>Thanks to the <code>resource://</code> protocol, Draw.io diagrams are managed as unified resources — no local path dependencies or sync issues.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="ai-powered-text-actions">AI-Powered Text Actions<a href="https://oatnil.com/blog/2026/04/27/markdown-features#ai-powered-text-actions" class="hash-link" aria-label="Direct link to AI-Powered Text Actions" title="Direct link to AI-Powered Text Actions">​</a></h3>
<p>Select any text to reveal the bubble menu. Beyond standard formatting (bold, italic, strikethrough), you get AI-powered actions:</p>
<ul>
<li><strong>Refine</strong> — rewrite selected text for clarity</li>
<li><strong>Translate</strong> — translate to another language instantly</li>
<li><strong>Chat</strong> — ask questions about the selected content</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/slide-7.png" alt="Feature overview — everything you need to document" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="entity-links--connect-tasks-like-a-wiki">Entity Links — Connect Tasks Like a Wiki<a href="https://oatnil.com/blog/2026/04/27/markdown-features#entity-links--connect-tasks-like-a-wiki" class="hash-link" aria-label="Direct link to Entity Links — Connect Tasks Like a Wiki" title="Direct link to Entity Links — Connect Tasks Like a Wiki">​</a></h3>
<p>Link to other tasks, notes, expenses, budgets, and accounts directly in your descriptions using custom protocols like <code>task://</code>, <code>note://</code>, and more. The links render as clickable references — click to jump straight to the referenced entity.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/entity-links-rendered.png" alt="Rendered entity links in a task description" class="img_ev3q"></p>
<p>Switch to source mode to see the raw markdown — each link uses a custom protocol like <code>[API Integration Guide](task://189571b0-...)</code>:</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/entity-links-raw.png" alt="Raw markdown showing task:// protocol links" class="img_ev3q"></p>
<p>This turns your task descriptions into a connected wiki, where context flows naturally between related items.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="cli--ai-agent--let-ai-write-your-content">CLI + AI Agent — Let AI Write Your Content<a href="https://oatnil.com/blog/2026/04/27/markdown-features#cli--ai-agent--let-ai-write-your-content" class="hash-link" aria-label="Direct link to CLI + AI Agent — Let AI Write Your Content" title="Direct link to CLI + AI Agent — Let AI Write Your Content">​</a></h3>
<p>Because Markdown is plain text, it's a natural fit for AI agent collaboration. Through the <code>ud</code> CLI, you can let Claude Code, Codex, OpenCode, or any terminal-based AI tool read, write, and update task content directly:</p>
<ul>
<li><strong>Summarize</strong> — have AI read a set of tasks and generate a weekly report or sprint review</li>
<li><strong>Refine &amp; restructure</strong> — turn scattered notes into structured documentation</li>
<li><strong>Classify &amp; tag</strong> — automatically add tags and categories based on content</li>
<li><strong>Batch create</strong> — generate multiple tasks from meeting notes or requirement docs</li>
</ul>
<p>Content pipes directly in:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cat &lt;&lt;'EOF' | ud apply -f -</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">title: API Integration Guide</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">status: in-progress</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tags: [api, backend]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">## Authentication Flow</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">1. Client sends credentials to `/auth/login`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">2. Server returns JWT token</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">3. Include in `Authorization: Bearer &lt;token&gt;` header</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></span></code></pre></div></div>
<p>AI-generated Markdown renders identically to hand-written Markdown across web, desktop, and shared views. Your content stays portable and version-controllable.</p>
<hr>
<p>Markdown support in UnDercontrol means your tasks can be as detailed and technical as they need to be — without leaving the tool where the work gets tracked. Code blocks, diagrams, tables, and checklists all live alongside your task status, deadlines, and collaboration context.</p>
<p>Try it out — create a task and type <code>/</code> to see what's possible.</p>]]></content:encoded>
            <category>Feature</category>
            <category>markdown</category>
            <category>productivity</category>
        </item>
        <item>
            <title><![CDATA[Remote Workspace — Run AI Agents on Your Machine from the Browser]]></title>
            <link>https://oatnil.com/blog/2026/04/25/remote-workspace</link>
            <guid>https://oatnil.com/blog/2026/04/25/remote-workspace</guid>
            <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How UnDercontrol's remote workspace feature lets you trigger AI agents like Claude Code on your local machine directly from the desktop app, with real-time progress tracking.]]></description>
            <content:encoded><![CDATA[<p>You write a task description in the web UI. You click a button. Thirty seconds later, an AI agent on your laptop is reading the task, writing code, running tests, and posting progress notes back to the same task page — all while you watch from the browser.</p>
<p>That's Remote Workspace. It bridges the gap between a cloud-based task manager and your local development environment, turning UnDercontrol into a remote control for AI-powered coding agents.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-problem">The Problem<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#the-problem" class="hash-link" aria-label="Direct link to The Problem" title="Direct link to The Problem">​</a></h2>
<p>Most task management tools live entirely in the browser. Your code lives on your machine. When you want an AI agent to work on something, you switch to a terminal, paste context, babysit the process, and manually update the task when it's done. The task tracker and the execution environment are two separate worlds.</p>
<p>Remote Workspace collapses that distance. Your task descriptions become executable instructions. Your browser becomes a control panel. Your machine does the work.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-it-works">How It Works<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works">​</a></h2>
<p>The architecture has four pieces:</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/slide-2.png" alt="Architecture: Web UI connects to Server via REST, Server connects to Daemon via SSE, Daemon spawns AI Agent via PTY" class="img_ev3q"></p>
<p><strong>1. The Electron Desktop App</strong> — runs on your machine and acts as the daemon. When you register your device in the Workspaces page, the app connects to the UnDercontrol server via Server-Sent Events (SSE) and waits for instructions.</p>
<p><strong>2. The Server</strong> — relays commands between the web UI and the daemon. Persists session state, notes, and status updates.</p>
<p><strong>3. The Daemon Core</strong> — the "brain" inside the Electron renderer. It holds auth tokens, manages SSE connections, and drives all API calls. When a workspace session starts, it forwards the init event to the Electron main process.</p>
<p><strong>4. The AI Agent</strong> — a coding tool (like Claude Code) spawned in a pseudo-terminal by the Electron main process. It reads the task, does the work, and reports progress through task notes.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="setting-it-up">Setting It Up<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#setting-it-up" class="hash-link" aria-label="Direct link to Setting It Up" title="Direct link to Setting It Up">​</a></h2>
<p>Open the UnDercontrol desktop app and navigate to the <strong>Workspaces</strong> page. Click <strong>Register this device</strong> — the app detects your machine name and platform automatically. That's it. Your machine is now a daemon, connected and ready.</p>
<p>The Electron app handles everything behind the scenes: daemon registration, SSE connection, heartbeat, and reconnection. No terminal commands needed.</p>
<p>For the AI agent to work, you'll need <strong>Claude Code</strong> installed on your machine:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npm install -g @anthropic-ai/claude-code</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="triggering-a-session">Triggering a Session<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#triggering-a-session" class="hash-link" aria-label="Direct link to Triggering a Session" title="Direct link to Triggering a Session">​</a></h2>
<p>Once your device is registered and online, go to any task in the web UI (or the desktop app itself). Click the globe icon in the actions bar. You'll see a list of your connected daemons — pick one, and the session starts.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/task-detail.png" alt="Task detail page showing the task description and workspace controls" class="img_ev3q"></p>
<p>The daemon receives the init event, and the Electron main process spawns the AI agent in a PTY window. From this point, everything happens automatically:</p>
<ol>
<li>The agent reads the task description and notes</li>
<li>It plans and executes the work</li>
<li>Progress notes appear on the task in real-time</li>
<li>When done, it updates the task status</li>
</ol>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="watching-it-work">Watching It Work<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#watching-it-work" class="hash-link" aria-label="Direct link to Watching It Work" title="Direct link to Watching It Work">​</a></h2>
<p>This is where it gets satisfying. As the agent runs on your machine, you see its progress in the browser — live.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/slide-3.png" alt="How it works: five steps from starting the daemon to watching live progress" class="img_ev3q"></p>
<p>Notes stream in as the agent works: what it's reading, what it changed, which files it committed. You get a running log of the entire session without touching the terminal.</p>
<p>The session panel shows:</p>
<ul>
<li><strong>Status</strong> — running, planning, awaiting input, idle, or stopped</li>
<li><strong>Duration</strong> — how long the session has been active</li>
<li><strong>Daemon info</strong> — which machine is doing the work</li>
<li><strong>Notes stream</strong> — real-time updates from the agent</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="interactive-control">Interactive Control<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#interactive-control" class="hash-link" aria-label="Direct link to Interactive Control" title="Direct link to Interactive Control">​</a></h2>
<p>Remote Workspace isn't fire-and-forget. You can interact with the agent while it's running:</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/slide-4.png" alt="Interactive controls: send instructions, screenshots, interrupt, and stop" class="img_ev3q"></p>
<ul>
<li><strong>Send instructions</strong> — type additional context or redirect the agent mid-task</li>
<li><strong>Take screenshots</strong> — capture the current state of the workspace</li>
<li><strong>Interrupt</strong> — send a Ctrl+C signal to pause execution</li>
<li><strong>Stop</strong> — end the session entirely</li>
</ul>
<p>There's also a prompt system. Save frequently-used instructions as templates, and apply them with one click. If you've set up skills in UnDercontrol, you can reference those too — pipe a skill's content directly to the agent.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-workspaces-dashboard">The Workspaces Dashboard<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#the-workspaces-dashboard" class="hash-link" aria-label="Direct link to The Workspaces Dashboard" title="Direct link to The Workspaces Dashboard">​</a></h2>
<p>The Workspaces page gives you a bird's-eye view of all active sessions and connected daemons.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/workspaces-dashboard.png" alt="Workspaces dashboard showing active sessions and connected daemons" class="img_ev3q"></p>
<p>Each session card shows the task it's working on, which daemon is running it, how long it's been going, and the latest notes. Daemons are listed with their online/offline status, machine name, platform, and sharing permissions.</p>
<p>You can register your device right from this page — one click and you're online.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="security">Security<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#security" class="hash-link" aria-label="Direct link to Security" title="Direct link to Security">​</a></h2>
<p>Running AI agents remotely raises obvious safety questions. Remote Workspace handles this with a layered blacklist system:</p>
<p><strong>Built-in protections:</strong></p>
<ul>
<li>Tools like <code>bash</code>, <code>sh</code>, <code>zsh</code> are blocked as the implementation tool — the agent runs through a controlled interface, not a raw shell</li>
<li>Destructive commands (<code>rm -rf</code>, <code>mkfs</code>, <code>dd</code>) are blocked at the daemon level</li>
</ul>
<p><strong>Custom configuration:</strong> Drop a YAML file at <code>~/.config/ud/workspace-blacklist.yml</code> to define your own rules:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">blocked_tools</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> bash</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> sh</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">allowed_tools</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> claude</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">blocked_commands</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"rm -rf"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mkfs"</span><br></span></code></pre></div></div>
<p>The daemon also requires proper authentication — the Electron app uses your logged-in session tokens, so unauthorized access isn't possible without your credentials.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="sharing-daemons">Sharing Daemons<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#sharing-daemons" class="hash-link" aria-label="Direct link to Sharing Daemons" title="Direct link to Sharing Daemons">​</a></h2>
<p>In team setups, you can share a daemon with your group. Set the sharing permissions so teammates can trigger workspace sessions on your machine — useful for shared build servers or dedicated CI machines.</p>
<p>Sharing is controlled through the daemon's permissions: read-only lets others see the daemon status, read-write lets them initiate sessions.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-this-matters">Why This Matters<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#why-this-matters" class="hash-link" aria-label="Direct link to Why This Matters" title="Direct link to Why This Matters">​</a></h2>
<p>Most developer tools treat task management and code execution as separate concerns. You plan in one app, execute in another, and manually bridge the gap with copy-paste and context switching.</p>
<p>Remote Workspace merges them. Your task description <em>is</em> the instruction. Your browser <em>is</em> the control panel. The work happens on your machine, with your tools, in your environment — you just don't have to be in the terminal to start it.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/slide-5.png" alt="Security layers: tool blacklist, command blacklist, API key auth, custom config" class="img_ev3q"></p>
<p>For solo developers, it means less context switching. Write the task, trigger the agent, review the output — all in the same interface. For teams, it means a shared queue of work that machines can pick up and execute, with full visibility into what's happening.</p>
<p>The architecture also means you're not locked into any particular AI tool. Today it works with Claude Code; tomorrow it could work with any agent that runs in a terminal. The protocol is simple: receive a task, do the work, report progress.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting Started<a href="https://oatnil.com/blog/2026/04/25/remote-workspace#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started">​</a></h2>
<ol>
<li>Download the <a href="https://undercontrol.app/" target="_blank" rel="noopener noreferrer">UnDercontrol desktop app</a></li>
<li>Install Claude Code: <code>npm install -g @anthropic-ai/claude-code</code></li>
<li>Open the Workspaces page and click <strong>Register this device</strong></li>
<li>Open any task, click the globe icon, and select your daemon</li>
</ol>
<p>Check the <a href="https://oatnil.com/docs/workspace-terminal">documentation</a> for the full setup guide, including security configuration and troubleshooting.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Share a Task Without Sharing Your Password]]></title>
            <link>https://oatnil.com/blog/2026/04/24/task-sharing</link>
            <guid>https://oatnil.com/blog/2026/04/24/task-sharing</guid>
            <pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How UnDercontrol's task sharing lets you generate public links with access codes, expiration controls, and QR codes — no login required for viewers.]]></description>
            <content:encoded><![CDATA[<p>You're at a coffee shop. Someone asks about that project plan you've been working on. You could pull up your account, log in, and hand them your laptop — but you'd rather not.</p>
<p>UnDercontrol's task sharing solves this exact problem. Generate a public link, hand over a short access code, and anyone can view that specific task — your description, notes, attachments, the whole thing — without needing an account or touching your credentials.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-it-works">How It Works<a href="https://oatnil.com/blog/2026/04/24/task-sharing#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works">​</a></h2>
<p>From any task detail page, click the share icon to open the share dialog. You get two things:</p>
<ol>
<li><strong>A direct link</strong> — a full URL that opens the task in a clean, read-only view</li>
<li><strong>An access code</strong> — a short alphanumeric code (like <code>QJVOFL</code>) that someone can type into the share code page</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-sharing/share-dialog.png" alt="Share dialog showing configuration options, active links with access code QJVOFL" class="img_ev3q"></p>
<p>The access code is designed for situations where you can't easily send a link — like telling someone over the phone, writing it on a whiteboard, or dropping it in a chat where long URLs get mangled.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="control-what-gets-shared">Control What Gets Shared<a href="https://oatnil.com/blog/2026/04/24/task-sharing#control-what-gets-shared" class="hash-link" aria-label="Direct link to Control What Gets Shared" title="Direct link to Control What Gets Shared">​</a></h2>
<p>Not every task should be shared the same way. The share dialog gives you two controls:</p>
<p><strong>Expiration</strong>: Choose how long the link stays active. Options range from 1 hour (for quick look-overs) to 7 days (for ongoing collaboration) to never (for permanent reference links).</p>
<p><strong>Attachment access</strong>: By default, attachments are listed but not downloadable. Toggle "Allow attachments" if you want viewers to download files attached to the task.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-sharing/slide-2.png" alt="Flow diagram: Your Task → Generate Link → Share → Anyone Views" class="img_ev3q"></p>
<p>You can create multiple share links for the same task with different settings — maybe a 1-hour link for a quick review and a 7-day link for a collaborator.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-viewer-experience">The Viewer Experience<a href="https://oatnil.com/blog/2026/04/24/task-sharing#the-viewer-experience" class="hash-link" aria-label="Direct link to The Viewer Experience" title="Direct link to The Viewer Experience">​</a></h2>
<p>When someone opens a shared link, they see a clean, focused view of the task:</p>
<ul>
<li><strong>Title and status</strong> with the familiar status icon</li>
<li><strong>Tags</strong> for context</li>
<li><strong>Full markdown description</strong> rendered with all formatting, tables, and images</li>
<li><strong>Notes</strong> sorted by most recent, also fully rendered</li>
<li><strong>Attachments</strong> (downloadable if the link allows it)</li>
<li><strong>Linked items</strong> shown as references</li>
<li><strong>QR code</strong> in the corner for easy re-sharing on mobile</li>
<li><strong>Expiration countdown</strong> so viewers know how long the link is valid</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-sharing/shared-view.png" alt="Shared task view with title, QR code, markdown description, and expiration countdown" class="img_ev3q"></p>
<p>The page is self-contained. No navigation chrome, no login prompts, no app shell. Just the content.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="access-codes--the-analog-bridge">Access Codes — The Analog Bridge<a href="https://oatnil.com/blog/2026/04/24/task-sharing#access-codes--the-analog-bridge" class="hash-link" aria-label="Direct link to Access Codes — The Analog Bridge" title="Direct link to Access Codes — The Analog Bridge">​</a></h2>
<p>The access code feature deserves its own mention. Navigate to the share page (<code>/share</code>), type in the code, and you're redirected to the shared task.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-sharing/code-entry.png" alt="Access code entry page with large monospace input showing QJVOFL" class="img_ev3q"></p>
<p>This is particularly useful for:</p>
<ul>
<li><strong>Presentations</strong>: Put the code on a slide, let the audience look up the reference material</li>
<li><strong>Phone calls</strong>: "Check out task code QJVOFL" is easier than reading a 60-character URL</li>
<li><strong>Physical spaces</strong>: Write the code on a sticky note or whiteboard</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="managing-share-links">Managing Share Links<a href="https://oatnil.com/blog/2026/04/24/task-sharing#managing-share-links" class="hash-link" aria-label="Direct link to Managing Share Links" title="Direct link to Managing Share Links">​</a></h2>
<p>Back in the share dialog, you can see all active links for a task. Each entry shows:</p>
<ul>
<li>The access code and a truncated token</li>
<li>Whether attachments are enabled (paperclip icon)</li>
<li>When it expires (relative time)</li>
</ul>
<p>Revoke any link instantly with the delete button. The shared view will immediately show an error for anyone who tries to access it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="under-the-hood">Under the Hood<a href="https://oatnil.com/blog/2026/04/24/task-sharing#under-the-hood" class="hash-link" aria-label="Direct link to Under the Hood" title="Direct link to Under the Hood">​</a></h2>
<p>Sharing is token-based. Each share link gets a unique token that maps to the task. The public endpoint (<code>/share/todolist/:token</code>) requires no authentication — it's a true public route. Resources embedded in the markdown description (images, diagrams) are resolved through the share token too, so inline images render correctly without exposing your storage credentials.</p>
<p>The access code is a separate entity that maps to the token, providing an additional access path without compromising security. Codes are uppercase alphanumeric for readability.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="when-to-use-this">When to Use This<a href="https://oatnil.com/blog/2026/04/24/task-sharing#when-to-use-this" class="hash-link" aria-label="Direct link to When to Use This" title="Direct link to When to Use This">​</a></h2>
<p>A few scenarios where task sharing shines:</p>
<ul>
<li><strong>Cross-team handoffs</strong>: Share a requirements doc with someone outside your organization</li>
<li><strong>Client updates</strong>: Send a project status task to a client without giving them app access</li>
<li><strong>Meeting prep</strong>: Share an agenda task before a meeting via access code</li>
<li><strong>Knowledge sharing</strong>: Create a permanent link to a reference document or runbook</li>
<li><strong>Quick reviews</strong>: Generate a 1-hour link for a quick peer review</li>
</ul>
<p>The key insight is that not every piece of information needs to live behind a login wall. Sometimes you just need to show someone a thing, quickly, without friction.</p>
<hr>
<p>Task sharing is available now in UnDercontrol. Open any task, click share, and try it out.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Kanban Board Filters: 4 Levels from Broad Scope to Laser Focus]]></title>
            <link>https://oatnil.com/blog/2026/04/14/kanban-board-filters</link>
            <guid>https://oatnil.com/blog/2026/04/14/kanban-board-filters</guid>
            <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[UnDercontrol's kanban board uses a 4-level filtering system — board scope, scope tags, column queries, and ephemeral filters — to let you control exactly which tasks appear where.]]></description>
            <content:encoded><![CDATA[<p>When you have dozens or hundreds of tasks, the kanban board needs to be more than a flat dump of everything. UnDercontrol's kanban boards use a layered filtering system that narrows your view step by step — from the broadest scope down to a real-time search. Understanding these four levels will change how you organize your work.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-four-levels">The Four Levels<a href="https://oatnil.com/blog/2026/04/14/kanban-board-filters#the-four-levels" class="hash-link" aria-label="Direct link to The Four Levels" title="Direct link to The Four Levels">​</a></h2>
<p>Here is the mental model: each filter level narrows what the previous level passed through. Think of it as a pipeline.</p>
<p><strong>Level 1: Board Scope</strong> (private vs shared) → <strong>Level 2: Scope Tags</strong> (board-level tag filter) → <strong>Level 3: Column Query</strong> (per-column conditions) → <strong>Level 4: Ephemeral Filter</strong> (search, tags, assignees in the toolbar)</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-board-filters/slide-6.png" alt="How the 4 filter levels combine — from all tasks to your exact focus" class="img_ev3q"></p>
<p>Let us walk through each one.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="level-1-board-scope--who-sees-what">Level 1: Board Scope — Who Sees What<a href="https://oatnil.com/blog/2026/04/14/kanban-board-filters#level-1-board-scope--who-sees-what" class="hash-link" aria-label="Direct link to Level 1: Board Scope — Who Sees What" title="Direct link to Level 1: Board Scope — Who Sees What">​</a></h2>
<p>This is the implicit layer. When you create a board, you choose between <strong>private</strong> and <strong>shared</strong>.</p>
<ul>
<li><strong>Private boards</strong> show all tasks you have access to — your own tasks plus tasks others have shared with you. This means tasks from multiple people can appear together on one board, giving you a single place to see everything relevant to you. Only you can see this board and its arrangement.</li>
<li><strong>Shared boards</strong> are tied to a group. The board only shows tasks that belong to that group, and everyone in the group can see and interact with it based on their permissions.</li>
</ul>
<p>You do not configure this as a "filter" — it is baked into the board's identity. But it is the first gate that determines which tasks are even candidates for display.</p>
<p>The private board model is particularly useful when several people share tasks with you individually. Instead of jumping between different sources, you see all of them in one kanban view — your own work alongside tasks shared by teammates, collaborators, or managers.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="level-2-scope-tags--narrowing-the-boards-focus">Level 2: Scope Tags — Narrowing the Board's Focus<a href="https://oatnil.com/blog/2026/04/14/kanban-board-filters#level-2-scope-tags--narrowing-the-boards-focus" class="hash-link" aria-label="Direct link to Level 2: Scope Tags — Narrowing the Board's Focus" title="Direct link to Level 2: Scope Tags — Narrowing the Board's Focus">​</a></h2>
<p>This is where things get powerful. Each board can have <strong>scope tags</strong> (also called default tags) — a set of tags that act as a persistent filter for the entire board.</p>
<p>When you set scope tags on a board:</p>
<ol>
<li><strong>Only tasks with ALL specified tags appear</strong> on the board</li>
<li><strong>New tasks created on the board automatically get these tags</strong>, so they match the filter from the start</li>
</ol>
<p>For example, a "Sprint 1" board with scope tag <code>sprint-1</code> will only show tasks that have the <code>sprint-1</code> tag. Your "All Tasks" view might show 40+ tasks across all sprints, but the Sprint 1 board shows only the 8 that matter right now.</p>
<p>This is configured in <strong>Settings → Edit Board Details → Scope Tags</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-board-filters/scope-tags-settings.png" alt="Board settings showing scope tag configuration" class="img_ev3q"></p>
<p>The key insight: scope tags create a <strong>named, persistent view</strong> over your task pool. You can have a "Frontend" board (scope tag: <code>frontend</code>), a "Sprint 2" board (scope tag: <code>sprint-2</code>), and they all draw from the same tasks. Change a task's tags and it automatically appears or disappears from the relevant boards.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-board-filters/sprint1-board.png" alt="Sprint 1 board showing only sprint-1 tagged tasks" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="level-3-column-query--sorting-tasks-into-lanes">Level 3: Column Query — Sorting Tasks into Lanes<a href="https://oatnil.com/blog/2026/04/14/kanban-board-filters#level-3-column-query--sorting-tasks-into-lanes" class="hash-link" aria-label="Direct link to Level 3: Column Query — Sorting Tasks into Lanes" title="Direct link to Level 3: Column Query — Sorting Tasks into Lanes">​</a></h2>
<p>Within each board, every column has its own <strong>match condition</strong> — a query that determines which tasks land in that column.</p>
<p>The most common pattern is filtering by status:</p>
<ul>
<li><strong>To Do</strong> column: <code>Status = 'todo'</code></li>
<li><strong>In Progress</strong> column: <code>Status = 'in-progress'</code></li>
<li><strong>Done</strong> column: <code>Status = 'done'</code></li>
</ul>
<p>But columns can use any field and operator. You could create a column for <code>tags CONTAINS 'urgent'</code>, or <code>cf.priority &gt;= '3'</code>, or combine multiple conditions with AND/OR logic.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-board-filters/column-query.png" alt="Column condition editor showing Status = todo match condition" class="img_ev3q"></p>
<p>The powerful part: columns also have <strong>auto-actions</strong>. When you drag a task into a column, its conditions are applied automatically. Drag a task into "Done" and its status changes to <code>done</code>. Drag it into a column filtered by <code>tags CONTAINS 'reviewed'</code> and the tag gets added. The board does the bookkeeping.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="level-4-ephemeral-board-filter--real-time-refinement">Level 4: Ephemeral Board Filter — Real-Time Refinement<a href="https://oatnil.com/blog/2026/04/14/kanban-board-filters#level-4-ephemeral-board-filter--real-time-refinement" class="hash-link" aria-label="Direct link to Level 4: Ephemeral Board Filter — Real-Time Refinement" title="Direct link to Level 4: Ephemeral Board Filter — Real-Time Refinement">​</a></h2>
<p>The top toolbar provides three real-time filters that layer on top of everything else:</p>
<ul>
<li><strong>Search</strong>: Type in the search bar to filter by task title across all columns</li>
<li><strong>Tags</strong>: Click the Tags button to select one or more tags — only tasks with ALL selected tags appear</li>
<li><strong>Assignees</strong>: Filter by who is assigned to the task</li>
</ul>
<p>These filters are <strong>ephemeral</strong> — they do not modify the board permanently. Close the tab and they are gone. But they are <strong>URL-persisted</strong>: the filter state is encoded in the URL as query parameters like <code>?tags=backend&amp;search=API</code>.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-board-filters/ephemeral-filter.png" alt="Ephemeral filter bar with search and tag filter active" class="img_ev3q"></p>
<p>This means you can bookmark a filtered view, or share the URL with a teammate to show them exactly the subset of tasks you are looking at.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-the-levels-combine">How the Levels Combine<a href="https://oatnil.com/blog/2026/04/14/kanban-board-filters#how-the-levels-combine" class="hash-link" aria-label="Direct link to How the Levels Combine" title="Direct link to How the Levels Combine">​</a></h2>
<p>Here is a concrete example showing all four levels working together:</p>
<ol>
<li><strong>Board Scope</strong>: You open your private "Sprint 1" board</li>
<li><strong>Scope Tags</strong>: The board has scope tag <code>sprint-1</code>, narrowing from 40+ tasks to 8</li>
<li><strong>Column Query</strong>: The "In Progress" column shows <code>status = 'in-progress'</code>, narrowing to 3 tasks</li>
<li><strong>Ephemeral Filter</strong>: You type "API" in the search bar, narrowing to 1 task — "Update API documentation"</li>
</ol>
<p>Each level operates independently. You can change any layer without affecting the others. Switch the scope tags and the columns still work. Apply a search filter and the column queries remain unchanged.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="practical-patterns">Practical Patterns<a href="https://oatnil.com/blog/2026/04/14/kanban-board-filters#practical-patterns" class="hash-link" aria-label="Direct link to Practical Patterns" title="Direct link to Practical Patterns">​</a></h2>
<p><strong>Sprint planning</strong>: Create a board per sprint with the sprint tag as scope tag. All sprints share the same tasks, but each board shows only its sprint.</p>
<p><strong>Cross-cutting views</strong>: A "Frontend" board and a "Backend" board can both show tasks from Sprint 1 and Sprint 2 — they just use different scope tags. A task tagged <code>frontend</code> + <code>sprint-1</code> appears on both the Frontend board and the Sprint 1 board.</p>
<p><strong>Quick triage</strong>: Use ephemeral filters to zoom into a specific area during a standup. Filter by assignee to see what each person is working on, then clear the filter to see the full picture.</p>
<p><strong>Complex columns</strong>: Instead of simple status columns, create columns like "Blocked Backend" (<code>status = 'in-progress' AND tags CONTAINS 'blocked' AND tags CONTAINS 'backend'</code>) for specialized workflow views.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-filter-is-the-control">The Filter is the Control<a href="https://oatnil.com/blog/2026/04/14/kanban-board-filters#the-filter-is-the-control" class="hash-link" aria-label="Direct link to The Filter is the Control" title="Direct link to The Filter is the Control">​</a></h2>
<p>This layered approach follows one of UnDercontrol's core design principles: the filter is the control. You are not navigating through menus to manage what appears where. The board's scope tags, each column's query, and the toolbar's search and tag badges are all visible, interactive, and directly editable. You can see exactly what is filtering your view, and change it with a click.</p>
<p>The URL-persisted ephemeral filter takes this further — your current view state is always shareable and bookmarkable, turning a transient search into a reusable link.</p>]]></content:encoded>
            <category>Feature</category>
            <category>kanban</category>
        </item>
        <item>
            <title><![CDATA[Reusable AI Skills — Prompt Templates You Own]]></title>
            <link>https://oatnil.com/blog/2026/04/12/skills-system</link>
            <guid>https://oatnil.com/blog/2026/04/12/skills-system</guid>
            <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how UnDercontrol's skills system lets you create, manage, and reuse prompt templates across your team — piped directly into Claude Code or any AI agent.]]></description>
            <content:encoded><![CDATA[<p>If you've spent any time working with AI assistants, you've probably built up a mental library of prompts that actually work. The refactoring prompt that always gets clean output. The commit message template that matches your team's style. The bug-triage checklist you paste in every time something breaks in production.</p>
<p>The problem is those prompts live in your head, or a sticky note, or buried in a Notion doc nobody remembers to open. UnDercontrol's skills system fixes that.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-are-skills">What Are Skills?<a href="https://oatnil.com/blog/2026/04/12/skills-system#what-are-skills" class="hash-link" aria-label="Direct link to What Are Skills?" title="Direct link to What Are Skills?">​</a></h2>
<p>Skills are named, reusable prompt templates stored inside UnDercontrol. Each skill has a slug (e.g., <code>refactor-ts</code>, <code>daily-standup</code>, <code>pr-review</code>), a markdown body, and belongs to a group. That last part matters — skills are group-scoped, so a team can share a library of prompts without everyone maintaining their own copy.</p>
<p>You can create and edit skills through the web UI, manage them from the CLI, or apply them declaratively from a YAML file. The markdown content supports the full range of prompt structures: instructions, variables, multi-step workflows, whatever your use case needs.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="creating-a-skill">Creating a Skill<a href="https://oatnil.com/blog/2026/04/12/skills-system#creating-a-skill" class="hash-link" aria-label="Direct link to Creating a Skill" title="Direct link to Creating a Skill">​</a></h2>
<p>From the web editor, creating a skill is about as straightforward as writing a note. Give it a name, a slug, and write your prompt in the markdown editor. Save it, and it's immediately available to everyone in the group.</p>
<p>From the CLI, you can apply a skill definition the same way you'd apply a task — as a markdown file with YAML frontmatter:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud apply skill -f pr-review.md</span><br></span></code></pre></div></div>
<p>Your file might look like this:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token front-matter-block punctuation" style="color:#393A34">---</span><span class="token front-matter-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">name</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> pr</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">-</span><span class="token front-matter-block front-matter yaml language-yaml">review</span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">description</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> PR Review Checklist</span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">tags</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml">  </span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">-</span><span class="token front-matter-block front-matter yaml language-yaml"> ai</span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml">  </span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">-</span><span class="token front-matter-block front-matter yaml language-yaml"> development</span><span class="token front-matter-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block"></span><span class="token front-matter-block punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Review the following pull request diff and provide feedback on:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Logic correctness</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Error handling</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Test coverage</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Naming and readability</span><br></span></code></pre></div></div>
<p>This makes skills portable. Check them into your dotfiles repo, version them with the rest of your configuration, and deploy them to a new UnDercontrol instance with a single command.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/skills-system/skills-list.png" alt="Skills page showing system and custom skills with search and tags" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="using-a-skill--the-ud-prompt-command">Using a Skill — The <code>ud prompt</code> Command<a href="https://oatnil.com/blog/2026/04/12/skills-system#using-a-skill--the-ud-prompt-command" class="hash-link" aria-label="Direct link to using-a-skill--the-ud-prompt-command" title="Direct link to using-a-skill--the-ud-prompt-command">​</a></h2>
<p>The real payoff is how you consume skills. The <code>ud prompt</code> command fetches a skill by slug and outputs its content to stdout. From there, you can pipe it anywhere.</p>
<p>Want to feed a skill into Claude Code?</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">claude-code $(ud prompt pr-review)</span><br></span></code></pre></div></div>
<p>Want to compose it with other commands in a pipeline?</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud prompt pr-review | pbcopy</span><br></span></code></pre></div></div>
<p>Because it's just stdout, <code>ud prompt</code> works with any tool that reads from stdin — Claude Code, other local AI agents, shell pipelines, whatever fits your workflow. UnDercontrol doesn't try to own the AI layer; it just manages the prompts so you don't have to.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/skills-system/skill-detail.png" alt="Skill detail view with markdown content and CLI usage commands" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="built-in-system-skills">Built-in System Skills<a href="https://oatnil.com/blog/2026/04/12/skills-system#built-in-system-skills" class="hash-link" aria-label="Direct link to Built-in System Skills" title="Direct link to Built-in System Skills">​</a></h2>
<p>UnDercontrol ships with built-in system skills that are seeded on first startup. These are read-only — you can't accidentally overwrite them — and they serve as both useful defaults and examples of how skills are structured. The <code>ud-cli</code> system skill, for instance, provides a comprehensive reference for the CLI's command structure, making it immediately available to AI agents that need to interact with your tasks.</p>
<p>System skills are marked with a lock icon in the web UI and appear alongside your custom skills. You can reference them by slug just like any custom skill.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="managing-skills-over-time">Managing Skills Over Time<a href="https://oatnil.com/blog/2026/04/12/skills-system#managing-skills-over-time" class="hash-link" aria-label="Direct link to Managing Skills Over Time" title="Direct link to Managing Skills Over Time">​</a></h2>
<p>Skills are first-class resources in UnDercontrol, which means full CRUD support through both the web UI and the CLI.</p>
<ul>
<li><code>ud get skills</code> — list all skills in your group</li>
<li><code>ud describe skill &lt;id&gt;</code> — inspect a specific skill's full content</li>
<li><code>ud delete skill &lt;id&gt;</code> — remove one you no longer need</li>
<li><code>ud apply skill -f skills/</code> — apply an entire directory of skill definitions at once</li>
</ul>
<p>The web editor is useful for quick edits and browsing what's available. The CLI is better for automation, bulk updates, and treating your prompt library as code.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-this-matters">Why This Matters<a href="https://oatnil.com/blog/2026/04/12/skills-system#why-this-matters" class="hash-link" aria-label="Direct link to Why This Matters" title="Direct link to Why This Matters">​</a></h2>
<p>The idea behind skills is simple: the prompts that work well are worth keeping. Storing them in a self-hosted, group-scoped system means they don't disappear when someone leaves the team, don't get lost in a chat history, and don't require everyone to reinvent the same workflow independently.</p>
<p>It also keeps your prompts private. Because UnDercontrol is self-hosted, your prompt library — which often encodes institutional knowledge, internal processes, and domain-specific context — stays on your own infrastructure.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="get-started">Get Started<a href="https://oatnil.com/blog/2026/04/12/skills-system#get-started" class="hash-link" aria-label="Direct link to Get Started" title="Direct link to Get Started">​</a></h2>
<p>Skills are available in UnDercontrol today. If you're already running an instance, you'll find the Skills page in the sidebar. If you're setting up for the first time, the self-hosting guide walks through deployment in about ten minutes.</p>
<p><a href="https://undercontrol.dev/docs" target="_blank" rel="noopener noreferrer">Read the documentation</a> or <a href="https://undercontrol.dev/docs/self-hosting" target="_blank" rel="noopener noreferrer">deploy your own instance</a> to get started.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[v0.65.1–0.65.2: Task Detail Gets a Performance Overhaul]]></title>
            <link>https://oatnil.com/blog/2026/04/09/v0.65-performance</link>
            <guid>https://oatnil.com/blog/2026/04/09/v0.65-performance</guid>
            <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How we fixed a 5-second navigation delay, eliminated N+1 queries, and made the editor feel instant — a deep dive into the performance work in UnDercontrol v0.65.1 and v0.65.2.]]></description>
            <content:encoded><![CDATA[<p>The last two patch releases focused on one thing: making task detail pages load and respond faster. Here's what changed and why.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-5-second-navigation-freeze">The 5-Second Navigation Freeze<a href="https://oatnil.com/blog/2026/04/09/v0.65-performance#the-5-second-navigation-freeze" class="hash-link" aria-label="Direct link to The 5-Second Navigation Freeze" title="Direct link to The 5-Second Navigation Freeze">​</a></h2>
<p>The most impactful fix was a navigation regression where clicking a task from the kanban board froze the UI for up to 5 seconds.</p>
<p>The root cause was subtle. A previous optimization eagerly pre-loaded the TaskDetail chunk so that <code>React.lazy()</code> resolved instantly. This sounds like it should be faster, but it had the opposite effect: without the brief Suspense pause, React committed the route change synchronously. That meant unmounting the entire kanban board — hundreds of sortable items, drag-and-drop contexts, all of it — in one blocking frame before the new page could even start mounting.</p>
<p>With standard lazy loading, the first navigation triggers a short Suspense boundary. React Router uses a transition that keeps the old page visible while the new chunk loads, and cleanup happens non-blockingly in the background. Subsequent navigations use the cached chunk anyway.</p>
<p>The fix: revert to standard <code>React.lazy()</code> and let the framework do what it's designed to do.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="backend-fixing-n1-queries-on-task-detail">Backend: Fixing N+1 Queries on Task Detail<a href="https://oatnil.com/blog/2026/04/09/v0.65-performance#backend-fixing-n1-queries-on-task-detail" class="hash-link" aria-label="Direct link to Backend: Fixing N+1 Queries on Task Detail" title="Direct link to Backend: Fixing N+1 Queries on Task Detail">​</a></h2>
<p>The <code>GET /todolist/:id</code> endpoint had two problems:</p>
<ol>
<li>
<p><strong>Duplicate queries</strong> — <code>enrichItem()</code> called <code>loadRelatedData()</code> to fetch notes and share links, but <code>GetByIDEnriched()</code> already loaded those same relations. Every request ran 2 redundant queries.</p>
</li>
<li>
<p><strong>N+1 on linked items</strong> — each linked task was fetched individually in a loop. A task with 5 linked items meant 5 separate <code>SELECT</code> statements.</p>
</li>
</ol>
<p>After the fix, a task detail request runs 4 queries total (main record + notes + share links + one batch <code>WHERE id IN (...)</code> for linked items), down from 7+ before.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="lazy-note-editors-with-intersectionobserver">Lazy Note Editors with IntersectionObserver<a href="https://oatnil.com/blog/2026/04/09/v0.65-performance#lazy-note-editors-with-intersectionobserver" class="hash-link" aria-label="Direct link to Lazy Note Editors with IntersectionObserver" title="Direct link to Lazy Note Editors with IntersectionObserver">​</a></h2>
<p>Tasks in UnDercontrol can have many notes, each rendered with a TipTap rich-text editor. Every TipTap instance creates DOM nodes, event listeners, and extension chains — initializing 10 of them on page mount adds up fast.</p>
<p>Now only the first 2 notes render their editors immediately. The rest show a lightweight placeholder until they scroll within 200px of the viewport. On a task with 10 notes, this avoids initializing 8 editor instances upfront.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="no-more-flash-when-switching-to-edit-mode">No More Flash When Switching to Edit Mode<a href="https://oatnil.com/blog/2026/04/09/v0.65-performance#no-more-flash-when-switching-to-edit-mode" class="hash-link" aria-label="Direct link to No More Flash When Switching to Edit Mode" title="Direct link to No More Flash When Switching to Edit Mode">​</a></h2>
<p>Toggling a note from read mode to edit mode used to produce a brief white flash. Two things caused it:</p>
<ul>
<li>TipTap's default behavior waits a frame before rendering content</li>
<li>Resource URLs (<code>resource://...</code>) were resolved before setting any content, so the editor was empty until async resolution completed</li>
</ul>
<p>The fix sets <code>immediatelyRender: true</code> on the editor and loads raw markdown content synchronously first. Resource images resolve in the background — you see the text instantly, and images fill in a moment later. If the content has no <code>resource://</code> URLs at all, the processing step is skipped entirely.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="editor-toolbar-fixes">Editor Toolbar Fixes<a href="https://oatnil.com/blog/2026/04/09/v0.65-performance#editor-toolbar-fixes" class="hash-link" aria-label="Direct link to Editor Toolbar Fixes" title="Direct link to Editor Toolbar Fixes">​</a></h2>
<p>A few smaller but annoying editor issues were resolved:</p>
<ul>
<li><strong>Toolbar frozen on horizontal scroll</strong> — the formatting toolbar used <code>fixed</code> positioning, so scrolling right left it stuck at x=0. Switched to <code>sticky</code> so it scrolls with its container.</li>
<li><strong>Toggle button scrolls away</strong> — on long notes, the edit/preview toggle button scrolled out of view. It's now pinned in a sticky action bar.</li>
<li><strong>Mobile action bar overlap</strong> — on mobile, the editor action bar sat behind the floating chat button. Raised it to clear the obstruction.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="china-download-mirror">China Download Mirror<a href="https://oatnil.com/blog/2026/04/09/v0.65-performance#china-download-mirror" class="hash-link" aria-label="Direct link to China Download Mirror" title="Direct link to China Download Mirror">​</a></h2>
<p>For users in mainland China, downloading the desktop app from our default CDN (Cloudflare R2) can be slow or unreliable. We added a 中国大陆下载 button in the download dialog that pulls from Bitiful, a China-based CDN. Download metrics track which mirror was used so we can monitor adoption.</p>
<p>The release pipeline now automatically uploads builds to both R2 and Bitiful, and cleans up old versions on Bitiful (keeping the latest 2) to manage storage.</p>
<hr>
<p>These changes are available in <a href="https://github.com/oatnil/undercontrol/releases" target="_blank" rel="noopener noreferrer">v0.65.2</a>. If you're self-hosting, pull the latest image and you're set.</p>]]></content:encoded>
            <category>Release</category>
        </item>
        <item>
            <title><![CDATA[Team Up Without Giving Up Control: Collaboration in UnDercontrol]]></title>
            <link>https://oatnil.com/blog/2026/04/07/collaboration</link>
            <guid>https://oatnil.com/blog/2026/04/07/collaboration</guid>
            <pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how UnDercontrol's group system, role-based permissions, and shared tasks let you collaborate with others while keeping your data self-hosted and private.]]></description>
            <content:encoded><![CDATA[<p>Most productivity apps treat collaboration as an afterthought — you get a basic share button, maybe a comments section, and that's it. UnDercontrol takes a different approach: groups, role-based permissions, and shared tasks are first-class features built on the same foundation as everything else in the app.</p>
<p>The best part? You're still self-hosted. You're not handing your data to a third-party service to enable multi-user workflows.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-groups-work">How Groups Work<a href="https://oatnil.com/blog/2026/04/07/collaboration#how-groups-work" class="hash-link" aria-label="Direct link to How Groups Work" title="Direct link to How Groups Work">​</a></h2>
<p>A group in UnDercontrol is a shared workspace. Think of it as a container for people who need to see or work on the same things together. Common setups include a family tracking a shared budget, a small team coordinating project tasks, or two people splitting household expenses.</p>
<p>Creating a group takes about ten seconds: give it a name, add a description, and you're the owner. From there, you generate invite links to bring people in.</p>
<p>Invite links are flexible. You can set an expiration date so a link becomes invalid after a day or a week — useful when you're adding a temporary collaborator. You can also revoke any link at any time. It's a small detail, but it matters when you care about who has access to your workspace.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/collaboration/groups.png" alt="Group management for team collaboration" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="role-based-permissions">Role-Based Permissions<a href="https://oatnil.com/blog/2026/04/07/collaboration#role-based-permissions" class="hash-link" aria-label="Direct link to Role-Based Permissions" title="Direct link to Role-Based Permissions">​</a></h2>
<p>Once people are in a group, roles determine what they can do. There are three group-level roles:</p>
<ul>
<li><strong>Owner</strong> — full control over the group, including members, settings, and all shared content</li>
<li><strong>Admin</strong> — can manage members and invites, access all shared content</li>
<li><strong>Member</strong> — can view and interact with shared resources based on the permission level set on each item</li>
</ul>
<p>Beyond group roles, UnDercontrol also has system-level roles: Admin, User, and Visitor. If you're running a self-hosted instance for a small organization, you can create custom roles with specific permission sets covering tasks, expenses, budgets, files, and more. This is the kind of access control you'd expect from enterprise tools, available in a self-hosted app you run yourself.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="sharing-tasks-with-the-right-level-of-access">Sharing Tasks with the Right Level of Access<a href="https://oatnil.com/blog/2026/04/07/collaboration#sharing-tasks-with-the-right-level-of-access" class="hash-link" aria-label="Direct link to Sharing Tasks with the Right Level of Access" title="Direct link to Sharing Tasks with the Right Level of Access">​</a></h2>
<p>When you share a task with a group, you choose the permission level: read-only or read-write.</p>
<p>Read-only is useful when you want someone to stay informed without being able to modify anything — a manager reviewing a task list, for example, or a partner who needs to see what's on the agenda without accidentally editing it.</p>
<p>Read-write sharing enables real collaboration. Group members can update the task, check off items, and work alongside you. Shared tasks show up in each member's task list, so nothing gets buried.</p>
<p>Files attached to a shared task are automatically accessible to group members. There's no separate file-sharing step — if you've attached a PDF or an image to a task, everyone with access to that task can see it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="kanban-boards-and-shared-workflows">Kanban Boards and Shared Workflows<a href="https://oatnil.com/blog/2026/04/07/collaboration#kanban-boards-and-shared-workflows" class="hash-link" aria-label="Direct link to Kanban Boards and Shared Workflows" title="Direct link to Kanban Boards and Shared Workflows">​</a></h2>
<p>Shared kanban boards integrate directly with the group system. When you create a shared board, it automatically creates a group behind the scenes. The board creator becomes the group admin, and collaborators join as members. This means your team's workflow is always tied to a proper access model — not just an open link that anyone can stumble into.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/collaboration/shared-board.png" alt="Shared kanban board for team collaboration" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="practical-tips">Practical Tips<a href="https://oatnil.com/blog/2026/04/07/collaboration#practical-tips" class="hash-link" aria-label="Direct link to Practical Tips" title="Direct link to Practical Tips">​</a></h2>
<p>A few things worth knowing before you set up your first group:</p>
<p>Currently, each user can belong to one group at a time, so think about how you structure your workspace. If you're coordinating a work project and a household budget, the people involved will need to choose which group they're part of — or you handle both under a single group with clear task naming conventions.</p>
<p>Use descriptive group names from the start. "Family Budget 2026" or "Backend Team Q2" is far more useful three months later than "My Group."</p>
<p>Set expiration dates on invite links whenever you're adding someone for a limited purpose. It takes two extra seconds and removes the need to remember to revoke the link later.</p>
<p>Review your member list occasionally. People change roles, projects end, living situations shift. Keeping the member list current is basic hygiene for any shared workspace.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="get-started">Get Started<a href="https://oatnil.com/blog/2026/04/07/collaboration#get-started" class="hash-link" aria-label="Direct link to Get Started" title="Direct link to Get Started">​</a></h2>
<p>If you already have a self-hosted instance running, head to the Groups page and create your first group. If you haven't set up UnDercontrol yet, the <a href="https://oatnil.com/docs/self-deployment">self-deployment guide</a> walks through the full setup — you can be running in under an hour with Docker.</p>
<p>The <a href="https://oatnil.com/docs/features/collaboration">collaboration documentation</a> covers every feature in detail, including the full permission system and how groups interact with budgets and resources.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Real-Time Multi-Client Sync with Server-Sent Events]]></title>
            <link>https://oatnil.com/blog/2026/04/06/sse-realtime-sync</link>
            <guid>https://oatnil.com/blog/2026/04/06/sse-realtime-sync</guid>
            <pubDate>Mon, 06 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How UnDercontrol keeps your tasks and finances in sync across browser tabs, desktop, and mobile using Server-Sent Events and a smart event-driven architecture.]]></description>
            <content:encoded><![CDATA[<p>If you have UnDercontrol open in a browser tab, on your desktop app, and on your phone at the same time, you probably expect them to stay in sync. Adding a task in one place should show up in the others without a page refresh. Logging an expense should reflect in your budget immediately, everywhere.</p>
<p>That kind of real-time sync sounds simple but gets complicated fast when you factor in reconnects, offline states, and the cost of keeping connections alive. Here is how UnDercontrol handles it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-server-sent-events">Why Server-Sent Events<a href="https://oatnil.com/blog/2026/04/06/sse-realtime-sync#why-server-sent-events" class="hash-link" aria-label="Direct link to Why Server-Sent Events" title="Direct link to Why Server-Sent Events">​</a></h2>
<p>WebSockets are the obvious choice for real-time features, but they come with overhead — bidirectional connections, custom protocol handling, and more complexity on both ends. For UnDercontrol, the update flow is almost entirely one-directional: the server pushes changes down to clients. That maps perfectly onto Server-Sent Events (SSE), which is a standard HTTP mechanism built into every modern browser.</p>
<p>SSE gives us persistent connections over plain HTTP, automatic reconnection built into the browser spec, and no additional infrastructure beyond the Go backend that already runs your instance. It keeps the self-hosted model clean.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-it-works">How It Works<a href="https://oatnil.com/blog/2026/04/06/sse-realtime-sync#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works">​</a></h2>
<p>When you open UnDercontrol, the frontend establishes an SSE connection to the backend. Your session is registered in a per-user connection hub — a structure that tracks every active connection for your account across tabs, devices, and the Electron desktop app. When something changes (a task is updated, an expense is logged, a file is renamed), the backend emits an event onto an internal async event bus. That event fans out to every connection registered under your user ID.</p>
<p>This means if you log an expense on your phone, your browser tab sees it within milliseconds. No polling, no manual refresh.</p>
<p>The connection lifecycle is managed deliberately. Connections are kept alive for up to 30 minutes, after which they cycle and reconnect. This prevents resource leaks on long-running instances and plays nicely with load balancers and reverse proxies that have their own timeout rules. If a connection drops for any reason — network hiccup, sleep/wake cycle, proxy timeout — the client reconnects automatically using exponential backoff. It starts with a short delay and increases gradually, so a briefly offline device does not hammer your server the moment it comes back online.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="smart-cache-updates-on-the-client">Smart Cache Updates on the Client<a href="https://oatnil.com/blog/2026/04/06/sse-realtime-sync#smart-cache-updates-on-the-client" class="hash-link" aria-label="Direct link to Smart Cache Updates on the Client" title="Direct link to Smart Cache Updates on the Client">​</a></h2>
<p>Getting an event is one thing. Knowing what to do with it is another. UnDercontrol does not blindly refetch the entire dataset when an SSE event arrives. Instead, the frontend applies differential cache updates: it looks at what changed, finds the relevant entry in the local Zustand store, and patches only that record.</p>
<p>For example, if a task's status changes from "in progress" to "done," the event carries just that task's updated state. The client merges it into the existing cache. The list re-renders with the new status. Everything else stays untouched.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/sse-realtime-sync/kanban-board.png" alt="Kanban board with live status updates pushed via SSE" class="img_ev3q"></p>
<p>This keeps the UI fast and prevents the jarring full-reload effect you see in apps that refetch aggressively.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="optimistic-ui-and-server-reconciliation">Optimistic UI and Server Reconciliation<a href="https://oatnil.com/blog/2026/04/06/sse-realtime-sync#optimistic-ui-and-server-reconciliation" class="hash-link" aria-label="Direct link to Optimistic UI and Server Reconciliation" title="Direct link to Optimistic UI and Server Reconciliation">​</a></h2>
<p>SSE works alongside UnDercontrol's optimistic update model. When you make a change locally, the UI updates immediately — no spinner, no waiting. The write goes to the server in the background. If it succeeds, the server emits an SSE event that propagates to your other clients. If it fails, the local state reverts and you see an error.</p>
<p>The result is that your primary device feels instant, while your secondary devices stay consistent. The server is the source of truth, and SSE is the mechanism that keeps everyone aligned with it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="practical-benefits-you-will-notice">Practical Benefits You Will Notice<a href="https://oatnil.com/blog/2026/04/06/sse-realtime-sync#practical-benefits-you-will-notice" class="hash-link" aria-label="Direct link to Practical Benefits You Will Notice" title="Direct link to Practical Benefits You Will Notice">​</a></h2>
<p>Open the same UnDercontrol instance in two browser tabs. Make a change in one. Watch it appear in the other without touching anything.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/sse-realtime-sync/task-list.png" alt="Task list view — changes sync in real-time across all connected clients" class="img_ev3q"></p>
<p>This is particularly useful when you have a budget overview open on one screen and you are logging transactions on another.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/sse-realtime-sync/budget-overview.png" alt="Budget overview — expense changes propagate instantly to all open views" class="img_ev3q"></p>
<p>The Electron desktop app participates in the same sync. Changes made through the CLI or the Chrome extension propagate back through SSE to whatever else you have open. The whole multi-platform story depends on this layer working reliably.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="try-it-yourself">Try It Yourself<a href="https://oatnil.com/blog/2026/04/06/sse-realtime-sync#try-it-yourself" class="hash-link" aria-label="Direct link to Try It Yourself" title="Direct link to Try It Yourself">​</a></h2>
<p>All of this runs on your own hardware. There is no cloud dependency, no third-party sync service, and no data leaving your control. The SSE endpoint is part of the standard UnDercontrol backend.</p>
<p>If you are not running UnDercontrol yet, the self-deployment guide covers getting started with Docker in a few minutes. If you are already running an instance, the sync is already active — just open a second tab and see for yourself.</p>
<p>Check the <a href="https://undercontrol.dev/docs" target="_blank" rel="noopener noreferrer">documentation</a> for deployment instructions and configuration options.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Terminal-First Task Management with the ud CLI]]></title>
            <link>https://oatnil.com/blog/2026/04/05/cli-tool</link>
            <guid>https://oatnil.com/blog/2026/04/05/cli-tool</guid>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How UnDercontrol's kubectl-style CLI brings task management into your terminal workflow — with query syntax, TUI, and AI agent integration.]]></description>
            <content:encoded><![CDATA[<p>If you spend most of your day in a terminal, switching to a browser tab just to log a task or check what's on your plate is friction you don't need. The <code>ud</code> CLI was built to eliminate that context switch. It brings the full power of UnDercontrol — task CRUD, notes, querying, kanban boards — into your shell, and it follows a command structure you already know.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="kubectl-style-commands-because-it-works">kubectl-style Commands, Because It Works<a href="https://oatnil.com/blog/2026/04/05/cli-tool#kubectl-style-commands-because-it-works" class="hash-link" aria-label="Direct link to kubectl-style Commands, Because It Works" title="Direct link to kubectl-style Commands, Because It Works">​</a></h2>
<p>The CLI uses the same verb-resource pattern that made <code>kubectl</code> feel intuitive to so many engineers. You get, describe, apply, and delete resources. No new mental model required.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># List all tasks</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud get task</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Show full details on a task (short ID prefix supported)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud describe task 3de9f82b</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Create or update from a markdown file</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud apply -f task.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Delete a task</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud delete task abc123</span><br></span></code></pre></div></div>
<p>The <code>apply</code> command deserves a highlight. Your task is a markdown file with YAML frontmatter. If the file has an <code>id</code> field, it updates the existing task. No <code>id</code>? It creates a new one. This makes bulk updates, scripted workflows, and version-controlled task definitions straightforward.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">echo '---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">title: Write release notes</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">status: in-progress</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tags:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  - release</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">deadline: 2026-04-10</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Draft the changelog and update the docs site.' | ud apply -f -</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/cli-tool/task-list.png" alt="Task list view — what the ud CLI manages from your terminal" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="query-syntax-that-actually-filters">Query Syntax That Actually Filters<a href="https://oatnil.com/blog/2026/04/05/cli-tool#query-syntax-that-actually-filters" class="hash-link" aria-label="Direct link to Query Syntax That Actually Filters" title="Direct link to Query Syntax That Actually Filters">​</a></h2>
<p><code>ud task query</code> gives you a SQL-like filter language over your tasks. It is useful when you have enough tasks that "scroll through the list" stops being a real strategy.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Tasks with a deadline this week</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task query "deadline BETWEEN 'today' AND '+7d'"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Active tasks tagged urgent</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task query "(status = 'todo' OR status = 'in-progress') AND tags = 'urgent'"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Title search</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task query "title ILIKE '%api%'"</span><br></span></code></pre></div></div>
<p>If you would rather not think about syntax at all, <code>ud task nlquery</code> accepts plain English and translates it via AI:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud task nlquery "show me overdue tasks"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task nlquery "tasks tagged with work that are not done"</span><br></span></code></pre></div></div>
<p>Both commands accept <code>--sort</code>, <code>--order</code>, and <code>--limit</code> flags for pagination and ordering, so they compose cleanly into scripts or shell aliases.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="notes-as-a-progress-log">Notes as a Progress Log<a href="https://oatnil.com/blog/2026/04/05/cli-tool#notes-as-a-progress-log" class="hash-link" aria-label="Direct link to Notes as a Progress Log" title="Direct link to Notes as a Progress Log">​</a></h2>
<p>Every task supports notes — short freeform entries that act as a running log. This is useful for tracking what you actually did, not just the end state.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud task note add 3de9f82b "Finished the backend changes, opening PR now"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task note ls 3de9f82b</span><br></span></code></pre></div></div>
<p>Notes also make the CLI a natural fit for AI agent workflows. An agent can create a task from a plan file, append progress notes at each step, and mark the task done — all without touching a browser. The human side of the team sees the full history in the UnDercontrol web UI or desktop app. It is a low-ceremony handoff that actually works in practice.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="interactive-tui-for-when-you-want-a-visual">Interactive TUI for When You Want a Visual<a href="https://oatnil.com/blog/2026/04/05/cli-tool#interactive-tui-for-when-you-want-a-visual" class="hash-link" aria-label="Direct link to Interactive TUI for When You Want a Visual" title="Direct link to Interactive TUI for When You Want a Visual">​</a></h2>
<p>Running <code>ud</code> with no arguments opens the terminal UI — a keyboard-driven kanban-style interface with vim bindings.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/cli-tool/workspace-terminal.png" alt="Built-in terminal with ud CLI commands" class="img_ev3q"></p>
<table><thead><tr><th>Key</th><th>Action</th></tr></thead><tbody><tr><td><code>j</code> / <code>k</code></td><td>Move through tasks</td></tr><tr><td><code>Enter</code></td><td>Open task detail</td></tr><tr><td><code>i</code></td><td>Create a new task</td></tr><tr><td><code>x</code></td><td>Toggle status</td></tr><tr><td><code>/</code></td><td>Search</td></tr><tr><td><code>f</code></td><td>File picker (fuzzy search local files to create tasks from)</td></tr></tbody></table>
<p>The file picker is a nice touch. Press <code>f</code>, fuzzy-search a markdown file in your current directory, and it becomes a task — first line is the title, the rest becomes the description. Useful when you keep notes in a project repo and want them tracked.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="multi-context-for-multiple-servers">Multi-Context for Multiple Servers<a href="https://oatnil.com/blog/2026/04/05/cli-tool#multi-context-for-multiple-servers" class="hash-link" aria-label="Direct link to Multi-Context for Multiple Servers" title="Direct link to Multi-Context for Multiple Servers">​</a></h2>
<p>If you self-host UnDercontrol across multiple environments — personal, work, a staging instance — the context system handles it the same way <code>kubectl</code> does.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud config set-context work --api-url https://ud.company.com</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud login --context work</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Use a different context for a single command</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">UD_CONTEXT=personal ud get task</span><br></span></code></pre></div></div>
<p>Config lives in <code>~/.config/ud/config.yaml</code>. You can also drive the CLI entirely through environment variables (<code>UD_API_URL</code>, <code>UD_API_KEY</code>, <code>UD_TOKEN</code>), which makes it usable in CI pipelines without touching config files.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting Started<a href="https://oatnil.com/blog/2026/04/05/cli-tool#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started">​</a></h2>
<p>Install via npm, Homebrew, or a single curl command:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npm install -g @oatnil/ud</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># or</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">brew install oatnil-top/ud/ud</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># or</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">curl -fsSL https://get.oatnil.com/ud | bash</span><br></span></code></pre></div></div>
<p>Then point it at your UnDercontrol instance:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud login</span><br></span></code></pre></div></div>
<p>The full command reference — including file attachment, multi-context auth, and advanced query syntax — is in the <a href="https://oatnil.com/docs/cli">CLI Reference</a> docs. If you are not running UnDercontrol yet, the <a href="https://oatnil.com/docs/self-deployment">self-hosting guide</a> covers getting a server up with Docker in a few minutes.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Managing Multiple Accounts with CLI Contexts]]></title>
            <link>https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts</link>
            <guid>https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts</guid>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how UnDercontrol's kubectl-style context system lets you switch between multiple accounts and self-hosted instances from the command line.]]></description>
            <content:encoded><![CDATA[<p>If you run more than one UnDercontrol instance — say, a personal server and a work server — you've probably felt the friction of juggling different API endpoints and credentials. The <code>ud</code> CLI ships with a context system modeled after <code>kubectl</code>, so switching between accounts is a single command.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-a-context-is">What a Context Is<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#what-a-context-is" class="hash-link" aria-label="Direct link to What a Context Is" title="Direct link to What a Context Is">​</a></h2>
<p>A context in <code>ud</code> is a named configuration that bundles an API endpoint, credentials (either a session token from interactive login or a static API key), and a display username. Contexts live in <code>~/.config/ud/config.yaml</code> — nothing is sent anywhere.</p>
<p>A typical context list looks like this:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$ ud config get-contexts</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CURRENT  NAME      API URL                           USER</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">*        personal  https://ud.home.example.com       me@example.com</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">         work      https://ud.corp.example.com       me@corp.com</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">         local     http://localhost:4000              admin@oatnil.com</span><br></span></code></pre></div></div>
<p>The asterisk marks the active context. Every <code>ud</code> command uses that context unless you override it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="setting-up-your-first-context">Setting Up Your First Context<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#setting-up-your-first-context" class="hash-link" aria-label="Direct link to Setting Up Your First Context" title="Direct link to Setting Up Your First Context">​</a></h2>
<p>The fastest way to create a context is through login. The <code>-n</code> flag names the context in one step:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Login and create a named context</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud login --api-url https://ud.home.example.com -n personal</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Add another for work</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud login --api-url https://ud.corp.example.com -n work</span><br></span></code></pre></div></div>
<p>This prompts for your credentials, authenticates, and saves the token under the given context name. If you need a context for CI/CD or headless environments where interactive login isn't practical, use <code>config set-context</code> with an API key:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud config set-context ci --api-url https://ud.corp.example.com --api-key ak_xxxxx</span><br></span></code></pre></div></div>
<p>Generate API keys from the UnDercontrol web interface under Settings. No browser prompt needed at runtime.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="switching-contexts">Switching Contexts<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#switching-contexts" class="hash-link" aria-label="Direct link to Switching Contexts" title="Direct link to Switching Contexts">​</a></h2>
<p>To switch your active context:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud config use-context work</span><br></span></code></pre></div></div>
<p>That's it. Your next <code>ud get task</code> or <code>ud describe task</code> hits the work server with work credentials. Switch back just as easily:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud config use-context personal</span><br></span></code></pre></div></div>
<p>If you're in the TUI, type <code>:ctx</code> in command mode to open an interactive context picker — arrow keys to navigate, Enter to confirm. Same muscle memory as <code>kubectx</code>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="one-off-overrides">One-off Overrides<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#one-off-overrides" class="hash-link" aria-label="Direct link to One-off Overrides" title="Direct link to One-off Overrides">​</a></h2>
<p>Sometimes you want to target a specific instance for a single command without permanently switching. The <code>--context</code> flag and environment variables handle this:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Use a named context for one command</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud --context work get task</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Or via environment variable</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">UD_CONTEXT=work ud get task</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Point at an arbitrary endpoint without a named context</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">UD_API_URL=http://localhost:4000 UD_API_KEY=dev-key ud get task</span><br></span></code></pre></div></div>
<p>The priority order is: <code>--context</code> flag &gt; <code>UD_CONTEXT</code> env var &gt; <code>current-context</code> in config file. This makes scripts explicit about which instance they target, regardless of whatever context happens to be active on the machine.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="independent-auth-per-context">Independent Auth Per Context<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#independent-auth-per-context" class="hash-link" aria-label="Direct link to Independent Auth Per Context" title="Direct link to Independent Auth Per Context">​</a></h2>
<p>Each context maintains its own authentication state. Logging out of one context doesn't affect others. If your work session token expires, re-login just that context:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud login --context work</span><br></span></code></pre></div></div>
<p>Your personal and local contexts remain untouched.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="inspecting-your-setup">Inspecting Your Setup<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#inspecting-your-setup" class="hash-link" aria-label="Direct link to Inspecting Your Setup" title="Direct link to Inspecting Your Setup">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Show which context is active</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud config current-context</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># List all contexts</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud config get-contexts</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># View the full config (tokens are masked)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud config view</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="housekeeping">Housekeeping<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#housekeeping" class="hash-link" aria-label="Direct link to Housekeeping" title="Direct link to Housekeeping">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Rename a context</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud config rename-context old-name new-name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Delete a context you no longer need</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud config delete-context staging</span><br></span></code></pre></div></div>
<p>If you delete the currently active context, <code>ud</code> automatically switches to the first remaining one.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-this-matters-for-self-hosted-users">Why This Matters for Self-Hosted Users<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#why-this-matters-for-self-hosted-users" class="hash-link" aria-label="Direct link to Why This Matters for Self-Hosted Users" title="Direct link to Why This Matters for Self-Hosted Users">​</a></h2>
<p>UnDercontrol is built around the premise that your data belongs to you. Running your own instance is a first-class workflow. The context system reflects that — it assumes you might run multiple instances, want to move between them without friction, and need automation without compromising security.</p>
<p>Whether you're managing a personal instance, a family server, and a work deployment, or simply running local dev alongside production, contexts give you a clean mental model and a consistent CLI interface across all of them.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="get-started">Get Started<a href="https://oatnil.com/blog/2026/04/05/multi-account-cli-contexts#get-started" class="hash-link" aria-label="Direct link to Get Started" title="Direct link to Get Started">​</a></h2>
<p>Install the <code>ud</code> CLI via Homebrew (<code>brew install oatnil/ud/ud</code>) or grab a binary from the releases page. Set up your first context:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud login --api-url https://your-instance.example.com -n myserver</span><br></span></code></pre></div></div>
<p>Full documentation: <a href="https://oatnil.com/docs/cli-auth-context">CLI Authentication &amp; Contexts</a>.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Power User Queries and Saved Filters in UnDercontrol]]></title>
            <link>https://oatnil.com/blog/2026/04/05/query-syntax</link>
            <guid>https://oatnil.com/blog/2026/04/05/query-syntax</guid>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to use UnDercontrol's SQL-like query syntax, natural language search, and saved queries to find and filter tasks like a power user.]]></description>
            <content:encoded><![CDATA[<p>If you have more than a handful of tasks in UnDercontrol, you have probably hit the point where "scroll and scan" stops working. You know what you are looking for — overdue items, everything tagged <code>work</code> that is still in progress, tasks with no deadline yet — but getting to them quickly takes more than a status filter.</p>
<p>This post covers the query system built into UnDercontrol: a SQL-like syntax that works across the web UI, the CLI, custom views, kanban boards, and saved queries. Once it clicks, you will use it constantly.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-query-syntax">The Query Syntax<a href="https://oatnil.com/blog/2026/04/05/query-syntax#the-query-syntax" class="hash-link" aria-label="Direct link to The Query Syntax" title="Direct link to The Query Syntax">​</a></h2>
<p>The syntax is deliberately close to SQL WHERE clauses. If you have ever written a database query, it will feel familiar immediately. If you have not, the basics take about five minutes to pick up.</p>
<p>A simple query looks like this:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">status</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'todo'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> deadline </span><span class="token operator" style="color:#393A34">&lt;=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'today'</span><br></span></code></pre></div></div>
<p>That finds every task that is still todo and due today or earlier — in other words, overdue todo items.</p>
<p>You can build on that with tags, text search, date ranges, and custom fields:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">deadline </span><span class="token operator" style="color:#393A34">&lt;=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'today'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">OR</span><span class="token plain"> tags </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'urgent'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">status</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'done'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">status</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'archived'</span><br></span></code></pre></div></div>
<p>This is a reliable "needs attention now" query. Pin it as a saved query (more on that below) and you have a one-click urgent task list.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/query-syntax/task-query.png" alt="Task search with query syntax filtering" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="datetime-expressions">Datetime Expressions<a href="https://oatnil.com/blog/2026/04/05/query-syntax#datetime-expressions" class="hash-link" aria-label="Direct link to Datetime Expressions" title="Direct link to Datetime Expressions">​</a></h2>
<p>One of the more practical parts of the syntax is the relative date support. Instead of hardcoding dates, you write things like <code>'-7d'</code>, <code>'+1w'</code>, or just <code>'today'</code>.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">-- Tasks created in the last week</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">created_at </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'-7d'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">-- Due within the next month</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">deadline </span><span class="token operator" style="color:#393A34">BETWEEN</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'today'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'+1m'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">-- Updated today</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">updated_at </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'today'</span><br></span></code></pre></div></div>
<p>The supported units are days (<code>d</code>), weeks (<code>w</code>), months (<code>m</code>), and years (<code>y</code>), with <code>+</code> for future and <code>-</code> for past. Standard ISO 8601 dates like <code>2025-06-01</code> also work when you need a fixed date.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="text-search-and-custom-fields">Text Search and Custom Fields<a href="https://oatnil.com/blog/2026/04/05/query-syntax#text-search-and-custom-fields" class="hash-link" aria-label="Direct link to Text Search and Custom Fields" title="Direct link to Text Search and Custom Fields">​</a></h2>
<p>Text search uses <code>LIKE</code> (case-sensitive) and <code>ILIKE</code> (case-insensitive) with <code>%</code> as a wildcard:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">title </span><span class="token operator" style="color:#393A34">ILIKE</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'%api%'</span><br></span></code></pre></div></div>
<p>For custom fields, prefix with <code>cf.</code>:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cf</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">priority </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">AND</span><span class="token plain"> cf</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">department </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'engineering'</span><br></span></code></pre></div></div>
<p>Custom fields support the full range of comparison operators depending on their type — numbers, text, selects, checkboxes, and user references all work.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="natural-language-queries">Natural Language Queries<a href="https://oatnil.com/blog/2026/04/05/query-syntax#natural-language-queries" class="hash-link" aria-label="Direct link to Natural Language Queries" title="Direct link to Natural Language Queries">​</a></h2>
<p>Writing queries manually is fast once you know the syntax. But if you would rather just describe what you want, the AI integration handles the translation.</p>
<p>In the web UI, open the AI Chat panel on the task page and type something like "show me overdue tasks that have the work tag". The AI generates the structured query and runs it.</p>
<p>From the CLI, use <code>ud task nlquery</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud task nlquery "tasks I need to finish this week"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task nlquery "high priority engineering items with no deadline"</span><br></span></code></pre></div></div>
<p>The <code>nl</code> alias also works if you want to save a few keystrokes. This requires an AI provider to be configured, but once it is set up it handles surprisingly natural phrasing.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="saved-queries">Saved Queries<a href="https://oatnil.com/blog/2026/04/05/query-syntax#saved-queries" class="hash-link" aria-label="Direct link to Saved Queries" title="Direct link to Saved Queries">​</a></h2>
<p>This is where the query system becomes genuinely useful day-to-day. Saved Queries let you name and store any query, then run it with a single click from the sidebar.</p>
<p>A few worth setting up immediately:</p>
<table><thead><tr><th>Name</th><th>Query</th></tr></thead><tbody><tr><td>Overdue</td><td><code>deadline &lt; 'today' AND status != 'done' AND status != 'archived'</code></td></tr><tr><td>Due This Week</td><td><code>deadline BETWEEN 'today' AND '+7d' AND status != 'done'</code></td></tr><tr><td>Unplanned</td><td><code>deadline IS NULL AND status = 'todo'</code></td></tr><tr><td>Recently Active</td><td><code>updated_at &gt;= '-7d' AND status IN ('todo', 'in-progress')</code></td></tr></tbody></table>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/query-syntax/saved-queries.png" alt="Saved queries for quick access to filtered views" class="img_ev3q"></p>
<p>You can pin queries to keep your most-used ones at the top, reorder them by drag and drop, and edit them at any time. When you click a saved query, results expand inline — no navigation required.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="using-queries-in-the-cli">Using Queries in the CLI<a href="https://oatnil.com/blog/2026/04/05/query-syntax#using-queries-in-the-cli" class="hash-link" aria-label="Direct link to Using Queries in the CLI" title="Direct link to Using Queries in the CLI">​</a></h2>
<p>The CLI supports the same query syntax through <code>ud task query</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud task query "status = 'todo'" --sort deadline --order asc</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task query "(status = 'todo' OR status = 'in-progress') AND tags = 'work'"</span><br></span></code></pre></div></div>
<p>Flags for pagination (<code>--page</code>, <code>--limit</code>) and sorting (<code>--sort</code>, <code>--order</code>) are available. This makes it straightforward to pipe results into other tools or use queries inside shell scripts.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting Started<a href="https://oatnil.com/blog/2026/04/05/query-syntax#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started">​</a></h2>
<p>The full query syntax reference is in the <a href="https://oatnil.com/docs/query-syntax">Query Syntax documentation</a>, including every operator, all datetime expression formats, and a full set of practical examples to copy and adapt.</p>
<p>If you are not running UnDercontrol yet, the <a href="https://oatnil.com/docs/self-deployment">self-hosting guide</a> covers deployment with Docker. Your data stays on your own infrastructure — that is the whole point.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Keep Your Files Where Your Work Lives: Resource Management in UnDercontrol]]></title>
            <link>https://oatnil.com/blog/2026/04/05/resource-management</link>
            <guid>https://oatnil.com/blog/2026/04/05/resource-management</guid>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How UnDercontrol's file and resource management keeps receipts, documents, and diagrams attached to the tasks and expenses they actually belong to.]]></description>
            <content:encoded><![CDATA[<p>Most productivity apps treat file storage as an afterthought — a folder somewhere, disconnected from the work it supports. UnDercontrol takes a different approach: files live next to the tasks, expenses, and budgets they belong to, so a receipt stays with the expense it documents and a design diagram stays with the task it informs.</p>
<p>Here is how the resource management system works in practice.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="upload-the-way-you-actually-work">Upload the Way You Actually Work<a href="https://oatnil.com/blog/2026/04/05/resource-management#upload-the-way-you-actually-work" class="hash-link" aria-label="Direct link to Upload the Way You Actually Work" title="Direct link to Upload the Way You Actually Work">​</a></h2>
<p>Getting files into UnDercontrol should not require a trip through a file picker every time. There are three main ways to upload:</p>
<p><strong>Drag and drop</strong> — open the Resources page or an attachment panel on a task or expense, then drag a file in. Works for single files and batches.</p>
<p><strong>Paste from clipboard</strong> — press Ctrl+V and the file in your clipboard uploads immediately. This is particularly useful for screenshots. Take a screenshot of a receipt or a bug in a UI, switch to UnDercontrol, paste. No saving to disk first.</p>
<p><strong>CLI upload</strong> — if you live in the terminal, the <code>ud</code> CLI handles uploads directly:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud upload resource ./receipt.png --entity-type expense --entity-id exp-456</span><br></span></code></pre></div></div>
<p>This is useful for scripted workflows, like automatically attaching exported reports to a monthly budget review task.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="attach-once-reference-everywhere">Attach Once, Reference Everywhere<a href="https://oatnil.com/blog/2026/04/05/resource-management#attach-once-reference-everywhere" class="hash-link" aria-label="Direct link to Attach Once, Reference Everywhere" title="Direct link to Attach Once, Reference Everywhere">​</a></h2>
<p>A single resource can be linked to multiple items. If you have a contract PDF that is relevant to both a budget and a task, you attach it to both — no duplicates, no hunting through folders. When the task gets marked done, you can unlink the file from it while keeping it attached to the budget.</p>
<p>The inspector panel shows you exactly where a file is attached, so you never lose track of which items reference it.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/resource-management/task-with-attachments.png" alt="Task list view — files attach directly to tasks, expenses, and budgets" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="find-what-you-need">Find What You Need<a href="https://oatnil.com/blog/2026/04/05/resource-management#find-what-you-need" class="hash-link" aria-label="Direct link to Find What You Need" title="Direct link to Find What You Need">​</a></h2>
<p>The Resources page gives you a full overview of uploaded files with filtering built in. You can narrow down by file type, by which entity type the file is attached to (tasks, expenses, budgets, accounts), or by a date range. For images, the gallery view shows thumbnails so you can visually scan a set of receipts or screenshots without opening each one.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/resource-management/resources-grid.png" alt="Resource management page showing uploaded files with thumbnails and metadata" class="img_ev3q"></p>
<p>The inspector also surfaces EXIF metadata for photos — useful if you need to confirm when a photo was taken or where.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="storage-local-or-s3">Storage: Local or S3<a href="https://oatnil.com/blog/2026/04/05/resource-management#storage-local-or-s3" class="hash-link" aria-label="Direct link to Storage: Local or S3" title="Direct link to Storage: Local or S3">​</a></h2>
<p>Because UnDercontrol is self-hosted, you control where your files are stored. The default is local disk storage, which works fine for personal use on a home server or VPS. For larger setups or remote access, you can configure S3-compatible object storage — AWS S3, Backblaze B2, MinIO, whatever you already have.</p>
<p>The key point: your files do not flow through a third-party service. They go from your browser to your server.</p>
<p>Storage limits are configurable. Regular users get 1 GB and a 10 MB per-file cap by default. Admins can operate without limits.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="drawio-diagrams-in-app">Drawio Diagrams, In-App<a href="https://oatnil.com/blog/2026/04/05/resource-management#drawio-diagrams-in-app" class="hash-link" aria-label="Direct link to Drawio Diagrams, In-App" title="Direct link to Drawio Diagrams, In-App">​</a></h2>
<p>If your workflow involves system diagrams, flow charts, or architecture sketches, UnDercontrol includes built-in drawio support. You can create and edit diagrams directly in the app. No export-to-PNG, no switching to a separate tool and re-uploading. The diagram file lives as a resource attached to whatever task or note it belongs to.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="ai-on-images">AI on Images<a href="https://oatnil.com/blog/2026/04/05/resource-management#ai-on-images" class="hash-link" aria-label="Direct link to AI on Images" title="Direct link to AI on Images">​</a></h2>
<p>For receipts and document scans, the AI integration is worth knowing about. You can open an image resource and ask the AI to extract information from it — line items from a receipt, text from a scanned form. This feeds naturally into the expense tracking workflow: photograph a receipt, attach it to an expense, and let the AI pull out the amount and merchant name.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-this-looks-like-day-to-day">What This Looks Like Day-to-Day<a href="https://oatnil.com/blog/2026/04/05/resource-management#what-this-looks-like-day-to-day" class="hash-link" aria-label="Direct link to What This Looks Like Day-to-Day" title="Direct link to What This Looks Like Day-to-Day">​</a></h2>
<p>A practical example: you are tracking a freelance project. There is a task for "Review client contract." You attach the contract PDF to that task. Later, you log an expense for software you purchased for the project. You attach the invoice to the expense. Both files show up in the Resources page, filtered by entity type if you want to see only expense attachments, or together if you want a full file audit for the project.</p>
<p>Everything is in one place. No external file hosting, no email threads to dig through.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/resource-management/budget-overview.png" alt="Budget overview — receipts and invoices attach to expenses within budgets" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="get-started">Get Started<a href="https://oatnil.com/blog/2026/04/05/resource-management#get-started" class="hash-link" aria-label="Direct link to Get Started" title="Direct link to Get Started">​</a></h2>
<p>If you are already running UnDercontrol, the Resources page is available in the main navigation. If you are evaluating whether to self-host, the <a href="https://oatnil.com/docs/self-deployment">deployment guide</a> covers setting up your instance including storage configuration.</p>
<p>The <a href="https://oatnil.com/docs/features/resources">Resource Management documentation</a> has the full reference for CLI commands, storage configuration, and entity attachment options.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Own Your Data with Self-Hosting]]></title>
            <link>https://oatnil.com/blog/2026/04/05/self-hosting</link>
            <guid>https://oatnil.com/blog/2026/04/05/self-hosting</guid>
            <pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Deploy UnDercontrol on your own infrastructure with Docker Compose or Kubernetes — full data ownership, no vendor lock-in, your choice of SQLite or PostgreSQL.]]></description>
            <content:encoded><![CDATA[<p>There is a certain kind of frustration that builds slowly. You sign up for a productivity app, migrate your tasks and finances into it, build habits around it — and then one day the pricing changes, the company pivots, or worse, the service shuts down. Your data is gone or locked behind an export button that produces something barely usable.</p>
<p>UnDercontrol was built from the start to avoid that situation entirely. It is self-hosted, which means you run it on infrastructure you control, and your data lives wherever you put it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="deploy-in-minutes-with-docker-compose">Deploy in Minutes with Docker Compose<a href="https://oatnil.com/blog/2026/04/05/self-hosting#deploy-in-minutes-with-docker-compose" class="hash-link" aria-label="Direct link to Deploy in Minutes with Docker Compose" title="Direct link to Deploy in Minutes with Docker Compose">​</a></h2>
<p>For most people, Docker Compose is the fastest path to a running instance. A single <code>docker-compose.yml</code> file pulls the backend and frontend images, wires them together, and has UnDercontrol running on your server or local machine in a few minutes.</p>
<p>A minimal setup looks roughly like this: a backend service with your data directory mounted as a volume, a frontend service pointing at it, and an optional reverse proxy like Caddy or Nginx in front. That is genuinely all there is to it for a single-user or small household deployment. No managed cloud accounts, no API keys to a third-party service, no data leaving your network.</p>
<p>The Docker image is designed to be small and predictable. It does not phone home, it does not require an internet connection after the initial pull, and it stores everything in the paths you configure.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/self-hosting/dashboard.png" alt="UnDercontrol dashboard — self-hosted and fully under your control" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="kubernetes-for-teams-and-power-users">Kubernetes for Teams and Power Users<a href="https://oatnil.com/blog/2026/04/05/self-hosting#kubernetes-for-teams-and-power-users" class="hash-link" aria-label="Direct link to Kubernetes for Teams and Power Users" title="Direct link to Kubernetes for Teams and Power Users">​</a></h2>
<p>If you are running a homelab with Kubernetes, or you want the kind of reliability that comes with proper orchestration, UnDercontrol ships with Kubernetes manifests as well. You get standard Deployments and Services, PersistentVolumeClaims for your data, and ConfigMaps for environment-specific settings.</p>
<p>This is particularly useful if you are deploying UnDercontrol for a small team — a family, a group of friends, a small company — where you want proper resource limits, rolling updates, and the ability to scale the backend independently if needed. Kubernetes also makes it straightforward to add ingress rules, TLS termination, and namespace-level isolation.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="sqlite-or-postgresql--pick-what-fits">SQLite or PostgreSQL — Pick What Fits<a href="https://oatnil.com/blog/2026/04/05/self-hosting#sqlite-or-postgresql--pick-what-fits" class="hash-link" aria-label="Direct link to SQLite or PostgreSQL — Pick What Fits" title="Direct link to SQLite or PostgreSQL — Pick What Fits">​</a></h2>
<p>One of the practical decisions UnDercontrol makes easy is choosing your database. For a single user or a small number of users, SQLite is the default and it works extremely well. There is no database server to manage, no connection pooling to configure, and backups are as simple as copying a file. SQLite is surprisingly capable under these conditions, and it keeps the operational overhead close to zero.</p>
<p>When you need more — concurrent users, larger datasets, integration with existing database infrastructure — switching to PostgreSQL is a matter of changing your environment variables and running migrations. The schema is identical across both backends. You do not have to redesign anything; you just point the application at your Postgres instance and it works.</p>
<p>This flexibility matters because your needs change over time. Starting with SQLite and migrating later is a supported path, not an afterthought.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-full-data-ownership-actually-means">What Full Data Ownership Actually Means<a href="https://oatnil.com/blog/2026/04/05/self-hosting#what-full-data-ownership-actually-means" class="hash-link" aria-label="Direct link to What Full Data Ownership Actually Means" title="Direct link to What Full Data Ownership Actually Means">​</a></h2>
<p>Self-hosting means your tasks, your financial records, your uploaded files, and your AI conversation history all live on storage you control. If you want to move to a different server, you copy your data directory and update your deployment. If you want to back up everything, you back up that directory. If you decide to stop using UnDercontrol entirely, your data is still there, in formats you can read.</p>
<p>There is no account to delete, no support ticket to file, no waiting period. The data is yours because it was always on your machine.</p>
<p>This also means you control who has access. Running UnDercontrol on a private network or behind a VPN means your finance data never touches the public internet unless you explicitly route it there. For people who track detailed budgets or sensitive personal information, that is not a small thing.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="no-vendor-lock-in-by-design">No Vendor Lock-in by Design<a href="https://oatnil.com/blog/2026/04/05/self-hosting#no-vendor-lock-in-by-design" class="hash-link" aria-label="Direct link to No Vendor Lock-in by Design" title="Direct link to No Vendor Lock-in by Design">​</a></h2>
<p>The backend API is documented and open. The CLI uses kubectl-style commands and works against the same API the web app uses. You can script against it, integrate it with other tools, or build your own clients. The task and note formats are designed to be portable.</p>
<p>The goal was always to build something that earns your continued use because it is genuinely useful — not because migrating away is too painful to bother with.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="get-started">Get Started<a href="https://oatnil.com/blog/2026/04/05/self-hosting#get-started" class="hash-link" aria-label="Direct link to Get Started" title="Direct link to Get Started">​</a></h2>
<p>The deployment guide covers Docker Compose setup, Kubernetes manifests, database configuration, and backup strategies in detail. If you have a server or a spare machine running, you can have a working instance today.</p>
<p>Check out the <a href="https://oatnil.com/docs/self-deployment">self-hosting documentation</a> to get started, or open an issue on GitHub if something in the setup does not work the way you expect.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[AI-Powered Workflows in UnDercontrol — From Receipt to Record in Seconds]]></title>
            <link>https://oatnil.com/blog/2026/04/04/ai-assistant</link>
            <guid>https://oatnil.com/blog/2026/04/04/ai-assistant</guid>
            <pubDate>Sat, 04 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how UnDercontrol's AI assistant turns photos, text, and voice into structured expenses and tasks — including Apple Shortcuts for one-tap capture.]]></description>
            <content:encoded><![CDATA[<p>The part of personal finance and task management that nobody enjoys is the data entry. You finish lunch, you have a receipt, and now you have to open an app, tap through a form, type in the amount, pick a category, and save. Multiply that by every coffee, taxi, and grocery run and it becomes a real friction point.</p>
<p>UnDercontrol's AI assistant is built to eliminate that friction. Here is how it actually works in practice.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ai-assistant/dashboard.png" alt="UnDercontrol dashboard — AI assistant integrates across all features" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="snap-a-receipt-skip-the-form">Snap a Receipt, Skip the Form<a href="https://oatnil.com/blog/2026/04/04/ai-assistant#snap-a-receipt-skip-the-form" class="hash-link" aria-label="Direct link to Snap a Receipt, Skip the Form" title="Direct link to Snap a Receipt, Skip the Form">​</a></h2>
<p>The most immediately useful AI feature is receipt scanning. When you create a new expense, you can upload a photo — drag and drop, paste from clipboard, or select a file. The AI reads the image and extracts the amount, currency, merchant name, date, and a suggested category.</p>
<p>It handles crumpled receipts, tilted angles, and different receipt formats reasonably well. The key practical tip: good lighting matters more than perfect alignment. A clear photo in decent light will almost always parse correctly. A blurry photo taken in a dim restaurant probably will not.</p>
<p>You can also batch-upload multiple receipts at once, which is useful after a work trip or a week where you let things pile up.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="text-input-for-quick-logging">Text Input for Quick Logging<a href="https://oatnil.com/blog/2026/04/04/ai-assistant#text-input-for-quick-logging" class="hash-link" aria-label="Direct link to Text Input for Quick Logging" title="Direct link to Text Input for Quick Logging">​</a></h2>
<p>Not every expense comes with a receipt. For those, you can just describe it in plain English:</p>
<ul>
<li>"Lunch at the noodle place, 18 dollars"</li>
<li>"Uber home from the airport, 34 EUR"</li>
<li>"Monthly Figma subscription 15 USD"</li>
</ul>
<p>The AI parses the description into a structured expense with the right fields filled in. It is faster than tapping through a form, especially on mobile.</p>
<p>The same pattern works for tasks. Instead of filling out a task form, you describe what needs to happen:</p>
<ul>
<li>"Follow up with the accountant about Q1 taxes"</li>
<li>"Buy a birthday gift for Sarah before Friday"</li>
<li>"Research self-hosted backup solutions"</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ai-assistant/task-list.png" alt="Task list — AI can create and manage tasks from text or voice input" class="img_ev3q"></p>
<p>UnDercontrol creates a structured task from the description, including a title, any relevant tags it can infer, and a description. You review it, adjust anything that looks off, and save.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ai-assistant/ai-chat.png" alt="AI assistant chat interface for logging expenses and creating tasks" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="natural-language-queries">Natural Language Queries<a href="https://oatnil.com/blog/2026/04/04/ai-assistant#natural-language-queries" class="hash-link" aria-label="Direct link to Natural Language Queries" title="Direct link to Natural Language Queries">​</a></h2>
<p>Once you have tasks and expenses in the system, you can ask questions about them in plain language. Things like "show me overdue tasks" or "what is due this week" get translated into a query and return the matching results. It is not magic — it works best with straightforward questions — but it removes the need to remember UnDercontrol's query syntax for everyday lookups.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="apple-shortcuts-integration">Apple Shortcuts Integration<a href="https://oatnil.com/blog/2026/04/04/ai-assistant#apple-shortcuts-integration" class="hash-link" aria-label="Direct link to Apple Shortcuts Integration" title="Direct link to Apple Shortcuts Integration">​</a></h2>
<p>On iOS and macOS, UnDercontrol provides Apple Shortcuts that wire the AI features into the system share sheet and shortcut automation. The practical upside is one-tap capture from anywhere on your device.</p>
<p>The most useful shortcut: snap a photo with your camera, run the shortcut, and the receipt gets logged as an expense without ever opening the app. You can also share text — a copied email snippet, a message, a note — and have a task created from it directly.</p>
<p>The shortcuts are available to download from the Subscribe/Download page in your UnDercontrol instance.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="bring-your-own-ai-provider">Bring Your Own AI Provider<a href="https://oatnil.com/blog/2026/04/04/ai-assistant#bring-your-own-ai-provider" class="hash-link" aria-label="Direct link to Bring Your Own AI Provider" title="Direct link to Bring Your Own AI Provider">​</a></h2>
<p>UnDercontrol does not lock you into one AI backend. You can connect your own API key from OpenAI, Anthropic, or any OpenAI-compatible service. There is also support for local models via Ollama or LM Studio if you want everything running on your own hardware with no external API calls at all.</p>
<p>Setup is straightforward: go to Profile settings, find the AI Providers section, add your provider and API key, pick a model, and test the connection. You can add multiple providers and prioritize them. The first working provider in your list is what gets used.</p>
<p>If you are running a shared UnDercontrol instance, an administrator can also configure system-level providers that are available to all users — useful for a family server or a small team.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-to-keep-in-mind">What to Keep in Mind<a href="https://oatnil.com/blog/2026/04/04/ai-assistant#what-to-keep-in-mind" class="hash-link" aria-label="Direct link to What to Keep in Mind" title="Direct link to What to Keep in Mind">​</a></h2>
<p>AI extraction is good but not perfect. Always glance at the parsed result before saving, especially the amount and date fields. A receipt where the total is visually close to a subtotal line can confuse the parser. Two seconds of review is faster than hunting down a mis-logged expense later.</p>
<p>For text input, more specific descriptions give better results. "Coffee" is harder to categorize than "coffee at the airport before the flight, 6.50 USD."</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting Started<a href="https://oatnil.com/blog/2026/04/04/ai-assistant#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started">​</a></h2>
<p>If you are already running UnDercontrol, head to your Profile settings and add an AI provider. Then try uploading a receipt the next time you log an expense — the difference in workflow is noticeable immediately.</p>
<p>If you have not set up UnDercontrol yet, the self-hosting guide covers everything from Docker deployment to configuration: <a href="https://undercontrol.dev/docs/intro" target="_blank" rel="noopener noreferrer">UnDercontrol Documentation</a>.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Take Control of Your Personal Finances with UnDercontrol's Budget Tracker]]></title>
            <link>https://oatnil.com/blog/2026/04/04/budget-expense-tracking</link>
            <guid>https://oatnil.com/blog/2026/04/04/budget-expense-tracking</guid>
            <pubDate>Sat, 04 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how UnDercontrol's budget management feature helps you track spending, log expenses, and stay on top of your finances — all self-hosted.]]></description>
            <content:encoded><![CDATA[<p>Most personal finance tools make you choose between privacy and functionality. Either you hand over your financial data to a cloud service, or you settle for a spreadsheet that breaks the moment your situation gets even slightly complicated. UnDercontrol takes a different approach: a full-featured budget tracker that runs on your own infrastructure, with your data staying exactly where it belongs.</p>
<p>Here's a practical look at how budget tracking works in UnDercontrol.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="creating-a-budget-that-reflects-reality">Creating a Budget That Reflects Reality<a href="https://oatnil.com/blog/2026/04/04/budget-expense-tracking#creating-a-budget-that-reflects-reality" class="hash-link" aria-label="Direct link to Creating a Budget That Reflects Reality" title="Direct link to Creating a Budget That Reflects Reality">​</a></h2>
<p>Setting up a budget in UnDercontrol starts with the basics: a name, an initial amount, a start date, and a recurrence frequency. Weekly, monthly, quarterly, or yearly — pick whatever matches how you actually think about money.</p>
<p>But the real power comes from budget plans. Instead of locking you into a single fixed amount, UnDercontrol lets you add new plans over time. Say you start the year with a $500/month grocery budget, then prices go up and you need to revise it to $650 in March. You add a new plan with the updated amount. The system handles the math — calculating totals based on which plan was active during which periods — so your historical data stays accurate and your current view is always up to date.</p>
<p>One-time adjustments fill in the gaps that recurring plans can't cover. Got an unexpected refund? A bonus allocation from a project? A correction to fix a data entry mistake? Each adjustment records an amount, a date, and an optional reason. Future you will appreciate those reasons when reconciling months later.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="logging-expenses-and-linking-them-to-budgets">Logging Expenses and Linking Them to Budgets<a href="https://oatnil.com/blog/2026/04/04/budget-expense-tracking#logging-expenses-and-linking-them-to-budgets" class="hash-link" aria-label="Direct link to Logging Expenses and Linking Them to Budgets" title="Direct link to Logging Expenses and Linking Them to Budgets">​</a></h2>
<p>Expenses in UnDercontrol are first-class records. When you log an expense, you can link it to a specific budget. That link is what powers the "budget vs. actual" view — the expense amount rolls up into the budget's spent total, and it appears in that budget's ledger.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/budget-expense-tracking/transactions.png" alt="Transaction list with expense entries" class="img_ev3q"></p>
<p>This design means you can also have expenses that aren't tied to any budget, which is intentional. Not every transaction needs to be categorized immediately. You can log it, come back later, and link it when it makes sense.</p>
<p>The budget detail page brings everything together: a hero section showing total allocated, total spent, and remaining balance at a glance, plus a spending trend chart that plots your actual spend against the budget line over 7, 30, or 90 days. If you're running a monthly grocery budget and the chart shows you've crossed the allocation line by day 22, that's a clear signal — no mental math required.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/budget-expense-tracking/budget-detail.png" alt="Budget detail with spending trend chart" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="multi-account-support-and-the-full-picture">Multi-Account Support and the Full Picture<a href="https://oatnil.com/blog/2026/04/04/budget-expense-tracking#multi-account-support-and-the-full-picture" class="hash-link" aria-label="Direct link to Multi-Account Support and the Full Picture" title="Direct link to Multi-Account Support and the Full Picture">​</a></h2>
<p>UnDercontrol's account system lets you track money across multiple sources — checking, savings, a separate business account, whatever your setup looks like. Budget accounts contribute to your overall available balance, so when you're planning a new budget, you're working with real numbers rather than guesswork.</p>
<p>This becomes especially useful when you're managing finances for a small team or household. The collaboration system lets you share budgets with other users, so multiple people can log expenses against the same budget and see the same real-time totals. No more emailing spreadsheets back and forth or manually reconciling two separate tracking systems.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="staying-on-top-of-spending-without-the-overhead">Staying on Top of Spending Without the Overhead<a href="https://oatnil.com/blog/2026/04/04/budget-expense-tracking#staying-on-top-of-spending-without-the-overhead" class="hash-link" aria-label="Direct link to Staying on Top of Spending Without the Overhead" title="Direct link to Staying on Top of Spending Without the Overhead">​</a></h2>
<p>The budget list page is designed to give you a quick read on everything at once. Progress bars, spent and remaining amounts, and a summary sidebar with aggregated totals across all budgets. Search to find specific budgets quickly. A "Show hidden" toggle for budgets you want to archive without deleting.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/budget-expense-tracking/budget-list.png" alt="Budget overview with progress bars showing spent vs remaining" class="img_ev3q"></p>
<p>Privacy mode deserves a mention: toggle it to hide all monetary amounts across the interface. Useful when you're screen sharing during a meeting and don't need to explain your personal grocery budget to coworkers.</p>
<p>For reporting and data portability, UnDercontrol supports data export so you can pull your expense history out in a structured format. Since you're self-hosting, you also have direct access to the underlying database if you need to run your own queries or pipe data into another tool.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="self-hosted-your-rules">Self-Hosted, Your Rules<a href="https://oatnil.com/blog/2026/04/04/budget-expense-tracking#self-hosted-your-rules" class="hash-link" aria-label="Direct link to Self-Hosted, Your Rules" title="Direct link to Self-Hosted, Your Rules">​</a></h2>
<p>Everything described here runs on your own server. Your financial data doesn't pass through anyone else's infrastructure. You control the backups, the access, and the retention policy. For anyone who's ever felt uncomfortable typing their bank transactions into a third-party app, that matters.</p>
<p>If you want to get started, the <a href="https://oatnil.com/docs/self-deployment">self-deployment guide</a> walks through setting up UnDercontrol with Docker. The <a href="https://oatnil.com/docs/features/budget">budget documentation</a> covers every feature in detail, including how budget totals are calculated and how to use adjustments effectively.</p>
<p>Set up your first budget, link a few expenses, and check back in a week. The spending trend chart will tell you more than any spreadsheet ever did.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[Visual Project Management with Kanban Boards in UnderControl]]></title>
            <link>https://oatnil.com/blog/2026/04/04/kanban-boards</link>
            <guid>https://oatnil.com/blog/2026/04/04/kanban-boards</guid>
            <pubDate>Sat, 04 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how UnderControl's kanban boards bring drag-and-drop task management with auto status updates, custom columns, and team sharing to your self-hosted setup.]]></description>
            <content:encoded><![CDATA[<p>If you have been managing tasks through a flat list and wondering when things started feeling unwieldy, kanban boards are probably the answer. UnderControl's kanban view gives you a column-based layout over your existing tasks — no separate system to maintain, no data duplication, just a better way to see what is actually happening.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-board-is-a-view-not-a-silo">The Board is a View, Not a Silo<a href="https://oatnil.com/blog/2026/04/04/kanban-boards#the-board-is-a-view-not-a-silo" class="hash-link" aria-label="Direct link to The Board is a View, Not a Silo" title="Direct link to The Board is a View, Not a Silo">​</a></h2>
<p>This is the part worth understanding before anything else. In UnderControl, a kanban board is a visual layer on top of your task system. When you drag a card from "Todo" to "In Progress," you are not just moving a card on a board — you are updating the task's status. That change shows up immediately in your task list, your CLI queries, everywhere. There is no synchronization problem because there is only one source of truth.</p>
<p>This means you can switch between kanban and list views freely without worrying about things getting out of sync.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started-the-all-tasks-board">Getting Started: The All Tasks Board<a href="https://oatnil.com/blog/2026/04/04/kanban-boards#getting-started-the-all-tasks-board" class="hash-link" aria-label="Direct link to Getting Started: The All Tasks Board" title="Direct link to Getting Started: The All Tasks Board">​</a></h2>
<p>Every account comes with a built-in "All Tasks" board. Open it and you will see all your tasks organized across six columns: Todo, In Progress, Pending, Stale, Done, and Archived. This board cannot be deleted and it always appears first.</p>
<p>It is a good place to start. Drag a task from Todo to In Progress, and watch the status update instantly. That is the basic loop — visual placement drives task state, not the other way around.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="custom-boards-with-query-based-columns">Custom Boards with Query-Based Columns<a href="https://oatnil.com/blog/2026/04/04/kanban-boards#custom-boards-with-query-based-columns" class="hash-link" aria-label="Direct link to Custom Boards with Query-Based Columns" title="Direct link to Custom Boards with Query-Based Columns">​</a></h2>
<p>Where things get interesting is when you create your own boards. Each column is defined by a filter condition — essentially a query that determines which tasks belong there. A "Blocked" column might filter for tasks tagged <code>blocked</code>. A "Due This Week" column could filter on due date. The column is not just a label; it is a live query.</p>
<p>When you configure column actions, moving a card becomes a trigger. Drag a task into the "Done" column and it gets marked done automatically. Move something into a "Needs Review" column and it can be tagged and assigned in one gesture. You define what dragging into a column means, and the board handles the bookkeeping.</p>
<p>This is genuinely useful for multi-step workflows. If your process is something like Draft -&gt; Review -&gt; Ready to Ship -&gt; Done, you can model that exactly — with each column transition doing the right thing to the underlying task data.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="private-and-shared-boards">Private and Shared Boards<a href="https://oatnil.com/blog/2026/04/04/kanban-boards#private-and-shared-boards" class="hash-link" aria-label="Direct link to Private and Shared Boards" title="Direct link to Private and Shared Boards">​</a></h2>
<p>Private boards are scoped to you. You can create as many as you need — one for a side project, one for your weekly planning, one for a home renovation. They all pull from your full task pool, filtered through whatever columns you configure.</p>
<p>Shared boards work differently. When you share a board with a group, the board only shows tasks that belong to that group. Everyone in the group can see the board and move cards around based on their permissions. It is a clean model: the board's scope is the group, and access is controlled through group membership.</p>
<p>This makes shared boards practical for small teams. A sprint board for a two- or three-person team, where everyone can see what is in progress and what is blocked, is easy to set up and does not require any additional tooling.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="a-few-workflow-patterns-worth-trying">A Few Workflow Patterns Worth Trying<a href="https://oatnil.com/blog/2026/04/04/kanban-boards#a-few-workflow-patterns-worth-trying" class="hash-link" aria-label="Direct link to A Few Workflow Patterns Worth Trying" title="Direct link to A Few Workflow Patterns Worth Trying">​</a></h2>
<p>If you are not sure how to structure a board, here are a few approaches that work well:</p>
<p>A <strong>status-based board</strong> mirrors the default setup — Todo, In Progress, Done. Simple, low-overhead, good for solo work.</p>
<p>A <strong>time-based board</strong> uses columns like Backlog, This Week, Today, and Done. The filter conditions look at due dates or custom fields rather than status. Dragging a task into "Today" can update a priority field automatically.</p>
<p>A <strong>project board</strong> with shared access uses the group feature to scope tasks to a specific project. Columns map to your team's actual workflow stages, and everyone operates from the same board.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="everything-stays-connected">Everything Stays Connected<a href="https://oatnil.com/blog/2026/04/04/kanban-boards#everything-stays-connected" class="hash-link" aria-label="Direct link to Everything Stays Connected" title="Direct link to Everything Stays Connected">​</a></h2>
<p>Because boards are built on top of the same task and tag system as the rest of UnderControl, they compose naturally with other features. Tags you use in task filters work as column conditions. Custom fields you create for a project can drive column membership and be updated by column actions. The board is not a separate module — it is the task system made visual.</p>
<p>If you want to see how this fits into the broader setup, the <a href="https://oatnil.com/docs/features/kanban">Kanban Boards documentation</a> covers column configuration, automatic actions, and sharing in detail. And if you are not running UnderControl yet, the <a href="https://oatnil.com/docs/self-deployment">self-hosting guide</a> will get you up and running in under an hour — your data stays on your infrastructure, always.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
        <item>
            <title><![CDATA[How UnderControl Handles Task Management: Views, Links, and the CLI]]></title>
            <link>https://oatnil.com/blog/2026/04/04/task-management-overview</link>
            <guid>https://oatnil.com/blog/2026/04/04/task-management-overview</guid>
            <pubDate>Sat, 04 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A practical look at UnderControl's task system — multiple views, bidirectional linking, markdown notes, and a kubectl-style CLI for power users.]]></description>
            <content:encoded><![CDATA[<p>Most task managers give you a list. Maybe a kanban board if you're lucky. UnderControl takes a different approach: your tasks are a data structure you can view, query, and manipulate from multiple angles — whether you're in the browser, the desktop app, or a terminal.</p>
<p>Here's a practical walkthrough of how the task system works.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="six-statuses-that-actually-mean-something">Six Statuses That Actually Mean Something<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#six-statuses-that-actually-mean-something" class="hash-link" aria-label="Direct link to Six Statuses That Actually Mean Something" title="Direct link to Six Statuses That Actually Mean Something">​</a></h2>
<p>Tasks in UnderControl move through six statuses: <strong>Todo</strong>, <strong>In Progress</strong>, <strong>Pending</strong>, <strong>Done</strong>, <strong>Stale</strong>, and <strong>Archived</strong>.</p>
<p>The distinction between Pending and Stale is one I find genuinely useful. Pending means you're deliberately waiting — on a reply, a dependency, a decision. Stale means the task just hasn't been touched in a while. That difference matters when you're doing a weekly review and trying to figure out what to act on versus what to clean up.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="pick-the-view-that-matches-your-mental-model">Pick the View That Matches Your Mental Model<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#pick-the-view-that-matches-your-mental-model" class="hash-link" aria-label="Direct link to Pick the View That Matches Your Mental Model" title="Direct link to Pick the View That Matches Your Mental Model">​</a></h2>
<p>Different work calls for different views. UnderControl gives you seven:</p>
<ul>
<li><strong>List</strong> — The default. Fast, filterable, keyboard-friendly.</li>
<li><strong>Kanban</strong> — Drag cards between status columns. Good for sprint-style work.</li>
<li><strong>Calendar</strong> — Tasks plotted by deadline. Useful before a busy week.</li>
<li><strong>Tree</strong> — Hierarchical view of parent and child tasks. Great for projects with nested sub-tasks.</li>
<li><strong>Graph</strong> — A node-link diagram of all your bidirectional task relationships. Looks cool, but also genuinely useful for spotting how things connect.</li>
<li><strong>Mindmap</strong> — Freeform brainstorming layout.</li>
<li><strong>Trash</strong> — Review and restore deleted tasks.</li>
</ul>
<p>Switching between views doesn't change your data — it's the same tasks, just rendered differently. The Graph view in particular is worth trying if you use linked tasks heavily.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="bidirectional-linking">Bidirectional Linking<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#bidirectional-linking" class="hash-link" aria-label="Direct link to Bidirectional Linking" title="Direct link to Bidirectional Linking">​</a></h2>
<p>You can link any two tasks together, and the connection is bidirectional — follow it from either side. Over time, this turns your task list into something closer to a knowledge graph. The Graph view is where this becomes visible: nodes are tasks, edges are links, and you can see clusters form around related work.</p>
<p>Derived tasks (parent-child relationships) work differently. When you create a task from an existing one, the child inherits context from the parent and links back to it automatically. This is the right pattern for breaking down a large project into concrete steps.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="markdown-notes-with-edit-history">Markdown Notes with Edit History<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#markdown-notes-with-edit-history" class="hash-link" aria-label="Direct link to Markdown Notes with Edit History" title="Direct link to Markdown Notes with Edit History">​</a></h2>
<p>Every task has a notes section. Notes support full markdown — headers, code blocks, lists, links. More importantly, every note keeps a full edit history. If you wrote something, changed your mind, and want to go back, you can revert to any previous version.</p>
<p>I use notes for things like decision logs, blockers, and meeting summaries attached to a task. It keeps everything in one place instead of scattered across documents and chat threads.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="filtering-and-queries">Filtering and Queries<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#filtering-and-queries" class="hash-link" aria-label="Direct link to Filtering and Queries" title="Direct link to Filtering and Queries">​</a></h2>
<p>The quick filters cover the common cases: filter by status, tags, or deadline range (overdue, today, this week, and so on). For more specific needs, there's a structured query language:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">status = 'pending' AND tag:work = 'true'</span><br></span></code></pre></div></div>
<p>If you don't want to write the query yourself, the AI assistant accepts plain English — something like "show me overdue tasks tagged with client work" — and translates it into a filter.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="file-attachments-and-sharing">File Attachments and Sharing<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#file-attachments-and-sharing" class="hash-link" aria-label="Direct link to File Attachments and Sharing" title="Direct link to File Attachments and Sharing">​</a></h2>
<p>Attach any file to a task — images, PDFs, diagrams, exported reports. The attachments live on your server, under your control.</p>
<p>For sharing, you can generate a public link to a specific task with an optional expiration date. Or share with a group and set whether they get read or read-write access.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-cli-is-a-first-class-interface">The CLI Is a First-Class Interface<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#the-cli-is-a-first-class-interface" class="hash-link" aria-label="Direct link to The CLI Is a First-Class Interface" title="Direct link to The CLI Is a First-Class Interface">​</a></h2>
<p>If you work in a terminal, <code>ud</code> gives you the full task API from the command line:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud task list</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task create "Review Q2 metrics"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task done &lt;id&gt;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task query "status = 'todo' AND deadline &lt; '2026-05-01'"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task apply -f task.md</span><br></span></code></pre></div></div>
<p>The <code>apply</code> command is particularly useful — you can write a task in a markdown file and push it to UnderControl, which fits nicely into scripting and automation workflows. The CLI follows kubectl-style conventions, so if you spend time in Kubernetes or similar tools, the patterns feel familiar.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="recurring-tasks-and-check-ins">Recurring Tasks and Check-ins<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#recurring-tasks-and-check-ins" class="hash-link" aria-label="Direct link to Recurring Tasks and Check-ins" title="Direct link to Recurring Tasks and Check-ins">​</a></h2>
<p>For repeating work, recurring tasks generate new task instances on a schedule — daily standups, weekly reviews, monthly reports. You can use presets or write a custom CRON expression.</p>
<p>Check-ins are a lighter-weight alternative for habit tracking: each check-in increments a counter and records a timestamp, giving you a simple log of consistency without the overhead of a full task lifecycle.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="try-it-yourself">Try It Yourself<a href="https://oatnil.com/blog/2026/04/04/task-management-overview#try-it-yourself" class="hash-link" aria-label="Direct link to Try It Yourself" title="Direct link to Try It Yourself">​</a></h2>
<p>All of this runs on your own infrastructure. No data leaves your server, no subscriptions, no vendor lock-in.</p>
<p>The full task management documentation is at <a href="https://undercontrol.dev/docs/tasks" target="_blank" rel="noopener noreferrer">undercontrol.dev/docs/tasks</a>, and the self-hosting guide covers deployment with Docker or the prebuilt binaries. If you run into anything, the GitHub repo is the right place to file issues or ask questions.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
    </channel>
</rss>