also looking at this
fix(native): interaction, audio, document and checkpoint gaps stranded after #401
#415
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.
| @@ -1367,7 +1367,9 @@ dismissed picker resolves with an empty array; a canceled or oversized copy | ||
| 1367 | 1367 | never leaves a partial file behind. The same cancellation cleanup applies while |
| 1368 | 1368 | Android is decoding a picked image or copying a video, so a request canceled |
| 1369 | 1369 | after the picker closes cannot publish or leak an app-owned file. Use this for |
| 1370 | GPX, TCX, FIT, PDF, or other documents that are not photos or videos. | |
| 1370 | GPX, TCX, FIT, PDF, or other documents that are not photos or videos. On iOS, | |
| 1371 | failed photo JPEG creation or finalization also removes its staging directory, | |
| 1372 | so a rejected image import does not accumulate orphaned temporary files. | |
| 1371 | 1373 | |
| 1372 | 1374 | `craft.audio` playback is screen-owned on both hosts. Android requests audio |
| 1373 | 1375 | focus before preparing a player, reports a denied request as `AUDIO_FOCUS_DENIED`, |
| @@ -2150,6 +2150,15 @@ describe('Craft iOS builder', () => { | ||
| 2150 | 2150 | expect(audio).toContain('AVAudioSession.interruptionNotification') |
| 2151 | 2151 | }) |
| 2152 | 2152 | |
| 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 | ||
| 2153 | 2162 | it('runs an all-native app with the shell\'s tabs and paths, an app script and the web view\'s services', async () => { |
| 2154 | 2163 | const output = createTempDir('craft-ios-native-app-') |
| 2155 | 2164 | const bundle = join(output, 'bundle.js') |
| @@ -189,10 +189,12 @@ enum CraftNativeFiles { | ||
| 189 | 189 | do { |
| 190 | 190 | let url = try newFile(named: name) |
| 191 | 191 | guard let destination = CGImageDestinationCreateWithURL(url as CFURL, UTType.jpeg.identifier as CFString, 1, nil) else { |
| 192 | try? FileManager.default.removeItem(at: url.deletingLastPathComponent()) | |
| 192 | 193 | return .failure(CraftNativeActionError(code: "WRITE_ERROR", message: "The photo could not be saved")) |
| 193 | 194 | } |
| 194 | 195 | CGImageDestinationAddImage(destination, image, [kCGImageDestinationLossyCompressionQuality: quality] as CFDictionary) |
| 195 | 196 | guard CGImageDestinationFinalize(destination) else { |
| 197 | try? FileManager.default.removeItem(at: url.deletingLastPathComponent()) | |
| 196 | 198 | return .failure(CraftNativeActionError(code: "WRITE_ERROR", message: "The photo could not be saved")) |
| 197 | 199 | } |
| 198 | 200 | return .success(describe(url, type: "image/jpeg", extra: ["width": image.width, "height": image.height])) |