AppdorTry it

An AI off switch that actually stays off

AI · 5 min read

The loudest complaint we found about AI in this category is not about money.

We expected it to be. Credit pools, per-agent pricing, "$80 a month for one agent doing two tasks" — that is all real and it is all noisy. But underneath it is a quieter and angrier line, and it is about presence:

"It's always there on the bottom right corner, and I CANT get rid of it."

That person is not asking for a cheaper plan. Bringing your own API key does not answer them either, and we know that because we shipped that first and it did not answer them. They are asking for the button to be gone.

The bar we set

A workspace admin unticks one box, and:

  1. every AI affordance leaves the interface for everyone in that workspace, and
  2. a request that reaches the AI service anyway is refused.

Either half on its own is a checkbox that lies. Hiding without refusing leaves the API open to anything that still knows the URL. Refusing without hiding leaves a button on screen for people to click into an error, which is a worse experience than the one they complained about.

Why it is one CSS rule and not fifty checks

The obvious way to build this is if (aiEnabled) at every AI entry point. That is correct on the day you write it and wrong on the day somebody adds the next one, because nothing makes them remember.

So instead: every AI affordance carries one marker class, and a single rule keyed off an attribute on the document root removes all of them at once. A surface added after the switch was thrown is hidden too, because it inherits the rule rather than needing to be told about it. The server half is the same shape — one guard in front of the generation routes, which answer 403 ai-disabled rather than quietly doing nothing.

The part we want to be judged on: it fails closed

If the flag cannot be read, the tempting default is "on" — a transient database error should not degrade a working product.

That default is wrong here, and only here. The entire feature exists because users say the AI keeps coming back. A network blip that puts the sidebar back on screen reproduces the exact complaint, and the person looking at it cannot distinguish it from a vendor ignoring them. So a successful read is cached, a failed read uses the cached answer, and an unreadable policy answers 503 rather than serving. A kill switch that errors open silently re-enables the thing an admin turned off.

The one case that defaults to on is a workspace that has never been read at all, which is genuinely unknown rather than merely unavailable.

Seven surfaces are not covered, and here is the list

This is the part most product pages leave out.

The switch covers 7 registered surfaces. There are 7 more it does not cover, and each one is recorded in src/ai/policy.js with the reason, pinned by a test so the list cannot rot quietly.

They are not oversights, and the reasons are worth reading if you are evaluating this seriously. The AI column types are the clearest: unregistering them would hide them from the new-column picker and simultaneously make every existing AI column in the workspace fall back to unknown. Turning the switch off would damage data the workspace already had. The right fix leaves them resolvable and excludes them from the picker, and we have not built it yet.

We would rather publish that sentence than let you discover it.

What a browser found that the tests could not

We had a test for this. It walked the inventory and asked, for each registered surface, whether its file still carried the marker. It passed.

Then somebody signed in, turned the switch off, and looked at what was left on the screen. An "AI" button was still sitting in the application toolbar, and a "Create with AI" button on the applications header.

The cause is more useful than the fix. The test only ever walked inventory → tree. Nothing walked tree → inventory, so a surface nobody had listed lived in a file nobody opened — and both buttons were static markup in index.html, which the inventory had no reason to name. The test's own comment claimed it caught "an AI-looking affordance that carries no marker at all". It did not.

It does now: it walks the document and fails on any id containing ai as a word that is neither marked nor excused with a written reason. We checked it in both directions, because a new assertion that has never failed is not yet evidence.

Where the switch is

Settings → AI features → untick "AI is available in this workspace". It is a workspace-level decision made by an admin, not a per-user preference, because "I turned it off and my colleague still sees it" is the same complaint wearing a different hat.

And if you would rather not take our word for any of this: the refusal is an HTTP status. Turn it off, call the generation routes with your own session, and read what comes back.