ReviewOS

also looking at this

craft-native/craft

fix(native): interaction, audio, document and checkpoint gaps stranded after #401

#415
Merged glennmichael123 wants to merge codex/issues-388-400-344 into main
19 files +987 -50

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.

docs/guides/native-stx.mdmodified+3-1
Changes to docs/guides/native-stx.md
@@ -1367,7 +1367,9 @@ dismissed picker resolves with an empty array; a canceled or oversized copy
13671367never leaves a partial file behind. The same cancellation cleanup applies while
13681368Android is decoding a picked image or copying a video, so a request canceled
13691369after the picker closes cannot publish or leak an app-owned file. Use this for
1370GPX, TCX, FIT, PDF, or other documents that are not photos or videos.
1370GPX, TCX, FIT, PDF, or other documents that are not photos or videos. On iOS,
1371failed photo JPEG creation or finalization also removes its staging directory,
1372so a rejected image import does not accumulate orphaned temporary files.
13711373
13721374`craft.audio` playback is screen-owned on both hosts. Android requests audio
13731375focus before preparing a player, reports a denied request as `AUDIO_FOCUS_DENIED`,
packages/ios/src/index.test.tsmodified+9-0
Changes to packages/ios/src/index.test.ts
@@ -2150,6 +2150,15 @@ describe('Craft iOS builder', () => {
21502150 expect(audio).toContain('AVAudioSession.interruptionNotification')
21512151 })
21522152
2153 it('cleans failed native photo writes instead of leaving staging folders behind', async () => {
2154 const output = createTempDir('craft-ios-native-file-cleanup-')
2155 await init({ runtimeDir: null, name: 'Gate', bundleId: 'com.example.gate', output, config: { nativeScreens: { '/m/import': 'Import' } } as any })
2156 const files = readFileSync(join(output, 'Sources', 'CraftNativeFiles.swift'), 'utf8')
2157 expect(files).toContain('CGImageDestinationCreateWithURL')
2158 expect(files).toContain('CGImageDestinationFinalize(destination) else {')
2159 expect(files.match(/removeItem\(at: url\.deletingLastPathComponent\(\)\)/g)?.length).toBe(3)
2160 })
2161
21532162 it('runs an all-native app with the shell\'s tabs and paths, an app script and the web view\'s services', async () => {
21542163 const output = createTempDir('craft-ios-native-app-')
21552164 const bundle = join(output, 'bundle.js')
packages/ios/templates/CraftNativeFiles.swiftmodified+2-0
Changes to packages/ios/templates/CraftNativeFiles.swift
@@ -189,10 +189,12 @@ enum CraftNativeFiles {
189189 do {
190190 let url = try newFile(named: name)
191191 guard let destination = CGImageDestinationCreateWithURL(url as CFURL, UTType.jpeg.identifier as CFString, 1, nil) else {
192 try? FileManager.default.removeItem(at: url.deletingLastPathComponent())
192193 return .failure(CraftNativeActionError(code: "WRITE_ERROR", message: "The photo could not be saved"))
193194 }
194195 CGImageDestinationAddImage(destination, image, [kCGImageDestinationLossyCompressionQuality: quality] as CFDictionary)
195196 guard CGImageDestinationFinalize(destination) else {
197 try? FileManager.default.removeItem(at: url.deletingLastPathComponent())
196198 return .failure(CraftNativeActionError(code: "WRITE_ERROR", message: "The photo could not be saved"))
197199 }
198200 return .success(describe(url, type: "image/jpeg", extra: ["width": image.width, "height": image.height]))