Update iPad marketing navigation screenshots
This commit is contained in:
1 parent
b60e68e3d5
commit
2573df6f1b
3 files changed
+60
-40
No files matched your search
+33
-4
@@ -68,6 +68,11 @@ The required devices and outputs are:
|
||||
- Five scenes per locale: `vault`, `sftp`, `ssh`, `metrics`, and `assistant`.
|
||||
- Three locales, producing 15 screenshots per device and 30 total.
|
||||
|
||||
The runner defaults to the iOS/iPadOS 26.5 simulator runtime so installing a newer
|
||||
runtime cannot silently change the marketing UI or select another same-name
|
||||
device. Set `TERMIND_MARKETING_RUNTIME` to a full CoreSimulator runtime identifier
|
||||
only when intentionally updating the runtime used for a capture set.
|
||||
|
||||
For a targeted recapture, set both filters. This example refreshes only the three
|
||||
localized iPad Assistant sources:
|
||||
|
||||
@@ -77,7 +82,7 @@ TERMIND_MARKETING_DEVICES=ipad \
|
||||
Scripts/run-mobile-marketing-screenshots.sh
|
||||
```
|
||||
|
||||
The iPhone Vault, Metrics and Assistant fixtures use the same native `Tab` API,
|
||||
The iPhone and iPad Vault, Metrics and Assistant fixtures use the same native `Tab` API,
|
||||
sidebar-adaptable style and feature registration order as the production shell:
|
||||
Vault, Metrics, Workspaces, Assistant, Settings. Metrics is its own selected tab;
|
||||
Vault no longer contains the former Metrics navigation row. Refresh all nine
|
||||
@@ -93,10 +98,25 @@ Regenerate only their matching device frames, replace the corresponding Figma
|
||||
iPhone boards 04-06, and export those nine boards again. The three-device first
|
||||
boards use iPhone SSH and therefore do not change for this navigation refresh.
|
||||
|
||||
For the matching nine iPad sources, run:
|
||||
|
||||
```sh
|
||||
TERMIND_MARKETING_SCENES=vault,metrics,assistant \
|
||||
TERMIND_MARKETING_DEVICES=ipad \
|
||||
Scripts/run-mobile-marketing-screenshots.sh
|
||||
```
|
||||
|
||||
The iPad fixtures use the native adaptive tab container: Metrics shows the top
|
||||
tab bar, while Assistant and Vault show the expanded sidebar. Each capture fixes
|
||||
that presentation independently of restored simulator state. They do not add
|
||||
simulated sidebar or back buttons to the top-level Metrics and Assistant pages.
|
||||
Regenerate only the matching nine iPad device frames and App Store boards 04-06;
|
||||
first boards use iPad SFTP and remain unchanged.
|
||||
|
||||
The runner sets a deterministic `9:41` status bar, charged battery, Wi-Fi, and
|
||||
cellular state. iPad framebuffer output is rotated after capture because `simctl`
|
||||
keeps portrait pixel orientation for a landscape display. Confirm the final file
|
||||
dimensions rather than trusting Simulator orientation alone.
|
||||
cellular state. It sets the iPad orientation and captures its landscape display
|
||||
with `devicectl`, verifying `2752x2064` before acknowledging each capture. The
|
||||
iPhone uses `simctl` screenshots. No post-capture rotation is required.
|
||||
|
||||
Whenever SFTP, Metrics, Assistant navigation, or system chrome changes, recapture
|
||||
the affected device and every locale. A marketing fixture may be updated to expose
|
||||
@@ -352,3 +372,12 @@ images changed; the other 108 marketing files, including SSH/SFTP, first boards,
|
||||
iPad and Mac assets, stayed unchanged. The nine Figma image fills match the local
|
||||
device-frame PNGs byte for byte, and the nine final JPGs are full-size
|
||||
`1284x2778` opaque sRGB exports. The App Store directory still contains 54 JPGs.
|
||||
|
||||
The subsequent 2026-09-18 iPad refresh updated the same three scenes and locales:
|
||||
Metrics uses native top tabs; Assistant and Vault use the expanded native sidebar.
|
||||
Raw captures and final App Store exports are `2752x2064`, with `2960x2290` device
|
||||
frames. Figwright readback confirmed matching original image bytes and unchanged
|
||||
copy, fonts, layout, geometry and FIT mode. SHA-256 comparison against the completed
|
||||
iPhone baseline found exactly 27 changed iPad images and 108 unchanged marketing
|
||||
files, including iPhone, Mac, SSH/SFTP and the first boards. All 54 App Store JPGs
|
||||
remain opaque three-channel sRGB.
|
||||
@@ -10,6 +10,7 @@ derived_data="$repository_directory/.build/mobile-marketing-screenshots"
|
||||
marketing_xctestrun="$derived_data/Build/Products/Marketing-TFI.xctestrun"
|
||||
marketing_scenes="${TERMIND_MARKETING_SCENES:-}"
|
||||
marketing_devices="${TERMIND_MARKETING_DEVICES:-iphone,ipad}"
|
||||
marketing_runtime="${TERMIND_MARKETING_RUNTIME:-com.apple.CoreSimulator.SimRuntime.iOS-26-5}"
|
||||
|
||||
expected_capture_count() {
|
||||
if [[ -z "$marketing_scenes" ]]; then
|
||||
@@ -25,7 +26,8 @@ expected_capture_count() {
|
||||
find_device() {
|
||||
local name="$1"
|
||||
xcrun simctl list devices available -j \
|
||||
| jq -r --arg name "$name" '.devices[][] | select(.name == $name) | .udid' \
|
||||
| jq -r --arg name "$name" --arg runtime "$marketing_runtime" \
|
||||
'.devices[$runtime][]? | select(.name == $name) | .udid' \
|
||||
| head -n 1
|
||||
}
|
||||
|
||||
@@ -59,7 +61,7 @@ run_capture() {
|
||||
local device_id
|
||||
device_id="$(find_device "$name")"
|
||||
if [[ -z "$device_id" ]]; then
|
||||
print -u2 "Required simulator is unavailable: $name"
|
||||
print -u2 "Required simulator is unavailable: $name ($marketing_runtime)"
|
||||
exit 69
|
||||
fi
|
||||
|
||||
@@ -135,7 +137,7 @@ run_capture() {
|
||||
mkdir -p "$output_directory"
|
||||
build_device_id="$(find_device "iPhone 17 Pro Max")"
|
||||
if [[ -z "$build_device_id" ]]; then
|
||||
print -u2 "Required simulator is unavailable: iPhone 17 Pro Max"
|
||||
print -u2 "Required simulator is unavailable: iPhone 17 Pro Max ($marketing_runtime)"
|
||||
exit 69
|
||||
fi
|
||||
xcodebuild \
|
||||
|
||||
@@ -68,13 +68,9 @@ struct MobileMarketingScreenshotTests {
|
||||
if includes("metrics") {
|
||||
let metrics = makeMetrics(language: language)
|
||||
try await capture("metrics", language: language) {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
NavigationStack { MarketingMetricsScene(servers: metrics, language: language) }
|
||||
} else {
|
||||
MarketingPhoneTabShell(selected: .metrics) {
|
||||
NavigationStack {
|
||||
MarketingMetricsScene(servers: metrics, language: language, showsSidebarButton: false)
|
||||
}
|
||||
MarketingTabShell(selected: .metrics) {
|
||||
NavigationStack {
|
||||
MarketingMetricsScene(servers: metrics)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,16 +81,9 @@ struct MobileMarketingScreenshotTests {
|
||||
let conversation = ConversationModel.demo(language: try #require(AppLanguage(rawValue: language)))
|
||||
assistant.library.insertAndSelect(conversation)
|
||||
try await capture("assistant", language: language) {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
MarketingTabShell(selected: .assistant) {
|
||||
NavigationStack {
|
||||
MobileAssistantView(app: app, model: assistant, libraryRoute: .constant(nil))
|
||||
.toolbar { marketingBackButton(language: language) }
|
||||
}
|
||||
} else {
|
||||
MarketingPhoneTabShell(selected: .assistant) {
|
||||
NavigationStack {
|
||||
MobileAssistantView(app: app, model: assistant, libraryRoute: .constant(nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,6 +196,12 @@ struct MobileMarketingScreenshotTests {
|
||||
controller.view.layoutIfNeeded()
|
||||
try await Task.sleep(for: .milliseconds(900))
|
||||
|
||||
if isPad, let tabs = tabController(in: controller) {
|
||||
// Keep each scene's navigation presentation independent of restored simulator state.
|
||||
tabs.sidebar.isHidden = name == "metrics"
|
||||
try await Task.sleep(for: .milliseconds(900))
|
||||
}
|
||||
|
||||
if ProcessInfo.processInfo.environment["TERMIND_MARKETING_SYSTEM_CAPTURE"] == "1" {
|
||||
try await waitForSystemCapture(name, language: language)
|
||||
return
|
||||
@@ -233,6 +228,11 @@ struct MobileMarketingScreenshotTests {
|
||||
print("Marketing snapshot: \(output.path)")
|
||||
}
|
||||
|
||||
private func tabController(in controller: UIViewController) -> UITabBarController? {
|
||||
if let tabs = controller as? UITabBarController { return tabs }
|
||||
return controller.children.lazy.compactMap { tabController(in: $0) }.first
|
||||
}
|
||||
|
||||
private func waitForSystemCapture(_ name: String, language: String) async throws {
|
||||
let documents = try #require(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first)
|
||||
let control = documents.appending(path: "marketing-capture-control", directoryHint: .isDirectory)
|
||||
@@ -281,22 +281,18 @@ private struct MarketingVaultScene: View {
|
||||
let app: MobileAppModel
|
||||
|
||||
var body: some View {
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
MobileRootView(app: app)
|
||||
} else {
|
||||
MarketingPhoneTabShell(selected: .vault) {
|
||||
NavigationStack {
|
||||
MobileVaultView(
|
||||
app: app, editingHost: .constant(nil), activateHost: { _ in },
|
||||
open: { _, _ in }
|
||||
)
|
||||
}
|
||||
MarketingTabShell(selected: .vault) {
|
||||
NavigationStack {
|
||||
MobileVaultView(
|
||||
app: app, editingHost: .constant(nil), activateHost: { _ in },
|
||||
open: { _, _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MarketingPhoneTabShell<Content: View>: View {
|
||||
private struct MarketingTabShell<Content: View>: View {
|
||||
let selected: MobileDestination
|
||||
let content: Content
|
||||
private let features = MobileFeatureRegistry.standard()
|
||||
@@ -355,8 +351,6 @@ private struct MarketingTerminalScene: View {
|
||||
|
||||
private struct MarketingMetricsScene: View {
|
||||
let servers: [ServerMetric]
|
||||
let language: String
|
||||
var showsSidebarButton = true
|
||||
|
||||
var body: some View {
|
||||
MobileCardPage {
|
||||
@@ -375,11 +369,6 @@ private struct MarketingMetricsScene: View {
|
||||
}
|
||||
.navigationTitle("Metrics")
|
||||
.toolbar {
|
||||
if showsSidebarButton {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Sidebar", systemImage: "sidebar.left") {}.labelStyle(.iconOnly)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("Add Server", systemImage: "plus") {}.labelStyle(.iconOnly)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user