Skip to content

Theme: apply the saved theme pre-auth and offline; adopt the Strata dark palette #80

Closed

Description

@Adron

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
    .sheet modifier sits outside where preferredColorScheme is 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

  1. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    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.

  2. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    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

    Whether the app-level deep-link sheet (InterlinedListApp.swift:48) inherits the override — the
    .sheet modifier sits outside where preferredColorScheme is set.

    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 .tint up to the
    RootView() instance above the .sheet did 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.pendingDeepLink at launch, reverted before
    committing. (simctl openurl was 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.png are
    byte-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 = AccentColor is already set, and
    AccentColor.colorset holds the correct brand green in both appearances — yet links still
    rendered 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 a
    launch into a theme that differs from the OS will always show one frame of the OS appearance before
    the app's own root renders.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestws:moderation-privacyW2 — moderation, privacy, account settings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions