# Changelog

All notable changes to Contain.

## [0.4.0]

### Fixed
- **The window inside stays inside while you move the container.** Grab the
  container's title bar and the window it held vanished behind the container's
  own empty content area — and stayed there, until you clicked a tab. It read
  as a failure to repaint; nothing failed to repaint. Clicking the container
  activates Contain, macOS puts an active app's windows in front of every
  background app's, and the container was simply drawn over the window it was
  supposed to be framing. The re-raise that ran after each drag could never
  fix it either: `AXRaise` will not lift a background window in front of the
  active app's. Contain now orders *itself* below the window it contains — the
  window server is happy to stack windows from two processes — so the pair
  arrives at the front together and in the right order. Measured at 14 ms from
  the click, under one display frame.
- **Containing one window of an app no longer disturbs its others.** Contain
  one of three TextEdit documents and the other two came and went with the
  tab bar: switching to another tab took them off the screen, switching back
  threw them into the foreground. Hiding is an app-level operation in macOS —
  ⌘H takes every window an app owns — and Contain was using it on apps whose
  windows it did not all hold. An app is now only hidden when Contain holds
  all of it; one with a window still out on the desktop stays put, and its
  unselected contained windows stack behind the selected one, exactly as
  same-app windows always have. Contain also no longer unhides an app that
  *you* hid before it ever saw it.
- **Selecting a tab no longer buries the container.** Bringing an app forward
  brings every window it owns forward, so choosing the tab for an app with
  other windows open left the container — tab bar and all — behind them.
  Activation is asynchronous, so the fix is to wait for it and settle the
  order afterwards rather than before.

### Note
- This build keeps the preview deadline of **20 August 2026** rather than
  extending it. Settings → General counts down to it, and the ? beside the
  version says what a preview build is.

## [0.3.5]

### Fixed
- **A contained window follows the container back down again.** Zoom the
  container with the green button and zoom back, or drag it smaller than the
  window inside it will go, and that window stopped tracking the container's
  size — it kept whatever size it had when the container was big, sat
  overflowing a small container, and never recovered. The cause was one flag:
  the code that centres a window too big for the container reported "this
  window's size cannot be set", which is true of Calculator and false of every
  window that simply has a minimum. Contain wrote the position and never the
  size again. It now writes the size too, so the window converges on its
  minimum instead of drifting a little further out of true on every resize.
- **Resizing the container no longer overruns the app inside it.** A resize
  wrote the contained window's frame on every notification — once per
  mouse-move while dragging an edge, about twenty times during the green
  button's zoom animation — and each write is synchronous IPC into another
  process, measured at ~50 ms apiece against Music. Contain's main thread ran
  seconds behind the drag and the contained app was asked to re-lay-out far
  faster than it could paint, which is what looked like a failure to repaint.
  Writes are now rate-limited to one per display frame and use two calls
  rather than three while the geometry is still moving, with the full write
  left to the pass that runs once the interaction settles: 3.55 s of blocked
  main thread over a 60-event drag became 0.95 s. Windows of the same app that
  are stacked behind the selected one now track the resize as well, instead of
  lagging a quarter-second behind it.
- **A clamp is no longer learned from a half-finished resize.** The read-back
  that discovers an app's minimum size ran mid-animation too, where an app
  that has not caught up reports the size it still has — indistinguishable
  from a clamp, and minimums only ever grow, so one bad reading would have
  stuck for the rest of the session. Only settled writes teach it now.

## [0.3.4]

### Added
- **Contain now explains its own edges, in the app.** The behaviours macOS
  forces on it — the green button zooming rather than going full screen, a
  window that refuses to shrink, same-app windows stacked behind the selected
  one — read as bugs if you meet them without an explanation, and until now
  the explanation existed only on the product page. There are two ways to it
  now: a **Limitations** pane in Settings, and a **?** beside the version in
  the tab bar that pops the same list open where you hit the behaviour. Both
  are built from one list in `Limitations.swift`, so they cannot drift apart.

### Note
- This build keeps 0.3.3's preview deadline of **20 August 2026** rather than
  extending it. Settings → General counts down to it, and the ? beside the
  version says what a preview build is.

## [0.3.3]

### Fixed
- **The container stays the size you drag it to.** Resizing it snapped
  straight back to its previous size — a regression in 0.3.2's fit-oversized-
  windows work. Contain grew the container whenever it found a window that
  wouldn't shrink, and that check ran on *every* resize event, so dragging the
  container smaller than a contained window could go immediately grew it back.
  It now grows only once, when such a window is first adopted; after that the
  container's size is yours, and a window too big for it is centred so the
  overflow is symmetric.

## [0.3.2]

### Fixed
- **Windows that won't shrink now fit anyway.** Some apps refuse to resize
  below a minimum of their own — System Settings stops at 723×470 — and macOS
  reports the resize as successful while quietly clamping it, so the window
  simply overflowed the container. Contain now reads back the size it actually
  got, remembers the floor so it stops re-issuing a resize that can never
  succeed, grows the container to swallow that minimum where the screen
  allows, and centres the window when it can't.
- **Much less flickering when you move the container.** Three things were
  fighting: every mouse-move rewrote the contained window's *whole* frame
  (three round-trips into the other app, making it re-lay-out its content),
  every drag re-activated the contained app a moment later and yanked focus
  about, and a window that couldn't be resized had the same impossible resize
  re-issued once a second forever. Moving the container now writes position
  only, skips the write entirely when nothing has changed, raises the window
  without stealing focus, and stops retrying a resize it has learned cannot
  succeed. A 40-event drag went from ~120 window-server round-trips to 39.
