Theme: apply the saved theme pre-auth and offline; adopt the Strata dark palette #80
Description
From a light/dark/system review. The picker and the preferredColorScheme wiring already exist;
these are the gaps around them. #79 is the separate persistence bug and should land first.
Verified working today
SettingsView picker → user.theme → RootView.swift:19 .preferredColorScheme. ILColor ships a
full dual palette. AccentColor.colorset has a dark variant. Info.plist has no
UIUserInterfaceStyle lock and UILaunchScreen is empty, so both appearances are permitted. The
masthead teal adapts correctly (0x184860 → 0x0C2C3A), confirmed on device.
Gaps
1. The preference is server-only
RootView reads authState.user?.theme, and user is nil until GET /api/user returns. So:
- the login screen always renders in the OS appearance, ignoring the saved theme;
- a cold launch flashes the OS appearance and then snaps to the preference;
- an offline cold start loses the preference entirely.
Mirror the theme to UserDefaults and read it in RootView as the startup value, with the server
value as the authority once it arrives. It is a display preference, not a secret — the
Keychain-only rule does not apply.
2. Logo.imageset has no dark variant
The logo has a baked-in cream #F4EEE2 backing square that floats as a bright block on a dark
background. Visible at LoginView.swift:26 and FeedView.swift:348. Add a dark-appearance image
to the imageset (or ship it with a transparent background).
3. Links render iOS system blue, not ILColor.link
Visible on the login screen in both appearances ("Forgot password?", "Create account"). No
global .tint() is set on RootView.
4. Dark mode is Apple's dark, not InterlinedList's
ILColor.background / .surface / .surface2 appear in ~12 places across 54 view files, and only
MainTabView.swift:31 sets a screen background. Everything else uses system materials, so dark mode
renders as generic iOS black/grey and the Strata dark palette (0x121317 / 0x17191F) is
effectively unused. This is the bulk of the work and is design-dependent — worth splitting out if
1–3 are wanted sooner.
5. Unverified
- Whether the app-level deep-link sheet (
InterlinedListApp.swift:48) inherits the override — the
.sheetmodifier sits outside wherepreferredColorSchemeis set. - Feed / tab bar / lists / documents in dark. Needs a keychain-seeded logged-in run.
Acceptance criteria
- Saved theme applies on the login screen and on a cold offline launch, with no flash.
- Logo reads correctly in both appearances.
- Links use
ILColor.link. - A screenshot pass over the main screens in both appearances, with contrast checked.
Files: Views/RootView.swift, Views/SettingsView.swift, Assets.xcassets/Logo.imageset,
Services/ILColor.swift adoption across Views/.
Activity
Adron commentedon Sep 16, 2026
Scoped and split
Gaps 1, 2, 3 (pre-auth/offline persistence, the logo's cream backing square, system-blue links)
plus the verification in gap 5 are being implemented now, stacked on #97.
Gap 4 — adopting the Strata dark palette across ~54 view files — is split out to #98. It is
not a bug with one right answer: it is a choice about how branded vs. native the app should look in
dark, and it needs a design call before code. This issue already anticipated the split ("worth
splitting out if 1–3 are wanted sooner").
This issue stays open until the implementation PR lands; it will be closed then, with #98 carrying
the remainder.
Adron commentedon Sep 16, 2026
Gaps 1, 2, 3 and the verification in 5 are implemented in #101 (stacked on #97, which must merge
first). Closing here — gap 4 continues in #98, and the remainder tracks in the PR.
The open question in this issue is now answered
Answered empirically, not by reasoning: colour scheme yes, tint no.
With the OS in light and the saved theme dark, the sheet rendered dark — the window-level trait
override does reach it — but its "Cancel" button was system blue. Moving
.tintup to theRootView()instance above the.sheetdid not fix it; tint does not cross into sheet content.Tinting the sheet's content does, and that one-line change is in #101.
Measured with a throwaway build that set
router.pendingDeepLinkat launch, reverted beforecommitting. (
simctl openurlwas not usable here — SpringBoard shows an "Open in InterlinedList?"confirmation and there is no tap path in this environment.)
Logo — resolved with real brand artwork, nothing invented
brand-kit/already had what was needed. The decisive find:Logo@2x.png/Logo@3x.pngarebyte-identical (md5) to
brand-kit/icons/ios/AppIcon-120.png/AppIcon-180.png— the in-app"logo" is the sand app-icon tile. So the dark variant is its shipped counterpart,
AppIcon-dark-1024.png, resampled. The mark itself is unaltered.One thing worth a separate look — filed as #102
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColoris already set, andAccentColor.colorsetholds the correct brand green in both appearances — yet links stillrendered system blue. The global accent is not being honoured.
.tint()is what actually fixes it,which is a workaround over an unexplained cause.
Known limitation, documented in the PR
The static launch screen still follows the OS appearance — it cannot read
UserDefaults, so alaunch into a theme that differs from the OS will always show one frame of the OS appearance before
the app's own root renders.