feat: adaptive Warm Meadow palette (dark mode) and no placeholder identity - #38
Conversation
There is no account, so the monogram + "User" card on Profile and the
person.fill circle in the Dashboard greeting stood in for an identity the
app deliberately does not have.
- Profile: identity card removed; its one useful line ("Saving since
<month>" / "Just getting started") is now the lifetime income card's
subtitle.
- Dashboard: avatar circle removed from the greeting.
- Display-name plumbing deleted since nothing reaches it anymore:
EditProfileSheet, ProfileViewModel.displayName /
updatePersonalInformation(), AppViewModel.currentUserName /
updateDisplayName(), the "displayName" UserDefaults key, and the
orphaned InnerHeightPreferenceKey. Strings.Profile.editNameHint and its
catalog entry go with them.
Every Color.warm* token now resolves per trait collection through a
UIColor dynamic provider, so call sites never branch on colorScheme.
Light values are unchanged; dark values are the same hues at low chroma
("the notebook under a lamp"), verified against WCAG AA in
docs/plans/pr-d2-adaptive-palette.md.
New role tokens for what call sites used to hardcode: warmGreenFill (green
as a surface, stays deep in dark so white content keeps 5:1), warmOnGreen,
warmOnInk (ink flips light in dark, so its content flips to the ground),
warmTrack, warmShadow, warmLilac/warmLilacSoft. No call sites change in
this commit.
The sweep that makes the adaptive palette actually reach the screen. Of the 97 hardcoded color sites outside Color+Warm.swift, every one now resolves through a token; the handful that stay literal are deliberate and say so in a comment (the splash frame, the sprout brand mark, white controls drawn over the live camera feed, and the PDF, which is paper). Semantics, not search-and-replace: - Green *surfaces* (CTAs, the income card, the Planted screen, the selected radio and calendar day) use warmGreenFill; green *as ink* (text, glyphs, rings, bars, toggles, page dots) keeps warmGreen. - White content on those surfaces is warmOnGreen; white on an ink fill (the "+" shell, the scan banner, the Goals "+" buttons) is warmOnInk, which flips to the ground in dark because ink flips light. - The goal ring track is warmTrack; the "New goal" quick action is warmLilac/warmLilacSoft; the amber banner copy is warmAmberDeep. - GoalColor: each of the twelve goal accents and their tracks now carries a dark variant (accents a step lighter, ≥3:1 against the dark surface; tracks a deep tint of the same hue). Light values unchanged. - Shadows: the hero-card lifts and the "+" shell keep theirs through warmShadow / warmShellShadow; the rest were legacy and are gone. - Legacy: the ColorPalette asset catalog (primaryGreen, cardBackground…) is deleted, along with the four unreferenced files that still pointed at it or at nothing (NotificationTimePicker, InsightRow, QuickActionButton, PrimaryButton — dead since the auth removal / PR A) and the unused UIConstants.UIShadow. NetworkErrorView, ReportsView and the camera confirmation card are moved onto the tokens rather than deleted.
The palette now resolves per appearance, so the switch does what it says. Kept as an absolute override (off = light, on = dark) rather than following the system when off — owner's call.
warmInkMuted #8c8880 -> #746f66: 3.21:1 -> 4.54:1 on the ground and 3.53 -> 4.99 on paper. It carries every 12-13pt metadata line, which is body text under WCAG, and it failed AA. Same warm-ink family, one step darker. warmAmber #c48a2a -> #a8741c: 2.55:1 -> 3.45:1 as a glyph on amber-soft, clearing 1.4.11 for graphical objects. Dark values untouched. Owner accepted both (2026-08-16).
…67c56 Frontmatter now lists every token's light and dark value (light stays normative for the look). Prose updated for the ink-muted and amber AA corrections, the green ink/fill split, and the on-green/on-ink roles. warmGreenFill dark #357a55 -> #367c56: its ratio against the dark surface rounded to 3.00 but sat at 2.9995; the nudge clears 3:1 (3.08) with white content still at 5.03:1.
…ances On iOS 26+ the Home Screen takes its appearance variants from an Icon Composer document, not from the PNGs in a legacy .appiconset. Savely shipped dark/tinted PNGs that the system no longer reads, so the icon stayed light whatever the Home Screen's icon appearance was. - Savely/AppIcon.icon: the sprout as three SVG layers (light, dark, mono) drawn from the same 120-unit geometry as SproutMark.swift, on a warm cream gradient in light and the green-ink #27382d in dark; the sprout is opaque (the mark is flat) with a soft neutral shadow. Validated by rendering Default, Dark and TintedDark with Icon Composer's ictool; the built Assets.car carries an IconImageStack per appearance. - The legacy AppIcon.appiconset is removed (it would collide on the "AppIcon" name, and the deployment floor is iOS 26). - SproutMark's shaded leaf becomes trait-aware (#37795a light / #5d9c7c dark) so the in-app mark reads on the dark ground too.
Added: Icon Composer app icon (
|
Summary
Executes
docs/plans/pr-d2-adaptive-palette.md, in the plan's commit order:person.fillavatar in the Dashboard greeting are gone. "Saving since " moved into the lifetime income card. All display-name plumbing deleted (EditProfileSheet,ProfileViewModel.displayName,AppViewModel.currentUserName, the"displayName"UserDefaults key, orphanedInnerHeightPreferenceKey,Strings.Profile.editNameHint+ catalog entry).Color.warm*token now resolves per appearance through aUIColordynamic provider — zero call-site changes for the 18 existing tokens. Dark is "the notebook under a lamp": same hues, low chroma, one green. New role tokens:warmGreenFill(green as a surface — stays deep in dark so white content keeps ≥5:1),warmOnGreen,warmOnInk(ink flips light in dark, so its content flips to the ground),warmTrack,warmShadow/warmShellShadow,warmLilac(Soft),warmAmberDeep.Color(red:),.white,.black, legacyColor("primaryGreen")) now go through tokens, by semantics (fill vs ink, on-green vs on-ink) — not search-and-replace.GoalColor's 12 accents + tracks got dark variants (accents a step lighter, ≥3:1 vs dark surface). LegacyColorPaletteasset catalog deleted; four dead files that pointed at it or at nothing removed (NotificationTimePicker,InsightRow,QuickActionButton,PrimaryButton); legacy shadows andUIConstants.UIShadowgone — only the hero-card lifts and the "+" shell keep a shadow, perDESIGN.md. The remaining literals are deliberate and commented (splash frame, sprout mark, camera-overlay controls, the PDF).FeatureFlags.darkModeEnabled = true). Kept as an absolute override (off = light, on = dark), not system-following — owner's call.warmInkMuted #8c8880 → #746f66(3.21 → 4.54:1 — it carries every 12–13pt metadata line, body text under WCAG) andwarmAmber #c48a2a → #a8741cfor glyphs on amber-soft (2.55 → 3.45:1). Both imperceptible; the light look is otherwise unchanged.DESIGN.mdfrontmatter now lists light and dark for every token; prose updated.Contrast (computed from the final hex in
Color+Warm.swift, WCAG relative luminance)All pass.
Test plan
xcodebuild build— BUILD SUCCEEDEDxcodebuild test -skip-testing:SavelyUITests— TEST SUCCEEDEDswiftlint lint --strict— 0grep -rn "Color(red:\|\.white�\|\.black�" Savely --include=*.swift | grep -v Color+Warm→ only the commented deliberate exceptionssimctl(dark: warm charcoal ground, hairline-separated surface, light ink, inverted "+" shell; light: unchanged)warmGreenSoftchips, the auto-move banner, the Planted screen, goal tiles' white initials on lighter dark accents (decorative; goal name sits next to them).Risks
warmGreenwas split into ink vs fill by hand at ~25 sites; a fill mistaken for ink would show as a too-bright button in dark. Reviewed each; worth an eyeball.ColorPaletteassets and four dead files touchesproject.pbxproj(plutil -lintOK; build green).Checklist
feat/branch, Conventional Commits, no AI attributionDESIGN.mdre-documented with the dark values