feat: bundle ID migration + gitea#2 task-cache fix (recovered from fix/task-cache-unification) #4

Merged
admin merged 13 commits from feat/bundle-id-and-task-cache into master 2026-05-01 20:48:29 -05:00
5 changed files with 35 additions and 0 deletions
Showing only changes of commit 87771ef7f3 - Show all commits
@@ -48,6 +48,9 @@ struct AccessibilityIdentifiers {
static let addButton = "Residence.AddButton"
static let residencesList = "Residence.List"
static let residenceCard = "Residence.Card"
/// Prefix for individual residence cells in the list. Suffix with the
/// residence id to address a specific cell (e.g. "Residence.Cell.42").
static let cellPrefix = "Residence.Cell"
static let emptyStateView = "Residence.EmptyState"
static let emptyStateButton = "Residence.EmptyState.AddButton"
@@ -87,7 +90,15 @@ struct AccessibilityIdentifiers {
static let refreshButton = "Task.RefreshButton"
static let tasksList = "Task.List"
static let taskCard = "Task.Card"
/// Prefix for individual task rows. Suffix with the task id to
/// address a specific row (e.g. "Task.Row.42"). Use `BEGINSWITH`
/// in tests to detect "any task row exists".
static let rowPrefix = "Task.Row"
static let emptyStateView = "Task.EmptyState"
/// Label rendered when a residence-detail tasks section has no tasks
/// in any kanban column. Asserted ABSENT after onboarding bulk-create
/// in the gitea#2 regression test.
static let noTasksLabel = "Task.NoTasksLabel"
static let kanbanView = "Task.KanbanView"
static let overdueColumn = "Task.Column.Overdue"
static let upcomingColumn = "Task.Column.Upcoming"
@@ -229,8 +240,24 @@ struct AccessibilityIdentifiers {
static let taskSelectionCounter = "Onboarding.TaskSelectionCounter"
static let addPopularTasksButton = "Onboarding.AddPopularTasksButton"
static let addTasksContinueButton = "Onboarding.AddTasksContinueButton"
/// Submit/continue button at the bottom of the First-Task screen.
/// Triggers `POST /api/tasks/bulk/` for the selected templates.
static let submitTasksButton = "Onboarding.SubmitTasksButton"
/// Tab bar control above the task list. The "Browse All" segment is
/// addressed via `app.buttons["Browse All"]` from the segmented
/// picker once this identifier is set.
static let firstTaskTabBar = "Onboarding.FirstTaskTabBar"
/// Tab segment that shows the full template catalog.
/// Tap from a test by addressing the Picker's segment label
/// "Browse All" within the element identified above.
static let browseAllTab = "Onboarding.BrowseAllTab"
static let taskCategorySection = "Onboarding.TaskCategorySection"
static let taskTemplateRow = "Onboarding.TaskTemplateRow"
/// Prefix for individual template rows on the First-Task screen
/// (Browse All tab). Suffix with the backend template id
/// e.g. `"Onboarding.TemplateRow.123"`. Tests use `BEGINSWITH` to
/// pick the first N rows deterministically without knowing ids.
static let templateRowPrefix = "Onboarding.TemplateRow"
// Subscription Screen
static let subscriptionTitle = "Onboarding.SubscriptionTitle"
@@ -158,6 +158,7 @@ struct OnboardingFirstTaskContent: View {
OnboardingTaskTabBar(selectedTab: $selectedTab)
.padding(.horizontal, OrganicSpacing.comfortable)
.accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.firstTaskTabBar)
switch selectedTab {
case .forYou:
@@ -384,6 +385,7 @@ struct OnboardingFirstTaskContent: View {
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.naturalShadow(selectedCount > 0 ? .medium : .subtle)
}
.accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.submitTasksButton)
.disabled(vm.isSubmitting)
.animation(.easeInOut(duration: 0.2), value: selectedCount)
}
@@ -653,6 +655,7 @@ private struct OnboardingSuggestionRow: View {
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityIdentifier("\(AccessibilityIdentifiers.Onboarding.templateRowPrefix).\(suggestion.template.id)")
.accessibilityLabel("\(suggestion.template.title), \(suggestion.template.frequencyDisplay), \(relevancePercent)% match")
.accessibilityValue(isSelected ? "selected" : "not selected")
}
@@ -798,6 +801,7 @@ private struct OnboardingTemplateRow: View {
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityIdentifier("\(AccessibilityIdentifiers.Onboarding.templateRowPrefix).\(template.id)")
.accessibilityLabel("\(template.title), \(template.frequencyLabel)")
.accessibilityValue(isSelected ? "selected" : "not selected")
}
@@ -231,6 +231,7 @@ private struct ResidencesContent: View {
.padding(.horizontal, 16)
}
.buttonStyle(OrganicCardButtonStyle())
.accessibilityIdentifier("\(AccessibilityIdentifiers.Residence.cellPrefix).\(residence.id)")
.transition(.asymmetric(
insertion: .opacity.combined(with: .move(edge: .bottom)),
removal: .opacity
@@ -122,6 +122,7 @@ struct DynamicTaskCard: View {
.cornerRadius(12)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
.simultaneousGesture(TapGesture(), including: .subviews)
.accessibilityIdentifier("\(AccessibilityIdentifiers.Task.rowPrefix).\(task.id)")
.sheet(isPresented: $showCompletionHistory) {
CompletionHistorySheet(
taskTitle: task.title,
@@ -22,6 +22,8 @@ struct EmptyTasksView: View {
.background(Color.appBackgroundSecondary)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.naturalShadow(.subtle)
.accessibilityElement(children: .combine)
.accessibilityIdentifier(AccessibilityIdentifiers.Task.noTasksLabel)
}
}