Skip to content

Commit 4a025ce

Browse files
Merge pull request #45 from BlueFenixProductions/claude/66-widget-quickadd
feat(66): board widget snapshot + AddCardIntent quick-add
2 parents 04159f2 + 7fa1e53 commit 4a025ce

14 files changed

Lines changed: 858 additions & 0 deletions

FenixKanban.xcodeproj/project.pbxproj

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
17275D2E744DB68AE345032D /* ColumnTombstone+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86F7D77B94002FF0AFD97874 /* ColumnTombstone+CoreDataClass.swift */; };
2121
17B189DE5F79704AE4FDF910 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D10A143D4EF691E4A0436C89 /* NotificationService.swift */; };
2222
18A304663770D25F082F2E2C /* FizzyBoardMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F61A847D1870E4D02FFCCEA /* FizzyBoardMappingTests.swift */; };
23+
19DA249C4CDCFEBA413C42FF /* BoardSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39A67DC347EB0CD3589743 /* BoardSnapshot.swift */; };
2324
1A8A7DADB70C45BD073693AC /* FizzyClient+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D940AFA2D7BAA47055968F /* FizzyClient+Directory.swift */; };
2425
1A8EDCFA9E34232FEF82D754 /* BoardViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB041FBA0AE74AA75078C039 /* BoardViewModelTests.swift */; };
2526
1AC18227D4ECC15AE10448B8 /* SyncSchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76CCDC89B952EC09B79E7A1D /* SyncSchedulerTests.swift */; };
@@ -38,6 +39,7 @@
3839
2B4EC97C9498E37E6B2F4292 /* LabelBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A14F077027BF8262193AEE /* LabelBadge.swift */; };
3940
2BAB3B616AC9A95A8CC6D3B1 /* AuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4352F68CBFA8610EEC9EE57A /* AuthenticationServiceTests.swift */; };
4041
2BE7A2F079F329C4788390D0 /* OpenCardIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 411F394EE3281672E6FE03F0 /* OpenCardIntent.swift */; };
42+
2D81344AFBABA9207DD1FE8A /* BoardSnapshotWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D307A6D53840FA371B501FB /* BoardSnapshotWriter.swift */; };
4143
2E664F61738B26C3CC9976E1 /* CardCommentsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AF7FB57A60C6677FFC0860 /* CardCommentsViewModel.swift */; };
4244
2F13B71B01BE8EB33100F976 /* FizzyDTOs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A25B0D029F52671883DC255 /* FizzyDTOs.swift */; };
4345
2F25D210D868732361712F37 /* FizzyCardPairingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08BBF84F05A4640E3E49E5F7 /* FizzyCardPairingStore.swift */; };
@@ -52,12 +54,14 @@
5254
36D41A6DA41AFF8E4C7E6EAB /* SyncTriggering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5058A5BADA626E6BFD2B773B /* SyncTriggering.swift */; };
5355
37D4092F6735945F67D6AD4E /* FizzyAuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BDB1F0B68F0499ABCF92B6D /* FizzyAuthState.swift */; };
5456
38B76F596C9ADE228B1DBFAE /* BackupExporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E681D4F4ECB3F364C0C422F0 /* BackupExporterTests.swift */; };
57+
3D4C3E2E8F8F59CBAFC56B8C /* AddCardIntentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8201DB6CCE759773490DEC3D /* AddCardIntentTests.swift */; };
5558
3D5303B80E2BFC55CBE3D81F /* OpenBoardIntentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EA1CAC7BDDA773E2C3A583 /* OpenBoardIntentTests.swift */; };
5659
3E2D89F9EEBD7CA3CB01AFE2 /* FizzyDTOs+Comments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCF09CEDCB117837C6930E7 /* FizzyDTOs+Comments.swift */; };
5760
41945B9C390740A8D93C3ADF /* NotificationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AC2296425D56EBD2D865F6 /* NotificationServiceTests.swift */; };
5861
41D59D4CD494CE3FF0C722AE /* CardAssignee.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5CC12ADA6D06949A786EDC /* CardAssignee.swift */; };
5962
42100AF5E2377B639D8C7043 /* FizzyClientBoardsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A57D39B2CCA5985144EF4A /* FizzyClientBoardsTests.swift */; };
6063
4262C459E4BA6E8674A9214F /* BoardViewModelLifecycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61AF1066F57E5404E2BF366 /* BoardViewModelLifecycleTests.swift */; };
64+
42B28C6B6DDE0AEE871FD4D4 /* BoardSnapshotWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA05B3C28E71F324E32B74AC /* BoardSnapshotWriterTests.swift */; };
6165
431BFEDC9B5D0E3C958D1FE7 /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2D11BAE72815F0D7F274CD /* AuthView.swift */; };
6266
43CF64EA2977EEE9323333DB /* NavigationModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1E87D54C1B073A40441ED37 /* NavigationModelTests.swift */; };
6367
443FC87C48FE4092546E9C78 /* Board+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5909D4678DDACA354C6FDC36 /* Board+CoreDataClass.swift */; };
@@ -186,6 +190,7 @@
186190
E9E42C62557AFB457356C145 /* FizzySyncProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE7390530D1BAF4F5AA79B68 /* FizzySyncProviderTests.swift */; };
187191
EB1E5ED8E37A566F1B547B65 /* SyncActivityState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F7BB129739333741CBDBEB5 /* SyncActivityState.swift */; };
188192
EB89C1364903EF0A7B4F90E5 /* TipJarStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 754AA8D2F4FF050550CD5DED /* TipJarStore.swift */; };
193+
EBB171C06919AD7897D94A72 /* AddCardIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0FFB074417669C9B6CD3E92 /* AddCardIntent.swift */; };
189194
EC665A4938FC6D90E7BBAEFB /* CardSyncBadgeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA78C9E326E8DBEC86C46CA /* CardSyncBadgeState.swift */; };
190195
EC7C05CF82696BA1D92235FC /* ToggleGoldenIntentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961DC5177F1B1026D80284E7 /* ToggleGoldenIntentTests.swift */; };
191196
EE3AE5B8CCA089F5BAF7595B /* LiveTestEnv.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D689F2729CA0AD77E3AA50 /* LiveTestEnv.swift */; };
@@ -229,7 +234,9 @@
229234
0BDB1F0B68F0499ABCF92B6D /* FizzyAuthState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzyAuthState.swift; sourceTree = "<group>"; };
230235
0BFC2630360516F47B333AA9 /* FenixKanban 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "FenixKanban 3.xcdatamodel"; sourceTree = "<group>"; };
231236
0C407F37B22981DEA9599C6C /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
237+
0D307A6D53840FA371B501FB /* BoardSnapshotWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardSnapshotWriter.swift; sourceTree = "<group>"; };
232238
0E2D11BAE72815F0D7F274CD /* AuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = "<group>"; };
239+
0E39A67DC347EB0CD3589743 /* BoardSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardSnapshot.swift; sourceTree = "<group>"; };
233240
0EB4EFAD01CE12778897E024 /* FizzyAuthStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzyAuthStateTests.swift; sourceTree = "<group>"; };
234241
0FC40B241554124652DF6B03 /* BackgroundRefreshTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundRefreshTests.swift; sourceTree = "<group>"; };
235242
139288734275D3EB49C0F400 /* FizzyAuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzyAuthView.swift; sourceTree = "<group>"; };
@@ -306,6 +313,7 @@
306313
80B2297E0A59161848CA6210 /* FenixKanban 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "FenixKanban 5.xcdatamodel"; sourceTree = "<group>"; };
307314
81D940AFA2D7BAA47055968F /* FizzyClient+Directory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FizzyClient+Directory.swift"; sourceTree = "<group>"; };
308315
81F6E147784B7158A6DF4A4D /* BoardEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardEntity.swift; sourceTree = "<group>"; };
316+
8201DB6CCE759773490DEC3D /* AddCardIntentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCardIntentTests.swift; sourceTree = "<group>"; };
309317
821C842E496F5DE33DC42D4A /* FizzyClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzyClientTests.swift; sourceTree = "<group>"; };
310318
824D0B1348180DDDB6480B5D /* FizzyAuthStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzyAuthStatusView.swift; sourceTree = "<group>"; };
311319
8311753D4E7179A377F3C7DD /* NewColumnSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewColumnSheet.swift; sourceTree = "<group>"; };
@@ -362,6 +370,7 @@
362370
BBD6409104882DAF7C55FF7D /* PreviewPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewPersistence.swift; sourceTree = "<group>"; };
363371
BF01511B907D39483068BC2D /* FizzySyncEngineLifecycleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzySyncEngineLifecycleTests.swift; sourceTree = "<group>"; };
364372
C00AE87F56AE328025E43598 /* CachedComment+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CachedComment+CoreDataClass.swift"; sourceTree = "<group>"; };
373+
C0FFB074417669C9B6CD3E92 /* AddCardIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCardIntent.swift; sourceTree = "<group>"; };
365374
C5675C125FA0A0508D8E6A50 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
366375
C814FD4AE7DCBBA14E94FAA9 /* FizzyResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzyResponse.swift; sourceTree = "<group>"; };
367376
C8266DABF5BF6B426312B202 /* FenixKanbanShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FenixKanbanShortcuts.swift; sourceTree = "<group>"; };
@@ -383,6 +392,7 @@
383392
D26870A7B74A1F2FDA417A7D /* FizzySyncMappingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzySyncMappingTests.swift; sourceTree = "<group>"; };
384393
D613029DDB577CD954C47EEC /* FirstSyncModeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstSyncModeTests.swift; sourceTree = "<group>"; };
385394
D9864DD5AE3F9D3B1B6B2348 /* NavigationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModel.swift; sourceTree = "<group>"; };
395+
DA05B3C28E71F324E32B74AC /* BoardSnapshotWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardSnapshotWriterTests.swift; sourceTree = "<group>"; };
386396
DA08B935AE5F124F922B0769 /* FizzySyncEngineAdoptionResilienceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FizzySyncEngineAdoptionResilienceTests.swift; sourceTree = "<group>"; };
387397
DA5CC12ADA6D06949A786EDC /* CardAssignee.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardAssignee.swift; sourceTree = "<group>"; };
388398
DB041FBA0AE74AA75078C039 /* BoardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardViewModelTests.swift; sourceTree = "<group>"; };
@@ -441,6 +451,7 @@
441451
0B78ECEF227AF1BB1067D6CC /* Intents */ = {
442452
isa = PBXGroup;
443453
children = (
454+
C0FFB074417669C9B6CD3E92 /* AddCardIntent.swift */,
444455
81F6E147784B7158A6DF4A4D /* BoardEntity.swift */,
445456
DF5619875D16CD1B3B74E215 /* BoardQuery.swift */,
446457
E0435462C469D8222A55798D /* CardEntity.swift */,
@@ -558,6 +569,7 @@
558569
2E96249D89F978A03046C06B /* Intents */ = {
559570
isa = PBXGroup;
560571
children = (
572+
8201DB6CCE759773490DEC3D /* AddCardIntentTests.swift */,
561573
4E8E42CB99AC86B2F3EE7E8F /* BoardEntityTests.swift */,
562574
EF898B5807DF1101F6F9DF0C /* CardEntityTests.swift */,
563575
9814F76F5D1AB67F43B0B7CE /* FindGoldenCardsIntentTests.swift */,
@@ -612,6 +624,7 @@
612624
7EFB1A22FB14A9100724309C /* Settings */,
613625
9460B5BC7EB210D2C3DBA37F /* Sync */,
614626
3C4672B05850148A572D74F7 /* TipJar */,
627+
E4276F05748DFEFDE99541C4 /* Widget */,
615628
);
616629
path = Features;
617630
sourceTree = "<group>";
@@ -819,6 +832,7 @@
819832
4B004A08072329F2FB9EF382 /* Backup */,
820833
50FDB3E0608169A23DB40E68 /* Card */,
821834
2E22745F4F482FA3823879D8 /* Sync */,
835+
F656DC9BFC0012A09D6803D7 /* Widget */,
822836
);
823837
path = Features;
824838
sourceTree = "<group>";
@@ -928,6 +942,15 @@
928942
path = Navigation;
929943
sourceTree = "<group>";
930944
};
945+
E4276F05748DFEFDE99541C4 /* Widget */ = {
946+
isa = PBXGroup;
947+
children = (
948+
0E39A67DC347EB0CD3589743 /* BoardSnapshot.swift */,
949+
0D307A6D53840FA371B501FB /* BoardSnapshotWriter.swift */,
950+
);
951+
path = Widget;
952+
sourceTree = "<group>";
953+
};
931954
ED92A51CF99AD7208A720C96 /* Models */ = {
932955
isa = PBXGroup;
933956
children = (
@@ -960,6 +983,14 @@
960983
path = Extensions;
961984
sourceTree = "<group>";
962985
};
986+
F656DC9BFC0012A09D6803D7 /* Widget */ = {
987+
isa = PBXGroup;
988+
children = (
989+
DA05B3C28E71F324E32B74AC /* BoardSnapshotWriterTests.swift */,
990+
);
991+
path = Widget;
992+
sourceTree = "<group>";
993+
};
963994
FB5B8CAA5DFA52827B913145 /* UI */ = {
964995
isa = PBXGroup;
965996
children = (
@@ -1132,6 +1163,7 @@
11321163
isa = PBXSourcesBuildPhase;
11331164
buildActionMask = 2147483647;
11341165
files = (
1166+
EBB171C06919AD7897D94A72 /* AddCardIntent.swift in Sources */,
11351167
DCA918D48BE6C991C0F9B1A1 /* AppearanceMode.swift in Sources */,
11361168
E28CEDF04F9DC799CB8AA774 /* AssigneePickerView.swift in Sources */,
11371169
431BFEDC9B5D0E3C958D1FE7 /* AuthView.swift in Sources */,
@@ -1149,6 +1181,8 @@
11491181
C2902035A969FF5D17E7BAA0 /* BoardQuery.swift in Sources */,
11501182
ACDC1A0770CC952A0A5FD5F1 /* BoardRepository.swift in Sources */,
11511183
B93AABC8F465D0D71FB85737 /* BoardRowView.swift in Sources */,
1184+
19DA249C4CDCFEBA413C42FF /* BoardSnapshot.swift in Sources */,
1185+
2D81344AFBABA9207DD1FE8A /* BoardSnapshotWriter.swift in Sources */,
11521186
77C47FCFD6828F64E05B55C9 /* BoardSyncProvider.swift in Sources */,
11531187
4EE391539AF6711C5060C0E7 /* BoardView.swift in Sources */,
11541188
C7DC1F515DF0710E60B122EE /* BoardViewModel.swift in Sources */,
@@ -1249,6 +1283,7 @@
12491283
isa = PBXSourcesBuildPhase;
12501284
buildActionMask = 2147483647;
12511285
files = (
1286+
3D4C3E2E8F8F59CBAFC56B8C /* AddCardIntentTests.swift in Sources */,
12521287
A30A97626FA931BBFCD62BA3 /* AppearanceModeTests.swift in Sources */,
12531288
2BAB3B616AC9A95A8CC6D3B1 /* AuthenticationServiceTests.swift in Sources */,
12541289
FA69352B11E2800440F5C0CA /* BackgroundRefreshTests.swift in Sources */,
@@ -1257,6 +1292,7 @@
12571292
12851253CACE6154745B79E3 /* BoardEntityTests.swift in Sources */,
12581293
FDE100082D4EC59095D416DD /* BoardListViewModelTests.swift in Sources */,
12591294
6821052820164D6848537418 /* BoardRepositoryTests.swift in Sources */,
1295+
42B28C6B6DDE0AEE871FD4D4 /* BoardSnapshotWriterTests.swift in Sources */,
12601296
1FC7C8014EFC4F2A11140203 /* BoardViewModelGoldenTests.swift in Sources */,
12611297
4262C459E4BA6E8674A9214F /* BoardViewModelLifecycleTests.swift in Sources */,
12621298
1A8EDCFA9E34232FEF82D754 /* BoardViewModelTests.swift in Sources */,
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// FenixKanban/Features/Intents/AddCardIntent.swift
2+
import AppIntents
3+
import CoreData
4+
5+
struct AddCardIntent: AppIntent {
6+
static var title: LocalizedStringResource = "Add a Card"
7+
static var description = IntentDescription(
8+
"Creates a new card in FenixKanban with the given title, placed in the specified board and column."
9+
)
10+
static var openAppWhenRun: Bool = false
11+
12+
@Parameter(title: "Title") var title: String
13+
@Parameter(title: "Board") var board: BoardEntity
14+
@Parameter(
15+
title: "Column",
16+
description: "Name of the column to add the card to. Defaults to the first column.",
17+
default: nil
18+
)
19+
var columnName: String?
20+
21+
// Test seam — injected by tests, resolved via @Dependency at runtime.
22+
var contextOverride: NSManagedObjectContext?
23+
24+
@Dependency private var context: NSManagedObjectContext
25+
26+
private var resolvedContext: NSManagedObjectContext {
27+
contextOverride ?? context
28+
}
29+
30+
mutating func _injectDependencies(context: NSManagedObjectContext) {
31+
self.contextOverride = context
32+
}
33+
34+
@MainActor
35+
func perform() async throws -> some IntentResult & ProvidesDialog & ReturnsValue<CardEntity> {
36+
guard !title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
37+
throw $title.needsValueError("Please provide a title for the card.")
38+
}
39+
40+
let boardID = board.id
41+
let targetColumnName = columnName
42+
let ctx = resolvedContext
43+
44+
let newCard: Card = try await ctx.perform {
45+
// Resolve the board
46+
let boardRequest = Board.fetchRequest()
47+
boardRequest.predicate = NSPredicate(format: "id == %@", boardID as CVarArg)
48+
boardRequest.fetchLimit = 1
49+
guard let foundBoard = try ctx.fetch(boardRequest).first else {
50+
throw $board.needsValueError("That board no longer exists.")
51+
}
52+
53+
// Resolve the target column: named match first, fall back to first by sortOrder
54+
let columns = foundBoard.sortedColumns
55+
guard !columns.isEmpty else {
56+
throw $board.needsValueError("That board has no columns.")
57+
}
58+
59+
let targetColumn: Column
60+
if let name = targetColumnName,
61+
let match = columns.first(where: { $0.name?.caseInsensitiveCompare(name) == .orderedSame }) {
62+
targetColumn = match
63+
} else {
64+
targetColumn = columns[0]
65+
}
66+
67+
// Create the card
68+
let card = Card(context: ctx)
69+
card.id = UUID()
70+
card.title = title.trimmingCharacters(in: .whitespacesAndNewlines)
71+
card.createdAt = Date()
72+
card.modifiedAt = Date()
73+
card.isCompleted = false
74+
card.column = targetColumn
75+
76+
let maxSort = targetColumn.sortedCards.last?.sortOrder ?? -1000
77+
card.sortOrder = maxSort + 1000
78+
79+
let now = Date()
80+
targetColumn.modifiedAt = now
81+
targetColumn.board?.modifiedAt = now
82+
83+
try ctx.save()
84+
return card
85+
}
86+
87+
let entity = try CardEntity(from: newCard)
88+
let dialog = IntentDialog("Added \"\(entity.title)\" to \(board.name).")
89+
return .result(value: entity, dialog: dialog)
90+
}
91+
}

FenixKanban/Features/Intents/FenixKanbanShortcuts.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import AppIntents
22

33
struct FenixKanbanShortcuts: AppShortcutsProvider {
44
static var appShortcuts: [AppShortcut] {
5+
AppShortcut(
6+
intent: AddCardIntent(),
7+
phrases: [
8+
"Add a card to \(.applicationName)",
9+
"Create a card in \(.applicationName)",
10+
"New card in \(.applicationName)"
11+
],
12+
shortTitle: "Add Card",
13+
systemImageName: "plus.rectangle"
14+
)
515
AppShortcut(
616
intent: OpenBoardIntent(),
717
phrases: [

FenixKanban/Features/Sync/Fizzy/FizzySyncProvider.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ final class FizzySyncProvider: BoardSyncProvider, SyncTriggering {
102102
throw FizzyError.requiresInteractiveAuth
103103
}
104104
let result = try await engine.sync()
105+
106+
// Publish a fresh widget snapshot after every successful sync so
107+
// the PlaygroundBoardWidget always reflects the latest board state.
108+
// Failures are silent — a stale snapshot is better than a crash.
109+
let writer = BoardSnapshotWriter(context: persistence.viewContext)
110+
_ = try? writer.writeSnapshot()
111+
105112
return SyncResult(
106113
itemsCreated: result.itemsCreated,
107114
itemsUpdated: result.itemsUpdated,

0 commit comments

Comments
 (0)