Commit 91f4fc6315
Unsigned
Layout: unified · split
Hutch.xcodeproj/project.pbxproj +12 −12
| @@ -597,7 +597,7 @@ | ||
| 597 | 597 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; |
| 598 | 598 | CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements; |
| 599 | 599 | CODE_SIGN_STYLE = Automatic; |
| 600 | CURRENT_PROJECT_VERSION = 97; | |
| 600 | CURRENT_PROJECT_VERSION = 98; | |
| 601 | 601 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 602 | 602 | ENABLE_PREVIEWS = YES; |
| 603 | 603 | GENERATE_INFOPLIST_FILE = YES; |
| @@ -614,7 +614,7 @@ | ||
| 614 | 614 | "$(inherited)", |
| 615 | 615 | "@executable_path/Frameworks", |
| 616 | 616 | ); |
| 617 | MARKETING_VERSION = 3.11.1; | |
| 617 | MARKETING_VERSION = 3.12.0; | |
| 618 | 618 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 619 | 619 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 620 | 620 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -634,7 +634,7 @@ | ||
| 634 | 634 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; |
| 635 | 635 | CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements; |
| 636 | 636 | CODE_SIGN_STYLE = Automatic; |
| 637 | CURRENT_PROJECT_VERSION = 97; | |
| 637 | CURRENT_PROJECT_VERSION = 98; | |
| 638 | 638 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 639 | 639 | ENABLE_PREVIEWS = YES; |
| 640 | 640 | GENERATE_INFOPLIST_FILE = YES; |
| @@ -651,7 +651,7 @@ | ||
| 651 | 651 | "$(inherited)", |
| 652 | 652 | "@executable_path/Frameworks", |
| 653 | 653 | ); |
| 654 | MARKETING_VERSION = 3.11.1; | |
| 654 | MARKETING_VERSION = 3.12.0; | |
| 655 | 655 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; |
| 656 | 656 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 657 | 657 | STRING_CATALOG_GENERATE_SYMBOLS = YES; |
| @@ -714,7 +714,7 @@ | ||
| 714 | 714 | APPLICATION_EXTENSION_API_ONLY = YES; |
| 715 | 715 | CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; |
| 716 | 716 | CODE_SIGN_STYLE = Automatic; |
| 717 | CURRENT_PROJECT_VERSION = 97; | |
| 717 | CURRENT_PROJECT_VERSION = 98; | |
| 718 | 718 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 719 | 719 | GENERATE_INFOPLIST_FILE = NO; |
| 720 | 720 | INFOPLIST_FILE = HutchWidgetExtension/Info.plist; |
| @@ -724,7 +724,7 @@ | ||
| 724 | 724 | "@executable_path/Frameworks", |
| 725 | 725 | "@executable_path/../../Frameworks", |
| 726 | 726 | ); |
| 727 | MARKETING_VERSION = 3.11.1; | |
| 727 | MARKETING_VERSION = 3.12.0; | |
| 728 | 728 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; |
| 729 | 729 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 730 | 730 | SKIP_INSTALL = YES; |
| @@ -743,7 +743,7 @@ | ||
| 743 | 743 | APPLICATION_EXTENSION_API_ONLY = YES; |
| 744 | 744 | CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; |
| 745 | 745 | CODE_SIGN_STYLE = Automatic; |
| 746 | CURRENT_PROJECT_VERSION = 97; | |
| 746 | CURRENT_PROJECT_VERSION = 98; | |
| 747 | 747 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 748 | 748 | GENERATE_INFOPLIST_FILE = NO; |
| 749 | 749 | INFOPLIST_FILE = HutchWidgetExtension/Info.plist; |
| @@ -753,7 +753,7 @@ | ||
| 753 | 753 | "@executable_path/Frameworks", |
| 754 | 754 | "@executable_path/../../Frameworks", |
| 755 | 755 | ); |
| 756 | MARKETING_VERSION = 3.11.1; | |
| 756 | MARKETING_VERSION = 3.12.0; | |
| 757 | 757 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; |
| 758 | 758 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 759 | 759 | SKIP_INSTALL = YES; |
| @@ -772,7 +772,7 @@ | ||
| 772 | 772 | APPLICATION_EXTENSION_API_ONLY = YES; |
| 773 | 773 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
| 774 | 774 | CODE_SIGN_STYLE = Automatic; |
| 775 | CURRENT_PROJECT_VERSION = 97; | |
| 775 | CURRENT_PROJECT_VERSION = 98; | |
| 776 | 776 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 777 | 777 | GENERATE_INFOPLIST_FILE = NO; |
| 778 | 778 | INFOPLIST_FILE = HutchSafariExtension/Info.plist; |
| @@ -782,7 +782,7 @@ | ||
| 782 | 782 | "@executable_path/Frameworks", |
| 783 | 783 | "@executable_path/../../Frameworks", |
| 784 | 784 | ); |
| 785 | MARKETING_VERSION = 3.11.1; | |
| 785 | MARKETING_VERSION = 3.12.0; | |
| 786 | 786 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension; |
| 787 | 787 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 788 | 788 | SKIP_INSTALL = YES; |
| @@ -801,7 +801,7 @@ | ||
| 801 | 801 | APPLICATION_EXTENSION_API_ONLY = YES; |
| 802 | 802 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
| 803 | 803 | CODE_SIGN_STYLE = Automatic; |
| 804 | CURRENT_PROJECT_VERSION = 97; | |
| 804 | CURRENT_PROJECT_VERSION = 98; | |
| 805 | 805 | DEVELOPMENT_TEAM = ZCNAX3VL9D; |
| 806 | 806 | GENERATE_INFOPLIST_FILE = NO; |
| 807 | 807 | INFOPLIST_FILE = HutchSafariExtension/Info.plist; |
| @@ -811,7 +811,7 @@ | ||
| 811 | 811 | "@executable_path/Frameworks", |
| 812 | 812 | "@executable_path/../../Frameworks", |
| 813 | 813 | ); |
| 814 | MARKETING_VERSION = 3.11.1; | |
| 814 | MARKETING_VERSION = 3.12.0; | |
| 815 | 815 | PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension; |
| 816 | 816 | PRODUCT_NAME = "$(TARGET_NAME)"; |
| 817 | 817 | SKIP_INSTALL = YES; |
Hutch/Extensions/ErrorViews.swift +1
| @@ -37,6 +37,7 @@ struct SRHTErrorBanner: ViewModifier { | ||
| 37 | 37 | Image(systemName: "xmark.circle.fill") |
| 38 | 38 | .foregroundStyle(.white.opacity(0.8)) |
| 39 | 39 | } |
| 40 | .accessibilityLabel("Dismiss error") | |
| 40 | 41 | } |
| 41 | 42 | .padding(12) |
| 42 | 43 | .background(Color.red.gradient, in: RoundedRectangle(cornerRadius: 12)) |
Hutch/Views/Builds/BuildTaskLogView.swift +2
| @@ -164,6 +164,7 @@ private struct BuildTaskLogContentView: View { | ||
| 164 | 164 | Image(systemName: "chevron.up") |
| 165 | 165 | } |
| 166 | 166 | .disabled(matches.isEmpty) |
| 167 | .accessibilityLabel("Previous match") | |
| 167 | 168 | |
| 168 | 169 | Button { |
| 169 | 170 | moveSelection(step: 1) |
| @@ -171,6 +172,7 @@ private struct BuildTaskLogContentView: View { | ||
| 171 | 172 | Image(systemName: "chevron.down") |
| 172 | 173 | } |
| 173 | 174 | .disabled(matches.isEmpty) |
| 175 | .accessibilityLabel("Next match") | |
| 174 | 176 | } |
| 175 | 177 | .buttonStyle(.borderless) |
| 176 | 178 | .labelStyle(.iconOnly) |
Hutch/Views/Inbox/ThreadDetailView.swift +1
| @@ -295,6 +295,7 @@ private struct InboxMessageRow: View { | ||
| 295 | 295 | } |
| 296 | 296 | } |
| 297 | 297 | .buttonStyle(.plain) |
| 298 | .accessibilityHint(isCollapsed ? "Expands this message" : "Collapses this message") | |
| 298 | 299 | |
| 299 | 300 | if !isCollapsed { |
| 300 | 301 | ForEach(Array(message.contentBlocks.enumerated()), id: \.offset) { _, block in |
Hutch/Views/More/MoreView.swift +1
| @@ -96,6 +96,7 @@ struct MoreView: View { | ||
| 96 | 96 | } label: { |
| 97 | 97 | Image(systemName: "person.crop.circle.badge.plus") |
| 98 | 98 | } |
| 99 | .accessibilityLabel("Switch account") | |
| 99 | 100 | } |
| 100 | 101 | } |
| 101 | 102 | .sheet(isPresented: $showAccountSwitcher) { |
Hutch/Views/Pastes/PasteDetailView.swift +1
| @@ -413,6 +413,7 @@ private struct PasteVisibilitySheet: View { | ||
| 413 | 413 | .contentShape(Rectangle()) |
| 414 | 414 | } |
| 415 | 415 | .buttonStyle(.plain) |
| 416 | .accessibilityAddTraits(visibility == option ? [.isSelected] : []) | |
| 416 | 417 | } |
| 417 | 418 | .themedRow() |
| 418 | 419 | } |
Hutch/Views/Pastes/PasteListView.swift +1
| @@ -25,6 +25,7 @@ struct PasteListView: View { | ||
| 25 | 25 | } label: { |
| 26 | 26 | Image(systemName: "plus") |
| 27 | 27 | } |
| 28 | .accessibilityLabel("Create paste") | |
| 28 | 29 | } |
| 29 | 30 | } |
| 30 | 31 | } |
Hutch/Views/Projects/ProjectDetailView.swift +1
| @@ -217,6 +217,7 @@ struct ProjectDetailView: View { | ||
| 217 | 217 | } |
| 218 | 218 | } |
| 219 | 219 | .buttonStyle(.plain) |
| 220 | .accessibilityHint("Opens in your browser") | |
| 220 | 221 | } |
| 221 | 222 | .themedRow() |
| 222 | 223 | } |
Hutch/Views/Repositories/ArtifactsView.swift +1
| @@ -206,6 +206,7 @@ private struct ArtifactRow: View { | ||
| 206 | 206 | Image(systemName: "arrow.down.circle") |
| 207 | 207 | .imageScale(.large) |
| 208 | 208 | } |
| 209 | .accessibilityLabel("Download artifact") | |
| 209 | 210 | } |
| 210 | 211 | } |
| 211 | 212 | } |
Hutch/Views/Repositories/CommitDetailView.swift +1
| @@ -153,6 +153,7 @@ struct CommitDetailView: View { | ||
| 153 | 153 | } |
| 154 | 154 | .foregroundStyle(.secondary) |
| 155 | 155 | } |
| 156 | .accessibilityHint("Copies the commit SHA") | |
| 156 | 157 | |
| 157 | 158 | // Author |
| 158 | 159 | HStack { |
Hutch/Views/Repositories/DiffView.swift +2
| @@ -58,6 +58,7 @@ private struct DiffFileSectionView: View { | ||
| 58 | 58 | .contentShape(Rectangle()) |
| 59 | 59 | } |
| 60 | 60 | .buttonStyle(.plain) |
| 61 | .accessibilityHint(isExpanded ? "Collapses this file" : "Expands this file") | |
| 61 | 62 | .background(Color(.tertiarySystemBackground)) |
| 62 | 63 | |
| 63 | 64 | if isExpanded { |
| @@ -169,6 +170,7 @@ private struct DiffHunkView: View { | ||
| 169 | 170 | .contentShape(Rectangle()) |
| 170 | 171 | } |
| 171 | 172 | .buttonStyle(.plain) |
| 173 | .accessibilityHint(isExpanded ? "Collapses this hunk" : "Expands this hunk") | |
| 172 | 174 | .background(Color(.systemBackground).opacity(0.5)) |
| 173 | 175 | |
| 174 | 176 | if isExpanded { |
Hutch/Views/Tickets/TicketDetailView.swift +3
| @@ -606,6 +606,7 @@ private struct EventRow: View { | ||
| 606 | 606 | .foregroundStyle(.gray) |
| 607 | 607 | } |
| 608 | 608 | .buttonStyle(.plain) |
| 609 | .accessibilityLabel("About this status change") | |
| 609 | 610 | } |
| 610 | 611 | } else { |
| 611 | 612 | Text(descriptionText) |
| @@ -948,6 +949,7 @@ private struct AssignSheet: View { | ||
| 948 | 949 | .foregroundStyle(.red) |
| 949 | 950 | } |
| 950 | 951 | .buttonStyle(.plain) |
| 952 | .accessibilityLabel("Unassign \(assignee.canonicalName)") | |
| 951 | 953 | } |
| 952 | 954 | } |
| 953 | 955 | .themedRow() |
| @@ -1152,5 +1154,6 @@ private struct LabelToggleRow: View { | ||
| 1152 | 1154 | } |
| 1153 | 1155 | } |
| 1154 | 1156 | .disabled(isLoading) |
| 1157 | .accessibilityAddTraits(isApplied ? [.isSelected] : []) | |
| 1155 | 1158 | } |
| 1156 | 1159 | } |
Hutch/Views/Tickets/TicketListView.swift +7
| @@ -857,6 +857,9 @@ private struct TicketQuickFilterBar: View { | ||
| 857 | 857 | in: Capsule() |
| 858 | 858 | ) |
| 859 | 859 | } |
| 860 | .accessibilityAddTraits( | |
| 861 | activeSavedFilterID == savedFilter.id ? [.isSelected] : [] | |
| 862 | ) | |
| 860 | 863 | .contextMenu { |
| 861 | 864 | Button(role: .destructive) { |
| 862 | 865 | onDeleteSavedFilter(savedFilter) |
| @@ -964,6 +967,9 @@ private struct TicketFilterLabelsSheet: View { | ||
| 964 | 967 | } |
| 965 | 968 | } |
| 966 | 969 | .buttonStyle(.plain) |
| 970 | .accessibilityAddTraits( | |
| 971 | viewModel.selectedLabelIDs.contains(label.id) ? [.isSelected] : [] | |
| 972 | ) | |
| 967 | 973 | } |
| 968 | 974 | .themedRow() |
| 969 | 975 | } |
| @@ -1051,6 +1057,7 @@ private struct TicketListLabelToggleRow: View { | ||
| 1051 | 1057 | } |
| 1052 | 1058 | } |
| 1053 | 1059 | .disabled(isLoading) |
| 1060 | .accessibilityAddTraits(isApplied ? [.isSelected] : []) | |
| 1054 | 1061 | } |
| 1055 | 1062 | } |
| 1056 | 1063 | |
Hutch/Views/Tickets/TrackerListView.swift +1
| @@ -25,6 +25,7 @@ struct TrackerListView: View { | ||
| 25 | 25 | } label: { |
| 26 | 26 | Image(systemName: "plus") |
| 27 | 27 | } |
| 28 | .accessibilityLabel("Create tracker") | |
| 28 | 29 | } |
| 29 | 30 | } |
| 30 | 31 | } |
Hutch/Views/Tickets/TrackerManagementView.swift +2
| @@ -785,6 +785,7 @@ struct TrackerACLManagementSheet: View { | ||
| 785 | 785 | Image(systemName: "plus") |
| 786 | 786 | } |
| 787 | 787 | .disabled(viewModel.isSavingACL) |
| 788 | .accessibilityLabel("Add ACL") | |
| 788 | 789 | } |
| 789 | 790 | } |
| 790 | 791 | .task { |
| @@ -1155,6 +1156,7 @@ struct TrackerLabelManagementSheet: View { | ||
| 1155 | 1156 | Image(systemName: "plus") |
| 1156 | 1157 | } |
| 1157 | 1158 | .disabled(viewModel.isSavingLabel) |
| 1159 | .accessibilityLabel("Create label") | |
| 1158 | 1160 | } |
| 1159 | 1161 | } |
| 1160 | 1162 | .task { |
ROADMAP.txt +31 −6
| @@ -156,7 +156,7 @@ so "breaking change" does not apply. These buckets track *user-visible scale*. | ||
| 156 | 156 | | v3.9.0 | ~~hub.sr.ht project writes + discovery (#12–#15); multi-language highlighting (#16); App Intents expansion (#17); man-page catalog sync (#7); checklist / recent-activity / pull-to-refresh fixes (#18, #11, #9)~~ | Shipped — the cut this session | |
| 157 | 157 | | v3.10.0 | ~~git.sr.ht deploy keys~~ (shipped); ~~"What's cooking" ingest + doc truth-up~~ (done) | Ships one feature, corrects the map | |
| 158 | 158 | | v3.11.0 | ~~Mailing list subscribe/unsubscribe toggle~~ (shipped) | Ingest-surfaced; state via the `subscriptions` query (the `subscription` field is a trap) | |
| 159 | | v3.12.0 | Accessibility | Independent, device-verified | | |
| 159 | | v3.12.0 | ~~Accessibility~~ (done in code) | Still wants a VoiceOver pass on a device | | |
| 160 | 160 | | v4.0.0 | Localization *with* translations | The only true re-presentation | |
| 161 | 161 | | — | ~~Swift 6 language mode~~ (done); cache reads | Internal; ride along, no tag | |
| 162 | 162 | |
| @@ -206,11 +206,36 @@ the major number on regression risk while delivering nothing — the wrong trade | ||
| 206 | 206 | Hold the catalog until a translation lands. If it ever ships unbundled, it is |
| 207 | 207 | groundwork and belongs in a quiet minor, not a 4.0. |
| 208 | 208 | |
| 209 | ### Accessibility — v3.11.0 | |
| 210 | ||
| 211 | Labels and hints appear in 17 of 89 view files. Mechanical and low-risk, but it | |
| 212 | cannot be verified from a build — it needs VoiceOver driven on a device. | |
| 213 | Independent of every other bucket, so it can move if a device pass is convenient. | |
| 209 | ### Accessibility — done in code (v3.12.0) | |
| 210 | ||
| 211 | The earlier count here (17 of 89 view files) was wrong on both numbers: 22 of 95 | |
| 212 | carried a modifier, and of those, labels were the whole story — one hint in the | |
| 213 | whole app, no traits at all. Counting files also hid the shape of the gap, which | |
| 214 | was not spread evenly but concentrated in three kinds of control: | |
| 215 | ||
| 216 | - **Icon-only controls**, which VoiceOver reaches with nothing to announce. | |
| 217 | Eleven of them: the error banner's dismiss, log search next/previous match, | |
| 218 | account switching, four `plus` creates (paste, tracker, ACL, label), artifact | |
| 219 | download, the system-status info button, and assignee unassign. | |
| 220 | - **Selection state carried only by a checkmark or a tint.** Five: applied | |
| 221 | ticket labels in two views, the label filter, the saved-filter chip, and the | |
| 222 | paste visibility picker. These now carry `.isSelected` rather than relying on | |
| 223 | an icon VoiceOver does not read. | |
| 224 | - **Disclosure state carried only by a chevron.** Three: diff file, diff hunk, | |
| 225 | and inbox message. `PatchsetDetailView` already had the hint; the others now | |
| 226 | match it. | |
| 227 | ||
| 228 | Two more: the project external-link row says it leaves the app, and the commit | |
| 229 | SHA button says it copies — both read as bare text before. | |
| 230 | ||
| 231 | Decorative chevrons inside a control that already carries text were left alone. | |
| 232 | An unlabelled SF Symbol contributes nothing to a combined label, so hiding them | |
| 233 | would be churn with no announced difference. | |
| 234 | ||
| 235 | Still open: **this is not device-verified.** It builds and the suite is green, | |
| 236 | but neither proves a VoiceOver pass. What is verified is that no icon-only | |
| 237 | control is left unlabelled — a static check over the enclosing control's brace | |
| 238 | span, not a guess at line proximity. | |
| 214 | 239 | |
| 215 | 240 | ### SonarCloud backlog — done in code (v3.8.1) |
| 216 | 241 | |