Skip to content

Replace ProminentButtonModifier with standard button rows and styles - #554

Closed
bradleymackey wants to merge 2 commits into
mainfrom
native-standardization-buttons
Closed

bradleymackey wants to merge 2 commits into
mainfrom
native-standardization-buttons

Conversation

@bradleymackey

Copy link
Copy Markdown
Member

Applies the button convention from #551 to the screens outside
Views/Backup, and deletes the modifier that made the old treatment
possible.

The problem

ProminentButtonModifier reimplemented .borderedProminent by hand:
.font(.headline) + a forced .foregroundStyle(.white) + padding(16/12)

  • .background(color) + RoundedRectangle(cornerRadius: 12). It applied
    .buttonStyle(.borderless) inside its own body, so the wrapped button
    never received a pressed state, and the hardcoded white label had no
    contrast guarantee against the tint it was placed on.

Six of its nine call sites also placed the button inside a Section
footer as a padded, centred pill — the exact pattern #551 called out and
removed from the backup screens.

What changed

Screen Change
SettingsDangerView "Delete All Data" becomes a red row in its own section; the error message becomes that section's footer
VaultTagDetailView "Delete Tag" becomes a red row
VaultItemDetailView "Unlock" and "Dismiss" move out of section footers into their own rows
EncryptedItemDetailView "Decrypt" moves out of the footer into its own section
VaultDetailEncryptionEditView "Encrypt" and "Remove Encryption" become plain rows
OTPCodeDetailView "Delete" moves out of the editing-actions footer into its own red section
SecureNoteDetailView "Delete" likewise
VaultAutofillConfigurationView "Continue" is a genuine standalone CTA outside any Form, so it becomes .buttonStyle(.borderedProminent) + .controlSize(.large)

Destructive emphasis is preserved the way #551 preserved it: a red
FormRow glyph tile plus red row text, matching BackupRestoreView's
Import & Override. Primary actions use the accent colour the same way
BackupKeyDecryptorView does.

The ProgressView in each loading: branch loses its .tint(.white),
which only existed because the spinner sat on a filled pill.

Toolbar Cancel buttons keep their explicit .foregroundStyle(.red).
Button(role: .cancel) does not render red in a toolbar, so dropping the
tint would have been a visual regression rather than a standardization.

Liquid Glass does not survive snapshot rendering

"Continue" was first written as .buttonStyle(.glassProminent). That
rendered VaultAutofillConfigurationView as a completely blank image
— not just the button, the whole hierarchy — dropping the reference from
151KB to 62KB.

Liquid Glass samples a backdrop through the render server, and the
.image snapshot strategy rasterises off-screen, so the effect resolves
to nothing and takes the rest of the frame with it.
Snapshotting.image(drawHierarchyInKeyWindow:) exists as a possible
escape hatch but changes the rendering path for every test, so it is not
something to adopt as a side effect of this PR.

.borderedProminent is used instead. On iOS 26 the system already draws
it as a capsule, which is the shape the hand-rolled 12pt rounded
rectangle was imitating.

Verification

Local, iPhone 18 Pro Max / iOS 27.0:

  • xcodebuild build-for-testing — TEST BUILD SUCCEEDED
  • Affected suites pass: OTPCodeDetailViewSnapshotTests,
    SecureNoteDetailViewSnapshotTests,
    VaultAutofillConfigurationViewSnapshotTests
  • 43 snapshots re-recorded (18 OTP detail, 24 secure note, 1 autofill)
    and visually reviewed
  • make format + make lint — clean

SettingsDangerView, VaultTagDetailView, EncryptedItemDetailView and
VaultDetailEncryptionEditView have view-model tests but no snapshot
coverage, so there was nothing to re-record for them.

