ReviewOS

also looking at this

craft-native/craft

fix(mobile): preserve native editing, navigation, and durable media requests

#369
Merged chris wants to merge codex/native-vertical-scroll-tab-swipes into main
36 files +864 -185

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

.github/workflows/mobile-e2e.ymlmodified+8-8
Changes to .github/workflows/mobile-e2e.yml
@@ -132,11 +132,11 @@ jobs:
132132 strategy:
133133 fail-fast: false
134134 matrix:
135 suite: ${{ fromJSON(inputs.scope == 'ios-render' && '["uikit"]' || inputs.scope == 'ios-navigation' && '["navigation"]' || '["uikit","navigation"]') }}
135 suite: ${{ fromJSON(inputs.scope == 'ios-render' && '["uikit"]' || inputs.scope == 'ios-navigation' && inputs.selectors != '' && '["navigation"]' || inputs.scope == 'ios-navigation' && '["navigation-unit","navigation-ui"]' || '["uikit","navigation-unit","navigation-ui"]') }}
136136 runs-on: macos-15
137 # Compile and run each native fixture on its own fresh runner. Combining
138 # both consumed the job budget before navigation finished on slower hosts.
139 # Each suite keeps the same bounded backstop and strict XCTest assertions.
137 # UIKit reconciliation and both navigation XCTest targets each get a fresh
138 # runner and result bundle. Combining unit and UI navigation exceeded the
139 # existing job budget; all targets remain required with the same backstop.
140140 timeout-minutes: 45
141141 env:
142142 CRAFT_EVIDENCE_LABEL: ios-native
@@ -163,7 +163,7 @@ jobs:
163163 # @stacksjs/stx compiler (`stx native compile`) so this job cannot silently change
164164 # underneath us. Both iOS jobs and Android use the same compiler revision.
165165 - name: Checkout stx compiler
166 if: ${{ matrix.suite == 'navigation' }}
166 if: ${{ startsWith(matrix.suite, 'navigation') }}
167167 uses: actions/checkout@v6
168168 with:
169169 repository: stacksjs/stx
@@ -171,7 +171,7 @@ jobs:
171171 path: .stx-compiler
172172
173173 - name: Install stx compiler dependencies
174 if: ${{ matrix.suite == 'navigation' }}
174 if: ${{ startsWith(matrix.suite, 'navigation') }}
175175 working-directory: .stx-compiler
176176 # Pantry ships Bun 1.3.14, which cannot frozen-install the pinned stx
177177 # lockfile. Resolve the unchanged external manifest without rewriting
@@ -191,10 +191,10 @@ jobs:
191191 id: navigation
192192 # Reconciliation and navigation are independent suites. Keep the
193193 # navigation evidence when reconciliation fails; the job still fails.
194 if: ${{ !cancelled() && matrix.suite == 'navigation' }}
194 if: ${{ !cancelled() && startsWith(matrix.suite, 'navigation') }}
195195 env:
196196 STX_CLI: ${{ github.workspace }}/.stx-compiler/packages/stx/bin/cli.ts
197 CRAFT_ONLY_TESTING: ${{ inputs.selectors }}
197 CRAFT_ONLY_TESTING: ${{ matrix.suite == 'navigation-unit' && 'NativeNavigationUnitTests' || matrix.suite == 'navigation-ui' && 'NativeNavigationUITests' || inputs.selectors }}
198198 CRAFT_NATIVE_NAVIGATION_RESULT: ${{ github.workspace }}/artifacts/mobile-e2e/ios-native/navigation.xcresult
199199 run: |
200200 mkdir -p artifacts/mobile-e2e/ios-native
scripts/release-workflow.test.tsmodified+26-0
Changes to scripts/release-workflow.test.ts
@@ -1,6 +1,7 @@
11import { afterAll, expect, test } from 'bun:test'
22import { chmodSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
33import { join } from 'node:path'
4import { runInNewContext } from 'node:vm'
45import { requiredReleaseAssets } from './release-manifest'
56import { createTempDir, removeAllTempDirs } from '../packages/typescript/src/temp-dir'
67
@@ -76,6 +77,31 @@ test('native iOS suites and protocol checks have independent required jobs', ()
7677 expect(workflow.jobs[name].env?.DEVELOPER_DIR).toBe('/Applications/Xcode_26.2.app/Contents/Developer')
7778})
7879
80test('navigation shards retain both complete XCTest targets and focused selection', () => {
81 const workflow = Bun.YAML.parse(readFileSync(join(import.meta.dir, '../.github/workflows/mobile-e2e.yml'), 'utf8')) as { jobs: Record<string, Job> }
82 const native = workflow.jobs['ios-native']
83 const expression = (native.strategy?.matrix as unknown as { suite: string }).suite
84 const navigation = native.steps?.find(step => step.name === 'Test native multi-screen navigation')
85 const evaluate = (value: string, scope: string, selectors = '', suite = '') => runInNewContext(value.replace(/^\$\{\{\s*|\s*\}\}$/g, ''), {
86 inputs: { scope, selectors }, matrix: { suite }, fromJSON: JSON.parse,
87 startsWith: (text: string, prefix: string) => text.startsWith(prefix), cancelled: () => false,
88 }, { timeout: 100 })
89 for (const scope of ['', 'all'])
90 expect(evaluate(expression, scope)).toEqual(['uikit', 'navigation-unit', 'navigation-ui'])
91 expect(evaluate(expression, 'ios-navigation')).toEqual(['navigation-unit', 'navigation-ui'])
92 expect(evaluate(expression, 'ios-render')).toEqual(['uikit'])
93 const focused = 'NativeNavigationUITests/NativeNavigationUITests/testMultilineInputKeepsKeyboardFocusWhenTapped'
94 expect(evaluate(expression, 'ios-navigation', focused)).toEqual(['navigation'])
95 expect(evaluate(navigation!.env!.CRAFT_ONLY_TESTING, 'all', '', 'navigation-unit')).toBe('NativeNavigationUnitTests')
96 expect(evaluate(navigation!.env!.CRAFT_ONLY_TESTING, 'all', '', 'navigation-ui')).toBe('NativeNavigationUITests')
97 expect(evaluate(navigation!.env!.CRAFT_ONLY_TESTING, 'ios-navigation', focused, 'navigation')).toBe(focused)
98 for (const suite of ['navigation-unit', 'navigation-ui', 'navigation'])
99 expect(evaluate(navigation!.if!, 'all', '', suite)).toBe(true)
100 expect(evaluate(navigation!.if!, 'all', '', 'uikit')).toBe(false)
101 expect(native.strategy?.['fail-fast']).toBe(false)
102 expect(workflow.jobs['mobile-summary'].steps?.find(step => step.name === 'Fail if any leg failed')?.if).toContain('needs.ios-native.result')
103})
104
79105test('mobile device jobs preserve runner diagnostics when the harness fails', () => {
80106 const workflow = Bun.YAML.parse(readFileSync(join(import.meta.dir, '../.github/workflows/mobile-e2e.yml'), 'utf8')) as { jobs: Record<string, Job> }
81107 for (const [jobName, evidenceDir, expectedCommand] of [