# I'm building a companion island for my desktop AI assistant: approvals, a mascot, and a 14px wake strip

> Source: <https://dev.to/krish_verma_77e28d3fd63ca/im-building-a-companion-island-for-my-desktop-ai-assistant-approvals-a-mascot-and-a-14px-wake-a0l>
> Published: 2026-10-03 10:24:57+00:00

I'm building a desktop AI assistant, and it has a permission problem that no amount of prompt engineering fixes.

Ankita asks for approval before it does anything irreversible — sends a message, runs a script, opens a browser session. That's the right behaviour. The problem is *where* that question appears: in the main chat window. Minimise the app, hide it to the tray, alt-tab to your IDE — and the approval request sits there in a hidden window, waiting on you, while the agent sits there waiting on the request. Nobody is wrong here. But nothing happens.

So I'm building a companion island: a small always-on-top pill pinned to the top edge of the screen that only exists when the main window is gone. It's the one UI element the assistant is allowed to keep while it's otherwise out of your way.

The island has three views, and the sizes are deliberate constants, not vibes:

There's also a tucked view, which parks the tab just above the screen edge so only a 14-pixel strip shows. Fourteen pixels is the whole product thesis: just enough to notice peripherally, just enough to click to wake it. Hover it and it peeks out; click it and the main window comes back.

All of this geometry lives in a module with zero Electron imports, on purpose. `islandSizeFor(view, approvalCount)` clamps everything to the known presets — the comment in the code says it plainly: "a bad caller can never stretch the window." Positions, mode transitions, edge margins (8px so narrow displays never clip it) — all of it is unit-testable with plain `node:test`. I test desktop geometry the same way I test server code, because desktop geometry is the thing that breaks on everyone else's machine.

The interesting bit isn't the pill — it's the plumbing behind it. The `ApprovalRegistry` stores every pending request *on the entry itself*, not just as an emitted event. The code comment calls the island out by name: "a late subscriber such as the island can list what is still pending." The island subscribes late — it might be created after a request went out — and still gets the full list. The expanded view shows the tool name and what it wants to do, and you approve or deny from there without ever restoring the main window.

That's the design decision I'm proudest of: the island is not a second chat window. It doesn't let you talk to the agent. It does exactly two things — show status, and clear approvals. One job, no scope creep.

The island hosts a small companion mascot with states (idle, working, thinking), emotes, eye shapes, and cursor-follow — it glances toward your pointer. The animation engine is ported from Coucou's open-source Mochi engine (MIT licensed), re-skinned as Ankita's own character with original sound cues left out rather than ripped off. States are driven by tweens with easing functions, and the engine reports a `busy` flag so the renderer knows when everything has settled.

Is a mascot frivolous in a tool that runs shell commands for you? Maybe. But the island only earns its screen space if you leave it visible, and a tiny face that looks at you when you approve things is weirdly effective at that.

The island is riding along with two unreleased changes I'm finishing at the same time:

`http://127.0.0.1:<port>/callback` loopback redirect, S256 code exchange. No public URL, no domain, no broker to host. The token goes straight to the OS vault; the grant file keeps only the grant ID and timestamps. And `COMPOSIO_API_KEY` keeps working until the deprecation window closes, because breaking existing setups on a Tuesday is a choice you make once.
Both are in the Unreleased section of the changelog now. The island is the visible face of the same philosophy: the assistant should ask well, ask rarely, and never strand you.

I considered just firing OS notifications for approvals. They're free, they're native, and I already had the code path half-built for the proactive loop. I decided against it for a reason that's hard to measure: notifications are a queue you clear. An island is a presence you glance at. Pending approvals aren't interruptions competing with Slack — they're the agent's *state*, and state deserves a persistent surface. The 14px strip is my bet that ambient awareness beats interruptive pings.

It's not finished — I'm still debugging animation edge cases in the slide transitions as I write this. If you've built always-on-top companions, floating widgets, or anything that has to be visible-but-not-annoying, I'd genuinely like to hear what you learned. What's the right amount of pixels for a background presence?

Ankita is open source: [https://github.com/akyourowngames/A.N.K.I.T.A](https://github.com/akyourowngames/A.N.K.I.T.A)
