fix(theme): honour the AccentColor asset app-wide - #113
Merged
Merged
Conversation
Configuring UINavigationBar's appearance from `App.init()` builds a UIBarButtonItemAppearance before UIApplication has adopted the AccentColor asset. That resolves UIKit's default bar-button styling early and caches system blue as the process tint for the rest of the launch, so the asset was ignored and every control without an explicit tint rendered iOS blue. Moving the configuration to the app delegate's launch callback keeps the teal masthead and lets the accent apply, which reaches surfaces a `.tint` cannot: sheets, alerts and UIKit-hosted chrome. The hierarchy-wide `.tint` workarounds on RootView and the deep-link sheet are removed — the latter also rendered its Cancel button in the masthead colour against the masthead, i.e. invisible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bss5MgZa7Jvj2m9zdaUd1
This was referenced Sep 17, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #102. The
AccentColorasset was configured correctly all along — it was being destroyed atruntime by the app's own launch code. Fixing that makes the global accent work, which is strictly
better than explicit tinting: the accent is the process tint, so it reaches sheets, alerts and
UIKit-hosted chrome that a
.tintmodifier cannot. The two hierarchy-wide.tintworkarounds areremoved, and the convention is documented in
CLAUDE.md.Root cause
Not the colorset, not the build setting, not
Info.plist. All three were verified correct againstthe built product:
NSAccentColorName = AccentColoris present inInterlinedList.app/Info.plist,and
assetutil --info Assets.carshows theAccentColorcolour with both the light and darkappearance variants compiled in.
UIColor(named: "AccentColor")resolves to brand green at runtime.The cause is
configureNavigationBarAppearance()being called fromInterlinedListApp.init().Isolated statement by statement against a live simulator, the trigger is exactly this pair:
Materialising a bar-button state appearance that early resolves UIKit's default bar-button styling
before
UIApplicationhas adopted the accent asset. UIKit caches the system-blue fallback as theprocess tint for the rest of the launch, so
UIColor.tintColor— and therefore SwiftUI'sColor.accentColor— stays blue forever. ConstructingUIBarButtonItemAppearance()withouttouching
.normal, writingUINavigationBar.appearance().tintColor, assigning aUINavigationBarAppearanceto the proxy, and simply readingUINavigationBar.appearance()are allharmless on their own; only reaching into the state appearance from
init()breaks it.Differential check: a throwaway single-view app with a red
AccentColor, built with the same Xcodeand run on the same simulator, honours its accent — and still honours it after being given
InterlinedList's own
Info.plistverbatim withGENERATE_INFOPLIST_FILE = NO. So this was never aproject-configuration or iOS-version problem.
What's included
configureNavigationBarAppearance()moves fromApp.init()toAppDelegate.application(_:didFinishLaunchingWithOptions:). The teal masthead, white title andwhite back-button styling are unchanged; the accent now survives.
RootView's.tint(ILColor.link)(from feat(theme): apply the saved theme before the account loads #101) and the deep-link sheet's.tint(ILColor.link)areremoved. Controls now render
ILColor.primary— the brand green the asset has always held, andthe colour the app already uses for actions in 30+ places.
InterlinedListTests/ServiceTests/AppAccentColorTests.swift(5 tests): the Info.plist declares theasset, the asset matches
ILColor.green/ILColor.greenDarkin each appearance, and the processtint equals the accent rather than system blue. The last two fail on the pre-fix code — verified by
putting the old
init()back — so they are a real guard, not a tautology.CLAUDE.mdgains the convention: brand tint comes from the asset, a new sheet needs no.tint,and
UIAppearancemust not be configured fromApp.init().Bonus fix: the deep-link sheet's explicit tint was also a contrast bug.
ILColor.linkis#184860,the same colour as the masthead, so the sheet's "Cancel" was dark teal text on a dark teal bar —
invisible. Screenshots of both states are in the testing notes below.
Sheets audited
64
.sheetpresentations across 22 files; no.fullScreenCoveranywhere. Exactly one carried atint (the deep-link sheet in
InterlinedListApp) — every other one relied onRootView's.tintreaching it, which it did for sheets presented from inside
RootView's subtree and did not for theone presented above it. All of them now inherit the accent with no per-sheet modifier:
InterlinedListApp.swiftViews/ListsView.swiftViews/DocumentsView.swiftViews/FeedView.swiftViews/MessageDetailView.swiftViews/MainTabView.swiftViews/UserProfileView.swiftViews/OrganizationsView.swiftViews/EditProfileView.swiftViews/LoginView.swiftViews/MessageThreadView.swiftViews/AISeriesGeneratorSection.swiftViews/AIWritingAssistantButton.swiftViews/DocumentCollaboratorsView.swiftViews/FindPeopleView.swiftViews/FollowListView.swiftViews/MessagesInboxView.swiftViews/NotificationsView.swiftViews/RegisterView.swiftViews/ScheduledMessagesView.swiftViews/SettingsView.swiftViews/WatchersListView.swiftThe remaining
.tint(...)call sites inViews/are deliberately local and were left alone:swipe-action background colours (
ListsView,DocumentsView,AppDevicesView,MessagesInboxView) and aProgressViewon a dark image overlay (ComposeImageStrip).Testing
iPhone 16 Pro(iOS 18.0), serialized, E2E skipped: 1325 tests, 0 failures(1320 baseline + 5 new).
xcrun simctl ui <UDID> appearance light|dark:#2FA877in light,#3FBF8Cin dark; teal masthead and white titleunchanged. Sampled the rendered pixels: system blue
#007AFFbefore, brand green after.router.pendingDeepLink, reverted beforecommit) — "Cancel" renders brand green in both appearances with no tint of its own. On
mainthe same button is invisible:
ILColor.link#184860on the#184860masthead.App.init()makestest_processTint_isTheAccentAsset_notSystemBlue_*fail; the fix makes them pass.Not verified
documents, settings, and the 60-odd sheets those screens present — was not seen rendered. The
mechanism is process-wide rather than per-view (
UIColor.tintColoris now the accent, asserted bythe new tests), so they should all follow, but that is inference, not a screenshot.
simctl openurlon a custom scheme behind a SpringBoard confirmation thatcannot be tapped headlessly, so the deep-link sheet was reached by temporarily seeding the router
instead of through a real deep link. The temporary seed is not in this branch.
🤖 Generated with Claude Code