⚠️ Automatic CI is still disabled (#548), so this is local verification only.

🤖 Generated with Claude Code

bradleymackey and others added 2 commits September 14, 2026 13:24
Removes six view files and two `Color` members that reimplement SwiftUI
APIs the app already uses elsewhere. Every one has zero call sites
outside its own file, so this is deletion only — no behaviour change.

| Deleted | Superseded by |
| --- | --- |
| `SearchTextField.swift` | `.searchable`, already used at `VaultItemFeedView.swift:80` |
| `TextArea.swift` | `TextEditor`, already used in three places |
| `TextEditingView.swift` + `TextViewViewController.swift` | `TextEditor` |
| `OTPCodeLabels.swift` | the inline `labelsStack` copies that replaced it |
| `View+Center.swift` | `.frame(maxWidth:)` |
| `Color.contrastingForegroundColor` / `.contrastingBackgroudColor` | unreferenced |

`TextEditingView` carried a doc comment explaining it existed to dodge
"bugs we've experienced with raw SwiftUI text editors". That workaround
was never in service — `SecureNoteDetailView` and `BackupCreatePDFView`
both use a plain `TextEditor` — so nothing regresses by removing it.

`HorizontallyCenter` (`HStack { Spacer(); content; Spacer() }`) is
replaced at its nine call sites by `.frame(maxWidth: .infinity)`, which
centres its child at its ideal size the same way. Six of those sites are
buttons whose pill chrome is applied by an inner modifier, so the button
keeps its intrinsic size rather than stretching.

## Verification

Local, iPhone 18 Pro Max / iOS 27.0:

- `xcodebuild build-for-testing` — `TEST BUILD SUCCEEDED`, no warnings
  (`-warnings-as-errors` is on package-wide)
- Full suite, `-parallel-testing-enabled NO` — `TEST EXECUTE SUCCEEDED`,
  2834 passed, 0 failures (1417 tests across 12 bundles, run under both
  the Default and TSAN configurations)
- **Zero snapshots re-recorded.** That is the check that matters here: if
  any of these types had still been reachable, an image would have moved.
- `make format` + `make lint` — clean

⚠️ Automatic CI is still disabled (#548), so this is local verification only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Applies the button convention from #551 to the screens outside
`Views/Backup`, and deletes the modifier that made the old treatment
possible.

## The problem

`ProminentButtonModifier` reimplemented `.borderedProminent` by hand:
`.font(.headline)` + a forced `.foregroundStyle(.white)` + `padding(16/12)`
+ `.background(color)` + `RoundedRectangle(cornerRadius: 12)`. It applied
`.buttonStyle(.borderless)` *inside* its own body, so the wrapped button
never received a pressed state, and the hardcoded white label had no
contrast guarantee against the tint it was placed on.

Six of its nine call sites also placed the button inside a `Section`
footer as a padded, centred pill — the exact pattern #551 called out and
removed from the backup screens.

## What changed

| Screen | Change |
| --- | --- |
| `SettingsDangerView` | "Delete All Data" becomes a red row in its own section; the error message becomes that section's footer |
| `VaultTagDetailView` | "Delete Tag" becomes a red row |
| `VaultItemDetailView` | "Unlock" and "Dismiss" move out of section footers into their own rows |
| `EncryptedItemDetailView` | "Decrypt" moves out of the footer into its own section |
| `VaultDetailEncryptionEditView` | "Encrypt" and "Remove Encryption" become plain rows |
| `OTPCodeDetailView` | "Delete" moves out of the editing-actions footer into its own red section |
| `SecureNoteDetailView` | "Delete" likewise |
| `VaultAutofillConfigurationView` | "Continue" is a genuine standalone CTA outside any `Form`, so it becomes `.buttonStyle(.borderedProminent)` + `.controlSize(.large)` |

Destructive emphasis is preserved the way #551 preserved it: a red
`FormRow` glyph tile plus red row text, matching `BackupRestoreView`'s
Import & Override. Primary actions use the accent colour the same way
`BackupKeyDecryptorView` does.

The `ProgressView` in each `loading:` branch loses its `.tint(.white)`,
which only existed because the spinner sat on a filled pill.

Toolbar *Cancel* buttons keep their explicit `.foregroundStyle(.red)`.
`Button(role: .cancel)` does not render red in a toolbar, so dropping the
tint would have been a visual regression rather than a standardization.

## Liquid Glass does not survive snapshot rendering

"Continue" was first written as `.buttonStyle(.glassProminent)`. That
rendered `VaultAutofillConfigurationView` as a **completely blank image**
— not just the button, the whole hierarchy — dropping the reference from
151KB to 62KB.

Liquid Glass samples a backdrop through the render server, and the
`.image` snapshot strategy rasterises off-screen, so the effect resolves
to nothing and takes the rest of the frame with it.
`Snapshotting.image(drawHierarchyInKeyWindow:)` exists as a possible
escape hatch but changes the rendering path for every test, so it is not
something to adopt as a side effect of this PR.

`.borderedProminent` is used instead. On iOS 26 the system already draws
it as a capsule, which is the shape the hand-rolled 12pt rounded
rectangle was imitating.

## Verification

Local, iPhone 18 Pro Max / iOS 27.0:

- `xcodebuild build-for-testing` — `TEST BUILD SUCCEEDED`
- Affected suites pass: `OTPCodeDetailViewSnapshotTests`,
  `SecureNoteDetailViewSnapshotTests`,
  `VaultAutofillConfigurationViewSnapshotTests`
- 43 snapshots re-recorded (18 OTP detail, 24 secure note, 1 autofill)
  and visually reviewed
- `make format` + `make lint` — clean

`SettingsDangerView`, `VaultTagDetailView`, `EncryptedItemDetailView` and
`VaultDetailEncryptionEditView` have view-model tests but no snapshot
coverage, so there was nothing to re-record for them.

⚠️ Automatic CI is still disabled (#548), so this is local verification only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bradleymackey
bradleymackey deleted the branch main September 14, 2026 16:41
Base automatically changed from native-standardization-dead-code to main September 14, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant