Skip to content

Native motion, own rail icons, and a Translate tab that picks its languages - #11

Merged
ctimothe merged 7 commits into
dynamic-island-parityfrom
feat/native-motion-icons-translate
Sep 21, 2026
Merged

ctimothe merged 7 commits into
dynamic-island-parityfrom
feat/native-motion-icons-translate

Conversation

@ctimothe

Copy link
Copy Markdown
Owner

What changes for the user

Motion

  • Over the lock screen, a pause used to fold the pill away in a single frame. It now contracts into the notch on a calm, critically damped spring (0.5 s) and grows back out (0.42 s) when music starts. Unlocked, it moves the same way.
  • The cover and equalizer ride the contracting wings and dissolve (blur, scale, fade) before the edge reaches them. Before, the edge sliced through a cover that sat still.
  • A settled pause keeps its paused cover through the fold, where it used to flash back to full brightness. A fading black outline at the start of every fold is gone, and a peek ends on the same fold curve.
  • The lock card's lyrics glide a line at a time instead of every row jumping a slot. Scrolling the stage's lyrics by hand brightens every line until the page follows the song again.

Icons and the rail

  • The five rail glyphs were exactly the upstream project's. Music keeps the plain music.note (the owner's choice); the other four are now rectangle.stack, doc.on.clipboard, character.bubble and slider.horizontal.3, and the chosen tab shows its filled variant.
  • The rail changes tabs on a click only. The 150 ms hover dwell is withdrawn by a design amendment: a pointer passing over the rail used to flip panes and replay each glyph's fill behind it. A hover now draws a faint well, and the well and selection change on the same frame.

Translate

  • Sixteen languages, picked from menus in the column headings. The source can detect or be set, a swap exchanges the languages and the text, and the choice is remembered. With the source on Detect and the target on Russian, the old EN↔RU behaviour still holds.
  • Each pair goes to the best engine the Mac has. Apple's Translation framework handles installed pairs; on this Mac that covered en↔ru, en→tr and en→ko in 0.3–1.2 s. Next is the on-device model for its own languages, then MyMemory, used only when the new Translate Online switch is on (off by default and recorded as a scope divergence), and only for pairs neither on-device engine offers at all. A pair the Mac offers is never sent, even when it can't run right now; it is reported instead.
  • Uzbek and Kazakh have no on-device engine. With the switch off, the pane names the language and offers the switch. Uzbek was verified live both ways.
  • The model is capped at three tokens per source character. Uncapped, one sentence into Uzbek once generated for 218 s.
  • An open menu holds the panel the way a drag does, so moving down a language list that hangs below the panel no longer folds it.

Review

An independent review found an online leak: text the Mac could translate itself went to MyMemory after an on-device failure, with Apple Intelligence off, or for languages not yet downloaded. That is fixed and exhaustively tested; the test fails 22 times with the old rule put back. It also found:

  • the wrong message on macOS 15;
  • short-word misdetection;
  • Kazakh taken for Uzbek;
  • a stale-answer swap;
  • the cover flying out of the notch when opening from a folded pill;
  • the peek title being sliced by the fold;
  • a folded pill shaking onto the wallpaper;
  • overlapping lyric lines under Reduce Motion;
  • a menu hold that could leak.

All of these are fixed in 022355c.

Verification

  • ./scripts/check: all 11 steps pass, 470 tests (4 skipped: live and render, run only on request).
  • New tests: PillFoldTests, LockCardLyricGlideTests, TranslatorTests, LanguageDetectionTests, OnlineTranslationTests, MenuTrackingTests, plus additions to TabContractTests. The fold tests failed under mutation of both curves, and NotchMetrics.tabDwell was removed along with its assertion.
  • TRANSLATOR_LIVE=1 ran the full engine matrix on this Mac, and ONLINE_TRANSLATION_LIVE=1 checked MyMemory.
  • A harness measured that ScrollView honours the lyric spring on macOS 27.

🤖 Generated with Claude Code

… screen too

Over the lock screen a pause removed the pill in a single frame: the locked
branch refuses every animation from outside, and the pill was mounted only
while something played. It now stays mounted for the whole lock and answers
its own change on its own curve, declared beneath the refusal, so the lock
itself is still a cut.

Unlocked, the compact header left the tree the instant the pill folded and
kept its old width, so the narrowing clip swept across a cover that sat still;
it now stays laid out whenever the panel is shut and rides the wings in while
PillPresence dissolves it (blur, scale, fade) on a faster curve than the
shape's. The wings fold on 0.5 s and unfold on 0.42 s, both critically damped,
where every pill resize shared 0.28 s. A settled pause keeps its paused cover
through the fold instead of flashing back to full brightness, the redundant
black wing layer that faded at the old width is gone, and a peek ends on the
fold curve.

The lock card's lyric window now glides a line at a time instead of every
row jumping a slot, and scrolling the stage by hand lifts every line to a
readable brightness until the page follows the song again.