- **The green button maximizes instead of breaking.** Native full screen put
  the container in its own Space, where macOS does not allow another app's
  window to follow — so the container went full screen and left everything it
  contained behind on the old desktop. Full screen is now disabled and the
  green button fills the screen on the normal desktop, which contained windows
  follow correctly.
- **Contained windows are put back where they came from.** Contain now records
  each window's position, size and display before adopting it, and restores it
  when the window is released or when Contain quits. Previously releasing a
  window left it at the container's size and position, and quitting left every
  window it had ever touched stacked in the container's footprint.

## [0.3.1]

### Added
- **Contain updates itself.** New versions are picked up in the background and
  installed with a click, so you no longer have to go looking for a download.
  **Contain → Check for Updates…** runs the check on demand. Every update is
  signed, and Contain refuses to install one that isn't signed with the key
  the installed copy already trusts.
- **The window picker is a grid, not a menu.** Every open window gets a tile
  with the app icon at a size you can recognise, the app name and the window
  title, four to a row. A menu of `App — Window title` lines is fine for
  three entries and unreadable at thirty; tiles let you recognise a window
  instead of reading for it. Tiles can show a **live thumbnail** of the
  window, but only if you grant Screen Recording — Contain never asks on its
  own, explains exactly what the permission allows before you decide, and
  works perfectly well on icons if you decline.
- **The + button explains itself.** Hovering it slides "Contain a new app" out
  to its right, which retracts as soon as you move away — the tooltip it
  replaces only showed up after a pause, and only if you already suspected the
  button did something.
- **The version is always visible**, on the right of the tab bar, with a
  `BETA` pill for as long as this is a preview. Hover it for the expiry
  date.
- **This release is a time-limited preview.** It stops working on **20 August
  2026**, after which Contain explains that the preview has ended, points at
  the current release and quits. The date is checked when the app opens and at
  five other points — including once a second while it runs, so a container
  left open overnight stops at the deadline rather than carrying on until the
  next launch. Settings → General shows the date and counts down to it. Nothing
  you contained is left hidden when it quits.
- **A Settings window** (**Contain → Settings…**, ⌘,). *General* shows the
  installed version and build, when Contain last looked for an update and
  whether it does so automatically, and the current Accessibility grant.
  *Privacy* is the full account of what Contain collects — every signal named,
  with the reason it exists next to it — and the switch that turns it off.
  *About* says who publishes the app: WIZHUT TECH SOLE SHAREHOLDER CO. P.C.,
  trading as Wizhut.tech, with links to the site, the product page and the
  release notes.

### Fixed
- **The Settings window no longer clips its own content.** It was built around
  an empty tab controller and sized itself to that, so every pane was cut off
  on the right — version numbers, the Accessibility status and the ends of
  sentences all sat outside the window.
- **Anonymous usage statistics**, via TelemetryDeck (EU-hosted, GDPR-compliant).
  Four signals — launches, adoptions, releases, and the times Contain was
  blocked for want of Accessibility access — so the parts of the app that
  quietly fail can be found without asking anyone to file a report. **The apps
  and windows you contain are never sent**, not even their names: adoption is
  reported as a coarse tab count and nothing more. Wizhut is a privacy-first
  company — this exists to understand what users need, not to monitor anyone,
  and **Settings → Privacy** spells out every signal rather than asking you to
  take that on trust. The off switch is there too, and it takes effect the
  moment you click it.
- **Releases now ship as a notarized disk image**, signed with the same
  Developer ID every time. That matters more here than for most apps: macOS
  ties the Accessibility grant to an app's code signature, so keeping the
  signature stable is what lets an update land without you having to grant
  access all over again.

## [0.2.0]

### Added
- **An About panel worth opening.** It shows the app icon, name, marketing
  version and build number, a short description, and the copyright — and,
  more usefully, whether Accessibility access is currently granted, with a
  button straight through to the settings pane when it isn't. That grant is
  what the whole app depends on, it fails silently when missing, and it is
  easy to lose across rebuilds.

### Changed
- **Contain individual windows, not whole apps.** The picker used to list
  running apps and adopt each app's first window, so three open TextEdit
  documents could not be told apart. It now lists every open window in its own
  right ("TextEdit — alpha.txt") and each adopted window gets its own tab,
  labelled with the window title and following it when the document is
  renamed. Windows already contained drop out of the list.
- **The ↗ button and ⌘W release a window instead of quitting its app.** The
  tab goes away, the window carries on as a free-standing one, nudged clear of
  the container so it reads as detached. Contain never terminates an app —
  quitting Contain leaves everything it contained running.
- **Only the selected tab's window is on screen.** Apps that own no selected
  window are hidden, and unhidden again when one of their tabs comes forward.
  Windows of the same app can't be hidden individually, so the unselected ones
  are kept pinned to the same rect, stacked underneath.

### Fixed
- **Window pinning works at all.** The App Sandbox blocks outbound
  Accessibility messaging on macOS 26 — reading another app's windows fails
  even with Accessibility granted — so a sandboxed Contain could never move
  the window it had adopted. Contain now ships unsandboxed, which is also why
  it is distributed outside the Mac App Store.
- **A hidden app is still pickable.** Hidden apps expose no windows to the
  Accessibility API, which would have made an app Contain itself hid
  impossible to pick again. Contain now remembers the window list it last saw;
  an app you hid before Contain ever saw it shows up as a single
  "show windows…" entry that unhides it first.

## [0.1.0]

- First version: one container window that adopts an already-open window and
  keeps it pinned over the container's content area, with a tab bar to switch
  between contained windows.
