@@ -88,54 +88,6 @@ struct Context: Identifiable, Codable, Equatable, Hashable {
8888 }
8989}
9090
91- // Migration helper for old contexts.json
92- extension Context {
93- // Accepts a dictionary from JSONSerialization
94- static func migrate( from legacy: [ String : Any ] ) -> Context ? {
95- guard let name = legacy [ " name " ] as? String else { return nil }
96- let id = ( legacy [ " id " ] as? String ) . flatMap { UUID ( uuidString: $0) } ?? UUID ( )
97- let iconName = legacy [ " iconName " ] as? String
98- let iconBackgroundColor = legacy [ " iconBackgroundColor " ] as? String
99- let iconForegroundColor = legacy [ " iconForegroundColor " ] as? String
100- let createdAt = ( legacy [ " createdAt " ] as? String ) . flatMap { ISO8601DateFormatter ( ) . date ( from: $0) } ?? Date ( )
101- let lastUsed = ( legacy [ " lastUsed " ] as? String ) . flatMap { ISO8601DateFormatter ( ) . date ( from: $0) } ?? Date ( )
102- var items : [ ContextItem ] = [ ]
103- if let apps = legacy [ " applications " ] as? [ [ String : Any ] ] {
104- for appDict in apps {
105- if let data = try ? JSONSerialization . data ( withJSONObject: appDict) ,
106- let app = try ? JSONDecoder ( ) . decode ( AppItem . self, from: data) {
107- items. append ( . application( app) )
108- }
109- }
110- }
111- if let docs = legacy [ " documents " ] as? [ [ String : Any ] ] {
112- for docDict in docs {
113- if let data = try ? JSONSerialization . data ( withJSONObject: docDict) ,
114- let doc = try ? JSONDecoder ( ) . decode ( DocumentItem . self, from: data) {
115- items. append ( . document( doc) )
116- }
117- }
118- }
119- if let tabs = legacy [ " browserTabs " ] as? [ [ String : Any ] ] {
120- for tabDict in tabs {
121- if let data = try ? JSONSerialization . data ( withJSONObject: tabDict) ,
122- let tab = try ? JSONDecoder ( ) . decode ( BrowserTab . self, from: data) {
123- items. append ( . browserTab( tab) )
124- }
125- }
126- }
127- if let terms = legacy [ " terminalSessions " ] as? [ [ String : Any ] ] {
128- for termDict in terms {
129- if let data = try ? JSONSerialization . data ( withJSONObject: termDict) ,
130- let term = try ? JSONDecoder ( ) . decode ( TerminalSession . self, from: data) {
131- items. append ( . terminalSession( term) )
132- }
133- }
134- }
135- return Context ( id: id, name: name, items: items, iconName: iconName, iconBackgroundColor: iconBackgroundColor, iconForegroundColor: iconForegroundColor, createdAt: createdAt, lastUsed: lastUsed)
136- }
137- }
138-
13991struct AppItem : Identifiable , Codable , Equatable , Hashable {
14092 var id : UUID = UUID ( )
14193 var name : String
@@ -212,21 +164,19 @@ class ContextManager: ObservableObject {
212164 saveContexts ( )
213165 }
214166
215- func deleteContext( _ context: Context ) {
216- contexts. removeAll { $0. id == context. id }
217- if activeContexts. contains ( where: { $0. id == context. id } ) {
218- activeContexts. removeAll ( where: { $0. id == context. id } )
219- }
220- // Remove all launchers for the context
221- contextLaunchers. removeValue ( forKey: context. id)
167+ func deleteContext( contextID: UUID ) {
168+ contexts. removeAll { $0. id == contextID }
169+ activeContexts. removeAll { $0. id == contextID }
170+ contextLaunchers. removeValue ( forKey: contextID)
222171 saveContexts ( )
223172 }
224173
225174 // MARK: - Context Switching
226175
227- func switchToContext( _ context: Context ) {
228- activeContexts. append ( context)
229- openContext ( context)
176+ func switchToContext( contextID: UUID ) {
177+ guard let latestContext = contexts. first ( where: { $0. id == contextID } ) else { return }
178+ activeContexts. append ( latestContext)
179+ openContext ( contextID: contextID)
230180 saveContexts ( )
231181 }
232182
@@ -279,31 +229,75 @@ class ContextManager: ObservableObject {
279229 }
280230
281231 /// Open all items in the context, batching by app/bundle where possible, using launchers
282- private func openContext( _ context: Context ) {
232+ private func openContext( contextID: UUID ) {
233+ guard let contextToOpen = contexts. first ( where: { $0. id == contextID } ) else { return }
234+ print ( " 🚀 Opening context ' \( contextToOpen. name) ' with \( contextToOpen. items. count) items " )
235+
283236 var itemsByBundle : [ String : [ ContextItem ] ] = [ : ]
284- for item in context . items {
237+ for item in contextToOpen . items {
285238 if let bundleId = bundleId ( for: item) {
286239 itemsByBundle [ bundleId, default: [ ] ] . append ( item)
287240 }
288241 }
242+
289243 var launchers : [ ContextApplicationLauncher ] = [ ]
290244 for (bundleId, items) in itemsByBundle {
291245 let launcher = launcher ( for: bundleId, items: items)
246+
247+ // Trace which launcher is opening which items
248+ let launcherType = String ( describing: type ( of: launcher) )
249+ print ( " 📱 Using \( launcherType) for bundle ' \( bundleId) ' with \( items. count) items: " )
250+
251+ for item in items {
252+ switch item {
253+ case . application( let app) :
254+ print ( " • App: \( app. name) ( \( app. bundleIdentifier) ) " )
255+ if let windowTitle = app. windowTitle {
256+ print ( " Window: \( windowTitle) " )
257+ }
258+ if let filePath = app. filePath {
259+ print ( " File: \( filePath) " )
260+ }
261+ case . document( let doc) :
262+ print ( " • Document: \( doc. name) at \( doc. filePath) " )
263+ case . browserTab( let tab) :
264+ print ( " • Browser Tab: \( tab. title) - \( tab. url) " )
265+ case . terminalSession( let session) :
266+ print ( " • Terminal Session: \( session. title) in \( session. workingDirectory) " )
267+ if let command = session. command {
268+ print ( " Command: \( command) " )
269+ }
270+ }
271+ }
272+
292273 launcher. open ( )
293274 launchers. append ( launcher)
294275 }
276+
295277 // Store launchers for this context
296- contextLaunchers [ context. id] = launchers
278+ contextLaunchers [ contextToOpen. id] = launchers
279+
297280 // Open terminal sessions directly (not via launcher abstraction)
298- for item in context. items {
281+ let terminalSessions = contextToOpen. items. compactMap { item -> TerminalSession ? in
282+ if case . terminalSession( let session) = item { return session } else { return nil }
283+ }
284+
285+ if !terminalSessions. isEmpty {
286+ print ( " 🖥️ Opening \( terminalSessions. count) terminal session(s) directly: " )
287+ }
288+
289+ for item in contextToOpen. items {
299290 if case . terminalSession( let session) = item {
300291 let commandToRun : String
301292 if let command = session. command, !command. isEmpty {
302293 commandToRun = command
303294 } else {
295+ print ( " ⚠️ Skipping terminal session ' \( session. title) ' - no command specified " )
304296 continue // Skip if no command
305297 }
306298 let workingDir = session. workingDirectory
299+ print ( " • Terminal: ' \( session. title) ' in ' \( workingDir) ' running ' \( commandToRun) ' " )
300+
307301 let script = """
308302 tell application \" Terminal \"
309303 activate
@@ -314,11 +308,15 @@ class ContextManager: ObservableObject {
314308 var error : NSDictionary ? = nil
315309 appleScript. executeAndReturnError ( & error)
316310 if let error = error {
317- print ( " Failed to launch terminal session: \( error) " )
311+ print ( " ❌ Failed to launch terminal session: \( error) " )
312+ } else {
313+ print ( " ✅ Terminal session launched successfully " )
318314 }
319315 }
320316 }
321317 }
318+
319+ print ( " ✅ Context ' \( contextToOpen. name) ' opened with \( launchers. count) launcher(s) and \( terminalSessions. count) terminal session(s) " )
322320 }
323321
324322 // MARK: - Context Reordering
@@ -330,16 +328,14 @@ class ContextManager: ObservableObject {
330328
331329 // MARK: - Application Management
332330
333- func closeContext( _ context: Context ) {
334- // Use launchers to close apps/items
335- if let launchers = contextLaunchers [ context. id] {
331+ func closeContext( contextID: UUID ) {
332+ if let launchers = contextLaunchers [ contextID] {
336333 for launcher in launchers {
337334 launcher. close ( )
338335 }
339- contextLaunchers. removeValue ( forKey: context . id )
336+ contextLaunchers. removeValue ( forKey: contextID )
340337 }
341- // Remove the context from activeContexts if present
342- activeContexts. removeAll { $0. id == context. id }
338+ activeContexts. removeAll { $0. id == contextID }
343339 }
344340
345341 func closeAllContexts( ) {
@@ -372,21 +368,9 @@ class ContextManager: ObservableObject {
372368 private func loadContexts( ) {
373369 do {
374370 let data = try Data ( contentsOf: contextsFileURL)
375- do {
376- // Try decoding as new format
377- let decoded = try JSONDecoder ( ) . decode ( [ Context ] . self, from: data)
378- self . contexts = decoded
379- } catch {
380- // Try to migrate from old format
381- if let jsonArray = try ? JSONSerialization . jsonObject ( with: data) as? [ [ String : Any ] ] {
382- let migrated = jsonArray. compactMap { Context . migrate ( from: $0) }
383- self . contexts = migrated
384- // Save back in new format
385- saveContexts ( )
386- } else {
387- print ( " Failed to decode or migrate contexts.json: \( error) " )
388- }
389- }
371+ // Try decoding as new format
372+ let decoded = try JSONDecoder ( ) . decode ( [ Context ] . self, from: data)
373+ self . contexts = decoded
390374 } catch {
391375 print ( " No existing contexts.json found or failed to read: \( error) " )
392376 }
@@ -402,8 +386,8 @@ class ContextManager: ObservableObject {
402386 return nil
403387 }
404388
405- func isActive( context : Context ) -> Bool {
406- return activeContexts. contains ( where: { $0. id == context . id } )
389+ func isActive( contextID : UUID ) -> Bool {
390+ return activeContexts. contains ( where: { $0. id == contextID } )
407391 }
408392}
409393
@@ -454,4 +438,16 @@ extension ContextManager {
454438 contexts [ idx] . moveItems ( fromOffsets: fromOffsets, toOffset: toOffset)
455439 saveContexts ( )
456440 }
457- }
441+ }
442+
443+ // Helper to move elements in array
444+ extension Array {
445+ mutating func move( fromOffsets: IndexSet , toOffset: Int ) {
446+ let elements = fromOffsets. map { self [ $0] }
447+ // Remove elements at offsets manually
448+ for offset in fromOffsets. sorted ( by: > ) {
449+ self . remove ( at: offset)
450+ }
451+ self . insert ( contentsOf: elements, at: toOffset)
452+ }
453+ }
0 commit comments