PillFoldTests holds the content leaving before the edge reaches it and
arriving only once its wing exists; LockCardLyricGlideTests holds the glide
travelling exactly one row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The five tab glyphs were, one for one, the set the upstream project ships, so
the rail read as somebody else's island. Music is now the Now Playing play
mark, Shelf a stack of held things, Clipboard the pasteboard glyph macOS puts
on Paste, Translate a speech bubble with a letter the system localizes to the
reader's script, and Settings the controls sliders. The chosen tab draws the
filled variant and swaps with the system's replace effect, the way a tab bar
does. The Settings privacy rows and the Clipboard and Shelf empty states use
the same family.

TabContractTests holds that every glyph resolves, that no two tabs share one,
and that none of the retired set comes back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…line

The tab translated English and Russian only, decided by the script typed. Its
two column headings are now language menus over sixteen languages — the
source detects by default or can be told, a swap exchanges the languages and
the text, and the choice is remembered. With the source on Detect and the
target on Russian the old rule still holds.

Each pair goes to the best engine this Mac has: Apple's Translation framework
when it reports the pair installed (fast, and it translates text the model
refused as unsafe), the on-device model for its own languages plus Russian,
and MyMemory only when the new Translate Online switch is on. It is off by
default and recorded as a scope divergence; nothing the Mac can translate
itself ever leaves it, and an answer that did carries a globe. Uzbek and
Kazakh have no on-device engine, so with the switch off the pane names the
language and offers the switch. Apple's recognizer does not know Uzbek, so it
is detected by its own letters and words first.

The model's responses are capped at three tokens per source character: one
English sentence into Uzbek once generated for 218 seconds until its context
was full. An open menu now holds the panel the way a drag does, so moving down
a language list that hangs below the panel no longer folds it, and the pointer
gets 1.2 s to come back once the menu closes.

TranslatorTests, LanguageDetectionTests, OnlineTranslationTests and
MenuTrackingTests hold the routing, detection, request shape and the hold;
TRANSLATOR_LIVE=1 and ONLINE_TRANSLATION_LIVE=1 run the real engines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…le detecting

An inline picker drew the checkmark but had no row to tick while the source
detects, and logged an invalid-selection warning on every open. Each language
is now a toggle, which a menu draws as the native checkmarked item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hovering the rail switched tabs after a 150 ms dwell, so a pointer passing
through it flipped panes, and each flip replayed the chosen glyph's fill a
third of a second behind the cursor. A click is now the only thing that
changes the tab. A hover draws a faint well and nothing else — no dwell, no
glyph growing — and the well and the selection land on the frame they happen
instead of fading behind a fast pointer. The replace effect on the fill is
gone for the same reason.

Music wears music.quarternote.3; play.circle read as a button rather than a
place. The design's tab-hover dwell is withdrawn by a dated amendment, and
NotchMetrics.tabDwell with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
music.quarternote.3 read as busy, as play.circle had read as a button, and
the owner asked for the simple one. music.note is the system's own glyph for
music, free to every Mac app, and with the other four tab glyphs changed the
rail as a whole is no longer the upstream project's. The retired set keeps
the other four out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…'s other findings

Translate Online was appended as the last engine whenever the switch was on,
so a sentence the model refused, a pair whose language was not downloaded,
and any pair with Apple Intelligence off all went to MyMemory. It is now
listed only for a pair neither on-device engine offers at all — Uzbek and
Kazakh — so no on-device failure can fall through to it, and the pane offers
the online switch only where online is what would be used. Below macOS 26 the
pane says the system is too old rather than that Russian is not downloaded.
The Settings note and the docs say what a request really carries.

Detection: a word or two needs 0.95 from the recognizer, which called "Друг"
Ukrainian and "Hi" Turkish at 0.60–0.65, and otherwise keeps the old script
rule; scripts only one offered language writes decide at any length; і and ы
tell Kazakh from Uzbek where the recognizer calls both Kazakh. Swap moves the
answer into the field only when it answers what the field holds.

Motion: a folded pill gives up its end of the cover and equalizer travel, so
opening on a settled pause fades the cover in as before instead of growing it
out of the notch's edge; the peek title dissolves with the fold; a folded
pill does not shake onto the wallpaper; a track arriving folded does not peek;
Reduce Motion keeps the lock card's line change a cut.

Menus: counted by identity and held only while the run loop is tracking, so
a lost "closed" notice cannot keep the panel open; folding forgets them.

TranslatorTests holds exhaustively that no on-device pair lists the online
engine (22 failures with the old rule put back); LanguageDetectionTests,
MenuTrackingTests and FoldedMorphTests cover the rest. TRANSLATOR_LIVE=1
reran the whole matrix on this Mac.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ctimothe
ctimothe merged commit cdb5c5d into dynamic-island-parity Sep 21, 2026
1 check passed
@ctimothe
ctimothe deleted the feat/native-motion-icons-translate branch September 21, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants