chore(release): bump version to 2.1.6 (build 131)
Version: CURRENT_PROJECT_VERSION 130 -> 131 (project.yml, AGENTS.md, READMEs, docs/llms.txt). fix(flow): host-app identification now compiles in every build configuration (Debug, TestFlight, Release). The arbiter probe and HostAppIdentity were DEBUG-only, so TestFlight/App Store builds never knew the host bundle id and could not auto-return after a startflow handoff. Debug-only logging and the Home test card are unchanged. Also includes the pending work in the tree: - Home: single account/setup card surface (HomeAccountSurfacePolicy/Views) - Wide screens: voice key row and centred mic placement, iPhone Duo layout - Keyboard: clipboard skill ranking and AI skill layout updates - Debug: account session handoff to the Simulator (+ import script) - Docs: portfolio assets, privacy policy URL, site/README refresh
This commit is contained in:
@@ -46,6 +46,13 @@ public enum KeyboardChromeLayout {
|
||||
/// keyboard), which is why it no longer shares this constant.
|
||||
public static let voiceContentMaxWidth: CGFloat = 700
|
||||
|
||||
/// Hosts that draw the keyboard-style voice key row span the typing width:
|
||||
/// the row, top bar and logo must sit on the same frame as the typing keys,
|
||||
/// or they jump sideways on every tab switch.
|
||||
public static func voiceContentWidthCap(usesVoiceKeyRow: Bool) -> CGFloat {
|
||||
usesVoiceKeyRow ? .infinity : voiceContentMaxWidth
|
||||
}
|
||||
|
||||
/// Width at or above which the typing surface switches to wide-iPad
|
||||
/// metrics (taller rows). Chosen to sit between the widest iPad portrait
|
||||
/// width (1024 pt on 13") and the narrowest landscape width (1133 pt on
|
||||
@@ -60,11 +67,54 @@ public enum KeyboardChromeLayout {
|
||||
isPad && hasRegularWidth
|
||||
}
|
||||
|
||||
/// System-sized iPhone-shaped keys for a phone host with regular space on
|
||||
/// both axes — the unfolded iPhone Duo inner display. Landscape Max-size
|
||||
/// iPhones are regular × compact and keep the phone metrics.
|
||||
public static func usesExpandedPhoneMetrics(
|
||||
isPad: Bool,
|
||||
hasRegularWidth: Bool,
|
||||
hasRegularHeight: Bool
|
||||
) -> Bool {
|
||||
!isPad && hasRegularWidth && hasRegularHeight
|
||||
}
|
||||
|
||||
/// Shorter phone panel whenever the host is vertically compact: every
|
||||
/// iPhone in landscape, including the folded iPhone Duo. Compact-height
|
||||
/// iPad windows keep their own metrics.
|
||||
public static func usesCompactHeightPhoneMetrics(isPad: Bool, hasCompactHeight: Bool) -> Bool {
|
||||
!isPad && hasCompactHeight
|
||||
}
|
||||
|
||||
/// iPad always draws its own globe key. Elsewhere UIKit decides: Face ID
|
||||
/// iPhones get a system switch below the keyboard, while the unfolded
|
||||
/// iPhone Duo and Home-button iPhones need the keyboard to provide one.
|
||||
public static func showsInputModeSwitchKey(
|
||||
isPad: Bool,
|
||||
systemRequiresSwitchKey: Bool
|
||||
) -> Bool {
|
||||
isPad || systemRequiresSwitchKey
|
||||
}
|
||||
|
||||
/// Wide metrics need iPad-scale keys *and* enough width to justify them.
|
||||
public static func usesWideIPadMetrics(isIPad: Bool, width: CGFloat) -> Bool {
|
||||
isIPad && width >= wideIPadWidthThreshold
|
||||
}
|
||||
|
||||
/// Below this width the voice microphone capsule shrinks (Display Zoom on
|
||||
/// a small iPhone, iPad Slide Over). Every current iPhone is wider.
|
||||
public static let compactVoiceWidthThreshold: CGFloat = 350
|
||||
/// Below this width an iPad-metrics voice surface uses its smaller circles.
|
||||
public static let compactIPadVoiceWidthThreshold: CGFloat = 420
|
||||
|
||||
/// `width == 0` means "not measured yet" and never counts as compact.
|
||||
public static func usesCompactVoiceMetrics(width: CGFloat) -> Bool {
|
||||
width > 0 && width < compactVoiceWidthThreshold
|
||||
}
|
||||
|
||||
public static func usesCompactIPadVoiceMetrics(isIPad: Bool, width: CGFloat) -> Bool {
|
||||
isIPad && width > 0 && width < compactIPadVoiceWidthThreshold
|
||||
}
|
||||
|
||||
/// Splits the width left after three gaps into a 12 / 18 / 50 / 20 row for
|
||||
/// layouts that include a globe key, with a wide centre and balanced sides.
|
||||
public static func actionKeyWidths(availableWidth: CGFloat) -> (globe: CGFloat, side: CGFloat, center: CGFloat, side2: CGFloat) {
|
||||
@@ -93,28 +143,6 @@ public enum KeyboardChromeLayout {
|
||||
)
|
||||
}
|
||||
|
||||
/// iPad voice bottom row `[globe · delete · return · space]`. The phone's
|
||||
/// 12/18/50/20 split would hand the centre key ~577 pt once the surface
|
||||
/// spans an iPad; these fractions keep every key in a usable range while
|
||||
/// giving the primary return action a little more room.
|
||||
public static let iPadVoiceGlobeFraction: CGFloat = 0.10
|
||||
public static let iPadVoiceSideFraction: CGFloat = 0.24
|
||||
public static let iPadVoiceCenterFraction: CGFloat = 0.40
|
||||
public static let iPadVoiceSide2Fraction: CGFloat = 0.26
|
||||
|
||||
/// iPad variant of `actionKeyWidths` for the voice surface.
|
||||
public static func iPadVoiceActionKeyWidths(
|
||||
availableWidth: CGFloat
|
||||
) -> (globe: CGFloat, side: CGFloat, center: CGFloat, side2: CGFloat) {
|
||||
let keyWidth = max(0, availableWidth - actionKeySpacing * 3)
|
||||
return (
|
||||
globe: keyWidth * iPadVoiceGlobeFraction,
|
||||
side: keyWidth * iPadVoiceSideFraction,
|
||||
center: keyWidth * iPadVoiceCenterFraction,
|
||||
side2: keyWidth * iPadVoiceSide2Fraction
|
||||
)
|
||||
}
|
||||
|
||||
/// Six-slot iPad variant of `actionKeyWidths`.
|
||||
public static func iPadActionKeyWidths(
|
||||
availableWidth: CGFloat
|
||||
|
||||
@@ -29,6 +29,27 @@ public enum TypingSurfaceMetrics {
|
||||
public static let keyHorizontalSpacing: CGFloat = 6
|
||||
public static let secondRowInset: CGFloat = 18
|
||||
|
||||
// MARK: - Expanded phone (unfolded iPhone Duo inner display)
|
||||
|
||||
/// The system keyboard on the inner display keeps the iPhone key layout at
|
||||
/// ~38.5 pt rows inside a 263 pt panel, and the system adds ~23 pt of
|
||||
/// chrome around a custom input view. These rows give a 260 pt shell —
|
||||
/// still roomy for the voice surface's live transcript — so the panel
|
||||
/// stays ~20 pt above the system one instead of ~40 pt.
|
||||
public static let expandedPhoneKeyRowHeight: CGFloat = 41
|
||||
public static let expandedPhoneKeyRowSpacing: CGFloat = 12
|
||||
public static let expandedPhoneKeyHorizontalSpacing: CGFloat = 10
|
||||
|
||||
// MARK: - Compact-height phone (any iPhone in landscape, incl. Duo outer)
|
||||
|
||||
/// A landscape iPhone is only ~400–470 pt tall, so the 281 pt portrait
|
||||
/// panel would take 60–70% of it. These rows give a 244 pt shell — enough
|
||||
/// for the voice surface to centre its mic and still fit the 3-line live
|
||||
/// transcript above it — instead of following the system keyboard down
|
||||
/// to ~200 pt.
|
||||
public static let compactHeightPhoneKeyRowHeight: CGFloat = 40
|
||||
public static let compactHeightPhoneKeyRowSpacing: CGFloat = 8
|
||||
|
||||
// MARK: - iPad (narrow: portrait, or a compact-ish regular window)
|
||||
|
||||
public static let iPadKeyRowHeight: CGFloat = 54
|
||||
@@ -47,8 +68,43 @@ public enum TypingSurfaceMetrics {
|
||||
/// Key metrics for a device class and available width. Width — not device
|
||||
/// orientation — picks the wide bucket, so a resized Stage Manager window
|
||||
/// lands on the right metrics without consulting orientation at all.
|
||||
public static func metrics(isIPad: Bool, width: CGFloat) -> TypingKeyLayoutBuilder.Metrics {
|
||||
/// `isExpandedPhone` / `isCompactHeightPhone` are only meaningful when
|
||||
/// `isIPad` is false, and never true together (regular vs compact height).
|
||||
public static func metrics(
|
||||
isIPad: Bool,
|
||||
width: CGFloat,
|
||||
isExpandedPhone: Bool = false,
|
||||
isCompactHeightPhone: Bool = false
|
||||
) -> TypingKeyLayoutBuilder.Metrics {
|
||||
guard isIPad else {
|
||||
if isCompactHeightPhone {
|
||||
return TypingKeyLayoutBuilder.Metrics(
|
||||
keyRowHeight: compactHeightPhoneKeyRowHeight,
|
||||
keyRowSpacing: compactHeightPhoneKeyRowSpacing,
|
||||
keyHorizontalSpacing: keyHorizontalSpacing,
|
||||
// Ignored: landscape keys are ~70 pt wide and need the
|
||||
// system's half-key indent, not the portrait 18 pt.
|
||||
secondRowInset: 0,
|
||||
bottomRowHeight: compactHeightPhoneKeyRowHeight,
|
||||
bottomActionSpacing: KeyboardChromeLayout.actionKeySpacing,
|
||||
gridToBottomSpacing: compactHeightPhoneKeyRowSpacing,
|
||||
derivesSecondRowInsetFromKeyWidth: true
|
||||
)
|
||||
}
|
||||
if isExpandedPhone {
|
||||
return TypingKeyLayoutBuilder.Metrics(
|
||||
keyRowHeight: expandedPhoneKeyRowHeight,
|
||||
keyRowSpacing: expandedPhoneKeyRowSpacing,
|
||||
keyHorizontalSpacing: expandedPhoneKeyHorizontalSpacing,
|
||||
// Ignored: wide keys need the system's half-key indent.
|
||||
secondRowInset: 0,
|
||||
bottomRowHeight: expandedPhoneKeyRowHeight,
|
||||
// Bottom-row widths are pre-split with this spacing.
|
||||
bottomActionSpacing: KeyboardChromeLayout.actionKeySpacing,
|
||||
gridToBottomSpacing: expandedPhoneKeyRowSpacing,
|
||||
derivesSecondRowInsetFromKeyWidth: true
|
||||
)
|
||||
}
|
||||
return TypingKeyLayoutBuilder.Metrics(
|
||||
keyRowHeight: keyRowHeight,
|
||||
keyRowSpacing: keyRowSpacing,
|
||||
@@ -77,9 +133,19 @@ public enum TypingSurfaceMetrics {
|
||||
|
||||
/// Content-driven keyboard height. iPad grows its rows, so the shell has to
|
||||
/// grow with them or the bottom row is clipped.
|
||||
public static func contentHeight(isIPad: Bool, width: CGFloat) -> CGFloat {
|
||||
guard isIPad else { return KeyboardChromeLayout.totalHeight }
|
||||
let m = metrics(isIPad: true, width: width)
|
||||
public static func contentHeight(
|
||||
isIPad: Bool,
|
||||
width: CGFloat,
|
||||
isExpandedPhone: Bool = false,
|
||||
isCompactHeightPhone: Bool = false
|
||||
) -> CGFloat {
|
||||
guard isIPad || isExpandedPhone || isCompactHeightPhone else { return KeyboardChromeLayout.totalHeight }
|
||||
let m = metrics(
|
||||
isIPad: isIPad,
|
||||
width: width,
|
||||
isExpandedPhone: isExpandedPhone,
|
||||
isCompactHeightPhone: isCompactHeightPhone
|
||||
)
|
||||
let inner = topRegionHeight
|
||||
+ verticalKeySpacing
|
||||
+ (3 * m.keyRowHeight + 2 * m.keyRowSpacing)
|
||||
|
||||
Reference in New Issue
Block a user