Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
3436a18
fix(docs): Thaw's repo is the source of six pages the site wrote itself
diazdesandi Oct 15, 2026
6b80496
fix(site): Thaw 2.1.0 is out
diazdesandi Oct 15, 2026
b961a3b
feat(site): AI tools are told where llms.txt and each page's Markdown…
diazdesandi Oct 15, 2026
7680744
feat(site): llms.txt says what Thaw and Floe are, and lists the site'…
diazdesandi Oct 15, 2026
1472757
fix(site): Raycast's mark is a vector, sharp at any size
diazdesandi Oct 15, 2026
b0c9cbe
fix(roadmap): Minimal mode and Modules come first, with DockDoor and …
diazdesandi Oct 18, 2026
b7ae549
fix(roadmap): the plan from #1353, a minimal Thaw first and the rest …
diazdesandi Oct 18, 2026
679a036
fix(roadmap): take DockDoor off until there is a plan to describe
diazdesandi Oct 18, 2026
7e1697d
fix(roadmap): the reworked Simple Mode, and plainer words from the an…
diazdesandi Oct 18, 2026
326a8cb
docs(thaw): a guide for apps whose menu bar items should work with Thaw
diazdesandi Oct 18, 2026
e102a0c
fix(changelog): a version dated Unreleased is not a release yet
diazdesandi Oct 18, 2026
fdc406c
chore(data): the community numbers and roadmap requests as of 18 October
diazdesandi Oct 18, 2026
187e9ae
docs(thaw): apps whose names keep changing, and items with no name
diazdesandi Oct 18, 2026
d68931b
docs(thaw): the app guide opens with what to do, and claims only what…
diazdesandi Oct 18, 2026
523cf8f
docs(thaw): what Little Snitch went through, as the case for a named …
diazdesandi Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ export async function generateMetadata(props: PageProps<'/docs/[[...slug]]'>): P
page.data.shared
? `${docsRoute}/${[sharedHome, ...page.slugs.slice(1)].join('/')}`
: page.url,
// The same page in Markdown, which proxy.ts serves at the page's address plus .md.
`${page.url}.md`,
),
openGraph: {
images: getPageImageUrl(page).url,
Expand Down
6 changes: 3 additions & 3 deletions app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { docsLlms } from '@/lib/source';
import { llmsIndex } from '@/lib/llms';

export const revalidate = false;

export async function GET() {
return new Response(await docsLlms.index());
export function GET() {
return new Response(llmsIndex());
}
Binary file removed assets/raycast-glyph.png
Binary file not shown.
13 changes: 3 additions & 10 deletions components/install-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,11 @@ const layoutAccess = {
const screenRecording = {
label: 'Optional',
value: 'Screen Recording',
detail: 'It gives you live previews and a tint from your wallpaper. Hiding works without it.',
detail:
'It gives you live previews and a tint from your wallpaper. Without it, items show as their app’s icon.',
};
const factsFor = {
// Thaw 2 cannot draw another app's item without it; Thaw 3 falls back to the app's icon.
'macos-26': [
accessibility,
{
...screenRecording,
detail:
'Hiding works without it. The Thaw Bar, search and the Layout settings need it to show your items.',
},
],
'macos-26': [accessibility, screenRecording],
'macos-27': [accessibility, layoutAccess, screenRecording],
// Either release may be the one downloaded, so the one that is not always asked says when.
download: [accessibility, { ...layoutAccess, label: 'Needs, on macOS 27' }, screenRecording],
Expand Down
6 changes: 4 additions & 2 deletions components/product-marks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import droppyGlyph from '@/assets/droppy-glyph.png';
import floeGlyph from '@/assets/floe-glyph.png';
import nixMark from '@/assets/nix-mark.png';
import omniwmMark from '@/assets/omniwm-mark.png';
import raycastGlyph from '@/assets/raycast-glyph.png';
import { aiMarks, appleIntelligenceMark } from '@/lib/ai-marks';
import { ClaudeMark } from './claude-mark';
import { ThawMark } from './thaw-mark';
Expand All @@ -21,7 +20,10 @@ const marks: Record<
| { path: string; box?: string }
> = {
droppy: { src: droppyGlyph, glyph: true },
raycast: { src: raycastGlyph, glyph: true },
// Raycast's mark from Simple Icons (simpleicons.org, CC0), as a path so it stays sharp.
raycast: {
path: 'M6.004 15.492v2.504L0 11.992l1.258-1.249Zm2.504 2.504H6.004L12.008 24l1.253-1.253zm14.24-4.747L24 11.997 12.003 0 10.75 1.251 15.491 6h-2.865L9.317 2.692 8.065 3.944l2.06 2.06H8.691v9.31H18v-1.432l2.06 2.06 1.252-1.252-3.312-3.32V8.506ZM6.63 5.372 5.38 6.625l1.342 1.343 1.251-1.253Zm10.655 10.655-1.247 1.251 1.342 1.343 1.253-1.251zM3.944 8.059 2.692 9.31l3.312 3.314v-2.506zm9.936 9.937h-2.504l3.314 3.312 1.25-1.252z',
},
// Floe's menu bar item: the floe of its app icon in one colour, drawn from the same
// points as Sources/Floe/App/MenuBarGlyph.swift in thaw-app/Floe.
floe: { src: floeGlyph, glyph: true },
Expand Down
3 changes: 3 additions & 0 deletions components/site-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ const feet: Record<ProductSlug, Foot> = {
{ text: 'Security', href: `${thawDocs}/security` },
{ text: 'Code of Conduct', href: `${thawDocs}/contribute/code-of-conduct` },
{ text: 'Governance', href: `${thawDocs}/governance` },
// The site's pages listed for AI tools, which look for it from the home page.
{ text: 'For AI tools', href: '/llms.txt' },
],
},
floe: {
Expand Down Expand Up @@ -124,6 +126,7 @@ const feet: Record<ProductSlug, Foot> = {
text: 'Governance',
href: `${repoUrl('floe')}/blob/${products.floe.branch}/.github/GOVERNANCE.md`,
},
{ text: 'For AI tools', href: '/llms.txt' },
],
},
};
Expand Down
1 change: 1 addition & 0 deletions content/docs/floe/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"pages": [
"index",
"---Using Floe---",
"limitations",
"!changelog",
"---Developers---",
"contribute",
Expand Down
119 changes: 119 additions & 0 deletions content/docs/thaw/app-developers.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
title: Make your app work with Thaw
description: For developers of apps with menu bar items. How Thaw tells your items apart on macOS 27, how to keep them where people put them, and how to let each one be hidden on its own.
---

Thaw moves and hides other apps’ menu bar items. Up to macOS 26, each item had a window of its own, so this mostly worked whatever an app did. macOS 27 draws the whole menu bar in one window, and Thaw has to work out which item is which from what each app tells it. What your app does decides whether Thaw can keep its items where people put them.

This page is for the developers of those apps, and came out of [a question in Discussions](https://github.com/orgs/thaw-app/discussions/1242). Most of it was measured while making [Barometer](https://github.com/mackid1993/Barometer) work with menu bar managers on macOS 27.

## In short

1. Give every item a fixed `autosaveName` and accessibility identifier, before it is first shown.
2. Keep changing text, such as a temperature, out of the button’s title. Draw it into the image.
3. Make each item once, and hide it with `isVisible`.
4. Show each item that should hide on its own from a helper app with its own bundle identifier.
5. Test from `/Applications`, then check the item in Thaw’s Menu Bar Layout.

## How Thaw tells items apart

Thaw knows an item by two things:

1. The bundle identifier of the app that made it.
2. The item’s name, which it reads from the item’s `autosaveName` and its accessibility title, identifier, description and value.

If either changes, Thaw treats it as an item it has never seen, and the item loses its place. An item that renames itself when its icon changes is a common reason an item seems to wander or go missing.

## Give every item a fixed name

```swift
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
// Name it before it is first shown, so it is never seen without one.
item.autosaveName = "com.example.app.cpu"
if let button = item.button {
button.setAccessibilityIdentifier("com.example.app.cpu")
button.setAccessibilityLabel("CPU")
}
item.isVisible = true
```

- **Name it before it is shown.** Set the autosave name, accessibility identifier and label before the item’s first `isVisible`. A manager that sees an unnamed item can pair it with the wrong name.
- **One name per item, for good.** Give each item its own name and never reuse one for different content. A second item of the same kind gets `.2`, a third `.3`, and so on.
- **Make it once.** Keep each item for as long as the app runs, and hide it with `isVisible`. Removing an item and making it again, or using `.removalAllowed`, gives a manager a new item each time.
- **Leave the window’s title alone.** Don’t set the title of the status item’s window.

## Keep live values out of the title

A button title that changes, such as a temperature or a count, becomes the item’s accessibility title, and Thaw reads it as a new name. Draw changing text into the button’s image instead, and give the reading to accessibility as a value:

```swift
button.title = ""
button.imagePosition = .imageOnly
button.image = reading // the text, drawn into an image
button.setAccessibilityValue("62 °C")
```

### Apps whose names keep changing

Thaw has worked around a number of apps whose item names change all the time:

- **Dato** shows the date and a countdown to your next event, so its name keeps changing, whatever date format you pick ([#1175](https://github.com/thaw-app/Thaw/issues/1175)).
- **Amphetamine**’s one item switches between `Item-0`, `∞` and `𝗧`.
- **ThermalForge**, **MacThrottle**, **Macs Fan Control** and **iStat Menus** show live temperatures and readings.
- **Outlook** puts a countdown to your next meeting in front of its name.

A fixed name in your own app is the fix that lasts. Each app above needed a change in Thaw itself, and Thaw no longer adds workarounds for single apps.

For these apps, Thaw strips out the changing part of the name, or tells the items apart by where they sit. It learns to do the same for an app with a single item that renames itself, such as WeChat with its unread count.

Position only goes so far. When an app has several items, moving one can swap which is which.

### Items with no name

Set an autosave name and an accessibility identifier, as above, and your items never end up as one of these.

An item that publishes no name of its own gets a placeholder from macOS by its position: `Item-0`, `Item-1` and so on. Thaw can tell two of an app’s unnamed items apart only by their order, so moving one can make them trade places in Layout, or trade sections.

It happened to Rectangle, whose item became `Item-0` when its old AppKit placeholder name could no longer be read. Little Snitch had it worst, below.

### What Little Snitch went through

Little Snitch’s agent shows its item through Control Center, so Thaw saw it as `com.apple.controlcenter:Item-0`, with no name and no app behind it. Thaw had to work out which item was Little Snitch’s by matching it indirectly. For months, people saw what looked like Thaw bugs:

1. It wasn’t listed by name in Layout, and dragging it there didn’t last ([#709](https://github.com/thaw-app/Thaw/issues/709), [#372](https://github.com/thaw-app/Thaw/issues/372)).
2. It went back to Hidden a few minutes after being moved, and in 2.0.0-beta.13 it wouldn’t stay anywhere ([#651](https://github.com/thaw-app/Thaw/issues/651), [#575](https://github.com/thaw-app/Thaw/issues/575), [#643](https://github.com/thaw-app/Thaw/issues/643)).
3. Clicking it while hidden made Thaw’s own icon chase the cursor ([#332](https://github.com/thaw-app/Thaw/issues/332)).
4. Its agent came back twice after the Mac woke from sleep ([#641](https://github.com/thaw-app/Thaw/issues/641)).
5. On macOS 27 it was missing from Thaw altogether, and clicking it opened Thaw’s hidden items, until its GUI Scripting option was turned on ([#1179](https://github.com/thaw-app/Thaw/issues/1179), [#1220](https://github.com/thaw-app/Thaw/issues/1220)).

Each one took a fix or a workaround in Thaw, and some still need people to ⌘-drag the icon by hand. An item that comes from your own app’s process, with a fixed name, avoids all of it.

## Let each item be hidden on its own

macOS 27 hides menu bar items by app, not by item. Thaw can tell macOS which apps’ items to hide, so all of one app’s items go to Hidden together, and one item left in Visible keeps the others there too. This is on the [roadmap as blocked by macOS 27](/roadmap#blocked-by-macos-27).

The way round it is to show each item that should hide on its own from a separate app, with its own bundle identifier. Barometer tried this in [Barometer#4](https://github.com/mackid1993/Barometer/pull/4), with a small helper app inside the main one that shows a single item:

1. Build the helper as an app of its own, with its own bundle identifier, such as `com.example.app.gpu`. Set `LSUIElement` to true in its `Info.plist`, so it has no Dock icon.
2. Put it inside the main app, at `Contents/Library/LoginItems/`.
3. In the helper, check `Bundle.main.bundleIdentifier` before you touch `NSStatusBar`, and make its one item as described above.
4. Sign the helper first, under its own identifier, and then the main app, so the main app’s signature covers a helper that is already signed.
5. Start the helper from the main app, for example with `SMAppService.loginItem(identifier:)`, and make sure only one copy of it runs.

On macOS 27, a menu bar manager then lists the helper’s item on its own, and can hide it while the main app’s items stay visible.

## Other things that cost an item its place

- **Don’t edit saved positions.** Leave AppKit’s visibility and position defaults, and Control Center’s preferences, alone. An app that wrote to Control Center’s preferences sent its own icon to Hidden in Thaw and other menu bar managers ([#605](https://github.com/thaw-app/Thaw/issues/605)).
- **Don’t reorder items from code.** Leave where an item sits to macOS and the person using it.
- **Let `LSUIElement` make it an agent.** Setting the `.accessory` activation policy before the run loop starts gives items no height on macOS 27, and no manager can place them.
- **Test from `/Applications`.** The same app launched from a build folder can get items with no height. Install it before you judge how it works with a manager.
- **Run one copy, and expect to be relaunched.** A second launch should hand over to the first one and quit. To apply a new menu bar spacing, Thaw asks apps with menu bar items to quit and opens them again, and an app that also restarts its own helper ends up with two icons.

## Check it

1. Open Menu Bar Layout in Thaw’s Settings, and put each of your items in a section.
2. Quit your app and open it again.
3. Check that each item is listed once, under its own name, in the section you put it in.

If it isn’t and you’ve followed this page, [open an issue](https://github.com/thaw-app/Thaw/issues) with your app’s bundle identifier and the autosave names of its items.
84 changes: 0 additions & 84 deletions content/docs/thaw/getting-started.mdx

This file was deleted.

1 change: 1 addition & 0 deletions content/docs/thaw/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"---Developers---",
"contribute",
"development",
"app-developers",
"architecture",
"releases",
"verifying-releases",
Expand Down
42 changes: 0 additions & 42 deletions content/docs/thaw/network.mdx

This file was deleted.

Loading
Loading